/* =====================================================================================
   CamperBay — advertising BANDS + house promo MODULES.
   Plan + every measurement: camperplaats/BANNERS-PLAN.md · rendered by system/cp_banner.php

   THREE THINGS THIS FILE IS RESPONSIBLE FOR:

   1. DESKTOP ONLY. Every band dies at <=768px — advertising AND modules. Per Google's own
      documentation, hiding a unit with a media query means "no ad request is made and no ad
      is shown", so this is the policy-safe way to keep advertising out of the mobile app
      design, and it needs no user-agent sniffing (both master prompts forbid that).

   2. NO EMPTY SPACE. A band is always 100% of its container. In `mixed` the Google unit is a
      grid track of `auto` — exactly its IAB width — and the module takes `1fr`, i.e. all the
      rest. Flush for any container width and any ad size, with no lookup table.

   3. THE FORMAT IS CHOSEN BY THE BAND'S OWN WIDTH, not the viewport, because the same
      component sits in a 1232px homepage band, an 862px results column, a 380px VIP rail and
      a 300px store aside, each of which narrows differently. All widths below are MEASURED.

   ⚠ .cp-band carries ZERO horizontal padding: container-type:inline-size measures the content
     box and the seller aside is exactly 300px — one pixel of padding would drop it under the
     300 threshold and silently kill the only 300-wide rail in the portal.

   ⚠ --cp-* is declared per-plugin in this codebase (results.css:8, listing.css, store.css,
     account.css), so a page loading none of them never gets the tokens. This file carries its
     own :root block — the exact bug that cost the /rules/ page a full rebuild.
   ===================================================================================== */

:root{
  --cp-ink:#15211C; --cp-muted:#5C6A62; --cp-line:#CBD4CC; --cp-card:#fff; --cp-mist:#E2E7E1;
  --cp-pine:#1F3D30; --cp-pine2:#2E5A45; --cp-fern:#4E8163;
  --cp-amber:#E08A2C; --cp-amber-soft:#F2C078; --cp-amber-ink:#3a2408;
}

/* -------------------------------------------------------------------------------------
   THE BAND
   ------------------------------------------------------------------------------------- */
.cp-band{
  container-type:inline-size;
  display:block;
  margin:24px 0;
  padding:0;                 /* see the 300px note above — never add side padding */
}
.cp-band--single{ text-align:center; line-height:0; }

/* mixed: [ ad at its exact size ] [ everything that is left ] */
.cp-band--mixed{ display:grid; grid-template-columns:auto minmax(0,1fr); gap:16px; align-items:stretch; }
.cp-band--mixed[data-side="right"]{ grid-template-columns:minmax(0,1fr) auto; }
/* two house modules share the band 70/30 */
.cp-band--split{ display:grid; grid-template-columns:7fr 3fr; gap:16px; align-items:stretch; }

.cp-band__ad{ display:flex; flex-direction:column; align-items:center; justify-content:center; }
.cp-band__mod{ display:flex; min-width:0; container-type:inline-size; }
/* in a mixed band the row height is set by the ad, so the module's height is definite and can
   be queried — that is how a 90px strip drops its subtitle while a 250px panel keeps it */
.cp-band--mixed .cp-band__mod{ container-type:size; }

/* Google permits only "Advertisements" / "Sponsored links"; cp_ad_label() emits the plain
   per-language equivalent. ⚠ It belongs to the AD CELL only — a house module is the portal's
   own promotion, and labelling that as an advertisement would be both untrue and a reason for
   readers to skip it. It starts hidden and each ladder step switches it on for the sizes that
   step enables, so a band whose creatives are all too wide never shows a lone label. */
