/* 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: var(--font-body);
  -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: var(--sp-8); top: -60px; z-index: 10; background: var(--ink); color: var(--bg); padding: var(--sp-8) var(--sp-16); border-radius: 999px; font-family: var(--font-mono); text-transform: uppercase; font-size: var(--fs-13); letter-spacing: .04em; text-decoration: none; }
.skip:focus { top: var(--sp-8); }
/* 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: var(--sp-24) var(--gutter) 0; }
.mhead { padding-bottom: 0; }
.page.body { padding-top: var(--sp-8); }
/* 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; }


/* Primitives --------------------------------------------------------------- */
.key { font-family: var(--font-mono); text-transform: uppercase; font-size: 12px; letter-spacing: .1em; color: var(--ink50); }
.caption { font-family: var(--font-body); font-size: var(--fs-13); 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: var(--font-mono); text-transform: uppercase; font-size: var(--fs-13); letter-spacing: .04em; padding: var(--sp-8) var(--sp-16); 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: color-mix(in srgb, var(--red) 18%, transparent); } }
.chip.on { background: var(--ink); color: var(--bg); }
.seclabel { margin: 0 0 24px; }


/* Images ------------------------------------------------------------------- */
/* 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: color-mix(in srgb, var(--ink) 7%, transparent);  --pat-fg: var(--ink); }
.pat-red  { --pat-bg: color-mix(in srgb, var(--red) 9%, transparent); --pat-fg: var(--red); }
.pat-blue { --pat-bg: color-mix(in srgb, var(--blue) 7%, transparent); --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)); }

/* Home: latest scroller + repeats ----------------------------------------- */
/* The scroll mechanics only. Every rail in the built site carries .vrail too,
   which owns the sizing: gap, padding and the margins that give the focus ring
   room inside the overflow box and let the rail bleed past the band's gutter.
   The size and bleed declarations that used to sit here were overridden on
   every element that has ever used this class, one of them by viewport math
   (`calc(50% - 50vw)`) that no longer ran anywhere; deleted 2026-09-16. */
.scroller { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
/* Footer: its own sheet, assets/footer.css, linked from _includes/footer.njk. */

/* Category browse ---------------------------------------------------------- */
.catgrid { display: grid; grid-template-columns: 1fr; gap: 34px; margin-top: 36px; }
/* 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 ------------------------------------------------------------ */
.rbody { position: relative; margin-top: var(--sp-48); display: flex; flex-direction: column; gap: var(--sp-40); }
.rrow { display: grid; grid-template-columns: 1fr; gap: var(--sp-16); }

.glance { display: flex; flex-wrap: wrap; gap: var(--sp-24) var(--sp-32); }
.kk { margin-bottom: 5px; }
.glance .v { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-20); line-height: 1; }
.tags { display: flex; flex-wrap: wrap; gap: var(--sp-8); }
.serv { margin-top: 0; }
.ataglance { display: flex; flex-wrap: wrap; gap: 22px 44px; align-items: flex-start; }
.serv-k { margin-bottom: var(--sp-8); }
.pills { display: flex; gap: var(--sp-8); flex-wrap: wrap; }
.story { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-24); 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: var(--fs-16); line-height: 1.4; padding:  0 0 var(--sp-16); margin:  0 0 var(--sp-8); 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: var(--sp-4); font-family: var(--font-mono); font-size: var(--fs-13); 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); }
}

/* A sauce on this list the house makes rather than buys, as a card under the
   ingredients. Sits below the <ul>, never inside a row: every li.ing is a
   tap-to-strike button, so a link in there fires two things at once.
   The tint block is the device — no rule down the edge, no second border. */
.benchcards { display: flex; flex-direction: column; gap: var(--sp-8); max-width: 520px; margin-top: var(--sp-24); }
.benchcard { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-16); padding: var(--sp-16); border-radius: 4px; background: var(--primary-100); color: var(--red); text-decoration: none; transition: background var(--dur-micro) ease, color var(--dur-micro) ease, transform var(--dur-micro) var(--ease-out), box-shadow var(--dur-micro) var(--ease-out); }
.benchcard-t { display: flex; flex-direction: column; gap: var(--sp-4); }
/* --ink70, not --ink50: on the primary-100 tint --ink50 measures 4.42:1 and
   misses AA at 12px. check:contrast pairs tokens against the page background
   and cannot see a pairing that only exists inside a tinted block. */
.benchcard-kick { font-family: var(--font-mono); text-transform: uppercase; font-size: var(--fs-12); letter-spacing: .06em; color: var(--ink70); }
.benchcard-name { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-16); line-height: 1.25; }
.benchcard .va-arrow { flex: 0 0 auto; font-size: var(--fs-16); }
@media (hover: hover) and (pointer: fine) {
  .benchcard:hover { background: var(--red); color: var(--bg); transform: translateY(-.5px); box-shadow: 0 3px 8px color-mix(in srgb, var(--ink) 12%, transparent); }
  .benchcard:hover .benchcard-kick { color: var(--bg); }
  .benchcard:hover .va-arrow { transform: translateX(6px); }
}

.method-body ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-32); max-width: 620px; }
.method-body ol li { position: relative; padding-left: var(--sp-40); font-size: var(--fs-16); line-height: 1.5; }
.method-body ol li p { margin: 0; }
.method-body > p { font-size: var(--fs-16); line-height: 1.5; max-width: 620px; margin: var(--sp-32) 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: var(--sp-8); }


/* 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: var(--sp-24) var(--sp-40) 0; }
  .mast.sm + .mnav { right: var(--sp-40); }
  .mast.sm .wm { width: 230px; }
  .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; }
  .rbody { gap: var(--sp-64); }
  .rrow { grid-template-columns: 180px 1fr; gap: var(--sp-40); }
  .rrow .rlabel { position: sticky; top: var(--sp-24); align-self: start; }
  /* Second row of the content column, under the ingredient list —
     never auto-placed into the 180px label gutter. */
  .rrow > .benchcards { grid-column: 2; }
  .story { font-size: var(--fs-28); }
}

/* 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: var(--sp-16); }
.mhead .mast { width: 100%; }
/* The nav holds the two links and the search button in one row; the links
   borrow the search button's mono type so the three read as one control set. */
/* Reserve the scrollbar's room on every page: with classic scrollbars a page
   that gets its scrollbar a beat late shifted the right-pinned nav sideways. */
