
:root{
  /* ROUND 11. `--page` IS BACK TO #fff, WHICH IS WHAT IT HELD.
     Confirmed from git: every commit before round 10 carries `--page:#fff`.
     Round 10 set it to the #F8F8F8 measured in the mockups and that was an
     over-reach -- it turned the whole page grey, and because the content
     boxes are #f7f7f7, one step away, the step boxes, the reviews card, the
     gallery and the embed blocks lost their edges. The thing actually asked
     for was grey behind ONE caption strip, and that is where it now is. */
  --maroon:#51010e; --page:#fff; --card:#f7f7f7; --cream:#fffeec;
  --red:#c2554c; --dark:#1d2327; --body:#4e5155; --orange:#f18032; --green:#49b730;
  --r-lg:40px; --r-md:30px;

  /* PART 4. THE CONTENT INSET, IN ONE PLACE.
     Every full-width block inside the recipe card starts and ends on the
     same two lines: the ingredient box, the tool box, the nutrition panel,
     a direction card and the reviews block. It was five different insets
     before -- the ingredients at 56/64, nutrition at 56/78, the steps and
     the reviews at 50/50 -- which is why the reviews box did not line up
     with anything at a wide window. 56 and 64 are the approved file's own
     figures, the ones its meta row and its boxes already used. */
  --pad-l: 56px;  --pad-r: 64px;

  /* ROUND 6, PART 2: how far a step number sits in from the section
     heading. A Note directly under a heading reads the same token, so
     the two cannot drift apart. */
  --step-indent: 40px;

  /* ROUND 8, B2: the number gutter. A section heading's "1." sits in a box
     this wide, so the heading's TEXT -- the "D" of "Dough:" -- begins one
     of these in from the card's padding. That is the page's body text
     column, and a caption strip's text is aligned to it rather than to a
     padding chosen for the strip alone. */
  --num-gutter: 41px;

  /* ROUND 6, 5c: the two row tints, measured off Tom's mockup. The
     first row is the darker one; no row is white. */
  --vars-tint-a: #EFEFEF;
  --vars-tint-b: #F8F8F8;

  /* PART 3: the gallery is one rectangle -- the strip of thumbnails is
     exactly as tall as the photograph beside it, which is what makes it
     a window rather than a list. Live is 520 beside 520; the approved
     file draws the picture at 690.

     QUEUE 75, PHASE 1: 690px was a FIXED HEIGHT, and a fixed height cannot
     stay landscape while the column beside it changes width. Measured on the
     candidate before this change, `.gal-main` was

         1440   740.5 x 690   ratio 1.07     -- near-square
         1200   904   x 690   ratio 1.31
         1024   728   x 690   ratio 1.06     -- near-square again
         900 and below         ratio 1.33    (the stacked block's 4/3)

     so the box was tallest exactly where the column is narrowest. The shape
     is a RATIO now, not a height: one token, the same 3:2 landscape at every
     width, and the thumbnail strip takes its height from the photograph
     instead of from a number -- so the strip is still exactly as tall as the
     picture, which is the rule this token was written to hold. */
  --gal-ratio: 3 / 2;

  /* ==================================================================
     PART 2b. ONE TOKEN PER TEXT ROLE.
     ==================================================================
     Every piece of text on a recipe page belongs to a role, and a role's
     family, size, weight and line height are set HERE and nowhere else.
     No rule below writes a font size, weight or family literally; each
     reads its token. That is the groundwork for the screen Tom wants,
     where he sets a category once and it applies everywhere.

     A role that is deliberately different below 1023px gets a SECOND
     token -- `--step-size-s` -- rather than a hardcoded number buried in
     a media query. The media query sets the token; the rule never
     changes.

     Values here are exactly what the stylesheet already computed, so
     this is a refactor and not a redesign: the render is unchanged.
     ------------------------------------------------------------------ */
  --font: "Montserrat","Segoe UI",system-ui,-apple-system,Arial,sans-serif;

  /* the page's one h1, in the hero */
  --h1-size: 32px;      --h1-weight: 700;  --h1-line: 1.11;
  /* the recipe card's own title */
  --rtitle-size: 34px;  --rtitle-weight: 700; --rtitle-line: 34px;
  /* a direction section heading -- "1. Dough:" */
  --dirhead-size: 26px; --dirhead-weight: 700; --dirhead-line: 31px;
  /* THE DIRECTIONS BODY: every step at every level, and every note */
  --step-size: 17px;    --step-weight: 400; --step-line: 26px;
  --step-color: var(--body);
  /* the ingredient and tool lists */
  --ing-size: 16px;     --ing-weight: 400; --ing-line: 28px;
  --ingsec-size: 17px;
  --footnote-size: 14px; --footnote-line: 22px;
  /* the box headings -- Ingredients:, Tools:, Nutrition */
  --boxhead-size: 26px; --boxhead-weight: 700;
  /* the section labels -- Recipe:, Directions: */
  --label-size: 22px;   --label-weight: 700;
  /* a photograph's caption */
  /* the sub-heading's bold and the footnote's regular came from the
     browser's own defaults for <b> and <p>. A role whose weight is
     nobody's decision cannot be moved from one place, so both are
     written down and read like every other role. */
  --ingsec-weight: 700;
  --footnote-weight: 400;
  --caption-block-line: 24px;
  --caption-size: 15px; --caption-weight: 400; --caption-line: 19px;
  /* the article cards above the recipe */
  /* the small print: nutrition figures, the disclaimer line */
  --small-size: 15.5px; --small-weight: 400; --small-line: 1.65;
  /* the three buttons, each of which the approved file sizes differently */


  /* PART 2b: every remaining text role, one token each.
     Generated from the values the stylesheet already had, so
     this is a refactor and not a redesign. */
  --artbody-line: 1.55;
  --artbody-size: 15.5px;
  --arthead-size: 17px;
  --arthead-weight: 700;
  --author-size: 18px;
  --badge-size: 9px;
  --base-family: "Montserrat","Segoe UI",system-ui,-apple-system,Arial,sans-serif;
  --boxhead-size: 26px;
  --boxhead-weight: 700;
  --bswitem-line: 24px;
  --bswitem-size: 17px;
  --bswnote-line: 26px;
  --bswnote-size: 17px;
  --btnjump-line: 1;
  --btnjump-size: 17px;
  --btnjump-weight: 700;
  --btnprint-size: 16px;
  --btnprint-weight: 700;
  --bwsbody-size: 13.5px;
  --bwshead-size: 22px;
  --bwshead-weight: 700;
  --chev-line: 26px;
  --chev-size: 13px;
  --chevreset-line: 0;
  --chevreset-size: 0;
  --diet-line: 31px;
  --diet-size: 15px;
  --diet-weight: 700;
  --dirhead-line: 31px;
  --dirhead-size: 26px;
  --dirhead-weight: 700;
  --embedhead-size: 24px;
  --embedhead-weight: 700;
  --footnote-line: 22px;
  --footnote-size: 14px;
  --h1-line: 1.11;
  --h1-size: 32px;
  --h1-weight: 700;
  --ing-line: 28px;
  --ing-size: 16px;
  --ing-weight: 400;
  --ingsec-size: 17px;
  --intro-line: 24.5px;
  --intro-size: 15px;
  --kofi-size: 17px;
  --kofi-weight: 700;
  --label-size: 22px;
  --label-weight: 700;
  --mbitem-size: 19px;
  --mbright-size: 19px;
  --mbright-weight: 700;
  --modalbody-line: 1.6;
  --modalbody-size: 15.5px;
  --modalclose-line: 1;
  --modalclose-size: 18px;
  --modalhead-size: 24px;
  --modalhead-weight: 700;
  --nutbasis-size: 15.5px;
  --nutbasis-weight: 700;
  --nutdiet-line: 30px;
  --nutdiet-size: 14px;
  --nutdiet-weight: 700;
  --nuthead-size: 24px;
  --nuthead-weight: 700;
  --nutnote-line: 1.35;
  --nutnote-size: 15.5px;
  --nutnote-weight: 700;
  --pill-line: 33px;
  --pill-size: 12px;
  --pill-weight: 700;
  --pilllink-size: 14px;
  --pilllink-weight: 700;
  --pub-line: 1.25;
  --pub-size: 14px;
  --pub-weight: 700;
  --rating-size: 15px;
  --rating-weight: 700;
  --rcat-size: 15px;
  --rcat-weight: 700;
  --readmore-size: 15px;
  --readmore-weight: 700;
  --rintro-line: 26px;
  --rintro-size: 17px;
  --rmetaline-size: 18px;
  --rpub-size: 14px;
  --rrate-size: 15px;
  --rtitle-line: 1;
  --rtitle-size: 34px;
  --rtitle-weight: 700;
  --sharelabel-size: 16px;
  --slist-line: 26px;
  --slist-size: 17px;
  --small-line: 1.65;
  --small-size: 15.5px;
  --small-weight: 400;
  --sstep-line: 25.5px;
  --sstep-size: 17px;
  --stars-size: 18px;
  --stepnum-weight: 700;
  --timer-line: 1.35;
  --timer-size: 1em;
  --timer-weight: 700;
  --timerx-line: 1;
  --timerx-size: 1.05em;
  --timerx-weight: 700;
  --yield-line: 22px;
  --yield-size: 17px;
}

/* The second value of a role below 1023px is a TOKEN, set once here, not an
   override written into each rule. */
@media (max-width:1023px){
  /* the same roles, one step down at this width: the TOKEN
     changes, never the rule. */
  :root{
    --author-size: 16px;
    --dirhead-size: 22px;
    --intro-line: 25px;
    --intro-size: 15px;
    --mbitem-size: clamp(15px,4.5vw,19px);
    --mbright-size: clamp(15px,3.8vw,19px);
    --pilllink-line: 19px;
    --pilllink-size: 15px;
    --pub-size: 13px;
    --rating-size: 14px;
    --rmetaline-size: 16px;
    --rtitle-size: 26px;
    --sharetoggle-size: 18px;
    --slist-size: 16px;
    --small-size: 15px;
    --sstep-size: 16px;
    --yield-size: 16px;
  }

  :root{
    --step-size: 16px;
    --rtitle-size: 26px;
    --dirhead-size: 22px;
    --small-size: 15px;
  }
}
/* ROUND 37, JOB 37.1. THE RESET IS SCOPED TO THE RECIPE, NOT THE DOCUMENT.
 * This was a bare `*`, which was harmless only because recipe_v3.html was a
 * standalone document with no site around it -- and that is exactly the fault
 * 37.1 found: the switched-on page had no header, no logo, no search bar, no
 * menu, no newsletter box and no footer. Bringing the site's own header and
 * footer into this document means a bare `*` would flatten every margin and
 * padding in them.
 * Scoped here instead. Everything the v3 page draws lives inside .ttc-v2 --
 * measured, the only other children of <body> are five scripts and the
 * JSON-LD -- so the recipe area is unchanged, and the header and footer keep
 * their own spacing. Zero class names clash between the two stylesheets.
 *
 * AND IT IS WRAPPED IN :where(), WHICH IS THE WHOLE POINT. Scoping it as a
 * plain `.ttc-v2 *` raises its specificity from 0,0,0 to 0,1,0, so the reset
 * started BEATING rules that used to beat it: measured, the standalone
 * photograph lost 8px of padding each side and went flush to the viewport
 * edge at 1023px, and the shot-join gate caught it -- 234 seam pixels and 114
 * white ones in a corner. `:where()` contributes nothing to specificity, so
 * this matches the old bare `*` exactly while reaching only the recipe.
 *
 * `body` keeps a reset of its own. The old bare `*` was zeroing the browser's
 * default 8px body margin, and scoping it took that away: measured, .ttc-v2
 * went from 1023 to 1007 at a 1023px viewport on a page with no theme CSS.
 * The theme zeroes body anyway, so this changes nothing where the site is
 * present -- it just stops this stylesheet depending on the theme to be
 * correct. */
:where(body){margin:0}
:where(.ttc-v2,.ttc-v2 *,.ttc-v2 *::before,.ttc-v2 *::after){box-sizing:border-box;margin:0;padding:0}
body{background:var(--page);font-family:var(--base-family);color:var(--dark);-webkit-font-smoothing:antialiased}
.ttc-v2{width:1245px;margin:0 auto;max-width:100%}

