Spaces:
Running
Running
| /* 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 }; | |
| })(); | |