@lightbird/ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,16 @@ 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 canvasRef = React11.useRef(null);
1937
+ const subtitleInputRef = React11.useRef(null);
1938
+ const playerRef = React11.useRef(null);
1939
+ const subtitleFilesMapRef = React11.useRef(/* @__PURE__ */ new Map());
1940
+ const retryCountRef = React11.useRef(0);
1941
+ const retryTimerRef = React11.useRef(null);
1942
+ const streamStallDetectorRef = React11.useRef(null);
1943
+ const isStreamRef = React11.useRef(false);
1847
1944
  const { toast: toast2 } = useToast();
1848
1945
  const playlist = react.usePlaylist();
1849
1946
  const playback = react.useVideoPlayback(videoRef);
@@ -1871,24 +1968,26 @@ var LightBirdPlayer = () => {
1871
1968
  const { chapters, currentChapter, goToChapter } = react.useChapters(videoRef, playerRef);
1872
1969
  const magnetLinkEnabled = reactSdk.useBooleanFlagValue(core.FLAG_MAGNET_LINK, true);
1873
1970
  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(() => ({
1971
+ const [disclaimerPendingUri, setDisclaimerPendingUri] = React11.useState(null);
1972
+ const [shortcuts, setShortcuts] = React11.useState(() => core.loadShortcuts());
1973
+ const [showShortcutsHelp, setShowShortcutsHelp] = React11.useState(false);
1974
+ const [showShortcutsDialog, setShowShortcutsDialog] = React11.useState(false);
1975
+ const [showInfo, setShowInfo] = React11.useState(false);
1976
+ const progressEstimatorRef = React11.useRef(null);
1977
+ const [audioTracks, setAudioTracks] = React11.useState([]);
1978
+ const [activeAudioTrack, setActiveAudioTrack] = React11.useState("0");
1979
+ const [isLoading, setIsLoading] = React11.useState(false);
1980
+ const [loadingMessage, setLoadingMessage] = React11.useState("");
1981
+ const [processingProgress, setProcessingProgress] = React11.useState(0);
1982
+ const [processingEta, setProcessingEta] = React11.useState(null);
1983
+ const [processingThroughput, setProcessingThroughput] = React11.useState(null);
1984
+ const [playerError, setPlayerError] = React11.useState(null);
1985
+ const [cancellableProcessing, setCancellableProcessing] = React11.useState(false);
1986
+ const [mediaThumbnail, setMediaThumbnail] = React11.useState(null);
1987
+ const [tracksLoading, setTracksLoading] = React11.useState(false);
1988
+ const abLoopCycleRef = React11.useRef(() => {
1989
+ });
1990
+ const shortcutHandlers = React11.useMemo(() => ({
1892
1991
  "play-pause": () => playback.togglePlay(),
1893
1992
  "seek-forward-5": () => {
1894
1993
  const el = videoRef.current;
@@ -1937,9 +2036,13 @@ var LightBirdPlayer = () => {
1937
2036
  const prev = chapters[cur.index - 1];
1938
2037
  if (prev) el.currentTime = prev.startTime;
1939
2038
  }
1940
- }
2039
+ },
2040
+ "frame-step-forward": () => playback.frameStep("forward"),
2041
+ "frame-step-backward": () => playback.frameStep("backward"),
2042
+ "loop-toggle": () => playback.toggleLoop(),
2043
+ "ab-loop-cycle": () => abLoopCycleRef.current()
1941
2044
  // eslint-disable-next-line react-hooks/exhaustive-deps
1942
- }), [playback.togglePlay, playback.seek, playback.setVolume, playback.toggleMute, fullscreen.toggle, chapters, currentChapter]);
2045
+ }), [playback.togglePlay, playback.seek, playback.setVolume, playback.toggleMute, playback.frameStep, playback.toggleLoop, fullscreen.toggle, chapters, currentChapter]);
1943
2046
  react.useKeyboardShortcuts(shortcuts, shortcutHandlers);
