/* ===========================
   isnotus — South Wind, v2 (Aegean)
   Named for Notus, Greek god of
   the warm south wind, with a
   cool register for Metis admin.
   =========================== */

/* ---------------------------------------------------------------
   Self-hosted webfonts. Nothing here reaches a third-party origin.

   Until 2026-09-10 this was one `@import` of a Google Fonts stylesheet,
   which then pulled the files from Google's font CDN — so every load of
   this stylesheet, including the PUBLIC slideshow at `/` and every
   `/share/<token>`, told Google a browser had visited this family site.
   The files below are byte-for-byte the ones that request served, so the
   type is unchanged; only the origin is.

   PROVENANCE — fetched 2026-09-10 from
     fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap
   requested with a current Chrome UA (that is what selects woff2), which
   returned fraunces/v38 and inter/v20. Re-fetch from that same URL if the
   fonts ever need refreshing, and keep the block order below.
   ⚠ That line is deliberately WITHOUT a scheme. It is a fetch recipe, not a
   load, and the audit for third-party resources is an origin-anchored grep
   (`grep -rn 'https\?://' public/css public/js public/us`) — writing it in
   full would put this file back in that result and make the next audit lie.

   ⚠ DO NOT re-source these from the upstream family repos. Google's css2
   endpoint SLICES a variable font to the axes the request names, and these
   files were measured (fontTools `fvar`) to carry exactly:
       Fraunces — opsz 9..144, wght 100..900     (no SOFT, no WONK, no ital)
       Inter    — wght 100..900                  (no opsz, no ital)
   The full upstream Fraunces also ships SOFT and WONK, so adopting it would
   make the `font-variation-settings: "SOFT" 50` on h1-h4 and .hero-num
   suddenly LIVE — it is inert today — and silently restyle every heading.
   Neither family carries an `ital` axis, so every `font-style: italic` on
   Fraunces is browser-synthesised. That is the current look; adding a real
   italic face would be a change, not a fix.

   ⚠ BLOCK ORDER IS LOAD-BEARING. The three Fraunces `unicode-range`s overlap
   on U+0304, U+0308 and U+0329, and for a shared codepoint the LAST matching
   @font-face wins. This is Google's own order, unaltered.

   Licences: OFL-1.1, texts alongside the files in this directory.
   --------------------------------------------------------------- */

/* vietnamese */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/fraunces-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* vietnamese */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/fraunces-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* vietnamese */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/fraunces-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* greek-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}

/* greek */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}

/* vietnamese */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* greek-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}

/* greek */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}

/* vietnamese */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* greek-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}

/* greek */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}

/* vietnamese */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* warm register — Notus / chat / homeschool / photos */
  --wind-sky: #FBF8F3;
  --wind-sand: #F3EDE4;
  --wind-dune: #E8DFD2;
  --wind-shore: #D6CDBF;
  --wind-drift: #B8AD9C;
  --wind-stone: #8A7E6E;
  --wind-earth: #5C5346;
  --wind-deep: #3B352C;
  --wind-amber: #C4892E;
  --wind-amber-hover: #B07A25;
  --wind-amber-light: rgba(196, 137, 46, 0.10);
  /* #155 — text on amber surfaces (primary buttons). Amber stays a light color
     in BOTH themes, so this NON-flipping dark brown is defined once (no dark
     override) — white failed AA (3.02/2.19); this hits 5.28 (light) / 7.29 (dark). */
  --wind-on-amber: #2A2018;
  --wind-sunset: #C47A4A;
  --wind-terracotta: #B5654A;
  --wind-green: #5E9E5E;
  --wind-green-light: #EDF5ED;
  --wind-red: #B85454;
  --wind-red-light: #F5EDED;
  --wind-mine-bubble: #EDE5D6;
  --wind-white: #FFFFFF;
  --wind-white-rgb: 255 255 255;  /* Phase 7e A0 — for rgba(var(--wind-white-rgb) / α) glass primitives */

  /* cool register — Metis / financial / DITL / data viz */
  --sea-mist:  #EEF3F2;
  --sea-glass: #C9DDD9;
  --sea-shoal: #86AEB0;
  --sea-tide:  #4A7A85;
  --sea-deep:  #1F4652;
  --sea-ink:   #0F2A33;

  /* Phase 14f tokens — shadows + ink + status + banners. Dark equivalents
     live under [data-theme="dark"] below. See
     docs/plans/phase-14f-dark-mode-adr.md for the full taxonomy. */

  /* Shadow family — flips to higher alpha on dark surfaces (audit: 0.08
     vanishes against #1A1612). */
  --shadow-subtle:     0 1px 3px rgba(90, 80, 65, 0.06);
  --shadow-card:       0 1px 3px rgba(90, 80, 65, 0.08);
  --shadow-card-hover: 0 4px 12px rgba(90, 80, 65, 0.12);
  --shadow-elevated:   0 6px 18px rgba(0, 0, 0, 0.12);
  --shadow-modal:      0 20px 60px rgba(0, 0, 0, 0.25);

  /* Ink — high-contrast text on light surfaces (chat bubbles, legal copy). */
  --ink-deep: #000;

  /* Status palette — banner backgrounds + text on success/danger surfaces. */
  --status-success-bg:     #EFF6EA;
  --status-success-border: #BFD4A6;
  --status-success-text:   #4A6B2E;
  --status-danger-bg:      #FFF4F2;
  --status-danger-border:  #E0BFB8;
  --status-danger-text:    #B33A2A;
  --status-danger-stroke:  #E0BFBF;

  /* Banner / paper gradients (ADR exceptions #3 + #4) — gradient on light;
     flat darker color on dark. */
  --banner-notice-bg:     linear-gradient(180deg, #f5eacd 0%, #ead9b1 100%);
  --banner-notice-border: #d4bf8a;
  --banner-notice-text:   #4a3820;
  --banner-notice-accent: #8a5a2b;
  --paper-bg:             linear-gradient(180deg, #f5eacd 0%, #ead9b1 100%);
  --paper-border:         #d4bf8a;
  --paper-text:           #4a3820;
  --paper-accent:         #8a5a2b;

  /* Backdrop scrims for modals + overlays. */
  --scrim-modal: rgba(20, 16, 12, 0.4);
  --scrim-light: rgba(0, 0, 0, 0.05);

  /* Borders + dividers + surface tints. wind-deep tinted at 4 alpha steps for
     borders and one for surface overlays. Used by Metis chat, financial cards,
     proposal cards, DITL event rows, and the preview-table family. Flip to
     white-based in dark mode under [data-theme="dark"]. */
  --border-hairline: rgba(74, 57, 40, 0.06);
  --border-soft:     rgba(74, 57, 40, 0.08);
  --border-card:     rgba(74, 57, 40, 0.10);
  --border-emphatic: rgba(74, 57, 40, 0.12);

  /* #156 — tokens that were referenced with literal fallbacks and never defined. A fallback
     cannot respond to dark mode, so these declarations sat on light-only literals (and one
     chain, --wind-cloud → --wind-cream, resolved to nothing at all). Each is an alias into
     the register it belongs to; the rgba tints get an explicit dark value below. */
  --wind-stone-deep:   #75695A;                    /* the stone slot when stone lacks contrast: 5.05:1 on sky (stone is 3.75) */
  --text-muted:        var(--wind-stone-deep);     /* secondary text that must clear AA in light */
  --surface-border:    var(--border-soft);
  --bg-primary:        var(--wind-white);
  --status-error-text: var(--status-danger-text);
  --wind-mist-deep:    var(--sea-deep);            /* "unscheduled" ink — cool, AA in both modes */
  --wind-mist:         rgba(31, 70, 82, 0.10);     /* its tint */
  --wind-deep-mist:    rgba(31, 70, 82, 0.25);     /* its border */
  --wind-soft:         rgba(74, 57, 40, 0.08);     /* neutral tint (the cancelled badge) */
  --wind-sand-light:   rgba(196, 137, 46, 0.06);   /* the faintest amber tint (expected rows) */
  --surface-subtle:  rgba(74, 57, 40, 0.04);

  /* Wind-amber deeper variant — text on amber tints (proposal-kind monospace
     pill, where regular --wind-amber lacks contrast on the amber-tint bg). */
  --wind-amber-deep: #A66F1B;
  /* #299 — amber AS TEXT on the light surfaces. --wind-amber (#C4892E) is the brand fill and reads
     3.02:1 on white / 2.59 on sand / 2.85 on sky — under AA (4.5) for small text everywhere .link-btn
     is used; --wind-amber-deep is 4.28 / 3.67 / 4.04, still short. Measured (WCAG) 2026-09-13:
     #8F5E12 is 5.56 on white / 4.78 on sand / 5.25 on sky — sand is the binding surface. Dark aliases
     the brand amber (#E0A455, 8.23:1 on the dark surface) — a darker dark value would only lose contrast. */
  --wind-amber-text: #8F5E12;

  /* type scale */
  --text-xs:   0.78rem;
  --text-sm:   0.875rem;
  --text-base: 1rem;
  --text-lg:   1.125rem;
  --text-xl:   1.375rem;
  --text-2xl:  1.75rem;
  --text-3xl:  2.25rem;
  --text-hero: 3.5rem;
  --line-tight: 1.15;
  --line-body:  1.55;
  --line-ui:    1.35;

  /* font stacks */
  --font-display: "Fraunces", Georgia, serif;
  --font-ui: "Inter", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Phase 14f — dark theme.
   Pure token swap (Q1 from parent plan). Component-level overrides are
   forbidden except for ADR-listed exceptions; every divergence lands here.

   Palette is the parent-plan starting draft + minor tightening from the
   audit. James 2am eye-test on iPad Pro 11" refines values before prod.
   See docs/plans/phase-14f-dark-mode.md §"Dark palette". */
[data-theme="dark"] {
  /* Tells the browser to paint native chrome (select dropdowns, date pickers,
     scrollbars, autofill) in dark mode. Without this declaration, native form
     controls render in OS-default light chrome on top of our dark surfaces —
     stark white-on-black popovers that punch out of the page. Single biggest
     dark-mode-polish win for the smallest amount of CSS. */
  color-scheme: dark;

  /* warm register — page bg + surfaces */
  --wind-sky:    #1A1612;
  --wind-sand:   #221C16;
  --wind-dune:   #2C251D;
  --wind-shore:  #3A3128;
  --wind-drift:  #524539;
  --wind-stone:  #9C8E7C;
  --wind-earth:  #C8B9A2;
  --wind-deep:   #E6D9BF;
  --wind-amber:  #E0A455;
  --wind-amber-hover: #F0B469;
  --wind-amber-light: rgba(224, 164, 85, 0.16);
  --wind-sunset: #DC916E;
  --wind-terracotta: #D88B70;
  --wind-green:        #7FBE7F;
  --wind-green-light:  rgba(127, 190, 127, 0.14);
  --wind-red:          #E07A7A;
  --wind-red-light:    rgba(224, 122, 122, 0.14);
  --wind-mine-bubble:  #2C251D;
  --wind-white:        #1A1612;     /* "white" surfaces stay surface-colored on dark */

  /* cool register */
  --sea-mist:  #15201F;
  --sea-glass: #1F3835;
  --sea-shoal: #4F7D7F;
  --sea-tide:  #7BB0B4;
  --sea-deep:  #B8DDDD;
  --sea-ink:   #D8E8E8;

  /* shadows — counter-intuitive but correct: dark surfaces need higher alpha
     to render shadow at all. */
  --shadow-subtle:     0 1px 3px rgba(0, 0, 0, 0.30);
  --shadow-card:       0 2px 8px rgba(0, 0, 0, 0.40);
  --shadow-card-hover: 0 6px 16px rgba(0, 0, 0, 0.50);
  --shadow-elevated:   0 8px 22px rgba(0, 0, 0, 0.55);
  --shadow-modal:      0 24px 64px rgba(0, 0, 0, 0.60);

  /* ink */
  --ink-deep: #FFFFFF;

  /* status — desaturated pastels on dark backgrounds */
  --status-success-bg:     rgba(127, 190, 127, 0.12);
  --status-success-border: rgba(127, 190, 127, 0.40);
  --status-success-text:   #B8DAB8;
  --status-danger-bg:      rgba(224, 122, 122, 0.12);
  --status-danger-border:  rgba(224, 122, 122, 0.40);
  --status-danger-text:    #E8A0A0;
  --status-danger-stroke:  rgba(224, 122, 122, 0.40);

  /* banner / paper — gradients flatten to darker solid colors */
  --banner-notice-bg:     #2A2218;
  --banner-notice-border: #3E3320;
  --banner-notice-text:   #E2C99A;
  --banner-notice-accent: #D4A45C;
  --paper-bg:             #2A2218;
  --paper-border:         #3E3320;
  --paper-text:           #E2C99A;
  --paper-accent:         #D4A45C;

  /* scrims — match the new pure-black headroom */
  --scrim-modal: rgba(0, 0, 0, 0.65);
  --scrim-light: rgba(255, 255, 255, 0.05);

  /* borders flip to white-tinted on dark surfaces */
  --border-hairline: rgba(255, 255, 255, 0.06);
  --border-soft:     rgba(255, 255, 255, 0.08);
  --border-card:     rgba(255, 255, 255, 0.10);
  --border-emphatic: rgba(255, 255, 255, 0.12);
  /* #156 — dark values for the rgba tints defined above; the var() aliases follow their targets. */
  --wind-stone-deep:   var(--wind-stone);          /* dark stone already clears AA (5.6:1) */
  --wind-mist:         rgba(184, 221, 221, 0.12);
  --wind-deep-mist:    rgba(184, 221, 221, 0.25);
  --wind-soft:         rgba(255, 255, 255, 0.08);
  --wind-sand-light:   rgba(224, 164, 85, 0.08);
  --surface-subtle:  rgba(255, 255, 255, 0.04);

  --wind-amber-deep: #DCB46B;
  --wind-amber-text: var(--wind-amber);   /* #299 — dark amber is 8.23:1 already */
}

/* Base typography — body sans, headings serif */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: var(--line-tight);
  letter-spacing: -0.01em;
  font-variation-settings: "opsz" 48, "SOFT" 50;
}
h1 { font-variation-settings: "opsz" 64, "SOFT" 50; letter-spacing: -0.015em; }

.hero-num {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-hero);
  font-variation-settings: "opsz" 96, "SOFT" 50;
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: var(--sea-deep);
}
.hero-num .unit {
  font-size: 0.45em;
  letter-spacing: 0;
  color: var(--wind-earth);
  font-weight: 400;
}

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

/* #293 — the reset above matches <dialog> and deletes the UA stylesheet's `dialog { margin: auto }`,
   the rule that centres a modal dialog inside its top-layer inset; with nothing restoring it, every
   showModal() dialog rendered pinned to the top-left corner. Five families carry <dialog> in
   public/us/*.html (23 files): .agent-popover ×13 and .dialog-base (11 static + the uiDialog
   factory's) MOVE to the centre, kid-portfolios' #holding-modal (.modal-card) moves with them; the
   lightbox (.photo-modal, translate-centred with its own `margin: 0`) and .detail-drawer
   (`margin: 0 0 0 auto`) keep their own margins and do not move. */
dialog {
  margin: auto;
}

/* Phase 14e — global motion guard.
   When the user prefers reduced motion (OS-level setting), collapse every
   animation + transition to ~0ms. Keeps state changes instant rather than
   animated; preserves accessibility without per-rule hand-coverage.
   See docs/motion-rules.md for the full rules + animation inventory. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The HTML `hidden` attribute should always hide the element. Browser default
   for `[hidden]` is `display: none` at user-agent stylesheet specificity, which
   any author rule (e.g. `.homeschool-toolbar { display: flex }`) overrides.
   This global rule re-asserts the contract so [hidden] always wins. */
[hidden] { display: none !important; }

html, body {
  width: 100%;
  height: 100%;
  /* Phase 14f-followup: default body background uses the token so any gap
     between sticky/sticky-adjacent surfaces (e.g. .agent-strip on /us/minx
     and /us/dice) shows the page color, not pure black. The slideshow page
     overrides to #000 below — its hero is a black-letterboxed image. */
  background: var(--wind-sky);
  color: var(--wind-earth);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--line-body);
}

/* ===== Slideshow (public homepage) ===== */

.slideshow-page body,
body.slideshow-page {
  overflow: hidden;
  /* Public homepage is full-bleed photography — black letterbox is intentional. */
  background: #000;
}

.slideshow-page #slideshow {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
}

.slideshow-page .slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 1.5s ease-in-out;
}

.slideshow-page .slide.active {
  opacity: 1;
}

/* ===== Landing page (pre-login /us/) ===== */

/* ===== Login page ===== */

.login-page {
  background: var(--wind-sky);
  background-image: radial-gradient(ellipse at 50% 100%, rgba(196, 137, 46, 0.08) 0%, transparent 60%);
  color: var(--wind-earth);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

.login-container {
  width: 100%;
  max-width: 380px;
  padding: 40px 24px;
}

.login-container h1 {
  font-size: 2rem;
  font-weight: 700;
  color: var(--wind-deep);
  margin-bottom: 4px;
  letter-spacing: 0.02em;
}

.login-subtitle {
  font-size: 1rem;
  color: var(--wind-stone);
  margin-bottom: 32px;
}

.form-group {
  margin-bottom: 20px;
}

.form-group label {
  display: block;
  font-size: 0.9rem;
  color: var(--wind-stone);
  margin-bottom: 6px;
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="email"] {
  width: 100%;
  padding: 12px 14px;
  font-size: 1rem;
  background: var(--wind-white);
  border: 1px solid var(--wind-shore);
  border-radius: 8px;
  color: var(--wind-deep);
  outline: none;
  transition: border-color 0.2s;
}

/* ===== Minimal-mode login (Phase 95-style) =====
   When in magic-link mode, just the input + status messages render — chrome
   (h1, subtitle, label, button text, password toggle link) lives in the DOM
   for E2E parity but is .visually-hidden. Add `?password=1` to /us/ to reach
   the password fallback. */
.login-page--minimal .login-container {
  padding: 60px 24px;
}

.login-page--minimal #magic-form .form-group {
  margin-bottom: 0;
}

.login-page--minimal #magic-form input[type="email"] {
  font-size: 1.1rem;
  padding: 16px 18px;
  text-align: center;
}

.login-page--minimal .error-msg,
.login-page--minimal .success-msg {
  margin-top: 16px;
  font-size: 0.9rem;
  text-align: center;
}

.form-group input:focus {
  border-color: var(--wind-amber);
  box-shadow: 0 0 0 3px var(--wind-amber-light);
}

.checkbox-group label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  color: var(--wind-earth);
  cursor: pointer;
}

.checkbox-group input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--wind-amber);
}

#submit-btn {
  width: 100%;
  padding: 12px;
  font-size: 1rem;
  font-weight: 600;
  background: var(--wind-amber);
  color: var(--wind-white);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.2s;
  margin-top: 8px;
}

#submit-btn:hover {
  background: var(--wind-amber-hover);
}

#submit-btn:disabled {
  background: var(--wind-shore);
  color: var(--wind-drift);
  cursor: not-allowed;
}

.error-msg {
  background: var(--wind-red-light);
  border: 1px solid var(--status-danger-stroke);
  color: var(--wind-red);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 0.9rem;
  margin-bottom: 20px;
}

.success-msg {
  background: var(--status-success-bg);
  border: 1px solid var(--status-success-border);
  color: var(--status-success-text);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 0.9rem;
  margin-bottom: 20px;
}

.login-toggle {
  text-align: center;
  font-size: 0.9rem;
  margin-top: 16px;
}
.login-toggle a {
  color: var(--wind-amber);
  text-decoration: none;
}
.login-toggle a:hover {
  text-decoration: underline;
}

/* ===== Home page ===== */

/* #117 slice 4: inner-scroll 100dvh shell (mirrors .chat-page/.photos-today-page).
   The body locks to the visual viewport; .home-scroll scrolls; the tab bar is a
   flex child — so the lightbox body-lock can't strand it on iPad (#144). */
.home-page {
  background: var(--wind-sky);
  color: var(--wind-earth);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}

.home {
  max-width: 640px;
  margin: 0 auto;
  padding: 24px 16px 64px;
}

/* .home-header / .home-header-right deleted Phase 14a — use .page-header. */
.me-avatar-link {
  /* 44pt minimum touch target (Phase 14f follow-up — iPad pass turned up that
     a 36px circle is below Apple's recommended hit-area on touchscreens, which
     is also why the link "didn't open" on touch — Safari's tap-cancel on
     too-small interactive elements). Visual size stays small via the disc;
     padding extends the click box without shifting layout. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--wind-amber);
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  overflow: hidden;
  border: 1px solid var(--wind-dune);
  cursor: pointer;
  /* Skip the 300ms double-tap-zoom delay on iOS — confirms tap immediately. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(196, 137, 46, 0.18);
}
.me-avatar-link img { width: 100%; height: 100%; object-fit: cover; }
.me-avatar-link:hover { filter: brightness(0.95); }
.me-avatar-link:active { filter: brightness(0.88); }

.admin-section {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--wind-dune);
  /* Phase 14h: tables inside admin sections (Users with Email column,
     Active sessions with device strings, etc.) have intrinsic min-content
     wider than narrow viewports. Tables cannot shrink below their content,
     so let the section itself scroll horizontally rather than push the
     page wider. */
  overflow-x: auto;
}

.admin-section h2 {
  font-size: 1rem;
  font-weight: 600;
  color: var(--wind-stone);
  margin-bottom: 12px;
}

.admin-card {
  border: 1px solid var(--wind-dune);
}

/* ===== Admin page tables ===== */
.admin-page .chores-container { max-width: 820px; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  overflow: hidden;
  font-size: 0.9rem;
}
.admin-table thead {
  background: var(--wind-sand);
  color: var(--wind-stone);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.admin-table th,
.admin-table td {
  padding: 10px 12px;
  text-align: left;
  border-bottom: 1px solid var(--wind-dune);
}
.admin-table tr:last-child td { border-bottom: none; }
.admin-table td.empty { color: var(--wind-drift); font-style: italic; text-align: center; }
/* ⚠ #196 D3 — DELIBERATE DIVERGENCE, kept on purpose. This is the only revoke control
   site-wide not built on `.link-btn` (admin.js's other two revokes use it), and
   docs/design-system.md forbids inventing button styles. James chose 2026-09-07 to KEEP
   the bordered look and fix only the touch target, because this one rule is shared with
   the magic-links table and adopting `.link-btn` would visibly restyle both on a page he
   uses daily. So: appearance unchanged, 44px added. The min-height/min-width are the
   documented single-tap override (design-system.md), not a new pattern — same defect
   class as #197, which fixed the sibling policy-editor controls on this page (2026-09-10). */
.admin-table .revoke-btn {
  background: transparent;
  border: 1px solid var(--wind-red);
  color: var(--wind-red);
  font-size: 0.8rem;
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
  min-height: 44px;
  min-width: 44px;
}
.admin-table .revoke-btn:hover {
  background: var(--wind-red-light);
}

.admin-form-row {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.admin-form-row label { font-size: 0.9rem; color: var(--wind-stone); }
.admin-form-row .checkbox-label { display: flex; gap: 6px; align-items: center; }
.admin-form-row select { min-width: 220px; }

.admin-page form#magic-form {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}
.admin-page form#magic-form label {
  font-size: 0.9rem;
  color: var(--wind-earth);
}
.admin-page form#magic-form select {
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--wind-dune);
  background: var(--wind-white);
  font-size: 0.9rem;
}
.admin-page form#magic-form button {
  background: var(--wind-amber);
  color: #fff;
  border: none;
  padding: 8px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}
.magic-result {
  margin-top: 14px;
  padding: 12px;
  background: var(--wind-sand);
  border-radius: 8px;
  border: 1px solid var(--wind-dune);
}
.magic-result p { margin: 0 0 8px; font-size: 0.9rem; }
.magic-result .magic-link-value {
  width: 100%;
  padding: 6px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  border: 1px solid var(--wind-dune);
  border-radius: 4px;
  box-sizing: border-box;
}
.magic-result__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.magic-result__actions button {
  padding: 6px 12px;
  background: var(--wind-deep);
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.8rem;
}
.magic-result__actions button:hover { filter: brightness(1.1); }
.magic-result__status {
  font-size: 0.8rem;
  color: var(--wind-stone);
  font-style: italic;
}

/* Admin "add memory" form */
#add-memory-form {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 12px;
  align-items: start;
  max-width: 560px;
}
#add-memory-form label {
  font-size: 0.85rem;
  color: var(--wind-earth);
  padding-top: 6px;
}
#add-memory-form select,
#add-memory-form input[type="text"],
#add-memory-form textarea {
  padding: 8px 10px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  background: var(--wind-white);
  font-family: inherit;
  font-size: 0.9rem;
  width: 100%;
  box-sizing: border-box;
}
#add-memory-form textarea { resize: vertical; }
#add-memory-form button {
  grid-column: 2;
  justify-self: start;
  background: var(--wind-amber);
  color: #fff;
  border: none;
  padding: 8px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
}

/* ===== Chat page ===== */

.chat-page {
  background: var(--wind-sand);
  color: var(--wind-earth);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Phase 14h-B: lock the chat shell to the visual viewport so iOS Safari's
     keyboard doesn't push the page-header off-screen. `html, body` above sets
     `height: 100%` which on iOS resolves to the LAYOUT viewport (always full
     window — doesn't shrink when keyboard opens). With body at full window
     and the composer as the last flex child, the keyboard covers the
     composer AND scrolls the layout viewport up, hiding the header.
     `100dvh` = small viewport height when keyboard is up, large otherwise —
     it adapts. `100vh` fallback for browsers older than iOS 15.4 / Chrome 108
     (~Mar 2022). The existing flex-column rules above hold the structure:
     header (flex 0), .messages-container (flex 1, internal scroll), composer
     (flex 0), tab-bar (flex 0). All four reposition together as body shrinks. */
  height: 100vh;
  height: 100dvh;
}

/* .chat-header / .chores-header / .chat-nav / .nav-btn deleted Phase 14a — use .page-header + .btn-secondary. */

/* ===== Phase 14a: unified page chrome ===== */

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  background: var(--wind-white);
  border-bottom: 1px solid var(--wind-dune);
  flex-shrink: 0;
}

.page-header__titles {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.page-header__eyebrow {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--wind-stone);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 2px;
}

.page-header__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.4rem;
  font-weight: 500;
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  line-height: 1.15;
  margin: 0;
}

.page-header__menu {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
  /* margin-left: auto so the avatar always sits at the far right of the
     header, even on map-header / page-header--map where the flex layout
     doesn't use justify-content: space-between (the map adds floor pills
     and a list-view link in the middle, so we can't anchor the right edge
     via space-between without disrupting them). */
  margin-left: auto;
}

@media (min-width: 1024px) {
  .page-header {
    padding: 16px 24px;
  }
  .page-header__title {
    font-size: 1.6rem;
  }
}

/* Map-specific page header preserved as a modifier — DITL compass rose. */
.page-header--map {
  background: var(--wind-sand);
  border-bottom: 1px solid var(--wind-shore);
  padding: 16px 20px;
}

/* ===== Phase 14a: native <dialog> primitive ===== */

.dialog-base {
  border: none;
  border-radius: 12px;
  padding: 1.5rem;
  max-width: 520px;
  width: 90%;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);
  color: var(--wind-earth);
  background: var(--wind-white);
}

.dialog-base::backdrop {
  background: rgba(20, 16, 12, 0.4);
}

.dialog-base form {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.dialog-base h2 {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: 1.4rem;
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  margin: 0 0 0.5rem;
}

.dialog-base label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
  color: var(--wind-earth);
}

.dialog-base input,
.dialog-base select,
.dialog-base textarea {
  font: inherit;
  padding: 0.5rem;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  background: var(--wind-white);
  color: var(--wind-earth);
}

.dialog-base textarea {
  resize: vertical;
  min-height: 60px;
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

/* Wraps an <input> + sibling clear button on one row.
   Needed because iOS Safari <input type="date"> has no native clear control. */
.input-with-clear {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}
.input-with-clear > input { flex: 1; }
.input-with-clear > .btn-secondary { min-height: auto; padding: 0.4rem 0.75rem; }

.dialog-close-btn {
  background: transparent;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  padding: 0.2rem 0.5rem;
  cursor: pointer;
  color: var(--wind-stone);
  border-radius: 4px;
}

.dialog-close-btn:hover {
  color: var(--wind-deep);
  background: var(--wind-sand);
}

/* ===== Phase 14a: floating action button + agent popover ===== */

.fab {
  position: fixed;
  width: 3.6rem;
  height: 3.6rem;
  border-radius: 999px;
  border: none;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.4rem;
  z-index: 100;
  transition: transform 150ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 150ms;
}

.fab:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
}

