/* hero.css — VERSION 3.1: the FULL-BLEED AUTOPLAY CINEMATIC.
 *
 * Everything is scoped under .wss-hero / [data-hero-*], so this file is inert on
 * every page that doesn't render the cinematic (the in-flow image heroes in the
 * page/service/team/post templates also use [data-hero] — they are untouched
 * because every rule here is keyed on .wss-hero).
 * Consumes tokens.css (--gold, --ease, --ink, --sans, --serif).
 *
 * ── v3 (scrub) → v3.1 (cinematic) ──────────────────────────────────────────
 * The shape is the thing that changed. v3 was a 500vh scroll REGION containing a
 * sticky stage, the region supplying the scroll distance and the stage repainting
 * under the guest's own thumb. That is gone: Shawn's requirement is that the
 * animation plays through automatically, and Lori's is that her real, warm,
 * lights-on salon is what plays. So the hero is ONE SCREEN of ordinary document
 * flow holding a full-bleed <video> that runs once.
 *
 * The class name .wss-hero__sticky is kept even though nothing sticks any more.
 * It is not laziness: it is the selector home.css, chat.css and four screenshot
 * harnesses already agree on, and renaming it would be a diff across the property
 * in exchange for one adjective.
 *
 * STRUCTURE (templates/hero.js for markup, src/js/hero.js for behaviour):
 *   .wss-hero[data-hero-cinema]     one screen, in flow
 *     .wss-hero__sticky             the stage, 100svh minus the header
 *       .wss-hero__poster           LCP image = a DENSE HAIR frame, fades out  z 1
 *       .wss-hero__video            the cinematic, cover-fit                   z 1
 *       .wss-hero__mirror           the welcome's light layer                  z 2
 *         .wss-hero__welcome        wordmark + tagline + Book Now
 *       .scroll-cue                 (from home.js; home.css positions it)      z 4
 *       .wss-hero__skip             44px affordance                            z 3
 *       .wss-hero__play             tap-to-play, only if autoplay was refused   z 5
 *       .wss-hero__loader           the loading veil, over everything           z 6
 *
 * HEIGHT: .site-header is `position: sticky` AND in flow, so the stage must be
 * exactly that much shorter or its bottom spills past the fold. hero.js measures
 * the header and publishes --wss-header-h. Without JS the var is unset and the
 * fallback is a plain 100svh — the relationship the site had before any
 * cinematic existed.
 *
 * PERFORMANCE: the only animated properties are opacity and two transforms —
 * all compositable. Nothing here animates layout, and there is no mask, no
 * filter over the video and no counter-scaling.
 *
 * --wss-welcome-t is the reveal, 0..1, written by hero.js from the FILM's own
 * currentTime every frame of the closing hold. It is a variable rather than a
 * transition because the wordmark has to arrive WITH the camera settling; a
 * 900ms fade triggered at a threshold arrives on its own schedule instead. Its
 * fallback value is 1, so any path that forgets to write it still shows the
 * content — the failure mode is "visible", never "blank". */

.wss-hero[data-hero-cinema] {
  position: relative;
  width: 100%;
  background: #0a0a0b;
}

.wss-hero__sticky {
  position: relative;
  height: 100vh;
  height: calc(100svh - var(--wss-header-h, 0px));
  min-height: 420px;
  width: 100%;
  overflow: hidden;
  isolation: isolate;
  background: #0a0a0b; /* letterbox colour behind the cover-cropped film */
}

/* ── The film and its plate ────────────────────────────────────────────────── */
.wss-hero__poster,
.wss-hero__video {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center;
}

/* The poster is the LCP element: cinema-poster-*.jpg, painted straight from the
 * HTML before a line of JS runs. It sits UNDER the video and stays there until
 * the film is actually PLAYING — not until it has merely loaded — so there is
 * never a black flash, never a first-frame flicker, and on a device that refuses
 * video there is never a hole where the hero should be.
 *
 * The plate is cut 3.3s into the film, where the hair is dense and gold-lit, and
 * deliberately NOT frame 1: frame 1 is the hair still entering over a pale
 * ground, and "a static beige photo" is precisely what the owner has reported
 * seeing on her phone for three rounds. */
