/* ==========================================================================
   Cool Pro Services — Hero variant of the scroll-controlled zoom section
   --------------------------------------------------------------------------
   Layered on top of cp-scroll-zoom.css. Unlike the mid-page section, the hero
   keeps its copy as real Elementor widgets so every word stays editable in the
   builder — only the video layer comes from an HTML widget. The Elementor
   container itself plays the part of .cp-zoom-stage.

   Structure on the page:
     .cp-zoom.cp-zoom-hero            (container, the track)
       .cp-zoom-stage                 (container, sticky, was the old hero)
         .elementor-widget-html       (video + scrim, absolutely filled)
         .e-con                       (the existing copy column, above it)
   ========================================================================== */

.cp-zoom.cp-zoom-hero {
  /* The hero sits directly under the header, so the pin engages the moment the
     page moves. Slightly longer than the mid-page section because the shot
     travels further. */
  --cp-zoom-scroll: 150vh;
}

/* Here the container is both the root and the track, so the shared
   `.cp-zoom.is-scrub .cp-zoom-track` height rule cannot reach it. */
.cp-zoom.cp-zoom-hero.is-scrub {
  height: calc(100vh + var(--cp-zoom-scroll));
  height: calc(100svh + var(--cp-zoom-scroll));
}

/* The stage doubles as the fallback hero: with no JS, on a phone, or under
   reduced motion this is simply a background image behind the copy, which is
   what the hero was before. In scrub mode the video sits on top of it, so the
   poster is also what shows while the video is still decoding. */
.cp-zoom-hero .cp-zoom-stage {
  position: relative;
  overflow: hidden;
  aspect-ratio: auto;
  max-height: none;
  align-items: stretch;
  justify-content: center;
  background-image: url('cooling-hero-poster.jpg');
  background-position: center 40%;
  background-size: cover;
  background-repeat: no-repeat;
}

@media (max-width: 640px) {
  .cp-zoom-hero .cp-zoom-stage {
    background-image: url('cooling-hero-poster-portrait.jpg');
    background-position: center center;
  }
}

/* The video layer fills the stage behind everything else. */
.cp-zoom-hero .cp-zoom-frame {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* The stage is wider than 16:9 on most desktop windows, so cover crops top and
   bottom. The indoor unit the shot ends on sits high in frame — bias the crop
   upward so it stays clear of the edge on short viewports. */
.cp-zoom-hero .cp-zoom-video {
  object-position: center 40%;
}

/* Weighted to the left: this shot keeps its subject centred for most of the
   journey — the duct interior and the room both centre up — so the copy needs
   a genuine panel rather than an empty corner. */
.cp-zoom-hero .cp-zoom-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to right, rgba(7, 12, 28, .94) 0%, rgba(7, 12, 28, .88) 26%, rgba(7, 12, 28, .58) 48%, rgba(7, 12, 28, .12) 74%, rgba(7, 12, 28, .3) 100%),
    linear-gradient(to top, rgba(7, 12, 28, .5) 0%, rgba(7, 12, 28, 0) 38%);
}

@media (max-width: 960px) {
  .cp-zoom-hero .cp-zoom-scrim {
    background: linear-gradient(to bottom, rgba(7, 12, 28, .88) 0%, rgba(7, 12, 28, .7) 45%, rgba(7, 12, 28, .82) 100%);
  }
}

/* The copy column sits above both layers. `.e-con` carries Elementor's own
   display rule and loads after this file, so the child selector is qualified
   with it rather than relying on source order. */
.cp-zoom-hero .cp-zoom-stage > .e-con,
.cp-zoom-hero .cp-zoom-stage > .e-con-inner > .e-con {
  position: relative;
  z-index: 2;
}

/* Hold the copy inside the dark side of the scrim rather than letting it run
   the full 1200px container, which would push the last third of every line out
   over the bright part of the picture. Full width again once the layout
   stacks. */
@media (min-width: 961px) {
  /* Narrow the copy column, not the boxed wrapper — the wrapper carries the
     site's 1200px gutter and unpinning it from centre would shove the copy
     against the window edge. */
  .cp-zoom-hero .cp-zoom-stage .e-con-inner > .e-con {
    max-width: 680px;
    flex-grow: 0;
  }
}

/* The video widget is a zero-height flex item; keep it from adding gaps. */
.cp-zoom-hero .cp-zoom-stage > .elementor-widget-html {
  position: static;
  width: 100%;
  height: 0;
  margin: 0;
  padding: 0;
}

