/* ==========================================================================
   Jade Eye — app shell stylesheet
   Tokens · reset · shell (app bar, tab bar / sidebar) · shared primitives.

   OWNER: Lane F (shell & design system, 2026-10 "full burn").
   Every feature sheet under src/styles/ builds on the tokens and classes
   defined here. Those sheets load AFTER this one (see index.html), so a rule
   here that must beat one of theirs is written with a `body` prefix rather
   than with !important.

   THE TOKEN CONTRACT
   ------------------
   This file is now the ONE source of colour, type, space, radius, elevation
   and motion. theming/tokens.js no longer writes token values inline; it only
   decides WHICH theme is active (html[data-theme], body.night, body.low-stim)
   and the blocks below do the rest. Every custom-property name that existed
   before this rewrite still resolves — the old names are kept as first-class
   tokens or as aliases of the new scale — so the ~16 feature sheets keep
   working untouched.

   LUMINOUS JADE (JE-110 S3a, 2026-10-22). Sections 1-4 are the token layer
   from docs/je-110/design/SYSTEM.md §2 (Phase A): new names (--canvas,
   --surface-*, --text-1..3, --accent*, --signal-*, --elev-0..4, --type-*,
   --sp-*, --r-*, --dur-*, springs) plus the §10.2 alias block (3b) that maps
   every legacy name onto them. The shared component layer for S4b lives in
   styles/ui.css (loaded on demand by components/ui/*.js and #dev-gallery).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Structural tokens — the same in every theme
      LUMINOUS JADE (JE-110 S3a, docs/je-110/design/SYSTEM.md §2.1). Type
      roles, the 4px grid, radii, layout, motion and the glass recipe.
      The LEGACY scale (--text-xs…3xl sizes, --space-*, --radius-*,
      --gutter, --nav-height, --appbar-height) keeps its current values until
      S4b Phase B, so no screen's layout moves before it is restyled.
   -------------------------------------------------------------------------- */
:root {
  color-scheme: light;

  /* ---- Type families ----
     SF on Apple first: -apple-system matches, so Safari never fetches Inter.
     Inter (self-hosted, public/fonts) serves Android/Windows/Linux. */
  --font-sans:    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
  --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
  --font-num:     ui-rounded, "SF Pro Rounded", -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif;
  --font-name:    ui-serif, "New York", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif; /* person names >= 24px only */
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- Type roles: see the `:root, body` block just below. ---- */
  --track-display: -0.03em;  --track-title: -0.02em;  --track-body: -0.006em;
  --track-num: -0.04em;      --track-eyebrow: 0.12em;

  /* ---- Space: 4px grid ---- */
  --sp-0: 0;    --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-7: 32px; --sp-8: 40px; --sp-9: 56px; --sp-10: 72px;

  /* ---- Radii: one job each; inner = outer - padding ---- */
  --r-xs: 8px;  --r-sm: 12px; --r-md: 18px; --r-lg: 24px; --r-xl: 32px;
  --r-bar: 26px; --r-pill: 999px;

  /* ---- Layout ---- */
  --tap: 44px;
  --tabbar-h: 66px;
  --tabbar-float: 12px;
  --appbar-h: 56px;
  --row-min: 64px;
  --btn-h: 52px;  --btn-h-sm: 44px;
  --input-h: 52px;

  /* ---- Tier hues (lightness/saturation come from the theme) ---- */
  --hue-inner: 162; --hue-close: 178; --hue-circle: 198; --hue-network: 226;
  --hue-outer: 32;  --hue-extended: 22; --hue-ai: 258;

  /* ---- Motion (springs upgraded below when linear() exists) ---- */
  --dur-instant: 90ms;
  --dur-fast: 160ms;
  --dur-base: 260ms;
  --dur-slow: 480ms;
  --dur-reveal: 720ms;
  --dur-ring: 1100ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:       cubic-bezier(0.4, 0, 1, 1);
  --spring-snappy: cubic-bezier(0.25, 1.1, 0.4, 1);
  --spring-smooth: cubic-bezier(0.22, 1, 0.36, 1);
  --spring-bouncy: cubic-bezier(0.34, 1.45, 0.64, 1);
  --stagger: 55ms;
  --rise: 14px;
  --press-scale: 0.97;

  /* ---- Glass recipe ---- */
  --glass-blur: saturate(170%) blur(22px);
  --glass-blur-sheet: saturate(170%) blur(30px);

  /* ---- LEGACY scale, unchanged values (aliased onto --sp/--r in Phase B) ---- */
  --text-2xs: 0.6875rem;  --text-xs: 0.75rem;  --text-sm: 0.8125rem;  --text-md: 0.9375rem;
  --text-base: 1rem;      --text-lg: 1.125rem; --text-xl: 1.375rem;   --text-2xl: 1.65rem;
  --text-3xl: 2.125rem;
  --weight-regular: 420; --weight-medium: 550; --weight-semibold: 620; --weight-bold: 700;
  --tracking-tight: -0.02em; --tracking-snug: -0.011em;
  --leading-tight: 1.15; --leading-snug: 1.3; --leading-body: 1.5;
  --space-0: 0; --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-7: 32px; --space-8: 40px; --space-9: 56px;
  --radius-xs: 6px; --radius-sm: 10px; --radius-md: 14px; --radius: 18px;
  --radius-lg: 26px; --radius-xl: 32px; --radius-pill: 999px;
  --ease-spring: var(--spring-bouncy);

  /* Layout. --nav-height is the phone tab bar; it becomes 0 at the desktop
     breakpoint, so every fixed element that sits "above the nav" drops to the
     viewport floor there without knowing about the sidebar. */
  --nav-height: calc(var(--tabbar-h) + var(--tabbar-float)); /* 78: the floating capsule + its float (SYSTEM §10.2) */
  --appbar-height: 52px;
  --page-max: 520px;      /* sheets, dialogs, and the phone column */
  --screen-max: 520px;    /* the content column; widens per route on desktop */
  --sidebar-w: 0px;
  --gutter: 18px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  /* (--content-left / --content-right are declared on body, below, so a
     per-route --screen-max on body feeds into them.) */
  --fab-clear: var(--gutter);
  --pbf-gold: #B8860B;
}

/* TYPE ROLES (SYSTEM.md §3). Font shorthands in rem × --type-scale.
   rem is relative to <html>, so body.font-large / .font-xlarge (which set
   body's font-size) would NOT scale a plain rem; they set --type-scale
   instead, and the roles are declared on body too so they re-resolve there. */
:root, body {
  --type-scale: 1;
  --type-hero-num: 820 calc(4rem * var(--type-scale))/0.85 var(--font-num);
  --type-stat-num: 780 calc(2.75rem * var(--type-scale))/1 var(--font-num);
  --type-row-num: 740 calc(1.25rem * var(--type-scale))/1 var(--font-num);
  --type-large-title: 760 calc(2.125rem * var(--type-scale))/1.12 var(--font-display);
  --type-title-1: 740 calc(1.75rem * var(--type-scale))/1.15 var(--font-display);
  --type-title-2: 720 calc(1.375rem * var(--type-scale))/1.2 var(--font-display);
  --type-title-3: 640 calc(1.25rem * var(--type-scale))/1.25 var(--font-display);
  --type-headline: 640 calc(1.0625rem * var(--type-scale))/1.3 var(--font-sans);
  --type-body: 430 calc(1.0625rem * var(--type-scale))/1.47 var(--font-sans);
  --type-callout: 430 calc(1rem * var(--type-scale))/1.4 var(--font-sans);
  --type-subhead: 430 calc(0.9375rem * var(--type-scale))/1.4 var(--font-sans);
  --type-footnote: 430 calc(0.8125rem * var(--type-scale))/1.38 var(--font-sans);
  --type-caption: 560 calc(0.75rem * var(--type-scale))/1.33 var(--font-sans);
  --type-eyebrow: 700 calc(0.6875rem * var(--type-scale))/1.2 var(--font-sans);
  --type-name-xl: 600 calc(2.25rem * var(--type-scale))/1.08 var(--font-name);
  --type-name-lg: 600 calc(1.75rem * var(--type-scale))/1.1 var(--font-name);
}
body.font-large  { --type-scale: 1.125; }
body.font-xlarge { --type-scale: 1.25; }

/* --------------------------------------------------------------------------
   2. Light theme (default): "daylight on porcelain". SYSTEM.md §2.2.
      `.je-theme-light` / `.je-theme-dark` scope a theme to one subtree (the
      #dev-gallery shows both side by side); the app itself never uses them.
      Measured (WCAG 2.x, composited, see tools/verify-design-system.mjs and
      theming/contrast-pairs.js): text-1 17.6, text-2 7.9, text-3 5.5 on
      canvas; accent-text 5.4 on canvas.
   -------------------------------------------------------------------------- */
