/* ============================================================================
   LUCID MOTION — four effects, and deliberately no fifth.

   Requires lucid.tokens.css. Every duration and curve here is a token, so
   motion can be re-tuned in one place across every Lucid asset at once.

   The goal is what a well-made product feels like: content ARRIVES rather
   than appearing, surfaces answer the pointer, and nothing ever draws
   attention to the animation itself. The failure at the other end is worse
   than no motion at all — a page that bounces, spins or slides in from the
   sides, or, the expensive one, a page left blank because the script that was
   meant to reveal it never ran.

   Two properties only: opacity and transform. Those are the only two a
   browser can move without re-doing the page layout, which is the whole
   difference between smooth and stuttering on a mid-range phone.
   ========================================================================= */

/* ── 1. RISE ON ENTRY ─────────────────────────────────────────────────────
   Content fades in and rises a short distance as it scrolls into view, once.

   22px, not 60: long travel makes a page look like it is assembling itself,
   and on a phone it means words are still sliding while the reader is already
   trying to read them.

   The hidden state is applied by lucid.reveal.js AFTER the page has loaded,
   never written into the markup. A reader whose script fails then gets the
   page, rather than a blank screen nobody will ever report.                */

.lucid-reveal-out {
  opacity: 0;
  transform: translate3d(0, var(--travel), 0);
}

.lucid-reveal-in {
  opacity: 1;
  transform: none;
}

.lucid-reveal-out,
.lucid-reveal-in {
  transition:
    opacity var(--d-entrance) var(--ease),
    transform var(--d-entrance) var(--ease);
  will-change: opacity, transform;
}

/* ── 2. LIFT ON HOVER ─────────────────────────────────────────────────────
   A surface rises slightly toward the pointer and its shadow deepens, so the
   page reads as layered objects rather than a flat picture.

   3px is deliberate. Enough to say the surface responded; small enough that
   what was under the cursor is still under the cursor. A card that jumps 8px
   makes the click miss, which is a conversion bug wearing polish.

   Hover only where hovering exists — on a touchscreen this whole block is
   skipped, because a "hover" there is a tap that has already happened.     */

.lucid-lift {
  transition:
    transform var(--d-response) var(--ease),
    box-shadow var(--d-response) var(--ease);
}

@media (hover: hover) {
  .lucid-lift:hover {
    transform: translate3d(0, calc(-1 * var(--lift)), 0);
    box-shadow: var(--e-floating);
  }
}

/* ── 3. DEPTH ─────────────────────────────────────────────────────────────
   Handled by the elevation tokens in lucid.components.css: a large, soft,
   faint shadow always paired with a hairline. Listed here because depth is
   half of why a page feels expensive before anything has moved.

   Order of work, always: ground and depth first, then entrance, then hover.
   A flat page with good motion still feels flat.                           */

/* ── 4. GLIDE ─────────────────────────────────────────────────────────────
   In-page links slide rather than teleport, and stop clear of a fixed header
   instead of underneath it.

   NOTE, and it cost an afternoon to find: `scroll-behavior: smooth` is NOT set
   on `html` here, deliberately. Setting it there hijacks EVERY programmatic
   scroll on the page — a script's `window.scrollTo`, a "back to top" control,
   a test driving the page — and turns each into an animation that later calls
   interrupt. Measured on this page: a script asking for y=10800 arrived at
   6934 and the last three sections were never scrolled to at all, so their
   content was reported as lost when a reader would have seen it fine.

   The glide is applied by lucid.reveal.js to anchor clicks only, which is the
   only place it was ever wanted. `scroll-padding-top` stays here because it is
   pure layout: it keeps an anchor's target clear of the fixed header however
   the reader got there.                                                     */

html {
  scroll-padding-top: var(--s-8);
}

/* ── THE ONE LOOP ─────────────────────────────────────────────────────────
   The packet traveling along a filament is the only continuously moving thing
   the brand allows, because it is showing something true: work moving between
   systems on its own. lucid.reveal.js pauses it whenever it is off screen, so
   it never costs battery for a picture nobody is looking at.               */

.lucid-packet-travel {
  animation: lucid-travel 4s linear infinite;
}

.lucid-packet-travel[data-paused='true'] {
  animation-play-state: paused;
}

@keyframes lucid-travel {
  from { offset-distance: 0%;   opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  to   { offset-distance: 100%; opacity: 0; }
}

/* ── OFF, FOR ANYONE WHO ASKED ────────────────────────────────────────────
   Movement on a screen causes real nausea for people with vestibular
   disorders and can trigger a migraine. They have already told their device.
   This removes the movement — it does not merely shorten it.               */

@media (prefers-reduced-motion: reduce) {
  /* The catch-all, and the reason it exists: the rules under it only cover the
     system's OWN classes. A page that writes its own hover lift — which is a
     normal thing to do — escapes every one of them, and the homepage did
     exactly that with two buttons that kept translating for a reader who had
     asked for no movement.

     This stops anything from ANIMATING a move. It deliberately does not force
     `transform: none` on everything, because transforms are also used to place
     things, and blanking those would break layouts rather than calm them. A
     hand-written `:hover { transform: ... }` still needs its own
     `(prefers-reduced-motion: no-preference)` guard; check-motion.mjs is what
     catches the ones that forget, and it runs on every deploy. */
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 0s !important;
    transition-delay: 0s !important;
  }

  .lucid-reveal-out,
  .lucid-reveal-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .lucid-lift,
  .lucid-lift:hover {
    transform: none;
    transition: none;
  }

  .lucid-packet-travel {
    animation: none;
  }
}
