/**
 * CP · Hero — self-contained styles (token-driven, brand-agnostic).
 * Ported from the approved GenerX homepage hero and re-scoped under the block
 * root .cp-hero so the block is reusable on any page, independent of the
 * homepage's .cp-home scope. Support shades the palette doesn't carry (the dark
 * firefly backdrop, the scrim) stay as literals — the "what theme.json can't
 * express" layer.
 */
.cp-hero {
  --hero-white: var(--wp--preset--color--white);
  --hero-primary: var(--wp--preset--color--primary);
  --hero-firefly: #0D1B2A;
  --hero-scrim: rgba(37, 37, 37, 0.7);
  --hero-section-px: clamp(24px, 5vw, 80px);
  --hero-max: 1100px;
  --hero-font-display: var(--wp--preset--font-family--barlow-condensed);
  --hero-font-body: var(--wp--preset--font-family--roboto);
  position: relative; color: var(--hero-white); overflow: hidden; isolation: isolate;
  min-height: 620px; display: flex; align-items: center; padding: 80px var(--hero-section-px);
}
.cp-hero .hero__bg {
  position: absolute; inset: 0; z-index: -2;
  background-color: var(--hero-firefly);
  overflow: hidden;
}
/* The photo is an <img> rather than a background-image so the preload scanner
   finds the LCP element in the first bytes of the HTML — see the note in
   render.php. object-fit/object-position reproduce the background-size: cover
   and background-position: center they replace, so the framing is unchanged.
   The flat --hero-firefly above still shows through when a hero passes
   `none` and renders no photo at all. */
.cp-hero .hero__bg-img {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%; max-width: none;
  object-fit: cover; object-position: var(--hero-obj, center);
}
/* Below 980px the frame is far narrower, so a hero may want different framing
   again -- the Playbook anchors the homepage's square crop to the bottom and
   the two product banners to the right. Falls back to the desktop value, so a
   hero that says nothing is unchanged. */
@media (max-width: 980px) {
  .cp-hero .hero__bg-img { object-position: var(--hero-obj-mobile, var(--hero-obj, center)); }
}
.cp-hero::before { content: ''; position: absolute; inset: 0; z-index: -1; background: var(--hero-scrim); }
.cp-hero .hero__grid {
  max-width: var(--hero-max); margin: 0 auto; width: 100%;
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(360px, 0.9fr); gap: 48px; align-items: center;
}
.cp-hero .hero__copy { max-width: 601px; }
/* Breadcrumbs. Same type treatment as the eyebrow they replace, so the approved
   hero is unchanged to look at — but linked, and with the separator as a ::after
   rather than a text node, so a screen reader reads the crumbs without a stream
   of stray "›" characters between them. */
.cp-hero .hero__crumbs { margin-bottom: 18px; }
.cp-hero .crumbs__list {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0;
  list-style: none; margin: 0; padding: 0;
}
.cp-hero .crumbs__item {
  font-family: var(--hero-font-body); font-weight: 700; font-size: 11px; line-height: 24px;
  letter-spacing: 2.5px; text-transform: uppercase; color: var(--hero-white);
}
.cp-hero .crumbs__item:not(:last-child)::after {
  content: '\203a'; margin: 0 8px 0 6px; letter-spacing: 0; opacity: 0.65;
}
.cp-hero .crumbs__item a { color: inherit; text-decoration: none; }
.cp-hero .crumbs__item a:hover,
.cp-hero .crumbs__item a:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
.cp-hero .crumbs__item [aria-current="page"] { opacity: 0.75; }

.cp-hero .hero__eyebrow {
  font-family: var(--hero-font-body); font-weight: 700; font-size: 11px; line-height: 24px;
  letter-spacing: 2.5px; text-transform: uppercase; color: var(--hero-white); margin: 0 0 18px;
}
.cp-hero h1 {
  font-family: var(--hero-font-display); font-weight: 700; font-size: clamp(40px, 5.5vw, 60px);
  line-height: 1.02; margin: 0 0 22px;
}
/* White, not Cardinal: this sits on the 70% dark scrim, where Cardinal tops out
   near 3:1 and fails AA. The playbook overrides it to white for the same
   reason. Applies to every hero, interior pages included.
   A brand whose Playbook colours it (Grästen's orange clears AA there) sets
   --cp-hero-em (cp_blocks_brand_logo_vars(), brand.php `hero_emphasis`). */