.wss-hero__poster {
  opacity: 1;
  transition: opacity 600ms var(--ease);
}
.wss-hero.is-playing .wss-hero__poster {
  opacity: 0;
}

/* The film starts invisible and is faded UP over the poster on 'playing'. The
 * two are the same photograph 3.3 seconds apart, so the cross reads as the
 * picture coming to life rather than as a swap. */
.wss-hero__video {
  opacity: 0;
  transition: opacity 600ms var(--ease);
}
.wss-hero.is-playing .wss-hero__video {
  opacity: 1;
}
/* Nothing to show: the plate is the hero. `visibility` rather than `display` so
 * an engine that is still deciding does not have to re-lay-out to change its
 * mind (hero.js can leave is-static on a rejected play() and remove it again
 * when the engine plays anyway). */
.wss-hero.is-static .wss-hero__video {
  opacity: 0;
  visibility: hidden;
}

/* THE SETTLING PUSH. The film's last 2.6s hold a locked-off shot of the arch
 * mirror, and this is the only motion added on top of it: a 4% push toward the
 * glass as the welcome materialises, tied to the same variable the reveal is.
 *
 * It is CSS rather than a baked-in zoom on purpose. A digital push encoded into
 * a 1440x810 master magnifies compression; a compositor transform on the decoded
 * frame costs nothing and can be tuned without a re-encode. And it is small on
 * purpose too: the camera has ALREADY arrived. This is the breath after it, not
 * a second move. */
.wss-hero.is-playing .wss-hero__video {
  transform: scale(calc(1 + 0.04 * var(--wss-welcome-t, 0)));
  transform-origin: 50% 34%;
}
@media (orientation: landscape) {
  .wss-hero.is-playing .wss-hero__video {
    transform-origin: 50% 48%;
  }
}

/* ── The welcome, in the glass ─────────────────────────────────────────────── */
/* v3 measured the mirror off a manifest and projected an arch-clipped box
 * through a moving camera every frame. There is no moving camera any more — the
 * film ends on ONE locked-off shot — so the geometry is two static boxes, read
 * off a grid overlay on the actual shipped stills (hero4/sheets/):
 *
 *   9:16  cinema-settled-mobile.jpg   glass x 0.31-0.72, y 0.02-0.53
 *   16:9  cinema-settled-desktop.jpg  glass x 0.27-0.72, y 0.00-0.84
 *
 * The 16:9 numbers were re-measured after hero4/build.sh's B3_CY moved from 80
 * to 950 — the desktop beat-3 band was showing ceiling and mirror apexes and no
 * chairs at all ("only shows the top half of the chairs and mirrors"). The glass
 * is no longer full height in the landscape cut: the arch apex is now above the
 * frame and the mirror's bottom rail is at y 0.84. The welcome box below
 * (30%-70% x, 18%-78% y) was checked against the new shipped still with the box
 * drawn on it and lands entirely inside that glass, on the dark door reflection,
 * with both sconces still lit in the top corners.
 *
 * PORTRAIT: the glass is only 41% of the stage wide — far too narrow to hold a
 * wordmark, a letter-spaced tagline and a 44px button on a 390px phone. So the
 * block is 84% wide and CENTRED ON THE GLASS VERTICALLY (centre 30%, not 50%),
 * and the light wash below does the work of belonging: it spills across the
 * mirror and the two lit sconces either side of it, which are real light in the
 * real frame at exactly that height.
 *
 * LANDSCAPE: the glass is 42% of a 16:9 stage and full height, which is a
 * comfortable column. The block sits inside it. */
