/* =====================================================================
   ROUND 86 -- THE ADVERTISEMENT BOX, AND THE TWO STICKY UNITS.

   Reached only through `.ttc-ad`, which app/adboxes.box() emits and which is
   emitted only when `ad_slots_live` is ON **and** the slot's own row says
   `enabled = 1`. Off, there is no element on the page and not one declaration
   in this file matches anything.

   THE NUMBERS BELOW WERE MEASURED ON THE CANDIDATE, six page types, eleven
   widths, before a line of this was written:

       width    #main-right (newsletter)   floating Comments/Share bar
       <=1200   display:none               RIGHT, 42-79.2px
       1201     x868    w303               RIGHT x1121.8..1201  <- ON the rail
       1280     x947    w303               RIGHT x1200.8..1280  <- ON the rail
       1366     x1021   w303               RIGHT x1286.8..1366  <- ON the rail
       >=1400   x1038   w303               LEFT  x0..79.2       <- rail clear

   ROUND 86b (d) CLOSED THE MIDDLE BAND. The bar's breakpoints now sit on the
   rail's own edge -- RIGHT to 1200, LEFT from 1201 -- so the sign-up and the
   bar are never on the same side. recipe-v3-floating-bar.css carries the rule
   and the measurement; this file no longer has to work around it.

   Two things follow and both are load-bearing:
     1. the sticky sidebar is >=1201 ONLY -- the width at which the rail
        itself appears. ROUND 94 moved it there from 1400. 86b's note said the
        1201..1399 band was already clear of the floating bar and that the unit
        could run from 1201, and left the number alone because nothing had asked
        it to move; round 94's review asked, because at 1280 and 1366 the unit
        was in the markup and invisible. See the long note above the rule.
     2. the sidebar lives INSIDE the rail, under the newsletter box, at the
        rail's own width. It cannot narrow the rail or overlap it, because it
        is a block in the same column rather than anything positioned.
   ===================================================================== */

/* ---- ROUND 96: NOTHING UNTIL IT FILLS, AND THAT IS THE WHOLE FILE NOW ----
   ROUND 90 RESERVED THE HEIGHT AND THEN GAVE IT BACK. `--adh-d` / `--adh-m`
   became a min-height, the cream frame and the ADVERTISEMENT label were drawn
   at once, and `.is-empty` took all three away when Google answered
   "unfilled". That is right for a site whose slots fill. This account is not
   approved, every slot answers unfilled, and what a reader got was an empty
   labelled box on every slot of every page for about a second -- plus a 274px
   jump above the fold when the eager top slot handed its 250px back.

   SO THE DEFAULT STATE OF `.ttc-ad` IS NOW NOTHING AT ALL: no background, no
   border, no padding, no margin, no min-height, no label. It is a full-width
   block of zero height. `.is-filled` is what paints it, and `.is-filled` goes
   on only when that slot has a real ad -- written into the HTML by
   app/adboxes.box() for a house creative or the staging stub, and added by
   static/site/ad-slots.js when an AdSense unit reports data-ad-status="filled".

   WHAT IS **NOT** CONDITIONAL, AND MUST NEVER BE: the <ins> is in the markup
   and at the full width of its column in EVERY state, and the script pushes it
   in every state. `display: none`, `visibility: hidden` and `width: 0` on a
   unit that has not requested yet are all forbidden here -- a hidden or
   zero-width unit can never fill, and Google's site review has to see the
   units actively requesting. Which is why the rules below never hide the
   wrapper to make it empty; being empty is simply what it already is, and
   the only thing any of them switches is PAINT.

   THE TWO BREAKPOINT SLOTS ARE THE ONE EXCEPTION AND IT IS NOT A NEW ONE.
   `.ttc-ad--sidebar` is `display: none` below 1201 and `.ttc-ad--mobile-sticky`
   above 767, because below/above those widths the rail and the bottom bar do
   not exist to hold a unit at all. That predates this round, it is per
   breakpoint rather than per state, and both units still report a status at
   the width where they are shown. Nothing here widens it. */
/* ---- ROUND 98: THE SLOT IS ARMED AT ITS CONFIGURED HEIGHT, ONE SCREEN
   BEFORE THE READER GETS THERE, AND THAT IS WHAT BUYS THE REQUEST BACK ----

   READ THE ROUND 97 BLOCK BELOW FIRST: it is the measurement that produced
   this design, and it ends in a conflict this block resolves.

   WHERE 97 GOT TO. A zero-height wrapper removes the flash and removes the
   dead space and is measured doing both -- and at 390 it STOPS THE PHONE AD
   BEING REQUESTED. Alternating both builds under the same live origin through
   the same proxy, ninety-second windows (tools/q97_ask.py, tools/q97_dwell.py):

       at 390, recipe                     units answered     /pagead/ads
       pristine HEAD (round 96, no clip)  5 . 5 . 5 . 5      6 . 6 . 6 . 6
       round 97, fast scroll              3 . 3 . 2 . 2      4 . 4 . 3 . 3
       round 97, reader DWELLS 4s a slot  1 . 1 . 1          2 . 2 . 2

   Worse the longer a reader stops at the slot, which is the case that matters.
   Ruled out by measurement rather than argument: not our lazy loader
   (`data-adsbygoogle-status` reads `done` on every unit on both builds,
   including all four that go silent -- we push, Google processes, Google
   declines to fetch); not a clipping ancestor (a probe walking every ancestor
   finds none); not a height threshold (`1px` instead of `0fr` changes nothing);
   not latency (ninety seconds, and the survivors answer FASTER than the
   control). AdSense's own viewport deferral reads something about a slot that a
   zero-height CONTAINING BLOCK defeats, and CSS cannot reach it without giving
   the container real layout height.

   SO THE CONTAINER GETS REAL LAYOUT HEIGHT -- FOR THE SECONDS AROUND THE
   REQUEST, AND INVISIBLY. Three states, one row track, and the row is the only
   thing that ever changes:

       COLLAPSED   grid-template-rows: 0px    the resting state of every slot.
                   Zero height, nothing painted, and the <ins> at the full
                   width of its column, overflowing, unclipped, requestable.
       ARMED       grid-template-rows: var(--adh-m/--adh-d)
                   the slot's CONFIGURED height, from the row in `ad_slots`:
                   250px in content, 600px in the rail, 50px on the phone bar.
                   STILL NOTHING PAINTED -- no background, no border colour, no
                   label, no margins. It is empty space, not an empty box.
       FILLED      grid-template-rows: 1fr + overflow: hidden
                   the ad's OWN height, whatever Google sent.

   WHEN IT IS ARMED IS THE WHOLE DESIGN. static/site/ad-slots.js arms a slot
   when an IntersectionObserver with `rootMargin: 100%` says the reader is
   within ONE SCREEN of it, and pushes it in the same breath. So the space
   appears a full screen before the reader can see it, the answer arrives while
   they are still scrolling towards it, and by the time the slot is on their
   screen it is either an advertisement or nothing at all. The ONE slot that is
   above the fold is armed by the SERVER instead (`is-armed` in the HTML -- see
   app/adboxes.box()), because for that one the moment is page load and a class
   added a frame after first paint is a 250px jump under the header.

   WHAT THE READER GETS, AND WHY IT IS BETTER THAN EITHER ROUND BEFORE IT:
     - no empty BOX, ever: the chrome still waits for `is-filled`, exactly as
       round 96 and 97 left it. Armed is invisible.
     - no PERMANENT dead space: `unfilled` collapses the row back to 0px, and
       the round 96 unit that drew something and never said so is clipped too
       (`is-orphan`, QUESTIONS 97 (k)). Nothing holds space for longer than the
       few seconds between the push and the answer.
     - no flash: 0 -> configured -> 0 happens a screen away from the reader's
       eye, and above the fold the reserve is in the first paint rather than
       arriving after it.

   AND THE COLLAPSED ROW IS `0px` AND NOT ROUND 97's `0fr`, WHICH IS A SMALL
   CHANGE THAT BUYS TWO THINGS. `0px` and `var(--adh)` are both LENGTHS, so the
   transition between them INTERPOLATES -- the arm and the collapse are smooth
   200ms travels rather than jumps, which matters for the top slot, the one
   collapse a reader can actually be looking at. And a fixed-length track has no
   `minmax(auto, ...)` minimum, so round 97's bug #2 cannot come back: an item's
   margins and min-height can no longer feed the track and reinflate a slot that
   is supposed to be at zero (QUESTIONS 97 (e) #2, 44 failures out of 54).
   `1fr` survives for the FILLED state only, where the height is the ad's own
   and is not ours to know. */
