:root {
  --bg: #fdf6f0;
  --card: #fffdfb;
  --border: #f0dfd3;
  --text: #3f3236;
  /* Was #87766f, which lands between 3.7:1 and 4.4:1 depending on how
     tinted the card under it is - under AA either way, and this is the
     token behind most small print in the app. */
  --text-muted: #6d605a;
  --text-secondary: #6b5a5e;
  --teal: #2dd4bf;
  --green: #84cc16;
  --lime: #84cc16;
  --lime-light: #ecfccb;
  --amber: #f59e0b;
  --amber-light: #fbbf24;
  --purple: #e8a0bf;
  --coral: #f4978e;
  --red: #ef4444;

  /* Text-safe companions to the accents above. The accents are tuned to
     be used as fills - as small print on a light card they run between
     1.8:1 and 2.4:1, which is unreadable rather than merely soft. These
     hold the same hue at a luminance that clears AA, so anything setting
     an accent as a text colour uses these and anything filling a shape
     keeps the original. Darkening the accents themselves would have
     changed every pill, gauge and tile that fills with them. */
  --teal-ink: #176e63;
  --lime-ink: #466c0c;
  --coral-ink: #895550;
  --red-ink: #b33333;
  /* The pink above is a fill. As the small day/time label it sat at
     1.9:1 on white and 1.88:1 on Soft Glow's own warm ground, which is
     not "soft" so much as unreadable. Same mauve hue, deep enough to
     clear AA on every light surface the app uses. */
  --purple-ink: #96526f;

  /* Display type and day/time labels were being set literally at ~30 call
     sites across the view stylesheets, so a theme could restyle its cards
     and still be overruled by a hardcoded font and a fixed colour. These
     are the single hook each theme overrides: the font the big headings
     use, the colour those headings take, and the colour of the small
     day/time labels that head schedule rows and meal cards. Defaults
     reproduce the previous look exactly, so a theme that sets nothing is
     unchanged. */
  /* Was "Luckiest Guy", a heavy comic display face with `cursive` as its
     generic fallback - and it was only ever the *default*: Marker,
     Glacier, Mosaic and the cleanup theme all override this hook with
     something readable. So the one setup that got the least legible
     heading font was the plain one, which is backwards for a product
     sold on being readable when reading is hard work.
     Quicksand is the app's own DEFAULT_DASHBOARD_FONT, so the default
     setup now agrees with itself. Every theme still overrides it, so no
     theme changes appearance. */
  --hub-display-font: "Quicksand", ui-rounded, system-ui, sans-serif;
  --hub-display-ink: var(--text);
  --hub-daylabel-ink: var(--purple-ink);

  /* Softer, more saturated companions to the palette above - used for the
     Dashboard hero/focus tiles and any future big-illustrated-card work
     (see visual-refresh direction). Kept as their own tokens rather than
     reusing --purple/--lime/--amber directly since those already have an
     established job (task priority pills, gauge states) at a punchier
     saturation than a full-tile background wants. */
  --sage: #8fbe87;
  --sage-soft: #eaf5e7;
  --lavender: #a798d9;
  --lavender-soft: #eeeafd;
  --gold: #d9a84f;
  --gold-soft: #fff4d9;
  --coral-soft: #ffe9e4;
  --teal-soft: #e3f6f5;
}

* {
  box-sizing: border-box;
}

/* Household text size (migrations/0055, applied by branding.js).
   Scaling the root size means every rem-based size in the app moves
   together and nothing has to be re-specified. Sizes written in px do not
   follow, which is the honest limit of this approach - most of the app's
   type is in rem, so the great majority does. Defaults to 1, so a
   household that never touches it sees no change at all. */
html {
  font-size: calc(100% * var(--text-scale, 1));
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

body {
  background: linear-gradient(160deg, #fdf1e8 0%, #fbeaf0 45%, #eef7f0 100%);
  background-attachment: fixed;
}

body {
  padding-bottom: 84px;
  min-height: 100vh;
}

#app {
  max-width: 560px;
  margin: 0 auto;
  padding: 20px 16px 8px;
}

h1,
h2,
h3 {
  margin: 0 0 4px;
}

h1 {
  font-size: 1.4rem;
}

h2 {
  font-size: 1.05rem;
  color: var(--text-secondary);
  font-weight: 600;
  letter-spacing: 0.01em;
}

p {
  color: var(--text-secondary);
  margin: 0;
}

.settings-gear-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: none;
  border-radius: 50%;
  background: var(--card);
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: 0 1px 6px rgba(220, 170, 160, 0.18);
  transition: color 0.15s ease, transform 0.1s ease;
}

/* Persistent top bar (All apps + Settings gear) - lives at the top of
   #app, a sibling of every .view section rather than inside any one of
   them, so it's visible no matter which tab is active. Mirrors Budget's
   own .budget-topbar/.budget-plain-link for the same reason: someone
   switching between the two apps shouldn't have to relearn where "All
   apps" and Settings live. */
.hub-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.hub-plain-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px 8px 4px;
  border-radius: 10px;
  color: var(--text-muted);
  font-size: 0.88rem;
  font-weight: 650;
  text-decoration: none;
}

.hub-plain-link:hover {
  background: var(--card);
  color: var(--text);
}

/* ============================================================
   Dashboard hero + focus grid (visual refresh round 1) - replaces the
   old plain centered .header. One big illustrated card up top instead
   of a stack of same-weight cards, plus four tappable "at a glance"
   tiles surfacing one real number/status each from tabs the household
   would otherwise have to go open individually (Tasks, Chores &
   Schedule, Meals, capacity) - populated from data the app already
   loads on init(), not a new fetch. See app.js's renderFocus* functions,
   called from the same load functions that already feed the fuller
   views those tiles link to, so the tiles never drift from what's on
   the actual tab.
   ============================================================ */
.dashboard-hero {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 26px;
  padding: 20px 20px 22px;
  min-height: 180px;
  background: linear-gradient(135deg, #fff2ea, #f8edf7 55%, #edf8ef);
  box-shadow: 0 12px 34px rgba(104, 72, 70, 0.09);
  margin-bottom: 14px;
}

.dashboard-hero-copy {
  position: relative;
  z-index: 2;
  width: 66%;
}

.dashboard-hero-eyebrow {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 800;
  color: var(--coral-ink);
  margin-bottom: 4px;
}

.dashboard-hero .brand-title {
  text-align: left;
  font-size: 2.1rem;
  line-height: 1;
  margin: 0 0 8px;
}

.dashboard-hero-sub {
  color: var(--text-muted);
  font-size: 0.86rem;
  line-height: 1.4;
  margin: 0;
}

.dashboard-hero-art {
  position: absolute;
  right: -12px;
  bottom: -20px;
  width: 44%;
  max-width: 220px;
  filter: drop-shadow(0 9px 10px rgba(84, 59, 58, 0.1));
}

.focus-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 11px;
  margin-bottom: 14px;
}

