/* LUXVISION — Hero product carousel (landing page only) */

/* The background is a VIGNETTE now, not a fill. The smoke plate (.lv-atmos,
   see splash.css) sits underneath at z-index 1 and has to stay visible
   behind the turning ring - the logo arrives through that smoke and the
   products then rotate inside the same room, rather than the page cutting
   to an unrelated backdrop the moment the intro ends.

   It is not fully transparent either: the alpha ramp sinks the corners and
   knocks roughly a quarter of the light out of the centre, which is what
   keeps a bright product shot legible against moving smoke. Push these
   alphas down for more atmosphere, up for more product contrast - that one
   ramp is the whole dial. */
.lv-hero {
  position: fixed;
  inset: 0;
  height: 100dvh;
  width: 100vw;
  /* Centre pulled almost clear. The orb sits behind this layer and is now the
     thing the whole ring is composed around, so darkening the middle to
     protect product contrast - which is what this ramp was originally for -
     would be muting the subject. The edges still sink to near-black, which is
     what keeps the rim slides dissolving into the dark. */
  background: radial-gradient(
    ellipse at 50% 42%,
    rgba(11, 11, 16, 0.08) 0%,
    rgba(11, 11, 16, 0.62) 55%,
    rgba(11, 11, 16, 0.94) 100%
  );
  overflow: hidden;
  z-index: 5;
  opacity: 0;
}

.lv-hero.is-ready { opacity: 1; transition: opacity 2s var(--lv-ease); }

/* fades out + stops intercepting clicks once the user scrolls past it */
.lv-hero.is-scrolled {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s var(--lv-ease);
}
.lv-hero:not(.is-scrolled) { transition: opacity 0.6s var(--lv-ease); }

/*
  perspective-origin sits a little above centre so the eye reads the ring as
  an object standing in front of you rather than a flat diagram seen dead-on.

  A camera can only see a ring up to its tangent angle. Panels sit every 20 degrees, so that angle
  is a hard cap on how many can ever be on screen, and a SHORT viewing
  distance closes it down - which is why an earlier, much harder curve at
  900px could only ever show three products no matter what else moved.

  2400px sounds far too long for a ring that has to look curved, and on a
  circle it would be. It works here because the ring is an ELLIPSE (see
  RING_ACROSS / RING_DEPTH in carousel.js): the deep narrow egg turns the
  first neighbour 38.7 degrees off square where a circle would manage 20, so
  the wrap comes from the geometry rather than from lens distortion. That
  frees the lens to be long, which is what opens the tangent angle far
  enough to admit a fifth product - and long glass on a real subject is also
  simply what a product film looks like.
*/
.lv-carousel-stage {
  position: absolute;
  inset: 0;
  perspective: 2400px;
  perspective-origin: 50% 46%;
  cursor: pointer;
}

/*
  ---- The smoke sandwich ----

  Four depths, front to back: front smoke, the panel, mid smoke, the orb on
  the ring's axis (that last one is .lv-atmos in splash.css, sitting behind
  the whole hero). A panel turning round the ring passes bodily between the
  mid and front layers, which is what sells it as a rigid object moving
  through weather rather than a picture with fog drawn over it.

  Depth is expressed as z-index rather than translateZ. The slides carry a
  `filter` for their distance haze, and a filtered element is flattened out
  of any preserve-3d context, so true 3D depth sorting is not available to
  us here - painting order is. The numbers are chosen against the slides'
  own z-index, which carousel.js sets to round(facing * 100): the centre
  panel is 100 and a panel turned 60 degrees away is 50.
*/
.lv-smoke-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  will-change: opacity;
}

.lv-smoke-layer video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* BEHIND the front panels, in front of the rear ones. z-index 62 puts it
   between a panel turned ~52 degrees away and everything nearer, so the
   front three read as being in front of it and the rim slides sink behind
   it into the murk. */
