/* ============================================================
   Table of contents.

   grep a marker below, then Read that range with offset/limit —
   reading this file end to end is almost never the task. Section
   markers are comments of the form "--- label"; a few large "===="
   banners (also listed here, as group headers) mark bigger jumps
   in the design's history. Order is load-bearing (CLAUDE.md rule
   17) — append within a section, never reorder sections.
   (Never write the two-character comment terminator inside this or
   any comment: CSS comments do not nest, it ends the comment right
   there, and the parser then eats everything up to the next block —
   which was the entire :root token block for a day of v1.1.)

   Base tokens & chrome
     type · chrome · blocks: elevation over borders · buttons · motion

   Task 1 additions — desktop rail, tablet grids, ingredient check (Today)
     1c / 3d ingredient check · 3d stock summary card
     3b hero card meta + photo · 3c hero actions · 3e tomorrow thaw pill
     1b tablet, 640-1023px · 1a desktop rail, >=1024px

   Task 4 additions — recipe detail
     (one continuous block, no subsection markers)

   Task 5 additions — Fridge and Shopping
     fridge: search + edit (1d) · fridge: zones and tiles
     fridge: desktop zone rail (3b) · list: progress head (2e)
     list: rows (2e) · list: pinned action (2e)

   Task 6 additions — Settings and dietary rules
     5a grouped setting rows · 5b dietary rules · phone only, <=639px
     5c merged, >=640px · 5d desktop, >=1024px · 3c hero actions, narrow phones

   Phase 6 — Notes
     composer · the batch · the notes themselves · tablet, >=640px
     desktop, >=1024px · plan tab, night action chips
     Notes: the device queue

   Phase 6 step 5 — the shopping list read offline
     (no subsection markers)

   Shopping tab count
     1a desktop rail, >=1024px

   Notifications — the third Settings pane
     3a phone only, <=639px · 3b merged, >=640px · 3c desktop, >=1024px

   Phase 7 — the three-star rating
     quick meal

   Phase 7 — Claude usage (SPEC §13.4)
     this month against the cap · the months
     legend and the feature list · phone only, <=639px · merged, >=640px
     desktop, >=1024px

   Appended after Claude usage, still Phase 7 (no new banner — rule 17)
     recipe: household rule flag · counts and serving counts
     kitchen measures · long press a recipe card · library sort bar
     duplicate at import
   ============================================================ */

/* ============================================================
   Paper reskin — MealPlanner 2026 design update.
   Loads AFTER style.css/fridge.css/recipes.css/plan.css and
   overrides tokens + component chrome. Rollback: remove the
   <link> for this file (and the font links) from index.html.
   Source of truth: MealPlanner 2026.dc.html (design project).
   ============================================================ */

:root {
  --bg:          #f3f0e9;   /* paper */
  --surface:     #fbf9f5;
  --border:      rgba(31, 28, 23, 0.08);   /* row separators only — cards use elevation */
  --text:        #1e1c18;
  --muted:       #6e685c;   /* 4.5:1 on paper AND on the tinted surfaces */
  --accent:      #1c7458;   /* eucalyptus */
  --accent-press:#12513e;
  --accent-dim:  #e3ede8;
  --have:        #1c7458;
  --low:         #c98a1b;
  --out:         #9e4038;
  --radius:      16px;

  /* New Paper tokens */
  --well:        #f0ede6;                    /* inset track behind segments */
  --low-fill:    #8a5f12;                    /* filled amber segment (white text passes) */
  --low-text:    #8a5715;
  --out-text:    #8c3830;
  --low-tint:    color-mix(in srgb, var(--low) 16%, var(--surface));
  --out-tint:    color-mix(in srgb, var(--out) 14%, var(--surface));
  --shadow-1:    0 1px 2px rgba(31, 28, 23, 0.05);
  --shadow-2:    0 1px 2px rgba(31, 28, 23, 0.05), 0 12px 30px -16px rgba(31, 28, 23, 0.24);
  --shadow-cta:  0 1px 2px rgba(20, 80, 60, 0.2), 0 10px 22px -12px rgba(20, 80, 60, 0.6);
  --cook-track:  #cfd8d2;   /* cook mode's unfilled progress pips */
  --serif:       "Newsreader", Georgia, "Times New Roman", serif;
  --sans:        "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono:        "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --ease:        cubic-bezier(0.2, 0.7, 0.3, 1);

  /* Paper is a light-only design. There is no dark theme and none is planned:
   * the palette is built on paper white with amber/clay text sitting on its
   * own tint, and inverting it is a redesign, not a token swap. `color-scheme`
   * keeps the browser from darkening form controls out from under it. */
  color-scheme: light;
}

/* ------------------------------------------------------------ type */

html, body { font-family: var(--sans); font-weight: 400; }

/* Serif carries anything that names a dish, plus the view title. */
#view-title, .hero-title, .rtitle, .night-title, .rcard-title, .cook-dish {
  font-family: var(--serif);
  font-weight: 400;
  letter-spacing: -0.01em;
}
#view-title { font-size: 1.55rem; }
.rcard-title, .night-title { font-size: 1.06rem; }

/* Mono carries labels and counts. */
.hero-kicker, .night-kind, .subh, .card h2 .count, .night-date,
.cook-meta, .cook-steplabel, .cook-panel-label, .cook-timer-label {
  font-family: var(--mono);
  font-weight: 500;
}
.hero-kicker, .night-kind, .subh, .cook-meta { letter-spacing: 0.1em; }

/* ------------------------------------------------------------ chrome */

#topbar { border-bottom: 0; }

#tabbar {
  border-top: 0;
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: inset 0 1px 0 var(--border);
}
.tab[aria-selected="true"] { color: var(--accent-press); }
.tab[aria-selected="true"] .glyph {
  background: var(--accent-dim);
  border-radius: 999px;
  padding: 3px 13px;
}
.tab .glyph { padding: 3px 13px; border-radius: 999px; }
.tab .glyph svg { display: block; }

/* ------------------------------------------------------------ blocks: elevation over borders */

.card, .rcard, .swapcard, .ichoice {
  border-color: transparent;
  box-shadow: var(--shadow-1);
}
.card { border-radius: 20px; }
.hero-card { box-shadow: var(--shadow-2); border-radius: 22px; }

.night.is-today {
  border-color: transparent;
  box-shadow: inset 3px 0 0 var(--accent), var(--shadow-2);
}
.card.night:has(.night-hit.hard) {
  border-color: transparent;
  box-shadow: inset 3px 0 0 var(--out), var(--shadow-1);
}

#inv-search { border-color: transparent; box-shadow: var(--shadow-1); border-radius: 15px; }

.chip {
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(31, 28, 23, 0.04);
  font-weight: 500;
}
.chip.on { box-shadow: none; }

/* Tri-state control: tinted track instead of hairlines (mockup 1d). */
.seg-group {
  border: 0;
  background: var(--well);
  padding: 3px;
  gap: 2px;
  border-radius: 11px;
  overflow: visible;
}
.seg { background: transparent; border-radius: 8px; }
.seg + .seg { border-left: 0; }
.seg.have.on { background: var(--have); box-shadow: 0 1px 2px rgba(31, 28, 23, 0.12); }
.seg.low.on  { background: var(--low-fill); box-shadow: 0 1px 2px rgba(31, 28, 23, 0.12); }
.seg.out.on  { background: var(--out); box-shadow: 0 1px 2px rgba(31, 28, 23, 0.12); }

/* Scaler + rate buttons follow the same track logic. */
.sbtn, .ratebtn, .hwbtn, .row-btn { border-color: transparent; background: var(--well); }
.sbtn.on { background: var(--accent); }
.ratebtn.on, .hwbtn.on { border-color: transparent; }

/* Amber / clay text always on its tint, never raw on paper. */
.pill.warn  { background: var(--low-tint); color: var(--low-text); }
.pill.error { background: var(--out-tint); color: var(--out-text); }
.ck.low  .ck-state { background: var(--low-tint); color: var(--low-text); }
.ck.out  .ck-state { background: var(--out-tint); color: var(--out-text); }
.warnbox { background: var(--low-tint); color: var(--low-text); border-radius: 13px; }
.warnbox.danger, .night-hit.hard ul { background: var(--out-tint); color: var(--out-text); }
.night-hit ul { background: var(--low-tint); color: var(--low-text); }
.night-swaps { border-radius: 13px; }
.staple { color: var(--low-text); }
.allergen-note { color: var(--out-text) !important; }
.shop-row.allergen .shop-label { color: var(--out-text); }

/* ------------------------------------------------------------ buttons */

button.primary {
  border-radius: 15px;
  font-family: var(--sans);
  font-weight: 500;
  box-shadow: var(--shadow-cta);
}
.fab {
  border-color: transparent;
  box-shadow: 0 1px 2px rgba(31, 28, 23, 0.06), 0 8px 20px -12px rgba(31, 28, 23, 0.35);
  font-weight: 500;
}
.fab.primary-fab { box-shadow: var(--shadow-cta); }

/* Icon + label buttons (Task 3a). inline-flex is a no-op on a text-only
   button (one child behaves like inline-block), so this is safe on every
   existing .fab/.chip that hasn't been given an icon. */
.fab, .chip, .ratebtn, .hwbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* .ichoice b must stay block (recipes.css stacks it above a sibling
   <small>) — flex, not inline-flex, so the icon+title pair still sits on
   its own line above the description. */
.ichoice b {
  display: flex;
  align-items: center;
  gap: 6px;
}
.icon { flex: none; }

.sheet {
  border-radius: 22px 22px 0 0;
  box-shadow: 0 -6px 34px rgba(31, 28, 23, 0.22);
}
@media (min-width: 640px) { .sheet { border-radius: 22px; } }

.steps.big label::before { font-weight: 500; }

/* ------------------------------------------------------------ motion
   Five tokens (mockup 5e): 180ms hover/press ease, 97% press,
   rise on the hero and sheets, breathe on tonight's CTA. */

button, .chip, .seg, .tab, .fab, .sbtn, .ratebtn, .hwbtn, .row-btn, .icon-btn,
.rcard, .night-recipe, .ichoice {
  transition: background-color 0.18s ease-out, color 0.18s ease-out,
              box-shadow 0.18s ease-out, border-color 0.18s ease-out,
              transform 0.18s ease-out, opacity 0.18s ease-out;
}
button.primary:active, .fab:active, .seg:active, .chip:active,
.sbtn:active, .ratebtn:active, .hwbtn:active, .row-btn:active { transform: scale(0.97); }
button.primary:active { filter: none; background: var(--accent-press); }

