/* Amrit — Stylesheet
   Aesthetic: warm earthy tones, refined typography, Ayurvedic warmth
   Fonts: Cormorant Garamond (display) + DM Sans (body)
*/

:root {
    /* Saffron darkened #E8890C → #A85000 for WCAG-AA contrast (story #105):
       as text on white it now measures 5.5:1 (was 2.6:1), and white text on
       saffron fills (buttons, nav badge, chips) passes too. --saffron-lt stays
       light — it's only used on the dark header (light-on-dark) now. */
    --saffron:     #A85000;
    --saffron-lt:  #F5A832;
    --terra:       #C2522A;
    --terra-lt:    #E8765A;
    --cream:       #FAF6EE;
    --parchment:   #F0E9D8;
    --warm-brown:  #4A2C1A;
    --mid-brown:   #7A4F35;
    --soft-brown:  #A67C5B;
    --text:        #2C1A0E;
    --text-mid:    #5C3D28;
    --text-light:  #9C7A5A;
    --border:      #DDD0BC;
    --border-lt:   #EDE4D4;
    --success:     #3A7A4A;
    --error:       #A02020;
    --ai-color:    #5A4A8A;
    --shadow:      0 2px 12px rgba(74,44,26,0.10);
    --shadow-lg:   0 8px 32px rgba(74,44,26,0.15);
    --nav-h:       56px;  /* nav height; sync with .nav { height } and JS reads via getComputedStyle */

    /* State surfaces — recurring pale backgrounds/borders tokenized so the
       success/error/partial/AI families change in one place (story #133).
       Values are byte-identical to the literals they replaced. */
    --bg-success:     #EBF5EE;
    --border-success: #B8DFBF;
    --bg-error:       #FAEAEA;
    --border-error:   #E8BABA;
    --bg-partial:     #FFF8EE;
    --border-partial: #F5D8A0;
    --bg-ai:          #EEE8F5;

    /* Status-pill palette — one pale-pill language shared by the .md-badge
       status table and the .season-tag / .allergen-pill chips (story #133).
       Coupled deliberately: they are meant to read alike. Split only if a
       real need to diverge appears. */
    --pill-ok-bg:   #E8F5EA;  --pill-ok-fg:   #2E6B36;
    --pill-warn-bg: #FBE8E4;  --pill-warn-fg: #8A3A2A;
    --pill-mute-bg: #F0F0F0;  --pill-mute-fg: #555;

    /* Calendar meal-tile fills — own tokens (values match the --border-*
       tokens today) so darkening a border later can't silently change tile
       fills. Avoids the border-as-background dual-use trap (story #133). */
    --bg-meal-complete: #B8DFBF;
    --bg-meal-started:  #F5D8A0;

    /* Progress-bar track. */
    --bg-track: #EEE;

    /* Spacing scale (rem) — reuse a step; don't invent a new value. The
       existing rhythm already clusters on these; bespoke one-offs stay literal. */
    --space-2xs: 0.25rem;
    --space-xs:  0.5rem;
    --space-sm:  0.75rem;
    --space-md:  1rem;
    --space-lg:  1.5rem;
    --space-xl:  2rem;
    --space-2xl: 2.5rem;

    /* Type scale (rem). Keep the absolute 16px on form inputs (iOS-zoom guard,
       see the html{} comment) — it is deliberately NOT a token. */
    --text-2xs:  0.7rem;
    --text-xs:   0.75rem;
    --text-sm:   0.85rem;
    --text-base: 0.9rem;
    --text-md:   1rem;
    --text-lg:   1.25rem;
    --text-xl:   1.4rem;
    --text-2xl:  2rem;
}

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

/* Root font-size of 16px sets `1rem = 16px`. This is both the
   conventional web minimum for body text at iPad viewing distance
   AND the minimum below which iOS Safari auto-zooms on focused form
   inputs. Form-input selectors below use `font-size: 16px` absolute
   (rather than a `rem` value that could regress if this root changes)
   as belt-and-braces against that auto-zoom. See the iPad / touch
   optimization MUST story in BACKLOG.md. */
html { font-size: 16px; }

body {
    font-family: 'DM Sans', sans-serif;
    background: var(--cream);
    color: var(--text);
    line-height: 1.6;
    min-height: 100vh;
}

/* ── ACCESSIBILITY ─────────────────────────────────
   Global keyboard-focus ring. Only shows for keyboard users (:focus-visible,
   not :focus), so mouse clicks stay clean. Individual components may set a
   tighter ring (e.g. .help-icon below); those win by specificity. */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.filter-chip:focus-visible {
    outline: 2px solid var(--saffron);
    outline-offset: 2px;
}

/* ── NAV ─────────────────────────────────────────── */
/* The <header> landmark is the sticky container so the nav stays pinned
   across the full page scroll. (A bare wrapper would cap stickiness at the
   nav's own 56px height — its containing block. .nav keeps its own sticky
   for safety and as a positioning context for any absolute children.) */