:root, .je-theme-light {
  color-scheme: light;
  --canvas: #F2F5F3;
  --canvas-mesh:
    radial-gradient(90% 55% at 10% -10%, rgba(0,214,160,.20), transparent 60%),
    radial-gradient(70% 45% at 100% 16%, rgba(40,160,220,.13), transparent 62%),
    radial-gradient(80% 50% at 40% 92%, rgba(150,120,240,.08), transparent 60%);
  /* film grain, alpha baked in (no blend layer; painted once) */
  --noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .2 0 0 0 0 .25 0 0 0 0 .22 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  --surface-sunken: #E8EEEB;
  --surface-1: #FFFFFF;
  --surface-2: #F6F9F7;
  --surface-3: #EBF0ED;
  --surface-raised: linear-gradient(180deg, #FFFFFF, #FAFCFB);
  --hero-tint: rgba(0,214,160,.16);
  --hero-rest:
    radial-gradient(90% 80% at 100% 100%, rgba(40,170,230,.10) 0%, transparent 60%),
    linear-gradient(180deg, #FFFFFF 0%, #F7FBF9 100%);
  --surface-hero: radial-gradient(120% 90% at 0% 0%, var(--hero-tint) 0%, transparent 55%), var(--hero-rest);
  --surface-glass: rgba(255,255,255,.84);
  --surface-glass-solid: #FBFCFC;
  --surface-sidebar: #EEF3F1;
  --tab-lens: #E3F5EE;

  --stroke-1: rgba(8,40,32,.08);
  --stroke-2: rgba(8,40,32,.13);
  --stroke-strong: rgba(8,40,32,.24);
  --edge: inset 0 1px 0 rgba(255,255,255,.9);

  --text-1: #0A1714;
  --text-2: #3F524B;
  --text-3: #566A63;
  --text-on-accent: #01231A;
  --text-inverse: #FFFFFF;

  --accent: #00C795;
  --accent-strong: #00A87E;
  --accent-text: #00735A;
  --accent-tint: rgba(0,199,149,.11);
  --accent-tint-2: rgba(0,199,149,.18);
  --accent-glow: rgba(0,190,140,.30);
  --accent-gradient: linear-gradient(135deg, #3DEBC0 0%, #00CC98 45%, #00B4C4 100%);
  --num-fill: var(--accent-text);

  --signal-overdue: #B83A2E;
  --signal-drifting: #8F5300;
  --signal-watching: #0B6FA8;
  --signal-quiet: #566A63;
  --signal-birthday: #B0306F;
  --success: #00735A;  --warning: #8F5300;
  --danger: #C0392B;   --danger-text: #A5281B;   --danger-tint: rgba(192,57,43,.09);
  --ai: #6A4BD8;   --ai-text: #5236B8;       --ai-tint: rgba(106,75,216,.09);
  --star: #D99A00;
  --badge-bg: #C0392B; --badge-fg: #FFFFFF;

  --avatar-s: 58%; --avatar-l-bg: 91%; --avatar-l-fg: 27%;
  --ring-track: rgba(8,40,32,.08);

  --elev-0: none;
  --elev-1: 0 1px 2px rgba(10,40,32,.06), 0 4px 14px -6px rgba(10,60,48,.10), var(--edge);
  --elev-2: 0 1px 2px rgba(10,40,32,.05), 0 10px 28px -6px rgba(10,60,48,.12), var(--edge);
  --elev-3: 0 6px 16px rgba(10,40,32,.08), 0 26px 64px -12px rgba(10,60,48,.22), var(--edge);
  --elev-4: 0 -1px 0 var(--stroke-2), 0 -24px 70px -16px rgba(10,50,40,.28);
  --elev-hero: 0 26px 56px -22px rgba(0,150,110,.38), 0 2px 6px rgba(10,40,32,.06), var(--edge);
  --elev-press: inset 0 1px 3px rgba(10,40,32,.16), inset 0 0 0 1px rgba(10,40,32,.05);
  --glow-accent: 0 0 0 1px rgba(0,168,126,.25), 0 10px 26px -10px rgba(0,168,126,.55);

  --overlay: rgba(6,20,16,.38);
  /* Focus colour: --accent-text, not the spec's --accent-strong — #00A87E on
     the canvas is 2.77:1, under the 3:1 a focus indicator needs (WCAG
     1.4.11). #00735A is 5.3:1. Measured in verify-design-system. */
  --focus-color: var(--accent-text);
  --focus-ring: 0 0 0 2px var(--canvas), 0 0 0 4px var(--accent-text);
  --skel-a: #E6ECE9; --skel-b: #F3F6F4;

  /* Tiers & kinds — data colours, deliberately theme-stable in hue. Must
     match TIERS[*].color in core/constants.js. */
  --tier-inner-circle: #00D4AA;
  --tier-close:        #12BFC4;
  --tier-circle:       #4CC9F0;
  --tier-network:      #7C9BE0;
  --tier-outer-circle: #F4A259;
  --tier-extended:     #C98A5E;
  --tier-outside:      #7A8B99;
  --tier-unknown:      #566370;
  --kind-interest:     #B48EE0;
  --kind-community:    #5FB878;
  --kind-unsorted:     #8B98A1;
}

/* --------------------------------------------------------------------------
   3. Dark theme (lead): "jade light inside obsidian". SYSTEM.md §2.3.
      Active when theming/tokens.js resolves "night" — from the explicit Night
      setting, or from Auto + prefers-color-scheme: dark. `body.night` keeps
      the old manual switch working on its own; `html[data-theme]` makes the
      tokens readable on <html> itself (tokens.js reads --color-bg there).
   -------------------------------------------------------------------------- */
:root[data-theme="dark"], body.night, .je-theme-dark {
  color-scheme: dark;
  --canvas: #050A09;
  --canvas-mesh:
    radial-gradient(90% 55% at 12% -8%, rgba(38,214,160,.22), transparent 60%),
    radial-gradient(70% 45% at 100% 18%, rgba(28,150,190,.16), transparent 62%),
    radial-gradient(80% 50% at 40% 92%, rgba(120,90,220,.10), transparent 60%);
  --noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .85 0 0 0 0 1 0 0 0 0 .93 0 0 0 .045 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  --surface-sunken: #030706;
  --surface-1: #0C1513;
  --surface-2: #111D1A;
  --surface-3: #182723;
  --surface-raised: linear-gradient(180deg, #13211E, #0D1714);
  --hero-tint: rgba(61,242,190,.18);
  --hero-rest:
    radial-gradient(90% 80% at 100% 100%, rgba(34,199,232,.12) 0%, transparent 60%),
    linear-gradient(180deg, #102320 0%, #0A1513 100%);
  --surface-hero: radial-gradient(120% 90% at 0% 0%, var(--hero-tint) 0%, transparent 55%), var(--hero-rest);
  --surface-glass: rgba(10,20,18,.80);
  --surface-glass-solid: #0E1917;
  --surface-sidebar: #08100E;
  --tab-lens: #12302A;

  --stroke-1: rgba(205,255,236,.07);
  --stroke-2: rgba(205,255,236,.13);
  --stroke-strong: rgba(205,255,236,.22);
  --edge: inset 0 1px 0 rgba(255,255,255,.07);

  --text-1: #ECF6F2;
  --text-2: #A7BAB3;
  --text-3: #8AA098;
  --text-on-accent: #01231A;
  --text-inverse: #050A09;

  --accent: #3DF2BE;
  --accent-strong: #1FD6A2;
  --accent-text: #5CF5C9;
  --accent-tint: rgba(61,242,190,.12);
  --accent-tint-2: rgba(61,242,190,.20);
  --accent-glow: rgba(61,242,190,.45);
  --accent-gradient: linear-gradient(135deg, #6BF8D0 0%, #2EE2B0 45%, #18BFD0 100%);
  --num-fill: linear-gradient(180deg, #FFFFFF 0%, #9FF6DC 100%);

  --signal-overdue: #FF7F70;
  --signal-drifting: #FFB84D;
  --signal-watching: #6CCBFF;
  --signal-quiet: #8EA39B;
  --signal-birthday: #FF8FC7;
  --success: #3DF2BE;  --warning: #FFB84D;
  --danger: #FF6B5E;   --danger-text: #FF6B5E;   --danger-tint: rgba(255,107,94,.14);
  --ai: #B79CFF;   --ai-text: #B79CFF;       --ai-tint: rgba(183,156,255,.12);
  --star: #FFD166;
  --badge-bg: #FF6B5E; --badge-fg: #2A0603;

  --avatar-s: 52%; --avatar-l-bg: 17%; --avatar-l-fg: 76%;
  --ring-track: rgba(205,255,236,.09);

  --elev-0: none;
  --elev-1: 0 1px 2px rgba(0,0,0,.5), var(--edge);
  --elev-2: 0 2px 6px rgba(0,0,0,.45), 0 12px 32px rgba(0,0,0,.45), var(--edge);
  --elev-3: 0 8px 20px rgba(0,0,0,.5), 0 28px 70px rgba(0,0,0,.6), var(--edge);
  --elev-4: 0 -1px 0 var(--stroke-2), 0 -30px 80px -20px rgba(0,0,0,.6);
  --elev-hero: 0 24px 60px -18px rgba(31,214,162,.30), 0 2px 8px rgba(0,0,0,.5), var(--edge);
  --elev-press: inset 0 2px 4px rgba(0,0,0,.55), inset 0 0 0 1px rgba(205,255,236,.04);
  --glow-accent: 0 0 0 1px rgba(61,242,190,.35), 0 10px 30px -8px rgba(61,242,190,.55);

  --overlay: rgba(1,5,4,.66);
  --focus-color: var(--accent);
  --focus-ring: 0 0 0 2px var(--canvas), 0 0 0 4px var(--accent);
  --skel-a: #131F1C; --skel-b: #1B2A26;

  --tier-inner-circle: #33E7C2;
  --tier-close:        #3ED8DE;
  --tier-circle:       #6FC9F5;
  --tier-network:      #9BB0F5;
  --tier-outer-circle: #F4B26A;
  --tier-extended:     #D9A578;
  --tier-outside:      #8B93A8;
  --tier-unknown:      #6B7286;
  --kind-interest:     #C9A8F0;
  --kind-community:    #7FD69A;
  --kind-unsorted:     #8B93A8;
}

/* --------------------------------------------------------------------------
   3b. LEGACY ALIASES (SYSTEM.md §10.2). Every pre-Luminous name resolves onto
       the new tokens, so the 18 feature sheets keep working untouched.
       Declared on BODY as well as :root on purpose: a custom property that
       holds var() is substituted where it is declared and inherited as the
       finished value, so an alias declared only on :root would freeze the
       light value and ignore body.night / body.low-stim / html.lite. Delete
       this block once Phase C has converted the last sheet.
   -------------------------------------------------------------------------- */
:root, body, .je-theme-light, .je-theme-dark {
  /* surfaces & text */
  --color-bg: var(--canvas);
  --color-surface: var(--surface-1);
  --color-surface-2: var(--surface-2);
  --color-surface-3: var(--surface-3);
  --color-border: var(--stroke-2);
  --color-border-strong: var(--stroke-strong);
  --color-hairline: var(--stroke-1);
  --color-text: var(--text-1);
  --color-text-2: var(--text-2);
  --color-text-3: var(--text-3);
  --color-text-secondary: var(--text-2);
  --color-text-dim: var(--text-3);
  --ink: var(--text-1);
  --ink-soft: var(--text-2);
  /* brand */
  --jade: var(--accent);
  --jade-light: color-mix(in srgb, var(--accent) 70%, white);
  --jade-dark: var(--accent-text);
  --jade-tint: var(--accent-tint);
  --jade-tint-2: var(--accent-tint-2);
  --color-primary: var(--accent-strong);
  --color-primary-light: var(--accent);
  --color-primary-dark: var(--accent-text);
  --color-on-primary: var(--text-on-accent);
  --focus-ring-color: var(--focus-color);
  /* status */
  --color-success: var(--success);
  --color-warning: var(--warning);
  --color-error: var(--danger);
  --color-danger: var(--danger);
  --color-accent: var(--ai);
  --bucket-overdue: var(--signal-overdue);
  --bucket-drifting: var(--signal-drifting);
  --bucket-watching: var(--signal-watching);
  --bucket-quiet: var(--signal-quiet);
  /* elevation */
  --shadow-xs: var(--elev-1);
  --shadow: var(--elev-1);
  --shadow-md: var(--elev-2);
  --shadow-lg: var(--elev-3);
  /* chrome */
  --nav-bg: var(--surface-glass);
  --sidebar-bg: var(--surface-sidebar);
  --body-bg-image: var(--canvas-mesh);
  /* type & motion */
  --font-family: var(--font-sans);
  --ease-out: var(--ease-standard);
  --ease-in-out: var(--ease-standard);
  --dur-1: var(--dur-instant);
  --dur-2: var(--dur-fast);
  --dur-3: var(--dur-base);
  --transition: var(--dur-fast) var(--ease-standard);
}

/* --------------------------------------------------------------------------
   4. Low-stim — Jake's neurodivergent-friendly setting (SYSTEM.md §2.4).
      Calm, flat, still: no mesh, no grain, solid glass, no motion. The light
      theme stays desaturated as before.
   -------------------------------------------------------------------------- */
body.low-stim {
  --canvas-mesh: none;
  --noise: none;
  --surface-glass: var(--surface-glass-solid);
  --surface-hero: var(--surface-1);
  --accent-gradient: var(--accent);
  --glow-accent: none;
  --elev-hero: var(--elev-1);
  --num-fill: var(--text-1);
  --dur-instant: 0ms; --dur-fast: 0ms; --dur-base: 0ms; --dur-slow: 0ms; --dur-reveal: 0ms; --dur-ring: 0ms;
  --transition: 0ms;
}
body.low-stim:not(.night) {
  --accent: #7FCBB8; --accent-strong: #5FB7A2; --accent-tint: rgba(127,203,184,.18);
  /* the existing desaturated tier/bucket block, re-pointed */
  --jade:        #8FD9C8;
  --jade-light:  #B2E6DA;
  --jade-dark:   #3F7F6E;
  --color-primary: #7FCBB8;
  --canvas:      #F3F5F4;
  --color-bg:    #F3F5F4;
  --tier-inner-circle: #91E2CE;
  --tier-close:        #9BD8DB;
  --tier-circle:       #A9DCEE;
  --tier-network:      #B3C4E8;
  --tier-outer-circle: #F0C79E;
  --tier-extended:     #D9C2AC;
  --tier-outside:      #AAB6BE;
  --tier-unknown:      #7E8891;
  --bucket-overdue:    #DDA39D;
  --bucket-drifting:   #EBC49C;
  --shadow:    0 1px 6px rgba(18, 32, 29, 0.05);
  --shadow-md: 0 2px 10px rgba(18, 32, 29, 0.06);
  --shadow-lg: 0 4px 16px rgba(18, 32, 29, 0.08);
}
body.low-stim * { animation: none !important; transition: none !important; }
body.low-stim .bottom-nav, body.low-stim .toast, body.low-stim .modal-content, body.low-stim .je-sheet {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* --------------------------------------------------------------------------
   4b. Fallbacks (SYSTEM.md §2.5): no backdrop-filter, reduced transparency,
       html.lite (weak device; set by boot-hydrate.js before first paint),
       more contrast, reduced motion, forced colours. Every animated value is
       declared at its FINAL state and only animates FROM a start, so with
       motion off everything simply lands complete.
   -------------------------------------------------------------------------- */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  :root, :root[data-theme="dark"], body, body.night, .je-theme-light, .je-theme-dark { --surface-glass: var(--surface-glass-solid); }
}
@media (prefers-reduced-transparency: reduce) {
  :root, :root[data-theme="dark"], body, body.night, .je-theme-light, .je-theme-dark {
    --surface-glass: var(--surface-glass-solid); --glass-blur: none; --glass-blur-sheet: none;
  }
}
html.lite { --glass-blur: none; --glass-blur-sheet: none; }
html.lite body, html.lite .je-theme-light, html.lite .je-theme-dark {
  --surface-glass: var(--surface-glass-solid); --canvas-mesh: none; --noise: none;
}

@media (prefers-contrast: more) {
  :root, :root[data-theme="dark"], body.night, .je-theme-light, .je-theme-dark {
    --stroke-1: var(--stroke-strong); --stroke-2: var(--stroke-strong);
    --text-2: var(--text-1); --text-3: var(--text-2);
    --surface-glass: var(--surface-glass-solid);
  }
}

@media (prefers-reduced-motion: reduce) {
  :root, body {
    --dur-instant: 0.01ms; --dur-fast: 0.01ms; --dur-base: 0.01ms;
    --dur-slow: 0.01ms; --dur-reveal: 0.01ms; --dur-ring: 0.01ms;
    --rise: 0px; --press-scale: 1;
    --transition: 0ms;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important; scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
}
@media (forced-colors: active) {
  .card, .list, .je-hero, .modal-content, .toast, .bottom-nav, .btn { border: 1px solid CanvasText; }
}

/* Springs (SYSTEM.md §2.6) — progressive: linear() where it exists. */
@supports (animation-timing-function: linear(0, 1)) {
  :root {
    --spring-snappy: linear(0, .079, .247, .433, .602, .738, .841, .912, .959, .987, 1.002, 1.009, 1.011, 1.011, 1.009, 1.007, 1.005, 1.003, 1.002, 1.001, 1);
    --spring-smooth: linear(0, .062, .191, .337, .475, .594, .692, .769, .829, .874, .908, .934, .952, .966, .976, .983, .988, .991, 1);
    --spring-bouncy: linear(0, .06, .202, .382, .565, .731, .866, .968, 1.036, 1.076, 1.093, 1.093, 1.083, 1.068, 1.05, 1.033, 1.019, 1.008, 1, .994, .992, .991, .992, .993, .994, .996, .998, .999, 1);
  }
}
@property --p { syntax: "<number>"; inherits: false; initial-value: 0; }

/* --------------------------------------------------------------------------
   5. Reset
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, svg, video, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--canvas);
  /* The app bar sticks once scrolled (§3.4): anything scrolled into view —
     a focused field, a find-in-page hit — stops below it, not under it. */
  scroll-padding-top: calc(var(--appbar-height) + var(--safe-top) + 12px);
}

body {
  font-family: var(--font-family);
  font-size: 16px;
  line-height: 1.45;
  /* No font-feature-settings here: 'cv11'/'ss01' are Inter-only and misfire
     on SF. Inter keeps them through its @font-face in public/fonts/fonts.css. */
  color: var(--color-text);
  /* THE CANVAS (SYSTEM.md §5.2): colour + film grain + light pools, painted
     once. The grain's alpha is baked into the SVG — no blend layer, no
     full-screen overlay. NOT background-attachment: fixed (repaints on every
     scroll in desktop Chrome; ignored on iOS anyway). */
  background-color: var(--canvas);
  background-image: var(--noise), var(--canvas-mesh);
  /* --canvas-mesh is THREE gradient layers, so size/repeat list four values
     (a two-value list would cycle and tile the mesh at 160px). */
  background-repeat: repeat, no-repeat, no-repeat, no-repeat;
  background-size: 160px 160px, 100% 900px, 100% 900px, 100% 900px;
  background-attachment: scroll;
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overscroll-behavior-y: none;
  -webkit-tap-highlight-color: transparent;
}

body {
  --content-left: max(0px, calc((100vw - var(--sidebar-w) - var(--screen-max)) / 2 + var(--sidebar-w)));
  --content-right: max(0px, calc((100vw - var(--sidebar-w) - var(--screen-max)) / 2));
}

body.font-large  { font-size: 18px; }
body.font-xlarge { font-size: 20px; }

/* Night and day both paint a background-image now (different mesh + grain),
   so verify-jade-chat's "night differs from day" check still holds. The old
   body.night inset-highlight block is gone: --edge lives inside every
   --elev-* level (SYSTEM.md §4). */

::selection { background: var(--accent-tint-2); color: var(--text-1); }

/* Focus — visible for keyboards, absent for taps. One ring, everywhere. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px;
  border-radius: inherit;
}
.input:focus-visible, .textarea:focus-visible, .select:focus-visible { outline: none; }

/* --------------------------------------------------------------------------
   6. App shell
   -------------------------------------------------------------------------- */
#app {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding-left: var(--sidebar-w);
}

/* The global app bar. Brand on the left, `#je-header-actions` on the right.
   It scrolls with the page (not sticky) so it can never sit on top of a
   screen's own sticky header. */
.je-appbar {
  width: 100%;
  max-width: var(--screen-max);
  margin: 0 auto;
  min-height: calc(var(--appbar-height) + var(--safe-top));
  padding: var(--safe-top) var(--gutter) 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.je-appbar-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-snug);
  color: var(--color-text);
}
.je-appbar-brand img { width: 26px; height: 26px; border-radius: 8px; }

/* Scrolled (main.js toggles body.is-scrolled past 8px): the bar sticks and
   turns to glass — with the tab bar, the second of the two blur layers
   (SYSTEM §5.3). While a sheet or toast is up it goes solid instead. */
body.is-scrolled .je-appbar {
  position: sticky; top: 0; z-index: 50;
  background: var(--surface-glass);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 -1px 0 var(--stroke-1);
}
/* Screens with their own sticky TOP header (contact tabs, card editor head)
   keep the app bar in flow, so the two never stack. */
body.is-scrolled:is([data-route="contact"], [data-route="cards2-edit"]) .je-appbar {
  position: static; background: none; -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none;
}
body.is-scrolled.je-sheet-open .je-appbar,
body.is-scrolled.je-overlay-up .je-appbar {
  -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--surface-glass-solid);
}

/* THE HEADER ACTIONS SLOT. Lane C (notifications) appends its bell here at
   runtime: document.getElementById('je-header-actions').prepend(bellButton).
   Children are laid out right-aligned, 4px apart; give them the `.icon-btn`
   class for the standard 44px round target. */
.je-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
}

.screen {
  flex: 1;
  width: 100%;
  max-width: var(--screen-max);
  margin: 0 auto;
  /* The bottom clearance is the tab bar plus breathing room. The extra 86px
     that used to sit here existed only to clear a floating Quick Add button;
     that button lives in the app bar now. */
  padding: var(--space-1) var(--gutter) calc(var(--tabbar-h) + var(--tabbar-float) + var(--safe-bottom) + var(--sp-7));
  /* A short rise on entry. The final state has NO transform, so fixed
     footers inside a screen are only affected for the entrance itself. */
  animation: je-screen-in var(--dur-base) var(--ease-out-expo) both;
}
/* 6px for screens (lists use --rise, 14px). Reduced motion: --dur-base → 0. */
@keyframes je-screen-in { from { opacity: 0; transform: translateY(6px); } }

/* Screen header — every screen uses this so the app reads as one thing */
.screen-head { margin-bottom: var(--space-5); }
.screen-eyebrow {
  font: var(--type-eyebrow);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--accent-text);
}
/* ONE page title. Six classes used to do this at five sizes; they collapse
   here because the feature sheets load after this one and would otherwise
   win. Each keeps only its own margins and colour. */
.screen-title,
.je-screen-title,
.je-today-title,
.je-net-title,
.set-title,
.je-nf-h1 {
  font: var(--type-large-title);
  letter-spacing: var(--track-display);
  color: var(--text-1);
  text-wrap: balance;
}
.screen-sub { color: var(--text-2); font: var(--type-subhead); margin-top: 4px; }

/* Section heads: title-3, sentence case (SYSTEM §3). */
.section-title {
  font: var(--type-title-3);
  letter-spacing: var(--track-title);
  color: var(--text-1);
  margin: var(--sp-7) 0 var(--sp-3);
}

/* --------------------------------------------------------------------------
   7. Navigation — a floating glass capsule on phones, a 248px solid sidebar
      from 900px (SYSTEM.md §6.7). The same <nav class="bottom-nav"> element
      in both, so every suite that clicks `.bottom-nav .nav-item[data-route=…]`
      keeps working. main.js mounts the sliding `.je-nav-lens`
      (components/ui/tabbar.js); without it the active item paints the lens.
   -------------------------------------------------------------------------- */
.bottom-nav {
  position: fixed; z-index: 60;
  left: calc(var(--safe-left) + var(--tabbar-float));
  right: calc(var(--safe-right) + var(--tabbar-float));
  bottom: calc(var(--safe-bottom) + var(--tabbar-float) * .5);
  height: var(--tabbar-h); padding: 6px; border: 0; border-radius: var(--r-bar);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0;
  background: var(--surface-glass);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-3);
}
.je-nav-brand { display: none; }

.nav-item {
  position: relative; z-index: 1;
  min-height: var(--tap); padding: 0; border: 0; border-radius: 20px; background: transparent;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  /* --text-2 on glass α.84/.80 passes over the worst content (≥ 4.8, §0.2). */
  color: var(--text-2);
  font: var(--type-eyebrow); font-size: 0.66rem; letter-spacing: .01em; text-transform: none;
  transition: color var(--dur-fast) var(--ease-standard);
}
.nav-item .material-symbols-rounded {
  font-size: 23px; width: auto; height: 26px; display: grid; place-items: center; background: none;
  transition: transform var(--dur-instant) var(--spring-snappy);
}
.nav-item.active, .nav-item[aria-current="page"] { color: var(--accent-text); }
.nav-item.active .material-symbols-rounded, .nav-item[aria-current="page"] .material-symbols-rounded { font-variation-settings: 'FILL' 1, 'wght' 600; }
.nav-item:active .material-symbols-rounded { transform: scale(.9); }
.nav-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus-color); }
/* The glowing dot over the active tab. */
.nav-item.active::after, .nav-item[aria-current="page"]::after {
  content: ""; position: absolute; top: 3px; width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 8px 2px var(--accent-glow);
}
.je-nav-lens { /* one element, moved by JS: style.transform = translateX(index * 100%) */
  position: absolute; z-index: 0; top: 6px; bottom: 6px; left: 6px; width: calc((100% - 12px) / var(--tabs, 6));
  border-radius: 20px; background: var(--tab-lens); pointer-events: none;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent) 22%, transparent);
  transition: transform var(--dur-base) var(--spring-snappy);
}
/* Without the lens element (old DOM), the active item paints the lens itself. */
.bottom-nav:not(:has(.je-nav-lens)) .nav-item.active { background: var(--tab-lens); }
@media (hover: hover) { .nav-item:hover { color: var(--text-1); } }
/* Blur budget (§5.3): while a sheet is open the tab bar goes solid. */
body.je-sheet-open .bottom-nav, body.je-overlay-up .bottom-nav {
  -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--surface-glass-solid);
}