@keyframes paper-rise {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes paper-breathe {
  0%, 100% { box-shadow: 0 1px 2px rgba(20, 80, 60, 0.18), 0 8px 20px -10px rgba(20, 80, 60, 0.5); }
  50%      { box-shadow: 0 1px 2px rgba(20, 80, 60, 0.18), 0 12px 26px -10px rgba(20, 80, 60, 0.62); }
}
.hero-card { animation: paper-rise 0.5s var(--ease) both; }
.hero-card .primary-fab, .hero-card button.primary { animation: paper-breathe 4s ease-in-out infinite; }
@media (max-width: 639px) {
  .sheet { animation: paper-rise 0.32s var(--ease) both; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-card, .sheet { animation: none; }
  .hero-card .primary-fab, .hero-card button.primary { animation: none; }
}

/* ============================================================
   Task 1 additions — desktop rail, tablet grids, ingredient
   check. Source: design_handoff_paper_reskin/css/paper-v2-additions.css,
   adjusted against the live DOM (see notes inline).
   ============================================================ */

/* ---------------------------------------------- 1c / 3d ingredient check
   Styles markup today.js emits (.cklist / .ck). Task 3d replaced the row
   shape — dot + name + state word + Add, per the tinted-row spec in the
   README (mockup 3a; the older phone frame 1a draws these untinted, but
   the desktop frame and the written spec agree on tinted, so that wins) —
   so the old .ck-state pill-badge rules are gone rather than left dead. */

.cklist {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ck {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 13px;
  border-radius: 12px;
  background: var(--well);
  font-size: 0.86rem;
  line-height: 1.35;
}
.ck-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--muted);
}
.ck-text { flex: 1; min-width: 0; }
.ck-word { color: var(--muted); margin-left: 2px; }
.ck.have .ck-dot { background: var(--accent); }
.ck.low  { background: var(--low-tint); }
.ck.low  .ck-dot { background: var(--low); }
.ck.out  { background: var(--out-tint); }
.ck.out  .ck-dot { background: var(--out); }
.ck.unknown .ck-dot { background: var(--muted); }
.ck small.muted { color: var(--muted); }

.ck-add {
  flex: none;
  border: 0;
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--surface);
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.72rem;
  cursor: pointer;
}
.ck.low .ck-add { color: var(--low-text); }
.ck.out .ck-add, .ck.unknown .ck-add { color: var(--out-text); }
.ck-add:hover { background: #fff; }
.ck-add:disabled { opacity: 0.6; cursor: default; }

/* ---------------------------------------------- 3d stock summary card */

.stock-card { padding: 16px 18px 14px; }
.stock-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.stock-headline { font-weight: 500; font-size: 0.94rem; }
.stock-bar {
  display: flex;
  gap: 3px;
  height: 6px;
  margin-top: 11px;
}
.stock-bar i { border-radius: 999px; transform-origin: left; animation: paper-fill 0.7s 0.2s var(--ease) both; }
.stock-bar i.have { background: var(--accent); }
.stock-bar i.low  { background: var(--low); }
.stock-bar i.out  { background: var(--out); }
.stock-showall {
  display: block;
  width: 100%;
  margin-top: 4px;
  padding: 9px 0;
  border: 0;
  background: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.78rem;
  cursor: pointer;
}
.stock-showall:hover { color: var(--accent); }

@keyframes paper-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ---------------------------------------------- 3b hero card meta + photo */

.hero-top { display: flex; flex-direction: column; gap: 12px; }
.hero-info { flex: 1; min-width: 0; }
.hero-meta {
  display: flex;
  gap: 16px;
  margin-top: 12px;
  color: var(--muted);
  font-size: 0.81rem;
}
.hero-meta span { display: inline-flex; align-items: center; gap: 6px; }
.hero-photo {
  width: 100%;
  height: 170px;
  object-fit: cover;
  border-radius: 16px;
  flex: none;
}
@media (min-width: 640px) {
  .hero-top { flex-direction: row-reverse; align-items: flex-start; }
  .hero-photo { width: 150px; height: 150px; }
}

/* ---------------------------------------------- 3c hero actions
   New class rather than reusing .rbuttons/.fab — those are also recipes.js's
   detail action row (Task 4), and this row's shape (breathing primary +
   icon-only secondary + kept "Cooked it") is specific to Today. */

.hero-actions { display: flex; align-items: center; gap: 9px; margin-top: 16px; }
.hero-actions button.primary.hero-cook {
  flex: 1;
  width: auto;
  min-width: 0;
  padding: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 0.92rem;
  white-space: nowrap;
}
.hero-open, .hero-cooked {
  flex: none;
  border: 0;
  border-radius: 15px;
  background: var(--well);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.hero-open { width: 52px; height: 52px; }
.hero-cooked { padding: 0 16px; height: 52px; gap: 6px; font-family: var(--sans); font-weight: 500; font-size: 0.82rem; color: var(--text); white-space: nowrap; }
.hero-open:hover, .hero-cooked:hover { color: var(--accent); background: color-mix(in srgb, var(--well) 60%, var(--surface)); }
.hero-actions .pill.ok { flex: none; }

/* ---------------------------------------------- 3e tomorrow thaw pill */

.thaw-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 9px;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--low-tint);
  color: var(--low-text);
  font-size: 0.72rem;
}

@media (prefers-reduced-motion: reduce) {
  .stock-bar i { animation: none; }
}

/* ---------------------------------------------- 1b tablet, 640–1023px
   Bottom tab bar stays — this is a touch device. */

@media (min-width: 640px) and (max-width: 1023px) {
  #view { padding-left: 30px; padding-right: 30px; }

  .cklist { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: start; }
  .rlist  { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .rlist .rcard { margin-bottom: 0; }

  /* Turning a list into a grid exposes the divider between rows meant for a
     single column — kill it here rather than in fridge.css, so the reskin
     stays the only place that knows about the grid. */
  .inv-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 11px; }
  .inv-list > li { margin: 0; }
  .inv-list .inv-row + .inv-row { border-top: 0; }

  /* :has(.shop-head) picks out the shopping view specifically (it's the only
     one that renders that element), so the other four views sharing #view are
     untouched. Task 5 wrapped each source in a .shop-source section, so the
     two sections are the grid's columns (4j). Each now holds exactly one card
     -- the per-category cards inside them were dropped on purpose (CLEANUP.md
     4), which is why nothing here reaches past .shop-source any more. */
  #view:has(.shop-head) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    align-items: start;
  }
  #view:has(.shop-head) > .shop-head { grid-area: 1 / 1; }
  #view:has(.shop-head) > .shop-pin { grid-area: 1 / 2; justify-self: end; align-self: center; }
  #view:has(.shop-head) > .warnbox,
  #view:has(.shop-head) > .list-foot,
  #view:has(.shop-head) > .fab-row,
  #view:has(.shop-head) > #sheet-host {
    grid-column: 1 / -1;
  }
}

/* ---------------------------------------------- 1a desktop rail, ≥1024px
   #tabbar already holds icon+label buttons, so the sidebar is pure CSS.
   Cook mode must sit above this — see Task 2. */

@media (min-width: 1024px) {
  :root { --rail: 230px; }

  #tabbar {
    top: 0;
    bottom: 0;
    left: 0;
    right: auto;
    width: var(--rail);
    height: 100vh;
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    padding: 30px 18px calc(30px + env(safe-area-inset-bottom));
    background: #e9e4d8;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: inset -1px 0 0 var(--border);
  }

  /* Wordmark above the nav. Serif, matching the mockups. */
  #tabbar::before {
    content: "MealPlanner";
    font-family: var(--serif);
    font-size: 1.42rem;
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--text);
    padding: 0 10px;
    margin-bottom: 26px;
  }

  .tab {
    flex: none;
    width: 100%;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    gap: 13px;
    padding: 12px 14px;
    border-radius: 12px;
    font-size: 0.9rem;
    font-weight: 400;
    color: var(--muted);
  }
  .tab .glyph { padding: 0; background: none; }
  .tab[aria-selected="true"] {
    background: var(--accent-dim);
    color: var(--accent-press);
    font-weight: 500;
  }
  .tab[aria-selected="true"] .glyph { background: none; padding: 0; }
  .tab:hover { color: var(--accent-press); }

  #topbar,
  #view {
    margin-left: var(--rail);
    padding-left: 44px;
    padding-right: 44px;
  }
  #view { max-width: 1100px; padding-bottom: 60px; }
  #topbar #view-title { font-size: 2.1rem; }

  /* Two-column content where the mockups call for it. */
  .cklist { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: start; }
  .rlist  { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
  .rlist .rcard { margin-bottom: 0; }

  .inv-list { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
  .inv-list > li { margin: 0; }
  .inv-list .inv-row + .inv-row { border-top: 0; }

  #view:has(.shop-head) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;
  }
  #view:has(.shop-head) > .shop-head { grid-area: 1 / 1; }
  #view:has(.shop-head) > .shop-pin { grid-area: 1 / 2; justify-self: end; align-self: center; }
  #view:has(.shop-head) > .warnbox,
  #view:has(.shop-head) > .list-foot,
  #view:has(.shop-head) > .fab-row,
  #view:has(.shop-head) > #sheet-host {
    grid-column: 1 / -1;
  }

  /* Sheets stop being bottom sheets on a pointer device. */
  .sheet {
    left: 50%;
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 560px;
    border-radius: 22px;
  }

  /* Cook mode owns the screen at every size. */
  .cookmode { z-index: 60; }
}

@media (prefers-reduced-motion: reduce) {
  .cklist, .ck { animation: none; }
}

/* ============================================================
   Task 4 additions — recipe detail. Source: mockups 2c (phone),
   4c (desktop), 4h (tablet). Single markup, grid-area reflow per
   breakpoint (same technique as cook mode's grid-column/row
   placement above) rather than duplicating markup per size.
   ============================================================ */

.rdetail > .linkback {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 8px 14px 8px 11px;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--accent-press);
  font-weight: 500;
  font-size: 0.84rem;
}
.rdetail > .linkback .icon { transform: scaleX(-1); }

/* Meta row reuses Today's hero-meta (icon + label pairs) rather than
   inventing a second version of the same pattern. */
.rmeta.hero-meta { margin: 8px 0 16px; }
.rmeta.hero-meta a { color: var(--accent); }