/* ---------- HERO ---------- */
.hero{position:relative;z-index:2;height:532px;border-radius:var(--r-lg);overflow:hidden;background:var(--maroon)}
.hero-card{position:relative;z-index:2;width:560px;height:100%;padding:47px 44px 40px 50px;color:#fff;display:flex;flex-direction:column}
/* ROUND 38, JOB 39.24. THE HERO TEXT IS WHITE EXPLICITLY, NOT BY INHERITANCE,
 * AND THIS IS A REGRESSION I INTRODUCED IN ROUND 37.
 *
 * Restoring the site around the page (37.1) put the theme's bundled
 * stylesheet on this document for the first time. It carries
 *     h1 { color: var(--s-ink, #1a2428) }
 * and `.h-title` is an h1. AN INHERITED VALUE LOSES TO ANY MATCHING RULE,
 * whatever its specificity -- so the title stopped being white the moment the
 * header came back, and `.hero-card{color:#fff}` could not save it.
 *
 * MEASURED on Ranch Dip, ON rendering: the title and the intro paragraph both
 * rendered rgb(26,36,40) on the maroon rgb(81,1,14). CONTRAST 1.03:1 against
 * a 4.5:1 floor. Tom read the title as "almost the same colour as the maroon"
 * and the intro as missing entirely -- it was never missing, it was invisible.
 *
 * Every text node in the hero card is stated rather than inherited. */
.ttc-v2 .hero-card,
.ttc-v2 .hero-card h1,
.ttc-v2 .hero-card h2,
.ttc-v2 .hero-card p,
.ttc-v2 .hero-card span,
.ttc-v2 .hero-card a,
.ttc-v2 .hero-card b,
.ttc-v2 .hero-card strong,
.ttc-v2 .hero-card em,
.ttc-v2 .hero-card div{color:#fff}
.hero-photo{position:absolute;left:560px;top:0;right:0;bottom:0;border-radius:0 0 0 40px;overflow:hidden;
  background:linear-gradient(135deg,#cfd9c4,#eae2cf 45%,#d8c9a8)}
.pill{align-self:flex-start;height:35px;line-height:var(--pill-line);padding:0 20px;border:1px solid #fff;border-radius:999px;font-size:var(--pill-size);font-weight:var(--pill-weight);color:#fff}
/* A4: 32px at EVERY width. The two mobile overrides are deleted, not
 * changed -- the h1 never steps down. */
.h-title{font-size:var(--h1-size);font-weight:var(--h1-weight);line-height:var(--h1-line);margin-top:30px}
.h-yield{font-size:var(--yield-size);line-height:var(--yield-line);margin-top:24px}
.h-badges{display:flex;gap:12px;align-items:center;margin-top:13px;height:55px}
.flag{width:52px;height:52px;border-radius:50%;background:conic-gradient(#d7141a 0 50%,#fff 0);border:2px solid #fff;flex:0 0 52px}
.newstar{width:52px;height:52px;border-radius:50%;background:#ffdf1b;display:flex;align-items:center;justify-content:center;font:700 11px/1 inherit;color:#51010e;flex:0 0 52px}
.introwrap{position:relative;margin-top:27px;height:73.5px;flex:0 0 auto;overflow:hidden}
.h-intro{font-size:var(--intro-size);line-height:var(--intro-line)}
.h-more{font-size:var(--readmore-size);font-weight:var(--readmore-weight);text-decoration:underline;color:#fff;white-space:nowrap}
.spacer{margin-top:auto}
/* A1. Confirmed as a bug in the approved file, not a copy miss: the master
 * renders these at 0px at 1315 too. `.hero-card` is a fixed-height column
 * flex box a few pixels over-full, and an empty 1px div is the only child
 * with no content minimum, so the whole shortfall came out of these two.
 * `flex:0 0 1px` is what stops it, and ttc_rules now fails if either of them
 * ever measures under 0.9 again. */
.rule{height:1px;flex:0 0 1px;background:rgba(255,255,255,.35)}
.rule.a{margin:16px 0 14px}.rule.b{margin:14px 0 18px}
.h-rating{display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px;font-size:var(--rating-size);min-height:25px}
.rgrp{display:inline-flex;align-items:center;gap:10px;white-space:nowrap}
/* review + views travel together when the line runs out, and the view count
 * is pushed hard to the right edge of the row at every width */
.rtail{display:flex;flex:1 1 auto;align-items:center;gap:10px;
  justify-content:space-between;min-width:0;white-space:nowrap}
.stars{letter-spacing:2px;font-size:var(--stars-size)}
/* ROUND 85, PHASE 3: "(write a review)" IS UNDERLINED, IN BOTH BANNERS.
 * It was a link in the hero and a bold <b> that went nowhere in the recipe
 * card, and NEITHER was underlined -- the only thing marking it as clickable
 * was a pair of brackets. Both are anchors now (see the `rating()` macro) and
 * both are underlined at rest, because this one IS a call to action. The
 * byline beside it deliberately is not: phase 2 underlines that only on hover,
 * so the banner does not become a row of underlines.
 *
 * ONE RULE FOR BOTH BANNERS, and that is the point of it. The old rule said
 * `.h-rating .wr` and `text-decoration: none` -- an underline added anywhere
 * less specific would have lost to it and painted nothing while looking
 * perfectly right in the markup, which is why the gate reads
 * `text-decoration-line` off the COMPUTED style rather than trusting the file.
 *
 * `--rating-weight` IS DOING A SECOND JOB HERE. The card's review link was a
 * <b>, so its 700 came from the user agent's `bolder`, not from this file. An
 * <a> does not carry that, so dropping the <b> would have quietly un-bolded
 * it -- measured: weight 700 before, in both banners. Naming the token on
 * `.wr` restates it for both, so the two banners are now the same weight by
 * rule instead of by coincidence.
 *
 * `from-font` on the thickness so the rule is the one the typeface draws,
 * not a hairline scaled off the 14px and 15px these two sit at; 3px of offset
 * so the line clears the descender of the 'p' in "(write a review)". */
.h-rating .wr,section.rcard .rh2 .rate .wr{
  font-weight:var(--rating-weight);color:#fff;
  text-decoration:underline;text-decoration-thickness:from-font;
  text-underline-offset:3px}
.h-rating .wr:hover,section.rcard .rh2 .rate .wr:hover{text-decoration-thickness:2px}
.h-rating .wr:focus-visible,section.rcard .rh2 .rate .wr:focus-visible{
  outline:2px solid currentColor;outline-offset:3px;border-radius:3px}
/* LOCKED: these two never break across lines, at any width */
.wr,.h-more,.mb-right a{white-space:nowrap}
.h-author{display:flex;align-items:center;gap:12px;font-size:var(--author-size);height:44px}
.avatar{width:44px;height:44px;border-radius:50%;background:#fff;flex:0 0 44px}
/* Published: stays on the author line and breaks into two lines when it must,
   always vertically centred against the name */
.h-pub{margin-left:auto;font-size:var(--pub-size);font-weight:var(--pub-weight);text-align:right;line-height:var(--pub-line);align-self:center}
.h-pub span{white-space:nowrap}
.h-author .who{white-space:nowrap}

/* ROUND 85, PHASE 2: THE BYLINE IS A LINK TO /about/, IN BOTH HERO BANNERS.
 * The anchor wraps the portrait and the name in both banners.
 *
 * IT WAS `display: contents` FIRST, AND THAT WAS A BUG THE GATE CAUGHT.
 * `display:contents` was the tidy answer to the layout problem below -- it
 * gives the anchor no box, so the portrait and the name stay flex items of
 * the row around them and every gap rule keeps working with nothing restated.
 * It also TOOK THE LINK OUT OF THE KEYBOARD FOCUS ORDER. Measured, not
 * guessed: tools/byline_link_85.py calls `a.focus()` and then asks whether
 * the anchor is `document.activeElement`, and with `display:contents` it is
 * not -- in either banner. The mouse worked perfectly and every click test
 * passed, which is exactly how this would have shipped: a link a reader with
 * a mouse can use and a reader on a keyboard cannot reach at all.
 *
 * SO THE ANCHOR HAS A BOX, AND `gap: inherit` IS WHAT KEEPS IT HONEST. A
 * wrapper with a box becomes ONE flex item where there were two, so the gap
 * the row put BETWEEN the portrait and the name disappears -- and that gap is
 * set in five different places: 12px in `.h-author`, 16px in `.rh2`, 10px in
 * the 900-1049 container query, 8px in the 800-899 one, 12px again in `.rby`
 * below 768. Restating five numbers here would be five numbers to keep in
 * step with the five over there. `gap: inherit` takes the COMPUTED gap of
 * whatever box the anchor is in, so all five follow it without being named,
 * and a sixth added later follows it too.
 *
 * `min-width:0` because a flex item will not shrink below its content
 * otherwise, and this one sits in a row that has to fit a title, a yield, a
 * date and a rating beside it.
 *
 * THE PROOF THAT IT COST NOTHING is not this comment: the gate measures the
 * portrait's box and the name's box at nine widths on three recipes, before
 * and after, and 108 of 108 agree to a tenth of a pixel.
 *
 * THE THREE THINGS AN ANCHOR BRINGS THAT HAVE TO BE TURNED OFF, because a
 * banner whose every word is white cannot have one of them go link-blue:
 *   colour        `inherit`, so the name keeps the banner's own white --
 *                 `.rhead a{color:#fff}` already covers the card and the hero
 *                 has no such rule, which is why this is stated here
 *   underline     off at rest. The byline is a NAME, not a call to action;
 *                 phase 3 underlines "(write a review)" because that one IS a
 *                 call to action, and doing both would make the banner a row
 *                 of underlines.
 *   the ring      `outline-offset` so a keyboard focus ring clears the round
 *                 portrait instead of cutting through it.
 *
 * AND THE ONE IT BRINGS THAT HAS TO BE ADDED. With no underline at rest there
 * is nothing to say the name is clickable, so the affordance is on HOVER and
 * on FOCUS: the name underlines and the portrait lifts its opacity a little.
 * Both states are on the ANCHOR, so reaching it by keyboard shows the same
 * thing reaching it by mouse does. */
.byline-link{display:inline-flex;align-items:center;gap:inherit;min-width:0;
  color:inherit;text-decoration:none;cursor:pointer}
.byline-link .who,.byline-link .rwho{text-decoration:none}
.byline-link .avatar{transition:opacity .15s ease}
.byline-link:hover .who,.byline-link:hover .rwho,
.byline-link:focus-visible .who,.byline-link:focus-visible .rwho{text-decoration:underline;text-underline-offset:3px}
.byline-link:hover .avatar,.byline-link:focus-visible .avatar{opacity:.82}
.byline-link:focus-visible{outline:2px solid currentColor;outline-offset:3px;border-radius:4px}
.h-diets{position:absolute;right:130px;bottom:27px;display:flex;gap:16px;flex-wrap:wrap;justify-content:flex-end}
.h-diets span{background:#fff;color:var(--dark);height:31px;line-height:var(--diet-line);padding:0 18px;border-radius:999px;font-size:var(--diet-size);font-weight:var(--diet-weight)}
.share{position:absolute;right:0;top:0;background:#fff;border-bottom-left-radius:26px;padding:12px 16px 14px 22px;display:flex;align-items:center;gap:12px}
.share b{font-size:var(--sharelabel-size)}
.ic{width:44px;height:44px;border-radius:50%;flex:0 0 44px}
/* ITEM 3: the site's own social marks, unchanged. These are the same 44x44
 * SVGs the live pages use -- brand colour and glyph are inside the file, so
 * nothing here recolours or redraws them. Facebook is a rounded square, the
 * other two stay circles, and `cover` carries them down to the 36px the
 * narrow rule asks for. */
.ic{background-position:center;background-size:cover;background-repeat:no-repeat}
.ic.fb{background-image:url("/ttc-media/icons/facebook.svg");border-radius:11px}
.ic.pin{background-image:url("/ttc-media/icons/pinterest.svg")}
.ic.at{background-image:url("/ttc-media/icons/email.svg")}
.sh-ico{width:21px;height:21px;display:block}
.sh-ico circle{fill:currentColor}
.sh-ico path{stroke:currentColor;stroke-width:2.3;stroke-linecap:round;fill:none}


/* ---- meta bar grouping ---- */
.mb-times{display:flex;flex-wrap:nowrap;align-items:center;gap:0;min-width:0}
.metabar{flex-wrap:wrap;row-gap:8px}
.mb-right{white-space:normal}
/* ---- pills/badges that live on the photo (narrow only) ---- */
.pill.onphoto,.badges.onphoto{display:none}
.hero-photo .pill.onphoto{position:absolute;left:16px;top:16px;background:rgba(81,1,14,.82);border-color:rgba(255,255,255,.9)}
.hero-photo .badges.onphoto{position:absolute;left:16px;bottom:16px;gap:10px}
.hero-photo .badges.onphoto .flag,.hero-photo .badges.onphoto .newstar{width:44px;height:44px;flex:0 0 44px}
.share-toggle{display:none}
.photo-bottom{display:contents}
/* ---- diet pills centred on the photo, wrapping ---- */
.h-diets{left:30px;right:30px;justify-content:center;row-gap:10px}
/* narrow: badges own the bottom-left corner, so the diet row sits ABOVE them, centred */
/* narrow: nationality+NEW and the diet pills share ONE bottom strip.
   Diets sit on that line to the right; when they do not fit they stack upward,
   right-aligned, so the middle of the photo stays clear. */
@media (max-width:1023px){
  .photo-bottom{display:flex;align-items:flex-end;gap:12px;
    position:absolute;left:16px;right:16px;bottom:16px}
  .hero-photo .badges.onphoto{position:static;flex:0 0 auto}
  /* wrap-reverse fills the BOTTOM line first — leftovers rise to the line above */
  .h-diets{position:static;left:auto;right:auto;bottom:auto;flex:1 1 auto;
    display:flex;flex-wrap:wrap-reverse;justify-content:flex-end;align-content:flex-start;gap:10px}
}

/* ---------- META BAR (tucks under the hero) ---------- */
.metabar{position:relative;z-index:1;margin-top:-40px;height:131px;padding:40px 72px 0 68px;background:var(--card);
  border-radius:0 0 var(--r-lg) var(--r-lg);display:flex;align-items:center}
/* LOCKED: the number and its unit never split across lines */
.mb-item{display:flex;align-items:center;gap:16px;font-size:var(--mbitem-size);color:var(--dark);flex:0 0 184px;white-space:nowrap}
.metabar .mb-times .mb-item:nth-child(3){flex:0 0 200px}
.ring{width:30px;height:30px;flex:0 0 30px;display:block}
.bars{width:30px;height:22px;flex:0 0 30px;display:block}
.ring svg,.bars svg{width:100%;height:100%;display:block}
.ring svg circle,.ring svg path{fill:none;stroke:var(--orange);stroke-width:2.7;
  stroke-linecap:round;stroke-linejoin:round}
.ring.g svg circle,.ring.g svg path{stroke:var(--green)}
.mb-right{margin-left:auto;font-size:var(--mbright-size);text-align:right}
.mb-right b{color:var(--maroon);font-weight:var(--mbright-weight)}
.mb-right a{color:var(--maroon);font-weight:var(--mbright-weight);text-decoration:underline}

/* ---------- BEFORE WE START (pill centred) ---------- */
.bws{position:relative;margin-top:51px;padding-left:38px;min-height:46px;display:flex;align-items:center;flex-wrap:wrap;gap:16px}
.bws-txt{max-width:calc(50% - 150px)}
.bws h2{font-size:var(--bwshead-size);font-weight:var(--bwshead-weight);color:var(--maroon)}
.bws p{font-size:var(--bwsbody-size);color:var(--body);margin-top:4px}
.jump{position:absolute;left:50%;top:0;transform:translateX(-50%);width:247px;height:45px;border-radius:999px;background:var(--maroon);
  color:#fff;display:flex;align-items:center;justify-content:center;gap:12px;font-size:var(--btnjump-size);font-weight:var(--btnjump-weight);text-decoration:none}
.chev{width:26px;height:26px;border-radius:50%;background:#fff;color:var(--maroon);font-size:var(--chev-size);line-height:var(--chev-line);text-align:center;flex:0 0 26px}

/* ---------- CARDS ---------- */
.card{background:var(--card);border-radius:var(--r-lg);padding:48px 50px}
/* PART 4: a direction card is one of the full-width content blocks and
 * takes the shared inset. An article card is a card in a grid of four
 * and keeps its own. */
.card.step{padding-left:var(--pad-l);padding-right:var(--pad-r)}
.card+.card{margin-top:30px}
.card h3{font-size:var(--arthead-size);font-weight:var(--arthead-weight);color:var(--dark)}
.card p{font-size:var(--artbody-size);line-height:var(--artbody-line);color:var(--body);margin-top:14px}
.tips{margin-top:50px}

/* ---------- KO-FI + RECIPE LABEL ---------- */
.kofi{display:flex;justify-content:center;margin-top:33px}
.kofi a{width:222px;height:55px;border-radius:10px;background:var(--maroon);color:#fff;display:flex;align-items:center;
  justify-content:center;gap:12px;font-size:var(--kofi-size);font-weight:var(--kofi-weight);text-decoration:none}
.cup{width:22px;height:22px;border-radius:4px;background:#fff;flex:0 0 22px}
.rlabel{font-size:var(--label-size);font-weight:var(--label-weight);color:var(--maroon);margin:45px 0 0 53px}
/* ROUND 120, PART A.5. The recipe-name heading in its new home, directly
   above the ingredients box. It cannot keep `.rlabel`'s own geometry: that
   45px top margin and 53px left indent were measured for a label standing
   OUTSIDE the card, and inside it the indent lands the words a third of the
   way across the box. Inside, the card's padding is the indent. */
.rlabel--ing{margin:0 0 14px;padding:0}


/* ---------- RECIPE CARD ---------- */
.rcard{background:var(--card);border-radius:var(--r-lg);margin-top:42px}
/* ROUND 69. THE CARD HEADER'S VERTICAL PADDING IS SYMMETRICAL.
 * It was `height:155px` with `padding-top:34px` and `padding-bottom:0`, so
 * the title sat 34px below the top edge and the "2 pan pizza..." row sat 14px
 * above the bottom one -- measured, 34 against 14. The fixed height is what
 * decided the bottom gap, which is why it was never the same number twice.
 * Height comes from the content now and the two paddings are one value, so
 * the gap above the title and the gap below the last row are equal at every
 * width and on every recipe -- including the ones that carry a card category
 * above the title or the card's own paragraphs below the row. */
.rhead{background:var(--maroon);border-radius:var(--r-lg);height:auto;color:#fff;padding:34px var(--pad-r) 34px var(--pad-l);display:flex;flex-direction:column}
.rh1{display:flex;align-items:center;min-height:30px}
/* ROUND 38, JOB 39.24 / 39.22 (f). THE SAME REGRESSION, A SECOND TIME.
 * `.rhead{color:#fff}` reaches its children only by inheritance, and the
 * theme stylesheet that came back with the site in 37.1 carries
 * `h2{color:var(--s-ink,#1a2428)}`. An inherited colour loses to any matching
 * rule. MEASURED: the recipe card's own title rendered rgb(26,36,40) on the
 * maroon rgb(81,1,14) -- 1.03:1, the same as the hero title.
 * The contrast check in tools/contrast_39.py found this one; my eye did not,
 * and neither did any gate before tonight. Everything in the maroon header
 * states its colour. */
.rhead, .rhead h1, .rhead h2, .rhead h3, .rhead p, .rhead span,
.rhead a, .rhead b, .rhead strong, .rhead em, .rhead div{color:#fff}
.rh1 h2{font-size:var(--rtitle-size);font-weight:var(--rtitle-weight);line-height:var(--rtitle-line)}
.rh2{display:flex;align-items:center;gap:16px;margin-top:22px;font-size:var(--rmetaline-size)}
.rh2 .pub{font-size:var(--rpub-size)}
.rh2 .flag,.rh2 .newstar{width:40px;height:40px;flex:0 0 40px;font-size:var(--badge-size)}
/* LOCKED: nationality and NEW always sit on the same line */
.natnew{display:inline-flex;align-items:center;gap:12px;flex:0 0 auto;white-space:nowrap}
.rh2 .avatar{width:40px;height:40px;flex:0 0 40px}
.rh2 .rate{margin-left:auto;display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px;font-size:var(--rrate-size)}
.rh2 .pub{white-space:nowrap}
/* ROUND 85, PHASE 2: `gap: inherit` HAS TO BE CHAINED THROUGH HERE.
 * The byline anchor's `gap: inherit` takes the computed gap of its DOM
 * PARENT, and above 767px that parent is this span, not `.rh2` -- `.rby` is
 * `display: contents`, so it is the row's parent for LAYOUT and still the
 * anchor's parent for INHERITANCE. Without the line below it inherited this
 * span's own gap, which is `normal`, and the portrait and the name closed up
 * against each other: the gate measured the name 8px left at 1024 and 1440,
 * 10px at 1180 and 1600, and 16px at 1280 -- which is `.rh2`'s gap in each of
 * its three container bands, exactly the gap that had gone missing.
 * `gap: inherit` here passes `.rh2`'s own gap down the chain, so the five
 * places that set it are still the only five places that set it.
 * (Below 768 the media rule near the foot of this file gives `.rby` a real
 * `display: flex` and `gap: 12px`; it comes later and wins, and the anchor
 * then inherits that 12px, which is what the phone had before.) */
.rby{display:contents;gap:inherit}
.rrule{display:none;height:1px;background:rgba(255,255,255,.35)}
.rmeta{display:flex;align-items:center;padding:23px var(--pad-r) 25px var(--pad-l)}
.print{margin-left:auto;width:167px;height:40px;border-radius:8px;background:var(--green);color:#fff;display:flex;
  align-items:center;justify-content:center;gap:10px;font-size:var(--btnprint-size);font-weight:var(--btnprint-weight);flex:0 0 167px}
.boxes{display:flex;gap:32px;padding:0 var(--pad-r) 60px var(--pad-l)}
.box{border-radius:var(--r-md);padding:33px}
.box.ing{flex:0 0 729px;border:1px solid #e2e2e2}
.box.too{flex:0 0 364px;border:1.5px solid var(--red)}
.box h3{font-size:var(--boxhead-size);font-weight:var(--boxhead-weight)}
.sign{display:inline-flex;align-items:center;margin:20px 0 22px;height:34px;padding:0 40px 0 18px;border:1px solid var(--orange);
  border-radius:999px;background:#fff7ef;color:#b4531a;font-size:var(--pilllink-size);font-weight:var(--pilllink-weight);position:relative;text-decoration:none}
.sign i{position:absolute;right:-16px;top:-8px;width:38px;height:38px;border-radius:8px;background:#f7b27a;transform:rotate(15deg)}
.ing-cols{display:flex;gap:40px}
.ing-cols>div{flex:1}
.li{font-size:var(--ing-size);line-height:var(--ing-line);color:var(--body)}
.li b{color:var(--dark)}
/* ITEM 5: ingredients and tools link the same way -- near-black, regular
 * weight, underlined, and never the browser's blue or its visited purple.
 * The tool line is no longer bold as a whole; only its "1x" is, which is the
 * same treatment the measure gets in an ingredient line. */
.too .li{color:var(--dark)}
.box .li a{color:var(--dark);font-weight:var(--ing-weight);text-decoration:underline;
  text-underline-offset:2px}
.box .li a:visited{color:var(--dark)}
.box .li a:hover{color:#000}

/* ---------- NUTRITION ---------- */
.nut{background:var(--cream);border:1px solid #e8dfae;border-radius:var(--r-md);margin-top:38px;padding:33px calc(var(--pad-r) - 1px) 30px calc(var(--pad-l) - 1px)}
.nut-top{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.nut h3{font-size:var(--nuthead-size);font-weight:var(--nuthead-weight);color:var(--maroon)}
.nut-basis{display:block;margin-top:6px;font-size:var(--nutbasis-size);font-weight:var(--nutbasis-weight);color:var(--dark)}
/* diet pills live here now — their own row under the nutrition values */
.nut-diets{margin-top:20px;display:flex;gap:14px;flex-wrap:wrap}
.nut-diets span{height:32px;line-height:var(--nutdiet-line);padding:0 16px;border:1px solid var(--dark);border-radius:999px;font-size:var(--nutdiet-size);font-weight:var(--nutdiet-weight);background:#fff}
/* one running text line — wraps like a sentence at any width */
.nut-vals{margin-top:22px;font-size:var(--small-size);color:var(--body);line-height:var(--small-line);display:block}
.nut-vals span{white-space:nowrap}
.nut-vals span:not(:last-child)::after{content:" |";color:#b9b3a0;font-weight:var(--small-weight)}
.nut-vals span:not(:last-child){margin-right:.35em}
.nut-vals b{color:var(--dark)}
.nut-note{margin-top:20px;font-size:var(--nutnote-size);font-weight:var(--nutnote-weight);color:var(--dark);line-height:var(--nutnote-line)}

/* ---------- DIRECTIONS ---------- */
.dirlabel{font-size:var(--label-size);font-weight:var(--label-weight);color:var(--maroon);margin:46px 0 49px 53px}
.step{padding:55px 50px 48px 50px}
.step h3{font-size:var(--dirhead-size);font-weight:var(--dirhead-weight);line-height:var(--dirhead-line);margin-bottom:28px}
.step h3 .num{display:inline-block;width:var(--num-gutter)}
.sstep{font-size:var(--sstep-size);margin-left:var(--step-indent);line-height:var(--sstep-line);display:flex;align-items:flex-start}
.sstep .sbody{flex:1;min-width:0}
.sstep .sstep{margin-left:0;margin-top:18px}
.sstep .lead{font-weight:var(--stepnum-weight);flex:0 0 auto;min-width:41px;padding-right:6px}
.slist{margin:27px 0 0 0;font-size:var(--slist-size);line-height:var(--slist-line);color:var(--body)}
.slist b{color:var(--dark)}

/* ---------- COOKING TIMERS (any time in the directions is clickable) ---------- */
.tmr{font:inherit;color:var(--orange);font-weight:var(--timer-weight);background:none;border:0;padding:0;
  cursor:pointer;text-decoration:underline;text-underline-offset:2px;white-space:nowrap;display:inline}
.tmr:hover{color:#d96a13}
.tmr .watch{width:1em;height:1em;vertical-align:-.13em;margin-left:.28em}
.tmr .watch path,.tmr .watch circle,.tmr .watch line{stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
.tmr-run{margin-left:9px;display:inline-flex;align-items:center;gap:10px;background:var(--orange);color:#fff;font-weight:var(--timer-weight);
  border-radius:999px;padding:4px 12px 4px 16px;font-variant-numeric:tabular-nums;white-space:nowrap;
  font-size:var(--timer-size);line-height:var(--timer-line);vertical-align:-.15em}
.tmr-run .x{cursor:pointer;font-weight:var(--timerx-weight);font-size:var(--timerx-size);line-height:var(--timerx-line);opacity:.9;padding:0 2px}
.tmr-run .x:hover{opacity:1}
.tmr-run.done{background:var(--green);animation:tmrflash 1s steps(1) infinite}
@keyframes tmrflash{50%{background:#2f8a1f}}
@media (prefers-reduced-motion:reduce){.tmr-run.done{animation:none}}

/* ---------- GALLERY ---------- */
.gal{background:var(--card);border-radius:var(--r-lg);margin-top:30px;padding:22px;display:flex;gap:22px;align-items:flex-start}
.gal-main{flex:1;aspect-ratio:var(--gal-ratio);height:auto;border-radius:26px;background:linear-gradient(135deg,#e8d9b8,#cfe0c2);position:relative;overflow:hidden}
/* The gallery photographs are real <img> elements now, so Google Images
   can read the alt Tom accepted. They fill the tile the CSS background
   was drawing, at the same crop -- object-fit:cover IS center/cover --
   so the tile looks exactly as it did. The background stays underneath
   as the paint-in colour while the image decodes. */
.gal-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center;display:block;border:0}
.gal-main>.gal-img{border-radius:26px}
/* QUEUE 75, PHASE 1: `height:var(--gal-h)` was the same number the picture
   used. The picture is a ratio now, so the strip STRETCHES to the row it is
   in -- whose height the picture sets, because it is the taller item -- and
   the two stay exactly the same height without either one naming a number. */
.gal-thumbs{display:flex;flex-direction:column;gap:10px;align-items:center;
  flex:0 0 auto;align-self:stretch;height:auto}
/* PART 3: the strip is a WINDOW on the thumbnails, not the whole list. Its
   height is the main photograph's, exactly as live: 520px of thumbnails
   beside a 520px picture. Overflow is hidden and the arrows move it -- the
   bar is never drawn, because a scrollbar is not what live shows. */
/* QUEUE 75, PHASE 1: `height:0` is what keeps this a WINDOW now that the
   photograph is a ratio rather than 690px. The strip's own content is every
   thumbnail there is -- 898px of them on an eight-photograph recipe -- and a
   column whose content is 898 tall makes the flex LINE 898 tall, which is
   how the strip stopped being a window the moment the picture stopped naming
   a number: the picture drew 494 and the strip ran 400px past the bottom of
   it. With the strip's own height at 0 the column asks the line for its two
   arrows and nothing else, so the PICTURE sets the line's height again, and
   `flex-grow` then gives the strip exactly the room left between the two
   arrows. Reset to `auto` at <=1023, where this strip lies on its side and
   its height is the thumbnails' own. */
.gal-strip{flex:1 1 auto;height:0;min-height:0;overflow:hidden;scroll-behavior:smooth;
  scrollbar-width:none;-ms-overflow-style:none}
.gal-strip::-webkit-scrollbar{display:none}
.gal-track{display:flex;flex-direction:column;gap:14px}
.gal-track i{position:relative;overflow:hidden;display:block}
.gal-thumbs i{display:block;width:150px;height:100px;flex:0 0 100px;
  border-radius:12px;background:linear-gradient(135deg,#dfe7cf,#e9dcc0);
  cursor:pointer;position:relative;transition:opacity .25s ease}
/* live dims every thumbnail but the one being shown */
.gal-thumbs i{opacity:.5}
.gal-thumbs i.on,.gal-thumbs i:hover{opacity:1}
/* the site's own up and down arrows, on the same orange disc the main
   photograph's left and right arrows use */
.sarrow{width:44px;height:44px;flex:0 0 44px;border-radius:50%;cursor:pointer;
  background:var(--orange) no-repeat center/14px auto}
.sarrow.up{background-image:url("/ttc-media/icons/arrow-up-white.svg")}
.sarrow.down{background-image:url("/ttc-media/icons/arrow-down-white.svg")}
.sarrow[hidden]{display:none}
.arrow{position:absolute;top:50%;transform:translateY(-50%);width:56px;height:56px;border-radius:50%;background:var(--orange)}
.arrow.l{left:34px}.arrow.r{right:34px}
.pinbtn{position:absolute;right:26px;top:26px;width:46px;height:46px;border-radius:12px;background:#fff}

/* ---------- EMBED BLOCKS ---------- */
.embed{background:var(--card);border-radius:var(--r-lg);margin-top:30px;padding:48px var(--pad-r) 48px var(--pad-l)}
.embed h3{font-size:var(--embedhead-size);font-weight:var(--embedhead-weight)}
.embed .muted{color:var(--body);font-size:var(--small-size);margin-top:16px}



/* ---- fluid range 1024–1244: keep the desktop layout but proportional ---- */
@media (min-width:1024px) and (max-width:1244px){
  /* the same roles, one step down at this width: the TOKEN
     changes, never the rule. */
  :root{
    --pilllink-size: 13.5px;
  }

  .ttc-v2{width:100%;padding:0 20px}
  .hero{height:auto;min-height:480px;display:flex}
  .hero-card{width:45%;flex:0 0 45%;height:auto}
  .hero-photo{left:45%;width:55%}
  .metabar{padding-left:5.5%;padding-right:5.8%}
  /* size to content (they must never wrap) and space them with a safe margin */
  .mb-item{flex:0 0 auto;margin-right:clamp(16px,4.2%,64px)}
  .metabar .mb-times .mb-item:nth-child(3),.rmeta .mb-item:nth-child(3){flex:0 0 auto;margin-right:0}
  :root{ --pad-l:4.5%; --pad-r:5.2%; }
  .boxes{padding:0 var(--pad-r) 60px var(--pad-l);gap:2.6%}
  .box.ing{flex:1 1 auto;min-width:0}
  .box.too{flex:0 0 34.5%;min-width:0}
  /* the sign fills its box and keeps more room for the label */
  .sign{max-width:100%;padding-right:30px;padding-left:14px;font-size:var(--pilllink-size)}
  .sign i{right:-10px;width:34px;height:34px;top:-7px}
  /* the yield and the chef's name never break mid-phrase — the row wraps instead */
  /* ROUND 69: 34 top and 34 bottom here too -- it was 26 at the bottom. */
  .rhead{padding-left:4.6%;padding-right:5.2%;height:auto;padding-bottom:34px}
  .rh2{flex-wrap:wrap;row-gap:10px}
  .ryield,.rwho{white-space:nowrap}
  .rmeta{padding-left:var(--pad-l);padding-right:var(--pad-r)}
  .nut{padding-left:calc(var(--pad-l) - 1px);padding-right:calc(var(--pad-r) - 1px)}
  .card.step,.embed{padding-left:var(--pad-l);padding-right:var(--pad-r)}
  .card:not(.step){padding-left:4%;padding-right:4%}
}

/* ============ RESPONSIVE (rebuilt to Tom's rules) ============ */
@media (max-width:1023px){
  /* hero grows to its content; the photo is FIRST and absorbs any spare room
     (spare space stretches the image — it never opens a gap between the text) */
  .hero{display:flex;flex-direction:column;height:auto}
  .hero-photo{order:-1;position:relative;left:0;width:100%;height:auto;aspect-ratio:4/3;
    flex:1 1 auto;max-height:calc(100svh - 330px);min-height:190px;border-radius:0}
  .spacer{display:none}
  /* PART 4: the space under the author row is 40px at EVERY width. It was
     40 above 1023 and 20 below, so the row Tom said sat too close to the
     edge really did sit twice as close on a phone as on a desktop. 40 is
     the design file's own figure. */
  .hero-card{width:100%;padding:22px 22px 40px}
  .hero-card>.pill,.hero-card>.h-badges{display:none}
  .pill.onphoto{display:inline-block}
  .badges.onphoto{display:flex}
  .h-title{margin-top:0}
  .h-yield{margin-top:8px;font-size:var(--yield-size)}
  .introwrap{margin-top:14px;height:50px}
  .h-intro{font-size:var(--intro-size);line-height:var(--intro-line)}
  .rule.a{margin:12px 0 10px}.rule.b{margin:10px 0 12px}
  .h-rating{font-size:var(--rating-size);gap:4px 8px}
  .rgrp{gap:8px}
  /* (write a review) stays beside the stars; the view count is pushed hard right,
     and if the pair has to drop a line it keeps the same alignment */
  .rh2 .rate{flex-wrap:wrap;width:100%}
  .h-author{font-size:var(--author-size);height:40px}
  .avatar{width:40px;height:40px;flex:0 0 40px}
  .h-pub{font-size:var(--pub-size)}
  /* times + level spread across the whole bar instead of bunching left */
  .mb-times{justify-content:space-between;width:100%}
  .rmeta{justify-content:space-between}
  /* share collapses to one icon that opens to the left */
  .share{border-bottom-left-radius:22px;padding:8px 10px;gap:8px}
  .share-toggle{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:50%;
    background:#f1f1f1;color:#51010e;font-size:var(--sharetoggle-size);cursor:pointer}
  .share b{display:none}
  .share .ic{display:none;width:36px;height:36px;flex:0 0 36px}
  .share.open .ic{display:block}
  /* meta bar: times always one line, calories block wraps as a unit */
  .metabar{height:auto;padding:52px 22px 18px;gap:10px 18px}
  .mb-times{gap:12px;flex:1 1 100%}
  /* as large as fits while staying on ONE line — identical in both boxes */
  .mb-item,.metabar .mb-times .mb-item:nth-child(3){flex:0 0 auto;width:auto;
    font-size:var(--mbitem-size);gap:clamp(8px,2.4vw,12px)}
  .ring{width:clamp(26px,7.2vw,32px);height:clamp(26px,7.2vw,32px);flex:0 0 auto;border-width:3px}
  .bars{width:clamp(26px,7.2vw,32px);height:clamp(20px,5.4vw,24px);flex:0 0 auto;background-size:4px 6px,4px 11px,4px 16px,4px 22px}
  /* centred once it sits on its own row under the times */
  .mb-right{flex:1 0 100%;margin-left:0;text-align:center;font-size:var(--mbright-size)}
  /* boxes and cards */
  .boxes{flex-direction:column;padding:0 22px 34px;gap:22px}
  .box.ing,.box.too{flex:1 1 auto;width:100%;padding:26px 22px}

  /* label capped at two lines at any width */
  .sign{display:block;width:fit-content;margin:16px 0 18px;height:auto;min-height:38px;
    padding:9px 42px 9px 18px;white-space:normal;max-width:calc(100% - 22px);
    font-size:var(--pilllink-size);line-height:var(--pilllink-line)}
  .sign i{width:34px;height:34px;right:-12px;top:-7px}
  .rhead{height:auto;padding:22px}
  /* title / yield / nationality+NEW+chef / diets / decorative line / review + views */
  .rhead{gap:12px}
  .rh1,.rh2{display:contents}
  .rh1 h2{font-size:var(--rtitle-size);order:1}
  .ryield{order:2}
  .rby{order:3;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
  .rrule{display:block;order:5;margin:2px 0}
  .rh2 .rate{order:6;margin-left:0;margin-top:0}
  .rh2 .pub{display:none}
  .rh2{flex-wrap:wrap;gap:10px;margin-top:14px;font-size:var(--rmetaline-size)}
  .rh2 .rate{margin-left:0;flex:1 0 100%}
  .rmeta{flex-wrap:wrap;gap:14px;padding:18px 22px}
  .rmeta .mb-item{flex:0 0 auto}
  .print{flex:1 0 100%;margin-left:0;width:100%}
  :root{ --pad-l:22px; --pad-r:22px; }
  .nut{padding:24px calc(var(--pad-r) - 1px) 24px calc(var(--pad-l) - 1px)}
  .nut-vals{font-size:var(--small-size)}
  .card,.step,.embed{padding:26px var(--pad-r) 26px var(--pad-l)}
  .step h3{font-size:var(--dirhead-size)}
  .sstep{margin-left:0;font-size:var(--sstep-size)}
  .slist{font-size:var(--slist-size)}
  /* centred together with "Before We Start" */
  .rlabel,.dirlabel{margin-left:0;text-align:center}
  /* stacked, the cross axis is the WIDTH, so the flex-start that keeps
     the strip from stretching on a desktop would shrink the photograph
     to nothing here. */
  .gal{flex-direction:column;padding:16px;align-items:stretch}
  .gal-main{height:auto;aspect-ratio:var(--gal-ratio)}
  /* on a phone the same strip lies on its side: still a fixed window, still
     two arrows, still one thumbnail lit */
  .gal-thumbs{flex-direction:row;justify-content:center;height:auto;width:100%}
  .gal-strip{width:100%;height:auto;overflow:hidden}
  .gal-track{flex-direction:row;gap:10px}
  .gal-thumbs i{width:110px;height:75px;flex:0 0 110px}
  .sarrow{width:36px;height:36px;flex:0 0 36px}
  .sarrow.up{background-image:url("/ttc-media/icons/arrow-left-white.svg")}
  .sarrow.down{background-image:url("/ttc-media/icons/arrow-right-white.svg")}
  .bws{padding-left:4px;flex-direction:column;align-items:center;text-align:center}
  .bws-txt{max-width:100%}
  .jump{position:static;transform:none;margin:16px auto 0;flex:0 0 auto}
}
@media (max-width:600px){
  /* the same roles, one step down at this width: the TOKEN
     changes, never the rule. */
  :root{
    --diet-line: 28px;
    --diet-size: 13px;
  }

  .kofi{margin-top:28px}
  .h-diets{gap:8px}
  .h-diets span{height:28px;line-height:var(--diet-line);padding:0 14px;font-size:var(--diet-size)}
  .hero-photo{aspect-ratio:1/1}
  .introwrap{height:50px}
}
@media (max-width:380px){
  /* the same roles, one step down at this width: the TOKEN
     changes, never the rule. */
  :root{
    --pilllink-line: 17.5px;
    --pilllink-size: 13.5px;
  }

  /* very small phones: one step down so the label still fits two lines */
  .sign{font-size:var(--pilllink-size);line-height:var(--pilllink-line);padding:8px 38px 8px 15px;max-width:calc(100% - 18px)}
  .sign i{width:31px;height:31px;right:-10px;top:-6px}
}


/* ================= ROUND 4 FIX LIST: the site's own assets ================
 * Nothing below is drawn. Every mark is the one the live site already uses,
 * referenced from where it already lives.
 */

/* R1. The orange tilted rounded square beside the two signs was a shape from
 * the design file. The site's own pointing hand is an SVG mask token that the
 * live recipe pages use today -- `--ttc-hand` in static/site/recipe-redesign.css,
 * painted on `.ttc-sign__hand`. The same token is used here, copied exactly,
 * so the two pages cannot drift apart. */
:root {
  --ttc-hand: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 11V5.5a1.5 1.5 0 0 1 3 0V11m0-1.5a1.5 1.5 0 0 1 3 0V12m0-1a1.5 1.5 0 0 1 3 0v1m0 0v4a6 6 0 0 1-6 6h-1a6 6 0 0 1-5.2-3l-2.3-4a1.5 1.5 0 0 1 2.6-1.5L9 15' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.sign i {
  background: #b4531a;
  border-radius: 0;
  transform: rotate(0deg);
  -webkit-mask: var(--ttc-hand) center/contain no-repeat;
          mask: var(--ttc-hand) center/contain no-repeat;
}

/* R4. The gallery's two orange circles and its white rounded square were
 * shapes too. The site's own gallery arrows are `arrow-left-white.svg` and
 * `arrow-right-white.svg` on an orange disc -- `.arrow-btn--prev/--next` with
 * `.arrow-btn--orange` in the theme -- and the save button is the site's own
 * Pinterest mark. */
.arrow.l { background: var(--orange) url("/ttc-media/icons/arrow-left-white.svg") center/14px auto no-repeat; }
.arrow.r { background: var(--orange) url("/ttc-media/icons/arrow-right-white.svg") center/14px auto no-repeat; }
.pinbtn  { background: #fff url("/ttc-media/icons/pinterest.svg") center/32px auto no-repeat; }

/* R7. The chevron in "Jump to Recipe" is the site's own inline SVG, the one
 * `.ttc-jump__chev` carries on the live recipe pages: a white disc with a
 * maroon chevron. The markup holds it, so nothing here draws it. */
.jump .chev { background: none; font-size: var(--chevreset-size); line-height: var(--chevreset-line); }
.jump .chev svg { width: 26px; height: 26px; display: block; }

/* R5. The comments block brings its own heading, so it is styled like the
 * headings this design gives the other embed blocks rather than duplicated. */
.embed h2 { font-size: var(--embedhead-size); font-weight: var(--embedhead-weight); }

/* R6. The disclaimer is the site's own policy embed -- the same slot and the
 * same script the live recipe pages use -- and it fills with an iframe the
 * live page measures at 1487px too. What v3 was missing is the reservation
 * live makes for it, so the card stood empty until the script answered.
 * These are the live numbers, copied from static/site/recipe-redesign.css. */
.ttc-embed-slot { display: block; }
.ttc-embed-slot--policy { min-height: 1500px; }
@media (max-width: 1023px) { .ttc-embed-slot--policy { min-height: 1700px; } }
@media (max-width: 600px)  { .ttc-embed-slot--policy { min-height: 3150px; } }


/* R3. The number and its text did not share a baseline: `.sstep` aligned the
 * tops of the two boxes, so as soon as the body began with a list or a nested
 * step the number floated 16, 40, even 112 pixels above the line it belongs
 * to. Aligning the BASELINES is what "on one baseline" means, and it is one
 * word. The indent per level was already right -- 41px, the lead's own width,
 * once per level -- and the gap between numbered cards was already 30. */
.sstep { align-items: baseline; }

/* R10. A blank line before each ingredient sub-heading, exactly as the live
 * page spaces its `.section` headings -- the first one needs none, because
 * the sign above it already gives it room. */
.ing-cols .li.sec { margin-top: 22px; font-size: var(--ingsec-size); }
.ing-cols .li.sec:first-child { margin-top: 0; }

/* R11. The footnotes, under the list and in marker order, a step smaller than
 * the lines they refer to -- which is what the live page does with them. */
.ing-notes { margin-top: 22px; }
.ing-notes p {
  font-size: var(--footnote-size);
  line-height: var(--footnote-line);
  color: var(--body);
  margin-top: 10px;
  font-weight: var(--footnote-weight);
}
.ing-notes p:first-child { margin-top: 0; }

/* ===== ŽEMLOVKA, 8 OCTOBER -- A LINK IN THE INGREDIENT NOTES LOOKS LIKE A
 * LINK.
 *
 * WHAT TOM REPORTED: in "Notes under the ingredients" the links WORK but
 * render as plain dark text -- not underlined, not orange. MEASURED in a real
 * browser on the live page rather than read off this file: the one link in
 * Žemlovka's notes computes `color: rgb(0, 0, 0)` and
 * `text-decoration-line: none`.
 *
 * NOTHING ON THIS SITE WAS STYLING IT. There is no `.ing-notes a` rule and no
 * rule above it that reaches one, so the only thing that applies is the
 * theme bundle's own reset -- `a { color: black; text-decoration: none }` in
 * /bundle/<hash>.css, which every page loads first. The ingredient LINES have
 * had their own rule since the rebuild (`.box .li a`, dark and underlined);
 * the notes under them never got one, and round 112's "Notes under the
 * ingredients" box made them editable, so Tom started putting links there.
 *
 * WHY #b4531a AND NOT --orange (#f18032). This text is 13.8px, which is
 * "small" for WCAG, so it needs 4.5:1 against the card's white. --orange is
 * the site's orange for FILLS -- discs, buttons, the pill's border -- and
 * gives 2.67:1 as text; #b4531a is the site's orange for TEXT, already used
 * for the words on `.sign` (the Amazon shop pill), and gives 5.01:1.
 * tools/audit_a11y.py counts contrast on the rendered page and the lighter
 * one would have added a new `serious` finding to ACCESSIBILITY.md on every
 * recipe with a note link. QUESTIONS.md, 8 October (b).
 *
 * IT IS ONE RULE AND IT REACHES EVERY RECIPE'S NOTES, which is what Tom
 * asked for: 218 of the 360 stored bodies carry a notes block, and a link in
 * any of them now looks the same. The underline is `from-font` for the same
 * reason `.h-rating .wr` is -- a 1px line under a 13.8px serif descender is
 * the theme's own weight, not a thicker one invented here. */
.ing-notes a,
.ing-notes a:visited {
  color: #b4531a;
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 2px;
}
.ing-notes a:hover { color: #8f3f10; text-decoration-thickness: 2px; }
.ing-notes a:focus-visible { outline: 2px solid #b4531a; outline-offset: 2px; }

/* AND THE SAME RULE IN THE WRITING ABOVE THE RECIPE, for the same reason and
 * with the same measurement behind it.
 *
 * Tom's "Made with Hansen's Dairy" section carries a link in its paragraph
 * -- "find Hansen's products around Iowa here" -- and it rendered as plain
 * bold black text, exactly as the notes link did and for exactly the same
 * cause: no rule, so the theme bundle's `a { color: black; text-decoration:
 * none }` is the only thing that applies.
 *
 * MEASURED BEFORE WRITING IT, over the SEO article of all 172 published
 * recipes as `recipe_v2.page_prose` returns them: 0 links. Tom's is the
 * FIRST one on the site, so this rule changes exactly one card on exactly
 * one page today -- the one he wrote this morning -- and is waiting for the
 * next. That is why it is in tonight's Žemlovka job and not held back with
 * the systemic work: there is no "all recipes" for it to be systemic about.
 *
 * `.tips .card` is the article card; `.bsw-item` (Best served with), `.jump`,
 * `.kofi` and the pill links are buttons with their own rules and are not
 * reached by this -- they are not `.tips .card a`. QUESTIONS.md, 8 Oct (b). */
.tips .card a,
.tips .card a:visited {
  color: #b4531a;
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 2px;
}
.tips .card a:hover { color: #8f3f10; text-decoration-thickness: 2px; }
.tips .card a:focus-visible { outline: 2px solid #b4531a; outline-offset: 2px; }

/* R9. The hero was a FIXED 532px, so a recipe whose title runs to more lines
 * than Ranch Dip's pushed the author row out of the bottom of the card and
 * `overflow:hidden` cut it off -- measured at -31px past the edge on the
 * cheese-battered schnitzel, against 40px of padding, so 71px inside the
 * rule. That is what "the author row sits too close to the card edge" was.
 *
 * The height becomes a floor. A short recipe is 532 exactly, as the design
 * file draws it; a long one grows instead of losing its author row. The
 * file's own narrow rule already does this -- `.hero{height:auto}` at 1023 --
 * so this is the same behaviour carried up to the desktop. */
@media (min-width: 1245px) {
  .hero { height: auto; min-height: 532px; }
  .hero-card { height: auto; min-height: 532px; }
}


/* 3b. The Ko-fi cup was the design file's blank white square. This is the
 * site's own mark -- the exact data-URI `.ttc-kofi__cup` carries on the live
 * recipe pages, a white cup with Ko-fi's red heart in it, copied verbatim. */
.cup {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3.6 6.2h12.2v7.4a4.6 4.6 0 0 1-4.6 4.6H8.2a4.6 4.6 0 0 1-4.6-4.6V6.2Z' fill='%23fff'/%3E%3Cpath d='M15.8 7.6h1.9a2.6 2.6 0 0 1 0 5.2h-1.9' fill='none' stroke='%23fff' stroke-width='2.1' stroke-linecap='round'/%3E%3Cpath d='M9.7 8.6c.9-.9 2.3-.3 2.3.8 0 1-1.2 1.9-2.3 2.7-1.1-.8-2.3-1.7-2.3-2.7 0-1.1 1.4-1.7 2.3-.8Z' fill='%23ff5f5f'/%3E%3C/svg%3E") center/22px no-repeat;
  border-radius: 0;
}

/* 3c. "Jump to Recipe": the live button's own measurements, so the two pages
 * agree -- maroon pill, white text, 700, the chevron in a white disc. The
 * glyph is the site's own inline SVG and lives in the markup, not here. */
.jump {
  gap: 12px;
  background: #51010e;
  color: #fff;
  font-weight: var(--btnjump-weight);
  font-size: var(--btnjump-size);
  line-height: var(--btnjump-line);
  padding: 13px 26px 13px 13px;
  border-radius: 999px;
  text-decoration: none;
  width: auto;
  height: auto;
}
.jump:hover { background: #6b0212; color: #fff; text-decoration: none; }
.jump .chev { width: 28px; height: 28px; flex: 0 0 28px; background: none; }
.jump .chev svg { width: 28px; height: 28px; display: block; }

/* 5a. THE BOTTOM ROW FILLS FIRST, AT EVERY WIDTH.
 * The design file only turns on `wrap-reverse` at 1023 and below, so above
 * that the pills wrapped the ordinary way and the TOP line held the most --
 * five up and one down on garam masala at 1315, which is the wrong way round.
 * The lower line must never hold fewer than the line above it. */
.h-diets {
  flex-wrap: wrap-reverse;
  align-content: flex-start;
}

/* ===================== PART 6: "Read More" opens the full intro ==========
 * The site already has a modal and this is it: `.popup-dialog` with its
 * `.bg-overlay`, its `.dialog` and its round `.close`, the same component the
 * share dialog and the exit-intent dialog use. A v3 page loads no theme CSS,
 * so the component's own declarations are copied here -- same class names,
 * same structure, same behaviour. No second style of modal is introduced.
 */
.popup-dialog {
  width: 100%; height: 100%; position: fixed; left: 0; top: 0;
  z-index: 99999; display: block; opacity: 1;
  transition: opacity .3s ease;
}
.popup-dialog.hidden { top: -10000vh; opacity: 0; }
.popup-dialog .bg-overlay {
  width: 100%; height: 100%; position: absolute; left: 0; top: 0;
  display: block; overflow: hidden; z-index: 1;
  backdrop-filter: blur(10px);
  background-color: rgba(0, 0, 0, .3);
}
.popup-dialog .dialog {
  width: 60rem; max-width: calc(100% - 40px); max-height: calc(100vh - 40px);
  padding: 28px 60px 36px 40px;
  position: absolute; left: 50%; top: 50%; z-index: 10; overflow: auto;
  transform: translate(-50%, -50%);
  background: #fff; border-radius: var(--r-md);
}
.popup-dialog .dialog h3 {
  font-size: var(--modalhead-size); font-weight: var(--modalhead-weight); color: var(--dark); margin-bottom: 14px;
}
.popup-dialog .dialog p {
  font-size: var(--modalbody-size); line-height: var(--modalbody-line); color: var(--body);
}
.popup-dialog .dialog .close {
  width: 30px; height: 30px; position: absolute; right: 18px; top: 18px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background-color: #E5E5E5;
  color: var(--dark); font-size: var(--modalclose-size); line-height: var(--modalclose-line); cursor: pointer;
}
.popup-dialog .dialog .close:hover { background-color: #DDDDDD; }
@media (max-width: 600px) {
  .popup-dialog .dialog { padding: 44px 20px 20px; }
  .popup-dialog .dialog .close { right: 12px; top: 12px; }
}


/* PART 1. The recipe card's own header, which live prints and v3 did not.
 * `.rcat` is CookingHub's category above the card title -- a different
 * taxonomy from the hero pill's, so it is a second label and not a repeat.
 * `.rintro` is the card's prose, under the author and rating row. Both take
 * the card's own type rather than the theme's. */
.rcat { font-size: var(--rcat-size); font-weight: var(--rcat-weight); color: var(--red); margin-bottom: 8px; }
.rcat a { color: inherit; text-decoration: none; }
.rcat a:hover { text-decoration: underline; }
.rintro { margin-top: 18px; }
/* ROUND 76d: THE SAME REGRESSION, A THIRD TIME, AND THE FILE ALREADY SAYS
 * WHAT TO DO ABOUT IT.
 *
 * `.rintro` is prose inside `.rhead`, and `.rhead` is MAROON. This rule gave
 * it `var(--body)` -- the grey #4e5155 the page uses on its light ground --
 * so the card's intro rendered rgb(78,81,85) on rgb(81,1,14): 1.93:1, which
 * tools/contrast_39.py calls UNREADABLE and blocks a deploy over.
 *
 * `.rhead p{color:#fff}` twelve hundred lines above was already trying to
 * prevent exactly this. It loses because `.rintro p` has the SAME
 * specificity (one class, one type) and comes LATER in the file -- the same
 * shape of fault as the two the comment at `.rhead` records, where an
 * inherited white lost to a matching rule. The instruction written there is
 * "Everything in the maroon header states its colour", and this is that rule
 * being followed rather than a new decision.
 *
 * WHY NO ONE SAW IT: 169 of the 172 published recipes have no card intro at
 * all, so the declaration painted nothing. Three do -- BBQ Sauce, Pulled
 * pork and Sheet Kolach with Streusel -- and all three are grey on maroon on
 * the LIVE site today, measured on the old release as well as the new, so
 * this is a fault being fixed and not one being introduced. QUESTIONS.md,
 * ROUND 76d (d). */
.rintro p { font-size: var(--rintro-size); line-height: var(--rintro-line); color: #fff; margin-top: 12px; }
.rintro p:first-child { margin-top: 0; }

/* PART 1. "Best served with:" -- 31 recipes carry it and v3 showed none.
 * Live's own order: after the ingredient and tool boxes, before nutrition.
 * The cards take the v3 page's measurements, not the theme's. */
.bsw { padding: 0 var(--pad-r) 40px var(--pad-l); }
.bsw h3 { font-size: var(--boxhead-size); font-weight: var(--boxhead-weight); margin-bottom: 20px; }
.bsw-list { display: flex; gap: 24px; flex-wrap: wrap; }
.bsw-item { flex: 1 1 220px; min-width: 0; text-decoration: none; color: var(--dark); }
.bsw-img {
  display: block; width: 100%; aspect-ratio: 3 / 2;
  border-radius: var(--r-md); background: rgba(0,0,0,.04);
}
.bsw-item strong { display: block; margin-top: 10px; font-size: var(--bswitem-size); line-height: var(--bswitem-line); }
.bsw-item:hover strong { text-decoration: underline; }
.bsw-note { margin-top: 18px; font-size: var(--bswnote-size); line-height: var(--bswnote-line); color: var(--body); }
@media (max-width: 1023px) {
  .bsw { padding: 0 var(--pad-r) 30px var(--pad-l); }
  .bsw-list { gap: 16px; }
  .bsw-item { flex: 1 1 140px; }
}

/* PART 2, THE DIRECTIONS RULE, IN ONE PLACE.
 *
 * The section heading (1., 2., 3.) is the larger style. Everything below it
 * is this: every numbered step at every nesting level, the lines of a list
 * inside a step, and every note, warning and tip inside the directions. One
 * family, size, weight, colour and line height, and a second size below 1023
 * because that is a width, not a second role.
 *
 * The `.card.step` in the selector is what beats `.card p{font-size:15.5px}`
 * -- a step IS a card, so the article-card rule was reaching it. */
.ttc-v2 .card.step .sbody > p,
.ttc-v2 .card.step .slist > div,
.ttc-v2 .card.note > p {
  font-size: var(--step-size);
  line-height: var(--step-line);
  font-weight: var(--step-weight);
  color: var(--step-color);
  font-family: var(--font);
}
.ttc-v2 .card.step .sbody > p + p { margin-top: 22px; }
.ttc-v2 .card.note > p + p { margin-top: 14px; }
.ttc-v2 .card.note .note-label { margin-bottom: 0; }

/* the sub-heading is bold because the role says so, not because it
 * happens to be wrapped in <b> */
.li.sec b{font-weight:var(--ingsec-weight)}

/* ROUND 5, PART 1. A standalone photograph's caption is the caption role,
 * like every other photograph's. */
.pshot figcaption p { margin: 0; }

/* ROUND 5, PART 2. THE ARTICLE PHOTOGRAPH IS HALF THE BOX, AND THE SIDE
 * ALTERNATES DOWN THE PAGE.
 *
 * From Tom's grey-block diagram: the photograph fills the box's full height
 * and roughly half its width, and it is FLUSH to the box's own edge -- it
 * takes the box's rounded corner rather than sitting inset on it. So the
 * card loses its padding and the text column carries it instead.
 *
 * Only boxes that have a photograph take a turn. `app/recipe_v3.py` decides
 * whose turn it is, because an image-less box in the middle must not swap
 * the sides of the two around it.
 */
.card.shot-right, .card.shot-left {
  display: flex;
  align-items: stretch;
  padding: 0;
  overflow: hidden;
  gap: 0;
}
.card.shot-left { flex-direction: row-reverse; }
.card .card-txt { flex: 1 1 50%; min-width: 0; padding: 48px 50px; }
.card .card-shot { flex: 0 0 46%; min-width: 0; position: relative; }
.card .card-shot .ttc-stepshot {
  margin: 0;
  height: 100%;
  aspect-ratio: auto;
  border-radius: 0;
}
.card .card-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

@media (max-width: 1023px) {
  /* the photograph goes ABOVE its text, the full width of the box, and the
     alternation stops -- there is only one column to be on. */
  /* column-REVERSE, because the text column comes first in the markup and
     the photograph has to end up on top. `column` put it underneath. */
  .card.shot-right, .card.shot-left { flex-direction: column-reverse; }
  .card .card-shot { flex: 0 0 auto; width: 100%; }
  .card .card-shot .ttc-stepshot { aspect-ratio: 3 / 2; height: auto; }
  .card .card-txt { padding: 26px var(--pad-r) 26px var(--pad-l); }
}

/* ROUND 5, PART 4. THE VARIATIONS BLOCK.
 *
 * From 191734.png. A numbered section heading with a Completed checkbox
 * beneath it, then one full-width row per variation: the name in bold and
 * its bullets on the left, the photograph flush to the row's RIGHT edge and
 * filling the row's full height, about 40% of the width. The row tints
 * alternate grey, white, grey. The photograph does not alternate -- this
 * block is not the article.
 *
 * NOTE: the mockup shows a "Completed" checkbox and the brief calls it "the
 * same checkbox other sections have". No section on this site has one today,
 * on live or in v3 -- so this is the first, drawn from the mockup. Recorded
 * in QUESTIONS.md.
 */
.vars {
  background: var(--vars-tint-a);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-top: 30px;
}
.vars-head {
  background: var(--vars-tint-a);
  padding: 30px var(--pad-r) 22px var(--pad-l);
}
.vars-head h3 {
  font-size: var(--dirhead-size);
  font-weight: var(--dirhead-weight);
  line-height: var(--dirhead-line);
  color: var(--dark);
}
.vars-head h3 .num { margin-right: 14px; }

.vars-row { display: flex; align-items: stretch; background: var(--vars-tint-b); }
.vars-rows > .vars-row:nth-child(odd) { background: var(--vars-tint-a); }
.vars-txt {
  flex: 1 1 60%; min-width: 0;
  padding: 30px var(--pad-r) 30px var(--pad-l);
}
.vars-txt b { font-size: var(--step-size); line-height: var(--step-line); color: var(--dark); }
.vars-txt ul { margin-top: 12px; }
.vars-txt li {
  font-size: var(--step-size); line-height: var(--step-line);
  color: var(--step-color); margin-top: 4px;
}
.vars-shot { flex: 0 0 40%; min-width: 0; position: relative; }
.vars-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vars-row.no-shot .vars-txt { flex: 1 1 100%; }

@media (max-width: 1023px) {
  /* the photograph goes above the text at full width; the tints carry on */
  .vars-row { flex-direction: column-reverse; }
  .vars-shot { flex: 0 0 auto; width: 100%; aspect-ratio: 3 / 2; }
  .vars-head { padding: 24px var(--pad-r) 18px var(--pad-l); }
  .vars-txt { padding: 22px var(--pad-r) 24px var(--pad-l); }
}
/* the demonstration notice above a Variations block with no real data */
.vars-demo {
  margin: 30px var(--pad-r) -12px var(--pad-l);
  padding: 12px 16px; border-radius: 12px;
  background: #fff7ef; border: 1px solid var(--orange);
  font-size: var(--footnote-size); line-height: var(--footnote-line);
  color: var(--dark);
}

/* ROUND 5, PART 5. THE SAFE-TEMPERATURE BOXES.
 *
 * From the six mockups. A dark brown banner across the top in white; below
 * it the text on the left and the photograph on the right, flush to the
 * box's edge and filling its full height.
 *
 * The grey share button and the orange plus button in Tom's screenshots are
 * deliberately NOT here: the brief says to remove them. That is the one
 * place a screenshot and the brief disagree, and it is recorded in the
 * report and in audit/mockups/round5/WHAT-EACH-ONE-IS.md.
 */
.tbox {
  --tbox-brown: #4a2d06;
  background: var(--card);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-top: 30px;
}
.tbox-banner {
  background: var(--tbox-brown);
  color: #fff;
  padding: 20px var(--pad-r) 22px var(--pad-l);
  font-size: var(--step-size);
  line-height: 1.5;
  font-weight: 700;
}
.tbox-body { display: flex; align-items: stretch; }
.tbox-txt { flex: 1 1 50%; min-width: 0; padding: 30px var(--pad-r) 34px var(--pad-l); }
.tbox-shot { flex: 0 0 50%; min-width: 0; }
.tbox-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

.tbox-badge {
  display: inline-block;
  background: var(--tbox-brown); color: #fff;
  border-radius: 999px; padding: 8px 22px;
  font-size: var(--footnote-size); font-weight: 700;
  text-decoration: none;
}
.tbox h3 {
  margin-top: 22px;
  font-size: var(--dirhead-size); font-weight: var(--dirhead-weight);
  line-height: var(--dirhead-line); color: var(--dark);
}
.tbox-usda {
  margin-top: 16px;
  font-size: var(--step-size); line-height: var(--step-line); color: var(--step-color);
}
.tbox-label {
  margin-top: 18px;
  font-size: var(--step-size); line-height: var(--step-line);
  font-weight: 700; color: var(--dark);
}
.tbox-rows { list-style: none; margin-top: 6px; }
.tbox-rows li {
  font-size: var(--step-size); line-height: 1.7; color: var(--dark); font-weight: 700;
}
/* the temperature in red, the label in dark bold, any aside in grey regular */
.tbox-rows .t { color: #e01b1b; }
.tbox-rows .n { font-weight: 400; color: var(--body); }
/* ROUND 8, ITEM 5. BOLD, NOT BLACK.
 *
 * Tom read these rows as black rather than bold and he was right. Nothing in
 * the stylesheet asked for it: the row is `font-weight: 700` and the markup
 * puts the temperature and its label in `<b>`, and the browser's own rule for
 * `<b>` is `font-weight: bolder` -- which is RELATIVE. Bolder than 700 is
 * 900. So the two words a reader actually looks for were the only Montserrat
 * Black on the page, and the rest of the row was bold. Stated outright here.
 *
 * The other reading of "black" -- a pure #000 instead of the site's --dark --
 * was measured across all seven pages, both demonstration pages and all 173
 * recipes and does not occur anywhere; --dark is #1d2327 and the token is
 * what every piece of type on this page uses.
 *
 * Stated for the WHOLE page, which is what Tom asked for, because the cause
 * is not the temperature box: it is a relative keyword meeting a bold parent,
 * and that can happen anywhere. It already had -- a rustic bread article
 * heading is an h2 at 700 with a `<b>` inside it, and that `<b>` was 900 too.
 * Nothing on this page wants a weight above 700, so nothing above 700 is
 * allowed, and design/ttc_rules.py fails the page if one appears. Where a
 * `<b>` sits in ordinary 400 text this changes nothing: bolder than 400 is
 * 700 already. */
.ttc-v2 b,
.ttc-v2 strong { font-weight: 700; }

/* ROUND 8, ITEM 7. THE REVIEWS BOX'S CONTENT, BACK NEAR ITS EDGE.
 *
 * Tom measured, on the pizza page: both cards start at the same x; the host's
 * own "Comments" heading sits 60px in, the widget's "Reviews of ..." heading
 * 142px in. Where the 142 lives had to be found before anything was changed,
 * because the widget is a cross-origin frame from Tom's Desk and half of the
 * number is not ours to move. Measured at the frame's real widths:
 *
 *   HOST, ours          `.embed` padding-left. 56px at 1245 and up, a 4.5%
 *                       proportional value from 1024 to 1244 (52px at 1200,
 *                       48px at 1100, 44px at 1024), 22px below 1024. It is
 *                       the page's own --pad-l and it is the SAME 56px the
 *                       Comments card uses, so the host's share was never
 *                       the thing that was wrong.
 *
 *   INSIDE THE FRAME    the rest -- and it is not padding. `.rv-wrap` is
 *                       `max-width: 860px` and auto-centred, so its left
 *                       margin is (frame width - 860) / 2, on top of its own
 *                       24px (18px when the frame is 900px or less, 14px on
 *                       a phone). At the 1125px the host was handing it that
 *                       centring alone is 132.5px and the heading lands
 *                       156.5px inside the frame.
 *
 * So the host was making it worse by being generous: every extra pixel of
 * width given to the frame moved the widget's content half a pixel further
 * in. The widget's column can never exceed 860px, so width past that is
 * unused either way -- it was being spent as two 132px margins.
 *
 * The fix is host-side only: hand the frame the width its column actually
 * uses and put it against the left. The centring margin goes to zero, the
 * column stays 860px so nothing inside is squeezed or reflowed, and the
 * heading lands 18px inside the frame instead of 156.5px.
 *
 *   before  56 + 156.5 = 212.5px from the card's edge
 *   after   56 +  18   =  74px
 *
 * The remaining 18px is `.rv-wrap`'s own padding, inside the frame, and is
 * not ours to change. Nothing here reaches into the frame -- the Tom's Desk
 * builder is handling its side.
 *
 * The cap goes on the FRAME, not on the slot that holds it. The slot is
 * `#reviews > div`, which is one of the seven content edges tools/v3_align.py
 * lines up -- the reviews block is the one Tom could see out of place when
 * that tool was written -- so narrowing the slot would have fixed this fault
 * by reintroducing that one. The slot stays the full width of the card and
 * the frame inside it is the 860px its column uses, against the left.
 *
 * `!important` because the widget's script writes `width: 100%` inline on the
 * frame it injects, and an inline style beats a stylesheet rule. The frame
 * element is the host's -- it is in this document -- so this is still the
 * host's side of the line and nothing reaches into the frame's content. */
/* ROUND 17, JOB 17.2. THE WIDTH CAP IS GONE, BECAUSE THE DESK FIXED THEIR END.
 *
 * What stood here was a `width: 860px !important` on the frame and a long note
 * explaining that the widget took no width, no inset and no theming from the
 * host, that capping the frame was the whole of the fix available, and that the
 * remaining 18px belonged to Tom's Desk "until that side offers a way to set
 * it". That side has now offered it.
 *
 * Their instruction: the content column fills 100% of the iframe, so do NOT cap
 * the width -- give it the full width of its slot. The inset is set from the
 * mount div with `data-inset`, which `recipepage.reviews_embed` now passes as 0.
 *
 * So the frame simply fills its slot. Nothing here sizes it and nothing here
 * insets it; both are the widget's own, driven by the attribute we pass. */
#reviews > div[id^="tomreviews-"] iframe {
  width: 100%;
  max-width: 100%;
}
.tbox-more {
  display: inline-block; margin-top: 22px;
  background: var(--green); color: #fff;
  border-radius: 8px; padding: 13px 28px;
  font-size: var(--btnprint-size); font-weight: var(--btnprint-weight);
  text-decoration: none;
}
.tbox-more:hover { filter: brightness(1.06); }

@media (max-width: 1023px) {
  /* the photograph goes to the BOTTOM and is short: it is illustration, and
     the words are what someone standing at a hob needs first. */
  .tbox-body { flex-direction: column; }
  .tbox-shot { flex: 0 0 auto; width: 100%; max-height: 180px; }
  .tbox-shot img { max-height: 180px; }
  .tbox-banner { padding: 16px var(--pad-r) 18px var(--pad-l); }
  .tbox-txt { padding: 24px var(--pad-r) 28px var(--pad-l); }
}

/* ROUND 6, PART 2. A Note, Notes, warning or tip that is a top-level step's
 * own text lines up with the step NUMBERS below it, not with the section
 * heading above it. Measured before: heading 234, note 234, "2.1." 274. */
.ttc-v2 .card.step > .sbody.is-note { margin-left: var(--step-indent); }
@media (max-width: 1023px) {
  /* the sub-steps lose their indent at this width, so the note loses it too */
  .ttc-v2 .card.step > .sbody.is-note { margin-left: 0; }
}

/* ROUND 6, PART 4. The photograph pop-up, on the site's own .popup-dialog.
 * Only the inside of the dialog is new -- the overlay, the blur and the
 * hidden/shown behaviour are the component's own. */
.popup-dialog.ttc-lightbox .dialog.lb {
  width: min(1100px, calc(100% - 40px));
  background: none;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  /* ROUND 13, JOB 5. NO SCROLLBAR.
   *
   * The theme's `.dialog` is `overflow-y: auto`, and this one is 34px taller
   * than its box -- the picture is capped at 78vh and the caption sits under
   * it, which at a 900px window is 744 against 710. So a vertical scrollbar
   * with up and down arrows was drawn down the right edge of every
   * photograph, for 34px of slack that is mostly the caption's own padding.
   * It looked like a control and it was not one.
   *
   * `visible` rather than `hidden`: hidden would take the scrollbar away by
   * cutting the caption off instead, which is worse. The dialog is centred
   * in a full-screen overlay with room around it, so there is nothing for
   * the overflow to collide with. */
  overflow: visible;
}
.ttc-lightbox .dialog.lb figure {
  flex: 1 1 auto; min-width: 0;
  background: #fff; border-radius: 16px; overflow: hidden;
}
.ttc-lightbox .dialog.lb img {
  width: 100%; max-height: 78vh; object-fit: contain;
  display: block; background: #111;
}
.ttc-lightbox .dialog.lb figcaption {
  padding: 12px 18px 14px;
  font-size: var(--caption-size); line-height: var(--caption-line);
  color: var(--body);
}
.ttc-lightbox .lb-arrow {
  flex: 0 0 52px; width: 52px; height: 52px; border: 0; border-radius: 50%;
  background: #fff no-repeat center/16px auto; cursor: pointer;
}
.ttc-lightbox .lb-arrow.prev {
  background-image: url("/ttc-media/icons/arrow-left-white.svg");
  background-color: var(--orange);
}
.ttc-lightbox .lb-arrow.next {
  background-image: url("/ttc-media/icons/arrow-right-white.svg");
  background-color: var(--orange);
}
.ttc-lightbox .lb-arrow[hidden] { display: none; }
.ttc-lightbox .lb-count {
  position: absolute; left: 0; right: 0; bottom: -34px; text-align: center;
  color: #fff; font-size: var(--footnote-size);
}
.ttc-lightbox .dialog.lb .close { z-index: 2; }
@media (max-width: 1023px) {
  /* the arrows go under the picture on a phone, where a swipe is the real
     control and a 52px circle beside a full-width image has nowhere to sit */
  .popup-dialog.ttc-lightbox .dialog.lb { flex-wrap: wrap; justify-content: center; }
  .ttc-lightbox .dialog.lb figure { flex: 1 1 100%; order: -1; }
  .ttc-lightbox .lb-arrow { flex: 0 0 46px; width: 46px; height: 46px; }
  .ttc-lightbox .lb-count { position: static; margin-top: 8px; flex: 1 1 100%; }
}

/* ROUND 6, PART 7. A temperature box with no photograph.
 * Tom has not supplied replacements and the ones from his mockups are
 * CookingHub's, so the boxes ship text-only. The text takes the whole box
 * rather than half of it, at every width. When he adds a photograph on the
 * settings screen the box takes it with nothing else changing. */
.tbox-body:not(:has(.tbox-shot)) .tbox-txt { flex: 1 1 100%; }
.tbox-body.no-shot .tbox-txt { flex: 1 1 100%; }

/* The amount and unit inside step text, bold -- the same rule the ingredient
 * lists already use, so the two cannot look different. */
.ttc-v2 .card.step .sbody b,
.ttc-v2 .card.note b { color: var(--dark); }

/* ROUND 8, B3. A BULLET LIST IS INDENTED INSIDE ITS HEADING, NOT OUTSIDE IT.
 *
 * Measured under an "Important:" heading: the heading's left edge at x=72,
 * the bullet MARKER at x=49 -- 23px OUTSIDE it -- and the bullet text level
 * with the heading. The list hung out to the left of the block it belonged
 * to, because the `<ul>` had no padding of its own and a native marker is
 * drawn outside the content box.
 *
 * The marker now sits ONE STANDARD INDENT to the right of its heading: the
 * same `--step-indent` that separates a section heading from its step
 * numbers, not a new number.
 *
 * The marker is drawn by `::before` rather than left to `list-style`, for
 * one reason: a native outside-marker's distance from the text is the
 * browser's to choose and cannot be measured from script, so neither the
 * rule below nor ttc_rules could prove where it is. `•` is the same mark
 * the browser draws. Wrapped lines align under the TEXT, which is what the
 * padding on the `li` does.
 */
.ttc-v2 { --bullet-gap: 18px; }
.ttc-v2 .sbody ul,
.ttc-v2 .card.note ul,
.ttc-v2 .vars-txt ul,
.ttc-v2 .tbox-txt ul:not(.tbox-rows),
.ttc-v2 .box ul {
  list-style: none;
  margin: 8px 0 0;
  padding-left: var(--step-indent);
}
.ttc-v2 .sbody ul > li,
.ttc-v2 .card.note ul > li,
.ttc-v2 .vars-txt ul > li,
.ttc-v2 .tbox-txt ul:not(.tbox-rows) > li,
.ttc-v2 .box ul > li {
  position: relative;
  padding-left: var(--bullet-gap);
  list-style: none;
}
.ttc-v2 .sbody ul > li::before,
.ttc-v2 .card.note ul > li::before,
.ttc-v2 .vars-txt ul > li::before,
.ttc-v2 .tbox-txt ul:not(.tbox-rows) > li::before,
.ttc-v2 .box ul > li::before {
  content: '\2022';
  position: absolute;
  left: 0;
  top: 0;
  line-height: inherit;
}
@media (max-width: 1023px) {
  /* the sub-steps lose their indent at this width and so does the list, but
     it must still be INSIDE its heading rather than level with it */
  .ttc-v2 .sbody ul,
  .ttc-v2 .card.note ul,
  .ttc-v2 .vars-txt ul,
  .ttc-v2 .tbox-txt ul:not(.tbox-rows),
  .ttc-v2 .box ul { padding-left: var(--bullet-gap); }
}


/* ROUND 7, 2d. THE BOLD INTRO LINE UNDER A SECTION HEADING.
 *
 * "Let's start the 60-minute Pan Pizza by preparing the dough." sat in the
 * NUMBER GUTTER -- at the "1." column, not the "Dough:" column. It is the
 * top-level step's own text, which has no indent of its own, and nothing had
 * ever put it anywhere.
 *
 * It belongs in the heading TEXT column, one --num-gutter in from the card's
 * padding, which is where "Dough:" begins.
 *
 * SCOPE, deliberately narrow: the paragraphs of a top-level step's own body,
 * and nothing else. Not the step text, not the ingredient lines, not the
 * notes -- a note has its own rule from round 6 and keeps it -- and not the
 * photographs inside that body, which is why this is on the paragraphs
 * rather than on the block.
 */
.ttc-v2 .card.step > .sbody:not(.is-note) > p,
.ttc-v2 .card.step > .sbody:not(.is-note) > .slist,
/* ROUND 14, JOB 4. A CALLOUT INSIDE A STEP BODY IS STILL IN THE STEP BODY.
 *
 * "Do not pound the meat." on the chicken steak is a `<p>` inside
 * `div.recipe-important.inline-callout`, which is inside `.sbody` -- so it is
 * a GRANDCHILD, and the rule above only reaches direct children. It started
 * at 91px where the heading's text column starts at 132: one whole number
 * gutter to the left of the sentence above it, which is what Tom saw.
 *
 * Tom's locked rule is that notes indent INSIDE their heading, never outside,
 * so the callout's own paragraphs take the same gutter. The callout element
 * itself is not moved and nothing else about it changes. */
.ttc-v2 .card.step > .sbody:not(.is-note) > [class*="inline-callout"] > p,
.ttc-v2 .card.step > .sbody:not(.is-note) > [class*="inline-callout"] > .slist {
  margin-left: var(--num-gutter);
}
@media (max-width: 1023px) {
  /* the sub-steps lose their indent at this width; the heading's text column
     is still one gutter in, so this one stays */
  .ttc-v2 .card.step > .sbody:not(.is-note) > p,
  .ttc-v2 .card.step > .sbody:not(.is-note) > .slist {
    margin-left: var(--num-gutter);
  }
}

/* ROUND 7, 2f. THE VARIATIONS HEADING JOINS THE ALTERNATION.
 *
 * The heading and the first row were both the darker tint and ran unbroken
 * from one into the other, so the heading melted into the row beneath it.
 * The heading is the first band; the rows carry on from there, and no two
 * adjacent bands share a tint.
 *
 *   heading  --vars-tint-a      row 2  --vars-tint-a
 *   row 1    --vars-tint-b      row 3  --vars-tint-b
 */
.ttc-v2 .vars-rows > .vars-row { background: var(--vars-tint-b); }
.ttc-v2 .vars-rows > .vars-row:nth-child(even) { background: var(--vars-tint-a); }

/* ROUND 8, ITEM 1. THE VARIATIONS ROW'S PHOTOGRAPH IS A PHOTOGRAPH.
 *
 * It was the one layout Tom named that had no corner radius at all -- flush
 * to the row's right edge, square on all four corners. Every photograph on
 * the page is rounded on all four, so this one is too, at the same
 * `--shot-radius` the step photographs read. The tinted band shows at the
 * corners, which is what a rounded photograph on a tint looks like. */
/* ROUND 9: pinned to the value it had, NOT to --shot-radius. That token now
 * carries the radius measured off Tom's two mockups, and neither mockup shows
 * a Variations row. Borrowing the token would have moved an element nobody
 * measured, so it keeps its own number until there is a mockup to read. */
.ttc-v2 .vars-shot img { border-radius: 14px; }

/* ROUND 8, ITEM 4. THE ARTICLE STAYS READABLE WHATEVER ARRIVES.
 *
 * The cowboy steak's article rendered as six columns 50-90px wide, one word
 * to a line, 3,826px down the page. The cause was a truncated fragment --
 * fixed in app/recipe_v2._inner_text -- but a card with a photograph is a
 * flex row, so ANY stray block inside one becomes a column, and the next bad
 * fragment would do it again.
 *
 * So the row is told it may wrap, and a text column is given a floor: 320px,
 * which is about forty characters at the article's 15.5px and is the width
 * of the narrowest phone we support less its gutters. If it is readable
 * there it is readable here. `min()` keeps the floor from exceeding the
 * screen at 390, where there is one column anyway.
 *
 * MAXIMUM COLUMNS: two, ever -- the text and its photograph. Anything else
 * that finds its way inside takes a full row of its own.
 */
.ttc-v2 .card.shot-right,
.ttc-v2 .card.shot-left { flex-wrap: wrap; }
.ttc-v2 .card .card-txt { min-width: min(320px, 100%); }
.ttc-v2 .tips .card .card { flex: 1 1 100%; width: 100%; }

/* ROUND 37, JOB 37.1. THE SEAM, ONCE THE SITE IS BACK AROUND THE PAGE.
 * Restoring the header and footer put the theme's stylesheet on this document
 * for the first time, and the shot-join gate immediately failed at 1100 and
 * 1023 -- 234 seam pixels and 114 white ones in a corner. Round 33 recorded
 * that this gate's checks A and B "can no longer fail"; they could not, only
 * because the page had no site around it. With the site back they fail, and
 * they are right to.
 * The photograph carries the strip's own colour behind it, so a rounded
 * bottom corner can only ever reveal #f7f7f7, whatever the theme paints
 * underneath at any width. */
.ttc-v2 .pshot .pshot-img,
.ttc-v2 .pshot .pshot-img > a:first-child{background-color:#f7f7f7}

/* ROUND 65. THE CHROME IS NOT OURS TO STYLE.
 *
 * A block of rules lived here that rebuilt the furniture beside the
 * furniture: a two-column wrapper of our own, a 395px rail of our own, the
 * banner's 30px gaps copied out of header.css, and a `body:has(.ttc-v3-page)`
 * override that narrowed the site nav on this page only.
 *
 * Every one of them is deleted. recipe_v3.html now renders
 * `main.center > #main-left + #main-right`, the same structure
 * app/templates/page.html uses, so the header, the nav, the banners and the
 * newsletter rail are laid out by the theme's own style.css -- the one source
 * of truth -- and are automatically whatever the rest of the site says they
 * are. Nothing in this file may describe them again.
 *
 * What is left in this stylesheet is the RECIPE CONTENT and nothing else. */


/* ============================================================================
 * THE CARD HEADER'S SECOND LINE, KEYED TO THE COLUMN IT SITS IN.
 *
 * THE FAULT. `.rh2` is one flex row -- yield, flag, NEW, avatar, "Tom the
 * Chef", "Published:", rating, views -- and every rule that sized it asked
 * the VIEWPORT how much room there was. That was true while the recipe was
 * the width of the page. It stopped being true when the recipe moved into
 * the site's shared `#main-left`: at a 1440px viewport the column is 980px,
 * not 1440, so the CSS laid the row out for a width it does not have.
 * Measured on the candidate: `.ryield` wrapped to 44px over two lines,
 * `.rwho` broke "Tom the / Chef", and `.rate` split the stars from
 * "(write a review)" onto a second line 26px lower -- the text and the
 * symbols on four different levels.
 *
 * THE FIX IS TO ASK THE RIGHT BOX. `.rhead` is the container and these rules
 * read ITS inline size, which is the column. No viewport number can answer
 * this, because the same viewport gives different columns depending on
 * whether the rail is beside it: 1800px of window is a 1099px header, 1440px
 * of window is 837px, and 1100px -- where the rail is hidden -- is 921px.
 * A container query reads the box that actually exists.
 *
 * (The numbers below are CONTENT-box widths. `container-type: inline-size`
 * measures the content box, so 1219px of `.rhead` with its 56/64 padding
 * answers 1099. Found by measuring, after a first cut keyed to the border
 * box put the 1800px window in the wrong band.)
 *
 *   header content >= 1050   one row, full size -- the approved look
 *   900 - 1049               one row, tightened: 10px gaps, 34px symbols,
 *                            meta type down a point and a half
 *   800 - 899                one row, tighter: 8px gaps, 32px symbols
 *   below 800                the row wraps as a ROW, phrases kept whole --
 *                            the behaviour 9251a98 already had between 1024
 *                            and 1244, and never an overflow
 *
 * Inside `@media (min-width:1024px)` because at and below 1023 the header is
 * not a row at all: `.rh1,.rh2{display:contents}` stacks it, and that stack
 * is Tom's approved phone layout and is not this fix's business.
 * ========================================================================= */
@media (min-width: 1024px) {
  .ttc-v2 .rhead { container-type: inline-size; }

  @container (min-width: 800px) {
    .ttc-v2 .rh2 { flex-wrap: nowrap; }
    .ttc-v2 .rh2 .ryield,
    .ttc-v2 .rh2 .rwho,
    .ttc-v2 .rh2 .pub { white-space: nowrap; }
    .ttc-v2 .rh2 .rate { flex-wrap: nowrap; white-space: nowrap; }
  }

  @container (min-width: 900px) and (max-width: 1049px) {
    .ttc-v2 .rh2 { --rmetaline-size: 16.5px; --rpub-size: 13px;
                   --rrate-size: 14px; gap: 10px; }
    .ttc-v2 .rh2 .flag,
    .ttc-v2 .rh2 .newstar,
    .ttc-v2 .rh2 .avatar { width: 34px; height: 34px; flex: 0 0 34px; }
    .ttc-v2 .natnew { gap: 8px; }
  }

  @container (min-width: 800px) and (max-width: 899px) {
    .ttc-v2 .rh2 { --rmetaline-size: 16px; --rpub-size: 12.5px;
                   --rrate-size: 13.5px; gap: 8px; }
    .ttc-v2 .rh2 .flag,
    .ttc-v2 .rh2 .newstar,
    .ttc-v2 .rh2 .avatar { width: 32px; height: 32px; flex: 0 0 32px; }
    .ttc-v2 .natnew { gap: 7px; }
  }

  @container (max-width: 799px) {
    .ttc-v2 .rh2 { flex-wrap: wrap; row-gap: 10px; }
    .ttc-v2 .rh2 .ryield,
    .ttc-v2 .rh2 .rwho { white-space: nowrap; }
  }
}

/* ============================================================================
 * THE INGREDIENTS AND TOOLS BOXES, ALSO KEYED TO THE COLUMN.
 *
 * MEASURED, ON THE RELEASE THAT IS SERVING RIGHT NOW: at a 1440px viewport
 * the recipe card is 957px wide, its content box 837px -- and `.box.ing`
 * (`flex:0 0 729px`) plus the 32px gap plus `.box.too` (`flex:0 0 364px`)
 * ask for 1125px, because 729 + 32 + 364 = 1125 is exactly the content box of
 * the 1245px page `design/recipe-page-v3-FINAL.html` is drawn at. The Tools
 * box therefore ran 225px past the right edge of the column and was PAINTED
 * OVER THE NEWSLETTER RAIL. It is not a fault of 9251a98 -- at 1245px the
 * numbers fit to the pixel -- and it is not new to this change: the same
 * 224px of overflow is in 20260923-035422, which is live. It is what a
 * fixed-pixel layout does when the column it was drawn for is taken away.
 *
 * THE DESIGN'S PROPORTIONS, NOT ITS PIXELS. Below a 1245px card the two
 * boxes keep the ratio the drawing states -- 364/1125 = 32.36% for Tools,
 * 32/1125 = 2.84% for the gap, Ingredients taking the rest -- so at a 1219px
 * card Tools is 379px against the drawing's 364, and at 957px it is 279px.
 * At 1245px and above the original pixels apply, unchanged, and the drawing
 * is reproduced exactly.
 *
 * (The file already had this shape for 1024-1244px viewports, at 34.5%/2.6%.
 * Those rules stay; this one covers the case that band never saw -- a WIDE
 * viewport with a NARROW column, which only exists now that the rail is
 * beside the recipe.)
 * ========================================================================= */
@media (min-width: 1245px) {
  .ttc-v2 .rcard { container-type: inline-size; }

  @container (max-width: 1244px) {
    .ttc-v2 .boxes { gap: 2.84%; }
    .ttc-v2 .box.ing { flex: 1 1 auto; min-width: 0; }
    .ttc-v2 .box.too { flex: 0 0 32.36%; min-width: 0; }
    /* the sign keeps its label inside the narrower box */
    .ttc-v2 .box .sign { max-width: 100%; }
  }
}

/* ============================================================================
 * ROUND 69. THE DISCLAIMER BOX: THE STRAY SPACE UNDER IT, AND THE 30 BELOW.
 *
 * MEASURED at 1440, from the bottom of the policy iframe down: the slot's
 * reserved `min-height:1500px` stood 13px past the iframe's own 1487px; the
 * theme's `.text` padding added 38.4px (which is fine -- it is the same 38.4
 * that sits ABOVE the text); the block's `margin-bottom:23.04px` added
 * another 23 INSIDE a box that already pads itself 48px; and `.embed` closed
 * with its own 48px. 122px below the iframe, where the box's own padding is
 * 48 above and should be 48 below. Then the box's `margin-bottom:0` put the
 * next block hard against it -- the CookingHub banner touched the card.
 *
 * The reservation exists to stop the page jumping while the iframe loads, so
 * it stays until the iframe is there: `:has(iframe)` drops it the moment the
 * embed has its own height, and not before. The block margin inside the box
 * goes, because the box's padding is already the space it was asking for.
 * What is left below the iframe is 38.4 + 48 -- the same two paddings that
 * sit above it.
 *
 * Scoped to `.ttc-v2` and in this stylesheet, which only the recipe page
 * loads: no published page carries this embed at all (measured -- the policy
 * slot is not in the live recipe template).
 * ========================================================================= */
.ttc-v2 .ttc-embed-slot--policy:has(iframe) { min-height: 0; }
.ttc-v2 .embed .ttc-policy-embed { margin-bottom: 0; }
/* the 30 Tom asked for, between the end of this box and whatever follows.
 * The block after it is an advertising banner, which header.css already
 * starts 30px below its neighbour; stating it here as well cannot double it
 * -- adjacent margins collapse to the larger of the two -- and it holds if
 * something without a margin of its own is ever put there instead. */
.ttc-v2 .embed:has(.ttc-policy-embed) { margin-bottom: 30px; }


/* =========================================================================
   QUEUE 75, PHASE 1: THE GALLERY'S ARROWS.

   They were a flat orange disc with nothing behind them and no hover: on a
   pale photograph -- the salsa, the ranch dip -- the disc sat on cream and
   lost its edge, and a mouse got no answer at all from a control that is a
   button in everything but name (`role="button"`, `tabindex="0"`, and
   `recipe-v3-gallery.js` listens on it).

   A WHITE RING AND A SOFT SHADOW, so the disc reads as a button on any
   photograph without changing the site's own orange mark; a hover that
   darkens the orange, lifts the disc ~7% and deepens the shadow; and the
   same answer for the keyboard, which had none. The ring is drawn INSIDE the
   56px (`box-sizing:border-box` is set for everything in `.ttc-v2`), so the
   tap target does not change.

   This block sits at the end of the file because `.arrow.l`/`.arrow.r`
   further up set `background` as a SHORTHAND with the icon in it -- a
   `background-color` written before that line would be thrown away by it.
   ========================================================================= */
.arrow {
  border: 3px solid #fff;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .28);
  cursor: pointer;
  transition: background-color .18s ease, box-shadow .18s ease,
              transform .18s ease;
}
.arrow:hover,
.arrow:focus-visible {
  background-color: #d96c1e;            /* --orange #f18032, one step down */
  box-shadow: 0 6px 18px rgba(0, 0, 0, .36);
  transform: translateY(-50%) scale(1.07);   /* the -50% is the centring */
}
.arrow:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.arrow:active { transform: translateY(-50%) scale(.98); }

/* The strip's own two arrows sit on the card, not on a photograph, so they
   need no ring -- but they were just as dead to a mouse, and they are the
   same control. */
.sarrow {
  transition: background-color .18s ease, transform .18s ease,
              box-shadow .18s ease;
}
.sarrow:hover,
.sarrow:focus-visible {
  background-color: #d96c1e;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .22);
  transform: scale(1.07);
}
.sarrow:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; }

/* A PHONE. 56px of disc with 34px of inset ate a third of a 318px photograph,
   and two of them ate two thirds of the room a reader has to see the picture.
   48px is still well over the 44px a finger needs, and the inset comes in
   with it. `--gal-ratio` keeps the box landscape at this width too, so the
   arrows stay vertically centred on a shorter picture. */
@media (max-width: 600px) {
  .arrow { width: 48px; height: 48px; border-width: 2px; }
  .arrow.l { left: 14px; }
  .arrow.r { right: 14px; }
  .pinbtn { right: 14px; top: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .arrow, .sarrow { transition: none; }
  .arrow:hover, .arrow:focus-visible { transform: translateY(-50%); }
  .sarrow:hover, .sarrow:focus-visible { transform: none; }
}

/* =====================================================================
   ROUND 76, PHASE 2: ON A PHONE THE GALLERY IS SWIPED, NOT CLICKED.

   The two 48px discs are what a phone reader gets instead of the picture:
   measured on the candidate at 390, `.gal-main` is 318px wide and the two
   arrows plus their insets cover 124px of it -- 39% of the photograph, sat
   on top of the food. A phone has a better control for "next" than a button
   and it is the one every gallery on a phone already uses.

   A PHONE HERE IS <=600px, which is what this file has always called a phone:
   the block above ("A PHONE. 56px of disc with 34px of inset...") is the same
   query, and the arrows it shrinks are the arrows this one hides. Above 600
   nothing changes -- the discs are still there and still work.

   `touch-action: pan-y` is the one thing that has to be said out loud: it
   lets the page keep scrolling vertically under the finger while the gallery
   takes the horizontal drag. Without it the browser claims the gesture and
   the swipe never reaches the script.

   The slide is done by the script adding `.gal-slide-out-l/r` and
   `.gal-slide-in-l/r` to the photograph; the transitions live here, and a
   reader who has asked for less motion gets the same photograph with no
   movement at all (the block at the foot of this file).
   ===================================================================== */
@media (max-width: 600px) {
  .gal-main > .arrow { display: none; }
  .gal-main { touch-action: pan-y; }
}
.gal-main > .gal-img {
  transition: transform .18s ease, opacity .18s ease;
  will-change: transform;
}
.gal-main > .gal-img.gal-slide-out-l { transform: translateX(-28%); opacity: 0; }
.gal-main > .gal-img.gal-slide-out-r { transform: translateX(28%); opacity: 0; }
.gal-main > .gal-img.gal-slide-in-l { transform: translateX(28%); opacity: 0; transition: none; }
.gal-main > .gal-img.gal-slide-in-r { transform: translateX(-28%); opacity: 0; transition: none; }

@media (prefers-reduced-motion: reduce) {
  .gal-main > .gal-img,
  .gal-main > .gal-img.gal-slide-out-l,
  .gal-main > .gal-img.gal-slide-out-r,
  .gal-main > .gal-img.gal-slide-in-l,
  .gal-main > .gal-img.gal-slide-in-r {
    transition: none; transform: none; opacity: 1;
  }
}

/* THE "ESTIMATED" LABEL ON THE NUTRITION PANEL (24 September).
   Beside the heading, not under the figures: the note below the numbers has
   always said they are an estimate, but a reader who takes the first number
   and leaves never reaches it. `.nut-top` is already a wrapping flex row with
   a 16px gap, so this sits on the heading's baseline at any width and drops
   below it on a phone without a rule of its own. */
.nut-est{display:inline-block;padding:3px 10px;border-radius:999px;
  background:rgba(81,0,13,.08);color:var(--maroon);
  font-size:13px;font-weight:700;line-height:1.5;letter-spacing:.02em;
  text-transform:uppercase;white-space:nowrap}

/* ===================================================================
   THE HERO META ROW: A 45px FLOOR BETWEEN UNITS, AND A CLEAN REFLOW
   (MEGA round phase 4, 24 September)

   THE ROW HAS FOUR THINGS IN IT and three of them are one unit each:
   prep time, cook time and difficulty are an icon and a number that must
   never be separated, and the calories block is the fourth. The spacing
   rule is BETWEEN units and never inside one -- the 16px between an icon
   and its own number is set on `.mb-item` and is not touched here.

   WHAT WAS WRONG, MEASURED ON /recipes/60-min-pan-pizza/ AT TWELVE WIDTHS:

     1024-1244   the units were spaced by `margin-right:clamp(16px,4.2%,64px)`
                 and the clamp bottomed out: SIXTEEN PIXELS between units at
                 every width from 1024 to 1244. The floor asked for is 45.

     1245-1440   the calories block dropped below the times -- and was still
                 RIGHT-ALIGNED, hard against the edge, which is the cramped
                 lower line the brief says not to produce.

     >=1245      the units were fixed boxes, `flex:0 0 184px` and 200px for
                 the third, so the space between them was whatever was left
                 INSIDE each box: 67px after one, 109px after another. Even
                 spacing was not expressible.

   AND WHY THERE IS NO BREAKPOINT FOR THE REFLOW. The obvious fix is a media
   query at the width where the row stops fitting. There is no such width.
   Measured across ALL 172 published recipes at 1440: the ink in this row
   ranges from 281px (Bagel, which publishes no calories at all) to 887px
   (Pulled Pork -- "121 calories Per Serving (of 10)" beside three long
   times). Holding 45px gaps needs 1,022px of inner width for Pulled Pork and
   416px for Bagel. Any single breakpoint is wrong for most of the site.

   Worse, the width available here is not the viewport: a sidebar appears at
   1204 and the column collapses from 1120px to 778px in four pixels of
   window, then climbs back. A viewport number cannot describe that either.

   SO FLEXBOX DECIDES, and the centring falls out of it:

     .mb-times grows (`flex:1 1 auto`), so on a row that fits it eats the
     free space and the calories block ends flush right exactly as it does
     today -- `justify-content:center` on the bar has nothing to centre.

     When the calories block does not fit, it wraps to its own line, alone.
     Now there IS free space on that line, and the same `justify-content`
     centres it. One declaration, both states, and no width written down.

   THE FLOOR ITSELF is `clamp(45px, 4.4vw, 109px)`: 109 is the widest gap the
   old fixed boxes produced, so a big desktop looks as it did; 4.4vw shrinks
   it smoothly; 45px is the floor and the clamp cannot go under it.

   BELOW 1024 NOTHING HERE APPLIES, deliberately. The phone rules already do
   what this phase asks -- the calories block is `flex:1 0 100%` on its own
   centred line, measured at 0px off centre at 1023, 900, 768, 600, 480 and
   390 -- and a 45px floor is not achievable at 390px, where three units and
   two 45px gaps need 358px in a 346px column. QUESTIONS.md (f) records that.
   =================================================================== */
@media (min-width: 1024px) {
  .metabar {
    /* the one number this whole block exists for */
    --mb-gap: clamp(45px, 4.4vw, 109px);
    /* a wrapped calories line must not be clipped by the fixed 131px */
    height: auto;
    min-height: 131px;
    align-items: center;
    /* between the times group and the calories block */
    column-gap: var(--mb-gap);
    row-gap: 18px;
    /* only ever visible on a line the calories block has to itself */
    justify-content: center;
  }
  /* grows to fill a row that fits, so the calories block stays flush right
     and the centring above stays dormant */
  .mb-times { flex: 1 1 auto; gap: var(--mb-gap); }
  /* the units size to their own content: the GAP is the spacer now, not
     leftover room inside a fixed box, so every gap is the same */
  .mb-item,
  .metabar .mb-times .mb-item:nth-child(3) { flex: 0 0 auto; margin-right: 0; }
  /* shrink-to-fit, so it is centred as a block and not stretched */
  .mb-right { flex: 0 1 auto; margin-left: 0; }
}

/* ===================================================================
   ROUND 82, PHASE 1: THE HERO META ROW'S VERTICAL SPACING
   (24 September)

   THE SPACE ABOVE AND BELOW THIS ROW IS NOT THE SPACE THE RULE BOOK
   TALKS ABOUT. The site's general figure is 30px AROUND a block; this
   is INSIDE the hero's own white bar, between its edge and the one row
   it carries, and the round asks for 20 and 10. Nothing outside
   `.metabar` is touched by anything below.

   AND THE BAR'S TOP EDGE IS NOT WHERE THE ELEMENT STARTS. `.metabar`
   carries `margin-top:-40px`, so its first 40px sit behind `.hero`,
   which has `overflow:hidden` and paints over them. A reader sees the
   white begin 40px lower. Every number here is measured from the edge
   the reader sees, which is why the padding carries that 40 as a
   separate term (`--mb-tuck`) rather than folding it into the figure
   the round asked for.

   MEASURED BEFORE THIS BLOCK, on five recipes at nine widths:

     one line   30.5 above, 30.5 below      -- equal, but not 20
     stacked    5 above, 28 between, 5 below -- this is the tight bottom
                                               the round names on Pan
                                               Pizza, which stacks at
                                               1440 and 1244
     <1024      12 above, 10 between, 18 below

   THE TWO CASES CANNOT BOTH BE PADDING, because a stylesheet cannot ask
   whether a flex line wrapped. So the padding is the STACKED case (10
   above, 10 between via `row-gap`, 10 below) and the one-line case is
   reached with a `min-height` the single row cannot fill:

     min-height = 40 tuck + 20 + row + 20

   A row that fits is one line of `--mb-icon` and `align-content:center`
   puts the 20px of slack half above and half below it. A row that
   stacks is `icon + 10 + 23` of content and `icon + 93` tall, always
   above that floor by 13px, so the min-height is dormant and the 10s
   stand. One declaration, both cases, and no width written down.

   `--mb-icon` IS THE SAME VALUE `.ring` IS DRAWN AT and is set beside it
   below 1024, where the icons are a clamp; if one ever changes the
   other follows, and the floor cannot drift away from the row it is
   sized for.
   =================================================================== */
@media (min-width: 1024px) {
  .metabar {
    --mb-tuck: 40px;              /* hidden behind the hero above */
    --mb-icon: 30px;              /* `.ring` is 30x30 and sets the row */
    --mb-edge: 20px;              /* the one-line case, above AND below */
    --mb-tight: 10px;             /* the stacked case, and between rows */
    height: auto;
    padding-top: calc(var(--mb-tuck) + var(--mb-tight));
    padding-bottom: var(--mb-tight);
    row-gap: var(--mb-tight);
    align-content: center;
    min-height: calc(var(--mb-tuck) + var(--mb-edge) * 2 + var(--mb-icon));
  }
}

@media (max-width: 1023px) {
  .metabar {
    --mb-tuck: 40px;
    /* the same clamp `.ring` is drawn at, so the floor tracks the row */
    --mb-icon: clamp(26px, 7.2vw, 32px);
    --mb-edge: 20px;
    --mb-tight: 10px;
    height: auto;
    padding-top: calc(var(--mb-tuck) + var(--mb-tight));
    padding-bottom: var(--mb-tight);
    row-gap: var(--mb-tight);
    align-content: center;
    min-height: calc(var(--mb-tuck) + var(--mb-edge) * 2 + var(--mb-icon));
  }
}

/* ===================================================================
   ROUND 82, PHASE 2: WHEN THE CALORIES BLOCK DROPS, BOTH ROWS CENTRE
   (24 September)

   WHAT WAS WRONG, measured on 172 recipes at nine widths: when the
   calories block wrapped to its own line it WAS centred (round 81 put it
   there) and the times row above it was NOT -- 232px off centre on Pan
   Pizza at 1440, 116px on Pulled Pork. One row centred under another row
   jammed against the left edge is the thing this phase exists to stop.

   WHY IT HAPPENED. Round 81 gave `.mb-times` `flex:1 1 auto` so that on a
   row which fits it eats the free space and the calories block ends flush
   right, exactly as the mock-up has it. But a grown box is grown in BOTH
   states: when the calories block wraps, `.mb-times` still swells to the
   full line, so the bar's `justify-content:center` has nothing left to
   centre and the units sit at the box's left edge.

   AND A STYLESHEET CANNOT ASK WHETHER A FLEX LINE WRAPPED. There is no
   selector, no media feature and no container query for it. `.mb-times`
   is a box wider than its contents in BOTH cases -- that is exactly how
   round 81 pushed the calories block right -- so nothing about the box
   itself distinguishes them. Every arrangement of `flex-grow` and `auto`
   margins was tried and each one buys the stacked case by giving up the
   one-line case, or the other way round.

   SO THE ROW IS SIMPLY CENTRED, ALWAYS. `.mb-times` sizes to its contents
   and the bar centres whatever is on each line:

     stacked    the times row is centred and so is the calories block --
                which is what this phase was asked for
     one line   the four units are centred as one group, and all three
                gaps become exactly `--mb-gap` -- the 114px hole that used
                to sit between the difficulty mark and the calories was
                leftover room, not a designed figure, and round 81's whole
                point was that the space between units should be one
                number

   THIS IS THE ONE THING IN ROUND 82 THAT WAS NOT ASKED FOR: the one-line
   row no longer spans edge to edge. It is one declaration, reverting it
   is one word, the before-and-after pictures show it at 1600 and 1024,
   and QUESTIONS.md (a) records it with the alternative -- a few lines of
   script toggling a class on the bar -- for Tom to choose instead.

   THE 45px FLOOR IS UNAFFECTED: `--mb-gap` is untouched and is now the
   gap on BOTH sides of the join rather than on one.
   =================================================================== */
@media (min-width: 1024px) {
  /* sizes to its contents, so a line that holds only this row has free
     space for the bar's `justify-content:center` to share out */
  .mb-times { flex: 0 1 auto; }
}

/* ===== THE MEGA ROUND, ITEM 3: "Updated", and the date behind it =====
   A recipe that has never been updated renders exactly what it rendered
   before -- a `.h-pub` / `.pub` span with two spans in it -- and NOT ONE RULE
   BELOW MATCHES IT. Everything here is under `.has-orig`, which the macro
   only writes when there is an original publication date to show.

   TOM'S REVIEW: THE LINE SWAPS IN PLACE, it does not reveal a second line
   underneath. The two readings sit in ONE grid cell, so the box is as wide
   and as tall as the longer of them and NOTHING in the header can move when
   they trade places -- which is what the old reveal did wrong.

   THE BUTTON HAS TO LOOK LIKE THE TEXT IT REPLACED. It is a <button> because
   the swap must work by keyboard and on a phone, where there is no hover; so
   every one of the browser's button defaults is taken off and the type is
   inherited, leaving the same words in the same place with a dotted underline
   to say it can be asked a question. */
.has-orig .pubtoggle {
  -webkit-appearance: none; appearance: none;
  margin: 0; padding: 0; border: 0; background: none;
  font: inherit; color: inherit; line-height: inherit; text-align: inherit;
  cursor: help;
  border-bottom: 1px dotted currentColor;
  /* the two readings, stacked in one cell */
  display: inline-grid;
}
.has-orig .pubtoggle > span { grid-area: 1 / 1; white-space: nowrap; }
.has-orig .pubtoggle span { white-space: nowrap; }
.has-orig .pubtoggle:focus-visible { outline: 2px solid #51000d; outline-offset: 2px; }

/* `visibility`, NOT `display`: a hidden-but-laid-out element is what keeps
   the cell the width of the LONGER reading, so the swap cannot reflow the
   row it sits in. */
.has-orig .d-was { visibility: hidden; }
.has-orig .d-now { visibility: visible; }

/* HOVER IS THE CSS HALF and works before any script has run. `:not(.is-clicked)`
   is what stops a line that was just clicked back from appearing to do nothing
   while the pointer is still on it -- the handler adds that class on click and
   drops it when the pointer leaves. */
.has-orig:not(.is-clicked):hover .d-now,
.has-orig.is-swapped .d-now { visibility: hidden; }
.has-orig:not(.is-clicked):hover .d-was,
.has-orig.is-swapped .d-was { visibility: visible; }
