/* ==========================================================================
   MOTION — the site's motion language
   Owner: UI-3 (public/css/motion.css). Loads LAST, so this file is also
   where cross-cutting motion refinements over other agents' components
   live. Per CONTRACT.md: motion/transitions only here — no colors, no
   layout, no box model changes to another component.

   Duration/easing aliases mirror the ones in lightbox.css so the whole
   site shares one consistent, "slow, confident, expensive" rhythm.
   ========================================================================== */

:root {
  --mo-ease-expo: var(--ease-out-expo, cubic-bezier(.16, 1, .3, 1));
  --mo-ease-soft: var(--ease-in-out, cubic-bezier(.65, 0, .35, 1));
  --mo-dur-fast: var(--dur-fast, 180ms);
  --mo-dur-base: var(--dur-base, 350ms);
  --mo-dur-slow: var(--dur-slow, 550ms);
}

/* ==========================================================================
   Keyframe library — every named animation used across the site lives
   here so any file can reference it by name regardless of load order
   (keyframes resolve at compute time, not stylesheet-parse time). Built
   exclusively on transform / opacity / filter, per the animation rule.
   ========================================================================== */

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

@keyframes fade-up {
  from { opacity: 0; transform: translateY(1rem); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes blur-in {
  from { opacity: 0; filter: blur(16px); }
  to { opacity: 1; filter: blur(0); }
}

@keyframes scale-in {
  from { opacity: 0; transform: scale(.94); }
  to { opacity: 1; transform: scale(1); }
}

@keyframes slide-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

@keyframes shimmer {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.35); }
}

@keyframes pulse-soft {
  0%, 100% { opacity: 1; }
  50% { opacity: .55; }
}

@keyframes ken-burns {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to { transform: scale(1.08) translate3d(-1%, -1%, 0); }
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes reveal-mask {
  /* Transform-only curtain wipe for an overlay element; pair with
     transform-origin: <edge> on the consumer to pick a wipe direction. */
  from { transform: scaleX(1); }
  to { transform: scaleX(0); }
}

/* ==========================================================================
   Scroll reveal — grid tiles
   CODE-B renders ~60-tile chunks and adds .is-loaded once the 600px image
   has decoded; there's no per-tile index to key off, so the stagger comes
   from 12 nth-child buckets that repeat across all 639 tiles. Plain
   transitions (not keyframe animations) drive the reveal itself, since
   .is-loaded is a one-way switch per photo — no risk of an animation's
   fill-mode permanently pinning transform/opacity over a later hover
   effect grid.css might add.
   ========================================================================== */

.tile {
  opacity: .82;
  filter: saturate(.85);
  transform: translateY(1rem);
  transition: opacity var(--mo-dur-slow) var(--mo-ease-expo),
              filter var(--mo-dur-slow) var(--mo-ease-expo),
              transform var(--mo-dur-slow) var(--mo-ease-expo);
}

.tile.is-loaded {
  opacity: 1;
  filter: saturate(1);
  transform: translateY(0);
}

.tile:nth-child(12n + 1).is-loaded  { transition-delay: 0ms; }
.tile:nth-child(12n + 2).is-loaded  { transition-delay: 20ms; }
.tile:nth-child(12n + 3).is-loaded  { transition-delay: 40ms; }
.tile:nth-child(12n + 4).is-loaded  { transition-delay: 60ms; }
.tile:nth-child(12n + 5).is-loaded  { transition-delay: 80ms; }
.tile:nth-child(12n + 6).is-loaded  { transition-delay: 100ms; }
.tile:nth-child(12n + 7).is-loaded  { transition-delay: 120ms; }
.tile:nth-child(12n + 8).is-loaded  { transition-delay: 140ms; }
.tile:nth-child(12n + 9).is-loaded  { transition-delay: 160ms; }
.tile:nth-child(12n + 10).is-loaded { transition-delay: 180ms; }
.tile:nth-child(12n + 11).is-loaded { transition-delay: 200ms; }
.tile:nth-child(12n).is-loaded      { transition-delay: 220ms; }

/* A quiet shimmer signals a tile is still decoding. Lives on the image
   itself (not the figure), so it never fights the figure's own reveal
   transition above for the `filter` property. */
.tile:not(.is-loaded) .tile__img {
  animation: shimmer 1.8s var(--mo-ease-soft) infinite;
}

.tile.is-loaded .tile__img {
  animation: none;
}

/* ==========================================================================
   Cross-cutting interaction polish
   Transition-timing refinements only — no color, no box model. Several of
   these rules set no visual value at all; they exist purely so that
   whatever property another component already toggles eases with the
   site's shared rhythm instead of snapping.
   ========================================================================== */

.site-header {
  transition: transform var(--mo-dur-base) var(--mo-ease-soft),
              opacity var(--mo-dur-base) var(--mo-ease-soft),
              filter var(--mo-dur-base) var(--mo-ease-soft);
}

#scrollProgress span {
  transition: transform var(--mo-dur-fast) linear;
}

/* Hero scroll cue: a slow, quiet breathing invitation to scroll. */
.hero__scroll-line {
  animation: pulse-soft 2.4s var(--mo-ease-soft) infinite;
}

