/* ==========================================================================
   MOTION & MICRO-INTERACTION
   Techniques extracted from the Config template and adapted to The Room.
   Config supplies the MECHANISM; every distance, duration and hue is ours.

   Extracted facts this file is built on:
     · `transition: all .3s ease-in-out` — 15 of 18 declarations in Config's
       stylesheet. The single dominant transition.
     · `.primary-button-overlay { opacity: 0 }` → 1 on hover.
     · `.primary-button-icon.hover { translate3d(-50px,0,0) }` → 0, with the
       resting glyph travelling out the opposite way inside a 44px
       `overflow:hidden` box. An icon SWAP, not an icon nudge.
     · `.speaker-socials { translate3d(0,100%,0) }` → 0, inside
       `.speaker-image-wrapper { position:relative; overflow:hidden }`.
       Content rises from below its own edge into the image.
     · `.blog-thumbnail { transition: all .3s ease-in-out }` +
       `:hover { transform: scale(1.1) }` inside an overflow-hidden wrapper.
     · `.navbar-section { border-bottom: 1px solid #28282800 }` — fully
       transparent at rest, colour animated in on scroll.
     · Reveal granularity: `.section-title-wrapper` animates as ONE unit;
       repeating items (`.single-sponsor` ×9, `.single-pricing-card` ×2,
       `.schedule-content-wrapper` ×3) each carry their own `opacity:0`.
     · `.registraton-object { translate3d(-14.96px, 14.96px, 0) }` with
       `will-change: transform` — a symmetric diagonal drift.
   ========================================================================== */

/* --------------------------------------------------------------------------
   MEDIA — scale on hover, contained
   Config's technique. Scale reduced 1.1 → 1.05 (design-rules §16 caps image
   scale at 1.05). The wrapper's overflow is what makes it read as a crop
   tightening rather than an element growing.
   -------------------------------------------------------------------------- */
.media {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-control);
}

.media__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--duration-base) var(--ease-standard);
}

.media:hover .media__img,
.media:focus-within .media__img { transform: scale(var(--media-hover-scale)); }

/* R16f — REDUCED MOTION NEUTRALISES THE SCALE ITSELF, not just its duration.

   base.css already forces transition-duration to 0.01ms globally under
   `reduce`, and that is NOT enough on its own: the transform would still
   apply, so the image would JUMP to 1.05 the instant a pointer crossed it.
   An instant 5% jolt is worse than a 300ms one, not better. The transform is
   dropped outright, so under `reduce` the picture simply does not move.

   This was a gap in the component rather than in §5 — .media had no
   reduced-motion handling at all — so the fix belongs here, where every
   future consumer inherits it. */
@media (prefers-reduced-motion: reduce) {
  .media:hover .media__img,
  .media:focus-within .media__img { transform: none; }
}

/* Touch has no hover. The strip rests visible and simply does not move.
   design-rules §17: anything hover-dependent needs a non-hover equivalent. */
/* --------------------------------------------------------------------------
   REVEAL GRANULARITY
   The rule Config demonstrates and design-rules never wrote down:

     A title group reveals as ONE element.
     A repeating set reveals ITEM BY ITEM, staggered.

   Applying a stagger to an eyebrow + heading pair makes the page look like
   it is assembling itself. Applying a single reveal to twelve cards wastes
   the only rhythm the section has.
   -------------------------------------------------------------------------- */
.reveal-stagger > *:nth-child(1)  { --reveal-delay: calc(var(--stagger-step) * 0); }
.reveal-stagger > *:nth-child(2)  { --reveal-delay: calc(var(--stagger-step) * 1); }
.reveal-stagger > *:nth-child(3)  { --reveal-delay: calc(var(--stagger-step) * 2); }
.reveal-stagger > *:nth-child(4)  { --reveal-delay: calc(var(--stagger-step) * 3); }
.reveal-stagger > *:nth-child(5)  { --reveal-delay: calc(var(--stagger-step) * 4); }
.reveal-stagger > *:nth-child(6)  { --reveal-delay: calc(var(--stagger-step) * 5); }
.reveal-stagger > *:nth-child(7)  { --reveal-delay: calc(var(--stagger-step) * 6); }
.reveal-stagger > *:nth-child(8)  { --reveal-delay: calc(var(--stagger-step) * 7); }

