/* Narrative / structural beat card — the fallback that carries the paper's STORY beats (problem, prior * work, method framing, takeaway) and structural concepts (flow / cycle / timeline / layers / tree / * compare). It animates the beat's own points into a tasteful, staggered diagram: bullet flow, a * two-column "existing approaches vs this work" for compare, a vertical stack for layers/tree. Each beat * gets a kind pill so the whole paper reads as problem → prior → method → results → takeaway. */ (function () { const k = MANIMO.kit; const KIND = { problem: "Problem", pain: "Why it hurts", prior: "Prior work", compare: "Existing vs this", title: "The big question", hook: "The big question", takeaway: "Takeaway", closing: "Takeaway", results: "Result", flow: "Pipeline", cycle: "Cycle", timeline: "Timeline", layers: "Layers", tree: "Tree", }; function labelsOf(p) { return p.stages || p.layers || p.points || p.nodes || p.children || p.terms || []; } function mount(root, concept, theme) { const p = (concept && concept.params) || {}; const prim = (concept && concept.primitive) || "concept"; const kindLabel = KIND[prim] || (concept && concept.beat && KIND[concept.beat]) || "Concept"; const isCompare = prim === "compare" && (p.left || p.right); const isVertical = (prim === "layers" || prim === "tree"); let labels = labelsOf(p).slice(0, 8).map(String); if (!labels.length && !isCompare) labels = (concept.label ? [concept.label] : ["this concept"]); const head = k.el("div", { class: "beat-head" }, [ k.el("span", { class: "beat-kind" }, [kindLabel]), k.el("span", { class: "beat-ttl" }, [concept.title || concept.label || ""]), ]); const stage = k.el("div", { class: "diagram " + prim + (isVertical ? " vert" : "") }); function chip(text, i) { const c = k.el("div", { class: "node", style: "animation-delay:" + (i * 90) + "ms" }, [String(text)]); if (prim === "cycle" || prim === "flow") c.classList.add("flowy"); return c; } function arrow(kind) { return k.el("div", { class: "arrow" }, [kind === "cycle" ? "↻" : "→"]); } function drawCompare() { k.clear(stage); stage.classList.add("compare"); const cols = [["Existing approaches", p.left || [], "old"], ["This work", p.right || [], "new"]]; cols.forEach(([ttl, items, cls], ci) => { const col = k.el("div", { class: "vs-col " + cls }); col.appendChild(k.el("div", { class: "vs-h" }, [ttl])); (items || []).slice(0, 6).forEach((it, i) => col.appendChild(k.el("div", { class: "node", style: "animation-delay:" + ((ci * 3 + i) * 110) + "ms" }, [String(it)]))); stage.appendChild(col); if (ci === 0) stage.appendChild(k.el("div", { class: "vs-mid" }, ["vs"])); }); } function draw() { if (isCompare) return drawCompare(); k.clear(stage); if (prim === "tree" && labels.length) { stage.appendChild(k.el("div", { class: "node root", style: "animation-delay:0ms" }, [labels[0]])); const kids = k.el("div", { class: "kids" }); labels.slice(1).forEach((l, i) => kids.appendChild(chip(l, i + 1))); stage.appendChild(kids); return; } labels.forEach((l, i) => { stage.appendChild(chip(l, i)); if (!isVertical && i < labels.length - 1) stage.appendChild(arrow(prim)); }); if (prim === "cycle" && labels.length) stage.appendChild(arrow("cycle")); } const editor = !isCompare ? k.el("input", { class: "lbledit", value: labels.join(", "), title: "edit labels (comma-separated)" }) : null; if (editor) editor.addEventListener("input", () => { labels = editor.value.split(",").map((s) => s.trim()).filter(Boolean).slice(0, 8); draw(); }); root.appendChild(head); if (concept.explainer) root.appendChild(k.el("p", { class: "explainer" }, [concept.explainer])); const ctrls = [k.chip(prim, true)]; if (editor) ctrls.push(k.el("label", { class: "fld" }, ["labels ", editor])); root.appendChild(k.row(ctrls, "ctrls")); root.appendChild(stage); root.appendChild(k.row([k.button("↻ Replay", draw, "ghost")], "ctrls")); draw(); // player: re-run the staggered reveal, then signal done once the last node has popped in. let to = null; function stop() { if (to) { clearTimeout(to); to = null; } } const beats = isCompare ? ((p.left || []).length + (p.right || []).length) : labels.length; return { play: (done) => { stop(); draw(); to = setTimeout(() => { to = null; if (done) done(); }, Math.max(1500, beats * 190 + 950)); }, stop, reset: draw }; } MANIMO.registry["_generic"] = { title: "Concept", mount }; })();