/*
 * Campaign-home — campaign-specific styles only (docs/features/spa-home.md).
 * The page's own CSS (/a/css.acss) already styles the chrome, buttons, drawer,
 * background, header/menu and footer. Everything here is scoped under
 * `.campaign-home` so it can't affect page-builder pages.
 */

/* ── Background blur (applied to the page's existing .background) ──────────── */
.campaign-home .background {
  transition: filter 0.3s ease-out, transform 0.3s ease-out;
  will-change: filter;
  /* The fixed background is exactly the viewport, so blur(28px) fades its own
     top/bottom edges to transparent — revealing the black body as a dark band
     that lands at each (viewport-tall) block boundary and reads as a seam.
     Oversize top/bottom so those blurred edges sit off-screen. Width is left
     alone (the drawer animates it). */
  top: -52px;
  height: calc(100dvh + 104px);
}
.campaign-home.campaign-bg-blurred .background {
  /* --theme-scroll-blur: the site theme's max blur (css/v5/theme.tsx); the
     pixel and flat themes set it to 0 so the key art stays sharp. */
  filter: blur(var(--theme-scroll-blur, 28px));
  transform: scale(1.05);
}
/* Drawer open: the page reframes into a poster (css.tsx "underlay reveal")
   and sizes the .background to it (100dvh desktop / 33dvh mobile, with its
   own rounded poster corners). The -52px top offset above still applied,
   which left the bottom 52px of the poster uncovered — a band of the wrapper's
   flat bg colour under the key art. Drop the offset while open so the art
   covers the poster edge to edge; the height comes from css.tsx per breakpoint.
   Ease top/height with the reframe (css.tsx only lists width/border-radius —
   and, being the more specific body#Home rule, it also dropped the
   filter/transform easing declared above, so restore that here). */
body#Home.campaign-home .background {
  transition: width 0.7s ease-in-out, border-radius 0.7s ease-in-out, top 0.7s ease-in-out,
    height 0.7s ease-in-out, filter 0.3s ease-out, transform 0.3s ease-out;
}
body#Home.campaign-home.drawer-open .background {
  top: 0;
}

/* ── Block rhythm + reveal ────────────────────────────────────────────────── */
.campaign-home .campaign-block {
  padding: clamp(48px, 9vh, 110px) 0;
  position: relative;
}
.campaign-home.campaign-js [data-transition] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.campaign-home.campaign-js [data-transition].in-view { opacity: 1; transform: none; }
.campaign-home.campaign-js [data-transition='fade'] { transform: none; }
/* When a drawer is open (including a deep-link pre-open), the reframed page IS
   the poster — its reveal-on-scroll blocks must be shown, not stuck at opacity:0
   under the poster, and must not animate as the drawer opens. */
.campaign-home.drawer-open.campaign-js [data-transition] {
  opacity: 1;
  transform: none;
  transition: none;
}

/* ── Hero (reuses the title-module markup; scoped layout here) ─────────────── */
/* The hero is sized against ITS OWN width, not the viewport. When a drawer
   opens, the page reframes into a narrow poster (css.tsx "underlay reveal":
   the .wrapper shrinks to ~0.68 × 100dvh on desktop) while the viewport stays
   put — so anything sized in vw (the title image, the h1, the layer padding)
   would keep its full-viewport size and spill out of the poster's clip. The
   section is an inline-size query container and the hero uses cqw units, so
   the logo tracks the poster width through the open/close transition. The
   plain-vw declaration before each cqw one is the fallback for engines without
   container units (they keep the first, drop the second). */
.campaign-home .campaign-block--title {
  min-height: 86svh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  container-type: inline-size;
}
.campaign-home .campaign-block--title .titleDesignModule {
  /* The classic 900px content column, inset from the edges by the hero edge
     padding (--hero-pad, 20px on a phone) so full-width buttons never touch
     the viewport sides; the max-width grows by the same so desktop content
     stays exactly 900px wide. */
  box-sizing: border-box;
  padding-inline: var(--hero-pad, 0px);
  /* Full column width (the section is a flex column, which would otherwise
     shrink-wrap this module to its widest group, leaving a narrower group
     nothing to centre or align within). */
  width: 100%;
  max-width: calc(900px + 2 * var(--hero-pad, 0px));
  margin: 0 auto;
}
.campaign-home .title-treatment .titleImage {
  display: block;
  margin: 0 auto;
  width: auto;
  max-width: min(620px, 82vw);
  max-width: min(620px, 82cqw);
  max-height: 46vh;
  height: auto;
}
.campaign-home .campaign-title-text {
  font-size: clamp(2.5rem, 12vw, 6rem);
  font-size: clamp(2.5rem, 12cqw, 6rem);
  line-height: 0.95;
  margin: 0;
}
/* Hero groups stack their elements with the configurable element gap
   (design_home_hero_gap, default 1rem — the classic message offset). */