/* Hero media: the slow, cinematic drift the brief asks for. Hero.css owns
   sizing/position/overflow-clipping; this only ever touches transform. */
.hero__media {
  animation: ken-burns 16s var(--mo-ease-soft) infinite alternate;
}

a,
.btn {
  transition: opacity var(--mo-dur-fast) var(--mo-ease-soft),
              transform var(--mo-dur-fast) var(--mo-ease-soft),
              filter var(--mo-dur-fast) var(--mo-ease-soft);
}

.btn:active,
.icon-btn:active {
  opacity: .8;
}

/* Toolbar chip / layout icon-button active swap: chips stay visible the
   whole time (only their color/border ownership belongs to chrome.css), so
   this is a persistent, non-overshooting scale rather than a pop-from-
   nothing keyframe — confident, not bouncy, and never flickers the label. */
.chip,
.icon-btn {
  scale: 1;
  transition: opacity var(--mo-dur-fast) var(--mo-ease-soft),
              scale var(--mo-dur-fast) var(--mo-ease-expo),
              filter var(--mo-dur-fast) var(--mo-ease-soft);
}

.chip.is-active,
.icon-btn.is-active {
  scale: 1.05;
}

#backToTop {
  transition: opacity var(--mo-dur-base) var(--mo-ease-soft),
              transform var(--mo-dur-base) var(--mo-ease-soft);
}

#backToTop:hover {
  transform: translateY(-.15rem);
}

/* ==========================================================================
   View transitions — progressive enhancement, degrades to nothing where
   unsupported. Scoped to the root snapshot only; no per-photo name pairing
   between grid tiles and #lbImg, since both can be simultaneously present
   in the DOM (lightbox overlays the grid rather than replacing it) and a
   duplicate view-transition-name would just abort the transition.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--mo-dur-slow);
    animation-timing-function: var(--mo-ease-expo);
  }
}

/* ==========================================================================
   prefers-reduced-motion: reduce — first-class, not an afterthought.
   Every selector mirrors the exact class/id it calms at equal specificity;
   this file loads last, so plain source order lets these win without
   !important, as long as no other file exceeds single-class specificity
   (the project-wide rule everyone is holding to). Transforms/loops are
   neutralised; opacity-only feedback survives everywhere something needs
   to visibly open, close, or reveal.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {

  .site-header,
  .hero__scroll-line,
  .btn,
  .chip,
  .icon-btn,
  #backToTop,
  a {
    animation: none;
    transform: none;
    scale: 1;
  }

  .hero__media {
    animation: none;
  }

  /* Lightbox: still opens and closes, opacity-only, no scale/blur/slide. */
  #lightbox,
  html.lb-open #lightbox {
    transition: opacity var(--mo-dur-fast) linear;
  }

  .lightbox__img,
  html.lb-open .lightbox__img,
  #lightbox.is-zoomed .lightbox__img {
    scale: 1;
    transition: opacity var(--mo-dur-fast) linear;
  }

  .lightbox__blur,
  html.lb-open .lightbox__blur {
    transform: none;
    transition: opacity var(--mo-dur-fast) linear;
  }

  .lightbox__bar,
  .lightbox__filmstrip,
  .lb-nav,
  html.lb-open .lightbox__bar,
  html.lb-open .lightbox__filmstrip,
  html.lb-open .lb-nav,
  #lightbox.lb-chrome-hidden .lightbox__bar,
  #lightbox.lb-chrome-hidden .lightbox__filmstrip,
  #lightbox.lb-chrome-hidden .lb-nav {
    transform: none;
    transition: opacity var(--mo-dur-fast) linear;
  }

  .lb-nav::before {
    transition: opacity var(--mo-dur-fast) linear;
  }

  .lb-nav--prev:hover::before,
  .lb-nav--next:hover::before {
    translate: 0;
  }

  .film,
  .film:hover,
  .film.is-active {
    translate: 0;
    scale: 1;
    transition: opacity var(--mo-dur-fast) linear, filter var(--mo-dur-fast) linear;
  }

  /* Grid reveal: opacity-only fade, no rise, no stagger wait. */
  .tile {
    transform: none;
    transition: opacity var(--mo-dur-fast) linear, filter var(--mo-dur-fast) linear;
  }

  .tile.is-loaded {
    transform: none;
  }

  .tile:nth-child(12n + 1).is-loaded,
  .tile:nth-child(12n + 2).is-loaded,
  .tile:nth-child(12n + 3).is-loaded,
  .tile:nth-child(12n + 4).is-loaded,
  .tile:nth-child(12n + 5).is-loaded,
  .tile:nth-child(12n + 6).is-loaded,
  .tile:nth-child(12n + 7).is-loaded,
  .tile:nth-child(12n + 8).is-loaded,
  .tile:nth-child(12n + 9).is-loaded,
  .tile:nth-child(12n + 10).is-loaded,
  .tile:nth-child(12n + 11).is-loaded,
  .tile:nth-child(12n).is-loaded {
    transition-delay: 0ms;
  }

  .tile__img {
    animation: none;
  }

  #scrollProgress span {
    transition: none;
  }
}
