@reslide-dev/core 0.2.0 → 0.2.1

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.mjs CHANGED
@@ -1,1507 +1,7 @@
1
- import { Children, Fragment, createContext, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { C as ClickNavigation, S as useDeck, _ as NavigationBar, a as PresenterView, b as DrawingLayer, c as Mark, d as Slide, f as Deck, g as useSlideIndex, h as SlideIndexContext, i as GlobalLayer, l as Click, m as PrintView, n as Toc, o as SlotRight, p as ProgressBar, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, v as isPresenterView, x as DeckContext, y as openPresenterWindow } from "./Mermaid-BtXG2Ea2.mjs";
2
+ import { Fragment, useCallback, useEffect, useRef, useState } from "react";
2
3
  import * as runtime from "react/jsx-runtime";
3
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
- //#region src/ClickNavigation.tsx
5
- /**
6
- * Invisible click zones on the left/right edges of the slide.
7
- * Clicking the left ~15% goes to the previous slide,
8
- * clicking the right ~15% goes to the next slide.
9
- * Shows a subtle arrow indicator on hover.
10
- */
11
- function ClickNavigation({ onPrev, onNext, disabled }) {
12
- if (disabled) return null;
13
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(NavZone, {
14
- direction: "prev",
15
- onClick: onPrev
16
- }), /* @__PURE__ */ jsx(NavZone, {
17
- direction: "next",
18
- onClick: onNext
19
- })] });
20
- }
21
- function NavZone({ direction, onClick }) {
22
- const [hovered, setHovered] = useState(false);
23
- const isPrev = direction === "prev";
24
- return /* @__PURE__ */ jsx("button", {
25
- type: "button",
26
- onClick: useCallback((e) => {
27
- e.stopPropagation();
28
- onClick();
29
- }, [onClick]),
30
- onMouseEnter: () => setHovered(true),
31
- onMouseLeave: () => setHovered(false),
32
- "aria-label": isPrev ? "Previous slide" : "Next slide",
33
- style: {
34
- position: "absolute",
35
- top: 0,
36
- bottom: 0,
37
- [isPrev ? "left" : "right"]: 0,
38
- width: "15%",
39
- background: "none",
40
- border: "none",
41
- cursor: "pointer",
42
- zIndex: 20,
43
- display: "flex",
44
- alignItems: "center",
45
- justifyContent: isPrev ? "flex-start" : "flex-end",
46
- padding: "0 1.5rem",
47
- opacity: hovered ? 1 : 0,
48
- transition: "opacity 0.2s ease"
49
- },
50
- children: /* @__PURE__ */ jsx("svg", {
51
- width: "32",
52
- height: "32",
53
- viewBox: "0 0 24 24",
54
- fill: "none",
55
- stroke: "currentColor",
56
- strokeWidth: "2",
57
- strokeLinecap: "round",
58
- strokeLinejoin: "round",
59
- style: {
60
- color: "var(--slide-text, #1a1a1a)",
61
- opacity: .4,
62
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))",
63
- transform: isPrev ? "none" : "rotate(180deg)"
64
- },
65
- children: /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" })
66
- })
67
- });
68
- }
69
- //#endregion
70
- //#region src/context.ts
71
- const DeckContext = createContext(null);
72
- function useDeck() {
73
- const ctx = useContext(DeckContext);
74
- if (!ctx) throw new Error("useDeck must be used within a <Deck> component");
75
- return ctx;
76
- }
77
- //#endregion
78
- //#region src/DrawingLayer.tsx
79
- /**
80
- * Canvas-based freehand drawing overlay for presentations.
81
- * Drawings are stored per slide and persist across navigation.
82
- * Toggle with `d` key, clear current slide with `c` key.
83
- */
84
- function DrawingLayer({ active, currentSlide, color = "#ef4444", width = 3 }) {
85
- const canvasRef = useRef(null);
86
- const [isDrawing, setIsDrawing] = useState(false);
87
- const lastPoint = useRef(null);
88
- const drawingsRef = useRef(/* @__PURE__ */ new Map());
89
- const prevSlideRef = useRef(currentSlide);
90
- const getCanvasSize = useCallback(() => {
91
- const canvas = canvasRef.current;
92
- if (!canvas) return {
93
- w: 0,
94
- h: 0
95
- };
96
- return {
97
- w: canvas.width,
98
- h: canvas.height
99
- };
100
- }, []);
101
- const saveCurrentSlide = useCallback((slideIndex) => {
102
- const canvas = canvasRef.current;
103
- const ctx = canvas?.getContext("2d");
104
- if (!ctx || !canvas) return;
105
- const { w, h } = getCanvasSize();
106
- if (w === 0 || h === 0) return;
107
- const imageData = ctx.getImageData(0, 0, w, h);
108
- if (imageData.data.some((_, i) => i % 4 === 3 && imageData.data[i] > 0)) drawingsRef.current.set(slideIndex, imageData);
109
- else drawingsRef.current.delete(slideIndex);
110
- }, [getCanvasSize]);
111
- const restoreSlide = useCallback((slideIndex) => {
112
- const canvas = canvasRef.current;
113
- const ctx = canvas?.getContext("2d");
114
- if (!ctx || !canvas) return;
115
- const { w, h } = getCanvasSize();
116
- ctx.clearRect(0, 0, w, h);
117
- const saved = drawingsRef.current.get(slideIndex);
118
- if (saved && saved.width === w && saved.height === h) ctx.putImageData(saved, 0, 0);
119
- }, [getCanvasSize]);
120
- useEffect(() => {
121
- if (prevSlideRef.current !== currentSlide) {
122
- saveCurrentSlide(prevSlideRef.current);
123
- restoreSlide(currentSlide);
124
- prevSlideRef.current = currentSlide;
125
- }
126
- }, [
127
- currentSlide,
128
- saveCurrentSlide,
129
- restoreSlide
130
- ]);
131
- const getPoint = useCallback((e) => {
132
- const canvas = canvasRef.current;
133
- const rect = canvas.getBoundingClientRect();
134
- return {
135
- x: (e.clientX - rect.left) * (canvas.width / rect.width),
136
- y: (e.clientY - rect.top) * (canvas.height / rect.height)
137
- };
138
- }, []);
139
- const startDraw = useCallback((e) => {
140
- if (!active) return;
141
- setIsDrawing(true);
142
- lastPoint.current = getPoint(e);
143
- }, [active, getPoint]);
144
- const draw = useCallback((e) => {
145
- if (!isDrawing || !active) return;
146
- const ctx = canvasRef.current?.getContext("2d");
147
- if (!ctx || !lastPoint.current) return;
148
- const point = getPoint(e);
149
- ctx.beginPath();
150
- ctx.moveTo(lastPoint.current.x, lastPoint.current.y);
151
- ctx.lineTo(point.x, point.y);
152
- ctx.strokeStyle = color;
153
- ctx.lineWidth = width;
154
- ctx.lineCap = "round";
155
- ctx.lineJoin = "round";
156
- ctx.stroke();
157
- lastPoint.current = point;
158
- }, [
159
- isDrawing,
160
- active,
161
- color,
162
- width,
163
- getPoint
164
- ]);
165
- const stopDraw = useCallback(() => {
166
- setIsDrawing(false);
167
- lastPoint.current = null;
168
- }, []);
169
- useEffect(() => {
170
- const canvas = canvasRef.current;
171
- if (!canvas) return;
172
- const resize = () => {
173
- const rect = canvas.parentElement?.getBoundingClientRect();
174
- if (!rect) return;
175
- const newWidth = rect.width * window.devicePixelRatio;
176
- const newHeight = rect.height * window.devicePixelRatio;
177
- if (canvas.width !== newWidth || canvas.height !== newHeight) {
178
- saveCurrentSlide(currentSlide);
179
- drawingsRef.current.clear();
180
- canvas.width = newWidth;
181
- canvas.height = newHeight;
182
- }
183
- };
184
- resize();
185
- window.addEventListener("resize", resize);
186
- return () => window.removeEventListener("resize", resize);
187
- }, [currentSlide, saveCurrentSlide]);
188
- useEffect(() => {
189
- if (!active) return;
190
- function handleKey(e) {
191
- if (e.key === "c") {
192
- const canvas = canvasRef.current;
193
- const ctx = canvas?.getContext("2d");
194
- if (ctx && canvas) {
195
- ctx.clearRect(0, 0, canvas.width, canvas.height);
196
- drawingsRef.current.delete(currentSlide);
197
- }
198
- }
199
- }
200
- window.addEventListener("keydown", handleKey);
201
- return () => window.removeEventListener("keydown", handleKey);
202
- }, [active, currentSlide]);
203
- return /* @__PURE__ */ jsx("canvas", {
204
- ref: canvasRef,
205
- onMouseDown: startDraw,
206
- onMouseMove: draw,
207
- onMouseUp: stopDraw,
208
- onMouseLeave: stopDraw,
209
- style: {
210
- position: "absolute",
211
- inset: 0,
212
- width: "100%",
213
- height: "100%",
214
- cursor: active ? "crosshair" : "default",
215
- pointerEvents: active ? "auto" : "none",
216
- zIndex: 50
217
- }
218
- });
219
- }
220
- //#endregion
221
- //#region src/use-presenter.ts
222
- const CHANNEL_NAME = "reslide-presenter";
223
- /**
224
- * Hook for syncing presentation state across windows via BroadcastChannel.
225
- * The main presentation window broadcasts state changes and listens for
226
- * navigation commands from the presenter window.
227
- */
228
- function usePresenterSync(currentSlide, clickStep, handlers) {
229
- const channelRef = useRef(null);
230
- useEffect(() => {
231
- if (typeof BroadcastChannel === "undefined") return;
232
- const channel = new BroadcastChannel(CHANNEL_NAME);
233
- channelRef.current = channel;
234
- if (handlers) channel.onmessage = (e) => {
235
- if (e.data.type === "navigate") switch (e.data.action) {
236
- case "next":
237
- handlers.next();
238
- break;
239
- case "prev":
240
- handlers.prev();
241
- break;
242
- case "goTo":
243
- if (e.data.slideIndex != null) handlers.goTo(e.data.slideIndex);
244
- break;
245
- }
246
- };
247
- return () => {
248
- channel.close();
249
- channelRef.current = null;
250
- };
251
- }, [handlers]);
252
- useEffect(() => {
253
- channelRef.current?.postMessage({
254
- type: "sync",
255
- currentSlide,
256
- clickStep
257
- });
258
- }, [currentSlide, clickStep]);
259
- }
260
- /**
261
- * Hook for the presenter window to listen for sync messages and send
262
- * navigation commands back to the main window.
263
- */
264
- function usePresenterChannel(onSync) {
265
- const channelRef = useRef(null);
266
- const onSyncRef = useRef(onSync);
267
- onSyncRef.current = onSync;
268
- useEffect(() => {
269
- if (typeof BroadcastChannel === "undefined") return;
270
- const channel = new BroadcastChannel(CHANNEL_NAME);
271
- channelRef.current = channel;
272
- channel.onmessage = (e) => {
273
- if (e.data.type === "sync") onSyncRef.current(e.data);
274
- };
275
- return () => {
276
- channel.close();
277
- channelRef.current = null;
278
- };
279
- }, []);
280
- return {
281
- next: useCallback(() => {
282
- channelRef.current?.postMessage({
283
- type: "navigate",
284
- action: "next"
285
- });
286
- }, []),
287
- prev: useCallback(() => {
288
- channelRef.current?.postMessage({
289
- type: "navigate",
290
- action: "prev"
291
- });
292
- }, []),
293
- goTo: useCallback((index) => {
294
- channelRef.current?.postMessage({
295
- type: "navigate",
296
- action: "goTo",
297
- slideIndex: index
298
- });
299
- }, [])
300
- };
301
- }
302
- /**
303
- * Opens the presenter window at the /presenter route.
304
- */
305
- function openPresenterWindow() {
306
- const url = new URL(window.location.href);
307
- url.searchParams.set("presenter", "true");
308
- window.open(url.toString(), "reslide-presenter", "width=1024,height=768,menubar=no,toolbar=no");
309
- }
310
- /**
311
- * Check if the current window is the presenter view.
312
- */
313
- function isPresenterView() {
314
- if (typeof window === "undefined") return false;
315
- return new URLSearchParams(window.location.search).get("presenter") === "true";
316
- }
317
- //#endregion
318
- //#region src/NavigationBar.tsx
319
- /**
320
- * Slidev-style navigation bar that appears on hover at the bottom of the presentation.
321
- * Provides buttons for prev/next, overview, fullscreen, presenter, and drawing modes.
322
- */
323
- function NavigationBar({ isDrawing, onToggleDrawing }) {
324
- const { currentSlide, totalSlides, clickStep, totalClickSteps, isOverview, isFullscreen, next, prev, toggleOverview, toggleFullscreen } = useDeck();
325
- const [visible, setVisible] = useState(false);
326
- const timerRef = useRef(null);
327
- const barRef = useRef(null);
328
- const showBar = useCallback(() => {
329
- setVisible(true);
330
- if (timerRef.current) clearTimeout(timerRef.current);
331
- timerRef.current = setTimeout(() => setVisible(false), 3e3);
332
- }, []);
333
- useEffect(() => {
334
- function handleMouseMove(e) {
335
- const threshold = window.innerHeight - 80;
336
- if (e.clientY > threshold) showBar();
337
- }
338
- window.addEventListener("mousemove", handleMouseMove);
339
- return () => window.removeEventListener("mousemove", handleMouseMove);
340
- }, [showBar]);
341
- const handleMouseEnter = () => {
342
- if (timerRef.current) clearTimeout(timerRef.current);
343
- setVisible(true);
344
- };
345
- const handleMouseLeave = () => {
346
- timerRef.current = setTimeout(() => setVisible(false), 1500);
347
- };
348
- return /* @__PURE__ */ jsxs("div", {
349
- ref: barRef,
350
- className: "reslide-navbar",
351
- onMouseEnter: handleMouseEnter,
352
- onMouseLeave: handleMouseLeave,
353
- style: {
354
- position: "absolute",
355
- bottom: 0,
356
- left: "50%",
357
- transform: `translateX(-50%) translateY(${visible ? "0" : "100%"})`,
358
- display: "flex",
359
- alignItems: "center",
360
- gap: "0.25rem",
361
- padding: "0.375rem 0.75rem",
362
- backgroundColor: "rgba(0, 0, 0, 0.7)",
363
- backdropFilter: "blur(8px)",
364
- borderRadius: "0.5rem 0.5rem 0 0",
365
- transition: "transform 0.25s ease, opacity 0.25s ease",
366
- opacity: visible ? 1 : 0,
367
- zIndex: 200,
368
- color: "#e2e8f0",
369
- fontSize: "0.8125rem",
370
- fontFamily: "system-ui, sans-serif",
371
- fontVariantNumeric: "tabular-nums",
372
- pointerEvents: visible ? "auto" : "none"
373
- },
374
- children: [
375
- /* @__PURE__ */ jsx(NavButton, {
376
- onClick: prev,
377
- title: "Previous (←)",
378
- disabled: isOverview,
379
- children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "left" })
380
- }),
381
- /* @__PURE__ */ jsxs("span", {
382
- style: {
383
- padding: "0 0.5rem",
384
- userSelect: "none",
385
- whiteSpace: "nowrap"
386
- },
387
- children: [
388
- currentSlide + 1,
389
- " / ",
390
- totalSlides,
391
- clickStep > 0 && totalClickSteps > 0 && /* @__PURE__ */ jsxs("span", {
392
- style: {
393
- opacity: .6,
394
- marginLeft: "0.25rem"
395
- },
396
- children: [
397
- "(",
398
- clickStep,
399
- "/",
400
- totalClickSteps,
401
- ")"
402
- ]
403
- })
404
- ]
405
- }),
406
- /* @__PURE__ */ jsx(NavButton, {
407
- onClick: next,
408
- title: "Next (→ / Space)",
409
- disabled: isOverview,
410
- children: /* @__PURE__ */ jsx(ArrowIcon, { direction: "right" })
411
- }),
412
- /* @__PURE__ */ jsx(Divider, {}),
413
- /* @__PURE__ */ jsx(NavButton, {
414
- onClick: toggleOverview,
415
- title: "Overview (Esc)",
416
- active: isOverview,
417
- children: /* @__PURE__ */ jsx(GridIcon, {})
418
- }),
419
- /* @__PURE__ */ jsx(NavButton, {
420
- onClick: toggleFullscreen,
421
- title: "Fullscreen (f)",
422
- active: isFullscreen,
423
- children: /* @__PURE__ */ jsx(FullscreenIcon, { expanded: isFullscreen })
424
- }),
425
- /* @__PURE__ */ jsx(NavButton, {
426
- onClick: openPresenterWindow,
427
- title: "Presenter (p)",
428
- children: /* @__PURE__ */ jsx(PresenterIcon, {})
429
- }),
430
- /* @__PURE__ */ jsx(NavButton, {
431
- onClick: onToggleDrawing,
432
- title: "Drawing (d)",
433
- active: isDrawing,
434
- children: /* @__PURE__ */ jsx(PenIcon, {})
435
- })
436
- ]
437
- });
438
- }
439
- function NavButton({ children, onClick, title, active, disabled }) {
440
- return /* @__PURE__ */ jsx("button", {
441
- type: "button",
442
- onClick,
443
- title,
444
- disabled,
445
- style: {
446
- display: "flex",
447
- alignItems: "center",
448
- justifyContent: "center",
449
- width: "2rem",
450
- height: "2rem",
451
- background: active ? "rgba(255,255,255,0.2)" : "none",
452
- border: "none",
453
- borderRadius: "0.25rem",
454
- cursor: disabled ? "default" : "pointer",
455
- color: active ? "#fff" : "#cbd5e1",
456
- opacity: disabled ? .4 : 1,
457
- transition: "background 0.15s, color 0.15s",
458
- padding: 0
459
- },
460
- onMouseEnter: (e) => {
461
- if (!disabled) e.currentTarget.style.background = "rgba(255,255,255,0.15)";
462
- },
463
- onMouseLeave: (e) => {
464
- e.currentTarget.style.background = active ? "rgba(255,255,255,0.2)" : "none";
465
- },
466
- children
467
- });
468
- }
469
- function Divider() {
470
- return /* @__PURE__ */ jsx("div", { style: {
471
- width: 1,
472
- height: "1.25rem",
473
- backgroundColor: "rgba(255,255,255,0.2)",
474
- margin: "0 0.25rem"
475
- } });
476
- }
477
- function ArrowIcon({ direction }) {
478
- return /* @__PURE__ */ jsx("svg", {
479
- width: "16",
480
- height: "16",
481
- viewBox: "0 0 24 24",
482
- fill: "none",
483
- stroke: "currentColor",
484
- strokeWidth: "2",
485
- strokeLinecap: "round",
486
- strokeLinejoin: "round",
487
- children: direction === "left" ? /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" }) : /* @__PURE__ */ jsx("polyline", { points: "9 6 15 12 9 18" })
488
- });
489
- }
490
- function GridIcon() {
491
- return /* @__PURE__ */ jsxs("svg", {
492
- width: "16",
493
- height: "16",
494
- viewBox: "0 0 24 24",
495
- fill: "none",
496
- stroke: "currentColor",
497
- strokeWidth: "2",
498
- strokeLinecap: "round",
499
- strokeLinejoin: "round",
500
- children: [
501
- /* @__PURE__ */ jsx("rect", {
502
- x: "3",
503
- y: "3",
504
- width: "7",
505
- height: "7"
506
- }),
507
- /* @__PURE__ */ jsx("rect", {
508
- x: "14",
509
- y: "3",
510
- width: "7",
511
- height: "7"
512
- }),
513
- /* @__PURE__ */ jsx("rect", {
514
- x: "3",
515
- y: "14",
516
- width: "7",
517
- height: "7"
518
- }),
519
- /* @__PURE__ */ jsx("rect", {
520
- x: "14",
521
- y: "14",
522
- width: "7",
523
- height: "7"
524
- })
525
- ]
526
- });
527
- }
528
- function FullscreenIcon({ expanded }) {
529
- return /* @__PURE__ */ jsx("svg", {
530
- width: "16",
531
- height: "16",
532
- viewBox: "0 0 24 24",
533
- fill: "none",
534
- stroke: "currentColor",
535
- strokeWidth: "2",
536
- strokeLinecap: "round",
537
- strokeLinejoin: "round",
538
- children: expanded ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
539
- /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
540
- /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
541
- /* @__PURE__ */ jsx("line", {
542
- x1: "14",
543
- y1: "10",
544
- x2: "21",
545
- y2: "3"
546
- }),
547
- /* @__PURE__ */ jsx("line", {
548
- x1: "3",
549
- y1: "21",
550
- x2: "10",
551
- y2: "14"
552
- })
553
- ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
554
- /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
555
- /* @__PURE__ */ jsx("polyline", { points: "9 21 3 21 3 15" }),
556
- /* @__PURE__ */ jsx("line", {
557
- x1: "21",
558
- y1: "3",
559
- x2: "14",
560
- y2: "10"
561
- }),
562
- /* @__PURE__ */ jsx("line", {
563
- x1: "3",
564
- y1: "21",
565
- x2: "10",
566
- y2: "14"
567
- })
568
- ] })
569
- });
570
- }
571
- function PresenterIcon() {
572
- return /* @__PURE__ */ jsxs("svg", {
573
- width: "16",
574
- height: "16",
575
- viewBox: "0 0 24 24",
576
- fill: "none",
577
- stroke: "currentColor",
578
- strokeWidth: "2",
579
- strokeLinecap: "round",
580
- strokeLinejoin: "round",
581
- children: [
582
- /* @__PURE__ */ jsx("rect", {
583
- x: "2",
584
- y: "3",
585
- width: "20",
586
- height: "14",
587
- rx: "2"
588
- }),
589
- /* @__PURE__ */ jsx("line", {
590
- x1: "8",
591
- y1: "21",
592
- x2: "16",
593
- y2: "21"
594
- }),
595
- /* @__PURE__ */ jsx("line", {
596
- x1: "12",
597
- y1: "17",
598
- x2: "12",
599
- y2: "21"
600
- })
601
- ]
602
- });
603
- }
604
- function PenIcon() {
605
- return /* @__PURE__ */ jsxs("svg", {
606
- width: "16",
607
- height: "16",
608
- viewBox: "0 0 24 24",
609
- fill: "none",
610
- stroke: "currentColor",
611
- strokeWidth: "2",
612
- strokeLinecap: "round",
613
- strokeLinejoin: "round",
614
- children: [
615
- /* @__PURE__ */ jsx("path", { d: "M12 19l7-7 3 3-7 7-3-3z" }),
616
- /* @__PURE__ */ jsx("path", { d: "M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z" }),
617
- /* @__PURE__ */ jsx("path", { d: "M2 2l7.586 7.586" }),
618
- /* @__PURE__ */ jsx("circle", {
619
- cx: "11",
620
- cy: "11",
621
- r: "2"
622
- })
623
- ]
624
- });
625
- }
626
- //#endregion
627
- //#region src/slide-context.ts
628
- const SlideIndexContext = createContext(null);
629
- function useSlideIndex() {
630
- const index = useContext(SlideIndexContext);
631
- if (index == null) throw new Error("useSlideIndex must be used within a <Slide> component");
632
- return index;
633
- }
634
- //#endregion
635
- //#region src/PrintView.tsx
636
- /**
637
- * Renders all slides vertically for print/PDF export.
638
- * Use with @media print CSS to generate PDFs via browser print.
639
- */
640
- function PrintView({ children }) {
641
- return /* @__PURE__ */ jsx("div", {
642
- className: "reslide-print-view",
643
- children: Children.toArray(children).map((slide, i) => /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
644
- value: i,
645
- children: slide
646
- }, i))
647
- });
648
- }
649
- //#endregion
650
- //#region src/ProgressBar.tsx
651
- function ProgressBar() {
652
- const { currentSlide, totalSlides, clickStep, totalClickSteps } = useDeck();
653
- const slideProgress = totalSlides <= 1 ? 1 : currentSlide / (totalSlides - 1);
654
- const clickFraction = totalSlides <= 1 ? 0 : totalClickSteps > 0 ? clickStep / totalClickSteps * (1 / (totalSlides - 1)) : 0;
655
- return /* @__PURE__ */ jsx("div", {
656
- className: "reslide-progress-bar",
657
- style: {
658
- position: "absolute",
659
- top: 0,
660
- left: 0,
661
- width: "100%",
662
- height: "3px",
663
- zIndex: 100
664
- },
665
- children: /* @__PURE__ */ jsx("div", { style: {
666
- height: "100%",
667
- width: `${Math.min(slideProgress + clickFraction, 1) * 100}%`,
668
- backgroundColor: "var(--slide-accent, #3b82f6)",
669
- transition: "width 0.3s ease"
670
- } })
671
- });
672
- }
673
- //#endregion
674
- //#region src/SlideTransition.tsx
675
- const DURATION = 300;
676
- function SlideTransition({ children, currentSlide, transition }) {
677
- const slides = Children.toArray(children);
678
- const [displaySlide, setDisplaySlide] = useState(currentSlide);
679
- const [prevSlide, setPrevSlide] = useState(null);
680
- const [isAnimating, setIsAnimating] = useState(false);
681
- const prevCurrentRef = useRef(currentSlide);
682
- const timerRef = useRef(null);
683
- useEffect(() => {
684
- if (currentSlide === prevCurrentRef.current) return;
685
- const from = prevCurrentRef.current;
686
- prevCurrentRef.current = currentSlide;
687
- if (transition === "none") {
688
- setDisplaySlide(currentSlide);
689
- return;
690
- }
691
- setPrevSlide(from);
692
- setDisplaySlide(currentSlide);
693
- setIsAnimating(true);
694
- if (timerRef.current) clearTimeout(timerRef.current);
695
- timerRef.current = setTimeout(() => {
696
- setIsAnimating(false);
697
- setPrevSlide(null);
698
- }, DURATION);
699
- return () => {
700
- if (timerRef.current) clearTimeout(timerRef.current);
701
- };
702
- }, [currentSlide, transition]);
703
- const resolvedTransition = resolveTransition(transition, prevSlide, displaySlide);
704
- if (transition === "none" || !isAnimating) return /* @__PURE__ */ jsx("div", {
705
- className: "reslide-transition-container",
706
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
707
- value: displaySlide,
708
- children: /* @__PURE__ */ jsx("div", {
709
- className: "reslide-transition-slide",
710
- children: slides[displaySlide]
711
- })
712
- })
713
- });
714
- return /* @__PURE__ */ jsxs("div", {
715
- className: "reslide-transition-container",
716
- children: [prevSlide != null && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
717
- value: prevSlide,
718
- children: /* @__PURE__ */ jsx("div", {
719
- className: `reslide-transition-slide reslide-transition-${resolvedTransition}-exit`,
720
- children: slides[prevSlide]
721
- })
722
- }), /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
723
- value: displaySlide,
724
- children: /* @__PURE__ */ jsx("div", {
725
- className: `reslide-transition-slide reslide-transition-${resolvedTransition}-enter`,
726
- children: slides[displaySlide]
727
- })
728
- })]
729
- });
730
- }
731
- function resolveTransition(transition, from, to) {
732
- if (transition !== "slide-left" && transition !== "slide-right") return transition;
733
- if (from == null) return transition;
734
- const goingForward = to > from;
735
- if (transition === "slide-left") return goingForward ? "slide-left" : "slide-right";
736
- return goingForward ? "slide-right" : "slide-left";
737
- }
738
- //#endregion
739
- //#region src/use-fullscreen.ts
740
- function useFullscreen(ref) {
741
- const [isFullscreen, setIsFullscreen] = useState(false);
742
- useEffect(() => {
743
- function handleChange() {
744
- setIsFullscreen(document.fullscreenElement != null);
745
- }
746
- document.addEventListener("fullscreenchange", handleChange);
747
- return () => document.removeEventListener("fullscreenchange", handleChange);
748
- }, []);
749
- return {
750
- isFullscreen,
751
- toggleFullscreen: useCallback(() => {
752
- if (!ref.current) return;
753
- if (document.fullscreenElement) document.exitFullscreen();
754
- else ref.current.requestFullscreen();
755
- }, [ref])
756
- };
757
- }
758
- //#endregion
759
- //#region src/Deck.tsx
760
- function Deck({ children, transition = "none" }) {
761
- const containerRef = useRef(null);
762
- const [currentSlide, setCurrentSlide] = useState(0);
763
- const [clickStep, setClickStep] = useState(0);
764
- const [isOverview, setIsOverview] = useState(false);
765
- const [isDrawing, setIsDrawing] = useState(false);
766
- const [isPrinting, setIsPrinting] = useState(false);
767
- const [clickStepsMap, setClickStepsMap] = useState({});
768
- const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);
769
- const totalSlides = Children.count(children);
770
- const totalClickSteps = clickStepsMap[currentSlide] ?? 0;
771
- const registerClickSteps = useCallback((slideIndex, count) => {
772
- setClickStepsMap((prev) => {
773
- if (prev[slideIndex] === count) return prev;
774
- return {
775
- ...prev,
776
- [slideIndex]: count
777
- };
778
- });
779
- }, []);
780
- const next = useCallback(() => {
781
- if (isOverview) return;
782
- if (clickStep < totalClickSteps) setClickStep((s) => s + 1);
783
- else if (currentSlide < totalSlides - 1) {
784
- setCurrentSlide((s) => s + 1);
785
- setClickStep(0);
786
- }
787
- }, [
788
- isOverview,
789
- clickStep,
790
- totalClickSteps,
791
- currentSlide,
792
- totalSlides
793
- ]);
794
- const prev = useCallback(() => {
795
- if (isOverview) return;
796
- if (clickStep > 0) setClickStep((s) => s - 1);
797
- else if (currentSlide > 0) {
798
- const prevSlide = currentSlide - 1;
799
- setCurrentSlide(prevSlide);
800
- setClickStep(clickStepsMap[prevSlide] ?? 0);
801
- }
802
- }, [
803
- isOverview,
804
- clickStep,
805
- currentSlide,
806
- clickStepsMap
807
- ]);
808
- const goTo = useCallback((slideIndex) => {
809
- if (slideIndex >= 0 && slideIndex < totalSlides) {
810
- setCurrentSlide(slideIndex);
811
- setClickStep(0);
812
- if (isOverview) setIsOverview(false);
813
- }
814
- }, [totalSlides, isOverview]);
815
- usePresenterSync(currentSlide, clickStep, useMemo(() => ({
816
- next,
817
- prev,
818
- goTo
819
- }), [
820
- next,
821
- prev,
822
- goTo
823
- ]));
824
- const toggleOverview = useCallback(() => {
825
- setIsOverview((v) => !v);
826
- }, []);
827
- useEffect(() => {
828
- function handleKeyDown(e) {
829
- if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
830
- switch (e.key) {
831
- case "ArrowRight":
832
- case " ":
833
- e.preventDefault();
834
- next();
835
- break;
836
- case "ArrowLeft":
837
- e.preventDefault();
838
- prev();
839
- break;
840
- case "Escape":
841
- if (!document.fullscreenElement) {
842
- e.preventDefault();
843
- toggleOverview();
844
- }
845
- break;
846
- case "f":
847
- e.preventDefault();
848
- toggleFullscreen();
849
- break;
850
- case "p":
851
- e.preventDefault();
852
- openPresenterWindow();
853
- break;
854
- case "d":
855
- e.preventDefault();
856
- setIsDrawing((v) => !v);
857
- break;
858
- }
859
- }
860
- window.addEventListener("keydown", handleKeyDown);
861
- return () => window.removeEventListener("keydown", handleKeyDown);
862
- }, [
863
- next,
864
- prev,
865
- toggleOverview,
866
- toggleFullscreen
867
- ]);
868
- useEffect(() => {
869
- function onBeforePrint() {
870
- setIsPrinting(true);
871
- }
872
- function onAfterPrint() {
873
- setIsPrinting(false);
874
- }
875
- window.addEventListener("beforeprint", onBeforePrint);
876
- window.addEventListener("afterprint", onAfterPrint);
877
- return () => {
878
- window.removeEventListener("beforeprint", onBeforePrint);
879
- window.removeEventListener("afterprint", onAfterPrint);
880
- };
881
- }, []);
882
- const contextValue = useMemo(() => ({
883
- currentSlide,
884
- totalSlides,
885
- clickStep,
886
- totalClickSteps,
887
- isOverview,
888
- isFullscreen,
889
- next,
890
- prev,
891
- goTo,
892
- toggleOverview,
893
- toggleFullscreen,
894
- registerClickSteps
895
- }), [
896
- currentSlide,
897
- totalSlides,
898
- clickStep,
899
- totalClickSteps,
900
- isOverview,
901
- isFullscreen,
902
- next,
903
- prev,
904
- goTo,
905
- toggleOverview,
906
- toggleFullscreen,
907
- registerClickSteps
908
- ]);
909
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
910
- value: contextValue,
911
- children: /* @__PURE__ */ jsxs("div", {
912
- ref: containerRef,
913
- className: "reslide-deck",
914
- style: {
915
- position: "relative",
916
- width: "100%",
917
- height: "100%",
918
- overflow: "hidden",
919
- backgroundColor: "var(--slide-bg, #fff)",
920
- color: "var(--slide-text, #1a1a1a)"
921
- },
922
- children: [
923
- isPrinting ? /* @__PURE__ */ jsx(PrintView, { children }) : isOverview ? /* @__PURE__ */ jsx(OverviewGrid, {
924
- totalSlides,
925
- goTo,
926
- children
927
- }) : /* @__PURE__ */ jsx(SlideTransition, {
928
- currentSlide,
929
- transition,
930
- children
931
- }),
932
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ClickNavigation, {
933
- onPrev: prev,
934
- onNext: next,
935
- disabled: isDrawing
936
- }),
937
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ProgressBar, {}),
938
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(DrawingLayer, {
939
- active: isDrawing,
940
- currentSlide
941
- }),
942
- !isPrinting && /* @__PURE__ */ jsx(NavigationBar, {
943
- isDrawing,
944
- onToggleDrawing: () => setIsDrawing((v) => !v)
945
- })
946
- ]
947
- })
948
- });
949
- }
950
- function OverviewGrid({ children, totalSlides, goTo }) {
951
- const slides = Children.toArray(children);
952
- return /* @__PURE__ */ jsx("div", {
953
- className: "reslide-overview",
954
- style: {
955
- display: "grid",
956
- gridTemplateColumns: `repeat(${Math.ceil(Math.sqrt(totalSlides))}, 1fr)`,
957
- gap: "1rem",
958
- padding: "1rem",
959
- width: "100%",
960
- height: "100%",
961
- overflow: "auto"
962
- },
963
- children: slides.map((slide, i) => /* @__PURE__ */ jsx("button", {
964
- type: "button",
965
- onClick: () => goTo(i),
966
- style: {
967
- border: "1px solid var(--slide-accent, #3b82f6)",
968
- borderRadius: "0.5rem",
969
- overflow: "hidden",
970
- cursor: "pointer",
971
- background: "var(--slide-bg, #fff)",
972
- aspectRatio: "16 / 9",
973
- padding: 0,
974
- position: "relative"
975
- },
976
- children: /* @__PURE__ */ jsx("div", {
977
- style: {
978
- transform: "scale(0.25)",
979
- transformOrigin: "top left",
980
- width: "400%",
981
- height: "400%",
982
- pointerEvents: "none"
983
- },
984
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
985
- value: i,
986
- children: slide
987
- })
988
- })
989
- }, i))
990
- });
991
- }
992
- //#endregion
993
- //#region src/Slide.tsx
994
- const baseStyle = {
995
- width: "100%",
996
- height: "100%",
997
- display: "flex",
998
- boxSizing: "border-box"
999
- };
1000
- function isSlotRight(child) {
1001
- return isValidElement(child) && typeof child.type === "function" && "__reslideSlot" in child.type && child.type.__reslideSlot === "right";
1002
- }
1003
- function splitChildren(children) {
1004
- const left = [];
1005
- const right = [];
1006
- let inRight = false;
1007
- Children.forEach(children, (child) => {
1008
- if (isSlotRight(child)) {
1009
- inRight = true;
1010
- right.push(child.props.children);
1011
- return;
1012
- }
1013
- if (inRight) right.push(child);
1014
- else left.push(child);
1015
- });
1016
- return {
1017
- left,
1018
- right
1019
- };
1020
- }
1021
- function Slide({ children, layout = "default", image, className, style }) {
1022
- const cls = `reslide-slide reslide-layout-${layout}${className ? ` ${className}` : ""}`;
1023
- switch (layout) {
1024
- case "center": return /* @__PURE__ */ jsx("div", {
1025
- className: cls,
1026
- style: {
1027
- ...baseStyle,
1028
- flexDirection: "column",
1029
- justifyContent: "center",
1030
- alignItems: "center",
1031
- textAlign: "center",
1032
- padding: "3rem 4rem",
1033
- ...style
1034
- },
1035
- children
1036
- });
1037
- case "two-cols": {
1038
- const { left, right } = splitChildren(children);
1039
- return /* @__PURE__ */ jsxs("div", {
1040
- className: cls,
1041
- style: {
1042
- ...baseStyle,
1043
- flexDirection: "row",
1044
- gap: "2rem",
1045
- padding: "3rem 4rem",
1046
- ...style
1047
- },
1048
- children: [/* @__PURE__ */ jsx("div", {
1049
- style: {
1050
- flex: 1,
1051
- minWidth: 0
1052
- },
1053
- children: left
1054
- }), /* @__PURE__ */ jsx("div", {
1055
- style: {
1056
- flex: 1,
1057
- minWidth: 0
1058
- },
1059
- children: right
1060
- })]
1061
- });
1062
- }
1063
- case "image-right": return /* @__PURE__ */ jsxs("div", {
1064
- className: cls,
1065
- style: {
1066
- ...baseStyle,
1067
- flexDirection: "row",
1068
- ...style
1069
- },
1070
- children: [/* @__PURE__ */ jsx("div", {
1071
- style: {
1072
- flex: 1,
1073
- padding: "3rem 2rem 3rem 4rem",
1074
- overflow: "auto"
1075
- },
1076
- children
1077
- }), image && /* @__PURE__ */ jsx("div", { style: {
1078
- flex: 1,
1079
- backgroundImage: `url(${image})`,
1080
- backgroundSize: "cover",
1081
- backgroundPosition: "center"
1082
- } })]
1083
- });
1084
- case "image-left": return /* @__PURE__ */ jsxs("div", {
1085
- className: cls,
1086
- style: {
1087
- ...baseStyle,
1088
- flexDirection: "row",
1089
- ...style
1090
- },
1091
- children: [image && /* @__PURE__ */ jsx("div", { style: {
1092
- flex: 1,
1093
- backgroundImage: `url(${image})`,
1094
- backgroundSize: "cover",
1095
- backgroundPosition: "center"
1096
- } }), /* @__PURE__ */ jsx("div", {
1097
- style: {
1098
- flex: 1,
1099
- padding: "3rem 4rem 3rem 2rem",
1100
- overflow: "auto"
1101
- },
1102
- children
1103
- })]
1104
- });
1105
- case "section": return /* @__PURE__ */ jsx("div", {
1106
- className: cls,
1107
- style: {
1108
- ...baseStyle,
1109
- flexDirection: "column",
1110
- justifyContent: "center",
1111
- alignItems: "center",
1112
- textAlign: "center",
1113
- padding: "3rem 4rem",
1114
- backgroundColor: "var(--slide-accent, #3b82f6)",
1115
- color: "var(--slide-section-text, #fff)",
1116
- ...style
1117
- },
1118
- children
1119
- });
1120
- case "quote": return /* @__PURE__ */ jsx("div", {
1121
- className: cls,
1122
- style: {
1123
- ...baseStyle,
1124
- flexDirection: "column",
1125
- justifyContent: "center",
1126
- padding: "3rem 6rem",
1127
- ...style
1128
- },
1129
- children: /* @__PURE__ */ jsx("blockquote", {
1130
- style: {
1131
- fontSize: "1.5em",
1132
- fontStyle: "italic",
1133
- borderLeft: "4px solid var(--slide-accent, #3b82f6)",
1134
- paddingLeft: "1.5rem",
1135
- margin: 0
1136
- },
1137
- children
1138
- })
1139
- });
1140
- default: return /* @__PURE__ */ jsx("div", {
1141
- className: cls,
1142
- style: {
1143
- ...baseStyle,
1144
- flexDirection: "column",
1145
- padding: "3rem 4rem",
1146
- ...style
1147
- },
1148
- children
1149
- });
1150
- }
1151
- }
1152
- //#endregion
1153
- //#region src/Click.tsx
1154
- function Click({ children, at }) {
1155
- const { clickStep } = useDeck();
1156
- const visible = clickStep >= (at ?? 1);
1157
- return /* @__PURE__ */ jsx("div", {
1158
- className: "reslide-click",
1159
- style: {
1160
- opacity: visible ? 1 : 0,
1161
- visibility: visible ? "visible" : "hidden",
1162
- pointerEvents: visible ? "auto" : "none",
1163
- transition: "opacity 0.3s ease, visibility 0.3s ease"
1164
- },
1165
- children
1166
- });
1167
- }
1168
- /**
1169
- * Register click steps for the current slide.
1170
- * Automatically reads the slide index from SlideIndexContext.
1171
- * If slideIndex prop is provided, it takes precedence (for backwards compatibility).
1172
- */
1173
- function ClickSteps({ count, slideIndex }) {
1174
- const { registerClickSteps } = useDeck();
1175
- const contextIndex = useContext(SlideIndexContext);
1176
- const resolvedIndex = slideIndex ?? contextIndex;
1177
- useEffect(() => {
1178
- if (resolvedIndex != null) registerClickSteps(resolvedIndex, count);
1179
- }, [
1180
- resolvedIndex,
1181
- count,
1182
- registerClickSteps
1183
- ]);
1184
- return null;
1185
- }
1186
- //#endregion
1187
- //#region src/Mark.tsx
1188
- const markStyles = {
1189
- highlight: (colorName, resolvedColor) => ({
1190
- backgroundColor: `var(--mark-${colorName}, ${resolvedColor})`,
1191
- padding: "0.1em 0.2em",
1192
- borderRadius: "0.2em"
1193
- }),
1194
- underline: (colorName, resolvedColor) => ({
1195
- textDecoration: "underline",
1196
- textDecorationColor: `var(--mark-${colorName}, ${resolvedColor})`,
1197
- textDecorationThickness: "0.15em",
1198
- textUnderlineOffset: "0.15em"
1199
- }),
1200
- circle: (colorName, resolvedColor) => ({
1201
- border: `0.15em solid var(--mark-${colorName}, ${resolvedColor})`,
1202
- borderRadius: "50%",
1203
- padding: "0.1em 0.3em"
1204
- })
1205
- };
1206
- const defaultColors = {
1207
- orange: "#fb923c",
1208
- red: "#ef4444",
1209
- blue: "#3b82f6",
1210
- green: "#22c55e",
1211
- yellow: "#facc15",
1212
- purple: "#a855f7"
1213
- };
1214
- function Mark({ children, type = "highlight", color = "yellow" }) {
1215
- const resolvedColor = defaultColors[color] ?? color;
1216
- const styleFn = markStyles[type] ?? markStyles.highlight;
1217
- return /* @__PURE__ */ jsx("span", {
1218
- className: `reslide-mark reslide-mark-${type}`,
1219
- style: styleFn(color, resolvedColor),
1220
- children
1221
- });
1222
- }
1223
- //#endregion
1224
- //#region src/Notes.tsx
1225
- /**
1226
- * Speaker notes. Hidden during normal presentation,
1227
- * visible in overview mode.
1228
- */
1229
- function Notes({ children }) {
1230
- const { isOverview } = useDeck();
1231
- if (!isOverview) return null;
1232
- return /* @__PURE__ */ jsx("div", {
1233
- className: "reslide-notes",
1234
- style: {
1235
- marginTop: "auto",
1236
- padding: "0.75rem",
1237
- fontSize: "0.75rem",
1238
- color: "var(--slide-text, #1a1a1a)",
1239
- opacity: .7,
1240
- borderTop: "1px solid currentColor"
1241
- },
1242
- children
1243
- });
1244
- }
1245
- //#endregion
1246
- //#region src/Slot.tsx
1247
- /**
1248
- * Marks content as belonging to the right column in a two-cols layout.
1249
- * Used by remarkSlides to separate `::right` content.
1250
- */
1251
- function SlotRight({ children }) {
1252
- return /* @__PURE__ */ jsx(Fragment$1, { children });
1253
- }
1254
- SlotRight.displayName = "SlotRight";
1255
- SlotRight.__reslideSlot = "right";
1256
- //#endregion
1257
- //#region src/PresenterView.tsx
1258
- /**
1259
- * Presenter view that syncs with the main presentation window.
1260
- * Shows: current slide, next slide preview, notes, and timer.
1261
- * Supports bidirectional control — navigate from this window to
1262
- * drive the main presentation.
1263
- */
1264
- function PresenterView({ children, notes }) {
1265
- const [currentSlide, setCurrentSlide] = useState(0);
1266
- const [clickStep, setClickStep] = useState(0);
1267
- const [elapsed, setElapsed] = useState(0);
1268
- const slides = Children.toArray(children);
1269
- const totalSlides = slides.length;
1270
- const { next, prev, goTo } = usePresenterChannel((msg) => {
1271
- setCurrentSlide(msg.currentSlide);
1272
- setClickStep(msg.clickStep);
1273
- });
1274
- useEffect(() => {
1275
- function handleKeyDown(e) {
1276
- if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
1277
- switch (e.key) {
1278
- case "ArrowRight":
1279
- case " ":
1280
- e.preventDefault();
1281
- next();
1282
- break;
1283
- case "ArrowLeft":
1284
- e.preventDefault();
1285
- prev();
1286
- break;
1287
- }
1288
- }
1289
- window.addEventListener("keydown", handleKeyDown);
1290
- return () => window.removeEventListener("keydown", handleKeyDown);
1291
- }, [next, prev]);
1292
- useEffect(() => {
1293
- const start = Date.now();
1294
- const interval = setInterval(() => {
1295
- setElapsed(Math.floor((Date.now() - start) / 1e3));
1296
- }, 1e3);
1297
- return () => clearInterval(interval);
1298
- }, []);
1299
- const formatTime = (seconds) => {
1300
- const m = Math.floor(seconds / 60);
1301
- const s = seconds % 60;
1302
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
1303
- };
1304
- const noopReg = useCallback((_i, _c) => {}, []);
1305
- const noop = useCallback(() => {}, []);
1306
- const contextValue = useMemo(() => ({
1307
- currentSlide,
1308
- totalSlides,
1309
- clickStep,
1310
- totalClickSteps: 0,
1311
- isOverview: false,
1312
- isFullscreen: false,
1313
- next,
1314
- prev,
1315
- goTo,
1316
- toggleOverview: noop,
1317
- toggleFullscreen: noop,
1318
- registerClickSteps: noopReg
1319
- }), [
1320
- currentSlide,
1321
- totalSlides,
1322
- clickStep,
1323
- next,
1324
- prev,
1325
- goTo,
1326
- noop,
1327
- noopReg
1328
- ]);
1329
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
1330
- value: contextValue,
1331
- children: /* @__PURE__ */ jsxs("div", {
1332
- style: {
1333
- display: "grid",
1334
- gridTemplateColumns: "2fr 1fr",
1335
- gridTemplateRows: "1fr auto",
1336
- width: "100%",
1337
- height: "100%",
1338
- gap: "0.75rem",
1339
- padding: "0.75rem",
1340
- backgroundColor: "#1a1a2e",
1341
- color: "#e2e8f0",
1342
- fontFamily: "system-ui, sans-serif",
1343
- boxSizing: "border-box"
1344
- },
1345
- children: [
1346
- /* @__PURE__ */ jsx("div", {
1347
- style: {
1348
- border: "2px solid #3b82f6",
1349
- borderRadius: "0.5rem",
1350
- overflow: "hidden",
1351
- position: "relative",
1352
- backgroundColor: "var(--slide-bg, #fff)",
1353
- color: "var(--slide-text, #1a1a1a)"
1354
- },
1355
- children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1356
- value: currentSlide,
1357
- children: slides[currentSlide]
1358
- })
1359
- }),
1360
- /* @__PURE__ */ jsxs("div", {
1361
- style: {
1362
- display: "flex",
1363
- flexDirection: "column",
1364
- gap: "0.75rem",
1365
- minHeight: 0
1366
- },
1367
- children: [/* @__PURE__ */ jsx("div", {
1368
- style: {
1369
- flex: "0 0 40%",
1370
- border: "1px solid #334155",
1371
- borderRadius: "0.5rem",
1372
- overflow: "hidden",
1373
- opacity: .8,
1374
- backgroundColor: "var(--slide-bg, #fff)",
1375
- color: "var(--slide-text, #1a1a1a)"
1376
- },
1377
- children: currentSlide < totalSlides - 1 && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1378
- value: currentSlide + 1,
1379
- children: /* @__PURE__ */ jsx("div", {
1380
- style: {
1381
- transform: "scale(0.5)",
1382
- transformOrigin: "top left",
1383
- width: "200%",
1384
- height: "200%"
1385
- },
1386
- children: slides[currentSlide + 1]
1387
- })
1388
- })
1389
- }), /* @__PURE__ */ jsxs("div", {
1390
- style: {
1391
- flex: 1,
1392
- overflow: "auto",
1393
- padding: "1rem",
1394
- backgroundColor: "#0f172a",
1395
- borderRadius: "0.5rem",
1396
- fontSize: "0.875rem",
1397
- lineHeight: 1.6
1398
- },
1399
- children: [/* @__PURE__ */ jsx("div", {
1400
- style: {
1401
- fontWeight: 600,
1402
- marginBottom: "0.5rem",
1403
- color: "#94a3b8"
1404
- },
1405
- children: "Notes"
1406
- }), notes?.[currentSlide] ?? /* @__PURE__ */ jsx("span", {
1407
- style: { color: "#64748b" },
1408
- children: "No notes for this slide"
1409
- })]
1410
- })]
1411
- }),
1412
- /* @__PURE__ */ jsxs("div", {
1413
- style: {
1414
- gridColumn: "1 / -1",
1415
- display: "flex",
1416
- justifyContent: "space-between",
1417
- alignItems: "center",
1418
- padding: "0.5rem 1rem",
1419
- backgroundColor: "#0f172a",
1420
- borderRadius: "0.5rem"
1421
- },
1422
- children: [
1423
- /* @__PURE__ */ jsx("div", {
1424
- style: {
1425
- fontSize: "1.5rem",
1426
- fontVariantNumeric: "tabular-nums",
1427
- fontWeight: 700
1428
- },
1429
- children: formatTime(elapsed)
1430
- }),
1431
- /* @__PURE__ */ jsxs("div", {
1432
- style: {
1433
- display: "flex",
1434
- alignItems: "center",
1435
- gap: "0.5rem"
1436
- },
1437
- children: [
1438
- /* @__PURE__ */ jsx(PresenterNavButton, {
1439
- onClick: prev,
1440
- title: "Previous (←)",
1441
- children: "◀"
1442
- }),
1443
- /* @__PURE__ */ jsxs("span", {
1444
- style: {
1445
- fontSize: "1.125rem",
1446
- fontVariantNumeric: "tabular-nums"
1447
- },
1448
- children: [
1449
- currentSlide + 1,
1450
- " / ",
1451
- totalSlides,
1452
- clickStep > 0 && /* @__PURE__ */ jsxs("span", {
1453
- style: { color: "#94a3b8" },
1454
- children: [
1455
- " (click ",
1456
- clickStep,
1457
- ")"
1458
- ]
1459
- })
1460
- ]
1461
- }),
1462
- /* @__PURE__ */ jsx(PresenterNavButton, {
1463
- onClick: next,
1464
- title: "Next (→ / Space)",
1465
- children: "▶"
1466
- })
1467
- ]
1468
- }),
1469
- /* @__PURE__ */ jsx("div", { style: { width: "5rem" } })
1470
- ]
1471
- })
1472
- ]
1473
- })
1474
- });
1475
- }
1476
- function PresenterNavButton({ children, onClick, title }) {
1477
- return /* @__PURE__ */ jsx("button", {
1478
- type: "button",
1479
- onClick,
1480
- title,
1481
- style: {
1482
- display: "flex",
1483
- alignItems: "center",
1484
- justifyContent: "center",
1485
- width: "2.25rem",
1486
- height: "2.25rem",
1487
- background: "rgba(255,255,255,0.1)",
1488
- border: "1px solid rgba(255,255,255,0.15)",
1489
- borderRadius: "0.375rem",
1490
- cursor: "pointer",
1491
- color: "#e2e8f0",
1492
- fontSize: "0.875rem",
1493
- transition: "background 0.15s"
1494
- },
1495
- onMouseEnter: (e) => {
1496
- e.currentTarget.style.background = "rgba(255,255,255,0.2)";
1497
- },
1498
- onMouseLeave: (e) => {
1499
- e.currentTarget.style.background = "rgba(255,255,255,0.1)";
1500
- },
1501
- children
1502
- });
1503
- }
1504
- //#endregion
4
+ import { jsx } from "react/jsx-runtime";
1505
5
  //#region src/CodeEditor.tsx
