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