/* Dirty Foot Contessa — site styles.
   Ported faithfully from the adopted design of record (design/reference/,
   D-009). Tokens live in assets/tokens.css. Match the reference; don't tidy. */

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'IBM Plex Sans', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; text-align: left; }
img { max-width: 100%; }
/* Keyboard focus — one designed ring for every interactive element.
   :focus-visible shows for keyboard nav, not on mouse click. `outline`
   follows each element's own border-radius, so pills ring as pills. */
:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
/* Screen-reader-only: in the accessibility tree, out of view (hidden h1/h2). */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
/* Skip link: off-screen until keyboard focus, then pinned top-left. */
.skip { position: absolute; left: 8px; top: -60px; z-index: 10; background: var(--ink); color: var(--bg); padding: 10px 16px; border-radius: 999px; font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 13px; letter-spacing: .04em; text-decoration: none; }
.skip:focus { top: 8px; }
/* The <main> skip target isn't a control — don't ring the whole page on focus. */
#main:focus { outline: none; }

/* Page shell + masthead ---------------------------------------------------- */
.page { max-width: none; margin: 0 auto; padding: 24px 18px 0; }
.mhead { padding-bottom: 0; }
.page.body { padding-top: 10px; }
/* One SVG wordmark; the small state animates width (a box-affecting property)
   so the masthead's real height collapses and the page reflows with it —
   no transform/visibility, which would leave the full-size layout box behind. */
.mast { position: relative; cursor: pointer; line-height: 0; display: block; }
.wm { display: block; width: 100%; height: auto; color: var(--ink); transition: width var(--dur-expressive) var(--ease-out); }
.mast.sm .wm { width: 210px; }

.sect { margin-top: 52px; }
.catlatest { margin-top: 72px; }
.rule { border: 0; border-top: 2px dotted var(--line); margin: 0 0 22px; }

/* Primitives --------------------------------------------------------------- */
.label { font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 15px; letter-spacing: .08em; color: var(--red); }
.key { font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 12px; letter-spacing: .1em; color: var(--ink50); }
.caption { font-family: 'IBM Plex Sans', system-ui, sans-serif; font-size: 13px; line-height: 1.4; color: var(--ink50); }
/* Pills — one borderless style for serving chips, tags and browse facets.
   Rest = faint primary tint; selected (.on) = solid ink. No outlines. */
.chip { display: inline-block; font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 13px; letter-spacing: .04em; padding: 7px 15px; border: 0; border-radius: 999px; background: var(--primary-100); color: var(--ink); text-decoration: none; cursor: pointer; transition: background var(--dur-micro) ease, color var(--dur-micro) ease; }
@media (hover: hover) and (pointer: fine) { .chip:hover { background: rgba(97, 24, 24, .18); } }
.chip.on { background: var(--ink); color: var(--bg); }
.card { display: flex; flex-direction: column; text-align: left; text-decoration: none; color: var(--ink); }
.card h3 { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; line-height: 1.02; margin: 0; }
@media (hover: hover) and (pointer: fine) { .card:hover h3 { text-decoration: underline; text-underline-offset: 3px; } }
.seclabel { margin: 0 0 24px; }
h2.label, h3.label { margin: 0; font-weight: 400; }

.lhead { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; margin-bottom: 24px; }
.lhead h2 a { color: inherit; text-decoration: none; }
.viewall { flex: 0 0 auto; font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 13px; letter-spacing: .08em; color: var(--red); text-decoration: none; white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {
  .lhead h2 a:hover { text-decoration: underline; text-underline-offset: 3px; }
  .viewall:hover { text-decoration: underline; text-underline-offset: 3px; }
  .viewall:hover .va-arrow { transform: translateX(4px); }
}

/* Images ------------------------------------------------------------------- */
.card-img { width: 100%; overflow: hidden; background: transparent; border-radius: 4px; }
.card-img img, .rhero img, .rhero2 img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 4px; }
/* Hover: the image glides up ~5% inside its clipped frame — layout stays put. */
.card-img > img, .card-img > .pat { transition: transform .55s var(--ease-out); }
@media (hover: hover) and (pointer: fine) { .card:hover .card-img > img, .card:hover .card-img > .pat { transform: scale(1.05); } }
/* No-photo fallback: the dance pattern poured through a mask, tone on tone.
   Colorway class (pat-ink etc.) comes from the recipe slug via patTone. */
.pat { width: 100%; height: 100%; position: relative; background: var(--pat-bg); }
.pat::after { content: ''; position: absolute; inset: 0; background: var(--pat-fg);
  -webkit-mask: url('/assets/dance_pattern_mask.svg') center / cover no-repeat;
  mask: url('/assets/dance_pattern_mask.svg') center / cover no-repeat; }
.pat-ink  { --pat-bg: rgba(40, 25, 7, .07);  --pat-fg: var(--ink); }
.pat-red  { --pat-bg: rgba(97, 24, 24, .09); --pat-fg: var(--red); }
.pat-blue { --pat-bg: rgba(11, 35, 255, .07); --pat-fg: var(--blue); }
.pat-lime { --pat-bg: color-mix(in srgb, var(--lime) 42%, var(--bg)); --pat-fg: color-mix(in srgb, var(--lime) 55%, var(--ink)); }
/* Bold set: same pairs flipped — solid ground, light glyphs */
.pat-ink-bold  { --pat-bg: var(--ink);  --pat-fg: color-mix(in srgb, var(--ink) 25%, var(--bg)); }
.pat-red-bold  { --pat-bg: var(--red);  --pat-fg: color-mix(in srgb, var(--red) 20%, var(--bg)); }
.pat-blue-bold { --pat-bg: var(--blue); --pat-fg: color-mix(in srgb, var(--blue) 30%, var(--bg)); }
.pat-lime-bold { --pat-bg: var(--lime); --pat-fg: color-mix(in srgb, var(--lime) 55%, var(--ink)); }
.round .card-img, .round.rhero, .round.rhero2 { border-radius: 12px; }

/* Home: latest scroller + repeats ----------------------------------------- */
/* Top/left padding + compensating negative margins give the focus ring room
   inside the overflow box (overflow-x clips the cross axis too) without shifting
   the cards, which stay aligned to the section label. */
.scroller { display: flex; align-items: flex-start; gap: 22px; overflow-x: auto; scroll-snap-type: x mandatory; margin: -4px calc(50% - 50vw) 0 -4px; padding: 4px 18px 8px 4px; -webkit-overflow-scrolling: touch; }
.scroller::-webkit-scrollbar { height: 0; }
.scard { flex: 0 0 auto; width: 66vw; max-width: 240px; scroll-snap-align: start; gap: 12px; }
/* View-all card that caps the scroller: same width as a recipe card, stretched
   to the tallest card's height, lightest-red fill, arrow slides in on hover. */