.site-header { position: sticky; top: 0; z-index: 100; }
.nav {
    background: var(--warm-brown);
    padding: 0 2rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--nav-h);
    position: sticky;
    top: 0;
    z-index: 100;
    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.nav-brand {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.4rem;
    color: var(--saffron);
    text-decoration: none;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.nav-brand span { color: var(--parchment); font-weight: 400; margin-left: 0.4rem; }

.nav-links {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}
.nav-links a {
    color: var(--parchment);
    text-decoration: none;
    font-size: 0.9rem;
    opacity: 0.8;
    transition: opacity 0.15s;
}
.nav-links a:hover, .nav-links a.active { opacity: 1; color: var(--saffron-lt); }
.nav-user { color: var(--soft-brown); font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem; }
.nav-role-badge {
    background: var(--saffron);
    color: #fff;  /* white on the darkened saffron = 5.5:1 (story #105) */
    font-size: 0.7rem;
    font-weight: 600;
    padding: 0.1rem 0.45rem;
    border-radius: 0.25rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.nav-logout { color: var(--terra-lt) !important; }

/* Phones: the desktop single-row flex nav crams + overlaps on a narrow screen
   (a fixed 56px height holding the brand + 4 links + user + sign-out). Let it
   wrap cleanly — brand on the first row, links on their own row(s) below, at
   auto height. Still sticky (top: 0); only its height grows. */
@media (max-width: 640px) {
    .nav {
        height: auto;
        flex-wrap: wrap;
        padding: 0.5rem 1rem;
        row-gap: 0.4rem;
    }
    .nav-brand { font-size: 1.2rem; }
    .nav-links { width: 100%; flex-wrap: wrap; gap: 0.5rem 1rem; }
}

/* ── MAIN ─────────────────────────────────────────── */
.main {
    max-width: 1100px;
    margin: 0 auto;
    padding: 2rem 1.5rem 4rem;
}

/* ── FLASH MESSAGES ───────────────────────────────── */
.flash-container { margin-bottom: 1.5rem; }
.flash {
    padding: 0.75rem 1rem;
    border-radius: 6px;
    margin-bottom: 0.5rem;
    font-size: 0.9rem;
}
.flash-success { background: var(--bg-success); color: var(--success); border: 1px solid var(--border-success); }
.flash-error   { background: var(--bg-error); color: var(--error);   border: 1px solid var(--border-error); }
/* Story #140: non-blocking heads-up (e.g. yield vs servings × portion size). */
.flash-warning { background: rgba(192, 86, 33, 0.08); color: var(--terra); border: 1px solid var(--terra); }

/* ── SEASONAL REPETITION PROMPT (story #106) ──────── */
.repeat-banner {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    margin: 1rem 0; padding: 0.85rem 1.1rem; border-radius: 8px;
}
.repeat-banner-text { flex: 1 1 18rem; line-height: 1.45; }
.repeat-banner-fill    { background: #FBEFE3; color: #5A3210; border: 1px solid #E8B98A; }
.repeat-banner-refresh { background: #F4F1EC; color: #5C5346; border: 1px solid #D9D2C6; }

/* ── PAGE HEADER ──────────────────────────────────── */
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: 2rem;
    gap: 1rem;
    flex-wrap: wrap;
}
.page-title {
    font-family: 'Cormorant Garamond', serif;
    font-size: 2rem;
    font-weight: 600;
    color: var(--warm-brown);
    line-height: 1.2;
}
/* Compact header for the dense recipe-edit form (streamline 2026-07-02):
   trims the generous 2rem header gap + title so the "top of the page" costs
   less vertical space (operator: it "was taking up far too much real
   estate"). Scoped by an extra class so every other page's header is
   untouched. */
.page-header.page-header-compact { margin-bottom: var(--space-md); }
.page-header-compact .page-title { font-size: 1.5rem; }
/* Screen-reader-only text: gives a section an accessible name / heading with
   zero visual footprint. Used to keep the recipe-edit steps' heading order
   (h1 → h2 → h3) intact after the visible step-headings were dropped for
   density (streamline 2026-07-02). Standard clip pattern. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.page-sub {
    color: var(--text-light);
    font-size: 0.9rem;
    margin-top: 0.25rem;
}
.back-link {
    color: var(--saffron);
    text-decoration: none;
    font-size: 0.85rem;
    display: block;
    margin-bottom: 0.25rem;
}
.back-link:hover { text-decoration: underline; }
.header-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.stats-block {
    display: flex;
    flex-direction: column;
}
.stats-heading {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-mid);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    text-align: center;
    margin-bottom: 0.5rem;
}
.header-stats {
    display: flex;
    gap: 1.5rem;
}
.stat { text-align: center; }
.stat-num {
    display: block;
    font-family: 'Cormorant Garamond', serif;
    font-size: 2rem;
    font-weight: 600;
    color: var(--saffron);
    line-height: 1;
}
.stat-label { font-size: 0.75rem; color: var(--text-light); text-transform: uppercase; letter-spacing: 0.05em; }
.stat-empty .stat-num { color: var(--terra); }

/* ── BUTTONS ──────────────────────────────────────── */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 1.1rem;
    border-radius: 6px;
    font-family: 'DM Sans', sans-serif;
    font-size: 0.875rem;
    font-weight: 500;
    border: none;
    cursor: pointer;
    text-decoration: none;
    transition: all 0.15s;
    white-space: nowrap;
}
.btn-primary   { background: var(--saffron); color: white; }
.btn-primary:hover { background: #8A4500; }  /* darker on hover so white text stays AA (story #105); --saffron-lt was too light under white */
.btn-secondary { background: var(--parchment); color: var(--warm-brown); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--border); }
.btn-ghost     { background: transparent; color: var(--text-mid); border: 1px solid var(--border); }
.btn-ghost:hover { background: var(--parchment); }
.btn-full      { width: 100%; justify-content: center; }
.btn-sm        { padding: 0.3rem 0.7rem; font-size: 0.8rem; }
.btn:disabled  { opacity: 0.5; cursor: not-allowed; }

/* ── DASHBOARD ────────────────────────────────────── */
.season-section { margin-bottom: 2.5rem; }
.season-title {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.4rem;
    font-weight: 600;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 2px solid var(--border);
}
.season-spring { color: #4A7A3A; border-color: #B8D8A8; }
.season-summer { color: var(--terra); border-color: #E8C0A0; }
.season-winter { color: #3A5A7A; border-color: #A8C0D8; }

.week-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
    gap: 0.5rem;
}
.week-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.6rem 0.25rem;
    border-radius: 8px;
    text-decoration: none;
    border: 1px solid var(--border);
    background: white;
    transition: all 0.15s;
    min-height: 60px;
    position: relative;
}
.week-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.week-num  { font-size: 0.75rem; color: var(--text-light); }
.week-status { font-size: 1rem; font-weight: 600; margin-top: 0.1rem; }
.week-complete   { background: var(--bg-success); border-color: var(--border-success); }
.week-complete .week-status { color: var(--success); }
.week-partial    { background: var(--bg-partial); border-color: var(--border-partial); }
.week-partial .week-status-partial { color: var(--saffron); font-size: 0.8rem; }
.week-empty      { background: white; }
.week-empty .week-status-empty { color: var(--border); }
.week-ai-badge {
    position: absolute;
    top: 3px; right: 3px;
    font-size: 0.6rem;
    background: var(--ai-color);
    color: white;
    border-radius: 3px;
    padding: 0 3px;
}

/* ── MENU VIEW ────────────────────────────────────── */
.menu-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.menu-slot {
    background: white;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    box-shadow: var(--shadow);
}
.slot-header {
    background: var(--parchment);
    padding: 0.6rem 1rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 500;
    color: var(--text-mid);
    border-bottom: 1px solid var(--border);
}
.slot-soup .slot-header       { background: #FFF3E0; }
.slot-entree .slot-header     { background: #FBE8E0; }
.slot-carb .slot-header       { background: #EFF5E8; }
.slot-steamed_veg .slot-header { background: #E8F3F5; }

.slot-recipe { padding: 1rem; }
.slot-recipe-name {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--warm-brown);
    text-decoration: none;
    display: block;
    margin-bottom: 0.4rem;
}
.slot-recipe-name:hover { color: var(--saffron); }
/* Allergen parenthetical inline with the slot recipe title (story #58
   PR 1). Smaller + muted so it reads as a secondary annotation while
   staying scannable. Same neutral warm color family as .allergen-pill
   on the recipe detail header so the two surfaces feel related. */
.slot-allergen-inline {
    font-family: var(--font-sans, system-ui, sans-serif);
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--pill-warn-fg);
    margin-left: 0.25rem;
}
/* Recipe-detail base/scaled figures (story #187 PR 2). A two-column grid so
   the yields share a left edge and "2 QT" sits directly under "8 QT" — the
   label column names the DAY in slot mode. Collapses to stacked rows on a
   phone, where a max-content label column would squeeze the figures. */
.recipe-figures {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.15rem 0.9rem;
    margin: 0.4rem 0 0;
    font-size: 0.85rem;
}
.recipe-figures dt {
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.72rem;
    align-self: center;
}
.recipe-figures dd {
    margin: 0;
    color: var(--text-mid);
}
.recipe-figures .rf-scaled { color: var(--text); font-weight: 500; }
.recipe-figures .scale-factor { margin-left: 0.35rem; }
@media (max-width: 640px) {
    .recipe-figures { grid-template-columns: 1fr; gap: 0 0; }
    .recipe-figures dt { margin-top: 0.35rem; }
}

/* Slot figures (story #187). Two rows of the same shape, so the scaled
   yield sits directly beneath the base one and the numbers share a left
   edge — reading the card answers "did that save?" without hunting for
   the pill. Replaces .slot-meta, which put the BASE yield next to the
   SCALED serving count on one line. */
.slot-figures { font-size: 0.8rem; color: var(--text-light); }
.slot-figure { line-height: 1.5; }
.slot-figure-scaled { color: var(--text-mid); font-weight: 500; }
/* Only rendered when a whole number of portions can't hit the typed
   target exactly — volume yields only; the count path anchors straight on
   the declared yield and never drifts. */
.slot-drift {
    margin: 0.15rem 0 0;
    font-size: 0.75rem;
    font-style: italic;
    color: var(--text-light);
}
/* Status pills get their own row: they're status, not measurements, and
   inlining them made the figures line wrap at unpredictable points. */
.slot-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
    margin-top: 0.35rem;
}
.slot-tags .scaled-pill { margin-left: 0; }
.scaled-pill {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.4rem;
    background: #FFF3E0;
    color: var(--saffron);
    font-size: 0.7rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: 3px;
    vertical-align: middle;
}
.slot-scale-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.6rem;
    padding-top: 0.6rem;
    border-top: 1px dashed var(--border-lt);
}
.slot-scale-label {
    font-size: 0.75rem;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.slot-scale-input {
    width: 5rem;
    height: 26px;
    box-sizing: border-box;
    padding: 0.25rem 0.4rem;
    font-size: 0.9rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: white;
    color: var(--text-mid);
}
.slot-scale-save {
    padding: 0.25rem 0.6rem;
    font-size: 0.8rem;
}
/* Per-slot Clear-override button (story #50). Inline form right after
   the slot-scale-form, only rendered when the slot has at least one
   override set. Posts blanks to the same scale route. */
.slot-scale-clear-form {
    margin-top: 0.4rem;
}
.slot-scale-clear {
    padding: 0.2rem 0.55rem;
    font-size: 0.75rem;
    color: var(--text-light);
}
/* Scale-by-yield on the slot card (story #139 PR 2). The unit picker
   matches .slot-scale-input so the yield pair reads as one control. */
.slot-yield-unit {
    /* A native select's metrics don't match a text input's — 28px against
       26px in Chrome, and Safari differs again — so the pair sat on
       visibly different heights. Remove the native chrome and state the
       box explicitly rather than hoping two engines agree. The 1.4rem
       right padding keeps the longest unit (FLOZ) off the chevron. */
    appearance: none;
    -webkit-appearance: none;
    height: 26px;
    box-sizing: border-box;
    padding: 0 1.4rem 0 0.4rem;
    font-size: 0.9rem;
    line-height: 1;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-mid);
    background-color: white;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath fill='%237A4F35' d='M0 0h8L4 5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.45rem center;
    background-size: 8px 5px;
}
/* A count / weight yield (EA, SLICE, LOAF) has no alternative unit worth
   offering — EA converts to nothing — so the recipe's own unit renders as
   a plain label rather than a one-option select (story #187). */
.slot-yield-unit-fixed {
    font-size: 0.9rem;
    color: var(--text-mid);
}

/* Cook-per-day (story #33, PR #50). Admin inline form sits in the
   menu-view header under the day/date line; non-admins see a read-only
   "👤 Cook: <name>" paragraph. Matches the slot-scale-form idiom
   (compact flex row with subtle typography) so the two admin controls
   on menu_view read as a cohesive pair. */
.cook-form {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.5rem;
    margin-bottom: 1rem;
}
.cook-form-label {
    font-size: 0.75rem;
    color: var(--text-light);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.cook-form-input {
    width: 10rem;
    padding: 0.25rem 0.4rem;
    font-size: 0.9rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: white;
    color: var(--text-mid);
}
.cook-form-save {
    padding: 0.25rem 0.6rem;
    font-size: 0.8rem;
}
.page-cook {
    margin-top: 0.25rem;
    color: var(--text-mid);
}
.page-cook .cook-icon {
    margin-right: 0.25rem;
}

.slot-empty {
    padding: 1rem;
    color: var(--text-light);
    font-style: italic;
    font-size: 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.slot-assign-link { color: var(--saffron); text-decoration: none; font-style: normal; font-size: 0.85rem; }

.source-badge {
    padding: 0.3rem 0.7rem;
    border-radius: 20px;
    font-size: 0.8rem;
    font-weight: 500;
}
.source-historical         { background: var(--bg-success); color: var(--success); }
.source-ai                 { background: var(--bg-ai); color: var(--ai-color); }
.source-ai.source-confirmed { background: var(--bg-success); color: var(--success); }
/* Story #121: 'manual' = operator hand-picked (neutral green, like a confirmed
   menu). 'planned' stubs never render a badge (hidden until a dish is added),
   but the class is defined for safety. */
.source-manual             { background: var(--bg-success); color: var(--success); }
.source-planned            { background: var(--bg-ai); color: var(--ai-color); }

/* Story #74: year-over-year inheritance indicators. */
.inherited-pill {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.4rem;
    background: var(--parchment);
    color: var(--text-mid);
    border: 1px solid var(--border);
    font-size: 0.7rem;
    font-weight: 500;
    border-radius: 4px;
    letter-spacing: 0.02em;
}
.inherited-meal-note {
    margin: 0.5rem 0 0.75rem;
    padding: 0.5rem 0.75rem;
    background: var(--parchment);
    border-left: 3px solid var(--border);
    border-radius: 4px;
    font-size: 0.85rem;
    color: var(--text-mid);
}

.menu-notes {
    background: var(--parchment);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.75rem 1rem;
    font-size: 0.9rem;
    color: var(--text-mid);
}

/* ── MENU EDIT ────────────────────────────────────── */
.menu-edit-form { display: flex; flex-direction: column; gap: 1rem; }
.edit-slot {
    background: white;
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
}
.edit-slot-header {
    background: var(--parchment);
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--border);
}
.edit-slot-header h3 {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--warm-brown);
}
.edit-slot-hint { font-size: 0.8rem; color: var(--text-light); margin-top: 0.1rem; }
.edit-slot-body { padding: 1rem; }
.steamed-veg-note { font-size: 0.8rem; color: var(--text-light); margin-top: 0.5rem; }

/* Extra-dish expander (story #83; restyled #310). Extras used to be capped
   at 3, so this was a rare escape hatch and shipped unstyled — a bare
   <details> among finished cards. Now that a day can hold any number, it's a
   routine step in planning one, and it needs to read as such. Deliberately
   NOT another white card: the solid cards above hold real dishes, so this
   stays a dashed, cream-filled affordance — clearly "add here", clearly
   subordinate to content. Opening it makes the container solid so the
   pickers inside read as real slots. Matches the literal spacing/radius of
   the .edit-slot block above rather than the token scale, which that block
   predates. */
.extra-dish-add {
    border: 1px dashed var(--border);
    border-radius: 10px;
    background: var(--cream);
}
.extra-dish-add > summary {
    padding: 0.75rem 1rem;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--saffron);
    cursor: pointer;
    list-style: none;
    -webkit-user-select: none;
    user-select: none;
}
.extra-dish-add > summary::-webkit-details-marker { display: none; }
.extra-dish-add > summary:hover { text-decoration: underline; }
.extra-dish-add > summary:focus-visible {
    outline: 2px solid var(--saffron);
    outline-offset: -2px;
    border-radius: 10px;
}
.extra-dish-add[open] {
    background: transparent;
    border-style: solid;
    padding-bottom: 1rem;
}
.extra-dish-add[open] > summary {
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
}
.extra-dish-add .edit-slot { margin: 0 1rem 1rem; }
.extra-dish-add .edit-slot:last-child { margin-bottom: 0; }

/* ── RECIPE TABLE ─────────────────────────────────── */

/* Story #21 — sticky nav helpers for the recipe list ──────────────── */

/* Sticky strip wrapping the search form + A-Z bar. Pins below the nav. */
.recipe-filter-sticky {
    position: sticky;
    top: var(--nav-h);
    z-index: 50;
    background: var(--cream);
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-lt);
}
.recipe-filter-sticky .filter-bar { margin-bottom: 0; }

/* A-Z letter jump bar */
.az-bar {
    display: flex;
    gap: 0.15rem;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0.25rem 0 0;
}
.az-letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.9rem;
    padding: 0.45rem 0.15rem; /* ~44px touch height with 0.75rem font */
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--warm-brown);
    text-decoration: none;
    border-radius: 4px;
    flex-shrink: 0;
    transition: background 0.1s, color 0.1s;
}
.az-letter:hover { background: var(--parchment); color: var(--saffron); }
.az-letter-off   { color: var(--border); cursor: default; }