.fab--agent {
  background: var(--agent-tint, var(--sea-deep, var(--wind-deep)));
  color: white;
}

/* Phase 10-IA Slice 1: bottom-right anchor for the agent FAB on admin
   financial pages (DICE / billpay / lending / financial). Bottom offset
   clears the fixed tab-bar (.has-tab-bar adds 64px padding-bottom; FAB
   sits 88px from the viewport edge to leave a comfortable gap). */
.fab--agent-br {
  right: 16px;
  bottom: 88px;
}
@media (min-width: 1024px) {
  .fab--agent-br {
    right: 24px;
    bottom: 96px;
  }
}

/* Phase 10-IA Slice 1: sub-page rail on /us/dice glance — cards linking to
   admin financial sub-pages (billpay, lending, family cash-flow). Reuses
   the .topic-group / .home-card visual register. */
.sub-page-rail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  margin: 0.75rem 0 1.25rem;
}
@media (min-width: 700px) {
  .sub-page-rail { grid-template-columns: repeat(3, 1fr); }
}
/* #216-adjacent, 2026-08-28 — the debt hero's REVOLVING/INSTALLMENT cards became
   <button>s that select their segment (the summary now leads to its own list). Reset the
   UA button chrome so they render byte-identically to the .hero-card <div>s beside them,
   and inherit the rail card's hover/focus affordance since they are now interactive. */
.hero-card--segment {
  font: inherit;
  color: inherit;
  text-align: left;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  width: 100%;
}
.hero-card--segment:hover { box-shadow: var(--shadow-card-hover); }
.hero-card--segment:focus-visible {
  outline: 2px solid var(--wind-deep);
  outline-offset: 2px;
}

.sub-page-rail__card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.85rem 1rem;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
  text-decoration: none;
  color: var(--wind-deep);
  transition: border-color 150ms, box-shadow 150ms, transform 150ms;
}
.sub-page-rail__card:hover,
.sub-page-rail__card:focus-visible {
  border-color: var(--sea-deep, var(--wind-stone));
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
  transform: translateY(-1px);
  outline: none;
}
.sub-page-rail__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.05rem;
  font-weight: 500;
}
.sub-page-rail__hint {
  font-size: 0.8rem;
  color: var(--wind-stone);
}

/* Agent popover (Hestia + Metis share this — was metis-popover-*). */
.agent-popover {
  border: none;
  border-radius: 12px;
  padding: 0;
  max-width: 520px;
  width: 95%;
  max-height: 80vh;
  max-height: 80dvh;  /* #117 slice 1: shrink with the iOS keyboard */
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
  background: var(--wind-white);
}

.agent-popover::backdrop {
  background: rgba(20, 16, 12, 0.4);
}

.agent-popover__inner {
  display: flex;
  flex-direction: column;
  height: 70vh;
  /* #117 slice 1: dvh so the popover (header / thread / composer) shrinks with
     the iOS keyboard instead of leaving the composer hidden below it. */
  height: 70dvh;
}

.agent-popover__header {
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--wind-dune);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--sea-mist, var(--wind-sand));
  border-radius: 12px 12px 0 0;
}

.agent-popover__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.2rem;
  flex: 1;
  color: var(--wind-deep);
}

.agent-popover__thread {
  flex: 1;
  overflow-y: auto;
  padding: 1rem 1.25rem;
}

.agent-popover__form {
  border-top: 1px solid var(--wind-dune);
  padding: 0.75rem 1rem;
  display: flex;
  gap: 0.5rem;
  align-items: end;
}

.agent-popover__input {
  flex: 1;
  resize: none;
  padding: 0.5rem;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  font: inherit;
}

/* ===== Phase 14d: agent strip (sticky-top headline interaction) =====
   Replaces the floating .fab pattern on dashboard surfaces (/us/minx, /us/ditl)
   so the agent input is always reachable in zero scroll. Tap the avatar to
   expand into the full-thread .agent-popover dialog. */

.agent-strip {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--sea-mist, var(--wind-sand));
  border-bottom: 1px solid var(--wind-dune);
  padding: 0.65rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  margin-bottom: 0.75rem;
}

.agent-strip__expand {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 999px;
  background: var(--agent-tint, var(--sea-deep, var(--wind-deep)));
  color: white;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.2rem;
  font-weight: 500;
  border: none;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 150ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 150ms;
  padding: 0;
}

.agent-strip__expand:hover,
.agent-strip__expand:focus-visible {
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  outline: none;
}

.agent-strip__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  margin: 0;
}

.agent-strip__input {
  flex: 1;
  min-width: 0;
  resize: none;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  background: var(--wind-white);
  font: inherit;
  font-size: 0.95rem;
  min-height: 2.4rem;
  max-height: 5rem;
  color: var(--wind-earth);
}

.agent-strip__input:focus-visible {
  outline: 2px solid var(--agent-tint, var(--wind-amber));
  outline-offset: 1px;
  border-color: var(--agent-tint, var(--wind-amber));
}

.agent-strip__send {
  flex-shrink: 0;
}

@media (max-width: 743px) {
  .agent-strip {
    flex-wrap: wrap;
  }
  .agent-strip__form {
    flex: 1 1 100%;
    order: 3;
    margin-top: 0.5rem;
  }
}

.messages-container {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
}

#message-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  justify-content: flex-end;
}

.message {
  max-width: 80%;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  animation: none;
}
.message .msg-avatar {
  margin-top: 2px;
  width: 32px;
  height: 32px;
}
.message .msg-bubble-wrap {
  flex: 1;
  padding: 8px 12px;
  background: var(--wind-white);
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(90, 80, 65, 0.06);
  min-width: 0;
}

.message.mine {
  align-self: flex-end;
  flex-direction: row-reverse;
}
.message.mine .msg-bubble-wrap {
  background: var(--wind-mine-bubble);
}

.message.msg-new {
  animation: fadeIn 0.2s ease-in;
}

/* Figure bubble — historical voice (Phase 6a). Distinct parchment treatment
   so a kid instantly sees "this isn't Notus" without reading the name. */
.message.figure-bubble .msg-body {
  background: var(--paper-bg);
  border: 1px solid var(--paper-border);
  border-radius: 10px;
  padding: 10px 14px;
  color: var(--paper-text);
  font-family: Georgia, 'Times New Roman', serif;
  box-shadow: 0 2px 6px rgba(120, 90, 50, 0.10);
}
.message.figure-bubble .msg-name {
  color: var(--paper-accent) !important;
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-weight: 700;
}

/* Homeschool file library (Phase 6c) */
.files-filters {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.files-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}
.file-card {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
  padding: 14px 16px;
  display: block;
  text-decoration: none;
  color: var(--wind-deep);
  position: relative;
  transition: box-shadow 0.15s ease;
}
.file-card:hover { box-shadow: 0 4px 12px rgba(90, 80, 65, 0.08); }
.file-card.archived { opacity: 0.6; }
.file-kind-badge {
  display: inline-block;
  background: var(--wind-sand);
  color: var(--wind-earth);
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.75rem;
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.file-title {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 6px;
  line-height: 1.3;
}
.file-meta {
  display: flex;
  gap: 8px;
  font-size: 0.8rem;
  color: var(--wind-drift);
  flex-wrap: wrap;
}
.file-admin-actions {
  margin-top: 10px;
  display: flex;
  gap: 10px;
}
.file-body {
  max-width: 720px;
  margin: 0 auto;
  padding: 24px;
  background: var(--wind-white);
  border-radius: 10px;
  border: 1px solid var(--wind-dune);
  line-height: 1.6;
}
.file-body h1, .file-body h2, .file-body h3 {
  color: var(--wind-deep);
  margin-top: 1.2em;
}
.file-body h1 { page-break-after: avoid; }
.file-body table { border-collapse: collapse; margin: 12px 0; }
.file-body th, .file-body td {
  border: 1px solid var(--wind-dune);
  padding: 6px 10px;
  text-align: left;
}
.file-body pre {
  background: var(--wind-sand);
  padding: 12px;
  border-radius: 6px;
  overflow-x: auto;
  page-break-inside: avoid;
}
.file-body code {
  background: var(--wind-sand);
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 0.9em;
}
.file-body pre code { background: none; padding: 0; }
.file-body ul, .file-body ol { margin: 8px 0 12px; padding-left: 24px; }
.file-body blockquote {
  border-left: 3px solid var(--wind-dune);
  margin: 12px 0;
  padding: 4px 12px;
  color: var(--wind-earth);
  font-style: italic;
}
.file-empty, .file-loading {
  text-align: center;
  padding: 40px;
  color: var(--wind-drift);
}

/* Sheet music (Phase 6d) — server-rendered SVG from abcjs */
.sheet-music, .sheet-music-inline {
  background: #fff;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid var(--wind-dune);
  margin: 8px 0;
}
.sheet-music-svg svg,
.sheet-music-inline svg {
  width: 100%;
  height: auto;
  display: block;
  max-width: 100%;
}
.sheet-music-inline { max-height: 50vh; overflow-y: auto; }
.sheet-music-abc { margin-top: 16px; font-size: 0.9rem; color: var(--wind-earth); }
.sheet-music-abc summary { cursor: pointer; color: var(--wind-drift); padding: 6px 0; }
.sheet-music-abc pre { background: var(--wind-sand); padding: 10px; border-radius: 6px; overflow-x: auto; font-size: 0.85rem; }
@media print { .sheet-music-abc { display: none; } }

/* Notus chat: file pill rendered in reply when Notus includes a /us/homeschool/files/... URL */
.file-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--wind-sand);
  border: 1px solid var(--wind-dune);
  border-radius: 999px;
  padding: 4px 12px;
  color: var(--wind-deep);
  text-decoration: none;
  font-size: 0.9rem;
  margin: 2px 0;
}
.file-pill:hover { background: var(--wind-dune); }

/* Homeschool — curriculum tracker (Phase 6b) */
.homeschool-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.subject-pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  flex: 1;
}
.subject-pills .pill {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  color: var(--wind-earth);
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 0.85rem;
  cursor: pointer;
}
.subject-pills .pill.active {
  background: var(--wind-amber);
  color: white;
  border-color: var(--wind-amber);
}
.topics-groups {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.topic-group {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
  padding: 4px 14px;
}
.topic-group summary {
  font-weight: 600;
  padding: 10px 0;
  cursor: pointer;
  color: var(--wind-deep);
}
.topic-group summary .count {
  color: var(--wind-drift);
  font-weight: 400;
}
.topic-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 0 12px;
}
.topic-card {
  background: var(--wind-sand);
  border-radius: 8px;
  padding: 10px 14px;
  border: 1px solid var(--wind-dune);
}

/* Authored homeschool documents (#186). Same visual register as .topic-card — same tokens,
   radii and spacing — with link affordance and a summary line. */
.homeschool-docs {
  margin-top: 26px;
  /* body.has-tab-bar's 64px is not enough here — measured 26px of the last card still behind
     the fixed bar at true scroll bottom. Same 80px workaround the form rule above uses. */
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
.homeschool-docs__title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--wind-deep);
  margin: 0 0 2px;
}
.homeschool-docs__lede {
  color: var(--wind-drift);
  font-size: 0.85rem;
  margin: 0 0 12px;
}
.homeschool-docs__lede kbd {
  font: inherit;
  background: var(--wind-sand);
  border: 1px solid var(--wind-dune);
  border-radius: 4px;
  padding: 0 4px;
}
/* Current-chapter picker (#189) — reuses the card register, no new visual language. */
.homeschool-unit { display: flex; align-items: center; gap: 10px; padding: 4px 0 12px; }
.unit-select {
  font: inherit;
  padding: 7px 10px;
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  background: var(--wind-sand);
  color: var(--wind-deep);
}
.unit-status { font-size: 0.82rem; color: var(--wind-drift); }

.doc-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 0 12px;
}
.doc-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--wind-sand);
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  padding: 10px 14px;
  text-decoration: none;
  color: inherit;
}
.doc-card:hover,
.doc-card:focus-visible {
  border-color: var(--wind-amber);
}
.doc-card__title {
  font-weight: 600;
  color: var(--wind-deep);
}
.doc-card__meta {
  font-size: 0.78rem;
  color: var(--wind-drift);
}
/* Format badge — you should know whether a link gives you an HTML page or a plain-text
   markdown file BEFORE you click it. Derived from the extension, so it cannot lie. */
.doc-card__format {
  display: inline-block;
  margin-right: 7px;
  padding: 0 5px;
  border: 1px solid var(--wind-dune);
  border-radius: 3px;
  font-size: 0.68rem;
  letter-spacing: .04em;
  color: var(--wind-deep);
  background: var(--wind-white);
}
.doc-card__summary {
  font-size: 0.85rem;
  line-height: 1.45;
}
.topic-head {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.82rem;
  color: var(--wind-drift);
  margin-bottom: 4px;
}
.topic-subject { font-weight: 600; color: var(--wind-earth); }
.topic-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--wind-deep);
  margin-bottom: 4px;
}
.topic-meta {
  font-size: 0.85rem;
  color: var(--wind-drift);
  margin-bottom: 6px;
}
.topic-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 0.85rem;
}
.hint-inline {
  color: var(--wind-drift);
  font-weight: 400;
  font-size: 0.85em;
}
.error-msg {
  color: var(--wind-red);
  font-size: 0.9rem;
  margin-top: 8px;
}

/* Admin recent-figure-turns panel */
.figure-turns-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.figure-turn {
  background: var(--paper-bg);
  border: 1px solid var(--paper-border);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 0.9rem;
}
.figure-turn-head {
  margin-bottom: 6px;
}
.figure-turn-name {
  color: var(--paper-accent);
  font-variant: small-caps;
  letter-spacing: 0.05em;
  font-weight: 700;
}
.figure-turn-body {
  color: var(--paper-text);
  font-family: Georgia, 'Times New Roman', serif;
  line-height: 1.45;
  white-space: pre-wrap;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes gentleBreeze {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

.msg-header {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
}

.msg-name {
  font-size: 0.8rem;
  font-weight: 600;
}

.msg-time {
  font-size: 0.7rem;
  color: var(--wind-drift);
}

.msg-body {
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--wind-deep);
  white-space: pre-wrap;
  word-break: break-word;
}
.msg-body p {
  margin: 0 0 0.6em;
}
.msg-body p:last-child {
  margin-bottom: 0;
}
.msg-body strong {
  font-weight: 600;
  color: var(--wind-deep);
}
.msg-body em {
  font-style: italic;
}
.msg-body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.88em;
  background: rgba(0,0,0,0.05);
  padding: 1px 5px;
  border-radius: 4px;
}
.msg-body pre {
  background: rgba(0,0,0,0.05);
  padding: 10px 12px;
  border-radius: 6px;
  overflow-x: auto;
  margin: 0.4em 0;
}
.msg-body pre code {
  background: none;
  padding: 0;
  font-size: 0.85em;
}

.msg-image {
  max-width: 280px;
  max-height: 280px;
  border-radius: 8px;
  margin-top: 4px;
  cursor: pointer;
}

.typing-indicator {
  font-size: 0.8rem;
  color: var(--wind-drift);
  padding: 4px 0;
  font-style: italic;
  animation: gentleBreeze 2.5s ease-in-out infinite;
}

.load-more {
  text-align: center;
  padding: 12px;
}

.load-more button {
  background: var(--wind-white);
  color: var(--wind-stone);
  border: 1px solid var(--wind-shore);
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 0.85rem;
  cursor: pointer;
  transition: background 0.15s;
}

.load-more button:hover {
  background: var(--wind-dune);
}

.chat-input-area {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 12px 16px;
  background: var(--wind-white);
  border-top: 1px solid var(--wind-dune);
  flex-shrink: 0;
}

.chat-input-area textarea {
  flex: 1;
  background: var(--wind-sky);
  border: 1px solid var(--wind-shore);
  border-radius: 20px;
  padding: 10px 16px;
  font-size: 1rem;
  color: var(--wind-deep);
  resize: none;
  outline: none;
  max-height: 120px;
  line-height: 1.4;
  font-family: inherit;
}

.chat-input-area textarea:focus {
  border-color: var(--wind-amber);
  box-shadow: 0 0 0 3px var(--wind-amber-light);
}

.attach-btn,
.send-btn {
  background: none;
  border: none;
  color: var(--wind-drift);
  cursor: pointer;
  padding: 8px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s;
}

.send-btn {
  color: var(--wind-amber);
}

.send-btn:disabled {
  color: var(--wind-shore);
  cursor: default;
}

.attach-btn:hover,
.send-btn:hover:not(:disabled) {
  background: var(--wind-sand);
}

/* ===== Push notification prompt ===== */

.push-prompt {
  position: fixed;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.9rem;
  color: var(--wind-earth);
  z-index: 1000;
  box-shadow: 0 4px 20px rgba(90, 80, 65, 0.15);
}

.push-prompt button {
  padding: 6px 14px;
  border-radius: 6px;
  border: none;
  cursor: pointer;
  font-size: 0.85rem;
}

#push-yes {
  background: var(--wind-amber);
  color: var(--wind-white);
}

#push-no {
  background: var(--wind-sand);
  color: var(--wind-stone);
}

/* ===== Chores page ===== */

.chores-page {
  background: var(--wind-sky);
  color: var(--wind-earth);
  overflow-y: auto;
  min-height: 100vh;
}

.chores-container {
  max-width: 640px;
  margin: 0 auto;
  padding: 16px;
}

.chore-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.chore-card {
  background: var(--wind-white);
  border-radius: 12px;
  padding: 16px;
  box-shadow: var(--shadow-card);
}

.chore-card.status-pending {
  border-left: 3px solid var(--wind-amber);
}

.chore-card.status-done {
  border-left: 3px solid var(--wind-sunset);
}

.chore-card.status-verified {
  border-left: 3px solid var(--wind-green);
}

.chore-card.status-rejected {
  border-left: 3px solid var(--wind-red);
}

.chore-card.review {
  border-left: 3px solid var(--wind-sunset);
}

.chore-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.chore-card-header h3 {
  font-size: 1rem;
  font-weight: 600;
  color: var(--wind-deep);
}

.chore-status {
  font-size: 0.75rem;
  color: var(--wind-stone);
  background: var(--wind-sand);
  padding: 2px 8px;
  border-radius: 4px;
}

.chore-badge {
  font-size: 0.75rem;
  color: var(--wind-stone);
}

.chore-desc {
  font-size: 0.9rem;
  color: var(--wind-earth);
  margin-bottom: 8px;
  line-height: 1.4;
}

.chore-meta {
  display: flex;
  gap: 16px;
  font-size: 0.8rem;
  color: var(--wind-drift);
}

.chore-user {
  font-size: 0.85rem;
  font-weight: 600;
}

.admin-note {
  font-size: 0.85rem;
  color: var(--wind-sunset);
  margin-top: 8px;
  font-style: italic;
}

.btn-complete {
  margin-top: 12px;
  width: 100%;
  padding: 10px;
  font-size: 0.95rem;
  font-weight: 600;
  background: var(--wind-green);
  color: var(--wind-white);
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-complete:hover {
  filter: brightness(0.95);
}

/* ===== Phase 14a: canonical button family =====
   Source of truth: replaces the older .btn-primary/.btn-secondary
   (full-width chores style) and the parallel .primary-btn/.secondary-btn
   inline pattern. .btn-block carries forward the full-width affordance
   for forms that need it (e.g. chores create form). */

.btn-primary,
.btn-secondary,
.btn-danger {
  min-height: 44px;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid transparent;
  transition: filter 0.15s, background 0.15s, box-shadow 0.15s;
}

.btn-primary {
  background: var(--wind-amber);
  color: var(--wind-on-amber);
  border-color: var(--wind-amber-deep);
}

.btn-primary:hover:not([disabled]) { filter: brightness(0.95); }
.btn-primary[disabled] { opacity: 0.5; cursor: not-allowed; }

.btn-secondary {
  background: var(--wind-white);
  color: var(--wind-deep);
  border-color: var(--wind-dune);
}

.btn-secondary:hover { background: var(--wind-sand); }

/* Segment toggle active state — used by .debt-tab on /us/dice/debt and
   .tax-tab on /us/dice/taxonomy. Filled amber when selected so the active
   segment is unambiguous; without this rule the .active class on those
   buttons does nothing.

   #92 S3 — was --wind-white on --wind-amber, the exact pairing
   docs/design-system.md records as failing WCAG AA (3.0 light / 2.2 dark).
   --wind-on-amber exists for it and already carries .btn-primary below
   (5.3 / 7.3). Inherited fix, taken while the rule was being touched for the
   fourth taxonomy tab; it applies sitewide, which is the point. */
.btn-secondary.active,
.btn-secondary[aria-selected="true"] {
  background: var(--wind-amber);
  color: var(--wind-on-amber);
  border-color: var(--wind-amber);
}
.btn-secondary.active:hover,
.btn-secondary[aria-selected="true"]:hover {
  background: var(--wind-amber-hover);
  border-color: var(--wind-amber-hover);
}

/* /us/dice/debt tab group — reads more clearly as a "Show:" filter than the
   prior two-independent-buttons treatment. Per James 2026-05-23: "I did not
   see that as a filter." Containing the buttons inside a labelled pill-group
   gives them tab-of-a-set affordance instead of standalone-CTA affordance.

   #166 — the `.segment-tab*` aliases below are the SAME rules under a neutral
   name, added when the Best-of "By month" toggle needed this pattern on
   /us/photos/favorites. They add zero new styles (identical rendering) — the
   plan's "don't invent a third bespoke toggle CSS" holds — but a photos page
   shouldn't have to wear a class that says `debt`, which is the same label-doing-
   a-key's-job problem that made .photos-month its own family. New surfaces
   should use .segment-tab*; the .debt-* names stay for /us/dice/debt. */
.debt-tab-group,
.segment-tab-group {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.debt-tab-label,
.segment-tab-label {
  font-size: 0.85rem;
  color: var(--wind-stone);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}
.debt-tab-pills,
.segment-tab-pills {
  display: inline-flex;
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface-subtle);
}
.debt-tab-pills .debt-tab,
.segment-tab-pills .segment-tab {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 6px 14px;
  cursor: pointer;
  /* #167 — was --wind-stone (secondary text) on --surface-subtle: 2.77:1 light / 3.97:1 dark,
     both under AA. --wind-earth is the body-text step and clears it in both themes. The active
     pill is distinguished by its amber FILL, not by being the only readable one. */
  color: var(--wind-earth);
  font-weight: 500;
  font-family: inherit;
  font-size: 0.95rem;
  transition: background 0.1s, color 0.1s;
}
.debt-tab-pills .debt-tab + .debt-tab,
.segment-tab-pills .segment-tab + .segment-tab {
  border-left: 1px solid var(--border-soft);
}
/* #166 — keyboard focus ring. These pills are NOT .btn-secondary (checked:
   dice-debt.html:47 renders `class="debt-tab"` alone), so the
   .btn-primary/.btn-secondary/.btn-danger:focus-visible rule above never
   applied to them — they had no visible focus indicator at all. Matches that
   rule's treatment; `inset` because the pills sit inside an overflow:hidden
   group that would clip an outer ring. Additive a11y fix — also repairs the
   pre-existing gap on /us/dice/debt, and changes nothing for mouse users. */
.debt-tab-pills .debt-tab:focus-visible,
.segment-tab-pills .segment-tab:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: -2px;
}
.debt-tab-pills .debt-tab:hover,
.segment-tab-pills .segment-tab:hover {
  background: var(--wind-amber-light);
  color: var(--wind-amber);
}
/* #167 — the active pill was --wind-white on --wind-amber: 3.02:1 in BOTH themes, against a
   4.5:1 target (the text is 15.2px, so the 3:1 large-text allowance does not apply). This is the
   same pairing #155 already solved for .btn-primary, and --wind-on-amber exists for exactly it:
   a non-flipping dark brown, because amber stays light in both themes. Reusing the token rather
   than minting a value keeps every solid-amber fill on one rule. */
.debt-tab-pills .debt-tab.active,
.debt-tab-pills .debt-tab[aria-selected="true"],
.segment-tab-pills .segment-tab.active,
.segment-tab-pills .segment-tab[aria-selected="true"] {
  background: var(--wind-amber);
  color: var(--wind-on-amber);
  border-color: transparent;
}
.debt-tab-pills .debt-tab.active:hover,
.debt-tab-pills .debt-tab[aria-selected="true"]:hover,
.segment-tab-pills .segment-tab.active:hover,
.segment-tab-pills .segment-tab[aria-selected="true"]:hover {
  background: var(--wind-amber-hover);
  color: var(--wind-on-amber);
}

.btn-danger {
  background: var(--wind-white);
  color: var(--status-danger-text);
  border-color: var(--status-danger-border);
}

.btn-danger:hover { background: var(--status-danger-bg); }

.btn-primary:focus-visible,
.btn-secondary:focus-visible,
.btn-danger:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}

/* Full-width modifier — used by the chores Create Chore form. */
.btn-block {
  width: 100%;
  padding: 12px;
  font-size: 1rem;
  font-weight: 600;
  margin-top: 12px;
  border-radius: 8px;
  min-height: auto;
}

/* Admin tabs */
.admin-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  overflow-x: auto;
}

.tab-btn {
  padding: 8px 16px;
  font-size: 0.85rem;
  font-weight: 500;
  background: var(--wind-white);
  color: var(--wind-stone);
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}

.tab-btn.active {
  background: var(--wind-amber);
  color: var(--wind-white);
  border-color: var(--wind-amber);
}

.tab-content {
  display: none;
}

.tab-content.active {
  display: block;
}

/* Chore create form */
.chore-form {
  background: var(--wind-white);
  border-radius: 12px;
  padding: 20px;
  box-shadow: var(--shadow-card);
}

.chore-form label {
  display: block;
  font-size: 0.9rem;
  color: var(--wind-stone);
  margin-bottom: 16px;
}

.chore-form input[type="text"],
.chore-form input[type="number"],
.chore-form input[type="date"],
.chore-form textarea,
.chore-form select {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 10px 12px;
  font-size: 0.95rem;
  background: var(--wind-sky);
  border: 1px solid var(--wind-shore);
  border-radius: 8px;
  color: var(--wind-deep);
  outline: none;
  font-family: inherit;
}

.chore-form input:focus,
.chore-form textarea:focus,
.chore-form select:focus {
  border-color: var(--wind-amber);
  box-shadow: 0 0 0 3px var(--wind-amber-light);
}

.chore-form fieldset {
  border: 1px solid var(--wind-shore);
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 16px;
}

.chore-form fieldset legend {
  font-size: 0.9rem;
  color: var(--wind-stone);
  padding: 0 8px;
}

.chore-form fieldset label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  color: var(--wind-earth);
}

.chore-form fieldset input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--wind-amber);
}

#custom-days {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: 16px;
  padding: 8px 12px;
}

#custom-days label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.85rem;
  color: var(--wind-earth);
  margin-bottom: 0;
}

.form-msg {
  margin-top: 12px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 0.85rem;
}

.form-msg.error {
  background: var(--wind-red-light);
  color: var(--wind-red);
}

.form-msg.success {
  background: var(--wind-green-light);
  color: var(--wind-green);
}

/* Review actions */
.review-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
}

.review-note {
  flex: 1;
  padding: 8px 12px;
  font-size: 0.85rem;
  background: var(--wind-sky);
  border: 1px solid var(--wind-shore);
  border-radius: 8px;
  color: var(--wind-deep);
  outline: none;
}