.focus-tile {
  border: 0;
  border-radius: 22px;
  padding: 15px;
  text-align: left;
  min-height: 118px;
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  box-shadow: 0 5px 18px rgba(88, 65, 63, 0.06);
  display: flex;
  flex-direction: column;
  transition: transform 0.12s ease;
}

.focus-tile:active {
  transform: scale(0.97);
}

.focus-tile-icon {
  width: 34px;
  height: 34px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, 0.72);
  font-size: 1rem;
  font-weight: 800;
  margin-bottom: 8px;
}

.focus-tile-number {
  font-family: var(--hub-display-font, "Quicksand", system-ui, sans-serif);
  font-weight: 400;
  font-size: 1.9rem;
  line-height: 0.9;
  margin-bottom: 2px;
}

.focus-tile-label {
  font-size: 0.92rem;
  font-weight: 700;
}

.focus-tile-sub {
  font-size: .75rem;
  line-height: 1.3;
  color: var(--text-muted);
  margin-top: 3px;
}

.focus-tile-tonight { background: var(--coral-soft); }
.focus-tile-family { background: var(--lavender-soft); }
.focus-tile-meals { background: var(--sage-soft); }
.focus-tile-capacity { background: var(--teal-soft); }

.focus-tile-progress {
  height: 8px;
  background: rgba(255, 255, 255, 0.72);
  border-radius: 99px;
  overflow: hidden;
  margin-top: 10px;
}

.focus-tile-progress span {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--teal);
  transition: width 0.3s ease;
}

@media (max-width: 380px) {
  .dashboard-hero-copy { width: 70%; }
  .dashboard-hero-art { width: 38%; right: -16px; }
  .focus-tile { min-height: 108px; padding: 12px; }
  .focus-tile-number { font-size: 1.6rem; }
}

.settings-gear-btn:hover {
  color: var(--text);
}

.settings-gear-btn:active {
  transform: scale(0.94);
}

/* Settings panel - a dropdown from the header gear icon holding
   everything that personalizes the household (branding, family
   roster, capacity hours, tablet display, notifications) rather
   than leaving those forms permanently stacked on the Dashboard. */
.settings-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(63, 50, 54, 0.35);
  z-index: 40;
}

.settings-panel {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 41;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--bg);
  border-radius: 0 0 22px 22px;
  box-shadow: 0 12px 32px rgba(63, 50, 54, 0.22);
  padding: 16px 16px 24px;
  max-width: 560px;
  margin: 0 auto;
  animation: settings-panel-drop 0.18s ease;
}

@keyframes settings-panel-drop {
  from {
    transform: translateY(-12px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.settings-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.settings-panel-header .section-title {
  margin-bottom: 0;
}

.settings-close-btn {
  background: none;
  border: none;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px 8px;
}

.settings-close-btn:hover {
  color: var(--text);
}

.settings-panel-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin-top: 4px;
  padding-top: 4px;
}

.header .tagline {
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* --brand-font / --brand-color are set at runtime by branding.js from
   the household's own chosen dashboard name/font/color (editable at
   setup and from the Hub's Settings card) - the literal values here are
   just the fallback before that script runs. */
.brand-title {
  font-family: var(--brand-font, "Quicksand", sans-serif);
  font-size: 2.5rem;
  font-weight: 700;
  text-align: center;
  color: var(--brand-color-ink, var(--brand-color, var(--coral-ink)));
  margin: 0 0 2px;
  line-height: 1.15;
}

/* Daily scripture banner - persists above #app on every tab (outside the
   tab-switched <section class="view"> elements, see index.html). Quiet and
   small on purpose: a calm daily touch, not a loud attention-grabber. */
#scripture-banner {
  position: sticky;
  top: 0;
  z-index: 20;
  text-align: center;
  padding: 8px 16px;
  background: linear-gradient(120deg, #fff3e0, #fdeaf1);
  border-bottom: 1px solid var(--border);
  font-size: 0.82rem;
  line-height: 1.35;
}

#scripture-text {
  color: var(--text-secondary);
  font-style: italic;
}

#scripture-ref {
  font-family: var(--hub-display-font, "Quicksand", system-ui, sans-serif);
  color: var(--coral-ink);
  margin-left: 7px;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
}

/* Fun display font for prominent, short titles only - page/section
   headings, not dense body or list text (readability at a glance matters
   more than delight there). */
.view-header h1 {
  font-family: var(--hub-display-font, "Quicksand", system-ui, sans-serif);
  font-weight: 400;
  letter-spacing: 0.01em;
  font-size: 1.7rem;
}

.brand-verse {
  font-family: var(--brand-font, "Quicksand", sans-serif);
  text-align: center;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1.4;
  margin: 0 0 10px;
}

.logout-link {
  display: block;
  margin: 8px auto 0;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.75rem;
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
}

/* Per-view themed headers */
.view-header {
  display: flex;
  align-items: center;
  gap: 12px;
  border-radius: 20px;
  padding: 16px 18px;
}

.view-header h1 {
  margin: 0;
}

.view-header-icon {
  display: flex;
  flex-shrink: 0;
}

#view-tasks .view-header {
  background: linear-gradient(120deg, #ffe8df, #fff9f4);
}
#view-tasks .view-header-icon {
  color: var(--coral-ink);
}

#view-kids .view-header {
  background: linear-gradient(120deg, #ffe1ec, #fff6d8 55%, #e6f7ee);
}
#view-kids .view-header-icon {
  color: #ec4899;
}

#view-routines .view-header {
  background: linear-gradient(120deg, #e3e8fb, #f4e9fb);
}
#view-routines .view-header-icon {
  color: #818cf8;
}

#view-meals .view-header {
  background: linear-gradient(120deg, #ffe9df, #eaf7ee);
}
#view-meals .view-header-icon {
  color: var(--lime-ink);
}

#view-budget .view-header {
  background: linear-gradient(120deg, #e6f7ee, #eaf3fb);
}
#view-budget .view-header-icon {
  color: #0d9488;
}

#view-purge .view-header {
  background: linear-gradient(120deg, #fff2d9, #ffe8df);
}
#view-purge .view-header-icon {
  color: var(--amber);
}

/* Geometry comes from tokens.css so the Hub, the Budget and the Display
   are held to one scale - the literals here had drifted apart from the
   Budget's and the Display's own. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--card-radius);
  padding: var(--card-padding);
  margin-bottom: var(--space-4);
  box-shadow: var(--card-shadow);
}

.section-title {
  margin-bottom: 12px;
}

/* Capacity gauges */
.gauge-row {
  display: flex;
  gap: 12px;
}

