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,124 @@
1
+ import React from "react";
2
+ import { scaleLinear } from "d3-scale";
3
+ import { lineRadial, curveLinearClosed } from "d3-shape";
4
+ import type { SlideComponentProps } from "../../../core/types.ts";
5
+ import { CANVAS, SlideCanvas, vis } from "../../../core/presets.ts";
6
+
7
+ const W = 1200, H = 560;
8
+ const CX = W / 2, CY = H / 2 + 10;
9
+ const RADIUS = 210;
10
+
11
+ const AXES = ["Speed", "Accuracy", "Scalability", "Reliability", "Cost", "Usability"];
12
+ const A_VALUES = [85, 70, 90, 75, 60, 80];
13
+ const B_VALUES = [65, 90, 70, 85, 80, 75];
14
+
15
+ const COLOR_A = "#2E6FDB";
16
+ const COLOR_B = "#E8912D";
17
+
18
+ const rScale = scaleLinear().domain([0, 100]).range([0, RADIUS]);
19
+
20
+ // angle for axis i (start at top, clockwise)
21
+ function angle(i: number) {
22
+ return (Math.PI * 2 * i) / AXES.length - Math.PI / 2;
23
+ }
24
+
25
+ function point(i: number, value: number) {
26
+ const r = rScale(value);
27
+ const a = angle(i);
28
+ return [CX + r * Math.cos(a), CY + r * Math.sin(a)];
29
+ }
30
+
31
+ function polygonPath(values: number[]) {
32
+ const gen = lineRadial<number>()
33
+ .angle(function (d, i) { return angle(i) + Math.PI / 2; })
34
+ .radius(function (d) { return rScale(d); })
35
+ .curve(curveLinearClosed);
36
+ // lineRadial draws around origin; translate via transform on the path's group instead.
37
+ return gen(values) || undefined;
38
+ }
39
+
40
+ export default function DemoRadar({ Reveal }: SlideComponentProps) {
41
+ const rings = [25, 50, 75, 100];
42
+
43
+ return (
44
+ <SlideCanvas Reveal={Reveal} W={W} H={H}>{function (step, instant) {
45
+ return (
46
+ <g style={{ fontFamily: CANVAS.fontFamily }}>
47
+ {/* Step 0: grid + axes + labels */}
48
+ <g style={vis(step, 0, instant)}>
49
+ {rings.map(function (rv, i) {
50
+ const pts = AXES.map(function (_, ai) {
51
+ const p = point(ai, rv);
52
+ return p[0] + "," + p[1];
53
+ }).join(" ");
54
+ return (
55
+ <polygon key={"ring" + i} points={pts}
56
+ fill="none" stroke="#D8D8D8" strokeWidth="1" />
57
+ );
58
+ })}
59
+ {AXES.map(function (ax, i) {
60
+ const outer = point(i, 100);
61
+ const labelPt = point(i, 116);
62
+ const a = angle(i);
63
+ const anchor = Math.abs(Math.cos(a)) < 0.3 ? "middle"
64
+ : (Math.cos(a) > 0 ? "start" : "end");
65
+ return (
66
+ <g key={"ax" + i}>
67
+ <line x1={CX} y1={CY} x2={outer[0]} y2={outer[1]}
68
+ stroke="#C4C4C4" strokeWidth="1" />
69
+ <text x={labelPt[0]} y={labelPt[1]} dy="0.35em"
70
+ textAnchor={anchor} fontSize="16" fontWeight="700"
71
+ fill="#313131">
72
+ {ax}
73
+ </text>
74
+ </g>
75
+ );
76
+ })}
77
+ <text x={CX} y={CY - 4} textAnchor="middle" fontSize="14" fill="#9a9a9a">0</text>
78
+ <text x={CX} y={CY - rScale(100) - 4} textAnchor="middle" fontSize="14" fill="#9a9a9a">100</text>
79
+ </g>
80
+
81
+ {/* polygons: lineRadial centers at origin, so wrap in translate(CX,CY) */}
82
+ <g transform={"translate(" + CX + "," + CY + ")"}>
83
+ {/* Step 1: System A */}
84
+ <g style={vis(step, 1, instant)}>
85
+ <path d={polygonPath(A_VALUES)}
86
+ fill={COLOR_A} fillOpacity="0.25"
87
+ stroke={COLOR_A} strokeWidth="2.5" />
88
+ </g>
89
+ {/* Step 2: System B */}
90
+ <g style={vis(step, 2, instant)}>
91
+ <path d={polygonPath(B_VALUES)}
92
+ fill={COLOR_B} fillOpacity="0.25"
93
+ stroke={COLOR_B} strokeWidth="2.5" />
94
+ </g>
95
+ </g>
96
+
97
+ {/* data point dots (absolute coords) */}
98
+ <g style={vis(step, 1, instant)}>
99
+ {A_VALUES.map(function (v, i) {
100
+ const p = point(i, v);
101
+ return <circle key={"da" + i} cx={p[0]} cy={p[1]} r="4" fill={COLOR_A} />;
102
+ })}
103
+ </g>
104
+ <g style={vis(step, 2, instant)}>
105
+ {B_VALUES.map(function (v, i) {
106
+ const p = point(i, v);
107
+ return <circle key={"db" + i} cx={p[0]} cy={p[1]} r="4" fill={COLOR_B} />;
108
+ })}
109
+ </g>
110
+
111
+ {/* Legend top-right */}
112
+ <g style={vis(step, 1, instant)} transform={"translate(" + (W - 220) + ", 40)"}>
113
+ <rect x="0" y="0" width="14" height="14" rx="3" fill={COLOR_A} />
114
+ <text x="22" y="12" fontSize="15" fontWeight="700" fill="#313131">System A</text>
115
+ <g style={vis(step, 2, instant)}>
116
+ <rect x="0" y="26" width="14" height="14" rx="3" fill={COLOR_B} />
117
+ <text x="22" y="38" fontSize="15" fontWeight="700" fill="#313131">System B</text>
118
+ </g>
119
+ </g>
120
+ </g>
121
+ );
122
+ }}</SlideCanvas>
123
+ );
124
+ }
@@ -0,0 +1,169 @@
1
+ import React from "react";
2
+ import rough from "roughjs";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+ import { CANVAS, useRevealStep } from "../../../core/presets.ts";
5
+
6
+ const SVGNS = "http://www.w3.org/2000/svg";
7
+
8
+ const BOXES = [
9
+ { id: "frontend", label: "Frontend", x: 80, y: 200, w: 220, h: 140, fill: "#a8dadc" },
10
+ { id: "backend", label: "Backend", x: 490, y: 200, w: 220, h: 140, fill: "#f1faee" },
11
+ { id: "database", label: "Database", x: 900, y: 200, w: 220, h: 140, fill: "#a8dadc" },
12
+ ];
13
+
14
+ // Arrows connect box edges left-to-right.
15
+ const ARROWS = [
16
+ { from: 300, to: 490, y: 270 },
17
+ { from: 710, to: 900, y: 270 },
18
+ ];
19
+
20
+ function center(box: { x: number; y: number; w: number; h: number }) {
21
+ return { x: box.x + box.w / 2, y: box.y + box.h / 2 };
22
+ }
23
+
24
+ export default function DemoRough({ Reveal }: SlideComponentProps) {
25
+ const ref = useRevealStep(Reveal);
26
+ const step = ref[0], ready = ref[1], instant = ref[2];
27
+
28
+ const svgRef = React.useRef<SVGSVGElement>(null);
29
+ const boxLayer = React.useRef<SVGGElement>(null);
30
+ const arrowLayer = React.useRef<SVGGElement>(null);
31
+ const highlightLayer = React.useRef<SVGGElement>(null);
32
+
33
+ function clearLayer(layer: SVGGElement | null) {
34
+ if (!layer) return;
35
+ while (layer.firstChild) layer.removeChild(layer.firstChild);
36
+ }
37
+
38
+ // Step 0: draw the boxes in hand-drawn style.
39
+ React.useEffect(function () {
40
+ const svgEl = svgRef.current;
41
+ const layer = boxLayer.current;
42
+ if (!svgEl || !layer) return;
43
+ clearLayer(layer);
44
+ if (step < 0) return;
45
+ const rc = rough.svg(svgEl);
46
+ BOXES.forEach(function (b) {
47
+ const node = rc.rectangle(b.x, b.y, b.w, b.h, {
48
+ roughness: 2,
49
+ fill: b.fill,
50
+ fillStyle: "hachure",
51
+ fillWeight: 2,
52
+ stroke: "#457b9d",
53
+ strokeWidth: 2.5,
54
+ });
55
+ layer.appendChild(node);
56
+ });
57
+ }, [step >= 0]);
58
+
59
+ // Step 1: draw connecting arrows with animejs stagger reveal.
60
+ React.useEffect(function () {
61
+ const svgEl = svgRef.current;
62
+ const layer = arrowLayer.current;
63
+ if (!svgEl || !layer) return;
64
+ clearLayer(layer);
65
+ if (step < 1) return;
66
+ const rc = rough.svg(svgEl);
67
+ const groups: SVGGElement[] = [];
68
+ ARROWS.forEach(function (a) {
69
+ const g = document.createElementNS(SVGNS, "g");
70
+ const line = rc.line(a.from, a.y, a.to - 6, a.y, {
71
+ roughness: 1.8,
72
+ stroke: "#e63946",
73
+ strokeWidth: 3,
74
+ });
75
+ g.appendChild(line);
76
+ // Simple arrow head drawn with two rough lines.
77
+ const head1 = rc.line(a.to - 6, a.y, a.to - 22, a.y - 12, { roughness: 1.5, stroke: "#e63946", strokeWidth: 3 });
78
+ const head2 = rc.line(a.to - 6, a.y, a.to - 22, a.y + 12, { roughness: 1.5, stroke: "#e63946", strokeWidth: 3 });
79
+ g.appendChild(head1);
80
+ g.appendChild(head2);
81
+ layer.appendChild(g);
82
+ groups.push(g);
83
+ });
84
+ const anime = (window as any).anime;
85
+ if (!anime || instant) {
86
+ groups.forEach(function (g) { g.style.opacity = "1"; });
87
+ return;
88
+ }
89
+ groups.forEach(function (g) { g.style.opacity = "0"; });
90
+ anime.animate(groups, {
91
+ opacity: [0, 1],
92
+ translateX: [-20, 0],
93
+ duration: 600,
94
+ easing: "outCubic",
95
+ delay: anime.stagger(220),
96
+ });
97
+ }, [step >= 1, instant]);
98
+
99
+ // Step 3: rough circle highlight around the bottleneck (backend).
100
+ React.useEffect(function () {
101
+ const svgEl = svgRef.current;
102
+ const layer = highlightLayer.current;
103
+ if (!svgEl || !layer) return;
104
+ clearLayer(layer);
105
+ if (step < 3) return;
106
+ const rc = rough.svg(svgEl);
107
+ const c = center(BOXES[1]);
108
+ const circle = rc.circle(c.x, c.y, 300, {
109
+ roughness: 2.2,
110
+ stroke: "#e63946",
111
+ strokeWidth: 3.5,
112
+ fill: "rgba(230,57,70,0.05)",
113
+ fillStyle: "solid",
114
+ });
115
+ layer.appendChild(circle);
116
+ }, [step >= 3]);
117
+
118
+ return (
119
+ <div
120
+ style={{
121
+ width: "100%",
122
+ height: "100%",
123
+ display: "flex",
124
+ alignItems: "center",
125
+ justifyContent: "center",
126
+ fontFamily: CANVAS.fontFamily,
127
+ opacity: ready ? 1 : 0,
128
+ transition: "opacity 0.2s ease",
129
+ }}
130
+ >
131
+ <svg ref={svgRef} viewBox="0 0 1200 560" style={{ width: "100%", height: "100%", display: "block" }}>
132
+ <g ref={boxLayer} />
133
+ <g ref={arrowLayer} />
134
+ <g ref={highlightLayer} />
135
+
136
+ {/* Step 2: labels inside each box (plain SVG text over rough boxes). */}
137
+ <g style={{ opacity: step >= 2 ? 1 : 0, transition: instant ? "none" : "opacity 0.45s ease" }}>
138
+ {BOXES.map(function (b) {
139
+ const c = center(b);
140
+ return (
141
+ <text
142
+ key={b.id}
143
+ x={c.x}
144
+ y={c.y}
145
+ textAnchor="middle"
146
+ dominantBaseline="central"
147
+ style={{ fontSize: 26, fontWeight: 800, fill: "#1d3557" }}
148
+ >
149
+ {b.label}
150
+ </text>
151
+ );
152
+ })}
153
+ </g>
154
+
155
+ {/* Step 3: bottleneck annotation. */}
156
+ <g style={{ opacity: step >= 3 ? 1 : 0, transition: instant ? "none" : "opacity 0.45s ease" }}>
157
+ <text
158
+ x={center(BOXES[1]).x}
159
+ y={center(BOXES[1]).y - 175}
160
+ textAnchor="middle"
161
+ style={{ fontSize: 22, fontWeight: 900, fill: "#e63946" }}
162
+ >
163
+ bottleneck
164
+ </text>
165
+ </g>
166
+ </svg>
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,144 @@
1
+ import React from "react";
2
+ import { sankey, sankeyLinkHorizontal, sankeyJustify } from "d3-sankey";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+ import { CANVAS, SlideCanvas, vis } from "../../../core/presets.ts";
5
+
6
+ const W = 1200, H = 560;
7
+ const PAD = { top: 40, right: 150, bottom: 40, left: 150 };
8
+
9
+ // column index -> color
10
+ const COL_COLOR = ["#2E6FDB", "#3FA34D", "#E8912D"];
11
+
12
+ const NODES = [
13
+ { name: "User Requests", col: 0 },
14
+ { name: "System Events", col: 0 },
15
+ { name: "Scheduled Tasks", col: 0 },
16
+ { name: "Load Balancer", col: 1 },
17
+ { name: "Auth Check", col: 1 },
18
+ { name: "Business Logic", col: 1 },
19
+ { name: "Data Store", col: 1 },
20
+ { name: "Success Response", col: 2 },
21
+ { name: "Error Response", col: 2 },
22
+ ];
23
+
24
+ function idx(name: string) {
25
+ for (let i = 0; i < NODES.length; i++) if (NODES[i].name === name) return i;
26
+ return -1;
27
+ }
28
+
29
+ const LINKS: [string, string, number][] = [
30
+ ["User Requests", "Load Balancer", 50],
31
+ ["System Events", "Load Balancer", 20],
32
+ ["Scheduled Tasks", "Business Logic", 15],
33
+ ["Load Balancer", "Auth Check", 70],
34
+ ["Auth Check", "Business Logic", 55],
35
+ ["Auth Check", "Error Response", 15],
36
+ ["Business Logic", "Data Store", 50],
37
+ ["Business Logic", "Error Response", 20],
38
+ ["Data Store", "Success Response", 42],
39
+ ["Data Store", "Error Response", 8],
40
+ ];
41
+
42
+ // links that belong to the error flow path (highlighted red at step 2)
43
+ function isErrorLink(l: any) {
44
+ const tName = NODES[l.target.index].name;
45
+ return tName === "Error Response";
46
+ }
47
+
48
+ function computeSankey() {
49
+ const nodes = NODES.map(function (n) { return { name: n.name, col: n.col }; });
50
+ const links = LINKS.map(function (l) {
51
+ return { source: idx(l[0]), target: idx(l[1]), value: l[2] };
52
+ });
53
+ const gen = sankey()
54
+ .nodeWidth(22)
55
+ .nodePadding(26)
56
+ .nodeAlign(sankeyJustify)
57
+ .extent([[PAD.left, PAD.top], [W - PAD.right, H - PAD.bottom]]);
58
+ return gen({ nodes: nodes, links: links } as any);
59
+ }
60
+
61
+ export default function DemoSankey({ Reveal }: SlideComponentProps) {
62
+ const ref = React.useState<any>(null);
63
+ const layout = ref[0], setLayout = ref[1];
64
+
65
+ React.useEffect(function () {
66
+ setLayout(computeSankey());
67
+ }, []);
68
+
69
+ return (
70
+ <SlideCanvas Reveal={Reveal} W={W} H={H}>{function (step: number, instant: boolean) {
71
+ if (!layout) return null;
72
+ const pathGen = sankeyLinkHorizontal();
73
+
74
+ return (
75
+ <g style={{ fontFamily: CANVAS.fontFamily }}>
76
+ <defs>
77
+ {layout.links.map(function (l: any, i: number) {
78
+ const sc = COL_COLOR[layout.nodes[l.source.index].col];
79
+ const tc = COL_COLOR[layout.nodes[l.target.index].col];
80
+ return (
81
+ <linearGradient key={"g" + i} id={"sk-grad-" + i}
82
+ gradientUnits="userSpaceOnUse"
83
+ x1={l.source.x1} x2={l.target.x0}>
84
+ <stop offset="0%" stopColor={sc} />
85
+ <stop offset="100%" stopColor={tc} />
86
+ </linearGradient>
87
+ );
88
+ })}
89
+ </defs>
90
+
91
+ {/* Step 1: links */}
92
+ <g style={vis(step, 1, instant)}>
93
+ {layout.links.map(function (l: any, i: number) {
94
+ const err = step >= 2 && isErrorLink(l);
95
+ return (
96
+ <path key={"lk" + i} d={pathGen(l) || undefined}
97
+ fill="none"
98
+ stroke={err ? "#C00000" : "url(#sk-grad-" + i + ")"}
99
+ strokeOpacity={err ? 0.75 : 0.4}
100
+ strokeWidth={Math.max(1.5, l.width)}
101
+ style={{ transition: instant ? "none" : "stroke 0.4s ease, stroke-opacity 0.4s ease" }} />
102
+ );
103
+ })}
104
+ {/* link value labels */}
105
+ {layout.links.map(function (l: any, i: number) {
106
+ const mx = (l.source.x1 + l.target.x0) / 2;
107
+ const my = (l.y0 + l.y1) / 2;
108
+ return (
109
+ <text key={"lv" + i} x={mx} y={my} dy="0.35em"
110
+ textAnchor="middle" fontSize="14" fontWeight="700"
111
+ fill="#5a5a5a" pointerEvents="none">
112
+ {l.value}
113
+ </text>
114
+ );
115
+ })}
116
+ </g>
117
+
118
+ {/* Step 0: nodes */}
119
+ <g style={vis(step, 0, instant)}>
120
+ {layout.nodes.map(function (n: any, i: number) {
121
+ const w = n.x1 - n.x0;
122
+ const h = Math.max(2, n.y1 - n.y0);
123
+ const leftCol = n.col === 0;
124
+ const rightCol = n.col === 2;
125
+ const lx = leftCol ? n.x0 - 8 : (rightCol ? n.x1 + 8 : n.x0 + w / 2);
126
+ const anchor: "end" | "start" | "middle" = leftCol ? "end" : (rightCol ? "start" : "middle");
127
+ const ly = leftCol || rightCol ? (n.y0 + n.y1) / 2 : n.y0 - 6;
128
+ return (
129
+ <g key={"nd" + i}>
130
+ <rect x={n.x0} y={n.y0} width={w} height={h} rx="5" ry="5"
131
+ fill={COL_COLOR[n.col]} stroke="#fff" strokeWidth="1.5" />
132
+ <text x={lx} y={ly} dy="0.35em" textAnchor={anchor}
133
+ fontSize="14" fontWeight="700" fill="#313131">
134
+ {n.name}
135
+ </text>
136
+ </g>
137
+ );
138
+ })}
139
+ </g>
140
+ </g>
141
+ );
142
+ }}</SlideCanvas>
143
+ );
144
+ }
@@ -0,0 +1,181 @@
1
+ import React from "react";
2
+ import rough from "roughjs";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+ import { CANVAS, useRevealStep } from "../../../core/presets.ts";
5
+
6
+ const SVGNS = "http://www.w3.org/2000/svg";
7
+
8
+ // Each line: [time, level, service, message]
9
+ const LOG = [
10
+ { t: "10:23:41", lvl: "INFO", svc: "gateway", msg: "request /api/orders (uid=3847)" },
11
+ { t: "10:23:41", lvl: "INFO", svc: "auth", msg: "token validated (uid=3847)" },
12
+ { t: "10:23:42", lvl: "WARN", svc: "orders", msg: "slow query: 2847ms (threshold: 500ms)" },
13
+ { t: "10:23:44", lvl: "ERROR", svc: "payments", msg: "connection timeout to payments-db" },
14
+ { t: "10:23:44", lvl: "ERROR", svc: "orders", msg: "upstream failure: payments (retry 1/3)" },
15
+ { t: "10:23:45", lvl: "ERROR", svc: "gateway", msg: "503 Service Unavailable" },
16
+ ];
17
+
18
+ const LVL_COLOR: Record<string, string> = { INFO: "#4ade80", WARN: "#facc15", ERROR: "#f87171" };
19
+
20
+ // Terminal geometry in the 1200x560 overlay coordinate space.
21
+ const TERM = { x: 90, y: 70, w: 760, h: 360 };
22
+ const LINE_H = 44;
23
+ const FIRST_LINE_Y = TERM.y + 78; // baseline of first log line
24
+ const WARN_INDEX = 2;
25
+ const ERROR_INDICES = [3, 4, 5];
26
+
27
+ function lineCenterY(i: number) {
28
+ return FIRST_LINE_Y + i * LINE_H - 6;
29
+ }
30
+
31
+ export default function DemoScreenshotAnnotate({ Reveal }: SlideComponentProps) {
32
+ const ref = useRevealStep(Reveal);
33
+ const step = ref[0], ready = ref[1], instant = ref[2];
34
+
35
+ const svgRef = React.useRef<SVGSVGElement>(null);
36
+ const circleLayer = React.useRef<SVGGElement>(null);
37
+ const arrowLayer = React.useRef<SVGGElement>(null);
38
+ const boxLayer = React.useRef<SVGGElement>(null);
39
+
40
+ function clearLayer(layer: SVGGElement | null) {
41
+ if (!layer) return;
42
+ while (layer.firstChild) layer.removeChild(layer.firstChild);
43
+ }
44
+
45
+ // Step 1: rough red circle around the WARN line (the root cause).
46
+ React.useEffect(function () {
47
+ const svgEl = svgRef.current, layer = circleLayer.current;
48
+ if (!svgEl || !layer) return;
49
+ clearLayer(layer);
50
+ if (step < 1) return;
51
+ const rc = rough.svg(svgEl);
52
+ const cy = lineCenterY(WARN_INDEX);
53
+ const node = rc.ellipse(TERM.x + 360, cy, 660, 60, {
54
+ roughness: 2,
55
+ stroke: "#ef4444",
56
+ strokeWidth: 3,
57
+ });
58
+ layer.appendChild(node);
59
+ }, [step >= 1]);
60
+
61
+ // Step 2: rough arrows from each ERROR line back to the WARN line.
62
+ React.useEffect(function () {
63
+ const svgEl = svgRef.current;
64
+ const layer = arrowLayer.current;
65
+ if (!svgEl || !layer) return;
66
+ clearLayer(layer);
67
+ if (step < 2) return;
68
+ const rc = rough.svg(svgEl);
69
+ const targetX = TERM.x + 690;
70
+ const targetY = lineCenterY(WARN_INDEX);
71
+ ERROR_INDICES.forEach(function (idx: number) {
72
+ const sy = lineCenterY(idx);
73
+ const sx = TERM.x + 700;
74
+ // Curve outward to the right of the terminal then back to the WARN line.
75
+ const bend = sx + 70;
76
+ const path = rc.curve(
77
+ [[sx, sy], [bend, (sy + targetY) / 2], [targetX + 8, targetY + 18]],
78
+ { roughness: 1.8, stroke: "#f97316", strokeWidth: 2.5 }
79
+ );
80
+ layer.appendChild(path);
81
+ // Arrow head near the WARN line.
82
+ const h1 = rc.line(targetX + 8, targetY + 18, targetX - 6, targetY + 8, { roughness: 1.4, stroke: "#f97316", strokeWidth: 2.5 });
83
+ const h2 = rc.line(targetX + 8, targetY + 18, targetX + 22, targetY + 4, { roughness: 1.4, stroke: "#f97316", strokeWidth: 2.5 });
84
+ layer.appendChild(h1);
85
+ layer.appendChild(h2);
86
+ });
87
+ }, [step >= 2]);
88
+
89
+ // Step 3: rough rectangle annotation box to the right.
90
+ React.useEffect(function () {
91
+ const svgEl = svgRef.current, layer = boxLayer.current;
92
+ if (!svgEl || !layer) return;
93
+ clearLayer(layer);
94
+ if (step < 3) return;
95
+ const rc = rough.svg(svgEl);
96
+ const node = rc.rectangle(900, 220, 250, 130, {
97
+ roughness: 2,
98
+ stroke: "#ef4444",
99
+ strokeWidth: 3,
100
+ fill: "rgba(239,68,68,0.06)",
101
+ fillStyle: "solid",
102
+ });
103
+ layer.appendChild(node);
104
+ }, [step >= 3]);
105
+
106
+ return (
107
+ <div style={{
108
+ position: "relative", width: "100%", height: "100%",
109
+ display: "flex", alignItems: "center", justifyContent: "center",
110
+ fontFamily: CANVAS.fontFamily,
111
+ opacity: ready ? 1 : 0, transition: "opacity 0.2s ease",
112
+ }}>
113
+ {/* The fake terminal screenshot, positioned to match TERM geometry. */}
114
+ <div style={{
115
+ position: "absolute",
116
+ left: (TERM.x / 1200 * 100) + "%",
117
+ top: (TERM.y / 560 * 100) + "%",
118
+ width: (TERM.w / 1200 * 100) + "%",
119
+ height: (TERM.h / 560 * 100) + "%",
120
+ background: "#1a1a1a",
121
+ borderRadius: 8,
122
+ boxShadow: "0 12px 40px rgba(0,0,0,0.35)",
123
+ overflow: "hidden",
124
+ fontFamily: "Menlo, Consolas, monospace",
125
+ opacity: step >= 0 ? 1 : 0,
126
+ transition: instant ? "none" : "opacity 0.4s ease",
127
+ }}>
128
+ {/* Title bar with traffic lights */}
129
+ <div style={{
130
+ height: 30, background: "#2a2a2a", display: "flex",
131
+ alignItems: "center", gap: 8, padding: "0 14px",
132
+ }}>
133
+ <span style={{ width: 11, height: 11, borderRadius: "50%", background: "#ff5f56" }} />
134
+ <span style={{ width: 11, height: 11, borderRadius: "50%", background: "#ffbd2e" }} />
135
+ <span style={{ width: 11, height: 11, borderRadius: "50%", background: "#27c93f" }} />
136
+ <span style={{ marginLeft: 12, color: "#888", fontSize: 14 }}>service.log</span>
137
+ </div>
138
+ {/* Log lines */}
139
+ <div style={{ padding: "12px 18px" }}>
140
+ {LOG.map(function (l, i: number) {
141
+ return (
142
+ <div key={i} style={{
143
+ fontSize: 14, lineHeight: "30px", whiteSpace: "nowrap",
144
+ fontFamily: "Menlo, Consolas, monospace",
145
+ }}>
146
+ <span style={{ color: "#6b7280" }}>[{l.t}]</span>{" "}
147
+ <span style={{ color: LVL_COLOR[l.lvl], fontWeight: 700 }}>
148
+ {l.lvl.padEnd(5, " ")}
149
+ </span>{" "}
150
+ <span style={{ color: "#93c5fd" }}>{l.svc.padEnd(9, " ")}</span>
151
+ <span style={{ color: "#d1d5db" }}>{"→ " + l.msg}</span>
152
+ </div>
153
+ );
154
+ })}
155
+ </div>
156
+ </div>
157
+
158
+ {/* roughjs annotation overlay, full canvas */}
159
+ <svg ref={svgRef} viewBox="0 0 1200 560" style={{
160
+ position: "absolute", inset: 0, width: "100%", height: "100%",
161
+ pointerEvents: "none",
162
+ }}>
163
+ <g ref={circleLayer} />
164
+ <g ref={arrowLayer} />
165
+ <g ref={boxLayer} />
166
+ {/* Step 3: annotation text inside the rough box. */}
167
+ <g style={{ opacity: step >= 3 ? 1 : 0, transition: instant ? "none" : "opacity 0.45s ease" }}>
168
+ <text x="925" y="258" style={{ fontSize: 17, fontWeight: 900, fill: "#dc2626", fontFamily: CANVAS.fontFamily }}>
169
+ Root cause:
170
+ </text>
171
+ <text x="925" y="288" style={{ fontSize: 15, fontWeight: 600, fill: "#1a1a1a", fontFamily: CANVAS.fontFamily }}>
172
+ slow DB query
173
+ </text>
174
+ <text x="925" y="312" style={{ fontSize: 15, fontWeight: 600, fill: "#1a1a1a", fontFamily: CANVAS.fontFamily }}>
175
+ triggers cascade
176
+ </text>
177
+ </g>
178
+ </svg>
179
+ </div>
180
+ );
181
+ }