/* ==========================================================================
   ChefnFood — app.css
   ONE stylesheet. Design tokens first, then everything that consumes them.

   tokens.css used to be a second <link>, kept separate so Phase 1 could be
   reviewed on its own. Phase 4 folded it in here: the budget target is one
   editorial CSS file, and two <link>s is two round trips on the critical
   path for no benefit once the review is done.

   The token block below is still the single source of truth. Nothing in
   this file, or any template, may hardcode a hex value or a font size —
   folding the file in did not relax that rule, it just moved where the
   rule lives.
   ========================================================================== */

/* ==========================================================================
   ChefnFood — design tokens
   Single source of truth. Every value here traces to a line in
   docs/chefnfood-build-brief.md (§2.2 colour, §2.3 type, §2.4 components)
   or docs/chefnfood-pages-and-build.md (Parts 5 and 8).

   Nothing else in this theme may hardcode a hex value or a font size.
   ========================================================================== */

:root {

  /* --- Colour · brief §2.2 ------------------------------------------------
     Ten tokens. Do not add an eleventh without updating the brief.        */

  --cnf-cream:        #FBF7F0;  /* page background                          */
  --cnf-white:        #FFFFFF;  /* card surface                             */
  --cnf-ink:          #2A241C;  /* primary text, headings, footer surface   */
  --cnf-stone:        #6F6459;  /* secondary text, meta — 16px and up only  */
  --cnf-sand:         #EDE5D8;  /* borders, dividers, image placeholder     */
  --cnf-herb:         #3E7A4C;  /* links, secondary CTA, subscribe block bg */
  --cnf-tomato:       #D14A38;  /* BRAND AND LOGO ONLY — never a button     */
  --cnf-tomato-deep:  #C2422F;  /* ALL button backgrounds — see note below  */
  --cnf-golden:       #E8A23B;  /* badges, chip accents — NON-TEXT ONLY     */
  --cnf-mint:         #EDF3EB;  /* tinted surfaces, chip backgrounds        */

  /* Contrast rules, not preferences (brief §2.2):
       White on --cnf-tomato measures ~4.4:1 — BELOW the 4.5:1 AA floor for
       normal text. --cnf-tomato-deep is ~5.2:1. Every button fill on this
       site uses --cnf-tomato-deep. --cnf-tomato is brand and logo only.
       --cnf-golden fails badly on cream and is never used for text.
       --cnf-stone on cream measures 5.40:1 — comfortably AA at any size.

       ⚠ THIS LINE USED TO READ "~4.9:1 — AA, but 16px minimum. Never
       13px." Both halves were wrong and the second was actively
       misleading. 4.9 is not the measured ratio; 5.40 is, and every other
       figure in this block is exact, so the odd one out was the error.
       (The brief carries the same 4.9 at §2.2 and wants the same fix.)
       And "never 13px" is contradicted four times in this very file —
       .cnf-subscribe__label, .cnf-subscribe__consent, .cnf-hero__badge-text
       and .cnf-meta__label, the last at 12px — with the brief itself
       sanctioning the consent line at 13px stone in §2.4.

       The rule it was reaching for is a LEGIBILITY preference for body
       copy, not a contrast floor: at 5.40:1 stone clears AA at 13px and
       at 12px. That reclassification was already argued once, in the
       comment on .cnf-card__desc, and never made it back up here — which
       is why it kept being read as a hard floor.                         */

  /* Derived surfaces. color-mix keeps these honest — no second hex for a
     colour that is really an alpha variant of a token above.              */
  --cnf-golden-chip:   color-mix(in srgb, var(--cnf-golden) 22%, transparent);
  --cnf-card-hover:    color-mix(in srgb, var(--cnf-stone) 30%, transparent);
  --cnf-rule-on-ink:   color-mix(in srgb, var(--cnf-white) 10%, transparent);

  /* Button hover "darkens 6%" (brief §2.4). Mixing toward --cnf-ink
     rather than naming an eleventh hex. Never mix toward --cnf-tomato:
     that is lighter than the deep tone and would push white label text
     back under the 4.5:1 AA floor.                                      */
  --cnf-tomato-deep-hover: color-mix(in srgb, var(--cnf-tomato-deep) 94%, var(--cnf-ink));

  /* Dark-surface roles. The footer sits on ink, and the everyday text
     tokens do not survive the inversion — measured on --cnf-ink,
     --cnf-stone is 2.66:1 and --cnf-herb is 2.99:1, both well under the
     4.5:1 AA floor. Anything drawn on a dark surface takes these instead,
     so the next dark surface inherits a palette that already passes
     rather than re-deriving one.                                        */
  --cnf-surface-dark: var(--cnf-ink);
  --cnf-text-on-dark: var(--cnf-cream);   /* 14.38:1 on --cnf-surface-dark */
  --cnf-link-on-dark: var(--cnf-cream);   /* same — links carry weight and
                                             position, not a colour shift  */


  /* --- Type families · brief §2.3 --------------------------------------- */

  --cnf-font-display: 'Nunito', system-ui, -apple-system, sans-serif;
  --cnf-font-body:    'Inter', system-ui, -apple-system, sans-serif;

  --cnf-weight-regular:  400;  /* Inter  — body                            */
  --cnf-weight-semi:     600;  /* Inter  — meta, chips, buttons            */
  --cnf-weight-bold:     700;  /* Nunito — card titles, H3                 */
  --cnf-weight-black:    800;  /* Nunito — H1, H2                          */


  /* --- Type scale · brief §2.3 ------------------------------------------
     Fluid between a 375px and a 1440px viewport, the two ends of the
     Phase 4 responsive pass. Stated min and max are exact.               */

  --cnf-text-h1:           clamp(2.25rem,  1.810rem + 1.878vw, 3.5rem);    /* 36 → 56 */
  --cnf-text-h2:           clamp(1.75rem,  1.486rem + 1.127vw, 2.5rem);    /* 28 → 40 */
  --cnf-text-h3:           clamp(1.25rem,  1.162rem + 0.376vw, 1.5rem);    /* 20 → 24 */
  --cnf-text-body:         clamp(1.0625rem, 1.040rem + 0.094vw, 1.125rem); /* 17 → 18 */
  --cnf-text-instructions: clamp(1.125rem, 1.103rem + 0.094vw, 1.1875rem); /* 18 → 19 */
  --cnf-text-meta:         clamp(0.8125rem, 0.790rem + 0.094vw, 0.875rem); /* 13 → 14 */

  /* Page-specific sizes the specs name explicitly. Not new roles — each
     one is a literal value from the docs, parked here so no template has
     to write a px number.                                                 */
  --cnf-text-recipe-title:    clamp(2rem, 1.648rem + 1.502vw, 3rem);       /* 32 → 48  · pages Part 5.2 */
  --cnf-text-subscribe-h1:    clamp(2.5rem, 2.148rem + 1.502vw, 3.5rem);   /* 40 → 56  · pages Part 8   */
  --cnf-text-subscribe-sub:   clamp(1.375rem, 1.243rem + 0.563vw, 1.75rem);/* 22 → 28  · pages Part 8   */
  --cnf-text-input:           1.0625rem;  /* 17px · brief §2.4 form inputs             */
  --cnf-text-link:            1rem;       /* 16px · pages Part 5.7 jump-to-recipe      */
  --cnf-text-card-desc:       0.9375rem;  /* 15px · brief §2.4 recipe card description */
  --cnf-text-trust-line:      0.875rem;   /* 14px · pages Part 5.3 and 5.16            */
  --cnf-text-fine:            0.8125rem;  /* 13px · brief §2.4 consent line            */
  --cnf-text-label:           0.75rem;    /* 12px · pages Part 5.5 meta-row label      */

  /* Line height · brief §2.3, and pages Part 5.9 for instructions         */
  --cnf-leading-heading:      1.15;
  --cnf-leading-body:         1.65;
  --cnf-leading-instructions: 1.7;

  /* Measure · brief §2.3 — article text capped at 68 characters           */
  --cnf-measure: 68ch;


  /* --- Spacing · brief §2.4 ---------------------------------------------
     4px base unit. Token name is the px value, so a 24px gap is
     --cnf-space-24 and never a guess about what "md" means.              */

  --cnf-space-4:   4px;
  --cnf-space-6:   6px;   /* chip padding block                            */
  --cnf-space-8:   8px;
  --cnf-space-12: 12px;   /* chip padding inline                           */
  --cnf-space-14: 14px;   /* button padding block                          */
  --cnf-space-16: 16px;
  --cnf-space-20: 20px;   /* card content padding, mobile gutter           */
  --cnf-space-24: 24px;   /* card grid gap                                 */
  --cnf-space-28: 28px;   /* button padding inline, Chef's Take padding    */
  --cnf-space-32: 32px;
  --cnf-space-40: 40px;   /* desktop gutter                                */
  --cnf-space-48: 48px;
  --cnf-space-64: 64px;   /* section rhythm, mobile                        */
  --cnf-space-96: 96px;   /* section rhythm, desktop                       */

  /* Responsive spacing. Mobile-first; the desktop half lives in the
     media query at the foot of this file.                                */
  --cnf-section-y:   var(--cnf-space-64);
  --cnf-gutter:      var(--cnf-space-20);
  --cnf-band-pad:    var(--cnf-space-32);   /* breakfast band · brief §2.4 */
  --cnf-grid-gap:    var(--cnf-space-24);


  /* --- Layout · brief §2.4, pages Part 5 and Part 8 --------------------- */

  --cnf-max-content:  1120px;  /* page container                           */
  --cnf-max-article:   720px;  /* recipe and legal content column          */
  --cnf-max-copy:      640px;  /* subscribe page headline block            */
  --cnf-max-form:      480px;  /* subscribe page form card                 */
  --cnf-header-height:  72px;  /* brief §2.5 — sticky header               */
  --cnf-input-height:   52px;  /* brief §2.4 — form inputs                 */
  --cnf-touch-min:      44px;  /* brief §2.4 — minimum interactive target  */


  /* --- Shape · brief §2.4 ----------------------------------------------- */

  --cnf-radius-button: 8px;
  --cnf-radius-card:  12px;
  --cnf-radius-pill:  999px;

  --cnf-border-hairline: 1px;    /* card and divider borders               */
  --cnf-border-strong:   1.5px;  /* secondary button, form input           */
  --cnf-focus-ring:      2px;    /* herb ring — visible, never removed     */

  /* Every image slot on this site is 16:9. One value, brief §2.4.         */
  --cnf-aspect-image: 16 / 9;


  /* --- Motion · brief §2.1 and §2.4 -------------------------------------
     Hover states only, 150ms maximum. No entrance animations anywhere.   */

  --cnf-transition: 150ms ease;
}