/* ---- ROUND 97: ZERO HEIGHT FROM THE FIRST PAINT, AND THE RESERVE THAT
   WAS NEVER OURS ---------------------------------------------------------

   ROUND 96 TOOK OUR PAINT AWAY AND THE SPACE STAYED. Measured on live at
   1440 and 390, home and recipe, sampled every animation frame from
   `document_start` (tools/q97_stepa.py), the computed `min-height` of every
   `.ttc-ad` on the page was `0px` in every single frame -- so no reserve of
   OURS existed to remove. And yet:

     page @ width     slot            reserve   appears    collapses   visible
     home  @1440      home_top         280px     4982ms      7933ms     2951ms
     home  @ 390      home_top         390px     1041ms      2479ms     1438ms
     recipe@1440      recipe_top       280px     1180ms      2510ms     1330ms
     recipe@ 390      recipe_top       390px      942ms      2189ms     1248ms
     recipe@1440      sidebar_sticky   250px     1180ms      2611ms     1430ms

   THE RESERVE IS GOOGLE'S, NOT OURS, AND THE SAMPLES NAME THE CULPRIT. In
   every row the wrapper's height was EXACTLY the <ins>'s own height, and both
   went from 0 to their full value in the same frame that
   `data-adsbygoogle-status="done"` appeared on the <ins> -- the loader sizes a
   responsive unit WHEN IT ASKS, before it knows the answer. Our wrapper was
   `height: auto`, so it simply grew with the child. First paint is 344-844ms,
   so the box appears AFTER the reader is already looking at the page and
   disappears 1.2-3.0s later: an appear-then-vanish, which is the worst kind of
   shift there is.

   AND THE FLASH WAS THE MILD HALF. 11 of the 22 slot-measurements never got an
   answer inside a 12-second window at all -- `data-ad-status` still absent --
   and held 280px or 390px of dead space for the whole of it. See ROUND-97.

   SO THE WRAPPER NO LONGER GROWS WITH ITS CHILD. `.ttc-ad` is a one-row grid
   whose row is `0fr`, and the branded box is now an INNER element
   (`.ttc-ad__box`) inside that row. Whatever height Google puts on the <ins>
   overflows a zero-height track and takes no space from anything. There is
   nothing to collapse later because nothing was ever taken.

   WHY A GRID ROW AND NOT `height: 0`. `height: 0` -> `height: auto` does not
   animate, and the job asks for a smooth expand to the AD'S OWN height, which
   is a number we do not know and must not guess. `0fr` -> `1fr` interpolates,
   and `1fr` in an auto-height container resolves to exactly the content's
   height -- so the box expands to the ad Google actually sent, at any size, and
   goes on being right when the unit refreshes itself into a different one. No
   measurement, no JS-written pixel value, nothing to get out of step.

   AND `overflow: hidden` IS **NOT** ON THE UNFILLED STATE, WHICH IS THE MOST
   IMPORTANT LINE IN THIS FILE. It was, for most of this round, as belt to the
   `0fr` track -- and it cost the site its ad requests. MEASURED, both builds
   alternately under the same live origin through the same proxy, ninety
   seconds each (tools/q97_ask.py), on a recipe at 1440:

       pristine HEAD (no clip)   6 of 7 units answered, ALL within 6.2s
       clipped with overflow     3 of 7 units answered, in NINETY SECONDS
                                 /pagead/ads: 7 against 4

   NINETY SECONDS RULES OUT "LATE", and the three that did answer on the
   clipped build answered FASTER than the control (2698ms against 3564ms) -- so
   it is not slowness anywhere. The units that went silent were exactly the
   ones below the fold.

   THE MECHANISM, AND IT IS NOT SUBTLE ONCE SEEN: AdSense defers a below-the-
   fold unit until it approaches the viewport, and an element wholly clipped by
   an ancestor's `overflow: hidden` has an INTERSECTION RATIO OF ZERO -- clipping
   by an ancestor is part of how intersection is computed. The unit was
   processed (`data-adsbygoogle-status="done"` on both builds, identically) and
   then never asked, because by the only measure Google uses it never came into
   view. That is risk (a) of this job, found by measuring it.

   A `0fr` TRACK ALONE ALREADY TAKES NO HEIGHT. The container is zero tall and
   contributes nothing to layout whether or not the overflow is hidden; the
   only difference is whether the content that hangs out of it is PAINTED. An
   unfilled <ins> paints nothing and every one of our painted properties waits
   for `is-filled`, so there is nothing to see either way -- and with the
   overflow visible the unit has a real rect in a real viewport and asks for
   its ad like any other. `overflow: hidden` comes back ON with `is-filled`,
   where it is needed to make the expand a clean wipe and to hold the phone
   bar's `max-height` cap.

   WHAT IS STILL FORBIDDEN HERE, AND ROUND 96 WROTE THE RULE: `display: none`,
   `visibility: hidden` and `width: 0` on a unit that has not requested yet. A
   hidden or zero-WIDTH unit can never fill and stalls Google's site review.
   The <ins> keeps the full width of its column in every state, which is the
   axis a responsive unit measures. Proven in a real browser rather than
   asserted -- tools/q97_ask.py counts the answers on both builds.

   AND NO `opacity` ON THIS ELEMENT. An ancestor at `opacity: 0` is a hidden
   unit to any viewability heuristic, and an unfilled slot sits in its resting
   state for the life of the page. The fade is an ANIMATION on the inner box
   that exists only once `data-ad-reveal` is set -- see `ttc-ad-reveal` below --
   so a unit that has not filled has no opacity applied to it at any moment. */
.ttc-ad {
  display: grid;
  /* THE RESTING STATE. Nothing below it takes any height -- and it is a LENGTH
     so that the arm and the collapse can interpolate. See ROUND 98 above. */
  grid-template-rows: 0px;
  width: 100%;
  max-width: 100%;          /* never wider than the column it is in */
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  box-sizing: border-box;
  /* NOT `hidden` -- see the long note above. A clipped unit never comes into
     view as far as AdSense is concerned and is never asked for. */
  overflow: visible;
  transition: grid-template-rows 200ms ease;
}
/* REQUIRED, NOT COSMETIC. A grid item's `min-height` is `auto`, which refuses
   to shrink below its own content -- and round 97 measured what that costs with
   a `0fr` track: the row is as tall as the ad inside it and this whole file does
   nothing. ROUND 98's resting track is a fixed `0px`, which a content-based
   minimum cannot grow, so the rule is belt rather than braces now -- and it is
   kept, because it is also what stops a tall creative raising the FILLED row's
   own minimum past the height Google actually sent. */
.ttc-ad > * { min-height: 0; }

/* ---- AND AN INVISIBLE UNIT MUST NOT BE IN THE READER'S WAY -------------
   THIS IS THE BILL FOR `overflow: visible`, AND IT HAS TO BE PAID. A slot that
   has not filled is a zero-height box with a real, full-size <ins> hanging out
   of it -- Google sizes that <ins> to 280px at 1440 when it asks, before it
   knows the answer. Nothing is PAINTED there, so the reader sees the article
   underneath exactly as before. But an element with no background still
   RECEIVES POINTER EVENTS inside its box, so without this rule every unfilled
   slot on the page would put a few hundred pixels of invisible, unclickable
   glass over the text below it: links that do not open, words that do not
   select, taps that go nowhere. An invisible thing in the way is worse than a
   visible one, because nobody can see why it is happening.

   SO THE BOX IS TRANSPARENT TO THE POINTER UNTIL IT HAS SOMETHING IN IT.
   `is-filled` is the one state where the element occupies its own space in the
   flow and is carrying a real advertisement -- an ad a reader must be able to
   click -- and it takes the pointer back. `is-orphan` does not need to: it is
   clipped to nothing, so there is no box over anything. Pointer events have no
   bearing on whether AdSense considers a unit viewable, so this costs the
   request nothing. */
.ttc-ad:not(.is-filled) .ttc-ad__box { pointer-events: none; }