/* First row for each letter — scroll-margin so the row clears the sticky
   nav + filter strip. Value set dynamically by JS; fallback covers the
   approximate height when JS hasn't run yet. */
.az-anchor { scroll-margin-top: var(--thead-top, 180px); }

/* Recipe-list table wrapper — no overflow-x so thead position:sticky
   works against the viewport (overflow containers block it). On narrow
   screens overflow is restored and sticky thead degrades gracefully. */
.recipe-list-table-wrap { /* no overflow-x */ }
/* Restore horizontal scroll on phones. Aligned to the SAME 640px breakpoint as
   the static-thead override below (was 600px): the overflow ancestor is what
   breaks sticky containment, so the two must match or a 601–640px band would
   disable the sticky header without actually being a scroll container. */
@media (max-width: 640px) {
    .recipe-list-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* Sticky column headers — scoped to the recipe list wrapper so chop /
   purchase / ingredient tables are unaffected. top is set by JS. */
.recipe-list-table-wrap .recipe-table th {
    position: sticky;
    top: var(--thead-top, 180px);
    z-index: 40;
    /* background: var(--parchment) already set by the shared th rule */
}

/* Phones: the ≤640px `overflow-x: auto` above turns this wrapper into a scroll
   container, and an overflow (non-`visible`) ancestor BREAKS `position: sticky`
   containment — the th then sticks to the WRAPPER (which starts mid-page, after
   the filter chips) instead of the viewport, so the column header floats over
   the rows. That's the real mobile/iOS bug; PR #358 misread it as a stale JS
   offset and patched `--thead-top`, which can't fix a broken containing block.
   A sticky column header isn't worth it on a phone anyway — let it scroll. And
   the search strip: with the wrapped mobile nav it would overlap a `top:nav-h`
   sticky, so it scrolls too. On phones ONLY the nav stays sticky (no offset
   math, nothing to go stale). */
@media (max-width: 640px) {
    .recipe-list-table-wrap .recipe-table th { position: static; }
    .recipe-filter-sticky { position: static; }
    /* Only the nav is sticky here, and it wraps TALLER than the 56px --nav-h
       token — so an A-Z jump must clear the MEASURED nav height (JS sets
       --mobile-nav-h), not the stale token that --thead-top is built from.
       Otherwise the target row lands partly under the sticky nav. */
    .az-anchor { scroll-margin-top: var(--mobile-nav-h, 4.5rem); }
}

/* Jump-to-top / jump-to-bottom — fixed corner, always visible. */
.jump-btns {
    position: fixed;
    bottom: 2rem;
    right: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    z-index: 200;
}
.jump-btn {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--cream);
    color: var(--warm-brown);
    font-size: 1.1rem;
    cursor: pointer;
    box-shadow: var(--shadow);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.1s, border-color 0.1s;
}
.jump-btn:hover { background: var(--parchment); border-color: var(--saffron); }

/* ── end story #21 ─────────────────────────────────────────────────── */

.filter-bar { margin-bottom: 1.5rem; }
.filter-form { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.search-input {
    flex: 1;
    min-width: 200px;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: white;
    font-family: 'DM Sans', sans-serif;
    font-size: 16px; /* iPad: prevents iOS auto-zoom on focus. */
    color: var(--text);
}
.search-input:focus { outline: 2px solid var(--saffron); border-color: transparent; }

/* Horizontal-scroll wrapper for dense tables on narrow widths (iPad
   portrait, phones). `.recipe-table-wrap` is kept as an alias so
   existing admin templates keep working unchanged. New templates
   use `.table-wrap`. `-webkit-overflow-scrolling: touch` gives iOS its
   expected momentum-scroll feel. */
.table-wrap, .recipe-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.recipe-table, .ingredient-table, .chop-table, .purchase-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}
/* Recipe-edit ingredient grid: honor the per-column widths exactly so the
   text inputs stop rendering at their default ~20-char intrinsic width and
   the dropdown columns get their real estate (story #116 follow-up). */
.ingredient-table { table-layout: fixed; }
.ingredient-table td { word-break: break-word; }
.ingredient-table input[type="text"] { width: 100%; }
/* Row actions (Save / Delete). Lives here rather than as an inline style so
   the phone breakpoint below can re-align it — an inline style would outrank
   the stylesheet and the buttons would stay right-aligned in the card. */
.ingredient-editor-table td.ing-actions-cell { text-align: right; white-space: nowrap; }
.recipe-table th, .ingredient-table th, .chop-table th, .purchase-table th {
    text-align: left;
    padding: 0.6rem 0.75rem;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 500;
    color: var(--text-light);
    background: var(--parchment);
    border-bottom: 1px solid var(--border);
}
.recipe-table td, .ingredient-table td, .chop-table td, .purchase-table td {
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--border-lt);
    vertical-align: top;
}
.recipe-table tr:hover td, .chop-table tr:hover td, .purchase-table tr:hover td {
    background: #FDFAF5;
}
.recipe-link { color: var(--warm-brown); text-decoration: none; font-weight: 500; }
.recipe-link:hover { color: var(--saffron); }
.table-empty { color: var(--text-light); text-align: center; padding: 2rem !important; font-style: italic; }
.amount-cell { font-variant-numeric: tabular-nums; font-weight: 500; }

/* Ingredient-audit flag badges (story #54). The old `.type-badge`
   (Serving/Prep/Menu) rules were removed in story #82 PR 3b once the
   recipe_type column was dropped; this shares their shape. */