.gauge {
  flex: 1;
  padding: 14px;
}

.gauge-label {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
}

.gauge-track {
  height: 10px;
  overflow: hidden;
  margin-bottom: 8px;
}

.gauge-fill {
  height: 100%;
  transition: width 0.4s ease;
}

.gauge-value {
  font-size: 0.9rem;
  font-weight: 600;
}

.gauge-sub {
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* Per-account dashboard widgets (see public/dashboard-widgets.js) */
.dashboard-widgets-card {
  padding-bottom: 8px;
}

.dashboard-widgets-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}

.dashboard-widgets-head .section-title {
  margin-bottom: 0;
}

#dashboard-widgets-empty {
  margin-bottom: 8px;
}

.dashboard-widgets-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.dashboard-widget {
  display: block;
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  font-family: inherit;
  cursor: pointer;
}

.dashboard-widget-label {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}

.dashboard-widget-body {
  font-size: 0.95rem;
  font-weight: 600;
}

.dashboard-widget-sub {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-top: 2px;
  font-weight: 400;
}

.dashboard-widget .gauge-row {
  margin-top: 6px;
}

.dashboard-widgets-picker {
  margin-top: 12px;
  padding-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The unconditional display:flex above otherwise beats the [hidden]
   attribute the manage button toggles (same specificity, but this rule
   is more specific via the attribute selector) - without this, clicking
   "Done" sets .hidden = true but the panel never actually disappears. */
.dashboard-widgets-picker[hidden] {
  display: none;
}

.dashboard-widget-option {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}

.dashboard-widget-option input {
  margin-top: 3px;
}

.dashboard-widget-option-label {
  font-weight: 600;
  font-size: 0.92rem;
}

.dashboard-widget-option-desc {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Savings Goals (Budget tab) - progress bar reuses .gauge-track/.gauge-fill
   above; this is just the surrounding card spacing + the pacing guide line. */
.savings-goal-progress {
  padding: 0 14px 4px;
}

.savings-goal-guide {
  padding: 0 14px 12px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.savings-goal-guide.success {
  color: var(--lime-ink);
  font-weight: 600;
}

.savings-contribution-history {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Brain dump */
textarea.dump-input {
  width: 100%;
  min-height: 90px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  padding: 12px;
  font-size: 1rem;
  font-family: inherit;
  resize: vertical;
}

textarea.dump-input:focus {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

.btn {
  border: none;
  border-radius: 12px;
  padding: 12px 18px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 0.1s ease;
}

.btn:active {
  transform: scale(0.97);
}

.btn-primary {
  background: var(--teal);
  color: #04201c;
}

.btn-primary:disabled {
  opacity: 0.5;
  cursor: wait;
}

.btn-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}

.status-msg {
  font-size: 0.85rem;
  margin-top: 8px;
  min-height: 1.1em;
}

.status-msg.error {
  color: var(--red-ink);
}

.status-msg.success {
  color: var(--lime-ink);
}

/* Task list */
.task-group {
  margin-bottom: 20px;
}

.task-group-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.task-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  margin-bottom: 8px;
}

.task-check {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid var(--text-muted);
  background: transparent;
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
}

.task-check.done {
  background: var(--green);
  border-color: var(--lime-ink);
}

.task-check.done::after {
  content: "";
  position: absolute;
  left: 7px;
  top: 3px;
  width: 6px;
  height: 11px;
  border: solid #04200f;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.task-body {
  flex: 1;
  min-width: 0;
}

.task-title {
  font-size: 0.95rem;
  word-break: break-word;
}

.task-title.done {
  text-decoration: line-through;
  color: var(--text-muted);
}

.task-meta {
  display: flex;
  gap: 8px;
  margin-top: 3px;
  font-size: .75rem;
  color: var(--text-muted);
  flex-wrap: wrap;
}

.pill {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
}

/* Elements JS tints by category/status. The tint arrives as custom
   properties rather than inline color literals so a theme can redefine
   it - an inline literal would beat every stylesheet rule, which is how
   category colors used to leak into Gallery and Notebook. */
.pill.is-tinted {
  color: var(--tint-ink, inherit);
  border-color: var(--tint-edge, var(--border));
}

.zone-status-btn.is-tinted {
  color: var(--tint-ink, inherit);
  border-color: var(--tint-edge, var(--border));
  background: var(--tint-wash, none);
}

.priority-tag {
  display: inline-flex;
  align-items: center;
  font-size: .75rem;
  color: var(--text-secondary);
}

.priority-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  margin-right: 4px;
}

.priority-high {
  background: var(--red);
}

.priority-medium {
  background: var(--amber-light);
}

.priority-low {
  background: var(--text-muted);
}

.empty-state {
  color: var(--text-muted);
  font-size: 0.85rem;
  padding: 12px 4px;
}

.today-tasks-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
}

.today-tasks-header .section-title {
  margin-bottom: 0;
}

.today-tasks-remaining {
  font-size: 0.78rem;
  font-weight: 600;
}

.today-tasks-remaining.ok {
  color: var(--lime-ink);
}

.today-tasks-remaining.warn {
  color: var(--amber);
}

.today-tasks-remaining.over {
  color: var(--red-ink);
}

.today-tasks-sub {
  font-size: .75rem;
  color: var(--text-muted);
  margin-bottom: 10px;
}

.today-tasks-overflow {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.tonight-task-item {
  margin-bottom: 8px;
}

.tonight-task-item .task-row {
  margin-bottom: 4px;
}

.tonight-task-more {
  display: block;
  margin: 0 0 8px 4px;
  padding: 0;
  border: none;
  background: none;
  font-size: .75rem;
  color: var(--text-muted);
  cursor: pointer;
  text-decoration: underline;
}

.tonight-task-actions {
  display: flex;
  gap: 6px;
  padding: 6px 10px 8px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin: -4px 0 8px;
}

.tonight-task-actions[hidden] {
  display: none;
}

/* A later layer forces this bar to display:flex !important with a more
   specific selector (body.product-polish-active #today-tasks-list
   .tonight-task-actions). Matching that selector's specificity and adding
   one more class is the only way [hidden] wins the cascade without
   touching that layer. */
#today-tasks-list .tonight-task-item .tonight-task-actions[hidden] {
  display: none !important;
}

.tonight-task-actions .swap-toggle-btn {
  margin-top: 0;
  padding: 4px 10px;
  font-size: .75rem;
}

.today-tasks-add-row {
  margin-top: 6px;
}

/* Kids */
#view-kids .kid-column {
  margin-bottom: 18px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 14px 16px;
  box-shadow: 0 4px 18px rgba(230, 170, 150, 0.12);
  border-left: 6px solid var(--coral);
}

#view-kids .kid-column:nth-of-type(4n+2) {
  border-left-color: var(--teal-ink);
}

