@charset "UTF-8";
@import url("https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap");

/* ═══════════════════════════════════════════════════════════════════════════
   NORTEFY — Still Water v1.0
   Editorial brand system. Oyster ground · Stone grounding · Clay accent (rare).
   Three type families: EB Garamond (soul), Inter (interface), JetBrains Mono
   (metadata). Clay never touches Stone. Category palette preserved for Things.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══ 1 · Tokens ═══════════════════════════════════════════════════════════ */

:root {
  /* ── Still Water palette — core five tones ── */
  --oyster:      #EEF0EC;  /* primary surface */
  --mist:        #E7EAE5;  /* chrome surface — between oyster and fog */
  --fog:         #D7DCD4;  /* secondary surface */
  --stone:      #2F554E;  /* INTERACTIVE — primary fills, hover ink, focus rings, link-active */
  --stone-soft: #7FA39B;  /* STATIC — lightened stone for quiet decorative lines (e.g. inline-add-form dashed border) — never an interactive surface */
  --clay:       #B2694A;  /* single accent — capped at 5% */
  --clay-soft:  #f4ecea;  /* base raw token — soft warm tint; do NOT consume directly. Use one of the semantic surface roles below. */
  --deep:       #1C332F;  /* darkened stone — hover ink */

  /* ── Still Water palette — utility five tones ── */
  --paper:      #F5F6F2;  /* cards, elevated utility */
  --moss:       #5A6960;  /* italic quiet voice */
  --moss-deep:  #3E4A43;  /* moss-family hover for .btn-signout */
  --clay-deep:  #8F5238;  /* hover / active text accent */
  --ink:        #14201E;  /* body text, headlines */
  --rule:       #CFD4CB;  /* hairlines, dividers */

  /* ── Semantic surface roles mapped to Still Water ── */
  --bg:          var(--oyster);
  --bg-chrome:   var(--mist);  /* nav + footer chrome */
  --surface:     var(--paper);
  --surface2:    var(--fog);
  --border:      var(--rule);
  --border-soft: #DFE3DC;
  --text:        var(--ink);
  --text-mid:    var(--moss);
  /* --text-dim sits near the WCAG-AA 4.5:1 boundary on --surface — reserved
     for ≥ 14px secondary metadata (timestamps, counts, footnotes, disabled /
     future calendar cells, placeholder hints). Never body copy. Use --text
     or --text-mid for any prose paragraph or any text below 14px. */
  --text-dim:    #8A9086;

  /* ── Soft warm-surface roles (all start as --clay-soft) ──
     Distinct intents on the same base tint — pivoting any one of them is now
     a single-token edit instead of a 22-callsite sweep. */
  --modal-band:    var(--clay-soft);  /* modal & tip-modal header / footer band */
  --surface-hero:  var(--clay-soft);  /* db-foundation hero fill */
  --surface-quiet: var(--clay-soft);  /* quiet decorative card / item / chip / accordion-body fill */
  --hover-soft:    var(--clay-soft);  /* soft hover state on transient affordances (picker, accordion trigger) */

  /* ── Type families ── */
  --serif: 'EB Garamond', 'Iowan Old Style', Georgia, serif;
  --sans:  'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ── Semantic feedback — muted to sit alongside Still Water ── */
  --accent:          var(--clay);
  --accent-dim:      var(--clay-deep);
  --accent-glow:     #F3E6DE;
  --accent-line:     #D9B7A4;
  --success:         #4F7C68;
  --success-strong:  #2F554E;
  --success-glow:    #E4ECE4;
  --success-line:    #A8BDB0;
  --warn:            #C8894A;
  --warn-strong:     #8F5238;
  --warn-glow:       #F3E6DE;
  --danger:          #B44B3B;
  --danger-strong:   #8C3326;
  --danger-deep:     #6F1E17;
  --danger-glow:     #F1DED8;
  --danger-line:     #D3A79B;
  --blue:            var(--ink);

  /* ── Radii — soft but restrained; true pills for buttons ── */
  --radius-sm:   3px;
  --radius:      6px;
  --radius-md:   8px;
  --radius-lg:   10px;
  --radius-xl:   14px;
  --radius-pill: 9999px;

  /* ── Spacing — 4px scale ── */
  --space-0: 0;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-28: 7rem;
  --inset-pico:  0.1rem;
  --inset-nano:  0.2rem;
  --inset-mini:  0.4rem;
  --inset-tight: 0.6rem;
  --inset-snug:  0.8rem;

  /* ── Button padding tokens — three-step scale used across the button system.
     md = default (.btn, .btn-primary, .btn-danger, .modal-btn).
     sm = compact form actions (.btn-submit-narrow).
     xs = small chips (.toggle-btn). */
  --btn-py-md: 10px; --btn-px-md: 22px;
  --btn-py-sm: 8px;  --btn-px-sm: 18px;
  --btn-py-xs: 6px;  --btn-px-xs: 14px;

  /* ── Font sizes ── 14px floor (metadata); 15px floor for all helper/UI text.
     Three clear levels under body: 14 metadata · 15 small/UI · 16 body.
     3xs = micro utility chips (subordinate to UI). 2xs = mono metadata (tags, badges, eyebrows).
     sm = small/helper/secondary UI. base = body. md/lg/xl = sub/section heads.
     2xl–6xl = editorial display. */
  --fs-3xs:  0.75rem;    /* 12px — micro utility chips (e.g. inline clear pill) */
  --fs-2xs:  0.875rem;   /* 14px — metadata, mono tags, chips */
  --fs-sm:   0.9375rem;  /* 15px — helper text, form inputs, buttons, secondary UI */
  --fs-base: 1rem;       /* 16px — body */
  --fs-md:   1.125rem;   /* 18px — card titles */
  --fs-lg:   1.25rem;    /* 20px — sub-section */
  --fs-xl:   1.5rem;     /* 24px — pillar/card headings */
  --fs-2xl:  1.75rem;    /* 28px — section headings, modal titles */
  --fs-3xl:  2rem;       /* 32px */
  --fs-4xl:  2.25rem;    /* 36px — login title, stat-card values */
  --fs-5xl:  2.5rem;     /* 40px — page titles (display) */
  --fs-6xl:  3rem;       /* 48px — countdown, 404 (display+) */

  /* ── Weights — full range for Inter ── */
  --fw-normal:    400;
  --fw-medium:    500;
  --fw-semibold:  600;
  --fw-bold:      700;
  --fw-extrabold: 700;

  /* ── Shadows — quiet; the brand doesn't lift ── */
  --shadow-sm:  none;
  --shadow-md:  none;
  --shadow-lg:  none;
  --shadow-xl:  none;
  --shadow-focus-accent: 0 0 0 2px rgba(178, 105, 74, 0.22);
  --shadow-focus-danger: 0 0 0 2px rgba(180, 75, 59, 0.22);
  --shadow-drag:         0 14px 34px rgba(20, 32, 30, 0.18);

  /* ── Scrim (modal / tip overlay backdrop) ── */
  --scrim: rgba(20, 32, 30, 0.42);

  /* ── Transition timing ── */
  --transition-fast: 0.15s;
  --transition-base: 0.2s;
  --transition-slow: 0.6s;

  /* ── Easing curves ── */
  --ease-smooth:   cubic-bezier(0.22, 1, 0.36, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out-back: cubic-bezier(0.16, 1, 0.3, 1);

  /* ── Icon / dot sizes — consistent square footprints ── */
  --size-dot:     8px;
  --size-icon-xs: 14px;
  --size-icon-sm: 16px;
  --size-icon-md: 20px;
  --size-icon-lg: 24px;
  --size-icon-xl: 32px;

  /* ── Bar / rail thicknesses — distinct from --rule (color) ── */
  --rule-hairline: 2px;   /* thinnest accent / drop indicator */
  --rule-thin:     3px;   /* score-input / stat-card-accent / foundation */
  --rail:          4px;   /* pillar-section / things-section / toast left-rail */
  --bar-h-sm:      6px;   /* timeline progress */
  --bar-h:         8px;   /* score-bar / distribution */

  /* ── Table row min-height — shared cadence across every <table> so
     content-rich rows (Sleep daily log with inline score bars) read at
     the same vertical rhythm as text-only rows (Milestones events). ── */
  --table-row-h:   2.75rem;

  /* ── Container widths ── */
  --container-max: 980px;
  --login-max:     420px;
  --modal-max:     520px;
  --text-max:      68ch;

  /* ── Year badges — Still Water tints ── */
  --year-y0: var(--stone);    --year-y0-border: var(--rule);   --year-y0-glow: var(--paper);
  --year-y1: var(--moss);     --year-y1-border: var(--rule);   --year-y1-glow: var(--paper);
  --year-y2: var(--clay-deep);--year-y2-border: var(--rule);   --year-y2-glow: var(--accent-glow);

  /* ── Category palette — PRESERVED for Things module ── */
  --cat-teal:     #14B8A6; --cat-teal-name:    #0F766E;
  --cat-orange:   #F97316; --cat-orange-name:  #C2410C;
  --cat-purple:   #A855F7; --cat-purple-name:  #7E22CE;
  --cat-green:    #22C55E; --cat-green-name:   #15803D;
  --cat-amber:    #F59E0B; --cat-amber-name:   #B45309;
  --cat-red:      #EF4444; --cat-red-name:     #B91C1C;
  --cat-blue:     #3B82F6; --cat-blue-name:    #1D4ED8;
  --cat-pink:     #EC4899; --cat-pink-name:    #BE185D;
  --cat-yellow:   #EAB308; --cat-yellow-name:  #A16207;
  --cat-cyan:     #06B6D4; --cat-cyan-name:    #0E7490;
  --cat-lime:     #84CC16; --cat-lime-name:    #4D7C0F;
  --cat-indigo:   #6366F1; --cat-indigo-name:  #4338CA;
  --cat-brown:    #A8A29E; --cat-brown-name:   #57534E;
  --cat-magenta:  #D946EF; --cat-magenta-name: #A21CAF;
  --cat-coral:    #F43F5E; --cat-coral-name:   #BE123C;
}


/* ═══ 2 · Reset & base ════════════════════════════════════════════════════ */

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

html, body { margin: 0; padding: 0; }

html { font-size: 16px; }

/* Global anchor offset — every element targeted by `id` (e.g. #accordionSleep)
   keeps clearance under the 64px sticky nav, so deep links don't bury the
   title behind the chrome. Override per-element with a different value if a
   surface is taller.
   `scroll-padding-top` on the scroll container (html) covers the cases where
   a non-`:target` element is scrolled into view (in-flow layout shifts, JS
   `scrollIntoView()`, hash-less anchor clicks) — keeps the page-header
   from ducking under the sticky nav after accordion expand. See design-report #14. */
html { scroll-padding-top: 80px; }
:target { scroll-margin-top: 80px; }

body {
  font-family: var(--sans);
  font-weight: var(--fw-normal);
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ── Sticky footer ──
   Body is a flex column at viewport-min height; <main> grows to fill
   remaining space so the footer hugs the bottom on short pages instead
   of floating mid-screen. 100dvh tracks iOS Safari's dynamic toolbar
   (100vh overshoots when the address bar is visible); 100vh is the
   fallback for browsers that don't support dvh. */
body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
/* width:100% is required because <main class="page"> uses margin:0 auto for
   horizontal centering. In a flex column, auto cross-axis margins disable
   the default align-items:stretch, leaving the item at its content's
   intrinsic width. Setting width:100% restores the block-flow behavior:
   item fills cross axis, max-width caps it, auto margins center. */
body > main { flex: 1 0 auto; width: 100%; }

img, svg, canvas, video { max-width: 100%; display: block; }

img { background: var(--surface2); }

a { color: var(--clay-deep); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; transition: color var(--transition-fast); }
a:hover { color: var(--stone); }

button { font: inherit; color: inherit; cursor: pointer; }

input, textarea, select, button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

/* Headings — EB Garamond leads. Display weights are medium; italics carry */
/* the "te" voice ("Nor-te-fy"). Inter handles h5/h6 utility roles. */
h1, h2, h3, h4 {
  margin: 0 0 var(--space-3);
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  line-height: 1.15;
  color: var(--ink);
  letter-spacing: -0.015em;
}
h5, h6 {
  margin: 0 0 var(--space-3);
  font-family: var(--sans);
  font-weight: var(--fw-semibold);
  line-height: 1.3;
  color: var(--ink);
  letter-spacing: -0.005em;
}
h1 { font-size: var(--fs-5xl); letter-spacing: -0.02em; line-height: 1.2; }
h2 { font-size: var(--fs-2xl); letter-spacing: -0.018em; line-height: 1.3; }
h3 { font-size: var(--fs-lg); letter-spacing: -0.015em; line-height: 1.4; }
h4 { font-size: var(--fs-md); letter-spacing: -0.01em; line-height: 1.4; }
h5 { font-size: var(--fs-base); line-height: 1.4; }
h6 { font-size: var(--fs-sm); line-height: 1.4; text-transform: uppercase; letter-spacing: 0.08em; color: var(--moss); }

h1 em, h2 em, h3 em, h4 em { font-style: italic; color: var(--moss); font-weight: var(--fw-normal); }

p { margin: 0 0 var(--space-3); }

em, i { font-style: italic; }
strong, b { font-weight: var(--fw-bold); }

ul, ol { margin: 0 0 var(--space-3); padding-left: var(--space-5); }
li { margin-bottom: var(--space-1); }

/* ── Divider semantics — codified ──
   Two hairline tokens with distinct roles. Always pick by parent context,
   never by visual taste:

   --rule         (#CFD4CB, default <hr>)
     Use for:   page-header bottom, page-section bottom, modal borders,
                table th/td, .nav bottom, top-level card borders.
     In short: dividers between sibling top-level surfaces.

   --border-soft  (#DFE3DC, hr.section-divider)
     Use for:   section-header bottom borders inside a card (.pillar-section-header,
                .things-section-header, .year-cal-head, .pf-report-meta), inner
                row separators inside a list, sub-group dividers inside a card,
                AND .footer top — it terminates the page chrome (no sibling
                surface after it), so it can read quieter than .nav bottom.
     In short: dividers between children of a card. Plus the page-end seam.

   When in doubt: --rule lives on the outside of cards, --border-soft on the
   inside. ── */
hr { border: 0; border-top: 1px solid var(--rule); margin: var(--space-6) 0; }
hr.section-divider { border-top-color: var(--border-soft); }

::selection { background: rgba(178, 105, 74, 0.22); color: var(--ink); }


/* ═══ 3 · Layout primitives ═══════════════════════════════════════════════ */

.page, .wrap {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: var(--space-10) var(--space-5) var(--space-16);
}

/* Section-head utility — shared "header row that opens a section above its
   content" rule. One source of truth for padding-bottom + hairline. The five
   legacy section-head selectors (.page-header, .things-section-header,
   .pillar-section-header, .purpose-heading, .year-cal-head) all alias here.
   .page-header alone uses --rule per the divider contract (it's outside any
   card); the rest stay on --border-soft (inside-card hairlines). */
.section-head,
.page-header,
.things-section-header,
.pillar-section-header,
.purpose-heading,
.year-cal-head {
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-soft);
}
.page-header,
.things-section-header { padding-bottom: var(--space-5); }
.page-header { border-bottom-color: var(--rule); }

.page-header {
  margin-bottom: var(--space-8);
}
/* Page eyebrow — JetBrains Mono uppercase tag, the quiet archivist voice */
.page-eyebrow, .doc-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--moss);
  margin: 0 0 var(--space-3);
  font-weight: var(--fw-normal);
}
.page-title, .doc-title { font-family: var(--serif); font-weight: var(--fw-medium); font-size: var(--fs-5xl); line-height: 1.05; letter-spacing: -0.02em; color: var(--ink); margin: 0; display: flex; align-items: center; gap: var(--space-3); }
/* Italic discipline (audit §07): page / doc titles italicize exactly the second
   token via a single inline <em> ("My <em>Sleep</em>", "Design <em>Audit</em>").
   Auth screens are the one exception — they use a three-token italic phrase
   ("Welcome <em>back.</em>") for editorial voice. Don't add italic tokens
   anywhere else; don't italicize the leading word. */
.page-title em, .doc-title em { font-style: italic; color: var(--moss); font-weight: var(--fw-normal); }
/* ── Module glyph inside the page-title — pure SVG, no chip/circle.
   Sits as a flex sibling on the left of the title text (which is wrapped
   in .page-title-text so the title stays a single inline phrase). Mirrors
   the canonical iconography from branding.html § 03; uses currentColor
   (moss) for the stroke. The .page-title-icon-accent override paints
   clay where an icon's "now" dot belongs (Pitfall, Milestone). ── */
.page-title-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  color: var(--moss);
}
.page-title-icon-accent { fill: var(--clay); }
.page-title-text { min-width: 0; }
.page-subtitle, .doc-subtitle {
  color: var(--moss);
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-md);
  line-height: 1.5;
  margin: var(--space-3) 0 0;
  max-width: var(--text-max);
  font-weight: var(--fw-normal);
}

.page-section {
  margin-bottom: var(--space-8);
  padding: var(--space-6) var(--space-6);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
}
.page-section:last-child { margin-bottom: 0; }

/* Section labels — mono uppercase, the "§" voice of metadata */
.section-heading, .group-label, .form-label {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--moss);
  font-weight: var(--fw-semibold);
  margin: 0 0 var(--space-3);
  display: block;
}

.two-col, .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }


/* ═══ 4 · Navigation ══════════════════════════════════════════════════════ */

nav {
  border-bottom: 1px solid var(--rule);
  background: var(--bg-chrome);
  padding: var(--space-3) var(--space-5);
  position: sticky;
  top: 0;
  z-index: 40;
  backdrop-filter: saturate(1.2);
}
/* Constrained content row inside the nav. The horizontal gutter is supplied
   by the outer <nav> padding (var(--space-5)), matching .page's gutter so
   nav content and page content align at the same x-coordinate. No padding
   here — adding any would double the gutter on narrow viewports. */
.nav-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.nav-brand-group { display: flex; align-items: center; gap: var(--space-3); }
/* Nortefy lockup — Clay disk + Ink horizon SVG mark + Garamond wordmark.
   Only "te" is italic; the seam is the brand. See ref-docs/logo.html. */
.nav-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--ink);
  line-height: 1;
  transition: opacity var(--transition-fast);
}
.nav-brand:hover { color: var(--ink); opacity: 0.7; }
.nav-brand-wm {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-2xl);
  letter-spacing: -0.015em;
  line-height: 1;
}
.nav-brand-wm em { font-style: italic; }
.nortefy-mark { display: block; flex-shrink: 0; }
/* Year badge — mono monogram, quiet */
.nav-year {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--moss);
}
.nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Override the global `li { margin-bottom: 4px }` — that stray 4px extended the
   `.nav-links` flex line downward while the link content stayed at the top,
   pushing the menu text ~2px above the Nortefy brand instead of optically
   centering with it in the nav bar. */
.nav-links li { margin: 0; }
.nav-links a,
.nav-dropdown-toggle {
  text-decoration: none;
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: transparent;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: color var(--transition-fast), background var(--transition-fast);
}
.nav-links a:hover,
.nav-dropdown-toggle:hover { color: var(--stone); background: var(--paper); }
.nav-links a[aria-current="page"],
.nav-dropdown-menu a[aria-current="page"] {
  color: var(--clay-deep);
  font-weight: var(--fw-semibold);
}
/* Daily-log pill — Clay accent, the "begin today" CTA */
.nav-daily-log {
  background: var(--clay) !important;
  color: var(--oyster) !important;
  border: 1px solid var(--clay) !important;
  padding: var(--space-2) var(--space-4) !important;
  font-weight: var(--fw-semibold) !important;
}
.nav-daily-log:hover {
  background: var(--clay-deep) !important;
  border-color: var(--clay-deep) !important;
  color: var(--oyster) !important;
}
/* Pencil icon — sits to the right of "Log", sized to match text cap-height */
.nav-daily-log-icon { display: inline-block; flex-shrink: 0; }
/* Profile link — icon-only in desktop nav; stays sized to match nav link text */
/* Icon-only profile link — force the ink color (and keep it ink even on the profile page
   where `aria-current="page"` would otherwise switch it to clay-deep) so the glyph reads
   as the same color as the neighbouring text menu items instead of standing out as an accent */
.nav-profile-icon,
.nav-links a.nav-profile-icon[aria-current="page"] { color: var(--ink); }
.nav-profile-icon { display: inline-flex; align-items: center; gap: var(--space-1); }
/* Circle modifier — equal padding + 50% radius so the hover background renders as a perfect
   circle around the 14×14 icon instead of the pill shape inherited from `.nav-links a`. */
.nav-profile-icon--circle { padding: var(--space-2) !important; border-radius: 50% !important; }
/* Extra left margin on the profile li to separate it from the preceding nav cluster.
   Overridden to 0 inside the mobile media query where the row is already tight. */
#nav-profile-li { margin-left: var(--space-3); }

/* ── Reports dropdown — panel floats under the toggle; items inherit the
   first-level `.nav-links a` hover (Paper on Oyster) so they read as the
   same family of controls. Panel uses --oyster so the --paper hover is
   visible against it. */
.nav-dropdown { position: relative; }
.nav-dropdown-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 11rem;
  border: 1px solid var(--rule);
  background: var(--bg-chrome);
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 50;
  border-radius: var(--radius);
  /* Hidden state — kept in the DOM so opacity/transform can animate. The
     menu is faded out, lifted 4px, and removed from the a11y/hit-test tree
     until :hover or .is-open flips it on. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transform-origin: top right;
  pointer-events: none;
  transition:
    opacity 160ms ease-out,
    transform 160ms ease-out,
    visibility 0s linear 160ms;
}
/* Invisible hover bridge — fills the 6px gap between toggle and panel so
   the cursor can cross without the parent :hover dropping and closing the
   menu. Sits on top of the gap, same width as the panel. */
.nav-dropdown-menu::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -6px;
  height: 6px;
}
/* Open state — only `.is-open` flips visibility. The hover-intent timer
   in shell.js owns the open/close lifecycle (CSS `:hover` was removed so a
   diagonal cursor that briefly crosses the menu boundary doesn't snap the
   panel shut — see shell.js scheduleClose). */
.nav-dropdown.is-open .nav-dropdown-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
  /* On open, drop the visibility delay so the menu appears immediately and
     fades/slides in. */
  transition:
    opacity 160ms ease-out,
    transform 160ms ease-out,
    visibility 0s linear 0s;
}
/* Match the first-level pill shape + hover (color:stone, bg:paper) inherited
   from `.nav-links a:hover`. */
.nav-dropdown-menu a { padding: var(--space-2) var(--space-3); border-radius: var(--radius-pill); }

/* ═══════════════════════════════════════════════════════════════════════════
   Mobile nav — right-sliding drawer + 2-line asymmetric burger.
   Drawer is `position: fixed`, transform-driven, with a blurred backdrop,
   staggered item entrance and editorial counters. Visible only under 768px.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Burger — bare 2-line mark, Ink to match menu items; no pill, no border.
   Hover pulls the short top line to full width, aria-expanded flips the
   lines into an × ── */
.nav-burger {
  display: none;
  position: relative;
  width: 40px;
  height: 40px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  transition: color var(--transition-fast);
}
.nav-burger:hover { color: var(--stone); }
.nav-burger-line {
  position: absolute;
  left: 50%;
  display: block;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1),
              width 220ms ease,
              top 280ms cubic-bezier(0.32, 0.72, 0, 1);
}
/* Closed — short top line (right-aligned), full bottom line */
.nav-burger-line:first-child  { top: 14px; width: 14px; transform: translateX(-4px); }
.nav-burger-line:last-child   { top: 22px; width: 20px; transform: translateX(-50%); }
/* Hover — top line extends to match */
.nav-burger:hover .nav-burger-line:first-child { width: 20px; transform: translateX(-50%); }
/* Open — lines merge and rotate into × */
.nav-burger[aria-expanded="true"] .nav-burger-line:first-child {
  top: 18px; width: 20px; transform: translateX(-50%) rotate(45deg);
}
.nav-burger[aria-expanded="true"] .nav-burger-line:last-child {
  top: 18px; width: 20px; transform: translateX(-50%) rotate(-45deg);
}

/* ── Backdrop — dimmed Ink with gentle blur; fades with the drawer ── */
.nav-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(20, 32, 30, 0.42);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 360ms ease;
  z-index: 90;
}
.nav-backdrop[aria-hidden="false"] { opacity: 1; pointer-events: auto; }