.wss-hero__mirror {
  position: absolute;
  z-index: 2;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Driven: the glass fades up with the settle, frame by frame off the film's
 * clock. No transition — --wss-welcome-t IS the animation. */
.wss-hero.is-welcome .wss-hero__mirror {
  opacity: var(--wss-welcome-t, 1);
  visibility: visible;
  pointer-events: auto;
}
/* Collapsed / fallback: there is no film to ride, so it fades in on its own. */
.wss-hero.is-static .wss-hero__mirror {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 900ms var(--ease);
}

/* PORTRAIT: top is 14%, not 8%, and the six points are measured.
 *
 * V4's mobile landing is the arch mirror the beat-4 geometry contract is drawn
 * on (HERO-V4-KIT/04-mirror-zoom/NOTES.txt), and that file flags this exact
 * collision as an open item for the assembly side: inside the glass, two rattan
 * pendants are reflected across y 0.104-0.234 of the frame, and the welcome box
 * projected back onto the plate starts at y 0.174 — so the script "White Sand"
 * and the top of "SALON" land ON the pendants.
 *   It is not theoretical. Shot at 390x844 in real Chrome against the shipped
 * master, the wordmark's cap line sat at 25.4% of the stage and the pendants ran
 * 17.4%-31.0%: the left one crossed "White", the right one crossed "Sand", and
 * white script on a lit wicker dome is the one place this typography stops
 * being readable. Dropping the box 6 points puts the cap line at 31.4%, clear of
 * the pendants, and the Book Now button's baseline at 57.2% against a glass
 * bottom edge at 60.6% — still wholly inside the glass, which is the constraint
 * that stops this being a free move. Re-shoot before changing it again. */
.wss-hero__welcome {
  position: absolute;
  left: 8%;
  top: 14%;
  width: 84%;
  height: 44%; /* centre 36% — clear of the reflected pendants, still on glass */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(14px, 2.2vh, 24px);
  text-align: center;
  /* Rides the same variable as the opacity: 14px of lift consumed as the reveal
   * completes, so the block settles INTO the glass rather than appearing in it. */
  transform: translateY(calc(14px * (1 - var(--wss-welcome-t, 1))));
}
@media (orientation: landscape) {
  .wss-hero__welcome {
    left: 30%;
    top: 18%;
    width: 40%; /* the glass column in the 16:9 cut */
    height: 60%;
  }
}
.wss-hero.is-static .wss-hero__welcome {
  transform: translateY(0);
  transition: transform 900ms var(--ease);
}

/* NO WASH BEHIND THE WELCOME. There used to be one — a radial pool of
 * rgba(255,252,246) on .wss-hero__welcome::before — because the logo artwork is
 * ink-on-transparent and ink needs something pale to sit on. Every tightening
 * pass made it smaller and it was still the first thing anyone saw: a white aura
 * hanging in the middle of her mirror, a card pretending not to be a card.
 *
 * The fix was to stop lighting the background and change the ink instead. The
 * wordmark is rendered WHITE (below), the tagline is white, and both carry a
 * tight dark drop shadow. That reads as type standing off the glass with a
 * little depth behind it, which is what a title card in film actually does — and
 * her two lit sconces and the whole reflection stay exactly as graded.
 *
 * If a frame ever loses the type, the answer is more shadow, never a scrim. */

/* Her real logo artwork, rendered white.
 *
 * brightness(0) crushes the (already near-black) ink to pure black including any
 * antialiased grey edge; invert(1) then takes it to pure white. Doing it in that
 * order — rather than invert() alone, which is what the loading veil uses on the
 * smaller wordmark.png — guarantees #fff rather than "whatever 255-minus-the-ink
 * happened to be", so the tagline underneath matches it exactly.
 *
 * The two hairline white drop-shadows are NOT a glow. At 0 blur and 0.5px they
 * land inside the glyph's own antialiasing and fatten the strokes by about half
 * a pixel each — the script's thin upstrokes in "White Sand" are a single light
 * pixel at this size and would otherwise dissolve into a bright reflection.
 *
 * The last two are the dark ones that do the separating, and there are two of
 * them for a reason found in the 390px shot: on the portrait cut the block is
 * wider than the glass, so the S of SALON lands on a LIT SCONCE GLOBE — white
 * type on the brightest object in the frame. The 12px blur alone is too diffuse
 * to hold an edge there; the tight 3px one draws the contour. Depth, not halo.
 *
 * SCOPED TO THE HERO WELCOME. .wss-hero__loader-mark (the veil) and .brand__mark
 * (header/drawer) use the same-family artwork on their own terms; neither is
 * touched by this rule. */
.wss-hero__logo {
  width: min(78%, 300px);
  max-height: 42%;
  height: auto;
  object-fit: contain;
  display: block;
  filter: brightness(0) invert(1) drop-shadow(0 0 0.5px rgba(255, 255, 255, 0.9))
    drop-shadow(0 0 0.5px rgba(255, 255, 255, 0.9))
    drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5)) drop-shadow(0 2px 12px rgba(0, 0, 0, 0.5));
}
/* On a wide screen the 300px cap left her name reading timid inside a 605px-wide
 * pane of glass. The glass, not the viewport, is what it has to hold its own
 * against. */
