/*
 * Notarium motion system.
 * ────────────────────────────────────────────────────────────────────────────
 * Owned by the visual-excellence workstream. Everything animated anywhere in
 * Notarium should reach for a token or a utility from this file rather than
 * inventing a duration; that is what keeps a wax-seal stamp and a card hover
 * feel like they come from the same hand.
 *
 * Loaded before notarium.css (Propshaft includes app/assets/stylesheets
 * alphabetically via `stylesheet_link_tag :app`), so notarium.css wins any
 * collision at equal specificity. Keep the two files' selectors disjoint.
 *
 * THE RULES
 *   1. Animate `opacity` and `transform` only. Both run on the compositor;
 *      width/height/top/left force layout on every frame. A handful of
 *      `background-color` transitions on small elements (deadline dots going
 *      green) are the deliberate exception.
 *   2. No JS animation library. Stimulus controllers only toggle classes and
 *      set custom properties; CSS does every frame.
 *   3. Every motion has a reduced-motion answer, and the answer is never
 *      "invisible": elements end in their final, readable state.
 *
 * THE API
 *   Tokens        --nt-motion-*  (durations)   --nt-ease-*  (easings)
 *   Utilities     .nt-reveal .nt-reveal-left .nt-reveal-right .nt-reveal-scale
 *                 .nt-stagger  .nt-lift  .nt-press  .nt-underline
 *   Keyframes     nt-stamp nt-stamp-ring nt-breathe nt-ring-rotate
 *                 nt-letter-rise nt-sheet-in nt-rise-in nt-caret nt-drift
 *                 nt-flash-ok
 *   Controllers   reveal_controller · timeline_controller · countup_controller
 *                 (see app/javascript/controllers/*.js for their data-* API)
 */

:root {
  /* Durations. Named for intent, not length — so a redesign retunes one place. */
  --nt-motion-instant: 90ms;   /* state flips that must feel like a hardware key */
  --nt-motion-fast: 160ms;     /* hover, focus, press */
  --nt-motion-base: 260ms;     /* the default for anything the user caused */
  --nt-motion-slow: 460ms;     /* entrances: scroll reveals, panel swaps */
  --nt-motion-slower: 760ms;   /* set-piece beats in the hero timeline */
  --nt-motion-ambient: 1400ms; /* things that breathe while you read */

  /* Easings. */
  --nt-ease-out: cubic-bezier(0.22, 1, 0.36, 1);        /* the house default */
  --nt-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);     /* two-way state changes */
  --nt-ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);    /* long tail, settles calmly */
  --nt-ease-stamp: cubic-bezier(0.34, 1.42, 0.64, 1);   /* overshoot — the wax seal */
  --nt-ease-ink: cubic-bezier(0.4, 0, 0.2, 1);          /* text materialising */

  /* Stagger step used by .nt-stagger and by --i on any staggered child. */
  --nt-stagger: 70ms;
}

/* ── Reveal utilities ─────────────────────────────────────────────────────
 * Pair with reveal_controller: put data-controller="reveal" on a container,
 * .nt-reveal on anything inside that should arrive. The controller adds
 * .is-revealed to the container once it crosses the viewport.
 *
 *   <div data-controller="reveal" class="nt-stagger">
 *     <p class="nt-reveal">…</p>
 *     <p class="nt-reveal">…</p>
 *   </div>
 */

.nt-reveal,
.nt-reveal-left,
.nt-reveal-right,
.nt-reveal-scale {
  opacity: 0;
  transition:
    opacity var(--nt-motion-slow) var(--nt-ease-entrance),
    transform var(--nt-motion-slow) var(--nt-ease-entrance);
  transition-delay: calc(var(--nt-stagger) * var(--i, 0));
  will-change: opacity, transform;
}

.nt-reveal { transform: translate3d(0, 16px, 0); }
.nt-reveal-left { transform: translate3d(-20px, 0, 0); }
.nt-reveal-right { transform: translate3d(20px, 0, 0); }
.nt-reveal-scale { transform: scale(0.96); }

.is-revealed .nt-reveal,
.is-revealed .nt-reveal-left,
.is-revealed .nt-reveal-right,
.is-revealed .nt-reveal-scale,
.nt-reveal.is-revealed,
.nt-reveal-left.is-revealed,
.nt-reveal-right.is-revealed,
.nt-reveal-scale.is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Sequenced children without hand-writing --i on every element. */
.nt-stagger > :nth-child(1) { --i: 0; }
.nt-stagger > :nth-child(2) { --i: 1; }
.nt-stagger > :nth-child(3) { --i: 2; }
.nt-stagger > :nth-child(4) { --i: 3; }
.nt-stagger > :nth-child(5) { --i: 4; }
.nt-stagger > :nth-child(6) { --i: 5; }
.nt-stagger > :nth-child(7) { --i: 6; }
.nt-stagger > :nth-child(8) { --i: 7; }
.nt-stagger > :nth-child(9) { --i: 8; }
.nt-stagger > :nth-child(10) { --i: 9; }
.nt-stagger > :nth-child(11) { --i: 10; }
.nt-stagger > :nth-child(12) { --i: 11; }

