/* SITE-WIDE MOTION -- "The Chain of Custody".
 *
 * The contract is .impeccable/surfaces/site-motion.md and this file may not
 * drift from it. Short version: the site's subject is a thing that is MADE, so
 * motion is about PLACEMENT, not arrival. Elements resolve into position the
 * way ink resolves onto paper -- a short travel along the reading axis with a
 * blur clearing to zero. Nothing slides in from off-screen; nothing pops.
 *
 * THE UN-ANIMATED STATE IS THE FINISHED STATE, and this is the rule the whole
 * file is built around. Every `[data-set]` element ships visible, in place and
 * fully opaque. `motion.js` adds `.motion-armed` to <html> only once it is
 * running, and ONLY THEN does anything displace. A blocked bundle, a thrown
 * script or a browser that never runs the observer therefore shows a complete
 * page rather than a blank one -- the inverse of the usual reveal pattern,
 * chosen deliberately because this site's job is showing an object to someone
 * who might buy it, and its worst failure is showing them nothing.
 */

:root {
  /* Durations. `settle` is the workhorse; `beat` is for a HOLD resolving. */
  --motion-quick: 180ms;
  --motion-settle: 620ms;
  --motion-beat: 1100ms;
  --motion-stagger: 55ms;

  /* Exponential-ish ease out: fast departure, long quiet landing. The craft
     floor asks for exponential ease-out from an already-visible default and
     this is that curve. */
  --ease-set: cubic-bezier(0.16, 1, 0.3, 1);

  /* The SET move, in one place so the four variants cannot drift. */
  --motion-travel: 10px;
  --motion-blur: 3px;
}

/* ----------------------------------------------------------------- SET ---
 * Armed only when motion.js is live. Until then these selectors do not match
 * and every element sits at its finished value.
 */

.motion-armed [data-set] {
  opacity: 0;
  transform: translate3d(0, var(--motion-travel), 0);
  /* The blur is what makes this READ as resolving rather than as moving. An
     ordinary fade-up says "a component mounted"; ink coming into focus says
     the thing was placed. Cheap on the compositor at 3px. */
  filter: blur(var(--motion-blur));
  transition:
    opacity var(--motion-settle) var(--ease-set),
    transform var(--motion-settle) var(--ease-set),
    filter var(--motion-settle) var(--ease-set);
  transition-delay: var(--motion-delay, 0ms);
  /* `will-change` on EVERY text block would hand the compositor hundreds of
     layers on a thirty-volume shelf page. motion.js sets it per element just
     before that element animates and clears it after. */
}

.motion-armed [data-set].is-set {
  opacity: 1;
  transform: none;
  filter: none;
}

/* SET-RTL: Arabic and any right-to-left content settles FROM THE RIGHT, the
   direction it is read and written. Mirroring this is meaning, not symmetry --
   right-to-left content arriving from the left is the same category error as
   pinning an Arabic crop to the wrong edge, which this repo has shipped once
   already. */
.motion-armed [data-set="rtl"] {
  transform: translate3d(calc(var(--motion-travel) * 1.4), 0, 0);
}

/* A heading and its paragraph are ONE group, not two events: the heading leads
   by a single stagger step and the body follows. Anything tagged `lead` skips
   its own delay so it is the thing the eye catches first. */
.motion-armed [data-set="lead"] {
  transform: translate3d(0, calc(var(--motion-travel) * 1.6), 0);
}

/* ---------------------------------------------------------------- LIFT ---
 * Interaction only, and nothing travels. The change is in LIGHT, not position:
 * a button that moves under the cursor feels cheap on a page selling a
 * physical object. Applied to anything actionable that opts in.
 */

[data-lift] {
  transition:
    border-color var(--motion-quick) var(--ease-set),
    background-color var(--motion-quick) var(--ease-set),
    color var(--motion-quick) var(--ease-set),
    box-shadow var(--motion-quick) var(--ease-set);
}

/* ------------------------------------------------------- reduced motion ---
 * Removes MOTION, not CONTENT. No travel, no blur, no delay. Opacity may still
 * cross-fade, quickly, because a fade carries no vestibular load and keeps the
 * page from flicking. PRODUCT.md holds this as a tested contract.
 */

@media (prefers-reduced-motion: reduce) {
  .motion-armed [data-set],
  .motion-armed [data-set="rtl"],
  .motion-armed [data-set="lead"] {
    transform: none !important;
    filter: none !important;
    transition: opacity var(--motion-quick) linear !important;
    transition-delay: 0ms !important;
  }
  [data-lift] { transition: none; }
}
