@lightbird/ui 0.6.0 → 0.8.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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var React10 = require('react');
4
+ var React11 = require('react');
5
5
  var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var SliderPrimitive = require('@radix-ui/react-slider');
@@ -11,6 +11,7 @@ var classVarianceAuthority = require('class-variance-authority');
11
11
  var PopoverPrimitive = require('@radix-ui/react-popover');
12
12
  var TooltipPrimitive = require('@radix-ui/react-tooltip');
13
13
  var LabelPrimitive = require('@radix-ui/react-label');
14
+ var react = require('@lightbird/core/react');
14
15
  var lucideReact = require('lucide-react');
15
16
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
16
17
  var core$1 = require('@dnd-kit/core');
@@ -20,7 +21,6 @@ var ScrollAreaPrimitive = require('@radix-ui/react-scroll-area');
20
21
  var SelectPrimitive = require('@radix-ui/react-select');
21
22
  var core = require('@lightbird/core');
22
23
  var DialogPrimitive = require('@radix-ui/react-dialog');
23
- var react = require('@lightbird/core/react');
24
24
  var reactSdk = require('@openfeature/react-sdk');
25
25
  var AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
26
26
  var ToastPrimitives = require('@radix-ui/react-toast');
@@ -43,7 +43,7 @@ function _interopNamespace(e) {
43
43
  return Object.freeze(n);
44
44
  }
45
45
 
46
- var React10__namespace = /*#__PURE__*/_interopNamespace(React10);
46
+ var React11__namespace = /*#__PURE__*/_interopNamespace(React11);
47
47
  var SliderPrimitive__namespace = /*#__PURE__*/_interopNamespace(SliderPrimitive);
48
48
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
49
49
  var TooltipPrimitive__namespace = /*#__PURE__*/_interopNamespace(TooltipPrimitive);
@@ -58,7 +58,7 @@ var ToastPrimitives__namespace = /*#__PURE__*/_interopNamespace(ToastPrimitives)
58
58
  function cn(...inputs) {
59
59
  return tailwindMerge.twMerge(clsx.clsx(inputs));
60
60
  }
61
- var Slider = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
61
+ var Slider = React11__namespace.forwardRef(({ className, trackClassName, rangeClassName, thumbClassName, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
62
62
  SliderPrimitive__namespace.Root,
63
63
  {
64
64
  ref,
@@ -68,8 +68,33 @@ var Slider = React10__namespace.forwardRef(({ className, ...props }, ref) => /*
68
68
  ),
69
69
  ...props,
70
70
  children: [
71
- /* @__PURE__ */ jsxRuntime.jsx(SliderPrimitive__namespace.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsxRuntime.jsx(SliderPrimitive__namespace.Range, { className: "absolute h-full bg-primary" }) }),
72
- /* @__PURE__ */ jsxRuntime.jsx(SliderPrimitive__namespace.Thumb, { className: "block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" })
71
+ /* @__PURE__ */ jsxRuntime.jsx(
72
+ SliderPrimitive__namespace.Track,
73
+ {
74
+ className: cn(
75
+ "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary transition-all duration-150 ease-out",
76
+ trackClassName
77
+ ),
78
+ children: /* @__PURE__ */ jsxRuntime.jsx(
79
+ SliderPrimitive__namespace.Range,
80
+ {
81
+ className: cn(
82
+ "absolute h-full bg-primary transition-[box-shadow] duration-150",
83
+ rangeClassName
84
+ )
85
+ }
86
+ )
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsxRuntime.jsx(
90
+ SliderPrimitive__namespace.Thumb,
91
+ {
92
+ className: cn(
93
+ "block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-[transform,box-shadow] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
94
+ thumbClassName
95
+ )
96
+ }
97
+ )
73
98
  ]
74
99
  }
75
100
  ));
@@ -99,7 +124,7 @@ var buttonVariants = classVarianceAuthority.cva(
99
124
  }
100
125
  }
101
126
  );