.rdetail > .scaler { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.scaler-label {
  font-family: var(--mono);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.68rem;
  color: var(--muted);
}
.scaler-track { display: flex; gap: 2px; padding: 3px; background: var(--well); border-radius: 11px; }
.scaler-track .sbtn { min-width: 36px; height: 30px; padding: 0 8px; border-radius: 8px; font-size: 0.82rem; }
.rdetail .serves-out { margin-left: auto; }

/* Ingredient stock dots — same have/low/out/unknown color logic as the
   Today check rows (.ck-dot), scoped to .ing-row so the two never collide. */
.ings-card h2, .method-card h2 {
  font-family: var(--mono);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.7rem;
  color: var(--muted);
  margin: 0 0 12px;
}
/* .card h2 .count (fridge.css) is (0,2,1) — one class-count higher than
   .count.warn alone, so it would win the color on specificity even though
   this file loads later. Match its shape to actually override it. */
.card h2 .count.warn { color: var(--low-text); }

.ing-dot {
  flex: none;
  width: 7px;
  height: 7px;
  margin-top: 6px;
  border-radius: 999px;
  background: var(--muted);
}
.ing-row.have .ing-dot { background: var(--accent); }
.ing-row.low  .ing-dot { background: var(--low); }
.ing-row.out  .ing-dot { background: var(--out); }
.ing-row.unknown .ing-dot { background: var(--muted); }

.stock-legend {
  display: flex;
  gap: 14px;
  margin-top: 13px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 0.72rem;
  color: var(--muted);
}
.stock-legend span { display: inline-flex; align-items: center; gap: 5px; }
.stock-legend .dot { width: 7px; height: 7px; border-radius: 999px; }
.stock-legend .dot.have { background: var(--accent); }
.stock-legend .dot.low  { background: var(--low); }
.stock-legend .dot.out  { background: var(--out); }

/* Scoped to .method-card so print.css's own <ol class="steps"> (printRecipe(),
   plain numbered text) keeps its native markers untouched. */
.method-card .steps { list-style: none; padding-left: 0; margin: 0; }
.step-row { display: flex; gap: 12px; align-items: flex-start; }
.step-row + .step-row { margin-top: 14px !important; } /* overrides recipes.css .steps li + li */
.step-num {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--accent-dim);
  color: var(--accent-press);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.78rem;
}
.step-text { padding-top: 4px; font-size: 0.9375rem; line-height: 1.55; }

.cook-memory {
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.8rem;
}

.rbuttons { align-items: center; gap: 10px; }
.rbuttons .r-cookbtn {
  flex: 1;
  min-width: 0;
  padding: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 0.92rem;
}
.r-iconbtn {
  flex: none;
  width: 52px;
  height: 52px;
  border: 0;
  border-radius: 15px;
  background: var(--well);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.r-iconbtn:hover { color: var(--accent); background: color-mix(in srgb, var(--well) 60%, var(--surface)); }

/* Mobile/base: single-column grid so "actions" can sit near the bottom
   (mockup 2c) while every element still only exists once in the DOM. */
.rdetail {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas:
    "back" "photo" "title" "meta" "hits" "scaler"
    "ingredients" "method" "rate" "actions" "delete";
}
/* justify/align-self: grid items stretch by default, and "back" shares a row
   with the tall actions cell on desktop — without these the pill's rounded
   background balloons to fill the whole cell. */
.rdetail > .linkback    { grid-area: back; margin-bottom: 4px; justify-self: start; align-self: start; }
.rdetail > .rhero       { grid-area: photo; }
.rdetail > .rtitle      { grid-area: title; }
.rdetail > .rmeta       { grid-area: meta; }
/* Named, like every other child: `.rdetail` lays out by grid-template-areas at
   three different breakpoints, and an unnamed child is auto-placed into an
   implicit row that differs at each one. It landed above the title on a phone
   and would have gone somewhere else again on a tablet, silently. */
.rdetail > .rhits       { grid-area: hits; }
.rdetail > .scaler      { grid-area: scaler; }
.rdetail > .ings-card   { grid-area: ingredients; }
.rdetail > .method-card { grid-area: method; margin-top: 12px; }
.rdetail > .rate-row    { grid-area: rate; }
.rdetail > .rbuttons    { grid-area: actions; }
.rdetail > .linkdanger  { grid-area: delete; }

@media (min-width: 640px) and (max-width: 1023px) {
  /* "Photo, scaler and actions on the left; the ingredient list ... gets a
     full column of its own" (mockup 4h). Method's exact tablet position
     wasn't in the captured frame, so it degrades sensibly: full width
     below the split rather than a guessed placement. */
  .rdetail {
    grid-template-columns: 1fr 1fr;
    column-gap: 24px;
    grid-template-areas:
      "back        back"
      "photo       ingredients"
      "title       ingredients"
      "meta        ingredients"
      "hits        ingredients"
      "scaler      ingredients"
      "actions     ingredients"
      "method      method"
      "rate        rate"
      "delete      delete";
  }
}

@media (min-width: 1024px) {
  /* Header row (back pill + actions, mockup 4c) plus the ingredients/method
     split down the left/right columns. */
  .rdetail {
    grid-template-columns: 1fr 1fr;
    column-gap: 40px;
    max-width: 1000px;
    grid-template-areas:
      "back        actions"
      "photo       method"
      "title       method"
      "meta        method"
      "hits        method"
      "scaler      method"
      "ingredients method"
      "rate        rate"
      "delete      delete";
  }
  .rdetail > .rbuttons { justify-self: end; align-self: start; }
  .rdetail > .rbuttons .r-cookbtn { flex: none; }
}

/* ============================================================
   Task 5 additions — Fridge and Shopping. Sources: 1d / 4g / 3b
   (fridge, phone / tablet / desktop) and 2e / 4j / 4e (list).
   Same markup at every width; the phone card and the tablet
   tile are the same DOM reflowed, as in Task 4.
   ============================================================ */

/* ---------------------------------------------- fridge: search + edit (1d) */

.fridge-search { display: flex; align-items: stretch; gap: 9px; }
.search-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 15px;
  border-radius: 15px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--muted);
}
/* Beats the plain `#inv-search` rule further up, which styled the input as the
   whole field back when it was one. */
.search-wrap #inv-search {
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/* Square icon button — the mockup's edit affordance, and the same shape the
   desktop frames use for their header actions. */
.icon-sq {
  flex: none;
  width: 46px;
  min-height: 44px;
  border: 0;
  border-radius: 15px;
  background: var(--surface);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-1);
  cursor: pointer;
}
.icon-sq:hover { color: var(--accent); }
.icon-sq.on { background: var(--accent); color: #fff; }

.chip-count { font-family: var(--mono); font-weight: 500; font-size: 0.92em; opacity: 0.75; }

/* ---------------------------------------------- fridge: zones and tiles */

.zone { background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-1); padding: 16px 18px 8px; }
.zone + .zone { margin-top: 12px; }
.zone-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.zone-name { font-weight: 500; font-size: 0.91rem; text-transform: capitalize; }
.zone-count { font-family: var(--mono); font-weight: 500; font-size: 0.75rem; color: var(--muted); }

.zone-bar { display: flex; gap: 2px; height: 5px; margin-top: 10px; }
.zone-bar i { border-radius: 999px; transform-origin: left; animation: paper-fill 0.7s 0.2s var(--ease) both; }
.zone-bar i.have { background: var(--accent); }
.zone-bar i.low  { background: var(--low); }
.zone-bar i.out  { background: var(--out); }
.zone .inv-list { margin-top: 6px; }

@media (min-width: 640px) {
  /* The card falls away and the tiles carry their own elevation (4g / 3b).
     The stock bar goes with it: at this width the whole zone is on screen at
     once, which is the thing the bar existed to substitute for. */
  .zone { background: none; box-shadow: none; padding: 0; }
  .zone + .zone { margin-top: 24px; }
  .zone-bar { display: none; }
  .zone-head { display: block; }
  .zone-name, .zone-count {
    font-family: var(--mono);
    font-weight: 500;
    font-size: 0.69rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .zone-count::before { content: "· "; }
  .zone .inv-list { margin-top: 12px; }

  .inv-row {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: 13px 15px;
    border-radius: 15px;
    background: var(--surface);
    box-shadow: var(--shadow-1);
  }
  .inv-row .seg-group, .inv-row .row-actions { margin-top: 10px; }
  /* The mockup draws these tighter than the phone row; 9px keeps the tile's
     hit target the same height as the one people already use on the phone. */
  .inv-row .seg { flex: 1; min-width: 0; padding: 9px 0; text-align: center; }
  .inv-row .row-btn { flex: 1; }
}

/* ---------------------------------------------- fridge: desktop zone rail (3b) */

@media (min-width: 1024px) {
  .fridge-main { display: flex; align-items: flex-start; gap: 30px; }
  .fridge-main .chips {
    flex: none;
    width: 190px;
    flex-direction: column;
    gap: 3px;
    margin-top: 0;
    overflow: visible;
  }
  .fridge-main .chip {
    width: 100%;
    justify-content: flex-start;
    gap: 8px;
    padding: 11px 14px;
    border-radius: 12px;
    background: none;
    box-shadow: none;
    font-size: 0.84rem;
    font-weight: 400;
  }
  .fridge-main .chip .chip-count { margin-left: auto; }
  .fridge-main .chip.on { background: var(--accent-dim); color: var(--accent-press); font-weight: 500; }
  /* :not(.on) so the selected pill still wins its own colour. */
  .fridge-main .chip[data-staples]:not(.on) { color: var(--low-text); margin-top: 8px; }
  .fridge-main #inv-body { flex: 1; min-width: 0; }

  /* Three columns, not four: the rail takes 220px out of the content width,
     and three is what the mockup draws at this size. */
  .inv-list { grid-template-columns: repeat(3, 1fr); }
}

/* ---------------------------------------------- list: progress head (2e) */

.shop-head { display: block; margin-bottom: 16px; }
.shop-count { display: flex; align-items: baseline; flex-wrap: wrap; gap: 7px; }
.shop-count strong { font-weight: 500; font-size: 0.94rem; }
.shop-count .muted { font-size: 0.82rem; }
.shop-bar {
  height: 6px;
  margin-top: 10px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
}
.shop-bar i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}
/* The `fill` token is once per screen visit, so only the full paint asks for
   it; ticking a row animates through the transition above instead. */
.shop-bar i.fill { animation: paper-fillw 0.7s 0.2s var(--ease) both; }
@keyframes paper-fillw { from { width: 0; } }

.warnbox { display: flex; align-items: flex-start; gap: 10px; padding: 12px 15px; border-radius: 16px; }
.warnbox .icon { flex: none; margin-top: 1px; }

