analytica-frontend-lib 1.1.10 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6772,7 +6772,12 @@ var NotFound = ({
6772
6772
  var NotFound_default = NotFound;
6773
6773
 
6774
6774
  // src/components/VideoPlayer/VideoPlayer.tsx
6775
- import { useRef as useRef9, useState as useState13, useEffect as useEffect11, useCallback } from "react";
6775
+ import {
6776
+ useRef as useRef9,
6777
+ useState as useState13,
6778
+ useEffect as useEffect11,
6779
+ useCallback
6780
+ } from "react";
6776
6781
  import {
6777
6782
  Play as Play2,
6778
6783
  Pause,
@@ -6784,12 +6789,93 @@ import {
6784
6789
  DotsThreeVertical as DotsThreeVertical2
6785
6790
  } from "phosphor-react";
6786
6791
  import { jsx as jsx36, jsxs as jsxs30 } from "react/jsx-runtime";
6792
+ var CONTROLS_HIDE_TIMEOUT = 3e3;
6793
+ var LEAVE_HIDE_TIMEOUT = 1e3;
6787
6794
  var formatTime = (seconds) => {
6788
6795
  if (!seconds || isNaN(seconds)) return "0:00";
6789
6796
  const mins = Math.floor(seconds / 60);
6790
6797
  const secs = Math.floor(seconds % 60);
6791
6798
  return `${mins}:${secs.toString().padStart(2, "0")}`;
6792
6799
  };
6800
+ var ProgressBar2 = ({
6801
+ currentTime,
6802
+ duration,
6803
+ progressPercentage,
6804
+ onSeek
6805
+ }) => /* @__PURE__ */ jsx36("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx36(
6806
+ "input",
6807
+ {
6808
+ type: "range",
6809
+ min: 0,
6810
+ max: duration || 100,
6811
+ value: currentTime,
6812
+ onChange: (e) => onSeek(parseFloat(e.target.value)),
6813
+ 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",
6814
+ "aria-label": "Video progress",
6815
+ style: {
6816
+ background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
6817
+ }
6818
+ }
6819
+ ) });
6820
+ var VolumeControls = ({
6821
+ volume,
6822
+ isMuted,
6823
+ onVolumeChange,
6824
+ onToggleMute
6825
+ }) => /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
6826
+ /* @__PURE__ */ jsx36(
6827
+ IconButton_default,
6828
+ {
6829
+ icon: isMuted ? /* @__PURE__ */ jsx36(SpeakerSlash, { size: 24 }) : /* @__PURE__ */ jsx36(SpeakerHigh2, { size: 24 }),
6830
+ onClick: onToggleMute,
6831
+ "aria-label": isMuted ? "Unmute" : "Mute",
6832
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6833
+ }
6834
+ ),
6835
+ /* @__PURE__ */ jsx36(
6836
+ "input",
6837
+ {
6838
+ type: "range",
6839
+ min: 0,
6840
+ max: 100,
6841
+ value: Math.round(volume * 100),
6842
+ onChange: (e) => onVolumeChange(parseInt(e.target.value)),
6843
+ className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
6844
+ "aria-label": "Volume control",
6845
+ style: {
6846
+ background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
6847
+ }
6848
+ }
6849
+ )
6850
+ ] });
6851
+ var SpeedMenu = ({
6852
+ showSpeedMenu,
6853
+ playbackRate,
6854
+ onToggleMenu,
6855
+ onSpeedChange
6856
+ }) => /* @__PURE__ */ jsxs30("div", { className: "relative", children: [
6857
+ /* @__PURE__ */ jsx36(
6858
+ IconButton_default,
6859
+ {
6860
+ icon: /* @__PURE__ */ jsx36(DotsThreeVertical2, { size: 24 }),
6861
+ onClick: onToggleMenu,
6862
+ "aria-label": "Playback speed",
6863
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6864
+ }
6865
+ ),
6866
+ showSpeedMenu && /* @__PURE__ */ jsx36("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ jsxs30(
6867
+ "button",
6868
+ {
6869
+ onClick: () => onSpeedChange(speed),
6870
+ className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
6871
+ children: [
6872
+ speed,
6873
+ "x"
6874
+ ]
6875
+ },
6876
+ speed
6877
+ )) })
6878
+ ] });
6793
6879
  var VideoPlayer = ({
6794
6880
  src,
6795
6881
  poster,
@@ -6814,10 +6900,68 @@ var VideoPlayer = ({
6814
6900
  const [showControls, setShowControls] = useState13(true);
6815
6901
  const [hasCompleted, setHasCompleted] = useState13(false);
6816
6902
  const [showCaptions, setShowCaptions] = useState13(false);
6903
+ useEffect11(() => {
6904
+ setHasCompleted(false);
6905
+ }, [src]);
6817
6906
  const [playbackRate, setPlaybackRate] = useState13(1);
6818
6907
  const [showSpeedMenu, setShowSpeedMenu] = useState13(false);
6819
6908
  const lastSaveTimeRef = useRef9(0);
6820
6909
  const trackRef = useRef9(null);
6910
+ const controlsTimeoutRef = useRef9(null);
6911
+ const lastMousePositionRef = useRef9({ x: 0, y: 0 });
6912
+ const mouseMoveTimeoutRef = useRef9(null);
6913
+ const isUserInteracting = useCallback(() => {
6914
+ if (showSpeedMenu) return true;
6915
+ const activeElement = document.activeElement;
6916
+ const videoContainer = videoRef.current?.parentElement;
6917
+ if (activeElement && videoContainer?.contains(activeElement)) {
6918
+ const isControl = activeElement.matches("button, input, [tabindex]");
6919
+ if (isControl) return true;
6920
+ }
6921
+ return false;
6922
+ }, [showSpeedMenu]);
6923
+ const clearControlsTimeout = useCallback(() => {
6924
+ if (controlsTimeoutRef.current) {
6925
+ clearTimeout(controlsTimeoutRef.current);
6926
+ controlsTimeoutRef.current = null;
6927
+ }
6928
+ }, []);
6929
+ const clearMouseMoveTimeout = useCallback(() => {
6930
+ if (mouseMoveTimeoutRef.current) {
6931
+ clearTimeout(mouseMoveTimeoutRef.current);
6932
+ mouseMoveTimeoutRef.current = null;
6933
+ }
6934
+ }, []);
6935
+ const showControlsWithTimer = useCallback(() => {
6936
+ setShowControls(true);
6937
+ clearControlsTimeout();
6938
+ if (isPlaying) {
6939
+ controlsTimeoutRef.current = window.setTimeout(() => {
6940
+ setShowControls(false);
6941
+ }, CONTROLS_HIDE_TIMEOUT);
6942
+ }
6943
+ }, [isPlaying, clearControlsTimeout]);
6944
+ const handleMouseMove = useCallback(
6945
+ (event) => {
6946
+ const currentX = event.clientX;
6947
+ const currentY = event.clientY;
6948
+ const lastPos = lastMousePositionRef.current;
6949
+ const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
6950
+ if (hasMoved) {
6951
+ lastMousePositionRef.current = { x: currentX, y: currentY };
6952
+ showControlsWithTimer();
6953
+ }
6954
+ },
6955
+ [showControlsWithTimer]
6956
+ );
6957
+ const handleMouseLeave = useCallback(() => {
6958
+ clearControlsTimeout();
6959
+ if (isPlaying && !isUserInteracting()) {
6960
+ controlsTimeoutRef.current = window.setTimeout(() => {
6961
+ setShowControls(false);
6962
+ }, LEAVE_HIDE_TIMEOUT);
6963
+ }
6964
+ }, [isPlaying, clearControlsTimeout, isUserInteracting]);
6821
6965
  useEffect11(() => {
6822
6966
  if (videoRef.current) {
6823
6967
  videoRef.current.volume = volume;
@@ -6825,84 +6969,129 @@ var VideoPlayer = ({
6825
6969
  }
6826
6970
  }, [volume, isMuted]);
6827
6971
  useEffect11(() => {
6828
- if (!autoSave || !storageKey) return;
6829
- const raw = localStorage.getItem(`${storageKey}-${src}`);
6830
- const saved = raw !== null ? Number(raw) : NaN;
6831
- const hasValidSaved = Number.isFinite(saved) && saved >= 0;
6832
- const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
6833
- let start;
6834
- if (hasValidInitial) {
6835
- start = initialTime;
6836
- } else if (hasValidSaved) {
6837
- start = saved;
6972
+ const video = videoRef.current;
6973
+ if (!video) return;
6974
+ const onPlay = () => setIsPlaying(true);
6975
+ const onPause = () => setIsPlaying(false);
6976
+ const onEnded = () => setIsPlaying(false);
6977
+ video.addEventListener("play", onPlay);
6978
+ video.addEventListener("pause", onPause);
6979
+ video.addEventListener("ended", onEnded);
6980
+ return () => {
6981
+ video.removeEventListener("play", onPlay);
6982
+ video.removeEventListener("pause", onPause);
6983
+ video.removeEventListener("ended", onEnded);
6984
+ };
6985
+ }, []);
6986
+ useEffect11(() => {
6987
+ if (isPlaying) {
6988
+ showControlsWithTimer();
6838
6989
  } else {
6839
- start = void 0;
6990
+ clearControlsTimeout();
6991
+ setShowControls(true);
6992
+ }
6993
+ }, [isPlaying, showControlsWithTimer, clearControlsTimeout]);
6994
+ useEffect11(() => {
6995
+ const handleFullscreenChange = () => {
6996
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
6997
+ setIsFullscreen(isCurrentlyFullscreen);
6998
+ if (isCurrentlyFullscreen) {
6999
+ showControlsWithTimer();
7000
+ }
7001
+ };
7002
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
7003
+ return () => {
7004
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
7005
+ };
7006
+ }, [showControlsWithTimer]);
7007
+ const getInitialTime = useCallback(() => {
7008
+ if (!autoSave || !storageKey) {
7009
+ return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
6840
7010
  }
7011
+ const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
7012
+ const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
7013
+ const hasValidSaved = Number.isFinite(saved) && saved >= 0;
7014
+ if (hasValidInitial) return initialTime;
7015
+ if (hasValidSaved) return saved;
7016
+ return void 0;
7017
+ }, [autoSave, storageKey, src, initialTime]);
7018
+ useEffect11(() => {
7019
+ const start = getInitialTime();
6841
7020
  if (start !== void 0 && videoRef.current) {
6842
7021
  videoRef.current.currentTime = start;
6843
7022
  setCurrentTime(start);
6844
7023
  }
6845
- }, [src, storageKey, autoSave, initialTime]);
6846
- const saveProgress = useCallback(() => {
6847
- if (!autoSave || !storageKey) return;
6848
- const now = Date.now();
6849
- if (now - lastSaveTimeRef.current > 5e3) {
6850
- localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());
6851
- lastSaveTimeRef.current = now;
6852
- }
6853
- }, [autoSave, storageKey, src, currentTime]);
6854
- const togglePlayPause = useCallback(() => {
6855
- if (videoRef.current) {
6856
- if (isPlaying) {
6857
- videoRef.current.pause();
6858
- } else {
6859
- videoRef.current.play();
7024
+ }, [getInitialTime]);
7025
+ const saveProgress = useCallback(
7026
+ (time) => {
7027
+ if (!autoSave || !storageKey) return;
7028
+ const now = Date.now();
7029
+ if (now - lastSaveTimeRef.current > 5e3) {
7030
+ localStorage.setItem(`${storageKey}-${src}`, time.toString());
7031
+ lastSaveTimeRef.current = now;
6860
7032
  }
6861
- setIsPlaying(!isPlaying);
7033
+ },
7034
+ [autoSave, storageKey, src]
7035
+ );
7036
+ const togglePlayPause = useCallback(async () => {
7037
+ const video = videoRef.current;
7038
+ if (!video) return;
7039
+ if (!video.paused) {
7040
+ video.pause();
7041
+ return;
7042
+ }
7043
+ try {
7044
+ await video.play();
7045
+ } catch {
6862
7046
  }
6863
- }, [isPlaying]);
7047
+ }, []);
6864
7048
  const handleVolumeChange = useCallback(
6865
7049
  (newVolume) => {
6866
- if (videoRef.current) {
6867
- const volumeValue = newVolume / 100;
6868
- videoRef.current.volume = volumeValue;
6869
- setVolume(volumeValue);
6870
- if (volumeValue === 0) {
6871
- videoRef.current.muted = true;
6872
- setIsMuted(true);
6873
- } else if (isMuted) {
6874
- videoRef.current.muted = false;
6875
- setIsMuted(false);
6876
- }
7050
+ const video = videoRef.current;
7051
+ if (!video) return;
7052
+ const volumeValue = newVolume / 100;
7053
+ video.volume = volumeValue;
7054
+ setVolume(volumeValue);
7055
+ const shouldMute = volumeValue === 0;
7056
+ const shouldUnmute = volumeValue > 0 && isMuted;
7057
+ if (shouldMute) {
7058
+ video.muted = true;
7059
+ setIsMuted(true);
7060
+ } else if (shouldUnmute) {
7061
+ video.muted = false;
7062
+ setIsMuted(false);
6877
7063
  }
6878
7064
  },
6879
7065
  [isMuted]
6880
7066
  );
6881
7067
  const toggleMute = useCallback(() => {
6882
- if (videoRef.current) {
6883
- if (isMuted) {
6884
- const restoreVolume = volume > 0 ? volume : 0.5;
6885
- videoRef.current.volume = restoreVolume;
6886
- videoRef.current.muted = false;
6887
- setVolume(restoreVolume);
6888
- setIsMuted(false);
6889
- } else {
6890
- videoRef.current.muted = true;
6891
- setIsMuted(true);
6892
- }
7068
+ const video = videoRef.current;
7069
+ if (!video) return;
7070
+ if (isMuted) {
7071
+ const restoreVolume = volume > 0 ? volume : 0.5;
7072
+ video.volume = restoreVolume;
7073
+ video.muted = false;
7074
+ setVolume(restoreVolume);
7075
+ setIsMuted(false);
7076
+ } else {
7077
+ video.muted = true;
7078
+ setIsMuted(true);
6893
7079
  }
6894
7080
  }, [isMuted, volume]);
7081
+ const handleSeek = useCallback((newTime) => {
7082
+ const video = videoRef.current;
7083
+ if (video) {
7084
+ video.currentTime = newTime;
7085
+ }
7086
+ }, []);
6895
7087
  const toggleFullscreen = useCallback(() => {
6896
7088
  const container = videoRef.current?.parentElement;
6897
7089
  if (!container) return;
6898
- if (!isFullscreen) {
6899
- if (container.requestFullscreen) {
6900
- container.requestFullscreen();
6901
- }
6902
- } else if (document.exitFullscreen) {
7090
+ if (!isFullscreen && container.requestFullscreen) {
7091
+ container.requestFullscreen();
7092
+ } else if (isFullscreen && document.exitFullscreen) {
6903
7093
  document.exitFullscreen();
6904
7094
  }
6905
- setIsFullscreen(!isFullscreen);
6906
7095
  }, [isFullscreen]);
6907
7096
  const handleSpeedChange = useCallback((speed) => {
6908
7097
  if (videoRef.current) {
@@ -6920,29 +7109,28 @@ var VideoPlayer = ({
6920
7109
  setShowCaptions(newShowCaptions);
6921
7110
  trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
6922
7111
  }, [showCaptions, subtitles]);
6923
- const handleTimeUpdate = useCallback(() => {
6924
- if (videoRef.current) {
6925
- const current = videoRef.current.currentTime;
6926
- setCurrentTime(current);
6927
- saveProgress();
6928
- onTimeUpdate?.(current);
6929
- if (duration > 0) {
6930
- const progressPercent = current / duration * 100;
6931
- onProgress?.(progressPercent);
6932
- if (progressPercent >= 95 && !hasCompleted) {
6933
- setHasCompleted(true);
6934
- onVideoComplete?.();
6935
- }
7112
+ const checkVideoCompletion = useCallback(
7113
+ (progressPercent) => {
7114
+ if (progressPercent >= 95 && !hasCompleted) {
7115
+ setHasCompleted(true);
7116
+ onVideoComplete?.();
6936
7117
  }
7118
+ },
7119
+ [hasCompleted, onVideoComplete]
7120
+ );
7121
+ const handleTimeUpdate = useCallback(() => {
7122
+ const video = videoRef.current;
7123
+ if (!video) return;
7124
+ const current = video.currentTime;
7125
+ setCurrentTime(current);
7126
+ saveProgress(current);
7127
+ onTimeUpdate?.(current);
7128
+ if (duration > 0) {
7129
+ const progressPercent = current / duration * 100;
7130
+ onProgress?.(progressPercent);
7131
+ checkVideoCompletion(progressPercent);
6937
7132
  }
6938
- }, [
6939
- duration,
6940
- saveProgress,
6941
- onTimeUpdate,
6942
- onProgress,
6943
- onVideoComplete,
6944
- hasCompleted
6945
- ]);
7133
+ }, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
6946
7134
  const handleLoadedMetadata = useCallback(() => {
6947
7135
  if (videoRef.current) {
6948
7136
  setDuration(videoRef.current.duration);
@@ -6971,9 +7159,78 @@ var VideoPlayer = ({
6971
7159
  return () => {
6972
7160
  document.removeEventListener("visibilitychange", handleVisibilityChange);
6973
7161
  window.removeEventListener("blur", handleBlur);
7162
+ clearControlsTimeout();
7163
+ clearMouseMoveTimeout();
6974
7164
  };
6975
- }, [isPlaying]);
7165
+ }, [isPlaying, clearControlsTimeout, clearMouseMoveTimeout]);
6976
7166
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
7167
+ const getTopControlsOpacity = useCallback(() => {
7168
+ if (isFullscreen) {
7169
+ return showControls ? "opacity-100" : "opacity-0";
7170
+ }
7171
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7172
+ }, [isFullscreen, showControls, isPlaying]);
7173
+ const getBottomControlsOpacity = useCallback(() => {
7174
+ if (isFullscreen) {
7175
+ return showControls ? "opacity-100" : "opacity-0";
7176
+ }
7177
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7178
+ }, [isFullscreen, showControls, isPlaying]);
7179
+ const handleVideoKeyDown = useCallback(
7180
+ (e) => {
7181
+ if (e.key) {
7182
+ e.stopPropagation();
7183
+ showControlsWithTimer();
7184
+ }
7185
+ switch (e.key) {
7186
+ case " ":
7187
+ case "Enter":
7188
+ e.preventDefault();
7189
+ togglePlayPause();
7190
+ break;
7191
+ case "ArrowLeft":
7192
+ e.preventDefault();
7193
+ if (videoRef.current) {
7194
+ videoRef.current.currentTime -= 10;
7195
+ }
7196
+ break;
7197
+ case "ArrowRight":
7198
+ e.preventDefault();
7199
+ if (videoRef.current) {
7200
+ videoRef.current.currentTime += 10;
7201
+ }
7202
+ break;
7203
+ case "ArrowUp":
7204
+ e.preventDefault();
7205
+ handleVolumeChange(Math.min(100, volume * 100 + 10));
7206
+ break;
7207
+ case "ArrowDown":
7208
+ e.preventDefault();
7209
+ handleVolumeChange(Math.max(0, volume * 100 - 10));
7210
+ break;
7211
+ case "m":
7212
+ case "M":
7213
+ e.preventDefault();
7214
+ toggleMute();
7215
+ break;
7216
+ case "f":
7217
+ case "F":
7218
+ e.preventDefault();
7219
+ toggleFullscreen();
7220
+ break;
7221
+ default:
7222
+ break;
7223
+ }
7224
+ },
7225
+ [
7226
+ showControlsWithTimer,
7227
+ togglePlayPause,
7228
+ handleVolumeChange,
7229
+ volume,
7230
+ toggleMute,
7231
+ toggleFullscreen
7232
+ ]
7233
+ );
6977
7234
  return /* @__PURE__ */ jsxs30("div", { className: cn("flex flex-col", className), children: [
6978
7235
  (title || subtitleText) && /* @__PURE__ */ jsx36("div", { className: "bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ jsxs30("div", { className: "flex flex-col gap-1", children: [
6979
7236
  title && /* @__PURE__ */ jsx36(
@@ -7000,12 +7257,18 @@ var VideoPlayer = ({
7000
7257
  )
7001
7258
  ] }) }),
7002
7259
  /* @__PURE__ */ jsxs30(
7003
- "div",
7260
+ "section",
7004
7261
  {
7005
7262
  className: cn(
7006
7263
  "relative w-full bg-background overflow-hidden group",
7007
- title || subtitleText ? "rounded-b-xl" : "rounded-xl"
7264
+ title || subtitleText ? "rounded-b-xl" : "rounded-xl",
7265
+ // Hide cursor when controls are hidden and video is playing
7266
+ isPlaying && !showControls ? "cursor-none group-hover:cursor-default" : "cursor-default"
7008
7267
  ),
7268
+ "aria-label": title ? `Video player: ${title}` : "Video player",
7269
+ onMouseMove: handleMouseMove,
7270
+ onMouseEnter: showControlsWithTimer,
7271
+ onMouseLeave: handleMouseLeave,
7009
7272
  children: [
7010
7273
  /* @__PURE__ */ jsx36(
7011
7274
  "video",
@@ -7018,39 +7281,7 @@ var VideoPlayer = ({
7018
7281
  onTimeUpdate: handleTimeUpdate,
7019
7282
  onLoadedMetadata: handleLoadedMetadata,
7020
7283
  onClick: togglePlayPause,
7021
- onKeyDown: (e) => {
7022
- if (e.key) {
7023
- setShowControls(true);
7024
- }
7025
- if (e.key === " " || e.key === "Enter") {
7026
- e.preventDefault();
7027
- togglePlayPause();
7028
- }
7029
- if (e.key === "ArrowLeft" && videoRef.current) {
7030
- e.preventDefault();
7031
- videoRef.current.currentTime -= 10;
7032
- }
7033
- if (e.key === "ArrowRight" && videoRef.current) {
7034
- e.preventDefault();
7035
- videoRef.current.currentTime += 10;
7036
- }
7037
- if (e.key === "ArrowUp") {
7038
- e.preventDefault();
7039
- handleVolumeChange(Math.min(100, volume * 100 + 10));
7040
- }
7041
- if (e.key === "ArrowDown") {
7042
- e.preventDefault();
7043
- handleVolumeChange(Math.max(0, volume * 100 - 10));
7044
- }
7045
- if (e.key === "m" || e.key === "M") {
7046
- e.preventDefault();
7047
- toggleMute();
7048
- }
7049
- if (e.key === "f" || e.key === "F") {
7050
- e.preventDefault();
7051
- toggleFullscreen();
7052
- }
7053
- },
7284
+ onKeyDown: handleVideoKeyDown,
7054
7285
  tabIndex: 0,
7055
7286
  "aria-label": title ? `Video: ${title}` : "Video player",
7056
7287
  children: /* @__PURE__ */ jsx36(
@@ -7080,9 +7311,9 @@ var VideoPlayer = ({
7080
7311
  {
7081
7312
  className: cn(
7082
7313
  "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
7083
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7314
+ getTopControlsOpacity()
7084
7315
  ),
7085
- children: /* @__PURE__ */ jsx36("div", { className: "ml-auto block", children: /* @__PURE__ */ jsx36(
7316
+ children: /* @__PURE__ */ jsx36("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx36(
7086
7317
  IconButton_default,
7087
7318
  {
7088
7319
  icon: isFullscreen ? /* @__PURE__ */ jsx36(ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ jsx36(ArrowsOutSimple, { size: 24 }),
@@ -7098,29 +7329,18 @@ var VideoPlayer = ({
7098
7329
  {
7099
7330
  className: cn(
7100
7331
  "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
7101
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7332
+ getBottomControlsOpacity()
7102
7333
  ),
7103
7334
  children: [
7104
- /* @__PURE__ */ jsx36("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx36(
7105
- "input",
7335
+ /* @__PURE__ */ jsx36(
7336
+ ProgressBar2,
7106
7337
  {
7107
- type: "range",
7108
- min: 0,
7109
- max: duration || 100,
7110
- value: currentTime,
7111
- onChange: (e) => {
7112
- const newTime = parseFloat(e.target.value);
7113
- if (videoRef.current) {
7114
- videoRef.current.currentTime = newTime;
7115
- }
7116
- },
7117
- 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",
7118
- "aria-label": "Video progress",
7119
- style: {
7120
- background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
7121
- }
7338
+ currentTime,
7339
+ duration,
7340
+ progressPercentage,
7341
+ onSeek: handleSeek
7122
7342
  }
7123
- ) }),
7343
+ ),
7124
7344
  /* @__PURE__ */ jsxs30("div", { className: "flex items-center justify-between px-4 pb-4", children: [
7125
7345
  /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-4", children: [
7126
7346
  /* @__PURE__ */ jsx36(
@@ -7132,32 +7352,15 @@ var VideoPlayer = ({
7132
7352
  className: "!bg-transparent !text-white hover:!bg-white/20"
7133
7353
  }
7134
7354
  ),
7135
- /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2", children: [
7136
- /* @__PURE__ */ jsx36(
7137
- IconButton_default,
7138
- {
7139
- icon: isMuted ? /* @__PURE__ */ jsx36(SpeakerSlash, { size: 24 }) : /* @__PURE__ */ jsx36(SpeakerHigh2, { size: 24 }),
7140
- onClick: toggleMute,
7141
- "aria-label": isMuted ? "Unmute" : "Mute",
7142
- className: "!bg-transparent !text-white hover:!bg-white/20"
7143
- }
7144
- ),
7145
- /* @__PURE__ */ jsx36(
7146
- "input",
7147
- {
7148
- type: "range",
7149
- min: 0,
7150
- max: 100,
7151
- value: Math.round(volume * 100),
7152
- onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
7153
- className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
7154
- "aria-label": "Volume control",
7155
- style: {
7156
- background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
7157
- }
7158
- }
7159
- )
7160
- ] }),
7355
+ /* @__PURE__ */ jsx36(
7356
+ VolumeControls,
7357
+ {
7358
+ volume,
7359
+ isMuted,
7360
+ onVolumeChange: handleVolumeChange,
7361
+ onToggleMute: toggleMute
7362
+ }
7363
+ ),
7161
7364
  subtitles && /* @__PURE__ */ jsx36(
7162
7365
  IconButton_default,
7163
7366
  {
@@ -7176,29 +7379,15 @@ var VideoPlayer = ({
7176
7379
  formatTime(duration)
7177
7380
  ] })
7178
7381
  ] }),
7179
- /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs30("div", { className: "relative", children: [
7180
- /* @__PURE__ */ jsx36(
7181
- IconButton_default,
7182
- {
7183
- icon: /* @__PURE__ */ jsx36(DotsThreeVertical2, { size: 24 }),
7184
- onClick: toggleSpeedMenu,
7185
- "aria-label": "Playback speed",
7186
- className: "!bg-transparent !text-white hover:!bg-white/20"
7187
- }
7188
- ),
7189
- showSpeedMenu && /* @__PURE__ */ jsx36("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ jsxs30(
7190
- "button",
7191
- {
7192
- onClick: () => handleSpeedChange(speed),
7193
- className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
7194
- children: [
7195
- speed,
7196
- "x"
7197
- ]
7198
- },
7199
- speed
7200
- )) })
7201
- ] }) })
7382
+ /* @__PURE__ */ jsx36("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx36(
7383
+ SpeedMenu,
7384
+ {
7385
+ showSpeedMenu,
7386
+ playbackRate,
7387
+ onToggleMenu: toggleSpeedMenu,
7388
+ onSpeedChange: handleSpeedChange
7389
+ }
7390
+ ) })
7202
7391
  ] })
7203
7392
  ]
7204
7393
  }