102
- var Button = React10__namespace.forwardRef(
127
+ var Button = React11__namespace.forwardRef(
103
128
  ({ className, variant, size, asChild = false, ...props }, ref) => {
104
129
  const Comp = asChild ? reactSlot.Slot : "button";
105
130
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -115,7 +140,7 @@ var Button = React10__namespace.forwardRef(
115
140
  Button.displayName = "Button";
116
141
  var Popover = PopoverPrimitive__namespace.Root;
117
142
  var PopoverTrigger = PopoverPrimitive__namespace.Trigger;
118
- var PopoverContent = React10__namespace.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
143
+ var PopoverContent = React11__namespace.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
119
144
  PopoverPrimitive__namespace.Content,
120
145
  {
121
146
  ref,
@@ -132,7 +157,7 @@ PopoverContent.displayName = PopoverPrimitive__namespace.Content.displayName;
132
157
  var TooltipProvider = TooltipPrimitive__namespace.Provider;
133
158
  var Tooltip = TooltipPrimitive__namespace.Root;
134
159
  var TooltipTrigger = TooltipPrimitive__namespace.Trigger;
135
- var TooltipContent = React10__namespace.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
160
+ var TooltipContent = React11__namespace.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
136
161
  TooltipPrimitive__namespace.Content,
137
162
  {
138
163
  ref,
@@ -148,7 +173,7 @@ TooltipContent.displayName = TooltipPrimitive__namespace.Content.displayName;
148
173
  var labelVariants = classVarianceAuthority.cva(
149
174
  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
150
175
  );
151
- var Label = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
176
+ var Label = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
152
177
  LabelPrimitive__namespace.Root,
153
178
  {
154
179
  ref,
@@ -157,7 +182,164 @@ var Label = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @
157
182
  }
158
183
  ));
159
184
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
160
- var RadioGroup = React10__namespace.forwardRef(({ className, ...props }, ref) => {
185
+ var formatTime = (time) => {
186
+ if (isNaN(time)) return "00:00";
187
+ const date = /* @__PURE__ */ new Date(0);
188
+ date.setSeconds(time);
189
+ const timeString = date.toISOString().substr(11, 8);
190
+ return timeString.startsWith("00:") ? timeString.substr(3) : timeString;
191
+ };
192
+ var SeekBar = React11__namespace.default.memo(function SeekBar2({
193
+ progress,
194
+ duration,
195
+ isPlaying,
196
+ onSeek,
197
+ videoRef,
198
+ chapters = [],
199
+ abLoop = { pointA: null, pointB: null, isLooping: false },
200
+ onSeekHover,
201
+ seekPreviewThumbnail = null
202
+ }) {
203
+ const noopRef = React11.useRef(null);
204
+ const smooth = react.useSmoothProgress(videoRef ?? noopRef, {
205
+ isPlaying: !!videoRef && isPlaying,
206
+ fallback: progress
207
+ });
208
+ const displayProgress = videoRef ? smooth : progress;
209
+ const [seekHover, setSeekHover] = React11.useState(null);
210
+ const [isHovering, setIsHovering] = React11.useState(false);
211
+ const [isScrubbing, setIsScrubbing] = React11.useState(false);
212
+ const active = isHovering || isScrubbing;
213
+ const handleSeekHover = (e) => {
214
+ if (duration <= 0) return;
215
+ const rect = e.currentTarget.getBoundingClientRect();
216
+ if (rect.width <= 0) return;
217
+ const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
218
+ const time = ratio * duration;
219
+ setSeekHover({ ratio, time });
220
+ onSeekHover?.(time);
221
+ };
222
+ const handleSeekLeave = () => {
223
+ setSeekHover(null);
224
+ setIsHovering(false);
225
+ onSeekHover?.(null);
226
+ };
227
+ return /* @__PURE__ */ jsxRuntime.jsxs(
228
+ "div",
229
+ {
230
+ className: "relative w-full py-2",
231
+ "data-testid": "seek-bar",
232
+ "data-scrubbing": isScrubbing || void 0,
233
+ "data-hover": isHovering || void 0,
234
+ onMouseEnter: () => setIsHovering(true),
235
+ onMouseMove: handleSeekHover,
236
+ onMouseLeave: handleSeekLeave,
237
+ children: [
238
+ seekHover && /* @__PURE__ */ jsxRuntime.jsxs(
239
+ "div",
240
+ {
241
+ "data-testid": "seek-preview",
242
+ className: "absolute bottom-full mb-3 -translate-x-1/2 pointer-events-none flex flex-col items-center z-20",
243
+ style: { left: `${seekHover.ratio * 100}%` },
244
+ children: [
245
+ seekPreviewThumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
246
+ "img",
247
+ {
248
+ src: seekPreviewThumbnail,
249
+ alt: "",
250
+ className: "w-40 h-[90px] rounded border border-white/20 bg-black object-cover shadow-lg"
251
+ }
252
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-40 h-[90px] rounded border border-white/20 bg-black/80 shadow-lg" }),
253
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 rounded bg-black/80 px-1.5 py-0.5 font-mono text-xs text-white", children: formatTime(seekHover.time) })
254
+ ]
255
+ }
256
+ ),
257
+ /* @__PURE__ */ jsxRuntime.jsx(
258
+ Slider,
259
+ {
260
+ value: [displayProgress],
261
+ max: duration || 0,
262
+ step: 0.05,
263
+ onValueChange: ([val]) => onSeek(val),
264
+ onPointerDown: () => setIsScrubbing(true),
265
+ onPointerUp: () => setIsScrubbing(false),
266
+ onLostPointerCapture: () => setIsScrubbing(false),
267
+ className: "w-full",
268
+ trackClassName: cn(
269
+ "rounded-none transition-all duration-150 ease-out",
270
+ active ? "h-1.5" : "h-[3px]"
271
+ ),
272
+ rangeClassName: cn(
273
+ "rounded-none",
274
+ active && "shadow-[0_0_6px_hsl(var(--primary)/0.6)]"
275
+ ),
276
+ thumbClassName: cn(
277
+ "h-3 w-3 border transition-[transform,opacity] duration-150",
278
+ active ? "scale-100 opacity-100" : "scale-0 opacity-0",
279
+ isScrubbing && "scale-125"
280
+ )
281
+ }
282
+ ),
283
+ chapters.length > 0 && duration > 0 && chapters.slice(1).map((chapter) => /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
285
+ "div",
286
+ {
287
+ "data-testid": "chapter-tick",
288
+ style: {
289
+ position: "absolute",
290
+ left: `${chapter.startTime / duration * 100}%`,
291
+ top: "50%",
292
+ width: "2px",
293
+ height: active ? "10px" : "6px",
294
+ background: "white",
295
+ opacity: 0.55,
296
+ pointerEvents: "none",
297
+ transform: "translate(-1px, -50%)",
298
+ transition: "height 150ms ease-out"
299
+ }
300
+ }
301
+ ) }),
302
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
303
+ chapter.title,
304
+ " \u2014 ",
305
+ formatTime(chapter.startTime)
306
+ ] }) })
307
+ ] }, chapter.index)),
308
+ duration > 0 && abLoop.pointA !== null && abLoop.pointB !== null && /* @__PURE__ */ jsxRuntime.jsx(
309
+ "div",
310
+ {
311
+ "data-testid": "ab-loop-region",
312
+ className: cn(
313
+ "pointer-events-none absolute top-1/2 -translate-y-1/2 bg-primary/40 transition-[height] duration-150",
314
+ active ? "h-1.5" : "h-[3px]"
315
+ ),
316
+ style: {
317
+ left: `${abLoop.pointA / duration * 100}%`,
318
+ width: `${(abLoop.pointB - abLoop.pointA) / duration * 100}%`
319
+ }
320
+ }
321
+ ),
322
+ duration > 0 && abLoop.pointA !== null && /* @__PURE__ */ jsxRuntime.jsx(
323
+ "div",
324
+ {
325
+ "data-testid": "ab-marker-a",
326
+ className: "pointer-events-none absolute top-1/2 -translate-y-1/2 h-4 w-0.5 -translate-x-1/2 bg-primary",
327
+ style: { left: `${abLoop.pointA / duration * 100}%` }
328
+ }
329
+ ),
330
+ duration > 0 && abLoop.pointB !== null && /* @__PURE__ */ jsxRuntime.jsx(
331
+ "div",
332
+ {
333
+ "data-testid": "ab-marker-b",
334
+ className: "pointer-events-none absolute top-1/2 -translate-y-1/2 h-4 w-0.5 -translate-x-1/2 bg-primary",
335
+ style: { left: `${abLoop.pointB / duration * 100}%` }
336
+ }
337
+ )
338
+ ]
339
+ }
340
+ );
341
+ });
342
+ var RadioGroup = React11__namespace.forwardRef(({ className, ...props }, ref) => {
161
343
  return /* @__PURE__ */ jsxRuntime.jsx(
162
344
  RadioGroupPrimitive__namespace.Root,
163
345
  {
@@ -168,7 +350,7 @@ var RadioGroup = React10__namespace.forwardRef(({ className, ...props }, ref) =>
168
350
  );
169
351
  });
170
352
  RadioGroup.displayName = RadioGroupPrimitive__namespace.Root.displayName;
171
- var RadioGroupItem = React10__namespace.forwardRef(({ className, ...props }, ref) => {
353
+ var RadioGroupItem = React11__namespace.forwardRef(({ className, ...props }, ref) => {
172
354
  return /* @__PURE__ */ jsxRuntime.jsx(
173
355
  RadioGroupPrimitive__namespace.Item,
174
356
  {
@@ -183,14 +365,14 @@ var RadioGroupItem = React10__namespace.forwardRef(({ className, ...props }, ref
183
365
  );
184
366
  });
185
367
  RadioGroupItem.displayName = RadioGroupPrimitive__namespace.Item.displayName;
186
- var formatTime = (time) => {
368
+ var formatTime2 = (time) => {
187
369
  if (isNaN(time)) return "00:00";
188
370
  const date = /* @__PURE__ */ new Date(0);
189
371
  date.setSeconds(time);
190
372
  const timeString = date.toISOString().substr(11, 8);
191
373
  return timeString.startsWith("00:") ? timeString.substr(3) : timeString;
192
374
  };
193
- var PlayerControls = React10__namespace.default.memo(function PlayerControls2({
375
+ var PlayerControls = React11__namespace.default.memo(function PlayerControls2({
194
376
  isPlaying,
195
377
  progress,
196
378
  duration,
@@ -234,355 +416,260 @@ var PlayerControls = React10__namespace.default.memo(function PlayerControls2({
234
416
  onSeekHover,
235
417
  seekPreviewThumbnail = null,
236
418
  abLoop = { pointA: null, pointB: null, isLooping: false },
237
- onABLoopCycle
419
+ onABLoopCycle,
420
+ videoRef,
421
+ isDisabled = false
238
422
  }) {
239
- const formattedProgress = React10.useMemo(() => formatTime(progress), [progress]);
240
- const formattedDuration = React10.useMemo(() => formatTime(duration), [duration]);
241
- const [chaptersMenuOpen, setChaptersMenuOpen] = React10.useState(false);
242
- const [seekHover, setSeekHover] = React10.useState(null);
243
- const handleSeekHover = (e) => {
244
- if (duration <= 0) return;
245
- const rect = e.currentTarget.getBoundingClientRect();
246
- if (rect.width <= 0) return;
247
- const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
248
- const time = ratio * duration;
249
- setSeekHover({ ratio, time });
250
- onSeekHover?.(time);
251
- };
252
- const handleSeekLeave = () => {
253
- setSeekHover(null);
254
- onSeekHover?.(null);
255
- };
256
- return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 ease-in-out flex flex-col gap-2", children: [
257
- /* @__PURE__ */ jsxRuntime.jsxs(
258
- "div",
259
- {
260
- className: "relative w-full",
261
- "data-testid": "seek-bar",
262
- onMouseMove: handleSeekHover,
263
- onMouseLeave: handleSeekLeave,
264
- children: [
265
- seekHover && /* @__PURE__ */ jsxRuntime.jsxs(
266
- "div",
267
- {
268
- "data-testid": "seek-preview",
269
- className: "absolute bottom-full mb-3 -translate-x-1/2 pointer-events-none flex flex-col items-center z-20",
270
- style: { left: `${seekHover.ratio * 100}%` },
271
- children: [
272
- seekPreviewThumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
273
- "img",
274
- {
275
- src: seekPreviewThumbnail,
276
- alt: "",
277
- className: "w-40 h-[90px] rounded border border-white/20 bg-black object-cover shadow-lg"
278
- }
279
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-40 h-[90px] rounded border border-white/20 bg-black/80 shadow-lg" }),
280
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 rounded bg-black/80 px-1.5 py-0.5 font-mono text-xs text-white", children: formatTime(seekHover.time) })
281
- ]
282
- }
283
- ),
284
- /* @__PURE__ */ jsxRuntime.jsx(
285
- Slider,
286
- {
287
- value: [progress],
288
- max: duration,
289
- step: 1,
290
- onValueChange: ([val]) => onSeek(val),
291
- className: "w-full h-2"
292
- }
293
- ),
294
- chapters.length > 0 && duration > 0 && chapters.slice(1).map((chapter) => /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
295
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
296
- "div",
297
- {
298
- "data-testid": "chapter-tick",
299
- style: {
300
- position: "absolute",
301
- left: `${chapter.startTime / duration * 100}%`,
302
- top: 0,
303
- width: "2px",
304
- height: "100%",
305
- background: "white",
306
- opacity: 0.5,
307
- pointerEvents: "none",
308
- transform: "translateX(-1px)"
309
- }
310
- }
311
- ) }),
312
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
313
- chapter.title,
314
- " \u2014 ",
315
- formatTime(chapter.startTime)
316
- ] }) })
317
- ] }, chapter.index)),
318
- duration > 0 && abLoop.pointA !== null && abLoop.pointB !== null && /* @__PURE__ */ jsxRuntime.jsx(
319
- "div",
320
- {
321
- "data-testid": "ab-loop-region",
322
- className: "pointer-events-none absolute top-0 h-full bg-primary/30",
323
- style: {
324
- left: `${abLoop.pointA / duration * 100}%`,
325
- width: `${(abLoop.pointB - abLoop.pointA) / duration * 100}%`
326
- }
327
- }
328
- ),
329
- duration > 0 && abLoop.pointA !== null && /* @__PURE__ */ jsxRuntime.jsx(
330
- "div",
331
- {
332
- "data-testid": "ab-marker-a",
333
- className: "pointer-events-none absolute top-0 h-full w-0.5 -translate-x-1/2 bg-primary",
334
- style: { left: `${abLoop.pointA / duration * 100}%` }
335
- }
336
- ),
337
- duration > 0 && abLoop.pointB !== null && /* @__PURE__ */ jsxRuntime.jsx(
338
- "div",
339
- {
340
- "data-testid": "ab-marker-b",
341
- className: "pointer-events-none absolute top-0 h-full w-0.5 -translate-x-1/2 bg-primary",
342
- style: { left: `${abLoop.pointB / duration * 100}%` }
343
- }
344
- )
345
- ]
346
- }
347
- ),
348
- currentChapter && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: currentChapter.title }),
349
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
350
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
351
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
352
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SkipBack, {}) }) }),
353
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Previous (N)" }) })
354
- ] }),
355
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
356
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onPlayPause, children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, {}) }) }),
357
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
358
- isPlaying ? "Pause" : "Play",
359
- " (Space)"
360
- ] }) })
361
- ] }),
362
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
363
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SkipForward, {}) }) }),
364
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Next (P)" }) })
365
- ] }),
366
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
367
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
368
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onMuteToggle, children: isMuted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VolumeX, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, {}) }) }),
369
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Mute (M)" }) })
370
- ] }),
371
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [isMuted ? 0 : volume], max: 1, step: 0.05, onValueChange: ([val]) => onVolumeChange(val), className: "w-24" })
372
- ] }),
373
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-sm", children: [
374
- formattedProgress,
375
- " / ",
376
- formattedDuration
377
- ] })
378
- ] }),
379
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
380
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
381
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: () => onFrameStep("backward"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Rewind, { size: 18 }) }) }),
382
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Frame Backward" }) })
383
- ] }),
384
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
385
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: () => onFrameStep("forward"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FastForward, { size: 18 }) }) }),
386
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Frame Forward" }) })
387
- ] }),
388
- /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
389
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", className: "font-mono w-16", children: [
390
- playbackRate,
391
- "x"
392
- ] }) }),
393
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-40", children: /* @__PURE__ */ jsxRuntime.jsx(RadioGroup, { value: String(playbackRate), onValueChange: (val) => onPlaybackRateChange(Number(val)), children: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4].map((rate) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
394
- /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: String(rate), id: `rate-${rate}` }),
395
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: `rate-${rate}`, children: [
396
- rate,
397
- "x"
398
- ] })
399
- ] }, rate)) }) })
400
- ] }),
401
- audioTracks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
402
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "icon", className: "relative", children: [
403
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLines, {}),
404
- tracksLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute top-0 right-0 h-2.5 w-2.5 animate-spin text-primary" })
405
- ] }) }),
406
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 overflow-y-auto overscroll-contain pr-1", children: /* @__PURE__ */ jsxRuntime.jsx(RadioGroup, { value: activeAudioTrack, onValueChange: onAudioTrackChange, children: audioTracks.map((track) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
407
- /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: track.id, id: `audio-${track.id}` }),
408
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `audio-${track.id}`, children: track.name })
409
- ] }, track.id)) }) }) })
410
- ] }),
411
- /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
412
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "icon", className: "relative", children: [
413
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Subtitles, {}),
414
- tracksLoading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute top-0 right-0 h-2.5 w-2.5 animate-spin text-primary" }) : activeSubtitle !== "-1" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-0 right-0 block h-2 w-2 rounded-full bg-primary ring-2 ring-background" })
415
- ] }) }),
416
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
417
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
418
- /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium", children: "Subtitles" }),
419
- /* @__PURE__ */ jsxRuntime.jsxs(
420
- Button,
423
+ const formattedProgress = React11.useMemo(() => formatTime2(progress), [progress]);
424
+ const formattedDuration = React11.useMemo(() => formatTime2(duration), [duration]);
425
+ const [chaptersMenuOpen, setChaptersMenuOpen] = React11.useState(false);
426
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
427
+ "div",
428
+ {
429
+ "aria-disabled": isDisabled || void 0,
430
+ className: cn(
431
+ "absolute bottom-0 left-0 right-0 px-4 pb-3 pt-10 flex flex-col gap-1.5",
432
+ "bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-opacity duration-300 ease-in-out",
433
+ isDisabled ? "opacity-60 pointer-events-none" : "opacity-0 group-hover:opacity-100"
434
+ ),
435
+ children: [
436
+ /* @__PURE__ */ jsxRuntime.jsx(
437
+ SeekBar,
438
+ {
439
+ progress,
440
+ duration,
441
+ isPlaying,
442
+ onSeek,
443
+ videoRef,
444
+ chapters,
445
+ abLoop,
446
+ onSeekHover,
447
+ seekPreviewThumbnail
448
+ }
449
+ ),
450
+ currentChapter && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground -mt-0.5", children: currentChapter.title }),
451
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
452
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
453
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
454
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onPrevious, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SkipBack, {}) }) }),
455
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Previous (N)" }) })
456
+ ] }),
457
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
458
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onPlayPause, children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, {}) }) }),
459
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
460
+ isPlaying ? "Pause" : "Play",
461
+ " (Space)"
462
+ ] }) })
463
+ ] }),
464
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onNext, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SkipForward, {}) }) }),
466
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Next (P)" }) })
467
+ ] }),
468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ml-1", children: [
469
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
470
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onMuteToggle, children: isMuted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VolumeX, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, {}) }) }),
471
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Mute (M)" }) })
472
+ ] }),
473
+ /* @__PURE__ */ jsxRuntime.jsx(
474
+ Slider,
421
475
  {
422
- variant: "outline",
423
- size: "sm",
424
- onClick: onSubtitleUpload,
425
- className: "h-7 px-2",
426
- children: [
427
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3 mr-1" }),
428
- "Add"
429
- ]
476
+ value: [isMuted ? 0 : volume],
477
+ max: 1,
478
+ step: 0.05,
479
+ onValueChange: ([val]) => onVolumeChange(val),
480
+ "aria-label": "Volume",
481
+ className: "w-24",
482
+ trackClassName: "h-[3px]",
483
+ thumbClassName: "h-3 w-3 border"
430
484
  }
431
485
  )
432
486
  ] }),