/* --------------------------------------------------------------------------
   8. Quick Add — "+ add someone", reachable from every screen.

   It used to float over the bottom-right of the content (a FAB), and it
   covered a real control on today, circle, network and settings — any row
   could scroll under it — and forced a 78px right inset on circles and me.
   It now lives in the app bar's action slot (the Apple Contacts placement),
   where it can cover nothing. The class name is unchanged on purpose:
   suites click `.quick-add-fab`.
   -------------------------------------------------------------------------- */
.quick-add-fab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--accent-tint-2);
  color: var(--accent-text);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), var(--elev-1);
  transition: transform var(--dur-instant) var(--spring-snappy), background var(--dur-fast), box-shadow var(--dur-fast);
}
.quick-add-fab:active { transform: scale(.94); box-shadow: var(--elev-press); }
.quick-add-fab:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* width 1em + clip: before the icon font arrives (cold load, blocked font) the
   ligature renders as the literal word "person_add" and pushed #network to
   412px on a 390px screen (verify-jade-ai, seen 2026-10-20). Same guard as
   .je-nf-bell in notifications.css. */
.quick-add-fab .material-symbols-rounded { font-size: 22px; width: 1em; max-width: 1em; overflow: hidden; white-space: nowrap; flex: none; }
.quick-add-fab .je-add-label { display: none; font-size: var(--text-md); font-weight: var(--weight-semibold); }