/* ── Drawer — fixed right-edge panel; slides in via translateX ── */
.nav-drawer {
  display: none;
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(86vw, 340px);
  background: var(--bg-chrome);
  border-left: 1px solid var(--rule);
  padding: var(--space-4) var(--space-5) var(--space-6);
  flex-direction: column;
  gap: var(--space-4);
  transform: translateX(100%);
  transition: transform 420ms cubic-bezier(0.32, 0.72, 0, 1);
  z-index: 100;
  box-shadow: -24px 0 48px -22px rgba(20, 32, 30, 0.28);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.nav-drawer[aria-hidden="false"] { transform: translateX(0); }

/* ── Drawer header — brand mark + year, close × ── */
.nav-drawer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--rule);
}
.nav-drawer-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.nav-drawer-year {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--moss);
  letter-spacing: 0.2em;
}
.nav-drawer-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-1);
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--stone);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.nav-drawer-close:hover { background: var(--oyster); color: var(--deep); }

/* ── Drawer links — Garamond, generous leading ── */
.nav-drawer-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1 1 auto;  /* fill remaining drawer height so Profile can margin-top:auto to the bottom */
}
.nav-drawer-links li { margin: 0; }
.nav-drawer-links > li:not(.nav-drawer-group) > a {
  display: flex;
  align-items: center;
  padding: var(--space-2) var(--space-2);
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  text-decoration: none;
  letter-spacing: -0.01em;
  line-height: 1.2;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast), color var(--transition-fast);
}
/* Hover — quiet oyster fill; arrow slides in */
.nav-drawer-links > li:not(.nav-drawer-group) > a::after {
  content: "→";
  margin-left: auto;
  color: var(--clay);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.nav-drawer-links > li:not(.nav-drawer-group) > a:hover {
  background: var(--oyster);
  color: var(--stone);
}
.nav-drawer-links > li:not(.nav-drawer-group) > a:hover::after,
.nav-drawer-links > li:not(.nav-drawer-group) > a[aria-current="page"]::after {
  opacity: 1;
  transform: translateX(0);
}
.nav-drawer-links > li:not(.nav-drawer-group) > a[aria-current="page"] {
  color: var(--clay-deep);
}

/* ── Reports group — quiet mono eyebrow + indented list, no dropdown ── */
.nav-drawer-group {
  margin-top: var(--space-2);
  padding-top: var(--space-3);
}
.nav-drawer-group-label {
  display: block;
  padding: 0 var(--space-2) var(--space-2);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-normal);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--moss);
}
.nav-drawer-group > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.nav-drawer-group > ul > li > a {
  display: flex;
  align-items: center;
  padding: var(--space-2) var(--space-2);
  font-family: var(--serif);
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  color: var(--ink);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast), color var(--transition-fast);
}
/* Sliding arrow — mirrors the parent-list pattern but inherits the smaller submenu font-size */
.nav-drawer-group > ul > li > a::after {
  content: "→";
  margin-left: auto;
  color: var(--clay);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.nav-drawer-group > ul > li > a:hover { background: var(--oyster); color: var(--stone); }
.nav-drawer-group > ul > li > a[aria-current="page"] { color: var(--clay-deep); }
.nav-drawer-group > ul > li > a:hover::after,
.nav-drawer-group > ul > li > a[aria-current="page"]::after {
  opacity: 1;
  transform: translateX(0);
}

/* ── Log — Clay pill, anchored below the regular list with a little air ── */
.nav-drawer-links > li > a.nav-drawer-log {
  margin-top: var(--space-4);
  justify-content: center;
  padding: var(--space-3) var(--space-4);
  background: var(--clay);
  border: 1px solid var(--clay);
  border-radius: var(--radius-pill);
  color: var(--oyster);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
}
.nav-drawer-links > li > a.nav-drawer-log::after { display: none; }
.nav-drawer-links > li > a.nav-drawer-log:hover {
  background: var(--clay-deep);
  border-color: var(--clay-deep);
  color: var(--oyster);
}

/* ── Profile — pinned to the very bottom of the drawer; a quiet serif row
   separated by a hairline from the items above ── */
#nav-profile-mobile {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--rule);
}
.nav-drawer-links > li > a.nav-drawer-profile {
  gap: var(--space-2);
  font-size: var(--fs-md);
  color: var(--moss);
}
.nav-drawer-links > li > a.nav-drawer-profile:hover { color: var(--stone); }
/* No trailing arrow on Profile — it's a destination, not a CTA */
.nav-drawer-links > li > a.nav-drawer-profile::after { display: none; }

/* ── Staggered entrance — each item fades + slides in from the right ── */
.nav-drawer-links > li {
  opacity: 0;
  transform: translateX(14px);
  transition: opacity 380ms ease, transform 420ms cubic-bezier(0.32, 0.72, 0, 1);
}
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li { opacity: 1; transform: translateX(0); }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(1) { transition-delay: 120ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(2) { transition-delay: 160ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(3) { transition-delay: 200ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(4) { transition-delay: 240ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(5) { transition-delay: 280ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(6) { transition-delay: 320ms; }
.nav-drawer[aria-hidden="false"] .nav-drawer-links > li:nth-child(7) { transition-delay: 360ms; }

/* Body lock while drawer is open — prevents page scroll bleed-through */
body.no-scroll { overflow: hidden; }

/* ── Mobile activation — switch from inline .nav-links to the drawer ── */
@media (max-width: 768px) {
  .nav-year { display: none; }
  .nav-links { display: none; }
  .nav-burger { display: inline-block; }
  .nav-backdrop { display: block; }
  .nav-drawer { display: flex; }
}


/* ═══ 5 · Footer ══════════════════════════════════════════════════════════ */

/* Footer-top uses --border-soft (not --rule) so the page-end seam reads
   quieter than the nav-bottom rule above it — without that contrast both
   1px lines blurred into the body chrome. See the divider rule-book in
   section 2 for the full rationale. */
footer {
  border-top: 1px solid var(--border-soft);
  padding: var(--space-6) var(--space-5);
  margin-top: var(--space-16);
  font-size: var(--fs-2xs);
  color: var(--moss);
  background: var(--bg-chrome);
}
.footer-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--space-5);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.footer-left, .footer-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* Footer mark — symbol-only logo, static (non-link). See ref-docs/logo.html. */
.footer-mark {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}
.footer-sep { color: var(--rule); }
.footer-version, .footer-beta {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--moss);
}
.footer-link { color: var(--moss); text-decoration: none; }
.footer-link:hover { color: var(--clay-deep); }
.footer-status { display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--mono); font-size: var(--fs-2xs); letter-spacing: 0.1em; text-transform: uppercase; }
.footer-status-dot {
  width: var(--size-dot); height: var(--size-dot);
  border-radius: 50%;
  background: var(--success);
  display: inline-block;
}


/* ═══ 6 · Buttons ═════════════════════════════════════════════════════════ */

/* Buttons — pill-shaped, 1px stroke, no shadow, no lift. Secondary is the */
/* default. Primary (.btn-primary) fills Stone. Accent (Clay) is reserved. */
/* Button system — class-only opt-in. Every <button> in the markup carries
   one of these classes (audit'd 2026-04-26). The previous catch-all
   `button:not(...)...` element selector with a 24-class :not() chain is gone,
   along with the ~30 `!important` declarations that were only needed to beat
   its inflated specificity. Variants below (.btn-primary, .btn-danger,
   .btn-signout, .modal-btn-danger, .inline-add-btn) override via simple
   source-order at equal specificity. */
.btn,
.btn-primary,
.btn-submit-narrow,
.btn-danger,
.btn-signout,
.modal-btn,
.modal-btn-cancel,
.modal-btn-danger,
.inline-add-btn,
.tracker-desc-edit,
.tracker-name-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--btn-py-md) var(--btn-px-md);
  border: 1px solid var(--stone);
  background: transparent;
  color: var(--stone);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  font-style: normal;
  line-height: 1.2;
  border-radius: var(--radius-pill);
  text-decoration: none;
  cursor: pointer;
  transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
  letter-spacing: 0.005em;
}
/* Secondary hover — paper fill, full opacity. Only the truly-secondary
   variants opt in: .btn (the bare secondary), .modal-btn-cancel (the modal
   cancel side), and the inline-edit text inputs that look like buttons. The
   primary / danger / signout fills paint their own hover below.
   Hover convention: every variant shifts its FILL on hover and stays at
   opacity:1 — no half-transparent "I'm hovered" signal. */
.btn:hover:not(:disabled),
.modal-btn-cancel:hover:not(:disabled),
.tracker-desc-edit:hover:not(:disabled),
.tracker-name-edit:hover:not(:disabled) {
  background: var(--paper);
  opacity: 1;
}
/* Focus-visible — clay ring with offset, same pattern .info-toggle uses.
   Replaces the browser default outline (which the global reset partially
   suppresses) with a brand-consistent ring on every primary button surface. */
.btn:focus-visible,
.btn-primary:focus-visible,
.btn-submit-narrow:focus-visible,
.btn-danger:focus-visible,
.btn-signout:focus-visible,
.modal-btn:focus-visible,
.modal-btn-cancel:focus-visible,
.modal-btn-danger:focus-visible,
.inline-add-btn:focus-visible,
.tracker-desc-edit:focus-visible,
.tracker-name-edit:focus-visible {
  outline: 2px solid var(--accent-line);
  outline-offset: 2px;
}
.btn:disabled,
.btn-primary:disabled,
.btn-danger:disabled,
.btn-signout:disabled,
.modal-btn:disabled,
.inline-add-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Full-width primary modifier — explicit opt-in for auth pages where the
   primary CTA spans the column (single action per card). Replaces the old
   `.login-card .btn-primary` descendant override. */
.btn-primary--full { width: 100%; }

/* Primary — Stone fill, Oyster text; the grounding CTA. The `.btn-primary` class is the
   single shared primary-action style (Add / Save / Overwrite / New / Create / Change / Export).
   Note: .google-btn / .magic-btn are deliberately NOT primary — auth pages already host a
   .btn-primary "Sign in / Create" CTA, so OAuth + magic-link buttons render as secondary.
   See `.google-btn, .magic-btn` rule lower in the auth section. */
.btn-primary,
.inline-add-btn {
  background: var(--stone);
  color: var(--oyster);
  border-color: var(--stone);
}
.btn-primary:hover:not(:disabled),
.inline-add-btn:hover:not(:disabled) {
  background: var(--deep);
  border-color: var(--deep);
  color: var(--oyster);
  opacity: 1;
}

/* ── Size modifier ── Narrower padding + smaller font for tight form layouts. */
.btn-submit-narrow { padding: var(--btn-py-sm) var(--btn-px-sm); font-size: var(--fs-sm); }

/* Danger — the destructive CTA. `.btn-danger` is the shared filled variant (parallel to
   `.btn-primary`) used for irreversible actions like Delete account. `.modal-btn-danger` is
   the quieter outline variant reserved for modal confirm rows, where it sits next to a
   Cancel button and the fill appears only on hover. */
.btn-danger {
  background: var(--danger-strong);
  color: var(--oyster);
  border-color: var(--danger-strong);
}
.btn-danger:hover:not(:disabled) {
  background: var(--danger-deep);
  border-color: var(--danger-deep);
  color: var(--oyster);
  opacity: 1;
}
.modal-btn-danger {
  background: var(--danger-strong);
  color: var(--oyster);
  border: 1px solid var(--danger-strong);
}
.modal-btn-danger:hover {
  background: var(--danger-deep);
  border-color: var(--danger-deep);
  color: var(--oyster);
  opacity: 1;
}
/* Delete buttons — canonical class `.btn-del` (Method 4 of
   delete-patterns.html). Danger-filled circle with a centered oyster ×;
   pointer-hover snaps the fill to deep-danger to signal imminent removal.
   Provide context via `aria-label`. Pair with `.hover-reveal` when the
   button should stay invisible until its parent is hovered.
   `!important` on padding beats the reveal-state `padding: 0 var(--space-1)`
   from .hover-reveal — without it the button stretches into a pill instead
   of staying a true circle. */
.btn-del {
  color: var(--oyster);
  background: var(--danger);
  border: none;
  border-radius: 50%;
  width: 18px;
  aspect-ratio: 1;
  padding: 0 !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: var(--fs-2xs);
}
.btn-del:hover {
  background: var(--danger-deep);
}

/* ── Sign-out ── Moss fill, the "quiet voice" tone from the Still Water palette. Muted
   green-gray reads as a graceful departure: not a destructive action (reserved for
   `.btn-danger`) and not a primary CTA (reserved for `.btn-primary`). */
.btn-signout {
  background: var(--moss);
  color: var(--oyster);
  border-color: var(--moss);
}
.btn-signout:hover:not(:disabled) {
  background: var(--moss-deep);
  border-color: var(--moss-deep);
  color: var(--oyster);
  opacity: 1;
}

/* ── Compact "+" trigger ── Opens an inline-add-form; perfect circle, pushed to the far
   right of its flex parent. Source-order beats the base button rule above. */
.inline-add-btn {
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  font-size: var(--fs-base);
  line-height: 1;
  border-radius: 50%;
  aspect-ratio: 1 / 1;
  margin-left: auto;
}

/* Ghost / text buttons — transparent, no pill; for inline editorial actions.
   Nav dropdown toggles are intentionally excluded — their color/padding are managed by the
   `.nav-links a, .nav-dropdown-toggle` rule so "Reports" matches the other nav links. */
.tracker-history-toggle,
.update-accordion-trigger,
.info-toggle,
.cal-picker-arrow,
.cal-picker-btn {
  background: transparent;
  border: 1px solid transparent;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--stone);
}
.tracker-history-toggle:hover,
.update-accordion-trigger:hover,
.info-toggle:hover,
.cal-picker-btn:hover { background: var(--paper); color: var(--ink); }

/* ── Tip modal close — plain X in body text color ── */
.tip-modal-close {
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
}

/* Toggle buttons (yes/no/null triads) — Paper idle, Stone when pressed */
.toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--btn-py-xs) var(--btn-px-xs);
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--stone);
  border-radius: var(--radius-pill);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.toggle-btn:hover { background: var(--fog); color: var(--ink); }
.toggle-btn-active,
.toggle-btn[aria-pressed="true"],
.tracker-today-btn--active {
  background: var(--stone);
  color: var(--oyster);
  border-color: var(--stone);
  font-weight: var(--fw-semibold);
}
.toggle-group { display: inline-flex; gap: var(--space-1); }

/* ── Type picker (Things add-category modal) ──
   Segmented card selector for the Single / Group / Special category type.
   Label + italic-serif hint per card. Styles off [aria-pressed]; no active class. */
.type-picker {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}
.type-picker-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-3);
  background: var(--paper);
  color: var(--text-mid);
  font-family: var(--sans);
  text-align: left;
  cursor: pointer;
  border: 1.5px solid var(--border-soft);
  border-radius: var(--radius);
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.type-picker-btn:hover:not([aria-pressed="true"]) {
  color: var(--ink);
  background: var(--fog);
  border-color: var(--rule);
}
.type-picker-btn[aria-pressed="true"] {
  background: var(--fog);
  border-color: var(--stone);
  color: var(--ink);
}
.type-picker-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1.25;
}
.type-picker-hint {
  font-size: var(--fs-2xs);
  color: var(--text-dim);
  font-family: var(--serif);
  font-style: italic;
  line-height: 1.25;
}
/* Narrow screens — stack picker cards so long labels/hints don't truncate */
@media (max-width: 480px) {
  .type-picker { grid-template-columns: 1fr; }
}

/* `.tracker-today-btn` is a JS-marker class only — see section 17 (today-panel)
   for the visual styles. The renderers in update-pitfalls.js / update-momentum.js
   target this class to sync the `--active` state on the chosen action card. */

/* ── Pill chassis — single shape, three intents ──
   `.pill` is the canonical class. Markup uses `.pill` plus an optional
   intent modifier; the modifier never restyles the chassis, only adds
   structure or context-specific behaviour.
     • `.pill--mindset`       Purpose mindset slot — pure intent label, no
                              extra CSS; signals "this is a mindset value".
     • `.pill--anchor`        Purpose anchor slot — same: intent only.
     • `.pill--breaker`       Update Momentum rule-breaker pills — adds the
                              edit-mode overlay stage (position/isolation
                              + .item-actions inset). See section 17.
     • `.pill--breaker-chip`  Read-only momentum rule-breaker chip on the
                              Momentum report — no edit overlay.
   The audit flagged five class names sharing one shape; the chassis is
   now one rule, intents are modifiers. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  background: var(--paper);
  font-size: var(--fs-2xs);
  color: var(--stone);
  font-weight: var(--fw-medium);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
/* Pebble hover — when the pill wraps an .editable text span the whole
   pebble glows clay so the user reads "this is one click-to-edit chip"
   instead of seeing dotted underlines stacked inside chip borders. The
   inner .editable suppresses its own underline + pencil in this context
   (rule below). */
.pill:has(.editable):hover {
  border-color: var(--clay);
  background: var(--accent-glow);
}
/* Suppress per-text affordance when the .editable text lives inside a pill —
   the pebble itself carries the hover affordance and a dotted underline /
   pencil glyph inside the chip would compete with the chip border. */
.pill .editable { padding: 0; margin: 0; }
.pill .editable:hover { background: transparent; text-decoration-color: transparent; }
.pill .editable::after { display: none; }

/* Hover reveal (for delete buttons). Kills only border-width in the hidden
   state (not border-style/color) so per-class borders set elsewhere — e.g.
   the danger-tone circle on .things-del-btn et al. — survive into the revealed
   state, where border-width snaps back to 1px.
   Direct-parent only (`*:hover > .hover-reveal`): the previous grandchild
   variant (`*:hover > * > .hover-reveal`) caused every sibling's button to
   reveal at once, because :hover propagates up to the shared list container.
   All current consumers place the button as a direct child of the item
   element it belongs to, so direct-parent is sufficient. */
.hover-reveal {
  opacity: 0;
  transition: opacity var(--transition-fast);
  max-width: 0;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border-width: 0 !important;
}
*:hover > .hover-reveal { opacity: 1; max-width: 5rem; padding: 0 var(--space-1); border-width: 1px !important; }
/* ═══ 7 · Forms ═══════════════════════════════════════════════════════════
 *
 * Form layout primitives — pick one per form intent:
 *
 *   .form-group          One field, label-above-input. Default vertical
 *                        rhythm (margin-bottom: --space-4).
 *   .form-group-tight    Same, --space-2 bottom (rows that pair tightly).
 *   .form-group-lg       Same, --space-5 bottom (last-row breathing room).
 *
 *   .form-row            2+ form-groups side-by-side; flex with min-width
 *                        160px so they reflow to a single column on narrow
 *                        cards. Use whenever fields belong together (display
 *                        name + email, new password + confirm password).
 *
 *   .grid-2 / .two-col   Generic 2-col grid for non-form layouts (settings
 *                        panels, side-by-side cards). Collapses to 1-col
 *                        below 768px.
 *
 *   .inline-add-form-row Single-line inline form inside an .inline-add-form
 *                        container (Direction pillar add, Breakers).
 * ────────────────────────────────────────────────────────────────────────── */

.form-group,
.form-group-lg,
.form-group-tight {
  margin-bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.form-group-tight { margin-bottom: var(--space-2); }
.form-group-lg { margin-bottom: var(--space-5); }

.form-row,
.inline-add-form-row,
.anchor-form-row {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  flex-wrap: wrap;
}
.form-row > .form-group,
.form-row > * { flex: 1 1 160px; min-width: 0; }

/* Metadata-voice typography — shared by every "label above a value" surface.
   Mono uppercase 15px, looser 0.14em tracking, moss color. The layout
   specifics (margin, display, min-height) live in per-context rules below.
   `.form-label-primary` opts out and overrides to the sans semibold voice. */
.form-label,
.form-label-primary,
.stat-card-label,
.stat-card-label-streak {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  font-weight: var(--fw-normal);
}
/* Form-label layout — block with bottom gap so it pairs with an input. */
.form-label {
  margin-bottom: var(--space-2);
  display: block;
}
.form-label-primary {
  text-transform: none;
  letter-spacing: -0.005em;
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  margin-bottom: var(--space-2);
  display: block;
}
.form-label-hint {
  color: var(--text-dim);
  font-weight: var(--fw-normal);
  font-size: var(--fs-sm);
  margin-left: var(--space-2);
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
}
/* Required-field indicator — clay asterisk after the label whenever the
   .form-group contains a [required] input/textarea/select. Auto-applied via
   :has() so HTML doesn't need a parallel marker class. The opt-out modifier
   (.form-label--no-required) suppresses the asterisk on edge-case labels
   that wrap a required input but shouldn't read as "required" (e.g. a
   confirm-password field where the asterisk is redundant). */
.form-group:has(input[required]) > .form-label::after,
.form-group:has(textarea[required]) > .form-label::after,
.form-group:has(select[required]) > .form-label::after,
.form-row > .form-group:has(input[required]) > .form-label::after,
.form-row > .form-group:has(textarea[required]) > .form-label::after,
.form-row > .form-group:has(select[required]) > .form-label::after {
  content: " *";
  color: var(--clay);
  font-weight: var(--fw-medium);
}
.form-label--no-required::after { content: none; }
.field-hint {
  font-size: var(--fs-2xs);
  color: var(--moss);
  margin-top: var(--space-1);
  font-style: italic;
  font-family: var(--serif);
}

/* Inputs — the horizon, rendered as a form field.
   Idle border is dim (--border-soft). On hover it becomes Rule; on focus it
   shifts to Clay with a soft focus ring. Borders are always visible so the
   field is never an invisible hit-target. */
.form-input,
.form-input-flex,
.form-input-narrow,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="time"],
input[type="url"],
input[type="tel"],
input[type="search"],
textarea,
select {
  width: 100%;
  padding: 10px var(--space-3);
  border: 1px solid var(--border-soft);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  line-height: 1.5;
  border-radius: var(--radius);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
}
.form-input-narrow { max-width: 11.25rem; }
.form-input-flex { flex: 1 1 auto; }
textarea { min-height: 5.5rem; resize: vertical; font-family: var(--sans); }

/* Select — keep same chrome as input; the chevron is Moss, centred.
   The `[class]` qualifier raises specificity so our padding-right + chevron
   beat the padding/background shorthands on .form-input/.form-input-narrow.
   Without it, the chevron disappears and Chrome paints its native arrow. */
select, select[class] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: calc(var(--space-8) + 2px);
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 12px 12px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235a6960' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  cursor: pointer;
}
select:hover, select[class]:hover { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%232f554e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>"); }
select:focus, select[class]:focus { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238f5238' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>"); }
select option { font-family: var(--sans); background: var(--paper); color: var(--ink); }
/* Kill legacy IE/Edge arrow */
select::-ms-expand { display: none; }

input::placeholder, textarea::placeholder { color: var(--text-dim); font-style: italic; font-family: var(--serif); font-size: var(--fs-sm); }

input:hover:not(:focus):not(:disabled),
textarea:hover:not(:focus):not(:disabled),
select:hover:not(:focus):not(:disabled),
.form-input:hover:not(:focus):not(:disabled) {
  border-color: var(--rule);
}

input:focus, textarea:focus, select:focus,
.form-input:focus {
  outline: none;
  border-color: var(--clay);
  box-shadow: var(--shadow-focus-accent);
}

/* Validation error state — fires on inputs the user has typed in
   (`:not(:placeholder-shown)`) AND moved away from (`:not(:focus)`, so the
   error ring doesn't compete with the focus ring while still editing). The
   danger box-shadow mirrors the language of the focus-accent ring, just in
   --danger tone, so the error reads as "same control, different intent".
   `.form-input-error` is the explicit hook for JS-driven errors (e.g.
   "passwords don't match") that can't be expressed via :invalid alone. */
.form-input-error,
input.form-input-error,
textarea.form-input-error,
input:invalid:not(:placeholder-shown):not(:focus),
textarea:invalid:not(:placeholder-shown):not(:focus) {
  border-color: var(--danger);
  border-style: solid;
  box-shadow: var(--shadow-focus-danger);
}
/* Score input tint — quiet semantic accent on left border */
.form-input.score-input-good,
.score-input-good { border-left: var(--rule-thin) solid  var(--success); }
.form-input.score-input-warn,
.score-input-warn { border-left: var(--rule-thin) solid var(--warn); }
.form-input.score-input-bad,
.score-input-bad  { border-left: var(--rule-thin) solid var(--danger); }

.placeholder-na { color: var(--text-dim); font-style: italic; font-family: var(--serif); }


/* ═══ 8 · Cards — generic wireframe ═══════════════════════════════════════ */

/* `.card` is the canonical card wrapper: paper bg, 1px rule border, default
   radius, --card-pad inner spacing. New markup should use `.card` (with optional
   modifiers) instead of inventing per-page class names. The legacy classes
   below resolve to the same rule for back-compat — any new page-specific
   variation should be a modifier on `.card`, not a new selector here.

   Modifiers:
     .card--quiet      — softer border-soft outline (use inside another card)

   Token: --card-pad (16/20/24px) drives interior breathing room. Override per
   instance with --card-pad: var(--space-N). */
:root {
  --card-pad: var(--space-5); /* 20px default */
}
.card,
.hobby-card,
.mindset-anchors-card,
.direction-pillar,
.pillar-section,
.upcoming-item,
.tracker-section,
.tracker-card-header,
.recent-milestones-list > *,
.stat-card,
.insight-section,
.stat-card-featured-success,
.login-card,
.admin-section,
.missing-alert,
.legend-row {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--card-pad);
}
/* Modifiers — apply alongside .card to vary the wireframe */
.card--quiet         { border-color: var(--border-soft); }

/* Featured / danger card variants — semantic accent, no alarm */
.stat-card-featured-success { border-color: var(--success); border-left: var(--rail) solid var(--success); }
.stat-card-value-danger     { color: var(--danger-strong); }

/* `.stat-card` is the single canonical insight-card component — mono uppercase
   label above a serif value. Card chassis comes from section 8; this rule adds
   the inner layout. Cards in a row are equalized by their parent grid's
   default `align-items: stretch`; the `.stat-card--two-line` modifier exists
   for the rare case where one card legitimately wraps and other siblings need
   their value/label boxes pinned to a two-line height for baseline alignment. */
.stat-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 110px;
}
/* .stat-card--quiet — surface-quiet bg + softened border. Used by the home
   dashboard widgets where the parent .db-row already paints paper, so the
   inner stat surfaces need a quieter tint to read as distinct. */