433
- subtitles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 overflow-y-auto overscroll-contain pr-1", children: /* @__PURE__ */ jsxRuntime.jsxs(RadioGroup, { value: activeSubtitle, onValueChange: onSubtitleChange, children: [
434
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
435
- /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: "-1", id: "sub-off" }),
436
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "sub-off", children: "Off" })
437
- ] }),
438
- subtitles.map((sub) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between space-x-2", children: [
439
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2 flex-1", children: [
440
- /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: sub.id, id: `sub-${sub.id}` }),
441
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `sub-${sub.id}`, className: "truncate", children: sub.name })
442
- ] }),
443
- sub.type === "external" && onSubtitleRemove && /* @__PURE__ */ jsxRuntime.jsx(
444
- Button,
445
- {
446
- variant: "ghost",
447
- size: "sm",
448
- onClick: () => onSubtitleRemove(sub.id),
449
- className: "h-6 w-6 p-0",
450
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
451
- }
452
- )
453
- ] }, sub.id))
454
- ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground text-center py-2", children: "No subtitles available" })
455
- ] }) })
456
- ] }),
457
- chapters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: chaptersMenuOpen, onOpenChange: setChaptersMenuOpen, children: [
458
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
459
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Chapters", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-4 w-4" }) }) }) }),
460
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Chapters" }) })
487
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-xs tabular-nums ml-2 text-white/90", children: [
488
+ formattedProgress,
489
+ " ",
490
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-white/50", children: [
491
+ "/ ",
492
+ formattedDuration
493
+ ] })
494
+ ] })
461
495
  ] }),
462
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-72 p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col max-h-64 overflow-y-auto", children: chapters.map((chapter) => /* @__PURE__ */ jsxRuntime.jsxs(
463
- "button",
464
- {
465
- className: cn(
466
- "flex items-center justify-between px-4 py-2 text-sm hover:bg-accent transition-colors text-left",
467
- currentChapter?.index === chapter.index && "bg-accent font-medium"
468
- ),
469
- onClick: () => {
470
- onGoToChapter?.(chapter.index);
471
- setChaptersMenuOpen(false);
472
- },
473
- children: [
474
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: chapter.title }),
475
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-4 font-mono text-xs text-muted-foreground shrink-0", children: formatTime(chapter.startTime) })
476
- ]
477
- },
478
- chapter.index
479
- )) }) })
480
- ] }),
481
- /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
482
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings2, {}) }) }),
483
- /* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { className: "w-64 space-y-4", children: [
484
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
485
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
486
- "Brightness: ",
487
- filters.brightness,
488
- "%"
496
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
497
+ audioTracks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
498
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
499
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "icon", className: "relative", "aria-label": "Audio track", children: [
500
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AudioLines, {}),
501
+ tracksLoading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute top-0 right-0 h-2.5 w-2.5 animate-spin text-primary" })
502
+ ] }) }) }),
503
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Audio track" }) })
489
504
  ] }),
490
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.brightness], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, brightness: val }) })
505
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-56", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 overflow-y-auto overscroll-contain pr-1", children: /* @__PURE__ */ jsxRuntime.jsx(RadioGroup, { value: activeAudioTrack, onValueChange: onAudioTrackChange, children: audioTracks.map((track) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
506
+ /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: track.id, id: `audio-${track.id}` }),
507
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `audio-${track.id}`, children: track.name })
508
+ ] }, track.id)) }) }) })
491
509
  ] }),
492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
493
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
494
- "Contrast: ",
495
- filters.contrast,
496
- "%"
510
+ /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
511
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "icon", className: "relative", "aria-label": "Subtitles", children: [
513
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Subtitles, {}),
514
+ tracksLoading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "absolute top-0 right-0 h-2.5 w-2.5 animate-spin text-primary" }) : activeSubtitle !== "-1" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-0 right-0 block h-2 w-2 rounded-full bg-primary ring-2 ring-background" })
515
+ ] }) }) }),
516
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Subtitles" }) })
497
517
  ] }),
498
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.contrast], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, contrast: val }) })
518
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
520
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium", children: "Subtitles" }),
521
+ onSubtitleUpload && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "outline", size: "sm", onClick: onSubtitleUpload, className: "h-7 px-2", children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3 mr-1" }),
523
+ "Add"
524
+ ] })
525
+ ] }),
526
+ subtitles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 overflow-y-auto overscroll-contain pr-1", children: /* @__PURE__ */ jsxRuntime.jsxs(RadioGroup, { value: activeSubtitle, onValueChange: onSubtitleChange, children: [
527
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
528
+ /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: "-1", id: "sub-off" }),
529
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "sub-off", children: "Off" })
530
+ ] }),
531
+ subtitles.map((sub) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between space-x-2", children: [
532
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2 flex-1", children: [
533
+ /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: sub.id, id: `sub-${sub.id}` }),
534
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `sub-${sub.id}`, className: "truncate", children: sub.name })
535
+ ] }),
536
+ sub.type === "external" && onSubtitleRemove && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", onClick: () => onSubtitleRemove(sub.id), className: "h-6 w-6 p-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" }) })
537
+ ] }, sub.id))
538
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground text-center py-2", children: "No subtitles available" })
539
+ ] }) })
499
540
  ] }),
500
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
501
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
502
- "Saturation: ",
503
- filters.saturate,
504
- "%"
541
+ /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
542
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", className: "font-mono w-12 h-9 px-2 text-xs", children: [
544
+ playbackRate,
545
+ "x"
546
+ ] }) }) }),
547
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Playback speed" }) })
505
548
  ] }),
506
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.saturate], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, saturate: val }) })
549
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-40", children: /* @__PURE__ */ jsxRuntime.jsx(RadioGroup, { value: String(playbackRate), onValueChange: (val) => onPlaybackRateChange(Number(val)), children: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4].map((rate) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
550
+ /* @__PURE__ */ jsxRuntime.jsx(RadioGroupItem, { value: String(rate), id: `rate-${rate}` }),
551
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: `rate-${rate}`, children: [
552
+ rate,
553
+ "x"
554
+ ] })
555
+ ] }, rate)) }) })
507
556
  ] }),
508
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
509
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
510
- "Hue: ",
511
- filters.hue,
512
- "\xB0"
557
+ /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
558
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Settings", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings2, {}) }) }) }),
560
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Settings" }) })
513
561
  ] }),
514
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.hue], max: 360, onValueChange: ([val]) => onFiltersChange({ ...filters, hue: val }) })
562
+ /* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { className: "w-72 p-2 space-y-1", align: "end", children: [
563
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
564
+ pipSupported && /* @__PURE__ */ jsxRuntime.jsxs(
565
+ "button",
566
+ {
567
+ onClick: onTogglePiP,
568
+ "aria-label": isPiP ? "Exit picture-in-picture" : "Enter picture-in-picture",
569
+ className: cn(
570
+ "flex items-center gap-2 w-full px-2 py-1.5 rounded text-sm hover:bg-accent text-left",
571
+ isPiP && "text-primary"
572
+ ),
573
+ children: [
574
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PictureInPicture2, { className: "h-4 w-4" }),
575
+ "Picture-in-Picture"
576
+ ]
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: onScreenshot, className: "flex items-center gap-2 w-full px-2 py-1.5 rounded text-sm hover:bg-accent text-left", children: [
580
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, { className: "h-4 w-4" }),
581
+ "Screenshot"
582
+ ] }),
583
+ onShowInfo && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: onShowInfo, className: "flex items-center gap-2 w-full px-2 py-1.5 rounded text-sm hover:bg-accent text-left", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-4 w-4" }),
585
+ "Video information"
586
+ ] }),
587
+ onOpenShortcuts && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: onOpenShortcuts, className: "flex items-center gap-2 w-full px-2 py-1.5 rounded text-sm hover:bg-accent text-left", children: [
588
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Keyboard, { className: "h-4 w-4" }),
589
+ "Keyboard shortcuts"
590
+ ] })
591
+ ] }),
592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border/40 pt-2 space-y-2 px-1", children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-[10px] uppercase tracking-wider text-muted-foreground", children: "Display" }),
594
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
595
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-xs text-muted-foreground", children: [
596
+ "Brightness: ",
597
+ filters.brightness,
598
+ "%"
599
+ ] }),
600
+ /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.brightness], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, brightness: val }), trackClassName: "h-[3px]", thumbClassName: "h-3 w-3 border" })
601
+ ] }),
602
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
603
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-xs text-muted-foreground", children: [
604
+ "Contrast: ",
605
+ filters.contrast,
606
+ "%"
607
+ ] }),
608
+ /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.contrast], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, contrast: val }), trackClassName: "h-[3px]", thumbClassName: "h-3 w-3 border" })
609
+ ] }),
610
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
611
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-xs text-muted-foreground", children: [
612
+ "Saturation: ",
613
+ filters.saturate,
614
+ "%"
615
+ ] }),
616
+ /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.saturate], max: 200, onValueChange: ([val]) => onFiltersChange({ ...filters, saturate: val }), trackClassName: "h-[3px]", thumbClassName: "h-3 w-3 border" })
617
+ ] }),
618
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
619
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-xs text-muted-foreground", children: [
620
+ "Hue: ",
621
+ filters.hue,
622
+ "\xB0"
623
+ ] }),
624
+ /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [filters.hue], max: 360, onValueChange: ([val]) => onFiltersChange({ ...filters, hue: val }), trackClassName: "h-[3px]", thumbClassName: "h-3 w-3 border" })
625
+ ] }),
626
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
627
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-xs text-muted-foreground", children: [
628
+ "Zoom: ",
629
+ Math.round(zoom * 100),
630
+ "%"
631
+ ] }),
632
+ /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [zoom], min: 1, max: 3, step: 0.1, onValueChange: ([val]) => onZoomChange(val), trackClassName: "h-[3px]", thumbClassName: "h-3 w-3 border" })
633
+ ] })
634
+ ] })
635
+ ] })
515
636
  ] }),
516
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
517
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
518
- "Zoom: ",
519
- Math.round(zoom * 100),
520
- "%"
637
+ chapters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: chaptersMenuOpen, onOpenChange: setChaptersMenuOpen, children: [
638
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
639
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Chapters", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-4 w-4" }) }) }) }),
640
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Chapters" }) })
521
641
  ] }),
522
- /* @__PURE__ */ jsxRuntime.jsx(Slider, { value: [zoom], min: 1, max: 3, step: 0.1, onValueChange: ([val]) => onZoomChange(val) })
642
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-72 p-0", align: "end", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col max-h-64 overflow-y-auto", children: chapters.map((chapter) => /* @__PURE__ */ jsxRuntime.jsxs(
643
+ "button",
644
+ {
645
+ className: cn(
646
+ "flex items-center justify-between px-4 py-2 text-sm hover:bg-accent transition-colors text-left",
647
+ currentChapter?.index === chapter.index && "bg-accent font-medium"
648
+ ),
649
+ onClick: () => {
650
+ onGoToChapter?.(chapter.index);
651
+ setChaptersMenuOpen(false);
652
+ },
653
+ children: [
654
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: chapter.title }),
655
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-4 font-mono text-xs text-muted-foreground shrink-0", children: formatTime2(chapter.startTime) })
656
+ ]
657
+ },
658
+ chapter.index
659
+ )) }) })
660
+ ] }),
661
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
662
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onFullScreenToggle, children: isFullScreen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, {}) }) }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Fullscreen (F)" }) })
523
664
  ] })
524
665
  ] })
525
- ] }),
526
- onShowInfo && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
527
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onShowInfo, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-4 w-4" }) }) }),
528
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Video Information" }) })
529
- ] }),
530
- onOpenShortcuts && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
531
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onOpenShortcuts, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Keyboard, { className: "h-4 w-4" }) }) }),
532
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Keyboard Shortcuts" }) })
533
- ] }),
534
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
535
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onScreenshot, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, {}) }) }),
536
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Screenshot" }) })
537
- ] }),
538
- onABLoopCycle && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
539
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
540
- Button,
541
- {
542
- variant: "ghost",
543
- size: "icon",
544
- onClick: onABLoopCycle,
545
- "aria-label": "A-B loop",
546
- "data-testid": "ab-loop-button",
547
- "data-active": abLoop.isLooping,
548
- className: cn("font-mono text-xs font-bold", abLoop.isLooping && "text-primary"),
549
- children: [
550
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(abLoop.pointA !== null && "text-primary"), children: "A" }),
551
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-50", children: "-" }),
552
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(abLoop.pointB !== null && "text-primary"), children: "B" })
553
- ]
554
- }
555
- ) }),
556
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: abLoop.pointA === null ? "Set loop start (A)" : abLoop.pointB === null ? "Set loop end (B)" : "Clear A-B loop" }) })
557
- ] }),
558
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
559
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onLoopToggle, "data-active": loop, className: "data-[active=true]:text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, {}) }) }),
560
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loop" }) })
561
- ] }),
562
- pipSupported && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
563
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
564
- Button,
565
- {
566
- variant: "ghost",
567
- size: "icon",
568
- onClick: onTogglePiP,
569
- "aria-label": isPiP ? "Exit picture-in-picture" : "Enter picture-in-picture",
570
- className: isPiP ? "text-primary" : "",
571
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PictureInPicture2, {})
572
- }
573
- ) }),
574
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: isPiP ? "Exit picture-in-picture" : "Enter picture-in-picture" }) })
575
- ] }),
576
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
577
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "icon", onClick: onFullScreenToggle, children: isFullScreen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, {}) }) }),
578
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Fullscreen (F)" }) })
579
666
  ] })