/* A reader who has asked for less motion gets the end states and no travel.
   The slot is still zero when unfilled and still full height when filled. */
@media (prefers-reduced-motion: reduce) {
  .ttc-ad { transition: none; }
}

/* ---- ROUND 98: ARMED. THE CONFIGURED HEIGHT, AND NOT ONE PAINTED PIXEL --
   This is the state that buys the phone ad request back, and it is the only
   thing round 98 adds to the stylesheet.

   `--adh-m` / `--adh-d` are the slot's own configured height, written as an
   inline custom property by app/adboxes.box() from the `max_h_mobile` /
   `max_h_desktop` columns of its row in `ad_slots`: 250px on every in-content
   slot of all five page types, 600px on the desktop rail, 50px on the phone
   bar. THE SAME TWO NUMBERS THE FILLED BOX USES AS ITS FLOOR, so the space
   that is reserved is the space an ad will take and the page does not move
   when one lands.

   ON THE TRACK, NOT ON THE CONTENT, AND THAT IS DELIBERATE. A fixed-length
   track is exactly the configured height and NOTHING can push it further --
   not the 280px/390px Google writes on the <ins> the moment it asks, not a
   margin, not a min-height. Google's own reserve overflows an armed slot the
   same way it overflows a collapsed one; what the reader's layout sees is our
   number, at both widths, in every state.

   `:not(.is-filled):not(.is-orphan)` RATHER THAN SOURCE ORDER. Both of those
   are two-class selectors like this one, and one of them lives inside a
   `@media (min-width: 768px)` block further down -- so "whichever comes last"
   would depend on the width. Stated as exclusions, the three states cannot
   overlap at any width, whatever order a future edit puts them in.

   AND IT PAINTS NOTHING. Every painted property still hangs off `is-filled`
   (background, border colour, the vertical padding, the margins) and the label
   is still inserted by the script on fill. An armed slot is empty SPACE, a
   screen away from the reader, for the two or three seconds between the push
   and Google's answer. It is not an empty box, and the difference is the whole
   reason round 90's reserve was wrong and this one is not. */
.ttc-ad.is-armed:not(.is-filled):not(.is-orphan) {
  grid-template-rows: var(--adh-m, 0px);
}
@media (min-width: 768px) {
  .ttc-ad.is-armed:not(.is-filled):not(.is-orphan) {
    grid-template-rows: var(--adh-d, 0px);
  }
}

/* ---- THE EXPAND ------------------------------------------------------
   `overflow: hidden` arrives WITH the fill and not before it: it is what makes
   the open a wipe rather than an ad standing at full height while a gap grows
   underneath it, and it is what holds the phone bar inside its `max-height`
   cap. Before the fill it is the thing that must not be here.

   ROUND 98: AND THE HEIGHT IT TRAVELS IS NOW SHORT, BECAUSE THE SPACE IS
   ALREADY THERE. A filled slot was armed a screen ago at 250px, so the row
   goes from 250px to the ad's own height -- a few tens of pixels -- and what
   the reader actually sees appearing is the branded box fading up over 200ms
   (`ttc-ad-reveal`, below). Round 97 needed the row itself to travel 0 -> 311px
   because nothing was reserved; nothing needs to travel that far now, and the
   one number a reader would notice moving -- the text under the slot -- does
   not move at all. */
.ttc-ad.is-filled { grid-template-rows: 1fr; overflow: hidden; }

/* ---- AND THE ONE UNIT THAT IS NEITHER, WHICH REVERSES A ROUND 96 CALL --
   `is-orphan` is the outcome round 96 named and deliberately left alone:
   EMPTY_MS has passed, `data-ad-status` never arrived, and the unit HAS drawn
   something. 96's reasoning was that such a unit might be carrying a real ad,
   so hiding it would hide an impression a reader should have seen -- and it
   left the element at its natural height, unbranded.

   STEP A MEASURED WHAT THAT COSTS AND IT IS NOT HYPOTHETICAL. On the live site,
   ELEVEN of twenty-two slot-measurements ended a twelve-second window in
   exactly this state -- `data-ad-status` absent, an <ins> Google had sized,
   nothing to show -- each holding 280px or 390px of empty space. That is the
   dead space this round exists to remove, and it is the majority of the fault,
   not an edge of it.

   AND NONE OF THEM WAS AN AD. The account is not approved; the site serves no
   advertising at all today. Every one of those units was a PENDING unit with a
   blank frame in it, which is the case `drewSomething()` cannot tell from a
   real fill, because an AdSense iframe is cross-origin and sized the same way
   either way. Round 96 chose the branch that protects the rare case; measured,
   the rare case has never once occurred and the common case is on every page.

   SO IT IS CLIPPED, NOT RESTORED. Zero height, nothing painted, nothing in the
   reader's way. THE REQUEST HAS LONG SINCE GONE OUT by this point -- four
   seconds after the push -- so clipping here costs nothing that the note above
   is about. And it is `overflow: hidden` rather than `display: none` on
   purpose: the element stays rendered and the observer in ad-slots.js stays
   armed, so if an answer does eventually arrive the unit is revived and
   expands properly. Round 96's worry is answered by the revival, not by
   holding space open for it. */
/* ROUND 98 CHANGED TWO THINGS HERE AND THE SECOND ONE IS NOT COSMETIC.
   `0px` for the same reason the resting state is one: it interpolates, so the
   row travels rather than snapping. And `overflow: hidden` IS GONE, which is
   round 97 (j)'s own finding applied to the one state 97 left it on. An element
   wholly clipped by an ancestor's `overflow: hidden` has an intersection ratio
   of zero, and this unit has NOT had an answer -- Google processed its tag and
   may still fetch it. 97's note argued the clip was safe here because "the
   request went out four seconds earlier"; the request going out is not the same
   thing as Google deciding to fetch, which is the whole lesson of 97 (l). The
   `0px` track already takes the height, nothing here paints, and the box is
   transparent to the pointer -- so the overflow was buying nothing and costing
   a possible fill.

   WHICH LEAVES `is-orphan` AS A LABEL RATHER THAN A MECHANISM, and that is
   honest: it renders exactly as the resting state does. It stays because it is
   the one thing in the DOM that says "this unit drew something and never
   reported a status", which is what a gate and the next round read. */
.ttc-ad.is-orphan:not(.is-filled) { grid-template-rows: 0px; }

/* ---- AND THIS IS THE BRANDED BOX, WHICH ONLY A REAL AD BRINGS OUT ------
   ROUND 97 MOVED IT OFF `.ttc-ad` AND INTO `.ttc-ad__box`, ONE LEVEL IN, and
   the move is structural rather than cosmetic. `.ttc-ad` is the CLIP now, and
   a clip's own padding, border and margin sit OUTSIDE its row track -- they
   would go on taking space from the reader on a slot that never fills, which
   is the single thing this round exists to stop. Everything that paints is
   therefore inside the track, where `0fr` can take it to nothing:

       .ttc-ad          the clip        structure only, never paints
         .ttc-ad__box   the box         every painted property, the margins
           .ttc-ad__label               inserted on fill by ad-slots.js
           .ttc-ad__in                  the creative, and the height FLOOR
             ins.adsbygoogle            FULL WIDTH IN EVERY STATE

   THREE LEVELS AND EACH ONE EARNS ITS PLACE: the clip cannot paint (its own
   padding would sit outside the track), the box cannot hold the floor (a grid
   item's `min-height` feeds the track's minimum and turns the expand into a
   jump), so the floor goes to the content. Both of those are measurements
   below, not preferences.

   That split is what makes this a block another site can lift whole: the outer
   element is a general-purpose "collapse until it has something" wrapper with
   no opinions, and every opinion about how an advertisement looks is in the
   inner one.

   `--adh-d` / `--adh-m` stay a FLOOR for the filled box and are read in no
   other state, exactly as round 96 left them -- on `.ttc-ad__in`, for the
   reason given where they are set.

   THE ROUND 96 SPECIFICITY TRAP IS STRUCTURALLY GONE, and this is the note it
   replaces. 96 had `.ttc-ad.is-filled { display: flex }` -- two classes, which
   out-specified the one-class `display: none` on the breakpoint slots, so a
   filled `mobile_sticky` rendered 1440x302 IN THE DESKTOP PAGE FLOW. It was
   fixed there by making every hide two classes and every breakpoint's filled
   rule three, which works and has to be remembered forever. It does not have
   to be remembered any more: FILLING NO LONGER TOUCHES `display` AT ALL. The
   only thing `.is-filled` changes is the row track, so a slot that is
   `display: none` at a width stays `display: none` at that width whatever
   Google sends it. A SLOT THAT DOES NOT EXIST AT A WIDTH CANNOT BE BROUGHT
   INTO EXISTENCE BY FILLING -- now by construction rather than by arithmetic. */