.campaign-home .hero-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hero-gap, 1rem);
}
.campaign-home .hero-group > * { max-width: 100%; }
/* Classic (unpositioned) hero: the CTA group follows the title group at the
   same element gap, so message/buttons sit where they always did. */
.campaign-home .titleDesignModule > .hero-group + .hero-group { margin-top: var(--hero-gap, 1rem); }
/* A group given a width (design_home_title_width / design_home_cta_width) is a
   block narrower than the 900px column, so it sat against the column's LEFT
   edge while the other group stayed centred — a narrowed CTA's message and
   buttons no longer lined up with the title. Centre it, or hug the edge the
   group's own alignment names (the positioned layer does the same with
   align-self). */
.campaign-home .titleDesignModule > .hero-group { margin-inline: auto; }
.campaign-home .hero-title-align-left .titleDesignModule > .hero-group--title { margin-inline: 0 auto; }
.campaign-home .hero-title-align-right .titleDesignModule > .hero-group--title { margin-inline: auto 0; }
.campaign-home .hero-cta-align-left .titleDesignModule > .hero-group--cta { margin-inline: 0 auto; }
.campaign-home .hero-cta-align-right .titleDesignModule > .hero-group--cta { margin-inline: auto 0; }
.campaign-home .title-message {
  margin: 0;
}
.campaign-home .title-message h2 {
  font-weight: 500;
  font-size: clamp(1.1rem, 4vw, 1.6rem);
  font-size: clamp(1.1rem, 4cqw, 1.6rem);
  margin: 0;
}
.campaign-home .btns.drawer-action {
  display: flex;
  flex-direction: column; /* stacked on mobile */
  align-items: center;
  gap: var(--hero-cta-gap, 12px);
  margin-top: max(0px, calc(1.5rem - var(--hero-gap, 1rem))); /* classic 1.5rem offset, net of the group gap */
}
@media (min-width: 768px) {
  /* side by side on desktop */
  .campaign-home .btns.drawer-action { flex-direction: row; justify-content: center; }
}
/* lean on the site's .btn styling; just ensure the secondary reads as ghost */
.campaign-home .campaign-cta.is-secondary {
  background: transparent;
  border: 1px solid currentColor;
}

/* ── About: sticky poster + long detail column ─────────────────────────────── */
.campaign-home .campaign-about {
  display: grid;
  gap: 28px;
  align-items: start;
}
.campaign-home .campaign-poster img {
  display: block;
  width: 100%;
  max-width: 360px;
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}
.campaign-home .campaign-about-title {
  margin: 0 0 0.4em;
  font-size: clamp(2.2rem, 6vw, 3.75rem);
  line-height: 1.02;
}
.campaign-home .campaign-synopsis {
  font-size: clamp(1.1rem, 0.6rem + 1vw, 1.3rem);
  line-height: 1.6;
  max-width: 62ch;
  margin: 0 0 2rem;
}
/* Legibility: without an inverted panel the about copy sits straight on the
   blurred key art, where light text on a light frame washes out. A soft glass
   panel in the site's own background colour (plus a faint text shadow) keeps
   it readable on any artwork; the inverted-panel variant has its own ground. */
/* The about text sits straight on the key art: no tinted panel, no blur (a
   theme may add its own surface — see css/v5/theme.tsx). */
.campaign-home .campaign-block--about:not(.inverted-panel) .campaign-about-body {
  padding: clamp(18px, 3vw, 36px);
  border-radius: 16px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
/* Metadata grid (Directed by / Film details / Runtime …) + cast below. */
.campaign-home .campaign-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 22px 32px;
  margin: 0 0 1.75rem;
}
.campaign-home .campaign-meta-cast { margin: 0 0 2rem; max-width: 60ch; }
.campaign-home .campaign-meta dt,
.campaign-home .campaign-meta-cast dt {
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  opacity: 0.75;
  margin: 0 0 6px;
}
.campaign-home .campaign-meta dd,
.campaign-home .campaign-meta-cast dd { margin: 0; font-size: 1.08rem; line-height: 1.5; }
/* Cast: one person per line, the role / character muted after the name. */
.campaign-home .campaign-cast-list { list-style: none; margin: 0; padding: 0; }
.campaign-home .campaign-cast-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.6em; }
.campaign-home .campaign-cast-role { font-size: 0.86em; opacity: 0.7; }
/* Age certificate chip. Shows the per-territory rating code (e.g. "15", "R18")
   matched to the visitor, or the legacy certification string. Rendered as text
   for now; official regional symbols are a later upgrade (docs/features/ratings.md). */