1506
6
  /**
1507
7
  * Live code editor component for presentations.
@@ -1606,251 +106,6 @@ function CodeEditor({ value, language = "typescript", readOnly = false, height =
1606
106
  });
1607
107
  }
1608
108
  //#endregion
1609
- //#region src/GlobalLayer.tsx
1610
- /**
1611
- * Global overlay layer that persists across all slides.
1612
- * Use for headers, footers, logos, watermarks, or progress bars.
1613
- *
1614
- * Place inside <Deck> to render on every slide.
1615
- *
1616
- * @example
1617
- * ```tsx
1618
- * <Deck>
1619
- * <GlobalLayer position="above" style={{ bottom: 0 }}>
1620
- * <footer>My Company</footer>
1621
- * </GlobalLayer>
1622
- * <Slide>...</Slide>
1623
- * </Deck>
1624
- * ```
1625
- */
1626
- function GlobalLayer({ children, position = "above", style }) {
1627
- return /* @__PURE__ */ jsx("div", {
1628
- className: `reslide-global-layer reslide-global-layer-${position}`,
1629
- style: {
1630
- position: "absolute",
1631
- inset: 0,
1632
- pointerEvents: "none",
1633
- zIndex: position === "above" ? 40 : 0,
1634
- ...style
1635
- },
1636
- children: /* @__PURE__ */ jsx("div", {
1637
- style: { pointerEvents: "auto" },
1638
- children
1639
- })
1640
- });
1641
- }
1642
- GlobalLayer.displayName = "GlobalLayer";
1643
- GlobalLayer.__reslideGlobalLayer = true;
1644
- //#endregion
1645
- //#region src/Draggable.tsx
1646
- /**
1647
- * A draggable element within a slide.
1648
- * Click and drag to reposition during presentation.
1649
- */
1650
- function Draggable({ children, x = 0, y = 0, style }) {
1651
- const [pos, setPos] = useState({
1652
- x: typeof x === "number" ? x : 0,
1653
- y: typeof y === "number" ? y : 0
1654
- });
1655
- const dragRef = useRef(null);
1656
- return /* @__PURE__ */ jsx("div", {
1657
- className: "reslide-draggable",
1658
- onMouseDown: useCallback((e) => {
1659
- e.preventDefault();
1660
- dragRef.current = {
1661
- startX: e.clientX,
1662
- startY: e.clientY,
1663
- origX: pos.x,
1664
- origY: pos.y
1665
- };
1666
- function onMouseMove(me) {
1667
- if (!dragRef.current) return;
1668
- setPos({
1669
- x: dragRef.current.origX + (me.clientX - dragRef.current.startX),
1670
- y: dragRef.current.origY + (me.clientY - dragRef.current.startY)
1671
- });
1672
- }
1673
- function onMouseUp() {
1674
- dragRef.current = null;
1675
- window.removeEventListener("mousemove", onMouseMove);
1676
- window.removeEventListener("mouseup", onMouseUp);
1677
- }
1678
- window.addEventListener("mousemove", onMouseMove);
1679
- window.addEventListener("mouseup", onMouseUp);
1680
- }, [pos.x, pos.y]),
1681
- style: {
1682
- position: "absolute",
1683
- left: typeof x === "string" ? x : void 0,
1684
- top: typeof y === "string" ? y : void 0,
1685
- transform: `translate(${pos.x}px, ${pos.y}px)`,
1686
- cursor: "grab",
1687
- userSelect: "none",
1688
- zIndex: 30,
1689
- ...style
1690
- },
1691
- children
1692
- });
1693
- }
1694
- //#endregion
1695
- //#region src/Toc.tsx
1696
- /**
1697
- * Extract heading text (h1/h2) from a React element tree.
1698
- * Traverses children recursively to find the first h1 or h2.
1699
- */
1700
- function extractHeading(node) {
1701
- if (!isValidElement(node)) return null;
1702
- const el = node;
1703
- const type = el.type;
1704
- if (type === "h1" || type === "h2") return extractText(el.props.children);
1705
- const children = el.props.children;
1706
- if (children == null) return null;
1707
- let result = null;
1708
- Children.forEach(children, (child) => {
1709
- if (result) return;
1710
- const found = extractHeading(child);
1711
- if (found) result = found;
1712
- });
1713
- return result;
1714
- }
1715
- /** Extract plain text from a React node tree */
1716
- function extractText(node) {
1717
- if (node == null) return "";
1718
- if (typeof node === "string") return node;
1719
- if (typeof node === "number") return String(node);
1720
- if (Array.isArray(node)) return node.map(extractText).join("");
1721
- if (isValidElement(node)) return extractText(node.props.children);
1722
- return "";
1723
- }
1724
- /**
1725
- * Table of Contents component that renders a clickable list of slides
1726
- * with their heading text extracted from h1/h2 elements.
1727
- *
1728
- * Must be rendered inside a `<Deck>` component.
1729
- *
1730
- * ```tsx
1731
- * <Toc>
1732
- * <Slide><h1>Introduction</h1></Slide>
1733
- * <Slide><h2>Agenda</h2></Slide>
1734
- * </Toc>
1735
- * ```
1736
- */
1737
- function Toc({ children, className, style }) {
1738
- const { currentSlide, goTo } = useDeck();
1739
- const items = Children.toArray(children).map((slide, index) => {
1740
- return {
1741
- index,
1742
- title: extractHeading(slide) || `Slide ${index + 1}`
1743
- };
1744
- });
1745
- return /* @__PURE__ */ jsx("nav", {
1746
- className: `reslide-toc${className ? ` ${className}` : ""}`,
1747
- style: {
1748
- display: "flex",
1749
- flexDirection: "column",
1750
- gap: "0.25rem",
1751
- padding: "0.5rem 0",
1752
- ...style
1753
- },
1754
- children: items.map((item) => {
1755
- const isActive = item.index === currentSlide;
1756
- return /* @__PURE__ */ jsxs("button", {
1757
- type: "button",
1758
- onClick: () => goTo(item.index),
1759
- style: {
1760
- display: "flex",
1761
- alignItems: "center",
1762
- gap: "0.5rem",
1763
- padding: "0.5rem 1rem",
1764
- border: "none",
1765
- borderRadius: "0.375rem",
1766
- cursor: "pointer",
1767
- textAlign: "left",
1768
- fontSize: "0.875rem",
1769
- lineHeight: 1.4,
1770
- fontFamily: "inherit",
1771
- color: isActive ? "var(--toc-active-text, var(--slide-accent, #3b82f6))" : "var(--toc-text, var(--slide-text, #1a1a1a))",
1772
- backgroundColor: isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent",
1773
- fontWeight: isActive ? 600 : 400,
1774
- transition: "background-color 0.15s, color 0.15s"
1775
- },
1776
- onMouseEnter: (e) => {
1777
- if (!isActive) e.currentTarget.style.backgroundColor = "var(--toc-hover-bg, rgba(0, 0, 0, 0.05))";
1778
- },
1779
- onMouseLeave: (e) => {
1780
- e.currentTarget.style.backgroundColor = isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent";
1781
- },
1782
- children: [/* @__PURE__ */ jsx("span", {
1783
- style: {
1784
- minWidth: "1.5rem",
1785
- textAlign: "right",
1786
- opacity: .5,
1787
- fontSize: "0.75rem",
1788
- fontVariantNumeric: "tabular-nums"
1789
- },
1790
- children: item.index + 1
1791
- }), /* @__PURE__ */ jsx("span", { children: item.title })]
1792
- }, item.index);
1793
- })
1794
- });
1795
- }
1796
- //#endregion
1797
- //#region src/Mermaid.tsx
1798
- /**
1799
- * Renders a Mermaid diagram.
1800
- *
1801
- * Usage in MDX:
1802
- * ```mdx
1803
- * <Mermaid>
1804
- * graph TD
1805
- * A --> B
1806
- * </Mermaid>
1807
- * ```
1808
- *
1809
- * The mermaid library is dynamically imported on the client side,
1810
- * so it does not need to be installed as a project dependency.
1811
- */
1812
- function Mermaid({ children }) {
1813
- const containerRef = useRef(null);
1814
- const [svg, setSvg] = useState("");
1815
- const [error, setError] = useState("");
1816
- const id = useId().replace(/:/g, "_");
1817
- useEffect(() => {
1818
- let cancelled = false;
1819
- async function render() {
1820
- try {
1821
- const mermaid = await import("mermaid");
1822
- mermaid.default.initialize({
1823
- startOnLoad: false,
1824
- theme: "default",
1825
- securityLevel: "loose"
1826
- });
1827
- const code = typeof children === "string" ? children.trim() : "";
1828
- if (!code) return;
1829
- const { svg: rendered } = await mermaid.default.render(`mermaid-${id}`, code);
1830
- if (!cancelled) {
1831
- setSvg(rendered);
1832
- setError("");
1833
- }
1834
- } catch (err) {
1835
- if (!cancelled) setError(err instanceof Error ? err.message : "Failed to render diagram");
1836
- }
1837
- }
1838
- render();
1839
- return () => {
1840
- cancelled = true;
1841
- };
1842
- }, [children, id]);
1843
- if (error) return /* @__PURE__ */ jsx("div", {
1844
- className: "reslide-mermaid reslide-mermaid--error",
1845
- children: /* @__PURE__ */ jsx("pre", { children: error })
1846
- });
1847
- return /* @__PURE__ */ jsx("div", {
1848
- ref: containerRef,
1849
- className: "reslide-mermaid",
1850
- dangerouslySetInnerHTML: { __html: svg }
1851
- });
1852
- }
1853
- //#endregion
1854
109
  //#region src/ReslideEmbed.tsx
1855
110
  /** Built-in reslide components available in MDX */
1856
111
  const builtinComponents = {