.ingredient-flag {
    display: inline-block;
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 500;
    margin-right: 0.25rem;
}
.flag-non-food    { background: #F5E8E1; color: #8A4A1F; }
.flag-orphan      { background: #EEEEEE; color: #555; }
.flag-recipe      { background: #FFF7DA; color: #7A5A10; }
.flag-suspicious  { background: #FEF3C7; color: #92400E; }

.method-dot { font-size: 0.9rem; }
.method-manual { color: var(--success); }
.method-ai     { color: var(--ai-color); }
.method-none   { color: var(--border); }

/* Tag-filter chip row above the recipe table. Story #40 (MD)
   ships the first chip; story #39 will extend the row with
   course chips. */
.filter-chip-row {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    flex-wrap: wrap;
    /* Story #162: the first chip row sat flush against the sticky filter's
       border-bottom (recipe + ingredients lists share this class). A small
       top margin lifts it off the divider; the second recipe-list chip row
       used to patch this inline. Collapses with the prior row's 1.25rem
       margin-bottom, so inter-row spacing is unchanged. */
    margin-top: 0.5rem;
    margin-bottom: 1.25rem;
}
.filter-chip {
    display: inline-block;
    padding: 0.3rem 0.75rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: white;
    color: var(--text);
    font-size: 0.8rem;
    font-weight: 500;
    text-decoration: none;
    transition: background 0.1s, border-color 0.1s;
}
.filter-chip:hover {
    border-color: var(--saffron);
    background: #FDFAF5;
}
.filter-chip-active {
    background: var(--saffron);
    border-color: var(--saffron);
    color: white;
}
.filter-chip-active:hover {
    background: var(--saffron);
    color: white;
}

/* Clickable sort header on a recipe-table column. */
.col-sort {
    color: var(--text-light);
    text-decoration: none;
    display: inline-block;
}
.col-sort:hover { color: var(--saffron); }
.col-sort-active { color: var(--warm-brown); }

/* Prev/Next recipe nav cluster on the recipe detail page (story #45).
   Sits inline with the existing back-link, only renders Prev/Next when
   the route computed prev_url / next_url (i.e. the user arrived via
   origin=list and isn't at a boundary). */
.recipe-nav {
    display: flex;
    gap: 1.25rem;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 0.4rem;
}
.recipe-nav-link {
    color: var(--warm-brown);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 500;
}
.recipe-nav-link:hover {
    color: var(--saffron);
    text-decoration: underline;
}

/* Day-navigation cluster on the menu day view (issue #80).
   Sits inline with the existing back-link. Mirrors .recipe-nav
   shape but always renders (no hidden state — every menu day has
   a previous and next via continuous calendar wrap). Tap targets
   include the day name + arrow for ≥44px width on mobile/tablet. */
.day-nav {
    display: flex;
    gap: 1.25rem;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 0.4rem;
}
.day-nav-link {
    color: var(--warm-brown);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 500;
}
.day-nav-link:hover {
    color: var(--saffron);
    text-decoration: underline;
}

/* Compact MD badge in the recipe-table cell. */
.md-badge {
    display: inline-block;
    padding: 0.1rem 0.4rem;
    border-radius: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    background: var(--bg-ai);
    color: var(--ai-color);
    letter-spacing: 0.03em;
}

/* ── RECIPE DETAIL ────────────────────────────────── */
.recipe-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.4rem;
    font-size: 0.85rem;
    color: var(--text-mid);
}
.steamed-tag {
    background: #E8F3F5;
    color: #2A6070;
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
}

.season-tag {
    display: inline-block;
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 500;
}
.season-tag-spring   { background: var(--pill-ok-bg); color: var(--pill-ok-fg); }
.season-tag-summer   { background: #FFF3D9; color: #8A5A10; }
.season-tag-winter   { background: #E5ECF5; color: #2C4876; }
.season-tag-staple   { background: var(--pill-mute-bg); color: var(--pill-mute-fg); font-style: italic; }
.season-tag-untagged { background: var(--pill-warn-bg); color: var(--pill-warn-fg); font-style: italic; }

/* Story #134: canon-week progress strip above the calendar grid. */
.canon-progress {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 0.35rem var(--space-md);
    border-radius: 6px;
    background: var(--cream);
    margin-bottom: var(--space-xs);
    font-size: 0.85rem;
}
.canon-progress-label { font-weight: 600; color: var(--text); min-width: 9rem; }
.canon-pips { display: flex; gap: 4px; }
.canon-pip {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--border);
}
.canon-pip-done { background: var(--success); }
.canon-progress-count { color: var(--text-light); }
.canon-progress-ready { color: var(--success); font-weight: 600; }

/* Allergen pill row in the recipe detail header (story #58 PR 1).
   Sits on its own row directly below .recipe-meta. Single neutral
   warm color across all 8 canonical allergens — the letter inside
   is the discriminator, which keeps maintenance light vs picking
   8 distinct accessible colors for an audience of a dozen kitchen
   scanners. The row only renders when recipes.allergens is set,
   so unflagged recipes look identical to today. */
.recipe-allergens {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.5rem;
}
.allergen-pill {
    display: inline-block;
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    background: var(--pill-warn-bg);
    color: var(--pill-warn-fg);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    border: 1px solid #F2C9C0;
}

/* Story #15: Menu-type recipe banner.
   Sets expectations on the detail page since Menu-type rows render
   through the same template as Serving/Prep but lack methods and have
   aggregated event-style ingredient lists. */
.menu-archive-banner {
    background: var(--parchment);
    border: 1px solid var(--border);
    border-left: 3px solid #8A5A10;
    border-radius: 8px;
    padding: 0.75rem 1rem;
    margin-bottom: 1.5rem;
    font-size: 0.9rem;
    color: var(--text-mid);
    line-height: 1.45;
}
.menu-archive-banner strong {
    color: var(--text-strong, #2C2823);
    margin-right: 0.4rem;
}
.menu-archive-banner em {
    font-style: italic;
}

.scale-bar {
    background: var(--parchment);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.75rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
}
/* The yield-scaling row holds seven items — "Scale to:", the portions input,
   "portions", "or", the yield input, the unit select, and the Scale button —
   needing ~470px. Without wrapping it forced the whole PAGE wider than a
   375px phone (operator report 2026-08-02).

   `.scale-bar` already wrapped its own children, which is why this hid for so
   long: the culprit is the inner form. A flex item's default `min-width: auto`
   resolves to its min-content size, and with `nowrap` inside, that was the
   full sum of the row — so `.scale-form` refused to shrink and pushed the page
   out regardless of the parent wrapping.

   That page-level horizontal scroll caused BOTH reported symptoms: the
   ingredient column slid off the left edge, and the nav looked misaligned
   because `position: sticky` pins vertically only, so the header travelled
   sideways with the content and its background ran out mid-screen.

   `.slot-scale-form` (menu view) has wrapped since it was written — this is
   the same idiom, simply missed here. */
.scale-form { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.portions-input {
    width: 80px;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 5px;
    font-size: 16px; /* iPad: prevents iOS auto-zoom on focus. */
    text-align: center;
    font-family: 'DM Sans', sans-serif;
}
.scale-factor {
    font-size: 0.85rem;
    color: var(--saffron);
    font-weight: 500;
    background: var(--bg-partial);
    padding: 0.25rem 0.6rem;
    border-radius: 4px;
}
/* Scale-by-yield (story #139) */
.scale-or { font-size: 0.8rem; color: var(--text-light); }
.yield-unit-select {
    padding: 0.4rem 1.4rem 0.4rem 0.5rem; /* right pad clears the native chevron */
    border: 1px solid var(--border);
    border-radius: 5px;
    font-size: 16px; /* iPad: prevents iOS auto-zoom on focus. */
    font-family: 'DM Sans', sans-serif;
    background: white;
}
.implied-value { font-size: 0.85rem; color: var(--text-light); }
/* Wraps for the same reason as .scale-form above: two text chips ("hold
   portion size" / "hold servings") run ~280px side by side, which is tight
   inside a 375px phone — and they only ever render in yield mode, the exact
   mode that overflowed. Every sibling row on this page already wraps. */
.fixed-axis-chips { display: inline-flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
/* Story #159 — rounding-guidance nudge on the scale bar. Gentle (guidance,
   not an error): soft sand background, no heavy rule; the link is the CTA. */
.scale-hint {
    font-size: 0.82rem;
    line-height: 1.4;
    color: var(--text-mid);
    background: var(--bg-partial);
    border-radius: 5px;
    padding: 0.5rem 0.7rem;
    margin-top: 0.5rem;
}
.scale-hint .sh-icon { color: var(--saffron); font-weight: 700; margin-right: 0.15rem; }
.scale-hint a { color: var(--saffron); font-weight: 600; text-decoration: underline; }
.scale-hint a:hover { color: var(--terra); }

.recipe-sections { display: grid; gap: 1.5rem; }
.recipe-section {
    background: white;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.25rem;
}
.recipe-section h2 {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--warm-brown);
    margin-bottom: 1rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.section-sub { font-family: 'DM Sans', sans-serif; font-size: 0.8rem; font-weight: 400; color: var(--text-light); }

.ai-asterisk { color: var(--ai-color); font-weight: 700; margin-left: 2px; }
.no-prep { color: var(--border); }
.ai-legend { font-size: 0.8rem; color: var(--ai-color); margin-top: 0.75rem; font-style: italic; }
/* Story #177: a whole-item count that scaled to a fraction reads as approximate. */
.approx-marker { color: var(--text-light); font-weight: 600; }
.approx-legend { font-size: 0.8rem; color: var(--text-light); margin-top: 0.5rem; font-style: italic; }

/* Prep canonical dropdown + deferred-row display (story #13, PR #52).
   The recipe-edit form's prep column is now strictly canonical for
   any row with a canonical-eligible value (empty or in CANONICAL_PREPS).
   Rows whose prep is a deferred or flagged value render read-only
   with a "pending data cleanup" badge, so admin can't accidentally
   pick a canonical that erases meaningful dimension / state info.
   The deferred prep value passes through via a hidden input. */
.prep-select {
    width: 100%;
    /* Reserve room on the right for the native dropdown chevron so the
       option text never collides with it (story #116 follow-up). */
    padding: 0.25rem 1.4rem 0.25rem 0.5rem;
    font-size: 0.9rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: white;
    color: var(--text-mid);
    text-overflow: ellipsis;
}
.prep-deferred {
    display: inline-block;
    color: var(--text-light);
    font-size: 0.9rem;
    font-style: italic;
    cursor: help;
}
.prep-deferred-badge {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.4rem;
    background: var(--border-lt, #EEE);
    color: var(--text-light);
    font-size: 0.65rem;
    font-style: normal;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: 3px;
    vertical-align: middle;
}

/* Unit canonical dropdown + deferred-row display (story #14, PR #54).
   Mirror of the prep canonical pattern. Three unit fields in the
   recipe-edit form (per-row ingredient unit, add-new-ingredient unit,
   recipe yield_unit) become strict <select>. Rows whose current value
   is deferred (FRACTIONAL_YIELD, FLAG_FOR_REVIEW) render read-only
   with a "pending data cleanup" badge until their respective follow-up
   story (#46 for fractional yields; manual review for the residue)
   ships. */
.unit-select {
    width: 100%;
    /* Short values (CUP/TSP/EA) — a slimmer right gap for the chevron keeps
       the value from clipping in the narrow Unit column (story #116 f/u). */
    padding: 0.25rem 1.1rem 0.25rem 0.45rem;
    font-size: 0.9rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: white;
    color: var(--text-mid);
    text-transform: uppercase;
}
.unit-deferred {
    display: inline-block;
    color: var(--text-light);
    font-size: 0.9rem;
    font-style: italic;
    cursor: help;
}
.unit-deferred-badge {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.4rem;
    background: var(--border-lt, #EEE);
    color: var(--text-light);
    font-size: 0.65rem;
    font-style: normal;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: 3px;
    vertical-align: middle;
}

.method-text {
    line-height: 1.8;
    color: var(--text-mid);
    font-size: 0.95rem;
}

/* Story #31 — numbered steps for AI-generated methods. Step
   numerals sit in the gutter at iPad-readable size; per-step
   prose flows in 1–2 sentences with comfortable line-height
   and inter-step spacing, since cooks read this at arm's
   length in a busy kitchen. */
.method-steps {
    list-style-position: outside;
    padding-left: 2rem;
    margin: 0;
    color: var(--text-mid);
    font-size: 0.95rem;
}
.method-step {
    line-height: 1.7;
    margin-bottom: 0.85rem;
    padding-left: 0.25rem;
}
.method-step:last-child {
    margin-bottom: 0;
}
.method-step::marker {
    font-weight: 600;
    color: var(--text-mid);
}

/* Story #123 — free-form method notes / variations, rendered un-numbered
   after the steps. `pre-line` preserves the operator's line breaks (footnotes,
   sub-headers like "Stovetop method:") without collapsing them. */
.method-notes {
    margin-top: 1.25rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--border, #e6ddc9);
}
.method-notes-title {
    font-size: 0.95rem;
    margin: 0 0 0.4rem;
    color: var(--text-mid);
}
.method-notes-text {
    white-space: pre-line;
    line-height: 1.7;
    color: var(--text-mid);
    font-size: 0.95rem;
}
.method-empty {
    color: var(--text-light);
    font-style: italic;
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}
.method-source-tag {
    font-family: 'DM Sans', sans-serif;
    font-size: 0.75rem;
    font-weight: 400;
    padding: 0.15rem 0.5rem;
    border-radius: 4px;
}
.method-source-manual { background: var(--bg-success); color: var(--success); }
.method-source-ai     { background: var(--bg-ai); color: var(--ai-color); }
.ai-tag { background: var(--bg-ai); color: var(--ai-color); padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.75rem; }

/* ── FORMS ────────────────────────────────────────── */
.edit-form, .auth-form { display: flex; flex-direction: column; gap: 0; max-width: 760px; }
.form-section {
    background: white;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 1.5rem;
    margin-bottom: 1rem;
}
.form-section h2 {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.2rem;
    font-weight: 600;
    color: var(--warm-brown);
    margin-bottom: 1.25rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}
.field { margin-bottom: 1rem; }
.field label {
    display: block;
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text-mid);
    margin-bottom: 0.35rem;
}
.field input[type="text"],
.field input[type="number"],
.field input[type="password"],
.field textarea,
.field select,
.select-recipe,
.select-filter {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    font-family: 'DM Sans', sans-serif;
    font-size: 16px; /* iPad: prevents iOS auto-zoom on focus. */
    color: var(--text);
    background: white;
    transition: border-color 0.15s;
}
.field input:focus, .field textarea:focus, .field select:focus,
.select-recipe:focus, .select-filter:focus {
    outline: 2px solid var(--saffron);
    border-color: transparent;
}
.field textarea { resize: vertical; min-height: 100px; }
/* The derived "Total yield" box is read-only output, but it sits in a row with
   editable controls — give it the same box model so the row reads as one clean
   grid, with a muted grey fill + muted text so it still doesn't look typeable. */
/* The derived "Total yield" is a read-only <input> (not a div) so it renders at
   the exact native height of the editable fields beside it in every browser — a
   div can't match native control metrics (Safari drew it taller). The base
   `.field input` rule supplies the box model; this only recolours it as muted,
   non-editable output (higher specificity than `.field input` so the muted
   fill/text win, and no focus ring since it's not tabbable). */
.field input.readonly-derived {
    color: var(--text-light);
    background: var(--border-lt);
    cursor: default;
}
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
/* Story #163: rows holding exactly 3 fields (the yield/portion clusters on
   recipe-edit) force 3 columns — minmax(0,1fr) lets them shrink so all three
   stay on one line at tablet width instead of orphaning the third. They stack
   to a single column on phones (see the 640px block) rather than the 2+1 the
   generic auto-fit / 1fr-1fr rules would produce. Extra class = higher
   specificity, so it wins regardless of source order. */
.field-row.field-row-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Uniform control height across the 3-field yield/portion rows, cross-browser.
   Safari renders native <select> (and number-spinner inputs) taller than plain
   text inputs and IGNORES a min-height floor, so pin an explicit height AND
   strip the native select chrome (appearance:none) — every control is then
   exactly 38px by CSS, not by hoping native metrics agree across browsers. A
   custom chevron replaces the native arrow. Scoped to these rows only. */
.field-row-3 .field input:not([type="checkbox"]),
.field-row-3 .field select { height: 38px; }
.field-row-3 .field select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 2rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%238a7a63' stroke-width='1.6' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
}
.field-checkbox label { display: flex; align-items: flex-start; gap: 0.5rem; cursor: pointer; font-size: 0.9rem; font-weight: 400; }
.field-checkbox input[type="checkbox"] { margin-top: 3px; width: auto; accent-color: var(--saffron); }
.field-hint { font-size: 0.8rem; color: var(--text-light); margin-top: 0.4rem; }

/* Per-field help affordance (story #87) — ? button beside a label
   reveals a one-line definition. Tap/click toggle (iPad-friendly),
   keyboard + screen-reader accessible. */
.field-label-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.3rem; }
.field-label-row label { margin-bottom: 0; }
.help-icon {
    flex: 0 0 auto;
    width: 1.15rem; height: 1.15rem; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--saffron); border-radius: 50%;
    background: transparent; color: var(--saffron);
    font-size: 0.72rem; font-weight: 700; line-height: 1; cursor: pointer;
}
.help-icon:hover, .help-icon[aria-expanded="true"] { background: var(--saffron); color: #fff; }
.help-icon:focus-visible { outline: 2px solid var(--saffron); outline-offset: 1px; }
/* Help popover (story #112). Absolutely positioned so revealing it never
   reflows the form; shown on hover, keyboard focus-within, or when the
   click-JS un-hides it (the touch/tap path). */
.help-wrap { position: relative; display: inline-flex; }
.help-text {
    position: absolute;
    top: calc(100% + 0.35rem);
    left: 0;
    z-index: 30;
    width: max-content;
    max-width: 16rem;
    margin: 0;
    padding: 0.4rem 0.6rem;
    background: #FEF6E9; border: 1px solid var(--saffron-lt); border-radius: 4px;
    box-shadow: 0 2px 6px rgba(74, 44, 26, 0.14);
    font-size: 0.8rem; font-weight: 400; line-height: 1.35; color: var(--text-mid);
    white-space: normal; text-align: left;
    display: none;
}
/* Bridge the gap between the ? and the popover so moving the pointer onto
   the text keeps it open. */
.help-text::before {
    content: ""; position: absolute; bottom: 100%; left: 0; right: 0; height: 0.35rem;
}
.help-wrap:hover .help-text,
.help-wrap:focus-within .help-text,
.help-text:hover,
.help-text:not([hidden]) { display: block; }
/* The recipe-edit wizard's button row runs 431–490px depending on the step —
   Step 3 is the widest ("← Back to Ingredients" + "Save & Finish" + "✦ Generate
   with AI"), and a 375px phone leaves it ~295px once `.main` and `.form-section`
   take their padding. Without wrapping the row can't shrink, so the whole PAGE
   went 156px wider than the viewport (operator report 2026-08-06); measured at a
   true 375px viewport the document was 531px and the AI button was the only
   element past the right edge.

   Same root cause as `.scale-form` on recipe detail (PR #387): a flex item's
   default `min-width: auto` is its min-content size, so a `nowrap` row refuses
   to shrink below the sum of its children and pushes the page out. And the same
   sticky-nav symptom follows — `position: sticky` pins vertically only, so the
   header travels sideways with the content.

   `admin_review_allergen_proposals` and `admin_review_course_proposals` already
   patch this inline with `flex-wrap: wrap`; the need was known, just never made
   the base rule. Wrapping is inert on rows that already fit. */
.form-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; padding: 1rem 0 0; }

/* ── CHOPPING / PURCHASING ────────────────────────── */
.chop-group-row td {
    background: var(--parchment);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--text-light);
    padding: 0.4rem 0.75rem !important;
}
.chop-ingredient { font-weight: 500; color: var(--warm-brown); }
.chop-prep { color: var(--text-mid); }
.chop-amount { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--terra); }
.chop-recipe { font-size: 0.8rem; color: var(--text-light); }

.supplier-section { margin-bottom: 2rem; }
.supplier-name {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.3rem;
    font-weight: 600;
    color: var(--warm-brown);
    margin-bottom: 0.75rem;
    padding-bottom: 0.4rem;
    border-bottom: 2px solid var(--border);
}
.unassigned-label { color: var(--terra); }
.unassigned-note { font-size: 0.85rem; color: var(--terra); margin-top: 1rem; font-style: italic; }
.purchase-recipes { font-size: 0.8rem; color: var(--text-light); }

/* Ingredient-amount recovery preview (story #110) */
.recovery-recipe {
    border: 1px solid var(--border);
    border-radius: 4px;
    margin-bottom: 0.4rem;
    padding: 0.4rem 0.6rem;
}
.recovery-recipe > summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--warm-brown);
}
.recovery-recipe.recovery-review { border-color: var(--terra); background: rgba(192, 86, 33, 0.05); }
.recovery-factor {
    font-size: 0.8rem;
    color: var(--text-light);
    font-weight: 400;
    margin-left: 0.4rem;
}
.recovery-flag { font-size: 0.78rem; color: var(--terra); margin-left: 0.4rem; }
.recovery-table { margin-top: 0.5rem; }
.recovery-now { color: var(--text-light); }
.recovery-fixed { color: var(--warm-brown); font-weight: 600; }
.recovery-namelist { columns: 2; font-size: 0.85rem; margin-top: 0.5rem; }
.recovery-namelist li { break-inside: avoid; }
.recovery-course { font-size: 0.75rem; color: var(--text-light); }
.recovery-flagged { margin-top: 0.5rem; }
.recovery-flagged li { margin-bottom: 0.25rem; }
.recovery-skip {
    font-size: 0.72rem; font-weight: 400; color: var(--text-light);
    margin-right: 0.5rem; cursor: pointer; user-select: none;
}

/* ── YIELD↔INGREDIENTS ADVISORY (story #111) ──────── */
.yield-advisory {
    font-size: 0.82rem;
    line-height: 1.4;
    padding: 0.5rem 0.7rem;
    border-radius: 4px;
    border-left: 3px solid var(--border);
    background: var(--border-lt);
    color: var(--text-mid);
    margin: -0.4rem 0 1rem;
}
.yield-advisory .ya-icon { font-weight: 700; margin-right: 0.3rem; }
.yield-advisory.is-ok {
    border-left-color: var(--success);
    background: var(--bg-success);
    color: var(--success);
}
.yield-advisory.is-warn {
    border-left-color: var(--terra);
    background: rgba(192, 86, 33, 0.07);
    color: var(--terra);
}
.yield-advisory.is-na { color: var(--text-light); }
/* Story #140: header-internal mismatch — declared yield vs servings × portion
   size. A "harder" signal than the soft #111 advisory: heavier rule + the
   terra warning palette, carrying the one-click reconcile button. */
.yield-advisory.is-mismatch {
    border-left-color: var(--terra);
    border-left-width: 4px;
    background: rgba(192, 86, 33, 0.10);
    color: var(--terra);
    font-size: 0.85rem;
}

/* Drift tripwire on the read-only views. The recipe-detail box reuses
   .yield-advisory but drops the edit-page's negative top margin so it sits
   cleanly below the meta row; the cook sheet uses a terra ⚠ glyph (forced to
   black in print so it survives a B&W kitchen printer). */
.recipe-detail-drift { margin: 0.5rem 0 0; }
.cook-drift-flag { color: var(--terra); font-weight: 700; }
@media print { .cook-drift-flag { color: #000; } }

/* ── AI STATUS ────────────────────────────────────── */
.ai-status {
    display: none;  /* revealed by amrit.js via inline style.display = 'block' */
    padding: 0.6rem 1rem;
    border-radius: 6px;
    font-size: 0.875rem;
    margin-top: 0.75rem;
}
.ai-status-loading { background: var(--bg-ai); color: var(--ai-color); }
.ai-status-success { background: var(--bg-success); color: var(--success); }
.ai-status-error   { background: var(--bg-error); color: var(--error); }

/* ── PROGRESS BAR (admin review pages) ──────────────
   Extracted from duplicated inline styles in admin_review_*_proposals.html.
   Only the dynamic width stays inline (server-rendered percentage). */
.progress-bar {
    background: var(--bg-track);
    height: 14px;
    border-radius: 7px;
    overflow: hidden;
    margin: 0.5rem 0 1rem;
}
.progress-fill { background: var(--saffron); height: 100%; }
.progress-fill.is-terra { background: var(--terra); }

/* ── STATUS BADGE VARIANTS ──────────────────────────
   .md-badge base lives above; these are the OK / missing / N-A fills,
   extracted from inline styles on admin_test_drive_connection.html. */
.md-badge.is-ok      { background: var(--pill-ok-bg); color: var(--pill-ok-fg); }
.md-badge.is-missing { background: var(--pill-warn-bg); color: var(--pill-warn-fg); }
.md-badge.is-na      { background: var(--pill-mute-bg); color: var(--pill-mute-fg); }

/* ── SHARE-LINK CONTROL (recipe detail) ─────────────
   Extracted from inline styles on the public-share row. */
/* #6B6B6B is a deliberate single-use cool gray here (distinct from the warm
   --text-light #9C7A5A) — left literal rather than tokenized for one use. */
.share-link-label { display: block; font-size: var(--text-sm); color: #6B6B6B; }
.share-link-row   { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; margin-top: 0.25rem; }
.share-link-row input { flex: 1; min-width: 240px; padding: 0.3rem 0.4rem; }

/* ── EMPTY STATE ──────────────────────────────────── */
.empty-state {
    text-align: center;
    padding: 4rem 2rem;
    color: var(--text-light);
}
.empty-state p { margin-bottom: 0.5rem; }
.empty-actions { display: flex; gap: 0.75rem; justify-content: center; margin-top: 1.5rem; }

/* ── AUTH ─────────────────────────────────────────── */
.auth-body { background: var(--warm-brown); }
.auth-wrap {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
}
.auth-card {
    background: white;
    border-radius: 16px;
    padding: 2.5rem;
    width: 100%;
    max-width: 400px;
    box-shadow: var(--shadow-lg);
}
.auth-logo { text-align: center; margin-bottom: 2rem; }
.auth-devanagari {
    font-family: serif;
    font-size: 2.5rem;
    color: var(--saffron);
    display: block;
    margin-bottom: 0.25rem;
}
.auth-logo h1 {
    font-family: 'Cormorant Garamond', serif;
    font-size: 2rem;
    font-weight: 600;
    color: var(--warm-brown);
    line-height: 1;
}
.auth-logo p { color: var(--text-light); font-size: 0.85rem; margin-top: 0.4rem; }
.auth-form .field:last-of-type { margin-bottom: 1.5rem; }
.auth-alt {
    text-align: center;
    margin-top: 1.25rem;
    font-size: 0.9rem;
    color: var(--text-light);
}
.auth-alt a { color: var(--saffron); font-weight: 500; }
.auth-alt a:hover { text-decoration: underline; }

/* ── PRINT ────────────────────────────────────────── */
@media print {
    .nav, .header-actions, .scale-bar form, .btn, .back-link, .ai-status { display: none !important; }
    body { background: white; font-size: 12pt; }
    .main { max-width: none; padding: 0; }
    .chop-table, .purchase-table { font-size: 11pt; }
    /* Cook sheet (story #79): one recipe per printed page, chopping
       list on its own page at the end. Both legacy
       ``page-break-before`` and modern ``break-before`` are set for
       cross-browser print-engine coverage. ``.cook-sheet-pagebreak``
       is a dedicated empty block element template-side; some
       browser/print-engine combos honor a block-level break trigger
       more reliably than a break declared on a content section. */
    .cook-sheet-recipe + .cook-sheet-recipe,
    .cook-sheet-chopping,
    .cook-sheet-pagebreak {
        page-break-before: always;
        break-before: page;
    }
    .cook-sheet-recipe-title { page-break-after: avoid; break-after: avoid; }
}
.cook-sheet-pagebreak { display: none; }
@media print { .cook-sheet-pagebreak { display: block; height: 0; } }

/* ── COOK SHEET (story #79) ───────────────────────── */
.cook-sheet-recipe { margin: 2.5rem 0; }
.cook-sheet-recipe-title {
    font-size: 1.5rem;
    margin: 0 0 0.25rem;
    border-bottom: 2px solid var(--saffron);
    padding-bottom: 0.35rem;
}
.cook-sheet-slot-label {
    display: inline-block;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-light);
    margin-right: 0.5rem;
    vertical-align: middle;
}
.cook-sheet-recipe-meta {
    color: var(--text-light);
    font-size: 0.9rem;
    margin: 0 0 1rem;
}
.cook-sheet-section-title {
    font-size: 1.05rem;
    margin: 1.25rem 0 0.5rem;
}
.cook-sheet-chopping { margin-top: 3rem; }
.empty-inline { color: var(--text-light); font-style: italic; }

/* Cook-sheet menu summary (story #88) — the page-1 at-a-glance list. */
.cook-sheet-summary { margin: 1.5rem 0 2.5rem; }
.cook-sheet-summary-table { width: 100%; border-collapse: collapse; }
.cook-sheet-summary-table td {
    padding: 0.5rem 0.75rem 0.5rem 0;
    border-bottom: 1px solid var(--border, #e7e0d4);
    vertical-align: baseline;
}
.cook-sheet-summary-slot {
    white-space: nowrap;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    color: var(--text-light);
}
.cook-sheet-summary-dish { font-weight: 600; width: 100%; }
.cook-sheet-summary-portions {
    white-space: nowrap;
    color: var(--text-light);
    font-size: 0.9rem;
}

/* ── RESPONSIVE ───────────────────────────────────── */
@media (max-width: 640px) {
    .main { padding: 1rem 1rem 3rem; }
    .page-header { flex-direction: column; }
    .week-grid { grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); }
    .menu-grid { grid-template-columns: 1fr; }
    .field-row { grid-template-columns: 1fr 1fr; }
    /* Story #163: 3-field rows stack cleanly (1-up) on phones — never 2+1. */
    .field-row.field-row-3 { grid-template-columns: 1fr; }

    /* Recipe-edit ingredient rows become stacked cards on phones.
       The six fixed-percentage columns (Ingredient / Amount / Unit /
       Prelim Prep / Prep Method / Actions) have nowhere to go at 375px, so
       the header row bunched into an unreadable stack (operator report
       2026-07-29). One ingredient = one card, one field per line, each
       carrying its column name from `data-label`.

       Safe to restructure: every control binds to its row form via the
       `form="ing-form-N"` attribute rather than DOM nesting, so display
       changes can't detach an input from its form. Inputs keep their 16px
       size — that's the deliberate iOS-zoom guard, not a type-scale value.

       SCOPED TO `.ingredient-editor-table`, NOT `.ingredient-table` — the
       latter is shared by five other tables (recipe detail, cook sheet,
       settings ×2, drive-connection test) whose cells carry no `data-label`.
       On those, hiding the <thead> and stacking cells would strip the headers
       and leave unlabelled blocks — worst on the cook sheet, which the kitchen
       reads on a phone. The class is the markup contract: apply it only to a
       table whose every <td> has a `data-label`. (Caught in review of PR #385
       before it shipped.) */
    .ingredient-editor-table,
    .ingredient-editor-table tbody,
    .ingredient-editor-table tr,
    .ingredient-editor-table td { display: block; width: auto; }
    .ingredient-editor-table { table-layout: auto; }
    .ingredient-editor-table thead { display: none; }
    .ingredient-editor-table tr {
        border: 1px solid var(--border);
        border-radius: 6px;
        padding: var(--space-sm);
        margin-bottom: var(--space-sm);
        background: var(--cream);
    }
    .ingredient-editor-table td {
        border-bottom: 0;
        padding: var(--space-2xs) 0;
    }
    .ingredient-editor-table td + td { border-top: 1px solid var(--border-lt); }
    /* The column header, relocated per field — same vocabulary as the
       desktop <th> so nothing is lost, just moved. */
    .ingredient-editor-table td::before {
        content: attr(data-label);
        display: block;
        margin-bottom: var(--space-2xs);
        font-size: var(--text-xs);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        color: var(--text-light);
    }
    /* Save / Delete become side-by-side touch targets rather than a
       right-aligned nowrap pair squeezed against the card edge. */
    .ingredient-editor-table td.ing-actions-cell {
        display: flex;
        gap: var(--space-xs);
        text-align: left;
        white-space: normal;
        padding-top: var(--space-xs);
    }
    .ingredient-editor-table td.ing-actions-cell::before { content: none; }
    .ingredient-editor-table select { width: 100%; }
}

/* ── MULTI-MEAL DAY VIEW (story #72 PR A) ─────────── */
/* The menu_view page now stacks every opted-in meal for one day:
   breakfast section → lunch section → dinner section, in order.
   Each meal section gets a header with title + actions (Edit /
   Suggest / Remove) and a 4-slot grid below. The "+ Add Meal"
   row at the top surfaces meals not yet present for the day. */
.add-meal-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 0 0 1.5rem;
    padding: 0.75rem 1rem;
    background: var(--cream);
    border: 1px dashed var(--border);
    border-radius: 6px;
    flex-wrap: wrap;
}
.add-meal-label {
    color: var(--text-mid);
    font-size: 0.85rem;
    font-weight: 500;
}
.meal-section {
    margin-bottom: 2.5rem;
    padding: 1.25rem 0 0;
    border-top: 2px solid var(--border-lt);
}
.meal-section:first-of-type {
    border-top: none;
    padding-top: 0;
}
.meal-section-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}
.meal-title-block {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.meal-title {
    font-family: 'Cormorant Garamond', serif;
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--warm-brown);
    margin: 0;
    line-height: 1.1;
}
.meal-section-breakfast .meal-title { color: #B26B00; }
.meal-section-dinner .meal-title    { color: #5A3D7A; }
.meal-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* Fine print under a meal's action buttons, shown only when at least one
   slot in the meal is scaled — tells the team the scaled amounts live on
   the Cook Sheet / chopping / purchasing lists, not the recipe page. */
.meal-scale-note {
    margin: 0.35rem 0 0;
    font-size: 0.8rem;
    font-style: italic;
    color: var(--text-light);
}

/* ── CALENDAR MONTH DASHBOARD (story #72 PR B) ───── */
/* Story #71 / PR #111-#113's tile-strip layout (one row per fiscal
   week with variable-width tiles + extras-on-the-right) is replaced
   here with a true monthly calendar grid: 7 columns Sun..Sat × ~5-6
   rows of calendar dates. Each cell is a calendar date that may or
   may not have a fiscal day backing it. The +Day / +Brk / +Dnr /
   per-week × Clear-week buttons all moved off the dashboard
   entirely — meal management happens on the day's menu_view page
   (PR A). */
.month-nav {
    display: flex;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.75rem;
}
.month-nav .day-nav-link {
    color: var(--saffron);
    text-decoration: none;
    font-size: 0.9rem;
    padding: 0.25rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: 4px;
    transition: background 0.12s;
}
.month-nav .day-nav-link:hover {
    background: var(--parchment);
}
.season-turn-banner {
    margin: 1rem 0 1.25rem;
    padding: 0.5rem 0.85rem;
    background: var(--cream);
    border-left: 4px solid var(--saffron);
    border-radius: 4px;
    font-size: 0.9rem;
    color: var(--text-mid);
}
.season-turn-spring { border-left-color: #4A7A3A; }
.season-turn-summer { border-left-color: var(--terra); }
.season-turn-fall   { border-left-color: #B26B00; }
.season-turn-winter { border-left-color: #3A5A7A; }

.cal-month {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 4px;
    margin-bottom: 1.5rem;
}
.cal-day-header {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-light);
    text-align: center;
    padding: 0.4rem 0;
    font-weight: 600;
    border-bottom: 1px solid var(--border-lt);
}
.cal-day {
    min-height: 90px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    padding: 0.4rem 0.5rem;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: white;
    text-decoration: none;
    color: var(--text);
    position: relative;
    transition: background 0.12s, border-color 0.12s, transform 0.12s;
}
.cal-day:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow);
}
.cal-day-num {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-mid);
    line-height: 1;
}
.cal-day-out .cal-day-num {
    /* day from prev/next month — visually muted */
    color: var(--border);
}
.cal-day-out {
    background: var(--cream);
    border-style: dashed;
    cursor: default;
}
.cal-day-out:hover {
    transform: none;
    box-shadow: none;
}
.cal-day-empty {
    /* Out-of-month or no-fiscal-anchor cells — non-clickable muted cell. */
    background: var(--cream);
}
.cal-day-extendable {
    /* In-month, clickable, no rows yet (Sun/Mon/Fri default). The + indicator
       inside makes the click-to-extend affordance visible. */
    background: var(--cream);
    border-style: dashed;
}
.cal-day-extendable:hover {
    background: white;
    border-color: var(--saffron);
    border-style: solid;
}
.cal-add {
    margin-top: auto;
    font-size: 1.2rem;
    color: var(--saffron);
    font-weight: 500;
    line-height: 1;
    align-self: center;
    padding-bottom: 0.25rem;
}
.cal-day-today {
    border-color: var(--saffron);
    border-width: 2px;
}
.cal-day-complete {
    background: var(--bg-success);
    border-color: var(--border-success);
}
.cal-meals {
    display: flex;
    gap: 3px;
    align-items: center;
    margin-top: auto;
    padding-top: 0.5rem;
}
.cal-meal {
    display: inline-flex;
    width: 22px;
    height: 22px;
    align-items: center;
    justify-content: center;
    border-radius: 3px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-light);
    background: var(--parchment);
}
.cal-meal-complete {
    background: var(--bg-meal-complete);
    color: var(--success);
}
.cal-meal-started {
    background: var(--bg-meal-started);
    color: var(--saffron);
}
/* Story #74: tile is fully inherited from a prior year — subtle dashed
   underline to flag "on autopilot" without competing with the saffron
   started / green complete states. */
.cal-day-inherited {
    border-style: dashed;
}
.cal-day-inherited.cal-day-complete {
    border-color: var(--border-success);
    border-style: dashed;
}
/* Story #134: subtle left-border accent on canon-week calendar cells. */
.cal-day-canon          { border-left-width: 3px; border-left-color: var(--saffron-lt); }
.cal-day-canon-spring   { border-left-color: var(--pill-ok-fg); }
.cal-day-canon-summer   { border-left-color: var(--saffron-lt); }
.cal-day-canon-winter   { border-left-color: #2C4876; }
.cal-meal-inherited {
    /* Subtle outline so per-meal inheritance is visible even on tiles
       where some meals are owned by the current year. */
    box-shadow: inset 0 -2px 0 0 var(--border);
}
.cal-status {
    position: absolute;
    top: 0.4rem;
    right: 0.5rem;
    font-weight: 600;
    font-size: 1rem;
    color: var(--success);
    line-height: 1;
}
.cal-status-empty {
    color: transparent;
}
/* Used for small POST forms that should sit flush with sibling buttons
   in a flex row rather than opening a new block. */
.inline-form {
    display: inline-block;
    margin: 0;
}
/* Legacy classes from PR #111 / PR #113 (.week-row, .day-tile, etc.)
   intentionally NOT removed in this PR — old dashboard shape is gone
   from the template, but the rules are inert here in case any other
   surface picks them up. Cleanup pass can sweep them once the codebase
   is stable. */
.grid-header-cell {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-light);
    text-align: center;
    padding: 0.3rem 0;
    font-weight: 500;
}
.grid-week-label {
    font-size: 0.75rem;
    color: var(--text-light);
    display: flex;
    align-items: center;
    justify-content: center;
    font-variant-numeric: tabular-nums;
}
/* Story #26 — Calendar date column. Two stacked lines per row:
   full month name on top, Tue-of-week short date underneath. Sized
   to fit "September" + "Sep 30" without wrapping at typical iPad
   widths. */
.grid-week-date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    color: var(--text-light);
}
.grid-week-date .week-month {
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--text);
    min-height: 1em;
}
.grid-week-date .week-day-num {
    font-size: 0.65rem;
}
.day-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.4rem 0.2rem;
    border-radius: 5px;
    text-decoration: none;
    border: 1px solid var(--border);
    background: white;
    transition: all 0.12s;
    min-height: 40px;
    position: relative;
    font-size: 0.75rem;
}
.day-card:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.day-status { font-weight: 600; }
.day-complete        { background: var(--bg-success); border-color: var(--border-success); }
.day-complete .day-status { color: var(--success); font-size: 0.85rem; }
.day-partial         { background: var(--bg-partial); border-color: var(--border-partial); }
.day-partial-txt     { color: var(--saffron); font-size: 0.7rem; }
.day-empty           { background: white; }
.day-empty-txt       { color: var(--border); }
.day-ai-badge {
    position: absolute;
    top: 2px; right: 2px;
    font-size: 0.55rem;
    background: var(--ai-color);
    color: white;
    border-radius: 2px;
    padding: 0 2px;
}
/* Cook name line in the dashboard day cell (story #33, PR #50).
   Displayed only when a cook is assigned (rendered behind a
   {% if item.cook %} guard in dashboard.html). Truncates long
   names with ellipsis so the day card stays compact on iPad-
   portrait widths. Full name is still available via the day
   card's title attribute (hover tooltip) and on the menu view
   itself. */
