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,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
+ }