.btn-approve {
  padding: 8px 16px;
  font-size: 0.85rem;
  font-weight: 600;
  background: var(--wind-green);
  color: var(--wind-white);
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

.btn-reject {
  padding: 8px 16px;
  font-size: 0.85rem;
  font-weight: 600;
  background: var(--wind-red);
  color: var(--wind-white);
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

/* Points table */
.points-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 12px;
}

.points-table th,
.points-table td {
  padding: 10px 12px;
  text-align: left;
  font-size: 0.9rem;
}

.points-table th {
  color: var(--wind-stone);
  border-bottom: 1px solid var(--wind-dune);
  font-weight: 500;
}

.points-table td {
  color: var(--wind-earth);
  border-bottom: 1px solid var(--wind-sand);
}

.empty-msg {
  color: var(--wind-drift);
  font-size: 0.95rem;
  text-align: center;
  padding: 32px 16px;
}

/* ===== Bottom tab bar ===== */
.tab-bar {
  display: flex;
  align-items: stretch;
  justify-content: space-around;
  background: var(--wind-white);
  border-top: 1px solid var(--wind-dune);
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 50;
}

/* On scrollable pages, fix the tab bar to the viewport bottom */
body.has-tab-bar:not(.chat-page):not(.app-shell) {
  padding-bottom: calc(64px + env(safe-area-inset-bottom));
  /* scroll-padding so scrollIntoView / anchor jumps clear the fixed bar — fixes #31 */
  scroll-padding-bottom: calc(64px + env(safe-area-inset-bottom));
}

/* #31 v3: scroll-padding-bottom + scroll-margin-bottom both ignored by
   Playwright's scrollIntoViewIfNeeded under our chores-page overflow setup.
   Workaround that actually works: forms in tab-bar pages get 80px of bottom
   padding so the last button (typically a submit) sits 80px above the form
   end — clear of the fixed tab bar regardless of scroll position. */
body.has-tab-bar:not(.chat-page):not(.app-shell) form {
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
body.has-tab-bar:not(.chat-page):not(.app-shell) .modal form,
body.has-tab-bar:not(.chat-page):not(.app-shell) dialog form {
  padding-bottom: 0;
}
/* #206 — same #31 class, new victim: when the last section on a page ends in a
   BUTTON-bearing div rather than a form (the /us/adults memory-review list became
   last once the Notus panels left), its bottom buttons sat under the fixed bar.
   Give the last section the same 80px clearance forms get. */
body.has-tab-bar:not(.chat-page):not(.app-shell) .admin-section:last-of-type {
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
/* #216 C2 — the same clearance, on the CONTAINER, for pages whose last
   .admin-section is `hidden`.
   `/us/dice/debt` and `/us/dice/tax-report` both end in a sibling section that
   JS toggles (`#installment-section`, `#drill-section`). When it is hidden the
   80px above lands on a `display:none` element and the visible section gets
   nothing — `:last-of-type` is a DOM-position selector and knows nothing about
   visibility.
   ⚠ STRICTLY ADDITIVE. The rule above is NOT moved, narrowed or replaced: it
   carries its own #206 provenance, `/us/adults` is live, and `admin.html` has
   seven `.admin-section`s whose last is the button-bearing panel #206
   deliberately kept. Scoped to the two page bodies rather than applied to
   `.admin-financial-container` site-wide, which would double the clearance on
   every page whose last section IS visible (e.g. /us/dice/bills). */
body.has-tab-bar.dice-debt-page .admin-financial-container,
body.has-tab-bar.dice-tax-report-page .admin-financial-container {
  padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
body.has-tab-bar:not(.chat-page):not(.app-shell) .tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* #144: iOS Safari defers re-rasterizing position:fixed elements during a
     momentum-scroll gesture, so the bar appears to drift upward off the
     bottom edge mid-scroll and snaps back when scrolling stops (Erin, iPad,
     /us/photos). Promoting the bar to its own GPU compositor layer keeps it
     pinned during the gesture. translateZ(0) does NOT change the element's own
     fixed positioning — a transform on the fixed element itself still resolves
     `bottom: 0` against the viewport (only a transform on an *ancestor* would
     capture it). The definitive cure is the inner-scroll 100dvh shell (#117),
     deferred here because it collides with the lightbox body-scroll-lock
     (photo-lightbox.js) and needs on-device verification. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* On inner-scroll flex-column shells (chat/notus via .chat-page; the #117
   .app-shell opt-in pages — photos-today/favorites), the tab bar is a normal
   flex child pinned at the column bottom. No position:fixed → no GPU layer to
   strand → the #144 iPad strand is structurally impossible here. Its own
   unconditional `padding-bottom: env(safe-area-inset-bottom)` (.tab-bar above)
   still clears the home indicator. */
body.chat-page.has-tab-bar .tab-bar,
body.app-shell.has-tab-bar .tab-bar {
  flex-shrink: 0;
}

.tab-bar .tab {
  flex: 1;
  position: relative; /* Phase 10-IA Slice 2: anchors .tab-badge */
  /* Phase 14h: required so flex children (.tab-label) cannot push the tab
     wider than its 1fr share. Without this, admin's 10-tab nav at 390px
     phone width overflows the viewport because labels like "Calendar" are
     intrinsically wider than ~39px. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 8px 4px 6px;
  text-decoration: none;
  color: var(--wind-drift);
  font-size: 0.72rem;
  font-weight: 500;
  border-top: 2px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

.tab-bar .tab:hover {
  color: var(--wind-earth);
}

.tab-bar .tab.active {
  color: var(--wind-amber);
  border-top-color: var(--wind-amber);
}

/* Phase 10-IA Slice 2: count badge (e.g. DICE inbox has N items). Small pill with
   high-contrast accent, rendered ONLY when the count is > 0 — a badge showing "0"
   is noise, and the primitive's whole job is "there is something here".

   #168 S3 split the rule in two so the pill can be used OUTSIDE the tab bar (the
   webhook-DLQ rail card on the DICE glance). Visuals live on the bare class; the
   tab bar keeps its own absolute positioning against `.tab-bar .tab`'s
   `position: relative`. Nav rendering is unchanged. */
.tab-badge {
  display: inline-block;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--wind-amber, #C4892E);
  color: var(--wind-white, #fff);
  font-size: 0.66rem;
  font-weight: 600;
  line-height: 1.1rem;
  text-align: center;
}

.tab-bar .tab-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  pointer-events: none;
}

.tab-bar .tab-icon {
  font-size: 1.35rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.tab-bar .tab-icon svg {
  width: 22px;
  height: 22px;
  color: currentColor;
}

.tab-bar .tab-label {
  font-size: 0.7rem;
  letter-spacing: 0.01em;
  /* Phase 14h: truncate if container is narrower than the label. Pairs
     with min-width: 0 on .tab so very-narrow viewports (e.g. phone with
     all 10 admin tabs visible) clip rather than overflow the viewport. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* #76 P2-8 — two-column form row for grouping related inputs (e.g. Source +
   Amount in the Bill Pay composer modal) so the modal compresses vertically
   instead of stacking every field. Each column auto-sizes label + input as
   block children; the row stacks under 480px to stay readable on phones. */
.form-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.form-row__col {
  flex: 1;
  min-width: 0;
}
@media (max-width: 480px) {
  .form-row { flex-direction: column; gap: 0; }
}

/* #78 — Bill Pay recipient cards as <details>/<summary>. Default collapsed
   to a single-row chip showing display name + category badge + archived
   badge + chevron; expanding reveals address + account_number + sync
   timestamp + Edit/Archive buttons. Native <details> gets keyboard +
   accessibility for free; toggle event in billpay.js persists open state
   across data-refresh re-renders. Closes 81-recipient wall-of-text. */
.recipient-row {
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  margin-bottom: 6px;
  background: var(--wind-white);
}
.recipient-row > summary.recipient-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.recipient-row > summary.recipient-summary::-webkit-details-marker {
  display: none;
}
.recipient-row > summary.recipient-summary > strong {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recipient-row .recipient-chevron {
  display: inline-block;
  font-size: 1.2rem;
  color: var(--wind-stone);
  transition: transform 0.15s ease;
  margin-left: auto;
}
.recipient-row[open] .recipient-chevron {
  transform: rotate(90deg);
}
.recipient-row > summary.recipient-summary:hover {
  background: var(--wind-sand);
}
.recipient-row[open] > summary.recipient-summary {
  border-bottom: 1px solid var(--border-soft);
}
.recipient-row .recipient-body {
  padding: 10px 14px 12px;
}
.recipient-row .badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--wind-sand);
  color: var(--wind-earth);
  vertical-align: middle;
}
.recipient-row .badge-category {
  background: var(--wind-amber-light);
  color: var(--wind-deep);
}
.recipient-row .badge-canceled {
  background: var(--wind-dune);
  color: var(--wind-stone);
}

/* .lending-row — shared card primitive for stacked-list rows on
   /us/dice/billpay (Scheduled, Recent activity) + /us/dice/lending (loans).
   Mirrors .recipient-row above so the two list types sit visually consistent
   on the billpay page. Without this rule, .lending-row had no border, padding,
   or gap and three+ rows collapsed into a wall of text. */
.lending-row {
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 6px;
  background: var(--wind-white);
}
/* Recipient rows compose .lending-row + .recipient-row on the same <details>;
   their summary + body already pad themselves, so drop the parent padding. */
.lending-row.recipient-row { padding: 0; }
.lending-row-header {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.lending-row-body {
  margin-top: 0.35rem;
  color: var(--wind-stone);
  font-size: 0.9rem;
}
.lending-row-actions {
  margin-top: 0.6rem;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* #214 F4 S3 — the repayment ledger, expanded inside a lending row. Third child of
   `.lending-row`, the same shape `.snapshot-history` takes inside a debt row: the
   `.register` primitive reused as-is, no new table chrome. */
.repayment-history {
  flex-basis: 100%;
  width: 100%;
  margin-top: 8px;
}
.repayment-history[hidden] { display: none; }
/* `.register tbody tr` is `cursor: pointer` because on the Mercury register the whole row
   opens a drawer. Here only Reverse is clickable, so the pointer would be a lie. */
.repayment-history .register tbody tr { cursor: default; }
.repayment-history .register tbody tr:hover { background: transparent; }
/* #156's touch-target rule, same exception `.snapshot-delete` carries: a `.link-btn` is
   padding-sized at ~25px, which is wrong for the only destructive control in a row. */
.repayment-history .register .reverse-repayment {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Phase 7d Slice C1 — Photos of the Day section on /us/home.
   Year-grouped grid; native <dialog> for full-resolution viewing. Lives
   below the 2-col home grid, above the utility row. iPad-mini-first sizing:
   thumbnails are 1fr in a 3-col grid at narrow viewports, expanding to 5-col
   at ≥720px. Aspect-ratio preserved via padding-bottom on a wrapper would be
   prettier — but `aspect-ratio: 1` is widely supported and keeps the markup
   simpler.

   Untrusted-content note (SEC-7): asset filenames in this section come from
   iCloud / human-set device labels. All renders escape via esc() in
   photos-of-the-day.js. Do not move to innerHTML interpolation. */
.home-photos-today {
  padding: 0 1rem;
  margin: 1rem auto 1.5rem;
  max-width: 1200px;
}
.photos-today__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
  flex-wrap: wrap;
}
.photos-today__title {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: 1.2rem;
  color: var(--wind-deep);
  margin: 0;
  letter-spacing: -0.01em;
}
.photos-today__date {
  font-size: 0.85rem;
  color: var(--wind-deep);
}
.photos-today__empty {
  color: var(--wind-deep);
  font-size: 0.9rem;
  font-style: italic;
  margin: 0;
}

/* Phase 7d adult hero — 3×2 photo grid replacing the Metis/Hestia agent
   strip (2026-05-18 evening). The whole card is a tap-anywhere <a> to
   /us/photos/today; no per-thumbnail modal here (deliberately different
   affordance from the kid teaser-below-grid). */
.home-hero--adult-photos {
  display: block;
  text-decoration: none;
  color: inherit;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  padding: 18px 20px 16px;
  box-shadow: var(--shadow-subtle);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.home-hero--adult-photos:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
}
.adult-photos-hero__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.adult-photos-hero__title {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: 1.2rem;
  color: var(--wind-deep);
  margin: 0;
  letter-spacing: -0.01em;
}
.adult-photos-hero__date {
  font-size: 0.85rem;
  color: var(--wind-stone);
}
.adult-photos-hero__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
.adult-photos-hero__grid img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 6px;
  background: var(--wind-sand);
  display: block;
}
/* Phase 7e Slice C4 — cell wrapper holds an img + a heart sibling so the
   heart can absolute-position against the cell. img keeps its existing
   sizing rules above. */
.adult-photos-hero__cell {
  position: relative;
  display: block;
}

/* shelved #206 — the below-grid teaser existed because kids had no photos hero;
   every role now gets the hero and the server no longer emits #photos-today on
   /us/home. Rule kept unconditional as belt-and-braces for the dormant markup. */
#photos-today { display: none; }

/* Home teaser — single horizontal row of up to 6 photos sampled across
   years. Lives only on /us/home; the full per-year grid lives on
   /us/photos/today. Tiles are 1:1 with a soft cap so even on iPad the
   row never grows past hero-card height. */
.photos-teaser__row {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 6px;
  margin-bottom: 0.75rem;
}
@media (max-width: 720px) {
  /* On phones, drop to 4 across so each tile stays tappable (~80px+). */
  .photos-teaser__row { grid-template-columns: repeat(4, 1fr); }
  .photos-teaser__row .photo-card:nth-child(n+5) { display: none; }
}
.photos-teaser__chip {
  display: inline-block;
  font-size: 0.9rem;
  color: var(--wind-amber);
  text-decoration: none;
  padding: 2px 0;
}
.photos-teaser__chip:hover {
  color: var(--wind-amber-hover, var(--wind-amber));
  text-decoration: underline;
}

/* #117 Phase 14h-B: the scroll root inside the .photos-today-page shell
   (overrides the .home-photos-today max-width/margin on this element via
   source order). Full-width so the scrollbar sits at the viewport edge; the
   1200px reading column moves to the direct children below. min-height:0 lets
   the flex child shrink so it scrolls instead of growing to content height. */
.photos-today--full {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 1rem;
  max-width: none;
  margin: 0;
}
/* Center each rendered block (year sections, head, empty/error states) at the
   reading width — works for /us/photos/today and /us/photos/favorites, both of
   which render direct-child blocks into the scroll root. */
.photos-today--full > * {
  max-width: 1200px;
  margin-inline: auto;
}

/* #117 Phase 14h-B: inner-scroll shell (mirrors .chat-page:690). Was the
   body-scroll model (overflow-y:auto; min-height:100vh) shared with
   .chores-page — but on /us/photos/today + /us/photos/favorites the photo
   lightbox's body-scroll-lock toggles body{position:fixed}, which stranded the
   position:fixed tab bar mid-grid on iPad (#144). Locking the body to the
   visual viewport (overflow:hidden; 100dvh) with an inner <main data-scroll-root>
   that scrolls makes the tab bar a flex child (.app-shell rule) — no fixed
   element → no strand possible. The lightbox lock now targets the inner root,
   not the body (photo-lightbox.js getScrollRoot). */
.photos-today-page {
  background: var(--wind-sky);
  color: var(--wind-earth);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}
.photos-today__back {
  font-size: 0.9rem;
  color: var(--wind-amber);
  text-decoration: none;
}
.photos-today__back:hover {
  color: var(--wind-amber-hover, var(--wind-amber));
  text-decoration: underline;
}

/* Phase 7e Slice A6 — memory-card chrome per year section.
   Reuses the .home-hero--adult-photos pattern at :2309-2319: white bg,
   subtle border, soft shadow, rounded 14px. Polish #7: amber-dot
   chapter-divider ornament between years (replaces the original
   solid border-top stripe — reads as hand-stitched not printed). */
.photos-year {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  box-shadow: var(--shadow-subtle);
  padding: 22px 18px 14px;
  margin-bottom: 1rem;
  position: relative;
}
.photos-year::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--wind-amber);
  box-shadow:
    -22px 0 0 -2px var(--wind-dune),
     22px 0 0 -2px var(--wind-dune);
}
.photos-year__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}
.photos-year__year {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
  color: var(--wind-deep);
}
.photos-year__hint {
  font-size: var(--text-sm);
  /* #115 — --wind-earth, not --wind-stone: at 14px on the white card stone measures 3.97:1
     (AA wants 4.5:1), earth 7.55:1 — the same adjudication .photos-month__count already
     records below; this rule was the one the #166 gate missed. Re-measured 2026-09-14
     (dark: stone 5.63:1, earth 9.35:1 on #1A1612). */
  color: var(--wind-earth);
}
/* #165 C3 — kid-ages inline in the section header ("· Remy was 10, Charlie was 7").
   Italic for the memory "voice"; --wind-earth passes WCAG AA in both themes. */
.photos-year__ages {
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--wind-earth);
}

/* Phase 7e Slice A5 — hero card at the top of the grid. Anchor of the
   slice per design audit (pop-per-hour rank #1). Random year selection
   per Q7 (not oldest); years-ago badge computed from the chosen photo's
   year. Two-layer overlay for legibility against any photo; year glyph
   in Fraunces at --text-hero. */
.photos-today__hero {
  position: relative;
  display: block;
  width: 100%;
  border: 0;
  padding: 0;
  background: var(--wind-sand);
  border-radius: 16px;
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 16 / 9;
  margin-bottom: 1.25rem;
  box-shadow: var(--shadow-card);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.photos-today__hero:hover,
.photos-today__hero:focus-visible {
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
  outline: none;
}
.photos-today__hero:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}
.photos-today__hero img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photos-today__hero-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.55) 0%,
    rgba(0, 0, 0, 0.15) 35%,
    transparent 55%
  );
  pointer-events: none;
}
.photos-today__hero-badge {
  position: absolute;
  top: 14px;
  left: 14px;
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  /* Phase 7e polish #1 — "found polaroid" badge feel. Inner highlight + soft
     shadow makes it read printed-on, not stamped-on. Fraunces for the
     emotional weight on the most resonant element on the page. */
  background: linear-gradient(180deg,
    rgb(var(--wind-white-rgb) / 0.92),
    rgb(var(--wind-white-rgb) / 0.78));
  box-shadow:
    0 1px 0 rgb(255 255 255 / 0.35) inset,
    0 8px 24px rgba(0, 0, 0, 0.18);
  color: var(--wind-deep);
  font-family: 'Fraunces', Georgia, serif;
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.005em;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.photos-today__hero-year {
  position: absolute;
  bottom: 12px;
  left: 18px;
  /* Phase 7e polish #6 — italic Fraunces at display optical size is
     exquisite; the slight slant adds movement without motion. */
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-style: italic;
  font-variation-settings: "opsz" 144;
  font-size: var(--text-hero);
  color: #fff;
  letter-spacing: -0.02em;
  line-height: 1;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.photos-today__hero:hover .photos-today__hero-year { transform: translateY(-3px); }
@media (prefers-reduced-motion: reduce) {
  .photos-today__hero-year { transition: none; }
  .photos-today__hero:hover .photos-today__hero-year { transform: none; }
}
@media (max-width: 480px) {
  .photos-today__hero { aspect-ratio: 4 / 3; }
  .photos-today__hero-year { font-size: var(--text-3xl); bottom: 10px; left: 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .photos-today__hero { transition: none; }
}
.photos-year__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
@media (min-width: 540px) {
  .photos-year__grid { grid-template-columns: repeat(4, 1fr); }
}
@media (min-width: 720px) {
  .photos-year__grid { grid-template-columns: repeat(5, 1fr); }
}
@media (min-width: 960px) {
  .photos-year__grid { grid-template-columns: repeat(6, 1fr); }
}

/* Single-photo-per-row feed on the "On this day" page (2026-07-09). Scoped to
   #photos-today-full so the Favorites gallery (#photos-favorites-target) and the
   home teaser (.photos-teaser__row) keep their compact grids. We mostly view on
   mobile, where one large photo per row reads far better than a 3-across square
   grid. ID specificity overrides the min-width breakpoints above.

   The card keeps a reserved box (aspect-ratio from .photo-card) so a lazy-loaded
   image can't reflow the feed as it arrives — the manifest carries no per-image
   dimensions, so an unreserved natural-height box collapses pre-load and shifts
   scroll position, breaking the #117 inner-scroll restore contract
   (photo-lightbox.spec.ts:110). object-fit:contain shows the WHOLE photo (no
   face-cropping); instead of a flat sand letterbox, the margin is a soft, scaled,
   blurred copy of the photo's OWN colors (--photo, set per-card by
   photos-today-page.js) — the same immersive backdrop language the lightbox uses.
   Falls back to --wind-dune when --photo is absent (degraded / pre-JS manifests).

   #166 — the same single-photo-per-row feed is opted into by the .photos-feed--natural
   container class, so the Best-of month view on the Favorites page gets it too
   WITHOUT widening the #photos-today-full rules (which stay exactly as shipped).
   The two-class descendant form (0,2,0) outranks the bare .photos-year__grid /
   .photos-month__grid media-query rules (0,1,0) above, which is what the ID was
   buying here — a single container class would have LOST to them. Favorites only
   adds the class in month mode; the flat list keeps its compact grid. */
#photos-today-full .photos-year__grid,
.photos-feed--natural .photos-month__grid {
  grid-template-columns: 1fr;
  gap: 14px;
}
#photos-today-full .photo-card,
.photos-feed--natural .photo-card {
  position: relative;
  background: var(--wind-dune);
  border-radius: 16px;
  box-shadow: var(--shadow-card);
}
#photos-today-full .photo-card::before,
.photos-feed--natural .photo-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: var(--photo, none);
  background-size: cover;
  background-position: center;
  filter: blur(30px) saturate(1.15) brightness(0.9);
  transform: scale(1.3);
}
#photos-today-full .photo-card img,
.photos-feed--natural .photo-card img {
  object-fit: contain;
  position: relative;
  z-index: 1;
}
/* #165 (Phase C1) — natural-aspect cards. When the manifest carries per-image
   dimensions (Phase B2), photos-today-page.js sets an inline `aspect-ratio` +
   the .photo-card--natural class, so the card reserves the photo's REAL aspect
   ratio before the lazy image loads (no CLS) and the photo fills it edge-to-edge
   — no letterbox, no blurred-fill margin. Ultra-tall images are clamped in JS
   (<=1.6:1). Cards WITHOUT dims keep the reserved square + object-fit:contain +
   blur-fill above (graceful degrade / pre-#165 manifests). The contained-card
   chrome (radius, shadow) is retained — the redesign changes the box shape, not
   the card.

   2026-08-02 — "one photo can't dominate the viewport" was a `max-height: 80vh`
   cap, and it defeated the entire feature on desktop. The cap binds whenever the
   card is wider than half the viewport height, and because the natural card is
   `object-fit: cover`, everything past the cap was CROPPED rather than fitted.
   Measured at 1000x560: an 800x1200 portrait and a 1600x900 landscape BOTH
   rendered 930x448 — identical boxes, i.e. no natural aspect at all. It never
   showed on a phone (the tallest permitted card, 1.6:1, still fits under 80vh at
   phone widths) and the C1 E2E pinned its viewport to 414x896, so nothing caught
   it.

   The cap now applies to WIDTH, derived from the same ratio (--photo-ar, emitted
   next to the inline aspect-ratio): a card is at most `80dvh * ratio` wide, so
   its height reaches exactly 80dvh in the limit and never exceeds it — with the
   true aspect ratio intact and zero cropping. A tall photo gets narrower instead
   of cropped, and centres in the column. The cap rides on the wrap as well so the
   heart + caption track the photo's edges rather than the column's.
   `var(--photo-ar, 100)` = effectively uncapped when the property is absent
   (no-dims degrade cards, or a stale-cached JS bundle) — the safe direction.

   ⚠ `width: min(100%, …)`, NOT `max-width` + auto margins. On a grid item an auto
   margin suppresses the default `justify-self: stretch`, so the item falls back
   to shrink-to-fit — and since the card's <img> is `width: 100%` of its parent,
   shrink-to-fit resolves against a lazy image's zero intrinsic width and the
   whole card collapses. Measured while building this: cards rendered 0x0 (the
   first, whose image had loaded, rendered 105px wide on a 414px viewport). An
   explicit width is immune — auto margins then only centre it. */
#photos-today-full .photo-card-wrap,
.photos-feed--natural .photo-card-wrap,
#photos-today-full .photo-card--natural,
.photos-feed--natural .photo-card--natural {
  width: min(100%, calc(80vh * var(--photo-ar, 100)));
  width: min(100%, calc(80dvh * var(--photo-ar, 100)));
  margin-inline: auto;
}
#photos-today-full .photo-card--natural::before,
.photos-feed--natural .photo-card--natural::before { content: none; }
#photos-today-full .photo-card--natural img,
.photos-feed--natural .photo-card--natural img { object-fit: cover; }
/* #165 (Phase C3) — people names under each card (who's in the photo). Sits in
   the .photo-card-wrap on the feed background; --wind-earth passes WCAG AA on
   both --wind-sky (light) and the dark canvas (theme-aware token). */
#photos-today-full .photo-card__caption {
  padding: 8px 6px 2px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--wind-earth);
}
#photos-today-full .photo-card__video-badge { z-index: 2; }
.photos-feed--natural .photo-card__video-badge { z-index: 2; }

/* #166 — Best-of month sections on /us/photos/favorites ("By month" toggle).
   A DELIBERATELY SEPARATE family from .photos-year*, not a rename of it: that
   one is the year-axis grouping for the On This Day feed (one calendar day
   across many years), this one is the month-axis grouping of favorites. Reusing
   a class that literally says "year" to hold "August 2025" is the label/key
   confusion that rots later (James's call, plan §6.5 #6). Mirrors the
   .photos-year card/section chrome so the two feeds read as one system. */
.photos-month {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  box-shadow: var(--shadow-subtle);
  padding: 12px;
  margin-bottom: 16px;
}
.photos-month__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 10px;
}
.photos-month__label {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}
.photos-month__count {
  font-size: var(--text-sm);
  /* --wind-earth, not the --wind-stone used by the analogous .photos-year__hint:
     at 14px, stone measures 3.97:1 on the light card background (WCAG AA wants
     4.5:1), earth measures 7.55:1. Verified with getComputedStyle in both
     themes during the #166 design gate. */
  color: var(--wind-earth);
}
/* Un-dated favorites (hearted before #166, or from a surface with no date in
   scope) group by heart-date. Say so rather than implying a taken-date we
   don't have — per plan §3 this is the expected steady state, not an error. */
.photos-month__note {
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--wind-earth);
}
.photos-month__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
@media (min-width: 720px) {
  .photos-month__grid { grid-template-columns: repeat(5, 1fr); }
}
@media (min-width: 960px) {
  .photos-month__grid { grid-template-columns: repeat(6, 1fr); }
}
@media (prefers-reduced-motion: no-preference) {
  #photos-today-full .photo-card { transition: transform 0.14s ease, box-shadow 0.14s ease; }
}

.photo-card {
  border: 0;
  padding: 0;
  background: var(--wind-sand);
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 1;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.photo-card:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  outline: none;
}
.photo-card:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  outline: 2px solid var(--wind-deep);
  outline-offset: 2px;
}
.photo-card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Video grid cell — poster + centered ▶ that opens the lightbox (the single
   <video> player; see photos-today-page.js videoCardMarkup). The badge reuses
   the same translucent media-overlay treatment as the main gallery's
   .video-badge: a semantic overlay (not a theme token) that stays legible on
   any thumbnail in both light + dark. */
.photo-card--video { position: relative; }
.photo-card__video-badge {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  pointer-events: none;
}

/* 2026-08-02 — the pending-clip state. `.photo-card--video-pending` has been
   applied by photos-today-page.js since #152 but had ZERO rules, so a clip whose
   bytes were never pushed rendered pixel-identical to a playable one: same
   poster, same ▶, same tap-opens-the-lightbox, and the tap did nothing. On
   2026-08-02, 4 of 7 clips were pending and it read as "videos are broken"
   rather than "these four haven't synced." Now the ▶ is replaced by a labelled
   pill and the poster is dimmed, so the difference is visible at a glance.
   Scrim + colour + letter-spacing follow .favorites-card__pending so the two
   pending states read as one language; more opaque than the ▶ badge because
   text needs more contrast over an arbitrary photo than a single glyph. */
.photo-card__video-status {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: calc(100% - 24px);
  padding: 6px 11px;
  border-radius: 999px;
  background: rgb(20 16 12 / 0.68);
  color: #fff;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}
/* A dot rather than a spinner: it reads as in-progress while staying legible
   when motion is off, where it simply sits at a steady opacity. */
.photo-card__video-status::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.9;
}
@media (prefers-reduced-motion: no-preference) {
  .photo-card__video-status::before {
    animation: photo-sync-pulse 1.6s ease-in-out infinite;
  }
}
@keyframes photo-sync-pulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}
.photo-card--video-pending img { opacity: 0.62; }

/* Phase 7e Slice A — visual fundamentals.
   A1: removed `position: relative` to restore native :modal centering
       (position:fixed from UA :modal sheet is itself the containing block
        for the absolutely-positioned chevrons + close button).
   A2: edge-to-edge image (90vh/95vw), blurred photo backdrop driven by
       a CSS custom property set per-photo by photo-lightbox.js, with a
       dominant-color tint layered over the blur.
   A7: scale-in transition via @starting-style + transition-behavior:
       allow-discrete; respects reduced-motion. */