/* THE GEOMETRY IS SPLIT ALONG THE AXIS, AND THE SPLIT WAS MEASURED TWICE
   BEFORE IT SETTLED. Round 96 hung the padding and the border off
   `.is-filled` along with the background, which was right when the wrapper was
   the box. It is wrong now, and the verifier caught it: a responsive unit is
   sized by Google AT PUSH TIME, from the width available to it THEN -- and
   then, the box had no padding and no border, so Google measured 956px in a
   956px column. The instant the slot filled, 12px of padding and 1px of border
   appeared on each side, the content box became 934px, and the 956px creative
   Google had already sent was 22px too wide for it -- sitting 13px in from the
   left edge and running 13px past the right one. Measured at 1440 on
   home_bottom and three recipe slots: `clipR: 13` -- thirteen pixels of a paid
   advertisement cut off down its right-hand edge by the very `overflow: hidden`
   that makes this round work. After the fix the same probe reads
   `clipL: -13, clipR: -13`: inside the box on both sides, symmetrically.

   SO THE HORIZONTAL GEOMETRY IS UNCONDITIONAL AND THE VERTICAL GEOMETRY IS
   NOT, AND THE SPLIT IS EXACTLY ALONG THE AXIS THAT MATTERS. Only the
   left/right padding and the left/right border decide how wide the <ins> is,
   and those now exist in both states, so Google measures the width it will be
   given. Everything that adds HEIGHT -- top/bottom padding, top/bottom border,
   the margins and the floor -- still waits for `is-filled`.

   THAT IS NOT TIDINESS, IT IS THE CLIP. The first version of this made all of
   it unconditional on the reasoning that the track would swallow it, and the
   verifier failed 44 of 54 measurements: a `0fr` track is `minmax(auto, 0fr)`,
   and an item's MARGINS and its `min-height` both feed that auto minimum,
   which a `min-height: 0` on the item cannot reach. Measured: 274px on every
   top slot and 298px on every other, from ~100ms, on all five page types --
   the exact reserve this round exists to remove, put back by trying to be
   neat. An item's own CONTENT is capped by `min-height: 0`; its margins and
   floor are not.

   WHAT STAYS CONDITIONAL AND VISIBLE is `background` and `border-color`, which
   is the only part a reader could ever see -- so an unfilled slot paints
   nothing even in a browser that somehow failed to apply the grid. */
.ttc-ad__box {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  /* HORIZONTAL: unconditional. This is the width Google measures. */
  padding: 0 12px;
  border: 0 solid transparent;
  border-left-width: 1px;
  border-right-width: 1px;
  border-radius: 12px;
  min-height: 0;
}
/* VERTICAL: conditional, because every one of these adds height. */
.ttc-ad.is-filled .ttc-ad__box {
  padding-top: 10px;
  padding-bottom: 10px;
  border-top-width: 1px;
  border-bottom-width: 1px;
  margin: 24px 0;
}

/* ---- AND THE HEIGHT FLOOR IS ONE LEVEL FURTHER IN AGAIN, WHICH IS THE
   DIFFERENCE BETWEEN AN EXPAND AND A JUMP --------------------------------
   `--adh-d` / `--adh-m` are the floor for a filled box. Put on `.ttc-ad__box`
   -- the grid ITEM -- a `min-height` feeds the `minmax(auto, 0fr)` track's
   auto minimum directly, the same way margins do, and `is-filled` then takes
   the track from 0 to 250px IN ONE FRAME before the transition has ticked
   once. Measured: [0, 274, 274, 274, ...] -- 88% of the travel gone in a
   single frame, with only the last 37px easing.

   ON `.ttc-ad__in` IT IS CONTENT, AND CONTENT IS CAPPED. `.ttc-ad__box` keeps
   an explicit `min-height: 0`, which overrides its automatic minimum size, so
   nothing inside it -- however tall -- can raise the track's minimum. The
   floor still does its job in the only state that reads it, because `1fr`
   resolves to the item's MAX-content height and the floor is part of that. */
.ttc-ad.is-filled .ttc-ad__in { min-height: var(--adh-m, 0px); }
@media (min-width: 768px) {
  .ttc-ad.is-filled .ttc-ad__in { min-height: var(--adh-d, 0px); }
}

/* ---- THE FADE, AND WHY IT IS AN ANIMATION AND NOT A TRANSITION --------
   The job asks for height AND opacity. A `transition` needs a resting state to
   travel FROM, which would mean `opacity: 0` on an unfilled slot -- and an
   unfilled slot is the state this site is in on every unit of every page, for
   the life of the page, while Google's review looks at it. An element at
   `opacity: 0` is a hidden element to any viewability heuristic, and rule (a)
   of this job forbids anything that could suppress the request.

   AN ANIMATION HAS ITS OWN `from`. It does not exist until the rule matches,
   so a slot that has not filled has no opacity applied to it at any point in
   its life; the moment `data-ad-reveal` is set the box starts at 0 and arrives
   at 1 over the same 200ms the height takes.

   KEYED ON `data-ad-reveal` AND NOT ON `.is-filled`, because those are two
   different events. `.is-filled` is in the SERVER'S markup for a house
   creative and for the staging stub -- known before the page was sent -- and
   those must paint in the first frame with nothing to look at happening, which
   is how round 96 left them. `data-ad-reveal` is written by ad-slots.js and
   only ever when GOOGLE answered "filled" on a slot that was empty a moment
   ago, which is the one case where something appearing needs softening. */
@keyframes ttc-ad-reveal { from { opacity: 0; } to { opacity: 1; } }
.ttc-ad[data-ad-reveal] .ttc-ad__box {
  animation: ttc-ad-reveal 200ms ease both;
}
@media (prefers-reduced-motion: reduce) {
  .ttc-ad[data-ad-reveal] .ttc-ad__box { animation: none; }
}

.ttc-ad__in {
  display: flex; align-items: center; justify-content: center;
  width: 100%; flex: 1 1 auto; min-height: 0;
}
.ttc-ad__in img, .ttc-ad__in picture { max-width: 100%; height: auto; display: block; }
/* THE ONE WIDTH RULE THE WHOLE ROUND DEPENDS ON. A responsive unit measures
   the width available to it at push time and asks Google for a creative that
   fits; measured at zero it asks for nothing and can never fill. So the <ins>
   is full width in every state, filled or not, and this rule is the reason the
   wrapper above is a full-width block rather than something that collapses on
   both axes. */
.ttc-ad__in ins.adsbygoogle { display: block; width: 100%; }

/* ---- COLLAPSE ON EMPTY, WHICH IS NOW BELT AND BRACES ------------------
   `is-empty` is written by ad-slots.js when AdSense answers "unfilled", or
   when it answers nothing at all and the unit drew nothing -- a blocked
   loader, an ad blocker, or a host Google does not serve.

   AFTER ROUND 96 IT HAS ALMOST NOTHING LEFT TO UNDO, because an unfilled box
   was never painted in the first place. It is kept, and kept `!important`,
   for the one thing the default state cannot do on its own: take the ELEMENT
   out of the flow entirely, so that a stray margin from somewhere else, a
   `:has()` rule keyed on the wrapper's presence, or a unit that drew something
   and then emptied itself cannot leave a gap behind. It is the belt; the
   default state is the braces, and the braces are what the reader sees.

   `!important` because the two sticky units set their own display at their own
   breakpoints and `.ttc-ad--sidebar` turns itself back on inside a
   `@media (min-width: 1201px)` block, so a plain `display: none` would lose at
   exactly the widths where a leftover box is most visible. */
.ttc-ad.is-empty,
.ttc-ad[data-ad-empty] {
  display: none !important;
  min-height: 0 !important;
  height: 0 !important;
  margin: 0 !important;
  padding: 0 !important;
  border: 0 !important;
}
.ttc-ad.is-empty .ttc-ad__label,
.ttc-ad[data-ad-empty] .ttc-ad__label { display: none !important; }

