/* ─────────────────────────────────────────────────────────────────────────────
   DEPTH: parallax, 3D cards, and staged reveals.

   WHAT THIS DOES NOT TOUCH: the oil-drip canvas. That background is the one
   piece of this page the owner explicitly said was right, and nothing in this
   file paints, moves or layers over it. Everything here operates on content.

   THE RULE THIS FILE OBEYS: transform and opacity only. Animating width, height,
   top or left makes the browser re-lay-out the page on every frame, and on a
   long landing page with a WebGL canvas already running that is the difference
   between motion and a slideshow.

   EVERYTHING IS OFF BY DEFAULT AND TURNED ON BY JS. A card that is invisible
   until a reveal fires would be a blank page for anyone whose JavaScript failed,
   so the reveal state is applied only once the script has confirmed it is
   running — `html.gz-depth` is the switch. No JS, no motion, all content.
   ───────────────────────────────────────────────────────────────────────────── */

/* ── 1. SCROLL REVEAL ─────────────────────────────────────────────────────────
   Sections and cards arrive rather than appearing. Small distance, short
   duration: the point is that the page feels built rather than printed, not
   that anybody notices an animation. */
/* THE RISE IS A CUSTOM PROPERTY AND NOT A TRANSFORM, for the same reason the
   hover lift is one: an element can only have one `transform`, and on this page
   four different things want to move the same card — the reveal, the parallax,
   the hover lift and the pointer tilt. Written as four transforms they fight,
   and the winner is whichever rule was typed last, which is not a design.
   Written as four custom properties they compose, and each rule below spends
   the ones that apply to it.

   THIS IS WHAT USED TO SILENTLY EAT THE OTHER THREE. `.is-in` set
   `transform: none` at one class more than the tilt rule, so once a card had
   arrived it was pinned flat — and a parallax layer or a section heading that
   was also a reveal target never rose at all, because the generic
   [data-parallax] transform overwrote the reveal's. */
.gz-depth [data-reveal] {
  --gz-rise: 22px;
  opacity: 0;
  transform: translate3d(0, var(--gz-rise), 0);
  transition:
    opacity .62s cubic-bezier(.22, .61, .36, 1),
    transform .62s cubic-bezier(.22, .61, .36, 1);
  will-change: opacity, transform;
}

.gz-depth [data-reveal].is-in {
  --gz-rise: 0px;
  opacity: 1;
}

/* A grid reveals as a hand of cards, not as one block. The delay is set inline
   by the script from the card's index, capped so a long grid never leaves the
   last card waiting. */
.gz-depth [data-reveal][style*="--gz-delay"] {
  transition-delay: var(--gz-delay);
}

/* ── 2. PARALLAX ──────────────────────────────────────────────────────────────
   Layers move at different rates against the scroll, which is the whole of the
   effect. The script writes --gz-par (a signed pixel value) per element and CSS
   spends it, so JS never touches the style property directly and the browser
   can keep the transform on the compositor. */
.gz-depth [data-parallax] {
  transform: translate3d(0, calc(var(--gz-par, 0px) + var(--gz-rise, 0px)), 0);
  will-change: transform;
}

/* The hero screenshot drifts UP as the page scrolls down — it is the closest
   thing to the reader, so it moves most. */
.gz-depth .gz-hero .gz-shot {
  transform:
    translate3d(0, calc(var(--gz-par, 0px) + var(--gz-rise, 0px)), 0)
    rotateX(var(--gz-tiltx, 0deg));
  transform-style: preserve-3d;
}

/* Section headings drift the other way and by less, which is what reads as
   depth: two speeds in one frame. */
.gz-depth .gz-section-head[data-parallax] {
  transform: translate3d(0, calc(var(--gz-par, 0px) + var(--gz-rise, 0px)), 0);
}

/* ── 3. THE 3D CARDS ──────────────────────────────────────────────────────────
   A card tilts toward the pointer. The perspective lives on the DECK rather
   than on each card, so neighbouring cards share one vanishing point and the
   row reads as one surface being leaned on — per-card perspective makes each
   card its own little world and the effect looks cheap.

   IT IS KEYED ON AN ATTRIBUTE AND NOT ON `.gz-card`, WHICH IS THE FIX.
   Every rule in this section used to name `.gz-grid .gz-card`, and the result
   was the complaint that some of this site was enhanced and some of it was
   left basic: on the front page the four proposition cards dealt in, tilted,
   caught a sheen and lifted, and the four PLAN cards directly beneath them —
   the same shape, the same hairline, the same ground, in a grid called
   .gz-price-grid instead of .gz-grid — were completely inert. So were the
   removal ladder, the consent strip and the comparison strip. Nothing was
   deciding that; the selector simply did not reach them.

   Now assets/js/gz-depth.js tags every deck with `data-gz-deck` and every
   surface inside one with `data-gz-tilt`, and this file styles the attribute.
   A new card-like component gets the whole treatment by being added to one
   list in one function, which is the only way this stays true a year from now.

   THE NUMBERS ARE SMALL ON PURPOSE. 6 degrees is a card catching the light;
   15 is a novelty that nobody wants on a second visit. */