#view-kids .kid-column:nth-of-type(4n+3) {
  border-left-color: var(--lime-ink);
}

#view-kids .kid-column:nth-of-type(4n+4) {
  border-left-color: #a78bfa;
}

/* .kid-column-accent overrides the nth-of-type fallback above with the
   member's own configured color once one is known (see
   applyMemberAccent() in app.js). */
#view-kids .kid-column.kid-column-accent {
  border-left-color: var(--accent, #a78bfa);
}

#view-kids .kid-column h2 {
  font-size: 1.25rem;
  color: var(--text);
}

.kid-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.streak-badge {
  display: inline-flex;
  align-items: center;
  font-size: 1.3rem;
  font-weight: 700;
  color: #92400e;
  background: linear-gradient(90deg, #fff4d6, #ffe8b8);
  border: none;
  border-radius: 999px;
  padding: 6px 16px;
}

.kid-empty-state {
  font-size: 1rem;
  font-weight: 600;
  color: var(--lime-ink);
}

.chore-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 12px 16px;
  margin-bottom: 8px;
}

#view-kids .task-check {
  width: 34px;
  height: 34px;
  border-width: 3px;
  border-color: var(--lime-ink);
}

#view-kids .chore-row .task-title {
  font-size: 1.05rem;
  font-weight: 600;
}

.chore-day {
  font-size: .75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  min-width: 34px;
}

.add-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.add-form label {
  font-size: 0.78rem;
  color: var(--text-secondary);
  margin-bottom: -4px;
}

.add-form input,
.add-form select {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 10px 12px;
  font-size: 0.9rem;
  font-family: inherit;
}

/* Routines */
.routine-row {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.routine-row:last-child {
  border-bottom: none;
}

.routine-day {
  min-width: 78px;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--hub-daylabel-ink, var(--purple));
  text-transform: capitalize;
}

.routine-title {
  font-size: 0.95rem;
}

.routine-time {
  font-size: 0.78rem;
  color: var(--text-muted);
}

/* Bottom nav */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  background: var(--card);
  border-top: 1px solid var(--border);
  padding: 8px 4px calc(8px + env(safe-area-inset-bottom));
  z-index: 10;
}

.tab-btn {
  flex: 1;
  background: none;
  border: none;
  color: var(--text-muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 6px 2px;
  font-size: .75rem;
  cursor: pointer;
  font-family: inherit;
}

.tab-btn .tab-icon {
  font-size: 1.25rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The icons were single unicode glyphs, which each platform draws from a
   different font at a different weight and optical size - so the same tab
   bar looked like three products across a phone, a laptop and the wall
   display. All three apps now draw the same line icons from the same set
   (see ICON in display.js), and this is what sizes them wherever that set
   is used. */
.tab-btn .tab-icon svg {
  width: 22px;
  height: 22px;
  display: block;
}

.tab-btn.active {
  color: var(--teal-ink);
}

.view {
  display: none;
}

.view.active {
  display: block;
}

.install-hint {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-align: center;
  margin: 4px 0 14px;
}

/* Meal rotation */
.meal-card {
  margin-bottom: 8px;
  overflow: hidden;
}

.meal-card-head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  cursor: pointer;
  width: 100%;
  text-align: left;
  font-family: inherit;
  color: inherit;
}

.meal-day {
  min-width: 40px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--hub-daylabel-ink, var(--purple));
  text-transform: uppercase;
}

.meal-title-block {
  flex: 1;
  min-width: 0;
}

.meal-title {
  font-size: 0.95rem;
}

.meal-tag {
  font-size: .75rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.meal-chevron {
  color: var(--text-muted);
  transition: transform 0.15s ease;
  flex-shrink: 0;
}

.meal-card.expanded .meal-chevron {
  transform: rotate(90deg);
  color: var(--teal-ink);
}

.meal-detail {
  display: none;
  padding: 0 14px 14px;
  border-top: 1px solid var(--border);
}

.meal-card.expanded .meal-detail {
  display: block;
  padding-top: 12px;
}

.meal-detail-row {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.meal-detail-label {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-bottom: 2px;
}

/* This Week - day/slot layout */
.day-plan-card {
  margin-bottom: 18px;
}

.day-plan-title {
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--hub-daylabel-ink, var(--coral-ink));
  margin-bottom: 8px;
}

/* Day header doubles as the collapse toggle for that day's meals (see
   .section-collapse-* below) - flex row so the chevron sits at the end
   of the same line as the day name. */
.day-plan-title.section-collapse-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
}

/* ============================================================
   Generic section collapse/expand (Dashboard's Calendar + Tonight's
   window cards, Tasks tab's Completed section, and each day in the
   Meals tab's This Week view) - one shared pattern: a chevron button
   inside an otherwise-static header toggles a `.section-collapse-body`
   sibling between shown and hidden. `.section-open` is applied to the
   shared ancestor (the `.card` or `.day-plan-card` that both the header
   and body live inside) rather than to the head/body individually, so
   one class toggle drives both the chevron rotation and the body's
   visibility via descendant selectors below. It's present by default in
   markup for sections that start open, or added by JS on first
   expand for sections that start collapsed - either way there's a
   single source of truth per section.
   ============================================================ */
.section-collapse-head {
  flex-wrap: nowrap;
}

.section-collapse-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex-shrink: 0;
  border-radius: 8px;
}
.section-collapse-btn:hover {
  background: var(--bg-hover, rgba(0, 0, 0, 0.04));
}

.section-chevron {
  display: inline-flex;
  transition: transform 0.15s ease;
  transform: rotate(0deg);
}
.section-open .section-chevron {
  transform: rotate(90deg);
  color: var(--teal-ink);
}

.section-collapse-body {
  display: none;
}
.section-open .section-collapse-body {
  display: block;
}

.slot-empty-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  padding: 10px 14px;
  margin-bottom: 8px;
}

.slot-empty-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
}

.slot-empty-row .swap-picker {
  flex-basis: 100%;
  margin-top: 0;
}

/* Grocery list */
.grocery-group {
  margin-bottom: 16px;
}

.grocery-group-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.grocery-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin-bottom: 6px;
}

.grocery-item-name {
  flex: 1;
}