580
- ] })
581
- ] })
582
- ] }) });
667
+ ]
668
+ }
669
+ ) });
583
670
  });
584
671
  var player_controls_default = PlayerControls;
585
- var ScrollArea = React10__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
672
+ var ScrollArea = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
586
673
  ScrollAreaPrimitive__namespace.Root,
587
674
  {
588
675
  ref,
@@ -596,23 +683,23 @@ var ScrollArea = React10__namespace.forwardRef(({ className, children, ...props
596
683
  }
597
684
  ));
598
685
  ScrollArea.displayName = ScrollAreaPrimitive__namespace.Root.displayName;
599
- var ScrollBar = React10__namespace.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
686
+ var ScrollBar = React11__namespace.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
600
687
  ScrollAreaPrimitive__namespace.ScrollAreaScrollbar,
601
688
  {
602
689
  ref,
603
690
  orientation,
604
691
  className: cn(
605
692
  "flex touch-none select-none transition-colors",
606
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
607
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
693
+ orientation === "vertical" && "h-full w-2 border-l border-l-transparent p-[1px]",
694
+ orientation === "horizontal" && "h-2 flex-col border-t border-t-transparent p-[1px]",
608
695
  className
609
696
  ),
610
697
  ...props,
611
- children: /* @__PURE__ */ jsxRuntime.jsx(ScrollAreaPrimitive__namespace.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
698
+ children: /* @__PURE__ */ jsxRuntime.jsx(ScrollAreaPrimitive__namespace.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-muted-foreground/30 hover:bg-muted-foreground/55 active:bg-muted-foreground/75 transition-colors" })
612
699
  }
613
700
  ));
614
701
  ScrollBar.displayName = ScrollAreaPrimitive__namespace.ScrollAreaScrollbar.displayName;
615
- var Input = React10__namespace.forwardRef(
702
+ var Input = React11__namespace.forwardRef(
616
703
  ({ className, type, ...props }, ref) => {
617
704
  return /* @__PURE__ */ jsxRuntime.jsx(
618
705
  "input",
@@ -631,7 +718,7 @@ var Input = React10__namespace.forwardRef(
631
718
  Input.displayName = "Input";
632
719
  var Select = SelectPrimitive__namespace.Root;
633
720
  var SelectValue = SelectPrimitive__namespace.Value;
634
- var SelectTrigger = React10__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
721
+ var SelectTrigger = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
635
722
  SelectPrimitive__namespace.Trigger,
636
723
  {
637
724
  ref,
@@ -647,7 +734,7 @@ var SelectTrigger = React10__namespace.forwardRef(({ className, children, ...pro
647
734
  }
648
735
  ));
649
736
  SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
650
- var SelectScrollUpButton = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
737
+ var SelectScrollUpButton = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
651
738
  SelectPrimitive__namespace.ScrollUpButton,
652
739
  {
653
740
  ref,
@@ -660,7 +747,7 @@ var SelectScrollUpButton = React10__namespace.forwardRef(({ className, ...props
660
747
  }
661
748
  ));
662
749
  SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
663
- var SelectScrollDownButton = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
750
+ var SelectScrollDownButton = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
664
751
  SelectPrimitive__namespace.ScrollDownButton,
665
752
  {
666
753
  ref,
@@ -673,7 +760,7 @@ var SelectScrollDownButton = React10__namespace.forwardRef(({ className, ...prop
673
760
  }
674
761
  ));
675
762
  SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
676
- var SelectContent = React10__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
763
+ var SelectContent = React11__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
677
764
  SelectPrimitive__namespace.Content,
678
765
  {
679
766
  ref,
@@ -701,7 +788,7 @@ var SelectContent = React10__namespace.forwardRef(({ className, children, positi
701
788
  }
702
789
  ) }));
703
790
  SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
704
- var SelectLabel = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
791
+ var SelectLabel = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
705
792
  SelectPrimitive__namespace.Label,
706
793
  {
707
794
  ref,
@@ -710,7 +797,7 @@ var SelectLabel = React10__namespace.forwardRef(({ className, ...props }, ref) =
710
797
  }
711
798
  ));
712
799
  SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
713
- var SelectItem = React10__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
800
+ var SelectItem = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
714
801
  SelectPrimitive__namespace.Item,
715
802
  {
716
803
  ref,
@@ -726,7 +813,7 @@ var SelectItem = React10__namespace.forwardRef(({ className, children, ...props
726
813
  }
727
814
  ));
728
815
  SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
729
- var SelectSeparator = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
816
+ var SelectSeparator = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
730
817
  SelectPrimitive__namespace.Separator,
731
818
  {
732
819
  ref,
@@ -745,7 +832,7 @@ var NEXT_SIZE = {
745
832
  md: "lg",
746
833
  lg: "sm"
747
834
  };
748
- function formatTime2(seconds) {
835
+ function formatTime3(seconds) {
749
836
  if (!seconds || !isFinite(seconds)) return "";
750
837
  const h = Math.floor(seconds / 3600);
751
838
  const m = Math.floor(seconds % 3600 / 60);
@@ -774,7 +861,7 @@ function SortablePlaylistItem({ item, index, isActive, downloadReady, onSelect,
774
861
  transition,
775
862
  opacity: isDragging ? 0.5 : 1
776
863
  };
777
- const duration = formatTime2(item.duration ?? 0);
864
+ const duration = formatTime3(item.duration ?? 0);
778
865
  return /* @__PURE__ */ jsxRuntime.jsxs(
779
866
  "div",
780
867
  {
@@ -858,14 +945,14 @@ var PlaylistPanel = ({
858
945
  onTogglePin,
859
946
  onSizeChange
860
947
  }) => {
861
- const fileInputRef = React10.useRef(null);
862
- const folderInputRef = React10.useRef(null);
863
- const m3uInputRef = React10.useRef(null);
864
- const [streamUrl, setStreamUrl] = React10.useState("");
865
- const [magnetUri, setMagnetUri] = React10.useState("");
866
- const [showMagnetInput, setShowMagnetInput] = React10.useState(false);
867
- const [magnetError, setMagnetError] = React10.useState(null);
868
- const [sortKey, setSortKey] = React10.useState("");
948
+ const fileInputRef = React11.useRef(null);
949
+ const folderInputRef = React11.useRef(null);
950
+ const m3uInputRef = React11.useRef(null);
951
+ const [streamUrl, setStreamUrl] = React11.useState("");
952
+ const [magnetUri, setMagnetUri] = React11.useState("");
953
+ const [showMagnetInput, setShowMagnetInput] = React11.useState(false);
954
+ const [magnetError, setMagnetError] = React11.useState(null);
955
+ const [sortKey, setSortKey] = React11.useState("");
869
956
  const handleStreamUrlSubmit = (e) => {
870
957
  e.preventDefault();
871
958
  if (streamUrl) {
@@ -928,296 +1015,306 @@ var PlaylistPanel = ({
928
1015
  });
929
1016
  onReorder(sorted);
930
1017
  };
931
- return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: !isOpen ? (
932
- /* ── Collapsed drawer strip ── */
933
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center w-11 h-full bg-card border-l border-border shrink-0", children: [
934
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
935
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
936
- Button,
937
- {
938
- variant: "ghost",
939
- size: "icon",
940
- className: "h-9 w-9 mt-2 shrink-0",
941
- onClick: onToggle,
942
- "aria-label": "Expand Playlist",
943
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
944
- }
945
- ) }),
946
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "left", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Expand Playlist" }) })
947
- ] }),
948
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
949
- "span",
950
- {
951
- className: "text-[10px] font-semibold text-muted-foreground tracking-widest uppercase select-none",
952
- style: { writingMode: "vertical-rl", transform: "rotate(180deg)" },
953
- children: "Playlist"
954
- }
955
- ) }),
956
- playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-3 text-[10px] font-bold text-primary bg-primary/10 rounded-full w-6 h-6 flex items-center justify-center shrink-0", children: playlist.length > 99 ? "99+" : playlist.length })
957
- ] })
958
- ) : (
959
- /* ── Full panel ── */
960
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("h-full flex flex-col bg-card border-l border-border shrink-0 transition-[width] duration-200", SIZE_WIDTHS[size]), children: [
961
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b border-border shrink-0", children: [
962
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
963
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListVideo, { className: "h-4 w-4 text-primary shrink-0" }),
964
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm truncate", children: "Playlist" }),
965
- playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold text-muted-foreground bg-muted rounded-full px-1.5 py-0.5 shrink-0 leading-none", children: playlist.length })
966
- ] }),
967
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 shrink-0", children: [
968
- playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
969
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
970
- Button,
971
- {
972
- variant: "ghost",
973
- size: "icon",
974
- className: "h-7 w-7",
975
- onClick: () => core.exportPlaylist(playlist),
976
- "aria-label": "Export Playlist",
977
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3.5 w-3.5" })
978
- }
979
- ) }),
980
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Export as M3U8" }) })
981
- ] }),
982
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
983
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
984
- Button,
985
- {
986
- variant: "ghost",
987
- size: "icon",
988
- className: "h-7 w-7",
989
- onClick: () => m3uInputRef.current?.click(),
990
- "aria-label": "Import Playlist",
991
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "h-3.5 w-3.5" })
992
- }
993
- ) }),
994
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Import M3U/M3U8" }) })
995
- ] }),
996
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
997
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
998
- Button,
999
- {
1000
- variant: "ghost",
1001
- size: "icon",
1002
- className: cn("h-7 w-7", isPinned && "text-primary bg-primary/10"),
1003
- onClick: onTogglePin,
1004
- "aria-label": isPinned ? "Unpin Playlist" : "Pin Playlist",
1005
- children: isPinned ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pin, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PinOff, { className: "h-3.5 w-3.5" })
1006
- }
1007
- ) }),
1008
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: isPinned ? "Unpin (allow auto-hide on play)" : "Pin (keep open while playing)" }) })
1009
- ] }),
1010
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1011
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1012
- Button,
1013
- {
1014
- variant: "ghost",
1015
- size: "icon",
1016
- className: "h-7 w-7",
1017
- onClick: () => onSizeChange(NEXT_SIZE[size]),
1018
- "aria-label": "Resize Playlist",
1019
- children: size === "lg" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" })
1020
- }
1021
- ) }),
1022
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: size === "lg" ? "Make smaller" : "Make larger" }) })
1023
- ] }),
1018
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
1019
+ "div",
1020
+ {
1021
+ className: cn(
1022
+ "h-full flex flex-col bg-card border-l border-border shrink-0 overflow-hidden",
1023
+ "transition-[width] duration-300 ease-in-out motion-reduce:transition-none",
1024
+ isOpen ? SIZE_WIDTHS[size] : "w-11"
1025
+ ),
1026
+ children: !isOpen ? (
1027
+ /* ── Collapsed drawer strip ── */
1028
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center w-11 h-full shrink-0", children: [
1024
1029
  /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1025
1030
  /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1026
1031
  Button,
1027
1032
  {
1028
1033
  variant: "ghost",
1029
1034
  size: "icon",
1030
- className: "h-7 w-7",
1035
+ className: "h-9 w-9 mt-2 shrink-0",
1031
1036
  onClick: onToggle,
1032
- "aria-label": "Collapse Playlist",
1033
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
1037
+ "aria-label": "Expand Playlist",
1038
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
1034
1039
  }
1035
1040
  ) }),
1036
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Collapse" }) })
1037
- ] })
1038
- ] })
1039
- ] }),
1040
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-2 border-b border-border shrink-0", children: [
1041
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
1042
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { onClick: () => fileInputRef.current?.click(), className: "flex-1 h-8 text-xs", children: [
1043
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FilePlus, { className: "mr-1.5 h-3.5 w-3.5" }),
1044
- " Add Files"
1041
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "left", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Expand Playlist" }) })
1045
1042
  ] }),
