/**
 * Lemma — monetization UI styles
 * Patreon upsell/support strip, the contextual sign-in panel, and the ad slot.
 * Uses the same design tokens as style.css so it stays on-theme.
 *
 * The `.consent*` banner rules that gave this file its name were deleted on
 * 2026-08-05, when the homegrown banner was replaced by Google's Funding Choices
 * CMP (public/js/consent.js explains why). The consent dialog is now a
 * cross-origin Google iframe and this stylesheet cannot — and must not — style
 * it. The FILENAME stays: it is referenced by every view, by public/sw.js's
 * SHELL list and by the ad-slot rules below, and renaming it would churn all
 * three to describe a file whose job never was only consent.
 */

/* ---- Support / Patreon strip ---- */
.support {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 16px;
  padding: 14px 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 14px;
  color: var(--text-dim);
  text-align: center;
}
.support-msg { color: var(--text-dim); }
/* (.support-link-secondary removed with the strip's duplicated "I'm a patron — log in"
   anchor — that link now lives once, in the persistent header control. See
   views/_support.ejs and docs/patreon-login-button-spec.md §4.) */
.support-link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  padding: 0;
  /* Positioning context for the hit-target overlay below. */
  position: relative;
}
/* 44px HIT TARGET. The text box is 147.55 x 21px — under half the 44px minimum in
   both WCAG 2.5.8 and the platform HIG — and since 2026-08-07 this is the ONLY
   affordance inside a bordered card sitting at the head of the content column on all
   twenty ad views (ten until 2026-08-12, §0.2), so a near-miss
   tap lands on nothing at all rather than on some neighbouring control.
   `inset: -12px -8px` grows the target to 45 x 163.55 with ZERO layout change: an
   absolutely-positioned ::after is out of flow, so the flex row, the strip's height
   and all four collapsed states above are byte-for-byte what they were. Padding or a
   min-height would each have changed the strip's height and with it the 51px the
   collapsed states are pinned to. The 8px horizontal growth is the strip's own flex
   `gap`, so the target cannot reach a neighbouring one. Same device as
   `.verse-jump-cross::after` in style.css, which uses a transparent overlay for the
   same reason; that one is scoped to `(pointer: coarse)` because it sits in a dense
   sticky row, and this one is not, because the strip holds one control and a larger
   target helps a mouse too. */
.support-link::after {
  content: '';
  position: absolute;
  inset: -12px -8px;
}
.support-link:hover { text-decoration: underline; }
.support-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Inline Patreon sign-in panel (docs/patreon-login-button-spec.md §10) ----
   Rendered only below the ad slot, only for loginEnabled && !isPatron. Static flow
   element — never fixed, never a dialog, never dismissible-because-interruptive.
   The coral left rule is the ONE place Patreon's legacy brand colour appears; it is
   decorative (4.39:1 on --bg-card, no text ever sits on it). */
.auth-panel {
  max-width: 728px;
  margin: 28px auto 0;
  padding: 16px 18px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 3px solid var(--patreon-coral);
  border-radius: var(--radius);
  font-family: var(--ui-font);
}
.auth-panel-title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
}
.auth-panel-body { margin: 0 0 12px; font-size: 14px; line-height: 1.6; color: var(--text-dim); }
.auth-panel-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.auth-panel .auth-btn { font-size: 14px; padding: 0 16px; }
.auth-panel-alt {
  color: var(--accent);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.auth-panel-alt:hover { text-decoration: underline; }
.auth-panel-alt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Post-login return notice (spec §11) ---- */
.auth-notice {
  margin: 16px 0 4px;
  padding: 14px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg-card);
  font-family: var(--ui-font);
  font-size: 14px;
  line-height: 1.6;
}
.auth-notice--ok    { background: var(--success-soft); border-color: var(--success-border); }
.auth-notice--info  { background: var(--accent-soft);  border-color: var(--accent-border); }
.auth-notice--error { background: var(--error-soft);   border-color: var(--error-border); }
.auth-notice-title { margin: 0 0 4px; font-size: 15px; font-weight: 700; color: var(--text); }
.auth-notice--ok    .auth-notice-title { color: var(--success); }
.auth-notice--error .auth-notice-title { color: var(--error); }
.auth-notice-body { margin: 0; color: var(--text-dim); }
.auth-notice-actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; }
.auth-notice-actions a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.auth-notice-actions a:hover { text-decoration: underline; }
.auth-notice-actions a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Descendant-qualified, NOT !important. The rule it has to beat is
   `.auth-notice-actions a` (0,1,1) three lines up, which paints every action link
   gold; two classes is (0,2,0) and wins outright, later in source and at a higher
   specificity, with no escape hatch. The dismiss link is always the last child of
   .auth-notice-actions, so the qualification costs nothing. Same discipline as the
   .header-auth-qualified header rules in style.css — this file is otherwise
   !important-free and should stay that way. */