/* Beyond eight, the last delay holds. A twelve-item stagger at 70ms would
   run 770ms behind the first item and read as a loading state. */
.reveal-stagger > *:nth-child(n+9) { --reveal-delay: calc(var(--stagger-step) * 8); }

/* --------------------------------------------------------------------------
   AMBIENT DRIFT
   Config drives its decorative objects continuously with `will-change`.
   We reject the objects — rotating 3D forms are prohibited (§12) — but the
   symmetric diagonal drift underneath them is worth keeping, applied to the
   gradient atmosphere behind the hero and final CTA.

   Desktop only, and never on anything carrying text.
   -------------------------------------------------------------------------- */
@keyframes drift {
  from { transform: translate3d(calc(var(--drift-distance) * -1), var(--drift-distance), 0); }
  to   { transform: translate3d(var(--drift-distance), calc(var(--drift-distance) * -1), 0); }
}

@media (min-width: 992px) and (prefers-reduced-motion: no-preference) {
  .u-drift {
    will-change: transform;
    animation: drift var(--duration-drift) var(--ease-standard) infinite alternate;
  }
}

/* --------------------------------------------------------------------------
   SEQUENTIAL REVEAL — §4, the ✓ list
   A LONGER interval than .reveal-stagger. The stagger makes a set arrive
   together with rhythm; this makes a list arrive one item at a time, so it
   reads as an argument accumulating rather than a group appearing.

   3 × --stagger-step = 210ms. Derived from the existing token rather than a
   new one; four items land over 630ms.

   No cap. .reveal-stagger caps at the 8th item because a set can be long —
   a sequence that needs to be counted is short by definition, and any list
   long enough to need a cap should not be using this.
   -------------------------------------------------------------------------- */
.reveal-sequence > *:nth-child(1) { --reveal-delay: calc(var(--stagger-step) * 0); }
.reveal-sequence > *:nth-child(2) { --reveal-delay: calc(var(--stagger-step) * 3); }
.reveal-sequence > *:nth-child(3) { --reveal-delay: calc(var(--stagger-step) * 6); }
.reveal-sequence > *:nth-child(4) { --reveal-delay: calc(var(--stagger-step) * 9); }
.reveal-sequence > *:nth-child(5) { --reveal-delay: calc(var(--stagger-step) * 12); }
.reveal-sequence > *:nth-child(6) { --reveal-delay: calc(var(--stagger-step) * 15); }