.campaign-home .campaign-cert {
  display: inline-block;
  margin-left: 0.55em;
  padding: 0 0.4em;
  border: 1px solid currentColor;
  border-radius: 3px;
  font-size: 0.82em;
  line-height: 1.5;
  opacity: 0.8;
}
/* Trailer / Share action row. */
.campaign-home .campaign-about-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 30px;
  margin: 0 0 2rem;
}
/* Trailer / Share actions: quiet text links, not buttons. Doubled class so
   these out-specify the site's button rules — the nested `&:hover` in
   buttons.tsx resolves to `:is(.btn, button, …):hover`, (0,2,1), which
   painted the Share <button> a solid block on hover — and the theme button
   rules (body.theme-* button). The hover is a soft tint with real padding;
   the negative inline margin keeps the label on the text's left edge. */
.campaign-home .campaign-action.campaign-action {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  padding: 8px 12px;
  margin-inline: -12px;
  background: none;
  border: 0;
  border-radius: 6px;
  box-shadow: none;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  opacity: 0.85;
  transform: none;
  filter: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: opacity 0.2s ease, background-color 0.2s ease;
}
.campaign-home .campaign-action.campaign-action:hover,
.campaign-home .campaign-action.campaign-action:focus-visible {
  opacity: 1;
  color: inherit;
  background: color-mix(in srgb, currentColor 12%, transparent);
  border: 0;
  box-shadow: none;
  transform: none;
  filter: none;
}
.campaign-home .campaign-action-icon { display: inline-flex; }
.campaign-home .campaign-action.is-copied::after {
  content: 'Link copied';
  margin-left: 0.5em;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.7;
}
.campaign-home .campaign-about-showtimes {
  margin-top: 0.5rem;
  padding-top: 1.75rem;
  /* Tied to the text colour, not hardcoded white: the about block becomes an
     inverted panel when the site sets an inverted background (css/v5/css.tsx),
     and a white rule would vanish on a light one. On a dark page currentColor
     is the light body colour, so this matches what it replaced. */
  border-top: 1px solid color-mix(in srgb, currentColor 14%, transparent);
}
.campaign-home .campaign-section-title {
  font-size: clamp(1.6rem, 6vw, 2.4rem);
  margin: 0 0 1rem;
}

@media (min-width: 768px) {
  .campaign-home .campaign-about {
    grid-template-columns: minmax(220px, 340px) 1fr;
    gap: 56px;
  }
  /* Freeze the poster while the long right column (incl. showtimes) scrolls. */
  .campaign-home .campaign-poster {
    position: sticky;
    top: clamp(88px, 13vh, 130px);
    align-self: start;
  }
}

/* ── Trailer lightbox ─────────────────────────────────────────────────────── */
.campaign-lightbox {
  position: fixed;
  inset: 0;
  /* Above every piece of site chrome: the menu overlay (9998) and the header
     hamburger + corner ribbon (9999) used to paint over the lightbox and sit
     on top of its close button. */
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.campaign-lightbox[hidden] { display: none; }
.campaign-lightbox-scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.campaign-lightbox-panel {
  position: relative;
  z-index: 1;
  /* Size so the 16:9 frame always fits within both viewport width and height. */
  width: min(1100px, 100vw - 32px, calc((100vh - 120px) * 16 / 9));
}
.campaign-lightbox-frame { width: 100%; aspect-ratio: 16 / 9; border-radius: 12px; overflow: hidden; box-shadow: 0 30px 90px rgba(0, 0, 0, 0.6); }
.campaign-lightbox-frame iframe,
.campaign-lightbox-frame .trailerModule,
.campaign-lightbox-frame .trailerModule iframe { width: 100%; height: 100%; }
/* Reuses the drawer's X (.drawer-close two-span markup); just place it in the
   viewport corner instead of the drawer's. */
.campaign-lightbox .campaign-lightbox-close {
  position: absolute;
  top: clamp(12px, 3vh, 28px);
  right: clamp(12px, 3vw, 28px);
  z-index: 2;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
body.campaign-lightbox-open { overflow: hidden; }
/* The trailer is the only thing on screen while it plays: park the header
   hamburger and the corner ribbon, which otherwise crowd the close button. */
body.campaign-lightbox-open header label,
body.campaign-lightbox-open header .header-wrap { visibility: hidden; }
/* Close button: a 44px round hit target with the X centred in it (the drawer's
   .drawer-close rules draw the X 30px down a 40px box for the drawer's own
   layout), and a soft focus ring — the JS focuses it on open, and the UA's
   rectangular outline read as a broken box around the X. */
.campaign-lightbox .campaign-lightbox-close {
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  outline: none;
  transition: background 0.2s ease;
}
.campaign-lightbox .campaign-lightbox-close:hover { background: rgba(0, 0, 0, 0.55); }
.campaign-lightbox .campaign-lightbox-close:focus-visible { box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.7); }
.campaign-lightbox .campaign-lightbox-close span {
  top: 50%;
  left: 12px;
  width: 20px;
  margin-top: -1px;
}

/* ── Parallax layers (transforms come from campaign-home.js) ───────────────── */
.campaign-home.campaign-js [data-parallax] { will-change: transform; }
/* ── Trailer: image-to-background-zoom (scale comes from campaign-home.js) ───── */
.campaign-home .campaign-block--trailer { padding: 0; }
.campaign-home .campaign-trailer-zoom {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: min(72svh, 720px);
  overflow: hidden;
  text-decoration: none;
  color: #fff;
}
.campaign-home .campaign-trailer-zoom-img {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transform: scale(1);
  transform-origin: center;
  will-change: transform;
}
.campaign-home .campaign-trailer-zoom-scrim {
  position: absolute;
  inset: 0;
  /* A whisper of shading so the play button reads on a bright still — the
     previous 35/15/50% gradient buried the artwork. */
  background: linear-gradient(to bottom, rgba(0,0,0,0.10), rgba(0,0,0,0.02) 45%, rgba(0,0,0,0.20));
}
/* Centered circular play button (the still's only overlay). */
.campaign-home .campaign-trailer-play {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(76px, 9vw, 104px);
  height: clamp(76px, 9vw, 104px);
  border-radius: 999px;
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.35);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: transform 0.25s ease, background 0.25s ease;
}
.campaign-home .campaign-trailer-play svg {
  /* Twice the original 30px; margin scales with it to keep the triangle
     optically centred in the circle. */
  width: 60px;
  height: 60px;
  margin-left: 8px;
}
.campaign-home .campaign-trailer-zoom:hover .campaign-trailer-play {
  transform: scale(1.06);
  background: rgba(255, 255, 255, 0.28);
}

