/* Recipe Digest — phone-first, kitchen-legible.
   Big tap targets and high contrast: this gets used with wet hands on a
   propped-up phone under kitchen lighting. */

:root {
  --bg: #faf7f2;
  --surface: #ffffff;
  --surface-2: #f3ede4;
  --text: #23201c;
  --muted: #6f665c;
  --line: #e0d7ca;
  --accent: #8a3b12;
  --accent-soft: #f6e7dd;
  --flag: #a86a00;
  --flag-bg: #fdf3dd;
  --ok: #2f6b3a;
  --err: #a32020;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(30, 20, 10, .10), 0 6px 18px rgba(30, 20, 10, .05);

  /* The gutter <main> leaves between the page edge and its content. Named
     because a sticky bar has to bleed back out to that edge, and a second
     copy of the clamp() would drift out of sync the first time either one
     gets touched. */
  --pad: clamp(1rem, 3vw, 1.75rem);

  /* Overwritten from base.html with the topbar's measured height. The topbar
     is itself sticky at top:0, so anything else that sticks has to land
     underneath it — and its height changes when the nav wraps, so a
     hard-coded number would be wrong on exactly the narrow screens that need
     this most. The value here is only what the first paint uses. */
  --topbar-h: 56px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #17150f;
    --surface: #201d17;
    --surface-2: #2a261e;
    --text: #ece5da;
    --muted: #a1978a;
    --line: #37322a;
    --accent: #e8894f;
    --accent-soft: #35251a;
    --flag: #e0ac4a;
    --flag-bg: #33280f;
    --ok: #6fbc7e;
    --err: #e57373;
    --shadow: 0 1px 3px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .25);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 1rem;
  padding: .6rem clamp(.75rem, 3vw, 1.5rem);
  padding-top: max(.6rem, env(safe-area-inset-top));
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.brand { font-weight: 700; font-size: 1.05rem; color: var(--text); white-space: nowrap; }
.brand span { display: none; }
@media (min-width: 560px) { .brand span { display: inline; } }
.topbar { flex-wrap: nowrap; }
.topbar nav {
  display: flex; gap: .35rem; margin-left: auto; flex-wrap: wrap;
  justify-content: flex-end; min-width: 0;
}
.topbar nav a {
  padding: .45rem .7rem; border-radius: 999px; color: var(--muted);
  font-size: .9rem; font-weight: 600;
}
.topbar nav a:hover { background: var(--surface-2); text-decoration: none; }
.topbar nav a.on { background: var(--accent-soft); color: var(--accent); }
.badge {
  display: inline-block; margin-left: .35rem; padding: 0 .38rem;
  background: var(--accent); color: #fff; border-radius: 999px;
  font-size: .72rem; font-weight: 700; line-height: 1.5;
}

main {
  max-width: 1200px; margin: 0 auto;
  padding: var(--pad);
  padding-bottom: 4rem;
}

h1 { font-size: clamp(1.4rem, 4vw, 1.9rem); margin: 0 0 .2rem; line-height: 1.2; }
h2 { font-size: 1.15rem; margin: 1.6rem 0 .6rem; }
h3 { font-size: 1rem; margin: 0 0 .4rem; }
.sub { color: var(--muted); margin: 0 0 1.2rem; }
.muted { color: var(--muted); }
.small { font-size: .85rem; }

/* ---------- controls ---------- */
input[type=text], input[type=search], input[type=number], select, textarea {
  width: 100%; padding: .55rem .7rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); border-radius: 8px;
  font: inherit; font-size: 16px; /* 16px stops iOS zooming on focus */
}
textarea { resize: vertical; min-height: 4.5rem; line-height: 1.5; }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent); outline-offset: -1px; border-color: transparent;
}
label { font-size: .8rem; font-weight: 600; color: var(--muted); display: block; margin-bottom: .2rem; }

.btn {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .6rem 1rem; border-radius: 8px; border: 1px solid var(--line);
  background: var(--surface); color: var(--text);
  font: inherit; font-weight: 600; font-size: .92rem; cursor: pointer;
  min-height: 42px;
}
.btn:hover { background: var(--surface-2); text-decoration: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 85%, #000); }
.btn-ghost { background: transparent; }
.btn-sm { padding: .3rem .6rem; min-height: 32px; font-size: .82rem; }
.btn-danger { color: var(--err); }
.row { display: flex; gap: .6rem; flex-wrap: wrap; align-items: flex-end; }

/* ---------- cards ---------- */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem; box-shadow: var(--shadow);
}

.grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
/* The whole card is clickable via a stretched link on the title, with the
   photo layered above it so the photo can do something different. Nested <a>
   elements are invalid HTML, which is why this is done with a pseudo-element
   rather than by wrapping the card. */
