/* Kiwi Cookbook — the ONE stylesheet every generated page links.
 *
 * Row 14 · Block 1. Derived from index.html's <style> block (read, never
 * edited): the same A1 tokens, the same Fraunces/DM Sans pairing, the same
 * button and header treatment, trimmed to what a recipe page uses.
 *
 * 🔴 SHARED, NOT INLINED. Scope §4 says "inlined from index.html's style block",
 * and that is what the hand-written pages do — but there is one of those and
 * there will be ~1,200 of these. Inlining ~6 KB into every page costs ~7 MB in
 * the repo, adds 6 KB to every single page load (this file is fetched once and
 * then served from cache), and turns a design tweak into a 1,200-file rewrite.
 * §2.1 rules it shared for exactly that reason: "ONE shared stylesheet, cached —
 * not inlined 1,100 times".
 */

/* ── A1 tokens (index.html, itself from design-tokens_4.ts) ─────────────── */
:root {
  --paper: #FBF7EF;
  --cream: #FDFAF4;
  --surface: #F1EADC;
  --card: #FFFFFF;
  --ink: #2D2A24;
  --text-2: #6B5E4D;
  --muted: #8A8474;
  --border: #E4DCCB;
  --border-strong: #D8D0BD;
  --terra: #C24F25;
  --terra-hover: #a84520;
  /* Block 2 §2.4 — the rest of the app's terracotta ramp (constants/tokens.ts).
   * --terra IS terracotta[400] and --terra-hover IS terracotta[500]; these four
   * are 50/100/200/700, taken verbatim so the web's timing marker is the app's
   * colour rather than something eyeballed near it. */
  --terra-050: #fbeee5;
  --terra-100: #f5dac0;
  --terra-200: #eebe96;
  --terra-300: #e29d68;
  --terra-700: #6a2913;
  --sage: #5C7350;
  --sage-hover: #4d6143;
  --sage-brand: #3a5235;
  --on-sage: #F4F1E6;
  --on-sage-sub: #D5DCCB;
  --gold-text: #996E1B;
  --gold-bg: #F6E8C8;

  --r-chip: 10px;
  --r-btn: 12px;
  --r-card: 14px;
  --r-hero: 18px;
  --r-pill: 999px;

  --serif: 'Fraunces', Georgia, serif;
  --sans: 'DM Sans', system-ui, sans-serif;

  --shadow-card: 0 2px 8px rgba(90, 64, 48, .07);
  --shadow-lift: 0 10px 28px rgba(90, 64, 48, .10);
}

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

body {
  font-family: var(--sans);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.5;
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--terra); text-decoration: none }
a:hover { text-decoration: underline }
a:focus-visible,
button:focus-visible { outline: 2px solid var(--terra); outline-offset: 3px; border-radius: 4px }
img { max-width: 100%; }

h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.15; letter-spacing: -.01em }

.wrap { max-width: 800px; margin: 0 auto; padding: 0 24px }

/* Block 4 §2.1 — 960px, ON RECIPE PAGES ONLY.
 *
 * Hans, on the laptop: "it feels like we're missing something with all that
 * whitespace … it caught me off guard a little as not optimized." At 1440 the
 * page was an 800px column in the middle of the screen with a 335px hole under
 * the hero. The extra 160px is what pays for the two-column recipe body below:
 * a 300px ingredients rail plus a steps column that still keeps a readable
 * measure. It is a class on <main> rather than a change to .wrap because the
 * browse index and the hubs are grids and already have .wrap-wide at 1080. */
.wrap-recipe { max-width: 960px }

/* ── Buttons ───────────────────────────────────────────────────────────── */
.btn {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 15.5px;
  padding: 13px 26px;
  border-radius: var(--r-btn);
  border: 1.5px solid transparent;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .12s;
}
.btn-primary { background: var(--terra); color: var(--paper) }
.btn-primary:hover { background: var(--terra-hover); text-decoration: none }
.btn-secondary { background: var(--card); color: var(--ink); border-color: var(--border-strong) }
.btn-secondary:hover { background: var(--surface); text-decoration: none }
.btn:active { transform: translateY(1px) }