@media (prefers-reduced-motion: reduce) {
  /* ID-qualified so it out-specifies both the drawer-reframe transition list
     above (body#Home.campaign-home .background) and css.tsx's
     body#Home .background — otherwise those win and the poster reframe
     animates for users who asked for reduced motion. */
  body#Home.campaign-home .background,
  .campaign-home .background { transition: none; }
  .campaign-home.campaign-js [data-transition] { opacity: 1; transform: none; transition: none; }
  /* JS skips parallax/zoom under reduced motion; ensure nothing is left shifted. */
  .campaign-home [data-parallax],
  .campaign-home .campaign-trailer-zoom-img { transform: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Polish amends — cinematic refinement of the campaign chrome. Scoped to
   .campaign-home so page-builder sites are untouched. Ordered after the base
   rules above so same-specificity overrides win.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Shared reflective edge: a 2px gradient rim (catch-light at top → shadow at
   bottom) masked to the element's own radius. Reads as a thin reflective bevel. */
/* The ribbon is already position:fixed (a positioned element), so its ::after
   anchors correctly — do NOT add position:relative here or it drops out of the
   corner into normal flow. */
.campaign-home .campaign-poster,
.campaign-home .campaign-trailer-zoom { position: relative; }
.campaign-home .campaign-poster::after,
.campaign-home .campaign-trailer-zoom::after,
.campaign-home header .header-wrap a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.275) 0%,
    rgba(255, 255, 255, 0.06) 22%,
    rgba(0, 0, 0, 0) 55%,
    rgba(0, 0, 0, 0.21) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 3;
}

/* 1 ── Title CTAs: side-by-side on desktop (only stack on mobile). The site's
   .btns rules make each button flex-grow + min-width:245px, which forced a wrap;
   let them be auto-width and sit in a row once there's space. */
@media (min-width: 768px) {
  .campaign-home .campaign-block--title .btns.drawer-action {
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;
    gap: var(--hero-cta-gap, 1rem); /* the gutter between the two halves */
  }
  /* Equal 50/50 blocks spanning the title width; the container gap is the only
     gutter (zero the site's per-button margin). */
  .campaign-home .campaign-block--title .btns.drawer-action > .campaign-cta {
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
  }
}

/* 3 ── About poster: dominant on desktop, sized off the viewport height (90vh).
   The grid column collapses to the poster's intrinsic width at that height. */
.campaign-home .campaign-poster { border-radius: 12px; }
.campaign-home .campaign-poster img { border-radius: inherit; box-shadow: 0 32px 80px rgba(0, 0, 0, 0.5); }
@media (min-width: 768px) {
  .campaign-home .campaign-about { grid-template-columns: auto minmax(0, 1fr); gap: 64px; }
  /* Re-assert sticky here: the reflective-edge rule above sets position:relative
     (needed so the ::after anchors on mobile), which would otherwise override
     the sticky positioning — and make `top` a relative push-down that drops the
     poster below the right column. As sticky, `top` is the scroll offset and the
     poster's natural top stays aligned with the right column. */
  .campaign-home .campaign-poster { position: sticky; top: clamp(64px, 8vh, 110px); }
  .campaign-home .campaign-poster img {
    width: auto;
    max-width: none;
    height: 80vh;
    max-height: 80vh;
  }
}