@media (orientation: landscape) {
  .wss-hero__logo {
    width: min(84%, 380px);
  }
}

/* White, and one step up the brand's own weight scale (Light 300 → Medium 500;
 * see tokens.css). 300 was chosen when this sat on a pale wash — as white type
 * over a moving reflection, a 300 at 0.2em tracking is a hairline and it flickers
 * as the frame's luminance changes underneath it. The tracking is kept: that
 * spacing is the character of the line, the weight was never the point. The
 * text-shadow is the same idea as the logo's dark drop-shadow — a soft, tight
 * depth, not a halo. */
.wss-hero__tagline {
  margin: 0;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(0.95rem, 2.3vw, 1.45rem);
  line-height: 1.2;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.55), 0 1px 3px rgba(0, 0, 0, 0.45);
  text-wrap: balance;
}

.wss-hero__book {
  flex: none;
}

/* ── Bottom scrim ─────────────────────────────────────────────────────────── */
/* The cue and the Skip button have to stay legible over EVERY frame, and the
 * first four seconds are blown-out white hair. A shallow bottom gradient gives
 * both a dependable floor without touching the composition. It leaves with the
 * cue, so the settled mirror is never darkened. */
.wss-hero__sticky::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 26%;
  /* z 1 = the film's level; being later in tree order puts it over the video and
   * poster but UNDER the mirror layer (z 2), so it can never darken the welcome
   * content. */
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0));
  opacity: 1;
  transition: opacity 500ms var(--ease);
}
.wss-hero.is-welcome .wss-hero__sticky::after,
.wss-hero.is-static .wss-hero__sticky::after {
  opacity: 0;
}

/* ── Scroll cue ───────────────────────────────────────────────────────────── */
/* home.css fades it in with `animation: … forwards`, and a running keyframe
 * animation OUTRANKS an inline style — so the controller cannot dim it by
 * writing style.opacity. It toggles a class on the section instead, and the
 * animation is cancelled here. */
.wss-hero .scroll-cue {
  transition: opacity 420ms var(--ease);
}
.wss-hero.is-welcome .scroll-cue,
.wss-hero.is-tap .scroll-cue {
  animation: none;
  opacity: 0;
  pointer-events: none;
}

/* ── Skip ─────────────────────────────────────────────────────────────────── */
/* Not a dismissal and not a scroll: it jumps the film to its closing hold, so
 * the visitor lands on the settled mirror exactly as if they had watched it get
 * there. It hides itself once the welcome is up. */