/* ── Header / footer, matching the site ────────────────────────────────── */
header {
  background: rgba(251, 247, 239, .92);
  border-bottom: 1px solid var(--border);
  padding: 14px 0;
}
.nav { display: flex; align-items: center; justify-content: space-between; gap: 18px }
.wordmark {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 23px;
  color: var(--sage-brand);
  display: flex;
  align-items: center;
  gap: 9px;
}
.wordmark:hover { text-decoration: none }
.nav-links { display: flex; gap: 20px; list-style: none; font-size: 15px }
.nav-links a { color: var(--text-2) }

footer { border-top: 1px solid var(--border); margin-top: 56px; padding: 32px 0 44px }
.foot { display: flex; flex-direction: column; gap: 14px; align-items: center; text-align: center }
.foot-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 18px;
  list-style: none;
  font-size: 14.5px;
}
.foot-links a { color: var(--text-2) }
footer small { color: var(--muted); font-size: 13px }

/* ── Breadcrumb ────────────────────────────────────────────────────────── */
.crumb {
  font-size: 14px;
  color: var(--muted);
  margin: 22px 0 14px;
}
.crumb a { color: var(--text-2) }
.crumb span[aria-current] { color: var(--ink) }
.crumb .sep { padding: 0 6px; color: var(--border-strong) }

/* ── Recipe head ───────────────────────────────────────────────────────── */
/* Block 2 §2.2 — THE RECIPE HAS TO BE VISIBLE WITHOUT SCROLLING ON A LAPTOP.
 * Before this block the hero was a full-width square: 752×752 inside the 800px
 * wrap, so the entire first screen of every recipe page was the photograph and
 * the title began below the fold. Two columns from 768px up (a capped square on
 * the left, the whole answer on the right); one centred, smaller square below.
 *
 * 🔴 `min(320px, 36%)` keeps BOTH numbers §2.2 gives. The 36% cap is the one
 * that binds today — 36% of the 752px content box is 270.7px — and the 320px
 * ceiling is what the column would grow to if `.wrap` were ever widened past
 * 889px. Writing only the 270.7px result would lose that intent. */
.rhead {
  display: grid;
  gap: 20px;
  margin: 22px 0 0;
  justify-items: center;
}
/* min-width:0 — a grid item's automatic minimum size is its content, and a long
 * unbroken title would otherwise push the column wider than its track. */
.rhead-body { min-width: 0; width: 100% }
.hero {
  width: 100%;
  /* 🔴 `height: auto` IS LOAD-BEARING, not tidiness. The <img> carries
   * width="1024" height="1024" (CLS protection), and a width/height attribute
   * maps to a declaration in the PRESENTATIONAL-HINTS origin. Author CSS beats
   * that origin — but only where author CSS says something. `width: 100%`
   * overrides the width hint; with no author `height`, the HEIGHT hint survives,
   * and `aspect-ratio` applies only when one axis is auto. The card grid shipped
   * without this line and every card image rendered 240px wide by 1024px tall. */
  height: auto;
  max-width: 280px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--r-hero);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
@media (min-width: 768px) {
  .rhead {
    grid-template-columns: min(320px, 36%) minmax(0, 1fr);
    gap: 26px;
    align-items: start;
    justify-items: stretch;
  }
  .hero { max-width: 320px }
}
h1.recipe-title {
  font-size: clamp(27px, 5vw, 38px);
  margin: 22px 0 10px;
}
.rhead h1.recipe-title { margin-top: 0 }
/* time · servings · cuisine — the three facts a reader scans for before the
 * macros strip, and the one line §2.2 asks for that Block 1 never rendered. */
.meta { font-size: 14.5px; color: var(--muted); margin-bottom: 16px }
.meta .sep { padding: 0 7px; color: var(--border-strong) }
/* The description is prose, not part of the answer block — it reads full-width
 * under the two columns rather than squeezed into the right one. */