/* Desktop half of the responsive tokens. 768px is the mobile/desktop
   switch across the theme, taken from the Phase 4 breakpoint list.        */
@media (min-width: 768px) {
  :root {
    --cnf-section-y: var(--cnf-space-96);
    --cnf-gutter:    var(--cnf-space-40);
    --cnf-band-pad:  var(--cnf-space-48);
  }
}


/* Respect a reduced-motion preference. The theme has no entrance
   animations to disable, so this only neutralises hover transitions.      */
@media (prefers-reduced-motion: reduce) {
  :root {
    --cnf-transition: 0ms;
  }
}


/* --- Fonts · brief §2.3 --------------------------------------------------
   Self-hosted, latin subset, font-display: swap. Google now ships these
   families as variable woff2 only, so each family is one file covering
   the weights the brief calls for rather than two static cuts.

   THE @font-face RULES ARE NOT IN THIS FILE. They live in an inline
   <style> in default.hbs, and they have to.

   A stylesheet cannot call {{asset}}, so an @font-face here can only use
   a relative path, which resolves WITHOUT the ?v= cache-buster. The
   preload in default.hbs does use {{asset}} and therefore carries one.
   Two different URL strings for the same file means the browser fetches
   it twice — roughly 87KB of redundant transfer on first paint, on the
   critical path, working directly against the LCP budget. A preload you
   then re-fetch under another URL is worse than no preload at all.

   Moving them into the template makes the preload href and the src the
   same string, which is the only way to have both cache-busting and a
   single download. Do not move them back.                              */


/* --- Reset ------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body,
h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
  padding: 0;
}

ul[class], ol[class] { list-style: none; }

/* Explicit width/height on every image, ratio held so CLS stays at zero
   (brief §2.4). One ratio site-wide.                                     */
img, svg, video { max-width: 100%; height: auto; display: block; }

button, input, select, textarea { font: inherit; color: inherit; }


/* --- Base -------------------------------------------------------------- */

body {
  background: var(--cnf-cream);
  color: var(--cnf-ink);
  font-family: var(--cnf-font-body);
  font-size: var(--cnf-text-body);
  font-weight: var(--cnf-weight-regular);
  line-height: var(--cnf-leading-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--cnf-font-display);
  line-height: var(--cnf-leading-heading);
  color: var(--cnf-ink);
}

h1 { font-size: var(--cnf-text-h1); font-weight: var(--cnf-weight-black); }
h2 { font-size: var(--cnf-text-h2); font-weight: var(--cnf-weight-black); }
h3 { font-size: var(--cnf-text-h3); font-weight: var(--cnf-weight-bold); }

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

/* Links inside running prose carry a permanent underline.
   ⚠ THE BUILD LOG BLAMED THE FOOTER FOR THIS AND THE FOOTER IS INNOCENT.
   Lighthouse's link-in-text-block failure — the one a11y failure common
   to all three pages tested; heading-order was the other, on category
   pages only — resolves to
   `.cnf-subscribe__consent > a[href="/privacy/"]`, not to anything in the
   footer. axe's own words: link text #3e7a4c against surrounding text
   #6f6459 is 1.12:1 where 3:1 is required, "and the link has no styling
   (such as underline) to distinguish it from the surrounding text".
   That is --cnf-herb on --cnf-stone. The build log reached the same 1.12
   figure for footer cream-on-cream, which cannot be right — two identical
   colours measure 1.00:1 — so the number was carried over, not measured.
   Footer list links sit alone in <li> with no surrounding text, so
   link-in-text-block does not apply to them and never did.
   Colour is not the lever here regardless: axe compares the link against
   the TEXT AROUND IT, so any colour that fixed this pairing would be a
   colour the brief's palette doesn't contain.
   Not on `a` globally, deliberately. Nav items, category tiles, recipe
   cards and the hero browse link are all anchors; underlining those
   redraws half the site. The list below is every context THIS STYLESHEET
   GOVERNS where a link sits inside a sentence — not every such context in
   the theme. Two more live on the sponsor shell, which never loads this
   file, and they are fixed in two DIFFERENT places because the sponsor
   side has two stylesheets, not one:
     partials/sponsor-disclosure.hbs  -> sponsor.css
     offers-index.hbs                 -> its own inline <style>, since it
                                         loads no stylesheet at all
   The split is the containment working as designed, not an omission.
   .cnf-page__content and .cnf-legal__content are on it BEFORE they have
   any content. The Part 9 legal copy is prose full of links, and it would
   otherwise have arrived failing this same rule on four new pages, after
   the theme work was declared finished.                                  */