.stat-card--quiet {
  background: var(--surface-quiet);
  border-color: var(--border-soft);
}
/* Stat-card label layout — typography lives in the shared metadata-voice
   rule (next to .form-label). Tighter line-height keeps wrapped labels
   readable without padding the box. */
.stat-card-label,
.stat-card-label-streak {
  line-height: 1.25;
}
/* Stat values — EB Garamond medium, the number carries the weight. */
.stat-card-value {
  font-family: var(--serif);
  font-size: var(--fs-4xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  line-height: 1.05;
  letter-spacing: -0.02em;
}
/* Two-line modifier — pins label and value boxes at two-line height so a
   card whose value wraps (e.g. Milestones "Next Up: <long event name>") shares
   the same .stat-card-sub baseline as its single-line siblings. Apply on the
   `.stat-card` element when the row contains a wrapping value. */
.stat-card--two-line .stat-card-label,
.stat-card--two-line .stat-card-label-streak { min-height: 2.5em; }
.stat-card--two-line .stat-card-value { min-height: 2.1em; }
/* Stat-value semantic tints — match the strong tones used by sleep-distribution
   bars, pitfall severity, and the bar-fill below the number. Apply on the
   .stat-card-value element to color-encode the stat. */
.stat-card-value-success { color: var(--success-strong); }
.stat-card-value-warn    { color: var(--warn-strong); }
.stat-card-value-danger  { color: var(--danger-strong); }
.stat-card-unit { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--moss); font-weight: var(--fw-normal); margin-left: var(--space-2); letter-spacing: 0.08em; text-transform: uppercase; }
/* Stat-card subtitle voice — italic serif under the value. */
.stat-card-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.5;
}
.stat-card-center { text-align: center; align-items: center; }
/* Left-rail accent. Default is clay; semantic modifiers paint the rail in
   success / warn / danger to encode the stat's status (used by Pitfalls'
   Behavioral Patterns row to colour-code clean-rate and weekday relapse). */
.stat-card-accent { border-left: var(--rail) solid var(--clay); }
.stat-card-accent.accent-good  { border-left-color: var(--success); }
.stat-card-accent.accent-warn  { border-left-color: var(--warn); }
.stat-card-accent.accent-bad   { border-left-color: var(--danger); }
.stat-card-accent.accent-stone { border-left-color: var(--stone); }

/* Section titles — EB Garamond, the reading voice. Canonical class is
   .section-title; modifiers .section-title--tight (smaller bottom margin)
   and .section-title--sm (smaller fs-xl variant for breakers/events headers). */
.section-title {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0 0 var(--space-4);
  line-height: 1.15;
}
.section-title--tight { margin: 0 0 var(--space-1); }
.section-title--sm {
  font-size: var(--fs-xl);
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0;
}

.insight-section-grid,
.insight-section-grid-split,
.insight-section-grid-stats,
.insight-section-grid-cards,
.insight-section-grid-4col {
  display: grid;
  gap: var(--space-4);
}
.insight-section-grid-split { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.insight-section-grid-stats { grid-template-columns: repeat(3, 1fr); }
.insight-section-grid-cards { grid-template-columns: repeat(2, 1fr); }
.insight-section-grid-4col  { grid-template-columns: repeat(4, 1fr); }

/* ═══ 9 · Tables ══════════════════════════════════════════════════════════ */

table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
th, td {
  text-align: left;
  padding: var(--space-3);
  border-bottom: 1px solid var(--rule);
}
/* Shared row cadence — `td { height }` behaves as a row min-height in CSS
   table layout, so a Sleep row with an inline score-bar reads at the same
   vertical rhythm as a text-only Milestones row. One token, every table. */
td { height: var(--table-row-h); }
th {
  font-family: var(--mono);
  font-weight: var(--fw-normal);
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  border-bottom-color: var(--border);
}
/* Sticky header — keeps column labels in view while a long table (Sleep
   daily log up to 31 rows) scrolls past. `top: 68px` clears the sticky nav
   bar (~67px tall, see section 4) so the column labels park just below it
   instead of being clipped behind it. Background is required so body rows
   don't ghost through the sticky cell. z-index lifts the header above any
   inline progress-bar fills painted in tbody cells, and above the table
   wrapper's background. */
thead th {
  position: sticky;
  top: 68px;
  background: var(--paper);
  z-index: 1;
}
/* Zebra striping — gentle 50%-opacity surface tint on even tbody rows so
   long, repetitive lists (daily log, event tracker) stay scannable.
   Hover still wins via the rule below. */
tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--surface) 50%, transparent); }
tr:hover td { background: var(--surface); }


/* ═══ 10 · Modals, toasts, tip overlays ═══════════════════════════════════ */

/* ── Shared slide-in keyframe ── */
@keyframes slideUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Modal chassis — single source for confirm dialogs AND tip overlays ──
   Two intents share one chassis to avoid the parallel-selector-tree
   duplication the design audit flagged:
     • Default `.modal`        → destructive / form / display-name dialogs
     • `.modal--tip` modifier  → info/help overlays opened by .info-toggle
   Legacy `.tip-modal*` selectors are co-listed below as compatibility
   aliases until every page migrates to the modifier; behaviour is
   identical so markup can be changed in any order. */
.modal-overlay,
.tip-modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: var(--space-4);
}
.modal-overlay-open,
.tip-modal-overlay-open { display: flex; }

.modal,
.tip-modal {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  max-width: var(--modal-max);
  width: 100%;
  max-height: 85vh;
  overflow: auto;
  display: flex;
  flex-direction: column;
  animation: slideUp var(--transition-base) var(--ease-out-back);
  /* Layered depth shadow against scrim — close drop + long ambient cast */
  box-shadow: 0 12px 28px -6px rgba(20, 32, 30, 0.28), 0 40px 90px -20px rgba(20, 32, 30, 0.40);
}
/* ── .modal--wide — promotes the modal to ~860px max for surfaces that hold
   a categorized grid of choices (e.g. the dashboard Personalize modal). The
   .modal chassis caps at --modal-max (520px); this modifier widens the cap
   without changing any other behaviour. ── */
.modal--wide { max-width: 860px; }
.modal-title,
.tip-modal-title {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0;
}
/* Bare `.modal-title` (no surrounding header) gets its own padded band so
   simple modals like #displayNameModal still read with the oyster slab. */
.modal > .modal-title {
  padding: var(--space-5) var(--space-6);
  border-bottom: 1px solid var(--rule);
  background-color: var(--modal-band);
}
/* Modal header — wraps icon + title side-by-side. Default = `gap` (used by
   confirm modals where the icon hugs the title). Tip variant overrides to
   `space-between` so the close button pins to the trailing edge. */
.modal-header,
.tip-modal-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  border-bottom: 1px solid var(--rule);
  background-color: var(--modal-band);
}
.tip-modal-header,
.modal--tip .modal-header { justify-content: space-between; }
.modal-header .modal-title,
.tip-modal-header .tip-modal-title { padding: 0; border-bottom: 0; background: transparent; }
/* Add-category modal — tinted title to match oyster header convention */
#addCatModal .modal-title { background-color: var(--modal-band); }
.modal-body,
.tip-modal-body { padding: var(--space-6); }
.modal-footer {
  padding: var(--space-5) var(--space-6);
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
  background: var(--modal-band);
}
/* Delete slot — pins a destructive action to the left edge of a footer that
   otherwise right-aligns Cancel / Save. Used by editTextModal when the caller
   passes `onDelete` (milestones row, pitfall habit, momentum instance) so
   "delete this thing" lives inside the same modal as the rename. */
.modal-btn-delete-slot { margin-right: auto; }
/* Modal icon — destructive-only marker (delete account, remove item, delete
   habit). Inline Phosphor glyph at modal-title size, danger-coloured. By
   convention the icon is reserved for confirm/destructive flows: every
   confirmModal() in utils.js renders one; non-destructive modals (display
   name, add category, edit text, info forms) intentionally omit it so the
   warning sign keeps its meaning. Don't sprinkle it on every modal. */
.modal-icon {
  font-size: var(--fs-2xl);
  line-height: 1;
  color: var(--danger);
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.modal-textarea, .modal-habit-name {
  width: 100%;
  margin-bottom: var(--space-3);
}
.modal-char-count {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.1em;
  color: var(--moss);
  text-align: right;
}
/* Counter states — clay tint near the cap, danger-strong over it. The
   over state pairs with a disabled Save button (set in JS). */
.modal-char-count--warn { color: var(--clay); }
.modal-char-count--over { color: var(--danger-strong); font-weight: var(--fw-medium); }
.modal-first-item-row { display: flex; gap: var(--space-2); }

/* ── Modal-scoped error slot ──
   Sits between .modal-body and .modal-footer. Hidden at rest; revealed by
   setModalError() in utils.js when a save fails so the error reads inside
   the modal — page toasts hide behind the modal backdrop and miss the user.
   Paired with setModalSaving() which keeps the buttons disabled while a
   save is in flight, so the error and the retry both belong to the modal. */
.modal-error {
  display: none;
  margin: 0 var(--space-6) var(--space-5);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--danger) 12%, var(--paper));
  color: var(--danger-strong);
  font-size: var(--fs-sm);
  line-height: 1.4;
}
.modal-error--show { display: block; }
.tip-modal-note {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
}
/* ── Info toggle — serif marginalia marker, clay-accent hover.
   Pinned inline with the page-eyebrow row (top of the page-header) so it
   sits visually adjacent to the title block. The previous `bottom:10px`
   anchor floated it 800px+ from the title on wide screens. The -1px top
   nudge vertically-centers the 26px circle on the 24px eyebrow line. */
.info-toggle {
  position: absolute;
  top: -1px;
  right: 0;
  width: 26px; height: 26px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--rule);
  border-radius: 50%;
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  background: var(--paper);
  color: var(--moss);
  letter-spacing: 0;
  line-height: 1;
  cursor: pointer;
}
.info-toggle:hover {
  color: var(--accent);
  border-color: var(--accent-line);
  background: var(--accent-glow);
}
.info-toggle:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.page-header { position: relative; }

/* ── Toast vs status-box — picking which feedback surface to use ──
   These two share the same colour logic but answer different intents:
     • `.page-toast`  → ephemeral system acknowledgment that a side-effect
       happened (Saved., Exported., Loading…). Bottom-right. Auto-dismisses
       (~1.8s). Drives `setSuccessAuto()`. Use after persisted actions.
     • `.status-box`  → persistent inline form context (validation errors
       the user must fix, blocking notices). Lives next to the form,
       sticks until cleared. Drives `setError()` for non-transient errors.
   Same call site → different surfaces: pages that prefer toast for save
   success override `window.setStatus` to route to the page-toast (see
   js/update.js, js/profile.js). */
/* Floating toast — bottom-right. Not shouty; earn the silence. */
.page-toast {
  position: fixed;
  bottom: var(--space-8);
  right: var(--space-8);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  z-index: 2000;
  display: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  box-shadow: 0 18px 40px -14px rgba(20, 32, 30, 0.22);
}
.page-toast--loading,
.page-toast--success,
.page-toast--error {
  display: block;
  animation: toast-slide-up var(--transition-base) ease-out both;
}
.page-toast--loading { border-left: var(--rule-thin) solid var(--moss); color: var(--stone); }
.page-toast--success { border-left: var(--rule-thin) solid var(--success); color: var(--success-strong); }
.page-toast--error   { border-left: var(--rule-thin) solid var(--danger); color: var(--danger-strong); }
/* ── Saving-toast spinner ── quieter oyster ring against paper background */
.page-toast--loading .spinner { border-top-color: var(--oyster); }

@keyframes toast-slide-up {
  from { opacity: 0; transform: translateY(calc(100% + 2rem)); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Inline status box — feedback below forms, same color logic as toasts */
.status-box {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--rule);
  border-left-width: var(--rule-thin);
  border-radius: var(--radius);
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--stone);
  margin: var(--space-3) 0;
  display: none;
  background: var(--paper);
}
.status-box--loading,
.status-box--success,
.status-box--error { display: block; }
.status-box--loading { border-left-color: var(--moss); color: var(--moss); }
.status-box--success { border-left-color: var(--success); color: var(--success); }
.status-box--error   { border-left-color: var(--danger); color: var(--danger); }

/* ── Urgency label ── Single hand-drawn highlighter swipe injected by milestones.js into the subtitle.
   One pseudo-element fills the full text height like a real chisel-tip marker pass.
     • Horizontal gradient tapers in/out at the start and end (lift-off of the pen).
     • Vertical gradient is denser at the top + bottom and paler in the middle — the "ink rim"
       that real highlighters leave as fluid pools along the edges of the swipe.
     • SVG mask gives the top + bottom slightly wavy, imperfect edges instead of a clean rectangle.
     • mix-blend-mode: multiply so the swipe darkens paper + text the way real ink does.
     • clip-path keyframe animates the stroke in left-to-right on first paint. */
.urgency-label {
  position: relative;
  display: inline-block;
  padding: 0 .22em;
  color: var(--ink);
  isolation: isolate;       /* contain the blend mode to this element */
}
.urgency-label::before {
  content: '';
  position: absolute;
  inset: 4% -.16em 0% -.12em;
  pointer-events: none;
  z-index: -1;
  /* Two stacked layers, blended into the pseudo before multiplying with the page:
     (1) horizontal taper to soften the start + end of the stroke,
     (2) vertical ink-rim profile — denser near top + bottom, paler in the middle. */
  background:
    linear-gradient(
      95deg,
      transparent 0%,
      color-mix(in srgb, var(--warn) 0%,  transparent) 2%,
      color-mix(in srgb, var(--warn) 38%, transparent) 7%,
      color-mix(in srgb, var(--warn) 38%, transparent) 93%,
      color-mix(in srgb, var(--warn) 0%,  transparent) 98%,
      transparent 100%
    ),
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--warn-strong) 34%, transparent) 0%,
      color-mix(in srgb, var(--warn) 32%, transparent) 22%,
      color-mix(in srgb, var(--warn) 22%, transparent) 50%,
      color-mix(in srgb, var(--warn) 32%, transparent) 78%,
      color-mix(in srgb, var(--warn-strong) 36%, transparent) 100%
    );
  background-blend-mode: multiply;
  mix-blend-mode: multiply;
  /* Wavy hand-drawn top + bottom edges via inline SVG mask (preserveAspectRatio=none stretches it). */
  -webkit-mask:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 40' preserveAspectRatio='none'><path d='M0,5 C20,2 45,7 70,4 C95,1 120,6 150,3 C175,1 190,5 200,4 L200,36 C185,38 160,34 135,37 C110,40 80,35 55,38 C30,40 10,36 0,37 Z' fill='black'/></svg>") center / 100% 100% no-repeat;
          mask:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 40' preserveAspectRatio='none'><path d='M0,5 C20,2 45,7 70,4 C95,1 120,6 150,3 C175,1 190,5 200,4 L200,36 C185,38 160,34 135,37 C110,40 80,35 55,38 C30,40 10,36 0,37 Z' fill='black'/></svg>") center / 100% 100% no-repeat;
  transform: rotate(-.5deg) skewX(-2deg);
  filter: blur(.25px);                                /* hint of paper-fiber bleed */
  transform-origin: left center;
  animation: highlight-stroke 1100ms cubic-bezier(.45,.05,.25,1) 350ms both;
}
/* Draw-on — clip from left to right so the marker appears to ink in across the phrase. */
@keyframes highlight-stroke {
  0%   { clip-path: inset(0 100% 0 0); opacity: 0; }
  10%  { opacity: 1; }
  100% { clip-path: inset(0 0 0 0);    opacity: 1; }
}
/* Reduced motion — keep the highlighter, skip the stroke-on animation. */
@media (prefers-reduced-motion: reduce) {
  .urgency-label::before { animation: none; }
}

.badge,
.badge-ok, .badge-warn, .badge-danger,
.when-badge,
.when-badge-always, .when-badge-indef,
.arrow-label {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--moss);
  background: var(--paper);
  font-weight: var(--fw-normal);
}

/* Semantic badges — colored for sleep status (GOOD / FAIR / BAD) */
.badge-ok     { background: var(--success-glow); color: var(--success-strong); border-color: var(--success-line); }
.badge-warn   { background: var(--warn-glow);    color: var(--warn-strong);    border-color: var(--warn); }
.badge-danger { background: var(--danger-glow);  color: var(--danger-strong);  border-color: var(--danger-line); }

/* When-badge column alignment — Direction items render the badge as the
   trailing column after a dotted leader (see section 12 .dir-item-leader).
   Without a min-width the column width follows content, so a row with
   `2026` and a row with `INDEFINITELY` had different left-edges and the
   badges no longer formed a tidy column. min-width here is calibrated to
   `INDEFINITELY` at 0.12em tracking; `justify-content: center` keeps the
   short values (`2026`, `ALWAYS`) centred inside that column. */
.when-badge,
.when-badge-always,
.when-badge-indef {
  min-width: 7.5em;
  justify-content: center;
}

/* Semantic text utilities — Still Water tones */
.text-success { color: var(--success-strong); }
.text-warn    { color: var(--warn-strong); }
.text-danger  { color: var(--danger-strong); }


/* ═══ 11 · Dashboard (index) ══════════════════════════════════════════════ */

/* ── Home header ── The dashboard adopts the page-eyebrow + page-title voice
   used on every module, with the user's name as the italic second token (e.g.
   "Welcome <em>Bruno</em>"). The home-header drops the bottom rule because
   the foundation hero below carries its own visual edge — a hairline above
   the clay-gradient card would fight with it. */
.home-header {
  border-bottom: 0;
  padding-bottom: 0;
}

/* ── Foundation card ──
   Inherits the standard .db-row chassis (border, radius, padding, header
   pattern with hairline divider) so the title reads as a peer of the
   other insight cards. Overrides the paper background with the clay-soft
   hero surface and tucks a sunrise scene into the body so the card still
   carries its "norte" mood — the editorial anchor the dashboard returns
   to. Composition mirrors purpose.html so the brand idiom stays
   consistent across surfaces. */
.db-foundation {
  margin-bottom: var(--space-8);
}

/* ── Inner column — centered purpose statement ── */
.db-foundation-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--space-6);
  max-width: 56ch;
  margin: 0 auto;
}
/* Purpose statement — display-scale serif quote, the card's centerpiece */
.db-foundation-text {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0;
  font-weight: var(--fw-normal);
}
.db-foundation-text em { font-style: italic; color: var(--moss); }


/* ── .dash-grid — the 4×2 insight grid container. Wraps the 8 default
   insight cards (4 module + 4 cross) plus the 3 opt-in purpose mini cards.
   Foundation hero stays separate above this grid; the grid auto-flows when
   purpose cards are toggled in. ── */
/* `align-items: start` — without it CSS Grid's default `stretch` made
   shorter cards (Sleep, Momentum, Pitfalls) inflate to match the tallest
   sibling (Milestones, which earns its height with the .upcoming-horizon
   viz). Cards now grow to their content only. */
.dash-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5);
  align-items: start;
}

/* Module row — the atomic insight "postcard". Inside each card a header
   eyebrow sits above two stacked stat blocks separated by a plain
   --border-soft hairline (per the divider rulebook in section 2). The
   card uses flex column so every card in a row stretches to match the
   tallest sibling. */
.db-row {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--space-6);
  background: var(--paper);
  display: flex;
  flex-direction: column;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}
.db-row:hover { border-color: var(--stone); }
.db-row:has(a:hover) { border-color: var(--stone); }
.db-row header { margin-bottom: var(--space-5); padding-bottom: var(--space-4); border-bottom: 1px solid var(--border-soft); }
.db-row-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--ink);
  font-weight: var(--fw-normal);
}
.db-row-header:hover { color: var(--stone); }
.db-row-header:hover .db-row-arrow { color: var(--clay-deep); transform: translateX(2px); }
.db-row-name {
  flex: 1;
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.01em;
}
.db-row-arrow { color: var(--moss); transition: transform var(--transition-fast), color var(--transition-fast); }

/* ── .db-stat — the stacked-stat block inside a .db-row. Two flavors:
   --primary  the editorial hero stat (big serif value, optional horizon)
   --secondary the footnote stat (compact, single-line value)
   Both share the same label/value/sub typography with size shifts. ── */
.db-stat { display: flex; flex-direction: column; gap: var(--space-2); }
.db-stat--primary { padding-bottom: var(--space-1); }
.db-stat--secondary {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding-top: var(--space-1);
}
.db-stat-label {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  font-weight: var(--fw-normal);
}
.db-stat-value {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  line-height: 1.05;
  letter-spacing: -0.02em;
}
/* Secondary value is compact — the footnote, not the headline. */
.db-stat--secondary .db-stat-value {
  font-size: var(--fs-xl);
  letter-spacing: -0.01em;
}
.db-stat-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.5;
  margin: 0;
}
/* Secondary sub sits inline next to the value as a quiet annotation. */
.db-stat--secondary .db-stat-sub {
  flex: 1;
  font-size: var(--fs-xs);
  text-align: right;
  color: var(--text-mid);
}
/* Semantic value tints — apply via .text-success / .text-warn / .text-danger
   utilities on the value element (handled by railStat in index.js). */

/* ── .db-stat-divider — plain hairline between primary and secondary
   stat blocks inside a card. Uses --border-soft per the divider rulebook
   in section 2 (inside-card hairlines stay quieter than outside-card
   ones). No mark, no decoration — the brand horizon language is reserved
   for "where am I along X" stat visualizations only. ── */
.db-stat-divider {
  margin: var(--space-4) 0;
  border-top: 1px solid var(--border-soft);
  height: 0;
}
/* Vertical variant — used when stat blocks sit side-by-side inside a
   wide card (.db-row-wide). Same hairline weight, rotated 90°. */
.db-stat-divider--v {
  margin: 0;
  border-top: 0;
  border-left: 1px solid var(--border-soft);
  width: 0;
  align-self: stretch;
}

/* ── .db-row-wide — a .db-row that spans the full width of the .dash-grid
   instead of one column. Used by Milestones so the 30-day horizon timeline
   has the full card width to breathe. The Next Up stat and the After That
   on-deck slot sit balanced side-by-side (primary | hairline | ondeck)
   above the full-width horizon. Mobile collapse is handled by the
   existing ≤768px breakpoint that flattens .dash-grid to a single
   column. ── */
.db-row-wide { grid-column: 1 / -1; }
.db-row-wide .db-stat-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--space-6);
  align-items: stretch;
  margin-bottom: var(--space-5);
}

/* ── .db-ondeck — compact "After That" preview of the milestone queued
   right behind "Next Up". Sits as a .db-stat sibling on the right of the
   wide Milestones card. The inner item is a 2-column grid: countdown
   stack | info stack — a miniature of the milestones.html .upcoming-item
   shape (countdown-block + upcoming-item-info), so the dashboard preview
   reads as a quiet kin of the full upcoming list, not a new pattern. ── */
.db-ondeck { gap: var(--space-3); }
.db-ondeck-item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4);
  align-items: baseline;
}

/* Countdown stack — serif num + mono uppercase label. Mirrors
   .countdown-block at --fs-3xl vs the full --fs-6xl on milestones.html. */