.auth-notice-actions .auth-notice-dismiss { color: var(--text-dim); font-weight: 600; }

/* ---- Ad slot (free tier) ----

   TWO STATES, and the whole CLS guard is the difference between them.

   COLLAPSED is the default and it is `display: none`, not a zero height. Nothing
   here reserves anything until something affirmative says a reader will actually be
   shown an ad, so a decliner, a reader who has not answered the banner, and a
   reader with JS off see no gap — and `display: none` (rather than a 0px box) means
   the <aside> does not occupy a flex/grid slot in any future container either.

   RESERVED is `html.ads-reserved`, set BEFORE FIRST PAINT by /js/ad-reserve.js
   (a render-blocking <head> script, present only on responses whose CSP actually
   permits the AdSense loader) for a reader who has already consented, and set by
   /js/consent.js at the moment a reader accepts the banner.

   Why it is inverted this way rather than sizing a `hidden` element: the reveal
   used to be public/js/ads.js clearing the `hidden` attribute, in the same
   iteration that appended the <ins>, off the back of an async fetch('/api/me').
   The box was therefore zero-height until the ad arrived and the min-heights below
   only decided how far the document jumped.

   Measured 2026-08-04, granted-vs-denied control pair, googlesyndication blocked so
   only the reveal is in the number (docs/ad-placement-plan.md §3.2 carries the full
   tables). The ad's CONTRIBUTION to CLS, before -> after:
       /v/genesis/1/1 1280x900   +0.082 -> +0.000
       /v/genesis/1/1  390x844   +0.260 -> -0.008
       /               390x844   +0.283 -> -0.102
   (The 0.2345 sitting in the control column on home-390 is a pre-existing shift in
   the support strip / header, unrelated to ads and unchanged by this.)

   Sizing the box at paint is the only mechanism that reaches the <0.1 target:
   `[hidden]` is `display: none !important` site-wide and cannot be re-expanded by a
   stylesheet, so the attribute is now emitted only where an ad can NEVER load
   (patrons, a kill-switched deploy) — see views/_ad_slot.ejs. */
.ad-slot {
  display: none;
  margin: 28px auto 0;
  max-width: 728px;
  min-height: 100px;
  text-align: center;
}
/* The one rule that expands the box. Two classes (0,2,0) beats `.ad-slot` (0,1,0),
   and it is powerless against the `hidden` attribute above it — deliberately, since
   that attribute is the patron's absolute guarantee. */
.ads-reserved .ad-slot { display: block; }
.ad-slot::before {
  content: 'Advertisement';
  display: block;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 6px;
}
.ad-slot .adsbygoogle { display: block; }

/* ---- Ad slot positions ----
   Every ad-eligible page carries TWO units as of 2026-08-04 (docs/ad-placement-plan.md
   §1, Plan A — owner executive decision superseding the zoned Plan D): `top` and
   `bottom`, the terminal unit that has always been here. views/_ad_slot.ejs emits the
   modifier and the matching [data-ad-unit] hook together.

   The TOP unit moved to the head of the content column on 2026-08-07 (placement plan
   §0.1). It used to sit directly below the site header, where its reserved box — 270px
   at phone widths — pushed the top of every page down before the reader saw anything.

   On EIGHTEEN views that means directly above `<main>` and therefore outside it, which
   is the property the Better Ads density exemption depends on (§3.1). On TWO it is
   INSIDE `<main>` — `verse` (§0.1) and `morphology` (§0.2) — each licensed by its own
   measured ratio instead. This block said "still outside `<main>`" without
   qualification; it is the placement of eighteen, not of all twenty.

   Both keep the base rule above, so the print suppression in style.css (which hides
   `.ad-slot` with `!important`) still covers them in both states, as does the
   site-wide `[hidden]` rule wherever the server emits that attribute. */