html { scrollbar-gutter: stable; }
.mnav { display: flex; align-items: center; gap: var(--sp-24); }
/* Over art (the homepage cover, a recipe's picture) the bar gets a soft ink
   scrim from the top, so light type reads on pale paintings as well as dark
   ones (Rex, 2026-09-21: "these dont read super well on images"). The scrim
   sits behind the bar's own content and never catches a click. */
body.coverhome .mhead { isolation: isolate; }
body.coverhome .mhead::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 14rem; background: linear-gradient(to bottom, color-mix(in srgb, var(--ink) 55%, transparent), transparent); pointer-events: none; z-index: -1; }
body.coverhome .mnav, body.v2:has(.vart) .mnav { text-shadow: 0 1px 6px color-mix(in srgb, var(--ink) 45%, transparent); }
body.coverhome .wm-mono, body.v2:has(.vart) .wm-mono { filter: drop-shadow(0 1px 6px color-mix(in srgb, var(--ink) 45%, transparent)); }
/* Over a recipe's art the nav links go light, the same as Search does. */
body.v2:has(.vart) .mnav a { color: var(--bg); }
@media (hover: hover) and (pointer: fine) { body.v2:has(.vart) .mnav a:hover { color: var(--lime); } }
/* On a phone the small wordmark on inner pages gives way to the AS monogram so
   the nav has room. The home page keeps the full wordmark; its nav sits below. */
.wm-mono { display: none; height: 2.6rem; width: auto; color: var(--ink); }
body.coverhome .wm-mono, body.v2:has(.vart) .wm-mono { color: var(--bg); }
@media (max-width: 560px) {
  .mast.sm .wm { display: none; }
  .mast.sm .wm-mono { display: block; }
  /* The monogram and the nav share one line: the nav's top matches the
     monogram's middle, so the two read as a single bar. */
  .mast.sm + .mnav { top: calc(var(--sp-32) + 0.5rem); }
}
.mnav a { font-family: var(--font-mono); text-transform: uppercase; font-size: var(--fs-12); letter-spacing: .06em; color: var(--ink); text-decoration: underline 1px transparent; text-underline-offset: 4px; }
.mnav a[aria-current="page"] { text-decoration-color: currentColor; }
@media (hover: hover) and (pointer: fine) { .mnav a:hover { color: var(--red); } }
body.coverhome .mnav a { color: var(--bg); }
@media (hover: hover) and (pointer: fine) { body.coverhome .mnav a:hover { color: color-mix(in srgb, var(--bg) 70%, transparent); } }
@media (max-width: 560px) { .mnav { gap: var(--sp-16); } .mnav .kcap { display: none; } }
.search-open { display: flex; align-items: center; gap: var(--sp-8); font-family: var(--font-mono); text-transform: uppercase; font-size: var(--fs-12); letter-spacing: .06em; color: var(--ink); }
.mast.sm + .mnav { position: absolute; top: var(--sp-32); right: var(--gutter); }
/* 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: var(--paper); 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: var(--font-mono); font-size: var(--fs-12); letter-spacing: .05em; text-transform: uppercase; border: 1px solid var(--ink30); border-radius: 4px; padding: 2px var(--sp-8) /* 2px: a key cap glyph, below the grid\'s half-step on purpose */; color: var(--ink50); }
.spal { border: 0; border-radius: 14px; background: var(--bg); color: var(--ink); width: min(560px, 92vw); padding: var(--sp-24) var(--sp-24) var(--sp-16); margin-top: 10vh; }
.spal::backdrop { background: color-mix(in srgb, var(--ink) 28%, transparent); }
.spal-q { display: flex; align-items: baseline; gap: var(--sp-8); border-bottom: 1px solid var(--ink30); padding-bottom: var(--sp-16); }
.spal-input { flex: 1; min-width: 0; border: 0; background: none; outline: none; padding: 0; font-family: var(--font-body); font-size: var(--fs-20); color: var(--ink); caret-color: var(--red); }
.spal-input::placeholder { color: var(--ink30); }
#spal-list { max-height: 46vh; overflow-y: auto; }
.spal-k { margin: var(--sp-16) 0 var(--sp-8); font-size: var(--fs-13); }
.spal-row { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-8); padding: var(--sp-8) var(--sp-16); margin: 0 calc(-1 * var(--sp-16)) /* cancels the row\'s own inline padding so text aligns with the query */; border-radius: 8px; font-family: var(--font-display); font-weight: 500; font-size: var(--fs-16); 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: var(--sp-8) 0 var(--sp-4); }
/* 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: var(--sp-8); margin-top: var(--sp-16); }
.spal-k + .spal-chips { margin-top: 0; }
.spal-none { font-size: var(--fs-16); color: var(--ink50); margin: var(--sp-16) 0 var(--sp-8); }
.spal-foot { display: flex; gap: var(--sp-16); margin-top: var(--sp-16); padding-top: var(--sp-8); border-top: 1px solid color-mix(in srgb, var(--ink) 15%, transparent); }
.spal-foot span { font-family: var(--font-mono); font-size: var(--fs-11); 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,
  .search-open .kcap,
  .ing-amt, .ing-head, .ing-extra { transition: none; }
  .search-open:hover .kcap { transform: none; }
}

/* ── v2, the swaddle structure: every page on base.njk sets bodyClass v2 ─────
   A port of design/prototypes/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. 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: var(--fs-16); 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); padding-inline: var(--gutter); 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: var(--fs-12); 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; }
/* The search palette's section headings are .label too, and the unified label
   role above sets margin:0 at (0,2,0) — which beat .spal-k's own (0,1,0) rule
   50 lines earlier and left "Recipes" and "Ingredients & tags" sitting flush
   against the rows above and below them (Rex, 2026-09-10). This restores the
   palette's spacing only; size and colour stay the system's, because the point
   of one label role is that a component does not get its own type. */
.v2 .spal-k { margin: var(--sp-16) 0 var(--sp-8); }
/* 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: var(--fs-12); 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). */
.v2 .spal, .v2 .spal-row, .v2 .kcap, .v2 .skip { border-radius: 0; }
/* One exception to the squared keys: the ⌘K cap on the Search button takes a
   4px radius so it reads as a button (Rex, 2026-09-21). The palette's own keys
   stay square. */