.db-ondeck-count {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}
.db-ondeck-num {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  line-height: 0.95;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.db-ondeck-num-label {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
}

/* State tints — match the milestones countdown urgency palette
   (today clay, ≤7d danger, ≤30d warn). The number carries the signal;
   the label stays muted moss across all states. */
.db-ondeck-item--today .db-ondeck-num { color: var(--clay); }
.db-ondeck-item--soon  .db-ondeck-num { color: var(--danger); }
.db-ondeck-item--near  .db-ondeck-num { color: var(--warn-strong); }

/* Info stack — serif name + mono uppercase date. Mirrors
   .upcoming-item-name + .upcoming-item-date at the smaller dashboard
   scale. Name truncates instead of wrapping into a third line that would
   unbalance the height against the count column. */
.db-ondeck-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.db-ondeck-name {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-lg);
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.db-ondeck-date {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
}

/* Empty state — quiet italic line shown when no milestone is queued
   behind the headline event. */
.db-ondeck-empty {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}

/* ── Wide Milestones card — mobile (≤768px) ── */
@media (max-width: 768px) {
  /* Collapse the side-by-side stat row to a stacked layout, swapping the
     vertical hairline for the standard horizontal one. */
  .db-row-wide .db-stat-row {
    grid-template-columns: 1fr;
    gap: var(--space-4);
  }
  .db-stat-divider--v {
    border-left: 0;
    border-top: 1px solid var(--border-soft);
    width: auto;
    height: 0;
  }
  /* Interleave the horizon bar between "Next Up" and "After That" so it
     sits directly under the event it actually counts down (Next Up).
     Without this, source order stacks horizon last and it visually
     attaches to "After That" — the wrong event. .db-stat-row flattens
     into the article's flex flow via `display: contents`; flex `order`
     drives the new sequence; article gap replaces the lost grid gap. */
  .db-row-milestones { gap: var(--space-4); }
  .db-row-milestones header { margin-bottom: 0; }
  .db-row-milestones .db-stat-row { display: contents; }
  .db-row-milestones .db-stat--primary    { order: 1; }
  .db-row-milestones .upcoming-horizon--dash { order: 2; }
  .db-row-milestones .db-stat-divider     { order: 3; }
  .db-row-milestones .db-ondeck           { order: 4; }
}

/* ── .db-stat-accent-{good,warn,bad} — left rail painted by railStat() in
   index.js to encode primary-stat status. Only applied to .db-stat--primary
   (secondary stats stay un-railed; the text-tint utility alone carries the
   signal there). Colours mirror the .stat-card-accent system in section 8. ── */
.db-stat-accent-good,
.db-stat-accent-warn,
.db-stat-accent-bad {
  border-left: var(--rule-thin) solid var(--clay);
  padding-left: var(--space-3);
}
.db-stat-accent-good { border-left-color: var(--success); }
.db-stat-accent-warn { border-left-color: var(--warn); }
.db-stat-accent-bad  { border-left-color: var(--danger); }

/* ── Cross-module "special insight" cards — same .db-row chassis but the
   header pairs two module names with a multiplication mark and adds an
   eyebrow tag + one-line description. No per-module link/arrow because
   the card crosses two modules and doesn't belong to either. ── */
.db-row-cross-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.db-row-cross-name {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.db-row-cross-x {
  color: var(--clay);
  font-style: italic;
  margin: 0 var(--space-1);
}
.db-row-cross-tag {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--clay-deep);
}
.db-row-cross-sub {
  margin: var(--space-2) 0 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--text-mid);
  line-height: 1.5;
}

.score-bar,
.score-bar-dist,
.score-bar-log {
  position: relative;
  height: var(--bar-h);
  background: var(--fog);
  border: 0;
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.score-bar-fill {
  height: 100%;
  width: var(--bar-w, 100%);
  background: var(--bar-c, var(--stone));
  border-radius: inherit;
  transition: width var(--transition-slow) var(--ease-smooth);
}
.score-inline { display: inline-flex; align-items: center; gap: var(--space-3); min-width: 8.75rem; }
.score-inline .score-bar { flex: 1; }
.score-inline-num { font-weight: var(--fw-medium); color: var(--stone); font-family: var(--mono); }

/* ── Monthly bar chart (pitfalls report) ── */
/* Editorial 12-month chart: bars scale to max(observed, threshold) so the  */
/* dashed clay threshold line is always visible. The hue gradient on the    */
/* fill (--pf-hue, set inline per bar) maps break count to severity:        */
/* 140° green at zero, 0° red at/above the threshold, linear in between.    */
/* Fixed 70% saturation + 48% lightness keep every bar equally vivid so     */
/* severity reads by color independent of neighbors.                        */
.pf-report {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
}
/* Header — eyebrow on left, threshold legend on right */
.pf-report-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-soft);
}
.pf-report-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-mid);
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.pf-report-threshold {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-mid);
}
/* Tiny dashed swatch in the legend so the line in the chart reads as the   */
/* same visual element as the legend label.                                 */
.pf-report-threshold-swatch {
  display: inline-block;
  width: 18px;
  border-top: 1px dashed var(--clay);
}
/* Outer chart wrapper — provides padding-top breathing room so a count for */
/* a 100%-tall bar can render above without colliding with the meta header. */
.pf-report-chart {
  padding-top: var(--space-5);
}
/* Inner chart — 12 equal columns, fixed height, position: relative anchors */
/* the absolutely-positioned threshold line and baseline rule. The bars and */
/* threshold line share this coordinate system (no padding) so a bar at the */
/* threshold count aligns exactly with the dashed line.                     */
.pf-report-chart-inner {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-2);
  align-items: end;
  height: 11rem;
}
/* Baseline — solid hairline at the bottom of the chart area; sits below    */
/* the bars at exactly the same level for every column.                     */
.pf-report-chart-inner::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  border-top: 1px solid var(--border);
}
/* Threshold line — dashed clay rule placed at (bad / scale) * 100% from    */
/* bottom so it always sits within the chart, regardless of the year max.   */
.pf-report-threshold-line {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px dashed var(--clay);
  pointer-events: none;
  z-index: 1;
}
/* Per-column wrapper — full chart height; bar grows from the baseline,     */
/* count floats just above the bar via absolute positioning so it tracks    */
/* the bar height instead of pinning at the top of the column.              */
.pf-month {
  position: relative;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-width: 0;
}
.pf-month-fill {
  width: 65%;
  max-width: 1.75rem;
  height: var(--bar-h, 0%);
  min-height: 1px;
  background: hsl(var(--pf-hue, 140), 70%, 48%);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition: height var(--transition-slow) var(--ease-standard), background-color 0.3s ease, filter var(--transition-fast);
}
/* Count — mono numeral, sits just above the bar's top edge. Uses           */
/* bottom: var(--bar-h) so it tracks the bar height; the small +4px gap is  */
/* the visual breathing room between count baseline and bar top.            */
.pf-month-count {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--bar-h, 0%);
  margin-bottom: 4px;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  color: var(--text);
  pointer-events: none;
  white-space: nowrap;
}
/* Empty / future states — dim count, hide the fill entirely so the 1px     */
/* min-height doesn't draw a misleading green tick at the baseline. Future  */
/* is slightly more dimmed than empty ("not yet" vs. "no data"). A logged   */
/* zero-break month still shows the 1px green tick — a small "clean" pip.   */
.pf-month--empty  .pf-month-count { color: var(--text-dim); }
.pf-month--future .pf-month-count { color: var(--text-dim); opacity: 0.4; }
.pf-month--empty  .pf-month-fill,
.pf-month--future .pf-month-fill { display: none; }
/* Hover — slight darken on the bar to confirm interactivity (no tooltip    */
/* yet, but the fill responds so the chart feels alive when scanned).       */
.pf-month:hover .pf-month-fill { filter: brightness(0.9); }

/* Labels row — sits below the chart in its own grid, aligned to the same   */
/* 12 columns so each label is centered under its bar.                      */
.pf-report-labels {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.pf-month-label {
  text-align: center;
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-mid);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.pf-month-label--current {
  color: var(--ink);
  font-weight: var(--fw-bold);
}
/* Current-month dot accent below the label — small clay pip aligns the eye */
/* to "you are here" without recoloring the whole label.                    */
.pf-month-label--current::after {
  content: '';
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--clay);
  margin: 4px auto 0;
}
.pf-month-label--empty,
.pf-month-label--future { color: var(--text-dim); opacity: 0.55; }

/* ── Home personalize CTA — pinned to the top-right of .home-header.
   A tiny clay-sparkle medallion: 28px paper coin with a Phosphor sparkle
   inside, surrounded by a soft clay halo that blooms on hover (radial
   gradient mirrors the dbf-halo used in the foundation hero, so the
   control reads as a small piece of the dawn scene rather than chrome).
   The sparkle gives a slow editorial twinkle every ~6s to draw a glance,
   without being noisy. Single icon — no label needed; aria handles a11y. ── */
.home-header { position: relative; }
.home-personalize-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--paper);
  color: var(--clay);
  cursor: pointer;
  transition: color var(--transition-fast),
              border-color var(--transition-fast),
              transform var(--transition-fast);
}
.home-personalize-btn:hover {
  color: var(--clay-deep);
  border-color: var(--clay);
  transform: scale(1.06);
}
.home-personalize-btn:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 3px; }
/* Sparkle glyph — sits above the halo (z-index) so the bloom never washes
   it out. Slow twinkle keyframe pulses the icon barely-perceptibly, hinting
   at "personalize" without pestering. */
.home-personalize-icon {
  position: relative;
  z-index: 1;
  font-size: 14px;
  line-height: 1;
  animation: home-personalize-twinkle 6s ease-in-out infinite;
}
.home-personalize-btn:hover .home-personalize-icon { animation: none; transform: rotate(15deg); transition: transform var(--transition-fast); }
/* Clay halo — radial bloom behind the coin. Sits at 0 opacity, blooms on
   hover. Echoes the `dbf-halo` radial gradient on the foundation hero. */
.home-personalize-halo {
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  background: radial-gradient(circle at center,
                              rgba(178, 105, 74, 0.22) 0%,
                              rgba(178, 105, 74, 0.06) 45%,
                              rgba(178, 105, 74, 0)   75%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
}
.home-personalize-btn:hover .home-personalize-halo { opacity: 1; }
@keyframes home-personalize-twinkle {
  0%, 88%, 100% { opacity: 1;   transform: scale(1);    }
  92%           { opacity: 0.6; transform: scale(0.92); }
  96%           { opacity: 1;   transform: scale(1.08); }
}

/* ── Personalize modal — grouped grid of card previews. Each .personalize-card
   row is a label that wraps a glyph + meta + .switch; clicks anywhere on the
   row bubble to the JS handler which flips the matching dashboard card.
   The modal uses the .modal--wide modifier (section 10). ── */
/* Stack the title row above the subtitle, both inside the modal-header band.
   The inner row keeps the h2 vertically centered with the close button. */
.personalize-header {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}
.personalize-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.personalize-subtitle {
  margin: var(--space-2) 0 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--text-mid);
  line-height: 1.5;
}
/* ── Body — three vertical groups, each with its own header + grid. The
   between-group gap matches `.page-section` margin-bottom (--space-8) so
   the modal inherits the rest of the app's inter-section rhythm. The
   inside-group gap (header → grid) is --space-4 — same cadence as
   `.page-section` titles relative to their content. ── */
.personalize-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
.personalize-group { display: flex; flex-direction: column; gap: var(--space-4); }
.personalize-group-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-soft);
  flex-wrap: wrap;
}
.personalize-group-tag {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--moss);
  font-weight: var(--fw-medium);
}
.personalize-group-tag--accent { color: var(--clay-deep); }
.personalize-group-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--text-mid);
}

/* Grid of card previews — two columns on desktop, single column on mobile
   (handled by section 22's responsive collapse where applicable). */
.personalize-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}

/* Individual card preview — flex row: glyph · meta · switch. The label
   wrapper makes the entire row clickable while the inner <button class="switch">
   handles the actual aria-pressed state. */
.personalize-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.personalize-card:hover { border-color: var(--stone); background: var(--surface-quiet); }
.personalize-card:has(.switch[aria-pressed="false"]) { opacity: 0.62; }
.personalize-card:has(.switch[aria-pressed="false"]):hover { opacity: 1; }
/* Locked row — the underlying module is disabled so toggling here is a
   no-op. Strikethrough title + dim copy + neutralized hover read as
   "out of scope" rather than "off by choice". */
.personalize-card-locked {
  opacity: 0.45;
  cursor: not-allowed;
}
.personalize-card-locked:hover { border-color: var(--rule); background: var(--paper); }
.personalize-card-locked .personalize-card-title { text-decoration: line-through; }

/* Module glyph — pure SVG icon from branding § 03, no chip/circle. The
   inline-flex centering keeps the glyph aligned with the title row when
   icons drop below the cap-height; moss stroke + clay accent dot match
   the canonical iconography. The cross variant lays out two icons with
   the × multiplier between them. */
.personalize-card-glyph {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--moss);
  line-height: 1;
}
.personalize-card-glyph svg { width: 100%; height: 100%; }
.personalize-card-glyph-accent { fill: var(--clay); }
.personalize-card-glyph--cross {
  width: auto;
  min-width: 56px;
  gap: var(--space-1);
}
.personalize-card-glyph--cross svg { width: 24px; height: 24px; }
/* Weekday isn't a canonical Nortefy module — no branded glyph exists, so
   we lean on Phosphor's calendar mark and size it to match the SVGs in
   the cross variant. */
.personalize-card-glyph-ph { font-size: 22px; line-height: 1; color: var(--moss); }
.personalize-card-x {
  margin: 0 var(--space-1);
  color: var(--clay);
  font-style: italic;
}
.personalize-card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.personalize-card-title {
  font-family: var(--serif);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--ink);
  letter-spacing: -0.005em;
}
.personalize-card-desc {
  font-family: var(--sans);
  font-size: var(--fs-2xs);
  color: var(--text-mid);
  line-height: 1.4;
}
.personalize-switch { flex-shrink: 0; }


/* ── Dashboard Journal card — editorial wide row at the top of dash-grid.
   Three pinned sentences render as serif-italic lines with a small mono
   category eyebrow above each. Mirrors the page-eyebrow + page-title
   voice used throughout the app, scaled down to fit alongside the other
   .db-row cards. ── */
.db-journal-sentences {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  align-items: start;
}
.db-journal-sentence {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-left: var(--space-4);
  border-left: 2px solid var(--clay);
  min-width: 0;
}
.db-journal-sentence-cat {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--clay-deep);
  font-weight: var(--fw-medium);
}
.db-journal-sentence-text {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-md);
  line-height: 1.45;
  color: var(--ink);
  letter-spacing: -0.005em;
  overflow-wrap: break-word;
}
.db-journal-sentence-text em { color: var(--clay-deep); font-style: italic; }
/* Empty-slot variant — dimmed copy + dashed rail so it reads as "waiting
   to be filled" rather than a real reflection. */
.db-journal-sentence--empty {
  border-left-style: dashed;
  border-left-color: var(--rule);
}
.db-journal-sentence--empty .db-journal-sentence-text { color: var(--text-dim); }
/* Single-line all-empty state — span the full grid width so the prompt
   reads as one editorial line instead of one cramped column. */
.db-journal-sentences:has(> :only-child) {
  grid-template-columns: 1fr;
}


/* ═══ 12 · Pillar grids (purpose / direction) ═════════════════════════════ */

.pillars-grid,
.hobby-grid,
.mindset-anchors-grid,
.pf-habits-grid,
.pillar-section-items {
  display: grid;
  gap: var(--space-5);
}
/* `align-items: start` keeps each pillar at its natural height — without it
   CSS Grid's default `stretch` made the sibling pillar in the same row
   inflate to match a card with its add-form open, breaking horizontal
   alignment across the 4 pillars (audit §13). Cards now grow individually
   and the row's other pillar stays put. */
.pillars-grid { grid-template-columns: repeat(2, 1fr); align-items: start; }
.hobby-grid   { grid-template-columns: repeat(3, 1fr); }
.mindset-anchors-grid { grid-template-columns: repeat(2, 1fr); }
.pf-habits-grid { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

/* ── Icon system —
   Every glyph in the app renders as a Phosphor (bold) font-icon:
     <i class="ph-bold ph-target"></i>
   shell.js loads `@phosphor-icons/web@2/src/bold/style.css` once at boot.
   The icon inherits color + font-size from its wrapper, so the context
   chip below drives all visual sizing. To change the glyph for a slot,
   only change the `ph-{name}` class — no SVG, no asset pipeline.
   Wrappers below give every icon a consistent 32×32 chip with a 1px rule
   border and clay-soft fill. ── */
.pillar-icon,
.hobby-card-icon,
.mindset-anchors-icon,
.purpose-matrix-icon,
.things-section-icon {
  width: var(--size-icon-xl); height: var(--size-icon-xl);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--surface-quiet);
  color: var(--stone);
  /* Phosphor icons render via a font-glyph :before pseudo; font-size on the
     wrapper drives the inner icon's pixel size. fs-md keeps the bold glyph
     legible inside the 32px chip without crowding the border. */
  font-size: var(--fs-md);
  line-height: 1;
  flex-shrink: 0;
}

/* Direction pillar — editorial card with Stone left accent */
.pillar-section {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: var(--rail) solid var(--stone);
  border-radius: var(--radius);
  padding: var(--space-6);
  transition: border-color var(--transition-fast);
}
.pillar-section:hover { border-color: var(--stone); }
.pillar-section-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pillar-section-header > div { flex: 1; min-width: 0; }
.pillar-section-name {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.01em;
  color: var(--ink);
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* Pillar item — identical footprint to .things-section-item */
.pillar-section-items {
  list-style: none;
  padding: 0;
  margin: 0;
  gap: var(--space-2);
}
.pillar-section-item {
  /* Row acts as the stage for the hover edit-mode overlay. position: relative
     anchors .item-actions (inset: 0) and isolation: isolate keeps the overlay's
     stacking context scoped to the row so it never bleeds into siblings. */
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  padding: var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-quiet);
  font-size: var(--fs-sm);
  color: var(--ink);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.pillar-section-item:hover,
.pillar-section-item.is-actions-open { border-color: var(--rule); background: var(--paper); }

/* ── Leading icon — wraps the Phosphor when-value glyph returned by itemIcon() ── */
.dir-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-right: var(--space-2);
  font-size: var(--fs-md);
  line-height: 1;
  color: var(--moss);
}
/* Dotted leader between text and when-badge. The min-width guarantees a
   visible leader even when .dir-item-text wraps to multi-line and would
   otherwise crush the leader's flex remainder to a sub-pixel sliver. On
   the narrowest widths where there's truly no room, the leader hides in
   place via the media query in section 22 — never partial.
   .dir-item-leader-wrap holds the SVG line + a centered Phosphor arrow
   that interrupts the line as a directional cue (text → pill). The arrow
   carries an inset background that matches the row surface so the dotted
   line appears to terminate cleanly on either side. */
.dir-item-leader-wrap {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  margin: 0 var(--space-2);
  min-width: 1.5rem;
}
.dir-item-leader {
  flex: 1;
  height: 8px;
  color: var(--rule);
  pointer-events: none;
}
.dir-item-arrow {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  color: var(--stone);
  font-size: var(--fs-sm);
  line-height: 1;
  background: var(--surface-quiet);
  padding: 0 var(--space-1);
  pointer-events: none;
}
/* Match the arrow's cut-through bg to the row's active surface (hover or
   menu-open) so the dotted leader keeps reading as terminated cleanly. */
.pillar-section-item:hover .dir-item-arrow,
.pillar-section-item.is-actions-open .dir-item-arrow { background: var(--paper); }
.dir-item-text { flex: 0 1 auto; }
.dir-item-when { font-family: var(--mono); font-size: var(--fs-2xs); letter-spacing: 0.1em; color: var(--moss); text-transform: uppercase; }

/* ── Note indicator (premium) ──
   Tiny clay note-pencil glyph that sits between the item text and the
   leader line when an item has a non-empty observation. Renders at rest
   (not just on hover) so the user can tell at a glance which items carry
   a note. Premium-only — emitted by noteIndicatorHtml() in direction.js. */
.dir-item-note-icon {
  flex-shrink: 0;
  margin-left: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--clay);
  line-height: 1;
}

/* ── Menu trigger ──
   Small ghost-glyph that opens the row's action overlay (drag / edit /
   delete) on click. Replaces the legacy hover-to-reveal so the affordance
   is discoverable on touch and deliberate on desktop. Sits as a sibling
   of .item-content at the far right of the row (row chrome, not content)
   so it never competes visually with the text + leader + when-badge.
   Click handling lives in direction.js — toggles the row's
   .is-actions-open class; click-outside / Esc / re-tap dismiss. */
.item-menu {
  flex-shrink: 0;
  margin-left: var(--space-2);
  padding: 2px var(--space-1);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: var(--fs-sm);
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.item-menu:hover {
  color: var(--text-mid);
  background: var(--surface2);
}
.item-menu:focus-visible {
  outline: 2px solid var(--clay);
  outline-offset: 2px;
}
/* When the actions overlay is open, fade the trigger so it doesn't bleed
   through the overlay surface. The overlay sits above (z-index: 1) and
   covers the trigger anyway, but matching the visual state keeps the row
   coherent during the transition. */
.pillar-section-item.is-actions-open .item-menu,
.things-section-item.is-actions-open .item-menu {
  opacity: 0;
  pointer-events: none;
}

/* ── Custom pillar (premium) — shares the same stone rail as default
   pillars so all boxes look uniform. ── */

/* ── Icon picker (premium add-pillar modal) ──
   Phosphor icons in a 6-up grid; selection tracked by aria-pressed so the
   styling and screen-reader announcement stay in sync. */
.icon-pick-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.icon-pick-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface-quiet);
  color: var(--moss);
  font-size: var(--fs-lg);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
.icon-pick-btn:hover { border-color: var(--clay); color: var(--ink); }
.icon-pick-btn[aria-pressed="true"] {
  border-color: var(--clay);
  background: var(--paper);
  color: var(--clay);
}

/* Optional hint span next to a form-label (e.g. "(optional)") — quieter
   typeface than the label itself so it reads as a secondary cue. */
.form-label-hint {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-normal);
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--moss);
  margin-left: var(--space-2);
}

/* ── Profile premium section ──
   Mounted between Account and Module Visibility. Free state surfaces perks
   + a manual upgrade CTA (replaced by Stripe Checkout when integrated);
   active state shows plan + renewal date + a cancel CTA (will redirect to
   the Stripe Billing Portal when integrated). The clay accent rail signals
   the section's special status without competing with the danger-toned
   .btn-danger inside Session & Account. */
.premium-section { border-left: var(--rail) solid var(--clay); }
.premium-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.premium-header .section-title { margin: 0; }
.premium-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--clay);
  background: var(--clay-soft, var(--surface-quiet));
  color: var(--clay);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.premium-perks {
  list-style: none;
  margin: var(--space-3) 0 var(--space-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.premium-perks li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.premium-perks li i.ph-bold { color: var(--clay); font-size: var(--fs-md); flex-shrink: 0; }
.premium-perks-soon { color: var(--moss); font-style: italic; }
.premium-perks-soon i.ph-bold { color: var(--moss); }

/* ── Purpose Hero — the page's "norte" anchor ──
   Inherits the standard .page-section chassis (paper bg, rule border, base
   padding); the decorative still-water SVG is suppressed so the eyebrow +
   foundation text read as plain copy on the page surface. */
.purpose-hero-scene {
  display: none;
}
.purpose-hero-svg {
  width: 100%;
  height: 100%;
  display: block;
  /* One-time entrance — scene fades in as the page reveals */
  opacity: 0;
  animation: dbf-scene-in 1.6s ease-out 0.1s forwards;
}
/* Sun — clay disc tangent to the horizon; shares dbf-* keyframes (section 11) */
.purpose-hero-sun {
  transform-origin: 990px 244px;
  transform-box: fill-box;
  animation: dbf-sun-rise 2.4s cubic-bezier(0.22, 0.61, 0.36, 1) 0.2s both;
}
/* Halo — soft bloom that breathes on a slow cycle (~14s, below conscious notice) */
.purpose-hero-halo {
  transform-origin: 990px 244px;
  transform-box: fill-box;
  animation: dbf-halo-breathe 14s ease-in-out 1.2s infinite;
}
/* Mist parallax — counter-phase, ultra-slow */
.purpose-hero-mist-far  { animation: dbf-drift-far  90s ease-in-out infinite alternate; }
.purpose-hero-mist-near { animation: dbf-drift-near 130s ease-in-out infinite alternate; }

/* Inner column — content above the scene; max measure preserved */
.purpose-hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 62ch;
}
/* Hero eyebrow — clay deep, mono uppercase, the "this is the foundation" stamp */
.purpose-hero-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--clay-deep);
  margin: 0;
  font-weight: var(--fw-normal);
}
/* Hero text — display-scale serif quote */
.purpose-hero .foundation-text,
.foundation-text {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-weight: var(--fw-normal);
  margin: 0;
}
.purpose-hero .foundation-text em,
.foundation-text em { font-style: italic; color: var(--moss); }