.shop-source > .subh { margin: 0 0 10px; font-size: 0.66rem; letter-spacing: 0.12em; }
/* Only while the sections are stacked. Once they are grid columns the margin
   would push the second column's heading below the first one's. */
@media (max-width: 639px) {
  .shop-source + .shop-source { margin-top: 18px; }
}

/* ---------------------------------------------- list: rows (2e) */

.shop-row label { position: relative; align-items: center; gap: 13px; padding: 10px 0; }
/* The native box is kept for the change event, focus and the label click; the
   circle beside it is what anyone actually sees. */
.shop-row input[type="checkbox"] {
  position: absolute;
  width: 24px;
  height: 24px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}
.shop-box {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--well);
  color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.18s ease-out, color 0.18s ease-out;
}
.shop-box .icon { stroke-width: 2.4; }
.shop-row.done .shop-box { background: var(--accent); color: #fff; }
.shop-row input:focus-visible + .shop-box { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Muted and struck through, never removed: a row that vanishes loses your
   place in the aisle. Colour rather than opacity so the note under it stays
   readable at arm's length. */
.shop-row.done .shop-label { color: var(--muted); opacity: 1; }
.shop-row.done .shop-main small { color: var(--muted); opacity: 1; }
.shop-row + .shop-row { border-top: 0; box-shadow: inset 0 1px 0 var(--border); }
.shop-label { font-weight: 400; }
.shop-main small { font-size: 0.72rem; }

/* ---------------------------------------------- list: pinned action (2e) */

.shop-pin {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--tabbar-h) + var(--safe-b));
  z-index: 9;
  display: flex;
  gap: 9px;
  padding: 34px 16px 14px;
  background: linear-gradient(to top, var(--bg) 56%, transparent);
  pointer-events: none;
}
.shop-pin > * { pointer-events: auto; }
.shop-pin button.primary {
  flex: 1;
  width: auto;
  min-width: 0;
  padding: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 0.92rem;
}
.pin-icon {
  flex: none;
  width: 52px;
  border: 0;
  border-radius: 15px;
  background: var(--surface);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 2px rgba(31, 28, 23, 0.06), 0 8px 20px -12px rgba(31, 28, 23, 0.35);
  cursor: pointer;
}
.pin-icon:hover { color: var(--accent); }
/* The pinned bar floats over the list, so the list has to end above it. */
#view:has(.shop-pin) { padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + 100px); }

/* Rebuild and put-away are real but rare. They keep working; they stop
   competing with the one action you came to the screen for. */
.list-foot { display: flex; justify-content: center; gap: 20px; margin: 18px 0 4px; }
.linkbtn {
  border: 0;
  background: none;
  padding: 8px 4px;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.82rem;
  cursor: pointer;
}
.linkbtn:hover { color: var(--accent); }

@media (min-width: 640px) {
  /* A tablet in the kitchen isn't a phone in an aisle: nothing needs pinning,
     so the action goes inline with the progress (4j / 4e). The grid blocks
     above put it in the header row. */
  .shop-pin {
    position: static;
    padding: 0;
    background: none;
    justify-content: flex-end;
  }
  .shop-pin button.primary { flex: none; padding: 13px 20px; }
  #view:has(.shop-pin) { padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + 24px); }
  .warnbox { max-width: 640px; }
}

/* No bottom bar to clear on desktop. Lives here rather than in the Task 1
   block above so it comes after the ≥640 rule and actually wins. */
@media (min-width: 1024px) {
  #view:has(.shop-pin) { padding-bottom: 60px; }
}

@media (prefers-reduced-motion: reduce) {
  .zone-bar i, .shop-bar i.fill { animation: none; }
  .shop-bar i, .shop-box { transition: none; }
}

/* ============================================================
   Task 6 additions — Settings and dietary rules. Sources:
   5a (phone settings), 5b (phone rules), 5c / 5d (the two
   merged into one two-pane screen at tablet and desktop).

   Both panes are always in the DOM. Which one a phone shows is
   the .on-rules class; at 640px and up they sit side by side
   and nothing is hidden. Rendering by width instead would need
   a resize listener to stay right.
   ============================================================ */

.settings-wrap .linkback {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 12px;
  padding: 8px 14px 8px 11px;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--accent-press);
  font-weight: 500;
  font-size: 0.84rem;
}
.settings-wrap .linkback .icon { transform: scaleX(-1); }
.settings-wrap .linkback:hover { background: var(--accent-dim); }

/* ---------------------------------------------- 5a grouped setting rows */

.setcard { padding: 2px 18px; }
.setlabel {
  margin: 0;
  padding: 14px 0;
  font-family: var(--mono);
  font-size: 0.69rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Rows divide with an inset hairline, so the card itself keeps the
   elevation-only edge every other card has. */
.setrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 11px 0;
  border: 0;
  background: none;
  box-shadow: inset 0 1px 0 var(--border);
  color: var(--text);
  font: inherit;
  text-align: left;
}
button.setrow { cursor: pointer; }
.setrow-main { flex: 1; min-width: 0; font-size: 0.94rem; line-height: 1.3; }
.setrow-main small {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 0.72rem;
  line-height: 1.4;
}
.setrow-value { color: var(--muted); font-size: 0.84rem; }
.setrow .pill { flex: none; font-weight: 500; }
.setnav .icon { flex: none; color: var(--muted); }
.setnav:hover .setrow-main { color: var(--accent-press); }

.who-av {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: var(--accent-dim);
  color: var(--accent-press);
  font-weight: 500;
  font-size: 0.87rem;
  line-height: 34px;
  text-align: center;
}
.who-av.hard { background: var(--out-tint); color: var(--out-text); }
.who-av.soft { background: var(--low-tint); color: var(--low-text); }

/* "familiar / mixed / adventurous" will not fit beside a label at 390px, so
   the track takes its own full-width line — which also puts every segment
   past the 44px minimum. */
.segrow { flex-wrap: wrap; }
.settrack {
  display: flex;
  gap: 2px;
  width: 100%;
  margin-top: 4px;
  padding: 3px;
  border-radius: 11px;
  background: var(--well);
}
.setseg {
  flex: 1;
  padding: 12px 8px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.79rem;
  font-weight: 500;
  cursor: pointer;
}
.setseg.on {
  background: var(--accent);
  color: #fff;
  box-shadow: 0 1px 2px rgba(31, 28, 23, 0.12);
}
.setseg:active { transform: scale(0.97); }
.stapletags { flex-wrap: wrap; gap: 5px; padding-top: 14px; }

/* Version numbers and backend names are worth keeping and are not what anyone
   opens settings for. Native <details>, so it costs no JS. */
.diag > summary { padding: 6px 0; color: var(--muted); font-weight: 500; cursor: pointer; }
.diag[open] > summary { margin-bottom: 6px; }
.diag .subh { margin-top: 16px; }

/* ---------------------------------------------- 5b dietary rules */

.rules-title {
  margin: 0 0 8px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.55rem;
  letter-spacing: -0.01em;
}
.rules-intro { margin: 0; color: var(--muted); font-size: 0.84rem; line-height: 1.55; text-wrap: pretty; }
.rulespane > .subh { margin: 20px 0 10px; font-size: 0.66rem; letter-spacing: 0.12em; }
.rulespane > .muted { margin: 0 0 4px; font-size: 0.88rem; line-height: 1.5; }

.rulecard {
  margin-bottom: 10px;
  padding: 15px 17px;
  border-radius: 20px;
  background: var(--surface);
  animation: paper-rise 0.5s var(--ease) both;
}
/* Hard in clay, preference in amber. The colour difference is the point:
   hard rules do not bend, preferences do. */
.rulecard.hard { box-shadow: inset 3px 0 0 var(--out), var(--shadow-1); }
.rulecard.soft { box-shadow: inset 3px 0 0 var(--low), var(--shadow-1); }
/* Every .rulecard is an <article> and nothing else in the pane is, so
   nth-of-type staggers them in document order across both groups. */
.rulecard:nth-of-type(2) { animation-delay: 0.06s; }
.rulecard:nth-of-type(3) { animation-delay: 0.12s; }
.rulecard:nth-of-type(n + 4) { animation-delay: 0.18s; }

.rulecard-top { display: flex; align-items: center; gap: 10px; }
.rule-name {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.06rem;
  line-height: 1.25;
  letter-spacing: -0.01em;
}
.rulecard .pill { flex: none; font-size: 0.66rem; font-weight: 500; }

.rule-alias, .rule-reason, .rule-because {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.5;
  text-wrap: pretty;
}
.rule-reason { font-style: italic; }
.rule-effects { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.effect {
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  font-size: 0.69rem;
}
.rule-acts { display: flex; gap: 18px; margin-top: 8px; }
.rule-acts .linkbtn { padding: 8px 0; }
.linkbtn.danger:hover { color: var(--out); }

/* Remembered swaps: quiet, because un-teaching is rare and destructive. */
.card.quiet {
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  border-color: transparent;
  box-shadow: none;
}
.swaplist { padding: 4px 17px; }
.swaplist > .muted { margin: 13px 0; font-size: 0.82rem; line-height: 1.5; }
.swap-row { display: flex; align-items: center; gap: 10px; padding: 12px 0; }
.swap-row + .swap-row { box-shadow: inset 0 1px 0 var(--border); }
.swap-row .icon { flex: none; color: var(--accent-press); }
.swap-text { flex: 1; min-width: 0; font-size: 0.85rem; line-height: 1.4; }
.swap-text b { font-weight: 500; }
.swap-forget {
  flex: none;
  padding: 9px 13px;
  border: 0;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.73rem;
  font-weight: 500;
  cursor: pointer;
}
.swap-forget:hover { color: var(--out); }

.rules-pin {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--tabbar-h) + var(--safe-b));
  z-index: 9;
  display: flex;
  padding: 34px 16px 14px;
  background: linear-gradient(to top, var(--bg) 56%, transparent);
  pointer-events: none;
}
.rules-pin button.primary {
  flex: 1;
  width: auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 15px;
  font-size: 0.92rem;
  pointer-events: auto;
}

/* ---------------------------------------------- phone only, ≤639px
   One pane at a time. Capped here so that at 640px and up nothing sets
   `display` on a pane at all, leaving the grid rules below free to. */

