analytica-frontend-lib 1.1.80 → 1.1.82

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.
@@ -1,9 +1,9 @@
1
1
  // src/components/VideoPlayer/VideoPlayer.tsx
2
2
  import {
3
3
  useRef,
4
- useState as useState2,
4
+ useState as useState3,
5
5
  useEffect as useEffect2,
6
- useCallback
6
+ useCallback as useCallback2
7
7
  } from "react";
8
8
  import { createPortal } from "react-dom";
9
9
  import {
@@ -208,13 +208,197 @@ var useMobile = () => {
208
208
  };
209
209
  };
210
210
 
211
+ // src/components/DownloadButton/DownloadButton.tsx
212
+ import { useCallback, useState as useState2 } from "react";
213
+ import { DownloadSimple } from "phosphor-react";
214
+ import { jsx as jsx3 } from "react/jsx-runtime";
215
+ var getMimeType = (url) => {
216
+ const extension = getFileExtension(url);
217
+ const mimeTypes = {
218
+ pdf: "application/pdf",
219
+ png: "image/png",
220
+ jpg: "image/jpeg",
221
+ jpeg: "image/jpeg",
222
+ mp3: "audio/mpeg",
223
+ mp4: "video/mp4",
224
+ vtt: "text/vtt"
225
+ };
226
+ return mimeTypes[extension] || "application/octet-stream";
227
+ };
228
+ var triggerDownload = async (url, filename) => {
229
+ try {
230
+ const response = await fetch(url, {
231
+ mode: "cors",
232
+ credentials: "same-origin"
233
+ });
234
+ if (!response.ok) {
235
+ throw new Error(
236
+ `Failed to fetch file: ${response.status} ${response.statusText}`
237
+ );
238
+ }
239
+ const blob = await response.blob();
240
+ const mimeType = getMimeType(url);
241
+ const typedBlob = new Blob([blob], { type: mimeType });
242
+ const blobUrl = URL.createObjectURL(typedBlob);
243
+ const link = document.createElement("a");
244
+ link.href = blobUrl;
245
+ link.download = filename;
246
+ link.rel = "noopener noreferrer";
247
+ document.body.appendChild(link);
248
+ link.click();
249
+ link.remove();
250
+ setTimeout(() => {
251
+ URL.revokeObjectURL(blobUrl);
252
+ }, 1e3);
253
+ } catch (error) {
254
+ console.warn("Fetch download failed, falling back to direct link:", error);
255
+ const link = document.createElement("a");
256
+ link.href = url;
257
+ link.download = filename;
258
+ link.rel = "noopener noreferrer";
259
+ link.target = "_blank";
260
+ document.body.appendChild(link);
261
+ link.click();
262
+ link.remove();
263
+ }
264
+ };
265
+ var getFileExtension = (url) => {
266
+ try {
267
+ const u = new URL(url, globalThis.location?.origin || "http://localhost");
268
+ url = u.pathname;
269
+ } catch {
270
+ }
271
+ const path = url.split(/[?#]/)[0];
272
+ const dot = path.lastIndexOf(".");
273
+ return dot > -1 ? path.slice(dot + 1).toLowerCase() : "file";
274
+ };
275
+ var generateFilename = (contentType, url, lessonTitle = "aula") => {
276
+ const sanitizedTitle = lessonTitle.toLowerCase().replaceAll(/[^a-z0-9\s]/g, "").replaceAll(/\s+/g, "-").substring(0, 50);
277
+ const extension = getFileExtension(url);
278
+ return `${sanitizedTitle}-${contentType}.${extension}`;
279
+ };
280
+ var DownloadButton = ({
281
+ content,
282
+ className,
283
+ onDownloadStart,
284
+ onDownloadComplete,
285
+ onDownloadError,
286
+ lessonTitle = "aula",
287
+ disabled = false
288
+ }) => {
289
+ const [isDownloading, setIsDownloading] = useState2(false);
290
+ const isValidUrl = useCallback((url) => {
291
+ return Boolean(
292
+ url && url.trim() !== "" && url !== "undefined" && url !== "null"
293
+ );
294
+ }, []);
295
+ const getAvailableContent = useCallback(() => {
296
+ const downloads = [];
297
+ if (isValidUrl(content.urlDoc)) {
298
+ downloads.push({
299
+ type: "documento",
300
+ url: content.urlDoc,
301
+ label: "Documento"
302
+ });
303
+ }
304
+ if (isValidUrl(content.urlInitialFrame)) {
305
+ downloads.push({
306
+ type: "quadro-inicial",
307
+ url: content.urlInitialFrame,
308
+ label: "Quadro Inicial"
309
+ });
310
+ }
311
+ if (isValidUrl(content.urlFinalFrame)) {
312
+ downloads.push({
313
+ type: "quadro-final",
314
+ url: content.urlFinalFrame,
315
+ label: "Quadro Final"
316
+ });
317
+ }
318
+ if (isValidUrl(content.urlPodcast)) {
319
+ downloads.push({
320
+ type: "podcast",
321
+ url: content.urlPodcast,
322
+ label: "Podcast"
323
+ });
324
+ }
325
+ if (isValidUrl(content.urlVideo)) {
326
+ downloads.push({ type: "video", url: content.urlVideo, label: "V\xEDdeo" });
327
+ }
328
+ return downloads;
329
+ }, [content, isValidUrl]);
330
+ const handleDownload = useCallback(async () => {
331
+ if (disabled || isDownloading) return;
332
+ const availableContent = getAvailableContent();
333
+ if (availableContent.length === 0) {
334
+ return;
335
+ }
336
+ setIsDownloading(true);
337
+ try {
338
+ for (let i = 0; i < availableContent.length; i++) {
339
+ const item = availableContent[i];
340
+ try {
341
+ onDownloadStart?.(item.type);
342
+ const filename = generateFilename(item.type, item.url, lessonTitle);
343
+ await triggerDownload(item.url, filename);
344
+ onDownloadComplete?.(item.type);
345
+ if (i < availableContent.length - 1) {
346
+ await new Promise((resolve) => setTimeout(resolve, 200));
347
+ }
348
+ } catch (error) {
349
+ console.error(`Erro ao baixar ${item.label}:`, error);
350
+ onDownloadError?.(
351
+ item.type,
352
+ error instanceof Error ? error : new Error(`Falha ao baixar ${item.label}`)
353
+ );
354
+ }
355
+ }
356
+ } finally {
357
+ setIsDownloading(false);
358
+ }
359
+ }, [
360
+ disabled,
361
+ isDownloading,
362
+ getAvailableContent,
363
+ lessonTitle,
364
+ onDownloadStart,
365
+ onDownloadComplete,
366
+ onDownloadError
367
+ ]);
368
+ const hasContent = getAvailableContent().length > 0;
369
+ if (!hasContent) {
370
+ return null;
371
+ }
372
+ return /* @__PURE__ */ jsx3("div", { className: cn("flex items-center", className), children: /* @__PURE__ */ jsx3(
373
+ IconButton_default,
374
+ {
375
+ icon: /* @__PURE__ */ jsx3(DownloadSimple, { size: 24 }),
376
+ onClick: handleDownload,
377
+ disabled: disabled || isDownloading,
378
+ "aria-label": (() => {
379
+ if (isDownloading) {
380
+ return "Baixando conte\xFAdo...";
381
+ }
382
+ const contentCount = getAvailableContent().length;
383
+ const suffix = contentCount > 1 ? "s" : "";
384
+ return `Baixar conte\xFAdo da aula (${contentCount} arquivo${suffix})`;
385
+ })(),
386
+ className: cn(
387
+ "!bg-transparent hover:!bg-black/10 transition-colors",
388
+ isDownloading && "opacity-60 cursor-not-allowed"
389
+ )
390
+ }
391
+ ) });
392
+ };
393
+ var DownloadButton_default = DownloadButton;
394
+
211
395
  // src/components/VideoPlayer/VideoPlayer.tsx
212
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
396
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
213
397
  var CONTROLS_HIDE_TIMEOUT = 3e3;
214
398
  var LEAVE_HIDE_TIMEOUT = 1e3;
215
399
  var INIT_DELAY = 100;
216
400
  var formatTime = (seconds) => {
217
- if (!seconds || isNaN(seconds)) return "0:00";
401
+ if (!seconds || Number.isNaN(seconds)) return "0:00";
218
402
  const mins = Math.floor(seconds / 60);
219
403
  const secs = Math.floor(seconds % 60);
220
404
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -225,14 +409,14 @@ var ProgressBar = ({
225
409
  progressPercentage,
226
410
  onSeek,
227
411
  className = "px-4 pb-2"
228
- }) => /* @__PURE__ */ jsx3("div", { className, children: /* @__PURE__ */ jsx3(
412
+ }) => /* @__PURE__ */ jsx4("div", { className, children: /* @__PURE__ */ jsx4(
229
413
  "input",
230
414
  {
231
415
  type: "range",
232
416
  min: 0,
233
417
  max: duration || 100,
234
418
  value: currentTime,
235
- onChange: (e) => onSeek(parseFloat(e.target.value)),
419
+ onChange: (e) => onSeek(Number.parseFloat(e.target.value)),
236
420
  className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
237
421
  "aria-label": "Video progress",
238
422
  style: {
@@ -248,23 +432,23 @@ var VolumeControls = ({
248
432
  iconSize = 24,
249
433
  showSlider = true
250
434
  }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
251
- /* @__PURE__ */ jsx3(
435
+ /* @__PURE__ */ jsx4(
252
436
  IconButton_default,
253
437
  {
254
- icon: isMuted ? /* @__PURE__ */ jsx3(SpeakerSlash, { size: iconSize }) : /* @__PURE__ */ jsx3(SpeakerHigh, { size: iconSize }),
438
+ icon: isMuted ? /* @__PURE__ */ jsx4(SpeakerSlash, { size: iconSize }) : /* @__PURE__ */ jsx4(SpeakerHigh, { size: iconSize }),
255
439
  onClick: onToggleMute,
256
440
  "aria-label": isMuted ? "Unmute" : "Mute",
257
441
  className: "!bg-transparent !text-white hover:!bg-white/20"
258
442
  }
259
443
  ),
260
- showSlider && /* @__PURE__ */ jsx3(
444
+ showSlider && /* @__PURE__ */ jsx4(
261
445
  "input",
262
446
  {
263
447
  type: "range",
264
448
  min: 0,
265
449
  max: 100,
266
450
  value: Math.round(volume * 100),
267
- onChange: (e) => onVolumeChange(parseInt(e.target.value)),
451
+ onChange: (e) => onVolumeChange(Number.parseInt(e.target.value)),
268
452
  className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
269
453
  "aria-label": "Volume control",
270
454
  style: {
@@ -314,17 +498,17 @@ var SpeedMenu = ({
314
498
  document.removeEventListener("mousedown", handleClickOutside);
315
499
  };
316
500
  }, [showSpeedMenu, onToggleMenu]);
317
- const menuContent = /* @__PURE__ */ jsx3(
501
+ const menuContent = /* @__PURE__ */ jsx4(
318
502
  "div",
319
503
  {
320
504
  ref: speedMenuRef,
321
505
  role: "menu",
322
506
  "aria-label": "Playback speed",
323
507
  className: isFullscreen ? "absolute bottom-12 right-0 bg-background border border-border-100 rounded-lg shadow-lg p-2 min-w-24 z-[9999]" : "fixed bg-background border border-border-100 rounded-lg shadow-lg p-2 min-w-24 z-[9999]",
324
- style: !isFullscreen ? {
508
+ style: isFullscreen ? void 0 : {
325
509
  top: `${position.top}px`,
326
510
  left: `${position.left}px`
327
- } : void 0,
511
+ },
328
512
  children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ jsxs(
329
513
  "button",
330
514
  {
@@ -341,13 +525,13 @@ var SpeedMenu = ({
341
525
  ))
342
526
  }
343
527
  );
344
- const portalContent = typeof window !== "undefined" && typeof document !== "undefined" ? createPortal(menuContent, document.body) : null;
528
+ const portalContent = showSpeedMenu && globalThis.window !== void 0 && globalThis.document !== void 0 && !!globalThis.document?.body ? createPortal(menuContent, globalThis.document.body) : null;
345
529
  return /* @__PURE__ */ jsxs("div", { className: "relative", ref: speedMenuContainerRef, children: [
346
- /* @__PURE__ */ jsx3(
530
+ /* @__PURE__ */ jsx4(
347
531
  IconButton_default,
348
532
  {
349
533
  ref: buttonRef,
350
- icon: /* @__PURE__ */ jsx3(DotsThreeVertical, { size: iconSize }),
534
+ icon: /* @__PURE__ */ jsx4(DotsThreeVertical, { size: iconSize }),
351
535
  onClick: onToggleMenu,
352
536
  "aria-label": "Playback speed",
353
537
  "aria-haspopup": "menu",
@@ -370,29 +554,34 @@ var VideoPlayer = ({
370
554
  onVideoComplete,
371
555
  className,
372
556
  autoSave = true,
373
- storageKey = "video-progress"
557
+ storageKey = "video-progress",
558
+ downloadContent,
559
+ showDownloadButton = false,
560
+ onDownloadStart,
561
+ onDownloadComplete,
562
+ onDownloadError
374
563
  }) => {
375
564
  const videoRef = useRef(null);
376
565
  const { isUltraSmallMobile, isTinyMobile } = useMobile();
377
- const [isPlaying, setIsPlaying] = useState2(false);
378
- const [currentTime, setCurrentTime] = useState2(0);
379
- const [duration, setDuration] = useState2(0);
380
- const [isMuted, setIsMuted] = useState2(false);
381
- const [volume, setVolume] = useState2(1);
382
- const [isFullscreen, setIsFullscreen] = useState2(false);
383
- const [showControls, setShowControls] = useState2(true);
384
- const [hasCompleted, setHasCompleted] = useState2(false);
385
- const [showCaptions, setShowCaptions] = useState2(false);
566
+ const [isPlaying, setIsPlaying] = useState3(false);
567
+ const [currentTime, setCurrentTime] = useState3(0);
568
+ const [duration, setDuration] = useState3(0);
569
+ const [isMuted, setIsMuted] = useState3(false);
570
+ const [volume, setVolume] = useState3(1);
571
+ const [isFullscreen, setIsFullscreen] = useState3(false);
572
+ const [showControls, setShowControls] = useState3(true);
573
+ const [hasCompleted, setHasCompleted] = useState3(false);
574
+ const [showCaptions, setShowCaptions] = useState3(false);
386
575
  useEffect2(() => {
387
576
  setHasCompleted(false);
388
577
  }, [src]);
389
- const [playbackRate, setPlaybackRate] = useState2(1);
390
- const [showSpeedMenu, setShowSpeedMenu] = useState2(false);
578
+ const [playbackRate, setPlaybackRate] = useState3(1);
579
+ const [showSpeedMenu, setShowSpeedMenu] = useState3(false);
391
580
  const lastSaveTimeRef = useRef(0);
392
581
  const trackRef = useRef(null);
393
582
  const controlsTimeoutRef = useRef(null);
394
583
  const lastMousePositionRef = useRef({ x: 0, y: 0 });
395
- const isUserInteracting = useCallback(() => {
584
+ const isUserInteracting = useCallback2(() => {
396
585
  if (showSpeedMenu) {
397
586
  return true;
398
587
  }
@@ -409,28 +598,28 @@ var VideoPlayer = ({
409
598
  }
410
599
  return false;
411
600
  }, [showSpeedMenu]);
412
- const clearControlsTimeout = useCallback(() => {
601
+ const clearControlsTimeout = useCallback2(() => {
413
602
  if (controlsTimeoutRef.current) {
414
603
  clearTimeout(controlsTimeoutRef.current);
415
604
  controlsTimeoutRef.current = null;
416
605
  }
417
606
  }, []);
418
- const showControlsWithTimer = useCallback(() => {
607
+ const showControlsWithTimer = useCallback2(() => {
419
608
  setShowControls(true);
420
609
  clearControlsTimeout();
421
610
  if (isFullscreen) {
422
611
  if (isPlaying) {
423
- controlsTimeoutRef.current = window.setTimeout(() => {
612
+ controlsTimeoutRef.current = globalThis.setTimeout(() => {
424
613
  setShowControls(false);
425
614
  }, CONTROLS_HIDE_TIMEOUT);
426
615
  }
427
616
  } else {
428
- controlsTimeoutRef.current = window.setTimeout(() => {
617
+ controlsTimeoutRef.current = globalThis.setTimeout(() => {
429
618
  setShowControls(false);
430
619
  }, CONTROLS_HIDE_TIMEOUT);
431
620
  }
432
621
  }, [isFullscreen, isPlaying, clearControlsTimeout]);
433
- const handleMouseMove = useCallback(
622
+ const handleMouseMove = useCallback2(
434
623
  (event) => {
435
624
  const currentX = event.clientX;
436
625
  const currentY = event.clientY;
@@ -443,14 +632,14 @@ var VideoPlayer = ({
443
632
  },
444
633
  [showControlsWithTimer]
445
634
  );
446
- const handleMouseEnter = useCallback(() => {
635
+ const handleMouseEnter = useCallback2(() => {
447
636
  showControlsWithTimer();
448
637
  }, [showControlsWithTimer]);
449
- const handleMouseLeave = useCallback(() => {
638
+ const handleMouseLeave = useCallback2(() => {
450
639
  const userInteracting = isUserInteracting();
451
640
  clearControlsTimeout();
452
641
  if (!isFullscreen && !userInteracting) {
453
- controlsTimeoutRef.current = window.setTimeout(() => {
642
+ controlsTimeoutRef.current = globalThis.setTimeout(() => {
454
643
  setShowControls(false);
455
644
  }, LEAVE_HIDE_TIMEOUT);
456
645
  }
@@ -514,7 +703,12 @@ var VideoPlayer = ({
514
703
  }
515
704
  };
516
705
  let raf1 = 0, raf2 = 0, tid;
517
- if (typeof window.requestAnimationFrame === "function") {
706
+ if (globalThis.requestAnimationFrame === void 0) {
707
+ tid = globalThis.setTimeout(init, INIT_DELAY);
708
+ return () => {
709
+ if (tid) clearTimeout(tid);
710
+ };
711
+ } else {
518
712
  raf1 = requestAnimationFrame(() => {
519
713
  raf2 = requestAnimationFrame(init);
520
714
  });
@@ -522,18 +716,15 @@ var VideoPlayer = ({
522
716
  cancelAnimationFrame(raf1);
523
717
  cancelAnimationFrame(raf2);
524
718
  };
525
- } else {
526
- tid = window.setTimeout(init, INIT_DELAY);
527
- return () => {
528
- if (tid) clearTimeout(tid);
529
- };
530
719
  }
531
720
  }, []);
532
- const getInitialTime = useCallback(() => {
721
+ const getInitialTime = useCallback2(() => {
533
722
  if (!autoSave || !storageKey) {
534
723
  return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
535
724
  }
536
- const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
725
+ const saved = Number(
726
+ localStorage.getItem(`${storageKey}-${src}`) || Number.NaN
727
+ );
537
728
  const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
538
729
  const hasValidSaved = Number.isFinite(saved) && saved >= 0;
539
730
  if (hasValidInitial) return initialTime;
@@ -547,7 +738,7 @@ var VideoPlayer = ({
547
738
  setCurrentTime(start);
548
739
  }
549
740
  }, [getInitialTime]);
550
- const saveProgress = useCallback(
741
+ const saveProgress = useCallback2(
551
742
  (time) => {
552
743
  if (!autoSave || !storageKey) return;
553
744
  const now = Date.now();
@@ -558,7 +749,7 @@ var VideoPlayer = ({
558
749
  },
559
750
  [autoSave, storageKey, src]
560
751
  );
561
- const togglePlayPause = useCallback(async () => {
752
+ const togglePlayPause = useCallback2(async () => {
562
753
  const video = videoRef.current;
563
754
  if (!video) return;
564
755
  if (!video.paused) {
@@ -570,7 +761,7 @@ var VideoPlayer = ({
570
761
  } catch {
571
762
  }
572
763
  }, []);
573
- const handleVolumeChange = useCallback(
764
+ const handleVolumeChange = useCallback2(
574
765
  (newVolume) => {
575
766
  const video = videoRef.current;
576
767
  if (!video) return;
@@ -589,7 +780,7 @@ var VideoPlayer = ({
589
780
  },
590
781
  [isMuted]
591
782
  );
592
- const toggleMute = useCallback(() => {
783
+ const toggleMute = useCallback2(() => {
593
784
  const video = videoRef.current;
594
785
  if (!video) return;
595
786
  if (isMuted) {
@@ -603,13 +794,13 @@ var VideoPlayer = ({
603
794
  setIsMuted(true);
604
795
  }
605
796
  }, [isMuted, volume]);
606
- const handleSeek = useCallback((newTime) => {
797
+ const handleSeek = useCallback2((newTime) => {
607
798
  const video = videoRef.current;
608
799
  if (video) {
609
800
  video.currentTime = newTime;
610
801
  }
611
802
  }, []);
612
- const toggleFullscreen = useCallback(() => {
803
+ const toggleFullscreen = useCallback2(() => {
613
804
  const container = videoRef.current?.parentElement;
614
805
  if (!container) return;
615
806
  if (!isFullscreen && container.requestFullscreen) {
@@ -618,23 +809,23 @@ var VideoPlayer = ({
618
809
  document.exitFullscreen();
619
810
  }
620
811
  }, [isFullscreen]);
621
- const handleSpeedChange = useCallback((speed) => {
812
+ const handleSpeedChange = useCallback2((speed) => {
622
813
  if (videoRef.current) {
623
814
  videoRef.current.playbackRate = speed;
624
815
  setPlaybackRate(speed);
625
816
  setShowSpeedMenu(false);
626
817
  }
627
818
  }, []);
628
- const toggleSpeedMenu = useCallback(() => {
819
+ const toggleSpeedMenu = useCallback2(() => {
629
820
  setShowSpeedMenu(!showSpeedMenu);
630
821
  }, [showSpeedMenu]);
631
- const toggleCaptions = useCallback(() => {
822
+ const toggleCaptions = useCallback2(() => {
632
823
  if (!trackRef.current?.track || !subtitles) return;
633
824
  const newShowCaptions = !showCaptions;
634
825
  setShowCaptions(newShowCaptions);
635
826
  trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
636
827
  }, [showCaptions, subtitles]);
637
- const checkVideoCompletion = useCallback(
828
+ const checkVideoCompletion = useCallback2(
638
829
  (progressPercent) => {
639
830
  if (progressPercent >= 95 && !hasCompleted) {
640
831
  setHasCompleted(true);
@@ -643,7 +834,7 @@ var VideoPlayer = ({
643
834
  },
644
835
  [hasCompleted, onVideoComplete]
645
836
  );
646
- const handleTimeUpdate = useCallback(() => {
837
+ const handleTimeUpdate = useCallback2(() => {
647
838
  const video = videoRef.current;
648
839
  if (!video) return;
649
840
  const current = video.currentTime;
@@ -656,7 +847,7 @@ var VideoPlayer = ({
656
847
  checkVideoCompletion(progressPercent);
657
848
  }
658
849
  }, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
659
- const handleLoadedMetadata = useCallback(() => {
850
+ const handleLoadedMetadata = useCallback2(() => {
660
851
  if (videoRef.current) {
661
852
  setDuration(videoRef.current.duration);
662
853
  }
@@ -680,46 +871,46 @@ var VideoPlayer = ({
680
871
  }
681
872
  };
682
873
  document.addEventListener("visibilitychange", handleVisibilityChange);
683
- window.addEventListener("blur", handleBlur);
874
+ globalThis.addEventListener("blur", handleBlur);
684
875
  return () => {
685
876
  document.removeEventListener("visibilitychange", handleVisibilityChange);
686
- window.removeEventListener("blur", handleBlur);
877
+ globalThis.removeEventListener("blur", handleBlur);
687
878
  clearControlsTimeout();
688
879
  };
689
880
  }, [isPlaying, clearControlsTimeout]);
690
881
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
691
- const getIconSize = useCallback(() => {
882
+ const getIconSize = useCallback2(() => {
692
883
  if (isTinyMobile) return 18;
693
884
  if (isUltraSmallMobile) return 20;
694
885
  return 24;
695
886
  }, [isTinyMobile, isUltraSmallMobile]);
696
- const getControlsPadding = useCallback(() => {
887
+ const getControlsPadding = useCallback2(() => {
697
888
  if (isTinyMobile) return "px-2 pb-2 pt-1";
698
889
  if (isUltraSmallMobile) return "px-3 pb-3 pt-1";
699
890
  return "px-4 pb-4";
700
891
  }, [isTinyMobile, isUltraSmallMobile]);
701
- const getControlsGap = useCallback(() => {
892
+ const getControlsGap = useCallback2(() => {
702
893
  if (isTinyMobile) return "gap-1";
703
894
  if (isUltraSmallMobile) return "gap-2";
704
895
  return "gap-4";
705
896
  }, [isTinyMobile, isUltraSmallMobile]);
706
- const getProgressBarPadding = useCallback(() => {
897
+ const getProgressBarPadding = useCallback2(() => {
707
898
  if (isTinyMobile) return "px-2 pb-1";
708
899
  if (isUltraSmallMobile) return "px-3 pb-1";
709
900
  return "px-4 pb-2";
710
901
  }, [isTinyMobile, isUltraSmallMobile]);
711
- const getCenterPlayButtonPosition = useCallback(() => {
902
+ const getCenterPlayButtonPosition = useCallback2(() => {
712
903
  if (isTinyMobile) return "items-center justify-center -translate-y-12";
713
904
  if (isUltraSmallMobile) return "items-center justify-center -translate-y-8";
714
905
  return "items-center justify-center";
715
906
  }, [isTinyMobile, isUltraSmallMobile]);
716
- const getTopControlsOpacity = useCallback(() => {
907
+ const getTopControlsOpacity = useCallback2(() => {
717
908
  return showControls ? "opacity-100" : "opacity-0";
718
909
  }, [showControls]);
719
- const getBottomControlsOpacity = useCallback(() => {
910
+ const getBottomControlsOpacity = useCallback2(() => {
720
911
  return showControls ? "opacity-100" : "opacity-0";
721
912
  }, [showControls]);
722
- const handleVideoKeyDown = useCallback(
913
+ const handleVideoKeyDown = useCallback2(
723
914
  (e) => {
724
915
  if (e.key) {
725
916
  e.stopPropagation();
@@ -775,30 +966,43 @@ var VideoPlayer = ({
775
966
  ]
776
967
  );
777
968
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
778
- (title || subtitleText) && /* @__PURE__ */ jsx3("div", { className: "bg-subject-1 px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
779
- title && /* @__PURE__ */ jsx3(
780
- Text_default,
781
- {
782
- as: "h2",
783
- size: "lg",
784
- weight: "bold",
785
- color: "text-text-900",
786
- className: "leading-5 tracking-wide",
787
- children: title
788
- }
789
- ),
790
- subtitleText && /* @__PURE__ */ jsx3(
791
- Text_default,
969
+ (title || subtitleText) && /* @__PURE__ */ jsxs("div", { className: "bg-subject-1 px-8 py-4 flex items-end justify-between min-h-20", children: [
970
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
971
+ title && /* @__PURE__ */ jsx4(
972
+ Text_default,
973
+ {
974
+ as: "h2",
975
+ size: "lg",
976
+ weight: "bold",
977
+ color: "text-text-900",
978
+ className: "leading-5 tracking-wide",
979
+ children: title
980
+ }
981
+ ),
982
+ subtitleText && /* @__PURE__ */ jsx4(
983
+ Text_default,
984
+ {
985
+ as: "p",
986
+ size: "sm",
987
+ weight: "normal",
988
+ color: "text-text-600",
989
+ className: "leading-5",
990
+ children: subtitleText
991
+ }
992
+ )
993
+ ] }),
994
+ showDownloadButton && downloadContent && /* @__PURE__ */ jsx4(
995
+ DownloadButton_default,
792
996
  {
793
- as: "p",
794
- size: "sm",
795
- weight: "normal",
796
- color: "text-text-600",
797
- className: "leading-5",
798
- children: subtitleText
997
+ content: downloadContent,
998
+ lessonTitle: title,
999
+ onDownloadStart,
1000
+ onDownloadComplete,
1001
+ onDownloadError,
1002
+ className: "flex-shrink-0"
799
1003
  }
800
1004
  )
801
- ] }) }),
1005
+ ] }),
802
1006
  /* @__PURE__ */ jsxs(
803
1007
  "section",
804
1008
  {
@@ -814,7 +1018,7 @@ var VideoPlayer = ({
814
1018
  onTouchStart: handleMouseEnter,
815
1019
  onMouseLeave: handleMouseLeave,
816
1020
  children: [
817
- /* @__PURE__ */ jsx3(
1021
+ /* @__PURE__ */ jsx4(
818
1022
  "video",
819
1023
  {
820
1024
  ref: videoRef,
@@ -829,7 +1033,7 @@ var VideoPlayer = ({
829
1033
  onKeyDown: handleVideoKeyDown,
830
1034
  tabIndex: 0,
831
1035
  "aria-label": title ? `Video: ${title}` : "Video player",
832
- children: /* @__PURE__ */ jsx3(
1036
+ children: /* @__PURE__ */ jsx4(
833
1037
  "track",
834
1038
  {
835
1039
  ref: trackRef,
@@ -842,17 +1046,17 @@ var VideoPlayer = ({
842
1046
  )
843
1047
  }
844
1048
  ),
845
- !isPlaying && /* @__PURE__ */ jsx3(
1049
+ !isPlaying && /* @__PURE__ */ jsx4(
846
1050
  "div",
847
1051
  {
848
1052
  className: cn(
849
1053
  "absolute inset-0 flex bg-black/30 transition-opacity",
850
1054
  getCenterPlayButtonPosition()
851
1055
  ),
852
- children: /* @__PURE__ */ jsx3(
1056
+ children: /* @__PURE__ */ jsx4(
853
1057
  IconButton_default,
854
1058
  {
855
- icon: /* @__PURE__ */ jsx3(Play, { size: 32, weight: "regular", className: "ml-1" }),
1059
+ icon: /* @__PURE__ */ jsx4(Play, { size: 32, weight: "regular", className: "ml-1" }),
856
1060
  onClick: togglePlayPause,
857
1061
  "aria-label": "Play video",
858
1062
  className: "!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200"
@@ -860,17 +1064,17 @@ var VideoPlayer = ({
860
1064
  )
861
1065
  }
862
1066
  ),
863
- /* @__PURE__ */ jsx3(
1067
+ /* @__PURE__ */ jsx4(
864
1068
  "div",
865
1069
  {
866
1070
  className: cn(
867
1071
  "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
868
1072
  getTopControlsOpacity()
869
1073
  ),
870
- children: /* @__PURE__ */ jsx3("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx3(
1074
+ children: /* @__PURE__ */ jsx4("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx4(
871
1075
  IconButton_default,
872
1076
  {
873
- icon: isFullscreen ? /* @__PURE__ */ jsx3(ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ jsx3(ArrowsOutSimple, { size: 24 }),
1077
+ icon: isFullscreen ? /* @__PURE__ */ jsx4(ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ jsx4(ArrowsOutSimple, { size: 24 }),
874
1078
  onClick: toggleFullscreen,
875
1079
  "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
876
1080
  className: "!bg-transparent !text-white hover:!bg-white/20"
@@ -886,7 +1090,7 @@ var VideoPlayer = ({
886
1090
  getBottomControlsOpacity()
887
1091
  ),
888
1092
  children: [
889
- /* @__PURE__ */ jsx3(
1093
+ /* @__PURE__ */ jsx4(
890
1094
  ProgressBar,
891
1095
  {
892
1096
  currentTime,
@@ -905,16 +1109,16 @@ var VideoPlayer = ({
905
1109
  ),
906
1110
  children: [
907
1111
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", getControlsGap()), children: [
908
- /* @__PURE__ */ jsx3(
1112
+ /* @__PURE__ */ jsx4(
909
1113
  IconButton_default,
910
1114
  {
911
- icon: isPlaying ? /* @__PURE__ */ jsx3(Pause, { size: getIconSize() }) : /* @__PURE__ */ jsx3(Play, { size: getIconSize() }),
1115
+ icon: isPlaying ? /* @__PURE__ */ jsx4(Pause, { size: getIconSize() }) : /* @__PURE__ */ jsx4(Play, { size: getIconSize() }),
912
1116
  onClick: togglePlayPause,
913
1117
  "aria-label": isPlaying ? "Pause" : "Play",
914
1118
  className: "!bg-transparent !text-white hover:!bg-white/20"
915
1119
  }
916
1120
  ),
917
- /* @__PURE__ */ jsx3(
1121
+ /* @__PURE__ */ jsx4(
918
1122
  VolumeControls,
919
1123
  {
920
1124
  volume,
@@ -925,10 +1129,10 @@ var VideoPlayer = ({
925
1129
  showSlider: !isUltraSmallMobile
926
1130
  }
927
1131
  ),
928
- subtitles && /* @__PURE__ */ jsx3(
1132
+ subtitles && /* @__PURE__ */ jsx4(
929
1133
  IconButton_default,
930
1134
  {
931
- icon: /* @__PURE__ */ jsx3(ClosedCaptioning, { size: getIconSize() }),
1135
+ icon: /* @__PURE__ */ jsx4(ClosedCaptioning, { size: getIconSize() }),
932
1136
  onClick: toggleCaptions,
933
1137
  "aria-label": showCaptions ? "Hide captions" : "Show captions",
934
1138
  className: cn(
@@ -943,7 +1147,7 @@ var VideoPlayer = ({
943
1147
  formatTime(duration)
944
1148
  ] })
945
1149
  ] }),
946
- /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx3(
1150
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx4(
947
1151
  SpeedMenu,
948
1152
  {
949
1153
  showSpeedMenu,