/* Reduced motion — honor user preference; scene holds completely still */
@media (prefers-reduced-motion: reduce) {
  .purpose-hero-svg,
  .purpose-hero-sun,
  .purpose-hero-halo,
  .purpose-hero-mist-far,
  .purpose-hero-mist-near {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* Zero group-label's bottom margin inside icon+label header rows — lets flex-center align icon and label visually */
.mindset-anchors-header .group-label,
.purpose-anchor-head .group-label,
.purpose-pairs-col-label .group-label,
.hobby-card-meta .group-label { margin: 0; }

.mindset-anchors-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
/* Header meta column — group-label stacked with a tiny italic hint that
   gives each card a distinct voice without inventing new typography */
.mindset-anchors-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mindset-anchors-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.3;
}
.mindset-anchors-body { color: var(--moss); font-family: var(--serif); font-style: italic; font-size: var(--fs-md); line-height: 1.55; }
.mindset-anchors-accent { border-left: var(--rule-thin) solid var(--stone); padding-left: var(--space-4); }
/* Card variants — paired voices on the same chassis. Mindset = stone (the
   interior voice); Anchor = clay (the daily ritual). The rail color carries
   the difference; everything else stays the same. */
.mindset-anchors-card--mindset { border-left: var(--rail) solid var(--stone); }
.mindset-anchors-card--anchor  { border-left: var(--rail) solid var(--clay); }

.mindset-pills-list,
.breakers-pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ── Anchor pairs ── one pill per pair, one pair per row ──
   Each pair lives inside a single `.pill.pill--anchor-pair`: two .editable
   text segments on either side of an inline SVG connector. The connector
   is a thin rule with caret arrows on both ends — the pair reads as
   "these two relate to one another" (bidirectional). The list is a flex
   column so each pill drops to its own row instead of inline-wrapping. */
.anchor-pairs-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Pair pill — wider gap than the base .pill so the connector breathes
   between the two text segments without crowding either side. */
.pill--anchor-pair {
  gap: var(--space-2);
  padding: 4px var(--space-3);
}

/* Connector — fixed-width inline SVG, vertically centred in the pill.
   currentColor + opacity so the rule reads as "soft tie" rather than
   competing with the text; on pill hover the chassis already tints clay,
   which lifts the connector along with everything else. */
.anchor-link {
  display: inline-block;
  flex: 0 0 auto;
  width: 38px;
  height: 10px;
  color: var(--text-dim);
  opacity: 0.7;
}

.purpose-heading {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.015em;
  color: var(--ink);
}
/* Shared bottom gap for section-heading rows across purpose / direction /
   things / breakers / events — the rule keeps the card or items underneath
   at a consistent distance from the heading above it. */
.purpose-heading,
.pillar-section-header,
.things-section-header,
.breakers-header,
.events-header {
  margin-bottom: var(--space-5);
}
/* ── Purpose Matrix — declarations on top, paired lenses below ──
   The design's centerpiece is the dotted-thread-with-clay-sun connector
   between Vision[i] and Goals[i]: every pair reads as one thought split
   across the connector lane. Top row keeps the two singular declarations
   (Purpose / Mission); the bottom block is the paired structure where
   the relationship is the visible feature, not an alignment trick. */

/* Top — two big editorial declarations, side by side */
.purpose-matrix-anchors {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
  margin-bottom: var(--space-8);
}
.purpose-anchor {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--bg);
}
.purpose-anchor--why  { border-left: var(--rule-thin) solid var(--clay); }
.purpose-anchor--what { border-left: var(--rule-thin) solid var(--stone); }
/* Header — icon chip + meta block; shared with the pairs column-labels */
.purpose-anchor-head,
.purpose-pairs-col-label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.purpose-anchor-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
/* Axis caption — quiet italicised "Why / What / How / Showing up" name
   for the question each side answers; paired with the mono group-label */
.purpose-anchor-axis {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
}
.purpose-anchor-line {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
}

/* Bottom — three Vision↔Goal pairs. The grid (1fr · 140px · 1fr) reserves
   a wide-enough connector lane for the dotted thread to read clearly, and
   keeps Vision text right-aligned + Goal text left-aligned so both lean
   into the link mark. */
.purpose-matrix-pairs {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Column header row — chips for Vision (left) and Goals (right) frame the
   connector lane. Vision chip pushes to the right edge and Goals chip to
   the left edge, mirroring the text alignment of the rows below. */
.purpose-pairs-head {
  display: grid;
  grid-template-columns: 1fr 140px 1fr;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-5) var(--space-4);
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: var(--space-3);
}
.purpose-pairs-col-label--vision { justify-content: flex-end; }
.purpose-pairs-col-label--vision .purpose-anchor-meta { flex: 0 0 auto; }
.purpose-pairs-col-label--goals  { justify-content: flex-start; }
.purpose-pairs-col-label--goals  .purpose-anchor-meta { flex: 0 0 auto; }
.purpose-pairs-spacer { display: block; }

/* One pair — Vision side · dotted-thread connector · Goal side. The card
   chassis matches .purpose-anchor so all five matrix cards (2 anchors +
   3 pairs) read as a single family. */
.purpose-pair {
  display: grid;
  grid-template-columns: 1fr 140px 1fr;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--bg);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.purpose-pair:hover { border-color: var(--rule); background: var(--surface-quiet); }
/* Boost the arrow on hover so the paired thread becomes the visual focus */
.purpose-pair:hover .purpose-pair-link-arrow {
  transform: translate(-50%, -50%) scale(1.15);
  background: var(--surface-quiet);
}
.purpose-pair-side {
  font-family: var(--serif);
  font-size: var(--fs-md);
  line-height: 1.4;
  color: var(--ink);
  margin: 0;
}
.purpose-pair-side--vision { text-align: right; }
.purpose-pair-side--goal   { text-align: left; font-style: italic; color: var(--moss); }

/* ── Link mark — the dotted thread that ties Vision[i] to Goals[i] ──
   A horizontal dotted rule + a clay right-arrow at center: the arrow
   makes the "vision leads to goal" semantic explicit (was a sun-dot
   centerpiece before; the dot read as punctuation, not direction).
   The arrow's own background paints the card bg behind it so the
   dotted line breaks cleanly under the icon — same lift-off effect
   the dot's box-shadow halo did, but adapted to a typographic glyph. */
.purpose-pair-link {
  position: relative;
  display: block;
  height: 14px;
  pointer-events: none;
}
.purpose-pair-link-line {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dotted var(--rule);
}
.purpose-pair-link-arrow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--clay);
  font-size: var(--fs-2xs);
  line-height: 1;
  /* Background patches the card bg behind the icon so the dotted line
     visibly breaks under the arrow; rehomes to surface-quiet on pair
     hover (see .purpose-pair:hover above) */
  background: var(--bg);
  padding: 0 var(--space-1);
  transition: transform var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
/* Hobby card — paper chassis from section 8, plus a stone left-rail to
   match .pillar-section / .things-section. The previous absolutely-
   positioned .hobby-card-accent div was replaced by `border-left` so all
   three card families draw the same accent the same way (single technique). */
.hobby-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border-left: var(--rail) solid var(--stone);
}
.hobby-card-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.hobby-card-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.hobby-card-body { display: flex; flex-direction: column; gap: var(--space-2); }
.hobby-card-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-quiet);
}
.hobby-card-item:hover { background: var(--paper); border-color: var(--rule); }
.hobby-card-name {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-md);
  color: var(--ink);
  letter-spacing: -0.005em;
}
.hobby-card-desc { font-family: var(--serif); font-style: italic; font-size: var(--fs-sm); color: var(--moss); line-height: 1.45; }


/* ═══ 13 · Things (category palette preserved) ════════════════════════════ */

.add-bar,
.things-toolbar {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}
/* ── Full-width variant — used on standalone pages (e.g. milestones) where the
   open form should span the whole content column; the trigger button keeps
   its intrinsic size. ── */
.add-bar--full > .inline-add-form { width: 100%; }
/* `align-items: start` mirrors .pillars-grid — without it CSS Grid's default
   `stretch` made a shorter category card inflate to match its taller row
   sibling. Cards now grow to their content only. */
.things-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
  align-items: start;
}

/* Category card — mirrors .pillar-section: Paper surface, Rule border,
   category colour as the left accent rule (via --cat-border from JS). */
.things-section, .things-category {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: var(--rail) solid var(--cat-border, var(--rule));
  border-radius: var(--radius);
  padding: var(--space-6);
}
/* Header row — icon / name+description / add button, matching
   .pillar-section-header so the two pages share the same grammar. */
.things-section-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.things-section-header-body { flex: 1; min-width: 0; }
.things-section-name-row { display: flex; align-items: center; }
.things-section-name {
  /* Reset the global h2 margin-bottom so the single-line name doesn't add
     phantom height to the header body — the icon's flex align-items: center
     would otherwise anchor against the margin-box and sit visually too high.
     Direction's pillar-section-name keeps the margin because the arrow-label
     subtitle sits below it. */
  margin: 0;
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  color: var(--cat-name, var(--ink));
  line-height: 1.2;
  letter-spacing: -0.01em;
}
/* Item-count chip on a Things category card ("3 ITEMS"). Distinct from
   `.pill`: square-ish .radius (vs pill), clay-soft fill (vs paper), mono
   tracked metadata voice (vs sans medium). The two coexist on the same
   header — the chip is a quantity readout, .pill is a category tag. */
.things-section-count {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.1em;
  padding: 2px var(--space-2);
  background: var(--surface-quiet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  color: var(--text-mid);
}
.things-section-badges { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.things-section-badges-label { display: none; }
.things-section-type {
  padding: var(--inset-nano) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--bg);
  color: var(--text);
}
/* Type pills share one shape; only the background tint differentiates the
   variant. Tints derive from the brand palette so the trio reads as a
   designed family rather than three random swatches. */
.things-section-type--single  { background: color-mix(in srgb, var(--clay) 14%, var(--bg)); }
.things-section-type--group   { background: color-mix(in srgb, var(--cat-teal) 16%, var(--bg)); }
.things-section-type--special { background: color-mix(in srgb, var(--cat-purple) 16%, var(--bg)); }

.things-section-items {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Belt-and-braces: kill any browser-default <li> marker — prevents a stray disc
   bullet from reappearing next to items that have a leading emoji */
.things-section-items > li::marker { content: ""; }
.things-section-item {
  /* See .pillar-section-item — same stage + isolated stacking context for the
     edit-mode overlay. */
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  padding: var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-quiet);
  font-size: var(--fs-sm);
  color: var(--ink);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.things-section-item:hover,
.things-section-item.is-actions-open { border-color: var(--rule); background: var(--paper); }

/* ── Category dot — receives its colour via the `--dot` custom-property
       channel set inline on the element (see things.js); falls back to
       --text-mid when not provided. Inline `style="--dot:..."` is the
       narrowest legitimate use of an inline attr — it passes a token, not
       a hardcoded design value, and the rule itself stays in CSS. ── */
.thing-item-dot {
  width: var(--size-dot); height: var(--size-dot);
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
  margin-right: var(--space-2);
  background: var(--dot, var(--text-mid));
}
/* ── Leading emoji — shown in place of .thing-item-dot when an item has an emoji ── */
.thing-item-lead-emoji {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-right: var(--space-2);
  font-size: var(--fs-base);
  line-height: 1;
}
.thing-item-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.thing-item-emoji { margin-left: auto; }

/* ── Journal sentence row — opts OUT of the single-line truncate above.
   Things items are short labels (one line, ellipsis on overflow); journal
   items are full sentences up to 300 chars and must wrap, otherwise the
   widest sentence sets the card's min-content width and the grid column
   overflows the viewport on narrow screens. ── */
.journal-sentence-item { align-items: center; }
.journal-sentence-item .thing-item-name {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.45;
}

/* ── Drag handle / delete-btn overlays shared with .pillar-section-item live
   in section 21 — do not redefine here. ── */

/* Hobby card variants — brand semantics, not random Things-palette tints.
   Each rail names what the card is about: clay = the earning fire,
   stone = the discipline of body, moss = the quiet creative voice.
   Override .hobby-card's default stone rail (set in section 12). Icon
   color stays unified with the rest of the purpose page (stone via
   the icon system); the rail carries the family difference alone. */
.hobby-card--earn  { border-left-color: var(--clay); }
.hobby-card--shape { border-left-color: var(--stone); }
.hobby-card--play  { border-left-color: var(--moss); }


/* ═══ 14 · Calendar / log grids ═══════════════════════════════════════════ */

/* Canonical calendar/log section wrapper — applied alongside .page-section
   to override the default 24px padding to 16px (logs read denser when the
   row gutter is tighter). The previous twin classes `.pitfall-log` /
   `.momentum-log` were folded into `.calendar-log` 2026-04-26; markup on
   pitfalls.html and momentum.html migrated. */
.calendar-log {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: var(--paper);
}

/* ── Streak hero — Momentum Records full-width banner ──
   Editorial date on the left tells the story of when the current streak
   started; a "Day N" counter on the right gives the live count. A dotted
   horizon line under both columns echoes the still-water + clay-sun brand
   language: a clay sun-dot rides the line as a visual marker for "today".
   Lives directly under the .section-title; the three stat-cards sit in a
   row below it. */
/* Padding-bottom (--space-8) reserves clearance for the absolutely-
   positioned dotted horizon line at bottom: var(--space-4); the line is
   only 8px tall so the card doesn't need a big bottom band like
   .upcoming-item (whose horizon viz is ~34px). */
.streak-hero {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-5) var(--space-8) var(--space-8);
  margin-bottom: var(--space-4);
  border: 1px solid var(--rule);
  border-left: var(--rail) solid var(--success);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
}
/* Empty-state — no active streak. Quiet the rail to moss so the card reads
   as "nothing to celebrate yet"; JS toggles .streak-hero--empty. */
.streak-hero--empty { border-left-color: var(--moss); }

.streak-hero-content { min-width: 0; }
.streak-hero-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  margin: 0 0 var(--space-3);
}
.streak-hero-date {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  letter-spacing: -0.015em;
  line-height: 1.1;
  margin: 0 0 var(--space-2);
}
.streak-hero-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-md);
  color: var(--moss);
  line-height: 1.5;
  margin: 0;
}
/* Live day-count — large serif number; hidden when there's no active streak.
   Sits separated from the date column by a thin paper-soft hairline. */
.streak-hero-counter {
  text-align: center;
  padding-left: var(--space-6);
  border-left: 1px solid var(--border-soft);
}
.streak-hero-counter-label {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  margin: 0 0 var(--space-1);
}
.streak-hero-counter-num {
  font-family: var(--serif);
  font-size: var(--fs-5xl);
  font-weight: var(--fw-medium);
  color: var(--success-strong);
  letter-spacing: -0.02em;
  line-height: 1;
  margin: 0;
}
.streak-hero--empty .streak-hero-counter { display: none; }

/* Decorative horizon — dotted line + clay sun. Sits at the bottom of the
   card, full-width minus the inner padding. JS sets the sun-dot's `cx`
   attribute as a fraction of the user's record streak so the dot reads as
   "where you are now along the journey". */
.streak-hero-horizon {
  position: absolute;
  left: var(--space-8);
  right: var(--space-8);
  bottom: var(--space-4);
  height: 8px;
  width: auto;
  color: var(--success-line);
  pointer-events: none;
}
.streak-hero--empty .streak-hero-horizon { color: var(--border); opacity: 0.6; }
/* Sun-dot pulse — quiet "still going" heartbeat. Runs continuously while
   the streak is active; the empty-state hides the dot via opacity:0. */
@keyframes streak-sun-pulse {
  0%, 100% { r: 4; opacity: 1; }
  50%      { r: 5.5; opacity: 0.85; }
}
.streak-hero-horizon circle {
  animation: streak-sun-pulse 2.4s ease-in-out infinite;
  transition: cx var(--transition-base);
}
.streak-hero--empty .streak-hero-horizon circle { animation: none; opacity: 0.35; }
@media (prefers-reduced-motion: reduce) {
  .streak-hero-horizon circle { animation: none; }
}

@media (max-width: 640px) {
  .streak-hero { grid-template-columns: 1fr; gap: var(--space-3); padding: var(--space-5) var(--space-5) var(--space-8); }
  .streak-hero-counter { padding-left: 0; border-left: 0; text-align: left; display: flex; align-items: baseline; gap: var(--space-3); }
  .streak-hero-counter-num { font-size: var(--fs-4xl); }
  .streak-hero-horizon { left: var(--space-5); right: var(--space-5); bottom: var(--space-3); }
}

/* When BB is off, the only card left is "Good Sleep Nights" — drop the
   2-col grid so it stretches to the full row. */
.stats-row-no-bb { grid-template-columns: 1fr; }

/* ── Sleep Insights — month records ──
   Per-metric card showing best & worst month side-by-side. */
.month-records {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.month-records-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
  margin-top: var(--space-1);
}
/* If only the best month qualifies (e.g. one month in the dataset), the
   solitary record block stretches across — no orphan empty column. */
.month-records-row:has(> .month-record:only-child) { grid-template-columns: 1fr; }

.month-record {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-left: var(--space-4);
  border-left: var(--rail) solid var(--border);
}
.month-record--best  { border-left-color: var(--success); }
.month-record--worst { border-left-color: var(--danger); }
.month-record-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--moss);
  margin: 0;
}
.month-record--best  .month-record-eyebrow { color: var(--success-strong); }
.month-record--worst .month-record-eyebrow { color: var(--danger-strong); }
.month-record-name {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  letter-spacing: -0.01em;
  margin: 0;
  line-height: 1.1;
}
.month-record-score {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.015em;
  margin: 0;
  line-height: 1;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.month-record--best  .month-record-score { color: var(--success-strong); }
.month-record--worst .month-record-score { color: var(--danger-strong); }
.month-record-unit {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--moss);
  font-weight: var(--fw-normal);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
@media (max-width: 640px) {
  .month-records-row { grid-template-columns: 1fr; gap: var(--space-3); }
}

/* Tracker switcher — label + select pair used by every page that picks
   between trackers (Sleep daily log filter, Pitfalls habit picker, Momentum
   instance picker, the per-habit month dropdown injected by update-pitfalls.js). */
.tracker-switcher {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-3);
  flex-wrap: wrap;
}
/* ── Fieldset reset — Pitfalls + Momentum wrap their tracker switcher in
   a `<fieldset>` for a11y. Browser default fieldset chrome (1px border +
   legend offset notch) was rendering on screen — strip it so only our
   .form-label + select treatment shows. ── */
fieldset.tracker-switcher {
  border: 0;
  padding: 0;
  margin-inline: 0;
  min-inline-size: auto;
}
fieldset.tracker-switcher > legend {
  /* The legend is still announced by AT, but visually it sits inline as a
     plain mono uppercase label — same family as .form-label. */
  padding: 0;
  margin: 0;
  float: left;
}

/* ── Tracker chooser panel — .tracker-chooser ──
   Sits at the top of each tracker accordion as a compact, bordered panel.
   Layout: mono eyebrow + select on the left (flex-grow), quiet "+ New" ghost
   button on the right. When the inline-add-form opens, it spans the full row
   below via flex-wrap, replacing the button that triggered it.
   The previous twin classes (.pf-top-controls, .mom-top-controls) collapsed
   into this single rule 2026-04-26 — new markup uses `.tracker-chooser`. */
.tracker-chooser {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}

/* ── Switcher (label + select) — flex-grows on the left ── */
.tracker-chooser .tracker-switcher {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-width: 14rem;
  margin: 0;
}
/* Form-label restyled as a quiet mono eyebrow inside the chooser panel */
.tracker-chooser .form-label {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--moss);
  margin: 0;
  white-space: nowrap;
}
/* Select fills the remaining space inside the switcher.
   `background-color` (not `background:`) so the base `select` rule's chevron
   image/position/size are preserved — the shorthand would reset them. */
.tracker-chooser .calendar-log-select {
  flex: 1;
  min-width: 0;
  max-width: 22rem;
  background-color: var(--surface);
  border: 1px solid var(--border-soft);
}

/* ── Add-bar — wraps the "+ New" ghost button + inline-add-form ── */
.tracker-chooser .add-bar {
  margin: 0;
  flex: 0 0 auto;
}

/* When the form is open, the add-bar grows to span the full panel width and
   the form inside fills it edge-to-edge. The switcher stays visible above on
   its own row via flex-wrap, so the user keeps context while creating. */
.tracker-chooser .add-bar:has(.inline-add-form-open) {
  flex: 1 1 100%;
  width: 100%;
}
.tracker-chooser .inline-add-form-open {
  width: 100%;
  box-sizing: border-box;
}

/* Horizontal-only clipping — `overflow-x: clip` does NOT establish a
   scroll containing block (unlike `auto`), so the sticky <thead> defined
   in section 9 latches to the page viewport instead of this wrapper.
   Mobile narrow-screen guard preserved by the clip itself. */
.calendar-log-table-wrap { overflow-x: clip; }

/* ── Monthly calendar (update page) — 7 cols × 6 rows day cells ── */
.calendar-log-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--rule-hairline);
}
.calendar-log-grid-hdr {
  font-size: var(--fs-sm);
  color: var(--text-mid);
  text-align: center;
  padding: var(--space-1) 0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.calendar-log-grid-sq {
  aspect-ratio: 1;
  max-height: 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: var(--bg);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}
@media (min-width: 768px) {
  .calendar-log-grid-sq { aspect-ratio: 2.5 / 1; }
}
.calendar-log-grid-sq:not(.calendar-log-grid-sq-future):not(.calendar-log-grid-sq-offset):hover {
  border-color: var(--border);
}
.calendar-log-grid-sq-empty  { background: var(--surface-quiet); border: 1px dashed var(--rule); color: var(--moss); }
.calendar-log-grid-sq-no     { background: var(--success-glow); color: var(--success-strong); border: 1px solid var(--success-line); }
.calendar-log-grid-sq-going  { background: var(--success);      color: var(--oyster);          border: 1px solid var(--success); }
.calendar-log-grid-sq-yes,
.calendar-log-grid-sq-broke  { background: var(--danger-glow);  color: var(--danger-strong);   border: 1px solid var(--danger-line); }
.calendar-log-grid-sq-future { background: transparent; color: var(--text-dim); opacity: 0.45; border: 1px dashed transparent; }
.calendar-log-grid-sq-today  { outline: 1.5px solid var(--clay); outline-offset: 1px; }
.calendar-log-grid-sq-offset,
.offset { visibility: hidden; }

/* Chain variant — connects adjacent "going" streak days */
.calendar-log-grid--chain .calendar-log-grid-sq { position: relative; z-index: 1; }
.calendar-log-grid--chain .calendar-log-grid-sq-going + .calendar-log-grid-sq-going::before {
  content: "";
  position: absolute;
  top: 50%;
  right: calc(100% - 2px);
  width: 12px;
  height: 4px;
  background: var(--success);
  transform: translateY(-50%);
  z-index: -1;
}
.calendar-log-grid--chain .calendar-log-grid-sq-going:nth-child(7n + 1)::before {
  display: none !important;
}
.calendar-log-grid--chain .calendar-log-grid-sq-today {
  outline-color: var(--warn);
}

/* ── Year calendar (pitfall + momentum logs) — 12 self-contained month cards
   Replaces the GitHub-style flat heatmap. Each month is its own Mon-aligned
   calendar grid, so every cell is anchored unambiguously to a day-of-month.
   Status drives the fill: green = going / clean, red = broke / broken,
   neutral = no data, dashed faint = future, accent ring = today. ── */

/* Outer block — header + month grid */
.year-cal { display: flex; flex-direction: column; gap: var(--space-4); }

.year-cal-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--space-3);
}
.year-cal-title {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  color: var(--text);
  letter-spacing: 0.01em;
}
.year-cal-stat {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-mid);
  letter-spacing: 0.02em;
}

/* Responsive 12-month grid — auto-fits 1 → 4 columns by viewport width */
.year-cal-months {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-3);
}

/* Month card — quiet container; transparent on the parent paper surface */
.year-cal-month {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: transparent;
}
/* Live month — soft accent treatment so it pulls focus without shouting */
.year-cal-month--current {
  border-color: var(--accent-line);
  background: linear-gradient(180deg, var(--accent-glow), transparent 70%);
}
.year-cal-month--current .year-cal-month-title { color: var(--accent-dim); }

/* Month header — serif name (editorial) + mono stat (metadata) */
.year-cal-month-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
}
.year-cal-month-title {
  margin: 0;
  font-family: var(--serif);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text);
  letter-spacing: 0.005em;
}
.year-cal-month-stat {
  font-family: var(--mono);
  font-size: var(--fs-3xs);
  color: var(--text-mid);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Weekday header + days grid — both 7-col, gap-aligned */
.year-cal-weekdays,
.year-cal-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
}
.year-cal-weekdays {
  font-family: var(--mono);
  font-size: var(--fs-3xs);
  color: var(--text-dim);
  text-align: center;
  letter-spacing: 0.04em;
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--border-soft);
}