/* --------------------------------------------------------------------------
   WIPE REVEAL — .reveal--wipe
   R18a — §8's photograph and §9's six tiles, by direction: "each tile will
   have a purple wiping animation on scrolling into view".

   A violet panel covers the box and retreats to the right, uncovering what is
   underneath. The box's own contents are NEVER TOUCHED.

   ⚠⚠ R18c — THIS IS THE SECOND MECHANISM. The first clipped the image itself
   (clip-path from zero width, violet on the box behind it) and it produced a
   violet hairline down the edges of every tile — reported as "a purple stroke
   along its edges. looks imperfect."

   THE CAUSE, because it will be reached for again: a clip boundary is
   ANTIALIASED. The tiles are 1fr columns, so their used widths are
   fractional, and a clip sitting exactly on the border box cuts through the
   image's own edge pixels and leaves them partly transparent. Whatever is
   behind then shows through as a hairline — and what was behind was the
   violet ground the wipe needed. The image was opaque; the clip made its
   edges translucent.

   No inset tuning fixes that class of fault, it only moves it. An overlay
   does not have the fault at all: at rest it is scaled to zero width and the
   photograph beneath has never been modified.

   THIS IS ALSO §7'S TECHNIQUE. The plate wipe is a violet pseudo-element
   scaling across its section, and the page is better off with one mechanism
   for one gesture than with two that merely look alike.

   IT REPLACES THE FADE, IT DOES NOT STACK ON IT. .reveal's opacity and its
   24px lift are both cancelled here. A tile that fades AND rises AND wipes is
   three arrivals for one element; the wipe is the arrival.

   LEFT TO RIGHT, matching §7. The panel is anchored to its right edge and
   collapses toward it, so the uncovered area grows from the left.

   ::after, NOT ::before, AND THE REASON IS NOT STYLE. .media__img takes a
   transform on hover, and a transformed element paints as though positioned —
   so a ::before, earlier in tree order, loses to it and the panel would sit
   UNDER the photograph. This is the same trap that left §5's scrim rendering
   nothing for two revisions. z-index: 1 states the intent rather than leaving
   it to tree order a second time.

   ⚠ PHOTOGRAPHS ONLY. §9's claim tile carried this for two revisions and no
   longer does, by direction: "the wipe animation is not required for this
   block on both desktop and mobile."

   It was never the same effect there. On a photograph the violet retreats to
   uncover something underneath; on a tile whose ground is ALREADY violet
   there is nothing to uncover, so the panel could only ever reveal the type —
   a violet block sitting still while two lines of it appear. Every argument
   for the modifier is an argument about a picture.

   The claim tile falls back to the page's ordinary .reveal and keeps its own
   ground, which the component stopped painting at R18c.

   pointer-events: none — while the panel covers the tile it must not swallow
   the hover or the focus ring of what is underneath.

   THE LEAD-IN — R18b, by direction: "there should a slight delay for this
   animation to look smoother". 200ms at first; --delay-reveal-lead (300ms) at
   R18e, when the same beat was put in front of every entrance on the page.
   The box is in position and settled before it starts resolving, so the wipe
   reads as a decision rather than a reflex fired by the scroll. It also buys
   the decode: every one of these photographs is loading="lazy" and several
   are multi-megabyte files, and without the beat the panel can pull back off
   an image the browser has not finished painting.

   ⚠ THE LEAD-IN IS ADDED TO --reveal-delay, NOT SUBSTITUTED FOR IT. Writing
   200ms straight into the property would silently discard any stagger a
   parent had set, so a .reveal-stagger added to the board later would appear
   to do nothing.

   NO .reveal-stagger ON THE BOARD TODAY. The observer already fires row by
   row — the tiles are 12% of a viewport apart at the trigger line — so the
   cascade is there without a fixed delay, and a fixed delay would also be
   paid on the mobile rail, where a tile the reader had just swiped to would
   sit violet for up to 350ms before resolving.

   ⚠ DESKTOP ONLY — R18d, by direction: "the wipe animation is not needed for
   mobile view." 992 is the project's mobile boundary, not 768: the tablet and
   landscape-phone widths were folded into the mobile treatment by direction,
   and §9's rail already switches at the same number. Two rules that describe
   the same section had better break at the same width.

   IT ALSO REMOVES THE WORST CASE THE WIPE HAD. Below 992 §9's board is a
   horizontal rail, so tiles waiting off the right edge are not intersecting
   and would sit as solid violet blocks until the reader — or the timer —
   brought them into view.

   THE FALLBACK IS THE PAGE'S ORDINARY REVEAL, not nothing. The cancellation
   of .reveal's fade and lift lives inside this query with everything else, so
   below 992 these boxes simply fade and rise like every other .reveal on the
   page. Gating the panel without gating the cancellation would have left them
   with no arrival at all.

   REDUCED MOTION NEVER SEES ANY OF IT EITHER. The panel is declared INSIDE
   the no-preference query, content and all, so under `reduce` no
   pseudo-element is generated and the photograph is simply there.
   -------------------------------------------------------------------------- */
@media (min-width: 992px) and (prefers-reduced-motion: no-preference) {
  .js .reveal--wipe {
    position: relative;
    opacity: 1;
    transform: none;
  }

  .js .reveal--wipe::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    background-color: var(--color-accent-identity-deep);
    /* Explicit, though `none` is the same matrix — the two ends of the
       interpolation should be readable as a pair. */
    transform: scaleX(1);
    transform-origin: right center;
    pointer-events: none;
    transition: transform var(--duration-reveal) var(--ease-reveal);
    transition-delay: calc(var(--reveal-delay, 0ms) + var(--delay-reveal-lead));
  }

  .js .reveal--wipe.is-revealed::after { transform: scaleX(0); }
}