.photo-modal {
  /* Phase 7e A1 fix 2 — explicit translate-based centering. The UA :modal
     stylesheet sets `inset-block: 0 0` + `margin: auto`, which is supposed
     to auto-center via the absolute-centering trick — but in some browsers
     it stretches the dialog vertically to the viewport instead of
     auto-sizing to content (observed on prod 2026-05-20: image at top,
     chevrons at viewport edges, footer hanging off the bottom cut off).
     Override with translate-based positioning for predictable behavior.

     Fill-the-viewport fix (2026-05-21): the previous `width: auto;
     height: auto` sized the dialog to the image's intrinsic dimensions,
     which on iPad reads as a small floating panel when the photo is small
     or the preview path returns a downscaled file. Switch to an explicit
     95vw × 95vh flex container so the modal always fills the viewport;
     the image stays aspect-correct via flex + object-fit on the img rule
     below. `dvh`/`dvw` (where supported) handles the iOS Safari URL-bar
     collapse so the modal doesn't jump when the bar hides/shows. */
  inset: auto;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  width: 95vw;
  height: 95vh;
  max-width: 95vw;
  max-height: 95vh;
  /* `display: flex` lives in the [open] rule below — putting it on the
     base .photo-modal selector overrides the UA `dialog:not([open]) {
     display: none }` rule (author CSS beats UA across the cascade) and
     causes the closed dialog to render the close/chevron buttons over
     whatever page is active. */
  padding: 0;
  /* A2: transparent modal bg so the blurred ::backdrop shows around the
     photo edges. The footer keeps its own white bg for caption legibility. */
  background: transparent;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: var(--shadow-modal);
}
/* Only flex-layout when open — otherwise the UA `dialog:not([open])` rule
   correctly hides the dialog. */
.photo-modal[open] {
  display: flex;
  flex-direction: column;
}
@supports (height: 95dvh) {
  .photo-modal {
    width: 95dvw;
    height: 95dvh;
    max-width: 95dvw;
    max-height: 95dvh;
  }
}

/* A7 — open/close transitions. Translate must be preserved through the
   scale-in transition so the modal stays centered while it grows. */
@media (prefers-reduced-motion: no-preference) {
  .photo-modal,
  .photo-modal::backdrop {
    transition:
      opacity 180ms ease-out,
      transform 180ms ease-out,
      overlay 180ms ease-out allow-discrete,
      display 180ms ease-out allow-discrete;
  }
  @starting-style {
    .photo-modal[open] {
      opacity: 0;
      transform: translate(-50%, -50%) scale(0.96);
    }
    .photo-modal[open]::backdrop {
      opacity: 0;
    }
  }
}

/* A2 — `::backdrop` background image driven by CSS custom property set
   from JS (dialog.style.setProperty('--lightbox-bg-url', 'url(...)')).
   Iteration history:
   - v1 backdrop-filter (wrong filter): photo showed unblurred. Replaced.
   - v2 filter:blur + 62% solid tint: green-grass photo washed solid green.
     Killed.
   - v3 filter:blur + flat rgba(0,0,0,0.42): safe, generic.
   - v4 18→42% dominant-color gradient: produced a broken-modal screenshot.
     Reverted because the gradient + scrim layers were too transparent;
     we suspected the photo bg-image was failing for some photos.
   - v5 flat black + opaque #1A1612 safety net: safe but generic.
   - v6 (this, retry of v4 after SW cache v15 bump): the v4 broken-modal
     turned out to be a SW cache mismatch (stale v14 style.css against
     new HTML markup), NOT the gradient transparency. v6 re-introduces
     the dominant-color gradient ON TOP of the opaque #1A1612 safety net
     from v5 — so even if photo bg-image fails or canvas extraction
     throws, the opaque bottom layer prevents page bleed. Best of both. */
.photo-modal::backdrop {
  background:
    linear-gradient(
      rgb(var(--lightbox-tint, 20 16 12) / 0.18),
      rgb(var(--lightbox-tint, 20 16 12) / 0.42)
    ),
    var(--lightbox-bg-url, none) center/cover,
    #1A1612;
  filter: blur(80px) saturate(115%);
  -webkit-filter: blur(80px) saturate(115%);
}
@supports not (backdrop-filter: blur(1px)) {
  .photo-modal::backdrop {
    background-image: none;
    background-color: var(--scrim-modal);
  }
}

.photo-modal img {
  display: block;
  /* Flex into the modal: take the full image area between the absolute-
     positioned action bar (top) and the .photo-modal__footer (bottom).
     `min-height: 0` lets the flex item shrink past intrinsic; without it
     a large natural-size image will push the footer out of view.
     `object-fit: contain` preserves aspect ratio while filling. */
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  margin: 0;
  /* Click-half navigation — cursor stays default; we don't signal "split"
     with cursor changes because it conflicts with user zoom expectations. */
  cursor: default;
}
/* 2026-08-02 — the lightbox <video> had NO rule at all, so it sized to its own
   intrinsic box (300x150 until metadata loads) as an ordinary column flex item:
   a small strip pinned to the TOP of the modal, directly beneath the absolutely-
   positioned close + action overlays, with dead space below it. Reported on a
   phone as "opens at the very top with the overlays on top of it". Give it the
   <img>'s box so a clip fills the area between the top chrome and the footer and
   centres vertically, exactly like a photo.

   Deliberately NO `display` declaration: the element ships `hidden` in the page
   shell, and an author `display` on this selector would beat the UA
   `[hidden] { display: none }` rule and flash an empty player on every load —
   the same trap the `.photo-modal` base-selector comment above documents. Flex
   items are blockified anyway, so it buys nothing. */
.photo-modal video {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  margin: 0;
}
.photo-modal__footer { flex-shrink: 0; }

/* A3 — Caption footer restructured. Primary line: Fraunces --text-lg
   "5 years ago today" (carries the emotional weight). Secondary line:
   Inter --text-sm muted "May 21, 2020". Drop the truncation — captions
   may wrap to multi-line on narrow viewports. Counter stays right. */
.photo-modal__footer {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem 0.85rem;
  background: var(--wind-white);  /* keeps caption legible against any photo */
}
.photo-modal__caption {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.photo-modal__caption-primary {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 500;
  font-size: var(--text-lg);
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  line-height: 1.25;
}
.photo-modal__caption-secondary {
  font-size: var(--text-sm);
  color: var(--wind-stone);
  line-height: 1.3;
}
/* Phase 7e Slices D + E — people caption line.
   - Pre-v3.3 (no peopleTags): rendered as plain text by Slice D's helper
     ("Charlie was 4, Remy was 7") in Fraunces italic amber.
   - Post-v3.3 (peopleTags present): rendered as Slice E people pills
     (.people-pill children) — tappable to filter the carousel. */
.photo-modal__caption-people {
  font-family: 'Fraunces', Georgia, serif;
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--wind-amber-deep, var(--wind-amber));
  line-height: 1.3;
  margin-top: 0.2rem;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.photo-modal__caption-people:empty { display: none; }

/* People pill — Slice E2 + E3. Tap to filter the carousel to that person.
   Active state has a deeper amber background; uses --wind-amber-light token
   for the resting state so it reads as a chip on the otherwise-italic line. */
.people-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--wind-amber-light);
  color: var(--wind-amber-deep, var(--wind-amber));
  border: 1px solid transparent;
  font-family: 'Fraunces', Georgia, serif;
  font-size: var(--text-sm);
  font-style: normal;
  cursor: pointer;
  transition: background 0.12s ease;
}
/* Slice E polish — brand-color dot before the name. Pulls from the
   --pill-dot CSS variable set inline by the renderer. */
.people-pill__dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--pill-dot, var(--wind-stone));
  flex-shrink: 0;
}
.people-pill--active .people-pill__dot {
  /* on active state, the dot stays the brand color but gets a subtle
     ring for legibility against the deeper amber background. */
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.7);
}
/* Polish #8 — soft brand-color ring on hover. Recognition cue, especially
   for kids tapping each other's pills. */
.people-pill:hover .people-pill__dot {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pill-dot) 25%, transparent);
  transition: box-shadow 0.18s ease;
}
@media (prefers-reduced-motion: reduce) {
  .people-pill:hover .people-pill__dot { transition: none; }
}
.people-pill:hover,
.people-pill:focus-visible {
  background: var(--wind-dune);
  outline: none;
}
.people-pill:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 1px;
}
.people-pill--active {
  background: var(--wind-amber);
  color: #fff;
  border-color: var(--wind-amber-hover, var(--wind-amber));
}
/* "Clear filter" affordance — shown on the people row when a filter is
   active. Tapping clears the filter. */
.people-pill--clear {
  background: transparent;
  color: var(--wind-stone);
  border: 1px solid var(--wind-dune);
  font-style: italic;
}
@media (prefers-reduced-motion: reduce) {
  .people-pill { transition: none; }
}
.photo-modal__counter {
  font-size: var(--text-sm);
  color: var(--wind-stone);
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  padding-top: 0.15rem;
}
@media (max-width: 480px) {
  .photo-modal__footer {
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
  }
  .photo-modal__counter { text-align: right; }
}

.photo-modal__close {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  z-index: 3;
  background: rgba(20, 16, 12, 0.45);
  color: #fff;
  border: 0;
  border-radius: 999px;
  width: 44px;
  height: 44px;
  font-size: 1.5rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

/* Phase 7e Slice B1 — glass action bar (top-right; bottom strip on mobile).
   Lives to the LEFT of the close button on desktop, on its own row at the
   bottom on phones to free up the top-right for the close target. */
.photo-modal__actions {
  position: absolute;
  top: 0.4rem;
  right: calc(0.4rem + 44px + 0.4rem);  /* sit to the left of the close pill */
  z-index: 3;
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: rgb(var(--wind-white-rgb) / 0.16);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
}
.photo-modal__action {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: #fff;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.12s ease;
}
.photo-modal__action:hover,
.photo-modal__action:focus-visible {
  background: rgb(var(--wind-white-rgb) / 0.18);
  outline: none;
}
.photo-modal__action:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}
.photo-modal__action svg { display: block; }
/* Phase 7e Slice F polish — Notus action button has an amber tint so it
   stands apart from share/download/heart as the "magic / new thing." */
.photo-modal__action--notus svg {
  stroke: var(--wind-amber);
  fill: rgb(196 137 46 / 0.15);
}
.photo-modal__action--notus:hover svg,
.photo-modal__action--notus[aria-pressed="true"] svg {
  fill: rgb(196 137 46 / 0.35);
}
/* Action bar stays at top-right across all viewports. The previous mobile
   override pushed it to the bottom via `bottom: calc(100% - 90vh + 8px)`
   — that math assumed the dialog auto-sized to the image (pre-2026-05-21
   layout). With the new flex 95vh modal, the action bar's natural
   top-right slot is unambiguous and fits even on 320px screens (close
   button 44px + 4 action buttons × 36px + gaps ≈ 220px from the right). */

/* Phase 7e Slice B — share popover, slides in below the action bar.
   Positioned absolute relative to the modal; uses the same glass style as
   the action bar for visual continuity. */
/* Phase 7e Slice B — share popover.
   Solid white bg (not translucent — translucent + warm photo backdrop
   produced beige-on-beige low-contrast text on staging). Dark, high-
   contrast text. Dark-mode override below flips bg + text. */
.photo-modal__share-popover {
  position: absolute;
  top: calc(0.4rem + 44px + 6px);
  right: 0.4rem;
  z-index: 4;
  min-width: 220px;
  padding: 6px;
  border-radius: 12px;
  background: #FFFFFF;
  color: #1A1612;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.photo-modal__share-popover[hidden] { display: none; }
.share-popover__opt {
  display: block;
  width: 100%;
  padding: 11px 14px;
  background: transparent;
  color: inherit;
  border: 0;
  border-radius: 8px;
  text-align: left;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.1s ease;
}
.share-popover__opt:hover,
.share-popover__opt:focus-visible {
  background: #F3EDE4;       /* wind-sand value at light-mode baseline */
  outline: none;
}
.share-popover__opt:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: -1px;
}
[data-theme="dark"] .photo-modal__share-popover {
  background: #221C16;       /* wind-sand value at dark-mode baseline */
  color: #FFFFFF;
}
[data-theme="dark"] .share-popover__opt:hover,
[data-theme="dark"] .share-popover__opt:focus-visible {
  background: #2C251D;       /* wind-dune value at dark-mode baseline */
}

/* Phase 7e Slice B3 — chat-share composer overlay on the photo bottom.
   Replaces the footer briefly while user types a caption, then hides on
   send or cancel. */
.photo-modal__chat-composer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  padding: 12px 16px 14px;
  background: rgb(var(--wind-white-rgb) / 0.96);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid var(--wind-dune);
}
.photo-modal__chat-composer[hidden] { display: none; }
.photo-modal__chat-composer-label {
  font-size: var(--text-sm);
  color: var(--wind-stone);
}
.photo-modal__chat-composer textarea {
  font: inherit;
  font-size: var(--text-sm);
  padding: 8px 10px;
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  background: var(--wind-white);
  color: var(--wind-earth);
  resize: vertical;
  min-height: 44px;
}
.photo-modal__chat-composer-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Phase 7e Slice C4 — shared heart button.
   Two visual modes:
   - On overlay surfaces (grid thumbs, adult hero, kid teaser): floats
     top-right, glass pill, white outline on hover, fills warm-red when
     active. The hosting surface positions it via .photo-heart's parent
     having position: relative.
   - In the lightbox action bar: looks like a sibling of the share +
     download buttons (white on glass). Selectors below scope styling
     by ancestor (.photo-card .photo-heart vs .photo-modal__actions
     .photo-heart). */
.photo-heart {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgb(20 16 12 / 0.45);
  color: #fff;
  cursor: pointer;
  transition: background 0.12s ease, transform 0.12s ease;
}
/* #167 — WCAG 2.5.5 / iOS HIG want ~44px; the heart is 32px (28px in --small) and is the
   primary control on a phone-first surface the kids use. Expand the HIT AREA with a centred
   pseudo-element instead of resizing the button, so the target grows and the visual does not.
   `position: relative` is safe on the base rule: .photo-heart--overlay sets `position: absolute`
   at equal specificity LATER in this file, so it still wins there — and an absolutely positioned
   element is itself a containing block, so the pseudo-element anchors correctly either way. */
.photo-heart {
  position: relative;
}
.photo-heart::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}
.photo-heart:hover,
.photo-heart:focus-visible {
  background: rgb(20 16 12 / 0.62);
  outline: none;
}
.photo-heart:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}
.photo-heart svg {
  display: block;
  transition: fill 0.15s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.photo-heart--filled svg {
  fill: var(--wind-terracotta);
  stroke: var(--wind-terracotta);
}
.photo-heart:active {
  transform: scale(0.88);
}
/* Phase 7e Slice C4 polish — pop-scale on fill so a tap reads as a real
   moment. The keyframes overshoot then settle. Polish-pass #4 adds a
   fleeting terracotta glow via drop-shadow so the heart breathes color,
   not just size — feels like a real moment, not a checkbox. */
.photo-heart--just-filled svg {
  animation: photo-heart-pop 380ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes photo-heart-pop {
  0%   { transform: scale(1);    filter: drop-shadow(0 0 0  rgb(181 101 74 / 0)); }
  35%  { transform: scale(1.28); filter: drop-shadow(0 0 12px rgb(181 101 74 / 0.55)); }
  60%  { transform: scale(0.95); filter: drop-shadow(0 0 6px  rgb(181 101 74 / 0.30)); }
  100% { transform: scale(1);    filter: drop-shadow(0 0 0  rgb(181 101 74 / 0)); }
}
@media (prefers-reduced-motion: reduce) {
  .photo-heart--just-filled svg { animation: none; }
  .photo-heart svg { transition: fill 0.15s ease; }
}
.photo-heart--small {
  width: 28px;
  height: 28px;
}
.photo-heart--small svg {
  width: 16px;
  height: 16px;
}

/* Wrapper around each tap-to-open card so the floating heart can absolute-
   position against it without being nested inside the <button>. The wrap
   has display: contents-ish behavior in the grid — but we need it to
   actually be a sized box for the heart to anchor against, so we make it
   the layout box itself (grid children become .photo-card-wrap, the
   button inside fills it). */
.photo-card-wrap,
.photos-today__hero-wrap {
  position: relative;
  display: block;
}
.photo-card-wrap .photo-card,
.photos-today__hero-wrap .photos-today__hero {
  width: 100%;
}
.photo-heart--overlay {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  /* Faint by default; full opacity on hover/focus or persistent on
     touch (no-hover) devices. */
  opacity: 0.85;
}
@media (hover: hover) and (pointer: fine) {
  .photo-heart--overlay:not(.photo-heart--filled) {
    opacity: 0;
    transition: opacity 0.12s ease;
  }
  .photo-card-wrap:hover .photo-heart--overlay,
  .photos-today__hero-wrap:hover .photo-heart--overlay,
  .photo-heart--overlay:focus-visible {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .photo-heart,
  .photo-heart--overlay { transition: none; }
}

/* Heart in the lightbox action bar — inherits .photo-modal__action's
   glass background, just smaller/inline. */
.photo-modal__actions .photo-heart {
  width: 36px;
  height: 36px;
  background: transparent;
}
.photo-modal__actions .photo-heart:hover,
.photo-modal__actions .photo-heart:focus-visible {
  background: rgb(var(--wind-white-rgb) / 0.18);
}
.photo-modal__actions .photo-heart--filled svg {
  fill: var(--wind-terracotta);
  stroke: var(--wind-terracotta);
}

/* Phase 7e Slice E (post-ship UX gap) — quick-jump chips on /us/photos
   so users can reach the on-this-day surface + favorites page from the
   gallery without typing the URL. Sits next to the New album button. */
.photos-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}
.photos-actions__chip {
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--wind-sand);
  color: var(--wind-deep);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  border: 1px solid var(--wind-dune);
  transition: background 0.12s ease, transform 0.12s ease;
}
.photos-actions__chip:hover,
.photos-actions__chip:focus-visible {
  background: var(--wind-dune);
  outline: none;
}
.photos-actions__chip:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .photos-actions__chip { transition: none; }
}

/* Phase 7e Slice C3 — "snapshot pending" badge on favorites cards.
   Photo is reachable via live-gateway proxy until the snapshot worker
   lands the JPEG in Storage; the badge signals "this might 404 if the
   gateway is offline." Subtle — bottom-left, low opacity. */
.favorites-card__pending {
  position: absolute;
  bottom: 6px;
  left: 6px;
  z-index: 2;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgb(20 16 12 / 0.55);
  color: #fff;
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.85;
}

/* Phase 7e Slice F3 — info panel. Bottom-anchored card (mirrors the
   chat-share composer's footprint) with Notus narration text + Ask
   Notus CTA. Closes on tap outside or photo navigation. */
.photo-modal__info-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  background: rgb(var(--wind-white-rgb) / 0.97);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  padding: 14px 18px 18px;
  border-top: 1px solid var(--wind-dune);
  color: var(--wind-deep);
}
.photo-modal__info-panel[hidden] { display: none; }
.info-panel__inner {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 640px;
  margin: 0 auto;
}
.info-panel__narration {
  font-family: 'Fraunces', Georgia, serif;
  font-size: var(--text-base);
  line-height: 1.4;
  color: var(--wind-deep);
}
.info-panel__narration:empty { display: none; }
.info-panel__ask {
  align-self: center;
  min-width: 240px;
}
.info-panel__hint {
  font-size: var(--text-sm);
  color: var(--wind-stone);
  text-align: center;
}
.info-panel__hint:empty { display: none; }
[data-theme="dark"] .photo-modal__info-panel {
  background: rgb(34 28 22 / 0.97);
  color: #FFFFFF;
  border-top-color: #3A3128;
}
[data-theme="dark"] .info-panel__narration {
  color: #FFFFFF;
}
[data-theme="dark"] .info-panel__hint {
  color: var(--wind-drift, #B8AD9C);
}

/* Phase 7e Slice B — toast (copy success, send success, errors). */
.photo-modal__toast {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgb(20 16 12 / 0.85);
  color: #fff;
  font-size: var(--text-sm);
  pointer-events: none;
}
.photo-modal__toast[hidden] { display: none; }

/* #123 — Prev/Next chevrons. 44x44 minimum touch target per Phase 14f
   a11y rules. On coarse pointers (touch/iPad) the buttons stay at 40%
   opacity so users always see the affordance; fade to 0% on fine
   pointers and reveal on hover/focus. Backdrop pill works against
   either light or dark photo content. */
.photo-modal__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 16, 12, 0.45);
  color: #fff;
  border: 0;
  border-radius: 999px;
  font-size: 1.8rem;
  line-height: 1;
  padding: 0 0 4px;  /* visual nudge so the chevron glyph is optically centered */
  cursor: pointer;
  /* Default visibility — replaced below by pointer-media queries. */
  opacity: 0.4;
  transition: opacity 0.15s ease;
}
.photo-modal__nav--prev { left: 0.6rem; }
.photo-modal__nav--next { right: 0.6rem; }
.photo-modal__nav:hover,
.photo-modal__nav:focus-visible {
  opacity: 1;
}
.photo-modal__nav:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
}
@media (hover: hover) and (pointer: fine) {
  /* Mouse: chevrons fade out until the user hovers the modal area. */
  .photo-modal__nav { opacity: 0; }
  .photo-modal:hover .photo-modal__nav,
  .photo-modal__nav:focus-visible { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .photo-card { transition: none; }
  .photo-modal__nav { transition: none; }
}

/* Phase 10-IA P0-5 — Tab-bar overflow at narrow viewports.
   Admin's 10 tabs cram unreadably below ~720px (labels truncate to 2-3
   chars at 375px). Solution: per-user primary tabs stay visible; rest move
   to a "More" popover triggered by the rightmost cell. CSS hides the More
   tab on wide viewports (full bar fits) and hides overflow tabs on narrow
   viewports (replaced by More). The .tab--more cell uses the same .tab
   styles for visual parity. */
.tab--more {
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: inherit;
}
@media (max-width: 720px) {
  .tab-bar .tab[data-priority="overflow"] { display: none; }
}
@media (min-width: 721px) {
  .tab-bar .tab[data-priority="more"] { display: none; }
}

.tab-more-menu {
  /* Bottom-anchored sheet on narrow viewports. Browsers with native [popover]
     support default to display:none until showPopover() is called; browsers
     without support get display:none via the global [hidden]{display:none
     !important} rule plus the nav.js fallback path that toggles [hidden]
     directly. Either way, the closed state is invisible — never override it
     with a forced display:block here, or the sheet will overlay the page
     and intercept clicks (regression caught in dad-list + map-authoring
     smoke tests at desktop 1280px during deploy 68 prep). */
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-width: 1px 0 0 0;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.12);
  padding: 10px 0 calc(10px + env(safe-area-inset-bottom));
  margin: 0;
  /* Bottom-anchored when the browser puts this in the top layer (native
     [popover] open state) or when the fallback JS removes [hidden]. The
     `inset` overrides the browser's default centered popover positioning;
     `position: fixed` covers the fallback path where the element isn't
     auto-promoted to top-layer. Closed state stays display:none either
     way (native via :not(:popover-open), fallback via [hidden]). */
  position: fixed;
  inset: auto 0 0 0;
  width: 100%;
  max-width: 100%;
  z-index: 60;
}
.tab-more-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  text-decoration: none;
  color: var(--wind-deep);
  font-size: 1rem;
  border-bottom: 1px solid var(--border-soft);
}
.tab-more-item:last-child { border-bottom: 0; }
.tab-more-item:hover,
.tab-more-item:focus-visible {
  background: var(--wind-sand);
  outline: none;
}
.tab-more-item.is-active {
  color: var(--wind-amber);
  font-weight: 600;
}
.tab-more-item__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
}
.tab-more-item__icon svg {
  width: 22px;
  height: 22px;
  color: currentColor;
}

/* ===== iOS install banner ===== */
.ios-install-banner {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(76px + env(safe-area-inset-bottom));
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  padding: 12px 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.88rem;
  color: var(--wind-earth);
  z-index: 60;
}
.ios-install-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ios-install-body strong { color: var(--wind-deep); }
.ios-install-body svg { vertical-align: -2px; color: var(--wind-amber); }
.ios-install-body em { font-style: normal; font-weight: 600; color: var(--wind-deep); }
.ios-install-dismiss {
  background: none;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--wind-drift);
  padding: 0 6px;
  cursor: pointer;
}
.ios-install-dismiss:hover { color: var(--wind-deep); }

/* ===== Photos page ===== */
.photos-container {
  max-width: 960px;
  margin: 0 auto;
  padding: 16px;
}
.photos-actions { margin: 16px 0 22px; }
/* .primary-btn / .secondary-btn deleted Phase 14a — use .btn-primary / .btn-secondary. */

/* ===== Phase 14a: MINX dashboard (extracted from inline <style>) ===== */
.minx-container { max-width: 720px; margin: 0 auto; padding: 1rem; padding-bottom: 2rem; }

/* Phase 14e — 2-column dashboard at wide viewports. Hero stays full-width;
   columns split below. LEFT = money mechanics, RIGHT = operational reality.
   At narrow viewports (<1024) the columns naturally stack and the page
   reads as single-column. align-items: start so the LEFT column (typically
   shorter) doesn't stretch to match the taller RIGHT column. */
.minx-cols { display: flex; flex-direction: column; }
.minx-col { display: flex; flex-direction: column; }
@media (min-width: 1024px) {
  .minx-container { max-width: 1180px; }
  /* Asymmetric 2:3 grid — LEFT column is a money-summary sidebar
     (Forecast + Saving for…), RIGHT carries the activity feed
     (Since Friday's + planner + wishes + reminders + calendar). The
     wider right column absorbs the natural height imbalance from the
     long uncategorized list without leaving the sidebar feeling empty. */
  .minx-cols { display: grid; grid-template-columns: 2fr 3fr; column-gap: 1.5rem; align-items: start; }
}

/* Phase 14e — dark-mode lift for category-bar + fund-progress fills. The
   color hex values stored in taxonomy_values.color (and the SWATCHES picker
   defaults) are tuned for light mode against #F3EDE4 sand. In dark mode
   the same hex against #221C16 sand collapses contrast (several seeded
   colors drop below WCAG AA 3:1). A blanket brightness+saturation lift
   pulls all fills into a viable range without picking 8 hand-tuned
   dark-mode overrides per category. Honest about the data (same hex
   identity), legible on the screen. */
[data-theme="dark"] .minx-section .category-bar-fill,
[data-theme="dark"] .minx-section .fund-progress-fill { filter: brightness(1.35) saturate(1.2); }
/* Lighter track in dark mode so the fills have something to push against. */
[data-theme="dark"] .minx-section .category-bar-track { background: rgba(255, 255, 255, 0.06); }
[data-theme="dark"] .minx-section .fund-progress-track { background: rgba(255, 255, 255, 0.08); }

/* Reduced-motion: drop the width transitions on progress fills. */
@media (prefers-reduced-motion: reduce) {
  .minx-section .category-bar-fill,
  .minx-section .fund-progress-fill { transition: none; }
}
/* #156 — global reduced-motion safety net. Honors prefers-reduced-motion app-wide
   (dialog open/close, hovers, sparkline draws, progress fills): near-instant
   transitions + animations, no smooth scroll. 0.01ms (not 0) so transitionend
   still fires for code that awaits it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
.minx-section { background: var(--wind-white); border-radius: 10px; padding: 1.25rem; margin-bottom: 1rem; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
.minx-section h2 { margin: 0 0 0.6rem; font-size: 1.05rem; color: var(--sea-deep, var(--wind-deep)); font-family: Inter, system-ui, sans-serif; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; }

.minx-section .hero-card { background: var(--sea-mist, var(--wind-sand)); border-radius: 12px; padding: 1.5rem; }
.minx-section .hero-eyebrow { font-size: 0.85rem; color: var(--wind-earth); margin-bottom: 0.25rem; }
.minx-section .hero-num { font-family: 'Fraunces', Georgia, serif; font-size: 2.4rem; line-height: 1.1; color: var(--sea-ink, var(--wind-deep)); }
.minx-section .hero-meta { font-size: 0.85rem; color: var(--wind-earth); margin-top: 0.4rem; }
.minx-section .sparkline { width: 100%; height: 56px; margin-top: 0.6rem; }
/* Slice D follow-up — chart legend so the sparkline reads as "actual (solid)
   → projected (dashed), split at today" instead of an unlabeled squiggle.
   Swatch colors mirror the SVG strokes drawn in minx.js (renderSparkline). */