.rhead + .desc { margin-top: 20px }
/* Block 4 §2.1 — the door LEFT the header, so the note that used to separate it
 * from the macros strip is now the last thing in the column and needs no gap
 * under it. 28px here was 28px of the budget between the hero and step 1. */
.rhead-body .macros-note { margin-bottom: 0 }
.answer {
  font-family: var(--serif);
  font-style: italic;
  font-size: 19px;
  color: var(--text-2);
  margin-bottom: 14px;
}
.desc { font-size: 17px; color: var(--text-2); margin-bottom: 22px }

/* ── Macros strip ──────────────────────────────────────────────────────── */
.macros {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  margin-bottom: 8px;
}
.macro {
  flex: 1 1 25%;
  min-width: 90px;
  padding: 14px 12px;
  text-align: center;
  border-right: 1px solid var(--border);
}
.macro:last-child { border-right: 0 }
.macro b { display: block; font-family: var(--serif); font-size: 21px; font-weight: 600 }
.macro span { font-size: 12.5px; color: var(--muted); text-transform: lowercase }
.macros-note {
  font-family: var(--serif);
  font-style: italic;
  font-size: 13.5px;
  color: var(--muted);
  margin-bottom: 28px;
}

/* ── The door ──────────────────────────────────────────────────────────── */
.door {
  background: var(--sage);
  color: var(--on-sage);
  border-radius: var(--r-card);
  padding: 26px 26px 28px;
  margin: 30px 0;
}
.door h2 {
  font-family: var(--serif);
  font-style: italic;
  font-size: 25px;
  color: var(--on-sage);
  margin-bottom: 10px;
}
.door p { color: var(--on-sage-sub); font-size: 16px; margin-bottom: 18px }
.door .btn-primary { background: var(--terra); color: var(--paper) }

/* ── The door, as a band (Block 4 §2.1) ────────────────────────────────── */
/* The recipe page's variant: one line of copy and the button, on one row, about
 * 72px tall. The box above is ~258px and stays on the browse index and the hubs,
 * where the pitch IS the page. Here it is a threshold between the description
 * and the recipe, and a threshold should not be taller than a hero image. */
.door-band {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 14px 18px 14px 22px;
  margin: 0 0 22px;
}
.door-band .door-band-copy {
  color: var(--on-sage-sub);
  font-size: 15.5px;
  line-height: 1.4;
  margin: 0;
}
.door-band .door-band-copy b { color: var(--on-sage); font-family: var(--serif); font-weight: 600 }
/* `flex: none` so the button keeps its width and the copy takes the wrapping. */
.door-band .btn { flex: none; padding: 10px 18px; font-size: 15px }
/* Below ~560px the row becomes two lines — a 200px button beside 80 characters
 * of copy is neither. The band is allowed to grow there; the phone layout is
 * not the one with the step-1 budget. */
@media (max-width: 560px) {
  .door-band { flex-direction: column; align-items: flex-start; gap: 12px; padding: 16px 18px }
  .door-band .btn { align-self: stretch; text-align: center }
}

/* ── The recipe body: ingredients rail + steps (Block 4 §2.1) ──────────── */
/* Under 900px this is a plain block, so the order is the DOM order §2.2 asks
 * for: image → title → description → band → ingredients → steps. */