/* Where Quick Add is not welcome: screens whose whole job is a sticky primary
   action (triage) or that ARE adding a person (contact-form). */
body[data-route="triage"] .quick-add-fab,
body[data-route="contact-form"] .quick-add-fab { display: none; }

/* Full-bleed presentation surfaces carry no shell chrome at all: the card
   preview is the card, shown the way someone else will see it. The public
   member/event pages are someone else's view too. */
body[data-route="cards2-preview"] .quick-add-fab,
body[data-route="cards2-preview"] .bottom-nav,
body[data-route="cards2-preview"] .je-appbar { display: none; }
body[data-route="public-gate"] .je-appbar,
body[data-route="member-events"] .je-appbar,
body[data-route="events"] .je-appbar,
body[data-route="community-events"] .je-appbar { display: none; }
body[data-route="cards2-preview"] .screen,
body[data-route="public-gate"] .screen,
body[data-route="member-events"] .screen,
body[data-route="events"] .screen,
body[data-route="community-events"] .screen { padding-top: calc(var(--safe-top) + 14px); }

/* --------------------------------------------------------------------------
   Inverted surfaces (the "ink" family).
   `--ink` flips with the theme; pair it with `--color-bg` as the foreground,
   which is its exact inverse in both themes.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Encrypted vault — the INLINE unlock offer. Ordinary content inside a
   screen's empty state; never floating (see components/vault-unlock.js).
   -------------------------------------------------------------------------- */
.vault-offer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  border: 0;
  border-radius: var(--r-md);
  background: var(--surface-1);
  box-shadow: var(--elev-1);
  text-align: center;
}
.vault-offer[hidden] { display: none; }   /* `hidden` must beat `display: flex` */
.vault-offer-icon { font-size: 24px; color: var(--accent-text); }
.vault-offer-text {
  margin: 0;
  font: var(--type-subhead);
  color: var(--text-2);
  max-width: 34ch;
}

.vault-error {
  color: var(--color-error);
  font-size: 0.85rem;
  min-height: 1.2em;
  margin: -4px 0 0;
}

/* --------------------------------------------------------------------------
   9. Shared primitives — Luminous Jade (SYSTEM.md §6), unscoped (S4b Phase B).
      Package B's look-alikes (`.je-btn*`, `.je-empty`, `.je-chip`,
      `.je-input`, `.je-icon-btn`, `.je-sheet`) are grouped onto the same
      rules here; their old copies in circle.css are gone. Depth comes from
      light (elevation + the lit top edge), never from borders.
   -------------------------------------------------------------------------- */

/* Cards (§6.1) — solid paper. Glass is for chrome only. */
.card {
  background: var(--surface-1); color: var(--text-1);
  border: 0; border-radius: var(--r-md);
  padding: var(--sp-4);
  box-shadow: var(--elev-1);
}
.card-raised { background: var(--surface-raised); box-shadow: var(--elev-2); }
.card + .card { margin-top: var(--sp-3); }
.card-flush { padding: 0; overflow: hidden; }
.card-title { font: var(--type-title-3); letter-spacing: var(--track-title); }

/* Stat tile */
.je-stat {
  min-height: 76px; display: flex; flex-direction: column; justify-content: center; gap: 4px;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
  background: var(--surface-1); box-shadow: var(--elev-1);
}
.je-stat-num { font: var(--type-stat-num); font-size: calc(1.875rem * var(--type-scale, 1)); }
.je-stat-label { font: var(--type-footnote); color: var(--text-2); }

/* Numerals: tabular, tight; solid in light, gradient-clipped in dark only. */
.num-fill { font-variant-numeric: tabular-nums; letter-spacing: var(--track-num); }
.num-fill { color: var(--accent-text); }
/* dark only — gradient text in light is 1.6:1 (SYSTEM §1.7) */
:where(:root[data-theme="dark"], body.night, .je-theme-dark) .num-fill {
  background: var(--num-fill); -webkit-background-clip: text; background-clip: text; color: transparent;
}
:where(.je-theme-light) .num-fill { background: none; color: var(--accent-text); }
body.low-stim .num-fill { background: none; color: var(--text-1); }

/* Hero (§6.2) — the lit jade surface. MAX ONE PER SCREEN.
   No --text-3 inside: the corner glow lifts the backdrop (3.0:1 measured). */
