/* ---------------------------------------------------------------------------
   parallax.css — scroll-linked image depth.

   Ported from the motion study of mtechnikbmw.net (Squarespace 7.0 Momentum,
   `positionIndexImages()`, drift constant h = 0.8). That template pinned each
   image into a position:fixed 100vh box and counter-translated the image by
   -0.8 x the section offset, so the on-screen image moved at 20% of scroll
   speed while content moved at 100%.

   Our sections are in normal flow, not fixed, so the same 20% is produced a
   different way: the image is overscaled by `--px-travel` top and bottom, and
   JS translates it within +/- that slack. Because |translate| never exceeds the
   slack, an edge can never be exposed. See parallax.js for the derivation.

   Everything here is scoped under `html.px-on`, a class JS adds only when the
   effect is actually allowed to run. With JS off, with reduced motion, or on
   touch/narrow screens, none of these rules apply and the page renders exactly
   as it does today.

   CONTAINER GEOMETRY
     Absolutely positioning the image takes it out of flow, so each container
     must already have its own height or it would collapse to nothing. All
     three homepage containers do, in the public stylesheet, and nothing here
     touches those declarations:

       .luxury-home-hero          min-height: min(920px, 100svh)
       .luxury-brand-panel        min-height: 520px
       .luxury-landscape-feature  min-height: clamp(680px, 80vw, 880px)

     A future container without an intrinsic height needs one added there, not
     patched in here.
   --------------------------------------------------------------------------- */

html.px-on [data-parallax] {
  position: relative;
  overflow: hidden;
}

/* <picture> generates no box, so the <img> positions against [data-parallax]. */
html.px-on [data-parallax] picture {
  display: contents;
}

html.px-on [data-parallax] [data-parallax-image] {
  position: absolute;
  left: 0;
  right: auto;
  bottom: auto;
  margin: 0;
  width: 100%;
  top: calc(-1 * var(--px-travel, 10%));
  height: calc(100% + 2 * var(--px-travel, 10%));
  /* The overscale is taller than the container by construction, so an
     inherited cap would clip it and re-expose the edge the overscale exists to
     hide. `right`/`bottom`/`margin` above are here for the same reason: a site
     rule with `inset: 0` would otherwise stretch the image back to the box. */
  max-width: none;
  max-height: none;
  object-fit: cover;
  /* object-position is deliberately not set: the site stylesheet owns each
     image's crop, including the responsive variants on the team panel. */
  transform: translate3d(0, var(--px-y, 0px), 0);
  will-change: transform;
}

/* Belt and braces. JS already refuses to add .px-on under reduced motion; this
   makes the CSS side inert too, in case the class is ever set another way. */
@media (prefers-reduced-motion: reduce) {
  html.px-on [data-parallax] [data-parallax-image] {
    top: 0;
    height: 100%;
    transform: none;
    will-change: auto;
  }

  /* `transform: none` above also drops the site's `transform: scale(1.015)`, so
     restate the hero's resting scale here. Stopping the motion must not also
     change what the image looks like at rest. */
  html.px-on .luxury-home-hero .hero-image {
    animation: none;
    scale: 1.015;
  }
}

/* --- Velvet Realty Group ---------------------------------------------------
   The homepage hero image is the one place where an author animation already
   owns a transform:

     .luxury-home-hero .hero-image {
       transform: scale(1.015);
       animation: luxury-image-settle 1100ms cubic-bezier(.22,1,.36,1) both;
     }

   `luxury-image-settle` animates `transform` (scale 1.035 -> 1.015) with
   fill-mode `both`, so it keeps owning the transform property for the life of
   the page — animations beat normal author declarations in the cascade. The
   drift would simply never appear.

   Waiting for `animationend` and then killing the animation was the previous
   approach and it is not robust: the event is missed on a bfcache restore, and
   releasing a filled animation snaps the image if the drift is non-zero at the
   moment of the hand-off.

   Instead, under `html.px-on` only, the entrance is re-expressed on the
   individual `scale` property. `scale` composes with `transform` instead of
   competing for it, so parallax owns `transform` outright from the first frame,
   with no hand-off and no timing dependency, and the drift can never snap.

   VERIFY BEFORE DEPLOY. Two things about `px-hero-settle` below are known and
   two are not. Known: the duration is 1100ms and the animation rests at
   `scale(1.015)` with fill-mode `both` — those come from the live rule quoted
   above. Not known from anything in this repository: the `from` keyframe
   (opacity and starting scale) and the timing function, because styles.css is
   served from production and is not stored here. The values below are a local
   re-authoring, not a transcription. Diff them against `luxury-image-settle`
   in production styles.css and correct them before this ships, or the entrance
   will differ between the enhanced and static renderings of the same page.

   Coverage is unaffected either way: scaling the image about its centre adds
   more bleed than it adds to the translate for every travel value, so the
   overscale still hides both edges while the entrance plays.

   The static fallbacks — JS off, reduced motion, coarse pointer, <=900px — never
   get `.px-on`, so they keep the original `luxury-image-settle` untouched.
   --------------------------------------------------------------------------- */
html.px-on .luxury-home-hero .hero-image {
  scale: 1.015;
  animation: px-hero-settle 1100ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes px-hero-settle {
  from {
    opacity: 0.76;
    scale: 1.035;
  }
  to {
    opacity: 1;
    scale: 1.015;
  }
}