.cp-hero h1 em { font-style: normal; color: var(--cp-hero-em, var(--hero-white)); }
/* The H1 size of a brand's template, read off its Playbook page by the
   generators (re-audit W2): 56px on Grästen's city pages, 54px on its service,
   product and financing pages. No class keeps the block's 60px. */
.cp-hero.is-h1-56 h1 { font-size: clamp(40px, 5vw, 56px); }
.cp-hero.is-h1-54 h1 { font-size: clamp(36px, 4.8vw, 54px); line-height: 1.04; }
.cp-hero .hero__keep { white-space: nowrap; }
/* Tampa's hero (the Playbook's hub-page .trusted span) puts the emphasised
   word on a line of its own. A className variant on that one hero. */
.cp-hero.is-emphasis-block h1 em { display: block; }
.cp-hero .hero__desc {
  font-family: var(--hero-font-body); font-weight: 400; font-size: 18px; line-height: 28px;
  color: var(--hero-white); margin: 0 0 28px; max-width: 620px;
}
/* A link in the lead (a city page naming its hub) reads as a link without leaving the white. */
.cp-hero .hero__desc a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
/* City pages: the place name on a line of its own under "Generator Installation in". */
.cp-hero.is-variant-spoke h1 em { display: block; }
.cp-hero .hero__trust { display: flex; align-items: center; gap: 16px; margin-top: 6px; }
.cp-hero .hero__trust-item { display: flex; align-items: center; gap: 8px; }
.cp-hero .hero__trust-item:not(:last-child)::after { content: ''; width: 1px; height: 18px; margin-left: 16px; background: rgba(255, 255, 255, 0.32); }
.cp-hero .hero__trust-icon { flex: none; width: 18px; height: 18px; color: var(--hero-primary); }
.cp-hero .hero__trust-icon svg { width: 100%; height: 100%; display: block; }
.cp-hero .hero__trust-text { font-family: var(--hero-font-body); font-weight: 600; font-size: 13px; line-height: 1.2; color: var(--hero-white); white-space: nowrap; }
a.hero__trust-text { text-decoration: none; }
a.hero__trust-text:hover { text-decoration: underline; }
/* Scheduling-portal column (cp-portal block).

   The portal is a cross-origin iframe, so it cannot be sized to its content: it
   has to reserve enough height for its tallest state (once Sales is picked, the
   zip and email fields appear) and the card simply paints shorter than that in
   every other state. The portal app posts no height or resize message — its
   whole postMessage vocabulary is apptSubmit / portal-popup-opened /
   selectedSlot / cps-service-click / portal-popup-close — so there is nothing to
   measure or listen for. See docs/PORTAL-HEIGHT.md for the permanent fix.

   What the theme can do is stop that reserved height from setting the hero's
   height. --portal-h is what the iframe gets (and must match the height passed
   to the plugin, CP_BLOCKS_PORTAL_HEIGHT in inc/portal.php); --portal-reserve is
   what the column contributes to layout. The difference is taken back with a
   negative margin, so the iframe still renders at full height — the tall state
   is never clipped — while the hero's height comes from its own min-height
   instead of from the widget. Nothing shifts between the two states. */
.cp-hero {
  --portal-h: 577px;
  --portal-reserve: 460px;
}
.cp-hero .hero__portal { justify-self: end; width: 100%; max-width: 500px; }
.cp-hero .hero__portal .cps-portal, .cp-hero .hero__portal .acf-block-preview { width: 100%; }
.cp-hero .hero__portal .portal > iframe { height: var(--portal-h); display: block; }
.cp-hero .hero__portal .portal {
  margin-bottom: calc(var(--portal-reserve) - var(--portal-h));
}