.wss-hero__skip {
  position: absolute;
  z-index: 3;
  right: calc(env(safe-area-inset-right, 0px) + 14px);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 14px);
  min-width: 44px; /* 44px touch target */
  min-height: 44px;
  padding: 0 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid rgba(255, 255, 255, 0.42);
  border-radius: 999px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: background-color 220ms var(--ease), border-color 220ms var(--ease),
    opacity 300ms var(--ease);
}
.wss-hero__skip:hover,
.wss-hero__skip:focus-visible {
  background: rgba(0, 0, 0, 0.48);
  border-color: var(--gold-light, #dcc389);
}
.wss-hero__skip[hidden] {
  display: none;
}
/* Nothing to skip. */
.wss-hero.is-static .wss-hero__skip,
.wss-hero.is-tap .wss-hero__skip {
  display: none;
}
/* On a phone chat.js parks a wide "CHAT WITH LORI" pill across the bottom of the
 * viewport, and it sat directly on top of Skip — caught in the 390x844 pass,
 * where the two affordances were literally overlapping circles. The pill is
 * centred and leaves the bottom-LEFT corner clear, so Skip moves there in
 * portrait. */
@media (orientation: portrait) {
  .wss-hero__skip {
    right: auto;
    left: calc(env(safe-area-inset-left, 0px) + 14px);
  }
}

/* ── Tap to play ──────────────────────────────────────────────────────────── */
/* THE OWNER'S OWN BUG, given a control. She has never seen this hero move on her
 * iPhone across three shipped rounds. The likeliest cause is Low Power Mode,
 * which refuses muted autoplay outright; Reduce Motion (which is on on her
 * phone) is the other, and this site honours it. Either way the answer is the
 * same: do not pretend the film is playing, and do not hide it either.
 *
 * It sits ABOVE the welcome layer (z 5) because when it is showing, the welcome
 * is already up — hero.js collapses to the settled plate first, so what she sees
 * is a FINISHED hero with an invitation on it, never a broken one with a demand.
 * Bottom-centre and small: it is an offer, not a paywall. */
.wss-hero__play {
  position: absolute;
  z-index: 5;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 84px);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 20px 0 16px;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink, #1a1a1a);
  background: rgba(255, 252, 246, 0.9);
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 999px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.16);
  transition: background-color 220ms var(--ease), box-shadow 220ms var(--ease);
}
.wss-hero__play:hover,
.wss-hero__play:focus-visible {
  background: #fffcf6;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.22);
}
.wss-hero__play[hidden] {
  display: none;
}
/* A play triangle drawn in borders — no SVG, no icon font, no request. */
.wss-hero__play-mark {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 6px 0 6px 10px;
  border-color: transparent transparent transparent var(--gold, #c9a961);
}
/* chat.js's bottom pill is centred too, so on a phone the offer moves up clear
 * of it rather than fighting it for the same 44px. */
@media (orientation: portrait) {
  .wss-hero__play {
    bottom: calc(env(safe-area-inset-bottom, 0px) + 96px);
  }
}

/* ── PRESSED, and waiting ─────────────────────────────────────────────────── */
/* The pill used to VANISH on press and then nothing happened until the film
 * started — which on the slow connection where the pill is most likely to be
 * showing in the first place is several seconds of "that button is broken".
 *
 * So a pressed pill stays exactly where it is, in the same place, at the same
 * size, and only its play triangle changes: the mark becomes a gold ring turning
 * on itself. Same pill, same colour language, one moving part. src/js/hero.js
 * holds this state from the press until 'canplay' or 'playing' (whichever lands
 * first) or the buffering runway expiring, and clears it on every exit — a
 * spinner that can outlive its own question is a lie told in gold. */
.wss-hero__play.is-loading {
  cursor: progress;
  background: rgba(255, 252, 246, 0.78);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
}
.wss-hero__play.is-loading:hover {
  /* it is busy, not offering: don't answer a second press with a lift */
  background: rgba(255, 252, 246, 0.78);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
}
.wss-hero__play.is-loading .wss-hero__play-mark {
  /* 10px, which is EXACTLY the triangle's box width (border-width 6px 0 6px 10px),
   * so the label does not jog sideways at the moment the guest presses. */
  width: 10px;
  height: 10px;
  box-sizing: border-box;
  border-style: solid;
  border-width: 2px;
  border-radius: 50%;
  border-color: rgba(201, 169, 97, 0.26);
  border-top-color: var(--gold, #c9a961);
  animation: wss-hero-spin 720ms linear infinite;
}
@keyframes wss-hero-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ── .is-holding: the poster is the picture, FOR NOW ──────────────────────── */
/* Deliberately no visual rules, and that is the whole design.
 *
 * src/js/hero.js sets it in two situations — the tab is hidden so the film has
 * not been started yet, and the stream is still arriving — and in both the right
 * picture is the one already on screen: the poster, which IS the film's opening
 * frames. A held poster is indistinguishable from a paused film. Anything drawn
 * on top of it (a spinner, a scrim, a second veil) would announce a wait the
 * guest cannot see and does not have.
 *
 * The class exists for ONE consumer: veilPainted() in src/js/hero.js, which reads
 * it as "this hero has a picture, you may leave" — otherwise the veil would sit
 * black over a perfectly good warm plate until its 2.5s deadline. If you are
 * tempted to style it, style nothing: the state is defined by what it does not
 * change. */

/* ── The loading veil ─────────────────────────────────────────────────────── */
/* What it covers is the ASSEMBLY, not the load: the poster decoding and the film
 * taking its first frame are two paints the owner read as "random artifacts on
 * load". src/js/hero.js removes the element from the DOM after the fade. It is
 * never merely hidden: a 100svh layer left parked over the stage forever is the
 * failure this whole file spends its comments avoiding. templates/hero.js's
 * <noscript><style> hides it outright when scripting is off — read the gate note
 * there, it is the load-bearing half of this. */
.wss-hero__loader {
  position: absolute;
  inset: 0;
  z-index: 6;
  background: #0a0a0b;
  display: grid;
  place-items: center;
  align-content: center;
  gap: var(--space-5);
  opacity: 1;
  transition: opacity 300ms var(--ease);
}
/* Her real logo artwork, inverted: the file is ink-on-transparent (it is the same
 * URL base.js puts in the header, so it costs nothing here) and ink on near-black
 * is nothing at all. invert() is a compositor filter on a 20KB PNG, and it keeps
 * ONE wordmark asset on the property rather than a second, light-on-dark export
 * that would have to be re-cut every time she changes her logo. */
.wss-hero__loader-mark {
  width: min(62vw, 340px);
  height: auto;
  filter: invert(1);
  opacity: 0.92;
}
.wss-hero__dots {
  display: flex;
  gap: 11px;
}
.wss-hero__dots i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
  animation: wss-hero-dot 1.25s infinite ease-in-out both;
}
.wss-hero__dots i:nth-child(2) {
  animation-delay: 0.18s;
}
.wss-hero__dots i:nth-child(3) {
  animation-delay: 0.36s;
}
@keyframes wss-hero-dot {
  0%,
  80%,
  100% {
    transform: scale(0.5);
    opacity: 0.3;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}
/* The fade out, and the frame before a router revisit has decided whether it
 * needs a veil at all (see the ~80ms arming window in src/js/hero.js). */
.wss-hero__loader.is-hidden,
.wss-hero__loader.is-arming {
  opacity: 0;
  pointer-events: none;
}
/* Arming is not a fade — it is "do not show yet", and it must be able to end in
 * either direction without a transition running backwards. */
.wss-hero__loader.is-arming {
  transition: none;
}

/* ── Reduced motion ───────────────────────────────────────────────────────── */
/* THIS TIME THE PREFERENCE WINS, and that is a reversal worth stating.
 *
 * Under v3 the walk still ran under this query, and the reasoning was sound for
 * what v3 was: every moving thing in it moved because the guest's own thumb moved
 * it, and WCAG 2.2.2/2.3.3 are about motion the INTERFACE starts by itself.
 *
 * v3.1 IS motion the interface starts by itself — a 15-second self-playing film
 * is the textbook case. So under Reduce Motion hero.js does not start it and does
 * not even download it: the settled mirror plate is the hero and the welcome is
 * up from the first paint. The film is still one tap away on .wss-hero__play,
 * because a preference is not a prohibition.
 *
 * The owner is the one who caught the old behaviour here (Reduce Motion is on on
 * her phone, and she was served "a static beige photo with our logo"), so it is
 * worth being exact about what changed: the still is no longer a pale plate with
 * a logo on it. It is her lit arch mirror with the welcome IN the glass — the
 * same frame the film ends on. */
@media (prefers-reduced-motion: reduce) {
  .wss-hero__poster,
  .wss-hero__video {
    transition: none;
  }
  .wss-hero__welcome {
    transform: none;
  }
  .wss-hero.is-playing .wss-hero__video {
    /* If she opts in by tapping, she gets the film — but not the push. */
    transform: none;
  }
  /* The pressed pill still says it is busy — dimmed, aria-busy, and a ring
   * instead of a triangle — it simply says it without turning. A guest who asked
   * for calm and then pressed PLAY is owed feedback, not stillness at the cost of
   * not knowing whether their press landed. */
  .wss-hero__play.is-loading .wss-hero__play-mark {
    animation: none;
  }
  /* The one thing on this layer that moves by itself. The veil still SHOWS and
   * still GOES — a guest who asked for calm did not ask to watch the hero
   * assemble itself — it simply does both without a pulse and without a fade
   * (src/js/hero.js removes it with no fade delay to match). */
  .wss-hero__dots i {
    animation: none;
  }
  .wss-hero__loader {
    transition: none;
  }
  .wss-hero.is-static .wss-hero__mirror,
  .wss-hero.is-static .wss-hero__welcome {
    transition: none;
  }
}
