/*! trentpower.fr · authored source */
/*
  trentpower.fr
  scroll reveal

  Role:
  fades the homepage's principle / trajectory / project sections in
  as they scroll into view. decoration only — the sections are fully
  legible without it, and reduced-motion users get them at rest.

  Source:
  edited here as reveal.template.js; compiled to reveal.js by
  generate_site.py (minified, no substitution).

  Constraints:
  - no fetch, no inline handlers, no third-party scripts
  - waits for the browser to be idle; never on the lcp critical path
  - enhancement only: with this absent every section paints at rest
    (css gates the hidden state on html.js, which only js sets)
*/

(function () {
  'use strict';

  var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  // scroll reveal · IntersectionObserver. stagger is scoped per
  // selector group so the index always matches the dom order of that
  // list rather than wrapping across groups.
  function _reveal() {
    var revealGroups = ['.principle', '.trajectory-item', '.project-card'];
    var allRevealEls = document.querySelectorAll(revealGroups.join(', '));
    if (!prefersReducedMotion && 'IntersectionObserver' in window) {
      var observer = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
          if (entry.isIntersecting) {
            entry.target.classList.add('visible');
            observer.unobserve(entry.target);
          }
        });
      }, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' });

      revealGroups.forEach(function (sel) {
        document.querySelectorAll(sel).forEach(function (el, i) {
          // phase 96 · data-* attribute instead of inline style
          // (csp style-src-attr 'none'). styles.src.css carries a
          // 16-row [data-reveal-index="N"] stagger table that maps
          // 0 → 0ms, 1 → 80ms … 15 → 1200ms. cap at 15 so the
          // attribute always matches a rule.
          el.setAttribute('data-reveal-index', Math.min(i, 15));
          observer.observe(el);
        });
      });
    } else {
      allRevealEls.forEach(function (el) {
        el.classList.add('visible');
      });
    }
  }

  // fonts-ready gate · the reveal animation reads heading metrics
  // before observing, so it must not run during the full-font swap.
  // waits for html.fonts-ready (set by fonts.template.js) or for
  // document.fonts.ready to resolve, whichever lands first, with a
  // 1500ms safety timeout matching fonts.template.js so a stalled
  // fonts api never strands the reveal.
  function whenFontsReady(fn) {
    var root = document.documentElement;
    if (root.classList.contains('fonts-ready')) { fn(); return; }
    var done = false;
    var fire = function () { if (done) return; done = true; fn(); };
    if (document.fonts && document.fonts.ready && typeof document.fonts.ready.then === 'function') {
      document.fonts.ready.then(fire, fire);
    }
    setTimeout(fire, 1500);
  }

  function _schedule() {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(_reveal, { timeout: 2000 });
    } else {
      setTimeout(_reveal, 200);
    }
  }

  // reduced-motion users force everything .visible at rest inside
  // _reveal; that path must fire immediately and not wait on fonts —
  // there is no animation to protect from a metric swap.
  if (prefersReducedMotion) {
    _schedule();
  } else {
    whenFontsReady(_schedule);
  }

})();