.minx-section .sparkline-legend { display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; margin-top: 0.5rem; font-size: 0.72rem; color: var(--wind-earth); }
.minx-section .sparkline-legend .legend-item { display: inline-flex; align-items: center; gap: 0.4rem; }
.minx-section .sparkline-legend .legend-line { display: inline-block; width: 18px; height: 0; border-top: 2px solid var(--sea-deep, var(--wind-deep)); }
.minx-section .sparkline-legend .legend-line--projected { border-top-style: dashed; opacity: 0.85; }
.minx-section .sparkline-legend .legend-tick { display: inline-block; width: 0; height: 12px; border-left: 1px dashed var(--wind-earth); }

/* Phase 14e Slice C — Moms companion stat. Side-by-side on wide viewports,
   stacked-under on iPad portrait (810) and mobile (<=820). Mint-tinted to
   match the Moms band in the stacked-area chart below. Visually smaller
   than the MINX primary balance — Moms is a companion, not a peer. */
.minx-section .hero-balances { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; }
.minx-section .hero-primary { flex: 1 1 auto; min-width: 0; }
.minx-section .hero-companion { flex: 0 0 auto; text-align: right; }
.minx-section .hero-companion-eyebrow { font-size: 0.8rem; color: var(--wind-earth); margin-bottom: 0.25rem; letter-spacing: 0.02em; }
.minx-section .hero-companion-num { font-family: 'Fraunces', Georgia, serif; font-size: 1.5rem; line-height: 1.1; color: var(--sea-tide, var(--sea-deep)); }
.minx-section .hero-companion-meta { font-size: 0.78rem; color: var(--wind-earth); margin-top: 0.3rem; }
/* Tether MINX ↔ Moms visually at side-by-side widths (audit P1 #5). */
@media (min-width: 821px) {
  .minx-section .hero-companion { padding-left: 1.5rem; border-left: 1px solid rgba(140, 130, 110, 0.25); }
}
@media (max-width: 820px) {
  .minx-section .hero-balances { flex-direction: column; gap: 0.75rem; }
  .minx-section .hero-companion { text-align: left; }
}

/* Sparkline chart bands (Slice C): Moms is the bottom filled band rising in
   stair-steps; MINX float stacks on top. Outline stroke on the combined top
   edge gives the eye a single line to follow. */
.minx-section .sparkline .fc-band-moms { fill: var(--sea-glass, #C9DDD9); fill-opacity: 0.65; }
.minx-section .sparkline .fc-band-minx { fill: var(--sea-shoal, #86AEB0); fill-opacity: 0.42; }

/* Phase 14e Slice D/E section header — heading + actions on one row,
   collapses on narrow viewports. */
.minx-section .section-header { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.minx-section .section-header h2 { margin: 0; }
.minx-section .section-meta { font-size: 0.8rem; color: var(--wind-earth); }
/* Audit P1 #4 — section-header__actions for multi-button right slots. The
   old .section-actions div lived below the h2 and wasted a row; the new
   pattern keeps actions inline with the heading. */
.minx-section .section-header__actions { display: inline-flex; gap: 0.5rem; flex-wrap: wrap; align-items: baseline; }

/* Slice D — "Since Friday's deposit" category bars */
.minx-section .category-bars { list-style: none; padding: 0; margin: 0 0 1rem; display: flex; flex-direction: column; gap: 0.45rem; }
.minx-section .category-bar-row { display: grid; grid-template-columns: 6rem 1fr 5rem; align-items: center; gap: 0.5rem; cursor: pointer; padding: 0.15rem 0.25rem; border-radius: 6px; transition: background 120ms ease; }
.minx-section .category-bar-row:hover { background: rgba(140, 130, 110, 0.08); }
.minx-section .category-bar-row:focus-visible { outline: 2px solid var(--sea-tide, var(--wind-amber)); outline-offset: 2px; }
.minx-section .category-bar-row.is-selected { background: rgba(140, 130, 110, 0.14); }
.minx-section .category-clear-btn { background: transparent; border: 0; color: var(--sea-tide, var(--wind-amber)); cursor: pointer; text-decoration: underline; font-size: 0.78rem; padding: 0 0.5rem 0 0; }
/* ⚠ #239 — the label lives in the fixed 6rem (96px) grid column above, and a grid
   item defaults to min-width:auto, so ANY category label wider than 96px pushed the
   row past the viewport on a phone. Found by the multi-viewport sweep, but it is not
   a test artifact: a user-typed category name is free text, so this overflowed for
   real. Truncate — the column is deliberately fixed-width, and title= keeps the full
   name reachable. */
.minx-section .category-bar-label { font-size: 0.85rem; color: var(--sea-ink, var(--wind-deep)); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.minx-section .category-bar-track { background: var(--wind-sand); border-radius: 6px; height: 14px; overflow: hidden; }
.minx-section .category-bar-fill { height: 100%; border-radius: 6px; transition: width 200ms ease; }
.minx-section .category-bar-amount { font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem; text-align: right; color: var(--sea-ink, var(--wind-deep)); }
.minx-section .uncategorized-block { background: var(--wind-sand); border-radius: 8px; padding: 0.75rem 0.85rem; }
.minx-section .uncategorized-header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 0.45rem; }
.minx-section .uncategorized-title { font-size: 0.85rem; color: var(--wind-earth); text-transform: uppercase; letter-spacing: 0.04em; }
.minx-section .uncategorized-meta { font-size: 0.8rem; color: var(--wind-earth); }
.minx-section .uncategorized-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.minx-section .uncategorized-row { display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid rgba(140, 130, 110, 0.18); }
.minx-section .uncategorized-row:last-child { border-bottom: 0; }
.minx-section .uncategorized-desc { font-size: 0.85rem; color: var(--sea-ink, var(--wind-deep)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.minx-section .uncategorized-amount { font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem; color: var(--wind-red); }
.minx-section .tag-pill-btn { background: transparent; border: 1px solid var(--wind-shore); border-radius: 999px; padding: 0.25rem 0.7rem; font-size: 0.78rem; cursor: pointer; color: var(--sea-ink, var(--wind-deep)); }
.minx-section .tag-pill-btn:hover { background: var(--wind-sand); }

/* Slice D — tag picker dialog */
#tag-dialog .tag-dialog-subhead { font-size: 0.85rem; color: var(--wind-earth); margin-bottom: 0.8rem; }
#tag-dialog .tag-chip-grid { display: flex; flex-wrap: wrap; gap: 0.45rem; margin-bottom: 0.75rem; }
#tag-dialog .tag-chip { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.4rem 0.7rem; border-radius: 999px; border: 0; cursor: pointer; font-size: 0.85rem; color: var(--sea-ink, var(--wind-deep)); background: var(--wind-sand); }
#tag-dialog .tag-chip:hover { filter: brightness(0.96); }
#tag-dialog .tag-chip .swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
#tag-dialog .tag-chip--new { border: 1px dashed var(--wind-drift); background: transparent; }
#tag-dialog .new-category-form { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.6rem; background: var(--wind-sand); border-radius: 6px; margin-bottom: 0.75rem; }
#tag-dialog .new-category-form input[type="text"] { padding: 0.4rem 0.6rem; border: 1px solid var(--wind-shore); border-radius: 6px; font-family: Inter, sans-serif; }
#tag-dialog .color-swatches { display: flex; gap: 0.35rem; }
#tag-dialog .color-swatches button { width: 22px; height: 22px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; }
#tag-dialog .color-swatches button.is-active { border-color: var(--sea-ink, var(--wind-deep)); }
#tag-dialog .dialog-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 0.5rem; gap: 0.5rem; flex-wrap: wrap; }
#tag-dialog .link-btn { background: transparent; border: 0; color: var(--sea-tide, var(--wind-amber)); cursor: pointer; text-decoration: underline; font-size: 0.85rem; padding: 0; }
/* Audit P2 #10 — manage view back-link sits top-left (close-X position),
   matching where users instinctively look for the dismiss/back affordance. */
#tag-dialog .tag-dialog-back-top { display: block; margin-bottom: 0.5rem; padding: 0.25rem 0; font-size: 0.9rem; }
#tag-dialog .manage-list { list-style: none; padding: 0; margin: 0 0 0.8rem; display: flex; flex-direction: column; gap: 0.4rem; }
#tag-dialog .manage-row { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid rgba(140, 130, 110, 0.18); }
#tag-dialog .manage-row .swatch { width: 14px; height: 14px; border-radius: 50%; }
#tag-dialog .manage-row .label { font-size: 0.9rem; }
#tag-dialog .manage-row .manage-action-btn { background: transparent; border: 1px solid var(--wind-shore); border-radius: 6px; padding: 0.25rem 0.55rem; font-size: 0.75rem; cursor: pointer; color: var(--sea-ink, var(--wind-deep)); }
#tag-dialog .manage-row .system-tag { font-size: 0.7rem; color: var(--wind-earth); text-transform: uppercase; letter-spacing: 0.04em; }
/* #147 — inline category editor (label + color) replaces the grid layout while editing */
#tag-dialog .manage-row.is-editing { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
#tag-dialog .manage-edit-label { flex: 1 1 100%; padding: 0.4rem 0.6rem; border: 1px solid var(--wind-shore); border-radius: 6px; font-family: Inter, sans-serif; font-size: 0.9rem; }
#tag-dialog .manage-row.is-editing .manage-edit-swatches { flex: 1 1 auto; flex-wrap: wrap; }

/* Slice E — Saving for… section */
.minx-section .funds-meta { font-size: 0.85rem; color: var(--wind-earth); margin-bottom: 0.55rem; }
.minx-section .funds-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; }
/* Expected transfers (#146) — reconcile rows. Reuses .minx-section chrome +
   .register-pill; below 1024px the actions wrap to their own 44px-target line
   (#156's touch-target lesson). */
.transfer-groups-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.transfer-row { padding: 0.6rem 0.75rem; background: var(--wind-sand); border-radius: 8px; }
.transfer-row-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.transfer-label { font-weight: 600; }
.transfer-meta { font-size: 0.85rem; color: var(--wind-stone); }
.transfer-row-body { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.4rem; }
.transfer-side-line { border-top: 1px solid var(--border-soft); padding-top: 0.4rem; }
.transfer-actions { display: flex; gap: 0.5rem; margin-left: auto; }
.transfer-actions .btn-secondary { min-height: 44px; min-width: 44px; }
@media (max-width: 1023px) {
  .transfer-actions { margin-left: 0; flex-basis: 100%; }
}
/* Pill variants for transfer sides — design-system.md gains these with this
   commit (the .register-pill family's first flow-direction variants). */
.register-pill--flow-out { background: var(--surface-subtle); color: var(--wind-stone); }
.register-pill--flow-in { background: var(--surface-subtle); color: var(--wind-earth); }
.register-pill--diverged { background: var(--wind-amber-light); color: var(--wind-amber); }
/* Minimal dialog form fields (first dialog on this page with labeled inputs). */
.dialog-field { display: block; margin: 0.6rem 0; font-size: 0.85rem; color: var(--wind-stone); }
.dialog-field input, .dialog-field select { display: block; width: 100%; margin-top: 0.25rem; min-height: 44px; }
.dialog-check { display: flex; align-items: center; gap: 0.5rem; margin: 0.6rem 0; }
.dialog-check input { width: 20px; height: 20px; }

.minx-section .fund-row { display: grid; grid-template-columns: 1fr auto; gap: 0.5rem 0.75rem; align-items: center; padding: 0.6rem 0.75rem; background: var(--wind-sand); border-radius: 8px; }
.minx-section .fund-row .fund-row-head { display: inline-flex; align-items: baseline; gap: 0.4rem; }
.minx-section .fund-row .fund-name { font-size: 0.95rem; color: var(--sea-ink, var(--wind-deep)); font-weight: 500; }
/* #156 — 44×44 tap target (WCAG 2.5.5). Hit area padded around the glyph; the
   ✎ itself stays 0.85rem (transparent button, so the area is invisible). */
.minx-section .fund-row .fund-rename-btn { background: transparent; border: 0; padding: 0; cursor: pointer; color: var(--wind-earth); font-size: 0.85rem; line-height: 1; min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.minx-section .fund-row .fund-rename-btn:hover { color: var(--sea-tide, var(--wind-amber)); }
.minx-section .fund-row .fund-stat { font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem; color: var(--sea-ink, var(--wind-deep)); }
.minx-section .fund-row .fund-progress { grid-column: 1 / 3; display: grid; grid-template-columns: 1fr auto; gap: 0.5rem; align-items: center; }
.minx-section .fund-progress-track { background: rgba(140, 130, 110, 0.2); border-radius: 6px; height: 10px; overflow: hidden; }
.minx-section .fund-progress-fill { background: var(--sea-glass, #C9DDD9); height: 100%; transition: width 200ms ease; }
.minx-section .fund-progress-fill.is-complete { background: var(--wind-green, #5E9E5E); }
.minx-section .fund-row .fund-allocate-btn { background: transparent; border: 1px solid var(--wind-shore); border-radius: 6px; padding: 0.25rem 0.6rem; font-size: 0.8rem; cursor: pointer; color: var(--sea-ink, var(--wind-deep)); }
.minx-section .fund-row .fund-allocate-btn:hover { background: var(--wind-sand); filter: brightness(0.95); }

#allocate-dialog .dialog-subhead { font-size: 0.85rem; color: var(--wind-earth); margin-bottom: 0.6rem; }

/* Phase 14e Slice A: forecast header + 30/60/90 window toggle */
.forecast-header { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.4rem; flex-wrap: wrap; }
.forecast-header h2 { margin: 0; }
.forecast-window-toggle { display: inline-flex; gap: 0; background: var(--wind-sand); border-radius: 8px; padding: 2px; font-family: Inter, sans-serif; }
.forecast-window-toggle .window-btn { background: transparent; border: 0; padding: 0.3rem 0.7rem; font-size: 0.8rem; color: var(--wind-earth); border-radius: 6px; cursor: pointer; font-family: inherit; }
.forecast-window-toggle .window-btn:hover { color: var(--sea-ink, var(--wind-deep)); }
/* #156 — active pill bg was var(--surface,#fff): --surface is undefined, so it
   forced #fff, and the text token flips light in dark mode → light-on-light
   (1.26:1). --wind-white is theme-aware (#fff light / #1A1612 dark), so the pill
   inverts with the text and clears AA in both themes. */
.forecast-window-toggle .window-btn.is-active { background: var(--wind-white); color: var(--sea-ink, var(--wind-deep)); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }

.forecast-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75rem; }
/* Slice A: 3-tier layout (lbl / val / when). Row height locked so cells
   don't bounce when the date string updates as the window toggles. */
.forecast-cell { background: var(--wind-sand); border-radius: 8px; padding: 0.6rem 0.7rem; font-family: Inter, sans-serif; min-height: 5.4rem; display: flex; flex-direction: column; justify-content: flex-start; gap: 0.15rem; }
.forecast-cell .lbl { font-size: 0.7rem; color: var(--wind-earth); text-transform: uppercase; letter-spacing: 0.04em; }
.forecast-cell .val { font-family: 'Fraunces', Georgia, serif; font-size: 1.3rem; line-height: 1.15; }
.forecast-cell .when { font-size: 0.8rem; color: var(--wind-earth); line-height: 1.2; min-height: 1rem; }
.forecast-cell.breach .val { color: var(--wind-red); }
/* Audit P1 #7 — breach state needs a non-color cue too (WCAG 1.4.1). The
   ⚠ prefix on the value and breach-warning text gives shape redundancy
   for users who can't perceive the red. */
.forecast-cell.breach .val::before { content: "⚠ "; font-family: Inter, sans-serif; }
.breach-warning::before { content: "⚠ "; }
/* legacy class kept for back-compat with any other surface that uses it */
.forecast-cell .val.breach { color: var(--wind-red); }
.breach-warning { color: var(--wind-red); font-size: 0.85rem; margin-top: 0.6rem; }

/* Slice A: sparkline today-tick + event-dot are styled inline on the SVG
   (stroke/fill use tokens). Class hooks below let themes override if needed. */
.minx-section .sparkline .fc-today-tick { /* token via inline stroke; class anchors test selectors */ }
.minx-section .sparkline .fc-event-dot { /* token via inline fill; class anchors test selectors */ }

.planner-list { list-style: none; padding: 0; margin: 0; }
.planner-row { display: flex; justify-content: space-between; align-items: baseline; padding: 0.6rem 0; border-bottom: 1px solid var(--wind-dune); }
.planner-row:last-child { border-bottom: none; }
.planner-title { color: var(--sea-deep, var(--wind-deep)); font-weight: 500; }
.planner-cat { font-size: 0.75rem; color: var(--wind-earth); margin-left: 0.5rem; text-transform: uppercase; letter-spacing: 0.04em; }
.planner-cost { font-family: 'Fraunces', Georgia, serif; color: var(--sea-deep, var(--wind-deep)); }
.planner-link { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; width: 100%; color: inherit; text-decoration: none; }
.planner-link:hover { color: var(--wind-amber); }

.minx-page .empty-msg { color: var(--wind-earth); font-style: italic; padding: 0.6rem 0; }

.wish-row { padding: 0.6rem 0; border-bottom: 1px solid var(--wind-dune); }
.wish-row:last-child { border-bottom: none; }
.wish-title { color: var(--sea-deep, var(--wind-deep)); font-weight: 500; }
.wish-note { color: var(--wind-earth); font-size: 0.85rem; margin-top: 0.2rem; }
.wish-actions { margin-top: 0.5rem; display: flex; gap: 0.4rem; flex-wrap: wrap; }
.wish-btn { background: var(--wind-sand); border: 1px solid var(--wind-dune); border-radius: 999px; padding: 0.3rem 0.7rem; font-size: 0.8rem; cursor: pointer; }
.wish-btn:hover { background: var(--sea-mist); }

.placeholder-block { color: var(--wind-earth); font-style: italic; font-size: 0.85rem; padding: 0.5rem 0; }
.section-actions { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.wishes-count { color: var(--wind-earth); font-weight: normal; }

/* MINX planner page extracted styles */
.planner-container { max-width: 720px; margin: 0 auto; padding: 1rem; }
.planner-container .filters { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.item-card { background: var(--wind-white); border-radius: 10px; padding: 1rem; margin-bottom: 0.75rem; box-shadow: 0 2px 6px rgba(0,0,0,0.04); }
.item-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.item-title { font-family: 'Fraunces', Georgia, serif; font-size: 1.1rem; color: var(--sea-deep, var(--wind-deep)); }
.item-cost { font-family: 'Fraunces', Georgia, serif; color: var(--sea-deep, var(--wind-deep)); }
.item-meta { font-size: 0.8rem; color: var(--wind-earth); margin-top: 0.25rem; }
.item-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; flex-wrap: wrap; }

.status-pill { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-weight: 500; }
.status-pending { background: var(--wind-sand); color: var(--wind-earth); }
.status-deferred { background: var(--wind-dune); color: var(--wind-earth); }
.status-purchased { background: var(--wind-green-light); color: var(--status-success-text); }
.status-cancelled { background: var(--wind-red-light); color: var(--status-danger-text); }
.status-active { background: var(--wind-green-light); color: var(--status-success-text); }
.status-expired { background: var(--wind-dune); color: var(--wind-earth); }
.status-revoked { background: var(--wind-red-light); color: var(--status-danger-text); }

/* MINX settings page extracted styles */
.settings-container { max-width: 720px; margin: 0 auto; padding: 1rem; }
.settings-section { background: var(--wind-white); border-radius: 10px; padding: 1.25rem; margin-bottom: 1rem; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }
.priority-list { list-style: none; padding: 0; margin: 0; }
.priority-row {
  display: flex; align-items: center; gap: 0.75rem;
  background: var(--wind-sand);
  padding: 0.6rem 0.8rem;
  border-radius: 8px;
  margin-bottom: 0.4rem;
  cursor: grab;
  transition: background 0.15s;
}
.priority-row[draggable="true"]:hover { background: var(--sea-mist, var(--wind-sand)); }
.priority-row.dragging { opacity: 0.5; cursor: grabbing; }
.priority-row.drop-target { box-shadow: 0 0 0 2px var(--sea-deep, var(--wind-deep)); }
.priority-num {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.2rem;
  width: 1.6rem; text-align: center;
  color: var(--sea-deep, var(--wind-deep));
}
.priority-name { flex: 1; color: var(--sea-deep, var(--wind-deep)); }
.priority-controls { display: flex; gap: 0.25rem; }
.priority-controls button {
  background: transparent;
  border: 1px solid var(--wind-dune);
  width: 2rem; height: 2rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  min-height: auto;
  padding: 0;
}
.priority-controls button:hover:not(:disabled) { background: var(--wind-white); }
.priority-controls button:disabled { opacity: 0.3; cursor: not-allowed; }
.save-row { display: flex; gap: 0.5rem; margin-top: 0.8rem; align-items: center; }
.save-status { font-size: 0.85rem; color: var(--wind-earth); margin-left: auto; }
.save-status.saved { color: var(--status-success-text); }
.save-status.error { color: var(--wind-red); }
.floor-display { font-family: 'Fraunces', Georgia, serif; font-size: 1.5rem; color: var(--sea-deep, var(--wind-deep)); }
.settings-section h2 { font-family: Inter, system-ui, sans-serif; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sea-deep, var(--wind-deep)); margin: 0 0 0.5rem; }
.settings-section p.help { color: var(--wind-earth); font-size: 0.85rem; margin: 0 0 0.8rem; }
.filters select { padding: 0.4rem; border: 1px solid var(--wind-dune); border-radius: 6px; background: var(--wind-white); }
.item-meta .chip { display: inline-block; background: var(--wind-sand); padding: 0.15rem 0.5rem; border-radius: 999px; margin-right: 0.4rem; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; }
.item-actions a, .item-actions button { background: transparent; border: 1px solid var(--wind-dune); padding: 0.3rem 0.7rem; border-radius: 6px; font-size: 0.85rem; color: var(--sea-deep, var(--wind-deep)); text-decoration: none; cursor: pointer; min-height: auto; }

/* ===== Phase 14a: /us/adults cross-surface card grid ===== */
.adults-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin: 16px 0 24px;
}

.adults-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  padding: 18px 20px;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow-subtle);
  transition: transform 150ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 150ms;
}

.adults-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
}

.adults-card__eyebrow {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--wind-stone);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.adults-card__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.4rem;
  font-weight: 500;
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.adults-card__hint {
  font-size: 0.85rem;
  color: var(--wind-earth);
  margin-top: 2px;
}

/* ===== Phase 14c: figure picker dialog ===== */
/* shelved #206 — no markup emits these; dormant in place for the unshelve/#208 */

.figure-picker {
  max-width: 720px;
  width: 92%;
  padding: 1.25rem 1.25rem 1.5rem;
}

.figure-picker__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.figure-picker__head h2 {
  margin: 0;
  /* Inherits Fraunces from .dialog-base h2 */
}

.picker-grid {
  display: grid;
  /* Mobile/portrait: 2-up. iPad/desktop: 4-up. */
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

@media (min-width: 744px) {
  .picker-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
  }
}

.picker-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  text-decoration: none;
  color: inherit;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  padding: 14px 10px 12px;
  box-shadow: var(--shadow-subtle);
  transition: transform 150ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 150ms, border-color 150ms;
}

.picker-tile:hover,
.picker-tile:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(90, 80, 65, 0.14);
  border-color: var(--tile-tint, var(--wind-amber));
  outline: none;
}

.picker-tile__portrait {
  width: 88px;
  height: 88px;
  border-radius: 50%;
  background: var(--wind-sand);
  border: 2px solid var(--tile-tint, var(--wind-amber));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin-bottom: 8px;
}

.picker-tile__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.picker-tile__initial {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 2.4rem;
  color: var(--tile-tint, var(--wind-amber));
}

.picker-tile__name {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.05rem;
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  margin-top: 2px;
}

.picker-tile__eyebrow {
  font-size: 0.7rem;
  color: var(--wind-stone);
  letter-spacing: 0.02em;
  margin-top: 2px;
}

.picker-tile__hook {
  font-size: 0.85rem;
  color: var(--wind-earth);
  line-height: 1.35;
  margin-top: 6px;
}

.link-btn {
  background: none;
  border: none;
  color: var(--wind-amber-text);   /* #299 — was --wind-amber, 3.02:1 on white; the token comment has the figures */
  cursor: pointer;
  text-decoration: underline;
  font-size: 0.88rem;
  padding: 4px 8px;
}
.link-btn.revoke { color: var(--wind-red); }
/* #19 M1b — the same touch-target exception `.snapshot-history .snapshot-delete` carries,
   and for the same reason (#156, design-system.md:258): `.link-btn` is padding-sized at
   ~25px, which is right for an inline text link and wrong for a one-tap, no-confirm
   mutation of a money field. Edit/Delete beside it are unchanged — both open a modal. */
.commitment-row .adopt-observed,
/* #297 S3 — Confirm / Revert are the same shape: one tap, no modal, a money-model mutation. */
.commitment-row .confirm-firm,
.commitment-row .confirm-revert,
/* #296 — Mark ended / Undo on the inbox's stopped alert: the same shape, the first site outside Bills. */
.commitment-row .mark-ended,
.commitment-row .undo-ended {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Albums grid */
.albums-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
}
.album-card {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.album-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
}
.album-card .album-cover {
  aspect-ratio: 4 / 3;
  background: var(--wind-dune);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.album-card .album-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.album-card .album-placeholder {
  width: 56px;
  height: 56px;
  color: var(--wind-drift);
  display: flex;
  align-items: center;
  justify-content: center;
}
.album-card .album-placeholder svg {
  width: 100%;
  height: 100%;
}
.album-card .album-meta {
  padding: 10px 12px 12px;
}
.album-card .album-title {
  font-weight: 600;
  font-size: 0.98rem;
  color: var(--wind-deep);
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.album-card .album-sub {
  font-size: 0.78rem;
  color: var(--wind-drift);
}

/* Album detail */
.album-detail-head {
  margin: 8px 0 22px;
}
.album-detail-head h2 {
  margin: 8px 0 4px;
  color: var(--wind-deep);
}
.album-detail-head .album-desc {
  color: var(--wind-earth);
  font-size: 0.95rem;
  margin: 0 0 16px;
  line-height: 1.5;
}
.album-detail-actions {
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.photos-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
  min-height: 180px;
  border-radius: 8px;
  transition: background-color 0.15s ease, outline 0.15s ease;
}
.photos-grid.drag-over {
  background-color: var(--wind-sand);
  outline: 2px dashed var(--wind-amber);
  outline-offset: -8px;
}
.photos-grid.drag-over::before {
  content: 'Drop photos to upload';
  grid-column: 1 / -1;
  text-align: center;
  padding: 40px 20px;
  color: var(--wind-amber);
  font-weight: 600;
}
.photo-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--wind-dune);
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
}
.photo-tile img,
.photo-tile video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.photo-tile.is-video .video-badge {
  position: absolute;
  inset: auto 8px 8px auto;
  /* Translucent overlay on photo media — semantic overlay color, not a
     theme token; legible on any background image in both light/dark. */
  background: rgba(0, 0, 0, 0.55);
  color: white;
  border-radius: 999px;
  padding: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.photo-tile.is-video .video-badge svg {
  width: 18px;
  height: 18px;
}
#lb-video {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.upload-hint {
  color: var(--wind-drift);
  font-size: 0.85rem;
  margin-left: 10px;
}

/* Inline-editable album title + description (Phase 6 sibling — photos UX) */
.editable-text {
  cursor: text;
  border-radius: 4px;
  padding: 2px 4px;
  margin: -2px -4px;
  transition: background-color 0.12s ease;
}
.editable-text:hover {
  background-color: var(--wind-amber-light);
  outline: 1px dashed var(--wind-amber);
  outline-offset: -1px;
}
.editable-text.placeholder {
  color: var(--wind-drift);
  font-style: italic;
}
.inline-edit-input {
  width: 100%;
  font-family: inherit;
  font-size: inherit;
  color: var(--wind-deep);
  background: var(--wind-white);
  border: 1px solid var(--wind-amber);
  border-radius: 4px;
  padding: 4px 6px;
  margin: -5px -7px;
  box-sizing: border-box;
  box-shadow: 0 0 0 3px var(--wind-amber-light);
  outline: none;
  resize: vertical;
}
textarea.inline-edit-input {
  line-height: 1.4;
  font-size: 0.95rem;
}
.photo-tile .photo-delete {
  position: absolute;
  top: 4px;
  right: 4px;
  /* 44pt iOS minimum touch target — was 24px pre-15a. The visible SVG is
     sized to ~18px inside; the rest is invisible padding for tap area. */
  width: 44px;
  height: 44px;
  padding: 0;
  /* Translucent media overlay — see .video-badge note. */
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  border: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}
.photo-tile .photo-delete svg {
  width: 18px;
  height: 18px;
}
.photo-tile:hover .photo-delete { opacity: 1; }
/* Touch devices have no hover — keep the delete affordance always visible
   so it's reachable. Slight opacity reduction so it doesn't dominate. */
@media (hover: none) {
  .photo-tile .photo-delete { opacity: 0.85; }
}

.upload-status {
  margin-top: 14px;
  padding: 10px 12px;
  background: var(--wind-sand);
  border-radius: 6px;
  font-size: 0.88rem;
  color: var(--wind-earth);
}

.empty-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--wind-drift);
  font-size: 0.95rem;
}

/* Share links panel */
.share-links-list {
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--wind-sand);
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
}
.share-links-list h4 {
  margin: 0 0 8px;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--wind-stone);
}
.share-link-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  font-size: 0.88rem;
  border-top: 1px dashed var(--wind-dune);
}
.share-link-row:first-of-type { border-top: none; }