/* Copy-only variant — interior page heroes (About Us et al). One wide column,
   a lead paragraph in place of the scheduling form, and a shorter band. */
.cp-hero.is-copy-only { min-height: 480px; padding-top: 80px; padding-bottom: 80px; }
.cp-hero.is-copy-only .hero__grid { grid-template-columns: minmax(0, 1fr); }
/* City heroes (className variants the service-area composer sets). Spokes,
   hubs and the homepage share one 620px hero, as the Grästen Playbook runs
   them (client audit SA1; GenerX's spokes were 720px, aligned 19 Oct 2026);
   the compact city layout keeps 480px. */
.cp-hero.is-height-city { min-height: 620px; }
.cp-hero.is-height-compact { min-height: 480px; }
/* About Us: the Playbook's copy-only hero keeps the full 620px and stacks its
   three proof points, one per line (client audit 2026-10-06, item 1). */
.cp-hero.is-copy-only.is-height-tall { min-height: 620px; }
.cp-hero.is-trust-stack .hero__trust { flex-direction: column; align-items: flex-start; gap: 12px; }
.cp-hero.is-trust-stack .hero__trust-item::after { display: none; }
/* City heroes also run the Playbook's wider grid: 1366px, a 420px portal column
   and a 64px gap (the homepage and inner pages keep 1100 / 360 / 48). On the
   narrow one the copy column was 566px, short of the 601px the four hero
   stats need to sit on one row. */
.cp-hero.is-height-city .hero__grid,
.cp-hero.is-height-compact .hero__grid {
  max-width: 1366px; grid-template-columns: minmax(0, 1.05fr) minmax(420px, 0.95fr); gap: 64px;
}
/*
 * The copy-only hero runs to the content measure. The Playbook's equivalent is
 * `.hero__inner { max-width: var(--content-max) }` with nothing narrower around
 * the copy, so the 820px cap that used to sit here stopped short of it.
 *
 * The lead keeps a cap, because the Playbook caps it too and only the copy
 * block runs full width: 640px on the repair page, 620px on About, none on
 * installation or products. 680px sits in that family; picking any one page's
 * number over the others would be arbitrary.
 */
.cp-hero.is-copy-only .hero__copy { max-width: none; }
.cp-hero.is-copy-only .hero__desc { max-width: 680px; margin-bottom: 0; }

@media (max-width: 980px) {
  /* .cp-hero.is-copy-only carries a 480px min-height and two classes of
     specificity, so it has to be named here or the band would keep its
     desktop height on a phone. */
  .cp-hero,
  .cp-hero.is-copy-only,
  .cp-hero.is-height-city,
  .cp-hero.is-height-compact,
  .cp-hero.is-copy-only.is-height-tall { min-height: 0; padding-top: 48px; padding-bottom: 48px; }
  /* Interior (copy-only) heroes keep 56px stacked, as the Playbooks' .hero does. */
  .cp-hero.is-copy-only,
  .cp-hero.is-copy-only.is-height-tall { padding-top: 56px; padding-bottom: 56px; }
  .cp-hero .hero__grid,
  .cp-hero.is-height-city .hero__grid,
  .cp-hero.is-height-compact .hero__grid { grid-template-columns: 1fr; gap: 28px; }
  .cp-hero .hero__copy { order: 2; max-width: none; }
  .cp-hero .hero__portal { order: 1; max-width: none; justify-self: stretch; }
  /* Stacked, the portal sits above the copy. It reserves only the card's first
     step (about 350px painted, 390px below 600px), so the headline follows the card as it does in
     the Playbook (client audit MW1/TB1) instead of after 190px of transparent
     iframe. The form can only grow once the visitor is in it, so
     assets/js/portal-reserve.js adds .is-portal-active on the iframe's first
     focus and the full height is reserved from then on. Until then the copy
     sits above the iframe's transparent tail, so its links stay tappable. */
  .cp-hero { --portal-reserve-stacked: 352px; }
  .cp-hero .hero__portal .portal { margin-bottom: calc(var(--portal-reserve-stacked) - var(--portal-h)); }
  .cp-hero.is-portal-active .hero__portal .portal { margin-bottom: 0; }
  .cp-hero .hero__copy { position: relative; z-index: 1; }
}
/* Below 600px the card's first step wraps onto one more line (about 390px
   painted); from 600px it paints about 350px, so 392px left a 40 to 95px gap
   on tablets and landscape phones (re-audit TB1). */