/* Day cell — square via aspect-ratio; default state = no data tracked */
.year-cal-day {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  font-family: var(--mono);
  font-size: var(--fs-3xs);
  color: var(--text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  transition: transform var(--transition-fast) var(--ease-smooth);
  cursor: default;
  user-select: none;
}
/* States — paired modifiers, no fill duplication */
.year-cal-day--blank   { visibility: hidden; }
.year-cal-day--empty   { background: var(--surface2); border-color: var(--border-soft); color: var(--text-dim); }
.year-cal-day--future  { color: var(--text-dim); border-color: var(--border-soft); border-style: dashed; opacity: 0.45; }
.year-cal-day--going   { background: var(--success); border-color: var(--success); color: var(--paper); font-weight: var(--fw-semibold); }
.year-cal-day--broke,
.year-cal-day--broken  { background: var(--danger);  border-color: var(--danger);  color: var(--paper); font-weight: var(--fw-semibold); }
.year-cal-day--clean   { background: var(--success-glow); border-color: var(--success-line); color: var(--success-strong); }

/* Today — accent ring sits on top of any state fill */
.year-cal-day--today {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  z-index: 1;
}

/* ── Chain variant — streak link between consecutive going days ──
   Opt-in via .year-cal--chain on the outer wrapper (momentum only). A short
   horizontal bar sits behind each going cell that follows another going cell,
   bridging the inter-cell gap so a streak reads as one continuous run. The
   bar is the same colour as the cell fill so it visually "merges" the
   squares; rounded corners on the cells let the bar peek through, knitting
   them together. Suppressed on Monday-of-week so the chain never wraps from
   one week row into the next. Mirrors the .calendar-log-grid--chain pattern
   on the update-page month grid. */
.year-cal--chain .year-cal-day { z-index: 1; }
.year-cal--chain .year-cal-day--going + .year-cal-day--going::before {
  content: "";
  position: absolute;
  top: 50%;
  right: calc(100% - 3px);
  width: 11px;
  height: 8px;
  background: var(--success);
  border-radius: 1px;
  transform: translateY(-50%);
  z-index: -1;
}
/* Mondays start a new week-row — suppress the chain so it doesn't visually wrap */
.year-cal--chain .year-cal-day:nth-child(7n + 1)::before {
  display: none !important;
}

.calendar-log-date { font-size: var(--fs-sm); color: var(--text-mid); }
.calendar-log-date-num { font-weight: var(--fw-bold); color: var(--text); }
.calendar-log-row-missing { opacity: 0.5; }

.calendar-log-select { min-width: 6.25rem; }

/* ── Year Spine — compact 12-row milestone calendar ──
   One row per month: monogram label, horizontal day-bar with event markers
   positioned by exact day-of-month, and a right-side count summary.
   Past months dim, current gets a clay "today" tick, empty future months mute. */
.year-spine-caption {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  margin: calc(var(--space-4) * -1) 0 var(--space-5);
}
.year-spine {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
}
.year-spine-row {
  display: grid;
  grid-template-columns: 3.25rem 1fr 5rem;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--border-soft);
  position: relative;
  transition: background var(--transition-fast);
}
.year-spine-row:first-child { border-top: 0; }
.year-spine-row:hover { background: var(--paper); }

/* Row temporal modifiers — dim past, surface+accent now */
.year-spine-row--past { opacity: 0.42; }
.year-spine-row--now {
  background: var(--paper);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* Month monogram — mono caps, like an editorial spine label */
.year-spine-month {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--moss);
}
.year-spine-row--now .year-spine-month {
  color: var(--ink);
  font-weight: var(--fw-bold);
}

/* Day-bar — hairline rule with absolutely-positioned markers along its length */
.year-spine-bar {
  position: relative;
  height: 18px;
}
.year-spine-rule {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--rule);
  pointer-events: none;
}

/* Today tick — thin clay vertical bar at today's day on the current month row */
.year-spine-today {
  position: absolute;
  width: 2px;
  height: 14px;
  background: var(--clay-deep);
  top: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1;
}

/* Event marker — clay dot on the rule; multi-event marker gets a ring */
.year-spine-marker {
  position: absolute;
  top: 50%;
  width: 11px;
  height: 11px;
  padding: 0;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  transform: translate(-50%, -50%);
  cursor: pointer;
  transition: transform var(--transition-fast), background var(--transition-fast);
  z-index: 2;
}
.year-spine-row--past .year-spine-marker { background: var(--moss); }
.year-spine-marker--multi {
  background: var(--clay-deep);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.year-spine-marker:hover,
.year-spine-marker:focus {
  transform: translate(-50%, -50%) scale(1.35);
  background: var(--clay-deep);
  outline: none;
  z-index: 4;
}

/* Tooltip — shows day + name(s) on hover/focus; appears above the marker */
.year-spine-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--ink);
  color: var(--paper);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-fast);
  z-index: 5;
}
.year-spine-tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: var(--ink);
}
.year-spine-tip-line { display: block; text-align: left; }
.year-spine-tip-day {
  font-family: var(--mono);
  letter-spacing: 0.08em;
  color: var(--accent-line);
  margin-right: var(--space-2);
}
.year-spine-marker:hover .year-spine-tip,
.year-spine-marker:focus .year-spine-tip { opacity: 1; }

/* Right-side count — em-dash for zero, "N events" otherwise */
.year-spine-count {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  text-align: right;
  color: var(--text-mid);
}
.year-spine-count--zero { color: var(--text-dim); }
.year-spine-count--has { color: var(--clay-deep); font-weight: var(--fw-bold); }
/* ── Calendar dropdown — absolute positioned sheet anchored under the trigger.
   Paper surface, soft shadow, 280px. Days are circular, Clay for selected,
   Clay-ring for today, dot below for days that already have data. */
.cal-picker {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 100;
  width: 300px;
  padding: var(--space-4);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: 0 20px 44px -18px rgba(20, 32, 30, 0.22);
  user-select: none;
}
.cal-picker.is-open { display: block; }
.sleep-cal-col, .ms-cal-col { position: relative; }

.cal-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}
.cal-picker-title {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-md);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.cal-picker-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--moss);
  font-size: var(--fs-md);
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.cal-picker-arrow:hover:not(:disabled) { background: var(--hover-soft); color: var(--ink); }
.cal-picker-arrow:disabled { opacity: 0.3; cursor: default; }

.cal-picker-weekdays,
.cal-picker-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}
.cal-picker-weekdays {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  color: var(--moss);
  text-transform: uppercase;
  text-align: center;
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--rule);
}
.cal-picker-days { gap: 2px; row-gap: 4px; margin-top: var(--space-2); }
.cal-picker-day {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  margin: 0 auto;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.cal-picker-day:hover:not(:disabled):not(.cal-picker-day--sel) {
  background: var(--hover-soft);
}
.cal-picker-day--today:not(.cal-picker-day--sel) {
  box-shadow: inset 0 0 0 1.5px var(--clay);
  color: var(--clay-deep);
  font-weight: var(--fw-semibold);
}
.cal-picker-day--sel {
  background: var(--clay);
  color: var(--oyster);
  font-weight: var(--fw-semibold);
}
.cal-picker-day--sel:hover { background: var(--clay-deep); }
.cal-picker-day--has-data:not(.cal-picker-day--sel)::after {
  content: "";
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--clay);
}
/* Score-tinted dots — mirror the score-input-{good,warn,bad} palette used on
   the sleep score inputs so the calendar reads at a glance. */
.cal-picker-day--score-good:not(.cal-picker-day--sel)::after { background: var(--success); }
.cal-picker-day--score-warn:not(.cal-picker-day--sel)::after { background: var(--warn); }
.cal-picker-day--score-bad:not(.cal-picker-day--sel)::after  { background: var(--danger); }
.cal-picker-day:disabled { color: var(--text-dim); opacity: 0.35; cursor: default; }

/* Trigger — "Pick date" pill-card. Two-line column variant shows an icon above
   the label; collapses to an inline pill on narrow screens. */
.cal-picker-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--stone);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
.cal-picker-btn:hover { background: var(--hover-soft); border-color: var(--stone); color: var(--ink); }
.cal-picker-btn--column { flex-direction: column; gap: var(--space-1); padding: var(--space-3) var(--space-4); min-width: 110px; }
.cal-picker-btn-icon { font-size: var(--fs-md); color: var(--moss); }
.cal-picker-btn:hover .cal-picker-btn-icon { color: var(--stone); }
.cal-picker-btn-label { font-size: var(--fs-2xs); }

/* Sleep / milestones date picker rows — trigger on left, editorial readout on right */
.ms-date-picker-row,
.sleep-date-picker-row {
  display: flex;
  gap: var(--space-5);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  background: var(--surface-quiet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.ms-date-readout,
.sleep-date-readout {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.ms-date-readout-label,
.sleep-date-readout-label {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--moss);
  text-transform: uppercase;
  letter-spacing: 0.16em;
}
.ms-date-readout-value,
.sleep-date-readout-value {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Weekday in italic Moss; the full date in Garamond below — editorial stack */
.sleep-date-readout-weekday, .ms-date-readout-weekday {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.3;
}
.sleep-date-readout-main, .ms-date-readout-main {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-2xl);
  letter-spacing: -0.015em;
  color: var(--ink);
  line-height: 1.1;
}
.ms-cal-col,
.sleep-cal-col { flex: 0 0 auto; }

/* Distribution bars (sleep score distribution) */
.dist-grid-single { grid-template-columns: 1fr; }
.dist-bar-row {
  display: flex;
  flex-direction: column;
  gap: var(--inset-nano);
  margin-bottom: var(--space-3);
}
.dist-bar-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--fs-2xs);
}
.dist-bar-label {
  font-weight: var(--fw-bold);
  color: var(--text);
}
.dist-bar-count {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-mid);
  font-size: var(--fs-sm);
}

/* Graph figure */
.graph, .graph-figure {
  margin: 0;
  padding: var(--space-3);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--paper);
}
.graph-canvas {
  width: 100% !important;
  max-width: 100%;
  max-height: 15rem;
  display: block;
}

/* Legend */
.legend-row {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  padding: var(--space-2) var(--space-3);
}
.legend-swatch {
  width: var(--size-icon-xs); height: var(--size-icon-xs);
  border: 1px solid var(--border);
  display: inline-block;
}
.legend-swatch-empty    { background: var(--bg); }
.legend-swatch-upcoming { background: var(--surface2); }
.legend-swatch-past     { background: var(--text-dim); }
.legend-swatch-overdue  { background: var(--text); }
.legend-text { font-size: var(--fs-2xs); color: var(--text-mid); }
.legend-divider { width: 1px; height: var(--size-icon-xs); background: var(--border); }


/* ═══ 15 · Countdown / milestones ═════════════════════════════════════════ */

/* Countdown block — left-aligned like every other Upcoming card. align-items
   pins the children to the start so a short "TODAY" string doesn't visually
   centre against the wider "7 days away" stack on neighbouring cards. */
.countdown-block { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); margin-bottom: var(--space-4); text-align: left; }
.countdown-block-num {
  font-family: var(--serif);
  font-size: var(--fs-6xl);
  font-weight: var(--fw-medium);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.countdown-block-num-today { color: var(--clay); }
.countdown-block-num-past  { color: var(--text-dim); }
.countdown-block-label {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--moss);
}

.upcoming-item {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  /* Reserves bottom space for the absolutely-positioned horizon — track
     (14px) + gap (6px) + scale (12px) + bottom inset = ~3rem of clearance. */
  padding-bottom: var(--space-12);
  text-align: left;
}
.upcoming-item-date { font-family: var(--mono); font-size: var(--fs-2xs); color: var(--moss); text-transform: uppercase; letter-spacing: 0.14em; }
.upcoming-item-name { font-family: var(--serif); font-weight: var(--fw-medium); font-size: var(--fs-xl); color: var(--ink); letter-spacing: -0.01em; line-height: 1.2; }
.upcoming-item-info { font-family: var(--serif); font-style: italic; font-size: var(--fs-sm); color: var(--moss); display: flex; flex-direction: column; gap: var(--inset-nano); line-height: 1.5; }

/* ── Upcoming-item horizon — incisive 30-day approach bar ──
   The dotted track is the unwalked path (days remaining); a clay-filled
   trail grows behind the sun-dot, which rides the leading edge. Tick marks
   + a 30d / 20d / 10d / today scale below give the bar a readable axis so
   users can count down at a glance. Active tick label tints clay when the
   sun crosses it. Preserves the brand horizon idiom (dotted line + clay
   sun) — the trail and ticks are an editorial enrichment, not a replacement. */
.upcoming-horizon {
  position: absolute;
  left: var(--space-5);
  right: var(--space-5);
  bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--accent-line);
  pointer-events: none;
}

/* Track — dotted background line + tick marks + trail + sun all share this row */
.upcoming-horizon-track {
  position: relative;
  height: 14px;
  /* Dotted axis (the future) — radial-gradient rasterizes consistently across
     browsers vs `border: dotted`, and pairs with the explicit tick marks. */
  background-image: radial-gradient(circle, currentColor 1px, transparent 1.4px);
  background-size: 7px 14px;
  background-repeat: repeat-x;
  background-position: 0 center;
}

/* Trail — solid clay segment growing from x=0 to the sun (the past) */
.upcoming-horizon-trail {
  position: absolute;
  top: 50%;
  left: 0;
  height: 2px;
  margin-top: -1px;
  background: var(--clay);
  border-radius: 1px;
  transition: width 1.4s ease-in-out;
}

/* Tick marks at 30d / 20d / 10d / today (0% / 33% / 67% / 100%) */
.upcoming-horizon-tick {
  position: absolute;
  top: 50%;
  width: 1px;
  height: 7px;
  margin: -3.5px 0 0 -0.5px;
  background: currentColor;
  opacity: 0.55;
}
.upcoming-horizon-tick--major {
  height: 12px;
  margin-top: -6px;
  opacity: 0.85;
}

/* Sun — clay disc at the leading edge of the trail; pulses + slides */
.upcoming-horizon-sun {
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--clay);
  box-shadow: 0 0 0 3px rgba(199, 116, 76, 0.18);
  transition: left 1.4s ease-in-out;
  animation: streak-sun-pulse-dot 2.4s ease-in-out infinite;
}

/* Sun heartbeat — same cadence as .streak-hero's pulse but driven by
   transform/box-shadow since the sun is now an HTML element, not an SVG circle. */
@keyframes streak-sun-pulse-dot {
  0%, 100% { transform: scale(1);   box-shadow: 0 0 0 3px rgba(199, 116, 76, 0.18); }
  50%      { transform: scale(1.15); box-shadow: 0 0 0 5px rgba(199, 116, 76, 0.10); }
}

/* Scale labels — mono uppercase, aligned to the four ticks. Active tick
   tints clay so the user can read "where am I" without parsing the dot. */
.upcoming-horizon-scale {
  position: relative;
  height: 12px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--moss);
  opacity: 0.85;
}
.upcoming-horizon-scale > span {
  position: absolute;
  top: 0;
  white-space: nowrap;
  transition: color var(--transition-base);
}
.upcoming-horizon-scale > span:nth-child(1) { left: 0; }
.upcoming-horizon-scale > span:nth-child(2) { left: 33.33%; transform: translateX(-50%); }
.upcoming-horizon-scale > span:nth-child(3) { left: 66.66%; transform: translateX(-50%); }
.upcoming-horizon-scale > span:nth-child(4) { right: 0; }
.upcoming-horizon-scale > span.is-active {
  color: var(--clay);
  font-weight: var(--fw-medium);
  opacity: 1;
}

/* States — the journey reads differently for past / today / waiting */
.upcoming-item--past .upcoming-horizon { color: var(--border); opacity: 0.55; }
.upcoming-item--past .upcoming-horizon-sun { animation: none; background: var(--moss); box-shadow: none; opacity: 0.7; }
.upcoming-item--past .upcoming-horizon-trail { background: var(--moss); opacity: 0.5; }
.upcoming-item--past .upcoming-horizon-scale { opacity: 0.55; }

.upcoming-item--today .upcoming-horizon-sun { animation: streak-sun-pulse-dot 1.4s ease-in-out infinite; }

.upcoming-item--waiting .upcoming-horizon-sun { animation: none; opacity: 0.55; }
.upcoming-item--waiting .upcoming-horizon-trail { display: none; }

/* Waiting-state caption — sibling row below the date inside .upcoming-item-info.
   Lives outside the date div so it doesn't inherit the mono/uppercase date-stamp
   styling — instead it picks up the info column's serif italic voice (overridden
   here only for size + color), reading as a soft aside against the date stamp. */
.upcoming-horizon-label {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-2xs);
  color: var(--text-dim);
  letter-spacing: 0;
  text-transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .upcoming-horizon-sun  { animation: none; transition: none; }
  .upcoming-horizon-trail { transition: none; }
}

/* ── Dashboard variant of the upcoming-horizon — drops the absolute
   positioning so the bar lives inline at the bottom of the home Milestones
   "Next Up" card. Slightly tighter scale typography to fit the compact card.
   Shares all other styling (track, trail, ticks, sun, scale) with the
   parent .upcoming-horizon — same brand language, smaller stage. */
.upcoming-horizon--dash {
  position: static;
  margin-top: var(--space-1);
}
.upcoming-horizon--dash .upcoming-horizon-track { height: 12px; }
.upcoming-horizon--dash .upcoming-horizon-tick--major { height: 10px; margin-top: -5px; }
.upcoming-horizon--dash .upcoming-horizon-tick { height: 6px; margin-top: -3px; }
.upcoming-horizon--dash .upcoming-horizon-sun { width: 10px; height: 10px; margin: -5px 0 0 -5px; }
.upcoming-horizon--dash .upcoming-horizon-scale { font-size: 8px; height: 11px; }

/* Milestones — upcoming events 3 per row */
.recent-milestones-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
  margin-bottom: var(--space-8);
}

/* Milestone tracker / event table */
/* Title sits tight to its subtitle (the subtitle then pushes space-4 to the
   table). Apply via .section-title--tight modifier on the markup. */
.ms-tracker-subtitle {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.5;
  margin: 0 0 var(--space-4);
}
/* Same overflow-x: clip rationale as .calendar-log-table-wrap — see
   section 9's sticky <thead>: `auto` would create a scroll containing
   block and prevent the viewport-level sticky from latching. */
.ms-tracker-table-wrap { overflow-x: clip; }
/* Event-name is the table's editable cell — emphasis weight stays here;
   click-to-edit hover affordance comes from the .editable utility. */
.ms-tracker-event-name { font-weight: var(--fw-bold); }
.ms-tracker-empty-msg { color: var(--text-dim); font-style: italic; padding: var(--space-3); }


/* ═══ 16 · Update panel (accordion) ═══════════════════════════════════════ */

.update-panels { display: flex; flex-direction: column; gap: var(--space-3); }
.update-accordion {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  /* visible (not hidden) so absolutely-positioned popups inside the body — e.g. the
     sleep/milestones .cal-picker — aren't clipped by the rounded container. The
     trigger carries its own top radii below to keep hover tint rounded up top. */
  overflow: visible;
  transition: border-color var(--transition-fast);
  /* When the page is opened with #accordionPitfalls etc., the browser scrolls
     the accordion to the top of the viewport — but the sticky nav (~64px)
     overlaps it. scroll-margin-top reserves clearance so the title is visible. */
  scroll-margin-top: 80px;
}
.update-accordion-open { border-color: var(--stone); }
.update-accordion-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-4) var(--space-5);
  background: transparent;
  border: 0;
  /* Round the top corners so the hover tint follows the accordion's own
     rounded top — replaces the clipping that the parent's overflow:hidden
     used to do before it was switched to `visible` for popup overflow. */
  border-radius: var(--radius) var(--radius) 0 0;
  text-align: left;
  font-family: var(--serif);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--ink);
  cursor: pointer;
}
.update-accordion-trigger:hover { background: var(--hover-soft); }
.update-accordion-trigger-left { display: flex; align-items: center; gap: var(--space-3); flex: 1; }
.update-accordion-index {
  width: 30px; height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--moss);
  flex-shrink: 0;
}
.update-accordion-open .update-accordion-index {
  font-weight: var(--fw-bold);
}
/* Module glyph inside the accordion index circle (Sleep/Pitfalls/Momentum).
   Inherits moss from the parent via currentColor; the Pitfall accent dot
   overrides to clay to echo the branding.html iconography. */
.update-accordion-icon { width: 20px; height: 20px; }
.update-accordion-icon-accent { fill: var(--clay); }
/* ── Accordion arrow — Phosphor caret, rotates on open ── */
.update-accordion-title { flex: 1; letter-spacing: -0.01em; }
.update-accordion-labels { font-family: var(--mono); font-size: var(--fs-2xs); letter-spacing: 0.12em; text-transform: uppercase; color: var(--moss); }
.update-accordion-arrow { font-size: var(--fs-lg); color: var(--moss); transition: transform var(--transition-fast); }
.update-accordion-open .update-accordion-arrow { transform: rotate(180deg); color: var(--stone); }
.update-accordion-body {
  display: none;
  padding: var(--space-5);
  border-top: 1px solid var(--rule);
  /* Match the accordion container's bottom corners so the oyster fill doesn't
     paint over the rounded green border now that the container is
     overflow:visible (previously hidden masked the squared corners). */
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--surface-quiet);
}
.update-accordion-open .update-accordion-body { display: block; }
.update-accordion-desc { font-family: var(--serif); font-style: italic; font-size: var(--fs-sm); color: var(--moss); margin-bottom: var(--space-4); line-height: 1.55; }


/* ═══ 17 · Tracker / inline-add / breakers ═══════════════════════════════ */

.tracker-section { margin-bottom: var(--space-6); padding: 0; border-radius: var(--radius); overflow: hidden; }
/* ── Tracker card header — graceful title + subtitle + delete ──
   The header is the identity slab for the active tracker (selected pitfall
   habit or momentum instance). Layout is a simple flex row: name + desc
   stack on the left (.item-content), delete pins to the top-right corner
   (.item-actions) and is always visible — no hover overlay, no z-index
   fight with the inline-edit clicks on .tracker-name-edit / -desc-edit. */
.tracker-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--paper);
  /* Reset the shared card border (section 1) — then re-apply bottom-only as a divider.
     Order matters: `border: 0` is a shorthand that would wipe `border-bottom` declared above it.
     Soft tone: this divider sits inside an accordion card → child-of-card lives on --border-soft. */
  border: 0;
  border-radius: 0;
  border-bottom: 1px solid var(--border-soft);
}
.tracker-card-name {
  font-family: var(--serif);
  font-size: var(--fs-xl);
  font-weight: var(--fw-medium);
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.tracker-card-desc {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.5;
  margin-top: var(--space-1);
}

/* Tracker header inline-edit slots — branded hover comes from the .editable
   utility (section 21); these spans only need their context-specific
   structure (inline-block to honor max-width, font/color inherit) plus
   their empty-state copy. The legacy fog-on-hover treatment was retired
   when .editable became the canonical click-to-edit standard. */
.tracker-card-header .tracker-name-edit,
.tracker-card-header .tracker-desc-edit {
  display: inline-block;
  max-width: 100%;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
}
/* Empty-state placeholder — italic faded "Add a description" when the desc
   is empty so the header always feels intentional, never broken. Uses
   :empty (no children, no text) which the renderer guarantees with empty
   textContent assignments. */
.tracker-card-header .tracker-desc-edit:empty::before {
  content: 'Add a description';
  color: var(--moss);
  opacity: 0.55;
  font-style: italic;
}
/* Same safety net for the name slot — shouldn't normally fire (the JS hides
   sections without a label) but keeps the header from collapsing visually
   if it ever does. */
.tracker-card-header .tracker-name-edit:empty::before {
  content: 'Untitled';
  color: var(--moss);
  opacity: 0.55;
  font-style: italic;
}
/* ── Today decision panel ──────────────────────────────────────────────────
   Hero CTA region inside each tracker card. Replaces the prior 3-pill toggle
   with two semantic action cards (Clean/Going vs Slipped/Broke), an explicit
   date in the header so the user knows what they're logging, and a clear-link
   that only surfaces once a state is set.
   The active visual on each card uses success-glow / danger-glow tints — clear
   feedback without loud colour, in keeping with the project's calm philosophy.
   No new JS wiring: status text and clear-link visibility are driven by :has()
   on the existing `.tracker-today-btn--active` class the renderers already
   toggle. */
.tracker-action-row { background: var(--paper); }

/* ── Panel container — paper bg, comfortable padding ── */
.today-panel { padding: var(--space-4) var(--space-5) var(--space-5); }

/* ── Header row: eyebrow • date • status • clear-link ── */
.today-panel-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
.today-panel-eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--moss);
}
/* Absolute date — reassures the user they're logging the right day */
.today-panel-date {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.06em;
  color: var(--ink);
  text-transform: uppercase;
}
/* Italic moss when idle ("Not yet logged"), upgrades to ink when a state is
   set via the :has() rule below. */