/* The TOP unit is the CLS-hostile position BY CONSTRUCTION (placement plan §3.2): it
   sits above most or all of the content column, so anything it does after paint moves
   everything below it. Its reserved box is therefore sized for the unit AdSense
   actually returns rather than for the 100px floor the terminal slot can afford to
   guess at — nothing between it and <main> absorbs the difference.

   "Above the whole content column" was written here and is now true of eighteen views
   only. On `verse` (§0.1) the unit is below the verse card and on `morphology` (§0.2)
   below the refine form, so on those two some content sits above it and only what is
   below still shifts. The reserve is unchanged and the reasoning is unweakened —
   less moves, never more — which is why this is a correction to the sentence and not
   to the number. §0.2 records that on `morphology`'s bare landing the unit renders at
   y=2,507px, below the fold.

   111px = a 728x90 leaderboard (what a responsive `auto` unit returns at this width)
   plus the "Advertisement" label the ::before rule above adds. The label is 21px,
   MEASURED, not assumed: a 15px line box at `font-size: 10px` plus its own 6px
   `margin-bottom`. It was written here as "~20px" against a 110px reserve, which is
   one pixel short — 21 + 90 = 111 — so a filled leaderboard grew the box by 1px on
   arrival. Reserving a pixel too many costs nothing; reserving one too few is a
   shift, which is the whole thing this number exists to prevent. */
.ad-slot--top {
  min-height: 111px;
}
/* NO `margin` here any more, and the reason is a fact about the markup rather than a
   taste call (2026-08-12, §0.2).

   It was `4px auto 0` — deliberately smaller than the base rule's `28px auto 0` —
   because the unit sat directly against `<main class="content">`, whose 28px top
   margin collapsed through this one's zero bottom margin, and the comment here argued
   at length that the difference made the declaration "load-bearing rather than
   decorative" on "the ten views where this unit stands alone".

   THERE ARE NO BARE UNITS LEFT. §0.2 put the top unit inside a `.ad-support-panel` on
   ALL TWENTY views, and `.ad-support-panel .ad-slot--top` below sets `margin: 0` at
   (0,2,0) — so this declaration was out-specified on every render of every page.
   Measured on `/`: panel top 256.5px, ad top 257.5px. The 1px is the wrapper's border;
   the 4px was already doing nothing.

   IT COMES BACK if a top unit is ever rendered outside the panel again. Rule P5 of
   `npm run verify:ad-placement` is what makes that a check rather than a hope: it
   fails any registered ad view whose top include is not inside exactly one
   `.ad-support-panel` with `_support`. If P5 is ever relaxed, restore `4px auto 0`
   here in the same commit — a bare unit under the base rule gains 24px of dead band
   above the content column, which is the opposite of what moving the unit was for.

   `min-height` above and the 271px override below are NOT dead and must stay: the
   panel rule sets `box-sizing: content-box` precisely so both keep meaning what they
   have always meant. They are the CLS reserve, not spacing. */

/* Phone widths: a full-width responsive unit renders TALLER here, commonly a 300x250
   medium rectangle. 271px = 250 + the same measured 21px label. Stated as a floor and
   not a promise — if Google returns something taller than a medium rectangle the page
   will still shift, and the honest fix then is to raise this number, not to shrink the
   unit. */
@media (max-width: 760px) {
  .ad-slot--top { min-height: 271px; }
}

/* The BOTTOM unit deliberately keeps the base rule unchanged — it is below the last
   content block, so a late-growing unit moves nothing above it, and the Better Ads
   density calculation does not count it at all (placement plan §3.1). No modifier
   rule of its own; the class exists so the position is addressable and so a future
   change has an obvious place to land. */