.v2 .search-open .kcap { border-radius: 4px; }
/* 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; color: inherit; } /* an <hr> is gray by browser default; a boundary line is the current ink */
/* 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; }

/* The rule marks the boundary BETWEEN bands, so the air belongs below it, not
   above. With the 6rem sitting in .vsec's top padding, the gap from a card's
   bottom edge down to the next line read 6rem larger than the band's own bottom
   padding — the mismatch Rex measured 2026-08-30. Moving the padding under the
   rule leaves every header exactly where it was and makes that gap the band
   padding on every section. A band that opens a page keeps the air above,
   having no band before it to supply any. */
/* Spacing first, rule second — deliberately in that order. Every band reserves
   its own 6rem of top air as padding, so the air never depends on an element
   that might not draw. That dependency is exactly what broke on 2026-08-30:
   the 6rem lived on `.rule`'s margin, inverted bands hide their rule, and a
   display:none element has no margin, so every colored band lost its spacing.

   The rule is then purely decorative on top of that. It marks the boundary
   BETWEEN TWO ORDINARY BANDS and nothing else — after the hero, after the
   full-bleed feature, after the gnocchi spread or after a colored band, the
   block itself is the boundary and a line is redundant (Rex, 2026-08-30:
   "since it comes after a big block it doesnt need it"). Where it does draw,
   the band drops its padding and the rule's own margin supplies the identical
   6rem, so the header lands in the same place either way. */
.vsec { padding: var(--sp-64) 0 0; }
.vsec > .rule { display: none; }

.band:not(.invert):not(.invert-red) + section.band:not(.invert):not(.invert-red) > .vsec { padding-top: 0; }
.band:not(.invert):not(.invert-red) + section.band:not(.invert):not(.invert-red) > .vsec > .rule { display: block; margin-bottom: var(--sp-64); }

.vsechead { display: flex; justify-content: space-between; align-items: flex-end; gap: var(--sp-24); padding: var(--sp-16) 0 var(--sp-32); }
/* 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. */
/* Type roles, stated once (2026-09-10 audit). A block's dek is the body face
   at the 24 stop; a card's dek is the same face at 16. Blocks and cards only
   add margin and measure. */
.v2 .dek, .vart-dek { font-size: var(--fs-24); line-height: 1.25; }
.raildek-row { display: flex; justify-content: space-between; align-items: flex-end; gap: var(--sp-24); margin: 0 0 var(--sp-32); } /* the collection rail's dek, with the count on its last line */
.raildek { margin: 0; max-width: 46ch; }
.vcard .dek { font-size: var(--fs-16); line-height: 1.35; }
.vsechead h2 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-64); line-height: 1; letter-spacing: -.02em; margin: var(--sp-8) 0 0; }
.vsechead h2 a, .vcoll-title 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, .vcoll-title 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: var(--gutter); 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, color-mix(in srgb, var(--ink) 82%, transparent) 0%, color-mix(in srgb, var(--ink) 35%, transparent) 45%, color-mix(in srgb, var(--ink) 50%, transparent) 100%); }
.vhero .inner { position: relative; z-index: 2; max-width: 80%; }
.vhero h1 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-96); line-height: 1; letter-spacing: -.02em; margin: var(--sp-8) 0 0; text-decoration: underline 6px transparent; text-underline-offset: .1em; transition: text-decoration-color .3s var(--vease); }
.vhero .dek { max-width: 46ch; margin: var(--sp-24) 0 0; }
.vhero .meta { position: relative; z-index: 2; display: flex; gap: var(--sp-32); margin-top: var(--sp-24); flex-wrap: wrap; }
@media (hover: hover) and (pointer: fine) { .vhero:hover h1 { text-decoration-color: currentColor; } }
/* The journal's cover (journal/index.njk) carries a post headline, which runs
   longer than a recipe title, so it takes the section-head size. */
.vhero.jcover h1 { font-size: var(--fs-64); }
/* 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: var(--sp-24); bottom: var(--sp-24); 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: var(--sp-24); scroll-padding-left: var(--gutter); margin: -4px calc(-1 * var(--gutter)) 0 calc(-4px - var(--gutter)); padding: 4px 0 var(--sp-80) var(--gutter); scrollbar-width: none; }
.scroller.vrail::-webkit-scrollbar { height: 0; display: none; }
/* The collection banner on a recipe page (D-174): a sibling's art beside a
   tinted panel with the set's name, her one line and the count. Only the title
   links, with the section head's underline (grouped with .vsechead h2 a). The
   block owns its top and bottom, the 64 over 80 the rails and trio carry. */
.vcoll-block { padding: var(--sp-64) 0 var(--sp-80); }
.vcoll { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); align-items: stretch; }
/* The question panel splits evenly, so the picture carries as much weight as
   the ask (Rex, 2026-09-21). The recipe collection panel keeps its 1:2. */
.vcoll.ask { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.vcoll.ask .dek { font-size: var(--fs-16); line-height: 1.45; }
/* The copy sets the row height. The art is taken out of flow so it adds no
   height of its own and crops to whatever the panel needs; it takes a 3:2
   shape only when stacked. */
.vcoll-art { position: relative; overflow: hidden; background: var(--ink30); }
.vcoll-art img, .vcoll-art .pat { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; display: block; border-radius: 0; }
.vcoll-copy { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: var(--sp-16); background: var(--primary-100); padding: var(--sp-40); }
.vcoll-title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-44); line-height: 1; letter-spacing: -.02em; margin: 0; }
.vcoll .dek { max-width: 40ch; margin: 0; }
/* The other sets are a separate group from this set's name and count, so
   they sit further from it than the panel's own gap. Space, never a rule. Links
   in the body face, a thin underline and the house colour flip on hover. */