.scroll-end { flex: 0 0 auto; align-self: stretch; width: 66vw; max-width: 240px; scroll-snap-align: end; display: flex; align-items: center; justify-content: center; gap: 10px; background: var(--primary-100); color: var(--red); font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 14px; letter-spacing: .08em; text-decoration: none; border-radius: 4px; }
.va-arrow { transition: transform var(--dur-micro) ease; }
@media (hover: hover) and (pointer: fine) { .scroll-end:hover .va-arrow { transform: translateX(6px); } }
.scard .card-img { aspect-ratio: 4/5; }
.scard h3 { font-size: 19px; }
.fone { gap: 18px; width: 100%; }
.fone-copy { display: flex; flex-direction: column; gap: 10px; order: -1; }
.fone-copy h3 { font-size: 28px; line-height: 1.03; letter-spacing: -.01em; }
.fone-copy p { font-size: 16px; line-height: 1.4; color: var(--ink70); margin: 0; }
.fone-img { aspect-ratio: 2/3; }
.trip { display: grid; grid-template-columns: 1fr; gap: 32px; margin-top: 40px; }
/* A grid straight under a section header takes only the header's gap; the top
   margin exists to separate a trip that follows the .fone feature card. */
.lhead + .trip { margin-top: 0; }
.tcard { gap: 14px; }
/* Mobile: copy above the image, like the feature card */
.trip .tcard .card-img { order: 1; }
.tcard .card-img { aspect-ratio: 4/5; }
.tcard h3 { font-size: 24px; line-height: 1; }
.tcard p { font-size: 15px; line-height: 1.4; color: var(--ink70); margin: 0; }

.bio { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: 26px; line-height: 1.12; letter-spacing: -.005em; margin: 0; }
/* The colophon under About: Rex speaking, quieter than the bio */
.colophon-k { margin: 44px 0 12px; }
.colophon-slop { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: 22px; line-height: 1.1; letter-spacing: -.005em; color: var(--ink); margin: 0 0 14px; }
.colophon { font-size: 16px; line-height: 1.55; color: var(--ink70); max-width: 640px; margin: 0; }
.bio .red { color: var(--red); }
.bio p { margin: 0 0 18px; }
.bio p:last-child { margin: 0; }
.about-split { display: flex; flex-direction: column; gap: 36px; }
.about-text { display: flex; flex-direction: column; }
.about-note { margin-top: 44px; }
.about-media img { display: block; width: 100%; height: auto; border-radius: 4px; }

/* Footer ------------------------------------------------------------------- */
.foot { margin-top: 60px; }
.foot-art { position: relative; }
.foot-img { display: block; width: 100%; height: auto; border-radius: 4px; }
.foot-dfc { position: absolute; inset: 0; width: 100%; height: 100%; }
.foot-bot { display: flex; flex-direction: column; gap: 8px; justify-content: space-between; padding: 18px 0 26px; }
.foot-bot span { font-family: 'IBM Plex Mono', monospace; font-size: 13px; letter-spacing: .02em; color: var(--ink70); }

/* Category browse ---------------------------------------------------------- */
.cathead { margin-top: 30px; }
.catcount { font-size: 13px; letter-spacing: .06em; margin-top: 14px; }
.catgrid { display: grid; grid-template-columns: 1fr; gap: 34px; margin-top: 36px; }
.loadmore { font-size: 13px; letter-spacing: .06em; padding: 11px 24px; margin: 44px 0 0; display: inline-block; text-align: center; }
/* Mobile: label stacks above its chips so they flow full-width; desktop
   restores the label-left / chips-right columns (820px block below). */
.bgroup { display: flex; flex-direction: column; gap: 12px; margin-top: 22px; }
.bg-k { letter-spacing: .12em; flex: none; }
.bchips { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-width: 0; }

/* Recipe detail ------------------------------------------------------------ */
.rtitle { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: clamp(40px, 11vw, 64px); line-height: .98; letter-spacing: -.02em; margin: 26px 0 0; }
.rlede-row { display: flex; flex-direction: column; gap: 24px; margin-top: 52px; }
.rlede { font-size: 20px; line-height: 1.42; color: var(--ink85); margin: 0; }
.rlede-solo { margin-top: 52px; max-width: 640px; }
.rhero-col { margin: 0; display: flex; flex-direction: column; gap: 12px; width: 100%; }
.rhero { position: relative; width: 100%; aspect-ratio: 4/5; overflow: visible; background: transparent; }
.rhero > img { position: absolute; inset: 0; }
.rbody { position: relative; margin-top: 44px; display: flex; flex-direction: column; gap: 40px; }
.rrow { display: grid; grid-template-columns: 1fr; gap: 14px; }
.notetext { font-size: 16px; line-height: 1.55; color: var(--ink); max-width: 640px; margin: 0; }

.glance { display: flex; flex-wrap: wrap; gap: 20px 30px; }
.kk { margin-bottom: 5px; }
.glance .v { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: 21px; line-height: 1; }
.tags { display: flex; flex-wrap: wrap; gap: 8px; }
.serv { margin-top: 0; }
.ataglance { display: flex; flex-wrap: wrap; gap: 22px 44px; align-items: flex-start; }
.serv-k { margin-bottom: 9px; }
.pills { display: flex; gap: 8px; flex-wrap: wrap; }
.story { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: 23px; line-height: 1.28; margin: 0; color: var(--ink); max-width: 640px; }

ul.ings { list-style: none; margin: 0; padding: 0; max-width: 520px; }
ul.ings li { font-size: 16px; line-height: 1.4; padding: 0 0 12px; margin: 0 0 12px; border-bottom: 1px solid var(--ink30); }
ul.ings li:last-child { border: 0; margin: 0; padding: 0; }
.ing-amt { font-variant-numeric: tabular-nums; font-weight: 500; }
.ing-extra { display: block; margin-top: 4px; font-family: 'IBM Plex Mono', monospace; font-size: 13px; letter-spacing: .03em; line-height: 1.5; color: var(--ink50); }

/* Tap-to-strike checklist. The line-through sits on .ing-amt and .ing-head so
   it never reaches the note in .ing-extra; the note dims instead. */
ul.ings li.ing { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.ing-amt, .ing-head, .ing-extra { transition: color var(--dur-micro) var(--ease-out); }
ul.ings li.ing.struck .ing-amt, ul.ings li.ing.struck .ing-head { text-decoration: line-through; text-decoration-thickness: 1.5px; color: var(--ink50); }
ul.ings li.ing.struck .ing-extra { color: var(--ink30); }
@media (hover: hover) and (pointer: fine) {
  ul.ings li.ing:not(.struck):hover .ing-amt, ul.ings li.ing:not(.struck):hover .ing-head { color: var(--red); }
}

ol.method, .method-body ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 28px; max-width: 620px; }
ol.method li, .method-body ol li { position: relative; padding-left: 36px; font-size: 16px; line-height: 1.5; }
.method-body ol li p { margin: 0; }
.method-body > p { font-size: 16px; line-height: 1.5; max-width: 620px; margin: 28px 0; }
.method-body > p:first-child { margin-top: 0; }
.method-body strong { font-weight: 600; color: var(--ink); }
.method-body em { font-style: normal; }

/* Check off steps as you cook (assets/steps.js) */
.method-body ol li.step { cursor: pointer; transition: transform var(--dur-micro) ease, color var(--dur-micro) ease; }
@media (hover: hover) and (pointer: fine) {
  .method-body ol li.step:hover { transform: translateX(6px); }
  .method-body ol li.step:hover .step-check { border-color: var(--ink); }
}
.step-check { position: absolute; left: 0; top: 1px; width: 20px; height: 20px; border: 1.5px solid var(--ink50); border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background var(--dur-micro), border-color var(--dur-micro); }
.method-body ol li.done { color: var(--ink50); }
.method-body ol li.done strong, .method-body ol li.done em { color: inherit; }
/* Checked step: the circle fills solid ink and the tick reverses out of it,
   so the affordance reads in the page's own two colors. */