@media (min-width: 900px) {
  /* 🔴 THE HERO COLUMN AND THE INGREDIENTS RAIL ARE THE SAME 300px TRACK, and
   * that is the actual answer to what Hans saw. The hole under the hero was
   * 335px of empty left column because the image ended at 416 and the header's
   * right column ran to 751. Moving the door out closed most of it; this closes
   * the rest and makes the two left-hand things on the page — the photograph
   * and the ingredients — share one edge all the way down instead of being
   * 320px and 300px wide for no reason a reader could see. `min(320px, 36%)`
   * stays below 900px, where there is no rail to line up with. */
  .wrap-recipe .rhead { grid-template-columns: 300px minmax(0, 1fr) }
  .wrap-recipe .hero { max-width: 300px }

  .rbody {
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 34px;
    /* `start` rather than the default `stretch`: a stretched grid item is as
     * tall as the row, and a sticky element cannot move inside a box that is
     * already the full height of its container. Without this line the rail
     * below is sticky in name only. */
    align-items: start;
  }
  .rcol-ing {
    position: sticky;
    top: 24px;
    /* A 44-line ingredient list is taller than any laptop screen. Capped to the
     * viewport and scrolled INSIDE its own column, so the tail is reachable
     * instead of pinned off-screen while the steps scroll past it. */
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    /* Room for the scrollbar so it never sits on top of the text. */
    padding-right: 6px;
  }
  /* Tighter rows at rail width — 300px is a column, not a page. */
  .rcol-ing ul.ingredients li { padding: 6px 0 6px 20px; font-size: 15.5px }
  .rcol-ing ul.ingredients li::before { top: 14px }
  .rcol-ing .dish-h { font-size: 17px; margin: 16px 0 6px }
  /* The first heading in each column sits at the top of the column; the 34px
   * that separates one section from the next is wrong at the start of a row. */
  .rbody .rcol > .section-h:first-child { margin-top: 0 }
}
/* §2.1 — the steps keep a readable measure however wide the page gets. 75ch of
 * 16.5px text is ~600px; the 44px is the step-number gutter, which is padding
 * rather than text. At today's 960px wrap the steps column is 578px and this
 * rule is slack — it is here so that widening .wrap-recipe again cannot
 * silently turn the steps into 110-character lines. */
.rbody ol.steps { max-width: calc(75ch + 44px) }
.steps-note { font-size: 14px; color: var(--muted); margin: 0 0 4px }
/* The legend's 14px top margin and the first dish heading's 22px are the gaps
 * that separate one block from the PREVIOUS one. At the top of the steps column
 * there is no previous block — the section heading and the note are right above
 * — so both are the wrong number in this one position, and both were spending
 * the budget between the hero and step 1. */
.rcol-steps .ts-legend { margin-top: 8px }
.rcol-steps .dish-h:first-of-type { margin-top: 12px }
/* §2.3 — under the Ingredients heading, at every width. */
.servings-note { font-size: 14px; color: var(--muted); margin: 0 0 6px }
.servings-note a { color: var(--muted); text-decoration: underline }
.servings-note a:hover { color: var(--terra) }

/* ── The ingredient collapse (Block 4 §2.2) ────────────────────────────── */
/* 🔴 `.ing-collapsed` and `.ing-extra` are added by the page's script and are in
 * no file on disk. With scripts off neither class exists, so every line shows
 * and the button stays `hidden`. Both rules are scoped to under 900px, which is
 * how "≥900px never collapses" is true without the script knowing the width. */
.ing-toggle { display: none }
@media (max-width: 899px) {
  .ing-collapsed .ing-extra { display: none }
  .ing-toggle {
    display: block;
    width: 100%;
    margin: 12px 0 4px;
    padding: 11px 14px;
    font-family: var(--sans);
    font-size: 15px;
    font-weight: 500;
    color: var(--ink);
    background: var(--card);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-chip);
    cursor: pointer;
  }
  .ing-toggle:hover { background: var(--surface) }
}

/* ── Ingredients & steps ───────────────────────────────────────────────── */
.section-h {
  font-size: 25px;
  margin: 34px 0 6px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}
.dish-h {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 18.5px;
  margin: 22px 0 10px;
}
.dish-h .role {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  margin-left: 9px;
}
ul.ingredients { list-style: none }
ul.ingredients li {
  padding: 8px 0 8px 22px;
  border-bottom: 1px dashed var(--border);
  position: relative;
  font-size: 16.5px;
}
ul.ingredients li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 17px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--terra);
}
ul.ingredients .note { color: var(--muted) }
ul.ingredients .optional { color: var(--muted); font-style: italic }

ol.steps { list-style: none; counter-reset: step }
ol.steps li {
  counter-increment: step;
  position: relative;
  padding: 0 0 18px 44px;
  font-size: 16.5px;
  line-height: 1.55;
}
ol.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 30px;
  height: 30px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  font-family: var(--serif);
  font-weight: 600;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-2);
}