1944
2047
  const stopStallDetection = () => {
1945
2048
  if (streamStallDetectorRef.current) {
@@ -1976,11 +2079,11 @@ var LightBirdPlayer = () => {
1976
2079
  retryTimerRef.current = null;
1977
2080
  }
1978
2081
  };
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 = []) => {
2082
+ const [playlistOpen, setPlaylistOpen] = React11.useState(true);
2083
+ const [playlistPinned, setPlaylistPinned] = React11.useState(false);
2084
+ const [playlistSize, setPlaylistSize] = React11.useState("md");
2085
+ const wasAutoHiddenRef = React11.useRef(false);
2086
+ const processFile = React11.useCallback(async (file, subtitleFiles = []) => {
1984
2087
  setIsLoading(true);
1985
2088
  setLoadingMessage("Initializing player...");
1986
2089
  setProcessingProgress(0);
@@ -2056,7 +2159,7 @@ var LightBirdPlayer = () => {
2056
2159
  setProcessingThroughput(null);
2057
2160
  }
2058
2161
  }, [subtitles, toast2]);
2059
- const handleCancelProcessing = React10.useCallback(() => {
2162
+ const handleCancelProcessing = React11.useCallback(() => {
2060
2163
  playerRef.current?.cancel?.();
2061
2164
  playerRef.current = null;
2062
2165
  setCancellableProcessing(false);
@@ -2064,7 +2167,7 @@ var LightBirdPlayer = () => {
2064
2167
  setLoadingMessage("");
2065
2168
  setProcessingProgress(0);
2066
2169
  }, []);
2067
- const loadVideo = React10.useCallback((index) => {
2170
+ const loadVideo = React11.useCallback((index) => {
2068
2171
  const item = playlist.playlist[index];
2069
2172
  if (!item) return;
2070
2173
  playlist.selectItem(index);
@@ -2087,14 +2190,14 @@ var LightBirdPlayer = () => {
2087
2190
  processFile(item.file, subs);
2088
2191
  }
2089
2192
  }, [playlist.playlist, playlist.selectItem, subtitles, processFile]);
2090
- const handleSkipToNext = React10.useCallback(() => {
2193
+ const handleSkipToNext = React11.useCallback(() => {
2091
2194
  setPlayerError(null);
2092
2195
  clearRetryTimer();
2093
2196
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2094
2197
  loadVideo((playlist.currentIndex + 1) % playlist.playlist.length);
2095
2198
  }
2096
2199
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2097
- const handleRetry = React10.useCallback(() => {
2200
+ const handleRetry = React11.useCallback(() => {
2098
2201
  setPlayerError(null);
2099
2202
  clearRetryTimer();
2100
2203
  retryCountRef.current = 0;
@@ -2102,11 +2205,11 @@ var LightBirdPlayer = () => {
2102
2205
  videoRef.current.load();
2103
2206
  }
2104
2207
  }, []);
2105
- const handleDismissError = React10.useCallback(() => {
2208
+ const handleDismissError = React11.useCallback(() => {
2106
2209
  setPlayerError(null);
2107
2210
  clearRetryTimer();
2108
2211
  }, []);
2109
- React10.useEffect(() => {
2212
+ React11.useEffect(() => {
2110
2213
  const el = videoRef.current;
2111
2214
  if (!el) return;
2112
2215
  const onError = () => {
@@ -2128,7 +2231,7 @@ var LightBirdPlayer = () => {
2128
2231
  el.addEventListener("error", onError);
2129
2232
  return () => el.removeEventListener("error", onError);
2130
2233
  }, [playlist.currentIndex, playlist.playlist.length]);
2131
- React10.useEffect(() => {
2234
+ React11.useEffect(() => {
2132
2235
  const el = videoRef.current;
2133
2236
  if (!el) return;
2134
2237
  const onEnded = () => {
@@ -2143,14 +2246,14 @@ var LightBirdPlayer = () => {
2143
2246
  el.addEventListener("ended", onEnded);
2144
2247
  return () => el.removeEventListener("ended", onEnded);
2145
2248
  }, [playback.loop, playlist.currentIndex, playlist.playlist.length, loadVideo]);
2146
- React10.useEffect(() => {
2249
+ React11.useEffect(() => {
2147
2250
  return () => {
2148
2251
  playerRef.current?.destroy();
2149
2252
  clearRetryTimer();
2150
2253
  stopStallDetection();
2151
2254
  };
2152
2255
  }, []);
2153
- React10.useEffect(() => {
2256
+ React11.useEffect(() => {
2154
2257
  const el = videoRef.current;
2155
2258
  if (!el || !playlist.currentItem) {
2156
2259
  setMediaThumbnail(null);
@@ -2169,7 +2272,7 @@ var LightBirdPlayer = () => {
2169
2272
  el.removeEventListener("loadeddata", onLoadedData);
2170
2273
  };
2171
2274
  }, [playlist.currentItem]);
2172
- React10.useEffect(() => {
2275
+ React11.useEffect(() => {
2173
2276
  if (playback.isPlaying) {
2174
2277
  if (!playlistPinned) {
2175
2278
  setPlaylistOpen((current) => {
@@ -2205,7 +2308,7 @@ var LightBirdPlayer = () => {
2205
2308
  await processFile(validVideoFiles[0], subtitleFiles);
2206
2309
  }
2207
2310
  };
2208
- const handleFolderFilesAdded = React10.useCallback(
2311
+ const handleFolderFilesAdded = React11.useCallback(
2209
2312
  async (files) => {
2210
2313
  const prevLen = playlist.playlist.length;
2211
2314
  await playlist.addFiles(files);
@@ -2217,16 +2320,16 @@ var LightBirdPlayer = () => {
2217
2320
  },
2218
2321
  [playlist, processFile]
2219
2322
  );
2220
- const handleRemoveItem = React10.useCallback((index) => {
2323
+ const handleRemoveItem = React11.useCallback((index) => {
2221
2324
  playlist.removeItem(index);
2222
2325
  }, [playlist]);
2223
- const handleReorder = React10.useCallback(
2326
+ const handleReorder = React11.useCallback(
2224
2327
  (newPlaylist) => {
2225
2328
  playlist.reorderItems(newPlaylist);
2226
2329
  },
2227
2330
  [playlist]
2228
2331
  );
2229
- const handleImportM3U = React10.useCallback(
2332
+ const handleImportM3U = React11.useCallback(
2230
2333
  (items) => {
2231
2334
  items.forEach((item) => {
2232
2335
  playlist.appendItem({ ...item, id: crypto.randomUUID() });
@@ -2242,7 +2345,7 @@ var LightBirdPlayer = () => {
2242
2345
  },
2243
2346
  [playlist, subtitles]
2244
2347
  );
2245
- const handleAddStream = React10.useCallback((url, name) => {
2348
+ const handleAddStream = React11.useCallback((url, name) => {
2246
2349
  const newIndex = playlist.playlist.length;
2247
2350
  const newItem = {
2248
2351
  id: crypto.randomUUID(),
@@ -2261,7 +2364,7 @@ var LightBirdPlayer = () => {
2261
2364
  startStallDetection();
2262
2365
  }
2263
2366
  }, [playlist, subtitles]);
2264
- const handleAddMagnet = React10.useCallback(async (uri) => {
2367
+ const handleAddMagnet = React11.useCallback(async (uri) => {
2265
2368
  if (!core.hasAcceptedDisclaimer()) {
2266
2369
  setDisclaimerPendingUri(uri);
2267
2370
  return false;
@@ -2283,7 +2386,7 @@ var LightBirdPlayer = () => {
2283
2386
  }
2284
2387
  return true;
2285
2388
  }, [magnet, playlist, subtitles, toast2]);
2286
- const handleDisclaimerAccepted = React10.useCallback(async () => {
2389
+ const handleDisclaimerAccepted = React11.useCallback(async () => {
2287
2390
  core.acceptDisclaimer();
2288
2391
  const uri = disclaimerPendingUri;
2289
2392
  setDisclaimerPendingUri(null);
@@ -2295,7 +2398,7 @@ var LightBirdPlayer = () => {
2295
2398
  }
2296
2399
  }
2297
2400
  }, [disclaimerPendingUri, handleAddMagnet, toast2]);
2298
- const handleSubtitleChange = React10.useCallback(async (id) => {
2401
+ const handleSubtitleChange = React11.useCallback(async (id) => {
2299
2402
  subtitles.switchSubtitle(id);
2300
2403
  if (playerRef.current) {
2301
2404
  try {
@@ -2305,7 +2408,7 @@ var LightBirdPlayer = () => {
2305
2408
  }
2306
2409
  }
2307
2410
  }, [subtitles]);
2308
- const handleAudioTrackChange = React10.useCallback(async (id) => {
2411
+ const handleAudioTrackChange = React11.useCallback(async (id) => {
2309
2412
  if (!playerRef.current) return;
2310
2413
  try {
2311
2414
  setIsLoading(true);
@@ -2320,7 +2423,7 @@ var LightBirdPlayer = () => {
2320
2423
  setLoadingMessage("");
2321
2424
  }
2322
2425
  }, [toast2]);
2323
- const captureScreenshot = React10.useCallback(() => {
2426
+ const captureScreenshot = React11.useCallback(() => {
2324
2427
  const video = videoRef.current;
2325
2428
  const canvas = canvasRef.current;
2326
2429
  if (!video || !canvas) return;
@@ -2337,49 +2440,52 @@ var LightBirdPlayer = () => {
2337
2440
  a.click();
2338
2441
  toast2({ title: "Screenshot Saved" });
2339
2442
  }, [toast2]);
2340
- const handleABLoopCycle = React10.useCallback(() => {
2443
+ const handleABLoopCycle = React11.useCallback(() => {
2341
2444
  if (abLoop.pointA === null) abLoop.setPointA();
2342
2445
  else if (abLoop.pointB === null) abLoop.setPointB();
2343
2446
  else abLoop.clear();
2344
2447
  }, [abLoop.pointA, abLoop.pointB, abLoop.setPointA, abLoop.setPointB, abLoop.clear]);
2345
- const abLoopState = React10.useMemo(
2448
+ React11.useEffect(() => {
2449
+ abLoopCycleRef.current = handleABLoopCycle;
2450
+ }, [handleABLoopCycle]);
2451
+ const abLoopState = React11.useMemo(
2346
2452
  () => ({ pointA: abLoop.pointA, pointB: abLoop.pointB, isLooping: abLoop.isLooping }),
2347
2453
  [abLoop.pointA, abLoop.pointB, abLoop.isLooping]
2348
2454
  );
2349
- const handleNext = React10.useCallback(() => {
2455
+ const handleNext = React11.useCallback(() => {
2350
2456
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2351
2457
  loadVideo((playlist.currentIndex + 1) % playlist.playlist.length);
2352
2458
  }
2353
2459
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2354
- const handlePrevious = React10.useCallback(() => {
2460
+ const handlePrevious = React11.useCallback(() => {
2355
2461
  if (playlist.currentIndex !== null && playlist.playlist.length > 1) {
2356
2462
  loadVideo((playlist.currentIndex - 1 + playlist.playlist.length) % playlist.playlist.length);
2357
2463
  }
2358
2464
  }, [playlist.currentIndex, playlist.playlist.length, loadVideo]);
2359
- const handleSubtitleUpload = React10.useCallback(() => {
2465
+ const handleSubtitleUpload = React11.useCallback(() => {
2360
2466
  subtitleInputRef.current?.click();
2361
2467
  }, []);
2362
- const handleSelectVideo = React10.useCallback((index) => {
2468
+ const handleSelectVideo = React11.useCallback((index) => {
2363
2469
  loadVideo(index);
2364
2470
  }, [loadVideo]);
2365
2471
  const handlePlaylistToggle = () => {
2366
2472
  wasAutoHiddenRef.current = false;
2367
2473
  setPlaylistOpen((v) => !v);
2368
2474
  };
2369
- const handleMediaPlay = React10.useCallback(() => {
2475
+ const handleMediaPlay = React11.useCallback(() => {
2370
2476
  const el = videoRef.current;
2371
2477
  if (el) el.play().catch(() => {
2372
2478
  });
2373
2479
  }, []);
2374
- const handleMediaPause = React10.useCallback(() => {
2480
+ const handleMediaPause = React11.useCallback(() => {
2375
2481
  const el = videoRef.current;
2376
2482
  if (el) el.pause();
2377
2483
  }, []);
2378
- const handleMediaSeekForward = React10.useCallback(() => {
2484
+ const handleMediaSeekForward = React11.useCallback(() => {
2379
2485
  const el = videoRef.current;
2380
2486
  if (el) playback.seek(el.currentTime + 10);
2381
2487
  }, [playback.seek]);
2382
- const handleMediaSeekBackward = React10.useCallback(() => {
2488
+ const handleMediaSeekBackward = React11.useCallback(() => {
2383
2489
  const el = videoRef.current;
2384
2490
  if (el) playback.seek(el.currentTime - 10);
2385
2491
  }, [playback.seek]);
@@ -2470,9 +2576,11 @@ var LightBirdPlayer = () => {
2470
2576
  )
2471
2577
  }
2472
2578
  ),
2473
- playlist.currentItem && /* @__PURE__ */ jsxRuntime.jsx(
2579
+ /* @__PURE__ */ jsxRuntime.jsx(
2474
2580
  player_controls_default,
2475
2581
  {
2582
+ isDisabled: !playlist.currentItem,
2583
+ videoRef,
2476
2584
  isPlaying: playback.isPlaying,
2477
2585
  progress: playback.progress,
2478
2586
  duration: playback.duration,
@@ -2541,9 +2649,17 @@ var LightBirdPlayer = () => {
2541
2649
  }
2542
2650
  }
2543
2651
  ),
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." })
2652
+ !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: [
2653
+ /* @__PURE__ */ jsxRuntime.jsx(
2654
+ "div",
2655
+ {
2656
+ "aria-hidden": true,
2657
+ className: "flex h-16 w-16 items-center justify-center rounded-full bg-accent/10 ring-1 ring-accent/30",
2658
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-7 w-7", style: { color: "hsl(var(--accent))" } })
2659
+ }
2660
+ ),
2661
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-5 text-lg font-medium text-foreground", children: "No video loaded" }),
2662
+ /* @__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
2663
  ] }) })
2548
2664
  ]
2549
2665
  }
@@ -2596,7 +2712,7 @@ var LightBirdPlayer = () => {
2596
2712
  ] });
2597
2713
  };
2598
2714
  var lightbird_player_default = LightBirdPlayer;
2599
- var PlayerErrorBoundary = class extends React10.Component {
2715
+ var PlayerErrorBoundary = class extends React11.Component {
2600
2716
  constructor() {
2601
2717
  super(...arguments);
2602
2718
  this.state = { hasError: false };
@@ -2626,7 +2742,7 @@ var PlayerErrorBoundary = class extends React10.Component {
2626
2742
  }
2627
2743
  };
2628
2744
  var ToastProvider = ToastPrimitives__namespace.Provider;
2629
- var ToastViewport = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2745
+ var ToastViewport = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2630
2746
  ToastPrimitives__namespace.Viewport,
2631
2747
  {
2632
2748
  ref,
@@ -2652,7 +2768,7 @@ var toastVariants = classVarianceAuthority.cva(
2652
2768
  }
2653
2769
  }
2654
2770
  );
2655
- var Toast = React10__namespace.forwardRef(({ className, variant, ...props }, ref) => {
2771
+ var Toast = React11__namespace.forwardRef(({ className, variant, ...props }, ref) => {
2656
2772
  return /* @__PURE__ */ jsxRuntime.jsx(
2657
2773
  ToastPrimitives__namespace.Root,
2658
2774
  {
@@ -2663,7 +2779,7 @@ var Toast = React10__namespace.forwardRef(({ className, variant, ...props }, ref
2663
2779
  );
2664
2780
  });
2665
2781
  Toast.displayName = ToastPrimitives__namespace.Root.displayName;
2666
- var ToastAction = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2782
+ var ToastAction = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2667
2783
  ToastPrimitives__namespace.Action,
2668
2784
  {
2669
2785
  ref,
@@ -2675,7 +2791,7 @@ var ToastAction = React10__namespace.forwardRef(({ className, ...props }, ref) =
2675
2791
  }
2676
2792
  ));
2677
2793
  ToastAction.displayName = ToastPrimitives__namespace.Action.displayName;
2678
- var ToastClose = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2794
+ var ToastClose = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2679
2795
  ToastPrimitives__namespace.Close,
2680
2796
  {
2681
2797
  ref,
@@ -2689,7 +2805,7 @@ var ToastClose = React10__namespace.forwardRef(({ className, ...props }, ref) =>
2689
2805
  }
2690
2806
  ));
2691
2807
  ToastClose.displayName = ToastPrimitives__namespace.Close.displayName;
2692
- var ToastTitle = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2808
+ var ToastTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2693
2809
  ToastPrimitives__namespace.Title,
2694
2810
  {
2695
2811
  ref,
@@ -2698,7 +2814,7 @@ var ToastTitle = React10__namespace.forwardRef(({ className, ...props }, ref) =>
2698
2814
  }
2699
2815
  ));
2700
2816
  ToastTitle.displayName = ToastPrimitives__namespace.Title.displayName;
2701
- var ToastDescription = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2817
+ var ToastDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2702
2818
  ToastPrimitives__namespace.Description,
2703
2819
  {
2704
2820
  ref,
@@ -2725,7 +2841,7 @@ function Toaster() {
2725
2841
  }
2726
2842
  var started = false;
2727
2843
  function FeatureFlagsProvider({ children }) {
2728
- React10.useEffect(() => {
2844
+ React11.useEffect(() => {
2729
2845
  if (started) return;
2730
2846
  started = true;
2731
2847
  core.initFeatureFlags().catch(console.error);