/* Blog runtime. The article itself is static HTML; this file only enhances it:
   reading progress, the contents scroll-spy, interactive figures, and index filters.
   Wrapped in an IIFE because every text/babel script shares one global scope. */
(() => {
  /* Reading progress under the nav */
  const bar = document.querySelector(".b-progress span");
  const article = document.querySelector(".b-body");
  if (bar && article) {
    const update = () => {
      const r = article.getBoundingClientRect();
      const total = r.height - window.innerHeight * 0.6;
      const p = Math.min(1, Math.max(0, -r.top / Math.max(1, total)));
      bar.style.transform = `scaleX(${p})`;
    };
    update();
    window.addEventListener("scroll", update, { passive: true });
    window.addEventListener("resize", update);
  }

  /* Contents scroll-spy: the last heading above the upper third of the viewport is current */
  const links = [...document.querySelectorAll(".b-toc a")];
  if (links.length) {
    const heads = links.map(a => document.getElementById(decodeURIComponent(a.hash.slice(1)))).filter(Boolean);
    const spy = () => {
      const line = window.innerHeight * 0.3;
      let current = heads[0];
      for (const h of heads) if (h.getBoundingClientRect().top <= line) current = h;
      links.forEach(a => a.classList.toggle("is-active", a.hash.slice(1) === current.id));
    };
    spy();
    window.addEventListener("scroll", spy, { passive: true });

    /* Wide figures break out across the contents column, so tuck the list away while one passes it */
    const toc = document.querySelector(".b-toc");
    const wides = [...document.querySelectorAll(".b-body .b-wide")];
    const tuck = () => {
      if (getComputedStyle(toc).position !== "sticky") { toc.classList.remove("is-tucked"); return; }
      const t = toc.getBoundingClientRect();
      const hit = wides.some(w => { const r = w.getBoundingClientRect(); return r.top < t.bottom + 24 && r.bottom > t.top - 24; });
      toc.classList.toggle("is-tucked", hit);
    };
    tuck();
    window.addEventListener("scroll", tuck, { passive: true });
    window.addEventListener("resize", tuck);
  }

  /* Interactive figures: <div class="b-visual" data-visual="Name"><script type="application/json">{props}</script></div> */
  const V = window.BlogVisuals || {};
  document.querySelectorAll("[data-visual]").forEach(el => {
    const Comp = V[el.dataset.visual];
    if (!Comp) { console.warn("Unknown blog visual:", el.dataset.visual); return; }
    const cfg = el.querySelector('script[type="application/json"]');
    let props = {};
    try { props = cfg ? JSON.parse(cfg.textContent) : {}; } catch (e) { console.error("Bad visual config", el.dataset.visual, e); return; }
    el.style.minHeight = "";
    ReactDOM.createRoot(el).render(<Comp {...props} />);
  });

  /* Index: category filters */
  const chips = [...document.querySelectorAll(".b-chip[data-filter]")];
  chips.forEach(chip => chip.addEventListener("click", () => {
    const f = chip.dataset.filter;
    chips.forEach(c => c.classList.toggle("is-active", c === chip));
    document.querySelectorAll(".b-card[data-category]").forEach(card => {
      card.hidden = f !== "all" && card.dataset.category !== f;
    });
  }));
})();
