@kadal/deck 0.0.0-stage → 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 (157) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSING.md +40 -0
  3. package/README.md +170 -2
  4. package/dist/_virtual/_rolldown/runtime.js +13 -0
  5. package/dist/audience/index.d.ts +14 -0
  6. package/dist/audience/index.js +680 -0
  7. package/dist/backgrounds/index.d.ts +65 -0
  8. package/dist/backgrounds/index.js +604 -0
  9. package/dist/backgrounds/shared.d.ts +7 -0
  10. package/dist/backgrounds/shared.js +89 -0
  11. package/dist/cli/bin.d.ts +1 -0
  12. package/dist/cli/bin.js +8 -0
  13. package/dist/cli/host.js +111 -0
  14. package/dist/cli/index.d.ts +4 -0
  15. package/dist/cli/index.js +140 -0
  16. package/dist/cli/login.js +56 -0
  17. package/dist/cli/publish.js +159 -0
  18. package/dist/cli/vite.js +39 -0
  19. package/dist/compiler/code-meta.d.ts +33 -0
  20. package/dist/compiler/code-meta.js +81 -0
  21. package/dist/compiler/index.d.ts +8 -0
  22. package/dist/compiler/index.js +8 -0
  23. package/dist/compiler/magic-move.d.ts +26 -0
  24. package/dist/compiler/magic-move.js +33 -0
  25. package/dist/compiler/plain-text.js +7 -0
  26. package/dist/compiler/rehype-code.d.ts +18 -0
  27. package/dist/compiler/rehype-code.js +66 -0
  28. package/dist/compiler/rehype-glossary.d.ts +35 -0
  29. package/dist/compiler/rehype-glossary.js +211 -0
  30. package/dist/compiler/slots.d.ts +3 -0
  31. package/dist/compiler/slots.js +36 -0
  32. package/dist/compiler/split.d.ts +63 -0
  33. package/dist/compiler/split.js +195 -0
  34. package/dist/components/Background.d.ts +15 -0
  35. package/dist/components/Background.js +24 -0
  36. package/dist/components/Click.d.ts +54 -0
  37. package/dist/components/Click.js +129 -0
  38. package/dist/components/Code.d.ts +27 -0
  39. package/dist/components/Code.js +124 -0
  40. package/dist/components/Motion.d.ts +25 -0
  41. package/dist/components/Motion.js +45 -0
  42. package/dist/components/Sketch.d.ts +44 -0
  43. package/dist/components/Sketch.js +141 -0
  44. package/dist/components/Term.d.ts +63 -0
  45. package/dist/components/Term.js +492 -0
  46. package/dist/components/bling.d.ts +75 -0
  47. package/dist/components/bling.js +146 -0
  48. package/dist/components/content.d.ts +232 -0
  49. package/dist/components/content.js +637 -0
  50. package/dist/components/index.d.ts +10 -0
  51. package/dist/components/index.js +88 -0
  52. package/dist/core/clicks.d.ts +45 -0
  53. package/dist/core/clicks.js +75 -0
  54. package/dist/core/hash.d.ts +33 -0
  55. package/dist/core/hash.js +49 -0
  56. package/dist/core/keyboard.d.ts +17 -0
  57. package/dist/core/keyboard.js +198 -0
  58. package/dist/core/layout.d.ts +16 -0
  59. package/dist/core/layout.js +25 -0
  60. package/dist/core/model.d.ts +259 -0
  61. package/dist/core/model.js +377 -0
  62. package/dist/core/navigation.d.ts +74 -0
  63. package/dist/core/navigation.js +244 -0
  64. package/dist/core/poster.js +30 -0
  65. package/dist/core/store.d.ts +20 -0
  66. package/dist/core/store.js +64 -0
  67. package/dist/core/touch.js +27 -0
  68. package/dist/draw/DrawingLayer.js +315 -0
  69. package/dist/draw/strokes.js +188 -0
  70. package/dist/export/index.d.ts +68 -0
  71. package/dist/export/index.js +251 -0
  72. package/dist/export/run.js +81 -0
  73. package/dist/glossary/model.d.ts +81 -0
  74. package/dist/glossary/model.js +156 -0
  75. package/dist/glossary/parse.d.ts +27 -0
  76. package/dist/glossary/parse.js +90 -0
  77. package/dist/index.d.ts +31 -0
  78. package/dist/index.js +30 -0
  79. package/dist/live/LiveProvider.d.ts +20 -0
  80. package/dist/live/LiveProvider.js +450 -0
  81. package/dist/live/analytics.d.ts +15 -0
  82. package/dist/live/analytics.js +103 -0
  83. package/dist/live/client.d.ts +51 -0
  84. package/dist/live/client.js +193 -0
  85. package/dist/live/components.d.ts +64 -0
  86. package/dist/live/components.js +471 -0
  87. package/dist/live/context.d.ts +15 -0
  88. package/dist/live/context.js +28 -0
  89. package/dist/live/index.d.ts +7 -0
  90. package/dist/live/index.js +7 -0
  91. package/dist/live/protocol.d.ts +284 -0
  92. package/dist/live/protocol.js +42 -0
  93. package/dist/live/turnstile.js +49 -0
  94. package/dist/presenter/index.d.ts +20 -0
  95. package/dist/presenter/index.js +1211 -0
  96. package/dist/react/AutoAnimate.js +92 -0
  97. package/dist/react/Backgrounds.d.ts +11 -0
  98. package/dist/react/Backgrounds.js +154 -0
  99. package/dist/react/ConfettiLayer.js +85 -0
  100. package/dist/react/Deck.d.ts +35 -0
  101. package/dist/react/Deck.js +983 -0
  102. package/dist/react/Frame.d.ts +16 -0
  103. package/dist/react/Frame.js +174 -0
  104. package/dist/react/Hints.d.ts +3 -0
  105. package/dist/react/Hints.js +201 -0
  106. package/dist/react/Lightbox.js +54 -0
  107. package/dist/react/Overview.js +93 -0
  108. package/dist/react/PrintView.js +128 -0
  109. package/dist/react/ScrollView.js +128 -0
  110. package/dist/react/SlideFrame.d.ts +51 -0
  111. package/dist/react/SlideFrame.js +323 -0
  112. package/dist/react/Stage.js +151 -0
  113. package/dist/react/ThemeMenu.d.ts +5 -0
  114. package/dist/react/ThemeMenu.js +124 -0
  115. package/dist/react/Zoom.js +23 -0
  116. package/dist/react/chrome.js +339 -0
  117. package/dist/react/context.d.ts +165 -0
  118. package/dist/react/context.js +19 -0
  119. package/dist/react/hooks.d.ts +33 -0
  120. package/dist/react/hooks.js +70 -0
  121. package/dist/react/layouts.d.ts +21 -0
  122. package/dist/react/layouts.js +104 -0
  123. package/dist/react/mdx.d.ts +4 -0
  124. package/dist/react/mdx.js +33 -0
  125. package/dist/react/terms.d.ts +6 -0
  126. package/dist/react/terms.js +66 -0
  127. package/dist/renderers/Island.d.ts +57 -0
  128. package/dist/renderers/Island.js +223 -0
  129. package/dist/renderers/contract.d.ts +173 -0
  130. package/dist/renderers/contract.js +152 -0
  131. package/dist/renderers/index.d.ts +12 -0
  132. package/dist/renderers/index.js +16 -0
  133. package/dist/renderers/react.d.ts +16 -0
  134. package/dist/renderers/react.js +32 -0
  135. package/dist/renderers/web-components.d.ts +4 -0
  136. package/dist/renderers/web-components.js +103 -0
  137. package/dist/sketch/Canvas.js +49 -0
  138. package/dist/sketch/dsl.d.ts +131 -0
  139. package/dist/sketch/dsl.js +281 -0
  140. package/dist/sketch/index.d.ts +4 -0
  141. package/dist/sketch/index.js +3 -0
  142. package/dist/sketch/palette.d.ts +21 -0
  143. package/dist/sketch/palette.js +66 -0
  144. package/dist/sketch/render.d.ts +43 -0
  145. package/dist/sketch/render.js +198 -0
  146. package/dist/themes/base.css +1704 -0
  147. package/dist/themes/bling.css +592 -0
  148. package/dist/themes/glossary.css +317 -0
  149. package/dist/themes/live.css +1557 -0
  150. package/dist/themes/minimal.css +83 -0
  151. package/dist/themes/rla.css +92 -0
  152. package/dist/themes/sketch.css +121 -0
  153. package/dist/themes/stack.css +62 -0
  154. package/dist/vite/index.d.ts +15 -0
  155. package/dist/vite/index.js +320 -0
  156. package/package.json +159 -4
  157. package/tools/verify-island.mjs +150 -0
