File size: 4,733 Bytes
2ce6518
 
 
 
 
d32737a
 
2ce6518
 
 
 
 
d32737a
2ce6518
d32737a
 
 
 
2ce6518
 
 
d32737a
2ce6518
d32737a
2ce6518
 
 
 
 
 
 
 
d32737a
 
 
 
 
2ce6518
 
 
 
 
 
 
 
 
 
 
d32737a
2ce6518
d32737a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce6518
 
 
 
d32737a
2ce6518
 
 
d32737a
 
 
2ce6518
9b672a8
 
2ce6518
cbd6f7b
d32737a
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/* 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 };
})();