@nationaldesignstudio/react 0.6.0 → 0.7.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 (106) hide show
  1. package/dist/accordion/index.d.ts +95 -0
  2. package/dist/accordion/index.js +143 -0
  3. package/dist/accordion/index.js.map +1 -0
  4. package/dist/background/index.d.ts +149 -0
  5. package/dist/background/index.js +200 -0
  6. package/dist/background/index.js.map +1 -0
  7. package/dist/banner/index.d.ts +101 -0
  8. package/dist/banner/index.js +81 -0
  9. package/dist/banner/index.js.map +1 -0
  10. package/dist/blurred-video-backdrop/index.d.ts +233 -0
  11. package/dist/blurred-video-backdrop/index.js +266 -0
  12. package/dist/blurred-video-backdrop/index.js.map +1 -0
  13. package/dist/button/index.d.ts +180 -0
  14. package/dist/button/index.js +169 -0
  15. package/dist/button/index.js.map +1 -0
  16. package/dist/button-B2g5fH9b.d.ts +152 -0
  17. package/dist/card/index.d.ts +406 -0
  18. package/dist/card/index.js +219 -0
  19. package/dist/card/index.js.map +1 -0
  20. package/dist/card-grid/index.d.ts +90 -0
  21. package/dist/card-grid/index.js +74 -0
  22. package/dist/card-grid/index.js.map +1 -0
  23. package/dist/component-registry.md +136 -2
  24. package/dist/dev-toolbar/index.d.ts +8 -0
  25. package/dist/dev-toolbar/index.js +206 -0
  26. package/dist/dev-toolbar/index.js.map +1 -0
  27. package/dist/dialog/index.d.ts +268 -0
  28. package/dist/dialog/index.js +288 -0
  29. package/dist/dialog/index.js.map +1 -0
  30. package/dist/faq-section/index.d.ts +47 -0
  31. package/dist/faq-section/index.js +152 -0
  32. package/dist/faq-section/index.js.map +1 -0
  33. package/dist/grid-overlay/index.d.ts +10 -0
  34. package/dist/grid-overlay/index.js +38 -0
  35. package/dist/grid-overlay/index.js.map +1 -0
  36. package/dist/hero/index.d.ts +462 -0
  37. package/dist/hero/index.js +494 -0
  38. package/dist/hero/index.js.map +1 -0
  39. package/dist/hooks/index.d.ts +150 -0
  40. package/dist/hooks/index.js +339 -0
  41. package/dist/hooks/index.js.map +1 -0
  42. package/dist/index.d.ts +46 -5339
  43. package/dist/index.js +157 -4080
  44. package/dist/index.js.map +1 -1
  45. package/dist/input/index.d.ts +404 -0
  46. package/dist/input/index.js +393 -0
  47. package/dist/input/index.js.map +1 -0
  48. package/dist/navbar/index.d.ts +68 -0
  49. package/dist/navbar/index.js +227 -0
  50. package/dist/navbar/index.js.map +1 -0
  51. package/dist/ndstudio-footer/index.d.ts +32 -0
  52. package/dist/ndstudio-footer/index.js +35 -0
  53. package/dist/ndstudio-footer/index.js.map +1 -0
  54. package/dist/pager-control/index.d.ts +173 -0
  55. package/dist/pager-control/index.js +267 -0
  56. package/dist/pager-control/index.js.map +1 -0
  57. package/dist/popover/index.d.ts +200 -0
  58. package/dist/popover/index.js +290 -0
  59. package/dist/popover/index.js.map +1 -0
  60. package/dist/prose/index.d.ts +39 -0
  61. package/dist/prose/index.js +56 -0
  62. package/dist/prose/index.js.map +1 -0
  63. package/dist/quote-block/index.d.ts +156 -0
  64. package/dist/quote-block/index.js +321 -0
  65. package/dist/quote-block/index.js.map +1 -0
  66. package/dist/river/index.d.ts +100 -0
  67. package/dist/river/index.js +107 -0
  68. package/dist/river/index.js.map +1 -0
  69. package/dist/select/index.d.ts +188 -0
  70. package/dist/select/index.js +295 -0
  71. package/dist/select/index.js.map +1 -0
  72. package/dist/theme/index.d.ts +149 -0
  73. package/dist/theme/index.js +211 -0
  74. package/dist/theme/index.js.map +1 -0
  75. package/dist/theme-CzBPUlh_.d.ts +332 -0
  76. package/dist/tooltip/index.d.ts +166 -0
  77. package/dist/tooltip/index.js +200 -0
  78. package/dist/tooltip/index.js.map +1 -0
  79. package/dist/tout/index.d.ts +157 -0
  80. package/dist/tout/index.js +315 -0
  81. package/dist/tout/index.js.map +1 -0
  82. package/dist/two-column-section/index.d.ts +122 -0
  83. package/dist/two-column-section/index.js +121 -0
  84. package/dist/two-column-section/index.js.map +1 -0
  85. package/dist/us-gov-banner/index.d.ts +141 -0
  86. package/dist/us-gov-banner/index.js +74 -0
  87. package/dist/us-gov-banner/index.js.map +1 -0
  88. package/dist/use-captions-AkKlJhov.d.ts +71 -0
  89. package/dist/utils/index.d.ts +7 -0
  90. package/dist/utils/index.js +12 -0
  91. package/dist/utils/index.js.map +1 -0
  92. package/dist/video-dialog/index.d.ts +106 -0
  93. package/dist/video-dialog/index.js +1305 -0
  94. package/dist/video-dialog/index.js.map +1 -0
  95. package/dist/video-player/index.d.ts +115 -0
  96. package/dist/video-player/index.js +879 -0
  97. package/dist/video-player/index.js.map +1 -0
  98. package/dist/video-player-qxf-BURH.d.ts +236 -0
  99. package/dist/video-with-backdrop/index.d.ts +267 -0
  100. package/dist/video-with-backdrop/index.js +1284 -0
  101. package/dist/video-with-backdrop/index.js.map +1 -0
  102. package/package.json +13 -2
  103. package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +5 -27
  104. package/src/theme/hooks.ts +2 -0
  105. package/src/theme/index.ts +2 -0
  106. package/src/theme/theme-provider.tsx +2 -0