.je-hero {
  position: relative; isolation: isolate; color: var(--text-1);
  padding: var(--sp-5); border-radius: var(--r-lg);
  /* Composed HERE, not via --surface-hero, so a per-tier --hero-tint set on
     this element is the one that paints. */
  background: radial-gradient(120% 90% at 0% 0%, var(--hero-tint) 0%, transparent 55%), var(--hero-rest);
  box-shadow: var(--elev-hero);
}
.je-hero::before { /* 1px luminous rim: a static conic, never sweeping */
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; pointer-events: none;
  background: conic-gradient(from 200deg, var(--stroke-1), var(--accent) 60deg, var(--stroke-2) 120deg, var(--stroke-1) 200deg, rgba(34,199,232,.5) 280deg, var(--stroke-1));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
@supports not ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .je-hero::before { display: none; }
  .je-hero { box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-hero); }
}
.je-hero[data-tier="close"]  { --hero-tint: hsl(var(--hue-close) 80% 50% / .16); }
.je-hero[data-tier="circle"] { --hero-tint: hsl(var(--hue-circle) 80% 52% / .15); }
.je-hero[data-tier="outer"]  { --hero-tint: hsl(var(--hue-outer) 85% 55% / .14); }
body.low-stim .je-hero { background: var(--surface-1); box-shadow: var(--elev-1); }
body.low-stim .je-hero::before { display: none; }
.je-hero .list-row-end,
.je-hero .dim { color: var(--text-2); }
.je-hero-top { display: flex; align-items: center; gap: var(--sp-2); }
.je-hero-top .je-star { margin-left: auto; color: var(--star); }
.je-hero-who { display: flex; align-items: center; gap: var(--sp-4); margin-top: var(--sp-4); }
.je-hero-name { font: var(--type-name-lg); text-wrap: balance; }
.je-hero-who p { font: var(--type-footnote); color: var(--text-2); }
.je-hero-metric { display: flex; align-items: flex-end; gap: var(--sp-3); margin-top: var(--sp-5); }
.je-hero-num { font: var(--type-hero-num); }
.je-hero-metric > div b { display: block; font: var(--type-headline); }
.je-hero-metric > div span { display: block; font: var(--type-footnote); color: var(--text-2); }
.je-rhythm { position: relative; height: 4px; margin-top: var(--sp-4); border-radius: var(--r-pill); background: var(--ring-track); overflow: visible; }
.je-rhythm-fill {
  position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(90deg, var(--accent), var(--signal-drifting), var(--signal-overdue));
  transform-origin: left; animation: je-grow var(--dur-reveal) var(--ease-out-expo) .2s both;
}
.je-rhythm-tick { position: absolute; top: -4px; width: 2px; height: 12px; border-radius: 1px; background: var(--text-1); left: var(--at, 60%); }
.je-hero-why { margin-top: var(--sp-4); font: var(--type-body); color: var(--text-1); }
.je-hero-why.is-ai { padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm); background: linear-gradient(var(--ai-tint), var(--ai-tint)), var(--surface-1); }
.je-hero-why .je-ai-label { color: var(--ai); font-weight: 640; }
.je-hero-actions { display: grid; gap: var(--sp-3); margin-top: var(--sp-5); }
.je-hero-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); }

/* Buttons (§6.6). `.btn` alone == secondary. One .btn-primary per view.
   `.je-btn*` (Package B, Cards v2) are the same buttons. */
.btn, .je-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  position: relative; overflow: hidden; isolation: isolate;
  min-height: var(--btn-h); padding: 0 22px; border: 0; border-radius: var(--r-pill);
  font: var(--type-headline); letter-spacing: -0.01em;
  background: var(--surface-glass-solid); color: var(--text-1);
  box-shadow: 0 0 0 1px var(--stroke-2), var(--edge);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: transform var(--dur-instant) var(--spring-snappy), box-shadow var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard), opacity var(--dur-fast);
}
.btn .material-symbols-rounded, .je-btn .material-symbols-rounded { font-size: 20px; }
.btn:active, .je-btn:active { transform: scale(var(--press-scale)); }
.btn:focus-visible, .je-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn:disabled, .btn[aria-disabled="true"], .je-btn:disabled, .je-btn[aria-disabled="true"] { opacity: .42; box-shadow: none; pointer-events: none; }
.btn-secondary:active, .btn:not([class*="btn-"]):active,
.je-btn:not([class*="je-btn-"]):active { box-shadow: var(--elev-press); background: var(--surface-sunken); }
.btn-primary,
.je-btn-primary {
  color: var(--text-on-accent); background: var(--accent-gradient);
  box-shadow: var(--glow-accent), inset 0 1px 0 rgba(255,255,255,.45);
}
.btn-primary::after, .je-btn-primary::after { /* one sheen pass when it mounts */
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(105deg, transparent 35%, rgba(255,255,255,.4) 50%, transparent 65%);
  transform: translateX(120%); animation: je-sheen 1.1s var(--ease-standard) .4s 1 both;
}
.btn-primary:focus-visible, .je-btn-primary:focus-visible { box-shadow: var(--focus-ring), var(--glow-accent); }
.btn-ghost, .je-btn-ghost { background: transparent; color: var(--accent-text); box-shadow: none; }
.btn-ghost:active, .je-btn-ghost:active { background: var(--accent-tint); }
.btn-danger, .je-btn-danger { background: var(--danger-tint); color: var(--danger-text, var(--danger)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 30%, transparent); }
.btn-dark { background: var(--surface-glass-solid); color: var(--text-1); } /* legacy alias → secondary */
.btn-block, .je-btn-block { display: flex; width: 100%; }
/* 44px, not 36px. `btn-sm` means smaller TYPE, not a smaller thumb target. */
.btn-sm { min-height: var(--btn-h-sm); padding: 0 16px; font: var(--type-subhead); font-weight: 640; }
.btn-lg { min-height: 56px; padding: 0 26px; }
@media (hover: hover) {
  .btn:hover, .je-btn:hover { background: var(--surface-2); }
  .btn-primary:hover, .je-btn-primary:hover { background: var(--accent-gradient); filter: brightness(1.04); }
  .btn-ghost:hover, .je-btn-ghost:hover { background: var(--accent-tint); }
  .btn-danger:hover, .je-btn-danger:hover { background: color-mix(in srgb, var(--danger) 14%, transparent); }
}

.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-row > .btn { flex: 1 1 0; min-width: 0; }

.icon-btn, .je-icon-btn {
  width: 44px; height: 44px; flex: none; border: 0; border-radius: 50%;
  display: inline-grid; place-items: center;
  color: var(--text-1); background: var(--surface-glass-solid);
  box-shadow: 0 0 0 1px var(--stroke-1), var(--elev-1);
  cursor: pointer;
  transition: transform var(--dur-instant) var(--spring-snappy), background var(--dur-fast);
}
.icon-btn .material-symbols-rounded, .je-icon-btn .material-symbols-rounded { font-size: 22px; }
.icon-btn:active, .je-icon-btn:active { transform: scale(.94); box-shadow: var(--elev-press); }
.icon-btn:focus-visible, .je-icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.icon-btn:disabled, .icon-btn[aria-disabled="true"] { opacity: .35; pointer-events: none; }
.icon-btn.is-quiet { background: transparent; box-shadow: none; }
@media (hover: hover) { .icon-btn:hover, .je-icon-btn:hover { background: var(--surface-2); } }

.je-tile { /* quiet action: Call / Text / Not today */
  min-height: 56px; display: grid; place-items: center; align-content: center; gap: 2px; padding: 6px 4px;
  border: 0; border-radius: var(--r-md); cursor: pointer;
  background: color-mix(in srgb, var(--surface-1) 55%, transparent); box-shadow: 0 0 0 1px var(--stroke-1);
  font: var(--type-caption); color: var(--text-1);
  transition: transform var(--dur-instant) var(--spring-snappy), box-shadow var(--dur-fast);
}
.je-tile .material-symbols-rounded { font-size: 22px; color: var(--accent-text); }
.je-tile:active { box-shadow: var(--elev-press); transform: scale(var(--press-scale)); }
.je-tile:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Inputs (§6.9) — ink-on-paper: the field is pressed INTO the surface. */
.field { margin-bottom: var(--sp-4); }
.field-label { display: block; margin-bottom: 6px; font: var(--type-footnote); font-weight: 640; color: var(--text-2); }
.input, .textarea, .select, .je-input {
  width: 100%; min-height: var(--input-h); padding: 14px 16px; border: 0; border-radius: var(--r-sm);
  background-color: var(--surface-sunken); color: var(--text-1);
  font: var(--type-callout); /* 16px: no iOS zoom */
  box-shadow: var(--elev-press); scroll-margin-bottom: 120px;
  transition: box-shadow var(--dur-fast) var(--ease-standard), background-color var(--dur-fast);
}
.select { appearance: none; -webkit-appearance: none; padding-right: 44px;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-2) 50%), linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
  background-position: calc(100% - 22px) 50%, calc(100% - 16px) 50%; background-size: 6px 6px; background-repeat: no-repeat; }
.input::placeholder, .textarea::placeholder, .je-input::placeholder { color: var(--text-3); opacity: 1; }
.input:focus, .textarea:focus, .select:focus, .je-input:focus {
  outline: none; background-color: var(--surface-1);
  box-shadow: inset 0 0 0 1.5px var(--focus-color), 0 0 0 4px var(--accent-tint);
}
.input.is-error, .input[aria-invalid="true"], .textarea[aria-invalid="true"] {
  box-shadow: inset 0 0 0 1.5px var(--danger), 0 0 0 4px var(--danger-tint);
}
.field-hint { margin-top: 6px; font: var(--type-footnote); color: var(--text-2); }
.field-error { margin-top: 6px; font: var(--type-footnote); color: var(--danger); }
.textarea { min-height: 112px; resize: vertical; line-height: 1.5; }

.search-wrap { position: relative; }
.search-wrap .material-symbols-rounded {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--text-2); pointer-events: none; font-size: 21px;
}
.search-wrap .input { padding-left: 44px; }

/* Chips (§6.13): 36px visual, 44px hit band. `.je-chip` / `.je-choice`
   (Circle filters) are the same chip; `.is-on` == `.active`. */
.chip-row { display: flex; gap: 8px; overflow-x: auto; padding: 4px 0; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }
.chip, .je-chip, .je-choice {
  position: relative; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; min-width: 44px; padding: 0 14px;
  border: 0; border-radius: var(--r-pill);
  background: var(--surface-3); color: var(--text-2);
  font: var(--type-caption); font-weight: 640; white-space: nowrap; cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast), box-shadow var(--dur-fast);
}
.chip::before, .je-chip::before, .je-choice::before { content: ""; position: absolute; inset: -4px 0; }
/* Circle's filter chips are its primary gesture: a real 44px box (suites measure it). */
.je-chip, .je-choice { min-height: 44px; }
.chip.active, .chip[aria-pressed="true"], .je-chip.is-on, .je-choice.is-on {
  background: var(--accent-tint-2); color: var(--accent-text);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}
.chip:focus-visible, .je-chip:focus-visible, .je-choice:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (hover: hover) { .chip:hover, .je-chip:hover, .je-choice:hover { color: var(--text-1); } }

.chip-signal { --sig: var(--signal-quiet); color: var(--sig); background: color-mix(in srgb, var(--sig) 13%, transparent);
  display: inline-flex; align-items: center; gap: 6px; min-height: 26px; padding: 0 10px; border-radius: var(--r-pill);
  font: var(--type-caption); font-weight: 640; }