.vcoll-also { margin: var(--sp-16) 0 0; }
.vcoll-also a { color: inherit; text-decoration: underline 1px; text-underline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { .vcoll-also a:hover { color: var(--red); } }
/* No rule between the banner and More to cook: the tinted panel already sets it apart. */
.band.vcoll-band:not(.invert):not(.invert-red) + section.band:not(.invert):not(.invert-red) > .vsec > .rule { display: none; }
@media (max-width: 700px) { .vcoll { grid-template-columns: 1fr; } .vcoll-art { aspect-ratio: 3/2; } .vcoll-copy { padding: var(--sp-24); } }
.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: 600; font-size: var(--fs-24); line-height: 1.2; margin: var(--sp-16) 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; margin: .5rem 0 0; opacity: .72; }
.vtrio .vcard .dek, .vcard.wide .dek { display: block; }
.vcard .cfoot { display: flex; gap: var(--sp-24); margin-top: auto; padding-top: var(--sp-16); }
.vcard.drink h3 { font-size: var(--fs-20); }
@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: var(--fs-12); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; display: inline-flex; align-items: center; gap: var(--sp-8); 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: var(--gutter); display: flex; align-items: center; justify-content: center; gap: 1rem; background: var(--red); color: var(--bg); font-family: var(--font-mono); font-size: var(--fs-12); 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: var(--sp-24); padding: 0 0 var(--sp-80); }

/* 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: var(--gutter); 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, color-mix(in srgb, var(--red) 80%, transparent), color-mix(in srgb, var(--red) 15%, transparent)); }
/* 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: 600; font-size: var(--fs-64); line-height: 1; letter-spacing: -.02em; margin: var(--sp-8) 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 { margin: var(--sp-24) 0 0; }
.vfeature .meta { display: flex; gap: var(--sp-32); flex-wrap: wrap; margin-top: var(--sp-48); }

/* 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: var(--sp-24); padding: var(--sp-64) 0 var(--sp-80); align-items: stretch; }
.vabout .col .vsec { padding-top: 0; }
/* the block owns the top space; the portrait's top is the section rule, the same top edge every section has */
.vabout .col { display: flex; flex-direction: column; justify-content: space-between; }
/* max-width is load-bearing, not belt-and-braces. The grid stretches this
   item's HEIGHT to the tall text column, and `aspect-ratio` then derives the
   WIDTH from that height — 562px inside a 510px track at 1200px wide, which
   pushed the portrait past the band's own padding and gave the whole page a
   horizontal scrollbar. Clamping the width lets the ratio yield to the track;
   the img is object-fit:cover inside overflow:hidden, so it simply crops and
   still fills the column's full height. (Measured and fixed 2026-08-30.) */
.vabout .portrait { position: relative; overflow: hidden; max-width: 100%; align-self: stretch; }
.vabout .portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 0; display: block; border-radius: 0; transition: transform 1.2s var(--vease); }
.vabout .vbio p:not(.dek) { font-size: var(--fs-16); line-height: 1.45; margin: 0 0 var(--sp-24); max-width: 56ch; color: inherit; }
.vabout .vbio .dek { margin: 0 0 var(--sp-24); max-width: 34ch; color: inherit; } /* size and leading from the shared dek role */
.vabout .slop { border-top: 1px solid currentColor; padding-top: var(--sp-16); margin-top: var(--sp-40); }
.vabout .slop h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-24); margin: var(--sp-8) 0 var(--sp-8); color: inherit; }
.vabout .slop p { font-size: var(--fs-16); 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 0 var(--sp-64); }
.vindex a { display: grid; grid-template-columns: 1fr auto auto; gap: var(--sp-24); 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: 600; font-size: var(--fs-32); line-height: 1.1; }
.vindex .m { font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .04em; text-transform: uppercase; }
.vidx-img { display: none; }
@media (hover: hover) and (pointer: fine) {
  .vindex a:hover { padding-left: var(--sp-16); }
  .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-block: var(--sp-80); 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: var(--sp-32); transition: color .4s var(--vease); }
.vcta h2 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-96); line-height: .95; letter-spacing: -.02em; margin: var(--sp-8) 0 0; }
.vcta .bigarrow { font-family: var(--font-mono); font-size: var(--fs-120); 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: minmax(0, 22rem) minmax(0, 22rem) minmax(0, 1fr); gap: var(--sp-24); padding: 0 0 var(--sp-80); 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-made     { grid-column: 3; grid-row: 1 / span 2; }
.vdrawer .bg-k { font-family: var(--font-mono); font-size: var(--fs-12); letter-spacing: .04em; text-transform: uppercase; font-weight: 500; color: inherit; opacity: .6; width: auto; margin: 0 0 var(--sp-16); }
.vdrawer .bchips { display: block; }
.vdrawer .bg-all { grid-column: 1 / -1; margin-top: var(--sp-32); }
.v2 .vdrawer .chip { display: block; background: none; border-radius: 0; padding: 0; color: inherit; font-family: var(--font-display); font-weight: 500; font-size: var(--fs-24); 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: var(--sp-24); }
.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; }
}

/* 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); } }

/* 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: var(--sp-24); 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: var(--sp-128) var(--gutter) var(--sp-64); min-height: 100vh; }
.vart-title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-64); line-height: 1; letter-spacing: -.02em; margin: var(--sp-16) 0 0; }
.vart-dek { margin: var(--sp-24) 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 { position: relative; grid-column: 2; grid-row: 1; margin: 0; display: flex; flex-direction: column; gap: var(--sp-8); }
.vart-fig img { width: 100%; height: 100vh; object-fit: cover; display: block; border-radius: 0; }
.vart-fig figcaption { padding-right: var(--gutter); }
/* On a recipe page the scrim belongs to the painting, not the bar: at every
   width it darkens only the painting's top edge, so the white half of the
   desktop split stays white and the nav over the art still reads. */
.vart-fig::after { content: ""; position: absolute; inset: 0 0 auto 0; height: 14rem; background: linear-gradient(to bottom, color-mix(in srgb, var(--ink) 55%, transparent), transparent); pointer-events: none; }

/* 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. */
.vbrowsehead { padding: var(--sp-40) 0 var(--sp-24); }
.vgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-40) var(--sp-24); margin-top: var(--sp-32); }
/* In a grid the card is a grid item, so its rail flex-basis no longer applies. */
.vgrid .vcard { flex: 1 1 auto; }

/* The all-recipes filter (D-119/D-120), ported from mock D. The Filter pill
   sits right of the head; the panel pushes the page aside on desktop (no
   scrim — the grid stays live while narrowing) and becomes a full takeover
   under 700px, where the Show-N button is the exit. One count line, under
   the title like every browse page. */