.day-cook {
    display: block;
    margin-top: 0.2rem;
    max-width: 100%;
    font-size: 0.62rem;
    color: var(--text-mid);
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.page-day {
    font-size: 1rem;
    color: var(--saffron);
    font-weight: 500;
    margin-top: 0.1rem;
}
.legend {
    display: flex;
    gap: 1.5rem;
    flex-wrap: wrap;
    font-size: 0.8rem;
    color: var(--text-light);
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-lt);
}
.legend-swatch {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 18px;
    border-radius: 3px;
    font-size: 0.65rem;
    font-weight: 600;
    margin-right: 4px;
    border: 1px solid var(--border);
}
.swatch-complete { background: var(--bg-success); color: var(--success); border-color: var(--border-success); }
.swatch-partial  { background: var(--bg-partial); color: var(--saffron); border-color: var(--border-partial); }
.swatch-empty    { background: white;   color: var(--border); }
.legend-ai-swatch {
    background: var(--ai-color);
    color: white;
    border-radius: 3px;
    padding: 1px 4px;
    font-size: 0.65rem;
    margin-right: 4px;
}

.page-date { color: var(--text-mid); font-size: 0.95rem; font-weight: 400; }
.settings-sections { display: flex; flex-direction: column; gap: 1rem; max-width: 760px; }