.lv-smoke-mid {
  z-index: 62;
  opacity: 0.55;
  -webkit-mask-image: radial-gradient(ellipse 58% 74% at 50% 49%, #000 0%, #000 34%, transparent 88%);
  mask-image: radial-gradient(ellipse 58% 74% at 50% 49%, #000 0%, #000 34%, transparent 88%);
}

.lv-smoke-mid video {
  filter: blur(3px);
  animation: lv-smoke-drift-mid 31s ease-in-out infinite;
}

/*
  IN FRONT of every panel. The mask is an ANNULUS - a clear hole over the
  centre panel's face, with the smoke densest just outside it. That hole is
  the entire trick: it lets smoke curl past the panel's left and right edges
  and across its corners without ever fogging the product you are trying to
  look at. A plain centred mask here would put haze over the face and read
  as a dirty screen instead of as depth.

  The gradient ellipse is deliberately WIDE and SHORT - close to the panel's
  own 16:9 - so the clear hole is roughly panel-shaped. A circular hole over
  a landscape panel either fogs the middle or leaves the corners bare.

  It clears the left and right edges with a little room and comes slightly
  OVER the top and bottom, which is the asymmetry you want: smoke drifting
  across the top and bottom edges while the face stays legible. Raise
  --lv-front-hole if smoke starts creeping into the middle.

  The hole is measured against the PANEL, not the viewport, so it has to be
  re-solved whenever the panel size changes - which it has, repeatedly. At
  the current 38vh base growing to 156% on arrival, the hero is about 59% of
  the screen width, so its half-width is ~30% of the viewport against an 82%
  gradient ellipse: 38% along the ray. Set it too high and the front smoke
  never touches a panel edge at all; too low and it hazes the product.
*/
.lv-smoke-front {
  --lv-front-hole: 38%;
  z-index: 150;
  opacity: 0.5;
  -webkit-mask-image: radial-gradient(
    ellipse 82% 60% at 50% 49%,
    transparent 0%, transparent var(--lv-front-hole),
    rgba(0, 0, 0, 0.9) 74%, #000 86%, transparent 100%
  );
  mask-image: radial-gradient(
    ellipse 82% 60% at 50% 49%,
    transparent 0%, transparent var(--lv-front-hole),
    rgba(0, 0, 0, 0.9) 74%, #000 86%, transparent 100%
  );
}

.lv-smoke-front video {
  filter: blur(6px);
  animation: lv-smoke-drift-front 23s ease-in-out infinite;
}

/* Each layer drifts at its own rate and in its own direction. Matching them
   would collapse the three depths back into one plane - the differential is
   the only thing telling your eye they are at different distances. Faster
   as they get closer, which is what parallax does. */
@keyframes lv-smoke-drift-mid {
  0%   { transform: scale(1.14) translate3d(1%, 0, 0); }
  50%  { transform: scale(1.22) translate3d(-2%, 1.5%, 0); }
  100% { transform: scale(1.14) translate3d(1%, 0, 0); }
}

@keyframes lv-smoke-drift-front {
  0%   { transform: scale(1.3) translate3d(-2%, 1%, 0); }
  50%  { transform: scale(1.42) translate3d(3%, -2%, 0); }
  100% { transform: scale(1.3) translate3d(-2%, 1%, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .lv-smoke-layer { display: none; }
}

/* No transform here. Every slide's position is a point ON a cylinder,
   computed in carousel.js and written through GSAP (xPercent/yPercent for
   the centring, then x/z/rotationY for the ring placement) - GSAP composes
   translate before rotate, which is exactly the order a cylinder needs:
   move out to the surface, then turn the panel tangent to it. Declaring a
   transform in CSS as well would just get overwritten on the first frame.

   16:9, matching the product page hero exactly, so ONE render per product
   serves both surfaces. The slide reading widest at the centre and
   narrowing as it turns away is not an effect applied here - it is just
   what the cylinder's own foreshortening does to a landscape panel. */
/*
  Sized from HEIGHT, not width, and uncapped in px. This is a full-screen
  product splash: the centre panel should own the viewport at any size, so
  it scales with the window rather than topping out at a fixed pixel width
  and stranding a big screen with a small hero.

  38vh is the base; the centred panel grows to 130% as it arrives, so the
  hero renders at about 49vh - noticeably larger than any previous version -
  and overlaps its neighbours on the way.

  42vw is the guard for tall or narrow windows, where that height would
  otherwise compute wider than the viewport.

  Panel size is what decides how many products can be on screen, and the
  relationship is not gradual. Measured on 1920x1080, holding a >40px gap
  and taking the best radius and perspective available at each size:

     70vh -> 2.25 products     48vh -> 3.25
     60vh -> 2.75              40vh -> 3.75   <- here
     52vh -> 2.75              36vh -> 4.25

  Note the dead zone. Between 70vh and 50vh the count barely moves: you give
  up a third of the hero and get nothing back. The count only starts
  responding below about 48vh. So there is no useful middle ground between
  the full-screen splash and a real cylinder - anything in that band is
  strictly worse than the splash, being smaller for the same three panels.

  40vh is the smallest panel that still feels like a hero while putting four
  products on screen through most of the rotation (three at the rest
  position, four through the rest of the travel).

  The bottom of the panel lands under the hero copy. That is intentional
  rather than a collision: the mask feathers the bottom edge away and the
  ::after ramp darkens it, so the product name reads over the panel the way
  a hero caption should.
*/
.lv-slide {
  position: absolute;
  top: 50%;
  left: 50%;
  height: min(38vh, 42vw);
  aspect-ratio: 16 / 9;
  width: auto;
  margin: 0;
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}

/* Both <img> and <video> are styled here. Slides start as posters and the
   ones near the front swap to playing video (see carousel.js) - nine
   simultaneous decodes would stall the compositor, and the animation only
   needs to exist where it can actually be watched. */
/* DIRECT children only.

   These rules describe the slide's MEDIA - the poster, the stills stack and
   the loop - all of which are appended straight onto the figure. They are not
   a general statement about images.

   The descendant selector this replaced was: it reached into the caption and
   caught the brand lockup there, which arrived absolutely positioned into the
   panel's top-left corner, stretched by object-fit, feathered by the hero's
   edge mask and - the part that actually mattered - run through the panel's
   saturate(0.85) grade, quietly shifting the brand purple and green off
   specification. Approved logo artwork must never inherit a scene grade. */
.lv-slide > img,
.lv-slide > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--lv-radius-lg);
  filter: saturate(0.85) contrast(1.05);
  /*
     A rectangular inset fade, not a radial one.

     The previous version was a radial ellipse, which is the wrong shape of
     tool for this: how far the fade reaches depends on the direction you
     leave the centre, so it eats the corners several times harder than the
     edge midpoints and cannot express "crisp centre, evenly soft border" at
     any setting. Two crossed linear gradients composited with `intersect`
     give a real inset border - a clean rectangle of untouched image with a
     soft band all the way round it.

     --lv-fade is the width of that band as a percentage of the panel, and
     carousel.js drives it from rotation: about 5% at the centre, so the
     middle ~90% of the hero is completely crisp, growing to a heavy dissolve
     as the panel turns away and gives itself back to the smoke.

     Each side of the band uses 24 stops following a smootherstep curve
     (6t^5 - 15t^4 + 10t^3). Two things are going on and both matter.

     The CURVE is why there is no visible edge: a plain two-stop gradient is
     a linear alpha ramp, and a linear ramp does not look linear - your eye
     finds the exact point where the image leaves full opacity, so it appears
     to have a hard edge sitting inside its own soft band. Smootherstep
     leaves 1 and arrives at 0 with zero slope, so the fade is underway
     before you can see it start.

     The STOP COUNT is why it is not chunky. Browsers interpolate straight
     between stops, so the curve is only ever approximated by flat facets.
     Six stops leaves a 1.9% alpha error at the middle of each segment, which
     on a 300px band is a visible kink. 24 stops takes that to 0.12%, roughly
     sixteen times smoother, at the cost of a long declaration and nothing
     else - it is the same single gradient either way.

     --lv-fade-y is the same band measured against height. It is NOT the same
     number: on a 16:9 panel, 5% of the width and 5% of the height are very
     different distances, and using one value for both puts a visibly thicker
     border on the sides than the top.

     The fallbacks live at the use site rather than as declarations here. A
     custom property declared on this element would beat the value inherited
     from the figure, so the JS-driven value would be silently ignored and
     every panel would sit at the fallback forever.
  */
  -webkit-mask-image:
    linear-gradient(to right,
      transparent 0%,
      rgba(0, 0, 0, 0.0007) calc(var(--lv-fade, 6%) * 0.0417),
      rgba(0, 0, 0, 0.0051) calc(var(--lv-fade, 6%) * 0.0833),
      rgba(0, 0, 0, 0.0161) calc(var(--lv-fade, 6%) * 0.1250),
      rgba(0, 0, 0, 0.0355) calc(var(--lv-fade, 6%) * 0.1667),
      rgba(0, 0, 0, 0.0645) calc(var(--lv-fade, 6%) * 0.2083),
      rgba(0, 0, 0, 0.1035) calc(var(--lv-fade, 6%) * 0.2500),
      rgba(0, 0, 0, 0.1522) calc(var(--lv-fade, 6%) * 0.2917),
      rgba(0, 0, 0, 0.2099) calc(var(--lv-fade, 6%) * 0.3333),
      rgba(0, 0, 0, 0.2752) calc(var(--lv-fade, 6%) * 0.3750),
      rgba(0, 0, 0, 0.3466) calc(var(--lv-fade, 6%) * 0.4167),
      rgba(0, 0, 0, 0.4222) calc(var(--lv-fade, 6%) * 0.4583),
      rgba(0, 0, 0, 0.5000) calc(var(--lv-fade, 6%) * 0.5000),
      rgba(0, 0, 0, 0.5778) calc(var(--lv-fade, 6%) * 0.5417),
      rgba(0, 0, 0, 0.6534) calc(var(--lv-fade, 6%) * 0.5833),
      rgba(0, 0, 0, 0.7248) calc(var(--lv-fade, 6%) * 0.6250),
      rgba(0, 0, 0, 0.7901) calc(var(--lv-fade, 6%) * 0.6667),
      rgba(0, 0, 0, 0.8478) calc(var(--lv-fade, 6%) * 0.7083),
      rgba(0, 0, 0, 0.8965) calc(var(--lv-fade, 6%) * 0.7500),
      rgba(0, 0, 0, 0.9355) calc(var(--lv-fade, 6%) * 0.7917),
      rgba(0, 0, 0, 0.9645) calc(var(--lv-fade, 6%) * 0.8333),
      rgba(0, 0, 0, 0.9839) calc(var(--lv-fade, 6%) * 0.8750),
      rgba(0, 0, 0, 0.9949) calc(var(--lv-fade, 6%) * 0.9167),
      rgba(0, 0, 0, 0.9993) calc(var(--lv-fade, 6%) * 0.9583),
      #000 var(--lv-fade, 6%),
      #000 calc(100% - var(--lv-fade, 6%)),
      rgba(0, 0, 0, 0.9993) calc(100% - var(--lv-fade, 6%) * 0.9583),
      rgba(0, 0, 0, 0.9949) calc(100% - var(--lv-fade, 6%) * 0.9167),
      rgba(0, 0, 0, 0.9839) calc(100% - var(--lv-fade, 6%) * 0.8750),
      rgba(0, 0, 0, 0.9645) calc(100% - var(--lv-fade, 6%) * 0.8333),
      rgba(0, 0, 0, 0.9355) calc(100% - var(--lv-fade, 6%) * 0.7917),
      rgba(0, 0, 0, 0.8965) calc(100% - var(--lv-fade, 6%) * 0.7500),
      rgba(0, 0, 0, 0.8478) calc(100% - var(--lv-fade, 6%) * 0.7083),
      rgba(0, 0, 0, 0.7901) calc(100% - var(--lv-fade, 6%) * 0.6667),
      rgba(0, 0, 0, 0.7248) calc(100% - var(--lv-fade, 6%) * 0.6250),
      rgba(0, 0, 0, 0.6534) calc(100% - var(--lv-fade, 6%) * 0.5833),
      rgba(0, 0, 0, 0.5778) calc(100% - var(--lv-fade, 6%) * 0.5417),
      rgba(0, 0, 0, 0.5000) calc(100% - var(--lv-fade, 6%) * 0.5000),
      rgba(0, 0, 0, 0.4222) calc(100% - var(--lv-fade, 6%) * 0.4583),
      rgba(0, 0, 0, 0.3466) calc(100% - var(--lv-fade, 6%) * 0.4167),
      rgba(0, 0, 0, 0.2752) calc(100% - var(--lv-fade, 6%) * 0.3750),
      rgba(0, 0, 0, 0.2099) calc(100% - var(--lv-fade, 6%) * 0.3333),
      rgba(0, 0, 0, 0.1522) calc(100% - var(--lv-fade, 6%) * 0.2917),
      rgba(0, 0, 0, 0.1035) calc(100% - var(--lv-fade, 6%) * 0.2500),
      rgba(0, 0, 0, 0.0645) calc(100% - var(--lv-fade, 6%) * 0.2083),
      rgba(0, 0, 0, 0.0355) calc(100% - var(--lv-fade, 6%) * 0.1667),
      rgba(0, 0, 0, 0.0161) calc(100% - var(--lv-fade, 6%) * 0.1250),
      rgba(0, 0, 0, 0.0051) calc(100% - var(--lv-fade, 6%) * 0.0833),
      rgba(0, 0, 0, 0.0007) calc(100% - var(--lv-fade, 6%) * 0.0417),
      transparent 100%),
    linear-gradient(to bottom,
      transparent 0%,
      rgba(0, 0, 0, 0.0007) calc(var(--lv-fade-y, 10%) * 0.0417),
      rgba(0, 0, 0, 0.0051) calc(var(--lv-fade-y, 10%) * 0.0833),
      rgba(0, 0, 0, 0.0161) calc(var(--lv-fade-y, 10%) * 0.1250),
      rgba(0, 0, 0, 0.0355) calc(var(--lv-fade-y, 10%) * 0.1667),
      rgba(0, 0, 0, 0.0645) calc(var(--lv-fade-y, 10%) * 0.2083),
      rgba(0, 0, 0, 0.1035) calc(var(--lv-fade-y, 10%) * 0.2500),
      rgba(0, 0, 0, 0.1522) calc(var(--lv-fade-y, 10%) * 0.2917),
      rgba(0, 0, 0, 0.2099) calc(var(--lv-fade-y, 10%) * 0.3333),
      rgba(0, 0, 0, 0.2752) calc(var(--lv-fade-y, 10%) * 0.3750),
      rgba(0, 0, 0, 0.3466) calc(var(--lv-fade-y, 10%) * 0.4167),
      rgba(0, 0, 0, 0.4222) calc(var(--lv-fade-y, 10%) * 0.4583),
      rgba(0, 0, 0, 0.5000) calc(var(--lv-fade-y, 10%) * 0.5000),
      rgba(0, 0, 0, 0.5778) calc(var(--lv-fade-y, 10%) * 0.5417),
      rgba(0, 0, 0, 0.6534) calc(var(--lv-fade-y, 10%) * 0.5833),
      rgba(0, 0, 0, 0.7248) calc(var(--lv-fade-y, 10%) * 0.6250),
      rgba(0, 0, 0, 0.7901) calc(var(--lv-fade-y, 10%) * 0.6667),
      rgba(0, 0, 0, 0.8478) calc(var(--lv-fade-y, 10%) * 0.7083),
      rgba(0, 0, 0, 0.8965) calc(var(--lv-fade-y, 10%) * 0.7500),
      rgba(0, 0, 0, 0.9355) calc(var(--lv-fade-y, 10%) * 0.7917),
      rgba(0, 0, 0, 0.9645) calc(var(--lv-fade-y, 10%) * 0.8333),
      rgba(0, 0, 0, 0.9839) calc(var(--lv-fade-y, 10%) * 0.8750),
      rgba(0, 0, 0, 0.9949) calc(var(--lv-fade-y, 10%) * 0.9167),
      rgba(0, 0, 0, 0.9993) calc(var(--lv-fade-y, 10%) * 0.9583),
      #000 var(--lv-fade-y, 10%),
      #000 calc(100% - var(--lv-fade-y, 10%)),
      rgba(0, 0, 0, 0.9993) calc(100% - var(--lv-fade-y, 10%) * 0.9583),
      rgba(0, 0, 0, 0.9949) calc(100% - var(--lv-fade-y, 10%) * 0.9167),
      rgba(0, 0, 0, 0.9839) calc(100% - var(--lv-fade-y, 10%) * 0.8750),
      rgba(0, 0, 0, 0.9645) calc(100% - var(--lv-fade-y, 10%) * 0.8333),
      rgba(0, 0, 0, 0.9355) calc(100% - var(--lv-fade-y, 10%) * 0.7917),
      rgba(0, 0, 0, 0.8965) calc(100% - var(--lv-fade-y, 10%) * 0.7500),
      rgba(0, 0, 0, 0.8478) calc(100% - var(--lv-fade-y, 10%) * 0.7083),
      rgba(0, 0, 0, 0.7901) calc(100% - var(--lv-fade-y, 10%) * 0.6667),
      rgba(0, 0, 0, 0.7248) calc(100% - var(--lv-fade-y, 10%) * 0.6250),
      rgba(0, 0, 0, 0.6534) calc(100% - var(--lv-fade-y, 10%) * 0.5833),
      rgba(0, 0, 0, 0.5778) calc(100% - var(--lv-fade-y, 10%) * 0.5417),
      rgba(0, 0, 0, 0.5000) calc(100% - var(--lv-fade-y, 10%) * 0.5000),
      rgba(0, 0, 0, 0.4222) calc(100% - var(--lv-fade-y, 10%) * 0.4583),
      rgba(0, 0, 0, 0.3466) calc(100% - var(--lv-fade-y, 10%) * 0.4167),
      rgba(0, 0, 0, 0.2752) calc(100% - var(--lv-fade-y, 10%) * 0.3750),
      rgba(0, 0, 0, 0.2099) calc(100% - var(--lv-fade-y, 10%) * 0.3333),
      rgba(0, 0, 0, 0.1522) calc(100% - var(--lv-fade-y, 10%) * 0.2917),
      rgba(0, 0, 0, 0.1035) calc(100% - var(--lv-fade-y, 10%) * 0.2500),
      rgba(0, 0, 0, 0.0645) calc(100% - var(--lv-fade-y, 10%) * 0.2083),
      rgba(0, 0, 0, 0.0355) calc(100% - var(--lv-fade-y, 10%) * 0.1667),
      rgba(0, 0, 0, 0.0161) calc(100% - var(--lv-fade-y, 10%) * 0.1250),
      rgba(0, 0, 0, 0.0051) calc(100% - var(--lv-fade-y, 10%) * 0.0833),
      rgba(0, 0, 0, 0.0007) calc(100% - var(--lv-fade-y, 10%) * 0.0417),
      transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right,
      transparent 0%,
      rgba(0, 0, 0, 0.0007) calc(var(--lv-fade, 6%) * 0.0417),
      rgba(0, 0, 0, 0.0051) calc(var(--lv-fade, 6%) * 0.0833),
      rgba(0, 0, 0, 0.0161) calc(var(--lv-fade, 6%) * 0.1250),
      rgba(0, 0, 0, 0.0355) calc(var(--lv-fade, 6%) * 0.1667),
      rgba(0, 0, 0, 0.0645) calc(var(--lv-fade, 6%) * 0.2083),
      rgba(0, 0, 0, 0.1035) calc(var(--lv-fade, 6%) * 0.2500),
      rgba(0, 0, 0, 0.1522) calc(var(--lv-fade, 6%) * 0.2917),
      rgba(0, 0, 0, 0.2099) calc(var(--lv-fade, 6%) * 0.3333),
      rgba(0, 0, 0, 0.2752) calc(var(--lv-fade, 6%) * 0.3750),
      rgba(0, 0, 0, 0.3466) calc(var(--lv-fade, 6%) * 0.4167),
      rgba(0, 0, 0, 0.4222) calc(var(--lv-fade, 6%) * 0.4583),
      rgba(0, 0, 0, 0.5000) calc(var(--lv-fade, 6%) * 0.5000),
      rgba(0, 0, 0, 0.5778) calc(var(--lv-fade, 6%) * 0.5417),
      rgba(0, 0, 0, 0.6534) calc(var(--lv-fade, 6%) * 0.5833),
      rgba(0, 0, 0, 0.7248) calc(var(--lv-fade, 6%) * 0.6250),
      rgba(0, 0, 0, 0.7901) calc(var(--lv-fade, 6%) * 0.6667),
      rgba(0, 0, 0, 0.8478) calc(var(--lv-fade, 6%) * 0.7083),
      rgba(0, 0, 0, 0.8965) calc(var(--lv-fade, 6%) * 0.7500),
      rgba(0, 0, 0, 0.9355) calc(var(--lv-fade, 6%) * 0.7917),
      rgba(0, 0, 0, 0.9645) calc(var(--lv-fade, 6%) * 0.8333),
      rgba(0, 0, 0, 0.9839) calc(var(--lv-fade, 6%) * 0.8750),
      rgba(0, 0, 0, 0.9949) calc(var(--lv-fade, 6%) * 0.9167),
      rgba(0, 0, 0, 0.9993) calc(var(--lv-fade, 6%) * 0.9583),
      #000 var(--lv-fade, 6%),
      #000 calc(100% - var(--lv-fade, 6%)),
      rgba(0, 0, 0, 0.9993) calc(100% - var(--lv-fade, 6%) * 0.9583),
      rgba(0, 0, 0, 0.9949) calc(100% - var(--lv-fade, 6%) * 0.9167),
      rgba(0, 0, 0, 0.9839) calc(100% - var(--lv-fade, 6%) * 0.8750),
      rgba(0, 0, 0, 0.9645) calc(100% - var(--lv-fade, 6%) * 0.8333),
      rgba(0, 0, 0, 0.9355) calc(100% - var(--lv-fade, 6%) * 0.7917),
      rgba(0, 0, 0, 0.8965) calc(100% - var(--lv-fade, 6%) * 0.7500),
      rgba(0, 0, 0, 0.8478) calc(100% - var(--lv-fade, 6%) * 0.7083),
      rgba(0, 0, 0, 0.7901) calc(100% - var(--lv-fade, 6%) * 0.6667),
      rgba(0, 0, 0, 0.7248) calc(100% - var(--lv-fade, 6%) * 0.6250),
      rgba(0, 0, 0, 0.6534) calc(100% - var(--lv-fade, 6%) * 0.5833),
      rgba(0, 0, 0, 0.5778) calc(100% - var(--lv-fade, 6%) * 0.5417),
      rgba(0, 0, 0, 0.5000) calc(100% - var(--lv-fade, 6%) * 0.5000),
      rgba(0, 0, 0, 0.4222) calc(100% - var(--lv-fade, 6%) * 0.4583),
      rgba(0, 0, 0, 0.3466) calc(100% - var(--lv-fade, 6%) * 0.4167),
      rgba(0, 0, 0, 0.2752) calc(100% - var(--lv-fade, 6%) * 0.3750),
      rgba(0, 0, 0, 0.2099) calc(100% - var(--lv-fade, 6%) * 0.3333),
      rgba(0, 0, 0, 0.1522) calc(100% - var(--lv-fade, 6%) * 0.2917),
      rgba(0, 0, 0, 0.1035) calc(100% - var(--lv-fade, 6%) * 0.2500),
      rgba(0, 0, 0, 0.0645) calc(100% - var(--lv-fade, 6%) * 0.2083),
      rgba(0, 0, 0, 0.0355) calc(100% - var(--lv-fade, 6%) * 0.1667),
      rgba(0, 0, 0, 0.0161) calc(100% - var(--lv-fade, 6%) * 0.1250),
      rgba(0, 0, 0, 0.0051) calc(100% - var(--lv-fade, 6%) * 0.0833),
      rgba(0, 0, 0, 0.0007) calc(100% - var(--lv-fade, 6%) * 0.0417),
      transparent 100%),
    linear-gradient(to bottom,
      transparent 0%,
      rgba(0, 0, 0, 0.0007) calc(var(--lv-fade-y, 10%) * 0.0417),
      rgba(0, 0, 0, 0.0051) calc(var(--lv-fade-y, 10%) * 0.0833),
      rgba(0, 0, 0, 0.0161) calc(var(--lv-fade-y, 10%) * 0.1250),
      rgba(0, 0, 0, 0.0355) calc(var(--lv-fade-y, 10%) * 0.1667),
      rgba(0, 0, 0, 0.0645) calc(var(--lv-fade-y, 10%) * 0.2083),
      rgba(0, 0, 0, 0.1035) calc(var(--lv-fade-y, 10%) * 0.2500),
      rgba(0, 0, 0, 0.1522) calc(var(--lv-fade-y, 10%) * 0.2917),
      rgba(0, 0, 0, 0.2099) calc(var(--lv-fade-y, 10%) * 0.3333),
      rgba(0, 0, 0, 0.2752) calc(var(--lv-fade-y, 10%) * 0.3750),
      rgba(0, 0, 0, 0.3466) calc(var(--lv-fade-y, 10%) * 0.4167),
      rgba(0, 0, 0, 0.4222) calc(var(--lv-fade-y, 10%) * 0.4583),
      rgba(0, 0, 0, 0.5000) calc(var(--lv-fade-y, 10%) * 0.5000),
      rgba(0, 0, 0, 0.5778) calc(var(--lv-fade-y, 10%) * 0.5417),
      rgba(0, 0, 0, 0.6534) calc(var(--lv-fade-y, 10%) * 0.5833),
      rgba(0, 0, 0, 0.7248) calc(var(--lv-fade-y, 10%) * 0.6250),
      rgba(0, 0, 0, 0.7901) calc(var(--lv-fade-y, 10%) * 0.6667),
      rgba(0, 0, 0, 0.8478) calc(var(--lv-fade-y, 10%) * 0.7083),
      rgba(0, 0, 0, 0.8965) calc(var(--lv-fade-y, 10%) * 0.7500),
      rgba(0, 0, 0, 0.9355) calc(var(--lv-fade-y, 10%) * 0.7917),
      rgba(0, 0, 0, 0.9645) calc(var(--lv-fade-y, 10%) * 0.8333),
      rgba(0, 0, 0, 0.9839) calc(var(--lv-fade-y, 10%) * 0.8750),
      rgba(0, 0, 0, 0.9949) calc(var(--lv-fade-y, 10%) * 0.9167),
      rgba(0, 0, 0, 0.9993) calc(var(--lv-fade-y, 10%) * 0.9583),
      #000 var(--lv-fade-y, 10%),
      #000 calc(100% - var(--lv-fade-y, 10%)),
      rgba(0, 0, 0, 0.9993) calc(100% - var(--lv-fade-y, 10%) * 0.9583),
      rgba(0, 0, 0, 0.9949) calc(100% - var(--lv-fade-y, 10%) * 0.9167),
      rgba(0, 0, 0, 0.9839) calc(100% - var(--lv-fade-y, 10%) * 0.8750),
      rgba(0, 0, 0, 0.9645) calc(100% - var(--lv-fade-y, 10%) * 0.8333),
      rgba(0, 0, 0, 0.9355) calc(100% - var(--lv-fade-y, 10%) * 0.7917),
      rgba(0, 0, 0, 0.8965) calc(100% - var(--lv-fade-y, 10%) * 0.7500),
      rgba(0, 0, 0, 0.8478) calc(100% - var(--lv-fade-y, 10%) * 0.7083),
      rgba(0, 0, 0, 0.7901) calc(100% - var(--lv-fade-y, 10%) * 0.6667),
      rgba(0, 0, 0, 0.7248) calc(100% - var(--lv-fade-y, 10%) * 0.6250),
      rgba(0, 0, 0, 0.6534) calc(100% - var(--lv-fade-y, 10%) * 0.5833),
      rgba(0, 0, 0, 0.5778) calc(100% - var(--lv-fade-y, 10%) * 0.5417),
      rgba(0, 0, 0, 0.5000) calc(100% - var(--lv-fade-y, 10%) * 0.5000),
      rgba(0, 0, 0, 0.4222) calc(100% - var(--lv-fade-y, 10%) * 0.4583),
      rgba(0, 0, 0, 0.3466) calc(100% - var(--lv-fade-y, 10%) * 0.4167),
      rgba(0, 0, 0, 0.2752) calc(100% - var(--lv-fade-y, 10%) * 0.3750),
      rgba(0, 0, 0, 0.2099) calc(100% - var(--lv-fade-y, 10%) * 0.3333),
      rgba(0, 0, 0, 0.1522) calc(100% - var(--lv-fade-y, 10%) * 0.2917),
      rgba(0, 0, 0, 0.1035) calc(100% - var(--lv-fade-y, 10%) * 0.2500),
      rgba(0, 0, 0, 0.0645) calc(100% - var(--lv-fade-y, 10%) * 0.2083),
      rgba(0, 0, 0, 0.0355) calc(100% - var(--lv-fade-y, 10%) * 0.1667),
      rgba(0, 0, 0, 0.0161) calc(100% - var(--lv-fade-y, 10%) * 0.1250),
      rgba(0, 0, 0, 0.0051) calc(100% - var(--lv-fade-y, 10%) * 0.0833),
      rgba(0, 0, 0, 0.0007) calc(100% - var(--lv-fade-y, 10%) * 0.0417),
      transparent 100%);
  mask-composite: intersect;
}

/* The playing copy sits over its own poster, so the swap has nothing to
   cover: the still is already the first frame underneath it.

   :not(.lv-still) IS LOAD-BEARING, and the reason is worth keeping.

   Since 25 Aug a product's FRAMES can be videos too - HD-X has seven of them.
   Those are direct children of .lv-slide, so without this exclusion the rule
   below would set every one of them to opacity 1 the moment the slide started
   playing, and all seven use cases would be stacked on top of each other at
   once. Frame visibility belongs to .lv-still / .is-shown alone; this pair of
   rules governs only a product with ONE continuous clip, like PROLUX Rugged.

   The same reasoning as the .lv-slide > img fix in June: a selector that was
   correct when there was one kind of media stops being correct the moment
   there are two. */
.lv-slide > video:not(.lv-still) {
  opacity: 0;
  transition: opacity 0.45s var(--lv-ease);
}
.lv-slide.is-playing > video:not(.lv-still) { opacity: 1; }

/* Cross-fading stills, for products whose story is breadth rather than a
   single continuous event.

   Each still sits over the poster and over its predecessors, so a fade-in is
   all that is needed - there is never a gap to cover, and no frame has to
   fade OUT before the next fades in. That avoids the dip to black a naive
   cross-dissolve produces.

   The fade is deliberately slower than the video swap (1.1s against 0.45s).
   A video swap is a state change the eye should barely notice; this is the
   content itself changing, and it should feel like a considered dissolve
   rather than a slideshow click. */
.lv-slide .lv-still {
  opacity: 0;
  transition: opacity 1.1s var(--lv-ease);
  z-index: 1;
}
.lv-slide .lv-still.is-shown { opacity: 1; }

/* Reduced motion: no sequence runs at all, so the panel simply holds its
   locked hero frame. The poster is already correct underneath. */
@media (prefers-reduced-motion: reduce) {
  .lv-slide .lv-still { display: none; }
}

/* The product name, riding under its own panel.

   Deliberately NOT masked: the edge feather is applied to the img and video
   only, so the caption stays crisp while the panel it belongs to dissolves.
   It does inherit the figure's opacity and distance haze, so it recedes with
   its product - which is what makes it read as a label ON the ring rather
   than a caption floating in front of it.

   Sits below the panel in the same 3D plane, so it turns with the slide.
   Type stays small and quiet: at the centre it is a label, not a headline -
   the panel is doing the talking. */
.lv-slide figcaption {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 1.1rem;
  text-align: center;
  font-family: var(--lv-font-display);
  font-size: clamp(0.95rem, 1.15vw, 1.35rem);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--lv-ink);
  text-shadow: 0 2px 18px rgba(11, 11, 16, 0.85);
  pointer-events: none;
  white-space: nowrap;
}

/* THE CAPTION LOCKUP — brand artwork plus variant word.

   Every name in a slide's sequence occupies the SAME grid cell, so the stack
   sizes itself to the widest name and the shorter ones cross-fade inside a
   box that never changes width. Rewriting textContent instead would have
   reflowed the caption mid-dissolve, which reads as a twitch under a panel
   that is otherwise gliding.

   `justify-items: center` rather than `text-align` because each name is a
   flex row of two dissimilar things - an image and a word - and they need to
   be centred as a unit, not as text. */
.lv-cap-stack {
  display: inline-grid;
  vertical-align: bottom;
  justify-items: center;
}

.lv-cap-name {
  grid-area: 1 / 1;
  /* A COLUMN now: the lockup row, then the descriptor. It was a row of two
     baseline-aligned items, and .lv-cap-line inherited that job. */
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  /* Matched to .lv-still's 1.1s so the word and the picture it names change
     together. A faster caption would announce the next frame before it
     arrives; a slower one would leave the old name sitting under the new
     image, which is worse than either. */
  transition: opacity 1.1s var(--lv-ease);
}
.lv-cap-name.is-shown { opacity: 1; }

/* The lockup row: the mark and the variant word, on a shared baseline. This is
   what .lv-cap-name used to be, lifted out one level when the descriptor made
   the name a column. */
.lv-cap-line {
  display: flex;
  align-items: baseline;
  gap: 0.5em;
}

/* ── THE DESCRIPTOR ────────────────────────────────────────────────────────
   Joel, 15 September 2026: "a small descriptor centered under each of the
   product lines to tell at a glance what they each serve ... the same Gray
   look as the Badges, this text should be visible and readable but not
   distract from the Hero."

   ⚠️ NO SEPARATE TRANSITION, AND THAT IS THE POINT. It sits INSIDE
   .lv-cap-name, so it inherits that element's 1.1s opacity fade and cannot
   drift out of step with the name above it. Joel asked for them to "fade in
   and out at the same rate"; giving the descriptor its own matching duration
   would have been two timings that agree today and diverge the first time
   either is touched. One timing cannot disagree with itself.

   Modelled on .lv-lbl from badge-strip.css, which is the same object in a
   different place: a small grey label centred beneath a tile. Same face, same
   weight, same tracking, same --lv-ink-muted. Measured on the page ground:
   #9c99ac on #0b0b10 is 7.08:1, which clears AA at this size with room spare.

   ⛔ NO CONTAINER. Joel, 15 September 2026: "lets get rid of the pill shaped
   container the new text sits in." It was a glass chip in the badges' material
   — gradient, inset rim, drop shadow — and the badge look he asked for turns
   out to have meant the badge LABEL, which is bare type, rather than the badge
   tile. He is right: six lit chips orbiting under six panels is furniture
   competing with the photography they sit beneath, which is the opposite of
   "not distract from the Hero".

   ⚠️ THE CHIP WAS CARRYING THE LEGIBILITY, so removing it is not just deleting
   three properties. The type had `text-shadow: none` precisely because it sat
   on its own opaque-ish ground; bare over the page it needs the shadow back,
   and a tighter one than the caption's — the caption is near-white at 600 and
   can take an 18px bloom, this is muted grey at 0.46em and an 18px bloom on it
   just makes it look smudged.

   ⚠️ AND NO backdrop-filter WAS EVER HERE, chip or not. The ring is a 3D
   transformed stage, and badge-strip.css carries a hard-won warning that live
   backdrop sampling inside a preserve-3d subtree kills the glass on every
   resting tile. */
.lv-cap-desc {
  margin-top: 0.7em;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15em;
  font-family: var(--lv-font-display);
  /* Sized against the caption's own clamp() rather than the root, so it stays
     in proportion through every breakpoint instead of needing its own. */
  font-size: 0.46em;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--lv-ink-muted);
  text-shadow: 0 1px 10px rgba(11, 11, 16, 0.95);
}