/* 4 ── Trailer: a contained 16/9 frame at the content width, rounded ~20px,
   instead of full-bleed. (The still still gently zooms within the frame.) */
.campaign-home .campaign-block--trailer { padding: clamp(48px, 9vh, 110px) 0; }
.campaign-home .campaign-block--trailer .campaign-trailer-zoom {
  width: 100%;
  min-height: 0;
  aspect-ratio: 16 / 9;
  border-radius: 1.25rem;
  box-shadow: 0 32px 90px rgba(0, 0, 0, 0.5);
}

/* 5 ── Footer: drop its own backdrop blur + tint; it fights the page's blurred
   key art and reads as a jarring panel. (.footer also carries `.grain`.) */
.campaign-home footer.grain,
.campaign-home footer {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* Transparent unless the design editor's footer background opacity is set. */
  background-color: var(--design_footer_bg, transparent);
}

/* 6 ── Footer top edge: a 2px reflective hairline (light, fading at the ends). */
.campaign-home footer { position: relative; }
.campaign-home footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.25) 18%,
    rgba(255, 255, 255, 0.25) 82%,
    rgba(255, 255, 255, 0) 100%
  );
  pointer-events: none;
}

/* (The site's menu texture — nav::before in css.tsx — is scoped to the site
   nav now, so it no longer bleeds onto the embedded widget's tab bar. The
   blanket `.campaign-home nav::before { background-image: none }` that used
   to stop that leak also switched the menu's own grain off; gone.) */

/* 7 ── Menu: actually centre the title + nav items. The site's `nav ul` keeps
   the browser's default ~40px inline-start padding, which shoved the centred
   text to the right. Zero it and centre explicitly. */
.campaign-home nav:not(.widget-nav):not(.assemble-widget nav) ul {
  padding: 0;
  margin: 0;
  text-align: center;
}

/* ── Hero placement + spacing controls (docs/features/module-position.md) ──
   The hero keeps its classic centred layout unless the section carries
   .campaign-hero--positioned; then each group (title / CTA, or both stacked)
   becomes a layer anchored at a POINT of the hero — any point, not just the 9
   grid cells. The renderer hands each layer its coordinates as unitless 0..1
   custom properties (--hero-x-lg/--hero-y-lg for desktop, --hero-x-sm/
   --hero-y-sm for mobile); the layer sits at that fraction of the padded hero
   box and is pulled back by the same fraction of its own size, so 0 is flush
   with the edge, 1 flush with the far edge and 0.5 dead centre — the grid's
   cells are exactly the 0 / 0.5 / 1 presets.

   Every length below reads a `--hero-*-lg` / `--hero-*-sm` custom property
   set on the section by hero-style.ts, each with the stylesheet's own default,
   so an untouched site renders exactly as before:
     --hero-pad      inset from the hero's edges       (design_home_hero_padding)
     --hero-gap      gap between stacked elements      (design_home_hero_gap)
     --hero-title-w  width of the title group          (design_home_title_width)
     --hero-cta-w    width of the CTA group            (design_home_cta_width)
                     (a % width also arrives as a `-cq` twin in cqw — % of
                     the HERO — used where container units are supported,
                     because a positioned layer is max-content sized and a %
                     of that would be a % of the buttons, not of the page)
     --hero-cta-gap  gap between the two buttons       (design_home_cta_gap)
     --hero-btn-pad  button padding                    (design_home_cta_button_padding)

   Placement never sets text alignment. Alignment INSIDE a group is the
   hero-title-align-* / hero-cta-align-* classes (design_home_*_align), centre
   by default — so a group placed top-right still reads centred within itself,
   as the classic hero does. ────────────────────────────────────────────── */

/* Breakpoint resolution: one set of unsuffixed vars, picked per breakpoint.
   Both blocks match at exactly 768px; the mobile one comes later and wins —
   the same tie-breaker as the site CSS (utils.mobile/desktop). */
/* Fallback contract (same as the hero's other cqw rules): the base values
   below use vw / %, and the @supports (width: 1cqw) blocks after them swap in
   container units so the hero tracks the reframed poster, not the viewport.
   Custom properties aren't validated at parse time — an invalid cqw value
   would only fail at use, unsetting the property that reads it — so the
   swap has to be a feature query, not a second declaration. */