.gz-depth [data-gz-deck] {
  perspective: 1100px;
  perspective-origin: 50% 40%;
}

/* THE RISE AND THE LIFT ARE ADDED, NOT CHOSEN BETWEEN. A card that is still
   arriving and is hovered on the way in gets both, and a card that has arrived
   has --gz-rise at 0 and is left with the lift.
   `opacity` is in the transition list because this rule replaces the reveal's
   transition wholesale, and without it a card would fade in with no fade. */
.gz-depth [data-gz-tilt] {
  position: relative;
  transform:
    translate3d(0, calc(var(--gz-rise, 0px) + var(--gz-lift, 0px)), 0)
    rotateX(var(--gz-rx, 0deg))
    rotateY(var(--gz-ry, 0deg));
  transition:
    transform .34s cubic-bezier(.22, .61, .36, 1),
    opacity .62s cubic-bezier(.22, .61, .36, 1),
    box-shadow .34s ease,
    border-color .34s ease;
  will-change: transform;
}

/* A SURFACE THAT HAS NO NEIGHBOURS CARRIES ITS OWN PERSPECTIVE. The consent
   strip and the comparison table are one-per-page slabs — there is no row for
   them to share a vanishing point with, so the objection to per-element
   perspective above does not apply to them. They are also page-wide, and a
   large surface tilting as far as a small one looks like the page falling
   over, which is the same argument section 4 already makes about the showcase
   blocks. Deeper perspective and a smaller angle: the script writes 2.5
   degrees into these rather than 6. */
.gz-depth [data-gz-tilt][data-gz-solo] {
  transform:
    perspective(1600px)
    translate3d(0, calc(var(--gz-rise, 0px) + var(--gz-lift, 0px)), 0)
    rotateX(var(--gz-rx, 0deg))
    rotateY(var(--gz-ry, 0deg));
}

/* THE SHADOW IS THE DEPTH CUE, not the rotation. A tilted card with a flat
   shadow reads as a texture; the same tilt over a shadow that grows and offsets
   reads as an object off the page. The shadow leans opposite the tilt, because
   that is where the light is not. */
.gz-depth [data-gz-tilt]:hover,
.gz-depth [data-gz-tilt]:focus-visible {
  box-shadow:
    calc(var(--gz-ry, 0deg) / 1deg * -1.1px) 18px 42px -12px rgba(0, 0, 0, .68),
    0 2px 10px -4px rgba(0, 0, 0, .5);
}

/* The base hover in style.css already lifted the card 4px. That rule and this
   one would fight over `transform`, so the lift moves into the same custom
   property the tilt uses and the two compose instead.
   Focus is here as well as hover. The shadow above always honoured
   :focus-visible and the lift never did, so a card reached with a keyboard got
   half the treatment a pointer got. */
.gz-depth [data-gz-tilt]:hover,
.gz-depth [data-gz-tilt]:focus-visible {
  --gz-lift: -6px;
}

/* A specular sheen that follows the pointer across the card. It is a radial
   gradient positioned from two custom properties the script writes, sitting
   above the card's own ::after so it does not fight the existing hover tint. */
.gz-depth [data-gz-tilt]::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .34s ease;
  background: radial-gradient(
    38% 60% at var(--gz-mx, 50%) var(--gz-my, 50%),
    rgba(255, 255, 255, .10),
    rgba(255, 255, 255, 0) 68%
  );
  z-index: 2;
}

.gz-depth [data-gz-tilt]:hover::before {
  opacity: 1;
}

/* A FIGURE'S SHEEN GOES OVER THE PICTURE, NOT UNDER IT. .gz-compare-item and
   the before/after pair carry their border and radius on the <img> rather than
   on the figure, so an ::before on the figure would sit behind the image and
   never be seen, and it would be square where the picture is round. */
.gz-depth [data-gz-tilt][data-gz-figure]::before {
  border-radius: var(--gz-r-lg);
  z-index: 1;
}