.recipe-card {
  overflow: hidden; padding: 0; display: flex; flex-direction: column;
  position: relative;
}
.recipe-card:hover { border-color: var(--accent); }
.stretched::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
}
.recipe-card img {
  width: 100%; aspect-ratio: 4/3; object-fit: cover;
  background: var(--surface-2); display: block;
}
.recipe-card .noimg {
  width: 100%; aspect-ratio: 4/3; background: var(--surface-2);
  display: grid; place-items: center; font-size: 2rem; opacity: .35;
}
.recipe-card .body { padding: .75rem .85rem 1rem; }
.recipe-card h3 { line-height: 1.3; }
.recipe-card a { color: var(--text); }
.chips { display: flex; gap: .3rem; flex-wrap: wrap; margin-top: .5rem; }
.chip {
  font-size: .72rem; padding: .12rem .5rem; border-radius: 999px;
  background: var(--surface-2); color: var(--muted); font-weight: 600;
}
.chip.warn { background: var(--flag-bg); color: var(--flag); }
.chip.ok { background: var(--accent-soft); color: var(--accent); }

/* ---------- flags ---------- */
.flag {
  background: var(--flag-bg); border-left: 3px solid var(--flag);
  padding: .5rem .7rem; border-radius: 0 8px 8px 0;
  font-size: .85rem; color: var(--flag); margin: .4rem 0;
}
.flag strong { color: var(--flag); }
.field-flagged input, .field-flagged select { border-color: var(--flag); background: var(--flag-bg); }

.empty { text-align: center; padding: 3.5rem 1rem; color: var(--muted); }
.empty .big { font-size: 2.5rem; display: block; margin-bottom: .5rem; opacity: .5; }

/* ---------- review: side by side ---------- */
.review-wrap { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 940px) {
  .review-wrap { grid-template-columns: minmax(0, 1fr) minmax(320px, 42%); align-items: start; }
  .photo-pane { position: sticky; top: 4.5rem; max-height: calc(100vh - 6rem); overflow: auto; }
}
.photo-pane img {
  width: 100%; border-radius: var(--radius); border: 1px solid var(--line);
  display: block; margin-bottom: .6rem; cursor: zoom-in; background: var(--surface);
}
.photo-pane img.zoomed { cursor: zoom-out; max-width: none; width: 200%; }

.ing-row, .step-row {
  display: grid; gap: .4rem; padding: .6rem; margin-bottom: .5rem;
  background: var(--surface-2); border-radius: 8px; border: 1px solid transparent;
}
.ing-row { grid-template-columns: 4.5rem 5rem 1fr; align-items: end; }
.ing-row .wide { grid-column: 1 / -1; }

/* Compact the row on anything wider than a phone. Reviewing 400 recipes means
   this screen's vertical rhythm is the difference between a chore and a habit. */