.cnf-subscribe__consent a,
.cnf-empty a,
.cnf-recipe__content a,
.cnf-page__content a,
.cnf-legal__content a,
.cnf-subscribe-page__content a {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* Focus is always visible. Never remove this without a replacement
   (brief §2.4, and CLAUDE.md non-negotiables).                           */
:focus-visible {
  outline: var(--cnf-focus-ring) solid var(--cnf-herb);
  outline-offset: 2px;
}

.cnf-container {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}

.cnf-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.cnf-skip-link {
  position: absolute;
  left: var(--cnf-space-8);
  top: calc(-1 * var(--cnf-space-64));
  z-index: 100;
  background: var(--cnf-tomato-deep);
  color: var(--cnf-white);
  font-weight: var(--cnf-weight-semi);
  padding: var(--cnf-space-12) var(--cnf-space-20);
  border-radius: var(--cnf-radius-button);
}
.cnf-skip-link:focus { top: var(--cnf-space-8); }

/* Every in-page anchor target clears the sticky header. The header is
   position:sticky at 72px, so without this a fragment navigation scrolls
   the target to y=0 and puts it BEHIND the bar.

   Three targets today and all three were affected: #categories (the
   header nav's "Categories" item points at /#categories, and
   .cnf-categories has no top padding, so the h2 is its first child),
   #main (the skip link — the accessibility feature landing under the
   header is the worst version of this bug), and #ingredients (the
   jump-to-recipe link on every recipe page, which is the highest-traffic
   anchor on the site and comes from snippet content, not a template).

   Attribute selector rather than a list of ids precisely because
   #ingredients is authored in the editor: a rule listing known ids would
   silently fail to cover the next anchor someone adds in Ghost. */
[id] { scroll-margin-top: calc(var(--cnf-header-height) + var(--cnf-space-16)); }


/* --- Header · brief §2.5 -------------------------------------------------
   Sticky, 72px, cream, 1px sand bottom border once scrolled.            */

.cnf-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--cnf-cream);
  border-bottom: var(--cnf-border-hairline) solid transparent;
  transition: border-color var(--cnf-transition);
}
.cnf-header[data-scrolled='true'] { border-bottom-color: var(--cnf-sand); }

.cnf-header__inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
  min-height: var(--cnf-header-height);
  display: flex;
  align-items: center;
  gap: var(--cnf-space-16);
}

/* EVERY child of .cnf-header__inner sets flex-shrink: 0, and each one had
   to. The default flex-shrink of 1 let all three compete for the bar and
   all three lost: at 375 the logo box collapsed to 123.8px against the
   226.7px its 34px height needs — the mark painted at 55% of spec — and
   the hamburger was squeezed to a 32px touch target, under the 44px
   minimum. At 768 the logo dropped to 76% the moment the nav had real
   items in it, which is why nothing caught it while Ghost's navigation
   was empty: an empty <ul> takes no width and hides the problem.

   Do not remove these. The header is a fixed-width row and needs to be
   laid out as one — see the logo note in header.hbs for why the mark
   swaps to the icon below 1024 rather than shrinking. */
.cnf-header__logo {
  display: flex;
  align-items: center;
  margin-inline-end: auto;
  min-height: var(--cnf-touch-min);
  flex-shrink: 0;
}
.cnf-header__logo img { height: 34px; width: auto; }

/* One nav list, two layouts. On mobile it drops below the bar as a
   drawer; on desktop it sits inline. The links are never duplicated in
   the markup — a second copy is a second thing to keep in sync.         */
.cnf-header__nav {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--cnf-cream);
  border-top: var(--cnf-border-hairline) solid var(--cnf-sand);
  border-bottom: var(--cnf-border-hairline) solid var(--cnf-sand);
}
.cnf-header__nav[data-open='true'] { display: block; }

.cnf-header__nav ul {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding: var(--cnf-space-8) var(--cnf-gutter) var(--cnf-space-16);
  list-style: none;
}

.cnf-header__nav a {
  display: flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  color: var(--cnf-ink);
  font-weight: var(--cnf-weight-semi);
  transition: color var(--cnf-transition);
}
.cnf-header__nav a:hover { color: var(--cnf-herb); text-decoration: none; }

/* Newsletter button. Stays visible at every width — compact pill on
   mobile, full button on desktop (brief §2.5).                           */
/* order:2 / order:3 below are the cosmetic half of the source-order change
   in header.hbs. The DOM runs logo → toggle → nav → cta so that the
   hamburger precedes the drawer it opens in the tab sequence; these two
   properties put the bar back the way it paints today, logo · cta ·
   hamburger. Desktop needs no counterpart: the toggle is display:none
   from 768 up and generates no box, so the remaining order-0 items fall
   back to source order and give logo · nav · cta.

   Changing these changes the paint only. Changing the DOM order changes
   who can reach the menu. */
.cnf-header__cta {
  order: 2;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--cnf-touch-min);
  padding: var(--cnf-space-12) var(--cnf-space-16);
  background: var(--cnf-tomato-deep);
  color: var(--cnf-white);
  font-size: var(--cnf-text-meta);
  font-weight: var(--cnf-weight-semi);
  border-radius: var(--cnf-radius-pill);
  white-space: nowrap;
  transition: background-color var(--cnf-transition);
}
.cnf-header__cta:hover { background: var(--cnf-tomato-deep-hover); text-decoration: none; }

.cnf-header__toggle {
  order: 3;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--cnf-touch-min);
  height: var(--cnf-touch-min);
  background: none;
  border: 0;
  border-radius: var(--cnf-radius-button);
  cursor: pointer;
  color: var(--cnf-ink);
}

.cnf-header__toggle-bars,
.cnf-header__toggle-bars::before,
.cnf-header__toggle-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  background: currentColor;
  border-radius: var(--cnf-radius-pill);
}
.cnf-header__toggle-bars { position: relative; }
.cnf-header__toggle-bars::before,
.cnf-header__toggle-bars::after { content: ''; position: absolute; left: 0; }
.cnf-header__toggle-bars::before { top: -6px; }
.cnf-header__toggle-bars::after  { top: 6px; }

@media (min-width: 768px) {
  .cnf-header__logo img { height: 38px; }

  .cnf-header__nav {
    display: block;
    position: static;
    background: none;
    border: 0;
  }
  .cnf-header__nav ul {
    display: flex;
    align-items: center;
    gap: var(--cnf-space-28);
    padding: 0;
  }
  .cnf-header__nav a { display: inline-flex; }

  .cnf-header__toggle { display: none; }

  .cnf-header__cta {
    padding: var(--cnf-space-14) var(--cnf-space-28);
    border-radius: var(--cnf-radius-button);
    font-size: var(--cnf-text-link);
  }
}


/* --- Footer · brief §2.5 -------------------------------------------------
   Four columns desktop, stacked mobile. Ink surface — the wordmark in the
   brand block is logo-full-light.svg, which is filled cream and only
   reads on a dark background.                                           */

.cnf-footer {
  background: var(--cnf-surface-dark);
  color: var(--cnf-text-on-dark);
  margin-top: var(--cnf-section-y);
}

.cnf-footer__columns {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding: var(--cnf-space-48) var(--cnf-gutter) var(--cnf-space-32);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-space-32);
}

.cnf-footer__brand img { height: 34px; width: auto; }

.cnf-footer__promise {
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-bold);
  color: var(--cnf-white);
  margin-top: var(--cnf-space-16);
}

.cnf-footer__trust {
  font-size: var(--cnf-text-trust-line);
  margin-top: var(--cnf-space-8);
  max-width: 40ch;
}

/* Hidden until Ghost actually has a social account set. :has() rather
   than :empty — the wrapper always contains whitespace and comments,
   so :empty would never match.                                          */
.cnf-footer__social { display: none; }
.cnf-footer__social:has(a) {
  display: flex;
  gap: var(--cnf-space-16);
  margin-top: var(--cnf-space-16);
}
.cnf-footer__social a {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  color: var(--cnf-link-on-dark);
  font-size: var(--cnf-text-meta);
  font-weight: var(--cnf-weight-semi);
}

.cnf-footer__heading {
  font-family: var(--cnf-font-body);
  font-size: var(--cnf-text-fine);
  font-weight: var(--cnf-weight-semi);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cnf-white);
  margin-bottom: var(--cnf-space-12);
}

.cnf-footer__list { list-style: none; }
.cnf-footer__list a {
  display: flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  color: var(--cnf-link-on-dark);
  font-size: var(--cnf-text-trust-line);
  /* The --cnf-link-on-dark token says links on a dark surface "carry
     weight and position, not a colour shift". Position was implemented;
     weight was not, so until now the token described an intention nobody
     had written down in CSS. .cnf-footer__social a already sets semi —
     these are the same kind of link and had drifted apart. No axe rule
     requires this (see the prose-link note near the global `a` rule for
     why the footer is not what link-in-text-block flags); it is here to
     make the token's own comment true.                                   */
  font-weight: var(--cnf-weight-semi);
}
.cnf-footer__list a:hover { color: var(--cnf-white); }