@media (max-width: 599px) { .cp-hero { --portal-reserve-stacked: 392px; } }
@media (max-width: 640px) {
  .cp-hero h1, .cp-hero[class*="is-h1-"] h1 { font-size: 36px; }
  .cp-hero.is-h1-m34 h1 { font-size: 34px; }
  .cp-hero.is-h1-m32 h1 { font-size: 32px; }
  .cp-hero .hero__trust { flex-direction: column; align-items: flex-start; gap: 12px; }
  .cp-hero .hero__trust-item::after { display: none; }
}

/* Interior-page hero call-to-action row. */
.cp-hero .hero__ctas { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; }
.cp-hero .hero__ctas .btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--hero-font-body); font-weight: 600; font-size: 15px; line-height: 1;
  padding: 13px 22px; border: 0; border-radius: 4px; text-decoration: none; gap: 8px; white-space: nowrap;
}
.cp-hero .hero__ctas .btn--primary { background: var(--hero-primary); color: var(--wp--custom--color--on-primary); }
.cp-hero .hero__ctas .btn--primary:hover { background: var(--wp--custom--color--primary-hover); }
.cp-hero .hero__ctas .btn--ghost {
  background: transparent; color: var(--hero-white); border: 1px solid rgba(255, 255, 255, 0.3);
}
.cp-hero .hero__ctas .btn--ghost:hover { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.6); }

/* Stats strip under the hero copy (the service-area heroes).

   These used to be a four-up grid on a grey band with 20px numbers, which read as
   a far heavier element than anything in the homepage hero and made the two page
   types look unrelated. They now use the same strip the homepage trust items use —
   one row, hairline dividers, 13px type, no band — so a city hero and the homepage
   hero carry their proof points identically. The rules deliberately mirror
   .hero__trust above rather than sharing a class: the markup is a number plus a
   label, not an icon plus a label.

   Both parts stay white. Cardinal on the hero scrim would fail AA at this size —
   the homepage strip gets its red accent from a decorative icon, which is exempt.

   No hairline dividers, unlike .hero__trust: on a narrower screen the strip
   wraps to two rows. A divider is drawn per item, and CSS cannot know which item ends a row —
   so every wrap would leave one dangling at the end of row one. The gap carries
   the separation instead, which is why it is wider here than on the trust strip. */
.cp-hero .hero__stats {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 10px 20px; margin-top: 6px;
}
.cp-hero .hero__stat { display: flex; align-items: baseline; gap: 7px; }
/* A stat's icon, picked in render.php from what it says (client review 2026-09-22). */
.cp-hero .hero__stat-icon { display: inline-flex; align-self: center; width: 16px; height: 16px; color: var(--hero-white); }
.cp-hero .hero__stat-icon svg { width: 16px; height: 16px; display: block; }
/* The Playbook's sizes: 12px, bold number, regular label, a 20px gap. At 13px
   semibold with a 30px gap the four stats wrapped to a second row on every
   city page (client audit 2026-10-05, W3); at these they sit on one. */
.cp-hero .hero__stat-num {
  font-family: var(--hero-font-body); font-weight: 700; font-size: 12px; line-height: 24px;
  color: var(--hero-white); white-space: nowrap;
}
.cp-hero .hero__stat-label {
  font-family: var(--hero-font-body); font-weight: 400; font-size: 12px; line-height: 24px;
  color: var(--hero-white); opacity: 0.92; white-space: nowrap;
}