@media (max-width: 639px) {
  .settings-wrap > .rulespane { display: none; }
  .settings-wrap.on-rules > .setpane { display: none; }
  .settings-wrap.on-rules > .rulespane { display: block; }
  /* #view-title already reads "Dietary rules" on this screen. */
  .rules-title { display: none; }
  /* Clear the pinned CTA. */
  #view:has(.settings-wrap.on-rules) {
    padding-bottom: calc(var(--tabbar-h) + var(--safe-b) + 100px);
  }
}

/* ---------------------------------------------- 5c merged, ≥640px */

@media (min-width: 640px) {
  .settings-wrap {
    display: grid;
    grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
    gap: 26px;
    align-items: start;
  }
  /* The rules row is a jump, not a trip: the pane it points at is already
     on screen, so the chevron that promised a new screen goes away. */
  #st-rules .icon { display: none; }
  .rulespane > .linkback { display: none; }
  /* Nothing to pin against on a device you are not holding one-handed. */
  .rules-pin {
    position: static;
    margin-top: 14px;
    padding: 0;
    background: none;
  }
  .rules-pin button.primary { flex: none; padding: 13px 22px; }
}

/* ---------------------------------------------- 5d desktop, ≥1024px
   After the ≥640 block so these win — paper.css has no @layer and
   position in the file is the tiebreak. */

@media (min-width: 1024px) {
  .settings-wrap { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 30px; }

  /* Rule cards two-up (5d). The pane's own children become the grid, with
     the headings and the CTA spanning it. */
  .rulespane {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    align-items: start;
  }
  .rulespane > .rules-title,
  .rulespane > .rules-intro,
  .rulespane > .subh,
  .rulespane > .muted,
  .rulespane > .rules-pin { grid-column: 1 / -1; }
  .rulespane > .subh { margin: 6px 0 0; }
  .rulecard, .swaplist { margin-bottom: 0; }

  /* 5d puts Settings at the foot of the rail. The button moves rather than
     being duplicated, so there is still one control with one aria-label.
     #topbar is sticky, not transformed, so it is not a containing block —
     but it IS a stacking context at z-index 10, and a child cannot climb out
     of one. Without lifting the header the rail (also 10, and later in the
     DOM) paints over the button and eats its clicks. Sheets sit at 20. */
  #topbar { z-index: 11; }
  #settings-btn {
    position: fixed;
    left: 18px;
    bottom: calc(30px + var(--safe-b));
    z-index: 11;
    display: flex;
    align-items: center;
    gap: 12px;
    width: calc(var(--rail) - 36px);
    padding: 12px 14px;
    border-radius: 12px;
    color: var(--muted);
    font-size: 0.9rem;
    font-weight: 500;
  }
  #settings-btn::after { content: "Settings"; }
  #settings-btn:hover { color: var(--accent-press); }
  #settings-btn[aria-current="page"] { background: var(--accent-dim); color: var(--accent-press); }
}

@media (prefers-reduced-motion: reduce) {
  .rulecard { animation: none; }
  .setseg:active { transform: none; }
}

/* ------------------------------------------- 3c hero actions, narrow phones

   The row holds three controls; the mockup's phone frame draws two. At 360px
   the CTA got 108px for 118px of `nowrap` content, so it spilled over its own
   rounded edge rather than growing or wrapping.

   `min-width:max-content` + `flex-wrap` make that impossible at any width: the
   CTA can never be sized below its own text, and the row wraps if it must.
   Below 640px "Cooked it" also collapses to a 52px icon square like the mockup,
   label clipped rather than removed so the button keeps its accessible name.

   Last in the file on purpose — position is the tiebreak (rule 17), so the
   `min-width: 0` above has to lose. */

.hero-actions { flex-wrap: wrap; }
.hero-actions button.primary.hero-cook { min-width: max-content; }

@media (max-width: 639px) {
  .hero-cooked { width: 52px; padding: 0; gap: 0; }
  .hero-cooked svg { width: 17px; height: 17px; }
  .hero-cooked .btn-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ============================================================
   Phase 6 — Notes. No mockup frame for this screen; it borrows
   the two component recipes that already carry the meaning it
   needs. The review checkbox is the shopping row's circle (2e)
   because ticking a line to be acted on is the same gesture,
   and the note cards rise in sequence like the rule cards (5b)
   because they arrive as a batch.

   Nothing here is pinned. The list has an end, unlike the
   shopping list, so the one Apply button sits at the foot of
   the batch and sticks to the bottom of the viewport only
   while there is more batch below it.
   ============================================================ */

/* ---------------------------------------------- composer */

.nt-compose { padding-bottom: 14px; }
.nt-compose > .subh { margin: 0 0 9px; font-size: 0.66rem; letter-spacing: 0.12em; }
.nt-compose textarea {
  display: block;
  width: 100%;
  padding: 11px 13px;
  /* 16px exactly: anything smaller and iOS zooms the whole page on focus. */
  font: 400 16px/1.45 var(--sans);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 13px;
  resize: none;
}
.nt-compose textarea::placeholder { color: var(--muted); }
.nt-compose textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: transparent;
}
.nt-compose button.primary { margin-top: 11px; }
/* fridge.css has an `.err`, but only inside a sheet. */
.nt-compose .err {
  margin: 9px 0 0;
  padding: 9px 12px;
  border-radius: 11px;
  background: var(--out-tint);
  color: var(--out-text);
  font-size: 0.84rem;
  line-height: 1.45;
}
.nt-compose .err[hidden] { display: none; }

.nt-note-warn { margin-bottom: 12px; }

/* ---------------------------------------------- the batch */

.nt-review > .subh,
.nt-past > .subh { margin: 18px 0 10px; font-size: 0.66rem; letter-spacing: 0.12em; }

.nt-note { animation: paper-rise 0.5s var(--ease) both; }
.nt-note:nth-of-type(2) { animation-delay: 0.06s; }
.nt-note:nth-of-type(3) { animation-delay: 0.12s; }
.nt-note:nth-of-type(n + 4) { animation-delay: 0.18s; }

/* What she actually typed, quoted back before anything is claimed about it. */
.nt-said {
  margin: 0 0 4px;
  padding-left: 11px;
  border-left: 2px solid var(--accent-dim);
  color: var(--muted);
  font-size: 0.88rem;
  line-height: 1.5;
}

.nt-rows { list-style: none; margin: 0; padding: 0; }
.nt-row {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 11px 0;
}
.nt-row + .nt-row { box-shadow: inset 0 1px 0 var(--border); }

/* Same circle as the shopping list, same hidden native box behind it. */
.nt-pick { position: relative; flex: none; display: flex; padding-top: 1px; }
.nt-pick input[type="checkbox"] {
  position: absolute;
  width: 24px;
  height: 24px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}
.nt-box {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--well);
  color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.18s ease-out, color 0.18s ease-out;
}
.nt-box .icon { stroke-width: 2.4; }
.nt-row.on .nt-box { background: var(--accent); color: #fff; }
.nt-pick input:focus-visible + .nt-box { outline: 2px solid var(--accent); outline-offset: 2px; }

.nt-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.nt-title { font-size: 0.95rem; line-height: 1.35; }
.nt-change { color: var(--muted); font-size: 0.78rem; line-height: 1.4; }
/* Amber, not red: the change is still offered, there is just something she
   should know before ticking it. */
.nt-warn { color: var(--low-text); font-size: 0.78rem; line-height: 1.4; }

.nt-drop {
  flex: none;
  padding: 0 2px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
}
.nt-drop:hover { color: var(--out); }

/* A row the batch cannot answer. It offers the screen that can, so it gets a
   link where the others get a checkbox, and the text lines up with them. */
.nt-hand { padding-left: 37px; }
.nt-go {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 5px 4px 5px 8px;
  color: var(--accent);
  font-size: 0.84rem;
  font-weight: 500;
  text-decoration: none;
}
.nt-go:hover { color: var(--accent-press); }

.nt-note-foot { display: flex; justify-content: flex-end; margin-top: 6px; }
.nt-note-foot .linkbtn { font-size: 0.78rem; }

#nt-apply {
  position: sticky;
  bottom: calc(var(--tabbar-h) + var(--safe-b) + 12px);
  z-index: 8;
  margin-top: 4px;
  box-shadow: var(--shadow-cta);
}
#nt-apply:disabled {
  background: var(--well);
  color: var(--muted);
  box-shadow: none;
  cursor: default;
}

/* ---------------------------------------------- the notes themselves */

.nt-list { list-style: none; margin: 0; padding: 0; }
.nt-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
}
.nt-item + .nt-item { box-shadow: inset 0 1px 0 var(--border); }
.nt-item-text { flex: 1; min-width: 0; font-size: 0.9rem; line-height: 1.45; }
/* Answered notes stay legible but stop competing with the open ones. */
.nt-item.done .nt-item-text,
.nt-item.quiet .nt-item-text { color: var(--muted); }