@media (min-width: 768px) {
  .campaign-home .campaign-block--title {
    --hero-pad: var(--hero-pad-lg, clamp(16px, 5vw, 72px));
    --hero-pad-y: var(--hero-pad-lg, clamp(16px, 5vh, 56px));
    --hero-gap: var(--hero-gap-lg, 1rem);
    --hero-title-w: var(--hero-title-w-lg, auto);
    --hero-cta-w: var(--hero-cta-w-lg, 100%);
    --hero-cta-gap: var(--hero-cta-gap-lg, 1rem);
    --hero-x: var(--hero-x-lg, 0.5);
    --hero-y: var(--hero-y-lg, 0.5);
  }
  .campaign-home .campaign-block--title[style*='--hero-btn-pad-lg'] { --hero-btn-pad: var(--hero-btn-pad-lg); }
}
@media (max-width: 768px) {
  .campaign-home .campaign-block--title {
    --hero-pad: var(--hero-pad-sm, clamp(16px, 5vw, 72px));
    --hero-pad-y: var(--hero-pad-sm, clamp(16px, 5vh, 56px));
    --hero-gap: var(--hero-gap-sm, 1rem);
    --hero-title-w: var(--hero-title-w-sm, auto);
    --hero-cta-w: var(--hero-cta-w-sm, 100%);
    --hero-cta-gap: var(--hero-cta-gap-sm, 12px);
    --hero-x: var(--hero-x-sm, 0.5);
    --hero-y: var(--hero-y-sm, 0.5);
  }
  .campaign-home .campaign-block--title[style*='--hero-btn-pad-sm'] { --hero-btn-pad: var(--hero-btn-pad-sm); }
}

/* Per-layer coordinates: the layer's inline style sets the *-lg / *-sm pairs;
   re-resolve the unsuffixed pair on the layer itself so the inheritance from
   the section's fallback doesn't win over the layer's own values. The CTA
   default inside a layer is the classic 620px block capped at the hero. */
@media (min-width: 768px) {
  .campaign-home .campaign-hero-layer {
    --hero-x: var(--hero-x-lg, 0.5);
    --hero-y: var(--hero-y-lg, 0.5);
    --hero-cta-w: var(--hero-cta-w-lg, min(620px, 100%));
  }
}
@media (max-width: 768px) {
  .campaign-home .campaign-hero-layer {
    --hero-x: var(--hero-x-sm, 0.5);
    --hero-y: var(--hero-y-sm, 0.5);
    --hero-cta-w: var(--hero-cta-w-sm, 100%);
  }
}
/* Container units where supported: the inset follows the hero's own width,
   and a % width means % of the HERO (hero-style.ts emits a `-cq` twin in cqw
   for every % width) — inside a max-content layer a plain % would be a share
   of the buttons themselves. */
@supports (width: 1cqw) {
  @media (min-width: 768px) {
    .campaign-home .campaign-block--title {
      --hero-pad: var(--hero-pad-lg, clamp(16px, 5cqw, 72px));
      --hero-title-w: var(--hero-title-w-lg-cq, var(--hero-title-w-lg, auto));
      --hero-cta-w: var(--hero-cta-w-lg-cq, var(--hero-cta-w-lg, 100%));
    }
    .campaign-home .campaign-hero-layer {
      --hero-cta-w: var(--hero-cta-w-lg-cq, var(--hero-cta-w-lg, min(620px, calc(100cqw - 2 * var(--hero-pad)))));
    }
  }
  @media (max-width: 768px) {
    .campaign-home .campaign-block--title {
      --hero-pad: var(--hero-pad-sm, clamp(16px, 5cqw, 72px));
      --hero-title-w: var(--hero-title-w-sm-cq, var(--hero-title-w-sm, auto));
      --hero-cta-w: var(--hero-cta-w-sm-cq, var(--hero-cta-w-sm, 100%));
    }
    .campaign-home .campaign-hero-layer {
      --hero-cta-w: var(--hero-cta-w-sm-cq, var(--hero-cta-w-sm, calc(100cqw - 2 * var(--hero-pad))));
    }
  }
}

/* Group widths + button geometry (apply to the classic hero too, so the
   controls work whether or not a placement is set). */
.campaign-home .hero-group--title { width: var(--hero-title-w, auto); max-width: 100%; }
.campaign-home .campaign-block--title[style*='--hero-title-w'] .title-treatment { width: 100%; }
.campaign-home .campaign-block--title[style*='--hero-title-w'] .title-treatment .titleImage {
  width: 100%;
  max-width: 100%;
  max-height: none;
}
.campaign-home .hero-group--cta { width: var(--hero-cta-w, auto); max-width: 100%; }
.campaign-home .hero-group--cta .btns.drawer-action { width: 100%; box-sizing: border-box; }
.campaign-home .campaign-block--title .btns.drawer-action { gap: var(--hero-cta-gap); }
/* Button padding (design_home_cta_button_padding): one length, applied
   vertically with double horizontally. Gated on the section carrying the
   property, so an unset control leaves the site's own .btn padding alone. */