/* Settings group headers (story #84) — label the reorganized clusters. */
.settings-group-header { margin: 1.25rem 0 0.25rem; }

/* Collapsible settings groups (story #84). The <summary> is the clickable
   group header; nested .form-section blocks keep their own styling. */
details.settings-group { display: flex; flex-direction: column; gap: 1rem; }
details.settings-group > summary {
    cursor: pointer;
    list-style: none;
    font-size: 1.25rem;
    font-weight: 600;
    margin: 1.25rem 0 0.25rem;
}
details.settings-group > summary::-webkit-details-marker { display: none; }
details.settings-group > summary::before { content: "\25B8\00a0"; }   /* ▸ */
details.settings-group[open] > summary::before { content: "\25BE\00a0"; }  /* ▾ */

/* ── DANGER BUTTON ────────────────────────────────── */
.btn-danger { background: #C02020; color: white; }
.btn-danger:hover { background: #A01818; }

/* ── SLOT SEARCH ──────────────────────────────────── */
.slot-search {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 6px 6px 0 0;
    border-bottom: none;
    font-family: 'DM Sans', sans-serif;
    font-size: 16px; /* iPad: prevents iOS auto-zoom on focus. */
    color: var(--text);
    background: var(--parchment);
}
.slot-search:focus {
    outline: 2px solid var(--saffron);
    border-color: transparent;
    z-index: 1;
    position: relative;
}
.slot-search + .select-recipe {
    border-radius: 0 0 6px 6px;
}

/* ── WEEK AT A GLANCE (story #136) ───────────────────────────────── */
.wag-weeks-toggle {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-top: .5rem;
}
.wag-toggle-label { font-size: .8rem; color: var(--text-light); }
.wag-toggle-chip {
    font-size: .75rem;
    font-weight: 600;
    padding: .2rem .55rem;
    border-radius: 99px;
    border: 1px solid var(--border);
    color: var(--text-light);
    text-decoration: none;
    background: transparent;
}
.wag-toggle-chip:hover { border-color: var(--saffron); color: var(--saffron); }
.wag-toggle-chip-active {
    background: var(--saffron);
    border-color: var(--saffron);
    color: white;
    cursor: default;
}

.wag-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 1.25rem;
    font-size: .9rem;
}
.wag-th {
    text-transform: uppercase;
    font-size: .7rem;
    letter-spacing: .06em;
    color: var(--text-light);
    font-weight: 600;
    padding: .45rem .75rem;
    text-align: left;
    border-bottom: 2px solid var(--border);
}
.wag-th-day { min-width: 6rem; }
.wag-th-open { width: 4rem; }

.wag-week-header td {
    padding: .5rem .75rem .3rem;
    font-size: .75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-light);
    background: var(--parchment-mid, var(--parchment));
    border-top: 2px solid var(--border);
    border-bottom: 1px solid var(--border);
}
.wag-week-header:first-child td { border-top: none; }