.grocery-item-amount {
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* Meals sub-tabs */
.subtabbar {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
  overflow-x: auto;
}

.subtab-btn {
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}

.subtab-btn.active {
  background: var(--teal);
  color: #04201c;
  border-color: var(--teal-ink);
  font-weight: 600;
}

.subview {
  display: none;
}

.subview.active {
  display: block;
}

/* Eaters / modifications / ingredients detail */
.eater-badges {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.eater-badge {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 0.78rem;
  font-weight: 600;
}

.mod-row {
  font-size: 0.85rem;
  color: var(--text-secondary);
  margin-bottom: 4px;
}

/* Per-person color: driven by each element's own --accent custom
   property (set inline from the family member's configured color - see
   applyMemberAccent() in app.js), rather than a fixed rule per real
   name, so any number of configured members share these same rules. */
.mod-person,
.agenda-owner-badge {
  font-weight: 700;
  color: var(--accent, var(--purple-ink));
}

.eater-badge {
  color: var(--accent, var(--text-secondary));
  border-color: color-mix(in srgb, var(--accent, var(--border)) 55%, transparent);
}

/* Schedule tab */
.schedule-preview-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.schedule-preview-day {
  flex-shrink: 0;
  min-width: 84px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px 8px;
  text-align: center;
}

.schedule-preview-day.is-today {
  border-color: var(--purple);
}

.schedule-preview-weekday {
  font-size: .75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.schedule-preview-daynum {
  font-weight: 700;
  font-size: 1.05rem;
  margin: 2px 0 6px;
}

.schedule-preview-counts {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: .75rem;
  color: var(--text-secondary);
}

.schedule-repeat-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 2px 0;
}

.schedule-repeat-controls {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 4px;
}

.schedule-repeat-controls select,
.schedule-repeat-controls input[type="date"] {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 8px 10px;
  font-size: 0.85rem;
  font-family: inherit;
}

.schedule-recurrence-sub {
  font-size: .75rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.meal-title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.chore-badge {
  display: inline-block;
  background: var(--amber-light);
  color: #5c3a00;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.schedule-range-fields {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.schedule-range-fields input[type="date"] {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 8px 10px;
  font-size: 0.85rem;
  font-family: inherit;
}

.agenda-owner-badge {
  font-weight: 600;
  margin-left: 6px;
}

.agenda-owner-badge:first-child {
  margin-left: 0;
}

.ingredient-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.85rem;
  color: var(--text-secondary);
  padding: 5px 0;
  border-bottom: 1px solid var(--border);
}

.ingredient-row:last-child {
  border-bottom: none;
}

.ingredient-amount {
  color: var(--text-muted);
  font-size: 0.78rem;
  flex-shrink: 0;
  text-align: right;
}

/* Swap picker */
.swap-toggle-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--teal-ink);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  margin-top: 4px;
}

#dump-mic.active {
  background: var(--teal);
  color: #fff;
  border-color: var(--teal-ink);
}

.swap-picker {
  margin-top: 10px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
  display: none;
}

.swap-picker.open {
  display: block;
}

.swap-option {
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.85rem;
}

.swap-option:hover,
.swap-option:active {
  background: var(--bg);
}

/* Category / owner filters */
.filter-bar {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.filter-chip {
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text-secondary);
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.filter-chip.active {
  background: var(--purple);
  color: #3a1626;
  border-color: var(--purple);
  font-weight: 600;
}

.filter-row {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.filter-select {
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 10px;
  font-size: 0.85rem;
  font-family: inherit;
  flex: 1;
  min-width: 120px;
}

.owner-checkboxes {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.owner-check-label {
  display: flex;
  align-items: center;
  gap: 5px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 10px;
  font-size: 0.8rem;
  cursor: pointer;
}

.owner-check-label input {
  margin: 0;
}

.owner-check-label.checked {
  border-color: var(--teal-ink);
  color: var(--teal-ink);
}

.chip-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 8px 5px 12px;
  font-size: 0.8rem;
}

.chip-remove {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 2px 4px;
}

.chore-row-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.chore-delete-btn {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  padding: 2px 6px;
}

.chore-delete-btn:hover {
  color: var(--pink, #ec4899);
}

/* Task cards (edit-in-place) */
.task-row {
  cursor: pointer;
}

.task-edit-panel {
  display: none;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.task-row.expanded .task-edit-panel {
  display: block;
}

.task-edit-panel .add-form {
  margin-top: 0;
}

.task-edit-panel select,
.task-edit-panel input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 8px 10px;
  font-size: 0.85rem;
  font-family: inherit;
}

.completed-row {
  opacity: 0.7;
}

.completed-date {
  font-size: .75rem;
  color: var(--text-muted);
}

/* Calendar */
.cal-nav {
  display: flex;
  align-items: center;
  gap: 10px;
}

.cal-nav-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 999px;
  width: 26px;
  height: 26px;
  font-size: 1rem;
  cursor: pointer;
  line-height: 1;
}

/* Brain dump card header + its tap-to-reveal "what can I upload" note */
.capture-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.capture-card-head .section-title {
  margin-bottom: 0;
}

.capture-help-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 999px;
  width: 24px;
  height: 24px;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  line-height: 1;
  flex-shrink: 0;
}

#capture-help-text {
  margin-top: -4px;
}

.cal-month-label {
  font-size: 0.85rem;
  color: var(--text-secondary);
  min-width: 100px;
  text-align: center;
}

/* Grid view must be able to actually hide the List view's full-month
   #cal-list-view (see initCalendarViewToggle() in app.js). A dynamically-
   loaded visual-refresh layer (product-polish-refresh.css) forces that
   element visible with `body.product-polish-active #cal-list-view {
   display: grid !important; }` - specificity (1 id, 1 class, 1 element).
   The id selector below ties that rule's id count, and the repeated class
   isn't a typo - it's the standard trick for then beating its class count
   (1 id, 2 classes, 0 elements beats 1 id, 1 class, 1 element - the id
   column is compared first and ties, then the class column decides), so
   this wins regardless of which stylesheet loads last. */
#cal-list-view.cal-list-view-hidden.cal-list-view-hidden {
  display: none !important;
}

/* Keep the Brain dump card's "Sort it out / Speak / Add photos or files"
   row on one line even on the narrowest phones, instead of the third
   button wrapping onto its own line below the other two (which read as
   dead space beneath it). The generic phone-layout rule in
   responsive-refresh.css (`body.hub-responsive[data-hub-effective-layout=
   "phone"] .btn-row { flex-wrap: wrap !important; }`) has specificity
   (0 ids, 2 classes/attrs, 1 element); the extra class below pushes this
   past it (0 ids, 3 classes/attrs, 1 element) so it wins regardless of
   stylesheet load order. The padding/font-size trims are just enough to
   fit all three controls in the freed-up width at common phone sizes. */
body.hub-responsive[data-hub-effective-layout="phone"] .btn-row.capture-actions-row {
  flex-wrap: nowrap !important;
}
.capture-actions-row .btn-primary {
  padding: 10px 14px !important;
}
.capture-actions-row .swap-toggle-btn {
  padding: 5px 8px !important;
  font-size: 0.74rem !important;
}

