// AI summaries per article section, running fully on-device via WebGPU. // Model runtime vendored from huggingface.co/spaces/webml-community/lfm2-webgpu-kernels // (Lfm2Mobile: GGUF loader + custom WGSL kernels + generation loop, all in lfm2_5.js). import { Lfm2Mobile, DEFAULT_MODEL_ID } from "./lfm2_5.js"; // A "section" is an H1 or H2 plus every direct-child element after it up to the next // H1/H2 (mirrors the boundaries the .article-sidebar TOC already uses — H3s are // sub-headings within their parent section, not their own summarizable unit). const HEADING_SELECTOR = ":scope > h1, :scope > h2"; const MIN_CHARS = 60; // skip empty/near-empty sections (e.g. a heading right before the next one) const MAX_NEW_TOKENS = 512; const CIRC = 2 * Math.PI * 13; // matches the r=13 in renderRing() // Model + its download/warmup are shared across every section on the page: only the // first click anywhere pays the load cost. `progressListeners` fans load progress out to // every ring currently waiting on it (a section clicked mid-download gets live updates, // not just the one that triggered the load). `genTail` serializes generate() calls, since // the model can only run one generation at a time. let model = null; let loadPromise = null; const progressListeners = new Set(); let genTail = Promise.resolve(); document.addEventListener("DOMContentLoaded", init); function init() { const article = document.querySelector(".article"); if (!article) return; // Read the section boundaries from the ORIGINAL tree before any wrapping happens below // (wrapping a heading only reparents the heading itself, not its following siblings, but // computing every section's content list up front keeps this a clean read-then-write pass). // Also: must run before script.js wraps
 blocks into .code-collapsible — relies on
  // this file's