/* ROUND 90: the staging placeholder, which only ever renders on a candidate --
   see the note in app/adboxes.box(). It is drawn as the real unit would be,
   inside the same frame and under the same label, so what Tom is looking at is
   the box and not the placeholder. */
.ttc-ad__stub {
  display: block; max-width: 100%; height: auto; margin: 0 auto;
}

/* ---- ROUND 94: THE TOP-OF-PAGE BAND ------------------------------------
   `.ttc-adtop` is the wrapper app/adboxes.top_band() emits in the position the
   retired 2404 banner held. THE GAP ITSELF IS NOT HERE: it is an inline
   `margin-top` on the element, because this stylesheet is requested only when a
   page actually draws a box and the band's whole job is to hold the space open
   in the states where nothing draws -- a page type with no top slot, a disabled
   slot, or a fill that collapsed. See the note in app/adboxes.py.

   WHAT IS HERE is the one thing that only matters when there IS a box: the box
   gives up its own 24px top margin, so the band owns the gap and the two do not
   add up to 54px. The bottom margin stays -- it is the 23.04px the retired
   banner's wrapper put between itself and the block below it, near enough that
   the page does not move. */
.ttc-adtop { display: block; }
.ttc-adtop > .ttc-ad .ttc-ad__box { margin-top: 0; }

/* ---- THE TWO VARIANTS -------------------------------------------------- */
/* ADSENSE: cream frame, and the label. The label is shown ONLY for adsense
   fills -- app/adboxes decides that, not this file, because "is this paid
   advertising" is a fact about the fill and not a style.

   ROUND 96 PUT `.is-filled` ON BOTH VARIANT RULES, and that is the entire
   user-visible change of the round: the cream ground, the border and the
   padding are the "branded chrome" that must not exist around a slot Google
   has not filled. Without the class this selector matches nothing, so an
   unfilled unit is a bare zero-height block -- which is what "renders nothing
   and reserves no space" means in CSS. */
/* ROUND 123: THE CREAM GROUND AND THE OLD LABEL RULE ARE DELETED, NOT
   OVERRIDDEN. Round 121 wrote a replacement at the foot of this file and left
   this rule sitting here, which worked -- same specificity, later in the file
   -- and left the exact trap round 121's own note warned about: two rules in
   one stylesheet disagreeing about one property, the dead one being the one
   that reads like the design. `#fffeec` existed in this file right up until
   this round, and anybody grepping for the cream box found it and believed it.

   WHAT USED TO BE HERE: `background: #fffeec; border-color: #e7dcc6` on the
   filled adsense box, and a `.66rem` centred label. The replacement is in the
   ROUND 121 block at the end of the file -- no ground, no border, and a 14px
   label on a transparent background. */
/* HOUSE: green frame, and NO label. A house promotion is ours; calling it
   "Advertisement" would be telling a reader something untrue about it.

   A house box gets `is-filled` FROM THE SERVER, in the HTML, because a house
   creative is ours and we know it is there -- so this frame is painted in the
   first frame with no flicker and nothing waits on a script. See the note in
   app/adboxes.box() on why that answer is server-side and AdSense's is not. */
/* ROUND 123: the house variant's green frame is deleted for the same reason
   as the cream one above -- round 121's replacement already strips the box on
   `.ttc-ad--house.is-filled` as well, so this rule had been dead since then
   and was only there to mislead the next reader. A house promotion is still
   marked by NOT carrying an "Advertisement" label, which is the honest half of
   the distinction and the half that never depended on a colour. */
.ttc-ad--house .ttc-ad__label { display: none; }

/* ---- THE DESKTOP STICKY SIDEBAR, IN THE RAIL, UNDER THE NEWSLETTER ------
   Hidden everywhere but >=1201, which is where the rail itself appears.
   `position: sticky` inside `#main-right`, which is a floated 303px column,
   so it scrolls with the rail and stops under the header. It is 303px wide
   because the rail is: it holds a 300x600 half page and not one pixel more. */
/* ROUND 94 MOVED THE FLOOR FROM 1400 TO 1201, AND THE REASON IS ABOVE.
   Round 86 chose 1400 because 1201..1399 had the floating Comments/Share bar
   sitting ON the rail; 86b (d) moved that bar to the LEFT from 1201 up -- the
   width at which `#main-right` stops being `display:none` -- and the note at the
   top of this file has said ever since that the unit "could run from 1201" and
   was left at 1400 only because nothing had asked it to move.

   ROUND 94 IS WHAT ASKED. Measured on the candidate, the unit was in the markup
   on all five page types, after the Subscribe box, last child of the rail, and
   `display: none` at 1201, 1280 and 1366 -- so reviewed in any ordinary laptop
   window it was simply not there, which is what "the sidebar sticky is not
   showing" was. The rail is the same 303px at 1201 as it is at 1440 (868, 947,
   1021.3, 1058.3 measured), the Subscribe box does not move, and 1201 is also
   the width below which there is no rail to be in -- so this is the rail's own
   breakpoint rather than a number of ours, and there is no band where the unit
   is shown without a rail to show it in. Below 1201 the rail is hidden and so is
   the unit; on a phone it is `display: none` twice over.

   ROUND 96 PREFIXED THIS WITH `.ttc-ad` and the reason is the trap note on
   `.ttc-ad.is-filled` above: one class loses to two, so without the prefix a
   fill would have turned the rail's slot on at 390. */
