@geekapps/silo-elements-nextjs 0.2.69 → 0.3.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.
@@ -118,6 +118,7 @@ function Video({
118
118
  const containerRef = useRef(null);
119
119
  const chromeRef = useRef(null);
120
120
  const playerRef = useRef(null);
121
+ const showAgeRatingRef = useRef(null);
121
122
  const videoRef = useRef(null);
122
123
  const progressRef = useRef(null);
123
124
  const hlsRef = useRef(null);
@@ -130,7 +131,6 @@ function Video({
130
131
  const [selectedAudio, setSelectedAudio] = useState(0);
131
132
  const [settingsOpen, setSettingsOpen] = useState(false);
132
133
  const [settingsVisible, setSettingsVisible] = useState(false);
133
- const settingsCloseTimerRef = useRef(null);
134
134
  const [settingsTab, setSettingsTab] = useState("root");
135
135
  const [playbackRate, setPlaybackRate] = useState(1);
136
136
  const [subtitleStyle, setSubtitleStyle] = useState({
@@ -178,12 +178,20 @@ function Video({
178
178
  const dragPointerIdRef = useRef(null);
179
179
  const [isPlaying, setIsPlaying] = useState(false);
180
180
  const [hasPlayed, setHasPlayed] = useState(false);
181
- const [ageRatingOverlay, setAgeRatingOverlay] = useState(false);
182
- const ageRatingTimerRef = useRef(null);
181
+ const ageRatingTlRef = useRef(null);
182
+ const ageRatingPauseTimerRef = useRef(null);
183
+ const ageRatingWrapRef = useRef(null);
184
+ const ageRatingIconRef = useRef(null);
185
+ const ageRatingTextRef = useRef(null);
183
186
  const [clickIcon, setClickIcon] = useState(null);
184
187
  const clickIconTimerRef = useRef(null);
188
+ const clickIconRef = useRef(null);
185
189
  const [isLoading, setIsLoading] = useState(true);
186
190
  const [controlsVisible, setControlsVisible] = useState(true);
191
+ const settingsPanelRef = useRef(null);
192
+ const settingsContentRef = useRef(null);
193
+ const progressTrackRef = useRef(null);
194
+ const progressThumbRef = useRef(null);
187
195
  const [volume, setVolume] = useState(defaultVolume);
188
196
  const [isMuted, setIsMuted] = useState(false);
189
197
  const [isFullscreen, setIsFullscreen] = useState(false);
@@ -249,6 +257,9 @@ function Video({
249
257
  setControlsVisible(false);
250
258
  }, 2400);
251
259
  }
260
+ if (video?.paused) {
261
+ showAgeRatingRef.current?.();
262
+ }
252
263
  }, [autoHideControls]);
253
264
  useEffect(() => {
254
265
  if (!containerRef.current) return;
@@ -273,13 +284,96 @@ function Video({
273
284
  const openSettings = useCallback(() => {
274
285
  setSettingsOpen(true);
275
286
  setSettingsTab("root");
276
- window.setTimeout(() => setSettingsVisible(true), 10);
287
+ setSettingsVisible(true);
277
288
  }, []);
278
289
  const closeSettings = useCallback(() => {
279
- setSettingsVisible(false);
280
- if (settingsCloseTimerRef.current) window.clearTimeout(settingsCloseTimerRef.current);
281
- settingsCloseTimerRef.current = window.setTimeout(() => setSettingsOpen(false), 200);
290
+ const panel = settingsPanelRef.current;
291
+ if (panel) {
292
+ gsap.killTweensOf(panel);
293
+ gsap.to(panel, {
294
+ opacity: 0,
295
+ y: 8,
296
+ scale: 0.97,
297
+ duration: 0.18,
298
+ ease: "power2.in",
299
+ onComplete: () => {
300
+ setSettingsOpen(false);
301
+ setSettingsVisible(false);
302
+ }
303
+ });
304
+ } else {
305
+ setSettingsOpen(false);
306
+ setSettingsVisible(false);
307
+ }
282
308
  }, []);
309
+ useEffect(() => {
310
+ if (!settingsOpen) return;
311
+ const panel = settingsPanelRef.current;
312
+ if (!panel) return;
313
+ gsap.killTweensOf(panel);
314
+ gsap.fromTo(
315
+ panel,
316
+ { opacity: 0, y: 8, scale: 0.97 },
317
+ { opacity: 1, y: 0, scale: 1, duration: 0.2, ease: "power2.out" }
318
+ );
319
+ }, [settingsOpen]);
320
+ useEffect(() => {
321
+ const el = settingsContentRef.current;
322
+ if (!el) return;
323
+ gsap.killTweensOf(el);
324
+ gsap.fromTo(
325
+ el,
326
+ { opacity: 0, x: 12 },
327
+ { opacity: 1, x: 0, duration: 0.18, ease: "power2.out" }
328
+ );
329
+ }, [settingsTab]);
330
+ useEffect(() => {
331
+ const el = chromeRef.current;
332
+ const player = playerRef.current;
333
+ if (!el) return;
334
+ gsap.killTweensOf(el);
335
+ gsap.to(el, {
336
+ opacity: controlsVisible ? 1 : 0,
337
+ duration: 0.2,
338
+ ease: "power1.inOut"
339
+ });
340
+ el.style.pointerEvents = controlsVisible ? "" : "none";
341
+ if (player) {
342
+ player.style.cursor = controlsVisible ? "" : "none";
343
+ }
344
+ }, [controlsVisible]);
345
+ useEffect(() => {
346
+ const el = clickIconRef.current;
347
+ if (!el) return;
348
+ gsap.killTweensOf(el);
349
+ if (clickIcon) {
350
+ gsap.to(el, { opacity: 1, duration: 0.08, ease: "power1.in" });
351
+ } else {
352
+ gsap.to(el, { opacity: 0, duration: 0.45, ease: "power1.out" });
353
+ }
354
+ }, [clickIcon]);
355
+ useEffect(() => {
356
+ const track = progressTrackRef.current;
357
+ const thumb = progressThumbRef.current;
358
+ const container = progressRef.current;
359
+ const targetH = isDragging ? 7 : isHoveringProgress ? 5 : 3;
360
+ const targetW = isDragging ? 18 : isHoveringProgress ? 14 : 10;
361
+ if (track) {
362
+ gsap.killTweensOf(track);
363
+ gsap.to(track, { height: targetH, duration: 0.15, ease: "power1.inOut" });
364
+ }
365
+ if (thumb) {
366
+ gsap.killTweensOf(thumb);
367
+ gsap.to(thumb, { width: targetW, height: targetW, duration: 0.2, ease: "back.out(1.7)" });
368
+ }
369
+ if (container) {
370
+ const markers = container.querySelectorAll("[data-chapter-marker]");
371
+ markers.forEach((el) => {
372
+ gsap.killTweensOf(el);
373
+ gsap.to(el, { height: targetH, duration: 0.15, ease: "power1.inOut" });
374
+ });
375
+ }
376
+ }, [isDragging, isHoveringProgress]);
283
377
  useEffect(() => {
284
378
  if (sourceIndex >= parsed.sources.length) {
285
379
  setSourceIndex(initialSourceIndex);
@@ -303,14 +397,51 @@ function Video({
303
397
  setDuration(Number.isFinite(video.duration) ? video.duration : 0);
304
398
  applySubtitleMode(subtitleMode);
305
399
  };
400
+ const hideAgeRating = () => {
401
+ const wrap = ageRatingWrapRef.current;
402
+ if (!wrap) return;
403
+ if (ageRatingTlRef.current) ageRatingTlRef.current.kill();
404
+ gsap.to(wrap, { opacity: 0, duration: 0.4, ease: "power2.inOut" });
405
+ };
406
+ const showAgeRating = () => {
407
+ if (!parsed.ageRating?.data) return;
408
+ const wrap = ageRatingWrapRef.current;
409
+ const icon = ageRatingIconRef.current;
410
+ const text = ageRatingTextRef.current;
411
+ if (!wrap) return;
412
+ if (ageRatingTlRef.current) ageRatingTlRef.current.kill();
413
+ const HOLD = 12;
414
+ const tl = gsap.timeline({ delay: 0.6 });
415
+ tl.fromTo(wrap, { opacity: 0 }, { opacity: 1, duration: 0.5, ease: "power2.out" });
416
+ if (icon) {
417
+ tl.fromTo(
418
+ icon,
419
+ { opacity: 0, scale: 0.7 },
420
+ { opacity: 1, scale: 1, duration: 0.55, ease: "back.out(1.6)" },
421
+ "-=0.2"
422
+ );
423
+ }
424
+ if (text) {
425
+ tl.fromTo(
426
+ text,
427
+ { opacity: 0, x: -10 },
428
+ { opacity: 1, x: 0, duration: 0.4, ease: "power2.out" },
429
+ "-=0.25"
430
+ );
431
+ }
432
+ tl.to({}, { duration: HOLD });
433
+ tl.to(wrap, { opacity: 0, duration: 0.7, ease: "power2.inOut" });
434
+ ageRatingTlRef.current = tl;
435
+ };
436
+ showAgeRatingRef.current = showAgeRating;
306
437
  const onPlay = () => {
307
438
  setIsPlaying(true);
439
+ if (ageRatingPauseTimerRef.current) {
440
+ window.clearTimeout(ageRatingPauseTimerRef.current);
441
+ ageRatingPauseTimerRef.current = null;
442
+ }
308
443
  setHasPlayed((prev) => {
309
- if (!prev && parsed.ageRating?.data) {
310
- setAgeRatingOverlay(true);
311
- if (ageRatingTimerRef.current) window.clearTimeout(ageRatingTimerRef.current);
312
- ageRatingTimerRef.current = window.setTimeout(() => setAgeRatingOverlay(false), 4e3);
313
- }
444
+ if (!prev) showAgeRating();
314
445
  return true;
315
446
  });
316
447
  showControlsTemporarily();
@@ -318,6 +449,14 @@ function Video({
318
449
  const onPause = () => {
319
450
  setIsPlaying(false);
320
451
  setControlsVisible(true);
452
+ hideAgeRating();
453
+ if (parsed.ageRating?.data) {
454
+ if (ageRatingPauseTimerRef.current) window.clearTimeout(ageRatingPauseTimerRef.current);
455
+ ageRatingPauseTimerRef.current = window.setTimeout(() => {
456
+ showAgeRating();
457
+ ageRatingPauseTimerRef.current = null;
458
+ }, 60 * 1e3);
459
+ }
321
460
  };
322
461
  const onWaiting = () => setIsLoading(true);
323
462
  const onCanPlay = () => setIsLoading(false);
@@ -341,6 +480,8 @@ function Video({
341
480
  video.removeEventListener("waiting", onWaiting);
342
481
  video.removeEventListener("canplay", onCanPlay);
343
482
  video.removeEventListener("ended", onEnded);
483
+ if (ageRatingTlRef.current) ageRatingTlRef.current.kill();
484
+ if (ageRatingPauseTimerRef.current) window.clearTimeout(ageRatingPauseTimerRef.current);
344
485
  };
345
486
  }, [applySubtitleMode, subtitleMode, showControlsTemporarily]);
346
487
  useEffect(() => {
@@ -992,932 +1133,940 @@ function Video({
992
1133
  "."
993
1134
  ] });
994
1135
  }
995
- return /* @__PURE__ */ jsxs(
1136
+ return /* @__PURE__ */ jsx(
996
1137
  "div",
997
1138
  {
998
1139
  ref: containerRef,
999
1140
  className: `@container mx-auto w-full max-w-6xl${className ?? ""}`,
1000
- children: [
1001
- /* @__PURE__ */ jsx("style", { children: `@keyframes silo-fade-out{0%{opacity:1}70%{opacity:1}100%{opacity:0}}` }),
1002
- /* @__PURE__ */ jsxs(
1003
- "div",
1004
- {
1005
- ref: playerRef,
1006
- tabIndex: 0,
1007
- onKeyDown: handleKeyDown,
1008
- onMouseMove: showControlsTemporarily,
1009
- onMouseLeave: () => {
1141
+ children: /* @__PURE__ */ jsxs(
1142
+ "div",
1143
+ {
1144
+ ref: playerRef,
1145
+ tabIndex: 0,
1146
+ onKeyDown: handleKeyDown,
1147
+ onMouseMove: showControlsTemporarily,
1148
+ onMouseLeave: () => {
1149
+ closeSettings();
1150
+ if (isPlaying && autoHideControls) {
1151
+ setControlsVisible(false);
1152
+ }
1153
+ },
1154
+ onBlur: (e) => {
1155
+ if (!e.currentTarget.contains(e.relatedTarget)) {
1010
1156
  closeSettings();
1011
- if (isPlaying && autoHideControls) {
1012
- setControlsVisible(false);
1157
+ }
1158
+ },
1159
+ onTouchStart: showControlsTemporarily,
1160
+ onTouchMove: showControlsTemporarily,
1161
+ className: "relative w-full overflow-hidden rounded-[14px] bg-black shadow-[0_30px_90px_rgba(15,15,15,0.22)] outline-none ring-1 ring-black/5",
1162
+ style: fixedHeight ? {
1163
+ height: typeof fixedHeight === "number" ? `${fixedHeight}px` : fixedHeight
1164
+ } : maxHeight ? {
1165
+ maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
1166
+ aspectRatio: "16/9"
1167
+ } : { aspectRatio: "16/9" },
1168
+ children: [
1169
+ /* @__PURE__ */ jsxs(
1170
+ "video",
1171
+ {
1172
+ ref: videoRef,
1173
+ className: `h-full w-full ${isFullscreen ? "object-contain" : "object-cover"}`,
1174
+ playsInline: true,
1175
+ preload: "metadata",
1176
+ crossOrigin: "anonymous",
1177
+ children: [
1178
+ captions.map((subtitle) => /* @__PURE__ */ jsx(
1179
+ "track",
1180
+ {
1181
+ kind: "subtitles",
1182
+ src: subtitle.src,
1183
+ srcLang: subtitle.srclang,
1184
+ label: subtitle.label,
1185
+ default: subtitle.default
1186
+ },
1187
+ `${activeSource.src}-${subtitle.srclang}-${subtitle.src}`
1188
+ )),
1189
+ parsed.storyboard?.src && /* @__PURE__ */ jsx(
1190
+ "track",
1191
+ {
1192
+ kind: "metadata",
1193
+ label: "thumbnails",
1194
+ src: parsed.storyboard.src
1195
+ }
1196
+ )
1197
+ ]
1013
1198
  }
1014
- },
1015
- onBlur: (e) => {
1016
- if (!e.currentTarget.contains(e.relatedTarget)) {
1017
- closeSettings();
1199
+ ),
1200
+ poster && !hasPlayed && /* @__PURE__ */ jsx(
1201
+ "img",
1202
+ {
1203
+ src: poster,
1204
+ "aria-hidden": true,
1205
+ className: "pointer-events-none absolute inset-0 h-full w-full object-contain bg-black"
1018
1206
  }
1019
- },
1020
- onTouchStart: showControlsTemporarily,
1021
- onTouchMove: showControlsTemporarily,
1022
- className: "relative w-full overflow-hidden rounded-[14px] bg-black shadow-[0_30px_90px_rgba(15,15,15,0.22)] outline-none ring-1 ring-black/5",
1023
- style: fixedHeight ? {
1024
- height: typeof fixedHeight === "number" ? `${fixedHeight}px` : fixedHeight
1025
- } : maxHeight ? {
1026
- maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
1027
- aspectRatio: "16/9"
1028
- } : { aspectRatio: "16/9" },
1029
- children: [
1030
- /* @__PURE__ */ jsxs(
1031
- "video",
1032
- {
1033
- ref: videoRef,
1034
- className: `h-full w-full ${isFullscreen ? "object-contain" : "object-cover"}`,
1035
- playsInline: true,
1036
- preload: "metadata",
1037
- crossOrigin: "anonymous",
1038
- children: [
1039
- captions.map((subtitle) => /* @__PURE__ */ jsx(
1040
- "track",
1041
- {
1042
- kind: "subtitles",
1043
- src: subtitle.src,
1044
- srcLang: subtitle.srclang,
1045
- label: subtitle.label,
1046
- default: subtitle.default
1047
- },
1048
- `${activeSource.src}-${subtitle.srclang}-${subtitle.src}`
1049
- )),
1050
- parsed.storyboard?.src && /* @__PURE__ */ jsx(
1051
- "track",
1052
- {
1053
- kind: "metadata",
1054
- label: "thumbnails",
1055
- src: parsed.storyboard.src
1056
- }
1057
- )
1058
- ]
1059
- }
1060
- ),
1061
- poster && !hasPlayed && /* @__PURE__ */ jsx(
1062
- "img",
1063
- {
1064
- src: poster,
1065
- "aria-hidden": true,
1066
- className: "pointer-events-none absolute inset-0 h-full w-full object-contain bg-black"
1067
- }
1068
- ),
1069
- ageRatingOverlay && parsed.ageRating?.data && (() => {
1070
- const ar = parsed.ageRating;
1071
- const regionKey = resolveAgeRatingRegion(ar.locale, ar.data);
1072
- if (!regionKey) return null;
1073
- const code = ar.data[regionKey];
1074
- const lookupKey = `${regionKey}:${code}`;
1075
- const info = ar.lookup?.[lookupKey];
1076
- return /* @__PURE__ */ jsx(
1077
- "div",
1078
- {
1079
- className: "pointer-events-none absolute inset-0 z-40 flex items-start justify-start p-4",
1080
- style: { animation: "silo-fade-out 4s forwards" },
1081
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 rounded-lg px-3 py-2", style: { background: "rgba(0,0,0,0.72)", backdropFilter: "blur(8px)" }, children: [
1082
- info?.imageUrl ? /* @__PURE__ */ jsx("img", { src: info.imageUrl, alt: code, className: "h-10 w-auto object-contain" }) : /* @__PURE__ */ jsx("span", { className: "flex h-10 min-w-10 items-center justify-center rounded border border-white/30 px-1.5 text-sm font-bold text-white", children: code }),
1083
- (info?.title || info?.description) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
1084
- info.title && /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-white", children: info.title }),
1085
- info.description && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-white/60 leading-tight max-w-48", children: info.description })
1086
- ] })
1087
- ] })
1088
- }
1089
- );
1090
- })(),
1091
- /* @__PURE__ */ jsx(
1207
+ ),
1208
+ parsed.ageRating?.data && (() => {
1209
+ const ar = parsed.ageRating;
1210
+ const regionKey = resolveAgeRatingRegion(ar.locale, ar.data);
1211
+ if (!regionKey) return null;
1212
+ const code = ar.data[regionKey];
1213
+ const lookupKey = `${regionKey}:${code}`;
1214
+ const info = ar.lookup?.[lookupKey];
1215
+ return /* @__PURE__ */ jsx(
1092
1216
  "div",
1093
1217
  {
1094
- className: "pointer-events-none absolute inset-0 z-10 grid place-items-center",
1218
+ ref: ageRatingWrapRef,
1219
+ className: "pointer-events-none absolute inset-0 z-20 flex items-start justify-start",
1095
1220
  style: {
1096
- opacity: clickIcon ? 1 : 0,
1097
- transition: clickIcon ? "opacity 0.08s ease-in" : "opacity 0.45s ease-out"
1221
+ opacity: 0,
1222
+ background: "linear-gradient(135deg, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.4) 40%, transparent 65%)"
1098
1223
  },
1099
- children: /* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.8)] @sm:size-14 @lg:size-16", children: clickIcon === "pause" ? /* @__PURE__ */ jsx(Pause, { className: "size-7 @sm:size-9 @lg:size-11", fill: "white" }) : /* @__PURE__ */ jsx(
1100
- Play,
1224
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3.5 px-5 pt-5 @sm:px-7 @sm:pt-7", children: [
1225
+ /* @__PURE__ */ jsx("div", { ref: ageRatingIconRef, style: { opacity: 0 }, children: info?.imageUrl ? /* @__PURE__ */ jsx("img", { src: info.imageUrl, alt: code, className: "h-14 w-auto object-contain @sm:h-16 drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]" }) : /* @__PURE__ */ jsx("span", { className: "flex h-14 min-w-14 items-center justify-center rounded-md border-2 border-white/40 px-2 text-lg font-bold text-white @sm:h-16 @sm:min-w-16 @sm:text-xl drop-shadow-[0_2px_8px_rgba(0,0,0,0.6)]", children: code }) }),
1226
+ (info?.title || info?.description) && /* @__PURE__ */ jsxs("div", { ref: ageRatingTextRef, className: "flex flex-col gap-0.5", style: { opacity: 0 }, children: [
1227
+ info.title && /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-white @sm:text-base drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]", children: info.title }),
1228
+ info.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-white/70 leading-snug max-w-56 @sm:text-sm @sm:max-w-72 drop-shadow-[0_1px_4px_rgba(0,0,0,0.8)]", children: info.description })
1229
+ ] })
1230
+ ] })
1231
+ }
1232
+ );
1233
+ })(),
1234
+ /* @__PURE__ */ jsx(
1235
+ "div",
1236
+ {
1237
+ ref: clickIconRef,
1238
+ className: "pointer-events-none absolute inset-0 z-10 grid place-items-center",
1239
+ style: { opacity: 0 },
1240
+ children: /* @__PURE__ */ jsx("span", { className: "grid size-10 place-items-center text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.8)] @sm:size-14 @lg:size-16", children: clickIcon === "pause" ? /* @__PURE__ */ jsx(Pause, { className: "size-7 @sm:size-9 @lg:size-11", fill: "white" }) : /* @__PURE__ */ jsx(
1241
+ Play,
1242
+ {
1243
+ className: "ml-0.5 size-7 @sm:size-9 @lg:size-11",
1244
+ fill: "white"
1245
+ }
1246
+ ) })
1247
+ }
1248
+ ),
1249
+ isLoading && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-20 grid place-items-center bg-black/10", children: /* @__PURE__ */ jsx("div", { className: "size-9 animate-spin rounded-full border-2 border-white/25 border-t-white" }) }),
1250
+ /* @__PURE__ */ jsxs(
1251
+ "div",
1252
+ {
1253
+ ref: chromeRef,
1254
+ onClick: togglePlay,
1255
+ className: `absolute inset-0 z-30 flex flex-col ${isFullscreen ? "justify-between" : "justify-end"}`,
1256
+ children: [
1257
+ isFullscreen && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-black/70 to-transparent" }),
1258
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-linear-to-t from-black/80 to-transparent" }),
1259
+ isFullscreen && /* @__PURE__ */ jsx(
1260
+ "header",
1101
1261
  {
1102
- className: "ml-0.5 size-7 @sm:size-9 @lg:size-11",
1103
- fill: "white"
1262
+ onClick: (e) => e.stopPropagation(),
1263
+ className: "relative z-10 flex items-start px-4 pt-4 text-white @sm:px-7 @sm:pt-7 @lg:px-9 @lg:pt-8",
1264
+ children: /* @__PURE__ */ jsxs("div", { children: [
1265
+ title && /* @__PURE__ */ jsx(
1266
+ "h1",
1267
+ {
1268
+ className: "text-sm font-bold tracking-wide @sm:text-base @md:text-lg @lg:text-xl",
1269
+ style: { textShadow: "0 1px 6px rgba(0,0,0,0.6)" },
1270
+ children: title
1271
+ }
1272
+ ),
1273
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-white/85 @sm:text-sm", children: description })
1274
+ ] })
1104
1275
  }
1105
- ) })
1106
- }
1107
- ),
1108
- isLoading && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-20 grid place-items-center bg-black/10", children: /* @__PURE__ */ jsx("div", { className: "size-9 animate-spin rounded-full border-2 border-white/25 border-t-white" }) }),
1109
- /* @__PURE__ */ jsxs(
1110
- "div",
1111
- {
1112
- ref: chromeRef,
1113
- onClick: togglePlay,
1114
- className: `absolute inset-0 z-30 flex flex-col transition-opacity duration-200 ${isFullscreen ? "justify-between" : "justify-end"} ${controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`,
1115
- children: [
1116
- isFullscreen && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-black/70 to-transparent" }),
1117
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-40 bg-linear-to-t from-black/80 to-transparent" }),
1118
- isFullscreen && /* @__PURE__ */ jsx(
1119
- "header",
1120
- {
1121
- onClick: (e) => e.stopPropagation(),
1122
- className: "relative z-10 flex items-start px-4 pt-4 text-white @sm:px-7 @sm:pt-7 @lg:px-9 @lg:pt-8",
1123
- children: /* @__PURE__ */ jsxs("div", { children: [
1124
- title && /* @__PURE__ */ jsx(
1125
- "h1",
1276
+ ),
1277
+ /* @__PURE__ */ jsxs(
1278
+ "footer",
1279
+ {
1280
+ onClick: (e) => e.stopPropagation(),
1281
+ className: "relative z-10 px-4 pb-4 text-white",
1282
+ children: [
1283
+ settingsOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
1284
+ /* @__PURE__ */ jsx(
1285
+ "div",
1126
1286
  {
1127
- className: "text-sm font-bold tracking-wide @sm:text-base @md:text-lg @lg:text-xl",
1128
- style: { textShadow: "0 1px 6px rgba(0,0,0,0.6)" },
1129
- children: title
1287
+ className: "fixed inset-0 z-70",
1288
+ onClick: closeSettings
1130
1289
  }
1131
1290
  ),
1132
- description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-white/85 @sm:text-sm", children: description })
1133
- ] })
1134
- }
1135
- ),
1136
- /* @__PURE__ */ jsxs(
1137
- "footer",
1138
- {
1139
- onClick: (e) => e.stopPropagation(),
1140
- className: "relative z-10 px-4 pb-4 text-white",
1141
- children: [
1142
- settingsOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
1143
- /* @__PURE__ */ jsx(
1144
- "div",
1145
- {
1146
- className: "fixed inset-0 z-70",
1147
- onClick: closeSettings
1148
- }
1149
- ),
1150
- /* @__PURE__ */ jsxs(
1151
- "div",
1152
- {
1153
- className: "absolute bottom-full right-2 z-70 mb-2 overflow-hidden rounded-2xl bg-[#1c1c1e]/95 shadow-2xl backdrop-blur-xl ring-1 ring-white/10",
1154
- style: {
1155
- width: Math.min(
1156
- 224,
1157
- Math.max(180, (playerWidth || 640) * 0.22)
1158
- ) + "px",
1159
- opacity: settingsVisible ? 1 : 0,
1160
- transform: settingsVisible ? "translateY(0) scale(1)" : "translateY(8px) scale(0.97)",
1161
- transition: "opacity 0.18s ease, transform 0.18s ease"
1162
- },
1163
- children: [
1164
- settingsTab === "root" && /* @__PURE__ */ jsx("div", { children: [
1291
+ /* @__PURE__ */ jsx(
1292
+ "div",
1293
+ {
1294
+ ref: settingsPanelRef,
1295
+ className: "absolute bottom-full right-2 z-70 mb-2 overflow-hidden rounded-2xl bg-[#1c1c1e]/95 shadow-2xl backdrop-blur-xl ring-1 ring-white/10",
1296
+ style: {
1297
+ width: Math.min(
1298
+ 224,
1299
+ Math.max(180, (playerWidth || 640) * 0.22)
1300
+ ) + "px",
1301
+ opacity: 0
1302
+ },
1303
+ children: /* @__PURE__ */ jsxs("div", { ref: settingsContentRef, children: [
1304
+ settingsTab === "root" && /* @__PURE__ */ jsx("div", { children: [
1305
+ {
1306
+ id: "quality",
1307
+ label: "Qualidade",
1308
+ value: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto"
1309
+ },
1310
+ ...captions.length > 0 ? [
1311
+ {
1312
+ id: "subtitles",
1313
+ label: "Legendas",
1314
+ value: subtitleStyle.track === "off" ? "Desligado" : captions.find(
1315
+ (s) => s.srclang === subtitleStyle.track
1316
+ )?.label ?? subtitleStyle.track
1317
+ }
1318
+ ] : [],
1319
+ ...audioTracks.length > 1 ? [
1320
+ {
1321
+ id: "audio",
1322
+ label: "\xC1udio",
1323
+ value: audioTracks.find(
1324
+ (t) => t.id === selectedAudio
1325
+ )?.label ?? ""
1326
+ }
1327
+ ] : [],
1328
+ {
1329
+ id: "playback",
1330
+ label: "Velocidade",
1331
+ value: playbackRate === 1 ? "Normal" : `${playbackRate}\xD7`
1332
+ }
1333
+ ].map((item) => /* @__PURE__ */ jsxs(
1334
+ "button",
1335
+ {
1336
+ type: "button",
1337
+ onClick: () => setSettingsTab(item.id),
1338
+ className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-sm transition hover:bg-white/8 first:pt-3.5 last:pb-3.5",
1339
+ children: [
1340
+ /* @__PURE__ */ jsx("span", { className: "text-white/80", children: item.label }),
1341
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-white/40", children: [
1342
+ item.value,
1343
+ /* @__PURE__ */ jsx(
1344
+ "svg",
1345
+ {
1346
+ className: "size-3 opacity-50",
1347
+ viewBox: "0 0 12 12",
1348
+ fill: "none",
1349
+ children: /* @__PURE__ */ jsx(
1350
+ "path",
1351
+ {
1352
+ d: "M4.5 3l3 3-3 3",
1353
+ stroke: "currentColor",
1354
+ strokeWidth: "1.5",
1355
+ strokeLinecap: "round",
1356
+ strokeLinejoin: "round"
1357
+ }
1358
+ )
1359
+ }
1360
+ )
1361
+ ] })
1362
+ ]
1363
+ },
1364
+ item.id
1365
+ )) }),
1366
+ settingsTab === "quality" && /* @__PURE__ */ jsxs("div", { children: [
1367
+ /* @__PURE__ */ jsxs(
1368
+ "button",
1369
+ {
1370
+ type: "button",
1371
+ onClick: () => setSettingsTab("root"),
1372
+ className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1373
+ children: [
1374
+ /* @__PURE__ */ jsx(
1375
+ "svg",
1376
+ {
1377
+ className: "size-3.5",
1378
+ viewBox: "0 0 12 12",
1379
+ fill: "none",
1380
+ children: /* @__PURE__ */ jsx(
1381
+ "path",
1382
+ {
1383
+ d: "M7.5 3L4.5 6l3 3",
1384
+ stroke: "currentColor",
1385
+ strokeWidth: "1.5",
1386
+ strokeLinecap: "round",
1387
+ strokeLinejoin: "round"
1388
+ }
1389
+ )
1390
+ }
1391
+ ),
1392
+ "Qualidade"
1393
+ ]
1394
+ }
1395
+ ),
1396
+ /* @__PURE__ */ jsx("div", { className: "py-1.5", children: [...qualities].reverse().map((quality) => /* @__PURE__ */ jsxs(
1397
+ "button",
1165
1398
  {
1166
- id: "quality",
1167
- label: "Qualidade",
1168
- value: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto"
1399
+ type: "button",
1400
+ onClick: () => {
1401
+ changeQuality(quality.id);
1402
+ setSettingsTab("root");
1403
+ },
1404
+ className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1405
+ children: [
1406
+ /* @__PURE__ */ jsx(
1407
+ "svg",
1408
+ {
1409
+ className: `size-4 shrink-0 ${selectedQuality === quality.id ? "text-white" : "text-transparent"}`,
1410
+ viewBox: "0 0 16 16",
1411
+ fill: "none",
1412
+ children: /* @__PURE__ */ jsx(
1413
+ "path",
1414
+ {
1415
+ d: "M3 8l3.5 3.5L13 4.5",
1416
+ stroke: "currentColor",
1417
+ strokeWidth: "1.8",
1418
+ strokeLinecap: "round",
1419
+ strokeLinejoin: "round"
1420
+ }
1421
+ )
1422
+ }
1423
+ ),
1424
+ /* @__PURE__ */ jsxs(
1425
+ "span",
1426
+ {
1427
+ className: selectedQuality === quality.id ? "font-semibold text-white" : "text-white/55",
1428
+ children: [
1429
+ quality.label,
1430
+ quality.id === "auto" ? " (ABR)" : ""
1431
+ ]
1432
+ }
1433
+ )
1434
+ ]
1169
1435
  },
1170
- ...captions.length > 0 ? [
1171
- {
1172
- id: "subtitles",
1173
- label: "Legendas",
1174
- value: subtitleStyle.track === "off" ? "Desligado" : captions.find(
1175
- (s) => s.srclang === subtitleStyle.track
1176
- )?.label ?? subtitleStyle.track
1177
- }
1178
- ] : [],
1179
- ...audioTracks.length > 1 ? [
1180
- {
1181
- id: "audio",
1182
- label: "\xC1udio",
1183
- value: audioTracks.find(
1184
- (t) => t.id === selectedAudio
1185
- )?.label ?? ""
1186
- }
1187
- ] : [],
1436
+ quality.id
1437
+ )) })
1438
+ ] }),
1439
+ settingsTab === "subtitles" && /* @__PURE__ */ jsxs("div", { children: [
1440
+ /* @__PURE__ */ jsxs(
1441
+ "button",
1188
1442
  {
1189
- id: "playback",
1190
- label: "Velocidade",
1191
- value: playbackRate === 1 ? "Normal" : `${playbackRate}\xD7`
1443
+ type: "button",
1444
+ onClick: () => setSettingsTab("root"),
1445
+ className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1446
+ children: [
1447
+ /* @__PURE__ */ jsx(
1448
+ "svg",
1449
+ {
1450
+ className: "size-3.5",
1451
+ viewBox: "0 0 12 12",
1452
+ fill: "none",
1453
+ children: /* @__PURE__ */ jsx(
1454
+ "path",
1455
+ {
1456
+ d: "M7.5 3L4.5 6l3 3",
1457
+ stroke: "currentColor",
1458
+ strokeWidth: "1.5",
1459
+ strokeLinecap: "round",
1460
+ strokeLinejoin: "round"
1461
+ }
1462
+ )
1463
+ }
1464
+ ),
1465
+ "Legendas"
1466
+ ]
1192
1467
  }
1193
- ].map((item) => /* @__PURE__ */ jsxs(
1468
+ ),
1469
+ /* @__PURE__ */ jsx("div", { className: "py-1.5", children: [
1470
+ { srclang: "off", label: "Desligado" },
1471
+ ...captions
1472
+ ].map((s) => /* @__PURE__ */ jsxs(
1194
1473
  "button",
1195
1474
  {
1196
1475
  type: "button",
1197
- onClick: () => setSettingsTab(item.id),
1198
- className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-sm transition hover:bg-white/8 first:pt-3.5 last:pb-3.5",
1476
+ onClick: () => {
1477
+ const next = s.srclang === "off" ? "off" : s.srclang;
1478
+ setSubtitleMode(next);
1479
+ setSubtitleStyle((st) => ({
1480
+ ...st,
1481
+ track: next
1482
+ }));
1483
+ },
1484
+ className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1199
1485
  children: [
1200
- /* @__PURE__ */ jsx("span", { className: "text-white/80", children: item.label }),
1201
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-white/40", children: [
1202
- item.value,
1203
- /* @__PURE__ */ jsx(
1204
- "svg",
1205
- {
1206
- className: "size-3 opacity-50",
1207
- viewBox: "0 0 12 12",
1208
- fill: "none",
1209
- children: /* @__PURE__ */ jsx(
1210
- "path",
1211
- {
1212
- d: "M4.5 3l3 3-3 3",
1213
- stroke: "currentColor",
1214
- strokeWidth: "1.5",
1215
- strokeLinecap: "round",
1216
- strokeLinejoin: "round"
1217
- }
1218
- )
1219
- }
1220
- )
1221
- ] })
1486
+ /* @__PURE__ */ jsx(
1487
+ "svg",
1488
+ {
1489
+ className: `size-4 shrink-0 ${subtitleStyle.track === s.srclang ? "text-white" : "text-transparent"}`,
1490
+ viewBox: "0 0 16 16",
1491
+ fill: "none",
1492
+ children: /* @__PURE__ */ jsx(
1493
+ "path",
1494
+ {
1495
+ d: "M3 8l3.5 3.5L13 4.5",
1496
+ stroke: "currentColor",
1497
+ strokeWidth: "1.8",
1498
+ strokeLinecap: "round",
1499
+ strokeLinejoin: "round"
1500
+ }
1501
+ )
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx(
1505
+ "span",
1506
+ {
1507
+ className: subtitleStyle.track === s.srclang ? "font-semibold text-white" : "text-white/55",
1508
+ children: s.label
1509
+ }
1510
+ )
1222
1511
  ]
1223
1512
  },
1224
- item.id
1513
+ s.srclang
1225
1514
  )) }),
1226
- settingsTab === "quality" && /* @__PURE__ */ jsxs("div", { children: [
1227
- /* @__PURE__ */ jsxs(
1228
- "button",
1229
- {
1230
- type: "button",
1231
- onClick: () => setSettingsTab("root"),
1232
- className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1233
- children: [
1234
- /* @__PURE__ */ jsx(
1235
- "svg",
1236
- {
1237
- className: "size-3.5",
1238
- viewBox: "0 0 12 12",
1239
- fill: "none",
1240
- children: /* @__PURE__ */ jsx(
1241
- "path",
1242
- {
1243
- d: "M7.5 3L4.5 6l3 3",
1244
- stroke: "currentColor",
1245
- strokeWidth: "1.5",
1246
- strokeLinecap: "round",
1247
- strokeLinejoin: "round"
1248
- }
1249
- )
1250
- }
1251
- ),
1252
- "Qualidade"
1253
- ]
1254
- }
1255
- ),
1256
- /* @__PURE__ */ jsx("div", { className: "py-1.5", children: [...qualities].reverse().map((quality) => /* @__PURE__ */ jsxs(
1257
- "button",
1258
- {
1259
- type: "button",
1260
- onClick: () => {
1261
- changeQuality(quality.id);
1262
- setSettingsTab("root");
1515
+ subtitleStyle.track !== "off" && /* @__PURE__ */ jsxs(
1516
+ "button",
1517
+ {
1518
+ type: "button",
1519
+ onClick: () => setSettingsTab("subtitles-style"),
1520
+ className: "flex w-full items-center justify-between gap-3 border-t border-white/8 px-4 py-3 text-sm transition hover:bg-white/8",
1521
+ children: [
1522
+ /* @__PURE__ */ jsx("span", { className: "text-white/60", children: "Apar\xEAncia" }),
1523
+ /* @__PURE__ */ jsx(
1524
+ "svg",
1525
+ {
1526
+ className: "size-3 opacity-50",
1527
+ viewBox: "0 0 12 12",
1528
+ fill: "none",
1529
+ children: /* @__PURE__ */ jsx(
1530
+ "path",
1531
+ {
1532
+ d: "M4.5 3l3 3-3 3",
1533
+ stroke: "currentColor",
1534
+ strokeWidth: "1.5",
1535
+ strokeLinecap: "round",
1536
+ strokeLinejoin: "round"
1537
+ }
1538
+ )
1539
+ }
1540
+ )
1541
+ ]
1542
+ }
1543
+ )
1544
+ ] }),
1545
+ settingsTab === "subtitles-style" && /* @__PURE__ */ jsxs("div", { children: [
1546
+ /* @__PURE__ */ jsxs(
1547
+ "button",
1548
+ {
1549
+ type: "button",
1550
+ onClick: () => setSettingsTab("subtitles"),
1551
+ className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1552
+ children: [
1553
+ /* @__PURE__ */ jsx(
1554
+ "svg",
1555
+ {
1556
+ className: "size-3.5",
1557
+ viewBox: "0 0 12 12",
1558
+ fill: "none",
1559
+ children: /* @__PURE__ */ jsx(
1560
+ "path",
1561
+ {
1562
+ d: "M7.5 3L4.5 6l3 3",
1563
+ stroke: "currentColor",
1564
+ strokeWidth: "1.5",
1565
+ strokeLinecap: "round",
1566
+ strokeLinejoin: "round"
1567
+ }
1568
+ )
1569
+ }
1570
+ ),
1571
+ "Apar\xEAncia"
1572
+ ]
1573
+ }
1574
+ ),
1575
+ /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 flex flex-col gap-3", children: [
1576
+ /* @__PURE__ */ jsxs("div", { children: [
1577
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Tamanho" }),
1578
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: ["small", "medium", "large", "xlarge"].map((s) => /* @__PURE__ */ jsx(
1579
+ "button",
1580
+ {
1581
+ type: "button",
1582
+ onClick: () => setSubtitleStyle((st) => ({ ...st, size: s })),
1583
+ className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition${subtitleStyle.size === s ? "bg-white/20 text-white" : "text-white/45 hover:bg-white/10"}`,
1584
+ children: s === "small" ? "P" : s === "medium" ? "M" : s === "large" ? "G" : "GG"
1263
1585
  },
1264
- className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1265
- children: [
1266
- /* @__PURE__ */ jsx(
1267
- "svg",
1268
- {
1269
- className: `size-4 shrink-0 ${selectedQuality === quality.id ? "text-white" : "text-transparent"}`,
1270
- viewBox: "0 0 16 16",
1271
- fill: "none",
1272
- children: /* @__PURE__ */ jsx(
1273
- "path",
1274
- {
1275
- d: "M3 8l3.5 3.5L13 4.5",
1276
- stroke: "currentColor",
1277
- strokeWidth: "1.8",
1278
- strokeLinecap: "round",
1279
- strokeLinejoin: "round"
1280
- }
1281
- )
1282
- }
1283
- ),
1284
- /* @__PURE__ */ jsxs(
1285
- "span",
1286
- {
1287
- className: selectedQuality === quality.id ? "font-semibold text-white" : "text-white/55",
1288
- children: [
1289
- quality.label,
1290
- quality.id === "auto" ? " (ABR)" : ""
1291
- ]
1292
- }
1293
- )
1294
- ]
1295
- },
1296
- quality.id
1297
- )) })
1298
- ] }),
1299
- settingsTab === "subtitles" && /* @__PURE__ */ jsxs("div", { children: [
1300
- /* @__PURE__ */ jsxs(
1301
- "button",
1302
- {
1303
- type: "button",
1304
- onClick: () => setSettingsTab("root"),
1305
- className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1306
- children: [
1307
- /* @__PURE__ */ jsx(
1308
- "svg",
1309
- {
1310
- className: "size-3.5",
1311
- viewBox: "0 0 12 12",
1312
- fill: "none",
1313
- children: /* @__PURE__ */ jsx(
1314
- "path",
1315
- {
1316
- d: "M7.5 3L4.5 6l3 3",
1317
- stroke: "currentColor",
1318
- strokeWidth: "1.5",
1319
- strokeLinecap: "round",
1320
- strokeLinejoin: "round"
1321
- }
1322
- )
1323
- }
1324
- ),
1325
- "Legendas"
1326
- ]
1327
- }
1328
- ),
1329
- /* @__PURE__ */ jsx("div", { className: "py-1.5", children: [
1330
- { srclang: "off", label: "Desligado" },
1331
- ...captions
1332
- ].map((s) => /* @__PURE__ */ jsxs(
1333
- "button",
1334
- {
1335
- type: "button",
1336
- onClick: () => {
1337
- const next = s.srclang === "off" ? "off" : s.srclang;
1338
- setSubtitleMode(next);
1339
- setSubtitleStyle((st) => ({
1586
+ s
1587
+ )) })
1588
+ ] }),
1589
+ /* @__PURE__ */ jsxs("div", { children: [
1590
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Cor" }),
1591
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: [
1592
+ ["white", "Branco", "#fff"],
1593
+ ["yellow", "Amarelo", "#facc15"],
1594
+ ["cyan", "Ciano", "#22d3ee"]
1595
+ ].map(([val, label, color]) => /* @__PURE__ */ jsx(
1596
+ "button",
1597
+ {
1598
+ type: "button",
1599
+ onClick: () => setSubtitleStyle((st) => ({
1340
1600
  ...st,
1341
- track: next
1342
- }));
1601
+ color: val
1602
+ })),
1603
+ className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition ring-1${subtitleStyle.color === val ? "ring-white/40" : "ring-transparent hover:ring-white/15"}`,
1604
+ style: { color },
1605
+ children: label
1343
1606
  },
1344
- className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1345
- children: [
1346
- /* @__PURE__ */ jsx(
1347
- "svg",
1348
- {
1349
- className: `size-4 shrink-0 ${subtitleStyle.track === s.srclang ? "text-white" : "text-transparent"}`,
1350
- viewBox: "0 0 16 16",
1351
- fill: "none",
1352
- children: /* @__PURE__ */ jsx(
1353
- "path",
1354
- {
1355
- d: "M3 8l3.5 3.5L13 4.5",
1356
- stroke: "currentColor",
1357
- strokeWidth: "1.8",
1358
- strokeLinecap: "round",
1359
- strokeLinejoin: "round"
1360
- }
1361
- )
1362
- }
1363
- ),
1364
- /* @__PURE__ */ jsx(
1365
- "span",
1366
- {
1367
- className: subtitleStyle.track === s.srclang ? "font-semibold text-white" : "text-white/55",
1368
- children: s.label
1369
- }
1370
- )
1371
- ]
1607
+ val
1608
+ )) })
1609
+ ] }),
1610
+ /* @__PURE__ */ jsxs("div", { children: [
1611
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Fundo" }),
1612
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: [
1613
+ ["none", "Nenhum"],
1614
+ ["semi", "Semi"],
1615
+ ["solid", "S\xF3lido"]
1616
+ ].map(([val, label]) => /* @__PURE__ */ jsx(
1617
+ "button",
1618
+ {
1619
+ type: "button",
1620
+ onClick: () => setSubtitleStyle((st) => ({ ...st, bg: val })),
1621
+ className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition${subtitleStyle.bg === val ? "bg-white/20 text-white" : "text-white/45 hover:bg-white/10"}`,
1622
+ children: label
1623
+ },
1624
+ val
1625
+ )) })
1626
+ ] })
1627
+ ] })
1628
+ ] }),
1629
+ settingsTab === "audio" && /* @__PURE__ */ jsxs("div", { children: [
1630
+ /* @__PURE__ */ jsxs(
1631
+ "button",
1632
+ {
1633
+ type: "button",
1634
+ onClick: () => setSettingsTab("root"),
1635
+ className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1636
+ children: [
1637
+ /* @__PURE__ */ jsx(
1638
+ "svg",
1639
+ {
1640
+ className: "size-3.5",
1641
+ viewBox: "0 0 12 12",
1642
+ fill: "none",
1643
+ children: /* @__PURE__ */ jsx(
1644
+ "path",
1645
+ {
1646
+ d: "M7.5 3L4.5 6l3 3",
1647
+ stroke: "currentColor",
1648
+ strokeWidth: "1.5",
1649
+ strokeLinecap: "round",
1650
+ strokeLinejoin: "round"
1651
+ }
1652
+ )
1653
+ }
1654
+ ),
1655
+ "\xC1udio"
1656
+ ]
1657
+ }
1658
+ ),
1659
+ /* @__PURE__ */ jsx("div", { className: "py-1.5", children: audioTracks.map((track) => /* @__PURE__ */ jsxs(
1660
+ "button",
1661
+ {
1662
+ type: "button",
1663
+ onClick: () => {
1664
+ changeAudio(track.id);
1665
+ setSettingsTab("root");
1372
1666
  },
1373
- s.srclang
1374
- )) }),
1375
- subtitleStyle.track !== "off" && /* @__PURE__ */ jsxs(
1376
- "button",
1377
- {
1378
- type: "button",
1379
- onClick: () => setSettingsTab("subtitles-style"),
1380
- className: "flex w-full items-center justify-between gap-3 border-t border-white/8 px-4 py-3 text-sm transition hover:bg-white/8",
1381
- children: [
1382
- /* @__PURE__ */ jsx("span", { className: "text-white/60", children: "Apar\xEAncia" }),
1383
- /* @__PURE__ */ jsx(
1384
- "svg",
1385
- {
1386
- className: "size-3 opacity-50",
1387
- viewBox: "0 0 12 12",
1388
- fill: "none",
1389
- children: /* @__PURE__ */ jsx(
1390
- "path",
1391
- {
1392
- d: "M4.5 3l3 3-3 3",
1393
- stroke: "currentColor",
1394
- strokeWidth: "1.5",
1395
- strokeLinecap: "round",
1396
- strokeLinejoin: "round"
1397
- }
1398
- )
1399
- }
1400
- )
1401
- ]
1402
- }
1403
- )
1404
- ] }),
1405
- settingsTab === "subtitles-style" && /* @__PURE__ */ jsxs("div", { children: [
1406
- /* @__PURE__ */ jsxs(
1407
- "button",
1408
- {
1409
- type: "button",
1410
- onClick: () => setSettingsTab("subtitles"),
1411
- className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1412
- children: [
1413
- /* @__PURE__ */ jsx(
1414
- "svg",
1415
- {
1416
- className: "size-3.5",
1417
- viewBox: "0 0 12 12",
1418
- fill: "none",
1419
- children: /* @__PURE__ */ jsx(
1420
- "path",
1421
- {
1422
- d: "M7.5 3L4.5 6l3 3",
1423
- stroke: "currentColor",
1424
- strokeWidth: "1.5",
1425
- strokeLinecap: "round",
1426
- strokeLinejoin: "round"
1427
- }
1428
- )
1429
- }
1430
- ),
1431
- "Apar\xEAncia"
1432
- ]
1433
- }
1434
- ),
1435
- /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 flex flex-col gap-3", children: [
1436
- /* @__PURE__ */ jsxs("div", { children: [
1437
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Tamanho" }),
1438
- /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: ["small", "medium", "large", "xlarge"].map((s) => /* @__PURE__ */ jsx(
1439
- "button",
1667
+ className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1668
+ children: [
1669
+ /* @__PURE__ */ jsx(
1670
+ "svg",
1440
1671
  {
1441
- type: "button",
1442
- onClick: () => setSubtitleStyle((st) => ({ ...st, size: s })),
1443
- className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition${subtitleStyle.size === s ? "bg-white/20 text-white" : "text-white/45 hover:bg-white/10"}`,
1444
- children: s === "small" ? "P" : s === "medium" ? "M" : s === "large" ? "G" : "GG"
1445
- },
1446
- s
1447
- )) })
1448
- ] }),
1449
- /* @__PURE__ */ jsxs("div", { children: [
1450
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Cor" }),
1451
- /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: [
1452
- ["white", "Branco", "#fff"],
1453
- ["yellow", "Amarelo", "#facc15"],
1454
- ["cyan", "Ciano", "#22d3ee"]
1455
- ].map(([val, label, color]) => /* @__PURE__ */ jsx(
1456
- "button",
1672
+ className: `size-4 shrink-0 ${selectedAudio === track.id ? "text-white" : "text-transparent"}`,
1673
+ viewBox: "0 0 16 16",
1674
+ fill: "none",
1675
+ children: /* @__PURE__ */ jsx(
1676
+ "path",
1677
+ {
1678
+ d: "M3 8l3.5 3.5L13 4.5",
1679
+ stroke: "currentColor",
1680
+ strokeWidth: "1.8",
1681
+ strokeLinecap: "round",
1682
+ strokeLinejoin: "round"
1683
+ }
1684
+ )
1685
+ }
1686
+ ),
1687
+ /* @__PURE__ */ jsx(
1688
+ "span",
1457
1689
  {
1458
- type: "button",
1459
- onClick: () => setSubtitleStyle((st) => ({
1460
- ...st,
1461
- color: val
1462
- })),
1463
- className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition ring-1${subtitleStyle.color === val ? "ring-white/40" : "ring-transparent hover:ring-white/15"}`,
1464
- style: { color },
1465
- children: label
1466
- },
1467
- val
1468
- )) })
1469
- ] }),
1470
- /* @__PURE__ */ jsxs("div", { children: [
1471
- /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-semibold text-white/40 uppercase tracking-wide", children: "Fundo" }),
1472
- /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: [
1473
- ["none", "Nenhum"],
1474
- ["semi", "Semi"],
1475
- ["solid", "S\xF3lido"]
1476
- ].map(([val, label]) => /* @__PURE__ */ jsx(
1477
- "button",
1690
+ className: selectedAudio === track.id ? "font-semibold text-white" : "text-white/55",
1691
+ children: track.label
1692
+ }
1693
+ )
1694
+ ]
1695
+ },
1696
+ track.id
1697
+ )) })
1698
+ ] }),
1699
+ settingsTab === "playback" && /* @__PURE__ */ jsxs("div", { children: [
1700
+ /* @__PURE__ */ jsxs(
1701
+ "button",
1702
+ {
1703
+ type: "button",
1704
+ onClick: () => setSettingsTab("root"),
1705
+ className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1706
+ children: [
1707
+ /* @__PURE__ */ jsx(
1708
+ "svg",
1478
1709
  {
1479
- type: "button",
1480
- onClick: () => setSubtitleStyle((st) => ({ ...st, bg: val })),
1481
- className: `flex-1 rounded-lg py-1.5 text-xs font-medium transition${subtitleStyle.bg === val ? "bg-white/20 text-white" : "text-white/45 hover:bg-white/10"}`,
1482
- children: label
1483
- },
1484
- val
1485
- )) })
1486
- ] })
1487
- ] })
1488
- ] }),
1489
- settingsTab === "audio" && /* @__PURE__ */ jsxs("div", { children: [
1490
- /* @__PURE__ */ jsxs(
1491
- "button",
1492
- {
1493
- type: "button",
1494
- onClick: () => setSettingsTab("root"),
1495
- className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1496
- children: [
1497
- /* @__PURE__ */ jsx(
1498
- "svg",
1499
- {
1500
- className: "size-3.5",
1501
- viewBox: "0 0 12 12",
1502
- fill: "none",
1503
- children: /* @__PURE__ */ jsx(
1504
- "path",
1505
- {
1506
- d: "M7.5 3L4.5 6l3 3",
1507
- stroke: "currentColor",
1508
- strokeWidth: "1.5",
1509
- strokeLinecap: "round",
1510
- strokeLinejoin: "round"
1511
- }
1512
- )
1513
- }
1514
- ),
1515
- "\xC1udio"
1516
- ]
1517
- }
1518
- ),
1519
- /* @__PURE__ */ jsx("div", { className: "py-1.5", children: audioTracks.map((track) => /* @__PURE__ */ jsxs(
1520
- "button",
1521
- {
1522
- type: "button",
1523
- onClick: () => {
1524
- changeAudio(track.id);
1525
- setSettingsTab("root");
1526
- },
1527
- className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1528
- children: [
1529
- /* @__PURE__ */ jsx(
1530
- "svg",
1531
- {
1532
- className: `size-4 shrink-0 ${selectedAudio === track.id ? "text-white" : "text-transparent"}`,
1533
- viewBox: "0 0 16 16",
1534
- fill: "none",
1535
- children: /* @__PURE__ */ jsx(
1536
- "path",
1537
- {
1538
- d: "M3 8l3.5 3.5L13 4.5",
1539
- stroke: "currentColor",
1540
- strokeWidth: "1.8",
1541
- strokeLinecap: "round",
1542
- strokeLinejoin: "round"
1543
- }
1544
- )
1545
- }
1546
- ),
1547
- /* @__PURE__ */ jsx(
1548
- "span",
1549
- {
1550
- className: selectedAudio === track.id ? "font-semibold text-white" : "text-white/55",
1551
- children: track.label
1552
- }
1553
- )
1554
- ]
1710
+ className: "size-3.5",
1711
+ viewBox: "0 0 12 12",
1712
+ fill: "none",
1713
+ children: /* @__PURE__ */ jsx(
1714
+ "path",
1715
+ {
1716
+ d: "M7.5 3L4.5 6l3 3",
1717
+ stroke: "currentColor",
1718
+ strokeWidth: "1.5",
1719
+ strokeLinecap: "round",
1720
+ strokeLinejoin: "round"
1721
+ }
1722
+ )
1723
+ }
1724
+ ),
1725
+ "Velocidade"
1726
+ ]
1727
+ }
1728
+ ),
1729
+ /* @__PURE__ */ jsx("div", { className: "py-1.5", children: PLAYBACK_SPEEDS.map((speed) => /* @__PURE__ */ jsxs(
1730
+ "button",
1731
+ {
1732
+ type: "button",
1733
+ onClick: () => {
1734
+ setPlaybackRate(speed);
1735
+ closeSettings();
1555
1736
  },
1556
- track.id
1557
- )) })
1558
- ] }),
1559
- settingsTab === "playback" && /* @__PURE__ */ jsxs("div", { children: [
1560
- /* @__PURE__ */ jsxs(
1561
- "button",
1737
+ className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1738
+ children: [
1739
+ /* @__PURE__ */ jsx(
1740
+ "svg",
1741
+ {
1742
+ className: `size-4 shrink-0 ${playbackRate === speed ? "text-white" : "text-transparent"}`,
1743
+ viewBox: "0 0 16 16",
1744
+ fill: "none",
1745
+ children: /* @__PURE__ */ jsx(
1746
+ "path",
1747
+ {
1748
+ d: "M3 8l3.5 3.5L13 4.5",
1749
+ stroke: "currentColor",
1750
+ strokeWidth: "1.8",
1751
+ strokeLinecap: "round",
1752
+ strokeLinejoin: "round"
1753
+ }
1754
+ )
1755
+ }
1756
+ ),
1757
+ /* @__PURE__ */ jsx(
1758
+ "span",
1759
+ {
1760
+ className: playbackRate === speed ? "font-semibold text-white" : "text-white/55",
1761
+ children: speed === 1 ? "Normal" : `${speed}\xD7`
1762
+ }
1763
+ )
1764
+ ]
1765
+ },
1766
+ speed
1767
+ )) })
1768
+ ] })
1769
+ ] })
1770
+ }
1771
+ )
1772
+ ] }),
1773
+ /* @__PURE__ */ jsxs(
1774
+ "div",
1775
+ {
1776
+ ref: progressRef,
1777
+ onPointerMove: handleProgressPointerMove,
1778
+ onPointerEnter: handleProgressPointerEnter,
1779
+ onPointerLeave: handleProgressPointerLeave,
1780
+ onPointerDown: handleProgressPointerDown,
1781
+ onPointerUp: handleProgressPointerUp,
1782
+ className: "relative mb-2 h-8 cursor-pointer overflow-visible",
1783
+ children: [
1784
+ /* @__PURE__ */ jsxs(
1785
+ "div",
1786
+ {
1787
+ ref: progressTrackRef,
1788
+ className: "absolute left-0 right-0 top-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-white/18",
1789
+ style: {
1790
+ height: "3px"
1791
+ },
1792
+ children: [
1793
+ /* @__PURE__ */ jsx(
1794
+ "div",
1562
1795
  {
1563
- type: "button",
1564
- onClick: () => setSettingsTab("root"),
1565
- className: "flex w-full items-center gap-2 border-b border-white/8 px-4 py-3 text-xs font-semibold text-white/50 transition hover:text-white",
1566
- children: [
1567
- /* @__PURE__ */ jsx(
1568
- "svg",
1569
- {
1570
- className: "size-3.5",
1571
- viewBox: "0 0 12 12",
1572
- fill: "none",
1573
- children: /* @__PURE__ */ jsx(
1574
- "path",
1575
- {
1576
- d: "M7.5 3L4.5 6l3 3",
1577
- stroke: "currentColor",
1578
- strokeWidth: "1.5",
1579
- strokeLinecap: "round",
1580
- strokeLinejoin: "round"
1581
- }
1582
- )
1583
- }
1584
- ),
1585
- "Velocidade"
1586
- ]
1796
+ className: "absolute inset-y-0 left-0 rounded-full bg-white/28",
1797
+ style: { width: `${bufferedPercent}%` }
1587
1798
  }
1588
1799
  ),
1589
- /* @__PURE__ */ jsx("div", { className: "py-1.5", children: PLAYBACK_SPEEDS.map((speed) => /* @__PURE__ */ jsxs(
1590
- "button",
1800
+ /* @__PURE__ */ jsx(
1801
+ "div",
1591
1802
  {
1592
- type: "button",
1593
- onClick: () => {
1594
- setPlaybackRate(speed);
1595
- closeSettings();
1596
- },
1597
- className: "flex w-full items-center gap-3 px-4 py-2.5 text-sm transition hover:bg-white/8",
1598
- children: [
1599
- /* @__PURE__ */ jsx(
1600
- "svg",
1601
- {
1602
- className: `size-4 shrink-0 ${playbackRate === speed ? "text-white" : "text-transparent"}`,
1603
- viewBox: "0 0 16 16",
1604
- fill: "none",
1605
- children: /* @__PURE__ */ jsx(
1606
- "path",
1607
- {
1608
- d: "M3 8l3.5 3.5L13 4.5",
1609
- stroke: "currentColor",
1610
- strokeWidth: "1.8",
1611
- strokeLinecap: "round",
1612
- strokeLinejoin: "round"
1613
- }
1614
- )
1615
- }
1616
- ),
1617
- /* @__PURE__ */ jsx(
1618
- "span",
1619
- {
1620
- className: playbackRate === speed ? "font-semibold text-white" : "text-white/55",
1621
- children: speed === 1 ? "Normal" : `${speed}\xD7`
1622
- }
1623
- )
1624
- ]
1625
- },
1626
- speed
1627
- )) })
1628
- ] })
1629
- ]
1630
- }
1631
- )
1632
- ] }),
1633
- /* @__PURE__ */ jsxs(
1634
- "div",
1635
- {
1636
- ref: progressRef,
1637
- onPointerMove: handleProgressPointerMove,
1638
- onPointerEnter: handleProgressPointerEnter,
1639
- onPointerLeave: handleProgressPointerLeave,
1640
- onPointerDown: handleProgressPointerDown,
1641
- onPointerUp: handleProgressPointerUp,
1642
- className: "relative mb-2 h-8 cursor-pointer overflow-visible",
1643
- children: [
1644
- /* @__PURE__ */ jsxs(
1803
+ className: "absolute inset-y-0 left-0 rounded-full bg-white",
1804
+ style: { width: `${progressPercent}%`, transition: "width 0.05s linear" }
1805
+ }
1806
+ )
1807
+ ]
1808
+ }
1809
+ ),
1810
+ /* @__PURE__ */ jsx(
1811
+ "div",
1812
+ {
1813
+ ref: progressThumbRef,
1814
+ className: "pointer-events-none absolute rounded-full bg-white shadow-[0_1px_6px_rgba(0,0,0,0.5)]",
1815
+ style: {
1816
+ top: "50%",
1817
+ left: `${progressPercent}%`,
1818
+ transform: "translate(-50%, -50%)",
1819
+ width: "10px",
1820
+ height: "10px"
1821
+ }
1822
+ }
1823
+ ),
1824
+ chapters.length > 0 && duration > 0 && chapters.map((ch, i) => /* @__PURE__ */ jsx(
1825
+ "div",
1826
+ {
1827
+ "data-chapter-marker": true,
1828
+ className: "pointer-events-none absolute top-1/2 -translate-y-1/2 w-0.5 rounded-full bg-white/50",
1829
+ style: { left: `${ch.startTime / duration * 100}%`, height: "3px" }
1830
+ },
1831
+ i
1832
+ )),
1833
+ preview && (() => {
1834
+ const frameW = preview.cue.w ?? 160;
1835
+ const frameH = preview.cue.h ?? 90;
1836
+ const thumbW = 200;
1837
+ const thumbH = Math.round(thumbW * (frameH / frameW));
1838
+ const scale = thumbW / frameW;
1839
+ const isSprite = preview.cue.x != null && preview.cue.y != null;
1840
+ return /* @__PURE__ */ jsxs(
1645
1841
  "div",
1646
1842
  {
1647
- className: "absolute left-0 right-0 top-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-white/18",
1843
+ className: "pointer-events-none absolute flex flex-col items-center gap-1",
1648
1844
  style: {
1649
- height: isDragging ? "7px" : isHoveringProgress ? "5px" : "3px",
1650
- transition: "height 0.15s ease"
1845
+ bottom: "calc(100% + 6px)",
1846
+ left: preview.left,
1847
+ transform: "translateX(-50%)",
1848
+ zIndex: 80
1651
1849
  },
1652
1850
  children: [
1653
1851
  /* @__PURE__ */ jsx(
1654
1852
  "div",
1655
1853
  {
1656
- className: "absolute inset-y-0 left-0 rounded-full bg-white/28",
1657
- style: { width: `${bufferedPercent}%` }
1854
+ className: "overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/20",
1855
+ style: { width: thumbW, height: thumbH, flexShrink: 0 },
1856
+ children: isSprite ? (
1857
+ // Sprite: need full sheet dimensions to compute backgroundSize correctly.
1858
+ // Load them once from the image's natural size.
1859
+ (() => {
1860
+ const imgUrl = preview.cue.image;
1861
+ if (storyboardSheetUrlRef.current !== imgUrl) {
1862
+ storyboardSheetUrlRef.current = imgUrl;
1863
+ const img = new window.Image();
1864
+ img.onload = () => setStoryboardSheetSize({ w: img.naturalWidth, h: img.naturalHeight });
1865
+ img.src = imgUrl;
1866
+ }
1867
+ if (!storyboardSheetSize) return null;
1868
+ const bsW = storyboardSheetSize.w * scale;
1869
+ const bsH = storyboardSheetSize.h * scale;
1870
+ return /* @__PURE__ */ jsx(
1871
+ "div",
1872
+ {
1873
+ style: {
1874
+ width: thumbW,
1875
+ height: thumbH,
1876
+ backgroundImage: `url(${imgUrl})`,
1877
+ backgroundRepeat: "no-repeat",
1878
+ backgroundPosition: `-${(preview.cue.x ?? 0) * scale}px -${(preview.cue.y ?? 0) * scale}px`,
1879
+ backgroundSize: `${bsW}px ${bsH}px`
1880
+ }
1881
+ }
1882
+ );
1883
+ })()
1884
+ ) : /* @__PURE__ */ jsx(
1885
+ "img",
1886
+ {
1887
+ src: preview.cue.image,
1888
+ alt: "",
1889
+ style: { width: thumbW, height: thumbH, objectFit: "cover", objectPosition: "center", display: "block" }
1890
+ }
1891
+ )
1658
1892
  }
1659
1893
  ),
1660
- /* @__PURE__ */ jsx(
1894
+ /* @__PURE__ */ jsxs(
1661
1895
  "div",
1662
1896
  {
1663
- className: "absolute inset-y-0 left-0 rounded-full bg-white",
1664
- style: { width: `${progressPercent}%`, transition: "width 0.05s linear" }
1897
+ className: "flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[11px] font-semibold text-white/90",
1898
+ style: { background: "rgba(0,0,0,0.55)", backdropFilter: "blur(6px)" },
1899
+ children: [
1900
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatTime(preview.time) }),
1901
+ (() => {
1902
+ const ch = [...chapters].reverse().find((c) => c.startTime <= preview.time);
1903
+ return ch ? /* @__PURE__ */ jsxs(Fragment, { children: [
1904
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\xB7" }),
1905
+ /* @__PURE__ */ jsx("span", { className: "max-w-36 truncate", children: ch.title })
1906
+ ] }) : null;
1907
+ })()
1908
+ ]
1665
1909
  }
1666
1910
  )
1667
1911
  ]
1668
1912
  }
1669
- ),
1670
- /* @__PURE__ */ jsx(
1671
- "div",
1672
- {
1673
- className: "pointer-events-none absolute rounded-full bg-white shadow-[0_1px_6px_rgba(0,0,0,0.5)]",
1674
- style: {
1675
- top: "50%",
1676
- left: `${progressPercent}%`,
1677
- transform: "translate(-50%, -50%)",
1678
- width: isDragging ? "18px" : isHoveringProgress ? "14px" : "10px",
1679
- height: isDragging ? "18px" : isHoveringProgress ? "14px" : "10px",
1680
- transition: "width 0.2s cubic-bezier(0.34,1.56,0.64,1), height 0.2s cubic-bezier(0.34,1.56,0.64,1)"
1681
- }
1682
- }
1683
- ),
1684
- chapters.length > 0 && duration > 0 && chapters.map((ch, i) => /* @__PURE__ */ jsx(
1685
- "div",
1686
- {
1687
- className: "pointer-events-none absolute top-1/2 -translate-y-1/2 w-0.5 rounded-full bg-white/50",
1688
- style: { left: `${ch.startTime / duration * 100}%`, height: isDragging ? "7px" : isHoveringProgress ? "5px" : "3px", transition: "height 0.15s ease" }
1689
- },
1690
- i
1691
- )),
1692
- preview && (() => {
1693
- const frameW = preview.cue.w ?? 160;
1694
- const frameH = preview.cue.h ?? 90;
1695
- const thumbW = 200;
1696
- const thumbH = Math.round(thumbW * (frameH / frameW));
1697
- const scale = thumbW / frameW;
1698
- const isSprite = preview.cue.x != null && preview.cue.y != null;
1913
+ );
1914
+ })()
1915
+ ]
1916
+ }
1917
+ ),
1918
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1919
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 @sm:gap-2", children: [
1920
+ /* @__PURE__ */ jsx(
1921
+ "button",
1922
+ {
1923
+ type: "button",
1924
+ onClick: togglePlay,
1925
+ className: "grid size-8 place-items-center rounded-full text-white transition hover:bg-white/10 @sm:size-9",
1926
+ "aria-label": isPlaying ? "Pause" : "Play",
1927
+ children: isPlaying ? /* @__PURE__ */ jsx(Pause, { className: "size-4 @sm:size-5", fill: "white" }) : /* @__PURE__ */ jsx(Play, { className: "ml-0.5 size-4 @sm:size-5", fill: "white" })
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-medium tabular-nums text-white/80", children: [
1931
+ formatTime(currentTime),
1932
+ /* @__PURE__ */ jsx("span", { className: "text-white/30", children: "/" }),
1933
+ /* @__PURE__ */ jsx("span", { className: "text-white/45", children: formatTime(duration) })
1934
+ ] }),
1935
+ (() => {
1936
+ if (chapters.length === 0) return null;
1937
+ const ch = [...chapters].reverse().find((c) => currentTime >= c.startTime);
1938
+ return ch ? /* @__PURE__ */ jsx("span", { className: "hidden max-w-40 truncate text-[11px] text-white/50 @sm:block", children: ch.title }) : null;
1939
+ })()
1940
+ ] }),
1941
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 @sm:gap-1", children: [
1942
+ parsed.rating && (() => {
1943
+ const REACTIONS = [
1944
+ { key: "LOVE", emoji: "\u2764\uFE0F" },
1945
+ { key: "LIKE", emoji: "\u{1F44D}" },
1946
+ { key: "DISLIKE", emoji: "\u{1F44E}" }
1947
+ ];
1948
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1949
+ REACTIONS.map(({ key, emoji }) => {
1950
+ const count = ratingCounts[key] ?? 0;
1951
+ const active = userReaction === key;
1699
1952
  return /* @__PURE__ */ jsxs(
1700
- "div",
1953
+ "button",
1701
1954
  {
1702
- className: "pointer-events-none absolute flex flex-col items-center gap-1",
1703
- style: {
1704
- bottom: "calc(100% + 6px)",
1705
- left: preview.left,
1706
- transform: "translateX(-50%)",
1707
- zIndex: 80
1955
+ type: "button",
1956
+ onClick: (e) => {
1957
+ e.stopPropagation();
1958
+ const next = active ? null : key;
1959
+ setUserReaction(next);
1960
+ setRatingCounts((prev) => {
1961
+ const updated = { ...prev };
1962
+ const prev_reaction = userReactionRef.current;
1963
+ if (active) updated[key] = Math.max(0, (updated[key] ?? 0) - 1);
1964
+ else {
1965
+ if (prev_reaction) updated[prev_reaction] = Math.max(0, (updated[prev_reaction] ?? 0) - 1);
1966
+ updated[key] = (updated[key] ?? 0) + 1;
1967
+ }
1968
+ return updated;
1969
+ });
1970
+ onReactRef.current?.(next);
1708
1971
  },
1972
+ className: `flex items-center gap-0.5 rounded px-1.5 py-1 text-[11px] transition hover:bg-white/10 ${active ? "opacity-100" : "opacity-50 hover:opacity-80"}`,
1973
+ "aria-label": key.toLowerCase(),
1974
+ "aria-pressed": active,
1709
1975
  children: [
1710
- /* @__PURE__ */ jsx(
1711
- "div",
1712
- {
1713
- className: "overflow-hidden rounded-lg shadow-2xl ring-1 ring-white/20",
1714
- style: { width: thumbW, height: thumbH, flexShrink: 0 },
1715
- children: isSprite ? (
1716
- // Sprite: need full sheet dimensions to compute backgroundSize correctly.
1717
- // Load them once from the image's natural size.
1718
- (() => {
1719
- const imgUrl = preview.cue.image;
1720
- if (storyboardSheetUrlRef.current !== imgUrl) {
1721
- storyboardSheetUrlRef.current = imgUrl;
1722
- const img = new window.Image();
1723
- img.onload = () => setStoryboardSheetSize({ w: img.naturalWidth, h: img.naturalHeight });
1724
- img.src = imgUrl;
1725
- }
1726
- if (!storyboardSheetSize) return null;
1727
- const bsW = storyboardSheetSize.w * scale;
1728
- const bsH = storyboardSheetSize.h * scale;
1729
- return /* @__PURE__ */ jsx(
1730
- "div",
1731
- {
1732
- style: {
1733
- width: thumbW,
1734
- height: thumbH,
1735
- backgroundImage: `url(${imgUrl})`,
1736
- backgroundRepeat: "no-repeat",
1737
- backgroundPosition: `-${(preview.cue.x ?? 0) * scale}px -${(preview.cue.y ?? 0) * scale}px`,
1738
- backgroundSize: `${bsW}px ${bsH}px`
1739
- }
1740
- }
1741
- );
1742
- })()
1743
- ) : /* @__PURE__ */ jsx(
1744
- "img",
1745
- {
1746
- src: preview.cue.image,
1747
- alt: "",
1748
- style: { width: thumbW, height: thumbH, objectFit: "cover", objectPosition: "center", display: "block" }
1749
- }
1750
- )
1751
- }
1752
- ),
1753
- /* @__PURE__ */ jsx(
1754
- "span",
1755
- {
1756
- className: "rounded-md px-2 py-0.5 text-[11px] font-semibold tabular-nums text-white/90",
1757
- style: { background: "rgba(0,0,0,0.55)", backdropFilter: "blur(6px)" },
1758
- children: formatTime(preview.time)
1759
- }
1760
- )
1976
+ /* @__PURE__ */ jsx("span", { children: emoji }),
1977
+ count > 0 && /* @__PURE__ */ jsx("span", { className: "text-white/70 tabular-nums", children: count })
1761
1978
  ]
1762
- }
1979
+ },
1980
+ key
1763
1981
  );
1764
- })()
1765
- ]
1766
- }
1767
- ),
1768
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1769
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 @sm:gap-2", children: [
1770
- /* @__PURE__ */ jsx(
1771
- "button",
1772
- {
1773
- type: "button",
1774
- onClick: togglePlay,
1775
- className: "grid size-8 place-items-center rounded-full text-white transition hover:bg-white/10 @sm:size-9",
1776
- "aria-label": isPlaying ? "Pause" : "Play",
1777
- children: isPlaying ? /* @__PURE__ */ jsx(Pause, { className: "size-4 @sm:size-5", fill: "white" }) : /* @__PURE__ */ jsx(Play, { className: "ml-0.5 size-4 @sm:size-5", fill: "white" })
1778
- }
1779
- ),
1780
- /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-medium tabular-nums text-white/80", children: [
1781
- formatTime(currentTime),
1782
- /* @__PURE__ */ jsx("span", { className: "text-white/30", children: "/" }),
1783
- /* @__PURE__ */ jsx("span", { className: "text-white/45", children: formatTime(duration) })
1784
- ] }),
1785
- (() => {
1786
- if (chapters.length === 0) return null;
1787
- const ch = [...chapters].reverse().find((c) => currentTime >= c.startTime);
1788
- return ch ? /* @__PURE__ */ jsx("span", { className: "hidden max-w-40 truncate text-[11px] text-white/50 @sm:block", children: ch.title }) : null;
1789
- })()
1790
- ] }),
1791
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 @sm:gap-1", children: [
1792
- parsed.rating && (() => {
1793
- const REACTIONS = [
1794
- { key: "LOVE", emoji: "\u2764\uFE0F" },
1795
- { key: "LIKE", emoji: "\u{1F44D}" },
1796
- { key: "DISLIKE", emoji: "\u{1F44E}" }
1797
- ];
1798
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1799
- REACTIONS.map(({ key, emoji }) => {
1800
- const count = ratingCounts[key] ?? 0;
1801
- const active = userReaction === key;
1802
- return /* @__PURE__ */ jsxs(
1803
- "button",
1804
- {
1805
- type: "button",
1806
- onClick: (e) => {
1807
- e.stopPropagation();
1808
- const next = active ? null : key;
1809
- setUserReaction(next);
1810
- setRatingCounts((prev) => {
1811
- const updated = { ...prev };
1812
- const prev_reaction = userReactionRef.current;
1813
- if (active) updated[key] = Math.max(0, (updated[key] ?? 0) - 1);
1814
- else {
1815
- if (prev_reaction) updated[prev_reaction] = Math.max(0, (updated[prev_reaction] ?? 0) - 1);
1816
- updated[key] = (updated[key] ?? 0) + 1;
1817
- }
1818
- return updated;
1819
- });
1820
- onReactRef.current?.(next);
1821
- },
1822
- className: `flex items-center gap-0.5 rounded px-1.5 py-1 text-[11px] transition hover:bg-white/10 ${active ? "opacity-100" : "opacity-50 hover:opacity-80"}`,
1823
- "aria-label": key.toLowerCase(),
1824
- "aria-pressed": active,
1825
- children: [
1826
- /* @__PURE__ */ jsx("span", { children: emoji }),
1827
- count > 0 && /* @__PURE__ */ jsx("span", { className: "text-white/70 tabular-nums", children: count })
1828
- ]
1829
- },
1830
- key
1831
- );
1832
- }),
1833
- /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-4 w-px bg-white/20 @md:mx-1" })
1834
- ] });
1835
- })(),
1836
- /* @__PURE__ */ jsx(
1837
- VolumeSlider,
1838
- {
1839
- volume: isMuted ? 0 : volume,
1840
- onMuteToggle: () => setIsMuted((v) => !v),
1841
- onVolumeChange: (v) => {
1842
- setVolume(v);
1843
- setIsMuted(v === 0);
1844
- },
1845
- isMuted
1846
- }
1847
- ),
1848
- /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-4 w-px bg-white/20 @md:mx-1" }),
1849
- captions.length > 0 && /* @__PURE__ */ jsx(
1850
- "button",
1851
- {
1852
- type: "button",
1853
- onClick: () => {
1854
- const next = subtitleMode === "off" ? captions[0]?.srclang ?? "off" : "off";
1855
- setSubtitleMode(next);
1856
- setSubtitleStyle((st) => ({ ...st, track: next }));
1857
- },
1858
- className: `grid size-8 place-items-center rounded transition hover:text-white/80 @sm:size-10 ${subtitleMode !== "off" ? "text-white" : "text-white/60"}`,
1859
- "aria-label": "Captions",
1860
- children: /* @__PURE__ */ jsx(Captions$1, { className: "size-4 @sm:size-5" })
1861
- }
1862
- ),
1863
- /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
1864
- "button",
1865
- {
1866
- type: "button",
1867
- onClick: () => settingsOpen ? closeSettings() : openSettings(),
1868
- className: `grid size-8 place-items-center rounded transition hover:text-white/80 @sm:size-10 ${settingsOpen ? "text-white" : "text-white/60"}`,
1869
- "aria-label": "Settings",
1870
- children: /* @__PURE__ */ jsx(Settings, { className: "size-4 @sm:size-5" })
1871
- }
1872
- ) }),
1873
- /* @__PURE__ */ jsx(
1874
- "button",
1875
- {
1876
- type: "button",
1877
- onClick: toggleFullscreen,
1878
- className: "grid size-8 place-items-center text-white/60 transition hover:scale-110 hover:text-white/80 @sm:size-10",
1879
- "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
1880
- children: isFullscreen ? /* @__PURE__ */ jsx(Minimize, { className: "size-4 @sm:size-5" }) : /* @__PURE__ */ jsx(Maximize, { className: "size-4 @sm:size-5" })
1881
- }
1882
- )
1883
- ] })
1982
+ }),
1983
+ /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-4 w-px bg-white/20 @md:mx-1" })
1984
+ ] });
1985
+ })(),
1986
+ /* @__PURE__ */ jsx(
1987
+ VolumeSlider,
1988
+ {
1989
+ volume: isMuted ? 0 : volume,
1990
+ onMuteToggle: () => setIsMuted((v) => !v),
1991
+ onVolumeChange: (v) => {
1992
+ setVolume(v);
1993
+ setIsMuted(v === 0);
1994
+ },
1995
+ isMuted
1996
+ }
1997
+ ),
1998
+ /* @__PURE__ */ jsx("div", { className: "mx-0.5 h-4 w-px bg-white/20 @md:mx-1" }),
1999
+ captions.length > 0 && /* @__PURE__ */ jsx(
2000
+ "button",
2001
+ {
2002
+ type: "button",
2003
+ onClick: () => {
2004
+ const next = subtitleMode === "off" ? captions[0]?.srclang ?? "off" : "off";
2005
+ setSubtitleMode(next);
2006
+ setSubtitleStyle((st) => ({ ...st, track: next }));
2007
+ },
2008
+ className: `grid size-8 place-items-center rounded transition hover:text-white/80 @sm:size-10 ${subtitleMode !== "off" ? "text-white" : "text-white/60"}`,
2009
+ "aria-label": "Captions",
2010
+ children: /* @__PURE__ */ jsx(Captions$1, { className: "size-4 @sm:size-5" })
2011
+ }
2012
+ ),
2013
+ /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
2014
+ "button",
2015
+ {
2016
+ type: "button",
2017
+ onClick: () => settingsOpen ? closeSettings() : openSettings(),
2018
+ className: `grid size-8 place-items-center rounded transition hover:text-white/80 @sm:size-10 ${settingsOpen ? "text-white" : "text-white/60"}`,
2019
+ "aria-label": "Settings",
2020
+ children: /* @__PURE__ */ jsx(Settings, { className: "size-4 @sm:size-5" })
2021
+ }
2022
+ ) }),
2023
+ /* @__PURE__ */ jsx(
2024
+ "button",
2025
+ {
2026
+ type: "button",
2027
+ onClick: toggleFullscreen,
2028
+ className: "grid size-8 place-items-center text-white/60 transition hover:scale-110 hover:text-white/80 @sm:size-10",
2029
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
2030
+ children: isFullscreen ? /* @__PURE__ */ jsx(Minimize, { className: "size-4 @sm:size-5" }) : /* @__PURE__ */ jsx(Maximize, { className: "size-4 @sm:size-5" })
2031
+ }
2032
+ )
1884
2033
  ] })
1885
- ]
1886
- }
1887
- )
1888
- ]
1889
- }
1890
- ),
1891
- activeCue && /* @__PURE__ */ jsx(
1892
- "div",
1893
- {
1894
- className: `pointer-events-none absolute inset-x-0 z-40 flex justify-center px-4 transition-all duration-200${controlsVisible ? "bottom-20 @sm:bottom-24 @lg:bottom-28" : "bottom-4 @sm:bottom-6"}`,
1895
- children: /* @__PURE__ */ jsx(
1896
- "div",
1897
- {
1898
- className: "max-w-[80%] rounded-lg px-4 py-1.5 text-center font-medium leading-snug",
1899
- style: {
1900
- fontSize: (() => {
1901
- const base = Math.max(12, Math.min(playerHeight * 0.028, 32));
1902
- if (subtitleStyle.size === "small") return `${base * 0.75}px`;
1903
- if (subtitleStyle.size === "large") return `${base * 1.35}px`;
1904
- if (subtitleStyle.size === "xlarge") return `${base * 1.8}px`;
1905
- return `${base}px`;
1906
- })(),
1907
- color: subtitleStyle.color === "yellow" ? "#facc15" : subtitleStyle.color === "cyan" ? "#22d3ee" : "#ffffff",
1908
- backgroundColor: subtitleStyle.bg === "none" ? "transparent" : subtitleStyle.bg === "solid" ? "rgba(0,0,0,0.9)" : "rgba(0,0,0,0.55)",
1909
- backdropFilter: subtitleStyle.bg === "semi" ? "blur(6px)" : void 0,
1910
- textShadow: subtitleStyle.bg === "none" ? "0 1px 8px rgba(0,0,0,1), 0 0 16px rgba(0,0,0,0.9)" : "0 1px 3px rgba(0,0,0,0.5)"
1911
- },
1912
- children: activeCue
2034
+ ] })
2035
+ ]
1913
2036
  }
1914
2037
  )
1915
- }
1916
- )
1917
- ]
1918
- }
1919
- )
1920
- ]
2038
+ ]
2039
+ }
2040
+ ),
2041
+ activeCue && /* @__PURE__ */ jsx(
2042
+ "div",
2043
+ {
2044
+ className: `pointer-events-none absolute inset-x-0 z-40 flex justify-center px-4 transition-all duration-200${controlsVisible ? "bottom-20 @sm:bottom-24 @lg:bottom-28" : "bottom-4 @sm:bottom-6"}`,
2045
+ children: /* @__PURE__ */ jsx(
2046
+ "div",
2047
+ {
2048
+ className: "max-w-[80%] rounded-lg px-4 py-1.5 text-center font-medium leading-snug",
2049
+ style: {
2050
+ fontSize: (() => {
2051
+ const base = Math.max(12, Math.min(playerHeight * 0.028, 32));
2052
+ if (subtitleStyle.size === "small") return `${base * 0.75}px`;
2053
+ if (subtitleStyle.size === "large") return `${base * 1.35}px`;
2054
+ if (subtitleStyle.size === "xlarge") return `${base * 1.8}px`;
2055
+ return `${base}px`;
2056
+ })(),
2057
+ color: subtitleStyle.color === "yellow" ? "#facc15" : subtitleStyle.color === "cyan" ? "#22d3ee" : "#ffffff",
2058
+ backgroundColor: subtitleStyle.bg === "none" ? "transparent" : subtitleStyle.bg === "solid" ? "rgba(0,0,0,0.9)" : "rgba(0,0,0,0.55)",
2059
+ backdropFilter: subtitleStyle.bg === "semi" ? "blur(6px)" : void 0,
2060
+ textShadow: subtitleStyle.bg === "none" ? "0 1px 8px rgba(0,0,0,1), 0 0 16px rgba(0,0,0,0.9)" : "0 1px 3px rgba(0,0,0,0.5)"
2061
+ },
2062
+ children: activeCue
2063
+ }
2064
+ )
2065
+ }
2066
+ )
2067
+ ]
2068
+ }
2069
+ )
1921
2070
  }
1922
2071
  );
1923
2072
  }
@@ -1931,6 +2080,9 @@ function VolumeSlider({
1931
2080
  const [hovered, setHovered] = useState(false);
1932
2081
  const [dragging, setDragging] = useState(false);
1933
2082
  const trackRef = useRef(null);
2083
+ const sliderWrapRef = useRef(null);
2084
+ const railRef = useRef(null);
2085
+ const thumbRef = useRef(null);
1934
2086
  const expanded = hovered || dragging;
1935
2087
  const seek = useCallback((clientX) => {
1936
2088
  const track = trackRef.current;
@@ -1948,6 +2100,31 @@ function VolumeSlider({
1948
2100
  if (dragging) seek(e.clientX);
1949
2101
  }, [dragging, seek]);
1950
2102
  const onPointerUp = useCallback(() => setDragging(false), []);
2103
+ useEffect(() => {
2104
+ const wrap = sliderWrapRef.current;
2105
+ if (!wrap) return;
2106
+ gsap.killTweensOf(wrap);
2107
+ gsap.to(wrap, {
2108
+ width: expanded ? 64 : 0,
2109
+ opacity: expanded ? 1 : 0,
2110
+ duration: 0.2,
2111
+ ease: "power2.inOut"
2112
+ });
2113
+ }, [expanded]);
2114
+ useEffect(() => {
2115
+ const rail = railRef.current;
2116
+ const thumb = thumbRef.current;
2117
+ const targetRailH = dragging ? 5 : hovered ? 4 : 3;
2118
+ const targetThumbSize = dragging ? 14 : hovered ? 11 : 0;
2119
+ if (rail) {
2120
+ gsap.killTweensOf(rail);
2121
+ gsap.to(rail, { height: targetRailH, duration: 0.15, ease: "power1.inOut" });
2122
+ }
2123
+ if (thumb) {
2124
+ gsap.killTweensOf(thumb);
2125
+ gsap.to(thumb, { width: targetThumbSize, height: targetThumbSize, duration: 0.15, ease: "power1.inOut" });
2126
+ }
2127
+ }, [dragging, hovered]);
1951
2128
  const fillPercent = volume * 100;
1952
2129
  return /* @__PURE__ */ jsxs(
1953
2130
  "div",
@@ -1971,10 +2148,10 @@ function VolumeSlider({
1971
2148
  /* @__PURE__ */ jsx(
1972
2149
  "div",
1973
2150
  {
2151
+ ref: sliderWrapRef,
1974
2152
  style: {
1975
- width: expanded ? "64px" : "0px",
1976
- opacity: expanded ? 1 : 0,
1977
- transition: "width 0.2s ease, opacity 0.2s ease",
2153
+ width: "0px",
2154
+ opacity: 0,
1978
2155
  overflow: "hidden"
1979
2156
  },
1980
2157
  children: /* @__PURE__ */ jsxs(
@@ -1990,11 +2167,9 @@ function VolumeSlider({
1990
2167
  /* @__PURE__ */ jsx(
1991
2168
  "div",
1992
2169
  {
2170
+ ref: railRef,
1993
2171
  className: "absolute inset-x-0 rounded-full bg-white/20",
1994
- style: {
1995
- height: dragging ? "5px" : hovered ? "4px" : "3px",
1996
- transition: "height 0.15s ease"
1997
- },
2172
+ style: { height: "3px" },
1998
2173
  children: /* @__PURE__ */ jsx(
1999
2174
  "div",
2000
2175
  {
@@ -2007,12 +2182,12 @@ function VolumeSlider({
2007
2182
  /* @__PURE__ */ jsx(
2008
2183
  "div",
2009
2184
  {
2185
+ ref: thumbRef,
2010
2186
  className: "pointer-events-none absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white shadow-md",
2011
2187
  style: {
2012
2188
  left: `${fillPercent}%`,
2013
- width: dragging ? "14px" : hovered ? "11px" : "0px",
2014
- height: dragging ? "14px" : hovered ? "11px" : "0px",
2015
- transition: "width 0.15s ease, height 0.15s ease"
2189
+ width: "0px",
2190
+ height: "0px"
2016
2191
  }
2017
2192
  }
2018
2193
  )