/* ── Block 2 §2.3/§2.4 — the app's two step treatments, ported ──────────── */

/* The amount highlight. The app's member step row styles a ref span with
 * Palette.cookMode.quantity: Colors.terracotta[400] (#C24F25) at weight 700.
 * #C24F25 is already `--terra` here — the site's tokens and the app's tokens are
 * the same A1 palette — so this is the app's colour, not a near match. */
.amt { color: var(--terra); font-weight: 700 }

/* The timing-sensitive step. The app recolours the step-number circle and does
 * nothing else: background Colors.terracotta[200], number Colors.terracotta[700]
 * (against sage[100]/sage[700] normally). This page's circle is cream rather
 * than sage — Block 1 derived it from index.html, not from the app — so the
 * DELTA is what is mirrored: the circle goes terracotta, in this page's register.
 *
 * 🔴 Colour alone is not a marker (WCAG 1.4.1), and a printed recipe may be
 * greyscale. So the <li> also carries visually-hidden text, the border thickens,
 * and a legend above the list says what the colour means. */
ol.steps li.ts::before {
  background: var(--terra-200);
  border-color: var(--terra-300);
  color: var(--terra-700);
}
.ts-legend {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  color: var(--text-2);
  background: var(--terra-050);
  border: 1px solid var(--terra-200);
  border-radius: var(--r-chip);
  padding: 9px 13px;
  margin: 14px 0 4px;
}
.ts-swatch {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--terra-200);
  border: 1px solid var(--terra-300);
  color: var(--terra-700);
  font-family: var(--serif);
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
}

/* The standard visually-hidden idiom: read by screen readers, occupies no space,
 * and — unlike `display:none` or `visibility:hidden` — is still in the
 * accessibility tree. `clip-path` over the old 1px/clip(rect) hack. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
ol.steps .mins {
  display: inline-block;
  margin-left: 7px;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* ── Related meals ─────────────────────────────────────────────────────── */
/* §2.2 — square thumbnails at ~120px. The track is fluid so the captions keep
 * a readable measure; `max-width` is what pins the IMAGE to 120px, so the
 * thumbnail is exactly 120×120 at every viewport wide enough for the track. */
.related { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 18px }
.related a { display: block; color: var(--ink) }
.related a:hover { text-decoration: none }
.related img {
  width: 100%;
  height: auto; /* see .hero — without it the height="1024" attribute wins */
  max-width: 120px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--r-chip);
  background: var(--surface);
}
.related .rt { font-family: var(--serif); font-weight: 600; font-size: 15.5px; margin-top: 8px; line-height: 1.25 }
.related .rm { font-size: 13px; color: var(--muted); margin-top: 2px }

/* ── Disclaimer ────────────────────────────────────────────────────────── */
.disclaimer {
  margin-top: 36px;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-2);
}
.disclaimer b { color: var(--ink) }

/* ── Browse index & cuisine hubs ───────────────────────────────────────── */
.wrap-wide { max-width: 1080px; margin: 0 auto; padding: 0 24px }
.hub-intro { font-size: 17px; color: var(--text-2); margin: 10px 0 26px; max-width: 640px }
/* Block 4 §2.7 — ONE CONTROL GROUP, not four loose boxes.
 *
 * The four controls were already here and already worked; Hans read the row as
 * cuisine-only. So nothing is added and nothing is taken away — the fields are
 * held in their own row and the match count moved onto the bar's right-hand
 * end, so the whole thing reads as one labelled group with a result beside it
 * rather than a scatter of selects with a stray line of grey text underneath.
 *
 * NO EATING-STYLE FILTER. Hans wants paleo/Whole30/keto for the New Year push
 * and ruled it can wait: "that's fine to hold back because this is public
 * facing". The catalog has no such tags anyway — BUG-284, the tags array is
 * difficulty + cuisine across all 1,219 meals — so the filter would be an empty
 * dropdown, and nothing eating-style goes on the site in this block at all. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  align-items: flex-end;
  justify-content: space-between;
  padding: 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  margin-bottom: 12px;
}
.filter-fields { display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: flex-end }
.filters label { font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 4px }
.filters select,
.filters input {
  font-family: var(--sans);
  font-size: 15px;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-chip);
  background: var(--paper);
  color: var(--ink);
  min-width: 170px;
  /* §2.7 — one box height for all four. A <select> and an <input> do not have
   * the same intrinsic height (the select carries a dropdown arrow), so with
   * the row aligned on its baseline the search field's LABEL sat two pixels
   * below the other three — which is exactly the kind of thing that stops a row
   * reading as one group. */
  box-sizing: border-box;
  height: 38px;
}
/* On the bar's right-hand end now, so it reads as this group's result. */
.filter-count { font-size: 14px; color: var(--muted); margin: 0; padding-bottom: 9px }