.chip-signal::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 8px currentColor; }
.chip-signal[data-signal="overdue"]  { --sig: var(--signal-overdue); }
.chip-signal[data-signal="drifting"] { --sig: var(--signal-drifting); }
.chip-signal[data-signal="watching"] { --sig: var(--signal-watching); }
.chip-signal[data-signal="birthday"] { --sig: var(--signal-birthday); }
.chip-signal[data-signal="ai"]       { --sig: var(--ai); }

/* Segmented (§6.8): 40px buttons in a 4px sunken tray = a 44px hit band. */
.segmented {
  display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 2px; padding: 4px;
  border: 0; border-radius: 14px;
  background: var(--surface-sunken); box-shadow: var(--elev-press);
}
.segmented > button, .segmented > .seg {
  position: relative; min-height: 40px; padding: 0 14px; border: 0; border-radius: 10px; background: transparent;
  font: var(--type-subhead); font-weight: 600; color: var(--text-2); cursor: pointer;
  transition: background var(--dur-base) var(--spring-snappy), color var(--dur-fast), box-shadow var(--dur-base);
}
.segmented > button::before, .segmented > .seg::before { content: ""; position: absolute; inset: -2px 0; }
.segmented > [aria-pressed="true"], .segmented > [aria-selected="true"],
.segmented > [aria-checked="true"], .segmented > .active {
  color: var(--text-1); background: var(--surface-2); box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-1);
}
.segmented > button:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The screens' own segmented controls (Circle lenses, Cards tabs, the
   Settings theme switch) wear the same tray and thumb. Their buttons keep a
   real 44px box (suites measure them), so the tray is 3px. */
.yc-seg, .c2-seg, .set-seg {
  display: flex; gap: 2px; padding: 3px; border: 0; border-radius: 15px;
  background: var(--surface-sunken); box-shadow: var(--elev-press);
}
.yc-seg-btn, .c2-seg-btn, .set-seg-btn {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 0 8px; border: 0; border-radius: 12px; background: transparent;
  font: var(--type-subhead); font-weight: 600; color: var(--text-2); white-space: nowrap; cursor: pointer;
  transition: background var(--dur-base) var(--spring-snappy), color var(--dur-fast), box-shadow var(--dur-base);
}
.yc-seg-btn.is-on, .c2-seg-btn.is-on, .set-seg-btn.is-on {
  color: var(--text-1); background: var(--surface-2); box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-1);
}
.yc-seg-btn.is-on .material-symbols-rounded, .c2-seg-btn.is-on .material-symbols-rounded, .set-seg-btn.is-on .material-symbols-rounded { color: var(--accent-text); }
:is(.yc-seg-btn, .c2-seg-btn, .set-seg-btn):focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Avatars + health ring (§6.13). Initials use theme lightness (4.8-10.5:1
   across all seven tier hues); never white-on-grey again. */
.avatar {
  position: relative; flex: 0 0 auto;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 30% 25%, hsl(var(--h,162) var(--avatar-s) calc(var(--avatar-l-bg) + 6%)), hsl(var(--h,162) var(--avatar-s) var(--avatar-l-bg)) 70%);
  color: hsl(var(--h,162) 70% var(--avatar-l-fg));
  font-family: var(--font-num); font-weight: 760; font-size: 0.92rem; letter-spacing: -0.02em;
  overflow: hidden; user-select: none;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.avatar-lg { width: 96px; height: 96px; font-size: 2rem; }
.avatar-xl { width: 112px; height: 112px; font-size: 2.2rem; }
.avatar-sm { width: 34px; height: 34px; font-size: 0.75rem; }
.avatar.has-ring { overflow: visible; }
.avatar.has-ring::before {
  content: ""; position: absolute; inset: -5px; border-radius: 50%; --p: var(--ring-p, 0);
  background: conic-gradient(from -90deg, var(--ring-c, var(--accent)) calc(var(--p) * 1%), var(--ring-track) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  animation: je-ring var(--dur-ring) var(--ease-out-expo) both;
}
.je-days-pill {
  position: absolute; left: 50%; bottom: -9px; transform: translateX(-50%); padding: 1px 7px; border-radius: var(--r-pill);
  font: var(--type-caption); font-weight: 720; white-space: nowrap;
  background: var(--sig, var(--signal-drifting)); color: var(--text-inverse);
}
:where(:root:not([data-theme="dark"]), .je-theme-light) .je-days-pill { background: #F4B26A; color: #3D1F02; }
:where(body.night, .je-theme-dark) .je-days-pill { background: var(--sig, var(--signal-drifting)); color: var(--text-inverse); }

/* Tier dot / pill */
.tier-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: var(--tier-outside); }
.tier-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--radius-pill);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
  background: var(--surface-3); color: var(--text-2);
}
.tier-inner-circle { --tier-color: var(--tier-inner-circle); }
.tier-circle       { --tier-color: var(--tier-circle); }
.tier-outer-circle { --tier-color: var(--tier-outer-circle); }
.tier-outside      { --tier-color: var(--tier-outside); }
.dupe-badge { flex-shrink: 0; background: color-mix(in srgb, var(--warning) 16%, var(--surface-1)); color: var(--warning); }
/* A duplicate-flagged row has more to say than a plain one — let it wrap. */
.list-row:has(.dupe-badge) { flex-wrap: wrap; row-gap: 4px; }
.list-row:has(.dupe-badge) .list-row-main { flex-basis: 100%; }
.tier-dot.tier-inner-circle, .tier-dot.tier-circle,
.tier-dot.tier-outer-circle, .tier-dot.tier-outside { background: var(--tier-color); }

/* List rows (§6.5) — one solid inset group, 64px rows, the hairline drawn by
   ::before and inset to the text column. */
.list {
  background: var(--surface-1); border: 0; border-radius: var(--r-md);
  overflow: hidden; box-shadow: var(--elev-1);
}
.list-row {
  position: relative; display: flex; align-items: center; gap: 14px; width: 100%;
  min-height: var(--row-min); padding: 10px 14px 10px 16px; text-align: left; color: var(--text-1);
  background: transparent; border: 0;
  transition: background var(--dur-fast) var(--ease-standard);
}
.list-row + .list-row::before { content: ""; position: absolute; top: 0; right: 0; left: var(--row-inset, 16px); height: 1px; background: var(--stroke-1); pointer-events: none; }
.list-row:has(> .avatar) { --row-inset: 74px; }
.list-row:active { background: var(--accent-tint); }
@media (hover: hover) { button.list-row:hover, a.list-row:hover { background: color-mix(in srgb, var(--text-1) 4%, transparent); } }
.list-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus-color); }
.list-row-main { flex: 1; min-width: 0; }
.list-row-title { font: var(--type-headline); letter-spacing: -0.012em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.list-row-sub { display: flex; align-items: center; gap: 6px; margin-top: 2px; font: var(--type-footnote); color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.list-row-sub .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--c, var(--signal-quiet)); box-shadow: 0 0 8px var(--c, transparent); }
.list-row-end { flex: 0 0 auto; text-align: right; color: var(--text-3); font: var(--type-footnote); }
.list-row-num { display: block; font: var(--type-row-num); font-variant-numeric: tabular-nums; color: var(--text-1); }
.list-row-num + small { font: var(--type-eyebrow); letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); }
.list-row .chev { color: var(--text-3); }

/* Empty state (§6.11) — a headline, one line, and a primary action.
   `.je-empty` (Package B) is the same component. */
.empty-state, .je-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  gap: 8px; padding: var(--sp-6) var(--sp-4); color: var(--text-2);
}
.empty-state .empty-icon, .je-empty .je-empty-icon {
  display: grid; place-items: center; width: 72px; height: 72px; margin: 0 auto 4px; border-radius: 50%;
  font-size: 36px !important; color: var(--accent-text);
  background: radial-gradient(circle at 50% 40%, var(--accent-tint-2), transparent 72%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent), 0 0 34px -10px var(--accent-glow);
}
.empty-state h2, .je-empty h2 { font: var(--type-title-2); letter-spacing: var(--track-title); color: var(--text-1); text-wrap: balance; margin: 0; }
.empty-state p, .je-empty p { font: var(--type-subhead); color: var(--text-2); max-width: 32ch; margin: 0 auto; }
.empty-state .btn, .je-empty .je-btn, .je-empty .btn { margin-top: 6px; min-width: 220px; }
.je-empty .je-btn + .je-btn { margin-top: 0; }
.je-empty > .vault-offer { order: -1; }
.je-empty-art { position: relative; width: 168px; height: 168px; margin-bottom: 12px; display: grid; place-items: center; }
.je-empty-art i { position: absolute; inset: 0; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--stroke-2); }
.je-empty-art i:nth-child(2) { inset: 22px; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent); }
.je-empty-art i:nth-child(3) {
  inset: 46px; background: radial-gradient(circle at 50% 40%, var(--accent-tint-2), transparent 70%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 40px -6px var(--accent-glow);
  animation: je-breathe 5s var(--ease-standard) infinite alternate;
}
.je-empty-art svg, .je-empty-art .material-symbols-rounded { position: relative; width: 48px; height: 48px; font-size: 44px; color: var(--accent-text); display: grid; place-items: center; }

/* Skeleton (§6.12) — shaped like the content; shimmer only while loading. */
.je-skel-row { display: flex; align-items: center; gap: 14px; min-height: var(--row-min); padding: 10px 16px; }
.je-skel-row > div { flex: 1; min-width: 0; }
.je-skel {
  display: block; border-radius: var(--r-xs); height: 12px;
  background: linear-gradient(90deg, var(--skel-a) 0%, var(--skel-b) 50%, var(--skel-a) 100%) 0 0 / 200% 100%;
  animation: je-shimmer 1.6s linear infinite;
}
.je-skel.circle { width: 48px; height: 48px; border-radius: 50%; flex: none; }
.je-skel.line-1 { width: 72%; }
.je-skel.line-2 { width: 46%; height: 10px; margin-top: 8px; }
.je-skel.num { width: 64px; height: 52px; border-radius: var(--r-sm); }

/* Bottom sheet (§6.3) — glass, springs up; a centred dialog from 900px.
   `.modal-*` is canonical; the tier picker's `.je-sheet*` (transition-driven
   by .je-sheet-in / .je-sheet-closing) wears the same material. */
.modal-overlay, .je-sheet-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: flex-end; justify-content: center;
}
.modal-overlay { background: var(--overlay); animation: je-fade var(--dur-base) var(--ease-standard) both; }
.je-sheet-overlay { z-index: 900; background: transparent; transition: background var(--dur-base) var(--ease-standard); }
.je-sheet-overlay.je-sheet-in { background: var(--overlay); }
.je-sheet-overlay.je-sheet-closing { background: transparent; }
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html:not(.lite) .modal-overlay { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
}
.modal-content, .je-sheet {
  position: relative; isolation: isolate; width: 100%; max-width: var(--page-max); max-height: 88dvh;
  overflow-y: auto; overscroll-behavior: contain; color: var(--text-1);
  padding: 10px var(--gutter) calc(var(--sp-6) + var(--safe-bottom));
  border: 0; border-radius: var(--r-xl) var(--r-xl) 0 0;
  background: var(--surface-glass); -webkit-backdrop-filter: var(--glass-blur-sheet); backdrop-filter: var(--glass-blur-sheet);
  box-shadow: var(--elev-4);
}
.modal-content { animation: je-sheet-up var(--dur-slow) var(--spring-smooth) both; }
.je-sheet { transform: translateY(100%); transition: transform var(--dur-slow) var(--spring-smooth); }
.je-sheet-overlay.je-sheet-in .je-sheet { transform: none; }
.je-sheet-overlay.je-sheet-closing .je-sheet { transform: translateY(100%); transition-duration: calc(var(--dur-slow) * .5); transition-timing-function: var(--ease-in); }
.modal-content::before, .je-sheet::before { /* jade bloom at the top edge */
  content: ""; position: absolute; inset: 0 0 auto; height: 160px; border-radius: inherit; pointer-events: none; z-index: -1;
  background: radial-gradient(80% 100% at 50% 0%, var(--accent-tint-2), transparent 70%);
}
.modal-handle, .je-sheet-handle {
  width: 36px; height: 5px; border-radius: var(--r-pill);
  background: var(--stroke-strong);
  margin: 2px auto 14px;
}
.je-sheet-handle { cursor: pointer; }
.modal-title, .je-sheet-title { font: var(--type-title-2); letter-spacing: var(--track-title); margin-bottom: 4px; }
.modal-sub, .je-sheet-sub { font: var(--type-subhead); color: var(--text-2); margin: 4px 0 var(--sp-4); }
body.je-sheet-open { overflow: hidden; }