.cnf-footer__legal {
  border-top: var(--cnf-border-hairline) solid var(--cnf-rule-on-ink);
}

.cnf-footer__legal-inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding: var(--cnf-space-24) var(--cnf-gutter) var(--cnf-space-32);
  display: grid;
  gap: var(--cnf-space-12);
  font-size: var(--cnf-text-fine);
  color: var(--cnf-text-on-dark);
}

@media (min-width: 768px) {
  .cnf-footer__columns {
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
    gap: var(--cnf-space-40);
    padding-block: var(--cnf-space-64) var(--cnf-space-40);
  }
  .cnf-footer__brand img { height: 38px; }
}


/* --- Error page --------------------------------------------------------- */

.cnf-error {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding: var(--cnf-section-y) var(--cnf-gutter);
  text-align: center;
}
/* --cnf-tomato is legitimate here, and the reason is the size, not the
   role. --cnf-text-h1 is 36px→56px at weight 800; the 36px mobile floor
   clears WCAG's 18.66px-bold large-text threshold, so the bar is 3:1 and
   not 4.5:1. Tomato on cream measures 4.14:1 — fails as normal text,
   passes comfortably as large. If this ever shrinks below 18.66px,
   switch it to --cnf-tomato-deep (4.78:1). */
.cnf-error__code {
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-black);
  font-size: var(--cnf-text-h1);
  color: var(--cnf-tomato);
}
.cnf-error__message { margin-top: var(--cnf-space-16); color: var(--cnf-stone); }
.cnf-error__actions {
  margin-top: var(--cnf-space-32);
  display: flex;
  flex-wrap: wrap;
  gap: var(--cnf-space-16);
  justify-content: center;
}
.cnf-error__actions a {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  padding: var(--cnf-space-14) var(--cnf-space-28);
  border-radius: var(--cnf-radius-button);
  font-weight: var(--cnf-weight-semi);
  background: var(--cnf-tomato-deep);
  color: var(--cnf-white);
  transition: background-color var(--cnf-transition);
}
.cnf-error__actions a:hover { background: var(--cnf-tomato-deep-hover); text-decoration: none; }
.cnf-error__actions a.is-secondary {
  background: transparent;
  color: var(--cnf-ink);
  border: var(--cnf-border-strong) solid var(--cnf-ink);
}
.cnf-error__actions a.is-secondary:hover { background: var(--cnf-sand); }


/* ==========================================================================
   COMPONENTS — brief §2.4
   Every value below comes from the token block at the top of this file
   (formerly tokens.css). No hex, no raw font sizes, and
   only the four token weights: the variable fonts make any weight
   reachable, which is exactly why the theme picks four and stops.
   ========================================================================== */


/* --- Layout ------------------------------------------------------------- */

/* ==========================================================================
   VERTICAL RHYTHM — owned by the container, never by the module.

   #main is a flex column with gap: var(--cnf-section-y). That gap IS the
   64/96 section rhythm the brief specifies, and it is the only thing that
   produces space between two modules.

   THE TWO RULES, and they are the whole convention:

     1. Rhythm lives on the container. A module never sets block margin or
        block padding to separate itself from a neighbour.
     2. A module's block padding is only ever its own painted surface —
        the band's mint, the subscribe block's green. If removing the
        background would leave the padding looking like empty page, it is
        rhythm and it does not belong on the module.

   WHY: every module previously declared its own block spacing, four as
   padding and two as margin. Padding never collapses and margin never
   collapses against padding, so every boundary paid twice — 192px against
   an intended 96px between the hero, the trust strip and the category
   grid. The brief invited it by specifying a "section gap above and
   below", which two adjacent modules necessarily double. Fixed in the
   brief and here together.

   THE TRADEOFF, stated because it is the reason this has to be written
   down rather than inferred: rhythm is now INVISIBLE from a module's own
   rules. Reading .cnf-trust tells you nothing about the space around it.
   That is the price of making it impossible to double, and it is why this
   comment exists and why the convention is in CLAUDE.md.

   Ends are not rhythm and are not governed here. gap produces no space at
   the ends of a flex container: the footer's own margin-top owns the
   bottom boundary (it sits outside #main, so nothing can double it), and
   the first module owns its offset from the sticky header, because that
   value is deliberately different per template — 96px under the homepage
   hero, 32px on a recipe at desktop, and 0 at mobile where the recipe
   hero goes edge to edge. None of those can double; there is no module
   above them.
   ========================================================================== */

#main {
  display: flex;
  flex-direction: column;
  gap: var(--cnf-section-y);
}

.cnf-wrap,
.cnf-section {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}

.cnf-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-grid-gap);
}

@media (min-width: 640px) {
  .cnf-grid--tiles,
  .cnf-grid--cards { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .cnf-grid--tiles,
  .cnf-grid--cards { grid-template-columns: repeat(3, 1fr); }
}

.cnf-page-header {
  border-bottom: var(--cnf-border-hairline) solid var(--cnf-sand);
  padding-block: var(--cnf-space-48);
}
.cnf-page-header__inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}
.cnf-page-header__title { font-size: var(--cnf-text-h1); }
.cnf-page-header__intro {
  max-width: var(--cnf-measure);
  margin-top: var(--cnf-space-16);
  color: var(--cnf-stone);
}
.cnf-page-header__framing {
  margin-top: var(--cnf-space-12);
  font-size: var(--cnf-text-trust-line);
  color: var(--cnf-stone);
}

/* padding-inline, not just padding-block: this is the only member of the
   max-width + auto-margin family with no surface of its own, so a missing
   gutter shows up as body text running into the viewport edge rather than
   as a rounded corner against it — less obvious, equally wrong. */
.cnf-empty {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding: var(--cnf-space-48) var(--cnf-gutter);
  color: var(--cnf-stone);
}


/* --- Chips · brief §2.4 --------------------------------------------------
   Category chip is --cnf-mint (ink on mint, 13.62:1). Time chip is golden
   at 22% (ink on the composited fill, 13.09:1) — golden is a background
   here and never the text, per §2.2.                                     */

.cnf-chip {
  display: inline-flex;
  align-items: center;
  border-radius: var(--cnf-radius-pill);
  padding: var(--cnf-space-6) var(--cnf-space-12);
  font-size: var(--cnf-text-meta);
  font-weight: var(--cnf-weight-semi);
  color: var(--cnf-ink);
  white-space: nowrap;          /* chips never wrap mid-label */
}
.cnf-chip--category { background: var(--cnf-mint); }
.cnf-chip--time     { background: var(--cnf-golden-chip); }


/* --- Recipe card · brief §2.4 -------------------------------------------
   White surface, 1px sand border, radius 12, FLAT at rest — no shadow.
   Hover shifts the border to stone-at-30% and lifts the card 2px. That is
   the entire hover treatment; nothing else moves.                        */

.cnf-card {
  background: var(--cnf-white);
  border: var(--cnf-border-hairline) solid var(--cnf-sand);
  border-radius: var(--cnf-radius-card);
  overflow: hidden;             /* clips the image to the top corners */
  transition: border-color var(--cnf-transition), transform var(--cnf-transition);
}
.cnf-card:hover {
  border-color: var(--cnf-card-hover);
  transform: translateY(-2px);
}
.cnf-card:focus-within { border-color: var(--cnf-card-hover); }

/* The whole card is one link target. */
.cnf-card__link { display: block; color: inherit; }
.cnf-card__link:hover { text-decoration: none; }

.cnf-card__media {
  aspect-ratio: var(--cnf-aspect-image);
  background: var(--cnf-sand);  /* placeholder while the image loads */
}
.cnf-card__image { width: 100%; height: 100%; object-fit: cover; }

