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.
- package/dist/index.js +119 -0
- package/package.json +36 -0
- package/template-full/README.md +99 -0
- package/template-full/package.json +47 -0
- package/template-full/src/reveal/components/auto-layout.ts +229 -0
- package/template-full/src/reveal/components/charts.tsx +213 -0
- package/template-full/src/reveal/core/blocks.ts +172 -0
- package/template-full/src/reveal/core/deck-init.ts +60 -0
- package/template-full/src/reveal/core/design.ts +46 -0
- package/template-full/src/reveal/core/layout.ts +187 -0
- package/template-full/src/reveal/core/mount-registry.ts +41 -0
- package/template-full/src/reveal/core/presets.ts +189 -0
- package/template-full/src/reveal/core/runtime.ts +141 -0
- package/template-full/src/reveal/core/types.ts +114 -0
- package/template-full/src/reveal/data/algorithms.ts +78 -0
- package/template-full/src/reveal/data/benchmark.ts +79 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-arc-progress.tsx +153 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-before-after.tsx +164 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-bigtext.tsx +70 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-card-flip.tsx +118 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-chat-bubbles.tsx +257 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-code.tsx +136 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-concept-map.tsx +336 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-counter.tsx +194 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-cover.tsx +188 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-dark-dashboard.tsx +166 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-eval-matrix.tsx +191 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-force-graph.tsx +169 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-fullbleed-bars.tsx +109 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-fullbleed-flow.tsx +177 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-heatmap.tsx +135 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-icon-wall.tsx +143 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-math.tsx +103 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-number-morph.tsx +126 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-path.tsx +185 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-radar.tsx +124 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-rough.tsx +169 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-sankey.tsx +144 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-screenshot-annotate.tsx +181 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-stacked-cards.tsx +159 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-tabs.tsx +206 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-timeline.tsx +162 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-treemap.tsx +161 -0
- package/template-full/src/reveal/decks/demo-showcase/components/demo-zoom-focus.tsx +223 -0
- package/template-full/src/reveal/decks/demo-showcase/components/registry.ts +63 -0
- package/template-full/src/reveal/decks/demo-showcase/demo.css +237 -0
- package/template-full/src/reveal/decks/demo-showcase/index.html +24 -0
- package/template-full/src/reveal/decks/demo-showcase/main.ts +7 -0
- package/template-full/src/reveal/decks/demo-showcase/slides.ts +271 -0
- package/template-full/src/reveal/decks/fse26-rca/components/aws-cascade.tsx +295 -0
- package/template-full/src/reveal/decks/fse26-rca/components/bench-compare.tsx +64 -0
- package/template-full/src/reveal/decks/fse26-rca/components/bench-deficiency.tsx +104 -0
- package/template-full/src/reveal/decks/fse26-rca/components/bench-loop.tsx +402 -0
- package/template-full/src/reveal/decks/fse26-rca/components/bench-needs.tsx +78 -0
- package/template-full/src/reveal/decks/fse26-rca/components/closing-takeaway.tsx +165 -0
- package/template-full/src/reveal/decks/fse26-rca/components/cloud-incidents.tsx +88 -0
- package/template-full/src/reveal/decks/fse26-rca/components/failure-modes.tsx +59 -0
- package/template-full/src/reveal/decks/fse26-rca/components/fault-heatmap.tsx +85 -0
- package/template-full/src/reveal/decks/fse26-rca/components/hierarchy-tree.tsx +93 -0
- package/template-full/src/reveal/decks/fse26-rca/components/incident-hard.tsx +72 -0
- package/template-full/src/reveal/decks/fse26-rca/components/rca-pipeline.tsx +193 -0
- package/template-full/src/reveal/decks/fse26-rca/components/registry.ts +37 -0
- package/template-full/src/reveal/decks/fse26-rca/components/simple-rca.tsx +216 -0
- package/template-full/src/reveal/decks/fse26-rca/components/sota-collapse.tsx +63 -0
- package/template-full/src/reveal/decks/fse26-rca/components/srca-results.tsx +115 -0
- package/template-full/src/reveal/decks/fse26-rca/images/aws-outage-2025-deployflow.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/aws-post-event-summary.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/bbc-crowdstrike.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/cnn-meta-outage-2021.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/cover.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/nyt-facebook-2021.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/qr-repo.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/verge-crowdstrike-2024.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/images/wiki-meta-outage-2021.png +0 -0
- package/template-full/src/reveal/decks/fse26-rca/index.html +30 -0
- package/template-full/src/reveal/decks/fse26-rca/main.ts +8 -0
- package/template-full/src/reveal/decks/fse26-rca/slides.ts +175 -0
- package/template-full/src/reveal/env.d.ts +38 -0
- package/template-full/src/reveal/theme.css +762 -0
- package/template-full/src/reveal/tools/dev.mjs +120 -0
- package/template-full/src/reveal/tools/export-pdf.mjs +86 -0
- package/template-full/src/reveal/tools/preview.mjs +132 -0
- package/template-full/tsconfig.json +19 -0
- package/template-full/vite.config.ts +95 -0
- package/template-minimal/package.json +42 -0
- package/template-minimal/src/reveal/components/auto-layout.ts +229 -0
- package/template-minimal/src/reveal/components/charts.tsx +213 -0
- package/template-minimal/src/reveal/core/blocks.ts +172 -0
- package/template-minimal/src/reveal/core/deck-init.ts +60 -0
- package/template-minimal/src/reveal/core/design.ts +46 -0
- package/template-minimal/src/reveal/core/layout.ts +187 -0
- package/template-minimal/src/reveal/core/mount-registry.ts +41 -0
- package/template-minimal/src/reveal/core/presets.ts +189 -0
- package/template-minimal/src/reveal/core/runtime.ts +141 -0
- package/template-minimal/src/reveal/core/types.ts +114 -0
- package/template-minimal/src/reveal/data/.gitkeep +0 -0
- package/template-minimal/src/reveal/decks/my-deck/components/example-component.tsx +28 -0
- package/template-minimal/src/reveal/decks/my-deck/components/registry.ts +9 -0
- package/template-minimal/src/reveal/decks/my-deck/index.html +14 -0
- package/template-minimal/src/reveal/decks/my-deck/main.ts +5 -0
- package/template-minimal/src/reveal/decks/my-deck/slides.ts +34 -0
- package/template-minimal/src/reveal/env.d.ts +38 -0
- package/template-minimal/src/reveal/theme.css +762 -0
- package/template-minimal/tsconfig.json +19 -0
- package/template-minimal/vite.config.ts +95 -0
|
@@ -0,0 +1,336 @@
|
|
|
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, H = 560;
|
|
6
|
+
const CX = 600, CY = 280;
|
|
7
|
+
|
|
8
|
+
// Branch palettes.
|
|
9
|
+
const BLUE = { main: "#4361ee", leafFill: "#e7ecff", leafText: "#1d2a6b" };
|
|
10
|
+
const PURPLE = { main: "#7c3aed", leafFill: "#f0e7ff", leafText: "#3d1d6b" };
|
|
11
|
+
const GREEN = { main: "#059669", leafFill: "#dcf5ec", leafText: "#0b4f3a" };
|
|
12
|
+
|
|
13
|
+
// Manually laid-out organic positions.
|
|
14
|
+
const BRANCHES = [
|
|
15
|
+
{
|
|
16
|
+
key: "data",
|
|
17
|
+
palette: BLUE,
|
|
18
|
+
head: { label: "Data Collection", x: 260, y: 130 },
|
|
19
|
+
leaves: [
|
|
20
|
+
{ label: "Traces", x: 80, y: 70 },
|
|
21
|
+
{ label: "Logs", x: 90, y: 200 },
|
|
22
|
+
{ label: "Metrics", x: 250, y: 40 },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: "causal",
|
|
27
|
+
palette: PURPLE,
|
|
28
|
+
head: { label: "Causal Inference", x: 940, y: 130 },
|
|
29
|
+
leaves: [
|
|
30
|
+
{ label: "Graph Model", x: 1120, y: 70 },
|
|
31
|
+
{ label: "Correlation", x: 1110, y: 210 },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "fault",
|
|
36
|
+
palette: GREEN,
|
|
37
|
+
head: { label: "Fault Localization", x: 600, y: 480 },
|
|
38
|
+
leaves: [
|
|
39
|
+
{ label: "Ranking", x: 380, y: 520 },
|
|
40
|
+
{ label: "Validation", x: 600, y: 540 },
|
|
41
|
+
{ label: "Reporting", x: 820, y: 520 },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Quadratic bezier path from parent to child with a gentle bow.
|
|
47
|
+
function curvePath(x1: number, y1: number, x2: number, y2: number) {
|
|
48
|
+
const mx = (x1 + x2) / 2;
|
|
49
|
+
const my = (y1 + y2) / 2;
|
|
50
|
+
// Bow perpendicular to the line for an organic curve.
|
|
51
|
+
const dx = x2 - x1, dy = y2 - y1;
|
|
52
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
53
|
+
const nx = -dy / len, ny = dx / len;
|
|
54
|
+
const bow = 28;
|
|
55
|
+
const qx = mx + nx * bow, qy = my + ny * bow;
|
|
56
|
+
return "M " + x1 + " " + y1 + " Q " + qx + " " + qy + " " + x2 + " " + y2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function DemoConceptMap({ Reveal }: SlideComponentProps) {
|
|
60
|
+
const rs = useRevealStep(Reveal);
|
|
61
|
+
const step = rs[0], ready = rs[1], instant = rs[2];
|
|
62
|
+
|
|
63
|
+
const centralRef = React.useRef<SVGGElement>(null);
|
|
64
|
+
const glowRef = React.useRef<SVGCircleElement>(null);
|
|
65
|
+
// Per-branch ref guards so each branch animates once.
|
|
66
|
+
const playedRef = React.useRef<Record<string, boolean>>({ central: false, data: false, causal: false, fault: false, pulse: false });
|
|
67
|
+
|
|
68
|
+
// Collect refs by branch key.
|
|
69
|
+
const pathRefs = React.useRef<Record<string, (SVGPathElement | null)[]>>({});
|
|
70
|
+
const nodeRefs = React.useRef<Record<string, (SVGGElement | null)[]>>({});
|
|
71
|
+
|
|
72
|
+
function registerPath(key: string, i: number, el: SVGPathElement | null) {
|
|
73
|
+
if (!pathRefs.current[key]) pathRefs.current[key] = [];
|
|
74
|
+
pathRefs.current[key][i] = el;
|
|
75
|
+
}
|
|
76
|
+
function registerNode(key: string, i: number, el: SVGGElement | null) {
|
|
77
|
+
if (!nodeRefs.current[key]) nodeRefs.current[key] = [];
|
|
78
|
+
nodeRefs.current[key][i] = el;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function showCentral(animate: boolean) {
|
|
82
|
+
const node = centralRef.current;
|
|
83
|
+
if (!node) return;
|
|
84
|
+
const anime = (window as any).anime;
|
|
85
|
+
node.style.opacity = String(1);
|
|
86
|
+
if (!animate || !anime || !anime.animate) {
|
|
87
|
+
node.style.transform = "scale(1)";
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
anime.animate(node, {
|
|
91
|
+
scale: [0, 1],
|
|
92
|
+
duration: 700,
|
|
93
|
+
ease: "spring(1, 80, 10, 0)",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hideCentral() {
|
|
98
|
+
const node = centralRef.current;
|
|
99
|
+
if (!node) return;
|
|
100
|
+
node.style.opacity = String(0);
|
|
101
|
+
node.style.transform = "scale(0)";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function showBranch(key: string, animate: boolean) {
|
|
105
|
+
const anime = (window as any).anime;
|
|
106
|
+
const paths = (pathRefs.current[key] || []).filter(Boolean) as SVGPathElement[];
|
|
107
|
+
const nodes = (nodeRefs.current[key] || []).filter(Boolean) as SVGGElement[];
|
|
108
|
+
|
|
109
|
+
paths.forEach(function (p) {
|
|
110
|
+
const len = p.getTotalLength ? p.getTotalLength() : 0;
|
|
111
|
+
p.style.strokeDasharray = String(len);
|
|
112
|
+
if (!animate || !anime || !anime.animate) {
|
|
113
|
+
p.style.strokeDashoffset = String(0);
|
|
114
|
+
p.style.opacity = String(1);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
p.style.opacity = String(1);
|
|
118
|
+
anime.animate(p, {
|
|
119
|
+
strokeDashoffset: [len, 0],
|
|
120
|
+
duration: 800,
|
|
121
|
+
ease: "inOutQuad",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
nodes.forEach(function (n, i) {
|
|
126
|
+
n.style.opacity = String(1);
|
|
127
|
+
if (!animate || !anime || !anime.animate) {
|
|
128
|
+
n.style.transform = "scale(1)";
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
anime.animate(n, {
|
|
132
|
+
scale: [0, 1],
|
|
133
|
+
duration: 600,
|
|
134
|
+
delay: 300 + i * 120,
|
|
135
|
+
ease: "spring(1, 80, 11, 0)",
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hideBranch(key: string) {
|
|
141
|
+
const paths = (pathRefs.current[key] || []).filter(Boolean) as SVGPathElement[];
|
|
142
|
+
const nodes = (nodeRefs.current[key] || []).filter(Boolean) as SVGGElement[];
|
|
143
|
+
paths.forEach(function (p) {
|
|
144
|
+
const len = p.getTotalLength ? p.getTotalLength() : 0;
|
|
145
|
+
p.style.strokeDasharray = String(len);
|
|
146
|
+
p.style.strokeDashoffset = String(len);
|
|
147
|
+
p.style.opacity = String(0);
|
|
148
|
+
});
|
|
149
|
+
nodes.forEach(function (n) {
|
|
150
|
+
n.style.opacity = String(0);
|
|
151
|
+
n.style.transform = "scale(0)";
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function pulseAll() {
|
|
156
|
+
const anime = (window as any).anime;
|
|
157
|
+
if (!anime || !anime.animate) return;
|
|
158
|
+
const allPaths: SVGPathElement[] = [];
|
|
159
|
+
["data", "causal", "fault"].forEach(function (k) {
|
|
160
|
+
(pathRefs.current[k] || []).filter(Boolean).forEach(function (p) { allPaths.push(p as SVGPathElement); });
|
|
161
|
+
});
|
|
162
|
+
if (allPaths.length) {
|
|
163
|
+
anime.animate(allPaths, {
|
|
164
|
+
opacity: [1, 0.4, 1],
|
|
165
|
+
duration: 1200,
|
|
166
|
+
ease: "inOutSine",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (glowRef.current) {
|
|
170
|
+
anime.animate(glowRef.current, {
|
|
171
|
+
opacity: [0, 0.55, 0.3],
|
|
172
|
+
scale: [1, 1.25, 1.15],
|
|
173
|
+
duration: 1400,
|
|
174
|
+
ease: "inOutSine",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
React.useEffect(function () {
|
|
180
|
+
const id = requestAnimationFrame(function () {
|
|
181
|
+
const played = playedRef.current;
|
|
182
|
+
|
|
183
|
+
// Central node (step 0).
|
|
184
|
+
if (step >= 0) {
|
|
185
|
+
if (instant) { showCentral(false); played.central = true; }
|
|
186
|
+
else if (!played.central) { played.central = true; showCentral(true); }
|
|
187
|
+
} else {
|
|
188
|
+
hideCentral();
|
|
189
|
+
played.central = false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Branches map to steps 1, 2, 3.
|
|
193
|
+
const order = [
|
|
194
|
+
{ key: "data", req: 1 },
|
|
195
|
+
{ key: "causal", req: 2 },
|
|
196
|
+
{ key: "fault", req: 3 },
|
|
197
|
+
];
|
|
198
|
+
order.forEach(function (b) {
|
|
199
|
+
if (step >= b.req) {
|
|
200
|
+
if (instant) { showBranch(b.key, false); played[b.key] = true; }
|
|
201
|
+
else if (!played[b.key]) { played[b.key] = true; showBranch(b.key, true); }
|
|
202
|
+
} else {
|
|
203
|
+
hideBranch(b.key);
|
|
204
|
+
played[b.key] = false;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Step 4: pulse + glow.
|
|
209
|
+
if (step >= 4 && !instant) {
|
|
210
|
+
if (!played.pulse) { played.pulse = true; pulseAll(); }
|
|
211
|
+
} else if (step < 4) {
|
|
212
|
+
played.pulse = false;
|
|
213
|
+
if (glowRef.current) glowRef.current.style.opacity = String(0);
|
|
214
|
+
} else if (instant && glowRef.current) {
|
|
215
|
+
glowRef.current.style.opacity = String(0.3);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return function () { cancelAnimationFrame(id); };
|
|
219
|
+
}, [step, instant]);
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<svg
|
|
223
|
+
viewBox={"0 0 " + W + " " + H}
|
|
224
|
+
style={{ width: "100%", height: "100%", display: "block", fontFamily: CANVAS.fontFamily, opacity: ready ? 1 : 0, transition: "opacity 0.2s ease" }}
|
|
225
|
+
>
|
|
226
|
+
<defs>
|
|
227
|
+
<filter id="conceptGlow" x="-80%" y="-80%" width="260%" height="260%">
|
|
228
|
+
<feGaussianBlur stdDeviation="12" result="b" />
|
|
229
|
+
<feMerge>
|
|
230
|
+
<feMergeNode in="b" />
|
|
231
|
+
<feMergeNode in="SourceGraphic" />
|
|
232
|
+
</feMerge>
|
|
233
|
+
</filter>
|
|
234
|
+
</defs>
|
|
235
|
+
|
|
236
|
+
{/* Connections (drawn under nodes) */}
|
|
237
|
+
{BRANCHES.map(function (br) {
|
|
238
|
+
return (
|
|
239
|
+
<g key={"paths-" + br.key}>
|
|
240
|
+
{/* central -> head */}
|
|
241
|
+
<path
|
|
242
|
+
ref={function (el) { registerPath(br.key, 0, el); }}
|
|
243
|
+
d={curvePath(CX, CY, br.head.x, br.head.y)}
|
|
244
|
+
fill="none"
|
|
245
|
+
stroke={br.palette.main}
|
|
246
|
+
strokeWidth={4}
|
|
247
|
+
strokeLinecap="round"
|
|
248
|
+
style={{ opacity: 0 }}
|
|
249
|
+
/>
|
|
250
|
+
{/* head -> leaves */}
|
|
251
|
+
{br.leaves.map(function (lf, i) {
|
|
252
|
+
return (
|
|
253
|
+
<path
|
|
254
|
+
key={i}
|
|
255
|
+
ref={function (el) { registerPath(br.key, i + 1, el); }}
|
|
256
|
+
d={curvePath(br.head.x, br.head.y, lf.x, lf.y)}
|
|
257
|
+
fill="none"
|
|
258
|
+
stroke={br.palette.main}
|
|
259
|
+
strokeWidth={3}
|
|
260
|
+
strokeLinecap="round"
|
|
261
|
+
style={{ opacity: 0 }}
|
|
262
|
+
/>
|
|
263
|
+
);
|
|
264
|
+
})}
|
|
265
|
+
</g>
|
|
266
|
+
);
|
|
267
|
+
})}
|
|
268
|
+
|
|
269
|
+
{/* Branch nodes */}
|
|
270
|
+
{BRANCHES.map(function (br) {
|
|
271
|
+
return (
|
|
272
|
+
<g key={"nodes-" + br.key}>
|
|
273
|
+
{/* head node */}
|
|
274
|
+
<g
|
|
275
|
+
ref={function (el) { registerNode(br.key, 0, el); }}
|
|
276
|
+
style={{ opacity: 0, transform: "scale(0)", transformOrigin: br.head.x + "px " + br.head.y + "px", transformBox: "fill-box" }}
|
|
277
|
+
>
|
|
278
|
+
<circle cx={br.head.x} cy={br.head.y} r={30} fill="#fff" stroke={br.palette.main} strokeWidth={4} />
|
|
279
|
+
{twoLineLabel(br.head.label).map(function (line, li, arr) {
|
|
280
|
+
const dy = (li - (arr.length - 1) / 2) * 15;
|
|
281
|
+
return (
|
|
282
|
+
<text key={li} x={br.head.x} y={br.head.y + dy} textAnchor="middle" dominantBaseline="central" fontSize={14} fontWeight={800} fill={br.palette.main}>
|
|
283
|
+
{line}
|
|
284
|
+
</text>
|
|
285
|
+
);
|
|
286
|
+
})}
|
|
287
|
+
</g>
|
|
288
|
+
{/* leaf nodes */}
|
|
289
|
+
{br.leaves.map(function (lf, i) {
|
|
290
|
+
return (
|
|
291
|
+
<g
|
|
292
|
+
key={i}
|
|
293
|
+
ref={function (el) { registerNode(br.key, i + 1, el); }}
|
|
294
|
+
style={{ opacity: 0, transform: "scale(0)", transformOrigin: lf.x + "px " + lf.y + "px", transformBox: "fill-box" }}
|
|
295
|
+
>
|
|
296
|
+
<circle cx={lf.x} cy={lf.y} r={22} fill={br.palette.leafFill} stroke={br.palette.main} strokeWidth={2} />
|
|
297
|
+
<text x={lf.x} y={lf.y} textAnchor="middle" dominantBaseline="central" fontSize={14} fontWeight={700} fill={br.palette.leafText}>
|
|
298
|
+
{lf.label}
|
|
299
|
+
</text>
|
|
300
|
+
</g>
|
|
301
|
+
);
|
|
302
|
+
})}
|
|
303
|
+
</g>
|
|
304
|
+
);
|
|
305
|
+
})}
|
|
306
|
+
|
|
307
|
+
{/* Central node glow (behind central) */}
|
|
308
|
+
<circle
|
|
309
|
+
ref={glowRef}
|
|
310
|
+
cx={CX}
|
|
311
|
+
cy={CY}
|
|
312
|
+
r={45}
|
|
313
|
+
fill="#4361ee"
|
|
314
|
+
filter="url(#conceptGlow)"
|
|
315
|
+
style={{ opacity: 0, transformOrigin: CX + "px " + CY + "px", transformBox: "fill-box" }}
|
|
316
|
+
/>
|
|
317
|
+
|
|
318
|
+
{/* Central node */}
|
|
319
|
+
<g
|
|
320
|
+
ref={centralRef}
|
|
321
|
+
style={{ opacity: 0, transform: "scale(0)", transformOrigin: CX + "px " + CY + "px", transformBox: "fill-box" }}
|
|
322
|
+
>
|
|
323
|
+
<circle cx={CX} cy={CY} r={45} fill="#4361ee" />
|
|
324
|
+
<text x={CX} y={CY - 8} textAnchor="middle" dominantBaseline="central" fontSize={15} fontWeight={900} fill="#fff">Root Cause</text>
|
|
325
|
+
<text x={CX} y={CY + 12} textAnchor="middle" dominantBaseline="central" fontSize={15} fontWeight={900} fill="#fff">Analysis</text>
|
|
326
|
+
</g>
|
|
327
|
+
</svg>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Split a head label into at most two lines on whitespace for circle fit.
|
|
332
|
+
function twoLineLabel(label: string) {
|
|
333
|
+
const parts = label.split(" ");
|
|
334
|
+
if (parts.length < 2) return [label];
|
|
335
|
+
return [parts[0], parts.slice(1).join(" ")];
|
|
336
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SlideComponentProps } from "../../../core/types.ts";
|
|
3
|
+
import { CANVAS, useRevealStep } from "../../../core/presets.ts";
|
|
4
|
+
|
|
5
|
+
const CARDS = [
|
|
6
|
+
{ label: "Users", target: 1247, suffix: "", decimals: 0, color: "#4361ee", spark: [4, 8, 6, 12, 9, 16, 14, 22] },
|
|
7
|
+
{ label: "Uptime", target: 99.9, suffix: "%", decimals: 1, color: "#3a0ca3", spark: [18, 17, 19, 18, 20, 19, 21, 22] },
|
|
8
|
+
{ label: "Latency", target: 42, suffix: "ms", decimals: 0, color: "#f72585", spark: [20, 16, 18, 12, 14, 9, 11, 7] },
|
|
9
|
+
{ label: "Requests", target: 3.2, suffix: "M", decimals: 1, color: "#4cc9f0", spark: [5, 9, 7, 13, 11, 17, 15, 21] },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function formatVal(v: number, decimals: number) {
|
|
13
|
+
const fixed = v.toFixed(decimals);
|
|
14
|
+
// Add thousands separators for integer-style counts.
|
|
15
|
+
const parts = fixed.split(".");
|
|
16
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
17
|
+
return parts.join(".");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sparkPath(points: number[], w: number, h: number) {
|
|
21
|
+
const max = Math.max.apply(null, points);
|
|
22
|
+
const min = Math.min.apply(null, points);
|
|
23
|
+
const range = max - min || 1;
|
|
24
|
+
const step = w / (points.length - 1);
|
|
25
|
+
return points.map(function (p, i) {
|
|
26
|
+
const x = i * step;
|
|
27
|
+
const y = h - ((p - min) / range) * h;
|
|
28
|
+
return (i === 0 ? "M" : "L") + x.toFixed(1) + "," + y.toFixed(1);
|
|
29
|
+
}).join(" ");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function DemoCounter({ Reveal }: SlideComponentProps) {
|
|
33
|
+
const ref = useRevealStep(Reveal);
|
|
34
|
+
const step = ref[0], ready = ref[1], instant = ref[2];
|
|
35
|
+
|
|
36
|
+
const valuesState = React.useState(CARDS.map(function () { return 0; }));
|
|
37
|
+
const values = valuesState[0], setValues = valuesState[1];
|
|
38
|
+
|
|
39
|
+
const barRefs = React.useRef<(HTMLDivElement | null)[]>([]);
|
|
40
|
+
const sparkRefs = React.useRef<(SVGPathElement | null)[]>([]);
|
|
41
|
+
|
|
42
|
+
// Step 1: count numbers from 0 to target.
|
|
43
|
+
React.useEffect(function () {
|
|
44
|
+
const anime = (window as any).anime;
|
|
45
|
+
const visible = step >= 1;
|
|
46
|
+
if (!visible) {
|
|
47
|
+
setValues(CARDS.map(function () { return 0; }));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!anime || instant) {
|
|
51
|
+
setValues(CARDS.map(function (c) { return c.target; }));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const counters = CARDS.map(function () { return { v: 0 }; });
|
|
55
|
+
const anims = counters.map(function (counter, i) {
|
|
56
|
+
return anime.animate(counter, {
|
|
57
|
+
v: CARDS[i].target,
|
|
58
|
+
duration: 1600,
|
|
59
|
+
easing: "outCubic",
|
|
60
|
+
onUpdate: function () {
|
|
61
|
+
setValues(function (prev) {
|
|
62
|
+
const next = prev.slice();
|
|
63
|
+
next[i] = counter.v;
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return function () { anims.forEach(function (a) { a.pause && a.pause(); }); };
|
|
70
|
+
}, [step, instant]);
|
|
71
|
+
|
|
72
|
+
// Step 2: progress bars fill.
|
|
73
|
+
React.useEffect(function () {
|
|
74
|
+
const anime = (window as any).anime;
|
|
75
|
+
const visible = step >= 2;
|
|
76
|
+
const fills = barRefs.current.filter(Boolean) as HTMLDivElement[];
|
|
77
|
+
if (!fills.length) return;
|
|
78
|
+
if (!visible) {
|
|
79
|
+
fills.forEach(function (el) { el.style.width = "0%"; });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const targets = [78, 99, 58, 84];
|
|
83
|
+
if (!anime || instant) {
|
|
84
|
+
fills.forEach(function (el, i) { el.style.width = targets[i] + "%"; });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
anime.animate(fills, {
|
|
88
|
+
width: function (el: any, i: number) { return [0 + "%", targets[i] + "%"]; },
|
|
89
|
+
duration: 1100,
|
|
90
|
+
easing: "inOutQuad",
|
|
91
|
+
delay: anime.stagger(120),
|
|
92
|
+
});
|
|
93
|
+
}, [step, instant]);
|
|
94
|
+
|
|
95
|
+
// Step 3: sparklines draw via strokeDashoffset.
|
|
96
|
+
React.useEffect(function () {
|
|
97
|
+
const anime = (window as any).anime;
|
|
98
|
+
const visible = step >= 3;
|
|
99
|
+
const paths = sparkRefs.current.filter(Boolean) as SVGPathElement[];
|
|
100
|
+
if (!paths.length) return;
|
|
101
|
+
paths.forEach(function (el) {
|
|
102
|
+
const len = el.getTotalLength();
|
|
103
|
+
el.style.strokeDasharray = String(len);
|
|
104
|
+
el.style.strokeDashoffset = String(visible && (!anime || instant) ? 0 : len);
|
|
105
|
+
});
|
|
106
|
+
if (!visible || !anime || instant) return;
|
|
107
|
+
anime.animate(paths, {
|
|
108
|
+
strokeDashoffset: [function (el: SVGPathElement) { return el.getTotalLength(); }, 0],
|
|
109
|
+
duration: 1200,
|
|
110
|
+
easing: "outCubic",
|
|
111
|
+
delay: anime.stagger(150),
|
|
112
|
+
});
|
|
113
|
+
}, [step, instant]);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
style={{
|
|
118
|
+
width: "100%",
|
|
119
|
+
height: "100%",
|
|
120
|
+
display: "flex",
|
|
121
|
+
flexDirection: "column",
|
|
122
|
+
justifyContent: "center",
|
|
123
|
+
gap: 28,
|
|
124
|
+
padding: "20px 40px",
|
|
125
|
+
boxSizing: "border-box",
|
|
126
|
+
fontFamily: CANVAS.fontFamily,
|
|
127
|
+
opacity: ready ? 1 : 0,
|
|
128
|
+
transition: "opacity 0.2s ease",
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<div style={{ display: "flex", gap: 24, justifyContent: "center" }}>
|
|
132
|
+
{CARDS.map(function (card, i) {
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
key={i}
|
|
136
|
+
style={{
|
|
137
|
+
flex: "1 1 0",
|
|
138
|
+
maxWidth: 250,
|
|
139
|
+
background: "#fff",
|
|
140
|
+
borderRadius: 14,
|
|
141
|
+
borderTop: "5px solid " + card.color,
|
|
142
|
+
boxShadow: "0 8px 24px rgba(49,49,49,0.12)",
|
|
143
|
+
padding: "24px 22px",
|
|
144
|
+
boxSizing: "border-box",
|
|
145
|
+
opacity: step >= 0 ? 1 : 0,
|
|
146
|
+
transform: step >= 0 ? "translateY(0)" : "translateY(20px)",
|
|
147
|
+
transition: instant ? "none" : "opacity 0.45s ease " + (i * 60) + "ms, transform 0.45s ease " + (i * 60) + "ms",
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
<div style={{ fontSize: 40, fontWeight: 900, color: card.color, lineHeight: 1 }}>
|
|
151
|
+
{formatVal(values[i], card.decimals) + card.suffix}
|
|
152
|
+
</div>
|
|
153
|
+
<div style={{ fontSize: 16, fontWeight: 700, color: "#767676", marginTop: 8 }}>
|
|
154
|
+
{card.label}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div
|
|
158
|
+
style={{
|
|
159
|
+
marginTop: 16,
|
|
160
|
+
height: 8,
|
|
161
|
+
borderRadius: 4,
|
|
162
|
+
background: "#eee",
|
|
163
|
+
overflow: "hidden",
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<div
|
|
167
|
+
ref={function (el) { barRefs.current[i] = el; }}
|
|
168
|
+
style={{ height: "100%", width: "0%", background: card.color, borderRadius: 4 }}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<svg
|
|
173
|
+
viewBox="0 0 200 50"
|
|
174
|
+
preserveAspectRatio="none"
|
|
175
|
+
style={{ width: "100%", height: 42, marginTop: 14, display: "block" }}
|
|
176
|
+
>
|
|
177
|
+
<path
|
|
178
|
+
ref={function (el) { sparkRefs.current[i] = el; }}
|
|
179
|
+
d={sparkPath(card.spark, 200, 44)}
|
|
180
|
+
fill="none"
|
|
181
|
+
stroke={card.color}
|
|
182
|
+
strokeWidth={3}
|
|
183
|
+
strokeLinecap="round"
|
|
184
|
+
strokeLinejoin="round"
|
|
185
|
+
transform="translate(0,3)"
|
|
186
|
+
/>
|
|
187
|
+
</svg>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
})}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|