.ttc-ad.ttc-ad--sidebar { display: none; }
@media (min-width: 1201px) {
  /* ROUND 96 SPLIT THIS IN TWO so that `display` never waited on a fill; ROUND
     97 KEEPS THE SPLIT AND NO LONGER NEEDS IT TO BE CAREFUL. The unit is a
     real, full-width, requestable grid at this breakpoint in every state, and
     the only thing filling changes anywhere in this file is the row track --
     so there is no longer a filled rule that could reach back and switch a
     `display` on. What still waits for `is-filled` is the 600px floor and the
     24px margin, and both of those are now on the inner box, inside the track,
     where a collapsed slot takes them to nothing. */
  .ttc-ad.ttc-ad--sidebar {
    display: grid;
    /* TOM'S REVIEW, 8 OCTOBER -- THE FLIP. The SUBSCRIBE BANNER is the pinned
       one now and the AD SCROLLS, which is the opposite of what this round
       first built. `static`, so the unit travels with the page like any other
       block. The `top` below is kept because it costs nothing on a static
       element and it is what the rule goes back to if the flip is undone. */
    position: static;
    /* THE MEGA ROUND, ITEM 12 (b). 24px WAS NOT CLEAR OF ANYTHING.
       The comment this replaces said "clear of the site header, which is the
       only thing above it" -- and the site header is NOT the only thing above
       it. `header .bottom` becomes `position: fixed` on scroll (the theme's
       own `initFloatingSidebar`/menu behaviour) and occupies the top band of
       the window: MEASURED 0..69px at 1201, 1280 and 1440, and 0..74px at
       1600. The unit stuck at 24px, so its first 18px -- which is the
       "ADVERTISEMENT" label, the one piece of type on an ad unit that is
       legally required to be legible -- was painted UNDERNEATH that bar.
       `elementsFromPoint` at the label's own centre returned
       `div.bottom.clearfix.fixed` above `span.ttc-ad__label`.
       That is the whole of "the label disappears when scrolled".

       98px = the tallest measured bar (74px) plus the same 24px of air the
       old number was asking for, so the label clears the bar at every width
       measured rather than at none of them. */
    top: 98px;
    margin: 0;
    min-height: 0;
  }

  /* ---- ITEM 12: THE AD AND THE SUBSCRIBE BANNER, FLIPPED PER TOM ------
     LAYOUT ONLY. No slot id, no AdSense parameter, no Desk configuration and
     no markup -- three declarations about where two blocks sit.

     WHAT WAS WRONG IN THE FIRST PLACE. Scrolled, the sticky ad was painted
     ON TOP OF the "Subscribe For Newsletters" banner and hid it completely.
     MEASURED at 1440 on a recipe, with the unit in its filled state:

                        at rest              scrolled to 2600
         subscribe      y216..680 relative   y139..603  FIXED
         sidebar ad     y680..1326 sticky    y24..670   sticky
         overlap        none                 303 x 464 -- the whole banner
         painted on top --                   THE AD

     The cause is the theme's own `initFloatingSidebar()` in
     static/wp-content/themes/cookinghub/assets/js/app.js, which sets
     `#floating-sidebar` to `position: fixed` on every scroll event. That
     takes the banner OUT OF FLOW, so the ad -- the next block in the rail --
     rises into the space it left and sticks in the same band. Both are
     `z-index: auto`, so the one later in the document paints over the other,
     and that is the ad.

     TOM'S DECISION: THE BANNER IS THE ONE THAT STAYS. So the banner is
     `sticky` and the ad is `static`.

     AND THE ORDER OF THE TWO IS SWAPPED, WHICH IS THE PART THAT IS NOT
     OBVIOUS AND IS THE WHOLE REASON THERE IS NO OVERLAP.

     With the banner pinned and the ad still BELOW it, the two overlap
     immediately and permanently -- a sticky element holds its place in the
     viewport while everything after it scrolls up INTO that place. Worked
     out on the real geometry (banner 720px, ad 646px, rail starting at
     y216): the banner pins at scrollY 118, and the ad's top enters the
     banner's band at scrollY 118. The same pixel. The ad would spend the
     whole page behind the banner, which is not "the ad scrolls", it is "the
     ad is never seen again".

     PUT THE AD FIRST AND IT IS EXACT, by construction rather than by
     arithmetic: a sticky element NEVER rises above its own place in the flow,
     so the banner cannot reach the ad, and the ad scrolls away above it and
     is gone. ZERO intersection at every scroll position and every width --
     not a small one, none.

     THE ONE CONSEQUENCE, AND IT IS A VISIBLE ONE: at rest the rail now reads
     ad first, Subscribe second. It is flagged in QUESTIONS.md and in the
     report rather than buried, and it is one line to undo.

     `!important` IS REQUIRED AND IS NOT A SHORTCUT: the theme sets `position`
     and `top` as INLINE styles from JavaScript on every scroll event, and an
     inline style is only beatable by `!important`. There is no scroll handler
     of ours to remove, and removing the theme's would be editing shared
     furniture other pages use.

     SCOPED BY `:has()` TO A PAGE THAT ACTUALLY CARRIES THE UNIT, the same
     scoping the out-of-flow rail rule below already uses.

     ROLLBACK: delete this block and put `position: sticky` back on
     `.ttc-ad--sidebar` above. */
  main:has(.ttc-ad--sidebar) #main-right {
    display: flex;
    flex-direction: column;
  }
  main:has(.ttc-ad--sidebar) .ttc-ad--sidebar { order: 1; }
  main:has(.ttc-ad--sidebar) #floating-sidebar {
    order: 2;
    position: sticky !important;
    /* the same 98px the ad used to stick at, and for the same reason: the
       theme's menu bar is `position: fixed` across the top of the window at
       0..69px (0..89px at 1920), and anything pinned above that is painted
       underneath it. */
    top: 98px !important;
    bottom: auto !important;
    align-self: flex-start;
  }
  .ttc-ad.ttc-ad--sidebar.is-filled .ttc-ad__box { margin: 24px 0 0; }
  /* the floor goes on the content, not on the item -- see the note above */
  .ttc-ad.ttc-ad--sidebar.is-filled .ttc-ad__in {
    min-height: var(--adh-d, 600px);
  }

  /* ---- ROUND 86b (c): I TRIED TO DROP THIS RULE AND IT DOES NOT DROP ----
     Tom asked for the sticky ad to sit UNDER the newsletter in the rail and
     stick on scroll -- which is where it already sits, measured at 1440:
     newsletter y216..936, ad y960..1560, inside the rail, at the rail's width
     -- and to drop the out-of-flow rule below IF that placement removes the
     need for it. It does not, and here is the measurement rather than the
     claim. `main` has exactly two children, `#main-left` (float:left) and
     `#main-right` (float:right); four ways to give the rail a runway:

       what                         column x    rail h     sticky travel
       rail left in flow (float)      78.7        1344            0px
       main -> display:flex           38.4       22396      19,000px
       main -> display:grid           38.4       22396      19,000px
       #main-right out of flow        78.7       22413      19,000px   <- kept

     THE NEGATIVE CONTROL IS THE FIRST ROW. With the rail left alone the ad
     renders under the newsletter and `position: sticky` moves it ZERO pixels,
     because a float is only as tall as its contents (1,344px against a
     22,396px article) and the ad is the last thing in it -- there is nothing
     below it to travel into. "Under the newsletter" and "sticks" are two
     requirements and the placement alone only satisfies the first.

     AND MAKING `main` A FLEX OR GRID CONTAINER MOVES THE WHOLE PAGE. 40.36px
     to the left, at 1440, on every page type: `.center` is `width:167rem;
     margin:0 auto` and its auto margins stop resolving evenly the moment
     `main` becomes a flex, grid or flow-root container. Reproduced with
     ad-slots.css disabled entirely, so it is the theme's own behaviour and
     not ours; a fresh 1282.55px `margin:0 auto` div in the same body centres
     correctly under all three, so it is specific to this element. Either
     would shift the reader's column by 40px to move an ad, which is the
     opposite trade of the one Tom is making.

     SO THE OUT-OF-FLOW RULE STAYS, as the least invasive of the four, and it
     is reported as a decision rather than buried. MEASURED AGAIN AT 1440 FOR
     86b, before and after -- nothing but the rail's own height moves:

                       before                  after
       #main-right     x1058 w303 h1344        x1058 w303 h22413
       newsletter      x1058 w303 h720         x1058 w303 h720
       #main-left      x78.7 w979.5 h22395.5   x78.7 w979.5 h22395.5
       main            x78.7 h22414            x78.7 h22414
       document        22897                   22897
       sticky travel   0px                     19,000px

     ONE DECLARATION IS GONE: `main:has(.ttc-ad--sidebar){position:relative}`.
     The theme already sets `position: relative` on BOTH `main` and `.center`,
     so we were restating something true and touching shared furniture to do
     it. The absolute rail's containing block is unchanged.

     SCOPED BY `:has()` TO A PAGE THAT ACTUALLY HAS THE UNIT, and this whole
     stylesheet is only requested when a slot renders -- so with the flag off
     the rail is not described by us at all, which is round 65's rule about
     the shared furniture and the reason this is not in the theme's own file.
     `top` is 1px and not 0 because that is where the float sat: measured, the
     float's top edge is 1px below `main`'s, and reproducing it exactly is how
     the newsletter box does not move by a pixel when a slot turns on. */
  main:has(.ttc-ad--sidebar) #main-right {
    float: none;
    position: absolute;
    right: 0;
    top: 1px;
    bottom: 0;
    width: 303px;
  }
}

/* ---- THE MOBILE STICKY BOTTOM BAR, AND THE BAR IT MUST NOT COVER --------
   PHONE ONLY (<=767px), which is exactly where the floating Comments/Share
   bar is on the RIGHT at 49.4px wide and vertically centred.

   THE COLLISION, AND WHY A MEDIA QUERY IS NOT ENOUGH. The floating bar is
   `top:50%; transform:translateY(-50%)` -- it is centred, so on a tall phone
   it is nowhere near the bottom and on a SHORT one (a landscape phone, a
   browser with every toolbar open) its lower edge comes down to meet a bar
   sitting on the bottom edge. Measured: a 104px-tall floating bar centred in
   a 300px-tall viewport occupies 98..202, and a 58px bottom bar occupies
   242..300 -- 40px apart, and closing.

   So the bottom bar does not merely sit below: it PUSHES THE FLOATING BAR UP
   by half its own height, through `--ttc-fbar-lift`, which recipe-v3-floating
   -bar.css reads. Neither can cover the other's controls at any viewport
   height, because the geometry is arithmetic and not a guess.

   z-index 55 is deliberately BELOW the floating bar (60) and its share popup
   (61): if the two ever did meet, the reader's own controls are the ones that
   stay on top and stay tappable.

   ROUND 96 PREFIXED THIS WITH `.ttc-ad` too, and here it was not theoretical:
   measured at 1440 before the fix, a filled bar was 1440 x 302 in the page
   flow. See the trap note on `.ttc-ad.is-filled`. */