.cnf-card__body { padding: var(--cnf-space-20); }

/* §2.4 says a chip row scrolls horizontally on mobile rather than
   stacking. That rule exists for rows of unbounded length; this row is
   capped at two chips and they always fit, so the scroll never fires.

   Worst case at the narrowest real viewport: 375px → 335px card → 295px
   content box. Two chips cost 56px in padding and gap, leaving 239px for
   labels. The longest pair on the site — "Breakfast & Brunch" plus
   "1 hr 20 min" — measures roughly 180px at 13px/600 Inter. ~25% headroom.

   So no overflow-x here. Scroll CSS that can never trigger reads as
   load-bearing to whoever finds it next, and it came with
   scrollbar-width:none, which would have hidden the only affordance that
   the row scrolled at all. If a third chip is ever added, this decision
   is what needs revisiting — not the cap. */
.cnf-card__chips {
  display: flex;
  gap: var(--cnf-space-8);
  margin-bottom: var(--cnf-space-12);
}

.cnf-card__title {
  font-size: var(--cnf-text-h3);
  font-weight: var(--cnf-weight-bold);
  display: -webkit-box;
  -webkit-line-clamp: 2;        /* truncates at two lines */
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cnf-card__desc {
  margin-top: var(--cnf-space-8);
  /* 15px stone on the white card surface measures 5.76:1 — comfortably
     AA. §2.2's "stone at 16px+" is a legibility preference for body copy,
     not a contrast floor, and this is a two-line label. */
  font-size: var(--cnf-text-card-desc);
  color: var(--cnf-stone);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}


/* --- Category tile ------------------------------------------------------ */

.cnf-tile {
  display: block;
  background: var(--cnf-white);
  border: var(--cnf-border-hairline) solid var(--cnf-sand);
  border-radius: var(--cnf-radius-card);
  overflow: hidden;
  color: inherit;
  transition: border-color var(--cnf-transition), transform var(--cnf-transition);
}
.cnf-tile:hover {
  border-color: var(--cnf-card-hover);
  transform: translateY(-2px);
  text-decoration: none;
}
.cnf-tile__media {
  aspect-ratio: var(--cnf-aspect-image);
  background: var(--cnf-sand);
}
.cnf-tile__image { width: 100%; height: 100%; object-fit: cover; }
.cnf-tile__body { padding: var(--cnf-space-20); }
.cnf-tile__name {
  font-size: var(--cnf-text-h3);
  font-weight: var(--cnf-weight-bold);
}
.cnf-tile__promise {
  margin-top: var(--cnf-space-4);
  color: var(--cnf-stone);
}


/* --- Trust strip --------------------------------------------------------- */

.cnf-trust {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}
.cnf-trust__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-space-24);
  list-style: none;
}
.cnf-trust__label {
  display: block;
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-bold);
  font-size: var(--cnf-text-h3);
}
.cnf-trust__detail {
  display: block;
  margin-top: var(--cnf-space-4);
  color: var(--cnf-stone);
}
@media (min-width: 640px) { .cnf-trust__list { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .cnf-trust__list { grid-template-columns: repeat(4, 1fr); } }


/* --- Breakfast band · brief §2.4 -----------------------------------------
   --cnf-mint surface, no border, no shadow — the tint does the separating.
   Two columns ~45/55 on desktop, image over text on mobile.

   NOTE ON THE EYEBROW: §2.4 asks for --cnf-golden here. Golden on mint
   measures 1.93:1 and is unreadable, and §2.2 plus the CLAUDE.md
   non-negotiables state golden is non-text only. Using --cnf-tomato-deep
   instead (4.53:1) — the warm accent the design wants, at a passing
   contrast. Flagged for the brief.                                       */

.cnf-band {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  background: var(--cnf-mint);
  border-radius: var(--cnf-radius-card);
  padding: var(--cnf-band-pad);
}
.cnf-band__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-space-24);
  align-items: center;
}
.cnf-band__media {
  aspect-ratio: var(--cnf-aspect-image);
  border-radius: var(--cnf-radius-card);
  overflow: hidden;
}
.cnf-band__image { width: 100%; height: 100%; object-fit: cover; }
.cnf-band__eyebrow {
  font-size: var(--cnf-text-fine);
  font-weight: var(--cnf-weight-semi);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cnf-tomato-deep);
}
.cnf-band__heading {
  margin-top: var(--cnf-space-8);
  font-size: var(--cnf-text-h2);
}
.cnf-band__promise {
  margin-top: var(--cnf-space-12);
  color: var(--cnf-stone);
  max-width: var(--cnf-measure);
}
.cnf-band__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  margin-top: var(--cnf-space-12);
  color: var(--cnf-herb);
  font-weight: var(--cnf-weight-semi);
}
@media (min-width: 768px) {
  .cnf-band__inner {
    grid-template-columns: 45fr 55fr;
    gap: var(--cnf-space-40);
  }
}

/* The band keeps a gutter until its own max-width provides one.
   .cnf-band has max-width:1120 and auto margins but no gutter of its own —
   its padding is the mint panel's INNER padding, not an inset — so at any
   viewport under 1200 it took the full width and sat its 12px radius flush
   against both screen edges while the grid beside it was inset by the
   gutter.

   THE BREAKPOINT IS 1199, NOT 1119, AND THE DIFFERENCE MATTERS. The band
   stops needing help only once the viewport exceeds max-width + two
   gutters = 1120 + 80 = 1200. Ending the rule at 1119 would leave 1120px
   to 1199px still flush: at exactly 1120 the max-width alone yields a
   1120px band in a 1120px viewport, margin auto with nothing to
   distribute. At 1199 the rule gives 1119 and at 1200 natural centring
   gives 1120 with 40px margins, so the handover is seamless.

   Verified against the grid: 375 → 335 and 1024 → 944, both matching the
   tile row to the pixel.

   §2.4's "full bleed to the content max-width (1120px)" is a statement
   about the band's WIDTH, not a licence to touch the viewport edge. It
   describes 1440, where the band spans 1120 while the tile row inside its
   gutters spans 1040 — the band is wider than the tiles, but both are
   inset from the screen. That relationship is what this rule preserves
   all the way down. An earlier version scoped this to max-width:767 and
   called the remainder intentional; it wasn't, and it left the band as
   the only module touching the screen edges at 1024. */
@media (max-width: 1199px) {
  .cnf-band { width: calc(100% - (2 * var(--cnf-gutter))); }
}


/* --- Subscribe form · brief §2.4 -----------------------------------------
   One partial, four variants. Inputs 52px, 1.5px sand border, radius 8.
   Focus is a 2px herb ring and is never removed.                         */

.cnf-subscribe__heading { font-size: var(--cnf-text-h2); }
.cnf-subscribe__subhead {
  margin-top: var(--cnf-space-8);
  color: var(--cnf-stone);
}

.cnf-subscribe__form {
  display: flex;
  flex-direction: column;
  gap: var(--cnf-space-12);
  margin-top: var(--cnf-space-20);
}

.cnf-subscribe__label {
  font-size: var(--cnf-text-fine);
  font-weight: var(--cnf-weight-semi);
  color: var(--cnf-stone);
}

.cnf-subscribe__input {
  min-height: var(--cnf-input-height);
  padding: 0 var(--cnf-space-16);
  background: var(--cnf-white);
  border: var(--cnf-border-strong) solid var(--cnf-sand);
  border-radius: var(--cnf-radius-button);
  font-size: var(--cnf-text-input);
  color: var(--cnf-ink);
}
.cnf-subscribe__input::placeholder { color: var(--cnf-stone); }
.cnf-subscribe__input:focus-visible {
  outline: var(--cnf-focus-ring) solid var(--cnf-herb);
  outline-offset: 1px;
  border-color: var(--cnf-herb);
}

/* Primary button: --cnf-tomato-deep, never --cnf-tomato. White on deep is
   5.10:1; white on --cnf-tomato is 4.4:1 and fails at label size. */
