/* =========================================================================
   Dentora — Motion system
   -------------------------------------------------------------------------
   Scroll reveals, the hero entrance, and the shared animation utilities.
   Component-level hover states live with their components; this file owns
   anything that plays on load or on scroll.

   TWO SAFETY RULES GOVERN EVERYTHING HERE
   ---------------------------------------
   1. FAIL VISIBLE. Reveal targets are hidden only under `.js-motion`, a
      class an inline script adds before first paint. If JavaScript is
      blocked, fails, or never runs, that class is never added and every
      element renders normally. A broken script must never leave a patient
      looking at an invisible page.

   2. REDUCED MOTION IS ABSOLUTE. The same inline script skips the class
      entirely when the visitor prefers reduced motion, so nothing is ever
      hidden in the first place — there is no animation to interrupt and
      no state to recover from. The media query below is a second belt.

   Only `opacity` and `transform` are animated. Both are GPU-composited,
   so no reveal triggers layout or paint, and none of this can shift the
   page (CLS stays where it is).
   ========================================================================= */

:root {
  /* Distance a revealing element travels. The brief asks for 20–30px. */
  --reveal-distance: 24px;
  --reveal-distance-sm: 16px;

  /* Scroll reveal: 400–600ms per the brief. */
  --reveal-duration: 520ms;

  /* Gap between staggered siblings. Applied by the reveal script. */
  --reveal-stagger: 80ms;

  /* Hero entrance: 500–700ms per the brief. */
  --hero-duration: 620ms;
  --hero-stagger: 90ms;

  /* Soft settle for background photography. */
  --hero-image-scale: 1.04;
}

/* =========================================================================
   1. Scroll reveal
   ========================================================================= */

.js-motion [data-reveal] {
  opacity: 0;
  transform: translateY(var(--reveal-distance));
  transition:
    opacity var(--reveal-duration) var(--ease-out),
    transform var(--reveal-duration) var(--ease-out);
}

.js-motion [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* ---- Variants ---------------------------------------------------------- */

/* Fade only — for elements where movement would look fussy. */
.js-motion [data-reveal='fade'] {
  transform: none;
}

/* Soft scale-in, used for image tiles and media panels. */
.js-motion [data-reveal='scale'] {
  transform: translateY(var(--reveal-distance-sm)) scale(0.97);
}

/* Smaller travel, for dense rows like chips and list items. */
.js-motion [data-reveal='sm'] {
  transform: translateY(var(--reveal-distance-sm));
}

/* Media that settles from a gentle zoom rather than sliding. */
.js-motion [data-reveal='media'] {
  transform: scale(1.03);
  transition:
    opacity 600ms var(--ease-out),
    transform 700ms var(--ease-out);
}

/* =========================================================================
   2. Hero entrance (plays on load, not on scroll)

   The hero is the first thing a visitor sees, so it must not wait on an
   IntersectionObserver callback. These are keyframe animations that start
   as soon as the document is parsed.
   ========================================================================= */

@keyframes dentora-rise {
  from {
    opacity: 0;
    transform: translateY(var(--reveal-distance));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes dentora-settle {
  from {
    opacity: 0;
    transform: scale(var(--hero-image-scale));
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* Background photograph: fades in and settles out of a slight zoom. */
.js-motion [data-hero-image] {
  animation: dentora-settle 900ms var(--ease-out) both;
}

/* Hero content children, staggered in source order. `both` holds the
   from-state before the delay elapses, so nothing flashes in first. */
.js-motion [data-hero-in] {
  animation: dentora-rise var(--hero-duration) var(--ease-out) both;
}

.js-motion [data-hero-in='1'] { animation-delay: 60ms; }
.js-motion [data-hero-in='2'] { animation-delay: calc(60ms + var(--hero-stagger) * 1); }
.js-motion [data-hero-in='3'] { animation-delay: calc(60ms + var(--hero-stagger) * 2); }
.js-motion [data-hero-in='4'] { animation-delay: calc(60ms + var(--hero-stagger) * 3); }
.js-motion [data-hero-in='5'] { animation-delay: calc(60ms + var(--hero-stagger) * 4); }
.js-motion [data-hero-in='6'] { animation-delay: calc(60ms + var(--hero-stagger) * 5); }

/* =========================================================================
   3. Patient journey — connector draw

   The one signature moment on the homepage. The connector line scales out
   from its left edge as the steps appear beside it.
   ========================================================================= */

.js-motion [data-draw-line] {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 900ms var(--ease-out);
}

.js-motion [data-draw-line].is-revealed {
  transform: scaleX(1);
}

/* =========================================================================
   4. Success panels and status messages

   Form confirmations appear rather than snap, which reads as a response
   to what the patient just did.
   ========================================================================= */

@keyframes dentora-pop {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.js-motion [data-success]:not([hidden]) {
  animation: dentora-pop 420ms var(--ease-out) both;
}

/* Inline validation messages slide down rather than popping into place. */
@keyframes dentora-slide-down {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.js-motion .field__error:not(:empty),
.js-motion .consent__error:not(:empty) {
  animation: dentora-slide-down 220ms var(--ease-out) both;
}

.js-motion [data-error-summary]:not([hidden]) {
  animation: dentora-slide-down 260ms var(--ease-out) both;
}

/* =========================================================================
   5. Lightbox open

   The dialog previously appeared instantly. Backdrop fades, panel settles.
   ========================================================================= */

@keyframes dentora-lightbox-in {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes dentora-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.js-motion .lb[open] .lb__panel {
  animation: dentora-lightbox-in 320ms var(--ease-out) both;
}

.js-motion .lb[open]::backdrop {
  animation: dentora-backdrop-in 260ms var(--ease-out) both;
}

/* =========================================================================
   6. Submit button busy state

   The forms now reach a real server, so a submit can take a moment. The
   button says so instead of looking inert.
   ========================================================================= */

.btn[aria-busy='true'] {
  cursor: progress;
  opacity: 0.85;
}

.btn[aria-busy='true'] .btn__icon {
  animation: dentora-nudge 900ms var(--ease-in-out) infinite;
}

@keyframes dentora-nudge {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(4px); }
}

/* =========================================================================
   7. Reduced motion — the hard stop

   `.js-motion` is never applied when the visitor prefers reduced motion,
   so in practice none of the above is even reachable. This block is the
   second line of defence: it neutralises every animation defined here,
   including the infinite one above, and forces revealed state on.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-hero-in],
  [data-hero-image],
  [data-draw-line] {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }

  .btn[aria-busy='true'] .btn__icon,
  .lb[open] .lb__panel,
  .lb[open]::backdrop,
  [data-success]:not([hidden]),
  [data-error-summary]:not([hidden]),
  .field__error:not(:empty),
  .consent__error:not(:empty) {
    animation: none !important;
  }
}