/* Merging Quick capture into Brain dump (see index.html) stacked two
   .status-msg elements (#dump-status for the brain-dump flow,
   #capture-status for the photo/file flow) plus #capture-thumbnails
   under the action row. Each reserves space (.status-msg's min-height,
   everyone's margin-top) even with nothing in it - harmless when it was
   the only one in its own card, but three stacked empty reservations in
   one card read as a large dead gap below the buttons. Collapse each
   back to zero while genuinely empty; the moment JS puts real content
   in one (an error, a thumbnail), :empty stops matching and its normal
   spacing returns immediately. */
#dump-status:empty,
#capture-status:empty {
  min-height: 0;
  margin-top: 0;
}
#capture-thumbnails:empty {
  margin-top: 0;
}

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-top: 12px;
}

.cal-dow {
  font-size: .75rem;
  text-align: center;
  color: var(--text-muted);
  text-transform: uppercase;
  padding-bottom: 4px;
}

.cal-day {
  aspect-ratio: 1;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  cursor: pointer;
  position: relative;
  color: var(--text-secondary);
}

.cal-day.empty {
  border: none;
  background: none;
  cursor: default;
}

.cal-day.today {
  border-color: var(--teal-ink);
  color: var(--teal-ink);
  font-weight: 700;
}

.cal-day.selected {
  background: var(--purple);
  color: #3a1626;
  font-weight: 700;
}

.cal-day-dots {
  display: flex;
  gap: 2px;
  margin-top: 2px;
}

.cal-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--teal);
}

.cal-dot.event {
  background: var(--purple);
}

.cal-agenda {
  margin-top: 14px;
}

.cal-event-source {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  flex-shrink: 0;
}

/* Synced iCloud events get a small pill + icon so they read as visually
   distinct from Hub-created ones at a glance, not just via text. */
.cal-event-source.is-synced {
  background: var(--lime-light);
  color: #3f6212;
  padding: 3px 8px;
  border-radius: 999px;
  font-weight: 700;
}

.cal-event-source-icon {
  display: inline-flex;
}

.cal-event-source-icon svg {
  display: block;
}

.cal-owner-filter-label {
  font-size: .75rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
}

#cal-owner-filter {
  margin-bottom: 12px;
}

/* Owner "assigned to" badges - a rounded pill with a tinted background
   rather than plain coloured text, so who's assigned reads clearly at a
   glance rather than blending into the row. */
.owner-pill {
  display: inline-block;
  font-weight: 700;
  font-size: .75rem;
  padding: 3px 9px;
  border-radius: 999px;
  margin-left: 4px;
  /* Driven by the element's own --accent custom property (set inline
     from the member's configured color - see applyMemberAccent() in
     app.js), so any number of configured members share this one rule. */
  background: color-mix(in srgb, var(--accent, var(--border)) 18%, white);
  border: 1px solid color-mix(in srgb, var(--accent, var(--border)) 55%, transparent);
  color: var(--accent, var(--text));
}

.owner-pill:first-child {
  margin-left: 0;
}

/* Family settings card */
.family-member-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 8px 10px;
  margin-bottom: 8px;
}

.family-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-weight: 700;
  color: white;
  background: var(--accent, var(--purple));
}

.family-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.family-member-name-input {
  flex: 1 1 120px;
  min-width: 90px;
}

.family-member-photo-input {
  max-width: 140px;
}

.cal-agenda-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  margin-bottom: 6px;
  font-size: 0.85rem;
}

.cal-agenda-owners {
  margin-left: auto;
  flex-shrink: 0;
}

/* Meal add/edit form */
.form-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 8px 10px;
  font-size: 0.85rem;
  font-family: inherit;
  width: 100%;
  box-sizing: border-box;
}

.ingredient-form-row {
  display: flex;
  gap: 6px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.ingredient-form-row .form-input {
  flex: 1;
  min-width: 90px;
}

/* Grocery cost estimate + budget nudge */
.grocery-cost-input {
  width: 66px;
  flex-shrink: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 4px 6px;
  font-size: 0.8rem;
  text-align: right;
}

.grocery-nudge {
  font-size: 0.85rem;
  font-weight: 600;
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 12px;
}

.grocery-nudge-ok {
  background: var(--lime-light);
  color: #365314;
}

.grocery-nudge-warn {
  background: #fef3c7;
  color: #92400e;
}

.grocery-nudge-over {
  background: #fee2e2;
  color: #991b1b;
}

/* Budget tab */
.budget-month-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 12px;
}

.budget-month-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.budget-month-label {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
}

.budget-amount-input {
  text-align: right;
}

.trend-row .grocery-item-name {
  flex: 0 0 44px;
}

.trend-status {
  font-size: .75rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  margin-left: auto;
}

.trend-status-surplus {
  background: var(--lime-light);
  color: #365314;
}

.trend-status-deficit {
  background: #fee2e2;
  color: #991b1b;
}

.trend-status-none {
  background: var(--surface-muted, #f1f1ef);
  color: var(--ink-muted, #6d605a);
}

/* Sleep / Shortcuts panel */
.shortcuts-panel {
  margin-top: 10px;
}

.shortcuts-key {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .75rem;
  word-break: break-all;
  text-align: left;
  max-width: 60%;
}

.shortcuts-instructions {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.shortcuts-instructions .mod-row {
  font-size: 0.8rem;
}

.notifications-note {
  font-size: 0.82rem;
  margin-bottom: 4px;
}

/* Purge & Organize */
.purge-config-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-width: 100px;
}

.purge-config-field label {
  font-size: 0.75rem;
  color: var(--text-muted);
}

.btn-block-dashed {
  display: block;
  width: 100%;
  background: var(--card);
  border: 1px dashed var(--border);
  border-radius: 12px;
  padding: 12px;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  margin-top: 6px;
}

.zone-status-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 10px;
  font-size: .75rem;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
}

.zone-row {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  margin-bottom: 8px;
  cursor: pointer;
}

.zone-row-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.zone-detail {
  display: none;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  cursor: default;
}

.zone-row.expanded .zone-detail {
  display: block;
}

.purge-box-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.purge-box-row .purge-config-field {
  min-width: 90px;
}

.purge-box-row input {
  max-width: 90px;
}

/* Budget planner */
.planner-scenario-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.planner-scenario-btn {
  flex: 1;
  min-width: 130px;
  background: var(--card);
  border: 2px solid var(--border);
  border-radius: 10px;
  padding: 10px;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
}

.planner-bar {
  margin-bottom: 12px;
}

.planner-bar-top {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  margin-bottom: 5px;
}

.planner-bar-amount {
  font-weight: 700;
}

.planner-savings-card {
  background: var(--lime-light);
  border-color: #bbf7a855;
}

.planner-bottomline-card {
  border: 2px solid var(--teal);
}

.planner-food-banner {
  border-radius: 10px;
  padding: 14px 16px;
  color: #fff;
}

.planner-food-banner-title {
  font-size: 1.05rem;
  font-weight: 700;
}

.planner-food-banner-sub {
  font-size: 0.85rem;
  opacity: 0.9;
  margin-top: 4px;
}

.planner-strike {
  color: var(--coral-ink);
  text-decoration: line-through;
  margin-right: 6px;
}

.planner-target {
  color: var(--lime-ink);
  font-weight: 700;
}

/* Celebratory task completion (adult tasks) - deliberately not subtle,
   the equivalent of the kids' flame streak but built from plain colored
   shapes rather than emoji, per the no-emoji rule elsewhere in the UI. */
.celebrate-toast {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translate(-50%, -14px);
  background: var(--green);
  color: #fff;
  font-weight: 700;
  padding: 10px 20px;
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.25s ease;
  z-index: 999;
}

.celebrate-toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

.celebrate-burst {
  position: fixed;
  top: 18px;
  left: 50%;
  width: 0;
  height: 0;
  z-index: 998;
  pointer-events: none;
}

.confetti-piece {
  position: absolute;
  top: 0;
  left: 0;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  animation: confetti-burst 0.8s ease-out forwards;
}

.task-steps-panel {
  margin-bottom: 6px;
}

.task-steps-list .inbox-item {
  padding: 8px 12px;
}

.task-steps-list .task-check {
  width: 22px;
  height: 22px;
}

.inbox-item-text.done {
  text-decoration: line-through;
  color: var(--text-muted);
}

.focus-timer-panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 6px;
}