1046
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1047
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1048
- Button,
1049
- {
1050
- variant: "outline",
1051
- size: "icon",
1052
- className: "h-8 w-8 shrink-0",
1053
- onClick: () => folderInputRef.current?.click(),
1054
- "aria-label": "Open Folder",
1055
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: "h-3.5 w-3.5" })
1056
- }
1057
- ) }),
1058
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Open Folder" }) })
1059
- ] })
1060
- ] }),
1061
- /* @__PURE__ */ jsxRuntime.jsx(
1062
- "input",
1063
- {
1064
- type: "file",
1065
- ref: fileInputRef,
1066
- className: "hidden",
1067
- multiple: true,
1068
- accept: "video/*,.mkv,.avi,.mov,.wmv,.flv,.webm,.vtt,.srt",
1069
- onChange: (e) => e.target.files && onFilesAdded(e.target.files)
1070
- }
1071
- ),
1072
- /* @__PURE__ */ jsxRuntime.jsx(
1073
- "input",
1074
- {
1075
- type: "file",
1076
- ref: folderInputRef,
1077
- className: "hidden",
1078
- multiple: true,
1079
- accept: "video/*",
1080
- webkitdirectory: "",
1081
- onChange: handleFolderSelect
1082
- }
1083
- ),
1084
- /* @__PURE__ */ jsxRuntime.jsx(
1085
- "input",
1086
- {
1087
- type: "file",
1088
- ref: m3uInputRef,
1089
- className: "hidden",
1090
- accept: ".m3u,.m3u8",
1091
- onChange: handleM3USelect
1092
- }
1093
- ),
1094
- /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleStreamUrlSubmit, className: "flex gap-1.5", children: [
1095
- /* @__PURE__ */ jsxRuntime.jsx(
1096
- Input,
1043
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1044
+ "span",
1097
1045
  {
1098
- type: "url",
1099
- placeholder: "Enter stream URL",
1100
- value: streamUrl,
1101
- onChange: (e) => setStreamUrl(e.target.value),
1102
- className: "h-8 text-xs"
1046
+ className: "text-[10px] font-semibold text-muted-foreground tracking-widest uppercase select-none",
1047
+ style: { writingMode: "vertical-rl", transform: "rotate(180deg)" },
1048
+ children: "Playlist"
1103
1049
  }
1104
- ),
1105
- /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "h-3.5 w-3.5" }) }),
1106
- showMagnet && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1107
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1108
- Button,
1050
+ ) }),
1051
+ playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-3 text-[10px] font-bold text-primary bg-primary/10 rounded-full w-6 h-6 flex items-center justify-center shrink-0", children: playlist.length > 99 ? "99+" : playlist.length })
1052
+ ] })
1053
+ ) : (
1054
+ /* ── Full panel ── */
1055
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("h-full flex flex-col shrink-0 transition-[width] duration-200", SIZE_WIDTHS[size]), children: [
1056
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b border-border shrink-0", children: [
1057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListVideo, { className: "h-4 w-4 text-primary shrink-0" }),
1059
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm truncate", children: "Playlist" }),
1060
+ playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold text-muted-foreground bg-muted rounded-full px-1.5 py-0.5 shrink-0 leading-none", children: playlist.length })
1061
+ ] }),
1062
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 shrink-0", children: [
1063
+ playlist.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1064
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1065
+ Button,
1066
+ {
1067
+ variant: "ghost",
1068
+ size: "icon",
1069
+ className: "h-7 w-7",
1070
+ onClick: () => core.exportPlaylist(playlist),
1071
+ "aria-label": "Export Playlist",
1072
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3.5 w-3.5" })
1073
+ }
1074
+ ) }),
1075
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Export as M3U8" }) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1079
+ Button,
1080
+ {
1081
+ variant: "ghost",
1082
+ size: "icon",
1083
+ className: "h-7 w-7",
1084
+ onClick: () => m3uInputRef.current?.click(),
1085
+ "aria-label": "Import Playlist",
1086
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "h-3.5 w-3.5" })
1087
+ }
1088
+ ) }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Import M3U/M3U8" }) })
1090
+ ] }),
1091
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1092
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1093
+ Button,
1094
+ {
1095
+ variant: "ghost",
1096
+ size: "icon",
1097
+ className: cn("h-7 w-7", isPinned && "text-primary bg-primary/10"),
1098
+ onClick: onTogglePin,
1099
+ "aria-label": isPinned ? "Unpin Playlist" : "Pin Playlist",
1100
+ children: isPinned ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pin, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PinOff, { className: "h-3.5 w-3.5" })
1101
+ }
1102
+ ) }),
1103
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: isPinned ? "Unpin (allow auto-hide on play)" : "Pin (keep open while playing)" }) })
1104
+ ] }),
1105
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1106
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1107
+ Button,
1108
+ {
1109
+ variant: "ghost",
1110
+ size: "icon",
1111
+ className: "h-7 w-7",
1112
+ onClick: () => onSizeChange(NEXT_SIZE[size]),
1113
+ "aria-label": "Resize Playlist",
1114
+ children: size === "lg" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" })
1115
+ }
1116
+ ) }),
1117
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: size === "lg" ? "Make smaller" : "Make larger" }) })
1118
+ ] }),
1119
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1120
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1121
+ Button,
1122
+ {
1123
+ variant: "ghost",
1124
+ size: "icon",
1125
+ className: "h-7 w-7",
1126
+ onClick: onToggle,
1127
+ "aria-label": "Collapse Playlist",
1128
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
1129
+ }
1130
+ ) }),
1131
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Collapse" }) })
1132
+ ] })
1133
+ ] })
1134
+ ] }),
1135
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-2 border-b border-border shrink-0", children: [
1136
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
1137
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { onClick: () => fileInputRef.current?.click(), className: "flex-1 h-8 text-xs", children: [
1138
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FilePlus, { className: "mr-1.5 h-3.5 w-3.5" }),
1139
+ " Add Files"
1140
+ ] }),
1141
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1142
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1143
+ Button,
1144
+ {
1145
+ variant: "outline",
1146
+ size: "icon",
1147
+ className: "h-8 w-8 shrink-0",
1148
+ onClick: () => folderInputRef.current?.click(),
1149
+ "aria-label": "Open Folder",
1150
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: "h-3.5 w-3.5" })
1151
+ }
1152
+ ) }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Open Folder" }) })
1154
+ ] })
1155
+ ] }),
1156
+ /* @__PURE__ */ jsxRuntime.jsx(
1157
+ "input",
1109
1158
  {
1110
- type: "button",
1111
- size: "icon",
1112
- variant: showMagnetInput ? "default" : "outline",
1113
- className: "h-8 w-8 shrink-0",
1114
- onClick: () => {
1115
- setShowMagnetInput((v) => !v);
1116
- setMagnetError(null);
1117
- },
1118
- "aria-label": "Add magnet link",
1119
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { className: "h-3.5 w-3.5" })
1159
+ type: "file",
1160
+ ref: fileInputRef,
1161
+ className: "hidden",
1162
+ multiple: true,
1163
+ accept: "video/*,.mkv,.avi,.mov,.wmv,.flv,.webm,.vtt,.srt",
1164
+ onChange: (e) => e.target.files && onFilesAdded(e.target.files)
1120
1165
  }
1121
- ) }),
1122
- /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add Magnet Link" }) })
1123
- ] })
1124
- ] }),
1125
- showMagnet && showMagnetInput && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleMagnetSubmit, className: "space-y-1.5", children: [
1126
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
1166
+ ),
1127
1167
  /* @__PURE__ */ jsxRuntime.jsx(
1128
- Input,
1168
+ "input",
1129
1169
  {
1130
- placeholder: "magnet:?xt=urn:btih:\u2026",
1131
- value: magnetUri,
1132
- onChange: (e) => {
1133
- setMagnetUri(e.target.value);
1134
- setMagnetError(null);
1135
- },
1136
- className: "h-8 text-xs font-mono",
1137
- disabled: torrentStatus.status === "loading-metadata"
1170
+ type: "file",
1171
+ ref: folderInputRef,
1172
+ className: "hidden",
1173
+ multiple: true,
1174
+ accept: "video/*",
1175
+ webkitdirectory: "",
1176
+ onChange: handleFolderSelect
1138
1177
  }
1139
1178
  ),