.method-body ol li.done .step-check { background: var(--ink); border-color: var(--ink); }
.method-body ol li.done .step-check::after { content: ''; width: 5px; height: 9px; border-right: 2px solid var(--bg); border-bottom: 2px solid var(--bg); transform: rotate(45deg); margin-top: -2px; }
.stepcount { text-transform: none; letter-spacing: .04em; color: var(--ink50); margin-top: 8px; }

.rhero2 { width: 100%; aspect-ratio: 3/2; overflow: hidden; background: transparent; margin-top: 44px; }

/* Home cover — full-bleed 100vh magazine cover (index.njk). The masthead
   floats over it via body.coverhome; everything else lives below the fold. */
.cover { position: relative; display: block; width: 100%; height: 100vh; height: 100dvh; min-height: 520px; overflow: hidden; color: var(--bg); text-decoration: none; }
.cover-media, .cover-media img, .cover-media .pat { position: absolute; inset: 0; width: 100%; height: 100%; }
.cover-media img { object-fit: cover; object-position: 50% 0%; display: block; height: calc(100% + 120px); transform: translateY(var(--py, -60px)); }
/* Feathered scrims: top carries the white masthead, bottom carries the title. */
.cover::before, .cover::after { content: ''; position: absolute; left: 0; right: 0; pointer-events: none; z-index: 1; }
.cover::before { top: 0; height: 260px; background: linear-gradient(to bottom, rgba(0, 0, 0, .42), rgba(0, 0, 0, 0)); }
.cover::after { bottom: 0; height: 62%; background: linear-gradient(to top, rgba(0, 0, 0, .6), rgba(0, 0, 0, .12) 46%, rgba(0, 0, 0, 0)); }
.cover-plate { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 0 18px 40px; max-width: 900px; }
/* Kicker = the shared section-label style (.label), turned white over the photo. */
.cover-kicker { margin: 0 0 14px; color: var(--bg); }
.cover-title { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: clamp(40px, 9vw, 84px); line-height: .96; letter-spacing: -.02em; margin: 0; text-wrap: balance; }
.cover-title a { color: inherit; text-decoration: none; }
.cover-dek { font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: clamp(19px, 2.6vw, 26px); line-height: 1.2; color: color-mix(in srgb, var(--bg) 88%, transparent); margin: 16px 0 0; max-width: 34ch; }
@media (hover: hover) and (pointer: fine) {
  .cover-title a:hover { text-decoration: underline; text-underline-offset: 3px; }
}

/* The gnocchi series — an editorial spread (prototype .spread) ported to the
   adopted tokens: a dark ink plate over a wide crop of the lead art. */
.spread { position: relative; display: block; text-decoration: none; color: inherit; margin: 72px 0; }
.spread-fig { position: relative; overflow: hidden; border-radius: 4px; height: clamp(400px, 66vh, 660px); }
.spread-media { height: 100%; }
.spread-media img { display: block; width: 100%; height: calc(100% + 120px); object-fit: cover; object-position: 50% 42%; transform: translateY(var(--py, -60px)); }
.spread-plate { position: absolute; left: 16px; bottom: 16px; z-index: 3; background: var(--ink); color: var(--bg); padding: 22px 26px; max-width: min(520px, 84vw); border-radius: 4px; }
.spread-plate .label { color: var(--bg); }
.spread-plate h2 { font-family: var(--font-display); font-weight: 600; letter-spacing: -.015em; line-height: 1.02; font-size: clamp(28px, 3.6vw, 44px); margin: 10px 0 10px; color: var(--bg); text-wrap: balance; }
.spread-plate p { font-family: var(--font-display); font-weight: 500; font-size: 17px; line-height: 1.25; color: rgba(251, 249, 249, .82); margin: 0; }
@media (hover: hover) and (pointer: fine) {
  .spread:hover h2 { text-decoration: underline; text-underline-offset: 3px; }
}
@media (prefers-reduced-motion: reduce) { .spread-media img, .cover-media img { height: 100%; transform: none; } }
/* Cover parallax is desktop-only: on mobile the hero stays a plain 100dvh fill,
   so the dynamic viewport (address bar) can't fight the translated image. */
@media (max-width: 819px) { .cover-media img { height: 100%; transform: none; } }
@media (min-width: 820px) { .spread-plate { left: 40px; bottom: 40px; padding: 28px 32px; } }

/* Masthead floating white over the cover, home only. Inner pages unchanged;
   the wordmark's width-resize animation across navigation still runs. */
body.coverhome .mhead { position: absolute; top: 0; left: 0; right: 0; z-index: 5; }
body.coverhome .wm { color: var(--bg); }
body.coverhome .search-open { color: var(--bg); transition: color var(--dur-micro) var(--ease-out); }
/* Over the photo, SEARCH just fades a touch on hover (not the red child-page tint). */
@media (hover: hover) and (pointer: fine) { body.coverhome .search-open:hover { color: color-mix(in srgb, var(--bg) 70%, transparent); } }

@media (min-width: 820px) {
  .page { padding: 24px 40px 0; }
  .cover-plate { padding: 0 40px 56px; }
  .mast.sm + .search-open { right: 40px; }
  .scroller { padding-right: 40px; }
  .mast.sm .wm { width: 230px; }
  .sect { margin-top: 80px; }
  .catlatest { margin-top: 108px; }
  .scard, .scroll-end { width: 230px; }
  .fone { flex-direction: row; align-items: flex-end; gap: 64px; }
  .fone-copy { flex: 0 0 34%; order: 0; }
  .fone-copy h3 { font-size: 40px; }
  .fone-img { flex: 1; aspect-ratio: 1/1; }
  .trip { grid-template-columns: repeat(3, 1fr); gap: 48px; margin-top: 64px; }
  .trip .tcard .card-img { order: 0; }
  .catgrid { grid-template-columns: repeat(3, 1fr); gap: 44px; }
  .bgroup { flex-direction: row; gap: 16px; align-items: baseline; margin-top: 18px; }
  .bg-k { width: 86px; }
  .bio { font-size: 32px; }
  .about-split { flex-direction: row-reverse; align-items: stretch; gap: 64px; }
  .about-text { flex: 1; }
  .about-media { flex: 1; }
  .about-note { margin-top: auto; }
  .foot { margin-top: 80px; }
  .foot-bot { flex-direction: row; padding: 22px 0 30px; }
  .rlede-row { flex-direction: row-reverse; align-items: flex-end; gap: 64px; }
  .rhero-col { flex: 1; }
  .rhero { aspect-ratio: 4/5; }
  .rlede { flex: 0 0 360px; font-size: 22px; }
  .rbody { gap: 56px; }
  .rrow { grid-template-columns: 180px 1fr; gap: 40px; }
  .rrow .rlabel { position: sticky; top: 24px; align-self: start; }
  .story { font-size: 28px; }
}

/* Search — the ⌘K palette (design/reference/search-palette.html) ---------- */
/* Big-logo pages: the button sits in flow under the wordmark, right-aligned
   (alignment comes free from the shared .page padding). Small-logo pages pin
   it to the top corner; the wordmark's width transition reflows the masthead,
   so the button rides up as the logo shrinks. */