.cnf-subscribe__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--cnf-touch-min);
  padding: var(--cnf-space-14) var(--cnf-space-28);
  background: var(--cnf-tomato-deep);
  color: var(--cnf-white);
  border: 0;
  border-radius: var(--cnf-radius-button);
  font-weight: var(--cnf-weight-semi);
  cursor: pointer;
  transition: background-color var(--cnf-transition);
}
.cnf-subscribe__button:hover { background: var(--cnf-tomato-deep-hover); }

.cnf-subscribe__consent {
  font-size: var(--cnf-text-fine);
  color: var(--cnf-stone);
}

/* Mailing address, page variant only. No margin — the form is a flex
   column and its gap owns the spacing. */
.cnf-subscribe__address {
  font-size: var(--cnf-text-fine);
  color: var(--cnf-stone);
}

/* Inline states. Ghost's members.js toggles .success / .error on the form,
   so both messages stay hidden until it does. No modal, no redirect. */
.cnf-subscribe__message { display: none; font-weight: var(--cnf-weight-semi); }
.cnf-subscribe__form.success .cnf-subscribe__message--success {
  display: block;
  color: var(--cnf-herb);
}
.cnf-subscribe__form.error .cnf-subscribe__message--error {
  display: block;
  color: var(--cnf-tomato-deep);
}

/* Hero and inline variants put the field and button on one row once
   there's width for it. */
@media (min-width: 640px) {
  .cnf-subscribe--hero .cnf-subscribe__form,
  .cnf-subscribe--inline .cnf-subscribe__form {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: start;
  }
  .cnf-subscribe--hero .cnf-subscribe__consent,
  .cnf-subscribe--hero .cnf-subscribe__message,
  .cnf-subscribe--inline .cnf-subscribe__consent,
  .cnf-subscribe--inline .cnf-subscribe__message { grid-column: 1 / -1; }
}

/* Inline variant is a cream card wherever it lands (end of a recipe,
   mid-category). */
.cnf-subscribe--inline {
  background: var(--cnf-cream);
  border: var(--cnf-border-hairline) solid var(--cnf-sand);
  border-radius: var(--cnf-radius-card);
  padding: var(--cnf-space-24);
}

/* Page variant: the largest form treatment on the site.

   The width calc is the gutter, and it is NOT what `padding` is doing
   here — that padding is the white card's own inner surface. Same trap as
   the breakfast band: at 375 the card has a 480px max-width it cannot
   reach, so it filled the viewport and put its 12px radius flush against
   both screen edges. Measured x=0 w=375 before this. */
.cnf-subscribe--page {
  background: var(--cnf-white);
  border-radius: var(--cnf-radius-card);
  padding: var(--cnf-space-24);
  max-width: var(--cnf-max-form);
  width: calc(100% - (2 * var(--cnf-gutter)));
  margin-inline: auto;
}
.cnf-subscribe--page .cnf-subscribe__button { width: 100%; }

@media (min-width: 768px) {
  .cnf-subscribe--inline { padding: var(--cnf-space-32); }
  .cnf-subscribe--page   { padding: var(--cnf-space-40); }
}


/* --- Subscribe block ----------------------------------------------------- */

.cnf-subscribe-block {
  background: var(--cnf-herb);
  color: var(--cnf-white);
  padding-block: var(--cnf-section-y);
}
.cnf-subscribe-block__inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-space-32);
  align-items: start;
}
.cnf-subscribe-block__eyebrow {
  font-size: var(--cnf-text-fine);
  font-weight: var(--cnf-weight-semi);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.cnf-subscribe-block__heading {
  margin-top: var(--cnf-space-8);
  font-size: var(--cnf-text-h2);
  color: var(--cnf-white);
}
.cnf-subscribe-block__lede {
  margin-top: var(--cnf-space-16);
  max-width: var(--cnf-measure);
}
.cnf-subscribe-block__benefits {
  margin-top: var(--cnf-space-16);
  display: grid;
  gap: var(--cnf-space-8);
  list-style: none;
}
.cnf-subscribe-block__card {
  background: var(--cnf-white);
  color: var(--cnf-ink);
  border-radius: var(--cnf-radius-card);
  padding: var(--cnf-space-24);
}
@media (min-width: 768px) {
  .cnf-subscribe-block__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--cnf-space-48);
  }
  .cnf-subscribe-block__card { padding: var(--cnf-space-32); }
}


/* --- Homepage sections --------------------------------------------------- */

.cnf-hero__inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  /* block-START only: this is the hero's offset from the sticky header,
     not rhythm. #main's gap owns the space below it. */
  padding-block-start: var(--cnf-section-y);
  padding-inline: var(--cnf-gutter);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cnf-space-32);
  align-items: center;
}
.cnf-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  background: var(--cnf-mint);
  border-radius: var(--cnf-radius-pill);
  padding: var(--cnf-space-6) var(--cnf-space-12);
  font-size: var(--cnf-text-meta);
  font-weight: var(--cnf-weight-semi);
}
.cnf-hero__headline {
  margin-top: var(--cnf-space-16);
  font-size: var(--cnf-text-h1);
}
.cnf-hero__sub {
  margin-top: var(--cnf-space-16);
  max-width: var(--cnf-measure);
  color: var(--cnf-stone);
}
.cnf-hero__browse {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  margin-top: var(--cnf-space-12);
  font-weight: var(--cnf-weight-semi);
}
.cnf-hero__media { position: relative; }
.cnf-hero__image {
  width: 100%;
  aspect-ratio: var(--cnf-aspect-image);
  object-fit: cover;
  border-radius: var(--cnf-radius-card);
}
/* Overlaps the lower-left, which image-production §1.1 keeps quiet. */
.cnf-hero__badge {
  position: absolute;
  left: var(--cnf-space-16);
  bottom: var(--cnf-space-16);
  display: flex;
  align-items: center;
  gap: var(--cnf-space-12);
  background: var(--cnf-white);
  border-radius: var(--cnf-radius-card);
  padding: var(--cnf-space-12) var(--cnf-space-16);
}
/* Same exemption as .cnf-error__code, different surface. --cnf-text-h3 is
   20px→24px at weight 800, so the 20px mobile floor is over the 18.66px
   bold threshold and the bar is 3:1. Note this numeral sits on the badge's
   --cnf-white, NOT on --cnf-cream: tomato on white is 4.42:1. Passes as
   large text; would fail as normal text. Shrink it below 18.66px and it
   needs --cnf-tomato-deep (5.10:1 on white). */
.cnf-hero__badge-number {
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-black);
  font-size: var(--cnf-text-h3);
  color: var(--cnf-tomato);
}
.cnf-hero__badge-text {
  font-size: var(--cnf-text-meta);
  color: var(--cnf-stone);
}
.cnf-hero__badge-text strong {
  display: block;
  font-weight: var(--cnf-weight-semi);
  color: var(--cnf-ink);
}

.cnf-categories,
.cnf-featured {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}
.cnf-categories__intro { margin-bottom: var(--cnf-space-32); }
.cnf-categories__heading,
.cnf-featured__heading { font-size: var(--cnf-text-h2); }
.cnf-categories__sub,
.cnf-featured__sub {
  margin-top: var(--cnf-space-8);
  color: var(--cnf-stone);
}
.cnf-featured__intro {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cnf-space-16);
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--cnf-space-32);
}
.cnf-featured__all {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  font-weight: var(--cnf-weight-semi);
}

@media (min-width: 768px) {
  .cnf-hero__inner { grid-template-columns: 1fr 1fr; gap: var(--cnf-space-48); }
}


/* --- Focus on dark surfaces ---------------------------------------------
   The base :focus-visible ring is --cnf-herb, which measures 2.99:1 on
   --cnf-surface-dark — under the 3:1 minimum for a UI component. On a
   dark surface the ring switches to --cnf-text-on-dark (14.38:1).
   Never resolve this by removing the outline.                            */

.cnf-footer :focus-visible,
.cnf-subscribe-block :focus-visible {
  outline-color: var(--cnf-text-on-dark);
}


