Spaces:
Running
Running
File size: 8,132 Bytes
d32737a 2ce6518 d32737a 9dde5d4 d32737a 9dde5d4 d32737a 9dde5d4 d32737a 9dde5d4 d32737a 2ce6518 d32737a 9b672a8 d32737a 9b672a8 d32737a 9b672a8 d32737a 9b672a8 9dde5d4 9b672a8 2ce6518 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | /* manimo playground — shared kit (no build step, no deps).
* A tiny toolkit every widget uses: DOM helpers, editable number grids, a canvas helper, a
* step engine, and the math used to drive BOTH these testers and the Manim video animations (the
* computation is identical, so the website and the video explain a concept the exact same way).
* Widgets register themselves into MANIMO.registry[<primitive>] = { title, mount(root, concept) }.
*/
window.MANIMO = window.MANIMO || {};
MANIMO.registry = MANIMO.registry || {};
/* A tiny shared data bus so a paper's concepts interconnect: each tester publishes its result and
* downstream testers consume it — the token matrix X flows into Q·Kᵀ, whose scores flow into softmax,
* whose weights flow into the weighted sum. The SAME numbers move through every card, exactly as they
* move through the network in the video. Cleared by app.js whenever a new paper is rendered. */
MANIMO.bus = (function () {
let vals = {}, subs = {};
return {
set(key, val) { if (!key) return; vals[key] = val; (subs[key] || []).forEach((cb) => { try { cb(val); } catch (e) { /* noop */ } }); },
get(key) { return vals[key]; },
on(key, cb) { (subs[key] = subs[key] || []).push(cb); if (key in vals) { try { cb(vals[key]); } catch (e) { /* noop */ } } return cb; },
clear() { vals = {}; subs = {}; },
};
})();
MANIMO.kit = (function () {
function el(tag, attrs, children) {
const n = document.createElement(tag);
if (attrs) for (const k in attrs) {
if (k === "class") n.className = attrs[k];
else if (k === "style") n.style.cssText = attrs[k];
else if (k === "html") n.innerHTML = attrs[k];
else if (k.slice(0, 2) === "on") n.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
else n.setAttribute(k, attrs[k]);
}
(children || []).forEach((c) => n.appendChild(typeof c === "string" ? document.createTextNode(c) : c));
return n;
}
function clear(n) { while (n.firstChild) n.removeChild(n.firstChild); }
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const lerp = (a, b, t) => a + (b - a) * t;
function fmt(x) {
if (!isFinite(x)) return "–";
const r = Math.round(x * 100) / 100;
return Number.isInteger(r) ? String(r) : r.toFixed(2);
}
// An editable grid of numbers -> {node, get():number[][], set(m)}.
function grid(rows, cols, values, opts) {
opts = opts || {};
const inputs = [];
const tbl = el("table", { class: "mx" });
for (let i = 0; i < rows; i++) {
const tr = el("tr");
const row = [];
for (let j = 0; j < cols; j++) {
const v = values && values[i] && values[i][j] != null ? values[i][j] : 0;
const inp = el("input", { class: "cell", type: "number", step: "any", value: String(v) });
if (opts.readonly) inp.readOnly = true;
row.push(inp);
tr.appendChild(el("td", null, [inp]));
}
inputs.push(row);
tbl.appendChild(tr);
}
return {
node: tbl,
get: () => inputs.map((r) => r.map((i) => { const x = parseFloat(i.value); return Number.isFinite(x) ? x : 0; })),
set: (m) => m.forEach((r, i) => r.forEach((v, j) => { if (inputs[i] && inputs[i][j]) inputs[i][j].value = fmt(v); })),
cell: (i, j) => inputs[i][j],
highlight: (cells, on) => { (cells || []).forEach(([i, j]) => { if (inputs[i] && inputs[i][j]) inputs[i][j].classList.toggle("hot", on !== false); }); },
};
}
function button(label, onclick, cls) { return el("button", { class: "btn " + (cls || ""), onclick }, [label]); }
function chip(text, accent) { return el("span", { class: "chip" + (accent ? " acc" : "") }, [text]); }
function row(children, cls) { return el("div", { class: "row " + (cls || "") }, children); }
function panel(title) { const p = el("div", { class: "steps", role: "log", "aria-live": "polite" }); if (title) p.appendChild(el("div", { class: "steps-h" }, [title])); return p; }
function logLine(panel, html, accent) { panel.appendChild(el("div", { class: "step" + (accent ? " acc" : ""), html })); panel.scrollTop = panel.scrollHeight; }
// Heatmap canvas: render a matrix as colored cells on the accent scale.
function heatmap(canvas, m, theme, opts) {
opts = opts || {};
const ctx = canvas.getContext("2d");
const R = m.length, C = m[0] ? m[0].length : 0;
const W = canvas.width, H = canvas.height, cw = W / C, ch = H / R;
let lo = Infinity, hi = -Infinity;
m.forEach((r) => r.forEach((v) => { if (isFinite(v)) { lo = Math.min(lo, v); hi = Math.max(hi, v); } }));
if (!isFinite(lo) || !isFinite(hi)) { lo = 0; hi = 1; } // all non-finite → neutral scale, never NaN colours
const span = hi - lo || 1;
ctx.clearRect(0, 0, W, H);
for (let i = 0; i < R; i++) for (let j = 0; j < C; j++) {
const v = m[i][j];
const t = isFinite(v) ? (v - lo) / span : 0;
ctx.fillStyle = isFinite(v) ? mix(theme.bg, theme.accent, 0.12 + 0.85 * t) : theme.muted;
ctx.fillRect(j * cw + 1, i * ch + 1, cw - 2, ch - 2);
if (opts.values) { ctx.fillStyle = t > 0.55 ? theme.bg : theme.ink; ctx.font = Math.min(cw, ch) * 0.34 + "px system-ui"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(fmt(m[i][j]), j * cw + cw / 2, i * ch + ch / 2); }
}
}
function mix(a, b, t) {
const pa = hex(a), pb = hex(b);
const c = pa.map((v, i) => Math.round(lerp(v, pb[i], clamp(t, 0, 1))));
return "rgb(" + c.join(",") + ")";
}
function hex(h) {
h = (h || "#000000").replace("#", "");
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
}
// softmax + dot product — the shared math (the SAME steps the video animations narrate).
function softmax(v) { const m = Math.max.apply(null, v); const ex = v.map((x) => Math.exp(x - m)); const s = ex.reduce((a, b) => a + b, 0); return ex.map((x) => x / s); }
function dot(a, b) { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; }
function matmul(A, B) { const m = A.length, k = B.length, n = B[0].length; const C = []; for (let i = 0; i < m; i++) { C[i] = []; for (let j = 0; j < n; j++) { let s = 0; for (let p = 0; p < k; p++) s += A[i][p] * B[p][j]; C[i][j] = s; } } return C; }
function col(M, j) { return M.map((r) => r[j]); }
function transpose(M) { return (M[0] || []).map((_, j) => M.map((r) => r[j])); }
function sameShape(a, b) { return !!a && !!b && a.length === b.length && (a[0] || []).length === (b[0] || []).length; }
// A simple async step runner: each click/auto-tick calls next(); returns controls.
// play(ms, onDone) auto-advances to the end then calls onDone() once (used by "Play all").
function stepper(steps, render) {
let i = 0, timer = null, done = null;
function go(k) { i = clamp(k, 0, steps.length); render(i); }
function next() { if (i < steps.length) go(i + 1); }
function reset() { stop(); go(0); }
function stop() { if (timer) { clearInterval(timer); timer = null; } done = null; }
function play(ms, onDone) {
stop(); done = onDone || null;
timer = setInterval(() => {
if (i >= steps.length) { const d = done; stop(); if (d) d(); return; }
next();
}, ms || 700);
}
render(0);
return { next, reset, play, stop, at: () => i, total: steps.length };
}
// Wrap a stepper as a uniform "player" so the gallery can drive every widget the same way:
// reset() to the start, play(done) runs to the end and calls done(), stop() cancels.
function player(ctrl, ms) {
// honour prefers-reduced-motion: snap through the steps near-instantly instead of animating.
const reduce = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
return { play: (done) => ctrl.play(reduce ? 1 : (ms || 750), done), stop: () => ctrl.stop(), reset: () => ctrl.reset() };
}
return { el, clear, clamp, lerp, fmt, grid, button, chip, row, panel, logLine, heatmap, mix, hex, softmax, dot, matmul, col, transpose, sameShape, stepper, player };
})();
|