.cp-ad__lbl{
  display:none;
  font:600 10px/1 Inter,system-ui,sans-serif;
  letter-spacing:.09em; text-transform:uppercase;
  color:var(--cp-muted,#5C6A62); margin:0 0 6px;
}
.cp-ad__b,.cp-ad__ins{ display:none; margin:0 auto; }
.cp-ad__b{ border-radius:10px; overflow:hidden; container-type:size; }
/* ⚠⚠ MEASURED DEFECT (DAY 38): a COMPOSED creative sizes its type in cqi/cqh and drops its
   sub-line through @container (max-height:...), but the only container above it was .cp-band,
   which is container-type:INLINE-SIZE — height queries never applied and cqh fell back to the
   viewport. Result on a real 468x60 strip: a 30px headline and 96px of content inside a 60px
   box. The creative box has an explicit width AND height from the format ladder, so it can be
   the size container itself. */
.cp-ad__b img{ display:block; width:100%; height:100%; object-fit:contain; border-radius:10px; }

/* -------------------------------------------------------------------------------------
   FORMAT LADDER — smallest to largest, so source order leaves the widest that fits.
   Each threshold is the format width + 16px breathing room, EXCEPT the 300 step.
   Every hide-rule is gated on [data-fmt~="..."], i.e. a band only drops a smaller size when
   it really owns the bigger one — an unconditional hide blanked the VIP rail, which offers
   300x250 and no 336x280.

   ⭐⭐ DAY-39 — THE TWO LADDERS ARE NO LONGER DISJOINT, AND THAT IS THE POINT.
   Ladder A means "the ad is ALONE in this band" and now applies to EVERY band; ladder B means
   "the ad SHARES the band with a house module" and overrides ladder A above its own, higher
   thresholds. A band therefore degrades on its own: ad + module while both fit (ladder B),
   the ad alone once the module no longer fits (ladder A), and — below even that — the module
   alone. Which of the three applies is decided per band by `data-step`, see "THE THREE ZONES".
   Before this, a `mixed` band was locked to ladder B and rendered NOTHING below its threshold:
   measured live on 2026-08-02, `lrp_side` (284px) / `vip_side` (380) / `store_side` (300) /
   `rail_left` / `rail_right` (176) were 0px tall — no paid creative AND no house module.

   ⚠⚠ DAY-39 — WHY NO HIDE-RULE MAY USE `:not(.cp-ad__f<size>)`.
   `[data-fmt~="728x90"] .cp-ad__b:not(.cp-ad__f728x90)` is (0,4,0) and out-specified the
   `.cp-band--mixed .cp-ad__f970x250{display:block}` of the NEXT step up (0,2,0), so a band
   offering both 970 and 728 hid the 728 at its own threshold and the 970 at the 970 threshold
   and ended up showing neither. Measured live: `/` at 1440 and 1600 rendered `home_mid` and
   `home_low` as a 1232x16 strip carrying only the word "Advertentie". Every hide-rule now
   names the SMALLER formats explicitly, so a larger size is never hidden by a smaller step.
   ------------------------------------------------------------------------------------- */

/* ---------- ladder A: the ad is ALONE in the band ---------- */
@container (min-width:176px){
  .cp-band .cp-ad__f160x600{display:block;width:160px;height:600px}
  .cp-band[data-fmt~="160x600"] .cp-ad__ins{display:block;width:160px;height:600px}
  .cp-band[data-fmt~="160x600"] .cp-ad__lbl{display:block}
}
@container (min-width:300px){
  .cp-band[data-fmt~="300x250"] .cp-ad__f160x600,
  .cp-band[data-fmt~="300x600"] .cp-ad__f160x600{display:none}
  .cp-band .cp-ad__f300x250{display:block;width:300px;height:250px}
  .cp-band .cp-ad__f300x600{display:block;width:300px;height:600px}
  .cp-band[data-fmt~="300x250"] .cp-ad__ins{display:block;width:300px;height:250px}
  .cp-band[data-fmt~="300x600"] .cp-ad__ins{display:block;width:300px;height:600px}
  .cp-band[data-fmt~="300x250"] .cp-ad__lbl,
  .cp-band[data-fmt~="300x600"] .cp-ad__lbl{display:block}
}
@container (min-width:352px){
  .cp-band[data-fmt~="336x280"] .cp-ad__f300x250,
  .cp-band[data-fmt~="336x280"] .cp-ad__f300x600,
  .cp-band[data-fmt~="336x280"] .cp-ad__f160x600{display:none}
  .cp-band .cp-ad__f336x280{display:block;width:336px;height:280px}
  .cp-band[data-fmt~="336x280"] .cp-ad__ins{display:block;width:336px;height:280px}
  .cp-band[data-fmt~="336x280"] .cp-ad__lbl{display:block}
}
@container (min-width:484px){
  .cp-band[data-fmt~="468x60"] .cp-ad__f336x280,
  .cp-band[data-fmt~="468x60"] .cp-ad__f300x250,
  .cp-band[data-fmt~="468x60"] .cp-ad__f300x600,
  .cp-band[data-fmt~="468x60"] .cp-ad__f160x600{display:none}
  .cp-band .cp-ad__f468x60{display:block;width:468px;height:60px}
  .cp-band[data-fmt~="468x60"] .cp-ad__ins{display:block;width:468px;height:60px}
  .cp-band[data-fmt~="468x60"] .cp-ad__lbl{display:block}
}
@container (min-width:744px){
  .cp-band[data-fmt~="728x90"] .cp-ad__f468x60,
  .cp-band[data-fmt~="728x90"] .cp-ad__f336x280,
  .cp-band[data-fmt~="728x90"] .cp-ad__f300x250,
  .cp-band[data-fmt~="728x90"] .cp-ad__f300x600,
  .cp-band[data-fmt~="728x90"] .cp-ad__f160x600{display:none}
  .cp-band .cp-ad__f728x90{display:block;width:728px;height:90px}
  .cp-band[data-fmt~="728x90"] .cp-ad__ins{display:block;width:728px;height:90px}
  .cp-band[data-fmt~="728x90"] .cp-ad__lbl{display:block}
}
@container (min-width:986px){
  .cp-band[data-fmt~="970x90"]  .cp-ad__f728x90,
  .cp-band[data-fmt~="970x90"]  .cp-ad__f468x60,
  .cp-band[data-fmt~="970x90"]  .cp-ad__f336x280,
  .cp-band[data-fmt~="970x90"]  .cp-ad__f300x250,
  .cp-band[data-fmt~="970x90"]  .cp-ad__f300x600,
  .cp-band[data-fmt~="970x90"]  .cp-ad__f160x600,
  .cp-band[data-fmt~="970x250"] .cp-ad__f728x90,
  .cp-band[data-fmt~="970x250"] .cp-ad__f468x60,
  .cp-band[data-fmt~="970x250"] .cp-ad__f336x280,
  .cp-band[data-fmt~="970x250"] .cp-ad__f300x250,
  .cp-band[data-fmt~="970x250"] .cp-ad__f300x600,
  .cp-band[data-fmt~="970x250"] .cp-ad__f160x600{display:none}
  .cp-band .cp-ad__f970x90{display:block;width:970px;height:90px}
  .cp-band .cp-ad__f970x250{display:block;width:970px;height:250px}
  .cp-band[data-fmt~="970x90"]  .cp-ad__ins{display:block;width:970px;height:90px}
  .cp-band[data-fmt~="970x250"] .cp-ad__ins{display:block;width:970px;height:250px}
  .cp-band[data-fmt~="970x90"] .cp-ad__lbl,
  .cp-band[data-fmt~="970x250"] .cp-ad__lbl{display:block}
}

/* ---------- ladder A, floor: the house module covers what a SOLD creative cannot ----------
   A creative is display:none until its own threshold above matches, so a slot that sold only
   its widest format would render an EMPTY band in a narrower container — and the priority
   ladder in cp_banner_slot_html() has already dropped the module. MEASURED before this block
   existed: a 728x90 sold into `lrp_top` gave 0 visible creatives, 0 modules and a 0px band at
   a 1024 viewport (container 676px). PHP therefore appends the module as `.cp-band__mod--fb`
   and stamps the band with `data-fbmin` = the threshold at which the creative takes over.
   ⚠ `data-fbmin` values MUST equal the @container thresholds above; cp_ad_reveal_at() in
     system/cp_banner.php holds the same map and test_banners.php compares the two files.
   ⚠ The band is `--single` here (text-align:center; line-height:0) — a module needs both back. */
.cp-band__mod--fb{ line-height:normal; text-align:left; }
@container (min-width:176px){ .cp-band[data-fbmin="176"] .cp-band__mod--fb{display:none} }
@container (min-width:300px){ .cp-band[data-fbmin="300"] .cp-band__mod--fb{display:none} }
@container (min-width:352px){ .cp-band[data-fbmin="352"] .cp-band__mod--fb{display:none} }
@container (min-width:484px){ .cp-band[data-fbmin="484"] .cp-band__mod--fb{display:none} }
@container (min-width:744px){ .cp-band[data-fbmin="744"] .cp-band__mod--fb{display:none} }
@container (min-width:986px){ .cp-band[data-fbmin="986"] .cp-band__mod--fb{display:none} }

/* ---------- ladder B: the ad SHARES the band with a module — every threshold + 216px ----------
   216 = the 16px gap + the 200px minimum below which a module is not worth rendering. Ladder B
   is LATER IN THE FILE THAN LADDER A ON PURPOSE: both are written at the same specificity
   (0,3,0), so wherever both match, ladder B wins and the ad steps DOWN to leave the module its
   room. Where ladder B does not match — i.e. below its own threshold — ladder A is left in
   charge and the ad has the band to itself. That is the whole degradation model.
   ⚠ Each step hides EVERY other format explicitly, larger ones included: without the larger
     ones ladder A's own show-rule would leak a second, wider creative into the same cell.
   ⚠ Each show-rule is gated on [data-fmt~="..."] so it ties with ladder A's hide-rules on
     specificity and wins on source order. An ungated show (0,2,0) silently loses. */
@container (min-width:392px){
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f970x250{display:none}
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__f160x600{display:block;width:160px;height:600px}
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__ins{display:block;width:160px;height:600px}
  .cp-band--mixed[data-fmt~="160x600"] .cp-ad__lbl{display:block}
}
@container (min-width:516px){
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f970x250,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f970x250{display:none}
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__f300x250{display:block;width:300px;height:250px}
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__f300x600{display:block;width:300px;height:600px}
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__ins{display:block;width:300px;height:250px}
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__ins{display:block;width:300px;height:600px}
  .cp-band--mixed[data-fmt~="300x250"] .cp-ad__lbl,
  .cp-band--mixed[data-fmt~="300x600"] .cp-ad__lbl{display:block}
}
@container (min-width:568px){
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f970x250{display:none}
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__f336x280{display:block;width:336px;height:280px}
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__ins{display:block;width:336px;height:280px}
  .cp-band--mixed[data-fmt~="336x280"] .cp-ad__lbl{display:block}
}
@container (min-width:700px){
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f970x250{display:none}
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__f468x60{display:block;width:468px;height:60px}
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__ins{display:block;width:468px;height:60px}
  .cp-band--mixed[data-fmt~="468x60"] .cp-ad__lbl{display:block}
}
@container (min-width:960px){
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f970x90,
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f970x250{display:none}
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__f728x90{display:block;width:728px;height:90px}
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__ins{display:block;width:728px;height:90px}
  .cp-band--mixed[data-fmt~="728x90"] .cp-ad__lbl{display:block}
}
@container (min-width:1202px){
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f970x250,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f160x600,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f300x250,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f300x600,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f336x280,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f468x60,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f728x90,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f970x90{display:none}
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__f970x90{display:block;width:970px;height:90px}
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__f970x250{display:block;width:970px;height:250px}
  .cp-band--mixed[data-fmt~="970x90"]  .cp-ad__ins{display:block;width:970px;height:90px}
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__ins{display:block;width:970px;height:250px}
  .cp-band--mixed[data-fmt~="970x90"] .cp-ad__lbl,
  .cp-band--mixed[data-fmt~="970x250"] .cp-ad__lbl{display:block}
}

/* =======================================================================================
   ⭐⭐⭐ THE THREE ZONES OF AN AD-BEARING BAND (DAY 39)

   The owner's rule, in his words: "the ad takes its exact size and the house module fills
   everything that is left — there is no empty space; and the module on its own fills the whole
   band." A band therefore has three states, decided by its OWN width, never by the viewport:

     zone 3   cq >= step+216   ad at its exact IAB size  +  16  +  module on 1fr   (ladder B)
     zone 2   step <= cq < +216  the module no longer fits, so the AD OWNS THE BAND (ladder A)
     zone 1   cq < step          the ad cannot be shown at all, so the MODULE OWNS THE BAND

   `data-step` is written by cp_ad_band_step() in system/cp_banner.php and is the ladder-A
   threshold of the SMALLEST format the band can actually show. zone 3 is the default (the rules
   at the top of this file); zones 1 and 2 are the overrides below.

   ⚠⚠ A @container rule can only style DESCENDANTS of the container it queries, never the
      container itself — that is why every rule here targets .cp-band__ad / .cp-band__mod and
      the band's own `display:grid` and `gap` are never touched.
   ⚠⚠ `.cp-band__mod` is `container-type:size` inside a mixed band, i.e. SIZE-CONTAINED: its
      height comes from the grid row, and the row is as tall as the ad cell. With an empty ad
      cell that is ZERO — which is exactly how five live positions ended up 0px tall. Zone 1
      therefore hands the module back `container-type:inline-size` so it sizes itself again.
   ⚠ `grid-column:1/-1` spans the gutter as well as both tracks, so the surviving cell really
     is the full width of the band — no 16px sliver left behind.
   ======================================================================================= */
@container (max-width:175px){
  .cp-band--mixed[data-step="176"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="176"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:176px) and (max-width:391px){
  .cp-band--mixed[data-step="176"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="176"] .cp-band__ad{grid-column:1/-1}
}
@container (max-width:299px){
  .cp-band--mixed[data-step="300"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="300"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:300px) and (max-width:515px){
  .cp-band--mixed[data-step="300"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="300"] .cp-band__ad{grid-column:1/-1}
}
@container (max-width:351px){
  .cp-band--mixed[data-step="352"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="352"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:352px) and (max-width:567px){
  .cp-band--mixed[data-step="352"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="352"] .cp-band__ad{grid-column:1/-1}
}
@container (max-width:483px){
  .cp-band--mixed[data-step="484"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="484"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:484px) and (max-width:699px){
  .cp-band--mixed[data-step="484"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="484"] .cp-band__ad{grid-column:1/-1}
}
@container (max-width:743px){
  .cp-band--mixed[data-step="744"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="744"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:744px) and (max-width:959px){
  .cp-band--mixed[data-step="744"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="744"] .cp-band__ad{grid-column:1/-1}
}
@container (max-width:985px){
  .cp-band--mixed[data-step="986"] .cp-band__ad{display:none}
  .cp-band--mixed[data-step="986"] .cp-band__mod{grid-column:1/-1;container-type:inline-size;min-height:148px}
}
@container (min-width:986px) and (max-width:1201px){
  .cp-band--mixed[data-step="986"] .cp-band__mod{display:none}
  .cp-band--mixed[data-step="986"] .cp-band__ad{grid-column:1/-1}
}

/* -------------------------------------------------------------------------------------
   HOUSE MODULE — the portal's own promotion. Built entirely from the real tokens, so it can
   never drift off-palette, and it is fluid, so it fills whatever the ad leaves behind.
   ------------------------------------------------------------------------------------- */
.cp-mod{
  position:relative; flex:1 1 auto; min-width:0;
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:16px 18px; min-height:88px;
  border-radius:14px; border-top:3px solid var(--cp-amber,#E08A2C); overflow:hidden;
  background:linear-gradient(160deg,#24483A 0%,var(--cp-pine,#1F3D30) 55%,#162C22 100%);
  color:#fff; line-height:normal; text-align:left;
}
.cp-mod__txt{ display:flex; flex-direction:column; gap:7px; min-width:0; flex:1 1 auto; }
.cp-mod__kicker{
  display:inline-flex; align-items:center; gap:6px;
  font:700 10.5px/1 Inter,system-ui,sans-serif; letter-spacing:.09em; text-transform:uppercase;
  color:var(--cp-amber-soft,#F2C078);
}
.cp-mod__title{
  margin:0; color:#fff;
  font-family:Archivo,system-ui,sans-serif; font-weight:700; line-height:1.16;
  letter-spacing:-.01em;
  /* measured: at 3.2cqi a 346px cell fell to the 15px floor and read as body copy. 5.2cqi
     gives a narrow cell ~18px and still caps a 1222px band at 27px. */
  font-size:clamp(16px,5.2cqi,27px);
  display:-webkit-box; -webkit-line-clamp:2; line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
/* the reference artwork accents a statement headline's closing full stop in amber; PHP only
   wraps a REAL trailing period, so nothing is added to a question or a bare phrase */
.cp-mod__dot{ color:var(--cp-amber,#E08A2C); }
.cp-mod__sub{
  margin:0; font:400 13px/1.45 Inter,system-ui,sans-serif; color:rgba(255,255,255,.74);
  display:-webkit-box; -webkit-line-clamp:2; line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.cp-mod__cta{
  align-self:flex-start; display:inline-flex; align-items:center; gap:7px; white-space:nowrap;
  margin-top:2px; padding:9px 14px; border-radius:9px; text-decoration:none;
  background:var(--cp-amber,#E08A2C); color:var(--cp-amber-ink,#3a2408);
  font:700 13px/1 Inter,system-ui,sans-serif;
}
.cp-mod__cta:hover{ background:var(--cp-amber-soft,#F2C078); color:var(--cp-amber-ink,#3a2408); }
/* whole-card target — only when there is no listing artwork, whose thumbs are links themselves */
.cp-mod--linked .cp-mod__cta::after{ content:""; position:absolute; inset:0; }

/* real listings as artwork */
.cp-mod__art{ display:flex; gap:8px; flex:0 0 auto; }
.cp-mod__it{ position:relative; display:block; width:104px; border-radius:10px; overflow:hidden; background:#0F150E; }
.cp-mod__it img{ display:block; width:100%; aspect-ratio:4/3; object-fit:cover; }
.cp-mod__ip{
  position:absolute; left:0; right:0; bottom:0; padding:14px 7px 5px;
  font:700 11.5px/1.2 "JetBrains Mono",ui-monospace,monospace; color:#fff;
  background:linear-gradient(180deg,rgba(15,21,14,0),rgba(15,21,14,.88));
}
.cp-mod__art--img{ align-self:stretch; display:flex; }
.cp-mod__art--img img{ display:block; height:100%; width:auto; max-width:46cqi; object-fit:cover; border-radius:10px; }

/* -------------------------------------------------------------------------------------
   v2 ARTWORK (migration 071)

   PHOTO — one REAL advert from the promoted category, bleeding to the module's own edge.
   Negative margins cancel the module padding; the module already clips with overflow:hidden
   and its own radius, so the picture inherits the rounded corner for free. The overlay fades
   the photo into the pine so the headline never sits on busy pixels.
   ------------------------------------------------------------------------------------- */
.cp-mod__art--photo{
  position:relative; display:block; align-self:stretch;
  /* ⚠ flex-shrink MUST be 0. With `0 1 44%` the text column (flex:1 1 auto) won the space and
     the photo collapsed to 67px in the homepage split band — measured. The text can shrink
     instead: it carries min-width:0 and line clamps. */
  flex:0 0 44%; min-width:0; margin:-16px -18px -16px 0;
  background:#152A21;
}
.cp-mod__art--photo img{ display:block; width:100%; height:100%; object-fit:cover; }
.cp-mod__art--photo::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(90deg,#1F3D30 0%,rgba(31,61,48,.68) 30%,rgba(31,61,48,0) 74%);
}

/* GLYPH — large line-art for modules that promote an idea, where no real listing photo could
   honestly illustrate the message. Amber outline, same stroke language as the small icons. */
.cp-mod__art--glyph{
  align-self:center; flex:0 0 auto; display:flex; align-items:center;
  color:var(--cp-amber,#E08A2C); opacity:.9;
}
.cp-mod__glyph{ display:block; width:clamp(62px,13cqi,124px); height:auto; }

/* -------------------------------------------------------------------------------------
   FEATURE ROWS and CHIPS — the reference artwork's three-point column and pill strip.
   Content comes from cp_ad_module_text.feats (per-language JSON), so both are translatable.
   ------------------------------------------------------------------------------------- */
.cp-mod__feats{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:11px;
  flex:0 1 auto; min-width:0; max-width:330px;
}
.cp-mod__feat{ display:flex; align-items:flex-start; gap:10px; min-width:0; }
.cp-mod__feati{
  flex:0 0 auto; display:grid; place-items:center; width:31px; height:31px;
  border-radius:9px; background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.14); color:var(--cp-amber-soft,#F2C078);
}
.cp-mod__featx{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.cp-mod__featx b{ font:700 13px/1.25 Inter,system-ui,sans-serif; color:#fff; }
.cp-mod__featx i{
  font:400 12px/1.35 Inter,system-ui,sans-serif; font-style:normal;
  color:rgba(255,255,255,.66);
}

.cp-mod__chips{ display:flex; flex-wrap:wrap; gap:8px; margin-top:3px; }
.cp-mod__chip{
  display:inline-flex; align-items:center; gap:6px; padding:6px 11px;
  border-radius:999px; background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.14);
  font:600 11.5px/1 Inter,system-ui,sans-serif; color:rgba(255,255,255,.87);
  white-space:nowrap;
}
.cp-mod__chip svg{ color:var(--cp-amber-soft,#F2C078); flex:0 0 auto; }

/* A module carrying artwork or a feature column needs vertical room to read as a promo rather
   than a strip. ⚠ Scoped away from `mixed`, where the row height is the AD's height and a
   min-height would push the module out of its cell. */
.cp-band:not(.cp-band--mixed) .cp-mod--photo,
.cp-band:not(.cp-band--mixed) .cp-mod--rows,
.cp-band:not(.cp-band--mixed) .cp-mod--glyph{ min-height:148px; }

/* -------------------------------------------------------------------------------------
   ⭐ FULL-BLEED BANNER ARTWORK (migration 072)

   Purpose-made artwork supplied by the owner: 3:1, subject on the RIGHT, left side left
   deliberately empty for the headline. It is the whole module, so the side-art slots are not
   rendered at all when it is present.

   ⚠ The baked-in orange border and rounded corners were CROPPED OFF the source files. They had
   to be: object-fit slices the image the moment the band is not exactly 3:1, which shaves a
   baked border to a different thickness on every surface. The border and radius below are the
   real, consistent ones.
   ------------------------------------------------------------------------------------- */
.cp-mod__bg{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%;
  object-fit:cover;
  /* the subject lives on the right of the artwork — never let a crop eat it */
  object-position:right center;
}
/* Scrim. The artwork is already dark on the left, so this only has to guarantee the headline's
   contrast when a crop pushes brighter pixels leftwards (the sunset and forest plates do). */
.cp-mod--bg::before{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(90deg,
    rgba(19,38,30,.94) 0%, rgba(19,38,30,.86) 30%,
    rgba(19,38,30,.42) 58%, rgba(19,38,30,0) 80%);
}
.cp-mod--bg > .cp-mod__txt,
.cp-mod--bg > .cp-mod__feats{ position:relative; z-index:2; }

/* Height follows the artwork instead of the text. 3:1 plates want room; the cap keeps a
   1222px homepage band from becoming a 407px hero. ⚠ non-mixed only — in a mixed band the row
   height is the AD's and the module must fit inside it. */
/* ⚠ clamp, not min(): a bare min(30cqi,272px) resolved to 85px in the 284px filter rail and
   undercut the 148px baseline set above, squashing that module to 109px. */
.cp-band:not(.cp-band--mixed) .cp-mod--bg{ min-height:clamp(148px, 30cqi, 272px); }
/* with artwork behind it the text column must not stretch across the whole plate */
.cp-mod--bg .cp-mod__txt{ max-width:min(62%, 560px); }
.cp-mod--bg .cp-mod__feats{ max-width:40%; }

/* ⚠ NARROW CELLS INVERT THE RELATIONSHIP. These plates put their subject in the right ~45%, so
   in a 346px cell the subject is most of the cell and the headline ends up sitting on top of it
   — measured legible, but it LOOKED wrong, which is the thing geometry cannot tell you. Below
   470px the artwork stops being the message and becomes a texture: the scrim goes nearly solid
   and the text takes the full width back. */
@container (max-width:470px){
  .cp-mod--bg::before{
    background:linear-gradient(90deg,
      rgba(19,38,30,.97) 0%, rgba(19,38,30,.94) 55%, rgba(19,38,30,.72) 100%);
  }
  .cp-mod--bg .cp-mod__txt{ max-width:100%; }
}
/* the middle band still wants the subject visible, just not under the words */
@container (min-width:471px) and (max-width:660px){
  .cp-mod--bg .cp-mod__txt{ max-width:70%; }
  .cp-mod--bg::before{
    background:linear-gradient(90deg,
      rgba(19,38,30,.95) 0%, rgba(19,38,30,.90) 42%, rgba(19,38,30,.50) 68%, rgba(19,38,30,0) 88%);
  }
}

/* ---- width variants (the cell is its own container) ---- */
/* thumb budget: 3x104 + 2x8 = 328px of artwork, plus 36 padding + 16 gap, leaves the text
   ~280px to stay readable -> 3 thumbs from ~700, 2 from ~560 (measured, not guessed) */
@container (max-width:700px){ .cp-mod__it:nth-child(3){ display:none } }
@container (max-width:560px){ .cp-mod__it:nth-child(2){ display:none } }
/* v2 drop order, widest first: a module sheds DECORATION before INFORMATION. The glyph beside
   a feature column goes first (it says nothing), then the feature column, then the chips, and
   the photo last because it is a real advert and a link. */
@container (max-width:820px){ .cp-mod--rows .cp-mod__art--glyph{ display:none } }
/* when the feature column goes, the illustration has room again and comes back */
@container (max-width:660px){ .cp-mod__feats{ display:none } .cp-mod--rows .cp-mod__art--glyph{ display:flex } }
@container (max-width:470px){ .cp-mod__chips{ display:none } }
@container (max-width:430px){ .cp-mod__art{ display:none } }
@container (max-width:300px){
  .cp-mod{ padding:13px 14px; gap:10px; }
  .cp-mod__sub{ display:none }
  .cp-mod__cta{ padding:8px 11px; font-size:12.5px }
}
@container (max-width:210px){
  .cp-mod__kicker{ display:none }
  .cp-mod__title{ -webkit-line-clamp:3; line-clamp:3 }
}
/* the photo has to give width back before it squeezes the headline out of readability */
@container (max-width:620px){ .cp-mod__art--photo{ flex-basis:36% } }

/* ⚠ MUST STAY LAST among the width blocks — it overrides the max-width:430px art hide.
   A glyph is the module's ONLY illustration, so unlike a photo (a real advert with its own
   link) or the thumbnail strip it survives into a narrow cell and simply becomes a compact
   badge, which is exactly what the reference artwork does in its narrow panels. */
@container (max-width:430px){
  .cp-mod__art--glyph{ display:flex }
  .cp-mod__glyph{ width:54px }
  /* the 430 threshold belongs to the THUMBNAIL strip, whose tiles are a fixed 104px and stop
     fitting there. A bleed photo is percentage-width and still reads at a third of a 360px
     cell — measured in the homepage split band, where hiding it left the right half bare. */
  .cp-mod__art--photo{ display:block; flex-basis:34% }
}
@container (max-width:258px){ .cp-mod__art--glyph{ display:none } }
@container (max-width:320px){ .cp-mod__art--photo{ display:none } }

/* ---- height variants (only inside a mixed band, where the height is definite) ---- */
@container (max-height:130px){
  .cp-mod{ padding:10px 14px; min-height:0; }
  .cp-mod__sub{ display:none }
  .cp-mod__txt{ gap:5px; flex-direction:row; align-items:center; gap:12px; }
  .cp-mod__cta{ margin-top:0 }
  .cp-mod__art{ display:none }
  /* a one-line strip has no room for either feature form */
  .cp-mod__feats,.cp-mod__chips{ display:none }
}
@container (max-height:74px){
  .cp-mod{ padding:8px 12px }
  .cp-mod__kicker{ display:none }
  .cp-mod__title{ -webkit-line-clamp:1; line-clamp:1; font-size:14px }
}

/* -------------------------------------------------------------------------------------
   VIEWPORT GATES — a container query cannot see that a COLUMN has stopped existing.
   .prod collapses to one column at <=980 and .shell / .gr-wrap at <=900 (measured), after
   which a "side" band would sit marooned in the middle of a full-width row.
   ------------------------------------------------------------------------------------- */
@media (max-width:980px){ .cp-band--vip_side  {display:none!important} }
@media (max-width:900px){ .cp-band--store_side{display:none!important} }

/* LRP filter rail. `.mp-lrp` is a 2-column grid (mp.css) with NO explicit placement on its two
   real children — the filter panel and `.mp-results` — so both are AUTO-placed.

   ⚠⚠ REGRESSION FIXED HERE (found by the owner, DAY 30). Pinning this third child to column 1
   with `grid-column:1` alone was not enough: auto-placement then puts `.mp-results` in ROW 2,
   leaving row 1 column 2 empty and pushing the whole results list BELOW the filter panel.
   Measured before the fix: `/cat/diensten/verhuur/` results at y=741; `/cat/caravans/`, whose
   panel is 3938px tall, results at **y=3962** — the entire list was a screen-height dump below
   the sidebar. It was invisible on DAY 29 only because every slot shipped `mode='off'`.
   ⚠ AND IT PASSED MY OWN CHECKS, because I measured `grid-template-columns` (still a correct
   `284px 862px`) and the card count — never the vertical POSITION. Measure geometry, not just
   dimensions.

   The cure is to place BOTH the band and the results explicitly, so nothing is auto-placed.
   ⚠ Desktop only: ≤768 `.mp-lrp` collapses to a single column, and forcing `grid-column:2`
   there would invent an implicit second column. */
.cp-band--lrp_side{ margin:16px 0 0; }
@media (min-width:769px){
  .mp-lrp > .cp-band--lrp_side{ grid-column:1; grid-row:2; }
  .mp-lrp > .mp-results        { grid-column:2; grid-row:1; }

  /* ⚠⚠ SECOND HALF OF THE SAME REGRESSION, reported by the owner on DAY 43 — and the comment
     above was describing an intent the grid did not deliver. "Row 2 of column 1" is NOT "under
     the panel": row 2 begins under the TALLEST item of row 1, and that is normally `.mp-results`.
     Measured on /en/…/stabilisatorkoppelingen/ at 1280 AND 1440 — filter panel 740px ending at
     y=1471, results column 982px, and the rail advert stranded at y=1752: **282px of white space
     between the panel and the advert**, the block sitting alone below the last result and below
     the safe-trading card, which is exactly why it read as "a banner that isn't being shown".
     It also made the grid 1170px tall for a 982px results column — 188px of dead page.

     The cure is to let the results column SPAN both rows, so row 1 is sized by the filter panel
     alone and row 2 is the advert's own row, immediately under it.
     ⚠ `grid-template-rows:auto 1fr` is load-bearing, not decoration: an item spanning a FLEXIBLE
     track is skipped when the intrinsic size of the spanned tracks is resolved, so row 1 stops
     inheriting the results height. Measured with plain `auto auto` the gap only fell 282 -> 67.
     ⚠ `align-self:start` keeps the advert its own 148px — the default `stretch` would have blown
     it out to the full 218px row, and `.cp-band__mod` is `container-type:size`, so a taller band
     is a different module layout, not just a taller box.
     ⚠ `margin-top:0` because the grid's own 24px row gap now does that job; keeping both gave 40px.
     ⚠ Gated on `:has()` so it applies ONLY when the advert is really there. A slot switched off,
     or `.mp-lrp--solo` (a dead category renders neither panel nor advert), must keep the plain
     single-row grid — otherwise an empty row 1 would push the results down by the row gap. A
     browser without `:has()` simply keeps the pre-DAY-43 layout: it fails safe.
     Measured after, both widths: gap 282 -> 24px, page 2206 -> 2018px, and on /cat/caravans/
     (where the panel is 3789px and TALLER than the results) 40 -> 24px — while `.mp-results`
     stays at x=358/438, y=731, i.e. the DAY-30 regression does not come back. */
  .mp-lrp:has(> .cp-band--lrp_side)                       { grid-template-rows:auto 1fr; }
  .mp-lrp:has(> .cp-band--lrp_side) > .mp-results         { grid-row:1 / span 2; }
  .mp-lrp:has(> .cp-band--lrp_side) > .cp-band--lrp_side  { align-self:start; margin-top:0; }

  /* ⚠⚠ AND THE MOMENT THE ADVERT MOVED UP, IT STARTED COVERING THE FILTER DROPDOWNS
     (owner-reported, same day). It is a STACKING problem, not a geometry one, and the
     dropdown's own `z-index:60` cannot solve it:
       · `.mp-filters` is `position:sticky`, which ALWAYS creates a stacking context, so
         the open `.dd-menu` is sealed inside the panel and can never rise above a sibling.
       · `.cp-band` is `container-type:inline-size`, which implies `contain:layout` and
         therefore ALSO creates a stacking context.
       · Both then sit at the same level with `z-index:auto`, so they paint in DOM ORDER —
         and the band is emitted AFTER the panel. The band wins, every time.
     Measured on /en/…/reservewielhouders/ with the last dropdown open: the menu runs
     328→588 and the band starts at 446, so **142px of the list was behind the advert**, and
     `elementFromPoint` in that overlap returned `IMG.cp-mod__bg` — the banner artwork.
     With this one line it returns `DIV.dd-opt`. ⚠ It was invisible before today only
     because the band used to sit 282px lower (see the note above).
     ⚠ `z-index:1` is deliberately tiny: the sticky site header is `z-index:1030`, so the
     panel must stay far below it or it would ride over the header while scrolling.
     ⚠ Scoped by `:has()` like the rest of this block, so a page without the advert keeps
     the panel exactly as it was, and ≤768 is untouched (there the panel is the fixed
     filter SHEET with its own z-index from mp.css, and the band is `display:none`).

     ⚠⚠⚠ AND `z-index:1` ALONE WAS NOT ENOUGH — the DOM said it was, the PIXELS said it
     was not. `elementFromPoint` in the overlap answered `DIV.dd-opt` (menu on top) while a
     real screenshot sampled RGB(32,50,43) at the same coordinate: the dark green advert.
     The seventh time in this project that a measurement passed and the screen disagreed.
     The cause, found by testing candidates and sampling PIXELS: `container-type` did not
     seal this band's children here, so the module's own positioned descendants — notably
     `.cp-mod--bg > .cp-mod__feats { position:relative; z-index:2 }` — were competing in
     the SAME stacking context as the panel and out-bidding `z-index:1`. Measured: panel
     `z-index:5` also cured it, but that is out-bidding a number that can change; making
     the band a real positioned box with `z-index:0` SEALS its children inside its own
     stacking context, which is the actual fix. `position:relative` with no offsets moves
     nothing — the band is a grid item and its geometry is unchanged (re-measured). */
  .mp-lrp:has(> .cp-band--lrp_side) > .mp-filters         { z-index:1; }
  .mp-lrp:has(> .cp-band--lrp_side) > .cp-band--lrp_side  { position:relative; z-index:0; }
  /* ⚠ DAY 31: an EMPTY category renders no filter panel and no rail advert, so the grid is a
     single column (`.mp-lrp--solo`, results.css). Forcing `grid-column:2` there would invent an
     implicit second track and push the results across a 24px gap — the same auto-placement trap
     as above, in reverse. Same reason the block is gated at >=769px in the first place. */
  .mp-lrp--solo > .mp-results  { grid-column:1; }
}

/* In-feed bands live inside the results grid — without this they take ONE column (measured:
   278px of an 862px row) instead of the full row. */
.cp-band--lrp_infeed,
.cp-band--garage_infeed{ grid-column:1 / -1; margin:6px 0 14px; }

/* Wide-screen gutter rails. The chrome caps at 1280px, so the free gutter is
   (viewport - 1280) / 2 — that only reaches 160+16px at about 1642px, hence the 1680 gate.
   Static, not sticky: a rail that follows the scroll is the classic Better Ads complaint. */
.cp-band--rail_left,.cp-band--rail_right{ display:none }
@media (min-width:1680px){
  /* ⚠ 176px, NOT 160px. The band is its own container, so a 160px-wide rail measures 160 and
     falls short of the 176px threshold its only format needs — it then positions perfectly
     and renders nothing at all (measured at 1920: height 0). */
  .cp-band--rail_left,.cp-band--rail_right{
    display:block; position:absolute; top:190px; width:176px; margin:0; z-index:1;
  }
  /* ⚠⚠ DAY-39 — this block sits LATER in the file than `.cp-band--mixed{display:grid}` and both
     are (0,1,0), so `display:block` was silently winning: a rail carrying a sold creative was
     not a grid at all and its two cells stacked. Measured at 1720: `disp=block cq176 H0`.
     A rail that shares its band keeps the grid; the plain module-only rail stays a block. */
  .cp-band--rail_left.cp-band--mixed,
  .cp-band--rail_right.cp-band--mixed{ display:grid; }
  .cp-band--rail_left { left:calc(50% - 640px - 192px); }
  .cp-band--rail_right{ left:calc(50% + 640px + 16px);  }
}

/* -------------------------------------------------------------------------------------
   HARD STOPS
   ------------------------------------------------------------------------------------- */
/* The mobile app design carries neither advertising nor promo modules — the owner's decision
   ("mobilajā cilvēkiem traucē"), and the mechanism Google documents for suppressing a unit.
   !important because per-plugin CSS loads after this file. */
@media (max-width:768px){ .cp-band{display:none!important} }

/* A listing PDF must never carry a third-party ad. print.css repeats this for the listing
   page; here it covers every other surface too. */
@media print{ .cp-band{display:none!important} }

/* Dark is mobile-only in this project and bands are desktop-only, so the two never meet —
   this is defensive, for the day desktop dark is switched on. */
html[data-theme="dark"] .cp-ad__lbl{ color:rgba(255,255,255,.62); }

/* ---------- ⭐⭐⭐ THE TRUE-TO-THE-PORTAL PREVIEW (DAY 39) ----------
   Used by /account/banner/ so a paying customer sees the unit the visitor will see. The inner
   element IS `.cp-ad__b` at its exact IAB pixel size — same element, same `container-type:size`,
   same `cqi`/`cqh` — and the whole thing is scaled with a transform. That is the only way the
   type stays in proportion: shrinking the BOX instead makes every clamp() hit its floor, which
   is how the old studio ended up showing a 13px headline overflowing a 24px strip while the
   portal rendered 23px inside 90px.
   ⚠ `.cp-ad__b` ships `display:none` (the format ladder reveals one) — here it is always shown,
     because the preview is deliberately not on the ladder: it shows the size that was BOUGHT.
   ⚠ The outer box reserves the SCALED size; a transform does not affect layout, so without it
     the page would keep a 970x250 hole for a 430px-wide preview. */
/* ⚠ DAY 40 — `overflow-x:auto` is gone from the default. It existed only to let the un-scaled
   1:1 unit be nudged sideways, i.e. to make a CROPPED preview navigable; with uniform scaling
   back there is never anything outside the box. (The stage re-enables it, deliberately, for the
   `1:1` mode, where it is driven by a real slider.)
   ⚠⚠ AND IT CARRIES NO RADIUS OF ITS OWN. This box is NOT transformed — only its child is — so a
   `border-radius:10px` here is painted at 10px whatever the scale. Measured on the mini page at
   x0.15: a 24x90 window with a **10px** radius where the portal paints 1.5px, i.e. the rail was
   drawn as a lozenge. The unit inside already carries the portal's own radius and IS scaled, so
   it draws the true corner at every size; this element only clips. */
.cp-adprev{ position:relative; border-radius:0; max-width:100%; overflow:hidden; }
.cp-adprev__in{ transform-origin:top left; }
.cp-adprev .cp-ad__b{ display:block; margin:0; }
/* mobile: the bands are hidden portal-wide, but the STUDIO is not a band — the customer must be
   able to build and check a banner on a phone (the owner's rule: creation works on mobile,
   display stays desktop-only). */
@media (max-width:768px){ .cp-adprev, .cp-adprev .cp-ad__b{ display:block!important; } }

/* ---------- a COMPOSED creative (DAY 38, plan §10.2 mode B) ----------
   The customer fills in a headline, a sub-line and a button label and the portal draws the unit
   at the exact IAB size. It lives inside the same .cp-ad__b wrapper as an uploaded image, so the
   ladders, the impression beacon and the click tracking need no change at all.
   ⚠ Every value here comes from a whitelist or htmlspecialchars() — never raw customer HTML. */
.cp-cr{display:flex;width:100%;height:100%;border-radius:10px;overflow:hidden;align-items:center}
.cp-cr__in{display:flex;flex-direction:column;gap:.35em;padding:0 4.5%;min-width:0;text-align:left}
.cp-cr__t{font-family:"Archivo",system-ui,sans-serif;font-weight:800;font-style:normal;
  font-size:clamp(12px,min(5cqi,14cqh),30px);line-height:1.12;letter-spacing:-.01em;
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.cp-cr__s{font-style:normal;font-size:clamp(10.5px,min(3.4cqi,6.2cqh),15px);line-height:1.3;opacity:.9;
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.cp-cr__c{text-decoration:none;align-self:flex-start;border-radius:7px;font-weight:700;
  font-size:clamp(10px,min(3cqi,5.6cqh),14px);padding:.45em .95em;white-space:nowrap}
/* ---------- ⭐⭐ THE PHOTO CELL (DAY 39, the owner's `preview (6)` mockup) ----------
   A composed banner may give the photo its OWN place beside the copy instead of bleeding it
   across the whole unit. `.cp-cr` is a flex row already, so the cell is one more child.
   ⚠⚠ IT IS NOT SHOWN ON EVERY SIZE, AND THAT IS THE POINT. A 728x90 strip is 90px tall and a
      160x600 rail is 160px wide: a photo cell there is a sliver that steals the headline's
      width and reads as a rendering fault. The rule lives HERE, with the format ladder, and is
      expressed as container queries against `.cp-ad__b` — the same mechanism, the same
      thresholds. Measured room for two columns: at least 260px wide AND 150px tall, which is
      970x250 / 336x280 / 300x250 and nothing else. */
/* ⚠⚠ DAY 39 — THE PHOTO IS NEVER HIDDEN ANY MORE, THE LAYOUT CHANGES INSTEAD.
   The first version simply dropped `.cp-cr__ph` on anything narrower than 260px or shorter than
   150px. On a 160x600 rail — a real, sellable format — the advertiser uploaded a photo, saw it
   in the field, and the banner stayed blank. "The picture I added is not in my banner" is not a
   subtlety, it is the product failing. A unit always has room for a picture; what changes is
   WHERE it goes. Three shapes, decided by the unit's own proportions:
     wide + tall  (970x250, 336x280, 300x250) : photo BESIDE the copy
     tall + narrow(160x600, 300x600)          : photo ON TOP, copy below
     short strip  (970x90, 728x90, 468x60)    : photo as a SQUARE at the end of the line */
/* ⭐⭐⭐ DAY 40 — THE PHOTO CELL IS A DEFINED BOX NOW, NOT WHATEVER THE ROW LEFT OVER.
   ⚠⚠ MEASURED on all eight sizes with ONE 1200x900 product photo, before this rewrite:
        970x250   300.7 x 143.3   ratio 2.10
        970x90 / 728x90 / 468x60  square tiles, correct
        336x280   104.2 x 243.1   ratio 0.43   <- a sliver, 2.3x taller than wide
        300x250    93.0 x 217.0   ratio 0.43   <- the size the owner photographed
        300x600    93.0 x 567.0   ratio 0.16   <- a 93px ribbon down a 600px rail
        160x600   137.6 x 204.0   ratio 0.67, with ~300px of the unit left empty below it
      The cell was `flex:0 0 31%` + `align-self:stretch`: its WIDTH came from the unit and its
      HEIGHT from whatever the flex row happened to be, so the ratio was an accident of the
      format and `background-size:cover` then discarded up to 85% of the picture. Percentage
      margins compounded it — a % margin resolves against the container's WIDTH, so the same
      `5.5%` was 53px on a 970 unit and 16px on a 300 one.
      ⚠ `300x600` also never reached the "tall and narrow" branch at all: that branch was gated
        on `max-width:259px` and a 300px rail is 300 wide, so a half-page unit was laid out with
        the side-by-side rule. That is where the 0.16 ribbon came from.
      Every box below is stated in the UNIT's own units (cqi/cqh resolve against `.cp-ad__b`,
      which carries the real IAB size) with a CHOSEN aspect ratio, so the same photo is cropped
      the same, predictable way on every format — and `phf` lets the advertiser aim that crop. */
.cp-cr__ph{
  display:block; flex:0 0 auto; border-radius:8px;
  background-size:cover; background-position:50% 50%; background-repeat:no-repeat;
  background-color:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.15);
}

/* ---- A · WIDE AND TALL (970x250) — the picture stands BESIDE the copy, 3:2.
        ⚠ The gutter is 12cqh, the SAME measure as the inset the 76cqh height leaves above and
          below it ((100-76)/2 = 12) — so the photo sits in an even frame instead of hugging the
          right edge, which is what a 4cqh gutter did (30px above, 10px beside). ---- */
.cp-cr--ph .cp-cr__ph{ align-self:center; height:76cqh; aspect-ratio:3/2; margin:0 12cqh 0 0; }
.cp-cr--ph .cp-cr__in{ max-width:none; flex:1 1 auto; }
.cp-cr--ph-left{ flex-direction:row-reverse; }
.cp-cr--ph-left .cp-cr__ph{ margin:0 0 0 12cqh; }

/* centred copy — the photo stacks above the text instead of being thrown away (DAY 39) */
.cp-cr--center.cp-cr--ph,
.cp-cr--center.cp-cr--ph.cp-cr--ph-left{ flex-direction:column; align-items:stretch; justify-content:center; }
.cp-cr--center.cp-cr--ph .cp-cr__ph{ align-self:stretch; height:auto; aspect-ratio:16/5;
  margin:4cqh 4cqh 3cqh; }
.cp-cr--center.cp-cr--ph .cp-cr__in{ max-width:100%; }
/* a short strip has no vertical room to stack — keep the square tile beside the copy */
@container (max-height:149px){
  .cp-cr--center.cp-cr--ph{ flex-direction:row; align-items:center; }
  /* ⚠ MEASURED: `.cp-cr--center.cp-cr--ph .cp-cr__ph` is (0,3,0) and out-specified the strip's
     square rule at (0,2,0), so a centred 728x90 rendered a 293x77 band instead of a 77x77 tile.
     The square is restated here at the same specificity as the rule that broke it. */
  .cp-cr--center.cp-cr--ph .cp-cr__ph{ align-self:center; width:84cqh; height:84cqh;
    aspect-ratio:auto; margin:0 8cqh 0 0; }
}

/* ---- B · A BOX (336x280, 300x250) — two columns here is a sliver, so the picture goes on TOP
        at the unit's full width. A stated 2:1, so both boxes crop the same photo identically. ---- */
@container (max-width:360px) and (min-height:150px) and (max-height:360px){
  /* ⚠ `column-reverse`, not `column`: the photo is the LAST child in the markup, and a stacked
     unit is a hero image with the copy under it — the old `column` rule put the picture at the
     BOTTOM and left the empty colour in the middle. Left/Right has nothing to decide once the
     layout is a stack, so both variants render the same way. */
  .cp-cr--ph, .cp-cr--ph.cp-cr--ph-left{ flex-direction:column-reverse; align-items:stretch; }
  /* ⚠ MEASURED: a stated `aspect-ratio:2/1` at full width came to 151px on a 280-tall box, and
     151 + 30 of margin + 118 of copy is 299 — the photo was pushed 3px ABOVE the unit and
     clipped. The height is stated in the unit's own cqh instead, which BOTH boxes share (300x250
     and 336x280 are both 1.2:1), so the crop is identical on the two of them: 2.57:1. */
  .cp-cr--ph .cp-cr__ph{ align-self:stretch; width:auto; height:42cqh; aspect-ratio:auto;
    margin:5cqi 5cqi 4cqi; }
  .cp-cr--ph .cp-cr__in{ max-width:none; flex:1 1 auto; min-height:0;
    justify-content:center; padding-bottom:5cqi; }
}

/* ---- C · A TALL RAIL (300x600, 160x600) — the picture earns the top of the rail, 4:5 portrait,
        and the copy is centred in what is left instead of leaving a slab of empty colour. ---- */
@container (max-width:360px) and (min-height:361px){
  .cp-cr--ph, .cp-cr--ph.cp-cr--ph-left{ flex-direction:column-reverse; align-items:stretch; }
  .cp-cr--ph .cp-cr__ph{ align-self:stretch; width:auto; height:auto; aspect-ratio:4/5;
    margin:5cqi 5cqi 5cqi; }
  .cp-cr--ph .cp-cr__in{ max-width:none; flex:1 1 auto; min-height:0;
    justify-content:center; padding-bottom:5cqi; }
}
/* ⚠ A 160-wide skyscraper is a different animal from a 300-wide half page: 4:5 of 144px is only
   180px of a 600px rail — under a third of it — and the rest reads as empty paint. A 2:3 portrait
   fills it honestly while still being a STATED ratio, not a leftover. */
@container (max-width:220px) and (min-height:361px){
  .cp-cr--ph .cp-cr__ph{ aspect-ratio:2/3; }
}

/* ---- D · A SHORT STRIP (970x90, 728x90, 468x60) — a square tile at the end of the line ---- */
@container (min-width:361px) and (max-height:149px){
  /* ⚠ MEASURED: `flex:0 0 auto` + `aspect-ratio:1` collapsed to the content width — a 970x90
     strip rendered a **12x12** speck. The square is sized from the unit's own HEIGHT instead,
     so it is always a readable tile: 728x90 -> 76x76, 468x60 -> 50x50. */
  /* ⚠ the gutter is 8cqh — the SAME measure as the 8cqh the 84cqh tile leaves above and below
     it. `4%` was a percentage of WIDTH, so it was 39px beside a 7px inset on a 970x90. */
  .cp-cr--ph .cp-cr__ph{ align-self:center; width:84cqh; height:84cqh; aspect-ratio:auto;
    margin:0 8cqh 0 0; }
  .cp-cr--ph.cp-cr--ph-left .cp-cr__ph{ margin:0 0 0 8cqh; }
  .cp-cr--ph .cp-cr__in{ max-width:none; }
}
/* a narrow strip (a 300-wide unit is never this short, but a future one could be) keeps the tile */
@container (max-width:360px) and (max-height:149px){
  .cp-cr--ph .cp-cr__ph{ align-self:center; width:84cqh; height:84cqh; aspect-ratio:auto;
    margin:0 8cqh 0 0; }
  .cp-cr--ph .cp-cr__in{ max-width:none; }
}

/* a short strip (468x60 / 728x90 / 970x90) has no room for three stacked lines */
@container (max-height:110px){
  .cp-cr__in{flex-direction:row;align-items:center;gap:.9em;text-align:left}
  .cp-cr__s{display:none}
  /* ⚠ MEASURED: with only the column formula a 970x90 strip rendered a 12.6px headline — the
     height factor that is right for a 250px billboard is far too timid once the copy is on ONE
     line. A strip is bounded by its height, so it can carry ~26% of it. */
  .cp-cr__t{font-size:clamp(13px,min(3.2cqi,26cqh),24px);-webkit-line-clamp:1}
  .cp-cr__c{font-size:clamp(10px,min(2.2cqi,17cqh),14px)}
}
@container (max-height:74px){ .cp-cr__t{-webkit-line-clamp:1} }
/* ⚠ a TALL NARROW unit (160x600) must stay a column and give the text room, and its button
   must not be forced onto one line — measured: "Bekijk aanbod" at 160px wide is 2 words. */
@container (max-width:220px){
  .cp-cr__in{padding:0 8%;gap:.5em}
  .cp-cr__t{-webkit-line-clamp:4}
  .cp-cr__s{-webkit-line-clamp:4}
  .cp-cr__c{white-space:normal;text-align:center;align-self:stretch}
}
/* ⚠⚠ DAY 40 — TYPE ON THE NARROW SIZES WAS SITTING ON ITS CLAMP FLOOR, MEASURED.
   The base formula is `min(5cqi, 14cqh)`, i.e. width-driven. On a 160-wide rail 5cqi is 8px, so
   every headline resolved to the 12px FLOOR — 12px inside a 600px-tall unit, which reads as an
   unfinished banner rather than a paid one. A tall unit is constrained by LINE LENGTH, not by
   type size: 160px carries ~14-15px comfortably, and the 4-line clamp above already handles the
   wrapping. Same for the 300-wide box family, which was rendering 15px in a 250px unit. */
@container (max-width:220px) and (min-height:150px){
  .cp-cr__t{ font-size:clamp(14px, min(9cqi, 3.4cqh), 20px); }
  .cp-cr__s{ font-size:clamp(11px, min(6.6cqi, 2.1cqh), 13px); }
  .cp-cr__c{ font-size:clamp(11px, min(6.4cqi, 1.9cqh), 13px); padding:.5em .7em; }
}
@container (min-width:221px) and (max-width:360px) and (min-height:150px){
  .cp-cr__t{ font-size:clamp(15px, min(7cqi, 9cqh), 24px); }
  .cp-cr__s{ font-size:clamp(11px, min(4.2cqi, 5cqh), 14px); }
  .cp-cr__c{ font-size:clamp(11px, min(4cqi, 4.6cqh), 14px); }
}