.nt-pill {
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.nt-pill.waiting { background: var(--accent-dim); color: var(--accent-press); }

/* ---------------------------------------------- tablet, ≥640px */

@media (min-width: 640px) {
  /* One column, capped. A note is a sentence; a 900px sentence is unreadable
     and the checkbox drifts a hand's width from the text it belongs to. */
  .nt-compose,
  .nt-note-warn,
  .nt-queue,
  .nt-review,
  .nt-past { max-width: 680px; }
  .nt-compose { display: block; }
  .nt-compose button.primary { width: auto; padding: 12px 24px; }
  .nt-row { padding: 12px 0; }
}

/* ---------------------------------------------- desktop, ≥1024px
   After the tablet block, per rule 17. */

@media (min-width: 1024px) {
  /* No bottom bar to clear once the tabs become the rail. */
  #nt-apply { bottom: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .nt-note { animation: none; }
  .nt-box { transition: none; }
}

/* ---------------------------------------------- plan tab, night action chips

   Three chips — Swap recipe / Minutes to cook / Not cooking — in one row.
   The base .chip (fridge.css) is nowrap + flex:none, sized to its own text,
   so the row wraps whole chips onto a second line once they stop fitting: the
   third chip drops down alone. Letting the chip's own text wrap instead keeps
   three across at any width, at the cost of the pill reading as a small
   rounded rect instead of a true pill on its two-line label. */
.night-actions { flex-wrap: nowrap; }
.night-actions .chip {
  flex: 1 1 0;
  min-width: 0;
  white-space: normal;
  text-align: center;
  line-height: 1.25;
}

/* ---------------------------------------------- Notes: the device queue

   Appended after the Notes block above rather than folded into it, because
   position is the tiebreak (rule 17) and these have to win over the earlier
   `.nt-item` rules they extend.

   The queue line is drawn whether or not anything is queued. An empty outbox
   she can see is the whole point (PHASE6.md 3.1): a pending count that only
   appears when non-zero is indistinguishable from a pending count that has
   stopped working, and the failure this screen exists to catch is a note
   quietly evicted by iOS. */

.nt-queue {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: -2px 0 12px;
  padding: 0 4px;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.45;
}
.nt-queue.waiting { color: var(--low-text); }
.nt-queue .linkbtn { flex: none; padding: 0 2px; font-size: 0.8rem; color: var(--accent); }
.nt-queue .linkbtn:disabled { color: var(--muted); cursor: default; }

/* Still on the phone. Amber rather than green: it is not a failure, but it is
   not done either, and the tag has to read as different from "Applied". */
.nt-item.unsent .nt-pill {
  background: var(--low-tint);
  color: var(--low-text);
}

/* ============================================================
   Phase 6 step 5 — the shopping list read offline.

   Almost nothing, on purpose. The read-only list is the same rows
   in the same cards; what changes is that the writing controls are
   not rendered at all, which is a shopping.js decision, not a CSS
   one. Disabling them here instead would have left a screen full of
   dead buttons inviting the tap that cannot work.

   The one new element is the "as of" line, and it is the stock amber
   .warnbox — same component the Notes screen uses for the same
   situation. Amber, not clay: she can still do the thing she opened
   this for, so it is a caution, not a failure.
   ============================================================ */

.sh-asof { margin-bottom: 12px; }
/* The timestamp is the whole reason the line exists — a three-day-old
   list is still worth ordering from, and only if she can see that it
   is three days old. 500 rather than mono: it sits mid-sentence, and
   mono here is tracked-out uppercase built for standalone labels. */
.sh-asof time { font-weight: 500; }

/* ============================================================
   Shopping tab count.

   Counts what is still unticked, so it empties out as she shops and
   disappears when the list is done — the number is "left to buy",
   not "on the list".

   Accent, not amber or clay: a full list is the normal state of this
   app, not a warning. It borrows the primary-button pairing (white on
   --accent), which is the one white-on-color combination already
   proven in this file.

   One element, placed twice. On the bottom bar it hangs off the icon's
   top-right corner, anchored to the tab's centre line rather than its
   right edge — tabs are flex:1 and get wider with the screen, so an
   offset from the edge would drift away from the icon. On the desktop
   rail the row runs left-to-right with room to spare, so it stops
   being a corner badge and becomes a count flush to the right.
   ============================================================ */

.tab { position: relative; }

.tab-badge {
  position: absolute;
  top: 5px;
  left: 50%;
  margin-left: 8px;

  display: flex;
  align-items: center;
  justify-content: center;
  /* A single digit lands on the min-width and draws a circle; two spread it
     into a pill. Tight padding matters more than it looks — five tabs share a
     360px bar, and this sits at the right-hand end of the fifth. */
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;

  background: var(--accent);
  color: #fff;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 10.5px;
  line-height: 1;
}

/* ------------------------------------------------ 1a desktop rail, ≥1024px */

@media (min-width: 1024px) {
  .tab-badge {
    position: static;
    /* Pushes it to the end of the row, past the label. */
    margin-left: auto;
    top: auto;
    left: auto;
  }
}

/* ============================================================
   Notifications — the third Settings pane.

   Same construction as the rules pane: always in the DOM, a class
   on the wrapper decides which one a phone shows, and at 640px and
   up nothing is hidden at all. What is different is where it lands
   in the merged grid. Rules is a long two-column list and wants the
   wide side; this is five rows and a button, so it stacks under the
   settings pane in the narrow column and rules spans both rows
   beside them. Auto-placement gets that wrong — it would leave a
   column-height gap under Settings — so both are placed explicitly.

   Nothing new is drawn here. The state block is a warnbox, the
   toggles are the segmented control from the adventurousness row,
   the buttons are primary and quiet-danger. Notifications did not
   need a component; it needed a screen.
   ============================================================ */

/* Matches .rules-title / .rules-intro rather than reusing them: the two panes
   read identically by design, but a selector named for dietary rules doing the
   styling on this one is how a stylesheet stops being legible. */
.nf-title {
  margin: 0 0 8px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.55rem;
  letter-spacing: -0.01em;
}
.nf-intro { margin: 0 0 14px; color: var(--muted); font-size: 0.84rem; line-height: 1.55; text-wrap: pretty; }

.nf-state { padding: 16px 18px; }
/* A warnbox brings its own tint and radius, so the card underneath it would be
   a second surface behind the first. Anything else in here is text and buttons
   on nothing, and does want the card. */
.nf-state:has(.warnbox) { padding: 0; background: none; box-shadow: none; }
.nf-state .warnbox { line-height: 1.5; }

.nf-say {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 14px;
  font-size: 0.92rem;
  line-height: 1.4;
}
.nf-say .pill { flex: none; }

.nf-acts { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.nf-acts button.primary { width: 100%; }
/* Turning notifications off is quiet and never a red button, but it still has
   to be a real 44px target rather than a line of text to aim at. */
.nf-acts .linkbtn { padding: 14px 2px; font-size: 0.82rem; }

.nf-toggles { padding: 4px 18px; }
/* "off / on" fits beside its label at 390px, unlike the three-segment
   adventurousness track that .settrack was drawn for — so it stops being a
   full-width line of its own and sits in the row. */
.nf-track {
  width: auto;
  flex: none;
  margin-top: 0;
}
/* 14px rather than the 12px the adventurousness track uses: three words across
   a full-width row clear 44px on their own, two segments 46px wide do not. */
.nf-track .setseg { flex: none; min-width: 46px; padding: 14px 10px; }
.nf-none { margin: 10px 2px 0; font-size: 0.82rem; line-height: 1.5; }

/* ------------------------------------------------ 3a phone only, ≤639px */

@media (max-width: 639px) {
  .settings-wrap > .notifpane { display: none; }
  .settings-wrap.on-notify > .setpane { display: none; }
  .settings-wrap.on-notify > .notifpane { display: block; }
  /* #view-title already reads "Notifications" on this screen. */
  .nf-title { display: none; }
}

/* ------------------------------------------------ 3b merged, ≥640px */

@media (min-width: 640px) {
  /* Explicit placement, not auto-flow: the panes are three children of a
     two-column grid, and left to itself the browser puts this one on a second
     row whose top edge is the bottom of the rules pane — a screen-high gap
     under Settings with nothing in it. */
  .settings-wrap > .rulespane { grid-column: 2; grid-row: 1 / span 2; }
  .settings-wrap > .notifpane { grid-column: 1; grid-row: 2; margin-top: 22px; }
  /* Same as the rules row: the pane it points at is already on screen, so the
     chevron that promised a new screen goes away. */
  #st-notify .icon { display: none; }
  .notifpane > .linkback { display: none; }
  /* Side by side, but wrapping: the pane lives in the narrow column, and at
     640-1023px the two of them together are wider than it. Without the wrap
     the link stays on the row and breaks into two cramped lines beside a
     one-line button (CLAUDE.md rule 21, same shape as Today's CTA). */
  .nf-acts { flex-direction: row; align-items: center; flex-wrap: wrap; gap: 14px; }
  .nf-acts button.primary { width: auto; flex: none; padding: 13px 22px; }
  .nf-acts .linkbtn { min-width: max-content; }
}

/* ------------------------------------------------ 3c desktop, ≥1024px
   After the ≥640 block so these win — position in this file is the
   tiebreak and there is no @layer. */

@media (min-width: 1024px) {
  /* The rules pane goes two-up here, which makes the right column taller
     still; the placement above already accounts for it. Nothing to change
     but the breathing room, which follows the wider gutter. */
  .settings-wrap > .notifpane { margin-top: 26px; }
}

/* ============================================================
   Phase 7 — the three-star rating.
   Appended, per rule 17: position in this file is the tiebreak,
   so this wins over anything above it and nothing above needed
   editing. Replaces the two thumb buttons in the Today sheet,
   the recipe detail sheet and the end of cook mode.
   ============================================================ */

.stars {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

/* 44px of tap target around a 26px glyph (style guide §1). The padding is the
   target, not the star: three stars 2mm apart is a control that gets the wrong
   answer from a thumb, and the wrong answer here quietly steers next week's
   dinners. */
.starbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.starbtn.on { color: var(--accent); }
.starbtn .icon { pointer-events: none; }

/* The one motion (style guide §2): 180ms, and the press is the same scale(0.97)
   every other button in the app uses. The colour crossfade rides the same
   transition, so filling three stars reads as one gesture rather than three. */
.starbtn {
  transition: color 0.18s ease-out, transform 0.18s ease-out;
}
.starbtn:active { transform: scale(0.97); }

/* The sheet's row: centred, and with the thumbs' old vertical rhythm so
   nothing else in the sheet moves. */
.rate-row {
  display: flex;
  justify-content: center;
  margin: 4px 0 2px;
}

/* On a recipe card the stars are a badge, not a control: smaller, inert, and
   never a tap target that competes with opening the recipe. */
.rcard-stars {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  color: var(--accent);
  vertical-align: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .starbtn { transition: none; }
  .starbtn:active { transform: none; }
}

/* ---------------------------------------------------------- quick meal
 *
 * Phase 7. The Recipes controls row becomes search + one toggle: what can
 * this kitchen cook right now, grouped by how much it is short.
 *
 * Recipes reuses the Fridge's `.fridge-search` > `.search-wrap` pair rather
 * than growing a second search field: that one is already the styled 44px
 * field the guide asks for, and the Recipes box had never been styled at all
 * (a bare 21px browser input, which only looked passable because nothing sat
 * next to it). `.fridge-controls` itself is shared by both screens and is
 * deliberately left alone -- laying it out here would have moved the Fridge. */

/* Secondary button geometry from the component recipes, stretched to match the
   field beside it. `min-width:max-content` is rule 21: without it the label
   spills out of the corner instead of holding the button open. */
.qm-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  min-width: max-content;
  padding: 0 14px;
  border: none;
  border-radius: 15px;
  background: var(--well);
  color: var(--text);
  box-shadow: var(--shadow-1);
  font: 500 14px var(--sans);
  cursor: pointer;
  transition: background .18s ease-out, color .18s ease-out, box-shadow .18s ease-out;
}
.qm-btn:hover { color: var(--accent-press); }
.qm-btn:active { transform: scale(0.97); }
.qm-btn.on {
  background: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-cta);
}
.qm-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-dim), var(--shadow-1);
}

/* Same treatment `#inv-search` gets: the wrapper is the field, the input is
   just the text inside it. Without this the browser's own box sits inside the
   wrapper and you see a field within a field. */