/* ── Micro-interactions ───────────────────────────────────────────────── */

/* Card lift. Shadow is pre-declared so only opacity/transform change on hover. */
.nt-lift {
  transition:
    transform var(--nt-motion-base) var(--nt-ease-out),
    border-color var(--nt-motion-base) var(--nt-ease-out),
    box-shadow var(--nt-motion-base) var(--nt-ease-out);
}

.nt-lift:hover,
.nt-lift:focus-within {
  transform: translate3d(0, -3px, 0);
}

/* Press depth: the surface gives under the finger, like a stamp into wax. */
.nt-press {
  transition:
    transform var(--nt-motion-fast) var(--nt-ease-out),
    box-shadow var(--nt-motion-fast) var(--nt-ease-out),
    background-color var(--nt-motion-fast) var(--nt-ease-out);
}

.nt-press:active { transform: translate3d(0, 1px, 0) scale(0.994); }

/* Link underline that draws from the left rather than blinking on. */
.nt-underline {
  position: relative;
  text-decoration: none;
}

.nt-underline::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--nt-motion-base) var(--nt-ease-out);
}

.nt-underline:hover::after,
.nt-underline:focus-visible::after { transform: scaleX(1); }

/* ── Keyframes ────────────────────────────────────────────────────────── */

/* The wax seal landing on the page. Overshoot is the whole point. */
@keyframes nt-stamp {
  0% { opacity: 0; transform: scale(2.3) rotate(-19deg); }
  55% { opacity: 1; }
  100% { opacity: 1; transform: scale(1) rotate(-7deg); }
}

/* The pressure ring that spreads out from under the seal on impact. */
@keyframes nt-stamp-ring {
  0% { opacity: 0.5; transform: scale(0.55); }
  100% { opacity: 0; transform: scale(1.85); }
}

/* Ambient: a seal that is alive but not busy. */
@keyframes nt-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.022); }
}

@keyframes nt-ring-rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* The letter sliding up out of its envelope on "check your inbox". A 3-D flap
   was tried first and rejected: at this size the rotation reads as a glitch,
   while a sheet rising out of a pocket is legible in one frame. */
@keyframes nt-letter-rise {
  0% { opacity: 0; transform: translate3d(0, 6px, 0); }
  35% { opacity: 1; }
  100% { opacity: 1; transform: translate3d(0, -46px, 0); }
}

@keyframes nt-caret {
  0%, 45% { opacity: 1; }
  55%, 100% { opacity: 0; }
}

/* The general-purpose arrival, for content that appears without a scroll
   trigger — a swapped Turbo Frame, an opened <details>, a re-rendered panel. */
@keyframes nt-rise-in {
  from { opacity: 0; transform: translate3d(0, 8px, 0); }
  to { opacity: 1; transform: none; }
}

/* A sheet arriving on the auth panel. The resting rotation is baked into both
   keyframes: an entrance animation that ends at `transform: none` would
   silently cancel a transform the element is supposed to keep. */
@keyframes nt-sheet-in {
  from { opacity: 0; transform: translate3d(0, 12px, 0) rotate(-2.2deg); }
  to { opacity: 1; transform: translate3d(0, 0, 0) rotate(-2.2deg); }
}

/* Very slow parallax drift for background ornament. */
@keyframes nt-drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, -14px, 0); }
}

/* The "det er i orden" confirmation flash — mirrors the app's
   data-flash-on-update pattern so marketing and product agree. */
@keyframes nt-flash-ok {
  0% { background-color: transparent; }
  18% { background-color: var(--nt-ok-soft); }
  100% { background-color: transparent; }
}

/* ── Reduced motion ───────────────────────────────────────────────────────
 * Not "turn animation off" — "arrive already finished". Anything that fades
 * in must be visible; anything that loops must stop on its final frame.
 * Individual components add their own rules; these cover the shared layer.
 */

@media (prefers-reduced-motion: reduce) {
  .nt-reveal,
  .nt-reveal-left,
  .nt-reveal-right,
  .nt-reveal-scale {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .nt-lift,
  .nt-press,
  .nt-underline::after { transition: none; }

  .nt-lift:hover,
  .nt-lift:focus-within,
  .nt-press:active { transform: none; }

  .nt-underline:hover::after,
  .nt-underline:focus-visible::after { transform: scaleX(1); }
}
