/* ==========================================================================
   Astratech Solutions — Motion

   The governing idea: almost nothing moves, and what moves arrives once and
   settles. Confidence reads as stillness. Anything that announces itself as
   an "effect" has been removed.

   What was deliberately taken out, and why:
     - count-up on numbers. Numbers that spin are a dashboard device; a brand
       states a figure and leaves it standing.
     - the accent scroll-progress bar. A reading-progress indicator belongs on
       an article, not a brand page.
     - per-element staggered cascades. Every element fading up in sequence is
       the single clearest tell of a template.

   What remains:
     - one quiet block-level entry: 10px, opacity, long settle, no overshoot
     - a slow mask wipe on photography, paired with a settling scale
     - hover states that are felt more than seen

   Rules held throughout: eased, never linear; 200-560ms for interface, 900ms
   for the image wipe; runs once; never blocks reading.
   ========================================================================== */

/* -- Entry reveal -----------------------------------------------------------
   Applied at block level, not to every child. Groups stagger at most three
   steps so the eye reads a section arriving, not a list animating. */

.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--reveal-distance), 0);
  transition:
    opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
  transition-delay: calc(min(var(--reveal-index, 0), 3) * var(--reveal-stagger));
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* -- Photography ------------------------------------------------------------
   A mask wipe from the bottom edge with a simultaneous settle from 1.06 to 1.
   Slower than the interface motion on purpose: the image should feel like it
   is being uncovered, not like it is sliding in. */

[data-clip-reveal] { overflow: clip; }

.js [data-clip-reveal] img {
  clip-path: inset(0 0 100% 0);
  transform: scale(1.06);
  transition:
    clip-path var(--duration-image) var(--ease-out),
    transform calc(var(--duration-image) + 400ms) var(--ease-out);
}

[data-clip-reveal].is-revealed img {
  clip-path: inset(0 0 0 0);
  transform: scale(1);
}

/* -- Hero -------------------------------------------------------------------
   A still lead image settles once on load. There is no scroll-linked parallax:
   a background that drifts under fixed type is the effect this brief is
   trying to avoid.

   The rule is scoped to img on purpose. The hero currently holds a video,
   which supplies its own movement — adding a scale settle on top of it would
   be two motions competing. If the hero goes back to a photograph, this picks
   up again with no further change. */

.hero__media img {
  transform: scale(1.05);
  transition: transform 1600ms var(--ease-out);
}

.hero.is-loaded .hero__media img { transform: scale(1); }

/* The video fades in over the poster once it is genuinely playing, so the
   first frame never pops and a paused video is indistinguishable from the
   poster. Class set in js/main.js on the 'playing' event. */
.js .hero__video {
  opacity: 0;
  transition: opacity 1200ms var(--ease-out);
}

.hero__video.is-playing { opacity: 1; }

/* -- Image loading ----------------------------------------------------------
   Photographs resolve rather than pop: hidden until decoded, then a short
   fade. Class set by js/products.js on load; the card's ground colour shows
   in the meantime, so nothing reads as broken while it loads. */
.js .product-card__media img,
.js .detail-panel__media img {
  opacity: 0;
}

.product-card__media img.is-loaded,
.detail-panel__media img.is-loaded {
  opacity: 1;
}

.detail-panel__media img {
  transition: opacity var(--duration-slow) var(--ease-out);
}

/* -- Hover ------------------------------------------------------------------
   Long, small, and mostly tonal. Nothing jumps, nothing lifts off the page. */

/* Product card: the image breathes, a rule draws under the name, and the
   quiet "specification" label resolves. Three small things, one gesture. */
.product-card__media img {
  transition:
    opacity 700ms var(--ease-out),
    transform 800ms var(--ease-out),
    filter 800ms var(--ease-out);
}

.product-card:hover .product-card__media img,
.product-card:focus-within .product-card__media img {
  transform: scale(1.04);
}

.product-card__rule {
  height: 1px;
  width: 100%;
  background: var(--color-border);
  position: relative;
  overflow: hidden;
}

.product-card__rule::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-accent-500);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--duration-slow) var(--ease-out);
}

.product-card:hover .product-card__rule::after,
.product-card:focus-within .product-card__rule::after {
  transform: scaleX(1);
}

.product-card__cue {
  opacity: 0;
  transform: translate3d(-4px, 0, 0);
  transition:
    opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}

.product-card:hover .product-card__cue,
.product-card:focus-within .product-card__cue {
  opacity: 1;
  transform: none;
}

/* On a touch device there is no hover, so the cue would never resolve and the
   card would end in a rule with empty space under it. Show it outright. */
@media (hover: none) {
  .product-card__cue {
    opacity: 1;
    transform: none;
  }
}

/* Nav: the underline draws rather than appearing. */
.site-nav__link {
  position: relative;
}

.site-nav__link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--duration-base) var(--ease-out);
}

.site-nav__link:hover::after,
.site-nav__link.is-current::after {
  transform: scaleX(1);
}

/* -- Reduced motion ---------------------------------------------------------
   Transforms and masks are removed entirely; opacity is held at its finished
   value so nothing is ever hidden. Layout is never altered. The .js prefix is
   carried here so these rules match the specificity of the initial states
   above and, being later in the file, win. */

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  [data-reveal],
  [data-reveal].is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .js [data-clip-reveal] img,
  [data-clip-reveal] img,
  [data-clip-reveal].is-revealed img {
    clip-path: none;
    transform: none;
    transition: none;
  }

  .hero__media img,
  .hero.is-loaded .hero__media img {
    transform: none;
    transition: none;
  }

  .js .hero__video,
  .js .product-card__media img,
  .js .detail-panel__media img {
    opacity: 1;
    transition: none;
  }

  .detail-panel.is-open .detail-panel__body { animation: none; }

  .product-card:hover .product-card__media img,
  .product-card:focus-within .product-card__media img { transform: none; }

  .product-card__rule::after,
  .product-card__cue,
  .site-nav__link::after {
    transition: none;
  }

  .detail-panel,
  .detail-panel.is-open { transition: none; }
}