@@ -0,0 +1,1305 @@
1
+ "use client";
2
+ import { Dialog } from '@base-ui-components/react/dialog';
3
+ import * as React5 from 'react';
4
+ import { tv, cnBase } from 'tailwind-variants';
5
+ import { clsx } from 'clsx';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import { MediaController, MediaLoadingIndicator, MediaControlBar, MediaPlayButton, MediaMuteButton, MediaVolumeRange, MediaTimeDisplay, MediaTimeRange } from 'media-chrome/react';
8
+
9
+ // src/components/molecules/video-dialog/video-dialog.tsx
10
+ function cn(...inputs) {
11
+ return cnBase(clsx(inputs));
12
+ }
13
+ var blurredVideoBackdropVariants = tv({
14
+ base: [
15
+ "absolute",
16
+ "pointer-events-none",
17
+ "select-none",
18
+ "will-change-contents",
19
+ "transform-gpu"
20
+ ],
21
+ variants: {
22
+ /**
23
+ * Blur intensity level.
24
+ * Higher values provide more diffused backgrounds.
25
+ */
26
+ blur: {
27
+ low: "",
28
+ medium: "",
29
+ high: "",
30
+ extreme: ""
31
+ },
32
+ /**
33
+ * Gradient overlay for visual depth.
34
+ */
35
+ overlay: {
36
+ none: "",
37
+ vignette: "",
38
+ "top-bottom": ""
39
+ }
40
+ },
41
+ defaultVariants: {
42
+ blur: "high",
43
+ overlay: "none"
44
+ }
45
+ });
46
+ var canvasVariants = tv({
47
+ base: ["w-full", "h-full", "object-cover"]
48
+ });
49
+ var gradientOverlayVariants = tv({
50
+ base: ["absolute", "inset-0", "pointer-events-none"]
51
+ });
52
+ var OVERLAY_GRADIENTS = {
53
+ vignette: "radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.4) 100%)",
54
+ "top-bottom": "linear-gradient(180deg, rgba(0, 0, 0, 0.4) 0%, transparent 30%, transparent 70%, rgba(0, 0, 0, 0.4) 100%)"
55
+ };
56
+ var BLUR_AMOUNTS = {
57
+ low: 40,
58
+ medium: 80,
59
+ high: 100,
60
+ extreme: 120
61
+ };
62
+ function useCanvasBlur({
63
+ videoRef,
64
+ blurAmount,
65
+ enabled = true,
66
+ targetFps = 30,
67
+ scale = 0.5
68
+ }) {
69
+ const canvasRef = React5.useRef(null);
70
+ const ctxRef = React5.useRef(null);
71
+ const [isRendering, setIsRendering] = React5.useState(false);
72
+ const [metrics, setMetrics] = React5.useState({ fps: 0, frameTime: 0 });
73
+ const [videoReady, setVideoReady] = React5.useState(false);
74
+ const lastFrameTimeRef = React5.useRef(0);
75
+ const frameCountRef = React5.useRef(0);
76
+ const fpsIntervalRef = React5.useRef(0);
77
+ const frameInterval = 1e3 / targetFps;
78
+ React5.useEffect(() => {
79
+ if (!enabled) return;
80
+ const checkRef = () => {
81
+ if (videoRef.current && canvasRef.current) {
82
+ setVideoReady(true);
83
+ }
84
+ };
85
+ checkRef();
86
+ const frameId = requestAnimationFrame(checkRef);
87
+ const timeoutId = setTimeout(checkRef, 100);
88
+ const timeoutId2 = setTimeout(checkRef, 500);
89
+ return () => {
90
+ cancelAnimationFrame(frameId);
91
+ clearTimeout(timeoutId);
92
+ clearTimeout(timeoutId2);
93
+ };
94
+ }, [enabled, videoRef]);
95
+ React5.useEffect(() => {
96
+ if (!enabled || !videoReady) return;
97
+ const video = videoRef.current;
98
+ const canvas = canvasRef.current;
99
+ if (!video || !canvas) return;
100
+ const ctx = canvas.getContext("2d", {
101
+ alpha: false,
102
+ desynchronized: true
103
+ // Reduces latency
104
+ });
105
+ if (!ctx) return;
106
+ ctxRef.current = ctx;
107
+ let animationFrameId;
108
+ let isActive = true;
109
+ const updateCanvasSize = () => {
110
+ if (video.videoWidth && video.videoHeight) {
111
+ canvas.width = Math.floor(video.videoWidth * scale);
112
+ canvas.height = Math.floor(video.videoHeight * scale);
113
+ }
114
+ };
115
+ const render = (timestamp) => {
116
+ if (!isActive || !video || !ctx) return;
117
+ const elapsed = timestamp - lastFrameTimeRef.current;
118
+ if (elapsed >= frameInterval) {
119
+ const frameStart = performance.now();
120
+ if (canvas.width === 0 || canvas.height === 0) {
121
+ updateCanvasSize();
122
+ }
123
+ if (video.readyState >= 2 && !video.paused) {
124
+ ctx.filter = `blur(${blurAmount * scale}px)`;
125
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
126
+ setIsRendering(true);
127
+ }
128
+ const frameTime = performance.now() - frameStart;
129
+ frameCountRef.current++;
130
+ if (timestamp - fpsIntervalRef.current >= 1e3) {
131
+ setMetrics({
132
+ fps: frameCountRef.current,
133
+ frameTime: Math.round(frameTime * 100) / 100
134
+ });
135
+ frameCountRef.current = 0;
136
+ fpsIntervalRef.current = timestamp;
137
+ }
138
+ lastFrameTimeRef.current = timestamp - elapsed % frameInterval;
139
+ }
140
+ animationFrameId = requestAnimationFrame(render);
141
+ };
142
+ const handleLoadedMetadata = () => {
143
+ updateCanvasSize();
144
+ };
145
+ const handlePlay = () => {
146
+ if (isActive) {
147
+ animationFrameId = requestAnimationFrame(render);
148
+ }
149
+ };
150
+ const handlePause = () => {
151
+ setIsRendering(false);
152
+ };
153
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
154
+ video.addEventListener("play", handlePlay);
155
+ video.addEventListener("pause", handlePause);
156
+ if (video.readyState >= 1) {
157
+ updateCanvasSize();
158
+ }
159
+ if (!video.paused) {
160
+ animationFrameId = requestAnimationFrame(render);
161
+ }
162
+ return () => {
163
+ isActive = false;
164
+ cancelAnimationFrame(animationFrameId);
165
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
166
+ video.removeEventListener("play", handlePlay);
167
+ video.removeEventListener("pause", handlePause);
168
+ setIsRendering(false);
169
+ };
170
+ }, [enabled, videoReady, videoRef, blurAmount, frameInterval, scale]);
171
+ return {
172
+ canvasRef,
173
+ isRendering,
174
+ metrics
175
+ };
176
+ }
177
+ var BlurredVideoBackdrop = React5.forwardRef(
178
+ ({
179
+ className,
180
+ videoRef,
181
+ blur = "high",
182
+ overlay = "none",
183
+ opacity = 0.6,
184
+ extension = 120,
185
+ targetFps = 30,
186
+ scale = 0.5,
187
+ showMetrics = false,
188
+ style,
189
+ ...props
190
+ }, ref) => {
191
+ const blurAmount = BLUR_AMOUNTS[blur ?? "high"];
192
+ const { canvasRef, isRendering, metrics } = useCanvasBlur({
193
+ videoRef,
194
+ blurAmount,
195
+ enabled: true,
196
+ targetFps,
197
+ scale
198
+ });
199
+ return /* @__PURE__ */ jsxs(
200
+ "div",
201
+ {
202
+ ref,
203
+ className: cn(
204
+ blurredVideoBackdropVariants({ blur, overlay }),
205
+ className
206
+ ),
207
+ style: {
208
+ inset: `-${extension}px`,
209
+ opacity,
210
+ contain: "layout style paint",
211
+ ...style
212
+ },
213
+ "data-blur": blur ?? "high",
214
+ "data-overlay": overlay ?? "none",
215
+ "data-rendering": isRendering,
216
+ "aria-hidden": "true",
217
+ ...props,
218
+ children: [
219
+ /* @__PURE__ */ jsx(
220
+ "canvas",
221
+ {
222
+ ref: canvasRef,
223
+ className: cn(
224
+ canvasVariants(),
225
+ // Scale up the low-res canvas to fill container
226
+ "scale-[2]",
227
+ // Inverse of 0.5 scale factor
228
+ "origin-center"
229
+ ),
230
+ style: {
231
+ // Additional CSS blur for smoother appearance
232
+ filter: `blur(${blurAmount * 0.3}px)`
233
+ }
234
+ }
235
+ ),
236
+ overlay && overlay !== "none" && /* @__PURE__ */ jsx(
237
+ "div",
238
+ {
239
+ className: gradientOverlayVariants(),
240
+ style: { background: OVERLAY_GRADIENTS[overlay] }
241
+ }
242
+ ),
243
+ showMetrics && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-8 left-8 z-10 bg-black/80 text-white typography-caption px-8 py-4 rounded-4 font-mono", children: [
244
+ /* @__PURE__ */ jsxs("div", { children: [
245
+ "FPS: ",
246
+ metrics.fps
247
+ ] }),
248
+ /* @__PURE__ */ jsxs("div", { children: [
249
+ "Frame: ",
250
+ metrics.frameTime,
251
+ "ms"
252
+ ] }),
253
+ /* @__PURE__ */ jsxs("div", { children: [
254
+ "Scale: ",
255
+ scale,
256
+ "x"
257
+ ] })
258
+ ] })
259
+ ]
260
+ }
261
+ );
262
+ }
263
+ );
264
+ BlurredVideoBackdrop.displayName = "BlurredVideoBackdrop";
265
+ var captionOverlayVariants = tv({
266
+ base: [
267
+ // Positioning - absolute at bottom of video container
268
+ "pointer-events-none",
269
+ "absolute right-0 left-0",
270
+ "z-10",
271
+ "flex justify-center",
272
+ "px-4"
273
+ ],
274
+ variants: {
275
+ position: {
276
+ bottom: "bottom-64",
277
+ "bottom-sm": "bottom-24"
278
+ }
279
+ },
280
+ defaultVariants: {
281
+ position: "bottom-sm"
282
+ }
283
+ });
284
+ var captionTextVariants = tv({
285
+ base: [
286
+ "flex items-center justify-center",
287
+ "w-fit max-w-[80%]",
288
+ "gap-10",
289
+ "px-12 py-6",
290
+ "text-center",
291
+ "font-normal leading-[1.4]",
292
+ "rounded-6",
293
+ "bg-video-player-caption-bg text-video-player-caption-text"
294
+ ],
295
+ variants: {
296
+ size: {
297
+ sm: "text-14",
298
+ md: "[font-size:clamp(16px,2vw,24px)]",
299
+ lg: "text-24"
300
+ }
301
+ },
302
+ defaultVariants: {
303
+ size: "md"
304
+ }
305
+ });
306
+ var CaptionOverlay = React5.forwardRef(
307
+ ({ className, cue, text, position, size, ...props }, ref) => {
308
+ const displayText = cue?.text ?? text;
309
+ if (!displayText) {
310
+ return null;
311
+ }
312
+ return /* @__PURE__ */ jsx(
313
+ "output",
314
+ {
315
+ ref,
316
+ className: cn(captionOverlayVariants({ position }), className),
317
+ "aria-live": "polite",
318
+ "aria-label": "Video caption",
319
+ ...props,
320
+ children: /* @__PURE__ */ jsx("span", { className: captionTextVariants({ size }), children: displayText })
321
+ }
322
+ );
323
+ }
324
+ );
325
+ CaptionOverlay.displayName = "CaptionOverlay";
326
+ function parseVttTimestamp(timestamp) {
327
+ const parts = timestamp.trim().split(":");
328
+ let hours = 0;
329
+ let minutes = 0;
330
+ let seconds = 0;
331
+ if (parts.length === 3) {
332
+ hours = Number.parseInt(parts[0], 10);
333
+ minutes = Number.parseInt(parts[1], 10);
334
+ seconds = Number.parseFloat(parts[2]);
335
+ } else if (parts.length === 2) {
336
+ minutes = Number.parseInt(parts[0], 10);
337
+ seconds = Number.parseFloat(parts[1]);
338
+ }
339
+ return hours * 3600 + minutes * 60 + seconds;
340
+ }
341
+ function parseVtt(vttContent) {
342
+ const cues = [];
343
+ const lines = vttContent.trim().split("\n");
344
+ let i = 0;
345
+ while (i < lines.length && !lines[i].includes("-->")) {
346
+ i++;
347
+ }
348
+ while (i < lines.length) {
349
+ const line = lines[i].trim();
350
+ if (line.includes("-->")) {
351
+ const [startStr, endStr] = line.split("-->").map((s) => s.trim());
352
+ const endParts = endStr.split(" ");
353
+ const endTime = parseVttTimestamp(endParts[0]);
354
+ const startTime = parseVttTimestamp(startStr);
355
+ const textLines = [];
356
+ i++;
357
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].includes("-->")) {
358
+ if (!/^\d+$/.test(lines[i].trim())) {
359
+ textLines.push(lines[i].trim());
360
+ }
361
+ i++;
362
+ }
363
+ if (textLines.length > 0) {
364
+ cues.push({
365
+ id: `cue-${cues.length}`,
366
+ startTime,
367
+ endTime,
368
+ text: textLines.join("\n")
369
+ });
370
+ }
371
+ } else {
372
+ i++;
373
+ }
374
+ }
375
+ return cues;
376
+ }
377
+ function stripVttTags(text) {
378
+ return text.replace(/<\/?[^>]+(>|$)/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").trim();
379
+ }
380
+ function useCaptions(options = {}) {
381
+ const { src, content, stripTags = true, currentTime: externalTime } = options;
382
+ const [cues, setCues] = React5.useState([]);
383
+ const [internalTime, setInternalTime] = React5.useState(0);
384
+ const [isLoading, setIsLoading] = React5.useState(false);
385
+ const [error, setError] = React5.useState(null);
386
+ const currentTime = externalTime ?? internalTime;
387
+ React5.useEffect(() => {
388
+ if (content) {
389
+ const parsed = parseVtt(content);
390
+ setCues(
391
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
392
+ );
393
+ return;
394
+ }
395
+ if (!src) {
396
+ setCues([]);
397
+ return;
398
+ }
399
+ setIsLoading(true);
400
+ setError(null);
401
+ fetch(src).then((response) => {
402
+ if (!response.ok) {
403
+ throw new Error(`Failed to fetch captions: ${response.status}`);
404
+ }
405
+ return response.text();
406
+ }).then((vttContent) => {
407
+ const parsed = parseVtt(vttContent);
408
+ setCues(
409
+ stripTags ? parsed.map((cue) => ({ ...cue, text: stripVttTags(cue.text) })) : parsed
410
+ );
411
+ }).catch((err) => {
412
+ setError(err instanceof Error ? err : new Error(String(err)));
413
+ }).finally(() => {
414
+ setIsLoading(false);
415
+ });
416
+ }, [src, content, stripTags]);
417
+ const activeCue = React5.useMemo(() => {
418
+ return cues.find(
419
+ (cue) => currentTime >= cue.startTime && currentTime <= cue.endTime
420
+ ) ?? null;
421
+ }, [cues, currentTime]);
422
+ const handleSetCurrentTime = React5.useCallback((time) => {
423
+ setInternalTime(time);
424
+ }, []);
425
+ return {
426
+ cues,
427
+ activeCue,
428
+ setCurrentTime: handleSetCurrentTime,
429
+ isLoading,
430
+ error
431
+ };
432
+ }
433
+ function useVideoKeyboard({
434
+ videoRef,
435
+ enabled = true,
436
+ seekAmount = 5,
437
+ volumeStep = 0.1,
438
+ onTogglePlay,
439
+ onToggleFullscreen,
440
+ onToggleCaptions,
441
+ onShowControls
442
+ }) {
443
+ const handleKeyDown = React5.useCallback(
444
+ (e) => {
445
+ if (!enabled) return;
446
+ const video = videoRef.current;
447
+ if (!video) return;
448
+ const target = e.target;
449
+ if (target.tagName === "BUTTON" || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.closest("button") || target.closest("input") || target.closest("[role='slider']")) {
450
+ return;
451
+ }
452
+ let handled = false;
453
+ switch (e.key) {
454
+ // Play/Pause
455
+ case " ":
456
+ case "Spacebar":
457
+ case "k":
458
+ if (onTogglePlay) {
459
+ onTogglePlay();
460
+ } else {
461
+ if (video.paused) {
462
+ video.play().catch(() => {
463
+ });
464
+ } else {
465
+ video.pause();
466
+ }
467
+ }
468
+ handled = true;
469
+ break;
470
+ // Seek backward
471
+ case "ArrowLeft":
472
+ case "j":
473
+ video.currentTime = Math.max(0, video.currentTime - seekAmount);
474
+ handled = true;
475
+ break;
476
+ // Seek forward
477
+ case "ArrowRight":
478
+ case "l":
479
+ video.currentTime = Math.min(
480
+ video.duration || 0,
481
+ video.currentTime + seekAmount
482
+ );
483
+ handled = true;
484
+ break;
485
+ // Volume up
486
+ case "ArrowUp":
487
+ video.volume = Math.min(1, video.volume + volumeStep);
488
+ video.muted = false;
489
+ handled = true;
490
+ break;
491
+ // Volume down
492
+ case "ArrowDown":
493
+ video.volume = Math.max(0, video.volume - volumeStep);
494
+ handled = true;
495
+ break;
496
+ // Toggle mute
497
+ case "m":
498
+ case "M":
499
+ video.muted = !video.muted;
500
+ handled = true;
501
+ break;
502
+ // Toggle fullscreen
503
+ case "f":
504
+ case "F":
505
+ onToggleFullscreen?.();
506
+ handled = true;
507
+ break;
508
+ // Toggle captions
509
+ case "c":
510
+ case "C":
511
+ onToggleCaptions?.();
512
+ handled = true;
513
+ break;
514
+ // Jump to start
515
+ case "Home":
516
+ video.currentTime = 0;
517
+ handled = true;
518
+ break;
519
+ // Jump to end
520
+ case "End":
521
+ video.currentTime = video.duration || 0;
522
+ handled = true;
523
+ break;
524
+ // Number keys for percentage seeking (0-9)
525
+ case "0":
526
+ case "1":
527
+ case "2":
528
+ case "3":
529
+ case "4":
530
+ case "5":
531
+ case "6":
532
+ case "7":
533
+ case "8":
534
+ case "9":
535
+ if (video.duration) {
536
+ const percent = parseInt(e.key, 10) / 10;
537
+ video.currentTime = video.duration * percent;
538
+ handled = true;
539
+ }
540
+ break;
541
+ }
542
+ if (handled) {
543
+ e.preventDefault();
544
+ e.stopPropagation();
545
+ onShowControls?.();
546
+ }
547
+ },
548
+ [
549
+ enabled,
550
+ videoRef,
551
+ seekAmount,
552
+ volumeStep,
553
+ onTogglePlay,
554
+ onToggleFullscreen,
555
+ onToggleCaptions,
556
+ onShowControls
557
+ ]
558
+ );
559
+ const containerProps = React5.useMemo(
560
+ () => ({
561
+ onKeyDown: handleKeyDown,
562
+ tabIndex: 0,
563
+ role: "application",
564
+ "aria-label": "Video player, press space to play or pause"
565
+ }),
566
+ [handleKeyDown]
567
+ );
568
+ return {
569
+ handleKeyDown,
570
+ containerProps
571
+ };
572
+ }
573
+ var videoPlayerVariants = tv({
574
+ base: [
575
+ "relative",
576
+ "bg-black",
577
+ "overflow-hidden",
578
+ // Focus styling for keyboard navigation
579
+ "focus:outline-none",
580
+ "focus-visible:ring-2",
581
+ "focus-visible:ring-white/50"
582
+ ],
583
+ variants: {
584
+ aspectRatio: {
585
+ "16/9": "aspect-video",
586
+ "4/3": "aspect-[4/3]",
587
+ "1/1": "aspect-square",
588
+ "9/16": "aspect-[9/16]",
589
+ auto: ""
590
+ },
591
+ rounded: {
592
+ none: "",
593
+ sm: "rounded-4",
594
+ md: "rounded-8",
595
+ lg: "rounded-12"
596
+ }
597
+ },
598
+ defaultVariants: {
599
+ aspectRatio: "16/9",
600
+ rounded: "none"
601
+ }
602
+ });
603
+ var mediaControllerVariants = tv({
604
+ base: [
605
+ "absolute inset-0",
606
+ "w-full h-full",
607
+ // Button styling - transparent base, hover shows background
608
+ "[--media-control-background:transparent]",
609
+ "[--media-control-hover-background:var(--color-video-player-button-bg-hover)]",
610
+ "[--media-control-padding:8px]",
611
+ "[--media-control-height:36px]",
612
+ "[--media-button-icon-width:20px]",
613
+ "[--media-button-icon-height:20px]",
614
+ // Progress bar / range styling
615
+ "[--media-range-track-background:var(--color-video-player-progress-bg)]",
616
+ "[--media-range-bar-color:var(--color-video-player-progress-fill)]",
617
+ "[--media-range-track-height:4px]",
618
+ "[--media-range-track-border-radius:2px]",
619
+ "[--media-range-thumb-background:var(--color-video-player-progress-fill)]",
620
+ "[--media-range-thumb-height:12px]",
621
+ "[--media-range-thumb-width:12px]",
622
+ "[--media-range-thumb-border-radius:50%]",
623
+ // Text/icon colors
624
+ "[--media-icon-color:var(--color-video-player-controls-text)]",
625
+ "[--media-primary-color:var(--color-video-player-controls-text)]",
626
+ "[--media-secondary-color:transparent]",
627
+ "[--media-text-color:var(--color-video-player-controls-text)]",
628
+ "[--media-font-size:14px]",
629
+ // Time display styling
630
+ "[--media-time-display-background:transparent]"
631
+ ]
632
+ });
633
+ var mediaButtonStyles = {
634
+ padding: "8px",
635
+ borderRadius: "50%",
636
+ // Tooltip styling - consistent across all buttons
637
+ "--media-tooltip-background": "var(--color-video-player-tooltip-bg)",
638
+ "--media-tooltip-arrow-display": "none",
639
+ "--media-tooltip-distance": "8px"
640
+ };
641
+ var timeRangeStyles = {
642
+ flex: 1,
643
+ background: "transparent",
644
+ // Preview tooltip styling - consistent with button tooltips
645
+ "--media-box-arrow-display": "none",
646
+ "--media-preview-box-margin": "0 0 8px 0",
647
+ "--media-preview-time-margin": "0 0 8px 0",
648
+ "--media-preview-time-background": "var(--color-video-player-tooltip-bg)"
649
+ };
650
+ var volumeRangeStyles = {
651
+ width: "80px",
652
+ background: "transparent",
653
+ // Tooltip styling - consistent with button tooltips
654
+ "--media-tooltip-background": "var(--color-video-player-tooltip-bg)",
655
+ "--media-tooltip-arrow-display": "none",
656
+ "--media-tooltip-distance": "8px"
657
+ };
658
+ var timeDisplayStyles = {
659
+ background: "transparent",
660
+ fontFamily: "monospace",
661
+ fontSize: "14px",
662
+ color: "white",
663
+ whiteSpace: "nowrap"
664
+ };
665
+ var controlBarVariants = tv({
666
+ base: [
667
+ // Layout handled in inline styles, but we need flex
668
+ "flex items-center",
669
+ // Background using semantic token
670
+ "bg-video-player-controls-bg",
671
+ // Animation
672
+ "transition-all duration-300"
673
+ ],
674
+ variants: {
675
+ visible: {
676
+ true: "opacity-100 translate-y-0",
677
+ false: "opacity-0 translate-y-16 pointer-events-none"
678
+ }
679
+ },
680
+ defaultVariants: {
681
+ visible: true
682
+ }
683
+ });
684
+ var controlButtonVariants = tv({
685
+ base: [
686
+ "flex items-center justify-center",
687
+ "p-8 rounded-full",
688
+ // Transparent by default, background on hover
689
+ "bg-transparent",
690
+ "text-video-player-controls-text",
691
+ "hover:bg-video-player-button-bg-hover",
692
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-video-player-progress-bg",
693
+ "transition-colors duration-150",
694
+ "cursor-pointer"
695
+ ]
696
+ });
697
+ var loadingOverlayVariants = tv({
698
+ base: [
699
+ "absolute inset-0",
700
+ "flex items-center justify-center",
701
+ "bg-black/50",
702
+ "pointer-events-none",
703
+ "z-10"
704
+ ]
705
+ });
706
+ function useHlsInternal(videoRef, src, enabled) {
707
+ const [isLoading, setIsLoading] = React5.useState(true);
708
+ const [error, setError] = React5.useState(null);
709
+ const hlsRef = React5.useRef(null);
710
+ React5.useEffect(() => {
711
+ if (!src || !videoRef.current) {
712
+ return;
713
+ }
714
+ const video = videoRef.current;
715
+ const isHlsSource = src.includes(".m3u8");
716
+ if (!isHlsSource) {
717
+ video.src = src;
718
+ setIsLoading(false);
719
+ return;
720
+ }
721
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
722
+ video.src = src;
723
+ setIsLoading(false);
724
+ return;
725
+ }
726
+ const loadHls = async () => {
727
+ try {
728
+ const HlsModule = await import('hls.js');
729
+ const Hls = HlsModule.default;
730
+ if (!Hls.isSupported()) {
731
+ video.src = src;
732
+ setIsLoading(false);
733
+ return;
734
+ }
735
+ const hls = new Hls({
736
+ enableWorker: true,
737
+ lowLatencyMode: false
738
+ });
739
+ hls.loadSource(src);
740
+ hls.attachMedia(video);
741
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
742
+ setIsLoading(false);
743
+ });
744
+ hls.on(
745
+ Hls.Events.ERROR,
746
+ (_event, data) => {
747
+ if (data.fatal) {
748
+ setError(new Error(`HLS error: ${data.details}`));
749
+ setIsLoading(false);
750
+ }
751
+ }
752
+ );
753
+ hlsRef.current = hls;
754
+ } catch {
755
+ video.src = src;
756
+ setIsLoading(false);
757
+ }
758
+ };
759
+ loadHls();
760
+ return () => {
761
+ if (hlsRef.current) {
762
+ hlsRef.current.destroy();
763
+ hlsRef.current = null;
764
+ }
765
+ };
766
+ }, [enabled, src, videoRef]);
767
+ return { isLoading, error };
768
+ }
769
+ var VideoPlayer = React5.forwardRef(
770
+ ({
771
+ className,
772
+ src,
773
+ cloudflare,
774
+ poster,
775
+ captionsSrc,
776
+ autoPlay = false,
777
+ loop = false,
778
+ muted = false,
779
+ controls = true,
780
+ autoHideControls = true,
781
+ autoHideDelay = 3e3,
782
+ captionsEnabled: initialCaptionsEnabled = false,
783
+ aspectRatio,
784
+ rounded,
785
+ onPlay,
786
+ onPause,
787
+ onEnded,
788
+ onTimeUpdate,
789
+ onError,
790
+ videoRef: externalVideoRef,
791
+ ...props
792
+ }, ref) => {
793
+ const containerRef = React5.useRef(null);
794
+ const internalVideoRef = React5.useRef(null);
795
+ const controlsTimeoutRef = React5.useRef(null);
796
+ const [isPlaying, setIsPlaying] = React5.useState(false);
797
+ const [currentTime, setCurrentTime] = React5.useState(0);
798
+ const [controlsVisible, setControlsVisible] = React5.useState(true);
799
+ const [captionsEnabled, setCaptionsEnabled] = React5.useState(
800
+ initialCaptionsEnabled
801
+ );
802
+ const [isFullscreen, setIsFullscreen] = React5.useState(false);
803
+ const videoSrc = React5.useMemo(() => {
804
+ if (cloudflare) {
805
+ return `https://customer-${cloudflare.customerCode}.cloudflarestream.com/${cloudflare.videoId}/manifest/video.m3u8`;
806
+ }
807
+ return src;
808
+ }, [cloudflare, src]);
809
+ const { isLoading, error: hlsError } = useHlsInternal(
810
+ internalVideoRef,
811
+ videoSrc,
812
+ true
813
+ );
814
+ const { activeCue } = useCaptions({
815
+ src: captionsSrc,
816
+ currentTime
817
+ });
818
+ React5.useEffect(() => {
819
+ if (externalVideoRef) {
820
+ externalVideoRef.current = internalVideoRef.current;
821
+ }
822
+ }, [externalVideoRef]);
823
+ React5.useImperativeHandle(
824
+ ref,
825
+ () => containerRef.current
826
+ );
827
+ React5.useEffect(() => {
828
+ if (hlsError && onError) {
829
+ onError(hlsError);
830
+ }
831
+ }, [hlsError, onError]);
832
+ React5.useEffect(() => {
833
+ const video = internalVideoRef.current;
834
+ if (!video) return;
835
+ const handlePlay = () => {
836
+ setIsPlaying(true);
837
+ onPlay?.();
838
+ };
839
+ const handlePause = () => {
840
+ setIsPlaying(false);
841
+ onPause?.();
842
+ };
843
+ const handleEnded = () => {
844
+ setIsPlaying(false);
845
+ onEnded?.();
846
+ };
847
+ const handleTimeUpdate = () => {
848
+ setCurrentTime(video.currentTime);
849
+ onTimeUpdate?.(video.currentTime);
850
+ };
851
+ const handleCanPlay = () => {
852
+ if (autoPlay) {
853
+ video.play().catch(() => {
854
+ });
855
+ }
856
+ };
857
+ video.addEventListener("play", handlePlay);
858
+ video.addEventListener("pause", handlePause);
859
+ video.addEventListener("ended", handleEnded);
860
+ video.addEventListener("timeupdate", handleTimeUpdate);
861
+ video.addEventListener("canplay", handleCanPlay);
862
+ return () => {
863
+ video.removeEventListener("play", handlePlay);
864
+ video.removeEventListener("pause", handlePause);
865
+ video.removeEventListener("ended", handleEnded);
866
+ video.removeEventListener("timeupdate", handleTimeUpdate);
867
+ video.removeEventListener("canplay", handleCanPlay);
868
+ };
869
+ }, [autoPlay, onPlay, onPause, onEnded, onTimeUpdate]);
870
+ React5.useEffect(() => {
871
+ if (!autoHideControls || !isPlaying || !controlsVisible) return;
872
+ controlsTimeoutRef.current = setTimeout(() => {
873
+ setControlsVisible(false);
874
+ }, autoHideDelay);
875
+ return () => {
876
+ if (controlsTimeoutRef.current) {
877
+ clearTimeout(controlsTimeoutRef.current);
878
+ }
879
+ };
880
+ }, [autoHideControls, isPlaying, controlsVisible, autoHideDelay]);
881
+ React5.useEffect(() => {
882
+ const handleFullscreenChange = () => {
883
+ setIsFullscreen(!!document.fullscreenElement);
884
+ };
885
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
886
+ document.addEventListener(
887
+ "webkitfullscreenchange",
888
+ handleFullscreenChange
889
+ );
890
+ return () => {
891
+ document.removeEventListener(
892
+ "fullscreenchange",
893
+ handleFullscreenChange
894
+ );
895
+ document.removeEventListener(
896
+ "webkitfullscreenchange",
897
+ handleFullscreenChange
898
+ );
899
+ };
900
+ }, []);
901
+ const togglePlay = React5.useCallback(() => {
902
+ const video = internalVideoRef.current;
903
+ if (!video) return;
904
+ if (video.paused) {
905
+ video.play().catch(() => {
906
+ });
907
+ } else {
908
+ video.pause();
909
+ }
910
+ }, []);
911
+ const toggleCaptions = React5.useCallback(() => {
912
+ setCaptionsEnabled((prev) => !prev);
913
+ }, []);
914
+ const toggleFullscreen = React5.useCallback(() => {
915
+ if (!document.fullscreenElement) {
916
+ containerRef.current?.requestFullscreen();
917
+ } else {
918
+ document.exitFullscreen();
919
+ }
920
+ }, []);
921
+ const showControls = React5.useCallback(() => {
922
+ setControlsVisible(true);
923
+ if (controlsTimeoutRef.current) {
924
+ clearTimeout(controlsTimeoutRef.current);
925
+ }
926
+ }, []);
927
+ const handleMouseLeave = React5.useCallback(() => {
928
+ if (autoHideControls && isPlaying) {
929
+ setControlsVisible(false);
930
+ }
931
+ }, [autoHideControls, isPlaying]);
932
+ const { containerProps: keyboardProps } = useVideoKeyboard({
933
+ videoRef: internalVideoRef,
934
+ onTogglePlay: togglePlay,
935
+ onToggleFullscreen: toggleFullscreen,
936
+ onToggleCaptions: captionsSrc ? toggleCaptions : void 0,
937
+ onShowControls: showControls
938
+ });
939
+ return (
940
+ // biome-ignore lint/a11y/noStaticElementInteractions: role is applied via keyboardProps spread
941
+ /* @__PURE__ */ jsxs(
942
+ "div",
943
+ {
944
+ ref: containerRef,
945
+ className: cn(videoPlayerVariants({ aspectRatio, rounded }), className),
946
+ onMouseMove: showControls,
947
+ onMouseLeave: handleMouseLeave,
948
+ ...keyboardProps,
949
+ ...props,
950
+ children: [
951
+ controls ? /* @__PURE__ */ jsxs(
952
+ MediaController,
953
+ {
954
+ noAutohide: true,
955
+ className: mediaControllerVariants(),
956
+ children: [
957
+ /* @__PURE__ */ jsx(
958
+ "video",
959
+ {
960
+ ref: internalVideoRef,
961
+ slot: "media",
962
+ poster,
963
+ loop,
964
+ muted,
965
+ playsInline: true,
966
+ crossOrigin: "anonymous",
967
+ className: "w-full h-full object-contain"
968
+ }
969
+ ),
970
+ /* @__PURE__ */ jsx(MediaLoadingIndicator, { slot: "centered-chrome", noAutohide: true }),
971
+ /* @__PURE__ */ jsx(
972
+ "div",
973
+ {
974
+ onClick: togglePlay,
975
+ className: "absolute inset-0 cursor-pointer z-[1]",
976
+ "aria-hidden": "true"
977
+ }
978
+ ),
979
+ /* @__PURE__ */ jsxs(
980
+ MediaControlBar,
981
+ {
982
+ className: controlBarVariants({ visible: controlsVisible }),
983
+ onClick: (e) => e.stopPropagation(),
984
+ style: {
985
+ position: "absolute",
986
+ left: "24px",
987
+ right: "24px",
988
+ bottom: "24px",
989
+ gap: "12px",
990
+ padding: "8px 16px",
991
+ borderRadius: "9999px",
992
+ backdropFilter: "blur(10px)",
993
+ WebkitBackdropFilter: "blur(10px)",
994
+ zIndex: 2
995
+ },
996
+ children: [
997
+ /* @__PURE__ */ jsx(MediaPlayButton, { style: mediaButtonStyles }),
998
+ /* @__PURE__ */ jsx(MediaMuteButton, { style: mediaButtonStyles }),
999
+ /* @__PURE__ */ jsx(MediaVolumeRange, { style: volumeRangeStyles }),
1000
+ /* @__PURE__ */ jsx(
1001
+ MediaTimeDisplay,
1002
+ {
1003
+ style: timeDisplayStyles,
1004
+ showDuration: true,
1005
+ noToggle: true
1006
+ }
1007
+ ),
1008
+ /* @__PURE__ */ jsx(MediaTimeRange, { style: timeRangeStyles }),
1009
+ captionsSrc && /* @__PURE__ */ jsx(
1010
+ "button",
1011
+ {
1012
+ type: "button",
1013
+ className: controlButtonVariants(),
1014
+ onClick: (e) => {
1015
+ e.stopPropagation();
1016
+ toggleCaptions();
1017
+ },
1018
+ "aria-label": captionsEnabled ? "Disable captions" : "Enable captions",
1019
+ "aria-pressed": captionsEnabled,
1020
+ children: /* @__PURE__ */ jsx(CaptionsIcon, { enabled: captionsEnabled })
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ jsx(
1024
+ "button",
1025
+ {
1026
+ type: "button",
1027
+ className: controlButtonVariants(),
1028
+ onClick: (e) => {
1029
+ e.stopPropagation();
1030
+ toggleFullscreen();
1031
+ },
1032
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
1033
+ children: /* @__PURE__ */ jsx(FullscreenIcon, { isFullscreen })
1034
+ }
1035
+ )
1036
+ ]
1037
+ }
1038
+ )
1039
+ ]
1040
+ }
1041
+ ) : (
1042
+ /* Video without controls */
1043
+ /* @__PURE__ */ jsx(
1044
+ "video",
1045
+ {
1046
+ ref: internalVideoRef,
1047
+ poster,
1048
+ loop,
1049
+ muted,
1050
+ playsInline: true,
1051
+ crossOrigin: "anonymous",
1052
+ className: "w-full h-full object-contain",
1053
+ onClick: togglePlay
1054
+ }
1055
+ )
1056
+ ),
1057
+ isLoading && /* @__PURE__ */ jsx("div", { className: loadingOverlayVariants(), children: /* @__PURE__ */ jsx("div", { className: "w-40 h-40 border-3 border-white/30 border-t-white rounded-full animate-spin" }) }),
1058
+ hlsError && /* @__PURE__ */ jsx("div", { className: loadingOverlayVariants(), children: /* @__PURE__ */ jsxs("div", { className: "text-white text-center px-16", children: [
1059
+ /* @__PURE__ */ jsx("p", { className: "typography-body-sm-sm", children: "Failed to load video" }),
1060
+ /* @__PURE__ */ jsx("p", { className: "typography-caption text-white/60 mt-4", children: hlsError.message })
1061
+ ] }) }),
1062
+ captionsEnabled && activeCue && /* @__PURE__ */ jsx(CaptionOverlay, { cue: activeCue })
1063
+ ]
1064
+ }
1065
+ )
1066
+ );
1067
+ }
1068
+ );
1069
+ VideoPlayer.displayName = "VideoPlayer";
1070
+ var CaptionsIcon = ({ enabled }) => /* @__PURE__ */ jsx(
1071
+ "svg",
1072
+ {
1073
+ className: "w-20 h-20",
1074
+ viewBox: "0 0 24 24",
1075
+ fill: "currentColor",
1076
+ "aria-hidden": "true",
1077
+ children: enabled ? (
1078
+ // Captions On
1079
+ /* @__PURE__ */ jsx("path", { d: "M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z" })
1080
+ ) : (
1081
+ // Captions Off (with strike-through)
1082
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1083
+ /* @__PURE__ */ jsx("path", { d: "M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z" }),
1084
+ /* @__PURE__ */ jsx(
1085
+ "line",
1086
+ {
1087
+ x1: "4",
1088
+ y1: "20",
1089
+ x2: "20",
1090
+ y2: "4",
1091
+ stroke: "currentColor",
1092
+ strokeWidth: "2"
1093
+ }
1094
+ )
1095
+ ] })
1096
+ )
1097
+ }
1098
+ );
1099
+ var FullscreenIcon = ({ isFullscreen }) => /* @__PURE__ */ jsx(
1100
+ "svg",
1101
+ {
1102
+ className: "w-20 h-20",
1103
+ viewBox: "0 0 24 24",
1104
+ fill: "none",
1105
+ stroke: "currentColor",
1106
+ strokeWidth: "2",
1107
+ strokeLinecap: "round",
1108
+ strokeLinejoin: "round",
1109
+ "aria-hidden": "true",
1110
+ children: isFullscreen ? (
1111
+ // Minimize (exit fullscreen)
1112
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1113
+ /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
1114
+ /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
1115
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
1116
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
1117
+ ] })
1118
+ ) : (
1119
+ // Maximize (enter fullscreen)
1120
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1121
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
1122
+ /* @__PURE__ */ jsx("polyline", { points: "9 21 3 21 3 15" }),
1123
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
1124
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
1125
+ ] })
1126
+ )
1127
+ }
1128
+ );
1129
+ var videoDialogVariants = tv({
1130
+ base: [
1131
+ // Fixed positioning covering viewport
1132
+ "fixed inset-0",
1133
+ // Dark background base
1134
+ "bg-black",
1135
+ // Flex centering for the video
1136
+ "flex items-center justify-center",
1137
+ // Animation
1138
+ "transition-opacity duration-300",
1139
+ "data-[starting-style]:opacity-0",
1140
+ "data-[ending-style]:opacity-0",
1141
+ // Focus outline
1142
+ "outline-none"
1143
+ ]
1144
+ });
1145
+ var closeButtonVariants = tv({
1146
+ base: [
1147
+ // Positioning
1148
+ "absolute z-50",
1149
+ // Size and shape
1150
+ "w-48 h-48 rounded-full",
1151
+ // Colors
1152
+ "bg-black/60 text-white",
1153
+ "hover:bg-black/80",
1154
+ // Transition
1155
+ "transition-all duration-150",
1156
+ // Focus
1157
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50",
1158
+ // Flex centering for icon
1159
+ "flex items-center justify-center",
1160
+ // Cursor
1161
+ "cursor-pointer"
1162
+ ],
1163
+ variants: {
1164
+ position: {
1165
+ "top-right": "top-24 right-24",
1166
+ "top-left": "top-24 left-24"
1167
+ }
1168
+ },
1169
+ defaultVariants: {
1170
+ position: "top-right"
1171
+ }
1172
+ });
1173
+ var videoContainerVariants = tv({
1174
+ base: [
1175
+ // Relative for z-index
1176
+ "relative z-10",
1177
+ // Fill available space
1178
+ "w-full h-full",
1179
+ // Flex to center the video
1180
+ "flex items-center justify-center",
1181
+ // Padding from viewport edges
1182
+ "p-16 sm:p-24 lg:p-32"
1183
+ ]
1184
+ });
1185
+ var VideoDialog = React5.forwardRef(
1186
+ ({
1187
+ trigger,
1188
+ src,
1189
+ cloudflare,
1190
+ blur = "high",
1191
+ overlay = "vignette",
1192
+ backdropOpacity = 0.6,
1193
+ showClose = true,
1194
+ closePosition = "top-right",
1195
+ rounded = "lg",
1196
+ open: controlledOpen,
1197
+ defaultOpen,
1198
+ onOpenChange,
1199
+ className,
1200
+ autoPlay,
1201
+ ...videoProps
1202
+ }, ref) => {
1203
+ const [internalOpen, setInternalOpen] = React5.useState(
1204
+ defaultOpen ?? false
1205
+ );
1206
+ const isControlled = controlledOpen !== void 0;
1207
+ const open = isControlled ? controlledOpen : internalOpen;
1208
+ const handleOpenChange = React5.useCallback(
1209
+ (newOpen) => {
1210
+ if (!isControlled) {
1211
+ setInternalOpen(newOpen);
1212
+ }
1213
+ onOpenChange?.(newOpen);
1214
+ },
1215
+ [isControlled, onOpenChange]
1216
+ );
1217
+ const primaryVideoRef = React5.useRef(null);
1218
+ return /* @__PURE__ */ jsxs(
1219
+ Dialog.Root,
1220
+ {
1221
+ open,
1222
+ defaultOpen,
1223
+ onOpenChange: handleOpenChange,
1224
+ children: [
1225
+ /* @__PURE__ */ jsx(
1226
+ Dialog.Trigger,
1227
+ {
1228
+ render: React5.isValidElement(trigger) ? trigger : void 0,
1229
+ children: !React5.isValidElement(trigger) ? trigger : void 0
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsx(Dialog.Portal, { children: /* @__PURE__ */ jsxs(
1233
+ Dialog.Popup,
1234
+ {
1235
+ ref,
1236
+ className: cn(videoDialogVariants(), className),
1237
+ "data-component": "video-dialog",
1238
+ children: [
1239
+ /* @__PURE__ */ jsx(
1240
+ BlurredVideoBackdrop,
1241
+ {
1242
+ videoRef: primaryVideoRef,
1243
+ blur,
1244
+ overlay,
1245
+ opacity: backdropOpacity,
1246
+ extension: 120
1247
+ }
1248
+ ),
1249
+ showClose && /* @__PURE__ */ jsxs(
1250
+ Dialog.Close,
1251
+ {
1252
+ className: closeButtonVariants({ position: closePosition }),
1253
+ children: [
1254
+ /* @__PURE__ */ jsx(
1255
+ "svg",
1256
+ {
1257
+ width: "20",
1258
+ height: "20",
1259
+ viewBox: "0 0 20 20",
1260
+ fill: "none",
1261
+ "aria-hidden": "true",
1262
+ children: /* @__PURE__ */ jsx(
1263
+ "path",
1264
+ {
1265
+ d: "M4 4L16 16M4 16L16 4",
1266
+ stroke: "currentColor",
1267
+ strokeWidth: "2",
1268
+ strokeLinecap: "round"
1269
+ }
1270
+ )
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
1274
+ ]
1275
+ }
1276
+ ),
1277
+ /* @__PURE__ */ jsx("div", { className: videoContainerVariants(), children: /* @__PURE__ */ jsx(
1278
+ VideoPlayer,
1279
+ {
1280
+ src,
1281
+ cloudflare,
1282
+ videoRef: primaryVideoRef,
1283
+ rounded,
1284
+ autoPlay: autoPlay ?? open,
1285
+ aspectRatio: "16/9",
1286
+ style: {
1287
+ width: "min(100%, calc((100vh - 64px) * 16 / 9))",
1288
+ maxHeight: "calc(100vh - 64px)"
1289
+ },
1290
+ ...videoProps
1291
+ }
1292
+ ) })
1293
+ ]
1294
+ }
1295
+ ) })
1296
+ ]
1297
+ }
1298
+ );
1299
+ }
1300
+ );
1301
+ VideoDialog.displayName = "VideoDialog";
1302
+
1303
+ export { VideoDialog, closeButtonVariants, videoContainerVariants, videoDialogVariants };
1304
+ //# sourceMappingURL=index.js.map
1305
+ //# sourceMappingURL=index.js.map