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,169 @@
1
+ import React from "react";
2
+ import {
3
+ forceSimulation, forceLink, forceManyBody, forceCenter, forceCollide,
4
+ } from "d3-force";
5
+ import { CANVAS, SlideCanvas, vis } from "../../../core/presets.ts";
6
+ import type { SlideComponentProps } from "../../../core/types.ts";
7
+
8
+ const W = 1200, H = 560;
9
+ const R = 22;
10
+
11
+ // type -> color
12
+ const TYPE_COLOR: Record<string, string> = {
13
+ api: "#2E6FDB", // blue
14
+ data: "#3FA34D", // green
15
+ compute: "#E8912D", // orange
16
+ infra: "#7B61FF", // purple
17
+ };
18
+
19
+ const RAW_NODES = [
20
+ { id: "API Gateway", type: "api" },
21
+ { id: "Auth", type: "compute" },
22
+ { id: "Users", type: "compute" },
23
+ { id: "Orders", type: "compute" },
24
+ { id: "Products", type: "compute" },
25
+ { id: "Payments", type: "compute" },
26
+ { id: "Inventory", type: "compute" },
27
+ { id: "Notifications", type: "compute" },
28
+ { id: "Search", type: "compute" },
29
+ { id: "Analytics", type: "data" },
30
+ { id: "Cache", type: "infra" },
31
+ { id: "Database", type: "data" },
32
+ ];
33
+
34
+ const RAW_LINKS = [
35
+ ["API Gateway", "Auth"],
36
+ ["API Gateway", "Users"],
37
+ ["API Gateway", "Orders"],
38
+ ["API Gateway", "Products"],
39
+ ["API Gateway", "Search"],
40
+ ["Orders", "Payments"],
41
+ ["Orders", "Inventory"],
42
+ ["Orders", "Notifications"],
43
+ ["Users", "Cache"],
44
+ ["Products", "Cache"],
45
+ ["Auth", "Database"],
46
+ ["Users", "Database"],
47
+ ["Orders", "Database"],
48
+ ["Inventory", "Database"],
49
+ ["Analytics", "Database"],
50
+ ];
51
+
52
+ // Failure cascade order (3-4 nodes turning red sequentially).
53
+ const CASCADE = ["Database", "Orders", "Payments", "Inventory"];
54
+
55
+ function computeLayout() {
56
+ const nodes: any[] = RAW_NODES.map(function (n) { return { id: n.id, type: n.type }; });
57
+ const links: any[] = RAW_LINKS.map(function (l) { return { source: l[0], target: l[1] }; });
58
+ const sim = forceSimulation(nodes)
59
+ .force("link", forceLink(links).id(function (d: any) { return d.id; }).distance(120).strength(0.4))
60
+ .force("charge", forceManyBody().strength(-420))
61
+ .force("center", forceCenter(W / 2, H / 2))
62
+ .force("collide", forceCollide(R + 18))
63
+ .stop();
64
+ sim.alpha(1).alphaDecay(0.02);
65
+ for (let i = 0; i < 300; i++) sim.tick();
66
+ // clamp into canvas
67
+ nodes.forEach(function (n) {
68
+ n.x = Math.max(R + 60, Math.min(W - R - 60, n.x));
69
+ n.y = Math.max(R + 50, Math.min(H - R - 30, n.y));
70
+ });
71
+ return { nodes: nodes, links: links };
72
+ }
73
+
74
+ export default function DemoForceGraph({ Reveal }: SlideComponentProps) {
75
+ const stateRef = React.useState<any>(null);
76
+ const graph = stateRef[0], setGraph = stateRef[1];
77
+
78
+ React.useEffect(function () {
79
+ setGraph(computeLayout());
80
+ }, []);
81
+
82
+ return (
83
+ <SlideCanvas Reveal={Reveal} W={W} H={H}>{function (step: number, instant: boolean) {
84
+ if (!graph) return null;
85
+ const nodes = graph.nodes;
86
+ const links = graph.links;
87
+ const byId: Record<string, any> = {};
88
+ nodes.forEach(function (n: any) { byId[n.id] = n; });
89
+
90
+ // cascade index reached at this step: step 2 -> show cascade reds
91
+ const cascadeActive = step >= 2;
92
+ const cascadeSet: Record<string, boolean> = {};
93
+ if (cascadeActive) {
94
+ CASCADE.forEach(function (id) { cascadeSet[id] = true; });
95
+ }
96
+
97
+ function nodeFill(n: any) {
98
+ if (cascadeSet[n.id]) return "#C00000";
99
+ return TYPE_COLOR[n.type];
100
+ }
101
+
102
+ return (
103
+ <g style={{ fontFamily: CANVAS.fontFamily }}>
104
+ <defs>
105
+ <marker id="dfg-arrow" viewBox="0 0 10 10" refX="9" refY="5"
106
+ markerWidth="6" markerHeight="6" orient="auto-start-reverse">
107
+ <path d="M0,0 L10,5 L0,10 z" fill="#BFBFBF" />
108
+ </marker>
109
+ </defs>
110
+
111
+ {/* Step 1: links */}
112
+ <g style={vis(step, 1, instant)}>
113
+ {links.map(function (l: any, i: number) {
114
+ const s = byId[l.source.id || l.source];
115
+ const t = byId[l.target.id || l.target];
116
+ if (!s || !t) return null;
117
+ const red = cascadeSet[s.id] && cascadeSet[t.id];
118
+ // trim line to circle edges
119
+ const dx = t.x - s.x, dy = t.y - s.y;
120
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
121
+ const ux = dx / len, uy = dy / len;
122
+ const x1 = s.x + ux * R, y1 = s.y + uy * R;
123
+ const x2 = t.x - ux * (R + 6), y2 = t.y - uy * (R + 6);
124
+ return (
125
+ <line key={"l" + i}
126
+ x1={x1} y1={y1} x2={x2} y2={y2}
127
+ stroke={red ? "#C00000" : "#BFBFBF"}
128
+ strokeWidth={red ? 3 : 1.6}
129
+ markerEnd="url(#dfg-arrow)"
130
+ style={{ transition: instant ? "none" : "stroke 0.4s ease, stroke-width 0.4s ease" }}
131
+ />
132
+ );
133
+ })}
134
+ </g>
135
+
136
+ {/* Step 3: ripple blast radius around cascade nodes */}
137
+ <g style={vis(step, 3, instant)}>
138
+ {CASCADE.map(function (id, i) {
139
+ const n = byId[id];
140
+ if (!n) return null;
141
+ return (
142
+ <circle key={"rip" + i} cx={n.x} cy={n.y} r={R + 18 + i * 4}
143
+ fill="none" stroke="#C00000" strokeWidth="2" strokeOpacity="0.35"
144
+ strokeDasharray="6 5" />
145
+ );
146
+ })}
147
+ </g>
148
+
149
+ {/* Step 0: nodes */}
150
+ <g style={vis(step, 0, instant)}>
151
+ {nodes.map(function (n: any, i: number) {
152
+ return (
153
+ <g key={"n" + i} transform={"translate(" + n.x + "," + n.y + ")"}>
154
+ <circle r={R} fill={nodeFill(n)}
155
+ stroke="#fff" strokeWidth="2.5"
156
+ style={{ transition: instant ? "none" : "fill 0.4s ease" }} />
157
+ <text y={R + 16} textAnchor="middle"
158
+ fontSize="14" fontWeight="700" fill="#313131">
159
+ {n.id}
160
+ </text>
161
+ </g>
162
+ );
163
+ })}
164
+ </g>
165
+ </g>
166
+ );
167
+ }}</SlideCanvas>
168
+ );
169
+ }
@@ -0,0 +1,109 @@
1
+ import React from "react";
2
+ import { CANVAS, useRevealStep } from "../../../core/presets.ts";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+
5
+ const DATA = [
6
+ { name: "SRCA", value: 94.2 },
7
+ { name: "MicroRCA", value: 87.1 },
8
+ { name: "RCAeval", value: 82.5 },
9
+ { name: "CloudRCA", value: 78.3 },
10
+ { name: "DiagFusion", value: 71.6 },
11
+ { name: "TraceAnomaly", value: 65.2 },
12
+ { name: "Sage", value: 58.4 },
13
+ ];
14
+
15
+ // Deep blue (top) -> lighter blue (bottom)
16
+ function barColor(i: number, n: number) {
17
+ const top = [30, 58, 95]; // #1e3a5f
18
+ const bot = [72, 149, 239]; // #4895ef
19
+ const t = n <= 1 ? 0 : i / (n - 1);
20
+ const c = top.map(function (v, k) { return Math.round(v + (bot[k] - v) * t); });
21
+ return "rgb(" + c[0] + "," + c[1] + "," + c[2] + ")";
22
+ }
23
+
24
+ export default function DemoFullbleedBars({ Reveal }: SlideComponentProps) {
25
+ const ref = useRevealStep(Reveal);
26
+ const step = ref[0], ready = ref[1], instant = ref[2];
27
+ const barsRef = React.useRef<(HTMLDivElement | null)[]>([]);
28
+
29
+ const maxValue = Math.max.apply(null, DATA.map(function (d) { return d.value; }));
30
+
31
+ React.useEffect(function () {
32
+ if (!ready || step < 0) return;
33
+ const anime = (window as any).anime;
34
+ barsRef.current.forEach(function (el, i) {
35
+ if (!el) return;
36
+ const target = (DATA[i].value / maxValue) * 100;
37
+ if (instant || !anime || !anime.animate) {
38
+ el.style.width = target + "%";
39
+ return;
40
+ }
41
+ anime.animate(el, {
42
+ width: ["0%", target + "%"],
43
+ duration: 900,
44
+ delay: i * 80,
45
+ ease: "outCubic",
46
+ });
47
+ });
48
+ }, [ready, instant]);
49
+
50
+ const n = DATA.length;
51
+
52
+ return (
53
+ <div style={{
54
+ width: "100%", height: "100%", boxSizing: "border-box",
55
+ background: "#f8f9fa", display: "flex", flexDirection: "column",
56
+ justifyContent: "center", gap: 4, padding: 0,
57
+ fontFamily: CANVAS.fontFamily,
58
+ opacity: ready ? 1 : 0, transition: "opacity 0.2s ease",
59
+ }}>
60
+ {DATA.map(function (d, i) {
61
+ const isTop = i === 0;
62
+ const dimmed = step >= 2 && i >= n - 3;
63
+ const highlighted = step >= 1 && isTop;
64
+ const rowOpacity = dimmed ? 0.3 : 1;
65
+ return (
66
+ <div key={d.name} style={{
67
+ position: "relative", flex: "1 1 0", minHeight: 0,
68
+ display: "flex", alignItems: "center",
69
+ opacity: rowOpacity,
70
+ transition: instant ? "none" : "opacity 0.45s ease",
71
+ }}>
72
+ <div
73
+ ref={function (el) { barsRef.current[i] = el; }}
74
+ style={{
75
+ position: "relative", height: "100%", width: "0%",
76
+ background: barColor(i, n),
77
+ display: "flex", alignItems: "center",
78
+ justifyContent: "space-between",
79
+ padding: "0 24px", boxSizing: "border-box",
80
+ borderRadius: "0 6px 6px 0",
81
+ boxShadow: highlighted
82
+ ? "0 0 0 3px #ffd60a, 0 0 24px rgba(255,214,10,0.55)"
83
+ : "none",
84
+ transition: instant ? "none" : "box-shadow 0.4s ease",
85
+ }}
86
+ >
87
+ <span style={{
88
+ color: "#fff", fontSize: 18, fontWeight: 700,
89
+ whiteSpace: "nowrap", letterSpacing: 0.2,
90
+ }}>{d.name}</span>
91
+ <span style={{
92
+ color: "#fff", fontSize: 18, fontWeight: 600,
93
+ whiteSpace: "nowrap", opacity: 0.95,
94
+ }}>{d.value.toFixed(1)}%</span>
95
+ {highlighted && (
96
+ <span style={{
97
+ position: "absolute", right: -84, top: "50%",
98
+ transform: "translateY(-50%)",
99
+ color: "#b8860b", fontSize: 16, fontWeight: 800,
100
+ letterSpacing: 1, textTransform: "uppercase",
101
+ }}>Best</span>
102
+ )}
103
+ </div>
104
+ </div>
105
+ );
106
+ })}
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,177 @@
1
+ import React from "react";
2
+ import { CANVAS, useRevealStep } from "../../../core/presets.ts";
3
+ import type { SlideComponentProps } from "../../../core/types.ts";
4
+
5
+ const W = 1200;
6
+ const H = 560;
7
+ const N = 5;
8
+ const STAGE_W = W / N; // 240px each — flows edge to edge
9
+ const CHEVRON = 34; // how far the arrow point juts into the next stage
10
+
11
+ const STAGES = [
12
+ { name: "Collect", sub: "Traces + Logs", color: "#1e3a5f" },
13
+ { name: "Parse", sub: "Dependency Graph", color: "#2563eb" },
14
+ { name: "Correlate", sub: "Causal Analysis", color: "#7c3aed" },
15
+ { name: "Rank", sub: "Fault Scoring", color: "#db2777" },
16
+ { name: "Report", sub: "Top-K Results", color: "#059669" },
17
+ ];
18
+
19
+ // Which step reveals each stage: [Collect,Parse]=0, [Correlate]=1, [Rank,Report]=2
20
+ const STAGE_STEP = [0, 0, 1, 2, 2];
21
+
22
+ // A chevron-shaped band for stage i: left edge is notched in, right edge juts out.
23
+ function chevronPath(i: number) {
24
+ const x0 = i * STAGE_W;
25
+ const x1 = x0 + STAGE_W;
26
+ const isFirst = i === 0;
27
+ const isLast = i === N - 1;
28
+ const leftNotch = isFirst ? x0 : x0 + CHEVRON;
29
+ const rightTip = isLast ? x1 : x1 + CHEVRON;
30
+ const mid = H / 2;
31
+ // Start top-left, across the top to the right tip, down to bottom, back, and the left notch indent.
32
+ return [
33
+ "M", x0, 0,
34
+ "L", x1, 0,
35
+ "L", rightTip, mid,
36
+ "L", x1, H,
37
+ "L", x0, H,
38
+ "L", leftNotch, mid,
39
+ "Z",
40
+ ].join(" ");
41
+ }
42
+
43
+ // Simple per-stage glyph drawn from primitive SVG shapes, centered at (cx, cy).
44
+ function StageIcon(props: { cx: number; cy: number; kind: string }) {
45
+ const cx = props.cx, cy = props.cy, kind = props.kind;
46
+ const s = "#ffffff";
47
+ if (kind === "Collect") {
48
+ // stacked layers / inbox
49
+ return (
50
+ <g stroke={s} strokeWidth="3" fill="none" strokeLinejoin="round">
51
+ <path d={`M ${cx - 26} ${cy - 14} L ${cx} ${cy - 26} L ${cx + 26} ${cy - 14} L ${cx} ${cy - 2} Z`} />
52
+ <path d={`M ${cx - 26} ${cy + 4} L ${cx} ${cy + 16} L ${cx + 26} ${cy + 4}`} />
53
+ <path d={`M ${cx - 26} ${cy + 18} L ${cx} ${cy + 30} L ${cx + 26} ${cy + 18}`} />
54
+ </g>
55
+ );
56
+ }
57
+ if (kind === "Parse") {
58
+ // dependency graph: nodes + edges
59
+ return (
60
+ <g stroke={s} strokeWidth="3" fill={s}>
61
+ <line x1={cx - 22} y1={cy - 18} x2={cx + 18} y2={cy - 4} />
62
+ <line x1={cx - 22} y1={cy - 18} x2={cx - 6} y2={cy + 22} />
63
+ <line x1={cx + 18} y1={cy - 4} x2={cx - 6} y2={cy + 22} />
64
+ <circle cx={cx - 22} cy={cy - 18} r="7" />
65
+ <circle cx={cx + 18} cy={cy - 4} r="7" />
66
+ <circle cx={cx - 6} cy={cy + 22} r="7" />
67
+ </g>
68
+ );
69
+ }
70
+ if (kind === "Correlate") {
71
+ // overlapping rings
72
+ return (
73
+ <g stroke={s} strokeWidth="3" fill="none">
74
+ <circle cx={cx - 12} cy={cy} r="20" />
75
+ <circle cx={cx + 12} cy={cy} r="20" />
76
+ </g>
77
+ );
78
+ }
79
+ if (kind === "Rank") {
80
+ // ranked bars
81
+ return (
82
+ <g fill={s}>
83
+ <rect x={cx - 26} y={cy + 2} width="14" height="26" rx="2" />
84
+ <rect x={cx - 7} y={cy - 12} width="14" height="40" rx="2" />
85
+ <rect x={cx + 12} y={cy - 26} width="14" height="54" rx="2" />
86
+ </g>
87
+ );
88
+ }
89
+ // Report: document with lines + checkmark
90
+ return (
91
+ <g stroke={s} strokeWidth="3" fill="none" strokeLinecap="round">
92
+ <rect x={cx - 18} y={cy - 26} width="36" height="48" rx="3" />
93
+ <line x1={cx - 9} y1={cy - 12} x2={cx + 9} y2={cy - 12} />
94
+ <line x1={cx - 9} y1={cy - 2} x2={cx + 9} y2={cy - 2} />
95
+ <path d={`M ${cx - 8} ${cy + 12} L ${cx - 1} ${cy + 19} L ${cx + 12} ${cy + 4}`} />
96
+ </g>
97
+ );
98
+ }
99
+
100
+ export default function DemoFullbleedFlow({ Reveal }: SlideComponentProps) {
101
+ const ref = useRevealStep(Reveal);
102
+ const step = ref[0], ready = ref[1], instant = ref[2];
103
+
104
+ const dotsRef = React.useRef<(SVGCircleElement | null)[]>([]);
105
+ const DOT_COUNT = 6;
106
+
107
+ // Step 3: animate dots traveling left -> right along the flow.
108
+ React.useEffect(function () {
109
+ if (step < 3) {
110
+ dotsRef.current.forEach(function (d) { if (d) d.style.opacity = String(0); });
111
+ return;
112
+ }
113
+ const anime = (window as any).anime;
114
+ const startX = 0, endX = W;
115
+ if (!anime || !anime.animate || instant) {
116
+ dotsRef.current.forEach(function (d) { if (d) { d.style.opacity = String(1); } });
117
+ return;
118
+ }
119
+ dotsRef.current.forEach(function (d) {
120
+ if (d) { d.style.opacity = String(1); d.setAttribute("transform", "translate(0,0)"); }
121
+ });
122
+ anime.animate(dotsRef.current.filter(Boolean), {
123
+ translateX: [startX - 30, endX + 30],
124
+ duration: 2600,
125
+ loop: true,
126
+ ease: "linear",
127
+ delay: anime.stagger(2600 / DOT_COUNT),
128
+ });
129
+ }, [step >= 3, instant]);
130
+
131
+ const midY = H / 2;
132
+
133
+ return (
134
+ <svg viewBox={"0 0 " + W + " " + H} style={{
135
+ width: "100%", height: "100%", display: "block",
136
+ fontFamily: CANVAS.fontFamily,
137
+ opacity: ready ? 1 : 0, transition: "opacity 0.2s ease",
138
+ }}>
139
+ {STAGES.map(function (st, i) {
140
+ const revealed = step >= STAGE_STEP[i];
141
+ const cx = i * STAGE_W + STAGE_W / 2 + (i === N - 1 ? 0 : CHEVRON / 2);
142
+ return (
143
+ <g key={i} style={{
144
+ opacity: revealed ? 1 : 0,
145
+ transition: instant ? "none" : "opacity 0.5s ease",
146
+ }}>
147
+ <path d={chevronPath(i)} fill={st.color} />
148
+ <StageIcon cx={cx} cy={midY - 30} kind={st.name} />
149
+ <text x={cx} y={midY + 38} textAnchor="middle" style={{
150
+ fontSize: 30, fontWeight: 900, fill: "#fff",
151
+ }}>{st.name}</text>
152
+ <text x={cx} y={midY + 66} textAnchor="middle" style={{
153
+ fontSize: 16, fontWeight: 600, fill: "rgba(255,255,255,0.78)",
154
+ }}>{st.sub}</text>
155
+ </g>
156
+ );
157
+ })}
158
+
159
+ {/* Step 3: data-flow dots traveling across the whole flow. */}
160
+ <g>
161
+ {Array.from({ length: DOT_COUNT }).map(function (_, i) {
162
+ return (
163
+ <circle
164
+ key={i}
165
+ ref={function (el) { dotsRef.current[i] = el; }}
166
+ cx={0}
167
+ cy={midY + 92}
168
+ r={7}
169
+ fill="#ffffff"
170
+ style={{ opacity: 0, filter: "drop-shadow(0 0 6px rgba(255,255,255,0.9))" }}
171
+ />
172
+ );
173
+ })}
174
+ </g>
175
+ </svg>
176
+ );
177
+ }
@@ -0,0 +1,135 @@
1
+ import React from "react";
2
+ import { scaleLinear } from "d3-scale";
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 ROWS = 8; // Mon-Sun + Avg
8
+ const COLS = 12; // 00:00..22:00 in 2h steps
9
+
10
+ const GRID = { x: 150, y: 90, gap: 4 };
11
+ const CELL = 70;
12
+
13
+ const ROW_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Avg"];
14
+ const COL_LABELS: string[] = [];
15
+ for (let c = 0; c < COLS; c++) COL_LABELS.push((c * 2 < 10 ? "0" : "") + (c * 2) + ":00");
16
+
17
+ // deterministic pseudo-random value in [0,100]
18
+ function valueAt(r: number, c: number) {
19
+ const v = Math.sin(r * 1.7 + 0.5) * Math.cos(c * 0.9 + 1.3) + Math.sin((r + c) * 0.6);
20
+ const n = (v + 2) / 4; // ~[0,1]
21
+ const bump = Math.exp(-Math.pow((c - 6.5) / 4, 2)) * 0.4; // daytime activity bump
22
+ return Math.max(0, Math.min(1, n * 0.7 + bump)) * 100;
23
+ }
24
+
25
+ const colorScale = scaleLinear<string>()
26
+ .domain([0, 50, 100])
27
+ .range(["#ffffb2", "#fd8d3c", "#bd0026"]);
28
+
29
+ const NEUTRAL = "#ECECEC";
30
+
31
+ function cellX(c: number) { return GRID.x + c * (CELL + GRID.gap); }
32
+ function cellY(r: number) { return GRID.y + r * (CELL + GRID.gap); }
33
+
34
+ // Inner component so the animation hook lives at component top level.
35
+ function HeatGrid(props: { step: number; instant: boolean }) {
36
+ const step = props.step, instant = props.instant;
37
+ const cellsRef = React.useRef<(SVGRectElement | null)[]>([]);
38
+
39
+ React.useEffect(function () {
40
+ const targets = cellsRef.current.filter(Boolean) as SVGRectElement[];
41
+ if (!targets.length) return;
42
+ const anime = typeof window !== "undefined" ? (window as any).anime : null;
43
+
44
+ if (step < 1) {
45
+ targets.forEach(function (el) { el.setAttribute("fill", NEUTRAL); });
46
+ return;
47
+ }
48
+ if (!anime || instant) {
49
+ targets.forEach(function (el) {
50
+ el.setAttribute("fill", el.getAttribute("data-color") || NEUTRAL);
51
+ });
52
+ return;
53
+ }
54
+ targets.forEach(function (el) { el.setAttribute("fill", NEUTRAL); });
55
+ anime.animate(targets, {
56
+ fill: function (el: SVGRectElement) { return el.getAttribute("data-color"); },
57
+ delay: function (el: SVGRectElement) {
58
+ const r = +(el.getAttribute("data-r") || 0);
59
+ const c = +(el.getAttribute("data-c") || 0);
60
+ return (r + c) * 45;
61
+ },
62
+ duration: 400,
63
+ ease: "outQuad",
64
+ });
65
+ }, [step, instant]);
66
+
67
+ const legendX = GRID.x, legendY = cellY(ROWS) + 28, legendW = 360, legendH = 16;
68
+
69
+ return (
70
+ <g style={{ fontFamily: CANVAS.fontFamily }}>
71
+ <defs>
72
+ <linearGradient id="hm-legend" x1="0" x2="1" y1="0" y2="0">
73
+ <stop offset="0%" stopColor="#ffffb2" />
74
+ <stop offset="50%" stopColor="#fd8d3c" />
75
+ <stop offset="100%" stopColor="#bd0026" />
76
+ </linearGradient>
77
+ </defs>
78
+
79
+ {/* Step 0: grid (neutral). Cells colored via anime at step 1. */}
80
+ <g style={vis(step, 0, instant)}>
81
+ {ROW_LABELS.map(function (_, r) {
82
+ return COL_LABELS.map(function (__, c) {
83
+ const color = colorScale(valueAt(r, c));
84
+ const i = r * COLS + c;
85
+ return (
86
+ <rect key={"cell" + i}
87
+ ref={function (el) { cellsRef.current[i] = el; }}
88
+ x={cellX(c)} y={cellY(r)} width={CELL} height={CELL}
89
+ rx="6" ry="6"
90
+ data-color={color} data-r={r} data-c={c}
91
+ fill={NEUTRAL} />
92
+ );
93
+ });
94
+ })}
95
+ </g>
96
+
97
+ {/* Step 2: labels + legend */}
98
+ <g style={vis(step, 2, instant)}>
99
+ {ROW_LABELS.map(function (lab, r) {
100
+ return (
101
+ <text key={"rl" + r} x={GRID.x - 14} y={cellY(r) + CELL / 2}
102
+ dy="0.35em" textAnchor="end" fontSize="15" fontWeight="700"
103
+ fill="#313131">{lab}</text>
104
+ );
105
+ })}
106
+ {COL_LABELS.map(function (lab, c) {
107
+ return (
108
+ <text key={"cl" + c} x={cellX(c) + CELL / 2} y={GRID.y - 12}
109
+ textAnchor="middle" fontSize="14" fontWeight="700"
110
+ fill="#5a5a5a">{lab}</text>
111
+ );
112
+ })}
113
+
114
+ {/* color scale legend */}
115
+ <rect x={legendX} y={legendY} width={legendW} height={legendH}
116
+ rx="4" fill="url(#hm-legend)" stroke="#ccc" strokeWidth="0.5" />
117
+ <text x={legendX} y={legendY + legendH + 16} fontSize="14" fill="#5a5a5a">Low</text>
118
+ <text x={legendX + legendW} y={legendY + legendH + 16} textAnchor="end"
119
+ fontSize="14" fill="#5a5a5a">High</text>
120
+ <text x={legendX + legendW / 2} y={legendY + legendH + 16}
121
+ textAnchor="middle" fontSize="14" fontWeight="700" fill="#313131">
122
+ Activity
123
+ </text>
124
+ </g>
125
+ </g>
126
+ );
127
+ }
128
+
129
+ export default function DemoHeatmap({ Reveal }: SlideComponentProps) {
130
+ return (
131
+ <SlideCanvas Reveal={Reveal} W={W} H={H}>{function (step, instant) {
132
+ return <HeatGrid step={step} instant={instant} />;
133
+ }}</SlideCanvas>
134
+ );
135
+ }