.ttc-ad.ttc-ad--mobile-sticky { display: none; }
@media (max-width: 767px) {
  /* ROUND 96, SPLIT FOR THE SAME REASON AS THE SIDEBAR ABOVE. The bar is
     pinned to the bottom of the viewport in both states -- full viewport
     width, which is the width the unit measures when it asks Google -- and
     when nothing fills it is a zero-height strip on the bottom edge that no
     reader can see. ROUND 97 CHANGED WHAT MAKES IT ZERO -- the clip does it
     now, not the withholding of the floor and the padding -- but the bar's
     published height still waits for `is-filled`, so with the `:has()` rule
     below it does not push the floating bar up off a bar that is not there. */
  .ttc-ad.ttc-ad--mobile-sticky {
    display: grid;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 55;
    margin: 0;
    padding: 0;
    border-radius: 0;
    min-height: 0;
  }
  /* ROUND 97: AN UNFILLED BAR IS NOW ZERO-HEIGHT BY THE SAME CLIP AS EVERY
     OTHER SLOT, rather than by having its padding and floor withheld. That
     matters more here than anywhere else on the site: this is the one unit
     pinned into the reader's viewport, so Google's transient reserve -- 390px
     of it, measured at 390 in STEP A -- was a third of a phone screen covered
     by an empty box for 1.3 seconds on every page load. It is clipped now. */
  /* the bar runs edge to edge, so its side border and side padding are the
     two horizontal numbers that are ZERO here rather than inherited -- which
     keeps them unconditional in the same way, at a different value. */
  .ttc-ad.ttc-ad--mobile-sticky .ttc-ad__box {
    border-left-width: 0; border-right-width: 0;
    border-radius: 0;
    margin: 0;
    padding-left: 10px; padding-right: 10px;
  }
  .ttc-ad.ttc-ad--mobile-sticky.is-filled .ttc-ad__in {
    min-height: var(--adh-m, 50px);
  }
  .ttc-ad.ttc-ad--mobile-sticky.is-filled .ttc-ad__box {
    border-bottom-width: 0;
    padding-top: 6px; padding-bottom: 6px;
    /* AND A CEILING, WHICH ROUND 86 DID NOT NEED. `data-ad-format="auto"` lets
       Google pick the height, and a bar fixed to the bottom of a phone screen
       is the one place on this site where "whatever Google picks" could cover
       the page. The cap is the reserved height plus half again -- enough for a
       100px mobile banner in a slot sized for 50 -- and `overflow: hidden` on
       `.ttc-ad` keeps anything taller inside it. */
    max-height: calc(var(--adh-m, 50px) * 1.5 + 20px);
  }
  /* the bar's own height, published so the floating bar can move off it. On
     the WRAPPER, because `:has()` below reads the wrapper's classes and the
     floating bar reads the custom property off the body -- see the lift rule. */
  .ttc-ad.ttc-ad--mobile-sticky.is-filled {
    --bar-h: calc(var(--adh-m, 50px) + 20px);
  }
  /* the dismiss control. A sticky ad a reader cannot get rid of is the one
     ad format every guideline names, so this is not optional furniture. */
  .ttc-ad--mobile-sticky .ttc-ad__x {
    position: absolute; top: 2px; right: 4px;
    width: 30px; height: 30px; line-height: 28px; text-align: center;
    border: 0; background: transparent; cursor: pointer;
    font: 400 18px/28px system-ui, sans-serif; color: #9a8f7a;
  }
  .ttc-ad--mobile-sticky .ttc-ad__x:focus-visible { outline: 2px solid #51000d; }
  /* dismissed for the session -- ad-slots.js sets this and sessionStorage
     remembers it, so it stays gone until the tab is closed. */
  /* THREE CLASSES, to beat `.ttc-ad.ttc-ad--mobile-sticky.is-filled` above.
     A bar the reader has closed stays closed even if the slot fills again. */
  .ttc-ad.ttc-ad--mobile-sticky.is-dismissed { display: none; }

  /* AND THE FLOATING BAR MOVES. `--ttc-fbar-lift` is 0 unless a mobile sticky
     ad bar is actually on the page and not dismissed. */
  /* ROUND 96 ADDED `.is-filled` HERE and it fixes a fault of its own. `:has()`
     matches on an element being IN THE DOM, not on it being visible -- so with
     round 90's markup the floating bar was lifted 39px on every phone view,
     including the ones where the sticky slot was unfilled and there was
     nothing under it to clear. Now the lift happens exactly when a bar is
     actually painted. */
  body:has(.ttc-ad--mobile-sticky.is-filled:not(.is-dismissed)) {
    --ttc-fbar-lift: 39px;   /* half of 50px reserved + 6+6 padding + 1 border */
  }
}

/* The label sits above a 50px bar awkwardly; on the sticky bar it goes beside
   the creative instead, vertically, so the reserved 50px stays 50px. */
@media (max-width: 767px) {
  .ttc-ad--mobile-sticky .ttc-ad__label {
    position: absolute; left: 8px; top: 50%; transform: translateY(-50%)
              rotate(180deg); writing-mode: vertical-rl;
    margin: 0; font-size: .5rem; letter-spacing: .1em;
  }
  .ttc-ad--mobile-sticky .ttc-ad__in { padding-left: 16px; padding-right: 26px; }
}

/* ---- PRINT. An advertisement is not part of a printed recipe. ---------- */
@media print { .ttc-ad { display: none !important; } }

/* ---- THE LIFT ITSELF, AND WHY IT IS A MARGIN AND NOT A TRANSFORM -------
   This rule lives HERE and not in recipe-v3-floating-bar.css on purpose:
   that stylesheet is live on every recipe and article today, and this round
   deploys nothing. ad-slots.css is loaded only on a page that has a slot, so
   with the flag off this declaration is not on the site at all.

   A MARGIN, because the floating bar's position is a transform -- `top:50%`
   plus `translateY(-50%)` -- and `.ttc-fbar.is-hidden` REPLACES that transform
   with its own `translateY(-50%) translateX(18.2px)`. Folding the lift into
   the transform would mean editing four rules and would silently drop the lift
   the moment the bar hid itself. `margin-top` is not part of the transform, so
   it survives every one of those states, and with `--ttc-fbar-lift` unset it
   computes to 0px and moves nothing. */
.ttc-fbar { margin-top: calc(-1 * var(--ttc-fbar-lift, 0px)); }


/* ---- THE THIRD COLLISION, WHICH WAS NOT ON THE LIST ---------------------
   FOUND BY OPENING THE PHONE FRAME. The job named two pairs to keep apart --
   the sticky bar against the floating Comments/Share bar, and the sidebar
   against the newsletter rail. There is a third thing fixed to the bottom of
   a phone screen on this site and it is the cookie bar: `#ttc-consent`, fixed,
   130px tall, z-index 9999. Measured at 390x844 with the slots on: the consent
   bar occupies y714..844 and the sticky ad bar y794..844 -- the ad is entirely
   inside it, and covered by it.

   WHICH WAY IT WAS ALREADY WRONG MATTERS. The consent bar is on top, so the
   Accept and Decline controls stayed tappable -- an ad covering a consent
   control would have been the serious version of this. What actually happened
   is the other one: a paid unit rendered where nobody could see it, which is
   an impression AdSense should never be asked to count.

   So the ad bar does not appear while there is a cookie question on screen.
   `[hidden]` is the site's OWN dismissal -- app/consent.py ships the bar
   hidden for a reader who has already answered and static/consent/consent.js
   sets `bar.hidden = true` when they answer -- so this needs no script of its
   own and cannot get out of step with the consent bar's real state. */
@media (max-width: 767px) {
  /* left at one class on purpose: `:has()` takes the specificity of its most
     specific argument, so the `#ttc-consent` id already puts this above every
     rule above it. The consent banner wins over a fill, which is right. */
  body:has(#ttc-consent:not([hidden])) .ttc-ad--mobile-sticky { display: none; }
  /* and the floating bar drops back to its centre while the ad bar is gone */
  body:has(#ttc-consent:not([hidden])) { --ttc-fbar-lift: 0px; }
}

/* ---- THE IN-FEED BOX IS A GRID ITEM, NOT A BLOCK -----------------------
   `div.list` is a three-column CSS grid whose children are the cards. A box
   dropped into it without this rule is placed as a FOURTH card in a
   three-card row: 303px wide, in the grid's flow, sitting where a recipe
   should be. Spanning every column is what makes it a band between two rows,
   which is what "in-feed, between rows of cards" means. */
.ttc-ad--infeed { grid-column: 1 / -1; }

/* =====================================================================
   ROUND 120, PART D. THE AD FURNITURE, SLIMMED.

   APPENDED RATHER THAN EDITED IN PLACE, and that is deliberate on a 924-line
   stylesheet whose every rule carries the reason it exists: everything this
   round changes is in one block at the end, so it can be read as a diff and
   reverted as a block. Each rule below names the one it overrides.

   WHAT IS NOT TOUCHED: no AdSense id, no Desk key, no slot's enabled flag, no
   `is-filled` gating. An unfilled unit still paints nothing and still takes no
   space -- rounds 96 and 98 settled that and this round does not reopen it.
   ===================================================================== */


/* ---- D.14: A SLIM LABELLED STRIP, NOT A BOX -------------------------- */
/* Overrides `.ttc-ad--adsense.is-filled .ttc-ad__box` (the cream ground and
   the frame) and the padding/borders on `.ttc-ad.is-filled .ttc-ad__box`.
   The unit keeps a visible "ADVERTISEMENT" marking -- that is the part that
   is not ours to drop -- but it is carried by a strip ABOVE the creative
   instead of by a frame around it, so the banner itself runs the full width
   of its column with nothing inset at the sides. */
/* the creative gets the whole width back */
.ttc-ad--adsense.is-filled .ttc-ad__in { padding-left: 0; padding-right: 0; }

/* ---- D.10: THE TOP BANNER NEVER TAKES HALF A PHONE -------------------- */
/* The top band is the one eager slot, so its reserved height is in the first
   layout a reader ever sees. A 250px unit on a 640px-tall phone viewport is
   39% of the screen before a word of the page; a tall creative served into it
   is worse. Capped at half the viewport, on the band and on its reserve
   alike, so the cap holds whether the slot is armed, filled or collapsed. */

/* ---- D.12: THE SKINNY IN-CONTENT UNIT, BETWEEN THE STEPS -------------- */
/* Narrower than a mid-content box on purpose: it stands between two numbered
   step cards, and a 250px block there reads as a step. It keeps the strip and
   the label -- a unit inside the instructions is exactly the one a reader must
   be able to tell apart from the instructions. */
.ttc-ad--skinny.is-filled .ttc-ad__box { margin: 16px 0; }
.ttc-ad--skinny.is-filled { grid-template-rows: minmax(0, 120px); }
.ttc-ad--skinny.is-filled .ttc-ad__in { max-height: 120px; min-height: 0; }
.ttc-ad--skinny.is-armed:not(.is-filled):not(.is-orphan) {
  grid-template-rows: 120px;
}

/* ---- ROUND 121, PART C.6: THE FEED FLOWS, WITH NO EMPTY CELLS --------- */
/* THE HOLE WAS AT TWO COLUMNS AND NOWHERE ELSE, which is why it was missed.
   The in-feed unit already spans `1 / -1`, so at three columns -- where it was
   looked at -- the nine cards before it fill three clean rows and the ad takes
   the fourth. At 760 and 900 the grid is TWO columns: nine cards leave the
   ninth alone on a line with an EMPTY CELL beside it, and then the ad takes
   the next row. Measured: 2 holes at 900, 2 at 760, 0 at 1024.

   `dense` is the fix rather than moving the splice, because the splice happens
   on the server, where the column count is not knowable -- it is a media query
   away. Dense packing lets the next card fall back into the hole instead of
   leaving it, so a reader never meets a gap mid-feed.

   IT CANNOT SCRAMBLE THE CARDS. Dense only backfills a gap with an item that
   FITS it, and every card is exactly one column wide; with nothing spanning,
   dense and the default flow lay out identically -- which is what the 1024
   measurement shows, unchanged at 0 holes either way. The only item that spans
   is the ad, and it is never pulled backwards because it cannot fit a
   one-column hole.

   The last card still sits alone on its line when the count is odd, which is
   what the brief asks for and what a feed should do. */
div.list { grid-auto-flow: row dense; }

/* ---- ROUND 120's PART D: THE RULES ROUND 121 REPLACED ARE DELETED ----- */
/* They are not overridden from further up the file -- that was tried and it
   did not work, because CSS resolves a tie on SOURCE ORDER and round 120's
   block came last. Two blocks disagreeing about the same property is a trap
   for whoever reads this next, so the superseded rules are gone and round
   121's are at the end of the file where they win on their own. What remains
   of round 120 below is the part that still stands. */

/* ================= ROUND 121: THE AD FURNITURE, CORRECTED ============= */

/* ---- B.4: THE LABEL, AT A SIZE A PERSON CAN READ --------------------- */
/* ROUND 120 DOUBLED `.66rem` TO `1.32rem` AND THAT WAS THE WRONG UNIT. This
   site's root font-size is 7.68px, so "double" took the label from 5.07px to
   10.14px -- arithmetically a doubling and still unreadable. An ad disclosure
   is not decoration; it is the one piece of type on the unit that has to be
   legible, so it is set in PIXELS and does not move when a root font-size
   somewhere else does. */
.ttc-ad__label,
.ttc-ad--adsense.is-filled .ttc-ad__label,
.ttc-ad--skinny .ttc-ad__label {
  font-size: 14px;
  line-height: 1.3;
  letter-spacing: .08em;
  color: #6f675a;
  text-transform: uppercase;
}

/* ---- B.5: NO BOX AT ALL, AND NO SPACE THE AD DOES NOT USE ------------ */
/* Round 120 took the cream ground off the BOX and then put a cream strip back
   behind the label, which is the same box an inch shorter. The label stands on
   the page now: no ground, no border, no frame anywhere on the unit. */
/* NO border either, the top hairline included. Round 120 kept one to mark the
   strip; the brief says no box and no borders, and the 14px label does the
   marking on its own. */
.ttc-ad .ttc-ad__box,
.ttc-ad--adsense.is-filled .ttc-ad__box,
.ttc-ad--house.is-filled .ttc-ad__box {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}
.ttc-ad__label,
.ttc-ad--adsense.is-filled .ttc-ad__label {
  background: none;
  border: 0;
  margin: 0 0 4px;
  padding: 0;
  text-align: left;
}
/* AND THE CONTAINER HUGS WHAT WAS ACTUALLY SERVED. `--adh-d/--adh-m` stay the
   RESERVE -- the height held open while the slot is armed, which is what keeps
   the page from jumping and what round 97 measured buying the phone's ad
   request back. What goes is the same number used as a FLOOR once the ad has
   landed: a 90px banner in a slot configured at 250px was sitting in 160px of
   nothing. Filled, the box is exactly as tall as its creative. */
.ttc-ad.is-filled .ttc-ad__in,
.ttc-ad--adsense.is-filled .ttc-ad__in,
.ttc-ad--skinny.is-filled .ttc-ad__in { min-height: 0; }
.ttc-ad.is-filled { grid-template-rows: auto; }
.ttc-ad.is-filled .ttc-ad__box { margin: 20px 0; }

/* ---- B.3: THE TOP BAND IS CAPPED BY THE REQUEST, NOT BY A CLIP ------- */
/* Round 120 used `max-height: 50vh; overflow: hidden` on a phone, which does
   not cap an ad -- it cuts one in half and bills the advertiser for the half a
   reader never saw. The cap is in the REQUEST now (app/adboxes.TOP_CAP_PX, a
   horizontal format and a 150px <ins>), so the clip is removed on every
   screen. The 150px here is a belt to that braces and clips nothing, because
   nothing taller is ever served. */
@media (max-width: 760px) {
  .ttc-adtop .ttc-ad,
  .ttc-ad--page-top { max-height: none; overflow: visible; }
  .ttc-adtop .ttc-ad.is-armed:not(.is-filled):not(.is-orphan),
  .ttc-adtop .ttc-ad.is-filled { grid-template-rows: auto; }
  .ttc-adtop .ttc-ad.is-filled .ttc-ad__in { max-height: none; }
}
.ttc-adtop .ttc-ad.is-armed:not(.is-filled):not(.is-orphan) {
  grid-template-rows: 150px;
}
.ttc-adtop .ttc-ad__in > ins.adsbygoogle { max-height: 150px; }