.vbrowsehead.filterable { display: flex; justify-content: space-between; align-items: flex-end; gap: var(--sp-24); }
.fbtn { display: inline-flex; align-items: center; gap: .6rem; font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; padding: var(--sp-8) var(--sp-24); margin-bottom: .6rem; }
.fbtn .dot { width: .7em; height: .7em; border-radius: 50%; background: var(--red); display: none; }
.fbtn.live .dot { display: inline-block; }
.fpanel { position: fixed; top: 0; right: 0; bottom: 0; width: min(34rem, 88vw); background: var(--bg); border-left: 1px solid var(--ink30); transform: translateX(102%); transition: transform .3s var(--vease); z-index: 9; display: flex; flex-direction: column; }
body.fopen .fpanel { transform: none; }
/* The push moves main AND the footer, by exactly the panel's width, so the
   footer stays under the page it belongs to (the width rule dates from the
   fixed reveal, where a wider push left a slit; Rex, 2026-08-30). */
body.v2 main, body.v2 .foot { transition: margin-right .3s var(--vease); }
body.v2.fopen main, body.v2.fopen .foot { margin-right: min(34rem, 88vw); }
/* While pushed, the grid sheds columns the viewport rules don't know about —
   they measure the window, not the squeezed content. */
@media (min-width: 901px) and (max-width: 1450px) { body.fopen .vgrid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 901px) and (max-width: 1100px) { body.fopen .vgrid { grid-template-columns: repeat(2, 1fr); } }
.fpanel-head { display: flex; justify-content: space-between; align-items: center; padding: var(--sp-24) var(--sp-24) var(--sp-16); border-bottom: 1px solid var(--ink30); }
.fpanel-x { background: none; border: 0; font-size: var(--fs-20); line-height: 1; color: var(--ink70); cursor: pointer; padding: var(--sp-4); }
.fpanel-scroll { flex: 1; overflow-y: auto; padding: var(--sp-16) var(--sp-24) var(--sp-24); }
.fsec { margin: 0 0 var(--sp-24); }
.fsec .key { display: block; margin: 0 0 var(--sp-8); }
.frow { display: flex; align-items: baseline; gap: var(--sp-8); width: 100%; background: none; border: 0; padding: .35rem 0; font-family: var(--font-mono); font-size: var(--fs-13); font-weight: 500; letter-spacing: .02em; color: var(--ink70); cursor: pointer; text-align: left; transition: color var(--dur-micro) ease; }
.frow .sq { flex: 0 0 auto; width: .9em; height: .9em; border: 1px solid var(--ink30); transform: translateY(.1em); transition: background var(--dur-micro) ease, border-color var(--dur-micro) ease; }
.frow .n { margin-left: auto; color: var(--ink50); letter-spacing: 0; }
.frow.on { color: var(--ink); }
.frow.on .sq { background: var(--red); border-color: var(--red); }
.frow.dead { opacity: .3; cursor: default; }
.fpanel-foot { display: flex; gap: 1rem; align-items: center; justify-content: space-between; padding: var(--sp-16) var(--sp-24); border-top: 1px solid var(--ink30); }
.fclear { background: none; border: 0; padding: 0; font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .06em; text-transform: uppercase; color: var(--red); cursor: pointer; }
.fapply { display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 0; background: var(--red); color: var(--bg); font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; padding: 1.4rem var(--sp-24); cursor: pointer; transition: background-color .25s var(--vease), color .25s var(--vease); }
.fempty { display: none; padding: var(--sp-64) 0; font-family: var(--font-display); font-weight: 500; font-size: var(--fs-24); }
.fempty.show { display: block; }
@media (hover: hover) and (pointer: fine) {
  .fpanel-x:hover { color: var(--red); }
  .frow:not(.dead):hover { color: var(--red); }
  .fapply:hover { background: var(--lime); color: var(--ink); }
}
@media (max-width: 700px) {
  .fpanel { width: 100vw; border-left: 0; }
  body.v2.fopen main, body.v2.fopen .foot { margin-right: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fpanel { transition: none; }
  body.v2 main, body.v2 .foot { transition: none; }
}

/* The drawer collapses on its own width, not the page's (Rex, 2026-09-21: it
   was not responsive around 960). Three columns of names, the last of them
   itself three columns wide, overflowed the page by 52px at 960 while the 900
   breakpoint below was still waiting its turn. */
@media (max-width: 1100px) {
  .vdrawer { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3rem; }
  .vdrawer .bg-course, .vdrawer .bg-occasion { grid-column: auto; grid-row: auto; }
  .vdrawer .bg-made { grid-column: 1 / -1; grid-row: auto; }
  .vdrawer .bg-made .bchips { columns: 2; }
}

@media (max-width: 620px) {
  .vdrawer { grid-template-columns: minmax(0, 1fr); }
  .vdrawer .bg-made .bchips { columns: 1; }
}

@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: var(--sp-32) var(--gutter) 0; order: 2; }
  .vart-title { font-size: var(--fs-36); }
  /* 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: var(--sp-32); }
  .vart-fig { order: 1; }
  /* Stacked, the art runs to the top of the page and the floating masthead sits
     on it, not on the paper column the desktop rule was written for. Bring the
     home cover's pair over: reverse the wordmark and feather a scrim under it.
     Desktop keeps the ink mark, which is correct over the paper column. */
  body.v2:has(.vart) .wm { color: var(--bg); }
  .vart-fig { position: relative; }
  .vart-fig::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 200px; z-index: 1; pointer-events: none; background: linear-gradient(to bottom, color-mix(in srgb, var(--ink) 42%, transparent), color-mix(in srgb, var(--ink) 0%, transparent)); }
  .vart-fig img { height: auto; aspect-ratio: 4/5; }
  .vart-fig figcaption { padding:  0 var(--gutter); }
  .vgrid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-64) var(--sp-24); }
  .vfeature .inner { max-width: 100%; }
  .vfeature h3 { font-size: var(--fs-36); }
  .vhero h1 { font-size: var(--fs-44); }
  .vhero .inner { max-width: 100%; }
  .vsechead h2 { font-size: var(--fs-36); }
  .vtrio { grid-template-columns: 1fr; gap: var(--sp-80); }
  /* The overlay trio holds its columns two-up here rather than stacking: four
     2:3 portraits single-file is an enormous scroll, and 2x2 is what Rex asked
     for at this width (2026-08-30). Three cards read 2 + 1. */
  .vabout { grid-template-columns: 1fr; }
  .vabout .portrait { aspect-ratio: 4/5; }
  .vindex .t { font-size: var(--fs-24); }
  .vindex a { grid-template-columns: 1fr auto; align-items: center; gap: var(--sp-8) var(--sp-24); }
  .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: var(--fs-44); }
  .vcta .bigarrow { font-size: 5.6rem; }
  .vcard { flex-basis: 26rem; }
  .vcard.wide { flex-basis: 30rem; }
  #vpeek { display: none; }
}

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