@media (min-width: 700px) {
  .ing-row {
    grid-template-columns: 4.5rem 5rem minmax(0, 1.2fr) minmax(0, 1fr);
    padding: .5rem .6rem; gap: .35rem;
  }
  .ing-row .prep { grid-column: auto; }
  .ing-row .maps { grid-column: 1 / -1; }
  /* the verbatim source text and the row controls share one line */
  .ing-row .raw {
    grid-column: 1 / 3; align-self: center; margin: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ing-row .ing-actions { grid-column: 3 / -1; align-self: center; }
}
.ing-row .raw {
  grid-column: 1 / -1; font-size: .78rem; color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: .2rem .4rem; background: var(--surface); border-radius: 4px;
  overflow-wrap: anywhere;
}
.ing-row.unmatched { border-color: var(--flag); }
.ing-actions { display: flex; gap: .4rem; align-items: center; grid-column: 1 / -1; }
.inline-check { display: flex; align-items: center; gap: .3rem; font-size: .8rem; color: var(--muted); font-weight: 600; }
.inline-check input { width: auto; }

.sticky-actions {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; gap: .6rem; flex-wrap: wrap;
  padding: .75rem 0; padding-bottom: max(.75rem, env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 72%, transparent);
}

/* ---------- recipe view ---------- */
.recipe-head { display: grid; gap: 1.25rem; margin-bottom: 1.5rem; }
@media (min-width: 720px) { .recipe-head { grid-template-columns: 1fr 300px; } }
.recipe-head img { width: 100%; border-radius: var(--radius); border: 1px solid var(--line); }
.meta { display: flex; gap: 1.25rem; flex-wrap: wrap; margin: .75rem 0; }
.meta div { font-size: .85rem; }
.meta b { display: block; font-size: 1.05rem; font-weight: 700; }

.cook-cols { display: grid; gap: 1.5rem; }
@media (min-width: 780px) { .cook-cols { grid-template-columns: 290px 1fr; align-items: start; } }
.ing-list { list-style: none; padding: 0; margin: 0; }
.ing-list li {
  padding: .55rem .2rem; border-bottom: 1px solid var(--line);
  display: flex; gap: .55rem; align-items: baseline; cursor: pointer;
}
.ing-list li.done { opacity: .4; text-decoration: line-through; }
.ing-list .q { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.ing-list .staple { color: var(--muted); font-size: .9em; }

.step { margin-bottom: 1.4rem; padding-bottom: 1.4rem; border-bottom: 1px solid var(--line); }
.step:last-child { border-bottom: 0; }
.step-n {
  display: inline-grid; place-items: center; width: 1.7rem; height: 1.7rem;
  border-radius: 50%; background: var(--accent); color: #fff;
  font-size: .85rem; font-weight: 700; margin-right: .5rem;
}
.step ul { margin: .5rem 0 0; padding-left: 1.2rem; }
.step li { margin-bottom: .45rem; }
.temp-check {
  display: inline-block; margin-top: .5rem; padding: .3rem .6rem;
  background: var(--accent-soft); color: var(--accent);
  border-radius: 6px; font-size: .82rem; font-weight: 700;
}

/* ---------- shopping list ---------- */
.aisle { margin-bottom: 1.25rem; }
.aisle h3 {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); border-bottom: 1px solid var(--line); padding-bottom: .3rem;
}
.shop-item { padding: .5rem .2rem; border-bottom: 1px solid var(--line); cursor: pointer; }
.shop-item.done { opacity: .4; text-decoration: line-through; }
.shop-item .why { font-size: .76rem; color: var(--muted); }

.pick { display: flex; align-items: center; gap: .5rem; padding: .45rem .2rem;
        border-bottom: 1px solid var(--line); }
.pick input { width: auto; }

table.jobs { width: 100%; border-collapse: collapse; font-size: .9rem; }
table.jobs th { text-align: left; font-size: .75rem; text-transform: uppercase;
                letter-spacing: .05em; color: var(--muted); padding: .4rem .5rem; }
table.jobs td { padding: .55rem .5rem; border-top: 1px solid var(--line); vertical-align: middle; }
.thumbs { display: flex; gap: .25rem; }
.thumbs img { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; border: 1px solid var(--line); }
.state { font-size: .72rem; font-weight: 700; padding: .15rem .45rem; border-radius: 999px;
         background: var(--surface-2); color: var(--muted); white-space: nowrap; }
.state.awaiting_review { background: var(--flag-bg); color: var(--flag); }
.state.failed { background: color-mix(in srgb, var(--err) 15%, transparent); color: var(--err); }
.state.extracting, .state.pending { background: var(--accent-soft); color: var(--accent); }

/* ---------- confirm the split ---------- */
.page-row {
  display: flex; gap: .85rem; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .7rem; box-shadow: var(--shadow);
}
.page-row img {
  width: 110px; flex: 0 0 110px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--surface-2); cursor: zoom-in; object-fit: cover; aspect-ratio: 3/4;
}
.page-row img.zoomed {
  position: fixed; inset: 3vh 3vw; width: auto; height: 94vh; z-index: 200;
  object-fit: contain; background: var(--surface); cursor: zoom-out;
  box-shadow: 0 10px 60px rgba(0,0,0,.5); aspect-ratio: auto;
}
.page-row.is-excluded { opacity: .45; }
.page-row.is-excluded img { filter: grayscale(1); }
.page-meta { min-width: 0; }
.page-n { font-weight: 700; }

/* The divider IS the control — tapping it joins or splits the two pages. */
.divider {
  display: flex; align-items: center; gap: .6rem;
  padding: .5rem .25rem; margin: .15rem 0; cursor: pointer;
  user-select: none; border-radius: 8px;
}
.divider:hover { background: var(--surface-2); }
.divider-line { flex: 1; height: 2px; border-radius: 2px; background: var(--line); }
.divider-label {
  font-size: .74rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
}
.divider.is-split .divider-line {
  background: var(--accent);
  height: 3px;
}
.divider.is-split .divider-label { color: var(--accent); }
.divider:not(.is-split) .divider-line {
  background: repeating-linear-gradient(90deg, var(--line) 0 6px, transparent 6px 12px);
}

/* ===========================================================================
   PRINT
   Daniell prints recipes to cook from and lists to shop with. Two rules drive
   everything below: no photographs (ink), and it has to be readable on a
   counter at arm's length.

   The approach is to redefine the design tokens for paper rather than bolt on
   a parallel stylesheet — every component then prints correctly by default and
   only the genuinely screen-specific pieces need hiding.
   =========================================================================== */