/* §2.7 — on a phone the four controls become a 2 × 2 grid and the count drops
 * to its own full-width line underneath, because a 170px select beside a count
 * on a 375px screen is neither. */
@media (max-width: 720px) {
  .filters { flex-direction: column; align-items: stretch; gap: 12px }
  .filter-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 12px }
  .filters select,
  .filters input { min-width: 0; width: 100% }
  .filter-count { padding-bottom: 0 }
}

/* ── §2.6 — the sample callout ─────────────────────────────────────────── */
/* Hans: "put a 'see the full cookbook in the app' callout (maybe under the
 * filters?), so people don't think this is all we have." About 110 of the
 * catalogue's 1,219 dinners are live here and nothing on the page said so —
 * a reader could reasonably have concluded the cookbook WAS the 110.
 *
 * Quieter than the door at the foot of the page: this is a fact about the
 * catalogue with a button after it, not the pitch. */
.sample-callout {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 20px;
  padding: 13px 16px;
  margin-bottom: 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}
.sample-callout p { margin: 0; font-size: 14.5px; color: var(--text-2); max-width: 62em }
.sample-callout .btn { flex: none; padding: 9px 16px; font-size: 14.5px }
@media (max-width: 560px) {
  .sample-callout .btn { align-self: stretch; text-align: center; width: 100% }
}

/* BUG-327 — shown only when the filters have emptied the grid. `hidden` in the
 * markup does the hiding; this is what it looks like once the script reveals it.
 * `[hidden]` is restated because `display: flex` below would otherwise beat the
 * UA stylesheet's `display: none` and the panel would be visible on every page
 * load, which is the classic way this bug ships inverted. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  padding: 44px 24px;
  margin-bottom: 22px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}
.empty-state[hidden] { display: none }
.empty-state p { margin: 0; font-size: 16.5px; color: var(--text-2); max-width: 34em }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 24px; list-style: none }
.grid > li > a { display: block; color: var(--ink) }
.grid > li > a:hover { text-decoration: none }
.grid img {
  width: 100%;
  /* 🔴 THE P1 BUG, one line. See .hero for why the height attribute wins
   * without this: every card on /cookbook/ and on every hub rendered 240px wide
   * and 1024px tall, so the browse grid was a column of full-bleed photographs
   * with a title every thousand pixels. `aspect-ratio` was already here and
   * could not help while the presentational height hint was unopposed. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--r-chip);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.grid .ct { font-family: var(--serif); font-weight: 600; font-size: 16.5px; margin-top: 10px; line-height: 1.25 }
/* Block 4 §2.5 — the catalog description, clamped to two lines.
 *
 * Hans: "the meals should have descriptions and macros displayed." Measured
 * 2026-09-28: every publishable meal has one, 117–200 characters, mean 150 —
 * which at a 215px card is four to six lines, so a clamp is not optional. Two
 * lines is enough to say what the dinner IS without a card growing taller than
 * its own photograph.
 *
 * 🔴 `-webkit-line-clamp` NEEDS ALL FOUR DECLARATIONS or it does nothing, and
 * the two -webkit- ones are the standard, universally-supported spelling rather
 * than a vendor prefix worth dropping. `max-height` is the fallback for a
 * renderer that ignores the clamp: 2 × 1.4 × 13.5px, so such a reader gets two
 * lines cut square instead of six lines of prose pushing the grid apart. */