@media (min-width: 768px) {
  .campaign-home .campaign-block--title[style*='--hero-btn-pad-lg'] .btns.drawer-action > .campaign-cta {
    padding: var(--hero-btn-pad) calc(var(--hero-btn-pad) * 2);
  }
}
@media (max-width: 768px) {
  .campaign-home .campaign-block--title[style*='--hero-btn-pad-sm'] .btns.drawer-action > .campaign-cta {
    padding: var(--hero-btn-pad) calc(var(--hero-btn-pad) * 2);
  }
}

/* Button arrangement (design_home_cta_layout): default row on desktop, stacked
   on mobile; the classes flip either. Full width by default — a row splits the
   CTA width 50/50, a stack is full-width blocks; hero-cta-fit-* (the "-fit"
   values) lets each button take its natural width (padding + label) instead,
   placed by the group's alignment. */
@media (min-width: 768px) {
  .campaign-home .campaign-block--title.hero-cta-stack-lg .btns.drawer-action { flex-direction: column; }
  .campaign-home .campaign-block--title.hero-cta-stack-lg .btns.drawer-action > .campaign-cta {
    flex: 0 1 auto;
    width: 100%;
    box-sizing: border-box;
    min-width: 0;
    margin: 0;
  }
  .campaign-home .campaign-block--title.hero-cta-fit-lg .btns.drawer-action { flex-wrap: wrap; }
  .campaign-home .campaign-block--title.hero-cta-fit-lg .btns.drawer-action > .campaign-cta {
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    margin: 0;
  }
}
@media (max-width: 768px) {
  .campaign-home .campaign-block--title.hero-cta-row-sm .btns.drawer-action { flex-direction: row; flex-wrap: nowrap; }
  /* The mobile column centres with align-items; a row needs justify-content.
     Kept at the alignment rules' specificity (and ahead of them) so a left /
     right CTA alignment still wins. */
  .campaign-home .hero-cta-row-sm .btns.drawer-action { justify-content: center; }
  .campaign-home .campaign-block--title.hero-cta-row-sm .btns.drawer-action > .campaign-cta {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    margin: 0;
  }
  .campaign-home .campaign-block--title.hero-cta-fit-sm .btns.drawer-action { flex-wrap: wrap; }
  .campaign-home .campaign-block--title.hero-cta-fit-sm .btns.drawer-action > .campaign-cta {
    flex: 0 0 auto;
    width: auto;
    min-width: 0;
    margin: 0;
  }
}

/* Text alignment inside each group — independent of placement. */
.campaign-home .hero-group { text-align: center; }
.campaign-home .hero-group .title-message h2 { text-align: inherit; }
.campaign-home .hero-title-align-left .hero-group--title { text-align: left; align-items: flex-start; }
.campaign-home .hero-title-align-right .hero-group--title { text-align: right; align-items: flex-end; }
.campaign-home .hero-cta-align-left .hero-group--cta { text-align: left; align-items: flex-start; }
.campaign-home .hero-cta-align-right .hero-group--cta { text-align: right; align-items: flex-end; }
/* Within the CTA row, buttons follow the group's alignment when the row is
   wider than its buttons (they keep their own centred labels). */
.campaign-home .hero-cta-align-left .btns.drawer-action { justify-content: flex-start; align-items: flex-start; }
.campaign-home .hero-cta-align-right .btns.drawer-action { justify-content: flex-end; align-items: flex-end; }
.campaign-home .campaign-block--title .btns.drawer-action > .campaign-cta { text-align: center; }

/* Extra hero elements (hero-layout.ts): text, quote, image, laurels, spacer. */
.campaign-home .hero-text p { margin: 0; line-height: 1.4; }
.campaign-home .hero-text--small p { font-size: clamp(0.8rem, 2.4cqw, 0.95rem); opacity: 0.85; }
.campaign-home .hero-text--normal p { font-size: clamp(1rem, 3cqw, 1.2rem); }
.campaign-home .hero-text--large p { font-size: clamp(1.3rem, 4.5cqw, 2rem); font-weight: 500; }
.campaign-home .hero-quote { margin: 0; max-width: 34em; }
.campaign-home .hero-quote-text { margin: 0; font-size: clamp(1.05rem, 3.2cqw, 1.4rem); font-style: italic; line-height: 1.35; }
.campaign-home .hero-quote-source {
  display: block;
  margin-top: 0.4em;
  font-size: 0.72rem;
  font-style: normal;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.7;
}
.campaign-home .hero-image { width: var(--hero-el-w, auto); max-width: 100%; }
.campaign-home .hero-image img { display: block; width: 100%; height: auto; }
.campaign-home .hero-laurels {
  display: flex;
  flex-wrap: wrap;
  justify-content: inherit;
  align-items: center;
  gap: clamp(8px, 2cqw, 20px);
  width: var(--hero-el-w, auto);
  max-width: 100%;
}
.campaign-home .hero-laurels img { display: block; height: clamp(48px, 9cqw, 96px); width: auto; }
.campaign-home .hero-spacer { height: var(--hero-el-h, 24px); flex: 0 0 auto; }