/* Sized in `em` so the mark tracks the variant word through the caption's
   clamp() instead of needing its own breakpoints.

   1.05em, and deliberately NOT matched to cap height.

   The artwork's ink fills its viewBox edge to edge with no padding, so the
   box height IS the wordmark's cap height. Setting it to the caption's cap
   height (0.86em) therefore made the two capitals geometrically equal - and
   it looked wrong, because the lockup is a hairline geometric face sitting
   next to a semibold one. Equal height with a quarter of the stroke weight
   reads as SMALLER, and the variant word ended up shouting over the brand,
   which is the hierarchy backwards.

   So the mark is oversized against the caps by about 20% to buy back the
   weight it gives away in stroke, and the variant steps down to 500 to meet
   it. Judged by rendering both at true caption size, not by arithmetic.

   Bottom edge on the baseline is correct as-is: the wordmark is all caps with
   no descender, so its baseline is its bottom edge, exactly like the R beside
   it. No nudge needed once the size is right. */
.lv-cap-mark {
  height: 1.05em;
  width: auto;
  display: block;
  /* Defensive resets. The slide's media rules are scoped to direct children
     now, so none of this should reach here - but approved logo artwork is
     the last thing that should silently inherit a scene grade or a mask if
     that selector is ever loosened again. */
  position: static;
  object-fit: contain;
  border-radius: 0;
  filter: none;
  -webkit-mask: none;
  mask: none;
}