.search-wrap #r-search {
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  min-width: 0;
}
/* Search is disabled while quick meal is on -- the two answer different
   questions and stacking them reads as a bug. Say so visually. */
.search-wrap #r-search:disabled { opacity: 0.5; }

/* Section label, straight from the style guide: mono, uppercase, tracked, no
   divider. First one loses its top margin so the list starts where the plain
   library starts. */
.qm-label {
  margin: 18px 0 8px;
  font: 500 10.5px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.qm-label:first-child { margin-top: 4px; }

/* What a recipe is short. Clay on its own tint, never on paper (§1 contrast). */
.rcard-short {
  display: inline-block;
  margin-top: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--out-tint);
  color: var(--out-text);
  font: 400 12.5px var(--sans);
}

@media (prefers-reduced-motion: reduce) {
  .qm-btn { transition: none; }
  .qm-btn:active { transform: none; }
}

/* ============================================================
   Phase 7 — Claude usage (SPEC §13.4).
   A fourth Settings pane, built like the notifications one:
   always in the DOM, shown on a phone by .on-usage, and just
   another column once the panes merge at 640px.

   Appended per rule 17, so nothing above needed editing.
   ============================================================ */

/* The pane's own title. On a phone #view-title already reads "Claude usage",
   so this is for the merged widths where the topbar says Settings. */
.usage-title {
  margin: 0 0 12px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.32rem;
  letter-spacing: -0.01em;
}

/* ---------------------------------------------- this month against the cap */

/* The focal card of this pane, and the only --shadow-2 on the screen: the
   number that can stop the app working outranks the history below it. */
.usage-cap {
  padding: 16px 18px 18px;
  border-radius: 22px;
  box-shadow: var(--shadow-2);
}
.usage-caplabel {
  margin: 0 0 8px;
  font: 500 0.66rem var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Money is data, so it is mono (style guide §5), and the cap beside it is the
   quiet half of the sentence. */
/* The one place mono drops its tracking: 0.1em is tuned for 10px uppercase
   labels, and at 1.5rem it spaces "$1.50" out into four separate things. */
.usage-figure {
  margin: 0 0 12px;
  font: 500 1.5rem var(--mono);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.usage-figure small {
  font: 400 0.9rem var(--sans);
  letter-spacing: 0;
  color: var(--muted);
}
/* The progress-bar recipe: 6px, 999px, --well track, --accent fill, animated
   once per visit with `fill` -- reusing paper-fillw from the shopping bar. */
.usage-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
}
.usage-bar i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  animation: paper-fillw 0.7s 0.2s var(--ease) both;
}
.usage-note {
  margin: 10px 0 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
}

/* ---------------------------------------------- the months */

.usage-label {
  margin: 20px 0 8px;
  font: 500 10.5px var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.usage-chart { padding: 14px 16px 12px; }

/* Label, bar, cost. The cost column is fixed so five months of figures line up
   under each other rather than dancing with the length of the number. */
.umonth {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 62px;
  align-items: center;
  gap: 10px;
  min-height: 30px;
}
.um-when {
  font: 500 10.5px var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.um-track {
  display: flex;
  height: 12px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
}
/* One green, five strengths of it. A segment is never thinner than 2px: a
   fraction of a cent beside a dollar still has to be visible as something. */
.um-track i {
  display: block;
  height: 100%;
  min-width: 2px;
  background: var(--accent);
}
.um-track i + i { margin-left: 1px; }
.s0 { opacity: 1; }
.s1 { opacity: 0.74; }
.s2 { opacity: 0.52; }
.s3 { opacity: 0.34; }
.s4 { opacity: 0.2; }

.um-cost {
  font: 500 11px var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
}
.um-cost.none { color: var(--muted); font-weight: 400; }

/* ---------------------------------------------- legend and the feature list */

.usage-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.uleg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--muted);
}
.uswatch {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--accent);
}

.usage-features { padding: 4px 16px 14px; }
.ufeat {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}
.ufeat + .ufeat { border-top: 1px solid var(--border); }
.uf-name { font-size: 0.94rem; }
.uf-calls {
  font: 400 11px var(--mono);
  letter-spacing: 0.06em;
  color: var(--muted);
}
.uf-cost {
  min-width: 58px;
  font: 500 12.5px var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.usage-features .usage-note { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }

/* ---------------------------------------------- phone only, ≤639px */

@media (max-width: 639px) {
  .settings-wrap > .usagepane { display: none; }
  .settings-wrap.on-usage > .setpane { display: none; }
  .settings-wrap.on-usage > .usagepane { display: block; }
  /* #view-title already reads "Claude usage" on this screen. */
  .usage-title { display: none; }
}

/* ---------------------------------------------- merged, ≥640px */

@media (min-width: 640px) {
  /* Explicit placement for the same reason the notifications pane needs it:
     auto-flow drops this under the full height of the rules column.

     Column 1, under notifications, and not the wide column beside it -- which
     was tried, because bars are the one thing here that gets better with
     width. A grid row starts below the tallest thing in the row above, and
     column 1 is the tall one, so anything placed at 2/3 lands a screen and a
     half under the rules card with nothing in between. A narrower chart beats
     a chart nobody scrolls to. */
  .settings-wrap > .usagepane { grid-column: 1; grid-row: 3; margin-top: 22px; }
  /* The pane is already on screen, so the chevron that promised one goes. */
  #st-usage .icon { display: none; }
  .usagepane > .linkback { display: none; }
}

/* ---------------------------------------------- desktop, ≥1024px
   After the ≥640 block so these win — position is the tiebreak. */

@media (min-width: 1024px) {
  .settings-wrap > .usagepane { margin-top: 26px; }
  /* Room enough to spell the month out and let the bars run longer. */
  .umonth { grid-template-columns: 74px minmax(0, 1fr) 70px; }
}

@media (prefers-reduced-motion: reduce) {
  .usage-bar i { animation: none; }
}

/* ---------------------------------------------- recipe: household rule flag
 *
 * Reuses .warnbox and its two tints (amber preference, clay allergy) rather
 * than growing a third box: this says the same kind of thing the shopping
 * list's warnings say, in the same voice, and should look like it. Only the
 * list geometry inside it is new. */
.rhits {
  align-items: flex-start;
  margin: 0 0 14px;
}
.rhits ul {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.88rem;
  line-height: 1.5;
}
.rhits li + li { margin-top: 4px; }
.rhits b { font-weight: 500; }

/* ---------------------------------------------- counts and serving counts
 *
 * QUANTITIES.md. Two controls, and they are deliberately quiet: both are
 * optional, both are blank in the common case, and neither may look like
 * something that wants answering. The count is the "+" on a fridge row that
 * becomes the number once there is one; "Serves N" is a night chip that sits
 * with the other night actions and reads as a fact until it is changed.
 *
 * Nothing here animates, so there is no reduced-motion block to extend. */

.inv-controls {
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
}

.qty {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  padding: 0 10px;
  border: 0;
  border-radius: 13px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
  color: var(--muted);
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: background 180ms ease-out, color 180ms ease-out, box-shadow 180ms ease-out;
}
.qty:hover { color: var(--text); }
.qty:active { transform: scale(0.97); }
/* A counted item states its number in the accent's tint: it is data, not a
   a warning. Uncounted stays a muted "+" that does not ask for attention. */
.qty.set {
  background: var(--accent-dim);
  color: var(--accent-press);
}

/* Quick numbers in a sheet. The chip styling is already right; these only need
   to wrap rather than scroll, since a sheet has no horizontal room to spare. */
.qty-chips, .serve-chips { flex-wrap: wrap; overflow: visible; }

/* The night actions are a nowrap row of equal columns, so a fourth chip would
   squeeze the three that were already there. This one is a short fact rather
   than an instruction, so it takes only the width its own words need and the
   other three keep the rest. */
.night-actions .chip[data-serves] { flex: 0 0 auto; white-space: nowrap; }

.pack-fields { display: flex; gap: 10px; }
.pack-fields label { flex: 1; min-width: 0; }

/* Tablet and desktop stand each row up as a tile (the .inv-row column flow
   above), where the count belongs beside the tri-state rather than above it.
   One block for both widths: they want the same thing, and appending it after
   both means it wins either way (rule 17). */
@media (min-width: 640px) {
  .inv-row .inv-controls { margin-top: 10px; }
  .inv-row .inv-controls > .seg-group { margin-top: 0; flex: 1; }
}

/* An email field in a sheet. `fridge.css` styles sheet fields by naming three
   input types -- text, search, and no type at all -- so `type="email"` arrives
   as a raw browser control: a 177px box 21px tall in the middle of a column of
   proper fields. It measures fine and looks wrong, which is why a screenshot
   caught it and nothing else did.

   Declared to match that older block exactly rather than to the form-field
   recipe in the style guide, because the point is that this field is
   indistinguishable from the ones beside it.

   `number` joined it on 13 Aug. It was held back at first -- restyling it
   would have touched the Household sheet and the setup interview, whose look
   Ben had signed off -- until the recipe review form became the *edit* screen
   and its Serves and Minutes boxes started being seen daily. Ben then asked
   for Settings to match. So: every field type this app puts in a sheet is
   styled here or in `fridge.css`, and there is no longer a type that falls
   through to the browser. If a new one appears, add it. */
.sheet input[type="email"],
.sheet input[type="number"] {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 11px 12px;
  font-size: 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
}

/* ---------------------------------------------- kitchen measures
 *
 * The same amount in cups and spoons, beside a recipe written in millilitres
 * or grams (QUANTITIES.md §3, `recipes.js:kitchenHint`). It sits between the
 * measure and the ingredient, quieter than both: the recipe as written is
 * still the thing being read, and this is the help. Muted rather than smaller
 * on the ingredient row, which is already at body size and has nowhere to go.
 *
 * Cook mode is read from across a kitchen, so there it only drops in weight,
 * not in size. Nothing here animates. */
.ing-alt { color: var(--muted); font-weight: 400; }
.cook-ing .ing-alt { color: var(--muted); }

/* ---------------------------------------------- long press a recipe card
 *
 * The card is a press target now, not just a link (recipes.js, the press
 * block). iOS answers a long press with the text-selection callout unless it
 * is told not to -- the same pair of declarations `.inv-row` carries, and for
 * the same reason. */
.rcard {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}


/* ---------------------------------------------- library sort bar
 *
 * One line between the search row and the first card: how many recipes are on
 * screen, and how they are ordered. It replaces a chip row that would have
 * cost a whole band of the phone screen for a control used a few times a
 * month, so it has to stay a line -- if anything is ever added here, it goes
 * in the sheet, not beside these two.
 *
 * The count is mono at the zone-count size (§the fridge zone heads), because
 * it is the same kind of fact and reading them differently on two screens is
 * how a house style dies. The sort side is a button that does not look like
 * one until it is touched: on paper, a text label plus the caret is enough of
 * an affordance, and a bordered pill here would out-shout the cards. */
.sortbar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 2px 2px 8px;
}
.sortbar #r-count {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.75rem;
  color: var(--muted);
}
.sortbtn {
  /* 40px of touch target from padding alone would push the cards down, so the
     height comes from a negative margin instead: the tap area overlaps the gap
     above and below rather than claiming it. */
  margin: -11px -6px;
  padding: 11px 6px;
  border: 0;
  background: none;
  color: var(--muted);
  font: 500 0.8rem var(--sans);
  cursor: pointer;
}
.sortbtn:active { color: var(--text); }
/* The card chevron turned to point down, rather than a ⌄ from whatever font
   has one: the dedicated glyphs sit low and light enough to read as a typo,
   and this is already the shape that means "there is more here" on this screen.
   Full strength, not a whisper -- the label alone reads as a status line, and
   the chevron is the only thing on the bar saying it can be changed. */
