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,46 @@
1
+ export const C = {
2
+ white: "#FFFFFF",
3
+ black: "#000000",
4
+ body: "#313131",
5
+ muted: "#767676",
6
+ faint: "#F7F7F7",
7
+ line: "#BFBFBF",
8
+ soft: "#E7E6E6",
9
+ red: "#C00000",
10
+ redSoft: "#FFF3F3",
11
+ blue: "#00A6D6",
12
+ green: "#76B82A",
13
+ yellow: "#FFC000",
14
+ teal: "#1B556B",
15
+ } as const;
16
+
17
+ export function esc(value: string | number): string {
18
+ return String(value)
19
+ .replaceAll("&", "&")
20
+ .replaceAll("<", "&lt;")
21
+ .replaceAll(">", "&gt;")
22
+ .replaceAll('"', "&quot;");
23
+ }
24
+
25
+ const UNITLESS = new Set([
26
+ "opacity", "z-index", "font-weight", "line-height", "flex-grow",
27
+ "flex-shrink", "order", "zoom", "column-count", "fill-opacity",
28
+ "stroke-opacity", "stroke-miterlimit",
29
+ ]);
30
+
31
+ export function style(obj: Record<string, string | number | undefined | null>): string {
32
+ return Object.entries(obj)
33
+ .filter((e): e is [string, string | number] => e[1] !== undefined && e[1] !== null)
34
+ .map(([key, value]) => {
35
+ if (typeof value === "number" && !UNITLESS.has(key)) return `${key}:${value}px`;
36
+ return `${key}:${value}`;
37
+ })
38
+ .join(";");
39
+ }
40
+
41
+ export function attrsToHtml(attrs: Record<string, string | number | boolean | undefined | null> = {}): string {
42
+ return Object.entries(attrs)
43
+ .filter((e): e is [string, string | number | boolean] => e[1] !== undefined && e[1] !== null && e[1] !== false)
44
+ .map(([key, value]) => (value === true ? esc(key) : `${esc(key)}="${esc(value as string | number)}"`))
45
+ .join(" ");
46
+ }
@@ -0,0 +1,187 @@
1
+ import type { Block, Slide, BlockSlideOptions, ReactSlideOptions } from "./types.ts";
2
+ import { C, esc } from "./design.ts";
3
+ import { defineSlide, htmlBlock, mountBlock, renderBlocks } from "./blocks.ts";
4
+
5
+ export function placedHtml(
6
+ inner: string,
7
+ x: number,
8
+ y: number,
9
+ w: number,
10
+ h: number,
11
+ {
12
+ id,
13
+ className = "",
14
+ fragment = true,
15
+ layer = "content",
16
+ css = {},
17
+ }: {
18
+ id?: string;
19
+ className?: string;
20
+ fragment?: boolean | number | null;
21
+ layer?: Block["layer"];
22
+ css?: Record<string, string | number>;
23
+ } = {},
24
+ ): Block {
25
+ return htmlBlock({
26
+ id,
27
+ html: inner,
28
+ x,
29
+ y,
30
+ w,
31
+ h,
32
+ className,
33
+ fragment,
34
+ layer,
35
+ css,
36
+ });
37
+ }
38
+
39
+ export function title(text: string): string {
40
+ return `<h1 class="slide-title">${esc(text)}</h1>`;
41
+ }
42
+
43
+ export function pageNo(n: number): string {
44
+ return `<div class="page-no">${String(n).padStart(2, "0")}</div>`;
45
+ }
46
+
47
+ export function svg(
48
+ width: number,
49
+ height: number,
50
+ body: string,
51
+ attrs: string = "",
52
+ ): string {
53
+ return `<svg class="native-svg" viewBox="0 0 ${width} ${height}" ${attrs}>${body}</svg>`;
54
+ }
55
+
56
+ export function blockSlide({
57
+ title: titleText,
58
+ blocks = [],
59
+ page,
60
+ notes = "",
61
+ html = "",
62
+ className = "native-slide",
63
+ attrs = {},
64
+ }: BlockSlideOptions = {}): Slide {
65
+ const chrome = [
66
+ titleText ? title(titleText) : "",
67
+ html,
68
+ renderBlocks(blocks),
69
+ page !== undefined && page !== null ? pageNo(page) : "",
70
+ ].filter(Boolean).join("\n");
71
+ return defineSlide({ className, attrs, html: chrome, notes });
72
+ }
73
+
74
+ export function tag(text: string, color: string): string {
75
+ return `<span class="tag" style="border-color:${color};color:${color}">${esc(text)}</span>`;
76
+ }
77
+
78
+ export function at(
79
+ html: string,
80
+ x: number,
81
+ y: number,
82
+ w: number,
83
+ h: number,
84
+ fragment: boolean | number | null = true,
85
+ className: string = "",
86
+ ): Block {
87
+ return placedHtml(html, x, y, w, h, { fragment, className });
88
+ }
89
+
90
+ export function claim(
91
+ text: string,
92
+ x: number,
93
+ y: number,
94
+ w: number,
95
+ color: string = C.red,
96
+ step: boolean | number | null = null,
97
+ ): Block {
98
+ return placedHtml(
99
+ `<div class="claim" style="color:${color}">${esc(text)}</div>`,
100
+ x,
101
+ y,
102
+ w,
103
+ 80,
104
+ { fragment: step ?? true },
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Shortcut for slides backed by a React mount component.
110
+ * Generates mountBlock + invisible fragment triggers automatically.
111
+ *
112
+ * reactSlide({ title: "...", component: "FaultHeatmap", steps: 7, notes: "..." })
113
+ */
114
+ export function reactSlide<C extends string = string>(
115
+ {
116
+ title: titleText,
117
+ component,
118
+ steps = 0,
119
+ notes = "",
120
+ page,
121
+ x = 20, y = 70, w = 1240, h = 640,
122
+ }: ReactSlideOptions<C>,
123
+ ): Slide {
124
+ return blockSlide({
125
+ title: titleText,
126
+ page,
127
+ blocks: [
128
+ mountBlock({
129
+ x, y, w, h,
130
+ fragment: false,
131
+ mount: { library: "react", exportName: component },
132
+ }),
133
+ ...Array.from({ length: steps }, function (_, i): Block {
134
+ return placedHtml(
135
+ '<span class="cascade-trigger"></span>',
136
+ 0, 0, 1, 1,
137
+ { fragment: i }
138
+ );
139
+ }),
140
+ ],
141
+ notes,
142
+ });
143
+ }
144
+
145
+ export function statCard(
146
+ value: string,
147
+ description: string,
148
+ {
149
+ variant = "large-stat",
150
+ centered = false,
151
+ extra = "",
152
+ }: { variant?: string; centered?: boolean; extra?: string } = {},
153
+ ): string {
154
+ const cls = [variant, centered ? "centered" : ""].filter(Boolean).join(" ");
155
+ return `<div class="${cls}"><strong>${value}</strong><span>${esc(description)}</span>${extra}</div>`;
156
+ }
157
+
158
+ export function table(
159
+ headers: string[],
160
+ rows: string[][],
161
+ {
162
+ title: titleText = "",
163
+ highlightCol = -1,
164
+ }: { title?: string; highlightCol?: number } = {},
165
+ ): string {
166
+ const headCells = headers.map((h) => `<th>${esc(h)}</th>`).join("");
167
+ const bodyRows = rows.map((cells) =>
168
+ `<tr>${cells.map((c, i) =>
169
+ `<td${i === highlightCol ? ' class="red-cell"' : ""}>${esc(c)}</td>`
170
+ ).join("")}</tr>`
171
+ ).join("");
172
+ return `${titleText ? `<div class="table-title">${esc(titleText)}</div>` : ""}
173
+ <table class="native-table">
174
+ <thead><tr>${headCells}</tr></thead>
175
+ <tbody>${bodyRows}</tbody>
176
+ </table>`;
177
+ }
178
+
179
+ export function dotList(
180
+ titleText: string,
181
+ items: { label: string; color: string }[],
182
+ ): string {
183
+ const rows = items.map(({ label, color }) =>
184
+ `<div class="fault-dot-row"><span>${esc(label)}</span><b style="background:${color}"></b></div>`
185
+ ).join("");
186
+ return `<div class="fault-panel"><h3>${esc(titleText)}</h3>${rows}</div>`;
187
+ }
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import type { ComponentRegistry, MountContext } from "./types.ts";
4
+
5
+ const roots = new WeakMap<Element, Root>();
6
+
7
+ let registeredComponents: ComponentRegistry = {};
8
+
9
+ export function registerComponents(components: ComponentRegistry): void {
10
+ registeredComponents = components;
11
+ }
12
+
13
+ export function hydrate(root: Element, ctx: MountContext): void {
14
+ root.querySelectorAll<HTMLElement>(".block-mount[data-mount-library]").forEach((el) => {
15
+ if (roots.has(el)) return;
16
+ const exportName = el.dataset.mountExport;
17
+ if (!exportName) return;
18
+ const Component = registeredComponents[exportName];
19
+ if (!Component) {
20
+ console.warn(`[mount-registry] component '${exportName}' not found in registry`);
21
+ return;
22
+ }
23
+ const propsStr = el.dataset.mountProps;
24
+ const extraProps = propsStr ? JSON.parse(propsStr) : {};
25
+ const reactRoot = createRoot(el);
26
+ reactRoot.render(
27
+ React.createElement(Component, { ...extraProps, Reveal: ctx.Reveal })
28
+ );
29
+ roots.set(el, reactRoot);
30
+ });
31
+ }
32
+
33
+ export function cleanup(root: Element): void {
34
+ root.querySelectorAll<HTMLElement>(".block-mount[data-mount-library]").forEach((el) => {
35
+ const reactRoot = roots.get(el);
36
+ if (reactRoot) {
37
+ reactRoot.unmount();
38
+ roots.delete(el);
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,189 @@
1
+ import React, { useState, useEffect, type ReactNode, type CSSProperties } from "react";
2
+ import type { RevealInstance } from "./types.ts";
3
+
4
+ export const MIN_FONT = 14;
5
+
6
+ export const FONT = {
7
+ hero: 64,
8
+ heading: 40,
9
+ title: 20,
10
+ body: 16,
11
+ caption: 14,
12
+ } as const;
13
+
14
+ export const COLOR = {
15
+ text: "#313131",
16
+ muted: "#767676",
17
+ bg: "#ffffff",
18
+ bgSubtle: "rgba(67, 97, 238, 0.04)",
19
+ border: "#e5e7eb",
20
+ blue: "#4361ee",
21
+ purple: "#7c3aed",
22
+ pink: "#f72585",
23
+ cyan: "#4cc9f0",
24
+ green: "#059669",
25
+ orange: "#f78166",
26
+ success: "#059669",
27
+ danger: "#dc2626",
28
+ warn: "#d97706",
29
+ series: ["#4361ee", "#7c3aed", "#f72585", "#4cc9f0", "#059669", "#f78166"],
30
+ } as const;
31
+
32
+ export const NODE = {
33
+ radius: 42,
34
+ radiusAccent: 46,
35
+ iconSize: 28,
36
+ strokeWidth: 3,
37
+ strokeWidthAccent: 4,
38
+ labelFont: 20,
39
+ subFont: 15,
40
+ labelColor: "#313131",
41
+ } as const;
42
+
43
+ export const DETAIL = {
44
+ width: 580,
45
+ height: 220,
46
+ margin: 14,
47
+ scanStep: 14,
48
+ illustrationRatio: 0.5,
49
+ titleFont: 20,
50
+ bodyFont: 16,
51
+ titleColor: "#313131",
52
+ bodyColor: "#767676",
53
+ bgOpacity: 0.85,
54
+ } as const;
55
+
56
+ export const TAG = {
57
+ font: 14,
58
+ pillFont: 14,
59
+ pillHeight: 24,
60
+ pillRadius: 12,
61
+ } as const;
62
+
63
+ export const EDGE = {
64
+ color: "#BFBFBF",
65
+ colorCascade: "#C00000",
66
+ width: 2.5,
67
+ markerSize: 7,
68
+ } as const;
69
+
70
+ export const CANVAS = {
71
+ fontFamily: "Aptos, Arial, sans-serif",
72
+ } as const;
73
+
74
+ export function annotationStyle(): CSSProperties {
75
+ return {
76
+ width: "100%",
77
+ height: "100%",
78
+ display: "flex",
79
+ flexDirection: "row",
80
+ alignItems: "center",
81
+ justifyContent: "center",
82
+ fontFamily: CANVAS.fontFamily,
83
+ gap: 20,
84
+ padding: "12px 24px",
85
+ boxSizing: "border-box",
86
+ };
87
+ }
88
+
89
+ export function illustrationStyle(): CSSProperties {
90
+ return {
91
+ flex: "0 0 auto",
92
+ width: (DETAIL.illustrationRatio * 100) + "%",
93
+ maxHeight: "100%",
94
+ };
95
+ }
96
+
97
+ export function annotationTitleStyle(): CSSProperties {
98
+ return {
99
+ fontSize: DETAIL.titleFont,
100
+ fontWeight: 800,
101
+ color: DETAIL.titleColor,
102
+ lineHeight: 1.4,
103
+ };
104
+ }
105
+
106
+ export function annotationBodyStyle(): CSSProperties {
107
+ return {
108
+ fontSize: DETAIL.bodyFont,
109
+ fontWeight: 700,
110
+ color: DETAIL.bodyColor,
111
+ lineHeight: 1.3,
112
+ };
113
+ }
114
+
115
+ export function vis(cur: number, req: number, instant: boolean): CSSProperties {
116
+ const show = cur >= req ? 1 : 0;
117
+ return { opacity: show, transition: instant ? "none" : "opacity 0.45s ease" };
118
+ }
119
+
120
+ export function visExact(cur: number, req: number, instant: boolean): CSSProperties {
121
+ const show = cur === req ? 1 : 0;
122
+ return { opacity: show, transition: instant ? "none" : "opacity 0.35s ease" };
123
+ }
124
+
125
+ export function getStep(Reveal: RevealInstance): number {
126
+ if (!Reveal) return -1;
127
+ const slide = Reveal.getCurrentSlide();
128
+ if (!slide) return -1;
129
+ const visible = slide.querySelectorAll(".fragment.visible[data-fragment-index]");
130
+ let max = -1;
131
+ visible.forEach(function (el) {
132
+ const idx = parseInt(el.getAttribute("data-fragment-index") || "", 10);
133
+ if (!isNaN(idx) && idx > max) max = idx;
134
+ });
135
+ return max;
136
+ }
137
+
138
+ export function useRevealStep(Reveal: RevealInstance | null): [number, boolean, boolean] {
139
+ const [step, setStep] = useState(-1);
140
+ const [ready, setReady] = useState(false);
141
+ const [instant, setInstant] = useState(true);
142
+
143
+ useEffect(function () {
144
+ if (!Reveal) return;
145
+ const sync = function () { setStep(getStep(Reveal)); };
146
+ const onFragment = function () { setInstant(false); sync(); };
147
+ const onSlideChanged = function () {
148
+ setInstant(true);
149
+ setReady(false);
150
+ setStep(-1);
151
+ requestAnimationFrame(function () {
152
+ sync();
153
+ setReady(true);
154
+ requestAnimationFrame(function () { setInstant(false); });
155
+ });
156
+ };
157
+ Reveal.on("fragmentshown", onFragment);
158
+ Reveal.on("fragmenthidden", onFragment);
159
+ Reveal.on("slidechanged", onSlideChanged);
160
+ requestAnimationFrame(function () {
161
+ sync();
162
+ setReady(true);
163
+ requestAnimationFrame(function () { setInstant(false); });
164
+ });
165
+ return function () {
166
+ Reveal.off("fragmentshown", onFragment);
167
+ Reveal.off("fragmenthidden", onFragment);
168
+ Reveal.off("slidechanged", onSlideChanged);
169
+ };
170
+ }, [Reveal]);
171
+
172
+ return [step, ready, instant];
173
+ }
174
+
175
+ interface SlideCanvasProps {
176
+ Reveal: RevealInstance;
177
+ W?: number;
178
+ H?: number;
179
+ children: (step: number, instant: boolean) => ReactNode;
180
+ }
181
+
182
+ export function SlideCanvas(props: SlideCanvasProps) {
183
+ const [step, ready, instant] = useRevealStep(props.Reveal);
184
+ return React.createElement("svg", {
185
+ viewBox: "0 0 " + (props.W || 1200) + " " + (props.H || 560),
186
+ style: { width: "100%", height: "100%", display: "block",
187
+ opacity: ready ? 1 : 0, transition: "opacity 0.2s ease" },
188
+ }, props.children(step, instant));
189
+ }
@@ -0,0 +1,141 @@
1
+ import type { RevealInstance, ComponentRegistry, MountContext } from "./types.ts";
2
+ import * as mountRegistry from "./mount-registry.ts";
3
+
4
+ declare global {
5
+ interface SVGPathElement {
6
+ _rcaDrawable?: any;
7
+ }
8
+ }
9
+
10
+ function connectorLength(path: SVGPathElement): number {
11
+ try {
12
+ return path.getTotalLength();
13
+ } catch {
14
+ return 0;
15
+ }
16
+ }
17
+
18
+ function prepareConnector(path: SVGPathElement, anime: any): void {
19
+ if (anime?.svg?.createDrawable) {
20
+ const isVisible = path.classList.contains("visible");
21
+ path._rcaDrawable = anime.svg.createDrawable(path, 0, isVisible ? 1 : 0)[0];
22
+ return;
23
+ }
24
+ const length = connectorLength(path);
25
+ if (!length) return;
26
+ path.dataset.length = String(length);
27
+ path.style.strokeDasharray = String(length);
28
+ path.style.strokeDashoffset = path.classList.contains("visible") ? "0" : String(length);
29
+ }
30
+
31
+ function animateConnector(path: SVGPathElement, anime: any): void {
32
+ if (anime?.svg?.createDrawable) {
33
+ const drawable = path._rcaDrawable || anime.svg.createDrawable(path, 0, 0)[0];
34
+ path._rcaDrawable = drawable;
35
+ anime.animate(drawable, {
36
+ draw: ["0 0", "0 1"],
37
+ duration: 420,
38
+ ease: "outCubic",
39
+ });
40
+ return;
41
+ }
42
+ const length = Number(path.dataset.length || connectorLength(path));
43
+ if (!length) return;
44
+ path.style.strokeDasharray = String(length);
45
+ path.style.strokeDashoffset = String(length);
46
+ anime.animate(path, {
47
+ strokeDashoffset: [length, 0],
48
+ duration: 420,
49
+ ease: "outCubic",
50
+ });
51
+ }
52
+
53
+ function resetConnector(path: SVGPathElement): void {
54
+ if (path._rcaDrawable) {
55
+ path._rcaDrawable.setAttribute("draw", "0 0");
56
+ return;
57
+ }
58
+ const length = Number(path.dataset.length || connectorLength(path));
59
+ if (!length) return;
60
+ path.style.strokeDashoffset = String(length);
61
+ }
62
+
63
+ function animateNode(fragment: Element, anime: any): void {
64
+ if (!fragment.querySelector(".icon-node, .claim, .large-stat, .pill, .result-box, .chart-shell")) return;
65
+ anime.animate(fragment, {
66
+ opacity: [0, 1],
67
+ scale: [0.965, 1],
68
+ duration: 260,
69
+ ease: "outQuad",
70
+ });
71
+ }
72
+
73
+ function prepareSlide(slide: Element, anime: any): void {
74
+ slide.querySelectorAll<SVGPathElement>("path[data-connector='true']").forEach((path) => prepareConnector(path, anime));
75
+ }
76
+
77
+ function connectorsForFragment(fragment: Element): SVGPathElement[] {
78
+ const slide = fragment.closest("section");
79
+ const index = fragment.getAttribute("data-fragment-index");
80
+ if (!slide || index === null) return [];
81
+ return [...slide.querySelectorAll<SVGPathElement>(`path[data-connector='true'][data-fragment-index="${index}"]`)];
82
+ }
83
+
84
+ export function installRuntime(
85
+ Reveal: RevealInstance,
86
+ anime: any,
87
+ components: ComponentRegistry
88
+ ): void {
89
+ if (!Reveal || !anime?.animate) return;
90
+
91
+ mountRegistry.registerComponents(components);
92
+
93
+ const ctx: MountContext = { anime, Reveal };
94
+
95
+ Reveal.on("ready", (event: any) => {
96
+ prepareSlide(event.currentSlide, anime);
97
+ mountRegistry.hydrate(event.currentSlide, ctx);
98
+ });
99
+
100
+ Reveal.on("slidechanged", (event: any) => {
101
+ const mounts: NodeListOf<HTMLElement> = event.currentSlide.querySelectorAll(".block-mount");
102
+ mounts.forEach((el: HTMLElement) => { el.style.visibility = "hidden"; });
103
+
104
+ prepareSlide(event.currentSlide, anime);
105
+ mountRegistry.hydrate(event.currentSlide, ctx);
106
+
107
+ requestAnimationFrame(() => {
108
+ requestAnimationFrame(() => {
109
+ mounts.forEach((el: HTMLElement) => { el.style.visibility = ""; });
110
+ });
111
+ });
112
+ });
113
+
114
+ Reveal.on("fragmentshown", (event: any) => {
115
+ const fragment = event.fragment as Element;
116
+ if (fragment.matches("path[data-connector='true']")) {
117
+ animateConnector(fragment as unknown as SVGPathElement, anime);
118
+ } else {
119
+ connectorsForFragment(fragment).forEach((path) => {
120
+ animateConnector(path, anime);
121
+ });
122
+ animateNode(fragment, anime);
123
+ mountRegistry.hydrate(fragment, ctx);
124
+ }
125
+ });
126
+
127
+ Reveal.on("fragmenthidden", (event: any) => {
128
+ const fragment = event.fragment as Element;
129
+ if (fragment.matches("path[data-connector='true']")) {
130
+ resetConnector(fragment as unknown as SVGPathElement);
131
+ } else {
132
+ connectorsForFragment(fragment).forEach(resetConnector);
133
+ }
134
+ });
135
+
136
+ document.querySelectorAll<Element>(".reveal .slides section").forEach((slide) => {
137
+ prepareSlide(slide, anime);
138
+ });
139
+ const initialSlide = Reveal.getCurrentSlide();
140
+ if (initialSlide) mountRegistry.hydrate(initialSlide, ctx);
141
+ }