.today-panel-status {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  margin-left: auto;
}
.today-panel:has(.today-action.tracker-today-btn--active) .today-panel-status {
  color: var(--ink);
  font-style: normal;
}

/* ── Clear-pill — only visible when a state is logged ──
   Special filled pill: same pill radius as `.btn-primary` but distinct palette
   (fog fill + stone text instead of stone fill + oyster text), and font sized
   one step smaller (`--fs-2xs` vs btn-primary's `--fs-sm`) so it reads as a
   utility chip rather than a primary CTA. Carries `.tracker-today-btn` +
   `data-today-state="empty"` so it routes through the existing JS click
   delegation; the active-state override keeps the chip palette stable when
   the panel is in a logged state. */
.today-panel-clear {
  display: none;
  font-family: var(--sans);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-medium);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--fog);
  border: 1px solid transparent;
  color: var(--stone);
  padding: 3px 12px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.today-panel:has(.today-action.tracker-today-btn--active) .today-panel-clear { display: inline-flex; }
.today-panel-clear:hover {
  background: var(--stone);
  color: var(--oyster);
  border-color: var(--stone);
}
.today-panel-clear.tracker-today-btn--active {
  background: var(--fog);
  color: var(--stone);
  border-color: transparent;
  font-weight: var(--fw-medium);
}

/* ── Two-card actions — Clean/Going (left) vs Slipped/Broke (right) ──
   Icon + label + italic hint on each so meaning is unambiguous at a glance. */
.today-panel-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
.today-action {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--paper);
  border: 1.5px solid var(--border-soft);
  border-radius: var(--radius);
  font-family: var(--sans);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.today-action:hover { background: var(--fog); border-color: var(--rule); }
.today-action-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--fog);
  color: var(--stone);
  flex-shrink: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}
/* ── Phosphor glyph inside the badge — sized via font-size ── */
.today-action-icon i { font-size: var(--fs-md); }
/* Text column — label stacks above hint, vertically centered next to the icon. */
.today-action-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.today-action-label {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.today-action-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  line-height: 1.4;
}

/* ── Active-state tints — quiet semantic colour ── */
.today-action--good.tracker-today-btn--active {
  border-color: var(--success);
  background: var(--success-glow);
}
.today-action--good.tracker-today-btn--active .today-action-icon {
  background: var(--success);
  color: var(--paper);
}
.today-action--bad.tracker-today-btn--active {
  border-color: var(--danger);
  background: var(--danger-glow);
}
.today-action--bad.tracker-today-btn--active .today-action-icon {
  background: var(--danger);
  color: var(--paper);
}

/* ── Narrow screens — stack the two cards so labels + hints stay readable ── */
@media (max-width: 480px) {
  .today-panel-actions { grid-template-columns: 1fr; }
  .today-panel-status { margin-left: 0; flex-basis: 100%; }
}

/* ── History disclosure: full-width row below the today panel ──
   Subordinated to today's CTA — quiet uppercase mono row that expands the
   month-grid below. */
.tracker-history-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 0;
  border-top: 1px solid var(--border-soft);
  background: var(--paper);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--moss);
  cursor: pointer;
  text-align: left;
  border-radius: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.tracker-history-toggle:hover { background: var(--fog); color: var(--ink); }
.tracker-history-toggle-arrow { margin-left: auto; font-size: var(--fs-2xs); color: var(--stone); transition: transform var(--transition-fast); }
.tracker-history-open .tracker-history-toggle-arrow { transform: rotate(180deg); }
.tracker-history-body {
  display: none;
  padding: var(--space-4) var(--space-5) var(--space-5);
  background: var(--surface);
  border-top: 1px solid var(--border-soft);
}
.tracker-history-open .tracker-history-body { display: block; }
.tracker-history-inner { display: flex; flex-direction: column; gap: var(--space-3); }

/* ── Month picker inside history — scoped polish on the shared select ──
   Uses a quiet inline pill: mono "Month" eyebrow + bordered native select. */
.tracker-history-body .tracker-switcher {
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  margin-bottom: var(--space-5);
}
.tracker-history-body .tracker-switcher .form-label {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--moss);
  margin: 0;
}
.tracker-history-body .calendar-log-select {
  border: 1px solid var(--border-soft);
  background-color: var(--surface);
  flex: 1;
  max-width: 14rem;
}

.inline-add-form {
  display: none;
  padding: var(--space-3);
  border: 1px dashed var(--stone-soft);
  border-radius: var(--radius);
  margin-top: var(--space-3);
  margin-bottom: var(--space-3);
  background: var(--surface);
}
/* When the form replaces a button inside .add-bar, it should start at the
   button's exact position — kill the margin-top that's meant for nested-in-card use. */
.add-bar .inline-add-form { margin-top: 0; margin-bottom: 0; }
.inline-add-form-open { display: block; }
.inline-add-form-title { font-weight: var(--fw-bold); margin-bottom: var(--space-2); font-size: var(--fs-sm); }
.inline-add-form-row { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.inline-add-form-row + .inline-add-form-row { margin-top: var(--space-2); }
/* Text input grows to fill the row; When select shrinks to a fixed width sized
   for its longest option ("Indefinitely") so the field stays stable as the
   selected value changes (override .form-input-narrow's 180px cap). */
.inline-add-form-text { flex: 1 1 180px; }
.inline-add-form-when { flex: 0 0 auto; width: 8rem; max-width: none; }
.inline-add-form-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }

/* ══════════════════════════════════════════════════════════════════════════
   EMOJI PICKER
   ══════════════════════════════════════════════════════════════════════════
   Inline emoji picker (js/emoji-picker.js). The trigger is a square clay-tile
   button; clicking expands a panel in-flow inside the trigger's
   .emoji-picker-slot sibling — never as a floating popover, so the picker
   always grows inside the form or modal that owns it. */

/* ── Trigger button ── Sits next to the item-name input in the same flex row;
   inherits the form-input chrome (padding / border / background / radius) so
   the two elements read as a paired unit. align-self: stretch lets the button
   match the input's intrinsic height even when the parent row uses
   align-items: center (e.g. .inline-add-form-row). */
.emoji-picker-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  align-self: stretch;
  width: 2.875rem;
  padding: 10px var(--space-3);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  line-height: 1.5;
  cursor: pointer;
  transition: border-color var(--transition-fast),
              background var(--transition-fast),
              box-shadow var(--transition-fast);
}
.emoji-picker-btn:hover { border-color: var(--accent-line); background: var(--accent-glow); }
.emoji-picker-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.emoji-picker-btn-open {
  border-color: var(--accent);
  background: var(--accent-glow);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.emoji-picker-glyph {
  display: block;
  line-height: 1;
  font-size: var(--fs-md);
}
/* Empty state — dim placeholder so the tile reads as "needs a pick" */
.emoji-picker-btn-empty .emoji-picker-glyph { color: var(--moss); opacity: 0.55; }

/* ── Slot ── Mount point for the inline panel. Full-width across whatever
   container holds it; contributes no space when empty. */
.emoji-picker-slot { display: block; width: 100%; }
.emoji-picker-slot:empty { display: none; }

/* ── Panel ── Picker body. Animates from collapsed (max-height: 0, opacity: 0)
   to open via the .emoji-picker-panel-open class so mount/unmount feels live. */
.emoji-picker-panel {
  margin-top: var(--space-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  background: var(--paper);
  box-shadow: 0 4px 14px -8px rgba(20, 32, 30, 0.18);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 280ms var(--ease-out-back),
              opacity 200ms var(--ease-smooth);
}
.emoji-picker-panel-open { max-height: 14rem; opacity: 1; }

/* ── Custom-emoji input ── Single affordance: the user pastes an emoji and
   the value syncs live to the hidden input via V.singleEmoji (validate.js).
   Empty clears the emoji; the parent modal's Save persists the value. */
.emoji-picker-custom {
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--modal-band);
}
.emoji-picker-custom-label {
  display: block;
  font-family: var(--mono, monospace);
  font-size: var(--fs-3xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--moss);
  margin-bottom: var(--space-2);
}
.emoji-picker-custom-input {
  display: block;
  width: 100%;
  padding: 6px var(--space-3);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  line-height: 1.4;
  transition: border-color var(--transition-fast),
              box-shadow var(--transition-fast),
              background var(--transition-fast);
}
.emoji-picker-custom-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-glow);
}
/* Invalid state — clay-deep border + soft red glow so the input feels
   like the rest of the form-input-error pattern in the codebase. */
.emoji-picker-custom-input-error,
.emoji-picker-custom-input-error:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 2px var(--danger-glow);
}
.emoji-picker-custom-error {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-3xs);
  color: var(--danger);
}

/* ── Breakers / events card — fully collapsible disclosure ──────────────────
   Mirrors the Month-History pattern (.tracker-history-toggle / -body): the
   only thing visible by default is a slim full-width toggle bar. The title,
   subtitle, "+" add button, form and pill list all live inside
   `.breakers-body` and stay hidden until `#breakersCard` carries the
   `breakers-open` class. The "+" add button auto-expands the section via JS. */
#breakersCard {
  background: var(--paper);
}
/* Toggle bar — same visual language as `.tracker-history-toggle`: full-width,
   mono uppercase, quiet hover. This is the only thing showing when collapsed.
   The `border-top` divider separates it from whatever sits above (today-panel
   when this is the first disclosure). */
.breakers-toggle-bar {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 0;
  border-top: 1px solid var(--border-soft);
  background: transparent;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--moss);
  cursor: pointer;
  text-align: left;
  border-radius: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.breakers-toggle-bar:hover { background: var(--fog); color: var(--ink); }
.breakers-toggle-bar-arrow { margin-left: auto; font-size: var(--fs-2xs); color: var(--stone); transition: transform var(--transition-fast); }
.breakers-open .breakers-toggle-bar-arrow { transform: rotate(180deg); }

/* Body — collapses by default; expands when the section carries the
   `breakers-open` class. Padding lives inside the body so the toggle bar
   stays slim/uncluttered when collapsed. */
.breakers-body {
  display: none;
  padding: 0 var(--space-5) var(--space-5);
  border-top: 1px solid var(--border-soft);
}
.breakers-open .breakers-body { display: block; }

/* ── Empty-state lock — when the instance has no rule breakers yet ──
   The form must always be visible so the user can create the first one,
   even if they tap the toggle bar. JS short-circuits `toggleBreakers()`
   while this class is set; the CSS below holds the body open, hides the
   cancel button (there's nothing to cancel back to), and neutralises the
   toggle bar's chevron + cursor so the section reads as locked-open. */
.breakers-empty .breakers-body { display: block; }
.breakers-empty #breakerCancelBtn { display: none; }
.breakers-empty .breakers-toggle-bar { cursor: default; }
.breakers-empty .breakers-toggle-bar:hover { background: transparent; color: var(--moss); }
.breakers-empty .breakers-toggle-bar-arrow { display: none; }

.breakers-header, .events-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-soft);
}

/* Labels column flex-grows so the chevron sits at the far right of the toggle. */
.breakers-header-labels, .eventes-header-labels {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
/* Subtitle voice for the breakers/events header. Title typography lives on
   .section-title + .section-title--sm (set in section 11); this block only
   carries the italic-serif subtitle line beneath it. */
.breakers-subtitle, .events-subtitle { font-family: var(--serif); font-style: italic; font-size: var(--fs-sm); color: var(--moss); line-height: 1.5; margin: 0; }
.pill--breaker .pill__num, .pill--breaker-chip .pill__num { font-family: var(--mono); color: var(--moss); margin-right: var(--space-1); }

/* Header's add-pill sits at the right of the header row — kill the shared
   `margin-left: auto` (section 6) that's meant for pill-ROW positioning; the
   flex:1 on the labels column already pushes the button to the end. */
.breakers-header .inline-add-btn { margin-left: 0 !important; }

/* ── Pill row — pills flow as flex-wrap list (no add button in row) ────── */
.breakers-pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.breakers-pill-row { margin: 0; }

/* ── Breaker pill — hover-to-delete ────────────────────────────────────────
   Rule-breaker-only deletion affordance: the pill itself is a <button>.
   At rest it shows the numbered label; on hover the label fades and a
   danger-toned "× Remove" prompt fills the entire pill. Clicking anywhere
   on the pill (in either visual state) opens the confirm modal. No inset
   × button — the whole pill is the click target.

   This pattern is rule-breaker-only. Pillar / things rows still use the
   shared .item-content / .item-actions overlay (see section 21). */
.pill--breaker {
  position: relative;
  isolation: isolate;
  cursor: pointer;
  /* button reset — strip native chrome so the .pill chassis carries the look.
     Inherit font-family only (UA buttons default to system-ui); font-size and
     font-weight stay governed by the .pill base so this matches the read-only
     .pill--breaker-chip on the Momentum page pixel-for-pixel. */
  font-family: inherit;
  appearance: none;
  -webkit-appearance: none;
  /* Slower, eased fill — the danger-glow "washes in" instead of snapping, so
     the state change reads as a deliberate transition rather than a flicker. */
  transition:
    background var(--transition-base) var(--ease-smooth),
    border-color var(--transition-base) var(--ease-smooth);
}
/* Danger fill on hover — deepens the pill into the same danger-glow palette
   used by .badge-danger and the confirm-modal cancel button so the swap
   reads as "this click destroys the chip". */
.pill--breaker:hover {
  background: var(--danger-glow);
  border-color: var(--danger-line);
}
/* Default content layer — number + breaker text. Fades on hover so the
   prompt overlay reads cleanly above an empty stage. Faster than the
   prompt fade-in so the stage clears before the new copy lands. */
.pill--breaker__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  opacity: 1;
  transition: opacity 0.12s ease-out;
}
.pill--breaker:hover .pill--breaker__label { opacity: 0; }

/* Hover prompt — absolute overlay that fills the pill. inset:0 mirrors the
   .item-actions pattern (section 21) but here the click target is the pill
   itself, so the overlay is presentational only (pointer-events: none) and
   carries no button of its own.

   Entrance choreography (staggered after the label clears):
   • opacity   0 → 1
   • translateY 3px → 0  (lifts into place)
   • letter-spacing 0.10em → 0.04em  (tracking "settles" inward)
   The 70ms delay lets the label finish fading first so the two layers
   never overlap mid-transition. */
.pill--breaker__prompt {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  color: var(--danger-strong);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-size: var(--fs-3xs);
  opacity: 0;
  transform: translateY(3px);
  pointer-events: none;
  transition:
    opacity 0.22s var(--ease-smooth) 0.07s,
    transform 0.28s var(--ease-out-back) 0.07s,
    letter-spacing 0.32s var(--ease-out-back) 0.07s;
}
.pill--breaker:hover .pill--breaker__prompt {
  opacity: 1;
  transform: translateY(0);
  letter-spacing: 0.04em;
}
/* × glyph "draws in" — quarter-turn + scale pop on a slightly later beat
   than the text so the mark feels like the closing accent of the gesture. */
.pill--breaker__prompt-glyph {
  font-size: var(--fs-base);
  line-height: 1;
  /* Optical lift — the × glyph sits a touch low against uppercase letters */
  margin-top: -1px;
  display: inline-block;
  transform: rotate(-45deg) scale(0.55);
  transition: transform 0.32s var(--ease-out-back) 0.11s;
}
.pill--breaker:hover .pill--breaker__prompt-glyph {
  transform: rotate(0deg) scale(1);
}

/* ── Rule-breakers recap (Momentum report page) ──────────────────────────
   Read-only list of the selected tracker's breakers. Wraps the chip row
   in a labelled group (mono eyebrow + italic-serif hint) so the user can
   tell what they're looking at — without the wrapper, the chips floated
   naked under the tracker name with no semantic anchor. The chip row uses
   explicit flex-wrap + gap instead of relying on inter-element whitespace
   for spacing. */
.rule-breakers-recap {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.rule-breakers-recap-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.rule-breakers-recap-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
}
.list-rule-breakers-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Module visibility row — content left, toggle far right, divider below */
.module-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-soft);
}
/* Strip the divider from the last .module-row in a group. :last-child isn't
   enough — sections often end with a trailing status div (e.g. #exportStatus,
   #moduleVisibilityStatus) that pushes the final row out of :last-child. This
   :has() guard keeps the rule purely about "is there another .module-row below?". */
.module-row:not(:has(~ .module-row)) { border-bottom: 0; }
.module-row-col {
  flex-direction: column;
  gap: var(--space-3);
  align-items: stretch;
}
.module-row-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}
.module-row-name {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: var(--fs-md);
  letter-spacing: -0.005em;
  color: var(--ink);
  margin-bottom: var(--inset-nano);
}
.module-row-desc {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  max-width: 30rem;
  line-height: 1.5;
}
.module-row-sub {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-3) 0 0 var(--space-4);
  border-left: 1px solid var(--border);
  margin-left: var(--space-1);
}
.module-row-sub .module-row-name { font-size: var(--fs-sm); }
.module-row-sub .module-row-desc { font-size: var(--fs-2xs); color: var(--text-dim); }

/* ── .switch — iOS-style binary toggle ──
   Canonical class for "is this module/feature enabled?" controls. Off state:
   fog track + moss thumb; on state: stone track + paper thumb. Width 44px,
   height 24px, thumb 16px (Apple HIG-aligned proportions). The `aria-pressed`
   attribute drives the visual state — JS keeps it in sync with the class. */
.switch {
  position: relative;
  width: 44px;
  height: 24px;
  padding: 0;
  border-radius: var(--radius-pill);
  border: 1px solid var(--rule);
  background: var(--fog);
  font-size: 0;
  flex-shrink: 0;
  cursor: pointer;
  transition: background var(--transition-base), border-color var(--transition-base);
}
.switch::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--moss);
  transform: translateY(-50%);
  transition: left var(--transition-base), background var(--transition-base);
}
.switch[aria-pressed="true"] {
  background: var(--stone);
  border-color: var(--stone);
}
.switch[aria-pressed="true"]::before {
  left: calc(100% - 19px);
  background: var(--surface-quiet);
}
.switch:hover:not(:disabled) { border-color: var(--moss); }
.switch[aria-pressed="true"]:hover:not(:disabled) { border-color: var(--deep); background: var(--deep); }
.switch:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.switch:disabled { opacity: 0.4; cursor: not-allowed; }
/* Sub-toggle cascade — when the parent's switch is disabled (JS sets this
   when the parent module is off), dim the entire sub-row so the indent reads
   as "depending on the parent above" rather than as a peer of its siblings. */
.module-row-sub:has(.switch:disabled) {
  opacity: 0.55;
}

.missing-alert-label {
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
  font-weight: var(--fw-bold);
}
.missing-alert-msg { font-size: var(--fs-sm); color: var(--text-mid); }

/* Habit card textarea — inside tracker */
.habit-card-textarea {
  width: 100%;
  min-height: 3.75rem;
  resize: vertical;
}
.tracker-desc-edit, .tracker-name-edit {
  padding: var(--inset-mini) var(--space-2);
  font-size: var(--fs-sm);
}


/* ═══ 18 · Auth pages ═════════════════════════════════════════════════════ */

.auth-body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8) var(--space-5);
  background: var(--oyster);
}
.login-wrap {
  width: 100%;
  max-width: var(--login-max);
}
.login-card {
  padding: var(--space-8);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  background: var(--paper);
  box-shadow: 0 22px 48px -28px rgba(20, 32, 30, 0.18);
}
.login-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  justify-content: center;
  margin: 0 0 var(--space-8);
  text-decoration: none;
  color: var(--ink);
  transition: opacity var(--transition-fast);
}
.login-brand:hover { opacity: 0.75; }
.login-brand-mark { display: block; flex-shrink: 0; }
.login-brand-wm {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink);
}
.login-brand-wm em { font-style: italic; }
.login-title {
  font-family: var(--serif);
  font-size: var(--fs-4xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.025em;
  line-height: 1.05;
  text-align: center;
  margin: 0 0 var(--space-3);
  color: var(--ink);
}
.login-subtitle {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-md);
  color: var(--moss);
  text-align: center;
  margin-bottom: var(--space-6);
  max-width: 32ch;
  margin-left: auto;
  margin-right: auto;
  line-height: 1.5;
}
.login-footer {
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--moss);
  margin-top: var(--space-6);
  font-family: var(--serif);
}
.login-footer a { color: var(--clay-deep); }
.login-forgot-wrap { text-align: right; margin-bottom: var(--space-3); }
.login-forgot-link {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--moss);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.login-forgot-link:hover { color: var(--clay-deep); border-bottom-color: var(--clay-deep); }
.login-sep {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0;
}
.login-sep-line {
  flex: 1;
  border-top: 1px solid var(--rule);
}
.login-sep-label {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--moss);
  text-transform: uppercase;
  letter-spacing: 0.18em;
}
.google-btn-icon { width: var(--size-icon-sm); height: var(--size-icon-sm); background: transparent; border: 0; }

/* Google / Magic-link — secondary CTAs on auth pages. The card already hosts a
   .btn-primary ("Sign in" / "Create account") so OAuth + magic-link render as
   clay-soft pills with a stone outline. They never appear outside .login-wrap,
   so the rule sits self-contained: chassis (pill / typography / transitions)
   is duplicated here rather than inherited from the catch-all secondary rule
   above — staying out of that rule's high-specificity :not() chain keeps the
   override gymnastics gone.
   On hover the pill flips to stone-fill / oyster-text — matching .btn-primary's
   active tone so the focus path stays calm. */
.google-btn, .magic-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--btn-py-md) var(--btn-px-md);
  background: var(--surface-quiet);
  color: var(--stone);
  border: 1px solid var(--stone);
  border-radius: var(--radius-pill);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1.2;
  letter-spacing: 0.005em;
  cursor: pointer;
  transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
.google-btn:hover:not(:disabled),
.magic-btn:hover:not(:disabled) {
  background: var(--stone);
  color: var(--oyster);
  opacity: 1;
}
.google-btn:disabled,
.magic-btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* ── Auth confirmation state ── */
/* Replaces the form section after email-bound auth actions (signup, password
   reset, magic link). The clay envelope chip mirrors the brand mark and the
   copy reads as quiet completion, not alert — so the form can't be re-submitted
   by mistake while the user goes to check their inbox. */
.auth-confirm {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
}
/* Soft clay chip housing the envelope glyph — matches login-card surface family */
.auth-confirm-icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--surface-quiet);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-2);
  color: var(--clay);
}
.auth-confirm-icon svg { display: block; }
/* Title — same serif treatment as .login-title but one size smaller */
.auth-confirm-title {
  font-family: var(--serif);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  margin: 0;
}
.auth-confirm-body {
  font-family: var(--sans);
  font-size: var(--fs-md);
  color: var(--moss);
  line-height: 1.55;
  max-width: 36ch;
  margin: 0;
}
/* Email address inside body — promote to ink so the user can verify it */
.auth-confirm-body strong { color: var(--ink); font-weight: var(--fw-medium); }
.auth-confirm-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--moss);
  margin: 0;
}

/* ── Welcome page — post-email-confirmation landing ──────────────────────
   Reached after a user clicks the signup confirmation link. Deliberately
   passive: the next action lives in their inbox, not on this page, so the
   layout opts out of .login-card / nav / footer entirely. Reuses
   .auth-body for the centered viewport; .login-brand-mark / .login-brand-wm
   are reused for the wordmark, with .welcome-brand sizing it down to a
   quieter top-of-page mark. */
.welcome-wrap {
  width: 100%;
  max-width: 36rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-6);
  padding: var(--space-8) 0;
}
/* Wordmark — non-interactive (no nav target on this page); sits smaller
   than .login-brand to keep the headline as the gravitational center.
   Children carry their own color (.login-brand-wm = ink, SVG = inline). */
.welcome-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-10);
}
.welcome-brand .login-brand-wm { font-size: var(--fs-xl); }
/* Display-scale modifier on .login-title — same serif treatment, only the
   size differs. Welcome page wants a larger anchor than auth-card pages.
   Margin reset because passive landings manage rhythm via wrap `gap`. */
.login-title--display { font-size: var(--fs-6xl); margin: 0; }
/* Subline — Inter / sans body voice, calm tone, narrow measure for breathing */
.welcome-sub {
  font-family: var(--sans);
  font-size: var(--fs-md);
  color: var(--text-mid);
  line-height: 1.55;
  max-width: 36ch;
  margin: 0;
}
/* Mono epigraph — Stone tint, sub-text scale; sits well below the message
   so the eye lands on it last, like a closing line in a quiet room */
.welcome-epigraph {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  color: var(--stone);
  margin: var(--space-12) 0 0;
}


/* ═══ 19 · 404 page ═══════════════════════════════════════════════════════ */