1140
1179
  /* @__PURE__ */ jsxRuntime.jsx(
1141
- Button,
1180
+ "input",
1142
1181
  {
1143
- type: "submit",
1144
- size: "icon",
1145
- variant: "default",
1146
- className: "h-8 w-8 shrink-0",
1147
- disabled: !magnetUri.trim() || torrentStatus.status === "loading-metadata",
1148
- "aria-label": "Load magnet link",
1149
- children: torrentStatus.status === "loading-metadata" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-3.5 w-3.5" })
1182
+ type: "file",
1183
+ ref: m3uInputRef,
1184
+ className: "hidden",
1185
+ accept: ".m3u,.m3u8",
1186
+ onChange: handleM3USelect
1150
1187
  }
1151
- )
1152
- ] }),
1153
- torrentStatus.status === "loading-metadata" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-muted-foreground flex items-center gap-1", children: [
1154
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 animate-spin" }),
1155
- "Fetching torrent info\u2026"
1188
+ ),
1189
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleStreamUrlSubmit, className: "flex gap-1.5", children: [
1190
+ /* @__PURE__ */ jsxRuntime.jsx(
1191
+ Input,
1192
+ {
1193
+ type: "url",
1194
+ placeholder: "Enter stream URL",
1195
+ value: streamUrl,
1196
+ onChange: (e) => setStreamUrl(e.target.value),
1197
+ className: "h-8 text-xs"
1198
+ }
1199
+ ),
1200
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "h-3.5 w-3.5" }) }),
1201
+ showMagnet && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1202
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1203
+ Button,
1204
+ {
1205
+ type: "button",
1206
+ size: "icon",
1207
+ variant: showMagnetInput ? "default" : "outline",
1208
+ className: "h-8 w-8 shrink-0",
1209
+ onClick: () => {
1210
+ setShowMagnetInput((v) => !v);
1211
+ setMagnetError(null);
1212
+ },
1213
+ "aria-label": "Add magnet link",
1214
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { className: "h-3.5 w-3.5" })
1215
+ }
1216
+ ) }),
1217
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add Magnet Link" }) })
1218
+ ] })
1219
+ ] }),
1220
+ showMagnet && showMagnetInput && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleMagnetSubmit, className: "space-y-1.5", children: [
1221
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
1222
+ /* @__PURE__ */ jsxRuntime.jsx(
1223
+ Input,
1224
+ {
1225
+ placeholder: "magnet:?xt=urn:btih:\u2026",
1226
+ value: magnetUri,
1227
+ onChange: (e) => {
1228
+ setMagnetUri(e.target.value);
1229
+ setMagnetError(null);
1230
+ },
1231
+ className: "h-8 text-xs font-mono",
1232
+ disabled: torrentStatus.status === "loading-metadata"
1233
+ }
1234
+ ),
1235
+ /* @__PURE__ */ jsxRuntime.jsx(
1236
+ Button,
1237
+ {
1238
+ type: "submit",
1239
+ size: "icon",
1240
+ variant: "default",
1241
+ className: "h-8 w-8 shrink-0",
1242
+ disabled: !magnetUri.trim() || torrentStatus.status === "loading-metadata",
1243
+ "aria-label": "Load magnet link",
1244
+ children: torrentStatus.status === "loading-metadata" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-3.5 w-3.5" })
1245
+ }
1246
+ )
1247
+ ] }),
1248
+ torrentStatus.status === "loading-metadata" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-muted-foreground flex items-center gap-1", children: [
1249
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 animate-spin" }),
1250
+ "Fetching torrent info\u2026"
1251
+ ] }),
1252
+ magnetError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-destructive flex items-center gap-1", children: [
1253
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3 w-3 shrink-0" }),
1254
+ magnetError
1255
+ ] })
1256
+ ] }),
1257
+ showMagnet && torrentStatus.status === "ready" && (torrentStatus.progress >= 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-emerald-500 flex items-center gap-1", children: [
1258
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3 w-3 shrink-0" }),
1259
+ "Download ready \u2014 hover a file to save it"
1260
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
1261
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-muted rounded-full h-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1262
+ "div",
1263
+ {
1264
+ className: "bg-emerald-500 h-1 rounded-full transition-all duration-500",
1265
+ style: { width: `${Math.round(torrentStatus.progress * 100)}%` }
1266
+ }
1267
+ ) }),
1268
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground flex items-center justify-between", children: [
1269
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1270
+ "\u2193 ",
1271
+ formatBytes(torrentStatus.downloadSpeed),
1272
+ "/s \xB7 ",
1273
+ torrentStatus.numPeers,
1274
+ " peer",
1275
+ torrentStatus.numPeers !== 1 ? "s" : ""
1276
+ ] }),
1277
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1278
+ Math.round(torrentStatus.progress * 100),
1279
+ "%"
1280
+ ] })
1281
+ ] })
1282
+ ] })),
1283
+ playlist.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(Select, { value: sortKey, onValueChange: handleSort, children: [
1284
+ /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "h-7 text-xs", "aria-label": "Sort playlist", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Sort by\u2026" }) }),
1285
+ /* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
1286
+ /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "name-asc", children: "Name A\u2013Z" }),
1287
+ /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "name-desc", children: "Name Z\u2013A" }),
1288
+ /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "duration-asc", children: "Shortest first" }),
1289
+ /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "duration-desc", children: "Longest first" })
1290
+ ] })
1291
+ ] })
1156
1292
  ] }),
1157
- magnetError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-destructive flex items-center gap-1", children: [
1158
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3 w-3 shrink-0" }),
1159
- magnetError
1160
- ] })
1161
- ] }),
1162
- showMagnet && torrentStatus.status === "ready" && (torrentStatus.progress >= 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-emerald-500 flex items-center gap-1", children: [
1163
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3 w-3 shrink-0" }),
1164
- "Download ready \u2014 hover a file to save it"
1165
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
1166
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-muted rounded-full h-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1167
- "div",
1293
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 space-y-0.5", children: playlist.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-xs text-muted-foreground py-10 px-2", children: [
1294
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your playlist is empty." }),
1295
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add files or a stream URL to get started." })
1296
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(core$1.DndContext, { collisionDetection: core$1.closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(
1297
+ sortable.SortableContext,
1168
1298
  {
1169
- className: "bg-emerald-500 h-1 rounded-full transition-all duration-500",
1170
- style: { width: `${Math.round(torrentStatus.progress * 100)}%` }
1299
+ items: playlist.map((i) => i.id),
1300
+ strategy: sortable.verticalListSortingStrategy,
1301
+ children: playlist.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
1302
+ SortablePlaylistItem,
1303
+ {
1304
+ item,
1305
+ index,
1306
+ isActive: index === currentVideoIndex,
1307
+ downloadReady: item.source === "torrent" && torrentStatus.progress >= 1,
1308
+ onSelect: onSelectVideo,
1309
+ onRemove: onRemoveItem
1310
+ },
1311
+ item.id
1312
+ ))
1171
1313
  }
1172
- ) }),
1173
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground flex items-center justify-between", children: [
1174
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1175
- "\u2193 ",
1176
- formatBytes(torrentStatus.downloadSpeed),
1177
- "/s \xB7 ",
1178
- torrentStatus.numPeers,
1179
- " peer",
1180
- torrentStatus.numPeers !== 1 ? "s" : ""
1181
- ] }),
1182
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1183
- Math.round(torrentStatus.progress * 100),
1184
- "%"
1185
- ] })
1186
- ] })
1187
- ] })),
1188
- playlist.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(Select, { value: sortKey, onValueChange: handleSort, children: [
1189
- /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "h-7 text-xs", "aria-label": "Sort playlist", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Sort by\u2026" }) }),
1190
- /* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
1191
- /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "name-asc", children: "Name A\u2013Z" }),
1192
- /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "name-desc", children: "Name Z\u2013A" }),
1193
- /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "duration-asc", children: "Shortest first" }),
1194
- /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "duration-desc", children: "Longest first" })
1195
- ] })
1314
+ ) }) }) })
1196
1315
  ] })
1197
- ] }),
1198
- /* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 space-y-0.5", children: playlist.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-xs text-muted-foreground py-10 px-2", children: [
1199
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your playlist is empty." }),
1200
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add files or a stream URL to get started." })
1201
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(core$1.DndContext, { collisionDetection: core$1.closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(
1202
- sortable.SortableContext,
1203
- {
1204
- items: playlist.map((i) => i.id),
1205
- strategy: sortable.verticalListSortingStrategy,
1206
- children: playlist.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
1207
- SortablePlaylistItem,
1208
- {
1209
- item,
1210
- index,
1211
- isActive: index === currentVideoIndex,
1212
- downloadReady: item.source === "torrent" && torrentStatus.progress >= 1,
1213
- onSelect: onSelectVideo,
1214
- onRemove: onRemoveItem
1215
- },
1216
- item.id
1217
- ))
1218
- }
1219
- ) }) }) })
1220
- ] })
1316
+ )
1317
+ }
1221
1318
  ) });
1222
1319
  };
1223
1320
  var playlist_panel_default = PlaylistPanel;
@@ -1310,7 +1407,7 @@ function PlayerErrorDisplay({ error, onRetry, onSkip, onDismiss }) {
1310
1407
  ] })
1311
1408
  ] });
1312
1409
  }
1313
- function formatTime3(seconds) {
1410
+ function formatTime4(seconds) {
1314
1411
  if (isNaN(seconds) || seconds === 0) return "\u2014";
1315
1412
  const h = Math.floor(seconds / 3600);
1316
1413
  const m = Math.floor(seconds % 3600 / 60);
@@ -1333,7 +1430,7 @@ function VideoInfoPanel({ metadata, onClose }) {
1333
1430
  const rows = [
1334
1431
  ["File", metadata.filename ? metadata.filename.split("/").pop() ?? metadata.filename : "\u2014"],
1335
1432
  ["Size", formatSize(metadata.fileSize)],
1336
- ["Duration", formatTime3(metadata.duration)],
1433
+ ["Duration", formatTime4(metadata.duration)],
1337
1434
  ["Container", metadata.container || "\u2014"],
1338
1435
  ["Resolution", metadata.width && metadata.height ? `${metadata.width} \xD7 ${metadata.height}` : "\u2014"],
1339
1436
  ["Frame Rate", metadata.frameRate ? `${metadata.frameRate} fps` : "\u2014"],
@@ -1371,7 +1468,7 @@ function VideoInfoPanel({ metadata, onClose }) {
1371
1468
  }
1372
1469
  var Dialog = DialogPrimitive__namespace.Root;
1373
1470
  var DialogPortal = DialogPrimitive__namespace.Portal;
1374
- var DialogOverlay = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1471
+ var DialogOverlay = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1375
1472
  DialogPrimitive__namespace.Overlay,
1376
1473
  {
1377
1474
  ref,
@@ -1383,7 +1480,7 @@ var DialogOverlay = React10__namespace.forwardRef(({ className, ...props }, ref)
1383
1480
  }
1384
1481
  ));
1385
1482
  DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
1386
- var DialogContent = React10__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
1483
+ var DialogContent = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
1387
1484
  /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
1388
1485
  /* @__PURE__ */ jsxRuntime.jsxs(
1389
1486
  DialogPrimitive__namespace.Content,
@@ -1419,7 +1516,7 @@ var DialogHeader = ({
1419
1516
  }
1420
1517
  );
1421
1518
  DialogHeader.displayName = "DialogHeader";
1422
- var DialogTitle = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1519
+ var DialogTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1423
1520
  DialogPrimitive__namespace.Title,
1424
1521
  {
1425
1522
  ref,
@@ -1431,7 +1528,7 @@ var DialogTitle = React10__namespace.forwardRef(({ className, ...props }, ref) =
1431
1528
  }
1432
1529
  ));
1433
1530
  DialogTitle.displayName = DialogPrimitive__namespace.Title.displayName;
1434
- var DialogDescription = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1531
+ var DialogDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1435
1532
  DialogPrimitive__namespace.Description,
1436
1533
  {
1437
1534
  ref,
@@ -1540,8 +1637,8 @@ function toast({ ...props }) {
1540
1637
  };
1541
1638
  }
1542
1639
  function useToast() {
1543
- const [state, setState] = React10__namespace.useState(memoryState);
1544
- React10__namespace.useEffect(() => {
1640
+ const [state, setState] = React11__namespace.useState(memoryState);
1641
+ React11__namespace.useEffect(() => {
1545
1642
  listeners.push(setState);
1546
1643
  return () => {
1547
1644
  const index = listeners.indexOf(setState);
@@ -1561,10 +1658,10 @@ function ShortcutSettingsDialog({
1561
1658
  onSave,
1562
1659
  onClose
1563
1660
  }) {
1564
- const [editing, setEditing] = React10.useState(shortcuts);
1565
- const [capturing, setCapturing] = React10.useState(null);
1661
+ const [editing, setEditing] = React11.useState(shortcuts);
1662
+ const [capturing, setCapturing] = React11.useState(null);
1566
1663
  const { toast: toast2 } = useToast();
1567
- React10.useEffect(() => {
1664
+ React11.useEffect(() => {
1568
1665
  if (!capturing) return;
1569
1666
  const handler = (e) => {
1570
1667
  e.preventDefault();
@@ -1643,8 +1740,8 @@ function ShortcutSettingsDialog({
1643
1740
  ] }) });
1644
1741
  }
1645
1742
  function SubtitleOverlay({ videoRef, activeSubtitle }) {
1646
- const [cueText, setCueText] = React10.useState("");
1647
- React10.useEffect(() => {
1743
+ const [cueText, setCueText] = React11.useState("");
1744
+ React11.useEffect(() => {
1648
1745
  const video = videoRef.current;
1649
1746
  if (!video || activeSubtitle === "-1") {
1650
1747
  setCueText("");
@@ -1737,7 +1834,7 @@ function SubtitleOverlay({ videoRef, activeSubtitle }) {
1737
1834
  }
1738
1835
  var AlertDialog = AlertDialogPrimitive__namespace.Root;
1739
1836
  var AlertDialogPortal = AlertDialogPrimitive__namespace.Portal;
1740
- var AlertDialogOverlay = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1837
+ var AlertDialogOverlay = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1741
1838
  AlertDialogPrimitive__namespace.Overlay,
1742
1839
  {
1743
1840
  className: cn(
@@ -1749,7 +1846,7 @@ var AlertDialogOverlay = React10__namespace.forwardRef(({ className, ...props },
1749
1846
  }
1750
1847
  ));
1751
1848
  AlertDialogOverlay.displayName = AlertDialogPrimitive__namespace.Overlay.displayName;
1752
- var AlertDialogContent = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogPortal, { children: [
1849
+ var AlertDialogContent = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogPortal, { children: [
1753
1850
  /* @__PURE__ */ jsxRuntime.jsx(AlertDialogOverlay, {}),
1754
1851
  /* @__PURE__ */ jsxRuntime.jsx(
1755
1852
  AlertDialogPrimitive__namespace.Content,
@@ -1792,7 +1889,7 @@ var AlertDialogFooter = ({
1792
1889
  }
1793
1890
  );
1794
1891
  AlertDialogFooter.displayName = "AlertDialogFooter";
1795
- var AlertDialogTitle = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1892
+ var AlertDialogTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1796
1893
  AlertDialogPrimitive__namespace.Title,
1797
1894
  {
1798
1895
  ref,
@@ -1801,7 +1898,7 @@ var AlertDialogTitle = React10__namespace.forwardRef(({ className, ...props }, r
1801
1898
  }
1802
1899
  ));
1803
1900
  AlertDialogTitle.displayName = AlertDialogPrimitive__namespace.Title.displayName;
1804
- var AlertDialogDescription = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1901
+ var AlertDialogDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1805
1902
  AlertDialogPrimitive__namespace.Description,
1806
1903
  {
1807
1904
  ref,
@@ -1810,7 +1907,7 @@ var AlertDialogDescription = React10__namespace.forwardRef(({ className, ...prop
1810
1907
  }
1811
1908
  ));
1812
1909
  AlertDialogDescription.displayName = AlertDialogPrimitive__namespace.Description.displayName;
1813
- var AlertDialogAction = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1910
+ var AlertDialogAction = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1814
1911
  AlertDialogPrimitive__namespace.Action,
1815
1912
  {
1816
1913
  ref,
@@ -1819,7 +1916,7 @@ var AlertDialogAction = React10__namespace.forwardRef(({ className, ...props },
1819
1916
  }
1820
1917
  ));
1821
1918
  AlertDialogAction.displayName = AlertDialogPrimitive__namespace.Action.displayName;
1822
- var AlertDialogCancel = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1919
+ var AlertDialogCancel = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1823
1920
  AlertDialogPrimitive__namespace.Cancel,
1824
1921
  {
1825
1922
  ref,
@@ -1834,16 +1931,15 @@ var AlertDialogCancel = React10__namespace.forwardRef(({ className, ...props },
1834
1931
  AlertDialogCancel.displayName = AlertDialogPrimitive__namespace.Cancel.displayName;
1835
1932
  var MAX_RETRIES = 3;
1836
1933
  var LightBirdPlayer = () => {
1837
- const videoRef = React10.useRef(null);
1838
- const containerRef = React10.useRef(null);
1839
- const canvasRef = React10.useRef(null);
1840
- const subtitleInputRef = React10.useRef(null);
1841
- const playerRef = React10.useRef(null);
1842
- const subtitleFilesMapRef = React10.useRef(/* @__PURE__ */ new Map());
1843
- const retryCountRef = React10.useRef(0);
1844
- const retryTimerRef = React10.useRef(null);
1845
- const streamStallDetectorRef = React10.useRef(null);
1846
- const isStreamRef = React10.useRef(false);
1934
+ const videoRef = React11.useRef(null);
1935
+ const containerRef = React11.useRef(null);
1936
+ const subtitleInputRef = React11.useRef(null);
1937
+ const playerRef = React11.useRef(null);
1938
+ const subtitleFilesMapRef = React11.useRef(/* @__PURE__ */ new Map());
1939
+ const retryCountRef = React11.useRef(0);
1940
+ const retryTimerRef = React11.useRef(null);
1941
+ const streamStallDetectorRef = React11.useRef(null);
1942
+ const isStreamRef = React11.useRef(false);
1847
1943
  const { toast: toast2 } = useToast();
1848
1944
  const playlist = react.usePlaylist();
1849
1945
  const playback = react.useVideoPlayback(videoRef);
@@ -1871,24 +1967,26 @@ var LightBirdPlayer = () => {
1871
1967
  const { chapters, currentChapter, goToChapter } = react.useChapters(videoRef, playerRef);
1872
1968
  const magnetLinkEnabled = reactSdk.useBooleanFlagValue(core.FLAG_MAGNET_LINK, true);
1873
1969
  const magnet = react.useMagnet();
1874
- const [disclaimerPendingUri, setDisclaimerPendingUri] = React10.useState(null);
1875
- const [shortcuts, setShortcuts] = React10.useState(() => core.loadShortcuts());
1876
- const [showShortcutsHelp, setShowShortcutsHelp] = React10.useState(false);
1877
- const [showShortcutsDialog, setShowShortcutsDialog] = React10.useState(false);
1878
- const [showInfo, setShowInfo] = React10.useState(false);
1879
- const progressEstimatorRef = React10.useRef(null);
1880
- const [audioTracks, setAudioTracks] = React10.useState([]);
1881
- const [activeAudioTrack, setActiveAudioTrack] = React10.useState("0");
1882
- const [isLoading, setIsLoading] = React10.useState(false);
1883
- const [loadingMessage, setLoadingMessage] = React10.useState("");
1884
- const [processingProgress, setProcessingProgress] = React10.useState(0);
1885
- const [processingEta, setProcessingEta] = React10.useState(null);
1886
- const [processingThroughput, setProcessingThroughput] = React10.useState(null);
1887
- const [playerError, setPlayerError] = React10.useState(null);
1888
- const [cancellableProcessing, setCancellableProcessing] = React10.useState(false);
1889
- const [mediaThumbnail, setMediaThumbnail] = React10.useState(null);
1890
- const [tracksLoading, setTracksLoading] = React10.useState(false);
1891
- const shortcutHandlers = React10.useMemo(() => ({
1970
+ const [disclaimerPendingUri, setDisclaimerPendingUri] = React11.useState(null);
1971
+ const [shortcuts, setShortcuts] = React11.useState(() => core.loadShortcuts());
1972
+ const [showShortcutsHelp, setShowShortcutsHelp] = React11.useState(false);
1973
+ const [showShortcutsDialog, setShowShortcutsDialog] = React11.useState(false);
1974
+ const [showInfo, setShowInfo] = React11.useState(false);
1975
+ const progressEstimatorRef = React11.useRef(null);
1976
+ const [audioTracks, setAudioTracks] = React11.useState([]);
1977
+ const [activeAudioTrack, setActiveAudioTrack] = React11.useState("0");
1978
+ const [isLoading, setIsLoading] = React11.useState(false);
1979
+ const [loadingMessage, setLoadingMessage] = React11.useState("");
1980
+ const [processingProgress, setProcessingProgress] = React11.useState(0);
1981
+ const [processingEta, setProcessingEta] = React11.useState(null);
1982
+ const [processingThroughput, setProcessingThroughput] = React11.useState(null);
1983
+ const [playerError, setPlayerError] = React11.useState(null);
1984
+ const [cancellableProcessing, setCancellableProcessing] = React11.useState(false);
1985
+ const [mediaThumbnail, setMediaThumbnail] = React11.useState(null);
1986
+ const [tracksLoading, setTracksLoading] = React11.useState(false);
1987
+ const abLoopCycleRef = React11.useRef(() => {
1988
+ });
1989
+ const shortcutHandlers = React11.useMemo(() => ({
1892
1990
  "play-pause": () => playback.togglePlay(),
1893
1991
  "seek-forward-5": () => {
1894
1992
  const el = videoRef.current;
@@ -1937,9 +2035,13 @@ var LightBirdPlayer = () => {
1937
2035
  const prev = chapters[cur.index - 1];
1938
2036
  if (prev) el.currentTime = prev.startTime;
1939
2037
  }
1940
- }
2038
+ },
2039
+ "frame-step-forward": () => playback.frameStep("forward"),
2040
+ "frame-step-backward": () => playback.frameStep("backward"),
2041
+ "loop-toggle": () => playback.toggleLoop(),
2042
+ "ab-loop-cycle": () => abLoopCycleRef.current()
1941
2043
  // eslint-disable-next-line react-hooks/exhaustive-deps
1942
- }), [playback.togglePlay, playback.seek, playback.setVolume, playback.toggleMute, fullscreen.toggle, chapters, currentChapter]);
2044
+ }), [playback.togglePlay, playback.seek, playback.setVolume, playback.toggleMute, playback.frameStep, playback.toggleLoop, fullscreen.toggle, chapters, currentChapter]);
1943
2045
  react.useKeyboardShortcuts(shortcuts, shortcutHandlers);
1944
2046
  const stopStallDetection = () => {
1945
2047
  if (streamStallDetectorRef.current) {
@@ -1976,11 +2078,11 @@ var LightBirdPlayer = () => {
1976
2078
  retryTimerRef.current = null;
1977
2079
  }
1978
2080
  };
1979
- const [playlistOpen, setPlaylistOpen] = React10.useState(true);
1980
- const [playlistPinned, setPlaylistPinned] = React10.useState(false);
1981
- const [playlistSize, setPlaylistSize] = React10.useState("md");
1982
- const wasAutoHiddenRef = React10.useRef(false);
1983
- const processFile = React10.useCallback(async (file, subtitleFiles = []) => {
2081
+ const [playlistOpen, setPlaylistOpen] = React11.useState(true);
2082
+ const [playlistPinned, setPlaylistPinned] = React11.useState(false);
2083
+ const [playlistSize, setPlaylistSize] = React11.useState("md");
2084
+ const wasAutoHiddenRef = React11.useRef(false);
2085
+ const processFile = React11.useCallback(async (file, subtitleFiles = []) => {
1984
2086
  setIsLoading(true);
1985
2087
  setLoadingMessage("Initializing player...");
1986
2088
  setProcessingProgress(0);
@@ -2056,7 +2158,7 @@ var LightBirdPlayer = () => {
2056
2158
  setProcessingThroughput(null);
2057
2159
  }
2058
2160
  }, [subtitles, toast2]);
2059
- const handleCancelProcessing = React10.useCallback(() => {
2161
+ const handleCancelProcessing = React11.useCallback(() => {
2060
2162
  playerRef.current?.cancel?.();
2061
2163
  playerRef.current = null;
2062
2164
  setCancellableProcessing(false);
@@ -2064,7 +2166,7 @@ var LightBirdPlayer = () => {
2064
2166
  setLoadingMessage("");
2065
2167
  setProcessingProgress(0);
2066
2168
  }, []);
2067
- const loadVideo = React10.useCallback((index) => {
2169
+ const loadVideo = React11.useCallback((index) => {
2068
2170
  const item = playlist.playlist[index];
2069
2171
  if (!item) return;
2070
2172
  playlist.selectItem(index);
@@ -2087,14 +2189,14 @@ var LightBirdPlayer = () => {
2087
2189
  processFile(item.file, subs);
2088
2190
  }
2089
2191
  }, [playlist.playlist, playlist.selectItem, subtitles, processFile]);
2090
- const handleSkipToNext = React10.useCallback(() => {
2192
+ const handleSkipToNext = React11.useCallback(() => {
2091
2193
  setPlayerError(null);
2092
2194
  clearRetryTimer();
2093
2195
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2094
2196
  loadVideo((playlist.currentIndex + 1) % playlist.playlist.length);
2095
2197
  }
2096
2198
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2097
- const handleRetry = React10.useCallback(() => {
2199
+ const handleRetry = React11.useCallback(() => {
2098
2200
  setPlayerError(null);
2099
2201
  clearRetryTimer();
2100
2202
  retryCountRef.current = 0;
@@ -2102,11 +2204,11 @@ var LightBirdPlayer = () => {
2102
2204
  videoRef.current.load();
2103
2205
  }
2104
2206
  }, []);
2105
- const handleDismissError = React10.useCallback(() => {
2207
+ const handleDismissError = React11.useCallback(() => {
2106
2208
  setPlayerError(null);
2107
2209
  clearRetryTimer();
2108
2210
  }, []);
2109
- React10.useEffect(() => {
2211
+ React11.useEffect(() => {
2110
2212
  const el = videoRef.current;
2111
2213
  if (!el) return;
2112
2214
  const onError = () => {
@@ -2128,7 +2230,7 @@ var LightBirdPlayer = () => {
2128
2230
  el.addEventListener("error", onError);
2129
2231
  return () => el.removeEventListener("error", onError);
2130
2232
  }, [playlist.currentIndex, playlist.playlist.length]);
2131
- React10.useEffect(() => {
2233
+ React11.useEffect(() => {
2132
2234
  const el = videoRef.current;
2133
2235
  if (!el) return;
2134
2236
  const onEnded = () => {
@@ -2143,14 +2245,14 @@ var LightBirdPlayer = () => {
2143
2245
  el.addEventListener("ended", onEnded);
2144
2246
  return () => el.removeEventListener("ended", onEnded);
2145
2247
  }, [playback.loop, playlist.currentIndex, playlist.playlist.length, loadVideo]);
2146
- React10.useEffect(() => {
2248
+ React11.useEffect(() => {
2147
2249
  return () => {
2148
2250
  playerRef.current?.destroy();
2149
2251
  clearRetryTimer();
2150
2252
  stopStallDetection();
2151
2253
  };
2152
2254
  }, []);
2153
- React10.useEffect(() => {
2255
+ React11.useEffect(() => {
2154
2256
  const el = videoRef.current;
2155
2257
  if (!el || !playlist.currentItem) {
2156
2258
  setMediaThumbnail(null);
@@ -2169,7 +2271,7 @@ var LightBirdPlayer = () => {
2169
2271
  el.removeEventListener("loadeddata", onLoadedData);
2170
2272
  };
2171
2273
  }, [playlist.currentItem]);
2172
- React10.useEffect(() => {
2274
+ React11.useEffect(() => {
2173
2275
  if (playback.isPlaying) {
2174
2276
  if (!playlistPinned) {
2175
2277
  setPlaylistOpen((current) => {
@@ -2205,7 +2307,7 @@ var LightBirdPlayer = () => {
2205
2307
  await processFile(validVideoFiles[0], subtitleFiles);
2206
2308
  }
2207
2309
  };
2208
- const handleFolderFilesAdded = React10.useCallback(
2310
+ const handleFolderFilesAdded = React11.useCallback(
2209
2311
  async (files) => {
2210
2312
  const prevLen = playlist.playlist.length;
2211
2313
  await playlist.addFiles(files);
@@ -2217,16 +2319,16 @@ var LightBirdPlayer = () => {
2217
2319
  },
2218
2320
  [playlist, processFile]
2219
2321
  );
2220
- const handleRemoveItem = React10.useCallback((index) => {
2322
+ const handleRemoveItem = React11.useCallback((index) => {
2221
2323
  playlist.removeItem(index);
2222
2324
  }, [playlist]);
2223
- const handleReorder = React10.useCallback(
2325
+ const handleReorder = React11.useCallback(
2224
2326
  (newPlaylist) => {
2225
2327
  playlist.reorderItems(newPlaylist);
2226
2328
  },
2227
2329
  [playlist]
2228
2330
  );
2229
- const handleImportM3U = React10.useCallback(
2331
+ const handleImportM3U = React11.useCallback(
2230
2332
  (items) => {
2231
2333
  items.forEach((item) => {
2232
2334
  playlist.appendItem({ ...item, id: crypto.randomUUID() });
@@ -2242,7 +2344,7 @@ var LightBirdPlayer = () => {
2242
2344
  },
2243
2345
  [playlist, subtitles]
2244
2346
  );
2245
- const handleAddStream = React10.useCallback((url, name) => {
2347
+ const handleAddStream = React11.useCallback((url, name) => {
2246
2348
  const newIndex = playlist.playlist.length;
2247
2349
  const newItem = {
2248
2350
  id: crypto.randomUUID(),
@@ -2261,7 +2363,7 @@ var LightBirdPlayer = () => {
2261
2363
  startStallDetection();
2262
2364
  }
2263
2365
  }, [playlist, subtitles]);
2264
- const handleAddMagnet = React10.useCallback(async (uri) => {
2366
+ const handleAddMagnet = React11.useCallback(async (uri) => {
2265
2367
  if (!core.hasAcceptedDisclaimer()) {
2266
2368
  setDisclaimerPendingUri(uri);
2267
2369
  return false;
@@ -2283,7 +2385,7 @@ var LightBirdPlayer = () => {
2283
2385
  }
2284
2386
  return true;
2285
2387
  }, [magnet, playlist, subtitles, toast2]);
2286
- const handleDisclaimerAccepted = React10.useCallback(async () => {
2388
+ const handleDisclaimerAccepted = React11.useCallback(async () => {
2287
2389
  core.acceptDisclaimer();
2288
2390
  const uri = disclaimerPendingUri;
2289
2391
  setDisclaimerPendingUri(null);
@@ -2295,7 +2397,7 @@ var LightBirdPlayer = () => {
2295
2397
  }
2296
2398
  }
2297
2399
  }, [disclaimerPendingUri, handleAddMagnet, toast2]);
2298
- const handleSubtitleChange = React10.useCallback(async (id) => {
2400
+ const handleSubtitleChange = React11.useCallback(async (id) => {
2299
2401
  subtitles.switchSubtitle(id);
2300
2402
  if (playerRef.current) {
2301
2403
  try {
@@ -2305,7 +2407,7 @@ var LightBirdPlayer = () => {
2305
2407
  }
2306
2408
  }
2307
2409
  }, [subtitles]);
2308
- const handleAudioTrackChange = React10.useCallback(async (id) => {
2410
+ const handleAudioTrackChange = React11.useCallback(async (id) => {
2309
2411
  if (!playerRef.current) return;
2310
2412
  try {
2311
2413
  setIsLoading(true);
@@ -2320,66 +2422,67 @@ var LightBirdPlayer = () => {
2320
2422
  setLoadingMessage("");
2321
2423
  }
2322
2424
  }, [toast2]);
2323
- const captureScreenshot = React10.useCallback(() => {
2425
+ const captureScreenshot = React11.useCallback(() => {
2324
2426
  const video = videoRef.current;
2325
- const canvas = canvasRef.current;
2326
- if (!video || !canvas) return;
2327
- canvas.width = video.videoWidth;
2328
- canvas.height = video.videoHeight;
2329
- const ctx = canvas.getContext("2d");
2330
- if (!ctx) return;
2331
- ctx.filter = video.style.filter;
2332
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2333
- const dataUrl = canvas.toDataURL("image/png");
2334
- const a = document.createElement("a");
2335
- a.href = dataUrl;
2336
- a.download = `lightbird-screenshot-${(/* @__PURE__ */ new Date()).toISOString()}.png`;
2337
- a.click();
2427
+ if (!video) return;
2428
+ const dataUrl = core.exportVideoFrame(video, { filter: video.style.filter });
2429
+ if (!dataUrl) {
2430
+ toast2({
2431
+ title: "Screenshot failed",
2432
+ description: "The frame could not be captured (the video may be cross-origin protected).",
2433
+ variant: "destructive"
2434
+ });
2435
+ return;
2436
+ }
2437
+ core.downloadDataUrl(dataUrl, core.frameExportFilename("png"));
2338
2438
  toast2({ title: "Screenshot Saved" });
2339
2439
  }, [toast2]);
2340
- const handleABLoopCycle = React10.useCallback(() => {
2440
+ const handleABLoopCycle = React11.useCallback(() => {
2341
2441
  if (abLoop.pointA === null) abLoop.setPointA();
2342
2442
  else if (abLoop.pointB === null) abLoop.setPointB();
2343
2443
  else abLoop.clear();
2344
2444
  }, [abLoop.pointA, abLoop.pointB, abLoop.setPointA, abLoop.setPointB, abLoop.clear]);
2345
- const abLoopState = React10.useMemo(
2445
+ React11.useEffect(() => {
2446
+ abLoopCycleRef.current = handleABLoopCycle;
2447
+ }, [handleABLoopCycle]);
2448
+ const abLoopState = React11.useMemo(
2346
2449
  () => ({ pointA: abLoop.pointA, pointB: abLoop.pointB, isLooping: abLoop.isLooping }),
2347
2450
  [abLoop.pointA, abLoop.pointB, abLoop.isLooping]
2348
2451
  );
2349
- const handleNext = React10.useCallback(() => {
2452
+ const handleNext = React11.useCallback(() => {
2350
2453
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2351
2454
  loadVideo((playlist.currentIndex + 1) % playlist.playlist.length);
2352
2455
  }
2353
2456
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2354
- const handlePrevious = React10.useCallback(() => {
2457
+ const handlePrevious = React11.useCallback(() => {
2355
2458
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2356
2459
  loadVideo((playlist.currentIndex - 1 + playlist.playlist.length) % playlist.playlist.length);
2357
2460
  }
2358
2461
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2359
- const handleSubtitleUpload = React10.useCallback(() => {
2462
+ const handleSubtitleUpload = React11.useCallback(() => {
2360
2463
  subtitleInputRef.current?.click();
2361
2464
  }, []);
2362
- const handleSelectVideo = React10.useCallback((index) => {
2465
+ const handleSelectVideo = React11.useCallback((index) => {
2363
2466
  loadVideo(index);
2364
2467
  }, [loadVideo]);
2365
2468
  const handlePlaylistToggle = () => {
2366
2469
  wasAutoHiddenRef.current = false;
2367
2470
  setPlaylistOpen((v) => !v);
2368
2471
  };
2369
- const handleMediaPlay = React10.useCallback(() => {
2472
+ const handleMediaPlay = React11.useCallback(() => {
2370
2473
  const el = videoRef.current;
2371
2474
  if (el) el.play().catch(() => {
2372
2475
  });
2373
2476
  }, []);
2374
- const handleMediaPause = React10.useCallback(() => {
2477
+ const handleMediaPause = React11.useCallback(() => {
2375
2478
  const el = videoRef.current;
2376
2479
  if (el) el.pause();
2377
2480
  }, []);
2378
- const handleMediaSeekForward = React10.useCallback(() => {
2481
+ const handleMediaSeekForward = React11.useCallback(() => {
2379
2482
  const el = videoRef.current;
2380
2483
  if (el) playback.seek(el.currentTime + 10);
2381
2484
  }, [playback.seek]);
2382
- const handleMediaSeekBackward = React10.useCallback(() => {
2485
+ const handleMediaSeekBackward = React11.useCallback(() => {
2383
2486
  const el = videoRef.current;
2384
2487
  if (el) playback.seek(el.currentTime - 10);
2385
2488
  }, [playback.seek]);
@@ -2410,7 +2513,6 @@ var LightBirdPlayer = () => {
2410
2513
  crossOrigin: "anonymous"
2411
2514
  }
2412
2515
  ),
2413
- /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, className: "hidden" }),
2414
2516
  /* @__PURE__ */ jsxRuntime.jsx(SubtitleOverlay, { videoRef, activeSubtitle: subtitles.activeSubtitle }),
2415
2517
  /* @__PURE__ */ jsxRuntime.jsx(
2416
2518
  VideoOverlay,
@@ -2470,9 +2572,11 @@ var LightBirdPlayer = () => {
2470
2572
  )
2471
2573
  }
2472
2574
  ),
2473
- playlist.currentItem && /* @__PURE__ */ jsxRuntime.jsx(
2575
+ /* @__PURE__ */ jsxRuntime.jsx(
2474
2576
  player_controls_default,
2475
2577
  {
2578
+ isDisabled: !playlist.currentItem,
2579
+ videoRef,
2476
2580
  isPlaying: playback.isPlaying,
2477
2581
  progress: playback.progress,
2478
2582
  duration: playback.duration,
@@ -2541,9 +2645,17 @@ var LightBirdPlayer = () => {
2541
2645
  }
2542
2646
  }
2543
2647
  ),
2544
- !playlist.currentItem && !isLoading && !loadingMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-muted-foreground", children: [
2545
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-semibold", children: "LightBird Player" }),
2546
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add a local file or stream to begin." })
2648
+ !playlist.currentItem && !isLoading && !loadingMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center px-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center text-center max-w-sm", children: [
2649
+ /* @__PURE__ */ jsxRuntime.jsx(
2650
+ "div",
2651
+ {
2652
+ "aria-hidden": true,
2653
+ className: "flex h-16 w-16 items-center justify-center rounded-full bg-accent/10 ring-1 ring-accent/30",
2654
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-7 w-7", style: { color: "hsl(var(--accent))" } })
2655
+ }
2656
+ ),
2657
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-5 text-lg font-medium text-foreground", children: "No video loaded" }),
2658
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Drop files anywhere, or pick a source from the playlist panel." })
2547
2659
  ] }) })
2548
2660
  ]
2549
2661
  }
@@ -2596,7 +2708,7 @@ var LightBirdPlayer = () => {
2596
2708
  ] });
2597
2709
  };
2598
2710
  var lightbird_player_default = LightBirdPlayer;
2599
- var PlayerErrorBoundary = class extends React10.Component {
2711
+ var PlayerErrorBoundary = class extends React11.Component {
2600
2712
  constructor() {
2601
2713
  super(...arguments);
2602
2714
  this.state = { hasError: false };
@@ -2626,7 +2738,7 @@ var PlayerErrorBoundary = class extends React10.Component {
2626
2738
  }
2627
2739
  };
2628
2740
  var ToastProvider = ToastPrimitives__namespace.Provider;
2629
- var ToastViewport = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2741
+ var ToastViewport = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2630
2742
  ToastPrimitives__namespace.Viewport,
2631
2743
  {
2632
2744
  ref,
@@ -2652,7 +2764,7 @@ var toastVariants = classVarianceAuthority.cva(
2652
2764
  }
2653
2765
  }
2654
2766
  );
2655
- var Toast = React10__namespace.forwardRef(({ className, variant, ...props }, ref) => {
2767
+ var Toast = React11__namespace.forwardRef(({ className, variant, ...props }, ref) => {
2656
2768
  return /* @__PURE__ */ jsxRuntime.jsx(
2657
2769
  ToastPrimitives__namespace.Root,
2658
2770
  {
@@ -2663,7 +2775,7 @@ var Toast = React10__namespace.forwardRef(({ className, variant, ...props }, ref
2663
2775
  );
2664
2776
  });
2665
2777
  Toast.displayName = ToastPrimitives__namespace.Root.displayName;
2666
- var ToastAction = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2778
+ var ToastAction = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2667
2779
  ToastPrimitives__namespace.Action,
2668
2780
  {
2669
2781
  ref,
@@ -2675,7 +2787,7 @@ var ToastAction = React10__namespace.forwardRef(({ className, ...props }, ref) =
2675
2787
  }
2676
2788
  ));
2677
2789
  ToastAction.displayName = ToastPrimitives__namespace.Action.displayName;
2678
- var ToastClose = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2790
+ var ToastClose = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2679
2791
  ToastPrimitives__namespace.Close,
2680
2792
  {
2681
2793
  ref,
@@ -2689,7 +2801,7 @@ var ToastClose = React10__namespace.forwardRef(({ className, ...props }, ref) =>
2689
2801
  }
2690
2802
  ));
2691
2803
  ToastClose.displayName = ToastPrimitives__namespace.Close.displayName;
2692
- var ToastTitle = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2804
+ var ToastTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2693
2805
  ToastPrimitives__namespace.Title,
2694
2806
  {
2695
2807
  ref,
@@ -2698,7 +2810,7 @@ var ToastTitle = React10__namespace.forwardRef(({ className, ...props }, ref) =>
2698
2810
  }
2699
2811
  ));
2700
2812
  ToastTitle.displayName = ToastPrimitives__namespace.Title.displayName;
2701
- var ToastDescription = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2813
+ var ToastDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2702
2814
  ToastPrimitives__namespace.Description,
2703
2815
  {
2704
2816
  ref,
@@ -2725,7 +2837,7 @@ function Toaster() {
2725
2837
  }
2726
2838
  var started = false;
2727
2839
  function FeatureFlagsProvider({ children }) {
2728
- React10.useEffect(() => {
2840
+ React11.useEffect(() => {
2729
2841
  if (started) return;
2730
2842
  started = true;
2731
2843
  core.initFeatureFlags().catch(console.error);