.focus-timer-display {
  font-size: 1.6rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.focus-timer-display.done {
  color: var(--coral-ink);
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text-secondary);
  cursor: pointer;
}

.inbox-input {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  padding: 10px 12px;
  font-size: 0.95rem;
  font-family: inherit;
}

.inbox-input:focus {
  outline: 2px solid var(--lime);
  outline-offset: 1px;
}

.inbox-list,
.slipped-tasks-list {
  margin-top: 10px;
}

.inbox-item,
.slipped-task-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 6px;
  flex-wrap: wrap;
}

.inbox-item-text,
.slipped-task-title {
  flex: 1;
  min-width: 140px;
}

.slipped-task-sub {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.inbox-item-actions,
.slipped-task-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

/* ---- capacity calibration ---- */
.calibration-row {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-top: 10px;
  background: var(--bg);
}

.calibration-window-name {
  font-weight: 600;
  font-size: 0.85rem;
  margin-bottom: 4px;
}

.calibration-detail {
  font-size: 0.85rem;
  color: var(--text-secondary);
  line-height: 1.5;
}

.calibration-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 8px;
}

.calibration-coverage {
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 6px;
}

/* ---- help requests (body doubling) ---- */
.help-request-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
  background: var(--bg);
}

.help-request-title {
  font-weight: 600;
  font-size: 0.9rem;
}

.help-request-sub {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-top: 3px;
}

.help-request-claimed {
  font-size: 0.82rem;
  color: var(--teal-ink);
  margin-top: 4px;
  font-weight: 600;
}

.help-request-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}

/* ---- focus mode ---- */
/* A full-screen overlay rather than a card: the entire point is that the
   rest of the list stops being visible. */
.focus-overlay {
  position: fixed;
  inset: 0;
  z-index: 900;
  background: var(--card);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  overflow-y: auto;
}

/* display:flex above would otherwise beat the hidden attribute's default
   display:none, leaving an invisible full-screen layer swallowing every
   click in the app. */
.focus-overlay[hidden] {
  display: none;
}

.focus-overlay-inner {
  width: 100%;
  max-width: 520px;
  text-align: center;
}

.focus-overlay-progress {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 14px;
}

.focus-overlay-task {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.35;
  margin-bottom: 18px;
}

/* Deliberately not colored by state. A timer that turns red on overtime
   punishes the exact thing this mode exists to help with. */
.focus-overlay-timer {
  font-size: 3rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--teal-ink);
  margin-bottom: 6px;
}

.focus-overlay-note {
  font-size: 0.85rem;
  color: var(--text-secondary);
  min-height: 1.2em;
  margin-bottom: 20px;
}

.focus-overlay-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

/* ---- image capture (Quick capture's photo-upload path) ---- */
.capture-thumbnails {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.capture-thumb {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.capture-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A text file has no preview, so its tile shows the file name instead of
   an empty square - it still needs to be identifiable before you commit
   to reading it. */
.capture-thumb-doc {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  background: var(--hub-surface-soft, var(--bg));
}

.capture-thumb-doc-name {
  font-size: 11px;
  line-height: 1.2;
  text-align: center;
  color: var(--hub-muted, var(--text-muted));
  word-break: break-word;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}

.capture-thumb-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: none;
  background: rgba(63, 50, 54, 0.72);
  color: #fff;
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.capture-review-image-group {
  margin-top: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 12px 14px;
  background: var(--bg);
}

.capture-review-image-title {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.capture-review-image-error {
  font-size: 0.85rem;
  color: var(--red-ink);
}

.capture-proposal-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 6px;
}

.capture-proposal-row input[type="checkbox"] {
  margin-top: 3px;
  flex-shrink: 0;
}

.capture-proposal-label {
  flex: 1;
  min-width: 0;
}

.capture-proposal-kind {
  display: inline-block;
  font-size: .75rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--teal-ink);
  margin-bottom: 2px;
}

.capture-proposal-title {
  font-weight: 600;
}

.capture-proposal-sub {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.capture-review-actions {
  margin-top: 12px;
}

@keyframes confetti-burst {
  0% {
    transform: translate(0, 0) rotate(0deg);
    opacity: 1;
  }
  100% {
    transform: translate(var(--dx), calc(var(--dy) + 60px)) rotate(var(--rot));
    opacity: 0;
  }
}

/* ---------------------------------------------------------------
   Baseline accessibility
   ---------------------------------------------------------------
   Two gaps that showed up reviewing this as a product sold on being
   accessible:

   1. There was no global :focus-visible style. A few of the visual-refresh
      layers defined one for their own components, so whether a keyboard
      user could see where they were depended on which layer happened to
      win - which is not a thing anyone should have to rely on. This is the
      floor, in the base stylesheet, under everything.

   2. Nothing responded to prefers-contrast. Someone who has asked their OS
      for more contrast - which includes a lot of the low-vision and
      sensory-sensitive people this app is for - got the same soft pastel
      borders as everyone else.
   --------------------------------------------------------------- */

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--lime, #5f9c2e);
  outline-offset: 2px;
  border-radius: 4px;
}

@media (prefers-contrast: more) {
  :root {
    --border: #4a4340;
    --text-muted: #3d3634;
  }

  .card,
  .view-header,
  input,
  select,
  textarea,
  .btn,
  .swap-toggle-btn,
  .tab-btn,
  .subtab-btn {
    border-color: currentColor;
  }

  /* Pastel fills carry meaning in several places (priority dots, gauge
     states, owner chips). Raise the text on top of them rather than
     flattening the fills themselves. */
  .notifications-note,
  .today-tasks-sub,
  .gauge-sub,
  .grocery-item-amount {
    color: var(--text-muted);
  }

  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline-width: 4px;
  }
}

