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,188 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CANVAS, useRevealStep } from "../../../core/presets.ts";
|
|
3
|
+
import type { SlideComponentProps } from "../../../core/types.ts";
|
|
4
|
+
|
|
5
|
+
const TITLE = "Component Showcase";
|
|
6
|
+
const SUBTITLE = "A gallery of React + Reveal.js techniques";
|
|
7
|
+
|
|
8
|
+
// Decorative dots scattered around the edges of the canvas.
|
|
9
|
+
const DOTS = [
|
|
10
|
+
{ x: "8%", y: "14%", c: "#4361ee", r: 8 },
|
|
11
|
+
{ x: "18%", y: "78%", c: "#f72585", r: 6 },
|
|
12
|
+
{ x: "88%", y: "22%", c: "#4cc9f0", r: 7 },
|
|
13
|
+
{ x: "92%", y: "70%", c: "#3a0ca3", r: 9 },
|
|
14
|
+
{ x: "30%", y: "10%", c: "#4cc9f0", r: 5 },
|
|
15
|
+
{ x: "72%", y: "86%", c: "#4361ee", r: 7 },
|
|
16
|
+
{ x: "6%", y: "48%", c: "#f72585", r: 6 },
|
|
17
|
+
{ x: "95%", y: "46%", c: "#4361ee", r: 5 },
|
|
18
|
+
{ x: "50%", y: "8%", c: "#3a0ca3", r: 6 },
|
|
19
|
+
{ x: "60%", y: "90%", c: "#4cc9f0", r: 8 },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export default function DemoCover({ Reveal }: SlideComponentProps) {
|
|
23
|
+
const ref = useRevealStep(Reveal);
|
|
24
|
+
const step = ref[0], ready = ref[1], instant = ref[2];
|
|
25
|
+
|
|
26
|
+
const titleRef = React.useRef<HTMLHeadingElement>(null);
|
|
27
|
+
const subtitleRef = React.useRef<HTMLParagraphElement>(null);
|
|
28
|
+
const dotsRef = React.useRef<HTMLDivElement>(null);
|
|
29
|
+
|
|
30
|
+
// Step 0: stagger letters of the title.
|
|
31
|
+
React.useEffect(function () {
|
|
32
|
+
const anime = (window as any).anime;
|
|
33
|
+
if (!anime || !titleRef.current) return;
|
|
34
|
+
const letters = titleRef.current.querySelectorAll(".cover-letter");
|
|
35
|
+
if (step < 0) {
|
|
36
|
+
letters.forEach(function (el: any) { el.style.opacity = 0; });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (instant) {
|
|
40
|
+
letters.forEach(function (el: any) {
|
|
41
|
+
el.style.opacity = 1;
|
|
42
|
+
el.style.transform = "none";
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
anime.animate(letters, {
|
|
47
|
+
opacity: [0, 1],
|
|
48
|
+
translateY: [40, 0],
|
|
49
|
+
scale: [0.6, 1],
|
|
50
|
+
easing: "outElastic(1, 0.6)",
|
|
51
|
+
duration: 1200,
|
|
52
|
+
delay: anime.stagger(45),
|
|
53
|
+
});
|
|
54
|
+
}, [step, instant]);
|
|
55
|
+
|
|
56
|
+
// Step 1: subtitle fades in.
|
|
57
|
+
React.useEffect(function () {
|
|
58
|
+
const anime = (window as any).anime;
|
|
59
|
+
if (!anime || !subtitleRef.current) return;
|
|
60
|
+
const visible = step >= 1;
|
|
61
|
+
if (instant) {
|
|
62
|
+
subtitleRef.current.style.opacity = String(visible ? 1 : 0);
|
|
63
|
+
subtitleRef.current.style.transform = "none";
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (visible) {
|
|
67
|
+
anime.animate(subtitleRef.current, {
|
|
68
|
+
opacity: [0, 1],
|
|
69
|
+
translateY: [16, 0],
|
|
70
|
+
easing: "outCubic",
|
|
71
|
+
duration: 700,
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
subtitleRef.current.style.opacity = String(0);
|
|
75
|
+
}
|
|
76
|
+
}, [step, instant]);
|
|
77
|
+
|
|
78
|
+
// Step 2: decorative dots pop in with stagger.
|
|
79
|
+
React.useEffect(function () {
|
|
80
|
+
const anime = (window as any).anime;
|
|
81
|
+
if (!anime || !dotsRef.current) return;
|
|
82
|
+
const dots = dotsRef.current.querySelectorAll(".cover-dot");
|
|
83
|
+
const visible = step >= 2;
|
|
84
|
+
if (instant) {
|
|
85
|
+
dots.forEach(function (el: any) {
|
|
86
|
+
el.style.opacity = visible ? 1 : 0;
|
|
87
|
+
el.style.transform = "none";
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (visible) {
|
|
92
|
+
anime.animate(dots, {
|
|
93
|
+
opacity: [0, 1],
|
|
94
|
+
scale: [0, 1],
|
|
95
|
+
easing: "spring(1, 80, 10, 0)",
|
|
96
|
+
duration: 900,
|
|
97
|
+
delay: anime.stagger(80),
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
dots.forEach(function (el: any) { el.style.opacity = 0; });
|
|
101
|
+
}
|
|
102
|
+
}, [step, instant]);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
style={{
|
|
107
|
+
position: "relative",
|
|
108
|
+
width: "100%",
|
|
109
|
+
height: "100%",
|
|
110
|
+
display: "flex",
|
|
111
|
+
flexDirection: "column",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
justifyContent: "center",
|
|
114
|
+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
|
|
115
|
+
fontFamily: CANVAS.fontFamily,
|
|
116
|
+
color: "#fff",
|
|
117
|
+
overflow: "hidden",
|
|
118
|
+
opacity: ready ? 1 : 0,
|
|
119
|
+
transition: "opacity 0.2s ease",
|
|
120
|
+
borderRadius: 8,
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<div ref={dotsRef} style={{ position: "absolute", inset: 0, pointerEvents: "none" }}>
|
|
124
|
+
{DOTS.map(function (d, i) {
|
|
125
|
+
return (
|
|
126
|
+
<span
|
|
127
|
+
key={i}
|
|
128
|
+
className="cover-dot"
|
|
129
|
+
style={{
|
|
130
|
+
position: "absolute",
|
|
131
|
+
left: d.x,
|
|
132
|
+
top: d.y,
|
|
133
|
+
width: d.r * 2,
|
|
134
|
+
height: d.r * 2,
|
|
135
|
+
borderRadius: "50%",
|
|
136
|
+
background: d.c,
|
|
137
|
+
opacity: 0,
|
|
138
|
+
boxShadow: "0 0 16px " + d.c,
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<h1
|
|
146
|
+
ref={titleRef}
|
|
147
|
+
style={{
|
|
148
|
+
fontSize: 72,
|
|
149
|
+
fontWeight: 900,
|
|
150
|
+
margin: 0,
|
|
151
|
+
letterSpacing: "0.02em",
|
|
152
|
+
textAlign: "center",
|
|
153
|
+
zIndex: 1,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{TITLE.split("").map(function (ch, i) {
|
|
157
|
+
return (
|
|
158
|
+
<span
|
|
159
|
+
key={i}
|
|
160
|
+
className="cover-letter"
|
|
161
|
+
style={{
|
|
162
|
+
display: "inline-block",
|
|
163
|
+
opacity: 0,
|
|
164
|
+
whiteSpace: "pre",
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{ch === " " ? " " : ch}
|
|
168
|
+
</span>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</h1>
|
|
172
|
+
|
|
173
|
+
<p
|
|
174
|
+
ref={subtitleRef}
|
|
175
|
+
style={{
|
|
176
|
+
fontSize: 26,
|
|
177
|
+
fontWeight: 500,
|
|
178
|
+
marginTop: 24,
|
|
179
|
+
color: "#c9c9e8",
|
|
180
|
+
opacity: 0,
|
|
181
|
+
zIndex: 1,
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{SUBTITLE}
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CANVAS, useRevealStep } from "../../../core/presets.ts";
|
|
3
|
+
import type { SlideComponentProps } from "../../../core/types.ts";
|
|
4
|
+
|
|
5
|
+
const SERIES = [62, 58, 65, 49, 53, 41, 44, 33, 29, 22];
|
|
6
|
+
|
|
7
|
+
const METRICS = [
|
|
8
|
+
{ name: "MTTR", value: 4.2, suffix: "min", decimals: 1, dot: "#3fb950",
|
|
9
|
+
trend: "down", trendColor: "#3fb950", spark: [9, 8, 7, 6, 5, 4], best: false },
|
|
10
|
+
{ name: "Precision", value: 94.7, suffix: "%", decimals: 1, dot: "#58a6ff",
|
|
11
|
+
trend: "up", trendColor: "#3fb950", spark: [80, 84, 86, 90, 92, 95], best: true },
|
|
12
|
+
{ name: "Coverage", value: 87.3, suffix: "%", decimals: 1, dot: "#bc8cff",
|
|
13
|
+
trend: "flat", trendColor: "#8b949e", spark: [85, 86, 87, 86, 87, 87], best: false },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function pathFor(vals: number[], w: number, h: number, pad: number) {
|
|
17
|
+
const max = Math.max.apply(null, vals);
|
|
18
|
+
const min = Math.min.apply(null, vals);
|
|
19
|
+
const range = max - min || 1;
|
|
20
|
+
const n = vals.length;
|
|
21
|
+
return vals.map(function (v, i) {
|
|
22
|
+
const x = pad + (i / (n - 1)) * (w - 2 * pad);
|
|
23
|
+
const y = pad + (1 - (v - min) / range) * (h - 2 * pad);
|
|
24
|
+
return (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1);
|
|
25
|
+
}).join(" ");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Sparkline({ vals, color }: { vals: number[]; color: string }) {
|
|
29
|
+
const w = 40, h = 16;
|
|
30
|
+
return (
|
|
31
|
+
<svg width={w} height={h} style={{ display: "block" }}>
|
|
32
|
+
<path d={pathFor(vals, w, h, 2)} fill="none" stroke={color}
|
|
33
|
+
strokeWidth={1.6} strokeLinecap="round" strokeLinejoin="round" />
|
|
34
|
+
</svg>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function TrendArrow({ trend, color }: { trend: string; color: string }) {
|
|
39
|
+
const sym = trend === "down" ? "↓" : trend === "up" ? "↑" : "→";
|
|
40
|
+
return <span style={{ color: color, fontSize: 18, fontWeight: 700 }}>{sym}</span>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function useCountUp(target: number, active: boolean, instant: boolean) {
|
|
44
|
+
const st = React.useState(0);
|
|
45
|
+
const val = st[0], setVal = st[1];
|
|
46
|
+
React.useEffect(function () {
|
|
47
|
+
if (!active) { setVal(0); return; }
|
|
48
|
+
if (instant) { setVal(target); return; }
|
|
49
|
+
let start: number | null = null;
|
|
50
|
+
const dur = 800;
|
|
51
|
+
let raf: number;
|
|
52
|
+
function frame(t: number) {
|
|
53
|
+
if (start === null) start = t;
|
|
54
|
+
const p = Math.min(1, (t - start) / dur);
|
|
55
|
+
const eased = 1 - Math.pow(1 - p, 3);
|
|
56
|
+
setVal(target * eased);
|
|
57
|
+
if (p < 1) raf = requestAnimationFrame(frame);
|
|
58
|
+
}
|
|
59
|
+
raf = requestAnimationFrame(frame);
|
|
60
|
+
return function () { if (raf) cancelAnimationFrame(raf); };
|
|
61
|
+
}, [active, instant, target]);
|
|
62
|
+
return val;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function MetricRow({ m, step, instant, idx }: { m: any; step: number; instant: boolean; idx: number }) {
|
|
66
|
+
const active = step >= 1;
|
|
67
|
+
const val = useCountUp(m.value, active, instant);
|
|
68
|
+
const display = active ? val.toFixed(m.decimals) : (0).toFixed(m.decimals);
|
|
69
|
+
const pulse = step >= 2 && m.best;
|
|
70
|
+
return (
|
|
71
|
+
<div style={{
|
|
72
|
+
display: "flex", alignItems: "center", gap: 16,
|
|
73
|
+
padding: "12px 18px", borderRadius: 10,
|
|
74
|
+
boxShadow: pulse ? "0 0 0 1px rgba(67,97,238,0.3), 0 0 20px rgba(67,97,238,0.1)" : "0 1px 4px rgba(0,0,0,0.04)",
|
|
75
|
+
background: pulse ? "rgba(67,97,238,0.05)" : "#fafafa",
|
|
76
|
+
transition: instant ? "none" : "box-shadow 0.4s ease, background 0.4s ease",
|
|
77
|
+
}}>
|
|
78
|
+
<span style={{
|
|
79
|
+
width: 10, height: 10, borderRadius: "50%", background: m.dot,
|
|
80
|
+
boxShadow: "0 0 10px " + m.dot, flex: "0 0 auto",
|
|
81
|
+
}} />
|
|
82
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
83
|
+
<div style={{ color: "#888", fontSize: 14, fontWeight: 600 }}>{m.name}</div>
|
|
84
|
+
<div style={{ color: "#1a1a1a", fontSize: 36, fontWeight: 800, lineHeight: 1.1 }}>
|
|
85
|
+
{display}<span style={{ fontSize: 18, fontWeight: 600, color: "#999", marginLeft: 4 }}>{m.suffix}</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div style={{ display: "flex", alignItems: "center", gap: 10, flex: "0 0 auto" }}>
|
|
89
|
+
<Sparkline vals={m.spark} color={m.trendColor} />
|
|
90
|
+
<TrendArrow trend={m.trend} color={m.trendColor} />
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default function DemoDarkDashboard({ Reveal }: SlideComponentProps) {
|
|
97
|
+
const ref = useRevealStep(Reveal);
|
|
98
|
+
const step = ref[0], ready = ref[1], instant = ref[2];
|
|
99
|
+
|
|
100
|
+
const CW = 600, CH = 360;
|
|
101
|
+
const pad = 30;
|
|
102
|
+
const linePath = pathFor(SERIES, CW, CH, pad);
|
|
103
|
+
const areaPath = linePath + " L" + (CW - pad) + " " + (CH - pad) + " L" + pad + " " + (CH - pad) + " Z";
|
|
104
|
+
|
|
105
|
+
const chartShown = step >= 0;
|
|
106
|
+
const n = SERIES.length;
|
|
107
|
+
const max = Math.max.apply(null, SERIES), min = Math.min.apply(null, SERIES);
|
|
108
|
+
const range = max - min || 1;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div style={{
|
|
112
|
+
width: "100%", height: "100%", boxSizing: "border-box",
|
|
113
|
+
background: "#ffffff",
|
|
114
|
+
display: "flex", alignItems: "stretch", gap: 0,
|
|
115
|
+
padding: "36px 44px", fontFamily: CANVAS.fontFamily,
|
|
116
|
+
opacity: ready ? 1 : 0, transition: "opacity 0.2s ease",
|
|
117
|
+
}}>
|
|
118
|
+
{/* Left: area chart 55% */}
|
|
119
|
+
<div style={{ flex: "0 0 55%", display: "flex", flexDirection: "column", justifyContent: "center" }}>
|
|
120
|
+
<div style={{ color: "#666", fontSize: 15, fontWeight: 600, marginBottom: 8, letterSpacing: 0.3 }}>
|
|
121
|
+
Detection Time
|
|
122
|
+
</div>
|
|
123
|
+
<svg viewBox={"0 0 " + CW + " " + CH} style={{ width: "100%", height: "auto", display: "block" }}>
|
|
124
|
+
<defs>
|
|
125
|
+
<linearGradient id="ddAreaFill" x1="0" y1="0" x2="0" y2="1">
|
|
126
|
+
<stop offset="0%" stopColor="#4361ee" stopOpacity="0.25" />
|
|
127
|
+
<stop offset="100%" stopColor="#4361ee" stopOpacity="0.02" />
|
|
128
|
+
</linearGradient>
|
|
129
|
+
<linearGradient id="ddLine" x1="0" y1="0" x2="1" y2="0">
|
|
130
|
+
<stop offset="0%" stopColor="#4361ee" />
|
|
131
|
+
<stop offset="100%" stopColor="#7c3aed" />
|
|
132
|
+
</linearGradient>
|
|
133
|
+
</defs>
|
|
134
|
+
<g style={{
|
|
135
|
+
opacity: chartShown ? 1 : 0,
|
|
136
|
+
clipPath: "inset(0 " + (chartShown ? "0%" : "100%") + " 0 0)",
|
|
137
|
+
transition: instant ? "none" : "clip-path 0.9s ease, opacity 0.3s ease",
|
|
138
|
+
}}>
|
|
139
|
+
<path d={areaPath} fill="url(#ddAreaFill)" />
|
|
140
|
+
<path d={linePath} fill="none" stroke="url(#ddLine)" strokeWidth={3}
|
|
141
|
+
strokeLinecap="round" strokeLinejoin="round" />
|
|
142
|
+
{SERIES.map(function (v, i) {
|
|
143
|
+
const x = pad + (i / (n - 1)) * (CW - 2 * pad);
|
|
144
|
+
const y = pad + (1 - (v - min) / range) * (CH - 2 * pad);
|
|
145
|
+
return (
|
|
146
|
+
<g key={i}>
|
|
147
|
+
<circle cx={x} cy={y} r={5} fill="#fff" stroke="#4361ee" strokeWidth={2.5} />
|
|
148
|
+
</g>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
</g>
|
|
152
|
+
</svg>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Right: metrics 45% */}
|
|
156
|
+
<div style={{
|
|
157
|
+
flex: "0 0 45%", display: "flex", flexDirection: "column",
|
|
158
|
+
justifyContent: "center", gap: 14, paddingLeft: 36,
|
|
159
|
+
}}>
|
|
160
|
+
{METRICS.map(function (m, i) {
|
|
161
|
+
return <MetricRow key={m.name} m={m} step={step} instant={instant} idx={i} />;
|
|
162
|
+
})}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CANVAS, vis, useRevealStep } from "../../../core/presets.ts";
|
|
3
|
+
import type { SlideComponentProps } from "../../../core/types.ts";
|
|
4
|
+
|
|
5
|
+
const anime = (typeof window !== "undefined") ? (window as any).anime : null;
|
|
6
|
+
|
|
7
|
+
const COLUMNS = [
|
|
8
|
+
{ key: "top1", label: "Top-1", base: [37, 99, 235] }, // blue
|
|
9
|
+
{ key: "top3", label: "Top-3", base: [147, 51, 234] }, // purple
|
|
10
|
+
{ key: "top5", label: "Top-5", base: [219, 39, 119] }, // pink
|
|
11
|
+
{ key: "speed", label: "Speed", base: [5, 150, 105] }, // green
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// rows: [Top-1, Top-3, Top-5, Speed("Nx")]
|
|
15
|
+
const ROWS = [
|
|
16
|
+
{ name: "SRCA", vals: [94.2, 97.1, 98.8], speed: 4.2 },
|
|
17
|
+
{ name: "MicroRCA", vals: [78.3, 85.2, 91.4], speed: 8.7 },
|
|
18
|
+
{ name: "CloudRCA", vals: [72.1, 80.5, 88.2], speed: 12.3 },
|
|
19
|
+
{ name: "DiagFusion", vals: [68.5, 76.3, 84.1], speed: 15.8 },
|
|
20
|
+
{ name: "Sage", vals: [58.4, 67.2, 75.6], speed: 22.1 },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// speed: lower is better -> normalize inversely for bar width
|
|
24
|
+
const SPEED_MAX = 22.1;
|
|
25
|
+
const SPEED_MIN = 4.2;
|
|
26
|
+
function speedFrac(s: number) {
|
|
27
|
+
// best (smallest) -> widest
|
|
28
|
+
const f = (SPEED_MAX - s) / (SPEED_MAX - SPEED_MIN);
|
|
29
|
+
return 0.25 + f * 0.75; // keep a visible minimum
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fillColor(base: number[], frac: number) {
|
|
33
|
+
// higher frac -> more saturated/opaque
|
|
34
|
+
const alpha = 0.35 + frac * 0.6;
|
|
35
|
+
return "rgba(" + base[0] + "," + base[1] + "," + base[2] + "," + alpha.toFixed(3) + ")";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function DemoEvalMatrix(props: SlideComponentProps) {
|
|
39
|
+
const Reveal = props.Reveal;
|
|
40
|
+
const ref = useRevealStep(Reveal);
|
|
41
|
+
const step = ref[0], ready = ref[1], instant = ref[2];
|
|
42
|
+
|
|
43
|
+
// progress 0..1 per cell, indexed [row][col]
|
|
44
|
+
const initCells = function (): number[][] {
|
|
45
|
+
return ROWS.map(function () { return COLUMNS.map(function () { return 0; }); });
|
|
46
|
+
};
|
|
47
|
+
const cellsRef = React.useState(initCells);
|
|
48
|
+
const cells = cellsRef[0], setCells = cellsRef[1];
|
|
49
|
+
|
|
50
|
+
const playedRef = React.useRef(false);
|
|
51
|
+
|
|
52
|
+
React.useEffect(function () {
|
|
53
|
+
if (step >= 1) {
|
|
54
|
+
if (playedRef.current) return;
|
|
55
|
+
playedRef.current = true;
|
|
56
|
+
|
|
57
|
+
const setOne = function (r: number, c: number, v: number) {
|
|
58
|
+
setCells(function (prev) {
|
|
59
|
+
const next = prev.map(function (row) { return row.slice(); });
|
|
60
|
+
next[r][c] = v;
|
|
61
|
+
return next;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (!anime || !anime.animate) {
|
|
66
|
+
ROWS.forEach(function (_, r) {
|
|
67
|
+
COLUMNS.forEach(function (_, c) { setOne(r, c, 1); });
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ROWS.forEach(function (_, r) {
|
|
73
|
+
COLUMNS.forEach(function (_, c) {
|
|
74
|
+
const obj = { v: 0 };
|
|
75
|
+
const delay = (r + c) * 70; // diagonal stagger top-left -> bottom-right
|
|
76
|
+
anime.animate(obj, {
|
|
77
|
+
v: 1,
|
|
78
|
+
duration: 700,
|
|
79
|
+
delay: delay,
|
|
80
|
+
ease: "outCubic",
|
|
81
|
+
update: function () { setOne(r, c, obj.v); },
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
playedRef.current = false;
|
|
87
|
+
setCells(initCells());
|
|
88
|
+
}
|
|
89
|
+
}, [step]);
|
|
90
|
+
|
|
91
|
+
const nameColW = 130;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div style={{
|
|
95
|
+
width: "100%", height: "100%",
|
|
96
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
97
|
+
fontFamily: CANVAS.fontFamily,
|
|
98
|
+
opacity: ready ? 1 : 0,
|
|
99
|
+
transition: "opacity 0.2s ease",
|
|
100
|
+
}}>
|
|
101
|
+
<div style={{ width: "100%", maxWidth: 1100 }}>
|
|
102
|
+
{/* column headers */}
|
|
103
|
+
<div style={Object.assign({
|
|
104
|
+
display: "grid",
|
|
105
|
+
gridTemplateColumns: nameColW + "px repeat(4, 1fr)",
|
|
106
|
+
gap: 18, alignItems: "center", marginBottom: 18,
|
|
107
|
+
}, vis(step, 0, instant))}>
|
|
108
|
+
<div></div>
|
|
109
|
+
{COLUMNS.map(function (col) {
|
|
110
|
+
const highlight = step >= 3 && col.key === "top1";
|
|
111
|
+
return (
|
|
112
|
+
<div key={col.key} style={{
|
|
113
|
+
textAlign: "center", fontSize: 14, fontWeight: 800,
|
|
114
|
+
color: "rgb(" + col.base[0] + "," + col.base[1] + "," + col.base[2] + ")",
|
|
115
|
+
textTransform: "uppercase", letterSpacing: 0.5,
|
|
116
|
+
background: highlight ? fillColor(col.base, 0.0) : "transparent",
|
|
117
|
+
borderRadius: 8,
|
|
118
|
+
padding: highlight ? "4px 0" : "4px 0",
|
|
119
|
+
boxShadow: highlight ? "0 0 0 2px rgba(" + col.base.join(",") + ",0.5)" : "none",
|
|
120
|
+
transition: instant ? "none" : "box-shadow 0.4s ease",
|
|
121
|
+
}}>{col.label}</div>
|
|
122
|
+
);
|
|
123
|
+
})}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* rows */}
|
|
127
|
+
{ROWS.map(function (row, r) {
|
|
128
|
+
const isSrca = r === 0;
|
|
129
|
+
const dim = step >= 2 && !isSrca;
|
|
130
|
+
const highlightRow = step >= 2 && isSrca;
|
|
131
|
+
return (
|
|
132
|
+
<div key={row.name} style={{
|
|
133
|
+
display: "grid",
|
|
134
|
+
gridTemplateColumns: nameColW + "px repeat(4, 1fr)",
|
|
135
|
+
gap: 18, alignItems: "center", marginBottom: 14,
|
|
136
|
+
opacity: (step >= 0 ? 1 : 0) * (dim ? 0.45 : 1),
|
|
137
|
+
transition: instant ? "none" : "opacity 0.4s ease",
|
|
138
|
+
paddingLeft: 10,
|
|
139
|
+
borderLeft: highlightRow ? "5px solid #2563eb" : "5px solid transparent",
|
|
140
|
+
}}>
|
|
141
|
+
<div style={{
|
|
142
|
+
fontSize: 16, fontWeight: 800,
|
|
143
|
+
color: highlightRow ? "#1d4ed8" : "#374151",
|
|
144
|
+
textAlign: "left",
|
|
145
|
+
}}>{row.name}</div>
|
|
146
|
+
|
|
147
|
+
{COLUMNS.map(function (col, c) {
|
|
148
|
+
let raw: number, frac: number, text: string;
|
|
149
|
+
if (col.key === "speed") {
|
|
150
|
+
raw = row.speed;
|
|
151
|
+
frac = speedFrac(raw);
|
|
152
|
+
text = raw.toFixed(1) + "x";
|
|
153
|
+
} else {
|
|
154
|
+
raw = row.vals[c];
|
|
155
|
+
frac = raw / 100;
|
|
156
|
+
text = raw.toFixed(1);
|
|
157
|
+
}
|
|
158
|
+
const prog = (cells[r] && cells[r][c]) || 0;
|
|
159
|
+
const widthPct = Math.max(0, Math.min(1, frac)) * 100 * prog;
|
|
160
|
+
const colHi = step >= 3 && col.key === "top1";
|
|
161
|
+
return (
|
|
162
|
+
<div key={col.key} style={{
|
|
163
|
+
position: "relative", height: 28,
|
|
164
|
+
background: "#f3f4f6", borderRadius: 14,
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
boxShadow: colHi ? "0 0 0 2px rgba(" + col.base.join(",") + ",0.55)" : "none",
|
|
167
|
+
transition: instant ? "none" : "box-shadow 0.4s ease",
|
|
168
|
+
}}>
|
|
169
|
+
<div style={{
|
|
170
|
+
position: "absolute", top: 0, left: 0, bottom: 0,
|
|
171
|
+
width: widthPct + "%",
|
|
172
|
+
background: fillColor(col.base, frac),
|
|
173
|
+
borderRadius: 14,
|
|
174
|
+
}}></div>
|
|
175
|
+
<div style={{
|
|
176
|
+
position: "absolute", top: 0, left: 0, right: 0, bottom: 0,
|
|
177
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
178
|
+
fontSize: 14, fontWeight: 800,
|
|
179
|
+
color: prog > 0.01 ? "#1f2937" : "#9ca3af",
|
|
180
|
+
fontVariantNumeric: "tabular-nums",
|
|
181
|
+
}}>{text}</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
})}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|