@media (prefers-reduced-motion: reduce) {
  .vline > span, .vfade, .vcue { animation: none; }
  .vthumb img, .vthumb .pat, .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; }
  /* The tinted --bg prints as a colored sheet; paper is white. */
  html, body { background: var(--paper) !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],
  .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, .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. */
  .method-body ol { list-style: decimal !important; padding-left: 24px !important; gap: 16px !important; }
  .method-body ol li { padding-left: 6px !important; }
  .method-body ol li::marker { font-weight: 600; }
  ul.ings 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; }
}

/* ── Post ── the site's long-form template (D-130), on the v2 canvas. A reading
   column with modest heads; the rails, bands, labels and cards above are used
   as they are, and what follows are the components a long read adds to the
   system, not one page's styles: the reading column (.pcol), the installment
   head (.inst), the exhibit table (.ex), the record band (.rec), the notebook
   recipe (.trecipe), the phone and the deck, the mark block, the
   highlighter (<mark>), the closing letter and the lightbox (.lb). Every colour is a token; the only raw values
   left are the April card's, which is a period artifact and carries its own.
   Ported from the approved mock, design/prototypes/how-she-was-built/b-memoir.html. */
.post { padding: var(--sp-40) 0 0; }

/* The highlighter. A reader marking up someone else's account of them, which
   is what the memoir's corrections already do in prose. The element is
   <mark>, not a class: it is what HTML has for this, it carries the meaning
   to a screen reader, and it leaves .mark to the brand-mark block below,
   where that name belongs. The stroke stops short of the cap height so a
   sliver of paper shows above it and it reads as a pass of a marker rather
   than a filled label. box-decoration-break keeps the stroke whole across a
   line break. --lime under --ink measures 13.25:1. */
mark {
  color: inherit;
  background: linear-gradient(var(--lime), var(--lime)) no-repeat;
  background-size: 100% 86%;
  background-position: 0 100%;
  padding: 0 .16em;
  margin: 0 -.08em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.pcol { max-width: 64rem; margin: 0 auto; padding: 0 var(--gutter); }
.pcol.wide { max-width: 88rem; }
.pcol p { font-size: var(--fs-16); line-height: 1.55; margin: 0 0 var(--sp-24); }
.pcol .lede { font-size: var(--fs-20); line-height: 1.42; color: var(--ink85); }
.pcol .pull mark { background: none; padding: 0; margin: 0; } /* a pull quote is never highlighted (Rex, 2026-09-21) */
.pcol .pull { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-24); line-height: 1.22; letter-spacing: -.01em; background: var(--primary-100); padding: var(--sp-24); margin: var(--sp-32) 0; } /* a card on the light oxblood tint, no rule */
/* The opening: title block left, the full portrait right on a hairline. */
/* The memoir's article head is the recipe page's .vart. Only two things differ:
   the kicker and the meta row, which a recipe does not carry. The post's own
   top padding would push the art below the fold, so it is dropped on this page. */
.vart-copy .label { display: inline-flex; align-self: flex-start; }
.vart-meta { display: flex; gap: var(--sp-24); flex-wrap: wrap; margin-top: var(--sp-24); }
.post:has(.vart) { padding-top: 0; }
/* Installment heads. */
.inst { margin: var(--sp-80) 0 var(--sp-24); padding-top: var(--sp-24); border-top: 1px solid currentColor; } /* a boundary line, like every section rule */
.inst h2 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-28); line-height: 1.1; letter-spacing: -.015em; margin: 1rem 0 .6rem; text-wrap: balance; }
.inst .label.plain { display: inline-flex; }
.pcol > .label { display: inline-flex; margin: var(--sp-16) 0 0; }
/* Rails inside the story: the head stays in the column; only the scroller
   leaves it, with its first card on the column's left edge. Named srail, not
   story, because the recipe page owns .story at 640px. */
.inst.rail { border-top: 0; padding-top: 0; margin: var(--sp-48) 0 var(--sp-24); }
.band.rails { width: 100%; }
.scroller.vrail.srail { margin-top: 0; padding-top: 0; padding-bottom: var(--sp-40); margin-bottom: var(--sp-8); }
.vcard.pic { flex-basis: 28rem; }
/* A rail with no cap card still ends with the site's 2rem gutter. */
.scroller.vrail.srail > :last-child { margin-right: var(--sp-24); }
/* A rail of three cards or fewer (the rail macro marks it .few and sets --n)
   is a grid at the hero's width, so a short set sits in the same frame as the
   post's opening picture instead of stopping short of the right edge
   (Rex, 2026-09-21). A phone keeps the swipe. */