/* Modal */
.modal {
  position: fixed;
  inset: 0;
  /* Scrim strengthened 0.35 -> 0.55 (James, 2026-08-28: the loan edit modal is
     "quite hard to read like this"). THIS was the actual defect — .modal-card
     already scrolled itself, but at 0.35 the loan list behind stayed fully
     legible and competed with the form. Worst in dark mode, where --wind-white
     IS the page surface (#1A1612), so the card and the page behind it are
     nearly the same tone and the scrim is the only thing separating them. */
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 200;
}
.modal[hidden] { display: none; }
.modal-content {
  background: var(--wind-white);
  padding: 24px;
  border-radius: 12px;
  width: 100%;
  max-width: 420px;
  /* Latent, not the bug James reported — .modal-content is used by ONE page
     (kid-portfolios) while every DICE modal uses .modal-card, which already
     bounds itself at 90vh. Fixed anyway because the omission is real: without
     these a tall form overflows a `align-items: center` flex container, which
     clips it at BOTH ends and leaves the top unreachable. */
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.modal-content h3 {
  margin: 0 0 16px;
  color: var(--wind-deep);
}
.modal-content label {
  display: block;
  margin-bottom: 12px;
  font-size: 0.9rem;
  color: var(--wind-earth);
}
.modal-content input,
.modal-content textarea,
.modal-content select {
  width: 100%;
  padding: 8px 10px;
  margin-top: 4px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
  box-sizing: border-box;
  background: var(--wind-white);
  color: var(--wind-deep);
}
.modal-content textarea {
  min-height: 60px;
  resize: vertical;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 6px;
}

/* ===== Profile page ===== */
.profile-container { max-width: 560px; }
.profile-section {
  margin: 22px 0;
  padding: 18px 20px;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
}
.profile-section h2 {
  margin: 0 0 12px;
  font-size: 1rem;
  color: var(--wind-deep);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}
.profile-section .hint {
  font-size: 0.85rem;
  color: var(--wind-drift);
  margin: 8px 0 14px;
}
.profile-section input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  font-size: 1rem;
  font-family: inherit;
  margin-bottom: 10px;
  box-sizing: border-box;
}

.avatar-row {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 8px;
}
.avatar-preview-big {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: var(--wind-amber);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  font-weight: 600;
  flex-shrink: 0;
  overflow: hidden;
}
.avatar-preview-big img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.avatar-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.color-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.color-row input[type="color"] {
  width: 56px;
  height: 44px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  cursor: pointer;
  background: none;
  padding: 2px;
}
.color-row .color-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9rem;
  color: var(--wind-stone);
}

.status-banner {
  margin: 14px 0;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 0.9rem;
}
.status-banner.info { background: var(--wind-sand); color: var(--wind-earth); }
.status-banner.success { background: var(--status-success-bg); color: var(--status-success-text); border: 1px solid var(--status-success-border); }
.status-banner.error { background: var(--wind-red-light); color: var(--wind-red); border: 1px solid var(--status-danger-stroke); }

/* Small avatar bubble (for chat, admin, etc.) */
.avatar-mini {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 600;
  font-size: 0.85rem;
  overflow: hidden;
  flex-shrink: 0;
}
.avatar-mini img { width: 100%; height: 100%; object-fit: cover; }

/* ===== Notus memory page ===== */
.memory-container { max-width: 680px; }
.memory-intro {
  color: var(--wind-earth);
  font-size: 0.95rem;
  line-height: 1.5;
  margin: 8px 0 18px;
}
.memory-filters {
  margin-bottom: 14px;
  font-size: 0.88rem;
  color: var(--wind-stone);
}
.memory-filters input { margin-right: 6px; }

.memory-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.memory-card {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 10px;
  padding: 14px 16px;
}
.memory-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
  font-size: 0.78rem;
  color: var(--wind-stone);
}
.mem-category {
  background: var(--wind-sand);
  padding: 2px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: var(--wind-deep);
}
.mem-subject {
  color: var(--wind-amber);
  font-weight: 600;
}
.mem-badge.expired {
  background: var(--wind-dune);
  color: var(--wind-stone);
  padding: 2px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  font-size: 0.7rem;
}
.mem-salience {
  margin-left: auto;
  font-style: italic;
  color: var(--wind-drift);
}
.memory-fact {
  color: var(--wind-deep);
  font-size: 0.98rem;
  line-height: 1.45;
  margin-bottom: 8px;
}
.memory-meta {
  font-size: 0.78rem;
  color: var(--wind-drift);
  margin-bottom: 6px;
}
.memory-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

/* Lightbox (shared with share viewer) */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.9);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}
.lightbox.open { display: flex; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.lightbox .lb-close {
  position: absolute;
  top: 18px;
  right: 22px;
  /* 44pt iOS minimum touch target — was font-size:2.2rem text-only pre-15a. */
  width: 44px;
  height: 44px;
  color: #fff;
  background: none;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lightbox .lb-close svg {
  width: 28px;
  height: 28px;
}

/* ---------------------------------------------------------------------------
   Dad's List (Phase 9a) — deferred decisions queue + gates + expected events
   (originally "surprise log"; renamed Phase 10O Slice E 2026-05-24)
   --------------------------------------------------------------------------- */

.dad-list-page .dad-list-container { padding-bottom: 120px; }

.dad-list-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin: 12px 0 8px;
  flex-wrap: wrap;
}

.member-hint {
  background: var(--wind-amber-light);
  border-left: 3px solid var(--wind-amber);
  padding: 10px 14px;
  border-radius: 6px;
  color: var(--wind-deep);
  margin-bottom: 12px;
  font-size: 0.95rem;
}

.urgency-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 0.75rem;
  font-weight: 600;
  margin-right: 6px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.urgency-badge.urgency-critical { background: var(--wind-red); color: var(--wind-white); }
.urgency-badge.urgency-high     { background: var(--wind-amber); color: var(--wind-white); }
.urgency-badge.urgency-low      { background: var(--wind-shore); color: var(--wind-earth); }
/* #297 S3 — the MODELLED tier on a bills row: the low-urgency token pair, not amber (amber is
   "essential", a different axis). */
.urgency-badge.commitment-modelled { background: var(--wind-shore); color: var(--wind-earth); }
/* Inferred-pattern status pills (/us/dice/inbox). Defined, theme-aware token
   pairs — the old inline styles used undefined --accent-soft/--accent-strong and
   rendered pale-on-pale in dark mode (#160). shore/earth + earth/sky both invert
   to keep WCAG-AA contrast in light and dark. */
.urgency-badge.urgency-candidate { background: var(--wind-shore); color: var(--wind-earth); }
.urgency-badge.urgency-confirmed { background: var(--wind-earth); color: var(--wind-sky); }
/* #214 S2 — the RESOLVED statuses, now that dismissed/promoted rows render (in the
   `<details class="closed-accordion">` on /us/dice/inbox). Deliberately the SAME two
   token pairs as candidate/confirmed rather than new colors: both are already
   contrast-audited in light and dark (#160), the badge TEXT carries the meaning, and the
   tone weight matches the semantics — light pill = set aside, solid pill = acted on.
   ⚠ These rows do NOT take `.event-row.is-closed`: its opacity 0.65 pushes shore/earth
   (4.80:1 measured, light) under AA, and the accordion already says "resolved". */
.urgency-badge.urgency-dismissed { background: var(--wind-shore); color: var(--wind-earth); }
.urgency-badge.urgency-promoted  { background: var(--wind-earth); color: var(--wind-sky); }

.zone-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 0.75rem;
  background: var(--border-card);
  color: var(--wind-deep);
  margin-right: 6px;
}

.gate-summary {
  margin-top: 6px;
  padding: 6px 10px;
  background: var(--status-danger-bg);
  border-left: 2px solid var(--status-danger-text);
  border-radius: 4px;
  font-size: 0.88rem;
  color: var(--status-danger-text);
}

.hint-inline {
  font-weight: normal;
  font-size: 0.85rem;
  color: var(--wind-stone);
}

.cost-range-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
.cost-range-row input[type="number"] { flex: 1 1 80px; min-width: 80px; }
.cost-range-row select { flex: 0 1 auto; }

/* -----------------------------------------------------------------------
   .modal-card — new modal pattern (Phase 9a onward).
   Original .modal-content pattern above (pre-9a) uses a narrower fixed
   420px card; this card scales up for the denser forms introduced with
   Dad's List / admin-financial / admin-metis.
   ----------------------------------------------------------------------- */
.modal-card {
  background: var(--wind-white);
  padding: 22px 24px;
  border-radius: 12px;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: var(--shadow-modal);
  /* ⚠ In DARK mode --wind-white IS the page surface (#1A1612), so the card and
     the page behind it are the same tone and only the scrim separates them —
     a shadow against a same-coloured background reads as nothing. This gives
     the card a definite edge. In light, --wind-dune is a soft warm hairline. */
  border: 1px solid var(--wind-dune);
}
.modal-card h2 {
  margin: 0 0 14px;
  color: var(--wind-deep);
  font-size: 1.2rem;
}
.modal-card label {
  display: block;
  margin: 10px 0 4px;
  font-size: 0.88rem;
  color: var(--wind-earth);
  font-weight: 500;
}
.modal-card input[type="text"],
.modal-card input[type="number"],
.modal-card input[type="date"],
.modal-card input[type="datetime-local"],
.modal-card input[type="email"],
.modal-card select,
.modal-card textarea {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
  box-sizing: border-box;
  background: var(--wind-white);
  color: var(--wind-deep);
}
.modal-card textarea {
  min-height: 60px;
  resize: vertical;
}
.modal-card .hint-inline {
  font-weight: 400;
  font-size: 0.8rem;
  color: var(--wind-stone);
}
/* Phase 10O Slice F — read-only field display (event-type in the edit modal,
   any other field that's structurally locked rather than disabled). Visually
   matches an input box so the form layout stays consistent, but inert. */
.modal-card .readonly-field {
  padding: 8px 10px;
  border: 1px solid var(--wind-dune);
  border-radius: 6px;
  background: var(--wind-sand);
  color: var(--wind-stone);
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}

/* Phase 10j Slice D — wider modal for the Mercury outflow picker (denser
   rows than a single-column form). */
.modal-card--wide { max-width: 640px; }

.link-tx-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 50vh;
  overflow-y: auto;
  margin: 8px 0 12px;
}
.link-tx-row {
  text-align: left;
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 8px;
  padding: 10px 12px;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.link-tx-row:hover { background: var(--wind-sand); }
.link-tx-row__head { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.link-tx-row__body { font-size: 0.85rem; color: var(--wind-stone); margin-top: 2px; }
.link-tx-row .badge-amber {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 500;
  background: var(--wind-amber);
  color: var(--wind-white);
}
.link-tx-row .badge-warn {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 500;
  background: var(--wind-sand);
  color: var(--wind-amber-deep);
  border: 1px solid var(--wind-amber-deep);
}
/* #100 — account-name badge on cross-account fallback rows in the picker */
.link-tx-row .badge-info {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 500;
  background: var(--wind-sand);
  color: var(--wind-stone);
  border: 1px solid var(--wind-stone);
}
/* #100 — warn-shaped hint above the candidate list when cross-account
   fallback rows are present, signaling the source-account was likely wrong.
   General-purpose: not scoped to .modal-card so it can warn in batch previews
   (paystub upload, etc.) too. */
.hint--warn {
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--wind-sand);
  color: var(--wind-amber-deep);
  border: 1px solid var(--wind-amber-deep);
  margin: 8px 0;
}
/* Rows that should remain inline (cost range, labor hours+type, checkbox labels) */
.modal-card .cost-range-row,
.modal-card .checkbox-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
.modal-card .cost-range-row input,
.modal-card .cost-range-row select {
  flex: 1 1 100px;
  min-width: 100px;
  display: inline-block;
  width: auto;
}
.modal-card .checkbox-row input[type="checkbox"] {
  width: auto;
  display: inline-block;
}
.modal-card .modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border-soft);
}
.modal-card.wide { max-width: 720px; }

.gates-section,
.cost-amendments-section { margin: 12px 0; }
.gates-section h3,
.cost-amendments-section h3 {
  font-size: 1rem;
  margin: 0 0 8px;
  color: var(--wind-deep);
}

.gates-list,
.cost-amendments-list {
  list-style: none;
  padding: 0;
  margin: 0 0 8px;
}
.gate-item,
.cost-amendments-list li {
  padding: 6px 10px;
  background: var(--wind-amber-light);
  border-radius: 6px;
  margin-bottom: 4px;
  font-size: 0.92rem;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.gate-item.cleared {
  opacity: 0.5;
  text-decoration: line-through;
}
.gate-type {
  font-size: 0.75rem;
  background: var(--border-card);
  padding: 1px 6px;
  border-radius: 8px;
  color: var(--wind-earth);
  text-transform: uppercase;
}
.gate-item .gate-act { margin-left: auto; }
.gate-item .gate-act + .gate-act { margin-left: 4px; }

.add-gate-form {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 6px;
}
.add-gate-form select,
.add-gate-form input { flex: 1 1 120px; min-width: 120px; }
.add-gate-form button { flex: 0 0 auto; }

.admin-section {
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px dashed var(--border-emphatic);
}

/* #76 P2-1 — drop the dashed separator inside admin financial pages
   (/us/dice glance, /us/dice/billpay, /us/dice/lending, /us/financial,
   etc.). With 6+ summary cards stacked, the rules read as a busy ladder;
   whitespace + the cards' own surface contrast carry the rhythm cleanly.
   Other admin surfaces (/us/admin/users, /us/adults, /us/profile) keep
   their solid divider via the line-471 rule. */
.admin-financial-container .admin-section,
.dad-list-container .admin-section {
  border-top: 0;
  padding-top: 8px;
}

.events-list { margin-top: 10px; }
.event-row {
  display: grid;
  /* date / type / amount / account / desc / delete (right-aligned) */
  grid-template-columns: 110px 90px 100px 110px 1fr auto;
  gap: 8px;
  padding: 6px 10px;
  align-items: center;
  border-bottom: 1px solid var(--border-soft);
  font-size: 0.92rem;
}
/* /us/dice/expected — columnar variant. Outer row is flex (main + actions);
   inner .event-row-main holds the columnar grid so badge + confidence chip
   live in a dedicated flags column instead of shoving the type/amount/account
   cells out of alignment. Replaces the original direct-children grid that
   broke whenever a row had badges or extra action buttons (cause of the
   "× migrates around" + "Drop in freezer in the date column" bug, 2026-05-24). */
.event-row.event-row--cols {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: 8px;
}
.event-row.event-row--cols .event-row-main {
  display: grid;
  /* date / flags / type / amount / account / desc.
     #210 Slice 2 (R2): the desc column is minmax(0,1fr), not 1fr — with the
     content-sized min, rows carrying an account chip forced main past the
     available width and the ✏/✕ actions wrapped onto a ragged second line
     (observed at 1280px). Letting desc compress (its text wraps internally)
     keeps the actions on the row. */
  grid-template-columns: 120px 130px 90px 100px 110px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
}
.event-row.event-row--cols .event-row-actions {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-shrink: 0;
}
.event-row.event-row--cols .recommendation-result {
  flex-basis: 100%;
  width: 100%;
}
.event-row.event-row--cols .recommendation-result[hidden] { display: none; }
/* Inferred-pattern cards (/us/dice/inbox) have only two children — main + actions
   — so the base .event-row 6-col grid jammed .event-row-main into the 110px date
   column and the meta text wrapped one word per line (#160). Lay them out as flex:
   main grows, actions sit at the end and wrap below on narrow viewports. */
.event-row.inferred-card {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}
.event-row.inferred-card .event-row-main { flex: 1 1 240px; min-width: 0; }
.event-row.inferred-card .event-row-actions { display: flex; gap: 8px; flex-shrink: 0; }
.event-row-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}
.event-row .event-date { white-space: nowrap; }
.event-row .event-account {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--wind-stone);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.event-row .event-delete { justify-self: end; }
/* #76 P2-5: tinted pill for the event-type column instead of plain
   colored text. Same color hierarchy (red=out, green=in, amber=correction)
   but rendered as a bordered chip so the column visually parses as a
   category label, not a free-floating word. */
.event-row .event-type {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: center;
}
.event-row .event-type-outflow {
  color: var(--wind-red);
  background: var(--wind-red-light);
}
.event-row .event-type-inflow {
  color: var(--wind-green);
  background: var(--wind-green-light);
}
.event-row .event-type-transfer {
  color: var(--wind-deep);
  background: var(--wind-sand);
}
.event-row .event-type-correction {
  color: var(--wind-amber);
  background: var(--wind-amber-light);
}

/* Phase 10O Slice A — status badge + confidence chip for expected events.
   Posted rows render no badge/chip (would be visual noise on the dominant
   case). Expected rows pick up a dashed border + softer background so the
   row visually reads as "not yet real." */