.sortbtn .caret {
  display: inline-block;
  margin-left: 6px;
  font-size: 1.05rem;
  line-height: 0;
  vertical-align: -1px;
  transform: rotate(90deg);
}

/* The current choice in the sort sheet. Accent on the label and a full border
   rather than a tick glyph -- the sheet holds three rows, and at three the
   selected one should be visible before it is read. */
.sortopt.on {
  border-color: var(--accent);
  background: var(--accent-dim);
}
.sortopt.on b { color: var(--accent); }

/* ---------------------------------------------- duplicate at import
 *
 * The recipe she already has, shown at the moment the app declines to read the
 * same card twice (services/dupes.py, recipes.js:duplicateSheet). It is a
 * statement, not a control: no chevron, no tap target of its own, because the
 * answers are the buttons underneath it. The picture is the point -- she
 * recognises her own card faster than she reads its title. */
.dup-card {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 2px 0 14px;
  padding: 12px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}
.dup-card b { display: block; font-size: 0.97rem; margin-bottom: 3px; }
.dup-card small { display: block; color: var(--muted); line-height: 1.35; }
.dup-thumb {
  width: 62px;
  height: 62px;
  flex: none;
  object-fit: cover;
  border-radius: 10px;
}

/* --- plan: long-press drag to trade nights, and the library picker (v1.2) */
/* The gesture is fridge.js's long press wearing plan clothes; rule 20 applies:
   callout and text selection off, and plan.js eats the click that follows. */
.night {
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
}
.night.dragging {
  pointer-events: none;
  position: relative;
  z-index: 5;
  box-shadow: var(--shadow-2);
  opacity: 0.95;
}
.night.drop-target {
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-1);
}
.pick-search {
  width: 100%;
  margin: 2px 0 10px;
}

/* --- shopping: the "should be in stock" double-check section (v1.2) */
/* The rows are ordinary shop-rows arriving pre-checked; only the hint under
   the section label is new. 12.5px muted meta text per the style guide. */
.shop-stock-hint {
  margin: -2px 0 8px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* --- settings: the cook timer alarm sheet (v1.2) */
/* The one range input in the app. fridge.css styles sheet inputs BY TYPE
   (style guide, "Text field in a sheet"), so type=range arrives unstyled;
   accent-color is enough to make the native control wear the house green. */
.al-volrow {
  display: flex;
  align-items: center;
  gap: 12px;
}
.al-volrow input[type="range"] {
  flex: 1;
  min-height: 44px;
  accent-color: var(--accent);
}
.al-volval {
  flex: none;
  min-width: 44px;
  text-align: right;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--muted);
}
#al-test {
  margin: 2px 0 10px;
}

/* ============================================================
   Soft mould (2026-09-03) - MealPlanner inherits Paladin's Soft UI.

   Paladin cannot push a theme into a module (MODULES.md: a module never
   learns its mount, and nothing is injected into it), so "inherit" is this
   append: the elevation language changes, nothing else does. Every colour,
   the type system and the amber/clay status tints carry over untouched.
   Handoff: Paladin\ClaudeDesignFiles\design_handoff_paladin_soft_ui\README.md
   ("Modules - inheriting the look"), frame 3b.

   The recipe, token for token:
     --surface  folds into --bg   (cards mould OUT of the paper instead of
                                   sitting on a whiter card)
     --shadow-1 -> the raised pair (--up)
     --shadow-2 -> --up-big        (still the ONE hero per screen)
     --well     -> the pressed pair (--in / --in-sm), so every well below
                   gains an inset shadow; the colour stays the paper's
     --shadow-cta -> --acc-up      (the green primary button, raised)
   Radii step up a few px: primary 15->16, icon buttons 13->14. No borders,
   no new hex literals - the shadows are new custom properties and the
   surface is the paper we already have. Rollback: delete this block.
   ============================================================ */

:root {
  --up:     7px 7px 16px rgba(90, 80, 60, 0.16), -7px -7px 16px rgba(255, 255, 252, 0.95);
  --up-sm:  4px 4px 10px rgba(90, 80, 60, 0.14), -4px -4px 10px rgba(255, 255, 252, 0.95);
  --up-big: 9px 9px 22px rgba(90, 80, 60, 0.20), -9px -9px 22px rgba(255, 255, 252, 1);
  --in:     inset 4px 4px 10px rgba(90, 80, 60, 0.14), inset -4px -4px 10px rgba(255, 255, 252, 0.95);
  --in-sm:  inset 2px 2px 6px rgba(90, 80, 60, 0.16), inset -2px -2px 6px rgba(255, 255, 252, 0.95);
  --acc-up: 6px 6px 14px rgba(28, 116, 88, 0.30), -4px -4px 12px rgba(255, 255, 252, 0.7);

  /* The fold. Everything that painted --surface now paints the paper and
     gets its depth from the shadow beside it. */
  --surface:    var(--bg);
  --well:       var(--bg);
  --shadow-1:   var(--up);
  --shadow-2:   var(--up-big);
  --shadow-cta: var(--acc-up);
}

/* Wells: the colour alone no longer says "pressed", so the shadow does. The
   segment tracks and the ingredient-check rows take the small pair; the
   progress tracks stay thin and take it too. */
.seg-group, .scaler-track, .settrack { box-shadow: var(--in-sm); border-radius: 13px; }
.ck { box-shadow: var(--in-sm); border-radius: 14px; }
.shop-bar, .usage-bar, .um-track, .zone-bar { box-shadow: var(--in-sm); }
.shop-box, .nt-box { box-shadow: var(--in-sm); }
.shop-row.done .shop-box, .nt-row.done .nt-box { box-shadow: none; }
.effect, .swap-forget, .nt-pill { box-shadow: var(--in-sm); }
.nt-pill.waiting { box-shadow: none; }
#nt-apply:disabled { box-shadow: var(--in-sm); }

/* Secondary buttons were flat --well fills; in the mould they are RAISED
   (a button is something you press, so it stands proud until you do). */
.sbtn, .ratebtn, .hwbtn, .row-btn, .hero-open, .hero-cooked, .r-iconbtn {
  box-shadow: var(--up-sm);
  border-radius: 14px;
}
.sbtn:active, .ratebtn:active, .hwbtn:active, .row-btn:active,
.hero-open:active, .hero-cooked:active, .r-iconbtn:active { box-shadow: var(--in-sm); }
.sbtn.on { box-shadow: var(--acc-up); }
.hero-open:hover, .hero-cooked:hover, .r-iconbtn:hover { background: var(--bg); }
.scaler-track .sbtn { box-shadow: none; }
.scaler-track .sbtn.on { box-shadow: var(--acc-up); }

/* Chips, small fields and rows take the small raised pair rather than the
   card's - a chip with a 16px shadow shouts. */
.chip, #inv-search, .search-wrap, .icon-sq, .pin-icon, .linkback, .dup-card,
.qm-btn, .inv-row, .swapcard, .ichoice, .rcard { box-shadow: var(--up-sm); }
.chip.on { box-shadow: none; }
.qm-btn.on { box-shadow: var(--acc-up); }
.qm-btn:focus-visible { box-shadow: 0 0 0 2px var(--accent-dim), var(--up-sm); }
.icon-sq.on { box-shadow: var(--acc-up); }
.night.drop-target { box-shadow: 0 0 0 2px var(--accent), var(--up-sm); }

/* Radii step up, as the handoff asks: primary 15->16, icon buttons 13->14. */
button.primary { border-radius: 16px; }
.fab { border-radius: 16px; box-shadow: var(--up-sm); }
.fab.primary-fab { box-shadow: var(--acc-up); }
.icon-sq, .pin-icon, #inv-search { border-radius: 16px; }
.sortbtn { border-radius: 14px; }

/* The status stripes compose the raised pair instead of the old flat one. */
.night.is-today { box-shadow: inset 3px 0 0 var(--accent), var(--up-big); }
.card.night:has(.night-hit.hard) { box-shadow: inset 3px 0 0 var(--out), var(--up); }
.rulecard.hard { box-shadow: inset 3px 0 0 var(--out), var(--up); }
.rulecard.soft { box-shadow: inset 3px 0 0 var(--low), var(--up); }

/* The tab bar is pressed into the paper (frame 3b), not floated on glass. */
#tabbar {
  background: var(--bg);
  box-shadow: var(--in);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.tab[aria-selected="true"] .glyph { border-radius: 12px; }

/* The quiet card was --surface at 70%; on the paper it is simply flat. */
.card.quiet { background: var(--bg); }
.cook-panel { background: var(--bg); }

/* Status chips (amber, clay, eucalyptus tints) stay flat pill fills: they are
   tags, not surfaces, and are not moulded. Nothing to do here - said so the
   next reader does not "fix" it. */

/* --- cook mode: the Next step button at desktop widths (2026-09-03) */
/* style.css gives every button.primary width:100%, and recipes.css makes
   .cook-next `flex: none` from 1024px up, so the button was 100% of the nav
   PLUS the back button and the gap: its right edge ran off the screen on a
   tablet in landscape and on desktop. Let it flex again and the row adds up. */
@media (min-width: 1024px) {
  .cook-next { flex: 1 1 auto; width: auto; min-width: 340px; }
}