.mhead { position: relative; display: flex; flex-direction: column; align-items: flex-end; gap: 12px; }
.mhead .mast { width: 100%; }
.search-open { display: flex; align-items: center; gap: 8px; font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; font-size: 13px; letter-spacing: .06em; color: var(--ink); }
.mast.sm + .search-open { position: absolute; top: 30px; right: 18px; }
/* The ⌘ K keycap rides a white chip with a small lift, on every page; the
   default border/ink stay so it still reads as the child-page button. */
.search-open .kcap { background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .15); transition: transform var(--dur-micro) var(--ease-out), box-shadow var(--dur-micro) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .search-open:hover { color: var(--red); }
  .search-open:hover .kcap { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0, 0, 0, .13); }
}
.kcap { font-family: 'IBM Plex Mono', monospace; font-size: 12px; letter-spacing: .05em; text-transform: uppercase; border: 1px solid var(--ink30); border-radius: 4px; padding: 2px 6px; color: var(--ink50); }
.spal { border: 0; border-radius: 14px; background: var(--bg); color: var(--ink); width: min(560px, 92vw); padding: 22px 24px 14px; margin-top: 10vh; }
.spal::backdrop { background: rgba(40, 25, 7, .28); }
.spal-q { display: flex; align-items: baseline; gap: 10px; border-bottom: 2px dotted var(--line); padding-bottom: 14px; }
.spal-input { flex: 1; min-width: 0; border: 0; background: none; outline: none; padding: 0; font-family: 'IBM Plex Sans', system-ui, sans-serif; font-size: 20px; color: var(--ink); caret-color: var(--red); }
.spal-input::placeholder { color: var(--ink30); }
#spal-list { max-height: 46vh; overflow-y: auto; }
.spal-k { margin: 16px 0 8px; font-size: 13px; }
.spal-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 9px 12px; margin: 0 -12px; border-radius: 8px; font-family: 'Familjen Grotesk', sans-serif; font-weight: 500; font-size: 18px; cursor: pointer; }
.spal-row.sel { background: var(--primary-100); }
.spal-row .kcap { visibility: hidden; flex: none; }
.spal-row.sel .kcap { visibility: visible; }
.spal-rule { margin: 12px 0 4px; }
/* The chips carry their own top gap. They used to inherit it from the .spal-k
   label above them, which the resting state no longer renders — so when a label
   IS present (a live query), the chips drop the margin and take the label's. */
.spal-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.spal-k + .spal-chips { margin-top: 0; }
.spal-none { font-size: 16px; color: var(--ink50); margin: 18px 0 8px; }
.spal-foot { display: flex; gap: 16px; margin-top: 18px; padding-top: 10px; border-top: 1px solid rgba(40, 25, 7, .15); }
.spal-foot span { font-family: 'IBM Plex Mono', monospace; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink50); }

/* Reduced motion: every animated element opts out */
@media (prefers-reduced-motion: reduce) {
  .wm, .chip, .method-body ol li.step, .step-check, .va-arrow,
  .card-img > img, .card-img > .pat, .search-open .kcap,
  .ing-amt, .ing-head, .ing-extra { transition: none; }
  .card:hover .card-img > img, .card:hover .card-img > .pat,
  .search-open:hover .kcap { transform: none; }
}

/* ── /v2/ — the swaddle-structure homepage (v2.njk) ────────────────────────
   A port of mock-swaddle-layout.html (be1f955) onto this site's tokens, macros
   and data. The mock's numbers come across verbatim, which is why its 10px rem
   canvas comes with them: no other CSS in this repo uses rem (checked), so the
   canvas can be scoped to /v2/ without moving a single pixel on another page.
   Everything below is scoped to html:has(body.v2), body.v2, or .band. */

html:has(body.v2)                { font-size: 10px; }
@media (min-width: 768px)  { html:has(body.v2) { font-size: 11px; } }
@media (min-width: 1280px) { html:has(body.v2) { font-size: 11.5px; } }
@media (min-width: 1600px) { html:has(body.v2) { font-size: 12px; } }
@media (min-width: 2000px) { html:has(body.v2) { font-size: 13px; } }

body.v2 { --vease: cubic-bezier(.16, 1, .3, 1); font-size: 1.6rem; line-height: 1.3; }

/* Bands. One --ground / --on-ground pair each; a variant re-skins by
   redeclaring the pair, never by overriding selectors. Vertical rhythm belongs
   to the blocks inside, the way the mock has it — the band only carries ground. */
.band            { --ground: var(--bg);  --on-ground: var(--ink); background: var(--ground); color: var(--on-ground); }
.band.invert     { --ground: var(--ink); --on-ground: var(--bg); }
.band.invert-red { --ground: var(--red); --on-ground: var(--bg); }

/* The mono label and its em-sized square bullet — the mock's signature mark. */
/* Eyebrow labels carry the accent so they pull the eye; .plain is the quiet
   variant used for meta rows, which inherit the ground's ink instead. */
.v2 .label { font-family: var(--font-mono); font-size: 1.2rem; line-height: 1.125; letter-spacing: .04em; text-transform: uppercase; font-weight: 500; color: var(--red); display: inline-flex; align-items: center; margin: 0; }
.band.invert .label, .band.invert-red .label, .vhero .label, .vfeature .label { color: var(--lime); }
.v2 .label.plain { color: inherit; opacity: .72; }
/* One mono voice. Every mono element sits at the same size and the same weight —
   500, one step up from regular and a real shipped file
   (assets/fonts/ibm-plex-mono-500-latin.woff2), never a synthesised bold. This
   block sits above the drawer rules on purpose: .vdrawer .chip re-sets those
   links in display type and needs to win on source order. The CTA's big arrow
   is deliberately absent — it is a display glyph, not a label. */
.v2 .key, .v2 .chip, .v2 .kcap, .v2 .skip, .v2 .search-open,
.v2 .ing-extra, .v2 .stepcount, .v2 .spal-foot span, .v2 .caption {
  font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500;
}
/* The radius comes off the search palette and its rows, the keycaps, the skip
   link and the last 4px on the figures. Chips are the deliberate exception and
   keep their pill (see below). The old rules stay put — /styleguide/ still
   renders on them. */
.v2 .spal, .v2 .spal-row, .v2 .kcap, .v2 .skip,
.v2 .card-img, .v2 .card-img img, .v2 .rhero img, .v2 .rhero2, .v2 .rhero2 img,
.v2 .about-media img, .v2 .scroll-end, .v2 .foot-img { border-radius: 0; }
/* Chips stay pills — the one rounded thing in the system, on purpose. Their
   label sits lighter than body ink because a chip is a secondary control, not
   something to read first. :not(.on) is load-bearing: the selected chip is
   paper on solid ink and must keep its full contrast. */
/* Scoped to the three places a chip is actually a pill: recipe tags, the
   serving scaler, and the search palette. It is NOT written as a blanket
   `.v2 .chip:not(.on)` — :not() counts as a class, so that selector is (0,3,0)
   and silently outranked every component that re-skins .chip into something
   else (the drawer's display links at (0,2,0), the Load more block). Both went
   dark-on-dark before this was scoped. Name the pill contexts; don't blanket
   the class and then carve exceptions. */
.v2 .tags .chip:not(.on),
.v2 .pills .chip:not(.on),
.v2 .spal-chips .chip:not(.on) { color: var(--ink70); }
.v2 .rule { border: 0; border-top: 1px solid currentColor; margin: 0; }
/* In a colour block the ground already separates the section, so the rule is
   one line too many. */