.grid .cd {
  margin-top: 5px;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--text-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(13.5px * 1.4 * 2);
}
.grid .cm { font-size: 13px; color: var(--muted); margin-top: 6px }

.cuisine-list { display: flex; flex-wrap: wrap; gap: 9px; list-style: none; margin-bottom: 30px }
.cuisine-list a {
  display: inline-block;
  padding: 7px 14px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--border);
  font-size: 14.5px;
  color: var(--text-2);
}
.cuisine-list a:hover { background: var(--surface); text-decoration: none }

/* ── Print (recipes get printed) ───────────────────────────────────────── */
@media print {
  header,
  footer,
  .door,
  .related,
  .related-h,
  .print-link,
  .crumb,
  .filters { display: none !important }

  body { background: #fff; font-size: 11.5pt }
  .wrap { max-width: none; padding: 0 }
  .wrap-recipe { max-width: none }

  /* Block 4 §2.1/§2.2 — A PRINTED RECIPE IS ONE COLUMN, ALL OF IT.
   *
   * The screen layout is two columns with a sticky, scrolling, possibly
   * collapsed ingredient rail, and every one of those three words is wrong on
   * paper: a 300px track wastes half the sheet, `position: sticky` and
   * `overflow-y: auto` together clip a long list to one viewport's worth and
   * print nothing below the cut, and a collapse prints six lines of a
   * twenty-one-line list. So the grid is undone, the rail is un-stuck and
   * un-scrolled, every hidden line comes back, and the button goes. The reader
   * gets the ingredients and then the steps, complete. */
  .rbody { display: block }
  .rcol-ing {
    position: static;
    max-height: none;
    overflow: visible;
    padding-right: 0;
    margin-bottom: 14pt;
  }
  .rbody ol.steps { max-width: none }
  /* `revert`, not `list-item`: since Part E the hidden set can include a dish
   * HEADING (one whose every line is collapsed), and forcing an <h3> to
   * list-item would print it as a list row. `revert` rolls each element back to
   * the UA default — list-item for an <li>, block for the heading. */
  .ing-collapsed .ing-extra { display: revert !important }
  .ing-toggle { display: none !important }
  .servings-note,
  .steps-note { font-size: 9pt }
  /* §2.2 — two inches, floated, so the ingredients start beside it rather than
   * under it. The grid is undone for print: a float has no effect on a grid
   * item, so .rhead must be a block before .hero can float out of the flow. */
  .rhead { display: block; margin: 0 }
  .rhead-body { width: auto }
  .hero { width: 2in; max-width: 2in; border-radius: 0; box-shadow: none; float: right; margin: 0 0 8pt 10pt }
  h1.recipe-title { font-size: 20pt; margin-top: 0 }
  .meta { font-size: 9.5pt }
  .answer { font-size: 11.5pt }
  .macros { border-color: #bbb }
  .macro { border-right-color: #bbb }
  /* A step or an ingredient split across a page break is the one thing a
   * printed recipe must not do — the cook is holding it at the stove. */
  ol.steps li,
  ul.ingredients li { break-inside: avoid; page-break-inside: avoid }
  .section-h { break-after: avoid; page-break-after: avoid }
  /* The amount highlight survives printing — a colour printer keeps it and a
   * greyscale one still shows the weight. The timing marker keeps its ring
   * because tint is the first thing a cheap printer throws away. */
  ol.steps li.ts::before { border-width: 2px; border-color: #000 }
  .ts-legend { background: none; border-color: #bbb; font-size: 9pt }
  a { color: #000; text-decoration: none }
  .disclaimer { background: none; border: 1px solid #bbb; font-size: 8.5pt }
}

@media (max-width: 560px) {
  .nav-links { display: none }
  .macro { flex-basis: 50% }
}
