/* ==========================================================================
   motion.css — image placeholders and scroll reveals
   --------------------------------------------------------------------------
   1. .media  — every product / hero / page image sits in one of these boxes.
      Until a photo exists in /images, the box is a plain light-gray
      placeholder in the right aspect ratio (no text, no icon). When the photo
      loads, js/motion.js adds .is-loaded and the photo fades in with a very
      small settle (scale 1.03 -> 1). If the file is missing, .is-missing is
      added and only the gray box stays visible.

        <div class="media media--portrait">           4:5  (product images)
        <div class="media media--landscape">          3:2
        <div class="media media--square">             1:1
        <div class="media media--hero">               height set by page CSS

      One <img> per .media.

   2. [data-reveal] — any element with this attribute fades in and rises
      ~16px the first time it scrolls into view. Stagger with an inline
      style:  <article data-reveal style="--reveal-delay: 120ms">

   Hidden start states only apply under html.js, so without JavaScript
   everything is simply visible. On a slow connection photos still appear as
   soon as they have downloaded (snippet in each page's <head>), and text
   appears after 1s even if the scripts have not arrived yet. Reduced-motion users get a short fade with
   no movement. Only opacity and transform are animated (cheap for the
   browser); will-change is set by motion.js only while an animation runs.

   Owner: speeds live in the tokens (--dur-*, --ease) in css/base.css.
   ========================================================================== */


/* --------------------------------------------------------------------------
   Media boxes
   -------------------------------------------------------------------------- */

.media {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--color-placeholder);
}

.media--portrait  { aspect-ratio: 4 / 5; }
.media--landscape { aspect-ratio: 3 / 2; }
.media--square    { aspect-ratio: 1 / 1; }
/* .media--hero: no ratio here — the page CSS gives it a height. */

.media img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Hides alt text in case a broken image is ever painted. */
  color: transparent;
}

/* With JS: start hidden, reveal once loaded. */
.js .media img {
  opacity: 0;
  transform: scale(1.03);
  transition:
    opacity var(--dur-3) var(--ease),
    transform var(--dur-3) var(--ease);
}

.js .media.is-loaded img {
  opacity: 1;
  transform: none;
}

/* File not added yet: keep the image fully invisible so only the gray box
   shows — no broken-image icon, no alt text. */
.js .media.is-missing img {
  opacity: 0;
  visibility: hidden;
}

/* AI concept photo label (only on loaded photos, not on sketches). */
.media--concept.is-loaded:not(.is-sketch)::after {
  content: "Concept image";
  position: absolute;
  left: 10px;
  bottom: 10px;
  padding: 3px 7px;
  background: var(--color-bg);
  color: var(--color-muted);
  font-size: 10px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  line-height: 1.4;
  pointer-events: none;
}

/* Smaller label on narrow cards (phones, "Also in Drop 01") so it never
   runs past the image edge. */
@media (max-width: 759px) {
  .product-card .media--concept.is-loaded:not(.is-sketch)::after {
    left: 6px;
    bottom: 6px;
    max-width: calc(100% - 12px);
    padding: 2px 5px;
    font-size: 9px;
    letter-spacing: 0.08em;
    white-space: nowrap;
    overflow: hidden;
  }
}

/* Set by motion.js only for the duration of the reveal. */
.js .media.is-revealing img {
  will-change: opacity, transform;
}


/* --------------------------------------------------------------------------
   Scroll reveal
   -------------------------------------------------------------------------- */

.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition:
    opacity var(--dur-3) var(--ease),
    transform var(--dur-3) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

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

/* Safety net: if the scripts are slow to arrive, show the text anyway after
   1s instead of leaving it hidden. js/motion.js adds .motion-ready when it
   runs, which turns this off (FAILSAFE_MS there must match the 1s here). */
.js:not(.motion-ready) [data-reveal] {
  animation: motion-failsafe var(--dur-2) var(--ease) 1s forwards;
}

@keyframes motion-failsafe {
  to {
    opacity: 1;
    transform: none;
  }
}

.js [data-reveal].is-revealing {
  will-change: opacity, transform;
}


/* --------------------------------------------------------------------------
   Reduced motion: no movement, only a short fade
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .js .media img,
  .js [data-reveal] {
    transform: none;
    transition: opacity var(--dur-1) linear;
    transition-delay: 0ms;
  }

  .js .media.is-revealing img,
  .js [data-reveal].is-revealing {
    will-change: auto;
  }
}


/* --------------------------------------------------------------------------
   Print: always show everything
   -------------------------------------------------------------------------- */

@media print {
  .js [data-reveal],
  .js .media img {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