.band.invert .rule, .band.invert-red .rule { display: none; }

.vsec { padding: 6rem 2rem 0; }
.vsechead { display: flex; justify-content: space-between; align-items: flex-end; gap: 2rem; padding: 1.6rem 0 3.2rem; }
/* The section title IS the link to its browse page, so there is no "view all"
   beside it — only the cap card at the end of the rail. The underline on hover
   is what says the title is a link. */
.vsechead h2 { font-family: var(--font-display); font-weight: 700; font-size: 6.26rem; line-height: 1; letter-spacing: -.02em; margin: 1.2rem 0 0; }
.vsechead h2 a { color: inherit; text-decoration: underline 5px transparent; text-underline-offset: .1em; transition: text-decoration-color .25s var(--vease); }
@media (hover: hover) and (pointer: fine) { .vsechead h2 a:hover { text-decoration-color: currentColor; } }

/* Hero — a full-screen block in normal flow, opaque, scrolls away. Nothing
   fixed, so it never competes with the footer for the viewport. */
.vhero { position: relative; height: 100vh; overflow: hidden; background: var(--ink); color: var(--bg); display: flex; flex-direction: column; justify-content: flex-end; padding: 2rem; text-decoration: none; }
.vhero .vmedia img, .vhero .vmedia .pat { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 0; }
.vhero .scrim { position: absolute; inset: 0; background: linear-gradient(to top, rgba(40, 25, 7, .82) 0%, rgba(40, 25, 7, .35) 45%, rgba(40, 25, 7, .5) 100%); }
.vhero .inner { position: relative; z-index: 2; max-width: 80%; }
.vhero h1 { font-family: var(--font-display); font-weight: 700; font-size: 9.6rem; line-height: 1; letter-spacing: -.02em; margin: 1.2rem 0 0; text-decoration: underline 6px transparent; text-underline-offset: .1em; transition: text-decoration-color .3s var(--vease); }
.vhero .dek { font-size: 2.4rem; line-height: 1.25; max-width: 46ch; margin: 2rem 0 0; }
.vhero .meta { position: relative; z-index: 2; display: flex; gap: 3.2rem; margin-top: 2.4rem; flex-wrap: wrap; }
@media (hover: hover) and (pointer: fine) { .vhero:hover h1 { text-decoration-color: currentColor; } }
/* overflow:hidden is what lets a line rise out of a clipped box, and it is also
   what guillotines every descender: pad the box past them, pull the same amount
   back off the layout. */
.vline { display: block; overflow: hidden; padding-bottom: .12em; margin-bottom: -.12em; }
.vline > span { display: block; animation: vrise 1.15s var(--vease) both; }
@keyframes vrise { from { transform: translateY(102%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.vfade { animation: vfade 1.6s var(--vease) both; }
@keyframes vfade { from { opacity: 0; } to { opacity: 1; } }
.vcue { position: absolute; right: 2rem; bottom: 2rem; z-index: 2; animation: vbob 2.4s var(--vease) infinite; }
@keyframes vbob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(.6rem); } }

/* Rails — .scroller supplies the mechanics unchanged (mandatory snap, momentum,
   no scroll-behavior:smooth, no wheel handler); .vrail only resizes them.
   align-items:stretch plus margin-top:auto on the meta row is what lands every
   card's SERVES/TIME on one line across the rail. */
.scroller.vrail { align-items: stretch; gap: 2rem; scroll-padding-left: 2rem; margin: -4px 0 0 -4px; padding: 4px 0 8rem 2rem; scrollbar-width: none; }
.scroller.vrail::-webkit-scrollbar { height: 0; display: none; }
.vcard { flex: 0 0 32rem; scroll-snap-align: start; display: flex; flex-direction: column; color: inherit; text-decoration: none; }
.vcard.wide  { flex-basis: 46rem; }
.vcard.drink { flex-basis: 26rem; }
/* The generated art is 2:3 portrait, every one of them. 2/3 crops nothing. */
.vthumb { overflow: hidden; aspect-ratio: 2/3; background: var(--ink30); }
.vthumb img, .vthumb .pat { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 0; transition: transform .7s var(--vease); }
.vcard h3 { font-family: var(--font-display); font-weight: 700; font-size: 2.4rem; line-height: 1.2; margin: 1.6rem 0 0; text-decoration: underline 3px transparent; text-underline-offset: .16em; transition: text-decoration-color .25s var(--vease); }
/* The dek belongs to the two sections that are given room for it — Featured
   (.vtrio) and Worth the afternoon (.vcard.wide). Everywhere else a card is
   art, title and meta. Both exceptions are (0,3,0) against the base rule's
   (0,2,0), so they win on specificity and not on where they sit in the file.
   This stays in CSS rather than the macro because it is a property of the
   section, which the macro can't see — unlike the overline, which no card
   anywhere shows and so came out of the markup entirely. */
.vcard .dek { display: none; font-size: 1.6rem; line-height: 1.35; margin: .5rem 0 0; opacity: .72; }
.vtrio .vcard .dek, .vcard.wide .dek { display: block; }
.vcard .cfoot { display: flex; gap: 2rem; margin-top: auto; padding-top: 1.6rem; }
.vcard.drink h3 { font-size: 2rem; }
@media (hover: hover) and (pointer: fine) {
  .vcard:hover .vthumb img, .vcard:hover .vthumb .pat { transform: scale(1.07); }
  .vcard:hover h3 { text-decoration-color: currentColor; text-decoration-line: underline; }
}

/* The view-all idiom and the cap card that ends every rail. Colour shift plus
   the arrow moving is the whole affordance — an underline collided with the
   arrow and read as clutter at label size. */
.v2 .viewall { flex: 0 0 auto; font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; display: inline-flex; align-items: center; gap: .8rem; color: inherit; text-decoration: none; transition: color .2s ease; }
.v2 .va-arrow { display: inline-block; transition: transform .15s ease; }
.vrailend { flex: 0 0 26rem; align-self: stretch; scroll-snap-align: end; margin-right: 2rem; display: flex; align-items: center; justify-content: center; gap: 1rem; background: var(--red); color: var(--bg); font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; text-decoration: none; transition: background-color .25s var(--vease), color .25s var(--vease); }
/* On an inverted band the red block sinks into the ground, so it flips to paper. */
.invert .vrailend, .invert-red .vrailend { background: var(--bg); color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .v2 .viewall:hover { color: var(--red); text-decoration: none; }
  .invert .viewall:hover, .invert-red .viewall:hover { color: var(--lime); }
  .v2 .viewall:hover .va-arrow { transform: translateX(4px); }
  .vrailend:hover, .invert .vrailend:hover, .invert-red .vrailend:hover { background: var(--lime); color: var(--ink); }
  .vrailend:hover .va-arrow { transform: translateX(6px); }
}

/* Featured triplet. */
.vtrio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; padding: 0 2rem 8rem; }

/* Full-bleed feature — a real parallax. background-attachment:fixed pins the
   image to the viewport while the band scrolls over it, and it also means cover
   sizes against the VIEWPORT, so a 2:3 portrait fills the height instead of
   showing a letterboxed sliver. No hover; the parallax is the motion. */