/* Steps back from the figcaption's 600 so the brand leads and the variant
   supports. The tracking opens as the weight drops - lighter type at this
   size closes up and starts to look cramped beside the mark's generous
   letterfit.

   Joel asked for the variant to stay "in the same font and colour you already
   have", and it does: same face, same ink, same size. Only the weight moves,
   and it moves so the logo can be seen. */
.lv-cap-variant {
  display: block;
  font-weight: 500;
  letter-spacing: 0.045em;
}

/* Reduced motion: the sequence never runs, so only the lead name is ever
   shown. Without this it would sit at opacity 0 forever, since the reveal
   normally arrives via setPlayback. */
@media (prefers-reduced-motion: reduce) {
  .lv-cap-name { transition: none; }
  .lv-cap-name[data-name="-1"] { opacity: 1; }
}

.lv-slide::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--lv-radius-lg);
  background: linear-gradient(180deg, transparent 55%, rgba(11, 11, 16, 0.55) 100%);
  pointer-events: none;
}

/* ---- SELECTION TRANSITION: the panel grows into the next page ----

   A clone of the centred slide, promoted out of the ring onto a fixed layer
   where nothing is writing to its transform, then grown to fill the viewport.
   See flyToPage() in carousel.js for why it is a clone and not the slide.

   Deliberately carries NONE of the ring's treatment: no edge feather, no
   distance haze, no rounded corners by the end of the tween. Those exist to
   make a panel sit inside a rotating ring, and this panel is leaving it. */
