/* Motion. Every animation clarifies a physical relationship; none decorate.
   Two paths: native scroll-driven animations where supported, IntersectionObserver
   fallback where not. Content is VISIBLE BY DEFAULT in both — a JS or feature
   failure can never leave the page blank. */

/* ---- Hero headline: a left-to-right wipe, like concrete poured into form.
        Applied only under no-preference, so the default state is fully visible. ---- */
@media (prefers-reduced-motion: no-preference){
  .hero__title{clip-path:inset(0 100% 0 0);animation:wipe var(--dur-cine) var(--ease-out-expo) 200ms forwards}
  .hero__rule{transform:scaleX(0);transform-origin:left;
    animation:rule-in var(--dur-slow) var(--ease-out-expo) 900ms forwards}
}
@keyframes wipe{to{clip-path:inset(0 0 0 0)}}
@keyframes rule-in{to{transform:scaleX(1)}}

/* ---- Section reveals: native scroll-driven timeline. ---- */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    /* Both ends of the range are expressed in `entry`, deliberately. A range that
       ENDS in `cover` does not resolve the same way once the subject is taller
       than the viewport, and this site's full-bleed plates are ~1050px tall on a
       900px viewport — they were reaching only ~7% of the fade and staying there.
       `entry` is well defined at any subject height: it runs from the subject's
       leading edge entering to the subject having fully entered. */
    .reveal{animation:reveal-in linear both;animation-timeline:view();
      animation-range:entry 8% entry 90%}
    .eng__num{animation:num-settle linear both;animation-timeline:view();
      animation-range:entry 8% entry 95%}

    /* Photographs settle rather than appear: a 1.03 -> 1 scale over the entry
       range. Compositor-only (transform), and slow enough to read as the image
       arriving rather than as an effect. The overflow clip stops the scaled
       frame bleeding past its box. */
    .plate img,.arc__frame img{animation:plate-settle linear both;
      animation-timeline:view();animation-range:entry 0% cover 40%}
    .plate,.arc__frame{overflow:clip}

    /* The hero breathes — a very slow drift so a still frame is not dead on
       arrival. Ends where it starts scaling so nothing crops out of frame. */
    .hero__poster img{animation:hero-drift 24s var(--ease-out-expo) both}
  }
}
@keyframes plate-settle{from{transform:scale(1.03)}to{transform:none}}
@keyframes hero-drift{from{transform:scale(1.045)}to{transform:scale(1)}}
@keyframes reveal-in{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@keyframes num-settle{from{opacity:0;transform:scale(.92)}to{opacity:.42;transform:none}}

/* ---- Fallback: gated on a class that reveal.js sets only after it loads.
        If the module never runs, nothing is hidden. ---- */
.has-js-reveal .reveal{opacity:0;transform:translateY(22px);
  transition:opacity var(--dur-slow) var(--ease-out-expo),transform var(--dur-slow) var(--ease-out-expo)}
.has-js-reveal .reveal.is-in{opacity:1;transform:none}

/* ---- Global brake. ---- */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important}
  .has-js-reveal .reveal{opacity:1;transform:none}
  .hero__title{clip-path:none}
  .hero__rule{transform:none}
  .hero__scroll{display:none}
}