.vfeature { position: relative; min-height: 100vh; display: flex; align-items: flex-end; padding: 2rem; color: var(--bg); text-decoration: none; background-size: cover; background-position: center; background-attachment: fixed; }
/* iOS/iPadOS Safari does NOT fall back to a still — it keeps `fixed` but sizes
   `cover` against the whole DOCUMENT instead of the viewport, so on a long home
   page the image scales up enormously and the band shows a blown-out crop of
   the middle. Turn the parallax off there. `-webkit-touch-callout` is WebKit-on-
   iOS only, which catches iPad even with a trackpad attached (where the pointer
   reads as fine); the coarse-pointer arm covers other touch browsers. */
@supports (-webkit-touch-callout: none) {
  .vfeature { background-attachment: scroll; }
}
@media (hover: none), (pointer: coarse) {
  .vfeature { background-attachment: scroll; }
}
.vfeature .scrim { position: absolute; inset: 0; background: linear-gradient(to top, rgba(97, 24, 24, .8), rgba(97, 24, 24, .15)); }
/* Half the width, so the title breaks rather than running the band. */
.vfeature .inner { position: relative; z-index: 2; max-width: 50%; }
.vfeature h3 { font-family: var(--font-display); font-weight: 700; font-size: 6.4rem; line-height: 1; letter-spacing: -.02em; margin: 1.2rem 0 0; text-decoration: underline 6px transparent; text-underline-offset: .1em; transition: text-decoration-color .3s var(--vease); }
@media (hover: hover) and (pointer: fine) { .vfeature:hover h3 { text-decoration-color: currentColor; } }
.vfeature .dek { font-size: 2.4rem; line-height: 1.25; margin: 2rem 0 0; }
.vfeature .meta { display: flex; gap: 3.2rem; flex-wrap: wrap; margin-top: 4.8rem; }

/* Series spread. display:block is load-bearing — this band is an <a>, and an
   inline box paints its background around itself only, so the block children
   render outside it and the section reads as transparent. */
/* No `color: inherit` here. This element IS the .band, and .band's own
   `color: var(--on-ground)` is a single-class rule, so a `color: inherit` on
   .vspread (also single-class, declared later) would beat it on source order
   and pull ink down from <body> — ink type on the ink ground. */
.vspread { display: block; text-decoration: none; }
.vspread .in { display: grid; grid-template-columns: 1.15fr .85fr; gap: 2rem; padding: 6rem 2rem 8rem; align-items: center; }
.vspread .rule { margin-bottom: 2.4rem; }
.vspread .viewall { display: inline-flex; margin-top: 2.4rem; }
/* 4/5, not the cards' 2/3: this figure sits beside a short text column at over
   half the page width, so the native ratio would tower past the viewport. */
.vspread .fig { overflow: hidden; aspect-ratio: 1/1; }
.vspread .fig img, .vspread .fig .pat { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 0; transition: transform 1.2s var(--vease); }
.vspread h2 { font-family: var(--font-display); font-weight: 700; font-size: 6.4rem; line-height: 1; letter-spacing: -.02em; margin: 1.2rem 0 0; text-decoration: underline 6px transparent; text-underline-offset: .1em; transition: text-decoration-color .3s var(--vease); }
.vspread .dek { font-size: 2.4rem; line-height: 1.25; margin: 2rem 0 0; max-width: 32ch; }
@media (hover: hover) and (pointer: fine) {
  .vspread:hover .fig img { transform: scale(1.05); }
  .vspread:hover h2 { text-decoration-color: currentColor; }
}

/* About. Every ink value here inherits from the band, so the colophon headline
   can't go ink-on-ink the way a hardcoded var(--ink) does. */
.vabout { display: grid; grid-template-columns: 1.1fr .9fr; gap: 2rem; padding: 0 2rem 8rem; align-items: stretch; }
.vabout .col { display: flex; flex-direction: column; justify-content: space-between; }
.vabout .portrait { position: relative; overflow: hidden; aspect-ratio: 4/5; }
.vabout .portrait img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 0; transition: transform 1.2s var(--vease); }
.vabout .vbio p { font-family: var(--font-display); font-weight: 400; font-size: 3.2rem; line-height: 1.2; margin: 0 0 2.4rem; max-width: 26ch; color: inherit; }
.vabout .slop { border-top: 1px solid currentColor; padding-top: 1.6rem; margin-top: 4rem; }
.vabout .slop h4 { font-family: var(--font-display); font-weight: 700; font-size: 2.4rem; margin: 1.2rem 0 .8rem; color: inherit; }
.vabout .slop p { font-size: 1.6rem; line-height: 1.45; max-width: 56ch; opacity: .82; margin: 0; color: inherit; }
@media (hover: hover) and (pointer: fine) { .vabout:hover .portrait img { transform: scale(1.04); } }

/* Index — title rows with a cursor-following image peek. The row slides right
   on hover and every other row dims, so the peek reads as belonging to one row. */
.vindex { padding: 0 2rem 6rem; }
.vindex a { display: grid; grid-template-columns: 1fr auto auto; gap: 2.4rem; align-items: baseline; padding: 1.8rem 0; border-top: 1px solid currentColor; position: relative; color: inherit; text-decoration: none; transition: padding-left .4s var(--vease), opacity .3s var(--vease); }
.vindex a:last-of-type { border-bottom: 1px solid currentColor; }
.vindex .t { font-family: var(--font-display); font-weight: 700; font-size: 3.2rem; line-height: 1.1; }
.vindex .m { font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500; letter-spacing: .04em; text-transform: uppercase; }
.vidx-img { display: none; }
@media (hover: hover) and (pointer: fine) {
  .vindex a:hover { padding-left: 1.6rem; }
  .vindex:hover a { opacity: .38; }
  .vindex:hover a:hover { opacity: 1; }
}
#vpeek { position: fixed; top: 0; left: 0; width: 24rem; height: 36rem; z-index: 30; pointer-events: none; opacity: 0; transform: translate(-50%, -50%) scale(.86); transition: opacity .3s var(--vease), transform .5s var(--vease); overflow: hidden; background: var(--ink); }
#vpeek.on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
#vpeek img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* All-recipes CTA — edge to edge, lime wiping up from the bottom on hover.
   The big arrow is IBM Plex Mono, not Familjen Grotesk: same character,
   completely different object at display size (the grotesk one goes soft and
   rounded, the mono one keeps a hard square terminal). */
.vcta { display: block; position: relative; overflow: hidden; padding: 8rem 2rem; border-top: 1px solid currentColor; text-decoration: none; }
.vcta .wipe { position: absolute; inset: 0; background: var(--lime); transform: scaleY(0); transform-origin: bottom; transition: transform .6s var(--vease); }
.vcta .vcta-in { position: relative; z-index: 2; display: flex; align-items: center; justify-content: flex-start; gap: 3.2rem; transition: color .4s var(--vease); }
.vcta h2 { font-family: var(--font-display); font-weight: 700; font-size: 9.6rem; line-height: .95; letter-spacing: -.02em; margin: 1.2rem 0 0; }
.vcta .bigarrow { font-family: var(--font-mono); font-size: 12rem; line-height: .8; flex: 0 0 auto; transition: transform .6s var(--vease); }
@media (hover: hover) and (pointer: fine) {
  .vcta:hover .wipe { transform: scaleY(1); }
  .vcta:hover .vcta-in { color: var(--ink); }
  .vcta:hover .bigarrow { transform: translateX(3.2rem); }
}