.event-row--expected {
  background: var(--wind-sand-light);
  border-left: 3px solid var(--wind-amber, #c4892e);
  padding-left: 8px;
}
.event-row--unscheduled {
  background: var(--wind-mist);
  border-left: 3px solid var(--wind-mist-deep);
  padding-left: 8px;
}
.event-row--cancelled {
  opacity: 0.55;
  text-decoration: line-through;
}
.event-row .event-badge {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--wind-amber-light, rgba(196, 137, 46, 0.15));
  color: var(--wind-amber, #c4892e);
}
.event-row .event-badge--expected {
  background: var(--wind-amber-light, rgba(196, 137, 46, 0.15));
  color: var(--wind-amber, #c4892e);
}
.event-row .event-badge--unscheduled {
  background: var(--wind-mist);
  color: var(--wind-mist-deep);
}
.event-row .event-badge--cancelled {
  background: var(--wind-soft);
  color: var(--wind-deep, #4a5568);
}
.event-row .event-conf {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 500;
  padding: 2px 6px;
  border-radius: 10px;
  text-transform: lowercase;
}
.event-row .event-conf--committed {
  border: 1px solid var(--wind-deep, #4a5568);
  color: var(--wind-deep, #4a5568);
}
.event-row .event-conf--likely {
  border: 1px dashed var(--wind-amber, #c4892e);
  color: var(--wind-amber, #c4892e);
}
.event-row .event-conf--discretionary {
  border: 1px dotted var(--wind-mist-deep);
  color: var(--wind-mist-deep);
  opacity: 0.85;
}
.events-section-heading {
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin: 16px 0 8px;
}
.events-section-heading:first-child { margin-top: 0; }

/* Phase 10O Slice D — recommendation card surfaced inline below an
   unscheduled event row when "Find best date" is clicked. */
.recommendation-result {
  grid-column: 1 / -1;
  margin-top: 8px;
}
.recommendation-card {
  background: var(--wind-sand-light);
  border-left: 4px solid var(--wind-amber, #c4892e);
  padding: 12px 16px;
  border-radius: 4px;
  line-height: 1.5;
}
.recommendation-card--no-window {
  background: var(--wind-mist);
  border-left-color: var(--wind-mist-deep);
}
.find-date-btn {
  font-size: 0.85rem;
  padding: 4px 10px;
}

/* Phase 10O Slice B — reconciliation suggestions in /us/dice/inbox.
   Group = one expected event + its candidate Mercury txs. */
.reconciliation-group {
  border: 1px solid var(--wind-deep-mist);
  border-radius: 4px;
  padding: 10px 14px;
  margin-bottom: 10px;
  background: var(--wind-sand-light);
}
.reconciliation-group__header {
  margin-bottom: 6px;
  padding-bottom: 6px;
  border-bottom: 1px dashed var(--wind-deep-mist);
}
.reconciliation-candidate {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
}
.reconciliation-candidate__line {
  flex: 1;
}
.reconciliation-candidate .btn-primary {
  font-size: 0.85rem;
  padding: 4px 10px;
}

/* Phase 10O Slice C — retro reason labels in the Mercury tx drawer. */
.retro-tag-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
}
.retro-tag-text {
  flex: 1;
  padding: 4px 8px;
  background: var(--wind-sand-light);
  border-left: 3px solid var(--wind-amber, #c4892e);
  border-radius: 3px;
  font-size: 0.9rem;
}

/* ---------------------------------------------------------------------------
   Admin financial page (Phase 9b-1)
   --------------------------------------------------------------------------- */

.admin-financial-container {
  padding-bottom: 120px;
  /* Phase 10-IA Slice 7 — wide-viewport density.
     Body content tile widens at md+/lg+/xl+ so the page no longer floats
     in a narrow center column on desktop / iPad-landscape. Mobile + tablet-
     portrait stay at the existing comfortable reading width. */
  margin-left: auto;
  margin-right: auto;
}
@media (min-width: 768px)  { .admin-financial-container { max-width: 760px; } }
@media (min-width: 1024px) { .admin-financial-container { max-width: 960px; } }
@media (min-width: 1440px) { .admin-financial-container { max-width: 1200px; } }
@media (min-width: 1880px) { .admin-financial-container { max-width: 1400px; } }

/* Phase 11c — dashboard hero (Phase 10-IA Slice 7: 4-card variant on /us/dice) */
.dashboard-hero {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin: 8px 0 24px;
}
@media (max-width: 720px) { .dashboard-hero { grid-template-columns: 1fr; } }
/* 4-card variant tiles 2x2 at tablet, 4-across at desktop, 1-col on phones.
   The narrow-viewport stack avoids the 2x2 + eyebrow-wrap bug at 375px
   (cards ~187px wide can't fit "14-DAY TRAJECTORY" / "OUTSTANDING LOANS"
   without wrapping). Hover-linkable cards (debt + loans) get the same
   hover affordance as .sub-page-rail__card. */
.dashboard-hero--4col { grid-template-columns: 1fr 1fr; }
@media (max-width: 500px) { .dashboard-hero--4col { grid-template-columns: 1fr; } }
@media (min-width: 1024px) { .dashboard-hero--4col { grid-template-columns: repeat(4, 1fr); } }
/* 3-card variant (2026-05-23 — outstanding-loans card removed). Stack on
   phone + tablet portrait, 3-up only at narrow-desktop+ (>=960px). The
   prior 720px breakpoint busted at kids-mini (744) and ipad-portrait (810)
   because 7-digit debt + 14-day chart img + "−$6,800 · Mortgage (auto)"
   subtitle each need ~300-320px per card to render without overflow. #145. */
.dashboard-hero--3col { grid-template-columns: 1fr; }
@media (min-width: 960px) { .dashboard-hero--3col { grid-template-columns: repeat(3, 1fr); } }
.hero-card--debt:hover,
.hero-card--debt:focus-visible,
.hero-card--loans:hover,
.hero-card--loans:focus-visible {
  border-color: var(--sea-deep, var(--wind-stone));
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
  outline: none;
}
.hero-card {
  background: var(--sea-mist);
  border-radius: 14px;
  padding: 24px 28px 20px;
  border: 1px solid var(--border-soft);
}
.hero-card .hero-eyebrow {
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--sea-tide);
  font-weight: 500;
  margin-bottom: 8px;
}
.hero-card .hero-num {
  margin-bottom: 4px;
}
.hero-card .hero-meta {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  color: var(--wind-earth);
  margin-top: 6px;
  line-height: var(--line-ui);
}
.hero-card .hero-meta .meta-label {
  color: var(--wind-stone);
  margin-right: 4px;
}
.hero-card--trajectory .hero-spark {
  margin: 8px 0 4px;
  height: 80px;
  display: block;
}
.hero-card--trajectory .hero-spark svg {
  width: 100%;
  height: 100%;
}

/* Phase 11c — upcoming-30d timeline */
.upcoming-timeline {
  display: grid;
  gap: 4px;
  margin: 12px 0;
}
.timeline-row {
  display: grid;
  grid-template-columns: 64px 1fr auto 120px;
  gap: 12px;
  align-items: baseline;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--wind-white);
  border: 1px solid var(--border-soft);
  font-size: var(--text-sm);
}
.timeline-row .timeline-date {
  color: var(--wind-stone);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.timeline-row .timeline-label { color: var(--wind-deep); font-weight: 500; }
/* #200 — marks a modelled figure (the quarterly bonus from the uploaded comp statement). Tokens
   only; reuses the existing muted colour rather than introducing a new one. */
.timeline-row .timeline-est,
.spark-tip .timeline-est {
  font-size: 0.72em; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--wind-stone); border: 1px solid var(--wind-stone); border-radius: 3px;
  padding: 0 0.25em; margin-left: 0.4em; vertical-align: 0.08em; opacity: 0.85;
}
/* #297 S1 — the badge names the TIER; `discretionary` (a choice not yet made) reads one step
   further from certain than `modelled`. MEASURED at S1's design audit (2026-09-12): `--wind-amber`
   text on the white register row is 3.02:1 in light — under AA for text — so the variant took
   `--wind-amber-deep`. ⚠ #299 (2026-09-13) re-measured that: `--wind-amber-deep` is 4.28:1 on white
   (the S1 comment's earlier light/dark figures overstated it — dark is 9.24), still under AA, so the
   TEXT takes `--wind-amber-text` (5.56 on white); the border, which is not text, keeps the deep amber. */
.timeline-row .timeline-est--discretionary,
.spark-tip .timeline-est--discretionary { color: var(--wind-amber-text); border-color: var(--wind-amber-deep); }
/* #297 S1 — the hero's income line. The warning is carried by the glyph and the words, NOT by a
   colour: every warm token measured under 4.5:1 for small text on the hero card in light
   (`--wind-amber` 2.69, `--wind-amber-deep` 3.82, `--wind-terracotta` 3.80), so the line keeps the
   meta ink (`--wind-earth`, 6.74:1) and the link is underlined. Deploy 38's render audit found the
   no-floor state's "set one on Accounts" link (plain `.hero-meta`, not `--warn`) in the UA's default
   blue on both themes — so the rule covers every anchor in a hero meta line, not one variant. */
.hero-card .hero-meta a { color: inherit; text-decoration: underline; }
.timeline-row .timeline-amount {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.timeline-out .timeline-amount { color: var(--wind-terracotta); }
.timeline-in  .timeline-amount { color: var(--wind-green); }
.timeline-row .timeline-account { color: var(--wind-stone); font-size: var(--text-xs); text-align: right; }
/* Running projected balance (aggregate) — slightly firmer than the old account label, still muted */
.timeline-row .timeline-balance { color: var(--wind-earth); font-variant-numeric: tabular-nums; }

/* #222 — trajectory hover + scale. Labels are HTML (SVG text would stretch
   under preserveAspectRatio=none — the minx sparkline-legend precedent);
   knockout chips mask the min/max-normalized curve at the corners. The tip is
   fixed-positioned (immune to card clipping), pointer-events:none per WCAG
   1.4.13 (hide rides the WRAPPER's pointerleave + Escape). No transitions —
   motion-rules untouched. */
.hero-card--trajectory .hero-spark { position: relative; cursor: crosshair; }
.spark-scale-max, .spark-scale-min {
  position: absolute;
  left: 2px;
  font-size: 0.68rem;
  color: var(--wind-earth);
  font-variant-numeric: tabular-nums;
  background: var(--wind-white);
  padding: 0 4px;
  border-radius: 4px;
  pointer-events: none;
}
.spark-scale-max { top: 2px; }
.spark-scale-min { bottom: 2px; }
.spark-tip {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  background: var(--wind-white);
  border: 1px solid var(--border-card);
  border-radius: 8px;
  box-shadow: var(--shadow-elevated);
  padding: 8px 10px;
  font-size: var(--text-xs);
  min-width: 150px;
  max-width: 240px;
}
.spark-tip__date { color: var(--wind-stone); text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.68rem; }
.spark-tip__balance { color: var(--wind-deep); font-weight: 600; font-variant-numeric: tabular-nums; margin: 1px 0 3px; }
.spark-tip__event { display: flex; justify-content: space-between; gap: 10px; line-height: 1.5; }
.spark-tip__event-label { color: var(--wind-earth); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spark-tip__event-amt { font-variant-numeric: tabular-nums; white-space: nowrap; }
.spark-tip__event-amt.is-out { color: var(--wind-terracotta); }
.spark-tip__event-amt.is-in { color: var(--wind-green); }
@media (max-width: 720px) {
  .timeline-row {
    grid-template-columns: 56px 1fr auto;
    grid-template-rows: auto auto;
  }
  .timeline-row .timeline-account {
    grid-column: 2 / 4;
    text-align: left;
  }
}

.balances-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
  margin: 8px 0 12px;
}
.account-tile {
  background: var(--wind-white);
  border: 1px solid var(--border-card);
  border-radius: 8px;
  padding: 14px 16px;
  box-shadow: var(--shadow-subtle);
}
.account-name {
  font-size: 0.9rem;
  color: var(--wind-earth);
  margin-bottom: 4px;
}
.account-balance {
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--wind-deep);
  font-variant-numeric: tabular-nums;
}
.account-asof {
  font-size: 0.78rem;
  color: var(--text-muted);   /* #299 — --wind-stone is 3.97:1 on the white tile; the muted-text slot (--wind-stone-deep) is 5.35 */
  margin-top: 2px;
}
.account-floor {
  font-size: 0.78rem;
  /* #297 S2 — every tile now carries this line ("No floor set" / "Floor: $X"), so its ink has to
     pass AA as small text: --wind-amber measured 3.02:1 on the white tile in light (it was fine
     while only a set floor rendered it, rarely); --wind-deep is 12.13 light / 12.88 dark. */
  color: var(--wind-deep);
  margin-top: 4px;
  font-weight: 600;
}

.snapshot-history-controls {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 8px 0;
}
.snapshot-history { margin: 6px 0; }
.snapshot-list { list-style: none; padding: 0; margin: 0; }
.snapshot-list li {
  padding: 4px 8px;
  border-bottom: 1px solid var(--border-hairline);
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  gap: 4px;
}
.snapshot-date    { color: var(--wind-earth); min-width: 160px; }
.snapshot-balance { font-weight: 600; font-variant-numeric: tabular-nums; }

/* #234 S3 — a paystub row carries several stacked detail blocks (earnings
   summary, the withheld block, the estimated-monthly line), and `.snapshot-list
   li` is `display: flex`, so every one of them became a COLUMN on the same row
   and the content ran off under the tab bar.

   ⚠ Pre-existing, not new: the single earnings `.hint` was already a flex item
   squeezed beside the date and the delete button. Adding the deductions block
   is what made it visible — a render check caught it while every text assertion
   in the E2E passed, because the text was all present, just unreadable.

   Scoped to #paystubs-history rather than fixing `.snapshot-list li` globally:
   the accounts page (dice-accounts.js:223) uses the same primitive for
   genuinely single-line rows, and widening the change there would be an
   unrelated risk. No new tokens, no new primitive — just wrapping. */
/* #227 — chart ink, tokenized.
   `public/js/financial.js` painted every SVG label with a hardcoded near-black
   (`#1a2530`, `#6c7d89`). Dark theme sets --wind-sky/--wind-white to #1A1612, so
   those sat near-black on near-black: MEASURED at **1.16:1** against WCAG AA's
   4.5:1, on the one DICE surface the whole family sees.

   ⚠ Classes, not a resolved hex read in JS. An SVG that bakes a hex is right
   only until the theme is toggled, and toggling does not re-render that page.
   With classes the browser restyles for free.

   ⚠ --wind-earth, NOT --wind-stone, for the axis. In DARK, stone (#9C8E7C)
   clears AA comfortably — but in LIGHT it is #8A7E6E on #FBF8F3, which computes
   to **3.70:1** and fails AA's 4.5 for the 10px axis text. The token that passes
   on BOTH surfaces is the one to pick; checking only the theme that was broken
   is how the opposite defect gets shipped.

   Measured after the fix (tests/e2e/financial.spec.ts asserts these):
     dark  — axis 9.35:1 · balance label 12.88:1  (were 4.22 and 1.16)
     light — axis 7.13:1 · balance label 11.45:1 */
.chart-ink          { fill: var(--wind-deep); }
.chart-axis-label   { fill: var(--wind-earth); }
.chart-axis         { stroke: var(--wind-earth); }
.chart-balance-line { stroke: var(--wind-deep); fill: none; }

#paystubs-history li { flex-wrap: wrap; align-items: baseline; }
#paystubs-history li .hint { flex: 1 0 100%; margin: 2px 0 0; }
/* At 375px the ×26/12 pill wraps onto its own line, and as a bordered inline
   box with the surrounding line-height it collided with the text above it.
   Caught on a mobile render check, not by any assertion — the text was all
   present and correct, just overlapping. */
#paystubs-history li .hint--warn { display: inline-block; margin-top: 3px; }

.commitments-list { margin: 8px 0; }
.commitment-row {
  background: var(--wind-white);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 6px;
}
.commitment-head {
  display: flex;
  align-items: center;
  gap: 6px;
}
.commitment-label {
  font-weight: 600;
  color: var(--wind-deep);
}
.commitment-meta {
  font-size: 0.85rem;
  color: var(--wind-stone);
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.commitment-notes { margin-top: 3px; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 6px 0;
}
.checkbox-row input[type="checkbox"] { width: auto; }

/* Upload preview tables (Phase 9b-2) */
.comp-preview {
  background: var(--wind-white);
  border: 1px solid var(--border-card);
  border-radius: 8px;
  padding: 14px 18px;
  margin: 10px 0;
}
.comp-preview h3 { margin: 0 0 8px; color: var(--wind-deep); font-size: 1rem; }
.preview-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.preview-table th {
  text-align: left;
  padding: 4px 10px 4px 0;
  color: var(--wind-earth);
  font-weight: 600;
}
.preview-table td {
  padding: 4px 10px;
  font-variant-numeric: tabular-nums;
}
.preview-table tr + tr th,
.preview-table tr + tr td {
  border-top: 1px solid var(--border-hairline);
}
/* #242 — a wide table in a narrow column. .preview-table has no mobile
   handling (it was built for a 2-column key/value preview), and the components
   panel is 4 columns: at 375px the State cell clipped, which is precisely the
   disposition the panel exists to make visible. Scroll rather than truncate.
   Scoped by its own class so no existing .preview-table caller changes. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.table-scroll > .preview-table {
  min-width: 30rem;
}

.preview-table .section-hd {
  padding-top: 12px;
  color: var(--wind-amber);
  text-transform: uppercase;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
}

/* ---------------------------------------------------------------------------
   Metis admin chat (Phase 9c)
   --------------------------------------------------------------------------- */

.metis-page { background: var(--wind-sky); }
.metis-body { padding: 16px; padding-bottom: 180px; max-width: 900px; margin: 0 auto; }

.metis-disclaimer {
  background: var(--wind-amber-light);
  border-left: 3px solid var(--wind-amber);
  padding: 10px 14px;
  border-radius: 6px;
  margin-bottom: 14px;
  font-size: 0.9rem;
  line-height: 1.35;
}

/* #117 slice 1: dvh so the message strip shrinks with the iOS keyboard
   (vh = layout viewport, stays tall → evicted the composer below it). */
.metis-messages { margin: 10px 0 16px; max-height: 70vh; max-height: 70dvh; overflow-y: auto; }
.metis-turn {
  background: var(--wind-white);
  border: 1px solid var(--border-card);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 8px;
}
.metis-turn-user { background: var(--wind-amber-light); }
.metis-turn-assistant { background: var(--wind-white); }
.metis-turn-system { background: var(--surface-subtle); font-style: italic; }
.metis-turn-head { font-size: 0.75rem; color: var(--wind-stone); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.04em; }
.metis-turn-body { font-size: 0.95rem; line-height: 1.45; color: var(--wind-deep); white-space: normal; }
.metis-summary { font-size: 0.85rem; color: var(--wind-earth); padding: 6px 0; }

.chat-compose textarea {
  width: 100%; padding: 10px 12px; border: 1px solid var(--border-emphatic); border-radius: 8px; resize: vertical;
  font-family: inherit; font-size: 0.95rem;
}
.chat-compose { display: flex; gap: 8px; align-items: flex-end; padding: 6px 0; }
.chat-compose textarea { flex: 1; }
.chat-compose button { flex: 0 0 auto; height: 44px; }

.metis-sidepanel {
  margin-top: 28px;
  padding: 12px 14px;
  background: var(--surface-subtle);
  border-radius: 8px;
}
.metis-sidepanel summary {
  cursor: pointer; font-weight: 600; color: var(--wind-deep);
}
.metis-memories { margin-top: 10px; }
.metis-memory {
  background: var(--wind-white);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  padding: 8px 12px;
  margin-bottom: 6px;
}
.metis-memory-expired { opacity: 0.5; }
.metis-memory-head { font-size: 0.8rem; color: var(--wind-earth); margin-bottom: 3px; }
.metis-memory-fact { font-size: 0.92rem; color: var(--wind-deep); }
.metis-memory-meta { margin-top: 3px; font-size: 0.75rem; }

/* Metis proposal cards (9e-4b accept/reject UI) */
.metis-proposals {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}
.proposal-card {
  background: var(--wind-white);
  border: 1px solid var(--border-emphatic);
  border-left: 4px solid var(--wind-amber);
  border-radius: 6px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.proposal-card.proposal-status-accepted { border-left-color: var(--wind-green); }
.proposal-card.proposal-status-executed {
  border-left-color: var(--wind-green);
  background: var(--surface-subtle);
}
.proposal-card.proposal-status-rejected {
  border-left-color: var(--wind-red);
  opacity: 0.7;
}
.proposal-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.proposal-kind {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 0.75rem;
  background: var(--wind-amber-light);
  color: var(--wind-amber-deep);
  padding: 2px 6px;
  border-radius: 3px;
  font-weight: 600;
}
.proposal-status {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--wind-earth);
}
.proposal-summary {
  font-size: 0.95rem;
  color: var(--wind-deep);
  line-height: 1.4;
}
.proposal-reasoning {
  font-size: 0.85rem;
  color: var(--wind-earth);
  font-style: italic;
  padding-left: 10px;
  border-left: 2px solid var(--wind-dune);
}
.proposal-response {
  font-size: 0.85rem;
  color: var(--wind-stone);
  padding: 4px 8px;
  background: var(--surface-subtle);
  border-radius: 3px;
}
.proposal-meta {
  font-size: 0.72rem;
  color: var(--wind-stone);
}
.proposal-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}
.proposal-actions .btn-primary,
.proposal-actions .btn-secondary {
  min-height: 36px;
  padding: 6px 14px;
  font-size: 0.88rem;
}
.proposal-error {
  font-size: 0.85rem;
  color: var(--status-danger-text);
  background: var(--status-danger-bg);
  border: 1px solid var(--status-danger-border);
  padding: 6px 8px;
  border-radius: 4px;
}

/* ---------------------------------------------------------------------------
   Phase 9e-2c — Home Depot mode (narrow viewport list view)

   The /us/dad-list/list surface is the default on iPhone (per ADR-9e-3)
   and serves as the "shopping list / what do I need to grab" view. On
   narrow viewports we enlarge tap targets, stack action buttons, and
   make the "Show on map" action prominent.

   "Degraded" relative to the full design spec: no per-project grouping,
   no purchase sub-task checklists, no receipt-capture. Those land when
   9e-3 (projects) + 9e-4b (Metis purchase sub-tasks) ship. What we get
   here is a thumb-friendly flat list James can drive one-handed from
   a Home Depot cart.
   --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .dad-list-page .topic-card {
    padding: 12px 14px;
    margin-bottom: 10px;
  }
  .dad-list-page .topic-title {
    font-size: 1.05rem;
    line-height: 1.35;
  }
  .dad-list-page .topic-meta {
    font-size: 0.88rem;
    line-height: 1.5;
  }
  .dad-list-page .topic-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    margin-top: 10px;
    border-top: 1px solid var(--wind-dune);
    padding-top: 10px;
  }
  .dad-list-page .topic-actions .link-btn {
    min-height: 44px;
    padding: 10px 12px;
    text-align: left;
    text-decoration: none;
    border-radius: 6px;
    font-size: 0.95rem;
  }
  .dad-list-page .topic-actions .link-btn:hover,
  .dad-list-page .topic-actions .link-btn:focus-visible {
    background: rgba(196, 137, 46, 0.08);
    text-decoration: underline;
  }
  /* "Show on map" stands out — map icon prefix + weight. */
  .dad-list-page .topic-actions .link-btn.show-on-map {
    font-weight: 600;
  }
  .dad-list-page .topic-actions .link-btn.show-on-map::before {
    content: '🗺️  ';
  }

  /* Zone pills: larger touch targets */
  .dad-list-page .subject-pills .subject-pill,
  .dad-list-page .subject-pills .zone-pill {
    min-height: 40px;
    padding: 8px 14px;
  }

  /* Modal: full-screen-ish on narrow */
  .dad-list-page .modal-card {
    width: 100%;
    max-width: none;
    max-height: calc(100vh - 24px);
    overflow-y: auto;
    margin: 12px;
    padding: 16px;
    box-sizing: border-box;
  }
  .dad-list-page .modal-card.wide {
    max-width: none;
  }

  /* New-item button full-width */
  .dad-list-page .dad-list-toolbar #new-item-btn {
    width: 100%;
    min-height: 48px;
    font-size: 1rem;
  }
}

/* Always — outside narrow media — make "Show on map" distinguishable */
.show-on-map { font-weight: 500; }

/* ==========================================================================
   Phase 14c — /us/home rebuild.
   Server-rendered shell on <body class="home-page--14c">. The old generic
   .home-grid / .home-card / .coming-soon / .landing styles were deleted in
   Phase 15a-α (they were orphaned after the 14c rebuild deleted landing.html).
   ========================================================================== */

.home-page--14c {
  background: var(--wind-sky);
  color: var(--wind-earth);
}
/* #117 slice 4: the scroll region inside the .home-page shell. Carries the
   centered 1180px content column + side gutters that used to live on the body;
   the body is now a full-width flex column so the tab bar spans edge-to-edge.
   The old 96px bottom tab-bar clearance is gone — the bar is a flex child that
   takes real layout space now. */
.home-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* #226 — was implicit `auto` (set one axis, the other computes visible→auto):
     an unbroken chat token let the region PAN sideways on mobile. */
  overflow-x: hidden;
  width: 100%;
  /* #226 — the scroll CONTAINER must be full-bleed or its scrollbar floats at
     the content column's edge, mid-window on wide screens (James's "weird
     scroll bar"). The 1180px centered column is now the computed side padding;
     no markup change. */
  padding: 0 max(16px, calc((100% - 1180px) / 2)) 24px;
}

/* Wordmark + wind streak ----------------------------------------------- */
.home-wordmark {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 4px 12px;
}

.home-wordmark__streak {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.home-wordmark__text {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 2.5rem;
  font-weight: 400;
  line-height: 1;
  color: var(--wind-deep);
  letter-spacing: -0.01em;
  position: relative;
  z-index: 1;
}

/* Joke band — shelved #206, no markup emits these; dormant in place ------ */
.home-joke,
.home-proverb {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  padding: 20px 24px 18px;
  margin: 4px 0 20px;
  box-shadow: var(--shadow-subtle);
}

.home-joke__text,
.home-proverb__text {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.45rem;
  line-height: 1.35;
  color: var(--wind-deep);
  margin: 0 0 10px;
}

/* Joke meta row: sig left + chip right, single row to reclaim vertical
   space. Replaces the old layout where sig and chip stacked on rows 3+4. */
.home-joke__meta,
.home-proverb__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.home-joke__sig,
.home-proverb__sig {
  font-size: 0.85rem;
  color: var(--wind-stone);
  font-style: italic;
}

.home-joke__chip {
  font-size: 0.85rem;
  color: var(--wind-amber);
  text-decoration: none;
  white-space: nowrap;
}
.home-joke__chip:hover { color: var(--wind-amber-hover, var(--wind-amber)); text-decoration: underline; }

/* 2-column grid (iPad landscape and up) -------------------------------- */
.home-grid-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  align-items: start;
  margin-bottom: 18px;
}

.home-grid-2col__left,
.home-grid-2col__right {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Hero ----------------------------------------------------------------- */
.home-hero {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  padding: 28px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow-subtle);
  flex: 1;
  --hero-tint: var(--wind-amber);
}

a.home-hero:hover { box-shadow: var(--shadow-card-hover); }

.home-hero__disc {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: var(--hero-tint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;
  flex-shrink: 0;
  border: none;
  cursor: pointer;
  padding: 0;
  transition: transform 150ms cubic-bezier(0.2, 0.7, 0.2, 1), box-shadow 150ms;
}

.home-hero__disc:hover,
.home-hero__disc:focus-visible {
  transform: scale(1.04);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  outline: none;
}

.home-hero__initial {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 2.5rem;
  font-weight: 500;
  color: var(--wind-deep);
  line-height: 1;
}

.home-hero__name {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.25rem;
  color: var(--wind-deep);
  margin-bottom: 6px;
}

.home-hero__hook {
  font-size: 0.95rem;
  color: var(--wind-stone);
  line-height: 1.4;
  max-width: 28ch;
}

.home-hero__see-all {
  margin-top: 18px;
  font-size: 0.85rem;
  background: transparent;
  border: 1px solid var(--wind-dune);
  border-radius: 999px;
  padding: 6px 14px;
  color: var(--wind-earth);
  cursor: pointer;
}
.home-hero__see-all:hover { background: var(--wind-sand); }

/* Adult hero — agent strip at hero size. Reuses .agent-strip primitive. */
.home-hero--adult.agent-strip--hero {
  position: static;
  flex-direction: column;
  gap: 0;
  padding: 28px 24px;
  background: var(--wind-white);
  border-bottom: none;
  margin-bottom: 0;
}

.home-hero--adult .home-hero__disc {
  --agent-tint: var(--hero-tint);
  background: var(--hero-tint);
  color: var(--wind-deep);
}

.home-hero--adult .home-hero__form {
  display: flex;
  width: 100%;
  align-items: stretch;
  gap: 8px;
  margin-top: 16px;
}

/* Right-column cards --------------------------------------------------- */
.home-card--chore,
.home-card--chat,
.home-card--next-up {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 14px;
  padding: 16px 18px;
  text-decoration: none;
  color: inherit;
  display: block;
  box-shadow: var(--shadow-subtle);
  /* #226 — user content (chat previews carry URLs) must wrap, not widen the
     page: one unbroken token here blew htmlScrollWidth 375→2542 at mobile.
     Same defense the chat page itself carries. */
  word-break: break-word;
}

/* #226 — the proverb card renders school-file text; same wrap defense. */
.home-proverb { word-break: break-word; }

a.home-card--chore:hover,
a.home-card--chat:hover,
a.home-card--next-up:hover { box-shadow: var(--shadow-card-hover); }

.home-card__label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--wind-stone);
  margin-bottom: 8px;
}

.home-card__title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.15rem;
  color: var(--wind-deep);
}

.home-card__empty {
  font-size: 0.9rem;
  color: var(--wind-stone);
  font-style: italic;
  min-height: 2.4em;
  display: flex;
  align-items: center;
}

.chat-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.chat-row__avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  color: white;
  font-weight: 600;
}

.chat-row__name {
  font-size: 0.85rem;
  color: var(--wind-deep);
  font-weight: 600;
}

.chat-row__time {
  font-size: 0.8rem;
  color: var(--wind-stone);
}

.chat-row__preview {
  font-size: 0.9rem;
  color: var(--wind-earth);
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Next-up card (Phase 14g — replaces presence row) -------------------- */
.next-up__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.next-up__row {
  display: grid;
  /* Phase 14h #52: was `64px 60px 1fr auto` — 64px wasn't wide enough for
     `TOMORROW` (needs ~81px), causing the day label to bleed into the time
     column. Auto-sizing the day + time columns sizes them to the widest
     content found across all rows in the grid, so TODAY/TOMORROW rows
     stay aligned and the label cannot overflow into the time. */
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  color: var(--wind-deep);
  min-width: 0;
}

.next-up__day {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--wind-stone);
}

.next-up__time {
  font-size: 0.85rem;
  color: var(--wind-earth);
  font-variant-numeric: tabular-nums;
}

.next-up__summary {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.next-up__pill {
  font-size: 0.7rem;
  border-radius: 4px;
  padding: 1px 6px;
  white-space: nowrap;
}

.next-up__see-all {
  margin-top: 8px;
  font-size: 0.85rem;
  color: var(--wind-amber);
}

/* Utility row ---------------------------------------------------------- */
.home-utility {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-top: 4px;
}

.home-utility__item {
  background: var(--wind-white);
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  padding: 16px 8px;
  text-decoration: none;
  color: var(--wind-deep);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  font-weight: 500;
  box-shadow: var(--shadow-subtle);
}

.home-utility__item:hover { box-shadow: var(--shadow-card-hover); }

.home-utility__icon { color: var(--wind-amber); display: inline-flex; }
.home-utility__icon svg { width: 24px; height: 24px; }

/* iPad portrait + iPhone collapse to single column --------------------- */
@media (max-width: 1023px) {
  .home-grid-2col { grid-template-columns: 1fr; }
  /* Phase 14h #1: in 2-col landscape the hero takes flex: 1 to match the
     right column's stack height. In 1-col there is no tall sibling, so
     flex: 1 stretches the hero to fill all leftover viewport space —
     producing a tall hero with a void below the disc. Reset to auto
     sizing in single-column. */
  .home-hero { flex: 0 1 auto; }
}

@media (max-width: 743px) {
  .home-scroll { padding: 0 12px 24px; }
  .home-wordmark__text { font-size: 2rem; }
  .home-joke__text, .home-proverb__text { font-size: 1.25rem; }
  .home-hero { padding: 22px 18px; }
  .home-hero__disc { width: 96px; height: 96px; }
  .home-hero__initial { font-size: 2rem; }
}

/* ==========================================================================
   Phase 14f — theme toggle UI (profile radio + home footer button) +
   accessibility utility.
   ========================================================================== */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.theme-radio {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.theme-radio label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.95rem;
  color: var(--wind-deep);
  cursor: pointer;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--wind-dune);
  background: var(--wind-white);
  transition: background 0.15s, border-color 0.15s;
}
.theme-radio label:hover { background: var(--wind-sand); }
.theme-radio input[type="radio"] {
  accent-color: var(--wind-amber);
  width: 18px;
  height: 18px;
}

/* Avatar popover menu (Phase 14g) -------------------------------------- */
.me-menu-wrap {
  position: relative;
  display: inline-flex;
}

.me-menu {
  /* Inherits [popover] positioning from the user agent. We anchor visually
     to the avatar via margin + a top arrow and rely on the page-header
     placing the avatar in the top-right corner. */
  margin: 0;
  padding: 8px;
  border: 1px solid var(--wind-dune);
  border-radius: 12px;
  background: var(--wind-white);
  box-shadow: var(--shadow-card-hover);
  min-width: 220px;
  max-width: 280px;
  color: var(--wind-deep);
  font-size: 0.95rem;
  /* Native [popover] elements inset to viewport center by default; we
     position to the top-right of the screen so the menu lands under the
     avatar regardless of header layout. */
  inset: auto 16px auto auto;
  top: 64px;
}

.me-menu__item {
  display: block;
  padding: 10px 12px;
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.me-menu__item:hover,
.me-menu__item:focus-visible {
  background: var(--wind-sand);
  outline: none;
}
.me-menu__item--danger { color: var(--wind-stone); }
.me-menu__item--danger:hover { background: var(--wind-sand); color: var(--wind-deep); }

.me-menu__divider {
  border: 0;
  border-top: 1px solid var(--wind-dune);
  margin: 6px 4px;
}

.me-menu__group {
  padding: 6px 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.me-menu__group-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--wind-stone);
  width: 100%;
  padding: 0 8px 4px;
}

.me-menu__theme {
  flex: 1 1 auto;
  min-height: 36px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--wind-dune);
  background: var(--wind-white);
  color: var(--wind-deep);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}
.me-menu__theme:hover { background: var(--wind-sand); }
.me-menu__theme.is-active {
  background: var(--wind-amber);
  color: #fff;
  border-color: var(--wind-amber);
}

/* ===========================================================================
 * Phase 17a/b-lite — kid finances page (/us/me/finances)
 * Minimal additions: forecaster sliders, portfolio table, summary rows.
 * Reuses .dashboard-hero / .hero-card / .hero-num / .chores-container.
 * =========================================================================== */

.me-finances-container {
  max-width: 920px;
  padding-bottom: 6rem;
}

.me-finances-page .page-header {
  position: relative;
}

.page-header__back {
  position: absolute;
  top: 0.6rem;
  left: 1rem;
  font-size: 0.85rem;
  color: var(--wind-deep);
  opacity: 0.7;
  text-decoration: none;
}

.page-header__back:hover { opacity: 1; }

.me-section {
  background: var(--wind-white);
  border: 1px solid var(--surface-border);
  border-radius: 14px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
}

.me-section h2 {
  font-size: 1.1rem;
  margin: 0 0 0.5rem 0;
  color: var(--wind-deep);
}

.forecaster {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin-top: 1rem;
}

.forecaster-row {
  display: grid;
  grid-template-columns: 9rem 1fr 6rem;
  align-items: center;
  gap: 0.75rem;
}

.forecaster-row label {
  font-weight: 600;
  font-size: 0.95rem;
}

.forecaster-row input[type="range"] {
  accent-color: var(--wind-amber);
}

.forecaster-val {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--wind-deep);
}

.forecaster-result {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--surface-border);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.forecaster-final {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

.forecaster-eyebrow {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--wind-deep);
  opacity: 0.6;
}

.forecaster-num {
  font-family: var(--font-display, serif);
  font-size: 1.85rem;
  color: var(--wind-amber);
  font-variant-numeric: tabular-nums;
}

.forecaster-breakdown {
  font-size: 0.85rem;
  color: var(--wind-deep);
  opacity: 0.75;
}

@media (max-width: 540px) {
  .forecaster-row {
    grid-template-columns: 1fr 5rem;
    grid-template-areas:
      "label val"
      "input input";
    row-gap: 0.4rem;
  }
  .forecaster-row label { grid-area: label; }
  .forecaster-row input[type="range"] { grid-area: input; }
  .forecaster-val { grid-area: val; }
}

/* Portfolio summary + table */

.portfolio-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-bottom: 1rem;
}

.portfolio-summary-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.portfolio-summary-label {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--wind-deep);
  opacity: 0.6;
}

.portfolio-summary-val {
  font-family: var(--font-display, serif);
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
}