/* --- Recipe page · pages doc Part 5 --------------------------------------
   Content column 720px; hero and related row break out to 1120px.        */

.cnf-recipe__hero { margin-top: var(--cnf-space-32); }
.cnf-recipe__hero-image {
  width: 100%;
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  aspect-ratio: var(--cnf-aspect-image);
  object-fit: cover;
}

.cnf-recipe__body {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}

.cnf-recipe__title {
  margin-top: var(--cnf-space-24);
  font-size: var(--cnf-text-recipe-title);
}
.cnf-recipe__trust {
  margin-top: var(--cnf-space-12);
  font-size: var(--cnf-text-trust-line);
  color: var(--cnf-stone);
}
.cnf-recipe__chips {
  display: flex;
  gap: var(--cnf-space-8);
  margin-top: var(--cnf-space-20);
}
.cnf-recipe__jump {
  display: inline-flex;
  align-items: center;
  min-height: var(--cnf-touch-min);
  margin-top: var(--cnf-space-12);
  font-size: var(--cnf-text-link);
  font-weight: var(--cnf-weight-semi);
}
.cnf-recipe__affiliate {
  margin-top: var(--cnf-space-32);
  font-size: var(--cnf-text-trust-line);
  color: var(--cnf-stone);
}

/* Mobile: hero goes edge to edge, no gutters and no radius (Part 5.1). */
@media (max-width: 767px) {
  .cnf-recipe__hero { margin-top: 0; }
}
/* The gutter and the radius turn on together, and that pairing is the
   point. Below 768 the hero is deliberately full-bleed with square
   corners (Part 5.1) — that is the design. From 768 the radius switches
   on, and until Phase 4 the gutter did not, so between 768 and 1199 the
   hero kept a 12px radius while touching both viewport edges. Measured
   x=0 at 768 AND at 1024; it only came inside the viewport around 1200,
   where max-width:1120 finally leaves margins of its own.

   Same defect as the breakfast band, third and fourth instance of it.
   A rounded corner flush against a screen edge is the tell: if the radius
   is visible, the element needs a gutter. */
@media (min-width: 768px) {
  .cnf-recipe__hero-image,
  .cnf-page__hero-image {
    border-radius: var(--cnf-radius-card);
    width: calc(100% - (2 * var(--cnf-gutter)));
  }
}


/* --- Recipe body · editor content ----------------------------------------
   Everything below styles markup the SNIPPETS emit, so these class names
   are a contract with Part 11.11, which names them from the other side.
   Rename one and a block silently unstyles on all 28 recipes — there is
   no template reference to update and nothing will error.                */

.cnf-recipe__content { margin-top: var(--cnf-space-24); }
.cnf-recipe__content > p { margin-top: var(--cnf-space-16); }

.cnf-recipe__content h2 {
  margin-top: var(--cnf-space-40);
  font-size: var(--cnf-text-h2);
}
.cnf-recipe__content h3 {
  margin-top: var(--cnf-space-28);
  font-size: var(--cnf-text-h3);
  font-weight: var(--cnf-weight-bold);
}

/* Ingredients. Never two columns — it breaks on mobile and the reading
   order turns ambiguous (Part 5.8). Quantities semibold so the list is
   scannable at a glance in a store. */
.cnf-recipe__content ul {
  margin-top: var(--cnf-space-16);
  padding-left: var(--cnf-space-20);
}
.cnf-recipe__content ul li { margin-top: var(--cnf-space-12); }
.cnf-recipe__content ul li strong { font-weight: var(--cnf-weight-semi); }

/* Instructions. The largest body text on the site — this is what people
   read while holding a knife. Numbers sit in a 32px gutter so the text
   block keeps a clean left edge for thumb-scrolling. Never collapsed
   behind an accordion, never paginated (Part 5.9).

   The numerals are --cnf-tomato-deep, NOT --cnf-tomato. They are text,
   and at the mobile instruction size (18px) they fall below WCAG's
   18.66px bold threshold for large text, so they need the full 4.5:1.
   --cnf-tomato on cream is 4.14:1 and fails there; --cnf-tomato-deep is
   4.78:1. This is the same reason buttons use the deep tone. */
.cnf-recipe__content ol {
  margin-top: var(--cnf-space-16);
  padding: 0;
  list-style: none;
  counter-reset: cnf-step;
}
.cnf-recipe__content ol > li {
  counter-increment: cnf-step;
  position: relative;
  padding-left: var(--cnf-space-32);
  margin-top: var(--cnf-space-20);
  font-size: var(--cnf-text-instructions);
  line-height: var(--cnf-leading-instructions);
}
.cnf-recipe__content ol > li::before {
  content: counter(cnf-step);
  position: absolute;
  left: 0;
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-black);
  color: var(--cnf-tomato-deep);
}

/* Meta row — Recipe Meta snippet. Two-column grid on mobile rather than a
   five-across scroll: people check this while shopping and a hidden value
   is a failed check (Part 5.5). Tabular numerals so columns align. */
.cnf-meta {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--cnf-space-16);
  margin-top: var(--cnf-space-24);
  padding: var(--cnf-space-20);
  background: var(--cnf-cream);
  border: var(--cnf-border-hairline) solid var(--cnf-sand);
  border-radius: var(--cnf-radius-card);
  font-variant-numeric: tabular-nums;
}
.cnf-meta__label {
  display: block;
  font-size: var(--cnf-text-label);
  font-weight: var(--cnf-weight-semi);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cnf-stone);
}
.cnf-meta__value {
  display: block;
  margin-top: var(--cnf-space-4);
  font-size: var(--cnf-text-input);
  font-weight: var(--cnf-weight-semi);
}
.cnf-meta__item--difficulty { grid-column: 1 / -1; }
@media (min-width: 768px) {
  .cnf-meta { grid-template-columns: repeat(5, 1fr); }
  .cnf-meta__item--difficulty { grid-column: auto; }
}

/* Chef's Take — the entire differentiator. Mint callout, no border; the
   tint does the separating (Part 5.10). */
.cnf-chefs-take {
  margin-block: var(--cnf-space-40);
  padding: var(--cnf-space-20);
  background: var(--cnf-mint);
  border-radius: var(--cnf-radius-card);
}
.cnf-chefs-take h2 {
  margin-top: 0;
  margin-bottom: var(--cnf-space-12);
  font-size: var(--cnf-text-h3);
}
/* Paragraph spacing INSIDE the callout has to be restated here, and the
   reason is the child combinator two rules up: `.cnf-recipe__content > p`
   only reaches direct children, and these paragraphs are nested one level
   deeper inside the HTML card. They were rendering at margin-top 0 while
   every other paragraph on the page had 16px — measured, not guessed —
   so the heading butted its first line and the paragraphs ran together as
   one block of text. In the module the design leans on hardest.

   Scoped to `p + p` plus the heading's own margin-bottom rather than
   widening the selector above to a descendant one: `.cnf-recipe__content p`
   would also start hitting paragraphs inside .cnf-meta, which is a grid
   and lays out its own children. */
.cnf-chefs-take p + p { margin-top: var(--cnf-space-12); }
@media (min-width: 768px) {
  .cnf-chefs-take { padding: var(--cnf-space-28); }
}

/* --- Koenig width classes -----------------------------------------------
   Ghost emits these on images and embeds when an author picks "Wide" or
   "Full" in the editor. gscan ranks both as ERRORS, not warnings, and it
   is right to: without them a wide or full image renders at the 720px
   column width with no breakout, which reads as broken rather than merely
   unstyled.

   Deliberately unscoped. Ghost can emit them in any rendered post body —
   recipe, article, or legal — and scoping to one wrapper is how a class
   silently stops applying when a fourth content template appears.

   Both centre against their own column rather than using negative
   margins, so they stay correct at every width with no media query. The
   column is 720px inside a 1120px container, so wide breaks out to the
   container and full goes edge to edge.

   ⚠ .kg-width-full uses 100vw, which INCLUDES the scrollbar in engines
   that reserve space for one. On overlay-scrollbar platforms (macOS
   default, iOS, Android) this is exact; where a classic scrollbar is
   reserved it can overflow by its width and produce a horizontal
   scrollbar. Verify in the responsive pass at 375/768/1024/1440 before
   launch. If it does overflow, the fix is a one-line width change here —
   either 100dvw, or calc(100vw - (100vw - 100%)) which subtracts the
   scrollbar. Do NOT restructure the content wrapper or add overflow
   rules to an ancestor; this is a width bug, and it is fixed at the
   width. */