/* Browse the drawer — browseblock.njk's own markup, re-laid as columns. Course
   and Occasion are sized to their content and packed left; Made with takes the
   whole remainder at three columns (1fr each left a dead gap in the middle).
   The links slide and go lime on hover, same idiom as the index rows.
   browseblock's small heading is hidden; .vsechead supplies the big one. */
.vdrawer { display: grid; grid-template-columns: 22rem 22rem 1fr; gap: 2rem; padding: 0 2rem 8rem; align-items: start; }
/* browseblock.njk opens with its own small "Browse the drawer" heading; .vsechead
   already supplies the big one. Needs .v2 in front to outrank .v2 .label, which
   sets display:inline-flex and would otherwise win the specificity tie. */
.v2 .vdrawer > h2 { display: none; }
.vdrawer .bgroup { display: block; margin: 0; }
.vdrawer .bg-course   { grid-column: 1; grid-row: 1 / span 2; }
.vdrawer .bg-occasion { grid-column: 2; grid-row: 1; }
.vdrawer .bg-diet     { grid-column: 2; grid-row: 2; margin-top: 3.2rem; }
.vdrawer .bg-made     { grid-column: 3; grid-row: 1 / span 2; }
.vdrawer .bg-k { font-family: var(--font-mono); font-size: 1.2rem; letter-spacing: .04em; text-transform: uppercase; font-weight: 500; color: inherit; opacity: .6; width: auto; margin: 0 0 1.6rem; }
.vdrawer .bchips { display: block; }
.v2 .vdrawer .chip { display: block; background: none; border-radius: 0; padding: 0; color: inherit; font-family: var(--font-display); font-weight: 500; font-size: 2.4rem; line-height: 1.5; letter-spacing: 0; text-transform: none; transition: transform .2s ease, color .2s ease; }
.vdrawer .bg-made .bchips { columns: 3; column-gap: 2rem; }
.v2 .vdrawer .bg-made .chip { break-inside: avoid; }
@media (hover: hover) and (pointer: fine) {
  .v2 .vdrawer .chip:hover { transform: translateX(.8rem); color: var(--lime); background: none; }
}

/* The footer reveal. .foot leaves normal flow to become the one fixed layer at
   z-index:-1, and the gap it shows through is a 100vh bottom MARGIN on the
   scrolling content — never a spacer div, because a transparent div sitting
   over a fixed footer eats every click on it. flow-root on .app keeps that
   margin from collapsing out of the document. The paper ground moves off <body>
   (a body background would paint over a z-index:-1 layer) onto #main, and
   <html> takes the ink so overscroll can't flash white. */
html:has(body.v2) { background: var(--ink); }
body.v2 { background: transparent; }
body.v2 .app { display: flow-root; }
body.v2 #main { background: var(--bg); margin-bottom: 100vh; }
/* The masthead sits outside #main, so without its own ground the fixed footer
   showed straight through the header strip. Not on the cover pages, where the
   masthead is meant to float over the hero. */
body.v2 .mhead { background: var(--bg); }
/* On a page with the half-and-half head the masthead floats instead, so the art
   runs to the very top. The wordmark sits over the paper column and stays ink;
   the search button sits over the art, so it reverses out. */
body.v2.coverhome .mhead, body.v2:has(.vart) .mhead { background: none; position: absolute; top: 0; left: 0; right: 0; z-index: 5; }
body.v2:has(.vart) .search-open { color: var(--bg); }
@media (hover: hover) and (pointer: fine) { body.v2:has(.vart) .search-open:hover { color: var(--lime); } }
body.v2 .page:has(> .foot) { padding: 0; }
body.v2 .foot { position: fixed; left: 0; right: 0; bottom: 0; height: 100vh; z-index: -1; margin: 0; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; background: var(--ink); color: var(--bg); }
body.v2 .foot-art { flex: 1 1 auto; min-height: 0; overflow: hidden; }
body.v2 .foot-img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }
body.v2 .foot-dfc { object-fit: contain; }
body.v2 .foot-bot { padding: 1.8rem 2rem 2.6rem; }
body.v2 .foot-bot span { font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500; color: rgba(251, 249, 249, .75); }

/* Article head — half and half, exactly one screen. The masthead floats over it
   (see body.v2:has(.vart) below), so both columns are a flat 100vh and the art
   starts at the very top of the page. The copy is bottom-aligned with generous
   top padding, which keeps a long title clear of the floating masthead. */
.vart { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: start; }
/* Explicit placement, not source order: the figure leads in the markup so print
   can float it from the top, but on screen the copy still holds the left column. */
.vart-copy { grid-column: 1; grid-row: 1; display: flex; flex-direction: column; justify-content: flex-end; padding: 12rem 2rem 6rem; min-height: 100vh; }
.vart-title { font-family: var(--font-display); font-weight: 700; font-size: 6.4rem; line-height: 1; letter-spacing: -.02em; margin: 1.2rem 0 0; }
.vart-dek { font-size: 2.4rem; line-height: 1.25; margin: 2rem 0 0; max-width: 34ch; }
/* The caption sits under the art in normal flow, not laid over it — over the
   image it fought the art for the same pixels and had to be scrimmed to read. */
.vart-fig { grid-column: 2; grid-row: 1; margin: 0; display: flex; flex-direction: column; gap: 1.2rem; }
.vart-fig img { width: 100%; height: 100vh; object-fit: cover; display: block; border-radius: 0; }
.vart-fig figcaption { padding-right: 2rem; }

/* Browse — the homepage card in a grid instead of a rail. "Load more" drops the
   pill and takes the cap card's block treatment, so the two ways of reaching
   more recipes look like the same control. */
.v2 .loadmore { display: inline-flex; align-items: center; justify-content: center; border-radius: 0; background: var(--red); color: var(--bg); font-family: var(--font-mono); font-size: 1.2rem; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; padding: 2.4rem 6rem; margin: 6rem 0 0; transition: background-color .25s var(--vease), color .25s var(--vease); }
@media (hover: hover) and (pointer: fine) { .v2 .loadmore:hover { background: var(--lime); color: var(--ink); } }
.vbrowsehead { padding: 4rem 0 2rem; }
.vgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4rem 2rem; margin-top: 3.2rem; }
/* In a grid the card is a grid item, so its rail flex-basis no longer applies. */
.vgrid .vcard { flex: 1 1 auto; }