@media print {
  :root {
    --bg: #fff;
    --surface: #fff;
    --surface-2: #fff;
    --text: #000;
    --muted: #444;
    --line: #999;
    --accent: #000;
    --accent-soft: #fff;
    --flag: #000;
    --flag-bg: #fff;
    --shadow: none;
    --radius: 0;
  }

  @page { margin: 14mm 15mm; }

  html, body { background: #fff !important; color: #000 !important; }

  body {
    /* A serif reads better in print than the screen sans, and the whole point
       of a printed recipe is being read, not scrolled. */
    font-family: Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
    font-size: 10.5pt;
    line-height: 1.45;
  }

  main { max-width: none; margin: 0; padding: 0; }

  /* Screen furniture that means nothing on paper. */
  .topbar, .sticky-actions, .btn, button, form.row, .chips,
  .review-wrap .photo-pane, .empty, nav,
  .print-hide { display: none !important; }

  /* "shared x2" is a planning signal, not a shopping one — in the aisle it is
     just something else to read past. */
  .chip { display: none !important; }

  /* One horizontal meta line reads better than stacked number/label pairs. */
  .meta b { display: inline; margin-right: 3pt; }
  .meta div + div::before { content: "·"; margin: 0 6pt 0 0; color: #999; }

  /* NO PHOTOGRAPHS. This is the ink rule and it is deliberately blunt. */
  img, .noimg, .thumbs, .page-row img, .recipe-head img,
  .photo-pane, details.photos { display: none !important; }

  .card {
    border: none; box-shadow: none; padding: 0; background: #fff;
  }

  a { color: #000; text-decoration: none; }
  /* A recipe printed from a URL should carry its source, but inline link
     targets in the middle of a step would be noise. Only the source line. */
  .print-url::after { content: " (" attr(href) ")"; font-size: 8.5pt; color: #444; }

  h1 { font-size: 20pt; line-height: 1.15; margin: 0 0 2pt; }
  h2 { font-size: 11pt; margin: 14pt 0 5pt; break-after: avoid;
       text-transform: uppercase; letter-spacing: .08em; }
  h3 { font-size: 10.5pt; break-after: avoid; }
  p { orphans: 3; widows: 3; }

  .print-only { display: block !important; }
  .sub { color: #333; margin: 0 0 6pt; font-style: italic; }

  /* ---------- recipe ---------- */
  .recipe-head {
    display: block; margin: 0 0 8pt;
    border-bottom: 1.5pt solid #000; padding-bottom: 6pt;
  }
  .meta { gap: 14pt; margin: 6pt 0 0; }
  .meta div { font-size: 9pt; color: #333; }
  .meta b { font-size: 11pt; color: #000; }

  /* Ingredients beside the method — the classic recipe-card shape, and it
     keeps most recipes on a single sheet. */
  .cook-cols {
    display: grid; grid-template-columns: 33% 1fr; gap: 0 14mm;
    align-items: start;
  }

  .ing-list li {
    border-bottom: none; padding: 1.5pt 0; display: flex; gap: 6pt;
    break-inside: avoid; opacity: 1 !important; text-decoration: none !important;
  }
  /* A real box to tick while cooking, drawn rather than a glyph so it renders
     identically on every printer. */
  .ing-list li::before {
    content: ""; flex: 0 0 auto; display: inline-block;
    width: 8pt; height: 8pt; margin-top: 3pt;
    border: 0.75pt solid #000;
  }
  .ing-list .q { font-weight: 700; }
  .ing-list .staple { color: #333; }

  .step {
    border-bottom: none; margin-bottom: 8pt; padding-bottom: 0;
    break-inside: avoid;
  }
  .step-n {
    background: #fff; color: #000; border: 1pt solid #000;
    width: 14pt; height: 14pt; font-size: 8.5pt;
  }
  .step ul { margin: 3pt 0 0; padding-left: 12pt; }
  .step li { margin-bottom: 3pt; }
  .temp-check {
    background: #fff; color: #000; border: 0.75pt solid #000;
    padding: 1pt 4pt; font-size: 8.5pt;
  }

  /* details/summary is forced open by the beforeprint handler in base.html */
  details > summary { list-style: none; font-weight: 700; }
  details > summary::-webkit-details-marker { display: none; }

  /* ---------- shopping list ---------- */
  /* Two columns so a week's shop lands on one sheet. */
  .shopping-print { columns: 2; column-gap: 12mm; }
  .aisle { break-inside: avoid; margin-bottom: 8pt; }
  .aisle h3 {
    font-size: 9pt; border-bottom: 0.75pt solid #000; padding-bottom: 2pt;
    margin-bottom: 3pt; color: #000;
  }
  .shop-item {
    border-bottom: none; padding: 2pt 0; display: flex; gap: 6pt;
    break-inside: avoid; opacity: 1 !important; text-decoration: none !important;
  }
  .shop-item::before {
    content: ""; flex: 0 0 auto; display: inline-block;
    width: 9pt; height: 9pt; margin-top: 2.5pt;
    border: 0.75pt solid #000;
  }
  /* Which recipe an item came from. Hidden here originally, on the reasoning
     that a shopping list should be scannable — but standing in the aisle
     looking at "Diced tomatoes, 18.5 oz" and wondering whether that was for
     the tagine or the curry is exactly when you need it, and there is nobody
     to ask. It stays small and grey so the item name still leads the eye.

     Indented past the tick box so the names form one clean column: 9pt box
     plus the 6pt flex gap. */
  .shop-item { flex-wrap: wrap; }
  .shop-item .why.print-only {
    /* padding, not margin: the flex basis is 100% of the row, and a 15pt
       margin on top of that pushed the text past the column edge — visible as
       clipped recipe names in the right-hand column. Padding is inside the
       box (box-sizing is border-box globally), so it indents without
       overflowing. Aligned under the name: 9pt tick box plus the 6pt gap. */
    display: block !important; flex: 1 0 100%;
    padding-left: 15pt; margin: 0.5pt 0 0;
    font-size: 7.75pt; line-height: 1.3; color: #555; font-style: italic;
  }
  .plan-sidebar, .plan-suggestions { display: none !important; }
  .cook-cols.plan-cols { display: block; }

  .print-footer {
    display: block !important;
    margin-top: 10pt; padding-top: 4pt; border-top: 0.5pt solid #999;
    font-size: 8pt; color: #444;
  }
}

/* Print-only content is hidden on screen. */
.print-only, .print-footer { display: none; }

/* ---------- lightbox: read the original scan ----------
   The source photos are kept forever precisely so they can be consulted, which
   is worth nothing if they can only be seen at thumbnail size. Fit-to-screen by
   default, tap to go to 100% and pan — small print on a recipe card needs the
   second one. */
.lb-open { overflow: hidden; }          /* stop the page scrolling behind it */

.lightbox {
  position: fixed; inset: 0; z-index: 300;
  display: none; flex-direction: column;
  /* Opaque, not translucent: at 94% the page's own top bar still showed
     through and sat behind the lightbox controls. */
  background: #0c0906;
}
.lightbox.on { display: flex; }

.lb-bar {
  display: flex; align-items: center; gap: .75rem;
  padding: .6rem .9rem;
  padding-top: max(.6rem, env(safe-area-inset-top));
  color: #f2ece4; font-size: .9rem; font-weight: 600;
  background: rgba(0, 0, 0, .35);
}
.lb-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-count { margin-left: auto; opacity: .75; font-variant-numeric: tabular-nums; white-space: nowrap; }
.lb-btn {
  flex: 0 0 auto; display: inline-grid; place-items: center;
  width: 40px; height: 40px; border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(255, 255, 255, .08); color: #f2ece4;
  font-size: 1.1rem; cursor: pointer; line-height: 1;
}
.lb-btn:hover { background: rgba(255, 255, 255, .18); }
.lb-btn[disabled] { opacity: .3; cursor: default; }

.lb-stage {
  position: relative;
  flex: 1; min-height: 0; overflow: auto;
  /* Flex, not grid: a grid row sizes to its content, so max-height:100% on the
     image resolves against a height the image itself determines and constrains
     nothing. In a flex container with a definite height it works.

     Centring is done with margin:auto on the image, NOT justify/align-content.
     Centring properties push an overflowing child into negative scroll space,
     which makes the left and top of a zoomed image permanently unreachable —
     you can scroll right but never back past the start. margin:auto centres
     while it fits and collapses to zero once it doesn't. */
  display: flex;
  padding: .5rem;
  -webkit-overflow-scrolling: touch;
}
.lb-stage img {
  margin: auto;                      /* see the note above — not justify-content */
  max-width: 100%; max-height: 100%; flex: 0 0 auto;
  object-fit: contain; cursor: zoom-in;
  background: #fff; border-radius: 4px;
  display: block;
}
/* Pin to the top-left so every part is reachable. align-self matters: a flex
   item with auto cross-axis margins isn't stretched, but setting margin:0 here
   re-enables the default `stretch`, which forces the image's HEIGHT to the
   container and lets object-fit letterbox inside it — the picture then has no
   vertical overflow to pan through at all. */
.lb-stage.zoomed img { margin: 0; align-self: flex-start; }
/* Width is set by the script, which walks a few zoom steps; the rule only has
   to get out of the way of the fitted constraints. */
.lb-stage.zoomed img {
  max-width: none; max-height: none;
  cursor: grab;                      /* drag to pan; a plain click steps the zoom */
  user-select: none; -webkit-user-drag: none;
}
.lb-stage.dragging img { cursor: grabbing; }


.lb-foot {
  display: flex; align-items: center; justify-content: center; gap: .75rem;
  padding: .6rem;
  padding-bottom: max(.6rem, env(safe-area-inset-bottom));
  background: rgba(0, 0, 0, .35);
}
.lb-foot .hint {
  color: #cfc6ba; font-size: .78rem; opacity: .85;
  text-align: center; line-height: 1.4;
}

/* Page chevrons sit over the edges of the image. They exist for a mouse — on
   a tablet the swipe does the same job — so they are large, out of the way of
   the middle (which closes), and hidden rather than disabled at the ends. */
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 2; width: 48px; height: 72px;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, .2);
  background: rgba(0, 0, 0, .45); color: #f2ece4;
  font-size: 1.9rem; line-height: 1; cursor: pointer;
  border-radius: 10px;
}
.lb-nav:hover { background: rgba(0, 0, 0, .7); }
.lb-nav.prev { left: .5rem; }
.lb-nav.next { right: .5rem; }
.lb-stage.zoomed .lb-nav { display: none; }
@media (max-width: 560px) { .lb-nav { width: 40px; height: 60px; font-size: 1.5rem; } }

/* A thumbnail that opens the lightbox says so. */
.zoomable { position: relative; display: block; }
.zoomable .zoom-badge {
  position: absolute; right: .4rem; bottom: .4rem;
  background: rgba(0, 0, 0, .55); color: #fff;
  border-radius: 6px; padding: .1rem .35rem;
  font-size: .7rem; font-weight: 700; line-height: 1.5;
  pointer-events: none;
}

@media print { .lightbox { display: none !important; } }

/* Naming a new canonical ingredient, revealed from the "Maps to" dropdown when
   nothing in the list fits. Hidden until asked for — most rows never need it. */
/* An author `display` beats the UA stylesheet's [hidden] rule, so without this
   the panel shows on every row despite the attribute. */
.new-canon[hidden] { display: none; }
.new-canon {
  grid-column: 1 / -1;
  display: grid; gap: .5rem;
  margin-top: .4rem; padding: .6rem;
  border: 1px dashed var(--accent); border-radius: 8px;
  background: var(--accent-soft);
}
@media (min-width: 700px) {
  .new-canon { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: end; }
  .new-canon .inline-check, .new-canon p { grid-column: 1 / -1; }
}

/* ---------- invites ---------- */
.code-block {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem; font-weight: 700; letter-spacing: .08em;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 8px; padding: .6rem .8rem;
  white-space: pre-wrap; word-break: break-word; user-select: all;
}

/* ---------- regroup: picking which recipes share pages ---------- */
.pick {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .55rem .3rem; border-radius: 8px; cursor: pointer;
  font-size: .95rem; font-weight: 400; color: var(--text);
  margin-bottom: 0;
}
.pick:hover { background: var(--surface-2); }
.pick input { margin-top: .25rem; width: 18px; height: 18px; flex: none; }
.pick .chip { margin-left: .4rem; }

/* ---------- account menu ----------
   Sign out lives behind this rather than in the nav row: mis-tapping a nav
   link costs a page load, mis-tapping sign out costs your session. */
/* Last child of the header, never inside <nav> — see the comment in base.html.
   The divider and the gap are the point: this is not another nav link. */
.account {
  position: relative; flex: none;
  margin-left: .6rem; padding-left: .7rem;
  border-left: 1px solid var(--line);
}
.account > summary {
  display: flex; align-items: center; gap: .4rem;
  padding: .3rem .55rem .3rem .3rem; border-radius: 999px;
  cursor: pointer; list-style: none; user-select: none;
  border: 1px solid var(--line); background: var(--surface);
}
.account > summary::-webkit-details-marker { display: none; }
.account > summary:hover { background: var(--surface-2); }
.account[open] > summary { background: var(--surface-2); }
.account .avatar {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  background: var(--accent); color: #fff;
  display: grid; place-items: center;
  font-size: .8rem; font-weight: 700; line-height: 1;
}
.account .who {
  font-size: .85rem; font-weight: 600; color: var(--muted);
  max-width: 8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 420px) { .account .who { display: none; } }
.account .menu {
  position: absolute; right: 0; top: calc(100% + .4rem); z-index: 60;
  /* Never wider than the screen it has to open on. */
  width: max-content; min-width: 200px;
  max-width: min(260px, calc(100vw - 1.5rem));
  padding: .7rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.account .menu .head { margin-bottom: .6rem; }

/* ---------- usage sparkline ----------
   Bars rather than a line: the data is a count per day, and a line implies a
   continuous quantity that was sampled. Every day gets a bar including the
   zeros, so a fortnight of silence looks like a fortnight of silence instead
   of the chart quietly closing the gap over it. */
.spark {
  display: flex; align-items: flex-end; gap: 1px;
  height: 28px; min-width: 110px; max-width: 220px;
  margin-bottom: .15rem;
}
.spark i {
  flex: 1 1 0; min-width: 2px; border-radius: 1px;
  background: var(--line);          /* a day with nothing on it */
}
.spark i.on { background: var(--accent); }
.usage td { vertical-align: top; }

/* Four columns will not fit a phone — every cell wraps to three lines and the
   table becomes harder to read than the numbers are worth. Below this the
   last-seen column folds into the line under the person's name, and the
   first-sign-in date goes: it is context, not the answer. */
.only-narrow { display: none; }
@media (max-width: 560px) {
  .only-narrow { display: inline; }
  .usage .col-seen, .usage .since { display: none; }
  .spark { min-width: 90px; }
}

/* ---------- library: the controls that stay put ----------
   With several hundred cards, scrolling to the bottom used to take the search
   box with it, so narrowing the list meant scrolling all the way back up
   first. The search form and the view toggles ride in one sticky bar that
   parks directly under the topbar; only the recipes move.

   Two things this needs to get right:
   - It has to be opaque. The topbar can get away with a blur because it is
     mostly a strip of links; a card sliding half-visible behind a search
     field reads as a rendering bug.
   - It has to bleed out to the page edges. <main> has a gutter, and without
     the negative margin the tiles pass through the gap on either side. */
.libbar {
  position: sticky; top: var(--topbar-h);
  /* Under the topbar (50) and the account menu (60), over the cards. */
  z-index: 30;
  margin: 0 calc(-1 * var(--pad)) 1.1rem;
  padding: .8rem var(--pad) .7rem;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
/* The two children were separately spaced when they were siblings of the
   grid. Inside the bar the bar owns the spacing. */
.libbar form.row { margin: 0 0 .55rem; }
.libbar .viewbar { margin: 0; }

/* A phone in landscape has ~370px of viewport. Sticking a topbar plus a
   wrapped search form plus two segmented controls to the top of that leaves
   almost nothing for the recipes, which is the opposite of helpful. Below
   this height the bar just scrolls with the page. */
@media (max-height: 520px) {
  .libbar { position: static; }
}
/* Every row this bar occupies is a row of recipes you cannot see, and on a
   phone the topbar above it is already two or three lines of wrapped nav. The
   toggles and the count fit on one line here if the padding gives a little —
   measured down to a 320px screen, which is the narrowest phone still in use,
   with no horizontal overflow. */
@media (max-width: 560px) {
  .libbar { padding-top: .6rem; padding-bottom: .5rem; }
  .libbar form.row { margin-bottom: .45rem; }
  .libbar .viewbar { flex-wrap: nowrap; gap: .35rem; }
  .libbar .segmented a { padding: .4rem .55rem; font-size: .8rem; }
  .libbar #shown { white-space: nowrap; font-size: .78rem; }
}

/* ---------- library: view toggle ---------- */
.viewbar { align-items: center; gap: .5rem; margin: -0.6rem 0 1.1rem; }
.segmented {
  display: inline-flex; border: 1px solid var(--line); border-radius: 999px;
  overflow: hidden; background: var(--surface);
}
.segmented a {
  padding: .4rem .8rem; font-size: .85rem; font-weight: 600;
  color: var(--muted); min-height: 38px; display: inline-flex;
  align-items: center; gap: .35rem;
}
.segmented a:hover { background: var(--surface-2); text-decoration: none; }
.segmented a.on { background: var(--accent); color: #fff; }
.segmented a + a { border-left: 1px solid var(--line); }
.segmented a.on + a, .segmented a:has(+ a.on) { border-left-color: transparent; }

/* ---------- library: list view ----------
   The point is density. A card is ~280px tall, so three hundred recipes is
   over a hundred screens on a phone; these rows are ~52px, which is about
   twenty. Still a 44px+ tap target, because this gets used with wet hands. */
.recipe-list { list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); overflow: hidden; }
.recipe-list li {
  display: flex; align-items: center; gap: .5rem;
  border-bottom: 1px solid var(--line);
}
.recipe-list li:last-child { border-bottom: 0; }
.recipe-list li:hover { background: var(--surface-2); }
.recipe-list a {
  /* Plain block, not a flex container. As flex with wrap, the name and the
     subtitle landed on separate flex lines and the row grew by 16px with the
     two texts visibly off each other's baseline. They are one line of text —
     so let them be one line of text. */
  flex: 1; display: block; padding: .6rem 1rem; min-height: 44px;
  color: var(--text); line-height: 1.4;
}
.recipe-list a:hover { text-decoration: none; }
.recipe-list .name { font-weight: 600; }
.recipe-list .sub { color: var(--muted); font-size: .88rem; margin-left: .4rem; }
.recipe-list .chip { margin: 0 1rem 0 0; flex: none; }

/* Live filtering hides rows and cards with [hidden]. An author display rule
   beats the user-agent stylesheet, so both need saying explicitly — the same
   trap that hid the new-ingredient panel on the review screen. */
.recipe-list li[hidden], .grid .recipe-card[hidden] { display: none; }

/* ---------- cook: the shopping list, backwards ---------- */
.cook-picker { position: sticky; top: 4.5rem; max-height: calc(100vh - 6rem);
               overflow: auto; }
@media (max-width: 860px) {
  .cook-picker { position: static; max-height: 22rem; }
}
.pantry-list { margin-top: .4rem; }
.pantry-aisle h3 {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: .9rem 0 .2rem;
  border-bottom: 1px solid var(--line); padding-bottom: .25rem;
}
.pantry-item { border-bottom: 0; padding: .35rem .2rem; }
.pantry-item[hidden], .pantry-aisle[hidden] { display: none; }
/* How many recipes the ingredient appears in — a hint at which ticks are
   worth making, without turning the list into a table. */
.pantry-item .small { margin-left: .3rem; opacity: .55; }

.cook-results { list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); overflow: hidden; }
.cook-results li {
  display: flex; gap: .6rem; align-items: center;
  padding: .6rem 1rem; border-bottom: 1px solid var(--line);
}
.cook-results li:last-child { border-bottom: 0; }
.cook-results li.can { background: color-mix(in srgb, var(--ok) 8%, transparent); }
.cook-results .cook-main { flex: 1; min-width: 0; }
.cook-results a { color: var(--text); display: block; }
.cook-results a:hover { text-decoration: none; color: var(--accent); }
.cook-why { font-size: .82rem; color: var(--muted); margin-top: .15rem; }
.cook-can { color: var(--ok); font-weight: 700; }
.cook-near { color: var(--flag); font-weight: 600; }
.cook-unknown { opacity: .7; font-style: italic; }
.cook-results form { margin: 0; flex: none; }

/* --- Nutrition estimate ------------------------------------------------ */
/* Deliberately quieter than the ingredient list beside it. The numbers are
   estimates; typography that shouted would be making a claim the arithmetic
   can't back. */
.nutri {
  margin-top: 1.4rem; padding-top: 1.1rem; border-top: 1px solid var(--line);
}
.nutri-head { margin: .9rem 0 .7rem; }
.nutri-kcal { font-size: 1.15rem; }
.nutri-kcal b { font-size: 1.5rem; font-weight: 700; }

.nutri-macros {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: .3rem .9rem; margin: .8rem 0;
}
.nutri-macros div {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: .85rem; padding: .25rem 0;
  border-bottom: 1px dotted var(--line);
}
.nutri-macros span { color: var(--muted); }

.nutri-groups { margin: 1rem 0 .6rem; }
.nutri-groups h4 {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 0 0 .4rem; font-weight: 600;
}
.nutri-bar {
  display: flex; height: 10px; border-radius: 999px; overflow: hidden;
  background: var(--surface-2);
}
.nutri-bar span { display: block; height: 100%; }
.nutri-key {
  list-style: none; padding: 0; margin: .5rem 0 0;
  display: flex; flex-wrap: wrap; gap: .2rem .8rem; font-size: .78rem;
}
.nutri-key li { display: flex; align-items: center; gap: .3rem; }
.nutri-key i { width: 9px; height: 9px; border-radius: 2px; display: block; }

/* One hue per food group, readable in both themes. */
.g-protein   { background: #b8532c; }
.g-vegetable { background: #4f7d3f; }
.g-grain     { background: #c08a2e; }
.g-fruit     { background: #a2447a; }
.g-dairy     { background: #4a7fa0; }
.g-fat       { background: #8a7a4a; }
.g-sweetener { background: #9a5aa8; }
.g-seasoning { background: #7a736a; }
.g-other     { background: #9a938a; }

.nutri-warn {
  background: var(--flag-bg); border-left: 3px solid var(--flag);
  padding: .5rem .7rem; border-radius: 0 8px 8px 0;
  color: var(--flag); margin: .7rem 0;
}
.nutri-warn b { color: var(--flag); }
.nutri-thin {
  background: var(--surface-2); border-radius: 8px;
  padding: .7rem .8rem; margin: .8rem 0; font-size: .88rem;
}
.nutri-thin p { margin: 0 0 .4rem; }
.nutri-thin p:last-child { margin-bottom: 0; }

.nutri-detail { margin-top: .8rem; color: var(--muted); }
.nutri-detail summary { cursor: pointer; font-weight: 600; }
.nutri-detail ul { padding-left: 1.1rem; margin: .4rem 0; }
.nutri-detail li { margin: .15rem 0; }

/* --- Alternatives from a meal-kit card --------------------------------- */
.alt-pick { margin-top: 1.2rem; }
.alt-pick h3 {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 0 0 .2rem; font-weight: 600;
}
.alt-pick p { margin: 0 0 .5rem; }
.alt-opt { margin: 0 0 .35rem; }
.alt-btn {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 8px; padding: .45rem .6rem; font: inherit; font-size: .85rem;
  color: var(--text);
}
.alt-btn:hover { background: var(--surface-2); }
.alt-btn.on {
  border-color: var(--accent); background: var(--accent-soft);
  color: var(--accent); font-weight: 600;
}
.alt-btn.on .muted { color: var(--accent); opacity: .75; }
span.alt-btn { cursor: default; }

/* --- Submission inbox -------------------------------------------------- */
.inbox-thumb {
  width: 72px; height: 72px; object-fit: cover;
  border-radius: 6px; border: 1px solid var(--line);
}