/* Shown only when the service worker had to fall back to its last cached
   read (see OFFLINE_READABLE in sw.js). Stale data presented as current
   is worse than no data in an app whose whole promise is that what's on
   screen can be trusted, so this is deliberately hard to miss. */
.offline-banner {
  position: sticky;
  top: 0;
  z-index: 60;
  padding: 9px 16px;
  background: #8a5a00;
  color: #fff;
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.4;
  text-align: center;
}

/* Refill heads-up. Deliberately calm at ten days out and only firm once
   something has actually run out - a medication card that shouts every
   day is one people stop seeing. */
.refill-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.refill-row:last-child { border-bottom: none; }
.refill-name { font-weight: 650; }
.refill-who { font-weight: 500; color: var(--text-muted); }
.refill-when {
  flex-shrink: 0;
  font-size: 0.85rem;
  font-weight: 650;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.refill-row.is-overdue .refill-when { color: #a5352a; }

/* Settings > Your data. The danger zone is deliberately the only place in
   the app that uses a red-bordered panel: it is the one action with no
   undo, and it should not read like the row of buttons above it. */
.danger-zone {
  border: 1px solid color-mix(in srgb, var(--red) 42%, transparent);
  background: color-mix(in srgb, var(--red) 5%, transparent);
  border-radius: 12px;
  padding: 14px 16px;
}
.danger-zone-title {
  margin: 0 0 6px;
  font-size: 0.95rem;
  color: var(--red-ink);
}
.danger-zone summary::-webkit-details-marker { display: none; }
.danger-zone summary { list-style: none; }
.danger-zone label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.danger-zone input {
  width: 100%;
  margin-top: 5px;
  font-weight: 400;
}
/* The phrase has to be copied exactly, so it is set apart from the
   sentence around it rather than just emboldened inside it. */
.danger-phrase {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.95em;
  background: var(--card-bg, #fff);
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
  border-radius: 5px;
  padding: 1px 5px;
  color: var(--red-ink);
}
.btn-danger {
  background: var(--red);
  color: #fff;
  border-color: var(--red);
}
.btn-danger:hover:not(:disabled) {
  background: var(--red-ink);
  border-color: var(--red-ink);
}
.btn-danger:disabled { opacity: 0.6; }

/* Roster editor: the relationship label and the chore-keys toggle that
   replaced the old Kid/Adult picker (see family_members in the schema). */
.family-member-relationship-input {
  flex: 1 1 130px;
  min-width: 0;
}

.family-member-supervised {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.84rem;
  color: var(--ink-secondary, var(--text-secondary));
  white-space: nowrap;
  cursor: pointer;
}

/* Task categories editor (Settings > Household). */
.task-categories-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.task-category-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.task-category-swatch {
  width: 12px;
  height: 12px;
  flex: 0 0 12px;
  border-radius: 3px;
  background: var(--accent, #65615f);
}

.task-category-name {
  flex: 1;
  min-width: 0;
  font-size: 0.92rem;
}

/* "‹ Pick a different meal slot" - a way back up the picker, not a meal. */
.swap-option-back {
  font-weight: 600;
  opacity: 0.75;
}

/* "Meals you plan" (Settings > Household). */
.meal-slots-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 4px 12px;
}

.meal-slot-row {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.9rem;
  cursor: pointer;
}

/* A tonight row for something that was scheduled earlier. States the fact
   quietly; the row still behaves like any other, because it is still just
   the next thing to do. */
.tonight-task-slipped {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--ink-muted, var(--text-muted));
}

/* ---- plan state: trial countdown, and the read-only state after it ----
   Two tones on purpose. The countdown is information and reads like the
   refill heads-up above; the ended state is the explanation for why the
   app stopped taking changes, so it is firmer and cannot be dismissed.
   Sticky rather than fixed, matching .offline-banner, so it scrolls with
   the page on a phone instead of eating a fifth of a small screen. */
.plan-banner {
  position: sticky;
  top: 0;
  z-index: 61;
  padding: 10px 16px;
  font-size: 0.86rem;
  line-height: 1.45;
  text-align: center;
  color: #fff;
}
.plan-banner strong { font-weight: 700; }
.plan-banner.is-ending { background: #8a5a00; }
.plan-banner.is-ended  { background: #8c2f22; }

/* The two controls the sync banner adds. Outlined in the banner's own
   white rather than given a colour of their own: a banner is already the
   loudest thing on the screen, and a second accent inside it competes
   with the sentence instead of supporting it.

   Both are 32px tall so they clear the 24x24 minimum target size in WCAG
   2.2 with room to spare - this banner turns up on a wall-mounted tablet
   as well as a phone, and the dismiss is a small control in a thin bar,
   which is exactly where that rule is usually broken. */
.plan-banner-action,
.plan-banner-dismiss {
  min-height: 32px;
  border-radius: 999px;
  border: 1.5px solid rgba(255, 255, 255, 0.75);
  background: transparent;
  color: #fff;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.plan-banner-action { padding: 0 14px; }
.plan-banner-action:hover,
.plan-banner-dismiss:hover { background: rgba(255, 255, 255, 0.16); }
.plan-banner-action:focus-visible,
.plan-banner-dismiss:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* Out of the flow, so dismissing does not shift the sentence it sits
   beside, and centred on the banner's own height at any text length. */
.plan-banner-dismiss {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  min-width: 32px;
  line-height: 1;
  font-size: 1.05rem;
}
/* Room for the dismiss, so a long sentence never runs under it.
   .plan-banner is already `position: sticky`, which is a positioned
   element, so it is the containing block the absolute above resolves
   against - no extra `position: relative` needed. */
.plan-banner:has(.plan-banner-dismiss) { padding-right: 52px; }

/* Write controls, while the household may not write.
   Not `pointer-events: none` on the whole app: reading, filtering,
   switching tabs, opening the export and looking at the budget all still
   work, and taking that away would be the lockout this deliberately is
   not. Only the controls that submit something are shown as unavailable,
   and api() still refuses in case one is missed. */
body[data-read-only="true"] .btn-primary,
body[data-read-only="true"] button[type="submit"],
body[data-read-only="true"] [data-write] {
  opacity: 0.45;
  cursor: not-allowed;
  filter: grayscale(0.4);
}
body[data-read-only="true"] .task-check {
  opacity: 0.5;
  cursor: not-allowed;
}