@media (max-width: 900px) {
  .vart { grid-template-columns: 1fr; }
  /* Single column here, so the explicit desktop placement has to be released or
     grid-column: 2 would invent a second column. */
  .vart-copy, .vart-fig { grid-column: 1; grid-row: auto; }
  /* Side padding is 1.8rem, not 2rem, so the title and dek line up with the
     recipe body below them — .page sits at 18px. */
  .vart-copy { min-height: 0; padding: 3rem 1.8rem 0; order: 2; }
  .vart-title { font-size: 3.6rem; }
  /* Stacked, the head's own bottom padding and .rbody's 44px top margin used to
     compound into an 84px chasm under the dek, against 42px above the title.
     Zeroing one and cutting the other balances the two. */
  .rbody { margin-top: 30px; }
  .vart-fig { order: 1; }
  .vart-fig img { height: auto; aspect-ratio: 4/5; }
  .vart-fig figcaption { padding: 0 1.8rem; }
  .vgrid { grid-template-columns: repeat(2, 1fr); gap: 6rem 2rem; }
  .vfeature .inner { max-width: 100%; }
  .vfeature h3 { font-size: 3.6rem; }
  .vhero h1 { font-size: 4.4rem; }
  .vhero .inner { max-width: 100%; }
  .vsechead h2 { font-size: 3.6rem; }
  .vtrio { grid-template-columns: 1fr; gap: 8rem; }
  .vspread .in { grid-template-columns: 1fr; }
  .vabout { grid-template-columns: 1fr; }
  .vindex .t { font-size: 2.4rem; }
  .vindex a { grid-template-columns: 1fr auto; align-items: center; gap: 1.2rem 2rem; }
  .vindex .t, .vindex .m { grid-column: 1; }
  .vidx-img { display: block; grid-column: 2; grid-row: 1 / span 3; align-self: center; width: 6rem; height: 9rem; object-fit: cover; border-radius: 0; }
  .vcta h2 { font-size: 4.4rem; }
  .vcta .bigarrow { font-size: 5.6rem; }
  .vcard { flex-basis: 26rem; }
  .vcard.wide { flex-basis: 30rem; }
  .vdrawer { grid-template-columns: repeat(2, 1fr); gap: 3rem; }
  .vdrawer .bg-course, .vdrawer .bg-occasion, .vdrawer .bg-diet { grid-column: auto; grid-row: auto; }
  .vdrawer .bg-diet { margin-top: 0; }
  .vdrawer .bg-made { grid-column: 1 / -1; grid-row: auto; }
  .vdrawer .bg-made .bchips { columns: 2; }
  #vpeek { display: none; }
}

@media (max-width: 560px) {
  .vgrid { grid-template-columns: 1fr; gap: 8rem; }
}

@media (prefers-reduced-motion: reduce) {
  .vline > span, .vfade, .vcue { animation: none; }
  .vthumb img, .vthumb .pat, .vspread .fig img, .vabout .portrait img,
  .vcta .wipe, .vcta .bigarrow, .vindex a, .vdrawer .chip { transition: none; }
  .vfeature { background-attachment: scroll; }
}


@media print {
  @page { margin: 15mm; }
  * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* #main carries the tinted --bg on screen, so whitening html/body alone still
     prints a colored sheet. */
  html, body, body.v2 #main { background: #fff !important; }
  /* The 100vh bottom margin reveals the fixed footer on screen. On paper it is
     a whole blank sheet. */
  body.v2 #main { margin-bottom: 0 !important; }
  /* Paper gets the recipe and nothing else: masthead, footer, the article art,
     the serving scaler, the step counter, and both recipe-page bands (More to
     cook, the drawer). */
  .mhead, .foot, .serv, .step-check, [data-steps-count],
  .rhero2, .band { display: none !important; }
  /* The top of the page is two real columns, no wrapping. The title block, the
     glance and the story live in three different containers on screen, so print
     dissolves those containers with display: contents and lays every one of
     their children out on a single grid owned by #main. */
  body.v2 #main { display: grid !important; position: relative; grid-template-columns: 1fr 1fr; column-gap: 34px; align-items: start; }
  .vart, .page.body, .rbody { display: contents !important; }
  /* The art is positioned absolutely (see below), so it no longer reserves its
     own cells and auto-placement will happily drop a full-width row into the
     space beside the copy. This empty grid item holds that column shut. It sizes
     nothing: no content, and its rows are sized by the left column. */
  body.v2 #main::before { content: ''; grid-column: 2; grid-row: 1 / 5; }
  .vart-copy   { grid-column: 1; grid-row: 1; margin-bottom: 20px !important; }
  /* The rule and the two section labels are screen navigation. On one printed
     page the reader can see the whole thing, so the copy runs unannounced. */
  .rbody .rule { display: none !important; }
  .rrow-glance .rlabel, .rrow-story .rlabel { display: none !important; }
  /* Doubled class to outrank the generic .rrow margin below, which is declared
     later and would otherwise win the tie and break the left column's rhythm. */
  .rrow.rrow-glance { grid-column: 1; grid-row: 3; margin-bottom: 20px !important; }
  .rrow.rrow-story  { grid-column: 1; grid-row: 4; margin-bottom: 0 !important; }
  /* Absolute, so the art does not feed its own height back into the four rows it
     spans — otherwise a 1px rule ends up in an 82px row and the left column is
     pried apart by the picture. Placed on a grid it does not size, it takes the
     grid area as its containing block and crops to exactly where the copy ends. */
  .vart-fig { position: absolute; inset: 0; grid-column: 2; grid-row: 1 / 5;
              display: flex !important; flex-direction: column; gap: 0; margin: 0; overflow: hidden; }
  .vart-fig img { flex: 1 1 auto; min-height: 0; width: 100%; height: auto !important; object-fit: cover; object-position: center; aspect-ratio: auto !important; }
  .vart-fig figcaption { padding: 0 !important; margin-top: 8px; flex: none; }
  /* Ingredients onward is full width again. The top margin lives here rather
     than on the story's bottom, because the story's bottom edge is what the art
     crops to — padding it would push the picture down with it. */
  .rrow-ings, .rrow-ings ~ .rrow { grid-column: 1 / -1; margin-top: 34px !important; }
  /* On screen the title block is a 100vh flex column pinned to the bottom. On
     paper it is a plain block at the top of the left column. */
  .vart-copy { display: block !important; min-height: 0 !important; padding: 0 !important; }
  .vart-title { font-size: 34px !important; margin: 0 !important; }
  .vart-dek { font-size: 16px !important; line-height: 1.5 !important; color: var(--ink) !important; max-width: none !important; margin: 10px 0 0 !important; }
  .page.body { padding: 0 !important; }
  .rrow { display: block !important; margin-bottom: 0 !important; }
  .rrow .rlabel { position: static !important; margin-bottom: 10px; }
  /* With the At a Glance and Story headers gone, the glance keys are the only
     labels left on the page, so they take the same red as Ingredients and
     Method rather than sitting alone in gray. */
  .rrow-glance .key { color: var(--red) !important; }
  /* Both of these are display type on screen. On paper they are just the data
     and the story, so they read at body size. */
  .glance .v { font-family: inherit !important; font-weight: 400 !important; font-size: 16px !important; line-height: 1.4 !important; }
  .story { font-family: inherit !important; font-weight: 400 !important; font-size: 16px !important; line-height: 1.5 !important; max-width: none !important; }
  ul.ings, ol.method, .method-body ol { max-width: none !important; }
  /* On screen the steps are a checklist: no marker, 36px reserved for the check
     circle. Paper has no checkboxes, so the list goes back to being numbered. */
  ol.method, .method-body ol { list-style: decimal !important; padding-left: 24px !important; gap: 16px !important; }
  ol.method li, .method-body ol li { padding-left: 6px !important; }
  ol.method li::marker, .method-body ol li::marker { font-weight: 600; }
  ul.ings li, ol.method li, .method-body ol li { break-inside: avoid; }
  /* Checklist state is a screen affordance; paper prints the full list clean. */
  ul.ings li.ing.struck .ing-amt, ul.ings li.ing.struck .ing-head { text-decoration: none !important; color: var(--ink) !important; }
  ul.ings li.ing.struck .ing-extra { color: var(--ink50) !important; }
  .method-body ol li.done { color: var(--ink) !important; }
}