/* ==========================================================================
   Scroll-keyed copy reveal (2026-08-12)
   --------------------------------------------------------------------------
   cp-scroll-zoom.js publishes the same eased scroll progress that drives the
   video as `--cp-p` (0 -> 1) on the root container. Each block of hero copy
   declares the slice of that journey it fades up over, so the type arrives
   *with* the camera instead of on a timer of its own.

   Per-element knobs, set by the band classes below:
     --cp-in   progress at which the block starts moving
     --cp-mul  1 / (out - in), i.e. how fast it completes

   Multiplication rather than division on purpose: dividing by a var() is a
   newer bit of calc() than every browser we support parses reliably.

   The last block finishes at 0.70. The scroll -> video-time curve is p^0.85,
   so 0.70 lands at 74% of the clip: the frame where the camera has cleared
   the doorway and the room is on screen.

   The eyebrow and the h1 do NOT ride the scroll. They fade in on their own
   when the page loads (.cp-lead-in) so a visitor who never scrolls still
   gets a headline, and so the h1 stays the LCP element.

   `--cp-p` defaults to 1, so with no JS, on a phone, or under reduced motion
   (none of which add .is-scrub) the copy is simply visible.

   The !important flags are not decoration: cp-scroll-zoom.css forces
   `opacity: 1 !important` onto every widget inside .cp-static to keep the
   site-wide scroll-reveal off this section, and these rules have to outrank
   it. That guard is (0,4,1) — html + .cp-anim + .cp-static + .elementor-widget
   + :not(.cp-lead-in), and :not() counts its argument — so .cp-reveal is
   written twice below to reach (0,5,0) and win outright. Not a typo: a tie
   broken by file order would be at the mercy of the enqueue graph.
   ========================================================================== */
.cp-zoom.cp-zoom-hero { --cp-p: 1; }

.cp-zoom.cp-zoom-hero.is-scrub .cp-reveal.cp-reveal {
  /* Anything given .cp-reveal without a band class rides the tail group. */
  --cp-in: .48;
  --cp-mul: 4.55;

  --lp: clamp(0, calc((var(--cp-p) - var(--cp-in)) * var(--cp-mul)), 1);
  /* Ease-out on the way in: fast off the mark, soft on arrival. */
  --e: calc(var(--lp) * (2 - var(--lp)));

  opacity: var(--e) !important;
  transform: translate3d(0, calc((1 - var(--e)) * 30px), 0) !important;
  filter: blur(calc((1 - var(--e)) * 5px)) !important;

  /* The site-wide classifier still tags these widgets .cp-a-rise, which
     carries `transition: opacity/transform .78s` plus an inline
     transition-delay of up to 360ms. On a scroll-linked value that reads as
     the copy lagging a second behind the camera, which is the one thing this
     effect must not do. The easing is already in the JS. */
  transition: none !important;
}

/* Same doubled class as above: these have to match the shared rule's
   specificity to override its fallback band, and being later in the file
   settles the tie. */
.cp-zoom.cp-zoom-hero.is-scrub .cp-r-lead.cp-r-lead     { --cp-in: .24; --cp-mul: 4.17; }
.cp-zoom.cp-zoom-hero.is-scrub .cp-r-btns.cp-r-btns     { --cp-in: .38; --cp-mul: 4.55; }
.cp-zoom.cp-zoom-hero.is-scrub .cp-r-badges.cp-r-badges { --cp-in: .48; --cp-mul: 4.55; }

/* Only the paragraph is still moving late enough to be worth a layer. */
.cp-zoom.cp-zoom-hero.is-scrub .cp-r-lead { will-change: opacity, transform; }

/* The buttons hold their layout space the whole time, so they would still
   take a click while invisible. The JS adds .is-copy-live once the reveal
   reaches them. */
.cp-zoom.cp-zoom-hero.is-scrub:not(.is-copy-live) .cp-r-btns,
.cp-zoom.cp-zoom-hero.is-scrub:not(.is-copy-live) .cp-r-badges {
  pointer-events: none;
}

/* ---------- Eyebrow + headline: on load, not on scroll ----------
   These are excluded from the .cp-static opacity guard in cp-scroll-zoom.css
   so that a keyframe animation can own their opacity (an !important
   declaration would outrank the animation and it would never play). */
/* Safety net. Because .cp-lead-in is exempt from the .cp-static guard, the
   only other thing setting its opacity is the site-wide reveal's plain
   `opacity: 0` — so if the entrance animation never runs (animations
   disabled, keyframes dropped) the headline would stay invisible. A running
   animation outranks this; nothing else does. */
.cp-zoom.cp-zoom-hero .cp-lead-in { opacity: 1; }

.cp-zoom-hero .cp-lead-in {
  animation: cpLeadIn .8s both cubic-bezier(.22, .61, .36, 1);
}

.cp-zoom-hero .cp-r-eyebrow { animation-delay: .12s; }
.cp-zoom-hero .cp-r-h1      { animation-delay: .24s; }

@keyframes cpLeadIn {
  from { opacity: 0; transform: translate3d(0, 26px, 0); filter: blur(5px); }
  to   { opacity: 1; transform: none; filter: none; }
}

/* Under reduced motion the site-wide hide rule never applies and the guard
   rule no longer covers these two, so put them back explicitly. */
@media (prefers-reduced-motion: reduce) {
  .cp-zoom-hero .cp-lead-in {
    animation: none;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}