.lv-flyer {
  position: fixed;
  z-index: 400; /* over the smoke front layer at 150 and the nav */
  overflow: hidden;
  border-radius: var(--lv-radius-lg);
  pointer-events: none;
  will-change: left, top, width, height;
}

/* The radius has to reach zero by the time the frame is full-bleed, or the
   product page - which has square corners - appears to snap at the cut. */
.lv-flyer { transition: border-radius 0.42s var(--lv-ease); }
.lv-flyer.is-full { border-radius: 0; }

.lv-flyer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The dip. Opaque black rather than a fade to the page background: the next
   document paints its own background before its hero decodes, and any
   transparency at that moment shows the seam we are hiding. */
.lv-flyer-veil {
  position: fixed;
  inset: 0;
  z-index: 401;
  background: #000;
  opacity: 0;
  pointer-events: none;
}

/* .lv-hero-copy removed 12 Aug 2026 - the block it positioned no longer sits
   over the ring. The brand line now lives in .lv-hero-handoff, an opaque
   panel below the hero; see landing.css. Nothing in the carousel layer
   positions copy any more, which is the point: this file describes the ring
   and only the ring. */

/* Base type for the standing brand line. Position and measure belong to
   whichever panel is carrying it - .lv-hero-handoff overrides the size and
   width there, since a line with a viewport to itself wants a longer measure
   than one tucked into a corner. */
.lv-hero-headline {
  font-family: var(--lv-font-display);
  font-size: clamp(1.8rem, 3.2vw, 2.8rem);
  font-weight: 600;
  margin-top: 0.4rem;
  max-width: 16ch;
  line-height: 1.1;
}

.lv-hero-hint {
  position: absolute;
  right: var(--lv-space-gutter);
  bottom: clamp(3rem, 8vh, 5.5rem);
  z-index: 6;
  color: var(--lv-ink-faint);
  font-size: 0.85rem;
  pointer-events: none;
}

@media (max-width: 860px) {
  /* Kept proportionally shorter than the desktop value. The radius shrinks
     with the viewport (see carousel.js), so holding the same viewing
     distance would flatten the curve out on a small screen. */
  .lv-carousel-stage { perspective: 620px; }
  /* Same height-first sizing, but the 52vw guard does the work on a phone:
     a 16:9 panel tall enough to feel like a splash would be far wider than
     a portrait viewport, so width is the real constraint here. */
  .lv-slide { height: min(34vh, 40vw); width: auto; }
  .lv-hero-hint { display: none; }
}