.portfolio-summary-val.pos { color: #2f7a3a; }
.portfolio-summary-val.neg { color: #b94f3f; }

.portfolio-kind-heading {
  font-size: 0.95rem;
  margin: 1.25rem 0 0.5rem 0;
  font-weight: 600;
  color: var(--wind-deep);
  opacity: 0.85;
}

.portfolio-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.portfolio-table th,
.portfolio-table td {
  padding: 0.5rem 0.75rem;
  text-align: left;
  border-bottom: 1px solid var(--surface-border);
}

.portfolio-table th {
  font-weight: 700;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--wind-deep);
  opacity: 0.6;
}

.portfolio-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.portfolio-table .pos { color: #2f7a3a; }
.portfolio-table .neg { color: #b94f3f; }

.portfolio-symbol {
  font-weight: 700;
  letter-spacing: 0.02em;
}

.portfolio-name {
  color: var(--wind-deep);
  opacity: 0.75;
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 540px) {
  .portfolio-table { font-size: 0.8rem; }
  .portfolio-table th,
  .portfolio-table td { padding: 0.4rem 0.4rem; }
  .portfolio-name { max-width: 6rem; }
}

.me-finances-empty {
  text-align: center;
  margin-top: 3rem;
  color: var(--wind-deep);
  opacity: 0.6;
}

/* Phase 17a-fu — admin kid-portfolios page (/us/adults/kid-portfolios) */

.kid-portfolios-container {
  max-width: 1100px;
  padding-bottom: 6rem;
}

.kid-section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.kid-section-header h2 {
  margin: 0;
}

.kid-section-actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.admin-portfolio-table th,
.admin-portfolio-table td {
  font-size: 0.85rem;
}

.admin-portfolio-table .row-actions {
  text-align: right;
  white-space: nowrap;
}

.btn-text {
  background: transparent;
  border: none;
  color: var(--wind-amber);
  cursor: pointer;
  font-size: 0.85rem;
  padding: 0.25rem 0.5rem;
  font-weight: 600;
}

.btn-text:hover { text-decoration: underline; }

.btn-text--danger {
  color: #b94f3f;
}

.admin-portfolio-table .stale {
  color: #b9803f;
  font-style: italic;
}

@media (max-width: 720px) {
  .admin-portfolio-table { font-size: 0.78rem; }
  .admin-portfolio-table th,
  .admin-portfolio-table td {
    padding: 0.35rem 0.35rem;
  }
  .admin-portfolio-table .portfolio-name {
    max-width: 6rem;
  }
}

/* Modal form rows */
.kid-portfolios-page .modal-card label {
  display: block;
  margin-bottom: 0.75rem;
  font-weight: 600;
}

.kid-portfolios-page .modal-card label input,
.kid-portfolios-page .modal-card label select {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.5rem;
  border-radius: 6px;
  border: 1px solid var(--surface-border);
  font-size: 1rem;
  color: var(--wind-deep);
  background: var(--wind-white);
}

.kid-portfolios-page .modal-card .form-row {
  display: flex;
  gap: 0.75rem;
}

.kid-portfolios-page .modal-card .form-row__col {
  flex: 1;
}

.kid-portfolios-page .modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* ---------------------------------------------------------------------------
   Phase 10L Slice 0 — /us/dice/taxonomy
   --------------------------------------------------------------------------- */

.tax-row {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  gap: 12px;
  padding: 10px 12px;
  align-items: start;
  border-bottom: 1px solid var(--border-soft);
}

.tax-row__handle {
  cursor: grab;
  color: var(--wind-stone);
  user-select: none;
  font-size: 1.1rem;
  line-height: 1.4;
  letter-spacing: -0.2em;
  padding-top: 2px;
}
.tax-row[draggable="false"] .tax-row__handle { cursor: default; opacity: 0.3; }
.tax-row--dragging { opacity: 0.5; }
.tax-row--drop-above { border-top: 2px solid var(--wind-amber); }
.tax-row--drop-below { border-bottom: 2px solid var(--wind-amber); }

.tax-row__main { min-width: 0; }
.tax-row__top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.tax-row__slug {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  background: var(--wind-sand);
  color: var(--wind-stone);
  padding: 1px 6px;
  border-radius: 4px;
}
.tax-row__desc {
  margin-top: 4px;
  font-size: 0.88rem;
  color: var(--wind-stone);
}
.tax-row__meta {
  margin-top: 4px;
  font-size: 0.78rem;
}

.tax-row__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-self: center;
}
.tax-row__actions button {
  padding: 4px 10px;
  font-size: 0.85rem;
}
.tax-row__up, .tax-row__down {
  min-width: 32px;
  padding: 4px 8px;
}

.tax-row--archived {
  opacity: 0.55;
  background: var(--wind-sand);
}
.tax-row--archived .tax-row__slug { text-decoration: line-through; }

/* #92 S3 — the row had NO narrow-viewport rule, so `24px 1fr auto` gave the
   four action buttons ~250px of a 390px screen and squeezed the content into
   ~90px: a label wrapping over four lines above an unreadable note. Latent for
   the first three taxonomies, whose descriptions are short or absent; the tax
   axis is the first with ~450-character notes, which is what made it visible.
   Below 640px the actions drop under the content and span its column. */
@media (max-width: 640px) {
  .tax-row {
    grid-template-columns: 24px 1fr;
    row-gap: 8px;
  }
  .tax-row__actions {
    grid-column: 2;
    justify-content: flex-start;
    align-self: start;
  }
}

.tax-pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--wind-amber-light);
  color: var(--wind-amber);
}
.tax-pill--archived {
  background: var(--wind-sand);
  color: var(--wind-stone);
}

.tax-color-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid var(--border-soft);
  vertical-align: middle;
}

/* ---------------------------------------------------------------------------
   Phase 10L Slice A — /us/dice/mercury-transactions
   New register-table primitive. Documented in docs/design-system.md.
   --------------------------------------------------------------------------- */

/* Failed-status warning banner */
/* Per-account tab strip on /us/dice/mercury-transactions. Account is the
   primary axis when scanning the register; defaulting to one account keeps
   the visual model "you are looking at THIS account" rather than dumping a
   mixed stream. The Account filter dropdown was removed in favor of these
   tabs (post-10c v2 design rework). */
.register-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 12px 0;
  border-bottom: 1px solid var(--border-soft);
  overflow-x: auto;
  scrollbar-width: thin;
}
.register-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 8px 14px;
  margin: 0;
  font: inherit;
  font-size: 0.9rem;
  color: var(--wind-stone);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.register-tab:hover {
  color: var(--wind-deep);
  background: var(--surface-subtle);
}
.register-tab.is-active {
  color: var(--wind-deep);
  border-bottom-color: var(--wind-amber);
  font-weight: 600;
}
.register-tab--all {
  margin-left: auto; /* push "All accounts" to the right end of the strip */
}
.register-tab:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: -2px;
}

.failed-banner {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 8px 12px;
  margin: 0 0 12px 0;
  background: var(--wind-amber-light);
  color: var(--wind-amber);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  font-size: 0.9rem;
}
.failed-banner__count {
  display: inline-block;
  min-width: 28px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--wind-amber);
  color: white;
  font-weight: 700;
  text-align: center;
}

/* Collapsible filter panel */
.filter-panel {
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  margin: 0 0 12px 0;
  background: var(--surface-subtle);
}
.filter-panel > summary {
  list-style: none;
  cursor: pointer;
  padding: 10px 14px;
  display: flex;
  gap: 12px;
  align-items: center;
}
.filter-panel > summary::-webkit-details-marker { display: none; }
.filter-panel > summary::after {
  content: '▾';
  margin-left: auto;
  color: var(--wind-stone);
  transition: transform 0.15s;
}
.filter-panel[open] > summary::after { transform: rotate(180deg); }
.filter-panel__label { font-weight: 600; }

.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
  padding: 0 14px 14px;
}
.filter-field { display: flex; flex-direction: column; gap: 4px; }
.filter-field--wide { grid-column: 1 / -1; }
.filter-field label { font-size: 0.78rem; color: var(--wind-stone); }
.filter-field select, .filter-field input {
  padding: 6px 10px;
  font-size: 0.92rem;
  border: 1px solid var(--border-emphatic);
  border-radius: 4px;
  background: var(--surface-subtle);
  color: var(--wind-earth);
}
.filter-actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Register-table toolbar (sort + count) */
.register-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 6px 0;
  font-size: 0.9rem;
}
.register-toolbar__spacer { flex: 1; }

/* Register table */
.register {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}
.register thead th {
  text-align: left;
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--wind-stone);
  padding: 6px 10px;
  border-bottom: 1px solid var(--border-emphatic);
}
.register tbody tr {
  cursor: pointer;
  border-bottom: 1px solid var(--border-hairline);
}
.register tbody tr:hover { background: var(--surface-subtle); }
.register tbody td { padding: 8px 10px; vertical-align: middle; }
.register__amt { text-align: right; font-variant-numeric: tabular-nums; }
.register__amt--in { color: var(--wind-green); }
.register__amt--out { color: var(--wind-red); }

.register-cp { font-weight: 500; }
.register-cp__sub { display: block; font-size: 0.78rem; color: var(--wind-stone); font-weight: 400; }

.register-pill {
  display: inline-block;
  padding: 1px 8px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-radius: 999px;
  background: var(--wind-sand);
  color: var(--wind-stone);
}
.register-pill--failed { background: var(--wind-amber-light); color: var(--wind-amber); }
.register-pill--pending { background: var(--surface-subtle); color: var(--wind-stone); border: 1px dashed var(--border-soft); }
/* #149 — 'excluded' (failed/cancelled/reversed, $0 moved): muted, de-emphasized. */
.register-pill--excluded { background: transparent; color: var(--text-muted); border: 1px dashed var(--border-soft); text-transform: none; }
/* #281 — the Archived register's restored / gone lines: muted through the TOKEN, never opacity
   (`.event-row.is-closed`'s 0.65 is documented above as falling below AA; `.file-card.archived`'s
   0.6 is the live anti-pattern). `--text-muted` clears AA on every surface in both themes. */
.archived-row--muted td, .archived-row--muted .archived-row__label { color: var(--text-muted); }
.archived-row--muted .register-pill { background: var(--surface-subtle); color: var(--text-muted); }

.register-classify {
  font-size: 0.85rem;
  padding: 3px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  background: var(--surface-subtle);
}

.register-pagination {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  justify-content: center;
}

/* Mobile: card-stack at <768px (per kickoff lock #3) */
@media (max-width: 767px) {
  .register thead { display: none; }
  .register, .register tbody, .register tr, .register td { display: block; width: 100%; }
  .register tbody tr {
    padding: 10px 12px;
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    margin-bottom: 8px;
  }
  .register tbody td {
    padding: 4px 0;
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 8px;
    /* A grid item's default `min-width: auto` refuses to shrink below its longest
       unbreakable token, so one long value blows the card out and scrolls the whole
       PAGE sideways. Measured on /us/dice/webhook-dlq at 375px:
       `checkingAccount.balance.updated` gave document scrollWidth 389 vs client 375
       (worst cell overflowing by 43px). Both properties are needed — min-width lets
       the column shrink, overflow-wrap lets the token break. Inert wherever the
       content already fits, and desktop is untouched (this is inside the <768px
       block). #168 S3, found in the render check with @smoke green. */
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .register tbody td::before {
    content: attr(data-label);
    font-size: 0.74rem;
    color: var(--wind-stone);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .register__amt { text-align: left; }
}

/* Slide-in detail drawer (right-anchored modal dialog) */
.detail-drawer {
  margin: 0 0 0 auto;
  padding: 0;
  border: none;
  width: min(560px, 100vw);
  height: 100vh;
  max-height: 100vh;
  max-width: 100vw;
  background: var(--bg-primary);
  color: var(--wind-earth);
}
.detail-drawer::backdrop {
  background: rgba(0,0,0,0.35);
}
.detail-drawer[open] { animation: slide-in-right 0.2s ease-out; }
@keyframes slide-in-right {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .detail-drawer[open] { animation: none; }
}
.detail-drawer__inner { display: flex; flex-direction: column; height: 100%; }
.detail-drawer__header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--border-soft);
}
.detail-drawer__eyebrow { margin: 0 0 2px 0; }
.detail-drawer__header h2 { margin: 0; font-size: 1.1rem; }
.detail-drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: 16px 18px 80px;
}
.detail-section { margin-bottom: 18px; }
.detail-section h3 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--wind-stone);
  margin: 0 0 6px 0;
}
.detail-grid {
  display: grid;
  grid-template-columns: 130px 1fr;
  row-gap: 4px;
  column-gap: 12px;
  margin: 0;
  font-size: 0.9rem;
}
.detail-grid dt { color: var(--wind-stone); }
/* Phase 10N Slice C — soften value text in dark mode. Default body color
   (--wind-deep in dark) ran near-white against the dark drawer bg; user
   2026-05-23 PM: "this is very bright." Stepped down to --wind-earth for
   a meaningful contrast reduction without losing legibility. */
.detail-grid dd { color: var(--wind-earth); margin: 0; }
.detail-grid dd { margin: 0; word-break: break-word; }

.detail-classify-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
.detail-bridge {
  padding: 6px 10px;
  margin-bottom: 6px;
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  font-size: 0.88rem;
}
.detail-bridge a { color: var(--wind-deep); text-decoration: underline; }

.detail-audit-entry {
  padding: 6px 0;
  border-bottom: 1px solid var(--border-hairline);
  font-size: 0.85rem;
}
.detail-audit-entry:last-child { border-bottom: none; }
.detail-audit-entry__meta { color: var(--wind-stone); font-size: 0.78rem; }

.detail-raw {
  font-size: 0.78rem;
  background: var(--surface-subtle);
  padding: 10px;
  border-radius: 4px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---------------------------------------------------------------------------
   Phase 10L Slice C — /us/dice/lending kind chips + kind badge
   --------------------------------------------------------------------------- */

.kind-chip {
  font-size: 0.85rem;
  padding: 4px 10px;
}

/* Kind badge used inline on lending rows. Subdued tint so it sits next to
   the bolder status badge without competing. */
.lending-list .badge-kind,
.badge-kind {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--wind-sand);
  color: var(--wind-stone);
}

/* ---------------------------------------------------------------------------
   Phase 10L Slice B — Closed accordions on /us/dice/debt + closed-row tone.
   --------------------------------------------------------------------------- */

.closed-accordion {
  margin-top: 1rem;
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  background: var(--surface-subtle);
}
.closed-accordion > summary {
  cursor: pointer;
  padding: 8px 12px;
  font-weight: 600;
  list-style: none;
  display: flex;
  gap: 8px;
  align-items: center;
}
.closed-accordion > summary::-webkit-details-marker { display: none; }
.closed-accordion > summary::after {
  content: '▾';
  margin-left: auto;
  color: var(--wind-stone);
  transition: transform 0.15s;
}
.closed-accordion[open] > summary::after { transform: rotate(180deg); }
.closed-accordion > p,
.closed-accordion > .events-list {
  padding: 0 12px 8px;
}

/* Subdued tone on closed cards/loans so they read as historical without
   disappearing entirely. */
.event-row.is-closed {
  opacity: 0.65;
}
.event-row.is-closed .event-title { text-decoration: line-through; text-decoration-color: var(--wind-stone); }

/* /us/dice/debt — credit-card-row + installment-loan-row override the .event-row
   grid (which was sized for the /us/dice/expected page: 6 narrow columns). Debt
   rows have only 2 children (.event-row-main flex-grows; .event-row-actions
   stays inline-row), so override to flex. Without this the row inherits the
   6-column grid template, the action buttons stack vertically, and the title
   wraps every few characters. Pre-existed before Phase 10N Slice A; surfaced
   2026-05-23 when James loaded /us/dice/debt after Slice A landed.
   Bug-class: shared .event-row CSS reused across surfaces with different
   child structure (sister to #76 P2-5 inbox-row split). */
.event-row.credit-card-row,
.event-row.installment-loan-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 8px 12px;
  /* #214 S3 — these rows gained a THIRD child (.snapshot-history). Same shape the
     expected page uses for .recommendation-result: wrap enabled here, 100% basis there. */
  flex-wrap: wrap;
}

/* #214 S3 — per-row snapshot history on /us/dice/debt. The table itself is the existing
   `.register` primitive (design-system.md:212 names it the reuse target for exactly this
   "browse a long-lived table" job — already tokenized, already card-stacks at <768px via
   td[data-label]); this only positions it and corrects one inherited affordance. */
.event-row .snapshot-history {
  flex-basis: 100%;
  width: 100%;
  margin-top: 8px;
}
.event-row .snapshot-history[hidden] { display: none; }
/* `.register tbody tr` is `cursor: pointer` because on the Mercury register the whole row
   opens a drawer. Here only the Delete button is clickable, so the pointer would be a lie. */
.snapshot-history .register tbody tr { cursor: default; }
.snapshot-history .register tbody tr:hover { background: transparent; }
/* #156's touch-target lesson, applied to NEW chrome: `.link-btn` is padding-sized (~25px tall),
   which is fine for an inline text link and not fine for the only destructive control in a row.
   `.btn-secondary` already carries min-height:44px, so the History/Restore buttons need nothing. */
.snapshot-history .register .snapshot-delete {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  text-decoration: none;
}
.event-row.credit-card-row .event-row-main,
.event-row.installment-loan-row .event-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.event-row.credit-card-row .event-title,
.event-row.installment-loan-row .event-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-weight: 600;
}
.event-row.credit-card-row .event-meta,
.event-row.installment-loan-row .event-meta {
  font-size: 0.85rem;
  color: var(--text-muted);   /* #299 — these rows sit on the page's sky, where --wind-stone is 3.75:1; the muted slot is 5.05 */
}
.event-row.credit-card-row .event-row-actions,
.event-row.installment-loan-row .event-row-actions {
  display: flex;
  flex-direction: row;
  gap: 6px;
  flex-shrink: 0;
  flex-wrap: wrap;
}
/* #294 — at 375 the five-button strip measured 512–594 px and the section scrolled sideways: the
   strip is a flex ITEM with `flex-shrink: 0` and no basis, so it sizes to its max-content width and
   its own `flex-wrap` has nothing to wrap within. The row already wraps (`flex-wrap: wrap`, #214 S3,
   for .snapshot-history's 100 % line above); giving the strip a 100 % basis drops it onto its own
   line under 768 px, where its buttons then fold. Same specificity as the base rule, later in
   source, so this wins over `flex-shrink: 0` (a rule inside the .register block at ~:7929 would
   not — it precedes the base rule). Inert at ≥ 768 px. */
@media (max-width: 767px) {
  .event-row.credit-card-row .event-row-actions,
  .event-row.installment-loan-row .event-row-actions {
    flex: 1 1 100%;
    min-width: 0;
  }
}

/* Phase 10N Slice A.3 — per-row visibility pills (M for Metis, H for Hestia).
   Click to toggle the visible_to_* flag without opening the edit modal.
   Sized small + monospaced so two pills next to a row title don't take
   meaningful space. Uses design-system color tokens so dark mode works. */
.visibility-pill {
  display: inline-block;
  padding: 0 6px;
  border-radius: 8px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.5;
  cursor: pointer;
  user-select: none;
  border: 1px solid transparent;
  transition: background 0.1s, border-color 0.1s;
}
.visibility-pill[data-current="0"] {
  background: transparent;
  color: var(--wind-stone);
  border-color: var(--border-soft);
}
.visibility-pill[data-current="0"]:hover {
  border-color: var(--wind-stone);
}
.visibility-pill[data-current="1"][data-axis="metis"] {
  background: var(--wind-green-light);
  color: var(--wind-green);
  border-color: var(--wind-green);
}
.visibility-pill[data-current="1"][data-axis="hestia"] {
  background: var(--wind-amber-light);
  color: var(--wind-amber);
  border-color: var(--wind-amber);
}

/* /us/dice/inbox — needs-review row.
   The original surfaces page reused .event-row's 6-column grid for this
   row, but the inbox renders 7 children with different intent (date /
   account / amount / desc / classify-select / status). The shared grid
   pinned the description column at 110px and gave the select 1fr — a
   11/10 footgun. This grid sizes for the inbox's actual content: desc
   is the elastic column, classify-select is fixed-width.
   Auto-save on change replaces the per-row Save button (Phase post-10c v2). */
.inbox-row {
  display: grid;
  grid-template-columns: 80px 100px 90px 1fr 220px 70px;
  gap: 10px;
  padding: 8px 10px;
  align-items: center;
  border-bottom: 1px solid var(--border-soft);
  font-size: 0.92rem;
}
.inbox-row__date { white-space: nowrap; color: var(--wind-stone); font-size: 0.85rem; }
.inbox-row__account {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--wind-stone);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.inbox-row__amt {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.inbox-row__amt.outflow { color: var(--wind-red); }
.inbox-row__amt.inflow { color: var(--wind-green); }
.inbox-row__desc { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.inbox-row .row-classify {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--border-emphatic);
  border-radius: 4px;
  background: var(--surface-subtle);
  color: var(--wind-earth);
  font-size: 0.88rem;
}
.inbox-row .row-status {
  font-size: 0.78rem;
  color: var(--wind-stone);
  white-space: nowrap;
}
/* #217 D11 — mint-failure modifier: color only, zero layout change. The mint
   outcome rides the EXISTING light per-row text line, never .status-banner
   (page-level primitive; its box blows out the 70px inbox row). */
.row-status.error { color: var(--wind-red); }

/* Mobile: stack the row as a card. */
@media (max-width: 767px) {
  .inbox-row {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "date    amount"
      "account amount"
      "desc    desc"
      "select  select"
      "status  status";
    row-gap: 4px;
  }
  .inbox-row__date    { grid-area: date; }
  .inbox-row__account { grid-area: account; }
  .inbox-row__amt     { grid-area: amount; }
  .inbox-row__desc    { grid-area: desc; }
  .inbox-row .row-classify { grid-area: select; }
  .inbox-row .row-status   { grid-area: status; }
}

/* ===========================================================================
   Phase 20 — kids' daily current events.
   Built on existing primitives: the card shape mirrors .me-section, colours are
   tokens only (so dark mode comes free via the :root overrides above), and tap
   targets meet the 44px floor used elsewhere.
   =========================================================================== */

.news-container { padding-bottom: 5rem; }

.news-intro {
  /* --wind-stone is 3.97:1 on white — BELOW AA for body text. --wind-earth is
     7.55:1 light and stays legible dark. Measured, not eyeballed. */
  color: var(--wind-earth);
  margin: 0 0 1rem 0;
}

.news-card {
  background: var(--wind-white);
  border: 1px solid var(--surface-border);
  border-radius: 14px;
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
}

.news-card--mine { border-color: var(--wind-amber); border-width: 2px; }
.news-card--taken { opacity: 0.72; }

.news-card__headline {
  font-size: 1.25rem;
  margin: 0 0 0.5rem 0;
  color: var(--wind-deep);
}

.news-card__summary { margin: 0 0 0.75rem 0; line-height: 1.55; }

.news-card__why {
  margin: 0 0 1rem 0;
  padding-left: 0.85rem;
  border-left: 3px solid var(--wind-sand);
  color: var(--wind-earth);   /* AA: --wind-stone is 3.97:1 on white */
}

/* Provenance for a card whose every word is model-written (#251 S4). Same token
   as __why for the same reason: --wind-stone measures 3.97:1 on white, below AA,
   and this is small text. */
.news-card__source {
  margin: -0.35rem 0 0.75rem 0;
  font-size: 0.85rem;
  color: var(--wind-earth);
}

/* The glossary is the younger child's route to presenting unaided, so it is a
   real control rather than fine print. */
.news-glossary { margin: 0 0 1rem 0; }

.news-glossary__toggle {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  font-weight: 600;
  color: var(--wind-deep);
}

.news-glossary__list { margin: 0.5rem 0 0 0; }
.news-glossary__term { font-weight: 600; margin-top: 0.5rem; }
.news-glossary__def { margin: 0 0 0 1rem; color: var(--wind-earth); }  /* AA */

.news-questions { margin: 0 0 1rem 0; }
.news-questions__heading,
.news-links__heading {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--wind-stone);
  margin: 0 0 0.4rem 0;
}
.news-questions__list { margin: 0; padding-left: 1.25rem; }
.news-questions__list li { margin-bottom: 0.35rem; line-height: 1.5; }

/* Links were running together as one unbroken string before this existed —
   they are separate destinations and must read as such. */
.news-links { margin: 0 0 1rem 0; }

.news-links__link {
  display: block;
  min-height: 44px;
  display: flex;
  align-items: center;
  padding: 0.35rem 0;
  color: var(--wind-deep);
  text-decoration: underline;
}

.news-links__link + .news-links__link {
  border-top: 1px solid var(--surface-border);
}

.news-card__actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}

.news-card__actions .btn { min-height: 44px; }

.news-empty {
  color: var(--wind-stone);
  padding: 1.5rem 0;
  text-align: center;
}

/* Adult review */
.news-health { margin: 0 0 0.75rem 0; }
.news-health--alarm { color: var(--status-error-text); font-weight: 600; }
.news-health__hint { color: var(--wind-stone); margin: 0 0 0.75rem 0; }

.news-report {
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  padding: 0.75rem 1rem;
  margin-bottom: 0.75rem;
}
.news-report__meta { color: var(--wind-stone); font-size: 0.85rem; margin: 0 0 0.35rem 0; }
.news-report__note { margin: 0 0 0.5rem 0; }

.news-today__list { margin: 0; padding-left: 1.25rem; }

.news-hosts__hint { color: var(--wind-stone); margin: 0 0 0.75rem 0; }
.news-hosts__list { list-style: none; margin: 0 0 1rem 0; padding: 0; }
.news-hosts__list li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  border-bottom: 1px solid var(--surface-border);
}
.news-hosts__host { font-family: ui-monospace, monospace; }
.news-hosts__notes { color: var(--wind-stone); flex: 1; }

.news-hosts__form { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.news-hosts__input {
  flex: 1;
  min-width: 12rem;
  min-height: 44px;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  background: var(--wind-white);
  color: inherit;
}

@media (prefers-reduced-motion: reduce) {
  .news-card { transition: none; }
}

/* Phase 20 — loading clock.
   Publishing normally takes milliseconds, so this usually flashes past; it
   exists so the slow paths do not render as the empty state, which reads as
   broken rather than working. */
.news-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 3rem 0;
}

.news-loading__clock {
  position: relative;
  width: 48px;
  height: 48px;
  border: 3px solid var(--wind-sand);
  border-radius: 50%;
}

.news-loading__hand {
  position: absolute;
  left: 50%;
  bottom: 50%;
  background: var(--wind-amber);
  transform-origin: 50% 100%;
  border-radius: 2px;
}

.news-loading__hand--hour {
  width: 3px;
  height: 12px;
  margin-left: -1.5px;
  animation: news-clock-spin 3s linear infinite;
}

.news-loading__hand--minute {
  width: 2px;
  height: 17px;
  margin-left: -1px;
  animation: news-clock-spin 0.75s linear infinite;
}

@keyframes news-clock-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.news-loading__label {
  color: var(--wind-stone);
  margin: 0;
}

/* The hands convey nothing the label does not, so stop them outright rather
   than slowing them down. */
@media (prefers-reduced-motion: reduce) {
  .news-loading__hand--hour,
  .news-loading__hand--minute { animation: none; }
}

/* Phase 20 — keyboard focus ring for the glossary toggle.
   The pick/report buttons are .btn-primary/.btn-secondary and inherit the shared
   :focus-visible rule above. The glossary toggle is a bare <summary>, so it
   matched NOTHING and had no visible focus indicator — the same gap #166 fixed
   for .debt-tab/.segment-tab, which are likewise not .btn-*. Matches that
   rule's treatment. It matters here because the glossary is the younger child's
   only route to presenting unaided. */
.news-glossary__toggle:focus-visible {
  outline: 2px solid var(--wind-amber);
  outline-offset: 2px;
  border-radius: 4px;
}

/* #170 — notification-policy threshold: keep the "$" on the same line as its input.
   The admin-table input rule stretches inputs to the cell, which pushed the prefix onto
   its own line. Flex keeps them together without overriding the shared input styling. */
.np-money { display: flex; align-items: center; gap: 4px; }
.np-money input { width: 7em; }

/* #197 — 44px touch targets on the three controls in every policy row. The standard #167 met on
   photo-hearts and #214/#19 met on their `.link-btn`s, missed by the same deploy that shipped this
   editor (measured live, dark theme: select 181×19, input 93×20, Save 48×24).
   ⚠ The #167 centred-pseudo-element trick does NOT apply to `select`/`input` — form controls render
   no `::before`/`::after` — so those two are genuinely resized, taking the same look
   `form#magic-form select` already has on this page. Save is a `.link-btn` and takes the
   `.adopt-observed` shape: hit area grown, visual unchanged. */
.np-style,
.np-money input {
  min-height: 44px;
  box-sizing: border-box;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid var(--wind-dune);
  background: var(--wind-white);
  color: var(--wind-deep);
  font-size: 0.9rem;
}
.np-save {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* ⚠ WebKit ignores `min-height` on a native menulist `<select>` — measured on the webkit project:
   22px with the rule above, 44px on chromium; `-webkit-appearance: menulist-button` did not help
   either (still 22). The only way to size a select on the family's own devices is
   `appearance: none`, which also removes the native arrow — so the arrow is drawn on a wrapper's
   `::after`, in a TOKEN colour, rather than as a data-URI image with a hex baked in. Same wrapper
   shape as `.np-money` beside it. `pointer-events: none` so the tap lands on the select. */
.np-select { position: relative; display: inline-block; }
.np-select .np-style {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 32px;
}
.np-select::after {
  content: '';
  position: absolute;
  right: 13px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 2px solid var(--wind-stone);
  border-bottom: 2px solid var(--wind-stone);
  transform: rotate(45deg);
  pointer-events: none;
}