@media (min-width: 621px) {
  .band.rails.few { width: min(88rem, calc(100vw - 2 * var(--gutter))); margin-left: 50%; transform: translateX(-50%); }
  .rails.few .scroller.vrail.srail { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); overflow: visible; margin: 0; padding: 0 0 var(--sp-40); }
  .rails.few .scroller.vrail.srail > :last-child { margin-right: 0; }
}
figure.vcard { margin: 0; }
/* Notebook recipes: the exhibit frame, two columns inside. */
.trecipe { border: 1px solid var(--line); background: var(--paper); margin: 3.6rem 0; }
.trecipe .head { padding: var(--sp-16) var(--sp-16) var(--sp-16); background: var(--primary-100); border-bottom: 1px solid var(--ink30); }
.trecipe h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-20); line-height: 1.15; letter-spacing: -.01em; margin: var(--sp-8) 0 0; }
.trecipe .cols { display: grid; grid-template-columns: 1fr 1.6fr; gap: 2.8rem; padding: 1.8rem 1.8rem 2.2rem; }
.trecipe .key { display: block; margin-bottom: 1rem; }
.trecipe ul { margin: 0; padding: 0; list-style: none; font-size: var(--fs-14); line-height: 1.4; }
.trecipe ul li { padding: var(--sp-8) 0; border-top: 1px solid var(--ink30); }
.trecipe ul li:last-child { border-bottom: 1px solid var(--ink30); }
.trecipe ol { margin: 0; padding: 0; list-style: none; counter-reset: step; font-size: var(--fs-14); line-height: 1.5; }
.trecipe ol li { position: relative; padding: 0 0 var(--sp-16) var(--sp-32); counter-increment: step; }
.trecipe ol li::before { content: counter(step, decimal-leading-zero); position: absolute; left: 0; top: .2rem; font-family: var(--font-mono); font-size: var(--fs-11); font-weight: 500; color: var(--red); }
/* Rails carrying things that are not 2:3 recipe art. */
.vcard.free .vthumb { aspect-ratio: auto; background: none; }
.vcard.free .vthumb img { height: auto; }
.vcard.free figcaption { display: flex; flex-wrap: wrap; gap: var(--sp-8) var(--sp-16); margin-top: var(--sp-8); }
.vcard.sq .vthumb { aspect-ratio: 1/1; }
/* A landscape picture that should stand as tall as the portraits beside it. */
.vcard.wideimg { flex-basis: auto; }
.vcard.wideimg .vthumb { height: 48rem; }
.vcard.wideimg .vthumb img { height: 100%; width: auto; }
/* A phone around a planner screen: ink bezel, rounded corners. */
.phone { background: var(--ink); border-radius: 3.4rem; padding: var(--sp-8); }
.phone .vthumb { aspect-ratio: 390/844; border-radius: 2.8rem; background: var(--paper); }
.phone .vthumb img { object-position: top; }
/* The deck: four planner screens dropped like cards, the add-a-dish sheet on
   top. A click brings a card forward; a click on the front card opens the
   lightbox. */
.deck { position: relative; height: 47rem; margin: var(--sp-24) 0 var(--sp-80); }
.deck .phone { position: absolute; bottom: 0; width: 21rem; transform-origin: 50% 100%; cursor: pointer; transition: transform .35s var(--vease); }
.deck .phone:nth-child(1) { left: 0; transform: rotate(-9deg); z-index: 1; }
.deck .phone:nth-child(2) { left: 11.5rem; transform: rotate(-3deg); z-index: 2; }
.deck .phone:nth-child(3) { left: 23rem; transform: rotate(3deg); z-index: 3; }
.deck .phone:nth-child(4) { left: 34.5rem; transform: rotate(9deg); z-index: 4; }
.deck .phone.front { z-index: 10; transform: rotate(0) translateY(-1.6rem); }
@media (hover: hover) and (pointer: fine) { .deck .phone:not(.front):hover { transform: rotate(0) translateY(-.8rem); } }
@media (prefers-reduced-motion: reduce) { .deck .phone { transition: none; } }
/* The mascot, inlined so she takes the page's ink (D-086 sibling). */
.mark { display: grid; grid-template-columns: 26rem 1fr; gap: var(--sp-40); align-items: center; background: var(--primary-100); padding: var(--sp-40); margin: 0 0 var(--sp-32); }
.mark svg { width: 19rem; height: auto; margin: 0 auto; display: block; }
.mark h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-24); line-height: 1.1; letter-spacing: -.015em; margin: var(--sp-8) 0; }
.mark p { margin: 0 0 var(--sp-8); }
.mark .meta { display: flex; gap: var(--sp-24); flex-wrap: wrap; margin-top: var(--sp-16); }
/* The closing letter. */
.close { margin: var(--sp-64) 0 var(--sp-80); } /* the last thing before the drawer band carries the band spacing */
.close .letter { font-size: var(--fs-20); line-height: 1.42; margin: var(--sp-8) 0 var(--sp-24); } /* the lede role, in the body face */
.close .sig { margin: var(--sp-24) 0 0; }
/* The shoot rail: the site's own rail component with the cards taken up to
   near-column width, because these are pictures to look at rather than cards
   to scan. Rex, 2026-09-02: "do the gallery full strip like the recipes but
   keep the images bigger". */
/* Every frame at the same height, whatever its shape, so a landscape picture
   is seen whole rather than cropped to a portrait slot — the behaviour
   .vcard.wideimg already gives the vanity cutaway. Height is fixed and width
   follows the image, so the strip mixes orientations without cropping any of
   them. Rex, 2026-09-02: "if they are landscape i want to see it all". */
.vcard.shot { flex-basis: auto; }
.vcard.shot .vthumb { aspect-ratio: auto; background: none; height: 56rem; }
.vcard.shot .vthumb img { height: 100%; width: auto; object-fit: contain; }
@media (max-width: 760px) { .vcard.shot .vthumb { height: 62vh; } }
/* ---- The journal (approved mocks: design/prototypes/journal/, 2026-09-18). ---- */
/* The post head: label, headline, dek, byline and date, then the artwork
   whole, all on the reading column's one measure. Three groups, one gap each:
   inside the head 16 then 24; head to art 48; art to the first line 48. */
.phero { padding-top: var(--sp-64); padding-bottom: var(--sp-48); }
.phero .label { display: inline-flex; text-decoration: none; }
.phero-title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-44); line-height: 1.04; letter-spacing: -.02em; text-wrap: balance; margin: var(--sp-16) 0 0; }
.pcol .phero-dek { font-size: var(--fs-20); line-height: 1.42; color: var(--ink85); margin: var(--sp-24) 0 0; }
.pmeta { display: flex; flex-wrap: wrap; gap: var(--sp-8) var(--sp-24); margin-top: var(--sp-24); }
.phero-art { margin: var(--sp-48) 0 0; }
/* The hero picture runs wider than the reading column but never edge to edge
   (Rex, 2026-09-20). 88rem is the same measure .pcol.wide uses for the
   artifact and the post lists, so the page has one wide edge, not two. */
.post .phero-art { width: min(88rem, calc(100vw - 2 * var(--gutter))); margin-left: 50%; transform: translateX(-50%); }
.phero-art img { display: block; width: 100%; height: auto; }

.post .pcol.wide { margin-top: var(--sp-64); margin-bottom: var(--sp-64); }
.post .pcol.wide .exhibit { margin: 0; }

/* An artifact needs nothing new: .pcol.wide holds it, as the memoir's first
   card always was, and the artifact's own label bar is its head. */

/* The post list: rows at the post body's width (.pcol). Art left, the date,
   title, dek and an arrow right, a section line between rows, no ground. The
   title is the site's card title and underlines when any part of the row is
   hovered, as every card on the site does. */
