create-slide-deck 0.1.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.
Files changed (105) hide show
  1. package/dist/index.js +119 -0
  2. package/package.json +36 -0
  3. package/template-full/README.md +99 -0
  4. package/template-full/package.json +47 -0
  5. package/template-full/src/reveal/components/auto-layout.ts +229 -0
  6. package/template-full/src/reveal/components/charts.tsx +213 -0
  7. package/template-full/src/reveal/core/blocks.ts +172 -0
  8. package/template-full/src/reveal/core/deck-init.ts +60 -0
  9. package/template-full/src/reveal/core/design.ts +46 -0
  10. package/template-full/src/reveal/core/layout.ts +187 -0
  11. package/template-full/src/reveal/core/mount-registry.ts +41 -0
  12. package/template-full/src/reveal/core/presets.ts +189 -0
  13. package/template-full/src/reveal/core/runtime.ts +141 -0
  14. package/template-full/src/reveal/core/types.ts +114 -0
  15. package/template-full/src/reveal/data/algorithms.ts +78 -0
  16. package/template-full/src/reveal/data/benchmark.ts +79 -0
  17. package/template-full/src/reveal/decks/demo-showcase/components/demo-arc-progress.tsx +153 -0
  18. package/template-full/src/reveal/decks/demo-showcase/components/demo-before-after.tsx +164 -0
  19. package/template-full/src/reveal/decks/demo-showcase/components/demo-bigtext.tsx +70 -0
  20. package/template-full/src/reveal/decks/demo-showcase/components/demo-card-flip.tsx +118 -0
  21. package/template-full/src/reveal/decks/demo-showcase/components/demo-chat-bubbles.tsx +257 -0
  22. package/template-full/src/reveal/decks/demo-showcase/components/demo-code.tsx +136 -0
  23. package/template-full/src/reveal/decks/demo-showcase/components/demo-concept-map.tsx +336 -0
  24. package/template-full/src/reveal/decks/demo-showcase/components/demo-counter.tsx +194 -0
  25. package/template-full/src/reveal/decks/demo-showcase/components/demo-cover.tsx +188 -0
  26. package/template-full/src/reveal/decks/demo-showcase/components/demo-dark-dashboard.tsx +166 -0
  27. package/template-full/src/reveal/decks/demo-showcase/components/demo-eval-matrix.tsx +191 -0
  28. package/template-full/src/reveal/decks/demo-showcase/components/demo-force-graph.tsx +169 -0
  29. package/template-full/src/reveal/decks/demo-showcase/components/demo-fullbleed-bars.tsx +109 -0
  30. package/template-full/src/reveal/decks/demo-showcase/components/demo-fullbleed-flow.tsx +177 -0
  31. package/template-full/src/reveal/decks/demo-showcase/components/demo-heatmap.tsx +135 -0
  32. package/template-full/src/reveal/decks/demo-showcase/components/demo-icon-wall.tsx +143 -0
  33. package/template-full/src/reveal/decks/demo-showcase/components/demo-math.tsx +103 -0
  34. package/template-full/src/reveal/decks/demo-showcase/components/demo-number-morph.tsx +126 -0
  35. package/template-full/src/reveal/decks/demo-showcase/components/demo-path.tsx +185 -0
  36. package/template-full/src/reveal/decks/demo-showcase/components/demo-radar.tsx +124 -0
  37. package/template-full/src/reveal/decks/demo-showcase/components/demo-rough.tsx +169 -0
  38. package/template-full/src/reveal/decks/demo-showcase/components/demo-sankey.tsx +144 -0
  39. package/template-full/src/reveal/decks/demo-showcase/components/demo-screenshot-annotate.tsx +181 -0
  40. package/template-full/src/reveal/decks/demo-showcase/components/demo-stacked-cards.tsx +159 -0
  41. package/template-full/src/reveal/decks/demo-showcase/components/demo-tabs.tsx +206 -0
  42. package/template-full/src/reveal/decks/demo-showcase/components/demo-timeline.tsx +162 -0
  43. package/template-full/src/reveal/decks/demo-showcase/components/demo-treemap.tsx +161 -0
  44. package/template-full/src/reveal/decks/demo-showcase/components/demo-zoom-focus.tsx +223 -0
  45. package/template-full/src/reveal/decks/demo-showcase/components/registry.ts +63 -0
  46. package/template-full/src/reveal/decks/demo-showcase/demo.css +237 -0
  47. package/template-full/src/reveal/decks/demo-showcase/index.html +24 -0
  48. package/template-full/src/reveal/decks/demo-showcase/main.ts +7 -0
  49. package/template-full/src/reveal/decks/demo-showcase/slides.ts +271 -0
  50. package/template-full/src/reveal/decks/fse26-rca/components/aws-cascade.tsx +295 -0
  51. package/template-full/src/reveal/decks/fse26-rca/components/bench-compare.tsx +64 -0
  52. package/template-full/src/reveal/decks/fse26-rca/components/bench-deficiency.tsx +104 -0
  53. package/template-full/src/reveal/decks/fse26-rca/components/bench-loop.tsx +402 -0
  54. package/template-full/src/reveal/decks/fse26-rca/components/bench-needs.tsx +78 -0
  55. package/template-full/src/reveal/decks/fse26-rca/components/closing-takeaway.tsx +165 -0
  56. package/template-full/src/reveal/decks/fse26-rca/components/cloud-incidents.tsx +88 -0
  57. package/template-full/src/reveal/decks/fse26-rca/components/failure-modes.tsx +59 -0
  58. package/template-full/src/reveal/decks/fse26-rca/components/fault-heatmap.tsx +85 -0
  59. package/template-full/src/reveal/decks/fse26-rca/components/hierarchy-tree.tsx +93 -0
  60. package/template-full/src/reveal/decks/fse26-rca/components/incident-hard.tsx +72 -0
  61. package/template-full/src/reveal/decks/fse26-rca/components/rca-pipeline.tsx +193 -0
  62. package/template-full/src/reveal/decks/fse26-rca/components/registry.ts +37 -0
  63. package/template-full/src/reveal/decks/fse26-rca/components/simple-rca.tsx +216 -0
  64. package/template-full/src/reveal/decks/fse26-rca/components/sota-collapse.tsx +63 -0
  65. package/template-full/src/reveal/decks/fse26-rca/components/srca-results.tsx +115 -0
  66. package/template-full/src/reveal/decks/fse26-rca/images/aws-outage-2025-deployflow.png +0 -0
  67. package/template-full/src/reveal/decks/fse26-rca/images/aws-post-event-summary.png +0 -0
  68. package/template-full/src/reveal/decks/fse26-rca/images/bbc-crowdstrike.png +0 -0
  69. package/template-full/src/reveal/decks/fse26-rca/images/cnn-meta-outage-2021.png +0 -0
  70. package/template-full/src/reveal/decks/fse26-rca/images/cover.png +0 -0
  71. package/template-full/src/reveal/decks/fse26-rca/images/nyt-facebook-2021.png +0 -0
  72. package/template-full/src/reveal/decks/fse26-rca/images/qr-repo.png +0 -0
  73. package/template-full/src/reveal/decks/fse26-rca/images/verge-crowdstrike-2024.png +0 -0
  74. package/template-full/src/reveal/decks/fse26-rca/images/wiki-meta-outage-2021.png +0 -0
  75. package/template-full/src/reveal/decks/fse26-rca/index.html +30 -0
  76. package/template-full/src/reveal/decks/fse26-rca/main.ts +8 -0
  77. package/template-full/src/reveal/decks/fse26-rca/slides.ts +175 -0
  78. package/template-full/src/reveal/env.d.ts +38 -0
  79. package/template-full/src/reveal/theme.css +762 -0
  80. package/template-full/src/reveal/tools/dev.mjs +120 -0
  81. package/template-full/src/reveal/tools/export-pdf.mjs +86 -0
  82. package/template-full/src/reveal/tools/preview.mjs +132 -0
  83. package/template-full/tsconfig.json +19 -0
  84. package/template-full/vite.config.ts +95 -0
  85. package/template-minimal/package.json +42 -0
  86. package/template-minimal/src/reveal/components/auto-layout.ts +229 -0
  87. package/template-minimal/src/reveal/components/charts.tsx +213 -0
  88. package/template-minimal/src/reveal/core/blocks.ts +172 -0
  89. package/template-minimal/src/reveal/core/deck-init.ts +60 -0
  90. package/template-minimal/src/reveal/core/design.ts +46 -0
  91. package/template-minimal/src/reveal/core/layout.ts +187 -0
  92. package/template-minimal/src/reveal/core/mount-registry.ts +41 -0
  93. package/template-minimal/src/reveal/core/presets.ts +189 -0
  94. package/template-minimal/src/reveal/core/runtime.ts +141 -0
  95. package/template-minimal/src/reveal/core/types.ts +114 -0
  96. package/template-minimal/src/reveal/data/.gitkeep +0 -0
  97. package/template-minimal/src/reveal/decks/my-deck/components/example-component.tsx +28 -0
  98. package/template-minimal/src/reveal/decks/my-deck/components/registry.ts +9 -0
  99. package/template-minimal/src/reveal/decks/my-deck/index.html +14 -0
  100. package/template-minimal/src/reveal/decks/my-deck/main.ts +5 -0
  101. package/template-minimal/src/reveal/decks/my-deck/slides.ts +34 -0
  102. package/template-minimal/src/reveal/env.d.ts +38 -0
  103. package/template-minimal/src/reveal/theme.css +762 -0
  104. package/template-minimal/tsconfig.json +19 -0
  105. package/template-minimal/vite.config.ts +95 -0
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import type { SlideComponentProps } from "../../../core/types.ts";
3
+ import { CANVAS, SlideCanvas, vis } from "../../../core/presets.ts";
4
+ import { FAULT_TYPES as TYPES } from "../../../data/benchmark.ts";
5
+
6
+ const COL_W = 320;
7
+ const COL_GAP = 40;
8
+ const PAD_X = 50;
9
+ const CHART_H = 130;
10
+ const CHART_W = 240;
11
+ const SVG_W = 3 * COL_W + 2 * COL_GAP + 2 * PAD_X;
12
+ const SVG_H = 520;
13
+
14
+ function colX(i: number) { return PAD_X + i * (COL_W + COL_GAP); }
15
+
16
+ function MiniChart({ type, x, y }: { type: number; x: number; y: number }) {
17
+ const barW = 36;
18
+ const gap = 12;
19
+ let heights: number[];
20
+ let colors: string[];
21
+ let labels: { i: number; text: string; color: string }[] = [];
22
+
23
+ if (type === 0) {
24
+ heights = [18, 110, 15, 20, 16];
25
+ colors = ["#ddd", "#C00000", "#ddd", "#ddd", "#ddd"];
26
+ labels = [{ i: 1, text: "injected", color: "#C00000" }];
27
+ } else if (type === 1) {
28
+ heights = [14, 16, 13, 15, 14];
29
+ colors = ["#ddd", "#ddd", "#ddd", "#ddd", "#ddd"];
30
+ labels = [{ i: 2, text: "no signal", color: "#767676" }];
31
+ } else {
32
+ heights = [16, 42, 20, 100, 18];
33
+ colors = ["#ddd", "#E8912D", "#ddd", "#76B82A", "#ddd"];
34
+ labels = [
35
+ { i: 1, text: "root cause", color: "#E8912D" },
36
+ { i: 3, text: "loudest", color: "#76B82A" },
37
+ ];
38
+ }
39
+
40
+ const totalW = 5 * barW + 4 * gap;
41
+ const startX = x + (CHART_W - totalW) / 2;
42
+
43
+ return (
44
+ <g>
45
+ <rect x={x - 10} y={y - 10} width={CHART_W + 20} height={CHART_H + 20} rx="10" fill="#F8F8F8" stroke="#eee" strokeWidth="1" />
46
+ {heights.map(function (h, i) {
47
+ const bx = startX + i * (barW + gap);
48
+ const by = y + CHART_H - h;
49
+ return <rect key={i} x={bx} y={by} width={barW} height={h} rx="4" fill={colors[i]} />;
50
+ })}
51
+ {labels.map(function (l) {
52
+ const bx = startX + l.i * (barW + gap) + barW / 2;
53
+ const lh = heights[l.i];
54
+ return <text key={l.i} x={bx} y={y + CHART_H - lh - 8} textAnchor="middle" fontSize="14" fontWeight="800" fill={l.color} fontFamily={CANVAS.fontFamily}>{l.text}</text>;
55
+ })}
56
+ </g>
57
+ );
58
+ }
59
+
60
+ export default function BenchDeficiency({ Reveal }: SlideComponentProps) {
61
+ return (
62
+ <SlideCanvas Reveal={Reveal} W={SVG_W} H={SVG_H}>{(step, instant) => <>
63
+
64
+ {TYPES.map(function (t, i) {
65
+ const x = colX(i);
66
+ const chartX = x + (COL_W - CHART_W) / 2;
67
+
68
+ return (
69
+ <g key={i} style={vis(step, i, instant)}>
70
+ <MiniChart type={i} x={chartX} y={20} />
71
+
72
+ <text x={x + COL_W / 2} y={220} textAnchor="middle" fontSize="42" fontWeight="900" fill={t.color} fontFamily={CANVAS.fontFamily}>{t.pct}%</text>
73
+
74
+ <text x={x + COL_W / 2} y={250} textAnchor="middle" fontSize="14" fontWeight="800" fill={t.color} fontFamily={CANVAS.fontFamily}>{t.label}</text>
75
+
76
+ <text x={x + COL_W / 2} y={280} textAnchor="middle" fontSize="22" fontWeight="900" fill="#313131" fontFamily={CANVAS.fontFamily}>{t.title}</text>
77
+
78
+ <foreignObject x={x} y={294} width={COL_W} height={60}>
79
+ <div style={{ textAlign: "center", fontFamily: CANVAS.fontFamily }}>
80
+ <div style={{ fontSize: 16, fontWeight: 700, color: "#767676", lineHeight: 1.3 }}>{t.desc}</div>
81
+ <div style={{ fontSize: 14, fontWeight: 700, color: t.color, marginTop: 4 }}>{t.detail}</div>
82
+ </div>
83
+ </foreignObject>
84
+ </g>
85
+ );
86
+ })}
87
+
88
+ <g style={vis(step, 3, instant)}>
89
+ <text x={SVG_W / 2} y={SVG_H - 68} textAnchor="middle" fontSize="22" fontWeight="800" fill="#313131" fontFamily={CANVAS.fontFamily}>
90
+ <tspan fill="#C00000" fontWeight="900">68%</tspan>
91
+ <tspan fill="#767676"> trivially easy + </tspan>
92
+ <tspan fill="#767676" fontWeight="900">18%</tspan>
93
+ <tspan fill="#767676"> no signal = </tspan>
94
+ <tspan fill="#C00000" fontWeight="900" fontSize="28">86%</tspan>
95
+ <tspan> fail to test causal reasoning</tspan>
96
+ </text>
97
+ <text x={SVG_W / 2} y={SVG_H - 34} textAnchor="middle" fontSize="18" fontWeight="700" fill="#767676" fontFamily={CANVAS.fontFamily}>
98
+ <tspan fill="#C00000" fontWeight="900">99%</tspan>
99
+ <tspan> of cases lack complete observability data</tspan>
100
+ </text>
101
+ </g>
102
+ </>}</SlideCanvas>
103
+ );
104
+ }
@@ -0,0 +1,402 @@
1
+ import React from "react";
2
+ import { hierarchy, tree as d3tree } from "d3-hierarchy";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+ import { findLargestSlot } from "../../../components/auto-layout.ts";
5
+ import { NODE, CANVAS, SlideCanvas, vis } from "../../../core/presets.ts";
6
+
7
+ const NODES = [
8
+ { id: "system", label: "TrainTicket", color: "#1B556B", cx: 80, cy: 230 },
9
+ { id: "load", label: "Dynamic load", color: "#76B82A", cx: 250, cy: 60 },
10
+ { id: "fault", label: "Fault controller", color: "#C00000", cx: 430, cy: 60 },
11
+ { id: "telemetry", label: "Telemetry", color: "#00A6D6", cx: 430, cy: 400 },
12
+ { id: "validator", label: "SLI validator", color: "#76B82A", cx: 250, cy: 400 },
13
+ ];
14
+
15
+ const ICON_PATHS = {
16
+ system: "M2 20h20M4 20V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16M12 12h.01M8 12h.01M16 12h.01",
17
+ load: "M22 12h-4l-3 9L9 3l-3 9H2",
18
+ fault: "M13 2L3 14h9l-1 8 10-12h-9l1-8z",
19
+ telemetry: "M4 7V4a2 2 0 0 1 2-2h8.5L20 7.5V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3M8 12h8M8 16h8M8 8h2",
20
+ validator: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10zM9 12l2 2 4-4",
21
+ };
22
+
23
+ const LINKS = [
24
+ { from: 0, to: 1, nodeStep: 1 },
25
+ { from: 1, to: 2, nodeStep: 2 },
26
+ { from: 2, to: 3, nodeStep: 3 },
27
+ { from: 3, to: 4, nodeStep: 4 },
28
+ { from: 4, to: 0, nodeStep: 4 },
29
+ ];
30
+
31
+ const STEP_MAP = [
32
+ { node: 0 }, { node: 1 }, { node: 1 }, { node: 1 },
33
+ { node: 1 }, { node: 1 }, { node: 1 },
34
+ { node: 2 }, { node: 3 }, { node: 4 },
35
+ ];
36
+
37
+ const TITLES = [
38
+ "50 services with realistic dependencies",
39
+ "Booking Request", "3 required parameters",
40
+ "Seat & Insurance options", "Passenger selection methods",
41
+ "Recursive sub-workflows", "36 API combinations per booking flow",
42
+ "31 fault types across 7 categories",
43
+ "Metrics, logs, traces from all services",
44
+ "84.4% filtered — only impacting cases kept",
45
+ ];
46
+
47
+ const LOAD_W = 760, LOAD_H = 280;
48
+
49
+ const VBS = {
50
+ 0: "0 0 360 200",
51
+ 1: "0 0 " + LOAD_W + " " + LOAD_H,
52
+ 2: "0 0 280 110",
53
+ 3: "0 0 280 100",
54
+ 4: "0 0 280 110",
55
+ };
56
+
57
+ function nodeForStep(step: number) {
58
+ if (step < 0 || step >= STEP_MAP.length) return -1;
59
+ return STEP_MAP[step].node;
60
+ }
61
+
62
+ function maxStepForNode(nodeIdx: number) {
63
+ let max = -1;
64
+ for (let i = 0; i < STEP_MAP.length; i++) {
65
+ if (STEP_MAP[i].node <= nodeIdx && i > max) max = i;
66
+ }
67
+ return max;
68
+ }
69
+
70
+ function anim(delay: number, instant: boolean) {
71
+ if (instant) return { opacity: 1 };
72
+ return { opacity: 0, animation: "blFadeIn 0.4s ease " + delay + "s forwards" };
73
+ }
74
+
75
+ function SystemIllust({ instant }: { instant: boolean }) {
76
+ const nodes: [number, number, string, string | null][] = [
77
+ [60,50,"#1B556B","order"],[150,35,"#00A6D6","travel"],[240,55,"#76B82A","seat"],
78
+ [300,110,"#1B556B","pay"],[210,125,"#E8912D","route"],[105,118,"#00A6D6","auth"],
79
+ [45,148,"#76B82A",null],[165,170,"#C00000",null],
80
+ ];
81
+ const lines: [number, number, number, number][] = [
82
+ [60,50,150,35],[150,35,240,55],[240,55,300,110],[300,110,210,125],
83
+ [210,125,105,118],[105,118,60,50],[105,118,45,148],[210,125,165,170],
84
+ [150,35,210,125],[60,50,105,118],
85
+ ];
86
+ return (
87
+ <g>
88
+ {lines.map(function(l,i) {
89
+ return <line key={"l"+i} x1={l[0]} y1={l[1]} x2={l[2]} y2={l[3]} stroke="#ddd" strokeWidth="1.5" style={anim(0.1+i*0.05, instant)} />;
90
+ })}
91
+ {nodes.map(function(n,i) {
92
+ return (
93
+ <g key={"n"+i} style={anim(0.3+i*0.1, instant)}>
94
+ <circle cx={n[0]} cy={n[1]} r={14} fill="white" stroke={n[2]} strokeWidth="2.5" />
95
+ {n[3] && <text x={n[0]} y={n[1]-20} textAnchor="middle" fontSize="14" fontWeight="700" fill="#999">{n[3]}</text>}
96
+ </g>
97
+ );
98
+ })}
99
+ </g>
100
+ );
101
+ }
102
+
103
+ const LOAD_TREE = {
104
+ n: "Booking Request", step: 0, color: "#76B82A", style: "root",
105
+ children: [
106
+ { n: "Passenger", step: 1, color: "#00A6D6", style: "param", count: 6,
107
+ children: [
108
+ { n: "Query", step: 3, style: "leaf", tag: "1" },
109
+ { n: "Modify", step: 3, style: "leaf", tag: "1" },
110
+ { n: "Create New", step: 3, color: "#C00000", style: "highlight",
111
+ children: [
112
+ { n: "ID Number", step: 4, color: "#C00000", style: "sub",
113
+ children: [
114
+ { n: "Generate", step: 4, style: "leaf" },
115
+ { n: "Retrieve", step: 4, style: "leaf" },
116
+ ]},
117
+ { n: "Passenger Name", step: 4, color: "#C00000", style: "sub",
118
+ children: [
119
+ { n: "Generate", step: 4, style: "leaf" },
120
+ { n: "Retrieve", step: 4, style: "leaf" },
121
+ ]},
122
+ ]},
123
+ ]},
124
+ { n: "Seat Preference", step: 1, color: "#00A6D6", style: "param", count: 3,
125
+ children: [
126
+ { n: "Window", step: 2, style: "leaf" },
127
+ { n: "Aisle", step: 2, style: "leaf" },
128
+ { n: "Random", step: 2, style: "leaf" },
129
+ ]},
130
+ { n: "Insurance", step: 1, color: "#00A6D6", style: "param", count: 2,
131
+ children: [
132
+ { n: "Add", step: 2, style: "leaf" },
133
+ { n: "Skip", step: 2, style: "leaf" },
134
+ ]},
135
+ ],
136
+ };
137
+
138
+ const loadRoot = hierarchy(LOAD_TREE as any);
139
+ d3tree().size([LOAD_W - 120, LOAD_H - 70]).separation(function(a, b) {
140
+ return a.parent === b.parent ? 1.1 : 1.5;
141
+ })(loadRoot);
142
+ const loadNodes = loadRoot.descendants();
143
+ const loadLinks = loadRoot.links();
144
+
145
+ function LoadIllust({ subStep, instant }: { subStep: number; instant: boolean }) {
146
+ const gc = "#76B82A", rc = "#C00000";
147
+ const pad = 10;
148
+ return (
149
+ <g transform={"translate(" + (pad + 40) + "," + 14 + ")"}>
150
+ {loadLinks.map(function(link, i) {
151
+ const s = link.source.data, t = link.target.data;
152
+ const linkStep = t.step;
153
+ return (
154
+ <line key={"e" + i} x1={link.source.x} y1={link.source.y}
155
+ x2={link.target.x} y2={link.target.y}
156
+ stroke="#ccc" strokeWidth="1"
157
+ style={vis(subStep, linkStep, instant)} />
158
+ );
159
+ })}
160
+
161
+ {loadNodes.map(function(nd, i) {
162
+ const d = nd.data;
163
+ let w, h = 24, fs = 12, stroke = "#999", sw = 1, dash = "3 2", fw = 700, tc = "#666";
164
+ if (d.style === "root") { w = 140; h = 28; fs = 14; stroke = d.color; sw = 2; dash = "none"; fw = 800; tc = d.color; }
165
+ else if (d.style === "param") { w = 130; h = 26; fs = 13; stroke = d.color; sw = 1.5; dash = "none"; fw = 800; tc = d.color; }
166
+ else if (d.style === "highlight") { w = 92; h = 25; fs = 13; stroke = d.color; sw = 1.5; dash = "none"; fw = 800; tc = d.color; }
167
+ else if (d.style === "sub") { w = 104; stroke = d.color || "#999"; tc = d.color || "#666"; }
168
+ else { w = 74; }
169
+
170
+ return (
171
+ <g key={"n" + i} style={vis(subStep, d.step, instant)}>
172
+ <rect x={nd.x! - w/2} y={nd.y! - h/2} width={w} height={h} rx="4"
173
+ fill="white" stroke={stroke} strokeWidth={sw} strokeDasharray={dash} />
174
+ <text x={nd.x!} y={nd.y! + 5} textAnchor="middle" fontSize={fs}
175
+ fontWeight={fw} fill={tc} fontFamily={CANVAS.fontFamily}>{d.n}</text>
176
+ {d.tag && <text x={nd.x!} y={nd.y! + h/2 + 14} textAnchor="middle" fontSize="14" fontWeight="700" fill="#999">{d.tag}</text>}
177
+ {d.count && <text x={nd.x!} y={nd.y! + h/2 + 16} textAnchor="middle" fontSize="14" fontWeight="900" fill={gc}>{d.count}</text>}
178
+ </g>
179
+ );
180
+ })}
181
+
182
+ <g style={vis(subStep, 4, instant)}>
183
+ <text x={LOAD_W / 2} y={LOAD_H - 36} textAnchor="middle" fontSize="14" fontWeight="800" fill={rc} fontFamily={CANVAS.fontFamily}>Create New: 2 x 2 = 4 paths | Query: 1 | Modify: 1 | Total: 1 + 1 + 4 = 6</text>
184
+ </g>
185
+
186
+ <g style={vis(subStep, 5, instant)}>
187
+ <text x={LOAD_W / 2} y={LOAD_H - 10} textAnchor="middle" fontSize="14" fontWeight="900" fill="#313131" fontFamily={CANVAS.fontFamily}>6 x 3 x 2 = 36 combinations</text>
188
+ </g>
189
+ </g>
190
+ );
191
+ }
192
+
193
+ function FaultIllust({ instant }: { instant: boolean }) {
194
+ const cats: [number, number, string, string, number][] = [
195
+ [30,25,"CPU","#C00000",0.1],[90,25,"HTTP","#1B556B",0.25],[150,25,"DNS","#E8912D",0.4],
196
+ [30,65,"Network","#76B82A",0.55],[90,65,"Code","#7B61FF",0.7],[150,65,"Time","#00A6D6",0.85],
197
+ [210,45,"Disk","#999",1.0],
198
+ ];
199
+ return (
200
+ <g>
201
+ {cats.map(function(c, i) {
202
+ return (
203
+ <g key={i} style={anim(c[4], instant)}>
204
+ <rect x={c[0]-24} y={c[1]-12} width="48" height="24" rx="6" fill={c[3]} fillOpacity="0.12" stroke={c[3]} strokeWidth="1.5" />
205
+ <text x={c[0]} y={c[1]+4} textAnchor="middle" fontSize="10" fontWeight="800" fill={c[3]}>{c[2]}</text>
206
+ </g>
207
+ );
208
+ })}
209
+ </g>
210
+ );
211
+ }
212
+
213
+ function TelemetryIllust({ instant }: { instant: boolean }) {
214
+ return (
215
+ <g>
216
+ <g style={anim(0.1, instant)}>
217
+ <text x="38" y="8" textAnchor="middle" fontSize="10" fontWeight="700" fill="#00A6D6">metrics</text>
218
+ <line x1="6" y1="75" x2="70" y2="75" stroke="#ddd" strokeWidth="1" />
219
+ <line x1="6" y1="75" x2="6" y2="10" stroke="#ddd" strokeWidth="1" />
220
+ <polyline points="10,55 22,50 34,45 42,22 50,30 58,40 66,42" fill="none" stroke="#00A6D6" strokeWidth="2" />
221
+ </g>
222
+ <g style={anim(0.5, instant)}>
223
+ <text x="118" y="8" textAnchor="middle" fontSize="10" fontWeight="700" fill="#1B556B">logs</text>
224
+ <rect x="82" y="14" width="72" height="11" rx="2" fill="#F5F5F5" stroke="#ddd" strokeWidth="0.8" />
225
+ <text x="86" y="22" fontSize="8" fill="#999" fontWeight="600">INFO ok</text>
226
+ <rect x="82" y="28" width="72" height="11" rx="2" fill="#FFF3F3" stroke="#C00000" strokeWidth="1" />
227
+ <text x="86" y="36" fontSize="8" fill="#C00000" fontWeight="800">ERROR refused</text>
228
+ <rect x="82" y="42" width="72" height="11" rx="2" fill="#F5F5F5" stroke="#ddd" strokeWidth="0.8" />
229
+ <text x="86" y="50" fontSize="8" fill="#999" fontWeight="600">WARN slow</text>
230
+ </g>
231
+ <g style={anim(0.9, instant)}>
232
+ <text x="196" y="8" textAnchor="middle" fontSize="10" fontWeight="700" fill="#76B82A">traces</text>
233
+ <rect x="168" y="18" width="55" height="9" rx="2" fill="#00A6D6" fillOpacity="0.25" />
234
+ <rect x="178" y="32" width="40" height="9" rx="2" fill="#76B82A" fillOpacity="0.25" />
235
+ <rect x="188" y="46" width="30" height="9" rx="2" fill="#C00000" fillOpacity="0.25" />
236
+ </g>
237
+ </g>
238
+ );
239
+ }
240
+
241
+ function ValidatorIllust({ instant }: { instant: boolean }) {
242
+ return (
243
+ <g>
244
+ <g style={anim(0.1, instant)}>
245
+ <text x="30" y="14" fontSize="10" fontWeight="700" fill="#999">9,152 injections</text>
246
+ </g>
247
+ {[0,1,2,3,4,5,6,7,8,9].map(function(i) {
248
+ const x = 10 + (i % 5) * 22;
249
+ const y = 22 + Math.floor(i / 5) * 22;
250
+ const pass = i === 2 || i === 7;
251
+ return (
252
+ <g key={i} style={anim(0.3 + i * 0.08, instant)}>
253
+ <rect x={x} y={y} width="16" height="16" rx="3"
254
+ fill={pass ? "#76B82A" : "#eee"} fillOpacity={pass ? 0.3 : 1}
255
+ stroke={pass ? "#76B82A" : "#ddd"} strokeWidth={pass ? 2 : 1} />
256
+ {!pass && <line x1={x+3} y1={y+3} x2={x+13} y2={y+13} stroke="#ccc" strokeWidth="1.5" />}
257
+ {!pass && <line x1={x+13} y1={y+3} x2={x+3} y2={y+13} stroke="#ccc" strokeWidth="1.5" />}
258
+ </g>
259
+ );
260
+ })}
261
+ <g style={anim(1.2, instant)}>
262
+ <line x1="130" y1="20" x2="130" y2="60" stroke="#ddd" strokeWidth="1" />
263
+ <text x="140" y="30" fontSize="10" fontWeight="800" fill="#C00000">84.4% silent</text>
264
+ <text x="140" y="44" fontSize="10" fontWeight="700" fill="#999">no user impact</text>
265
+ </g>
266
+ <g style={anim(1.5, instant)}>
267
+ <line x1="130" y1="68" x2="180" y2="68" stroke="#76B82A" strokeWidth="1.5" />
268
+ <text x="140" y="82" fontSize="10" fontWeight="900" fill="#76B82A">1,430 cases</text>
269
+ <text x="140" y="94" fontSize="10" fontWeight="700" fill="#999">validated impact</text>
270
+ </g>
271
+ </g>
272
+ );
273
+ }
274
+
275
+ const R = 30;
276
+ const ICON_SZ = 22;
277
+ const SVG_W = 1100;
278
+ const SVG_H = 480;
279
+ const MAX_D_W = 740;
280
+ const MAX_D_H = 470;
281
+
282
+ function curvedPath(x1: number, y1: number, x2: number, y2: number) {
283
+ const mx = (x1 + x2) / 2;
284
+ const my = (y1 + y2) / 2;
285
+ const dx = x2 - x1;
286
+ const dy = y2 - y1;
287
+ const len = Math.sqrt(dx * dx + dy * dy);
288
+ const nx = -dy / len * 30;
289
+ const ny = dx / len * 30;
290
+ return "M" + x1 + "," + y1 + " Q" + (mx + nx) + "," + (my + ny) + " " + x2 + "," + y2;
291
+ }
292
+
293
+ function edgePoints(n1: typeof NODES[number], n2: typeof NODES[number]) {
294
+ const dx = n2.cx - n1.cx;
295
+ const dy = n2.cy - n1.cy;
296
+ const len = Math.sqrt(dx * dx + dy * dy);
297
+ const ux = dx / len;
298
+ const uy = dy / len;
299
+ return {
300
+ x1: n1.cx + ux * (R + 6),
301
+ y1: n1.cy + uy * (R + 6),
302
+ x2: n2.cx - ux * (R + 6),
303
+ y2: n2.cy - uy * (R + 6),
304
+ };
305
+ }
306
+
307
+ function LoopNode({ node, step, idx, instant }: { node: typeof NODES[number]; step: number; idx: number; instant: boolean }) {
308
+ const curNode = nodeForStep(step);
309
+ const isCurrent = curNode === idx;
310
+ const isVisible = step >= 0 && idx <= curNode;
311
+ const op = isVisible ? (isCurrent ? 1 : 0.5) : 0;
312
+ const tr = instant ? "none" : "opacity 0.4s ease";
313
+ const r = isCurrent ? R + 4 : R;
314
+
315
+ return (
316
+ <g style={{ opacity: op, transition: tr }}>
317
+ <circle cx={node.cx} cy={node.cy} r={r + 6} fill={node.color + "11"} />
318
+ <circle cx={node.cx} cy={node.cy} r={r} fill="white" stroke={node.color} strokeWidth={isCurrent ? 3.5 : 2.5} />
319
+ <g transform={"translate(" + (node.cx - ICON_SZ / 2) + "," + (node.cy - ICON_SZ / 2) + ")"}>
320
+ <svg width={ICON_SZ} height={ICON_SZ} viewBox="0 0 24 24" fill="none"
321
+ stroke={node.color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
322
+ <path d={(ICON_PATHS as Record<string, string>)[node.id]} />
323
+ </svg>
324
+ </g>
325
+ <text x={node.cx} y={node.cy + r + 18} textAnchor="middle" fontSize="16" fontWeight="900"
326
+ fill={NODE.labelColor} fontFamily={CANVAS.fontFamily}>{node.label}</text>
327
+ </g>
328
+ );
329
+ }
330
+
331
+ function renderIllust(step: number, instant: boolean) {
332
+ const curNode = nodeForStep(step);
333
+ if (curNode === 0) return <SystemIllust instant={instant} />;
334
+ if (curNode === 1) { const sub = step - 1; return <LoadIllust subStep={sub} instant={instant} />; }
335
+ if (curNode === 2) return <FaultIllust instant={instant} />;
336
+ if (curNode === 3) return <TelemetryIllust instant={instant} />;
337
+ if (curNode === 4) return <ValidatorIllust instant={instant} />;
338
+ return null;
339
+ }
340
+
341
+ export default function BenchLoop({ Reveal }: SlideComponentProps) {
342
+ return (
343
+ <SlideCanvas Reveal={Reveal} W={SVG_W} H={SVG_H}>{(step, instant) => {
344
+ const tr = instant ? "none" : "opacity 0.4s ease";
345
+ const curNode = nodeForStep(step);
346
+ const curVb = curNode >= 0 ? (VBS as Record<number, string>)[curNode] : "0 0 240 120";
347
+
348
+ const occ = [];
349
+ for (let ni = 0; ni <= Math.min(curNode, NODES.length - 1); ni++) {
350
+ const nd = NODES[ni];
351
+ occ.push({ x: nd.cx - R - 12, y: nd.cy - R - 20, w: 2 * (R + 12), h: 2 * R + 20 + 30 });
352
+ }
353
+ const slot = step >= 0 ? findLargestSlot({
354
+ canvasW: SVG_W, canvasH: SVG_H, occupied: occ,
355
+ maxW: MAX_D_W, maxH: MAX_D_H, margin: 14, step: 16,
356
+ }) : { x: 530, y: 30, w: 560, h: 420 };
357
+
358
+ return (<>
359
+ <defs>
360
+ <marker id="loop-arr" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
361
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#BFBFBF" />
362
+ </marker>
363
+ <style>{"\n@keyframes blFadeIn { from { opacity: 0; } to { opacity: 1; } }\n"}</style>
364
+ </defs>
365
+
366
+ {LINKS.map(function (link, i) {
367
+ const n1 = NODES[link.from], n2 = NODES[link.to];
368
+ const e = edgePoints(n1, n2);
369
+ const linkVisible = curNode >= link.nodeStep;
370
+ return <path key={i} d={curvedPath(e.x1, e.y1, e.x2, e.y2)}
371
+ fill="none" stroke="#BFBFBF" strokeWidth="2.5" strokeLinecap="round"
372
+ markerEnd="url(#loop-arr)" style={{ opacity: linkVisible ? 0.6 : 0, transition: tr }} />;
373
+ })}
374
+
375
+ {NODES.map(function (n, i) {
376
+ return <LoopNode key={n.id} node={n} step={step} idx={i} instant={instant} />;
377
+ })}
378
+
379
+ {step >= 0 && curNode >= 0 && (
380
+ <g key={"detail-" + curNode + "-" + step}>
381
+ <rect x={slot.x} y={slot.y} width={slot.w} height={slot.h} rx="12"
382
+ fill="white" fillOpacity="0.85" stroke="none" />
383
+ <foreignObject x={slot.x} y={slot.y} width={slot.w} height={slot.h}>
384
+ <div style={{
385
+ width: "100%", height: "100%", display: "flex", flexDirection: "column",
386
+ alignItems: "center", justifyContent: "center",
387
+ fontFamily: CANVAS.fontFamily, padding: "12px 20px", boxSizing: "border-box",
388
+ }}>
389
+ <svg style={{ flex: "1 1 auto", width: "100%", maxHeight: "82%" }} viewBox={curVb}>
390
+ {renderIllust(step, instant)}
391
+ </svg>
392
+ <div style={{ marginTop: 8, fontSize: 18, fontWeight: 800, color: "#313131", textAlign: "center", lineHeight: 1.3 }}>
393
+ {TITLES[step] || ""}
394
+ </div>
395
+ </div>
396
+ </foreignObject>
397
+ </g>
398
+ )}
399
+ </>);
400
+ }}</SlideCanvas>
401
+ );
402
+ }
@@ -0,0 +1,78 @@
1
+ import React from "react";
2
+ import type { SlideComponentProps } from "../../../core/types.ts";
3
+ import { CANVAS, MIN_FONT, SlideCanvas, vis } from "../../../core/presets.ts";
4
+
5
+ const W = 1200, H = 560;
6
+ const R = 46;
7
+ const CY = 280;
8
+
9
+ const C = { red: "#C00000", green: "#76B82A", label: "#313131", sub: "#767676" };
10
+
11
+ function IconNode({ cx, color, label, sub, children }: { cx: number; color: string; label: string; sub: string; children: React.ReactNode }) {
12
+ const iconScale = (R * 1.45) / 24;
13
+ const tx = cx - 12 * iconScale;
14
+ const ty = CY - 12 * iconScale;
15
+ return (
16
+ <g>
17
+ <circle cx={cx} cy={CY} r={R} fill={color} fillOpacity={0.08} stroke={color} strokeWidth={4} />
18
+ <g transform={"translate(" + tx + "," + ty + ") scale(" + iconScale + ")"} stroke={color} color={color}>
19
+ {children}
20
+ </g>
21
+ <text x={cx} y={CY + R + 38} textAnchor="middle" fontSize={24} fontWeight={900} fill={C.label} fontFamily={CANVAS.fontFamily}>
22
+ {label}
23
+ </text>
24
+ <text x={cx} y={CY + R + 64} textAnchor="middle" fontSize={MIN_FONT} fontWeight={600} fill={C.sub} fontFamily={CANVAS.fontFamily}>
25
+ {sub}
26
+ </text>
27
+ </g>
28
+ );
29
+ }
30
+
31
+ function Arrow({ fromX, toX, instant, step, req }: { fromX: number; toX: number; instant: boolean; step: number; req: number }) {
32
+ const gap = R + 14;
33
+ const x1 = fromX + gap;
34
+ const x2 = toX - gap;
35
+ return (
36
+ <g style={vis(step, req, instant)}>
37
+ <line x1={x1} y1={CY} x2={x2} y2={CY} stroke={C.green} strokeWidth={3} markerEnd="url(#bn-arrow-green)" />
38
+ </g>
39
+ );
40
+ }
41
+
42
+ export default function BenchNeeds({ Reveal }: SlideComponentProps) {
43
+ const x0 = W * 0.2, x1 = W * 0.5, x2 = W * 0.8;
44
+ return (
45
+ <SlideCanvas Reveal={Reveal} W={W} H={H}>{(step, instant) => <>
46
+ <defs>
47
+ <marker id="bn-arrow-green" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
48
+ <path d="M0,0 L10,5 L0,10 z" fill={C.green} />
49
+ </marker>
50
+ </defs>
51
+
52
+ <Arrow fromX={x0} toX={x1} instant={instant} step={step} req={1} />
53
+ <Arrow fromX={x1} toX={x2} instant={instant} step={step} req={2} />
54
+
55
+ <g style={vis(step, 0, instant)}>
56
+ <IconNode cx={x0} color={C.red} label="Known source" sub="where fault starts">
57
+ <path d="M8 2l1.88 1.88M14.12 3.88 16 2M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" fill="none" strokeWidth="2" strokeLinecap="round" />
58
+ <path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" fill="none" strokeWidth="2" />
59
+ <path d="M12 20v-9M6.53 9C4.6 8.8 3 7.1 3 5M6 13H2M3 21c0-2.1 1.7-3.9 3.8-4M20.97 5c0 2.1-1.6 3.8-3.5 4M22 13h-4M17.2 17c2.1.1 3.8 1.9 3.8 4" fill="none" strokeWidth="2" strokeLinecap="round" />
60
+ </IconNode>
61
+ </g>
62
+
63
+ <g style={vis(step, 1, instant)}>
64
+ <IconNode cx={x1} color={C.green} label="Propagation path" sub="path is preserved">
65
+ <circle cx="6" cy="19" r="2" fill="none" strokeWidth="2" />
66
+ <circle cx="18" cy="5" r="2" fill="none" strokeWidth="2" />
67
+ <path d="M6 17V7a5 5 0 0 1 5-5h1a5 5 0 0 1 5 5v10a5 5 0 0 0 5 5h1" fill="none" strokeWidth="2" strokeLinecap="round" />
68
+ </IconNode>
69
+ </g>
70
+
71
+ <g style={vis(step, 2, instant)}>
72
+ <IconNode cx={x2} color={C.green} label="User impact" sub="impact is validated">
73
+ <path d="m12 14 4-4M3.34 19a10 10 0 1 1 17.32 0" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
74
+ </IconNode>
75
+ </g>
76
+ </>}</SlideCanvas>
77
+ );
78
+ }