.not-found-wrap {
  min-height: 70vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-6);
  gap: var(--space-3);
}
/* Big "404" numerals — serif display type, no illustration */
.not-found-glyph {
  font-family: var(--serif);
  font-size: var(--fs-6xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.03em;
  color: var(--ink);
  line-height: 1;
  margin-bottom: var(--space-5);
}
/* .not-found-primary is used as a button class on the "Back to dashboard" CTA.
   It inherits .btn-primary (Stone pill); we only provide a non-stretchy width. */
.not-found-primary { width: auto; }
/* .not-found-secondary is the quiet text link next to the primary button */
.not-found-secondary {
  color: var(--moss);
  font-family: var(--sans);
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  font-style: normal;
  align-self: center;
}
.not-found-secondary:hover { color: var(--clay-deep); }
.not-found-subtitle { font-family: var(--serif); font-style: italic; font-size: var(--fs-md); letter-spacing: -0.005em; text-transform: none; color: var(--moss); line-height: 1.55; max-width: 42ch; }
.not-found-actions { display: flex; gap: var(--space-4); margin-top: var(--space-6); align-items: center; }


/* ═══ 20 · App loader ═════════════════════════════════════════════════════ */

#app-loader {
  position: fixed;
  inset: 0;
  background: var(--oyster);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 300;
}
.app-loader-inner {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
}
/* Horizontal lockup — mark + wordmark on one line, brand's "complete logo" */
.app-loader-lockup {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  line-height: 1;
}
.app-loader-mark { display: block; flex-shrink: 0; }
.app-loader-wordmark {
  font-family: var(--serif);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-medium);
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1;
}
.app-loader-wordmark em { font-style: italic; }
/* Loader ring — 36px, 3px stroke, Clay top-cap (carried over from pre-brand CSS) */
.app-loader-ring {
  width: 36px;
  height: 36px;
  border: 3px solid var(--rule);
  border-top-color: var(--clay);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin-top: var(--space-2);
}
/* Inline spinner — 14px, 2px stroke; used inside buttons / inline loaders */
.spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--rule);
  border-top-color: var(--clay);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  display: inline-block;
  vertical-align: middle;
  margin-right: var(--space-2);
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Render gate — keep the page hidden under #app-loader (z-index 300) while
   JS initialises. Each page's init removes [data-loading] once data is in
   appCache; the loader hides via the rule below and .page reveals with the
   page-reveal animation in shell.js. Prewarm on /index loads every module's
   data into sessionStorage so subsequent navigation skips the fetch wait. */
body[data-loading] { overflow: hidden; }
body[data-loading] .page {
  opacity: 0;
  visibility: hidden;
}
body:not([data-loading]) #app-loader { display: none; }

/* Page reveal — fade + subtle rise after render gate clears */
.page-reveal { animation: fadeIn 0.25s ease-out; }
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}


/* ═══ 21 · Utilities & state ══════════════════════════════════════════════ */

.u-hidden { display: none !important; }
.u-off { visibility: hidden; }
.u-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;
}

/* ── .editable — canonical click-to-edit text affordance ──────────────────
   Single utility that powers every click-to-edit text field across the app:
   Purpose (foundation, matrix anchors, vision/goal pairs, hobby items,
   mindset/anchor pebbles), Update (pitfall + momentum tracker name/desc),
   Milestones (event-name table cells). One vocabulary, one hover state,
   one pencil glyph — across heroes, paragraphs, table cells, and pills.

   At rest:    invisible; cursor:pointer is the only cue.
   On hover:   clay-glow background + dotted clay underline + pencil glyph
               slides in next to the last word of the run.
   Empty:      [data-placeholder="..."] renders the attribute as italic
               faded text via ::before; pencil + underline still appear on
               hover so the slot reads as "click to fill in" rather than
               broken.

   Inline-friendly via `box-decoration-break: clone` — multi-line spans
   wrap without breaking the highlight band. Sized in `em` so the pencil
   scales with whatever font-size the parent inherits (works equally well
   on 28px hero serif and 14px pebble text). */
.editable {
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  margin: -2px -6px;
  position: relative;
  text-decoration-line: underline;
  text-decoration-style: dotted;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background var(--transition-fast),
              color var(--transition-fast),
              text-decoration-color var(--transition-fast);
}
.editable:hover {
  background: var(--accent-glow);
  color: var(--ink);
  text-decoration-color: var(--clay);
}
.editable:hover.placeholder-na { color: var(--clay-deep); }
.editable:focus-visible {
  outline: none;
  background: var(--accent-glow);
  text-decoration-color: var(--clay);
  box-shadow: var(--shadow-focus-accent);
}
/* Empty-state placeholder — render data-placeholder as italic faded text
   so an unfilled slot reads as "type here" instead of vanishing. Hover
   still applies. The legacy hardcoded `:empty::before` rules on
   .tracker-name-edit / .tracker-desc-edit keep working; this is the
   forward-looking opt-in for new usages. */
.editable[data-placeholder]:empty::before {
  content: attr(data-placeholder);
  color: var(--moss);
  opacity: 0.55;
  font-style: italic;
}
/* Pencil glyph — slides in on hover at the end of the text run. Inline-SVG
   data-URI rendered as a mask so the icon color is driven by a CSS var
   instead of being baked into the URI. Independent of icon-font load
   order; renders at exactly `0.85em` no matter the surrounding font. */
.editable::after {
  content: "";
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  margin-left: 0.35em;
  vertical-align: -0.08em;
  background-color: var(--clay-deep);
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'><path fill='black' d='M227.31 73.37 182.63 28.68a16 16 0 0 0-22.62 0L36.68 152a15.86 15.86 0 0 0-4.68 11.31V208a16 16 0 0 0 16 16h44.69a15.86 15.86 0 0 0 11.31-4.68L227.31 96a16 16 0 0 0 0-22.63ZM92.69 208H48v-44.69l88-88L180.69 120ZM192 108.68 147.31 64l24-24L216 84.68Z'/></svg>");
          mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'><path fill='black' d='M227.31 73.37 182.63 28.68a16 16 0 0 0-22.62 0L36.68 152a15.86 15.86 0 0 0-4.68 11.31V208a16 16 0 0 0 16 16h44.69a15.86 15.86 0 0 0 11.31-4.68L227.31 96a16 16 0 0 0 0-22.63ZM92.69 208H48v-44.69l88-88L180.69 120ZM192 108.68 147.31 64l24-24L216 84.68Z'/></svg>");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
  opacity: 0;
  transition: opacity var(--transition-fast);
}
.editable:hover::after,
.editable:focus-visible::after { opacity: 0.75; }
/* Empty + hover — pencil still appears so the slot remains discoverable */
.editable[data-placeholder]:empty:hover::after { opacity: 0.55; }

/* ── Pill context — pebbles inherit the editable system ──
   Mindset / anchor pebbles wrap an .editable text node. Inside the
   pebble, the dotted underline + pencil glyph fire on hover (same
   language as every other editable text in the app). The pill chassis
   still tints (border + bg) on hover via .pill:has(.editable):hover so
   you get layered feedback: the inner text says "click to edit this
   word", the pebble border says "this whole chip is editable as a
   unit". Pencil scales to 0.7em so it fits the small pebble metrics. */
.pill .editable {
  padding: 0;
  margin: 0;
}
.pill .editable:hover {
  background: transparent;
  text-decoration-color: var(--clay-deep);
}
.pill .editable::after {
  width: 0.7em;
  height: 0.7em;
  margin-left: 0.25em;
}

/* ── Edit-mode overlay ────────────────────────────────────────────────────
   Method 2 of delete-patterns.html (hover-row-then-modal). Shared pattern
   for list rows that expose drag + delete affordances on hover. The wrapper
   is the stage — on hover its .item-content dims out and .item-actions
   fades in on top, absolutely positioned inset:0 inside the wrapper's
   isolated stacking context. No geometry ever changes: the row's width,
   padding, and flex layout are static at all times.

   Opt-in wrappers — each sets position: relative and isolation: isolate in
   its own section: .pillar-section-item (12), .things-section-item (13).
   The --item-actions-padx custom property lets each surface tune the
   overlay's horizontal inset to match its own padding scale (default:
   var(--space-3)). Rule-breaker pills (.pill--breaker, section 17) opt
   OUT — they use a hover-to-fill prompt instead of an inset × button
   (Method 3). Things-category headers (things.js) also opt OUT — their
   delete is a hover-revealed inline .btn-del next to the heading,
   not an overlay (Method 4).

   .tracker-card-header (17) used to host its own opted-out variant of this
   overlay back when pitfall/momentum cards had an inline delete glyph —
   that affordance was retired in favor of Method 1 (the unified
   editInstanceModal owns delete). The header now contains only
   .item-content, no .item-actions. */

.item-content {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  opacity: 1;
  transition: opacity 150ms ease;
}

.item-actions {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  padding: 0 var(--item-actions-padx, var(--space-3));
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease;
  z-index: 1;
}

/* Reveal — dim the content layer and surface the action layer. Both
   surfaces (direction's pillar items + things' category items) are
   click-driven via .is-actions-open, set by their respective JS files
   when the .item-menu glyph is tapped — discoverable on touch and
   deliberate on desktop. Tracker-card-header is intentionally absent —
   it has its own static layout (overrides below); .pill--breaker is
   also absent — it uses the hover-to-fill prompt defined in section 17. */
.pillar-section-item.is-actions-open .item-content,
.things-section-item.is-actions-open .item-content { opacity: 0.4; }

.pillar-section-item.is-actions-open .item-actions,
.things-section-item.is-actions-open .item-actions {
  opacity: 1;
  pointer-events: auto;
}

/* ── Anchor + clean reveal (direction + things) ─────────────────────────
   Harmonized with the app's `feedback_module_grid_hover` brand language:
   a clay accent bar slides down the left edge to anchor the active row
   while the action overlay fades + soft-scales into place.

   Trigger is .is-actions-open (set by direction.js / things.js on
   .item-menu click), not :hover. Click outside the row, Esc, or tap the
   menu again to close.

   Why no per-button cascade: the cascade variant (drag → edit → delete
   staggered) read as busy / fidgety on long lists. This rev keeps the
   reveal calm — one anchor, one fade — and uses the established
   sibling-dim idiom from the index.html grid to focus attention.

   Choreography:
   • ::before — 3px clay accent bar on the left edge, scales scaleY(0→1)
     from the top with --ease-out-back; lands before the actions do.
   • .item-actions — opacity fade + 0.97 → 1 scale from right-center
     (anchored to the edit/delete edge) so buttons feel like they settle
     into place rather than pop on flat.
   • Sibling rows in the same list dim to 0.55 — focuses the eye on the
     active row without changing any background colors. */

/* Left accent bar — clay, sits flush inside the row's 1px border. The row
   has isolation: isolate so the pseudo-element is scoped here. */
.pillar-section-item::before,
.things-section-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 2px;
  background: var(--clay);
  transform: scaleY(0);
  transform-origin: top;
  opacity: 0;
  transition:
    transform 0.28s var(--ease-out-back),
    opacity 0.18s var(--ease-smooth);
  pointer-events: none;
  z-index: 2;
}
.pillar-section-item.is-actions-open::before,
.things-section-item.is-actions-open::before {
  transform: scaleY(1);
  opacity: 1;
}

/* Sibling dim — focus effect within the active row's list only. Scoped
   to the parent UL so dimming never crosses pillar/category boundaries.
   Gated to non-drag state via body:not(.is-dragging-active): the active
   row keeps .is-actions-open across the drag (closeOpenActions doesn't
   run on drag start), so without the gate siblings would pick up the
   0.55 dim alongside the placeholder's own drag-state dim. */
body:not(.is-dragging-active) .pillar-section-items:has(.is-actions-open) .pillar-section-item:not(.is-actions-open),
body:not(.is-dragging-active) .things-section-items:has(.is-actions-open) .things-section-item:not(.is-actions-open) {
  opacity: 0.55;
  transition: opacity 0.2s var(--ease-smooth);
}

/* Action overlay — slower eased fade + soft scale-in. Origin: right-center
   so the scale settles toward the edit/delete edge where the user's eye
   lands first when the menu opens. */
.pillar-section-item .item-actions,
.things-section-item .item-actions {
  transform: scale(0.97);
  transform-origin: right center;
  transition:
    opacity 0.24s var(--ease-smooth),
    transform 0.32s var(--ease-out-back);
}
.pillar-section-item.is-actions-open .item-actions,
.things-section-item.is-actions-open .item-actions {
  transform: scale(1);
}

/* Override the shared 0.4 content dim — hide content fully when the menu
   is open so the action glyphs (drag / edit / delete) read against a
   clean row surface instead of mixing with the dimmed label below.
   Confirmation context (which item) is provided by the delete/edit
   modals, so no visual fallback is needed in the open state. */
.pillar-section-item.is-actions-open .item-content,
.things-section-item.is-actions-open .item-content { opacity: 0; }

/* ── Drag-state visual reset ───────────────────────────────────────────
   When a row is being dragged, both the floating clone (.drag-clone) and
   the source placeholder (body.is-dragging-active .pillar-section-item /
   .things-section-item) must read as idle. Without this reset the source's
   active-state styles (action overlay at opacity 1, content at opacity 0,
   accent bar at scaleY(1)) would bleed into the start of the drag and
   slowly transition back to idle, leaving × / pencil glyphs visible on
   the placeholder for ~250ms.
   transition: none snaps the reset in instantly so no mid-flight artifacts
   are visible during the drag. */
.drag-clone::before,
body.is-dragging-active .pillar-section-item::before,
body.is-dragging-active .things-section-item::before {
  opacity: 0;
  transform: scaleY(0);
  transition: none;
}
.drag-clone .item-content,
body.is-dragging-active .pillar-section-item .item-content,
body.is-dragging-active .things-section-item .item-content {
  opacity: 1;
  transition: none;
}
.drag-clone .item-actions,
body.is-dragging-active .pillar-section-item .item-actions,
body.is-dragging-active .things-section-item .item-actions {
  opacity: 0;
  transform: scale(0.97);
  transition: none;
}

/* ── Tracker-card-header layout ──
   Reuses .item-content for the name + desc column. The wrapper sits in
   normal flex flow (no overlay) — name + desc are inline-clickable to open
   the unified editInstanceModal (Method 1), which owns delete via its
   onDelete slot. There is no .item-actions sibling here; the per-row
   overrides for it have been retired with the inline delete glyph. */
.tracker-card-header .item-content {
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  /* `stretch` (default) — let the name/desc rows span the full column width
     so the description text uses all available space before wrapping. The
     inner editable spans (`.tracker-name-edit` / `-desc-edit`) are
     inline-block, so they still shrinkwrap their content and sit
     left-aligned; only the row containers stretch. */
  align-items: stretch;
  gap: var(--space-1);
}

/* ── Action buttons ───────────────────────────────────────────────────────
   Ghost glyphs — transparent, tinted only on hover. Sits inside .item-actions
   as a left-aligned cluster: drag handle (reorder) + edit (rename / observation).
   Destructive delete lives INSIDE the edit modal per the app-wide modal
   standard, never as a row affordance. */

.item-drag {
  cursor: grab;
  color: var(--text-dim);
  background: none;
  border: none;
  font-size: var(--fs-lg);
  padding: var(--inset-tight);
  border-radius: var(--radius-sm);
  line-height: 1;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.item-drag:hover  { color: var(--text-mid); background: var(--surface2); }
.item-drag:active { cursor: grabbing; }

/* ── .item-edit — labeled edit affordance ──
   "Edit" label + trailing pencil glyph. Clay accent so it reads as a
   creative/edit action. Pencil margin-left mirrors the .editable
   click-to-edit utility (0.35em) so the trailing-glyph rhythm is the
   same vocabulary across the app. Available to every user; the rich-text
   notes inside the modal are premium-gated separately. */
.item-edit {
  cursor: pointer;
  color: var(--moss);
  background: none;
  border: 1px solid transparent;
  font-size: var(--fs-sm);
  padding: var(--inset-tight) var(--space-2);
  border-radius: var(--radius-sm);
  line-height: 1;
  display: inline-flex;
  align-items: center;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.item-edit:hover {
  background: var(--clay-soft);
  border-color: var(--clay);
  color: var(--clay);
}
/* Trailing pencil — matches .editable's icon margin so the label →
   glyph spacing reads identically to inline editable text. */
.item-edit > i { margin-left: 0.35em; }

/* Drag-and-drop states */
.is-dragging {
  opacity: 0.25;
  border: 1px dashed var(--border) !important;
  background: transparent !important;
  box-shadow: none !important;
}
body.is-dragging-active { cursor: grabbing; }
body.is-dragging-active .pillar-section-item,
body.is-dragging-active .things-section-item { pointer-events: none; }
.is-reordering > .pillar-section-item,
.is-reordering > .things-section-item { transition: transform var(--transition-base) cubic-bezier(0.2, 0, 0, 1); }

/* Drag clone — Trello-style floating card while dragging */
.drag-clone {
  position: fixed;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.95;
  transform: rotate(1.5deg) scale(1.03);
  border: 1px solid var(--text);
  background: var(--bg);
  box-shadow: var(--shadow-drag);
  transition: none;
  will-change: top, left;
}


/* ═══ 22 · Responsive ═════════════════════════════════════════════════════ */

/* ── Tablet / narrow desktop (≤980px) ── */
@media (max-width: 980px) {
  .page, .wrap { padding: var(--space-8) var(--space-4) var(--space-12); }
}

/* ── Mobile (≤768px) ── */
@media (max-width: 768px) {
  /* Layout collapses to single column */
  .two-col,
  .grid-2,
  .things-grid,
  .pillars-grid,
  .hobby-grid,
  .mindset-anchors-grid,
  .pf-habits-grid,
  .dash-grid,
  .db-journal-sentences,
  .personalize-grid,
  .recent-milestones-list,
  .insight-section-grid,
  .insight-section-grid-split,
  .insight-section-grid-stats,
  .insight-section-grid-cards,
  .insight-section-grid-4col {
    grid-template-columns: 1fr;
  }

  /* Personalize CTA — drop the absolute positioning on mobile so it stops
     covering the greeting; sits inline below the title on its own row. */
  .home-personalize-btn {
    position: static;
    margin-top: var(--space-4);
  }

  /* Year spine — tighten columns and collapse the "events" word on narrow screens */
  .year-spine-row {
    grid-template-columns: 2.5rem 1fr 2rem;
    gap: var(--space-2);
    padding: var(--space-2);
  }
  .year-spine-count-word { display: none; }

  /* Typography scale-down */
  .page-title, h1 { font-size: var(--fs-2xl); }
  .db-foundation-text { font-size: var(--fs-xl); }

  /* Foundation card — tighten the inner column padding on narrow screens;
     the centered hero layout itself needs no structural change. */
  .db-foundation-inner {
    padding: var(--space-8) var(--space-4) var(--space-12);
  }

  /* Purpose hero — foundation text shrinks on mobile to match other section copy */
  .purpose-hero .foundation-text { font-size: var(--fs-lg); }

  /* Footer: stack clusters; hide right cluster entirely (status indicator +
     My Account link are desktop-only chrome — left cluster carries identity) */
  .footer-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
  }
  .footer-right { display: none; }

  /* Profile — module rows stack vertically */
  .module-row,
  .module-row-inner,
  .module-row-sub {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
  }

  /* Purpose Matrix — top anchors stack; pair rows compress the connector
     lane and let the two text sides wrap vertically when there's no
     honest room for both side-by-side */
  .purpose-matrix-anchors { grid-template-columns: 1fr; }
  .purpose-pairs-head,
  .purpose-pair {
    grid-template-columns: 1fr 60px 1fr;
  }

  /* Sleep — hide inline score bar in table to save space */
  .score-bar-log { display: none; }

  /* Pitfall habits grid collapses */
  .list-habits-grid { grid-template-columns: 1fr; }

  /* Countdown shrinks */
  .countdown-block-num { font-size: var(--fs-4xl); }
}

/* ── Small phones (≤480px) ── */
@media (max-width: 480px) {
  .page, .wrap { padding: var(--space-6) var(--space-3) var(--space-10); }
  .page-title, h1 { font-size: var(--fs-xl); }
  /* Direction dotted-leader hides in place — at this width there is no
     honest room between a wrapped item label and the when-badge, so the
     leader keeps its layout slot but disappears. Better absent than
     partial. See section 12 for the leader's normal rule. */
  .dir-item-leader-wrap { min-width: 0; }
  .dir-item-leader,
  .dir-item-arrow { visibility: hidden; }
}

/* ── Touch / no-hover pointers ─────────────────────────────────────────────
   Direction and things rows both use a click-driven menu trigger
   (.item-menu → .is-actions-open) that works the same on touch as on
   desktop, so neither needs the always-visible-overlay fallback that the
   legacy hover model required. The breaker pill still relies on hover
   for its prompt fill, so its low-opacity tap affordance lives here. */
@media (hover: none) {
  /* Rule-breaker pill — surface the danger fill at low opacity so the
     "tap to remove" affordance is discoverable without hover. */
  .pill--breaker__prompt { opacity: 0.55; }
  .pill--breaker__label  { opacity: 0.85; }
}


/* ═══ 24 · Quill 2.0 snow theme overrides (premium edit-notes editor) ═══════
   Restyles Quill so the editor reads as a native part of the app rather
   than a CDN widget. Lives in this file (not a per-page block) because
   style.css is the single design system. The base snow-theme CSS is
   loaded lazily from CDN by loadEditorResources() in direction.js and is
   appended to <head> *after* style.css, so for tied specificity the CDN
   wins — structural rules below are scoped under #editNotesModal to beat
   the CDN on selector specificity. ═══════════════════════════════════════ */

/* Toolbar — paper rail above the editor, paired radius with container.
   #editNotesModal scope (id) beats the CDN's `.ql-toolbar.ql-snow` for
   border/background/padding which would otherwise tie and load later. */
#editNotesModal .ql-toolbar.ql-snow {
  border: 1px solid var(--rule);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--surface-quiet);
  padding: var(--space-2);
  font-family: var(--mono);
}

/* Editor container — paper card, serif voice, clay focus accent.
   margin-top negates the parent .form-group's flex gap so the toolbar
   and container fuse into one continuous bordered box (Quill injects the
   toolbar as a sibling of the editor div, so without this they're split
   by the form-group's var(--space-1) gap and the top border looks broken). */
#editNotesModal .ql-container.ql-snow {
  border: 1px solid var(--rule);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--paper);
  font-family: var(--serif);
  font-size: var(--fs-md);
  color: var(--ink);
  margin-top: calc(-1 * var(--space-1));
}
#editNotesModal .ql-container.ql-snow:focus-within { border-color: var(--clay); }
#editNotesModal .ql-container.ql-snow:focus-within + .modal-char-count { color: var(--ink); }

/* Editor surface — comfortable typing area + sensible min-height */
.ql-editor {
  min-height: 220px;
  padding: var(--space-4) var(--space-5);
  line-height: 1.6;
}

/* Placeholder — italic moss, mirrors the .placeholder-na voice */
.ql-editor.ql-blank::before {
  color: var(--moss);
  font-style: italic;
  font-family: var(--serif);
  left: var(--space-5);
  right: var(--space-5);
}

/* Toolbar buttons — quiet stone at rest, clay on hover/active.
   Quill ships SVG icons inline; .ql-stroke / .ql-fill are the SVG primitives. */
.ql-toolbar.ql-snow button .ql-stroke,
.ql-toolbar.ql-snow .ql-picker-label .ql-stroke { stroke: var(--moss); }
.ql-toolbar.ql-snow button .ql-fill,
.ql-toolbar.ql-snow .ql-picker-label .ql-fill   { fill:   var(--moss); }
.ql-toolbar.ql-snow button:hover .ql-stroke,
.ql-toolbar.ql-snow .ql-active .ql-stroke,
.ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke { stroke: var(--clay); }
.ql-toolbar.ql-snow button:hover .ql-fill,
.ql-toolbar.ql-snow .ql-active .ql-fill,
.ql-toolbar.ql-snow .ql-picker-label:hover .ql-fill   { fill:   var(--clay); }
.ql-toolbar.ql-snow button:hover,
.ql-toolbar.ql-snow .ql-active { background: var(--paper); border-radius: var(--radius-sm); }

/* Link tooltip — paper card so it reads against the editor surface */
.ql-snow .ql-tooltip {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
}
.ql-snow .ql-tooltip input[type="text"] {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-1);
}
.ql-snow .ql-tooltip a.ql-action,
.ql-snow .ql-tooltip a.ql-remove { color: var(--clay); }

/* Blockquote inside the editor — match the editorial voice */
.ql-editor blockquote {
  border-left: 3px solid var(--clay);
  padding-left: var(--space-3);
  color: var(--moss);
  font-style: italic;
  margin: var(--space-2) 0;
}