/* WHAT IS NOT HERE ANY MORE: a `transform-style: preserve-3d` and a translateZ
   on .gz-card-media and .gz-card-body, under a comment saying that without them
   the card tilts as a flat picture. It did tilt as a flat picture, for the
   whole time those rules shipped. style.css sets `overflow: hidden` on
   .gz-card, and per CSS Transforms 2 a grouping property forces the used value
   of `transform-style` to `flat` — so the preserve-3d never took and neither Z
   offset ever rendered.
   THEY ARE DELETED RATHER THAN FIXED, and the arithmetic is why: translateZ(30px)
   under a 1100px perspective scales the card body by 2.8%, which on a 268px
   tile pushes three pixels of text out past the border it is supposed to sit
   inside. The tilt, the leaning shadow and the sheen are the effect, and they
   are the effect on every card-like surface rather than on a quarter of them,
   which is a better trade than a thickness nobody has ever seen. */

/* ── 4. THE SHOWCASE AND FEATURE BLOCKS ───────────────────────────────────────
   The big alternating blocks get the same treatment at half strength: they are
   large, and a large surface tilting the same amount as a small one looks like
   the page is falling over.

   THE BLOCK IS THE DECK AND ITS PICTURE IS THE CARD, which is why neither of
   these two ever tilts as a whole. A container holding a tilting surface
   cannot also tilt, or the two compose and the block wobbles.

   These are also the two rules that never actually ran. `--gz-ry` and `--gz-rx`
   are written by the script's pointer handler, and that handler was bound to
   `.gz-grid` and nowhere else — so a showcase picture has been parallaxing and
   never once tilting, under a comment describing a tilt. The script now binds
   every deck, and this includes `--gz-lift` so the same hover raise applies
   here as on a card. */
.gz-depth .gz-showcase,
.gz-depth .gz-feature {
  perspective: 1400px;
}

.gz-depth .gz-showcase .gz-crop,
.gz-depth .gz-feature .gz-feature-media {
  transform:
    translate3d(0, calc(var(--gz-par, 0px) + var(--gz-rise, 0px) + var(--gz-lift, 0px)), 0)
    rotateY(var(--gz-ry, 0deg))
    rotateX(var(--gz-rx, 0deg));
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}

/* ── 5. REDUCED MOTION IS NOT A DEGRADED MODE ─────────────────────────────────
   Everything above is decoration on top of a page that already worked. Someone
   who has asked their system for less motion gets the finished page with none
   of it — not a slower version, and not a version where content is still
   waiting for a reveal that will never fire. */
@media (prefers-reduced-motion: reduce) {
  .gz-depth [data-reveal],
  .gz-depth [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* One attribute covers every card-like surface on the site, so this list can
     no longer fall behind the list in section 3 — which is exactly what it had
     done: it named .gz-card and knew nothing about the plan cards, the ladder
     or the consent strip. */
  .gz-depth [data-parallax],
  .gz-depth [data-gz-tilt],
  /* Listed a second time with the solo attribute, because the solo rule in
     section 3 carries one more attribute and would otherwise outrank this. */
  .gz-depth [data-gz-tilt][data-gz-solo],
  .gz-depth .gz-hero .gz-shot,
  .gz-depth .gz-showcase .gz-crop,
  .gz-depth .gz-feature .gz-feature-media {
    transform: none;
    transition: none;
  }

  .gz-depth [data-gz-tilt]:hover,
  .gz-depth [data-gz-tilt]:focus-visible {
    box-shadow: none;
  }

  .gz-depth [data-gz-tilt]::before {
    display: none;
  }
}

/* ── 6. TOUCH ─────────────────────────────────────────────────────────────────
   A pointer tilt needs a pointer. On a phone there is no hover, the sheen would
   stick on wherever the last tap landed, and the tilt would only ever fire on
   the card being pressed. Reveals and parallax stay — those are scroll-driven
   and are most of the effect anyway. */
@media (hover: none) {
  /* The tilt and the lift go; the reveal's rise stays, because it is
     scroll-driven and is most of the effect on a phone. Written as the rise
     alone rather than as `none`, which would have taken the arrival with it. */
  .gz-depth [data-gz-tilt],
  .gz-depth [data-gz-tilt][data-gz-solo] {
    transform: translate3d(0, var(--gz-rise, 0px), 0);
  }

  /* The showcase and feature pictures keep their parallax and lose the tilt,
     rather than losing both. `--gz-ry` and `--gz-rx` are never written on a
     touch device anyway — the script's handler is behind a `pointer: fine`
     query — but stating it here means the rule does not depend on that. */
  .gz-depth .gz-showcase .gz-crop,
  .gz-depth .gz-feature .gz-feature-media {
    transform: translate3d(0, var(--gz-par, 0px), 0);
  }

  .gz-depth [data-gz-tilt]::before {
    display: none;
  }
}