/* Toasts (§6.4) — glass, springs in above the tab bar. The legacy
   lib/toast.js container and the S3a stack share these rules. */
.toast-container {
  position: fixed;
  left: calc(var(--content-left) + (100vw - var(--content-left) - var(--content-right)) / 2);
  bottom: calc(var(--nav-height) + var(--safe-bottom) + 10px);
  transform: translateX(-50%);
  z-index: 400;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: min(calc(100vw - 2 * var(--gutter)), 460px);
  pointer-events: none;
}
/* R7: when the bottom stack would sit on a control, settleToasts() moves it under the app bar. */
.toast-container.toast-top { bottom: auto; /* top is set inline by settleToasts() */ }
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 12px; min-height: 52px; max-width: 100%;
  padding: 10px 18px; border-radius: var(--r-md);
  background: var(--surface-glass); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  color: var(--text-1); font: var(--type-subhead); font-weight: 560; text-align: left;
  box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-3);
  opacity: 0; transform: translateY(24px) scale(.96);
  transition: opacity var(--dur-base) var(--ease-standard), transform var(--dur-slow) var(--spring-bouncy);
}
.toast.toast-in { opacity: 1; transform: none; }
/* A status word needs more than colour: the legacy toasts get a lit edge
   in their signal colour, and the text stays --text-1 (no inverted ink). */
.toast-success { box-shadow: inset 3px 0 0 var(--accent), 0 0 0 1px var(--stroke-2), var(--elev-3); }
.toast-error   { box-shadow: inset 3px 0 0 var(--danger), 0 0 0 1px color-mix(in srgb, var(--danger) 40%, transparent), var(--elev-3); }
.toast-warning { box-shadow: inset 3px 0 0 var(--warning), 0 0 0 1px color-mix(in srgb, var(--warning) 35%, transparent), var(--elev-3); }

/* JE-110 BT — two pop-up looks, one switch (settings.popupStyle via components/ui/popup-style.js; Settings -> Pop-up style).
   BRANDED (default): a larger Luminous-Jade card, an icon per kind, spring motion,
   a pressed state. The icon is a pseudo-element with empty alt text so a toast's
   text (and what a screen reader says) is only its words. */
html:not([data-popup-style="ios"]) .toast-container:not(.je-toast-stack) .toast {
  position: relative; min-height: 60px; padding: 12px 16px 12px 12px; border-radius: 22px; gap: 12px;
  box-shadow: 0 0 0 1px var(--stroke-2), inset 0 1px 0 color-mix(in srgb, #fff 14%, transparent), var(--elev-3);
  touch-action: pan-x;
}
html:not([data-popup-style="ios"]) .toast-container:not(.je-toast-stack) .toast.toast-error { box-shadow: 0 0 0 1px color-mix(in srgb, var(--danger) 45%, transparent), var(--elev-3); }
html:not([data-popup-style="ios"]) .toast-container:not(.je-toast-stack) .toast.toast-in:active { transform: scale(.985); transition-duration: 90ms; }
.toast::before {
  content: 'info' / ''; flex: none; width: 38px; height: 38px; border-radius: 13px; display: grid; place-items: center;
  font: 400 22px/1 'Material Symbols Rounded'; font-feature-settings: 'liga'; letter-spacing: normal; text-transform: none; white-space: nowrap;
  background: var(--surface-3); color: var(--text-1);
}
.je-toast-stack .toast::before { display: none; }
.toast-success::before { content: 'check_circle' / ''; background: var(--accent-gradient); color: var(--text-on-accent); box-shadow: 0 6px 20px -6px var(--accent-glow); }
.toast-warning::before { content: 'warning' / ''; background: color-mix(in srgb, var(--warning) 16%, transparent); color: var(--warning); }
.toast-error::before   { content: 'error' / ''; background: var(--danger-tint); color: var(--danger); }
.toast.toast-undo::before { content: 'undo' / ''; background: var(--accent-gradient); color: var(--text-on-accent); }
.toast-text { flex: 1; min-width: 0; display: block; }
.toast-text b { display: block; font: var(--type-headline); line-height: 1.25; }
.toast-text .toast-explain { display: block; margin-top: 2px; font: var(--type-footnote); font-weight: 450; color: var(--text-2); }
.toast-container:not(.je-toast-stack) .toast-action:active { transform: scale(.94); }
.toast-container:not(.je-toast-stack) .toast :is(.toast-action, .toast-close) { min-height: 44px; min-width: 44px; } /* a finger target in either look */
/* iOS-native look: a compact system-material banner that drops from the top. */
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) { top: calc(var(--safe-top, 0px) + var(--appbar-h, 56px) + 6px); bottom: auto; } /* same anchor as the Luminous stack: under the app bar, never over its buttons */
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast {
  min-height: 48px; padding: 8px 8px 8px 14px; gap: 10px; border-radius: 24px; max-width: min(100%, 420px);
  background: color-mix(in srgb, var(--surface-glass) 88%, transparent);
  -webkit-backdrop-filter: blur(28px) saturate(1.8); backdrop-filter: blur(28px) saturate(1.8);
  box-shadow: 0 10px 32px rgba(0, 0, 0, .18), 0 0 0 .5px var(--stroke-2);
  font: 600 15px/20px -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
  transform: translateY(-140%) scale(.94); transform-origin: top center;
  transition: opacity 180ms linear, transform 520ms cubic-bezier(.2, .9, .25, 1);
}
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast.toast-in { opacity: 1; transform: none; }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast.is-leaving { opacity: 0; transform: translateY(-140%) scale(.94); }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast::before { display: grid; width: 24px; height: 24px; border-radius: 0; background: none; box-shadow: none; font-size: 24px; }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-success::before { color: var(--success); }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-warning::before { color: var(--warning); }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-error::before { color: var(--danger); }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-text b { font: inherit; font-weight: 600; }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-text .toast-explain { font: 400 13px/18px -apple-system, BlinkMacSystemFont, system-ui, sans-serif; }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast :is(.toast-action, .toast-close) { border: 0; background: transparent; color: var(--accent-text); box-shadow: none; font-weight: 600; min-height: 44px; min-width: 44px; padding: 0 12px; }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-orb { width: 24px; height: 24px; border-radius: 0; background: none; box-shadow: none; color: var(--text-2); }
html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast-progress { display: none; }
@media (prefers-reduced-motion: reduce) {
  .toast, html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast { transition: opacity 120ms linear; }
  .toast.toast-in, .toast.toast-in:active { transform: none; }
  html[data-popup-style="ios"] .toast-container:not(.je-toast-stack) .toast { transform: none; }
}

/* Progress */
.progress { height: 6px; border-radius: var(--r-pill); background: var(--surface-sunken); overflow: hidden; box-shadow: var(--elev-press); }
.progress-bar { height: 100%; border-radius: inherit; background: var(--accent-gradient); transition: width 240ms var(--ease-standard); }

/* Motion (§7). Every animation declares its FINAL state and only animates
   FROM a start value; reduced motion / low-stim land complete. */
.je-rise { animation: je-rise var(--dur-reveal) var(--ease-out-expo) both; animation-delay: calc(min(var(--i, 0), 8) * var(--stagger)); }
@keyframes je-rise { from { opacity: 0; transform: translateY(var(--rise)); } }
@keyframes je-grow { from { transform: scaleX(0); } }
@keyframes je-fade { from { opacity: 0; } }
@keyframes je-sheet-up { from { transform: translateY(100%); } }
@keyframes je-dialog-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes je-sheen { from { transform: translateX(-120%); } to { transform: translateX(120%); } }
@keyframes je-breathe { to { transform: scale(1.06); } }
@keyframes je-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
@keyframes je-ring { from { --p: 0; } }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: none; } }
@keyframes dialog-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }
/* The two allowed loops stop dead under reduced motion and low-stim. */
@media (prefers-reduced-motion: reduce) {
  .je-skel, .je-empty-art i { animation: none !important; }
  .btn-primary::after, .je-btn-primary::after { animation: none !important; transform: translateX(120%); }
}
body.low-stim .btn-primary::after, body.low-stim .je-btn-primary::after { display: none; }
body.low-stim .je-skel, body.low-stim .je-empty-art i { animation: none; }
@media (forced-colors: active) {
  .card, .list, .je-stat, .je-tile, .chip, .je-chip, .input, .je-input, .textarea, .select, .modal-content, .je-sheet, .toast, .icon-btn, .je-icon-btn { border: 1px solid CanvasText; }
}

