explainer-video 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +12 -0
- package/LICENSE +21 -0
- package/README.md +152 -2
- package/cordis.patch.yml +7 -0
- package/lib/index.js +35 -0
- package/package.json +56 -4
- package/plugin.json +13 -0
- package/skills/coursework-author/.synced-from +5 -0
- package/skills/coursework-author/SKILL.md +209 -0
- package/skills/craft-codeflow-flows/SKILL.md +116 -0
- package/skills/craft-codeflow-mine/SKILL.md +207 -0
- package/skills/craft-codeflow-plan/SKILL.md +65 -0
- package/skills/craft-codeflow-scene/SKILL.md +168 -0
- package/skills/craft-video-align/SKILL.md +109 -0
- package/skills/craft-video-assemble/SKILL.md +69 -0
- package/skills/craft-video-audio/SKILL.md +59 -0
- package/skills/craft-video-enrich/SKILL.md +46 -0
- package/skills/craft-video-plan/SKILL.md +57 -0
- package/skills/craft-video-publish/SKILL.md +79 -0
- package/skills/craft-video-render/SKILL.md +68 -0
- package/skills/craft-video-scene/SKILL.md +112 -0
- package/skills/craft-video-script/SKILL.md +80 -0
- package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
- package/skills/explainer-video-from-codebase/SKILL.md +659 -0
- package/skills/explainer-video-from-codebase/tools/apply-flows.mjs +531 -0
- package/skills/explainer-video-from-codebase/tools/approve-flows.mjs +56 -0
- package/skills/explainer-video-from-codebase/tools/codemap-md.mjs +116 -0
- package/skills/explainer-video-from-codebase/tools/config/dotenv.mjs +13 -0
- package/skills/explainer-video-from-codebase/tools/config/json.mjs +14 -0
- package/skills/explainer-video-from-codebase/tools/config/redact.mjs +16 -0
- package/skills/explainer-video-from-codebase/tools/config/toml-lite.mjs +13 -0
- package/skills/explainer-video-from-codebase/tools/config/yaml-lite.mjs +18 -0
- package/skills/explainer-video-from-codebase/tools/excerpt-check.mjs +186 -0
- package/skills/explainer-video-from-codebase/tools/excerpt.mjs +116 -0
- package/skills/explainer-video-from-codebase/tools/flow-diagram.mjs +168 -0
- package/skills/explainer-video-from-codebase/tools/flow-plan.mjs +129 -0
- package/skills/explainer-video-from-codebase/tools/flows-md.mjs +87 -0
- package/skills/explainer-video-from-codebase/tools/lang/c.mjs +684 -0
- package/skills/explainer-video-from-codebase/tools/lang/go.mjs +673 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-config.mjs +143 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-deps.mjs +62 -0
- package/skills/explainer-video-from-codebase/tools/lang/python-stdlib.mjs +24 -0
- package/skills/explainer-video-from-codebase/tools/lang/python.mjs +609 -0
- package/skills/explainer-video-from-codebase/tools/lang/typescript.mjs +963 -0
- package/skills/explainer-video-from-codebase/tools/lib/decompose.mjs +102 -0
- package/skills/explainer-video-from-codebase/tools/lib/groups.mjs +77 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe-coverage.mjs +68 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe-v8.mjs +232 -0
- package/skills/explainer-video-from-codebase/tools/lib/observe.mjs +162 -0
- package/skills/explainer-video-from-codebase/tools/lib/redact.mjs +28 -0
- package/skills/explainer-video-from-codebase/tools/lib/sandbox.mjs +97 -0
- package/skills/explainer-video-from-codebase/tools/lib/test-links.mjs +28 -0
- package/skills/explainer-video-from-codebase/tools/lib/toml.mjs +129 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-c.mjs +95 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-go.mjs +101 -0
- package/skills/explainer-video-from-codebase/tools/lib/trace-js.mjs +136 -0
- package/skills/explainer-video-from-codebase/tools/lib/tree-sitter.mjs +19 -0
- package/skills/explainer-video-from-codebase/tools/lib/walk.mjs +138 -0
- package/skills/explainer-video-from-codebase/tools/mine.mjs +409 -0
- package/skills/explainer-video-from-codebase/tools/propose.mjs +127 -0
- package/skills/explainer-video-from-codebase/tools/rank.mjs +119 -0
- package/skills/explainer-video-from-codebase/tools/ranking-md.mjs +76 -0
- package/skills/explainer-video-from-codebase/tools/rules/c-handlers.mjs +53 -0
- package/skills/explainer-video-from-codebase/tools/rules/celery-taskiq.mjs +48 -0
- package/skills/explainer-video-from-codebase/tools/rules/click-typer.mjs +6 -0
- package/skills/explainer-video-from-codebase/tools/rules/express.mjs +72 -0
- package/skills/explainer-video-from-codebase/tools/rules/fastapi.mjs +99 -0
- package/skills/explainer-video-from-codebase/tools/rules/go-http.mjs +173 -0
- package/skills/explainer-video-from-codebase/tools/rules/go-test.mjs +108 -0
- package/skills/explainer-video-from-codebase/tools/rules/grpc.mjs +57 -0
- package/skills/explainer-video-from-codebase/tools/rules/gtest.mjs +97 -0
- package/skills/explainer-video-from-codebase/tools/rules/haystack.mjs +55 -0
- package/skills/explainer-video-from-codebase/tools/rules/jest-vitest.mjs +100 -0
- package/skills/explainer-video-from-codebase/tools/rules/js-http-client.mjs +111 -0
- package/skills/explainer-video-from-codebase/tools/rules/nextjs.mjs +53 -0
- package/skills/explainer-video-from-codebase/tools/rules/process-spawn.mjs +49 -0
- package/skills/explainer-video-from-codebase/tools/rules/py-http-client.mjs +26 -0
- package/skills/explainer-video-from-codebase/tools/rules/pytest.mjs +107 -0
- package/skills/explainer-video-from-codebase/tools/rules/react-router.mjs +17 -0
- package/skills/explainer-video-from-codebase/tools/rules/registry.mjs +31 -0
- package/skills/explainer-video-from-codebase/tools/trace/c/fmtrace-sh +14 -0
- package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-net.cjs +33 -0
- package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-srcmap.cjs +41 -0
- package/skills/explainer-video-from-codebase/tools/trace/python/fmtrace_plugin.py +87 -0
- package/skills/explainer-video-from-codebase/tools/trace/python/sitecustomize.py +330 -0
- package/skills/explainer-video-from-codebase/tools/trace/sandbox/linux-bwrap.sh +73 -0
- package/skills/explainer-video-from-codebase/tools/trace/sandbox/macos-nonet.sb +41 -0
- package/skills/explainer-video-from-codebase/tools/trace.mjs +577 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-c.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-cpp.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-go.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-javascript.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-python.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-typescript.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/web-tree-sitter.txt +21 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/MANIFEST.json +172 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-c.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-cpp.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-go.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.js +4081 -0
- package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.wasm +0 -0
- package/skills/explainer-video-from-coursework/ARCHITECTURE.md +59 -0
- package/skills/explainer-video-from-coursework/PIPELINE.md +343 -0
- package/skills/explainer-video-from-coursework/ROADMAP.md +725 -0
- package/skills/explainer-video-from-coursework/SERIES.md +232 -0
- package/skills/explainer-video-from-coursework/SETUP.md +105 -0
- package/skills/explainer-video-from-coursework/SKILL.md +858 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-chart.js +12 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-equation.js +10 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-hubspoke.js +7 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-loopflow.js +7 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-sequence.js +12 -0
- package/skills/explainer-video-from-coursework/scenes/_pt-timeline.js +11 -0
- package/skills/explainer-video-from-coursework/src/scene-code.html +127 -0
- package/skills/explainer-video-from-coursework/src/scene-code.js +379 -0
- package/skills/explainer-video-from-coursework/src/scene-core.js +1250 -0
- package/skills/explainer-video-from-coursework/src/scene-diagram.html +50 -0
- package/skills/explainer-video-from-coursework/src/scene-svg.html +50 -0
- package/skills/explainer-video-from-coursework/src/timeline.js +297 -0
- package/skills/explainer-video-from-coursework/src/timing-chart.js +160 -0
- package/skills/explainer-video-from-coursework/tools/align.mjs +419 -0
- package/skills/explainer-video-from-coursework/tools/apply-enrichment.mjs +206 -0
- package/skills/explainer-video-from-coursework/tools/assemble.mjs +223 -0
- package/skills/explainer-video-from-coursework/tools/build-audio.mjs +234 -0
- package/skills/explainer-video-from-coursework/tools/chromium.mjs +73 -0
- package/skills/explainer-video-from-coursework/tools/debug-page.mjs +75 -0
- package/skills/explainer-video-from-coursework/tools/env.mjs +30 -0
- package/skills/explainer-video-from-coursework/tools/frame-dedup.mjs +131 -0
- package/skills/explainer-video-from-coursework/tools/key-source.mjs +151 -0
- package/skills/explainer-video-from-coursework/tools/model.mjs +21 -0
- package/skills/explainer-video-from-coursework/tools/naming.mjs +38 -0
- package/skills/explainer-video-from-coursework/tools/onset-audit.mjs +51 -0
- package/skills/explainer-video-from-coursework/tools/pacing-check.mjs +350 -0
- package/skills/explainer-video-from-coursework/tools/planner.mjs +616 -0
- package/skills/explainer-video-from-coursework/tools/publish.mjs +230 -0
- package/skills/explainer-video-from-coursework/tools/render.mjs +205 -0
- package/skills/explainer-video-from-coursework/tools/resolve-beats.mjs +239 -0
- package/skills/explainer-video-from-coursework/tools/script-check.mjs +174 -0
- package/skills/explainer-video-from-coursework/tools/stamp.mjs +71 -0
- package/skills/explainer-video-from-coursework/tools/timing-report.mjs +299 -0
- package/skills/explainer-video-from-coursework/tools/transcribe-gemini.mjs +96 -0
- package/skills/explainer-video-from-coursework/tools/tts-gemini.mjs +394 -0
- package/skills/explainer-video-from-coursework/tools/verify-audio.mjs +88 -0
- package/skills/explainer-video-from-coursework/tools/workdir.mjs +124 -0
- package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js +8 -0
- package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js.LICENSE.txt +16 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Diagram scene host</title>
|
|
6
|
+
<style>
|
|
7
|
+
html, body {
|
|
8
|
+
margin: 0; padding: 0; width: 1920px; height: 1080px;
|
|
9
|
+
overflow: hidden; background: #080e1a;
|
|
10
|
+
}
|
|
11
|
+
svg#stage { display: block; width: 1920px; height: 1080px; }
|
|
12
|
+
text { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<!--
|
|
17
|
+
Host for the DIAGRAM renderer. The scene config supplies the content and the
|
|
18
|
+
beat timings (relative to a narration segment); src/timeline.js supplies the
|
|
19
|
+
MEASURED segment windows produced by the audio stage.
|
|
20
|
+
-->
|
|
21
|
+
<svg id="stage" xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"></svg>
|
|
22
|
+
|
|
23
|
+
<script src="scene-core.js"></script>
|
|
24
|
+
<script>
|
|
25
|
+
// ?scene=<name> names the config; ?work=<dir> scopes it to a video's own dir so
|
|
26
|
+
// multiple videos don't share a timeline or scene namespace (ROADMAP §1, "Work-dir layout").
|
|
27
|
+
var Q = new URLSearchParams(location.search);
|
|
28
|
+
var NAME = Q.get('scene') || 'srp-split';
|
|
29
|
+
var WORK = Q.get('work') || '';
|
|
30
|
+
var tlEl = document.createElement('script');
|
|
31
|
+
tlEl.src = WORK ? WORK + '/timeline.js' : 'timeline.js';
|
|
32
|
+
tlEl.onload = function () {
|
|
33
|
+
var s = document.createElement('script');
|
|
34
|
+
s.src = WORK ? WORK + '/scenes/' + NAME + '.js' : '../scenes/' + NAME + '.js';
|
|
35
|
+
s.onload = function () {
|
|
36
|
+
var TL = window.TIMELINE;
|
|
37
|
+
var scene = window.SceneCore.createScene({
|
|
38
|
+
svg: document.getElementById('stage'),
|
|
39
|
+
config: window.SCENE_CONFIG,
|
|
40
|
+
timeline: TL,
|
|
41
|
+
});
|
|
42
|
+
window.renderAt = function (t) { scene.renderAt(t); return true; };
|
|
43
|
+
window.VIDEO_META = { width: TL.width, height: TL.height, fps: TL.fps, total: TL.total };
|
|
44
|
+
};
|
|
45
|
+
document.head.appendChild(s);
|
|
46
|
+
};
|
|
47
|
+
document.head.appendChild(tlEl);
|
|
48
|
+
</script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>SVG scene host — the open lane</title>
|
|
6
|
+
<style>
|
|
7
|
+
html, body { margin:0; padding:0; width:1920px; height:1080px; overflow:hidden; background:#080e1a; }
|
|
8
|
+
svg#stage { display:block; width:1920px; height:1080px; }
|
|
9
|
+
/* must match the font textW() measures with, or primitive layout drifts */
|
|
10
|
+
text { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<!--
|
|
15
|
+
Open lane for stage 5. The author's scene script defines:
|
|
16
|
+
|
|
17
|
+
window.SCENE_CONFIG = { renderAt(t, svg, timeline) }
|
|
18
|
+
|
|
19
|
+
renderAt is a PURE function of time: it draws the frame for t and returns, so
|
|
20
|
+
frames can be rendered in any order and re-rendered after an audio change. The
|
|
21
|
+
author owns the SVG completely — no node kinds, no fixed vocabulary.
|
|
22
|
+
-->
|
|
23
|
+
<svg id="stage" xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"></svg>
|
|
24
|
+
|
|
25
|
+
<script src="scene-core.js"></script>
|
|
26
|
+
<script>
|
|
27
|
+
var Q = new URLSearchParams(location.search);
|
|
28
|
+
var NAME = Q.get('scene') || 'svg';
|
|
29
|
+
var WORK = Q.get('work') || '';
|
|
30
|
+
var tlEl = document.createElement('script');
|
|
31
|
+
tlEl.src = WORK ? WORK + '/timeline.js' : 'timeline.js';
|
|
32
|
+
tlEl.onload = function () {
|
|
33
|
+
var s = document.createElement('script');
|
|
34
|
+
s.src = WORK ? WORK + '/scenes/' + NAME + '.js' : '../scenes/' + NAME + '.js';
|
|
35
|
+
s.onload = function () {
|
|
36
|
+
var TL = window.TIMELINE;
|
|
37
|
+
var cfg = window.SCENE_CONFIG;
|
|
38
|
+
if (!cfg || typeof cfg.renderAt !== 'function') {
|
|
39
|
+
throw new Error(NAME + ': SCENE_CONFIG must expose renderAt(t, svg, timeline)');
|
|
40
|
+
}
|
|
41
|
+
var stage = document.getElementById('stage');
|
|
42
|
+
window.renderAt = function (t) { cfg.renderAt(t, stage, TL); return true; };
|
|
43
|
+
window.VIDEO_META = { width: TL.width, height: TL.height, fps: TL.fps, total: TL.total };
|
|
44
|
+
};
|
|
45
|
+
document.head.appendChild(s);
|
|
46
|
+
};
|
|
47
|
+
document.head.appendChild(tlEl);
|
|
48
|
+
</script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
window.TIMELINE = {
|
|
2
|
+
"fps": 30,
|
|
3
|
+
"width": 1920,
|
|
4
|
+
"height": 1080,
|
|
5
|
+
"total": 392.220317,
|
|
6
|
+
"scenes": {
|
|
7
|
+
"hook": {
|
|
8
|
+
"start": 0.6,
|
|
9
|
+
"end": 23.88,
|
|
10
|
+
"dur": 23.28
|
|
11
|
+
},
|
|
12
|
+
"why": {
|
|
13
|
+
"start": 24.63,
|
|
14
|
+
"end": 55.67,
|
|
15
|
+
"dur": 31.04
|
|
16
|
+
},
|
|
17
|
+
"why-analogy": {
|
|
18
|
+
"start": 56.42,
|
|
19
|
+
"end": 75.74,
|
|
20
|
+
"dur": 19.32
|
|
21
|
+
},
|
|
22
|
+
"l4-vs-l7": {
|
|
23
|
+
"start": 76.49,
|
|
24
|
+
"end": 114.01,
|
|
25
|
+
"dur": 37.52
|
|
26
|
+
},
|
|
27
|
+
"l4l7-analogy": {
|
|
28
|
+
"start": 114.76,
|
|
29
|
+
"end": 135.52,
|
|
30
|
+
"dur": 20.76
|
|
31
|
+
},
|
|
32
|
+
"round-robin": {
|
|
33
|
+
"start": 136.27,
|
|
34
|
+
"end": 163.91,
|
|
35
|
+
"dur": 27.64
|
|
36
|
+
},
|
|
37
|
+
"weighted-round-robin": {
|
|
38
|
+
"start": 164.66,
|
|
39
|
+
"end": 183.38,
|
|
40
|
+
"dur": 18.72
|
|
41
|
+
},
|
|
42
|
+
"least-connections": {
|
|
43
|
+
"start": 184.13,
|
|
44
|
+
"end": 206.25,
|
|
45
|
+
"dur": 22.12
|
|
46
|
+
},
|
|
47
|
+
"ip-hash": {
|
|
48
|
+
"start": 207,
|
|
49
|
+
"end": 231.6,
|
|
50
|
+
"dur": 24.6
|
|
51
|
+
},
|
|
52
|
+
"consistent-hashing": {
|
|
53
|
+
"start": 232.35,
|
|
54
|
+
"end": 262.43,
|
|
55
|
+
"dur": 30.08
|
|
56
|
+
},
|
|
57
|
+
"least-response-time": {
|
|
58
|
+
"start": 263.18,
|
|
59
|
+
"end": 284.94,
|
|
60
|
+
"dur": 21.76
|
|
61
|
+
},
|
|
62
|
+
"health-checks": {
|
|
63
|
+
"start": 285.69,
|
|
64
|
+
"end": 313.57,
|
|
65
|
+
"dur": 27.88
|
|
66
|
+
},
|
|
67
|
+
"graceful-draining": {
|
|
68
|
+
"start": 314.32,
|
|
69
|
+
"end": 337.96,
|
|
70
|
+
"dur": 23.64
|
|
71
|
+
},
|
|
72
|
+
"code-demos": {
|
|
73
|
+
"start": 338.71,
|
|
74
|
+
"end": 366.55,
|
|
75
|
+
"dur": 27.84
|
|
76
|
+
},
|
|
77
|
+
"outro": {
|
|
78
|
+
"start": 367.3,
|
|
79
|
+
"end": 390.62,
|
|
80
|
+
"dur": 23.32
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"windows": {
|
|
84
|
+
"hook": {
|
|
85
|
+
"start": 0,
|
|
86
|
+
"end": 24.03,
|
|
87
|
+
"dur": 24.03
|
|
88
|
+
},
|
|
89
|
+
"why": {
|
|
90
|
+
"start": 24.03,
|
|
91
|
+
"end": 55.82,
|
|
92
|
+
"dur": 31.79
|
|
93
|
+
},
|
|
94
|
+
"why-analogy": {
|
|
95
|
+
"start": 55.82,
|
|
96
|
+
"end": 75.89,
|
|
97
|
+
"dur": 20.07
|
|
98
|
+
},
|
|
99
|
+
"l4-vs-l7": {
|
|
100
|
+
"start": 75.89,
|
|
101
|
+
"end": 114.16,
|
|
102
|
+
"dur": 38.27
|
|
103
|
+
},
|
|
104
|
+
"l4l7-analogy": {
|
|
105
|
+
"start": 114.16,
|
|
106
|
+
"end": 135.67,
|
|
107
|
+
"dur": 21.51
|
|
108
|
+
},
|
|
109
|
+
"round-robin": {
|
|
110
|
+
"start": 135.67,
|
|
111
|
+
"end": 164.06,
|
|
112
|
+
"dur": 28.39
|
|
113
|
+
},
|
|
114
|
+
"weighted-round-robin": {
|
|
115
|
+
"start": 164.06,
|
|
116
|
+
"end": 183.53,
|
|
117
|
+
"dur": 19.47
|
|
118
|
+
},
|
|
119
|
+
"least-connections": {
|
|
120
|
+
"start": 183.53,
|
|
121
|
+
"end": 206.4,
|
|
122
|
+
"dur": 22.87
|
|
123
|
+
},
|
|
124
|
+
"ip-hash": {
|
|
125
|
+
"start": 206.4,
|
|
126
|
+
"end": 231.75,
|
|
127
|
+
"dur": 25.35
|
|
128
|
+
},
|
|
129
|
+
"consistent-hashing": {
|
|
130
|
+
"start": 231.75,
|
|
131
|
+
"end": 262.58,
|
|
132
|
+
"dur": 30.83
|
|
133
|
+
},
|
|
134
|
+
"least-response-time": {
|
|
135
|
+
"start": 262.58,
|
|
136
|
+
"end": 285.09,
|
|
137
|
+
"dur": 22.51
|
|
138
|
+
},
|
|
139
|
+
"health-checks": {
|
|
140
|
+
"start": 285.09,
|
|
141
|
+
"end": 313.72,
|
|
142
|
+
"dur": 28.63
|
|
143
|
+
},
|
|
144
|
+
"graceful-draining": {
|
|
145
|
+
"start": 313.72,
|
|
146
|
+
"end": 338.11,
|
|
147
|
+
"dur": 24.39
|
|
148
|
+
},
|
|
149
|
+
"code-demos": {
|
|
150
|
+
"start": 338.11,
|
|
151
|
+
"end": 366.7,
|
|
152
|
+
"dur": 28.59
|
|
153
|
+
},
|
|
154
|
+
"outro": {
|
|
155
|
+
"start": 366.7,
|
|
156
|
+
"end": 392.22,
|
|
157
|
+
"dur": 25.52
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"segments": [
|
|
161
|
+
{
|
|
162
|
+
"key": "hook",
|
|
163
|
+
"start": 0.6,
|
|
164
|
+
"end": 23.88,
|
|
165
|
+
"duration": 23.28,
|
|
166
|
+
"pad": 0,
|
|
167
|
+
"words": 65,
|
|
168
|
+
"cached": true
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"key": "why",
|
|
172
|
+
"start": 24.63,
|
|
173
|
+
"end": 55.67,
|
|
174
|
+
"duration": 31.04,
|
|
175
|
+
"pad": 0,
|
|
176
|
+
"words": 70,
|
|
177
|
+
"cached": true
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"key": "why-analogy",
|
|
181
|
+
"start": 56.42,
|
|
182
|
+
"end": 75.74,
|
|
183
|
+
"duration": 19.32,
|
|
184
|
+
"pad": 0,
|
|
185
|
+
"words": 58,
|
|
186
|
+
"cached": true
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"key": "l4-vs-l7",
|
|
190
|
+
"start": 76.49,
|
|
191
|
+
"end": 114.01,
|
|
192
|
+
"duration": 37.52,
|
|
193
|
+
"pad": 0,
|
|
194
|
+
"words": 86,
|
|
195
|
+
"cached": true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"key": "l4l7-analogy",
|
|
199
|
+
"start": 114.76,
|
|
200
|
+
"end": 135.52,
|
|
201
|
+
"duration": 20.76,
|
|
202
|
+
"pad": 0,
|
|
203
|
+
"words": 55,
|
|
204
|
+
"cached": true
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"key": "round-robin",
|
|
208
|
+
"start": 136.27,
|
|
209
|
+
"end": 163.91,
|
|
210
|
+
"duration": 27.64,
|
|
211
|
+
"pad": 0,
|
|
212
|
+
"words": 66,
|
|
213
|
+
"cached": true
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"key": "weighted-round-robin",
|
|
217
|
+
"start": 164.66,
|
|
218
|
+
"end": 183.38,
|
|
219
|
+
"duration": 18.72,
|
|
220
|
+
"pad": 0,
|
|
221
|
+
"words": 49,
|
|
222
|
+
"cached": true
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"key": "least-connections",
|
|
226
|
+
"start": 184.13,
|
|
227
|
+
"end": 206.25,
|
|
228
|
+
"duration": 22.12,
|
|
229
|
+
"pad": 0,
|
|
230
|
+
"words": 52,
|
|
231
|
+
"cached": true
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"key": "ip-hash",
|
|
235
|
+
"start": 207,
|
|
236
|
+
"end": 231.6,
|
|
237
|
+
"duration": 24.6,
|
|
238
|
+
"pad": 0,
|
|
239
|
+
"words": 64,
|
|
240
|
+
"cached": true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"key": "consistent-hashing",
|
|
244
|
+
"start": 232.35,
|
|
245
|
+
"end": 262.43,
|
|
246
|
+
"duration": 30.08,
|
|
247
|
+
"pad": 0,
|
|
248
|
+
"words": 76,
|
|
249
|
+
"cached": true
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"key": "least-response-time",
|
|
253
|
+
"start": 263.18,
|
|
254
|
+
"end": 284.94,
|
|
255
|
+
"duration": 21.76,
|
|
256
|
+
"pad": 0,
|
|
257
|
+
"words": 49,
|
|
258
|
+
"cached": true
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"key": "health-checks",
|
|
262
|
+
"start": 285.69,
|
|
263
|
+
"end": 313.57,
|
|
264
|
+
"duration": 27.88,
|
|
265
|
+
"pad": 0,
|
|
266
|
+
"words": 67,
|
|
267
|
+
"cached": true
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"key": "graceful-draining",
|
|
271
|
+
"start": 314.32,
|
|
272
|
+
"end": 337.96,
|
|
273
|
+
"duration": 23.64,
|
|
274
|
+
"pad": 0,
|
|
275
|
+
"words": 60,
|
|
276
|
+
"cached": true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"key": "code-demos",
|
|
280
|
+
"start": 338.71,
|
|
281
|
+
"end": 366.55,
|
|
282
|
+
"duration": 27.84,
|
|
283
|
+
"pad": 0,
|
|
284
|
+
"words": 78,
|
|
285
|
+
"cached": true
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"key": "outro",
|
|
289
|
+
"start": 367.3,
|
|
290
|
+
"end": 390.62,
|
|
291
|
+
"duration": 23.32,
|
|
292
|
+
"pad": 0,
|
|
293
|
+
"words": 68,
|
|
294
|
+
"cached": true
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
timing-chart.js — browser side of the timing audit.
|
|
3
|
+
|
|
4
|
+
`tools/timing-report.mjs` sets window.TIMING_DATA and inlines this file (plus
|
|
5
|
+
Plotly) into timing.html, so the page is self-contained and opens with no
|
|
6
|
+
network. Shape of the data:
|
|
7
|
+
|
|
8
|
+
{ mode: 'flat' | 'grouped',
|
|
9
|
+
stages: [['3', 'script'], …],
|
|
10
|
+
modules: [{ name, display, videos: [{ index, title, stages: { '<stage>': secs } }] }] }
|
|
11
|
+
|
|
12
|
+
mode 'flat' — every video on one stacked-bar chart. Used when the series
|
|
13
|
+
spans a single module, where grouping would be a pointless
|
|
14
|
+
extra click.
|
|
15
|
+
mode 'grouped' — one aggregate bar per module (summed across its videos), click
|
|
16
|
+
a bar to break that module down by video. Used as soon as a
|
|
17
|
+
series spans more than one module, which is the case that made
|
|
18
|
+
a single 85-row chart unusable.
|
|
19
|
+
|
|
20
|
+
Layout notes, all learned the hard way:
|
|
21
|
+
· the <h1>/<h2> titles are HTML, not Plotly titles — a Plotly title anchors to
|
|
22
|
+
the container while the legend anchors to the plot, so on short charts they
|
|
23
|
+
collide;
|
|
24
|
+
· the legend is placed with yanchor:'bottom' at a pixel offset converted into
|
|
25
|
+
plot fractions, after reserving room for half a tick label;
|
|
26
|
+
· rows are 34px so a two-line wrapped label does not collide with its
|
|
27
|
+
neighbour, and wrapping is capped at two lines;
|
|
28
|
+
· labels are pushed off the axis with `ticks:'outside'` + `ticklen`, since
|
|
29
|
+
cartesian axes have no tick-label padding property.
|
|
30
|
+
========================================================================== */
|
|
31
|
+
(function () {
|
|
32
|
+
const D = window.TIMING_DATA;
|
|
33
|
+
if (!D || !window.Plotly) return;
|
|
34
|
+
|
|
35
|
+
const STAGES = D.stages || [];
|
|
36
|
+
const COLORS = ['#4f46e5', '#0ea5e9', '#14b8a6', '#f59e0b', '#ef4444', '#8b5cf6',
|
|
37
|
+
'#ec4899', '#10b981', '#f97316', '#6366f1', '#a855f7', '#22c55e'];
|
|
38
|
+
const stageName = {}, colorOf = {};
|
|
39
|
+
STAGES.forEach(([k, n], i) => { stageName[k] = n; colorOf[k] = COLORS[i % COLORS.length]; });
|
|
40
|
+
const label = (k) => k + ' · ' + (stageName[k] || '');
|
|
41
|
+
|
|
42
|
+
const escHtml = (s) => String(s ?? '').replace(/&/g, '&')
|
|
43
|
+
.replace(/</g, '<').replace(/>/g, '>');
|
|
44
|
+
const rev = (a) => a.slice().reverse();
|
|
45
|
+
const round1 = (n) => Math.round(n * 10) / 10;
|
|
46
|
+
|
|
47
|
+
function wrap(t, max) {
|
|
48
|
+
const words = escHtml(t).split(/\s+/);
|
|
49
|
+
const lines = []; let cur = '';
|
|
50
|
+
for (const w of words) {
|
|
51
|
+
if (!cur) cur = w;
|
|
52
|
+
else if ((cur + ' ' + w).length <= max) cur += ' ' + w;
|
|
53
|
+
else { lines.push(cur); cur = w; }
|
|
54
|
+
}
|
|
55
|
+
if (cur) lines.push(cur);
|
|
56
|
+
if (lines.length > 2) { // cap at two lines so rows stay predictable
|
|
57
|
+
const rest = lines.slice(1).join(' ');
|
|
58
|
+
lines.length = 1;
|
|
59
|
+
lines.push(rest.length > max ? rest.slice(0, max - 1) + '…' : rest);
|
|
60
|
+
}
|
|
61
|
+
return lines.join('<br>');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tracesFor(videos) {
|
|
65
|
+
return STAGES.map(([k]) => ({
|
|
66
|
+
type: 'bar', orientation: 'h', name: label(k),
|
|
67
|
+
y: videos.map((v) => v.title),
|
|
68
|
+
x: videos.map((v) => round1(v.stages[k] || 0)),
|
|
69
|
+
marker: { color: colorOf[k] },
|
|
70
|
+
hovertemplate: '%{y}<br>' + label(k) + ': %{x:.1f}s<extra></extra>',
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function aggTraces(rows) {
|
|
75
|
+
return STAGES.map(([k]) => ({
|
|
76
|
+
type: 'bar', orientation: 'h', name: label(k),
|
|
77
|
+
y: rows.map((r) => r.title),
|
|
78
|
+
x: rows.map((r) => round1(r.stages[k] || 0)),
|
|
79
|
+
customdata: rows.map((r) => r.count),
|
|
80
|
+
marker: { color: colorOf[k] },
|
|
81
|
+
hovertemplate: '%{y}<br>' + label(k) + ': %{x:.1f}s<br>%{customdata} video(s)<extra></extra>',
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function layoutFor(rows, wrapMax) {
|
|
86
|
+
const legendH = 22, labelHalf = 8, gap = 12;
|
|
87
|
+
const topPad = labelHalf + gap + legendH;
|
|
88
|
+
const b = 40, rowH = 34;
|
|
89
|
+
const h = Math.max(200, rows.length * rowH + topPad + b);
|
|
90
|
+
const plotH = Math.max(48, h - topPad - b);
|
|
91
|
+
return {
|
|
92
|
+
barmode: 'stack', hovermode: 'closest',
|
|
93
|
+
xaxis: { title: { text: 'seconds', font: { size: 11 } }, gridcolor: '#eceff3', zeroline: true },
|
|
94
|
+
yaxis: {
|
|
95
|
+
automargin: true, tickmode: 'array',
|
|
96
|
+
ticks: 'outside', ticklen: 16, tickcolor: '#cbd5e1',
|
|
97
|
+
tickvals: rows.map((r) => r.title),
|
|
98
|
+
ticktext: rows.map((r) => wrap(r.title, wrapMax || 42)),
|
|
99
|
+
tickfont: { size: 11 },
|
|
100
|
+
},
|
|
101
|
+
height: h,
|
|
102
|
+
margin: { l: 0, r: 24, t: topPad, b },
|
|
103
|
+
showlegend: true,
|
|
104
|
+
legend: {
|
|
105
|
+
orientation: 'h', yanchor: 'bottom',
|
|
106
|
+
y: 1 + (labelHalf + gap) / plotH,
|
|
107
|
+
x: 0, xanchor: 'left', traceorder: 'normal', font: { size: 11 },
|
|
108
|
+
},
|
|
109
|
+
hoverlabel: { namelength: -1 },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const CONFIG = { displaylogo: false, responsive: true };
|
|
114
|
+
|
|
115
|
+
function renderFlat() {
|
|
116
|
+
const all = [];
|
|
117
|
+
for (const m of (D.modules || [])) for (const v of (m.videos || [])) all.push(v);
|
|
118
|
+
all.sort((a, b) => a.index - b.index);
|
|
119
|
+
const vs = rev(all); // plan order reads top-down
|
|
120
|
+
Plotly.newPlot('timing-chart', tracesFor(vs), layoutFor(vs, 42), CONFIG);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderGrouped() {
|
|
124
|
+
const rows = rev((D.modules || []).map((m) => {
|
|
125
|
+
const stages = {};
|
|
126
|
+
for (const [k] of STAGES) stages[k] = m.videos.reduce((a, v) => a + (v.stages[k] || 0), 0);
|
|
127
|
+
return { title: m.display, stages, count: m.videos.length, videos: m.videos };
|
|
128
|
+
}));
|
|
129
|
+
const el = document.getElementById('timing-chart');
|
|
130
|
+
Plotly.newPlot(el, aggTraces(rows), layoutFor(rows, 30), CONFIG);
|
|
131
|
+
el.on('plotly_click', (e) => {
|
|
132
|
+
const name = e.points[0].y;
|
|
133
|
+
const m = (D.modules || []).find((x) => x.display === name);
|
|
134
|
+
if (m) showDetail(m);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function showDetail(m) {
|
|
139
|
+
const wrapEl = document.getElementById('timing-detail-wrap');
|
|
140
|
+
if (!wrapEl) return;
|
|
141
|
+
document.getElementById('timing-detail-title').textContent =
|
|
142
|
+
m.display + ' — ' + m.videos.length + ' video' + (m.videos.length === 1 ? '' : 's');
|
|
143
|
+
const vs = rev(m.videos.slice().sort((a, b) => a.index - b.index));
|
|
144
|
+
Plotly.react('timing-detail', tracesFor(vs), layoutFor(vs, 46), CONFIG);
|
|
145
|
+
wrapEl.hidden = false;
|
|
146
|
+
wrapEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const reset = document.getElementById('all-modules');
|
|
150
|
+
if (reset) {
|
|
151
|
+
reset.onclick = (e) => {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
const w = document.getElementById('timing-detail-wrap');
|
|
154
|
+
if (w) w.hidden = true;
|
|
155
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (D.mode === 'grouped') renderGrouped(); else renderFlat();
|
|
160
|
+
})();
|