.wag-row td { border-bottom: 1px solid var(--border-light, #E8E0D4); }
.wag-row:last-child td { border-bottom: none; }

.wag-day-cell {
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    padding: .6rem .75rem;
    vertical-align: top;
}
.wag-day-date {
    display: block;
    font-weight: 400;
    font-size: .78rem;
    color: var(--text-light);
}
.wag-recipe-cell {
    padding: .6rem .75rem;
    vertical-align: top;
}
.wag-recipe-name {
    color: var(--text);
    text-decoration: none;
    font-size: .88rem;
}
.wag-recipe-name:hover { color: var(--saffron); text-decoration: underline; }
.wag-empty { color: var(--text-light); font-size: .85rem; }
.wag-unplanned { color: var(--text-light); font-style: italic; font-size: .85rem; }
.wag-inherited {
    color: var(--text-light);
    font-size: .72rem;
    margin-left: .3rem;
}
.wag-open-link {
    padding: .6rem .5rem;
    vertical-align: top;
    white-space: nowrap;
}
.wag-open-link a { font-size: .8rem; color: var(--saffron); text-decoration: none; }
.wag-open-link a:hover { text-decoration: underline; }

.wag-extras-row td { border-bottom: 1px solid var(--border-light, #E8E0D4); }
.wag-extras-label {
    font-size: .75rem;
    font-style: italic;
    color: var(--text-light);
    padding: .25rem .75rem .4rem;
    vertical-align: top;
}
.wag-extras-cell {
    padding: .25rem .75rem .4rem;
    font-size: .8rem;
    color: var(--text-light);
    vertical-align: top;
}
/* Story #310: the dish separator sits back so the dish names read as the
   content and the dots as punctuation between them. */
.wag-extras-sep { color: var(--border); }