/* ---- The combined TOP-unit + support panel (2026-08-07, extended 2026-08-12) ----

   On ALL TWENTY ad views the top unit and the support strip render as ONE card: the
   ad on top, "Want to remove ads? Support Godless Engineer" — or the patron's
   "★ Ad-free — thank you for supporting!" — directly beneath it. It was ten of
   twenty until 2026-08-12 (docs/ad-placement-plan.md §0.2, which reverses a §0.1
   ruling that the asymmetry was intended); the ten that gained the strip are about,
   compare, concordance, faq, learn, learn-parse, lxx-differences, morphology,
   search, tools. Rule P5 of `npm run verify:ad-placement` asserts the pairing on all
   twenty, including — since §0.2 — a view carrying NEITHER half.

   WHERE THE CARD SITS is NOT uniform, and this header used to say "directly above
   `<main>`" as though it were. EIGHTEEN views put it above `<main>`, which is what
   keeps the ad out of the Better Ads density numerator structurally (§3.1). TWO put
   it INSIDE `<main>`, each licensed by its OWN measured density ratio rather than by
   structure: `verse` (§0.1) below the verse card — a CHILD of `<main class="content">`,
   which is a `display: grid` container, so read the `margin-top: 16px` note below
   with that in mind — and `morphology` (§0.2) below the faceted refine form, between
   two siblings inside `.morphology-search-main`, where the rule further down zeroes
   that same 16px. Neither is "directly above `<main>`" and the collapsed-state
   argument below does not depend on it being.

   THE PANEL OWNS THE CHROME, THE CHILDREN OWN THEIR PADDING, and that split is the
   whole reason a combined panel is safe here — because the ad half is ABSENT far more
   often than it is present:
     * a patron gets `hidden` from the server, and style.css's site-wide
       `[hidden] { display: none !important; }` is absolute;
     * an offline (SW-served) reader and a reader with JS off never get
       `html.ads-reserved`, so `.ad-slot { display: none }` above stands;
     * every print run hits `.ad-slot { display: none !important }` in style.css.
   A `display: none` box contributes no height, no padding and no border, so in every
   one of those the panel collapses to precisely the card the strip used to draw for
   itself — same background, border, radius and 14px/16px padding, and the same 16px
   top margin everywhere except `.morphology-search-main`, where the container owns
   the spacing and the rule below zeroes it. Re-measured 2026-08-12 on all four
   states at 390x844 and 1280x844, on both new placements and both pre-existing
   controls: 51px panel, 0px ad half, 49px strip, identical in every case (§0.2).
   There is no empty half to leave behind, and the separator rule lives on the AD's
   own bottom edge, so it goes when the ad goes. A paying supporter staring at an
   empty reserved box would be the worst outcome available here; this shape cannot
   produce one.

   No `overflow: hidden` on the panel, and the reason is now about the INJECTED frame
   rather than about backgrounds. The two authored children paint nothing into the
   rounded corners, but they are not the only things in this box: `public/js/ads.js`
   appends an <ins> that Google's tag then sizes itself, and with
   `data-full-width-responsive="true"` that tag reaches the viewport edges by applying
   NEGATIVE inline margins — which inside a bordered card in `.app`'s gutter would
   paint straight through the border and over the radius. `overflow: hidden` would
   hide that, at the price of CLIPPING a served creative (an AdSense policy hazard in
   its own right) and of cutting the `outline-offset: 2px` focus ring on
   `.support-link` two rows down. So the breakout is PREVENTED instead of clipped:
   ads.js sends `data-full-width-responsive="false"` for the `top` unit, keyed on its
   `data-ad-unit`, and the unit measures the card it is actually in. See that file. */