/* Utility */
.muted { color: var(--color-text-2); }
.dim { color: var(--color-text-3); }
.small { font-size: 0.82rem; }
.tiny { font-size: 0.72rem; }
.bold { font-weight: 700; }
.center { text-align: center; }
.row { display: flex; align-items: center; gap: 10px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.stack > * + * { margin-top: 10px; }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; }
  
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Boot / mount placeholders */

/* Late-loading feature sheet guard (packages/stylesheet.js). While a sheet
   that was injected at runtime is still in flight, the screen's content is
   held back and a skeleton stands in for it, so a cold boot is never a blank
   frame (JE-110 B1 P12). A timeout releases the content regardless. A sheet
   that arrived empty or failed keeps the content hidden for good and shows the
   Reload notice instead of an unstyled screen. Only style.css (always static)
   is relied on here. */
html.je-sheets-pending .screen,
html.je-sheets-failed .screen { position: relative; }
html.je-sheets-pending .screen > *,
html.je-sheets-failed .screen > * { visibility: hidden; }
html.je-sheets-pending .screen::before {
  content: "";
  position: absolute;
  top: var(--space-3); left: var(--gutter); right: var(--gutter);
  height: 360px;
  /* Static on purpose: the only forever-loops allowed are je-shimmer and
     je-breathe (verify-design-system section 7). */
  background:
    linear-gradient(var(--skel-a), var(--skel-a)) 0 0 / 52% 26px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 38px / 34% 14px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 76px / 100% 76px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 164px / 100% 76px no-repeat,
    linear-gradient(var(--skel-a), var(--skel-a)) 0 252px / 100% 76px no-repeat;
  border-radius: 12px;
  pointer-events: none;
}
.je-sheet-failed {
  position: fixed;
  top: 28vh; left: var(--gutter); right: var(--gutter);
  max-width: 420px; margin: 0 auto;
  padding: var(--space-5, 20px);
  text-align: center;
  background: var(--color-surface, #fff);
  color: var(--color-text);
  border-radius: 16px;
  z-index: 50;
}
.je-sheet-failed p { margin: 0 0 var(--space-3, 12px); }
.je-sheet-failed-title { font-weight: 600; font-size: 1.05rem; }

/* --------------------------------------------------------------------------
   10. Tablet (600–899px): the phone layout with a centred column.
   -------------------------------------------------------------------------- */
@media (min-width: 600px) {
  :root { --gutter: 24px; --screen-max: 640px; --page-max: 560px; }
}

/* --------------------------------------------------------------------------
   11. Desktop (≥900px): sidebar navigation + a wider content column.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  :root {
    --sidebar-w: 248px;
    --nav-height: 0px;
    --gutter: 32px;
    --screen-max: 760px;
    --page-max: 560px;
  }
  /* Routes whose content is a list or a canvas earn the full width. */
  body[data-route="circle"]      { --screen-max: 1000px; }
  body[data-route="today"]       { --screen-max: 880px; }
  body[data-route="network"],
  body[data-route="nearby"],
  body[data-route="circles"],
  body[data-route="your-circles"],
  body[data-route="cards2-home"] { --screen-max: 1100px; }

  /* The nav becomes a SOLID 248px sidebar (it sits over a static canvas, so
     blur would buy nothing). Items are 44px (audit F-12). */
  .bottom-nav {
    top: 0; left: 0; right: auto; bottom: 0;
    width: var(--sidebar-w); height: auto;
    display: flex; flex-direction: column; gap: 2px;
    padding: calc(var(--safe-top) + 18px) 14px 20px;
    border-radius: 0;
    background: var(--surface-sidebar);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    box-shadow: inset -1px 0 0 var(--stroke-1);
  }
  .bottom-nav .je-nav-lens { display: none; }
  .je-nav-brand {
    display: flex; align-items: center; gap: 10px;
    padding: 4px 10px 22px;
    font: var(--type-title-3); letter-spacing: var(--track-title);
    color: var(--text-1);
  }
  .je-nav-brand img { width: 30px; height: 30px; border-radius: 9px; box-shadow: var(--elev-1); }
  .nav-item {
    flex-direction: row; justify-content: flex-start; gap: 12px;
    min-height: 44px; /* JE-110 S3 BF6 / F-12: a full 44px target */
    padding: 0 12px;
    border-radius: var(--r-sm);
    font: var(--type-subhead); font-weight: 560; letter-spacing: -0.005em;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast);
  }
  .nav-item::after { display: none; }
  .nav-item:hover { background: color-mix(in srgb, var(--text-1) 5%, transparent); color: var(--text-1); }
  .nav-item .material-symbols-rounded { width: 24px; height: 24px; font-size: 21px; background: none !important; }
  .nav-item.active, .nav-item[aria-current="page"],
  .bottom-nav:not(:has(.je-nav-lens)) .nav-item.active { background: var(--surface-1); color: var(--accent-text); box-shadow: var(--elev-1); }
  .nav-item:active { transform: none; }

  /* The app bar loses its brand (the sidebar carries it) and Quick Add shows
     its label. */
  .je-appbar { min-height: 64px; padding-top: calc(var(--safe-top) + 12px); }
  .je-appbar-brand { display: none; }
  .quick-add-fab { width: auto; padding: 0 18px 0 14px; background: var(--accent-gradient); color: var(--text-on-accent); box-shadow: var(--glow-accent), inset 0 1px 0 rgba(255,255,255,.45); font: var(--type-subhead); font-weight: 640; }
  .quick-add-fab .je-add-label { display: inline; }

  .screen { padding-bottom: 48px; }
  .toast-container { bottom: calc(var(--safe-bottom) + 24px); }

  /* Today: the "also today" and "this week" lists go two-up. */
  body[data-route="today"] .je-mini-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }
  body[data-route="today"] .je-mini-list > * { margin: 0; }

  /* Sheets and modals become centred dialogs. */
  .modal-overlay { align-items: center; padding: 24px; }
  .modal-content {
    border-radius: var(--r-lg);
    padding: var(--sp-6);
    box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-3);
    animation-name: je-dialog-in;
  }
  .modal-handle { display: none; }
  body .je-sheet-overlay { align-items: center; padding: 24px; }
  body .je-sheet-handle { display: none; }
  body .je-sheet {
    border-radius: var(--r-lg);
    padding: var(--sp-6);
    box-shadow: 0 0 0 1px var(--stroke-2), var(--elev-3);
    transform: translateY(12px) scale(0.98);
    opacity: 0;
    transition: transform var(--dur-base) var(--ease-out-expo), opacity var(--dur-fast) var(--ease-standard);
  }
  body .je-sheet-overlay.je-sheet-in .je-sheet { transform: none; opacity: 1; }
  body .je-sheet-overlay.je-sheet-closing .je-sheet { transform: translateY(12px) scale(0.98); opacity: 0; }

  /* Fixed chrome that belongs to the content column follows the column, not
     the viewport (these rules out-rank the centred-phone versions in
     circle.css by their body prefix). */
  body .je-triage-sticky-foot {
    left: var(--content-left);
    right: var(--content-right);
    max-width: none;
    transform: none;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }
  body .je-filter-pill { left: calc(var(--content-left) + var(--gutter)); }
  body .je-az-rail { right: calc(var(--content-right) + 6px); }
}

/* --------------------------------------------------------------------------
   AI sheet (assistant surface) — canonical home, 2026-08-08 cleanup.
   Loaded here so which copy "wins" is not a function of package order.
   -------------------------------------------------------------------------- */
.ai-sheet { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; }
.ai-status { display: flex; align-items: center; gap: 8px; color: var(--text-2); font: var(--type-subhead); font-style: italic; }
.ai-spark { color: var(--ai); flex: none; }
.ai-out { white-space: pre-wrap; line-height: 1.5; font-size: 15px; min-height: 1.5em; }
.je-ai-btn .ai-spark { margin-right: 4px; vertical-align: -2px; }
/* Anything that asks Jade AI wears amethyst (SYSTEM §1.3): a quiet AI-tinted
   secondary, never a second jade primary. */
.je-btn.je-ai-btn, .btn.je-ai-btn {
  background: var(--ai-tint); color: var(--ai-text, var(--ai));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ai) 28%, transparent), var(--edge);
}
.je-ai-btn .ai-spark { color: var(--ai); }
@media (hover: hover) { .je-btn.je-ai-btn:hover, .btn.je-ai-btn:hover { background: color-mix(in srgb, var(--ai) 16%, transparent); } }
/* The spark pulses only while Jade is working (.ai-status), never on a resting button. */
@media (prefers-reduced-motion: no-preference) { :is(.ai-status, .je-ai-status) .ai-spark { animation: ai-pulse 1.4s ease-in-out infinite; } }
@keyframes ai-pulse { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }

/* W5 G7: a two-line button, short label over helper text (the paste gate's pills). */
.je-btn-stack { flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px; padding-block: var(--sp-3); text-align: left; }
.je-btn-stack .je-btn-main { font: var(--type-headline); }
.je-btn-stack .je-btn-help { font: var(--type-footnote); font-weight: 500; opacity: .85; white-space: normal; }

/* JE-110 B2 B5: Import option cards (S4B-PLAN 2.9). Icon tile + pill + title; the recommended vCard card is raised with an accent ring; the dry-run summary is a sunken well. */
.import-head { display: flex; align-items: center; gap: var(--sp-3); }
.import-head-text { min-width: 0; display: grid; gap: 4px; }
.import-title { margin: 0; font: var(--type-title-3); }
.import-ic { width: 46px; height: 46px; flex: none; display: grid; place-items: center; border-radius: var(--r-sm); background: var(--surface-3); color: var(--text-1); }
.import-reco { box-shadow: 0 0 0 1.5px var(--accent), var(--elev-2); }
/* JE-110 B2 B7: keyboard hints (Skip (S), Next ⏎, the 1/2/3 on the gender tiles) mean nothing on a touch screen (F-9). */
@media (hover: none) { .je-key-hint, .je-gender-btn .je-triage-key { display: none; } }
.import-head .tier-pill { border-radius: var(--r-md); }
.import-reco .import-ic, .import-reco .tier-pill { background: var(--accent-tint-2); color: var(--accent-text); }
.jade-dryrun { padding: var(--sp-3); border-radius: var(--r-md); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--stroke-1); display: grid; gap: 6px; }
.jade-photo-summary { padding: 6px 10px; border-radius: var(--r-sm); background: var(--surface-2); }