@@ -0,0 +1,259 @@
1
+ import { Frontmatter } from "../compiler/split.js";
2
+ import { GlossaryEntry, GlossaryOptions } from "../glossary/model.js";
3
+ import { ComponentType } from "react";
4
+ //#region src/core/model.d.ts
5
+ /**
6
+ * ─────────────────────────────────────────────────────────────────────────────
7
+ * THE DECK MODEL
8
+ * ─────────────────────────────────────────────────────────────────────────────
9
+ *
10
+ * A deck is a row of columns. Each column is a vertical stack whose first slide is the one you
11
+ * reach by moving right; the others sit beneath it (reveal.js's vertical slides). Every slide has a
12
+ * flat index in authored order too, which is what the progress bar, the table of contents and the
13
+ * scroll view use. Nothing here touches the DOM.
14
+ * ─────────────────────────────────────────────────────────────────────────────
15
+ */
16
+ export interface SlideModule {
17
+ default: ComponentType<Record<string, unknown>>;
18
+ }
19
+ export interface MagicMoveStepData {
20
+ lang: string;
21
+ lines: boolean;
22
+ highlight: string | null;
23
+ tokens: {
24
+ light: unknown;
25
+ dark: unknown;
26
+ };
27
+ }
28
+ export interface MagicMoveData {
29
+ options: Record<string, string>;
30
+ title: string | null;
31
+ steps: MagicMoveStepData[];
32
+ }
33
+ /** One entry of the manifest the Vite plugin emits. */
34
+ export interface SlideMeta {
35
+ index: number;
36
+ h: number;
37
+ v: number;
38
+ id: string;
39
+ title: string | null;
40
+ level: number;
41
+ frontmatter: Frontmatter;
42
+ hasNotes: boolean;
43
+ source: {
44
+ file: string;
45
+ line: number;
46
+ };
47
+ load: () => Promise<SlideModule>;
48
+ loadNotes: (() => Promise<SlideModule>) | null;
49
+ loadMagic: (() => Promise<{
50
+ default: MagicMoveData[];
51
+ }>) | null;
52
+ }
53
+ export interface DeckManifest {
54
+ headmatter: Frontmatter;
55
+ slides: SlideMeta[];
56
+ /** The deck's `glossary.yaml`, parsed at build time; absent in a deck without one. */
57
+ glossary?: GlossaryEntry[];
58
+ }
59
+ export type Transition = "none" | "fade" | "slide" | "convex" | "concave" | "zoom" | "view-transition";
60
+ export type TransitionSpeed = "default" | "fast" | "slow";
61
+ export type SlideNumberFormat = "h.v" | "h/v" | "c" | "c/t";
62
+ export type NavigationMode = "default" | "linear" | "grid";
63
+ export type ColorScheme = "auto" | "light" | "dark";
64
+ /** Every option, with the same names reveal.js and Slidev use wherever the idea is the same. */
65
+ export interface DeckConfig {
66
+ title: string;
67
+ author: string;
68
+ info: string;
69
+ /** The authored size; the stage scales uniformly to fit any viewport. */
70
+ width: number;
71
+ height: number;
72
+ /**
73
+ * Fraction of the viewport left empty around the stage. 0 by default: a 16:9 window shows the
74
+ * slide edge to edge, any other shape gets bars on two sides — the content never reflows.
75
+ */
76
+ margin: number;
77
+ minScale: number;
78
+ maxScale: number;
79
+ /** Vertically centre each slide's content. */
80
+ center: boolean;
81
+ controls: boolean | "speaker";
82
+ controlsLayout: "bottom-right" | "edges";
83
+ controlsTutorial: boolean;
84
+ controlsBackArrows: "faded" | "hidden" | "visible";
85
+ progress: boolean;
86
+ slideNumber: boolean | SlideNumberFormat;
87
+ showSlideNumber: "all" | "print" | "speaker";
88
+ hash: boolean;
89
+ hashOneBasedIndex: boolean;
90
+ history: boolean;
91
+ keyboard: boolean;
92
+ overview: boolean;
93
+ touch: boolean;
94
+ loop: boolean;
95
+ rtl: boolean;
96
+ navigationMode: NavigationMode;
97
+ shuffle: boolean;
98
+ fragments: boolean;
99
+ fragmentInURL: boolean;
100
+ embedded: boolean;
101
+ help: boolean;
102
+ pause: boolean;
103
+ showNotes: boolean | "separate-page";
104
+ autoPlayMedia: boolean | null;
105
+ autoAnimate: boolean;
106
+ autoAnimateEasing: string;
107
+ autoAnimateDuration: number;
108
+ autoAnimateUnmatched: boolean;
109
+ /** Milliseconds between automatic steps; 0 disables. */
110
+ autoSlide: number;
111
+ autoSlideStoppable: boolean;
112
+ /** Seconds per slide for the pacing timer, or null. */
113
+ defaultTiming: number | null;
114
+ /** Total seconds for the pacing timer, or null. */
115
+ totalTime: number | null;
116
+ mouseWheel: boolean;
117
+ previewLinks: boolean;
118
+ transition: Transition;
119
+ transitionSpeed: TransitionSpeed;
120
+ backgroundTransition: Transition;
121
+ viewDistance: number;
122
+ mobileViewDistance: number;
123
+ hideInactiveCursor: boolean;
124
+ hideCursorTime: number;
125
+ view: "slide" | "scroll";
126
+ scrollProgress: "auto" | boolean;
127
+ scrollSnap: "mandatory" | "proximity" | false;
128
+ /**
129
+ * Viewport width under which the scroll view switches on by itself; 0 (the default) never does
130
+ * — a phone gets the same stage, letterboxed, and `?view=scroll` stays an explicit choice.
131
+ */
132
+ scrollActivationWidth: number;
133
+ theme: string;
134
+ /** Themes this deck ships (their CSS is imported by main.tsx); the theme menu lists them. */
135
+ themes: string[];
136
+ colorScheme: ColorScheme;
137
+ pdfSeparateFragments: boolean;
138
+ pdfMaxPagesPerSlide: number;
139
+ jumpToSlide: boolean;
140
+ search: boolean;
141
+ zoom: boolean;
142
+ wakeLock: boolean;
143
+ selectable: boolean;
144
+ lineNumbers: boolean;
145
+ /** Scale a slide whose content overflows so it fits (measured after render). */
146
+ autoFit: boolean;
147
+ magicMoveDuration: number;
148
+ /** Live room: the backend base (same origin `/api` on the host), or null for an offline deck. */
149
+ live: {
150
+ api: string | null;
151
+ room: string | null;
152
+ turnstile: string | null;
153
+ };
154
+ drawings: {
155
+ enabled: boolean;
156
+ persist: boolean;
157
+ presenterOnly: boolean;
158
+ syncAll: boolean;
159
+ };
160
+ /**
161
+ * Term cards. The runtime reads `delay` and `sync`; the rest steers the compiler, which sees
162
+ * the headmatter only — set those there. `glossary: false` or a path are shorthands.
163
+ */
164
+ glossary: GlossaryOptions;
165
+ reactions: boolean;
166
+ qa: boolean;
167
+ polls: boolean;
168
+ /** Show every slide's notes to anyone on the audience page (after the talk, for instance). */
169
+ audienceNotes: boolean;
170
+ /** The deck's slug on the host, from the Vite base — decks/<slug>. */
171
+ slug: string;
172
+ /** Show the "Made with Kadal Deck" link in the corner (set by the host, see `withHostOverride`). */
173
+ badge: boolean;
174
+ /** Google Fonts families to load, e.g. ["Inter:wght@400;600"]. */
175
+ fonts: string[];
176
+ seoMeta: Record<string, string>;
177
+ }
178
+ export declare const DEFAULT_CONFIG: DeckConfig;
179
+ /** Headmatter and deck.config.ts both layer over the defaults; nested objects merge one level. */
180
+ /** What a deck.config.ts or headmatter may set: any option, with the nested groups partial too. */
181
+ export type DeckConfigInput = Partial<Omit<DeckConfig, "live" | "drawings" | "seoMeta" | "glossary">> & {
182
+ glossary?: Partial<GlossaryOptions> | string | false;
183
+ live?: Partial<DeckConfig["live"]>;
184
+ drawings?: Partial<DeckConfig["drawings"]>;
185
+ seoMeta?: Partial<DeckConfig["seoMeta"]>;
186
+ };
187
+ export declare function resolveConfig(...layers: Array<DeckConfigInput | Frontmatter | undefined>): DeckConfig;
188
+ /** Per-slide options read from frontmatter, normalised. */
189
+ export interface SlideOptions {
190
+ layout: string;
191
+ className: string;
192
+ hidden: boolean;
193
+ uncounted: boolean;
194
+ hideInToc: boolean;
195
+ transition: string | null;
196
+ transitionSpeed: TransitionSpeed | null;
197
+ backgroundTransition: Transition | null;
198
+ autoAnimate: boolean;
199
+ autoAnimateId: string | null;
200
+ autoAnimateRestart: boolean;
201
+ autoSlide: number | null;
202
+ clicks: number | null;
203
+ clicksStart: number;
204
+ preload: boolean;
205
+ timing: number | null;
206
+ state: string[];
207
+ /** Force dark or light tokens on this slide (a dark background under a light theme). */
208
+ colorScheme: "dark" | "light" | null;
209
+ zoom: number;
210
+ background: SlideBackground | null;
211
+ }
212
+ export interface SlideBackground {
213
+ color: string | null;
214
+ gradient: string | null;
215
+ image: string | null;
216
+ size: string;
217
+ position: string;
218
+ repeat: string;
219
+ opacity: number;
220
+ video: string | null;
221
+ videoLoop: boolean;
222
+ videoMuted: boolean;
223
+ iframe: string | null;
224
+ iframeInteractive: boolean;
225
+ /**
226
+ * A soft wash of one colour over the layer — a diagonal gradient from the colour to a paler
227
+ * tint of it, with a little more light in one corner — under a component background or on
228
+ * its own (`fill:` / `tint:` in frontmatter). `color` is the flat fill reveal.js has.
229
+ */
230
+ tint: string | null;
231
+ /** A registered component background, by name, with its props. */
232
+ component: string | null;
233
+ props: Record<string, unknown>;
234
+ }
235
+ /** `background: "#fff"`, `background: "/img.png"`, `background: { image, size }`, `background: { component: "aurora", … }`. */
236
+ export declare function resolveBackground(fm: Frontmatter): SlideBackground | null;
237
+ export declare function resolveSlideOptions(fm: Frontmatter, config: DeckConfig, isFirst: boolean): SlideOptions;
238
+ /** The slides grouped into columns, with hidden slides dropped. */
239
+ export interface Column {
240
+ h: number;
241
+ slides: SlideMeta[];
242
+ }
243
+ export declare function columnsOf(slides: SlideMeta[], config: DeckConfig): Column[];
244
+ /** What a host may say about a deck it serves. */
245
+ export interface HostOverride {
246
+ room?: string;
247
+ api?: string;
248
+ badge?: boolean;
249
+ }
250
+ /** The global a host sets, validated: anything malformed is ignored rather than trusted. */
251
+ export declare function readHostOverride(scope?: unknown): HostOverride | null;
252
+ /** The config with the host's word laid over it. Returns the same object when there is none. */
253
+ export declare function withHostOverride(config: DeckConfig, override: HostOverride | null): DeckConfig;
254
+ /**
255
+ * The id every host call is keyed by — the room, presenter login, stats, beacons, share links and
256
+ * exports. A published deck's is the room the host assigned; a self-hosted one's is its slug.
257
+ */
258
+ export declare function roomIdOf(config: Pick<DeckConfig, "live" | "slug">): string;
259
+ //#endregion
@@ -0,0 +1,377 @@
1
+ import { DEFAULT_GLOSSARY, glossaryOptions } from "../glossary/model.js";
2
+ //#region src/core/model.ts
3
+ const DEFAULT_CONFIG = {
4
+ title: "Untitled deck",
5
+ author: "",
6
+ info: "",
7
+ width: 1920,
8
+ height: 1080,
9
+ margin: 0,
10
+ minScale: .2,
11
+ maxScale: 2,
12
+ center: true,
13
+ controls: true,
14
+ controlsLayout: "bottom-right",
15
+ controlsTutorial: true,
16
+ controlsBackArrows: "faded",
17
+ progress: true,
18
+ slideNumber: false,
19
+ showSlideNumber: "all",
20
+ hash: true,
21
+ hashOneBasedIndex: false,
22
+ history: false,
23
+ keyboard: true,
24
+ overview: true,
25
+ touch: true,
26
+ loop: false,
27
+ rtl: false,
28
+ navigationMode: "default",
29
+ shuffle: false,
30
+ fragments: true,
31
+ fragmentInURL: true,
32
+ embedded: false,
33
+ help: true,
34
+ pause: true,
35
+ showNotes: false,
36
+ autoPlayMedia: null,
37
+ autoAnimate: true,
38
+ autoAnimateEasing: "ease",
39
+ autoAnimateDuration: 1,
40
+ autoAnimateUnmatched: true,
41
+ autoSlide: 0,
42
+ autoSlideStoppable: true,
43
+ defaultTiming: null,
44
+ totalTime: null,
45
+ mouseWheel: false,
46
+ previewLinks: false,
47
+ transition: "slide",
48
+ transitionSpeed: "default",
49
+ backgroundTransition: "fade",
50
+ viewDistance: 3,
51
+ mobileViewDistance: 2,
52
+ hideInactiveCursor: true,
53
+ hideCursorTime: 5e3,
54
+ view: "slide",
55
+ scrollProgress: "auto",
56
+ scrollSnap: "mandatory",
57
+ scrollActivationWidth: 0,
58
+ theme: "minimal",
59
+ themes: [
60
+ "minimal",
61
+ "rla",
62
+ "stack"
63
+ ],
64
+ colorScheme: "auto",
65
+ pdfSeparateFragments: true,
66
+ pdfMaxPagesPerSlide: 1,
67
+ jumpToSlide: true,
68
+ search: true,
69
+ zoom: true,
70
+ wakeLock: true,
71
+ selectable: true,
72
+ lineNumbers: false,
73
+ autoFit: true,
74
+ magicMoveDuration: 800,
75
+ live: {
76
+ api: "/api",
77
+ room: null,
78
+ turnstile: null
79
+ },
80
+ drawings: {
81
+ enabled: true,
82
+ persist: true,
83
+ presenterOnly: false,
84
+ syncAll: true
85
+ },
86
+ glossary: DEFAULT_GLOSSARY,
87
+ reactions: true,
88
+ qa: true,
89
+ polls: true,
90
+ audienceNotes: false,
91
+ slug: "",
92
+ badge: false,
93
+ fonts: [],
94
+ seoMeta: {}
95
+ };
96
+ function resolveConfig(...layers) {
97
+ const out = {
98
+ ...DEFAULT_CONFIG,
99
+ live: { ...DEFAULT_CONFIG.live },
100
+ drawings: { ...DEFAULT_CONFIG.drawings }
101
+ };
102
+ for (const layer of layers) {
103
+ if (!layer) continue;
104
+ for (const [key, value] of Object.entries(layer)) {
105
+ if (value === void 0) continue;
106
+ if (key === "glossary") out.glossary = glossaryOptions(value, out.glossary);
107
+ else if ((key === "live" || key === "drawings" || key === "seoMeta") && value && typeof value === "object") out[key] = {
108
+ ...out[key],
109
+ ...value
110
+ };
111
+ else out[key] = value;
112
+ }
113
+ }
114
+ const fm = Object.assign({}, ...layers.filter(Boolean));
115
+ if (typeof fm.aspectRatio === "number" && typeof fm.canvasWidth === "number") {
116
+ out.width = fm.canvasWidth;
117
+ out.height = Math.round(fm.canvasWidth / fm.aspectRatio);
118
+ }
119
+ return out;
120
+ }
121
+ function str(v) {
122
+ return typeof v === "string" && v.trim() ? v.trim() : null;
123
+ }
124
+ function bool(v, fallback) {
125
+ return typeof v === "boolean" ? v : fallback;
126
+ }
127
+ function num(v) {
128
+ return typeof v === "number" && Number.isFinite(v) ? v : typeof v === "string" && v.trim() && Number.isFinite(Number(v)) ? Number(v) : null;
129
+ }
130
+ const COLOR = /^(#|rgb|hsl|oklch|oklab|lab|lch|transparent|[a-z]+$)/i;
131
+ const GRADIENT = /gradient\(/i;
132
+ /** `background: "#fff"`, `background: "/img.png"`, `background: { image, size }`, `background: { component: "aurora", … }`. */
133
+ function resolveBackground(fm) {
134
+ const raw = fm.background ?? fm.backgroundColor ?? fm.backgroundImage;
135
+ const base = {
136
+ color: null,
137
+ gradient: null,
138
+ image: null,
139
+ size: "cover",
140
+ position: "center",
141
+ repeat: "no-repeat",
142
+ opacity: 1,
143
+ video: null,
144
+ videoLoop: false,
145
+ videoMuted: true,
146
+ iframe: null,
147
+ iframeInteractive: false,
148
+ tint: null,
149
+ component: null,
150
+ props: {}
151
+ };
152
+ let any = false;
153
+ const apply = (obj) => {
154
+ if (str(obj.fill) || str(obj.tint)) {
155
+ base.tint = str(obj.fill) ?? str(obj.tint);
156
+ any = true;
157
+ } else if (str(obj.color) && !str(obj.component)) {
158
+ base.color = str(obj.color);
159
+ any = true;
160
+ }
161
+ if (str(obj.gradient)) {
162
+ base.gradient = str(obj.gradient);
163
+ any = true;
164
+ }
165
+ if (str(obj.image)) {
166
+ base.image = str(obj.image);
167
+ any = true;
168
+ }
169
+ if (str(obj.size)) base.size = str(obj.size);
170
+ if (str(obj.position)) base.position = str(obj.position);
171
+ if (str(obj.repeat)) base.repeat = str(obj.repeat);
172
+ if (num(obj.opacity) !== null) base.opacity = num(obj.opacity);
173
+ if (str(obj.video)) {
174
+ base.video = str(obj.video);
175
+ any = true;
176
+ }
177
+ base.videoLoop = bool(obj.videoLoop ?? obj.loop, base.videoLoop);
178
+ base.videoMuted = bool(obj.videoMuted ?? obj.muted, base.videoMuted);
179
+ if (str(obj.iframe)) {
180
+ base.iframe = str(obj.iframe);
181
+ any = true;
182
+ }
183
+ base.iframeInteractive = bool(obj.interactive, base.iframeInteractive);
184
+ if (str(obj.component)) {
185
+ base.component = str(obj.component);
186
+ any = true;
187
+ const { component: _c, fill: _f, tint: _t, ...props } = obj;
188
+ base.props = props;
189
+ }
190
+ };
191
+ if (typeof raw === "string") {
192
+ const s = raw.trim();
193
+ if (GRADIENT.test(s)) base.gradient = s;
194
+ else if (/\.(mp4|webm|mov|ogv)(\?|$)/i.test(s)) base.video = s;
195
+ else if (/^(https?:)?\/\/|\/|\.\/|\.(png|jpe?g|gif|webp|svg|avif)(\?|$)/i.test(s) && !COLOR.test(s)) base.image = s;
196
+ else if (/^[a-z][\w-]*$/i.test(s) && !isCssColorKeyword(s)) base.component = s;
197
+ else base.color = s;
198
+ any = true;
199
+ } else if (raw && typeof raw === "object") apply(raw);
200
+ apply({
201
+ color: fm.backgroundColor,
202
+ gradient: fm.backgroundGradient,
203
+ image: fm.backgroundImage,
204
+ size: fm.backgroundSize,
205
+ position: fm.backgroundPosition,
206
+ repeat: fm.backgroundRepeat,
207
+ opacity: fm.backgroundOpacity,
208
+ video: fm.backgroundVideo,
209
+ videoLoop: fm.backgroundVideoLoop,
210
+ videoMuted: fm.backgroundVideoMuted,
211
+ iframe: fm.backgroundIframe,
212
+ interactive: fm.backgroundInteractive
213
+ });
214
+ return any ? base : null;
215
+ }
216
+ const CSS_COLORS = /* @__PURE__ */ new Set([
217
+ "black",
218
+ "white",
219
+ "red",
220
+ "green",
221
+ "blue",
222
+ "yellow",
223
+ "orange",
224
+ "purple",
225
+ "pink",
226
+ "gray",
227
+ "grey",
228
+ "silver",
229
+ "gold",
230
+ "navy",
231
+ "teal",
232
+ "aqua",
233
+ "lime",
234
+ "maroon",
235
+ "olive",
236
+ "fuchsia",
237
+ "coral",
238
+ "salmon",
239
+ "crimson",
240
+ "indigo",
241
+ "violet",
242
+ "cyan",
243
+ "magenta",
244
+ "aquamarine",
245
+ "transparent",
246
+ "currentcolor",
247
+ "beige",
248
+ "ivory",
249
+ "khaki",
250
+ "lavender",
251
+ "linen",
252
+ "mint",
253
+ "peach",
254
+ "plum",
255
+ "tan",
256
+ "tomato",
257
+ "turquoise",
258
+ "wheat",
259
+ "azure",
260
+ "brown",
261
+ "chocolate",
262
+ "darkblue",
263
+ "darkgreen",
264
+ "darkred",
265
+ "lightblue",
266
+ "lightgreen",
267
+ "orchid",
268
+ "sienna",
269
+ "slategray",
270
+ "snow",
271
+ "steelblue"
272
+ ]);
273
+ function isCssColorKeyword(s) {
274
+ return CSS_COLORS.has(s.toLowerCase());
275
+ }
276
+ function resolveSlideOptions(fm, config, isFirst) {
277
+ const state = fm.state;
278
+ const visibility = str(fm.visibility);
279
+ const transition = str(fm.transition);
280
+ const transitionSpeed = str(fm.transitionSpeed);
281
+ return {
282
+ layout: str(fm.layout) ?? (isFirst ? "cover" : "default"),
283
+ className: Array.isArray(fm.class) ? fm.class.join(" ") : str(fm.class) ?? "",
284
+ hidden: bool(fm.hide, false) || bool(fm.disabled, false) || visibility === "hidden",
285
+ uncounted: visibility === "uncounted",
286
+ hideInToc: bool(fm.hideInToc, false),
287
+ transition,
288
+ transitionSpeed,
289
+ backgroundTransition: str(fm.backgroundTransition),
290
+ autoAnimate: fm.autoAnimate === true || typeof fm.autoAnimate === "object" && fm.autoAnimate !== null,
291
+ autoAnimateId: str(fm.autoAnimateId) ?? (typeof fm.autoAnimate === "object" && fm.autoAnimate ? str(fm.autoAnimate.id) ?? null : null),
292
+ autoAnimateRestart: bool(fm.autoAnimateRestart, false),
293
+ autoSlide: num(fm.autoslide ?? fm.autoSlide),
294
+ clicks: num(fm.clicks),
295
+ clicksStart: num(fm.clicksStart) ?? 0,
296
+ preload: bool(fm.preload, true),
297
+ timing: num(fm.timing) ?? config.defaultTiming,
298
+ state: Array.isArray(state) ? state.map(String) : str(state) ? [str(state)] : [],
299
+ colorScheme: fm.colorScheme === "dark" || fm.colorScheme === "light" ? fm.colorScheme : null,
300
+ zoom: num(fm.zoom) ?? 1,
301
+ background: resolveBackground(fm)
302
+ };
303
+ }
304
+ function columnsOf(slides, config) {
305
+ const visible = slides.filter((s) => !resolveSlideOptions(s.frontmatter, config, s.index === 0).hidden);
306
+ const byH = /* @__PURE__ */ new Map();
307
+ for (const s of visible) {
308
+ const list = byH.get(s.h) ?? [];
309
+ list.push(s);
310
+ byH.set(s.h, list);
311
+ }
312
+ return [...byH.entries()].sort((a, b) => a[0] - b[0]).map(([, list], i) => ({
313
+ h: i,
314
+ slides: list.sort((a, b) => a.v - b.v)
315
+ }));
316
+ }
317
+ /** Column and row of the visible slide with this flat index. */
318
+ function fromFlat(columns, flat) {
319
+ let left = Math.max(0, flat);
320
+ for (let h = 0; h < columns.length; h++) {
321
+ const len = columns[h].slides.length;
322
+ if (left < len) return {
323
+ h,
324
+ v: left
325
+ };
326
+ left -= len;
327
+ }
328
+ const h = Math.max(0, columns.length - 1);
329
+ return {
330
+ h,
331
+ v: Math.max(0, (columns[h]?.slides.length ?? 1) - 1)
332
+ };
333
+ }
334
+ /** Find a slide by its id (frontmatter `id`, `routeAlias`, or the generated `sN`). */
335
+ function findById(columns, id) {
336
+ for (const c of columns) for (let v = 0; v < c.slides.length; v++) {
337
+ const s = c.slides[v];
338
+ if (s.id === id || s.frontmatter.routeAlias === id) return {
339
+ h: c.h,
340
+ v
341
+ };
342
+ }
343
+ return null;
344
+ }
345
+ /** The global a host sets, validated: anything malformed is ignored rather than trusted. */
346
+ function readHostOverride(scope = globalThis) {
347
+ const raw = scope?.__KADAL_DECK__;
348
+ if (!raw || typeof raw !== "object") return null;
349
+ const o = raw;
350
+ const out = {};
351
+ if (typeof o.room === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(o.room)) out.room = o.room;
352
+ if (typeof o.api === "string" && o.api.length > 0) out.api = o.api;
353
+ if (typeof o.badge === "boolean") out.badge = o.badge;
354
+ return out;
355
+ }
356
+ /** The config with the host's word laid over it. Returns the same object when there is none. */
357
+ function withHostOverride(config, override) {
358
+ if (!override || !override.room && !override.api && override.badge === void 0) return config;
359
+ return {
360
+ ...config,
361
+ live: {
362
+ ...config.live,
363
+ ...override.room ? { room: override.room } : {},
364
+ ...override.api ? { api: override.api } : {}
365
+ },
366
+ ...override.badge !== void 0 ? { badge: override.badge } : {}
367
+ };
368
+ }
369
+ /**
370
+ * The id every host call is keyed by — the room, presenter login, stats, beacons, share links and
371
+ * exports. A published deck's is the room the host assigned; a self-hosted one's is its slug.
372
+ */
373
+ function roomIdOf(config) {
374
+ return config.live.room || config.slug || "deck";
375
+ }
376
+ //#endregion
377
+ export { DEFAULT_CONFIG, columnsOf, findById, fromFlat, readHostOverride, resolveBackground, resolveConfig, resolveSlideOptions, roomIdOf, withHostOverride };