.kg-width-wide {
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  width: min(var(--cnf-max-content), calc(100vw - (2 * var(--cnf-gutter))));
  max-width: 100vw;
}

.kg-width-full {
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  max-width: 100vw;
}

/* Captions travel with both widths and must stay readable, so they keep
   the text measure even when the image does not. */
.kg-width-wide figcaption,
.kg-width-full figcaption {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}


/* --- Related recipes ----------------------------------------------------- */

.cnf-related { margin-block: var(--cnf-section-y); }
.cnf-related__inner {
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}
.cnf-related__heading {
  font-size: var(--cnf-text-h2);
  margin-bottom: var(--cnf-space-24);
}

.cnf-recipe__affiliate-wrap {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}


/* --- Article and legal pages · Part 7, Part 9 ---------------------------- */

.cnf-page__hero { margin-top: var(--cnf-space-32); }
.cnf-page__hero-image {
  width: 100%;
  max-width: var(--cnf-max-content);
  margin-inline: auto;
  aspect-ratio: var(--cnf-aspect-image);
  object-fit: cover;
}
.cnf-page__body,
.cnf-legal {
  max-width: var(--cnf-max-article);
  margin-inline: auto;
  padding: var(--cnf-space-48) var(--cnf-gutter);
}
.cnf-page__title,
.cnf-legal__title { font-size: var(--cnf-text-h1); }
.cnf-page__meta,
.cnf-legal__updated {
  margin-top: var(--cnf-space-12);
  font-size: var(--cnf-text-trust-line);
  color: var(--cnf-stone);
}
.cnf-page__content,
.cnf-legal__content { margin-top: var(--cnf-space-24); }
.cnf-page__content > p,
.cnf-legal__content > p { margin-top: var(--cnf-space-16); }
.cnf-page__content h2,
.cnf-legal__content h2 {
  margin-top: var(--cnf-space-40);
  font-size: var(--cnf-text-h2);
}
.cnf-page__content h3,
.cnf-legal__content h3 {
  margin-top: var(--cnf-space-28);
  font-size: var(--cnf-text-h3);
}
.cnf-page__content ul,
.cnf-legal__content ul {
  margin-top: var(--cnf-space-16);
  padding-left: var(--cnf-space-20);
}
.cnf-page__content li,
.cnf-legal__content li { margin-top: var(--cnf-space-8); }

@media (max-width: 767px) { .cnf-page__hero { margin-top: 0; } }
/* .cnf-page__hero-image's radius and gutter are set alongside
   .cnf-recipe__hero-image in the recipe section above — the two hero
   images are the same component in two templates and had drifted into
   two rules, which is how only one of them would have got fixed. */


/* --- Category page: mid-grid subscribe strip ----------------------------
   A grid child spanning every column, so the cards stay in one grid and
   the row rhythm survives.                                              */

.cnf-tag__strip { grid-column: 1 / -1; }


/* --- Subscribe page · Part 8 ---------------------------------------------
   The largest treatment on the site. Centred, narrow measure, no nav
   distractions — the header drops to a single link on this page.

   THE COPY IS EDITOR CONTENT. Everything from the subheadline down renders
   from the Ghost page body as native Lexical blocks, which cannot carry
   classes, so the rules below are element selectors scoped to
   .cnf-subscribe-page__content — the same contract .cnf-recipe__content
   keeps with the snippets (Part 11.11) and .cnf-legal__content keeps with
   the Part 9 copy.

   That makes THE BLOCK STRUCTURE OF THE GHOST PAGE part of this stylesheet's
   interface. The mapping, in the order it is typed:

     h2   the subheadline
     p    the lede
     ul   the five "what subscribers get" items, each a bolded lead phrase
          followed inline by its description — <strong> is what breaks the
          two onto separate lines, NOT a line break in the editor
     hr   a divider marking the start of the closing block
     p    after that hr, the closing "one email a week" paragraphs

   Restructure the page in the editor without reading this and a block
   unstyles silently. There is no template reference to update, gscan sees
   nothing, and the page still renders.                                    */

.cnf-subscribe-page {
  padding-block: var(--cnf-section-y);
}

/* H1 renders from the page title field, not the body — see the comment in
   custom-subscribe.hbs. It carries its own measure because there is no
   longer a headline wrapper to inherit one from. */
.cnf-subscribe-page__title {
  max-width: var(--cnf-max-copy);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
  text-align: center;
  font-size: var(--cnf-text-subscribe-h1);
}

.cnf-subscribe-page__content {
  max-width: var(--cnf-max-copy);
  margin-inline: auto;
  padding-inline: var(--cnf-gutter);
}

/* Subheadline. */
.cnf-subscribe-page__content > h2 {
  margin-top: var(--cnf-space-12);
  text-align: center;
  font-family: var(--cnf-font-display);
  font-weight: var(--cnf-weight-bold);
  font-size: var(--cnf-text-subscribe-sub);
  color: var(--cnf-stone);
}

/* The lede, and any body paragraph before the divider. The closing
   paragraphs are also `> p` and override colour and measure below. */
.cnf-subscribe-page__content > p {
  margin-top: var(--cnf-space-20);
  text-align: center;
}

/* Five items, bolded lead line above a plain description. No icons — icons
   here read as a landing-page template, and this is meant to read as the
   publication talking.

   Left-aligned against the centred prose above it: a lead line over a
   description does not read centred.

   `> ul > li > strong` is scoped that tightly on purpose. A bare `strong`
   under .cnf-subscribe-page__content would also catch the bolded first line
   of the closing block and turn it into a block-level ink heading. */
.cnf-subscribe-page__content > ul {
  margin-top: var(--cnf-space-48);
  padding-left: 0;
  list-style: none;
  display: grid;
  gap: var(--cnf-space-20);
  text-align: left;
  color: var(--cnf-stone);
}
.cnf-subscribe-page__content > ul > li > strong {
  display: block;
  font-weight: var(--cnf-weight-semi);
  color: var(--cnf-ink);
}

/* The divider that opens the closing block. It is LOAD-BEARING — the rule
   below keys off it, because the closing paragraphs and the lede are both
   bare <p> at the same depth and nothing but position separates them.

   `p:last-of-type` was tried first and does not work: the closer is two
   paragraphs and :last-of-type matches only the second, leaving the bolded
   "One email a week" line at lede width in ink.

   Rendered as a visible hairline rather than hidden, ON PURPOSE. A divider
   with no visual effect is clutter an editor deletes, and deleting it fails
   quietly — the closing block just renders at lede width. Visible, it reads
   as intentional typography and survives. */
.cnf-subscribe-page__content > hr {
  max-width: var(--cnf-max-form);
  margin: var(--cnf-space-40) auto 0;
  border: 0;
  border-top: var(--cnf-border-hairline) solid var(--cnf-sand);
}

/* Closing block — every paragraph after that divider. Narrower measure than
   the lede, and stone rather than ink. */
.cnf-subscribe-page__content > hr ~ p {
  max-width: var(--cnf-max-form);
  margin-top: var(--cnf-space-24);
  margin-inline: auto;
  color: var(--cnf-stone);
}
.cnf-subscribe-page__content > hr ~ p + p { margin-top: var(--cnf-space-12); }

.cnf-subscribe-page .cnf-subscribe--page { margin-top: var(--cnf-space-48); }

/* Reduced header on /subscribe/ — nav is a single link, no drawer. */
.cnf-header__nav--minimal {
  display: block;
  position: static;
  background: none;
  border: 0;
}
.cnf-header__nav--minimal ul { display: flex; padding: 0; }