.prows-label { display: inline-flex; margin: 0; }
.prows { list-style: none; margin: 0; padding: 0; }
.prows li:first-child .prow { border-top: 0; padding-top: var(--sp-24); }
/* Three groups down the front's foot: the rows, the button that leaves them,
   the mail panel. 48 from the last row to the button, 128 from it to the panel. */
.pcol .prows-all { margin: var(--sp-16) 0 var(--sp-128); }   /* the last row's own 32 below it makes the 48 */
/* A browse grid of posts is the last thing before the drawer band, so it
   carries the band spacing the recipe page gets from its More to cook band. */
.jgrid { padding-bottom: var(--sp-80); }
/* The journal's variation on the browse grid (Rex, 2026-09-21): the posts'
   heroes are horizontal, so its cards are too, three across instead of the
   recipe grid's four portrait columns. The recipe browse pages are untouched. */
.jgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-64) var(--sp-32); }
.jgrid .vthumb { aspect-ratio: 3 / 2; }
@media (max-width: 1100px) { .jgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .jgrid { grid-template-columns: minmax(0, 1fr); } }
.prow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-32); align-items: center; padding: var(--sp-32) 0; border-top: 1px solid currentColor; color: inherit; text-decoration: none; }
/* Row art is cropped 4:5 from the upper part of the painting (Rex's pick, 2026-09-18). */
/* Horizontal and larger now that every post's hero is horizontal (Rex,
   2026-09-21): the picture takes half the row at 3:2, where it was a 4:5 crop
   in three eighths of it. */
.prow .vthumb { aspect-ratio: 3 / 2; }
.prow .vthumb img { object-position: 50% 35%; }
.prow-copy { display: flex; flex-direction: column; align-items: flex-start; }
.prow h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-24); line-height: 1.2; margin: var(--sp-8) 0 0; text-decoration: underline 3px transparent; text-underline-offset: .16em; transition: text-decoration-color .25s var(--vease); }
.prow .prow-dek { font-size: var(--fs-16); line-height: 1.35; margin: var(--sp-8) 0 0; opacity: .72; }
/* The row's link is the site's own text control, .viewall: words and the mono arrow. */
.prow .viewall { margin-top: var(--sp-16); }
@media (hover: hover) and (pointer: fine) {
  .prow:hover h3 { text-decoration-color: currentColor; }
  .prow:hover .vthumb img { transform: scale(1.07); }
  .prow:hover .va-arrow { transform: translateX(4px); }
}
@media (max-width: 700px) { .prow { grid-template-columns: 1fr; gap: var(--sp-16); } }

/* The ask: the site's collection panel (.vcoll), holding a line and one
   button that opens an email. Contained in the wide column, never a band. */
.ask { margin: 0 0 var(--sp-96); min-height: 36rem; }
@media (max-width: 900px) { .phero-title { font-size: var(--fs-32); } }

/* The lightbox: one native dialog for every picture on the page. */
.post .vthumb img { cursor: zoom-in; }
.lb { border: 0; padding: 0; background: transparent; max-width: none; max-height: none; width: 100vw; height: 100dvh; }
.lb::backdrop { background: color-mix(in srgb, var(--ink) 92%, transparent); }
.lb-in { position: fixed; inset: 0; display: grid; grid-template-rows: 1fr auto; align-items: center; justify-items: center; padding: var(--sp-32) var(--sp-24) var(--sp-24); gap: var(--sp-8); }
.lb img { max-width: min(1400px, 92vw); max-height: calc(100dvh - 9.6rem); width: auto; height: auto; object-fit: contain; box-shadow: 0 20px 60px color-mix(in srgb, var(--ink) 50%, transparent); }
.lb-cap { font-size: var(--fs-13); line-height: 1.4; color: color-mix(in srgb, var(--bg) 80%, transparent); text-align: center; max-width: 60ch; }
.lb-x, .lb-p, .lb-n { position: fixed; top: var(--sp-16); right: var(--sp-24); background: none; border: 0; color: color-mix(in srgb, var(--bg) 85%, transparent); font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; padding: var(--sp-8) var(--sp-16); cursor: pointer; }
.lb-p, .lb-n { top: 50%; transform: translateY(-50%); font-size: var(--fs-28); padding: var(--sp-24) var(--sp-16); }
.lb-p { left: var(--sp-8); right: auto; } .lb-n { right: var(--sp-8); }
@media (hover: hover) and (pointer: fine) { .lb-x:hover, .lb-p:hover, .lb-n:hover { color: var(--lime); } }
/* The CTA button: the rail cap's block, freed from the rail. Red on paper,
   paper on a colour band, lime on hover everywhere, the site's arrow inside. */
.vbtn { display: inline-flex; align-items: center; gap: 1rem; padding: var(--sp-16) var(--sp-24); background: var(--red); color: var(--bg); font-family: var(--font-mono); font-size: var(--fs-12); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; text-decoration: none; transition: background-color .25s var(--vease), color .25s var(--vease); }
.vbtn.big { padding: var(--sp-24) var(--sp-64); margin: var(--sp-64) 0 0; }
.invert .vbtn, .invert-red .vbtn { background: var(--bg); color: var(--ink); }
@media (hover: hover) and (pointer: fine) {
  .vbtn:hover, .invert .vbtn:hover, .invert-red .vbtn:hover { background: var(--lime); color: var(--ink); }
  .vbtn:hover .va-arrow { transform: translateX(6px); }
}
/* The door on the home page: the button under her two lines, nothing else. */
.vabout .vbio .vbtn { margin-top: var(--sp-4); }
@media (max-width: 760px) {
  .trecipe .cols, .mark { grid-template-columns: 1fr; }
  /* The fan in percentages of the column, with a shallower tilt: at 375 the
     rem offsets ran the last phone 82px past the viewport, the one overflow
     the 2026-09-02 probe found. Measured fitting at 360 and 375. */
  .deck { height: 40rem; }
  .deck .phone { width: 38%; }
  .deck .phone:nth-child(1) { transform: rotate(-6deg); }
  .deck .phone:nth-child(2) { left: 18%; transform: rotate(-2deg); }
  .deck .phone:nth-child(3) { left: 36%; transform: rotate(2deg); }
  .deck .phone:nth-child(4) { left: 54%; transform: rotate(6deg); }
  .deck .phone.front { transform: rotate(0) translateY(-1.6rem); }
  .lb-p, .lb-n { display: none; }
}