.ad-support-panel {
  /* The strip's own former `margin-top: 16px`, moved up to the wrapper unchanged, so
     the collapsed states are pixel-identical to what they were before the merge. */
  margin-top: 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ONE container owns its own spacing and this margin fights it (2026-08-12, §0.2).
   `.morphology-search-main` is a flex column — `gap: 20px`, `24px` above 1080px — and
   style.css already zeroes its children's margins on the stated principle that "the
   flex `gap` owns the spacing (no collapsed child margins fighting it)"; margins do
   not collapse in a flex container, so the 16px above simply ADDS. Measured at
   390x844 in the reserved state before this rule: 36px above the panel against 20px
   below it, and 40px against 24px at 1280 — the card sitting off-centre in its own
   band.

   Scoped to that container rather than written into the rule above, because the 16px
   is correct on the other nineteen and the reason differs between them. On EIGHTEEN
   the panel sits directly above `<main class="content">`, whose own top margin
   collapses through it. On `verse` (§0.1) it does NOT: there the panel is a CHILD of
   `<main class="content">`, which is `display: grid` with `gap: 20px`, so margins do
   not collapse there either and the 16px simply adds — measured 36px above / 34px
   below at both widths. That is near-balanced and fine, so `verse` is deliberately
   left alone; a `.content > .ad-support-panel { margin-top: 0 }` was tried and
   MEASURED at 20px/34px, which is worse. `morphology` is therefore not "the one view
   where it lives between two siblings" — `verse` is one too — it is the one where the
   imbalance is large enough to be worth a rule.

   Either way the 16px is the same margin the support strip carried before the merge,
   which is what makes the four collapsed states (patron / offline / no-JS / print)
   pixel-identical to the card that strip drew for itself. Do not promote this to
   `.ad-support-panel`. */
.morphology-search-main > .ad-support-panel { margin-top: 0; }

/* The ad half. Full-bleed inside the panel — `padding: 14px 0`, no horizontal padding
   at all.

   The horizontal padding was `16px` and it was NOT free, whatever the comment here
   used to say. The <ins> fills the ad half, so 16px a side took 32px off the width
   AdSense measures, and at a 768px viewport that is the difference between a 728px
   leaderboard fitting and not: MEASURED 694px where the bare `.ad-slot` gave 728px,
   under a reserve documented as "leaderboard + label". Dropping to `14px 0` restores
   it; the separator still spans the whole card because a `border-bottom` sits on the
   PADDING box, which is full-bleed either way. One residual remains and is not worth
   chasing: the panel's own 1px border leaves 726px rather than 728px at exactly 768px,
   so a leaderboard is 2px from fitting there and Google returns the next size down.
   Above ~730px of card width the cap below binds first and the width is exactly 728.

   `box-sizing: content-box` HERE ONLY, and it is load-bearing rather than tidy: the
   global reset is `border-box`, under which the vertical padding would eat 28px out of
   the reserved height and leave 83px for a 90px leaderboard — a silent shrink of the
   CLS guard, in a rule whose own comment above would still claim a 111px reserve.
   content-box keeps `min-height` meaning exactly what it has always meant (the reserve
   for the label plus the unit) and keeps the `@media (max-width: 760px)` override
   working without a second, drifting copy of the arithmetic. */
.ad-support-panel .ad-slot--top {
  box-sizing: content-box;
  max-width: none;
  margin: 0;
  padding: 14px 0;
  border-bottom: 1px solid var(--border-soft);
}
/* The 728px cap moves from the <aside> to the unit itself, so the width AdSense
   measures — and therefore the size it returns, and therefore the honesty of the
   111px/271px reserves — is what those numbers were measured against. */
.ad-support-panel .ad-slot--top .adsbygoogle {
  max-width: 728px;
  margin-left: auto;
  margin-right: auto;
}

/* The strip gives up its own card so the panel's single border is the only one the
   reader sees. This is the BASE state and it is deliberately the collapsed one: the ad
   half is display:none for a patron, an offline reader, a reader with JS off and every
   print, and in all four the panel is exactly the 51px card the strip drew for itself
   before the merge, with the strip 49px inside it. Those four are the states a
   supporter can be left looking at, so they are pinned here rather than derived from a
   rule that also has to describe a rendering ad. */
.ad-support-panel .support {
  margin-top: 0;
  background: none;
  border: 0;
  border-radius: 0;
}

/* ---- A head and a foot (2026-08-07) ----

   Everything below applies ONLY while the ad half is actually rendering, and
   `@media screen` + `html.ads-reserved` is exactly that condition, not an
   approximation of it: the class is set before first paint by /js/ad-reserve.js,
   which the server emits only on responses whose CSP permits the AdSense loader
   (views/_head.ejs), and which returns early when `navigator.onLine === false`; a
   patron, a JS-disabled reader and an offline reader therefore never carry it, and
   print is excluded by the media query rather than by a rule that has to remember to
   walk itself back. That is what keeps the four collapsed states above pixel-identical
   while this differentiates the two halves.

   WHY differentiate them. Both halves had identical padding, identical background, and
   an internal separator drawn in the same `--border` token as the outer edge, so the
   card read as one undifferentiated two-row table — an ad with a gold call-to-action
   apparently inside it. Reviewers judge pixels, not the DOM, and a publisher's own CTA
   that looks like part of the creative is the reading to avoid. The strip is recessed
   instead: a darker surface, tighter padding, and a separator in `--border-soft`. */
@media screen {
  .ads-reserved .ad-support-panel .support {
    padding: 10px 16px;
    background: var(--bg-raised);
    /* Its own bottom radius, because the panel has no `overflow: hidden` (see the
       block header) — a square-cornered background would otherwise paint over the
       wrapper's rounded bottom edge. -1px for the wrapper's own border. */
    border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px);
  }

  /* ---- The frame exists only around content ----

     A unit AdSense could not fill leaves the reserve standing and empty, and a
     bordered, radiused ~299px empty box wearing the same chrome as the verse card
     below it reads as a module that failed to render. Before the merge an unfilled
     unit was invisible whitespace, which is the honest appearance for one.

     Google's tag reports the outcome on its own <ins> as
     `data-ad-status="filled"|"unfilled"` (documented at
     support.google.com/adsense/answer/10762946), so the chrome can key off it.

     EVERY DECLARATION HERE IS PAINT-ONLY — colours and a radius, never a size, a
     display or a padding. That is deliberate and it is the reason this does NOT use
     Google's own suggested `display: none` collapse: this unit sits in the first fold
     on a phone, so collapsing 299px of it would shift the whole page up, which is the
     same defect the reserve exists to prevent, pointed the other way. (Google declines
     to collapse in-viewport units for exactly this reason.) The reserve stays; only
     the frame goes.

     `:has()` is used here where style.css twice declined it, and the difference is
     what happens without it: those two were load-bearing (a sticky-offset token, a
     line reserve) and would have broken the layout, whereas this one is cosmetic and
     its fallback is the rendering we have today. */
  .ad-support-panel:has(.ad-slot--top ins.adsbygoogle[data-ad-status="unfilled"]) {
    background: none;
    border-color: transparent;
  }
  .ad-support-panel:has(.ad-slot--top ins.adsbygoogle[data-ad-status="unfilled"])
    .ad-slot--top {
    border-bottom-color: transparent;
  }
  /* The strip keeps its recessed padding (changing it would be a reflow) and takes a
     full radius, so what is left is an empty gap where the ad would have been and a
     deliberate-looking note beneath it. */
  .ad-support-panel:has(.ad-slot--top ins.adsbygoogle[data-ad-status="unfilled"])
    .support {
    border-radius: var(--radius);
  }
}

/* ---- Consent banner: DELETED 2026-08-05 ----
   The `.consent`, `.consent-text`, `.consent-link`, `.consent-actions`,
   `.consent-btn`, `.consent-accept` and `.consent-decline` rules lived here and
   styled a banner public/js/consent.js built with createElement. All of it went
   with the banner. Google's Privacy & messaging dialog renders in a cross-origin
   iframe governed by Google's own CSP and stylesheet, so there is nothing left
   for this file to style and no rule here can reach it.

   Three places in style.css named `.consent` and all three were updated in the
   same change: the print-suppression selector list, the `.basket-tray` z-index
   note (45 was chosen to sit below this banner's 50), and the `env()`-longhand
   idiom comment that cited `.consent` as its precedent. A selector kept for a
   class nothing emits is dead CSS, and a comment pointing at a deleted rule is
   worse than none. */