/* ── Positioned layers ───────────────────────────────────────────────────── */
.campaign-home .campaign-block--title.campaign-hero--positioned {
  align-items: stretch;
  justify-content: flex-start; /* the sole child spans full width; just defeat the centering */
}
.campaign-home .campaign-hero--positioned .titleDesignModule {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  max-width: none;
  margin: 0;
  min-height: 86svh;
}
.campaign-home .campaign-hero-layer {
  position: absolute;
  /* Anchor point: the fraction of the padded box… */
  left: calc(var(--hero-pad) + (100% - 2 * var(--hero-pad)) * var(--hero-x));
  top: calc(var(--hero-pad-y) + (100% - 2 * var(--hero-pad-y)) * var(--hero-y));
  /* …minus the same fraction of the layer itself, so the edges meet the edges. */
  transform: translate(calc(-100% * var(--hero-x)), calc(-100% * var(--hero-y)));
  /* Intrinsic width (never shrink-to-fit against the remaining space, which
     would wrap a right-placed title into a sliver), capped at the padded box. */
  width: max-content;
  max-width: calc(100% - 2 * var(--hero-pad));
  display: flex;
  flex-direction: column;
  gap: var(--hero-gap);
  margin: 0;
}
/* The layer itself stays neutral (centred) — a title and CTA sharing one
   layer must not move each other. Each GROUP aligns itself within the layer
   from its own alignment key, so a right-aligned CTA only shifts the CTA. */
.campaign-home .campaign-hero-layer { align-items: center; }
.campaign-home .hero-title-align-left .campaign-hero-layer .hero-group--title { align-self: flex-start; }
.campaign-home .hero-title-align-right .campaign-hero-layer .hero-group--title { align-self: flex-end; }
.campaign-home .hero-cta-align-left .campaign-hero-layer .hero-group--cta { align-self: flex-start; }
.campaign-home .hero-cta-align-right .campaign-hero-layer .hero-group--cta { align-self: flex-end; }
/* Positioned groups: the title keeps its intrinsic width (capped like the
   classic hero); the CTA group is a definite-width block (capped like the
   classic hero's title image) that the 50/50 rule fills — two buttons split
   it, a lone button spans it — rather than buttons shrinking to their labels. */
/* The cqw cap (second) sizes the max-content layer exactly — a % inside min()
   is ignored for intrinsic sizing and left the layer wider than the group.
   The % declaration first is the fallback for engines without container
   units (they keep it and drop the cqw one). */
.campaign-home .campaign-hero-layer .hero-group--title {
  max-width: min(900px, 100%);
  max-width: min(900px, calc(100cqw - 2 * var(--hero-pad)));
}
.campaign-home .campaign-hero-layer .hero-group--cta { width: var(--hero-cta-w); max-width: 100%; }
.campaign-home .campaign-hero-layer .btns.drawer-action { margin-top: 0; width: 100%; }
/* Stacked mobile CTAs are full-width blocks at their natural height — the
   site's `.btns > *` 20rem flex-basis is VERTICAL in this column layout and
   made each classic-hero button ~340px tall on phones. Applies to every hero
   button (classic or positioned). flex-shrink stays on for the 768px tie,
   where the desktop row direction still applies and the two full-width
   buttons must shrink back to 50/50. */
@media (max-width: 768px) {
  .campaign-home .campaign-block--title .btns.drawer-action > .campaign-cta {
    flex: 0 1 auto;
    box-sizing: border-box; /* the site css has no global border-box */
    width: 100%;
    min-width: 0;
    margin: 0;
  }
}
/* Layers overlap; only their content is interactive. */
.campaign-home .campaign-hero-layer { pointer-events: none; }
.campaign-home .campaign-hero-layer .hero-group { pointer-events: auto; }

/* Same-point-on-one-breakpoint handling (planHeroLayers): when the title and
   CTA placements match on exactly one breakpoint, the renderer emits a merged
   layer for that breakpoint plus split layers for the other, and these
   toggles pick which set shows. The ranges are complementary (a viewport can
   never hide BOTH variants) and the tie at exactly 768px goes to the MOBILE
   variant — the *-sm coordinates win there, so the visible layer set must be
   the mobile one or a mobile-only collision would overlap again at 768px. */
@media (min-width: 768.02px) {
  .campaign-home .hero-only-sm { display: none; }
}
@media (max-width: 768px) {
  .campaign-home .hero-only-lg { display: none; }
}
