analytica-frontend-lib 1.1.10 → 1.1.11

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.js CHANGED
@@ -6832,6 +6832,85 @@ var formatTime = (seconds) => {
6832
6832
  const secs = Math.floor(seconds % 60);
6833
6833
  return `${mins}:${secs.toString().padStart(2, "0")}`;
6834
6834
  };
6835
+ var ProgressBar2 = ({
6836
+ currentTime,
6837
+ duration,
6838
+ progressPercentage,
6839
+ onSeek
6840
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6841
+ "input",
6842
+ {
6843
+ type: "range",
6844
+ min: 0,
6845
+ max: duration || 100,
6846
+ value: currentTime,
6847
+ onChange: (e) => onSeek(parseFloat(e.target.value)),
6848
+ 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",
6849
+ "aria-label": "Video progress",
6850
+ style: {
6851
+ background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
6852
+ }
6853
+ }
6854
+ ) });
6855
+ var VolumeControls = ({
6856
+ volume,
6857
+ isMuted,
6858
+ onVolumeChange,
6859
+ onToggleMute
6860
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
6861
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6862
+ IconButton_default,
6863
+ {
6864
+ icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerHigh, { size: 24 }),
6865
+ onClick: onToggleMute,
6866
+ "aria-label": isMuted ? "Unmute" : "Mute",
6867
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6868
+ }
6869
+ ),
6870
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6871
+ "input",
6872
+ {
6873
+ type: "range",
6874
+ min: 0,
6875
+ max: 100,
6876
+ value: Math.round(volume * 100),
6877
+ onChange: (e) => onVolumeChange(parseInt(e.target.value)),
6878
+ className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
6879
+ "aria-label": "Volume control",
6880
+ style: {
6881
+ background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
6882
+ }
6883
+ }
6884
+ )
6885
+ ] });
6886
+ var SpeedMenu = ({
6887
+ showSpeedMenu,
6888
+ playbackRate,
6889
+ onToggleMenu,
6890
+ onSpeedChange
6891
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative", children: [
6892
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6893
+ IconButton_default,
6894
+ {
6895
+ icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.DotsThreeVertical, { size: 24 }),
6896
+ onClick: onToggleMenu,
6897
+ "aria-label": "Playback speed",
6898
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6899
+ }
6900
+ ),
6901
+ showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("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__ */ (0, import_jsx_runtime36.jsxs)(
6902
+ "button",
6903
+ {
6904
+ onClick: () => onSpeedChange(speed),
6905
+ 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"}`,
6906
+ children: [
6907
+ speed,
6908
+ "x"
6909
+ ]
6910
+ },
6911
+ speed
6912
+ )) })
6913
+ ] });
6835
6914
  var VideoPlayer = ({
6836
6915
  src,
6837
6916
  poster,
@@ -6856,10 +6935,51 @@ var VideoPlayer = ({
6856
6935
  const [showControls, setShowControls] = (0, import_react22.useState)(true);
6857
6936
  const [hasCompleted, setHasCompleted] = (0, import_react22.useState)(false);
6858
6937
  const [showCaptions, setShowCaptions] = (0, import_react22.useState)(false);
6938
+ (0, import_react22.useEffect)(() => {
6939
+ setHasCompleted(false);
6940
+ }, [src]);
6859
6941
  const [playbackRate, setPlaybackRate] = (0, import_react22.useState)(1);
6860
6942
  const [showSpeedMenu, setShowSpeedMenu] = (0, import_react22.useState)(false);
6861
6943
  const lastSaveTimeRef = (0, import_react22.useRef)(0);
6862
6944
  const trackRef = (0, import_react22.useRef)(null);
6945
+ const controlsTimeoutRef = (0, import_react22.useRef)(null);
6946
+ const lastMousePositionRef = (0, import_react22.useRef)({ x: 0, y: 0 });
6947
+ const mouseMoveTimeoutRef = (0, import_react22.useRef)(null);
6948
+ const clearControlsTimeout = (0, import_react22.useCallback)(() => {
6949
+ if (controlsTimeoutRef.current) {
6950
+ clearTimeout(controlsTimeoutRef.current);
6951
+ controlsTimeoutRef.current = null;
6952
+ }
6953
+ }, []);
6954
+ const clearMouseMoveTimeout = (0, import_react22.useCallback)(() => {
6955
+ if (mouseMoveTimeoutRef.current) {
6956
+ clearTimeout(mouseMoveTimeoutRef.current);
6957
+ mouseMoveTimeoutRef.current = null;
6958
+ }
6959
+ }, []);
6960
+ const showControlsWithTimer = (0, import_react22.useCallback)(() => {
6961
+ setShowControls(true);
6962
+ clearControlsTimeout();
6963
+ if (isPlaying) {
6964
+ const timeout = isFullscreen ? 2e3 : 3e3;
6965
+ controlsTimeoutRef.current = window.setTimeout(() => {
6966
+ setShowControls(false);
6967
+ }, timeout);
6968
+ }
6969
+ }, [isPlaying, isFullscreen, clearControlsTimeout]);
6970
+ const handleMouseMove = (0, import_react22.useCallback)(
6971
+ (event) => {
6972
+ const currentX = event.clientX;
6973
+ const currentY = event.clientY;
6974
+ const lastPos = lastMousePositionRef.current;
6975
+ const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
6976
+ if (hasMoved) {
6977
+ lastMousePositionRef.current = { x: currentX, y: currentY };
6978
+ showControlsWithTimer();
6979
+ }
6980
+ },
6981
+ [showControlsWithTimer]
6982
+ );
6863
6983
  (0, import_react22.useEffect)(() => {
6864
6984
  if (videoRef.current) {
6865
6985
  videoRef.current.volume = volume;
@@ -6867,84 +6987,129 @@ var VideoPlayer = ({
6867
6987
  }
6868
6988
  }, [volume, isMuted]);
6869
6989
  (0, import_react22.useEffect)(() => {
6870
- if (!autoSave || !storageKey) return;
6871
- const raw = localStorage.getItem(`${storageKey}-${src}`);
6872
- const saved = raw !== null ? Number(raw) : NaN;
6873
- const hasValidSaved = Number.isFinite(saved) && saved >= 0;
6874
- const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
6875
- let start;
6876
- if (hasValidInitial) {
6877
- start = initialTime;
6878
- } else if (hasValidSaved) {
6879
- start = saved;
6990
+ const video = videoRef.current;
6991
+ if (!video) return;
6992
+ const onPlay = () => setIsPlaying(true);
6993
+ const onPause = () => setIsPlaying(false);
6994
+ const onEnded = () => setIsPlaying(false);
6995
+ video.addEventListener("play", onPlay);
6996
+ video.addEventListener("pause", onPause);
6997
+ video.addEventListener("ended", onEnded);
6998
+ return () => {
6999
+ video.removeEventListener("play", onPlay);
7000
+ video.removeEventListener("pause", onPause);
7001
+ video.removeEventListener("ended", onEnded);
7002
+ };
7003
+ }, []);
7004
+ (0, import_react22.useEffect)(() => {
7005
+ if (isPlaying) {
7006
+ showControlsWithTimer();
6880
7007
  } else {
6881
- start = void 0;
7008
+ clearControlsTimeout();
7009
+ setShowControls(true);
7010
+ }
7011
+ }, [isPlaying, showControlsWithTimer, clearControlsTimeout]);
7012
+ (0, import_react22.useEffect)(() => {
7013
+ const handleFullscreenChange = () => {
7014
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
7015
+ setIsFullscreen(isCurrentlyFullscreen);
7016
+ if (isCurrentlyFullscreen) {
7017
+ showControlsWithTimer();
7018
+ }
7019
+ };
7020
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
7021
+ return () => {
7022
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
7023
+ };
7024
+ }, [showControlsWithTimer]);
7025
+ const getInitialTime = (0, import_react22.useCallback)(() => {
7026
+ if (!autoSave || !storageKey) {
7027
+ return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
6882
7028
  }
7029
+ const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
7030
+ const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
7031
+ const hasValidSaved = Number.isFinite(saved) && saved >= 0;
7032
+ if (hasValidInitial) return initialTime;
7033
+ if (hasValidSaved) return saved;
7034
+ return void 0;
7035
+ }, [autoSave, storageKey, src, initialTime]);
7036
+ (0, import_react22.useEffect)(() => {
7037
+ const start = getInitialTime();
6883
7038
  if (start !== void 0 && videoRef.current) {
6884
7039
  videoRef.current.currentTime = start;
6885
7040
  setCurrentTime(start);
6886
7041
  }
6887
- }, [src, storageKey, autoSave, initialTime]);
6888
- const saveProgress = (0, import_react22.useCallback)(() => {
6889
- if (!autoSave || !storageKey) return;
6890
- const now = Date.now();
6891
- if (now - lastSaveTimeRef.current > 5e3) {
6892
- localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());
6893
- lastSaveTimeRef.current = now;
6894
- }
6895
- }, [autoSave, storageKey, src, currentTime]);
6896
- const togglePlayPause = (0, import_react22.useCallback)(() => {
6897
- if (videoRef.current) {
6898
- if (isPlaying) {
6899
- videoRef.current.pause();
6900
- } else {
6901
- videoRef.current.play();
7042
+ }, [getInitialTime]);
7043
+ const saveProgress = (0, import_react22.useCallback)(
7044
+ (time) => {
7045
+ if (!autoSave || !storageKey) return;
7046
+ const now = Date.now();
7047
+ if (now - lastSaveTimeRef.current > 5e3) {
7048
+ localStorage.setItem(`${storageKey}-${src}`, time.toString());
7049
+ lastSaveTimeRef.current = now;
6902
7050
  }
6903
- setIsPlaying(!isPlaying);
7051
+ },
7052
+ [autoSave, storageKey, src]
7053
+ );
7054
+ const togglePlayPause = (0, import_react22.useCallback)(async () => {
7055
+ const video = videoRef.current;
7056
+ if (!video) return;
7057
+ if (!video.paused) {
7058
+ video.pause();
7059
+ return;
7060
+ }
7061
+ try {
7062
+ await video.play();
7063
+ } catch {
6904
7064
  }
6905
- }, [isPlaying]);
7065
+ }, []);
6906
7066
  const handleVolumeChange = (0, import_react22.useCallback)(
6907
7067
  (newVolume) => {
6908
- if (videoRef.current) {
6909
- const volumeValue = newVolume / 100;
6910
- videoRef.current.volume = volumeValue;
6911
- setVolume(volumeValue);
6912
- if (volumeValue === 0) {
6913
- videoRef.current.muted = true;
6914
- setIsMuted(true);
6915
- } else if (isMuted) {
6916
- videoRef.current.muted = false;
6917
- setIsMuted(false);
6918
- }
7068
+ const video = videoRef.current;
7069
+ if (!video) return;
7070
+ const volumeValue = newVolume / 100;
7071
+ video.volume = volumeValue;
7072
+ setVolume(volumeValue);
7073
+ const shouldMute = volumeValue === 0;
7074
+ const shouldUnmute = volumeValue > 0 && isMuted;
7075
+ if (shouldMute) {
7076
+ video.muted = true;
7077
+ setIsMuted(true);
7078
+ } else if (shouldUnmute) {
7079
+ video.muted = false;
7080
+ setIsMuted(false);
6919
7081
  }
6920
7082
  },
6921
7083
  [isMuted]
6922
7084
  );
6923
7085
  const toggleMute = (0, import_react22.useCallback)(() => {
6924
- if (videoRef.current) {
6925
- if (isMuted) {
6926
- const restoreVolume = volume > 0 ? volume : 0.5;
6927
- videoRef.current.volume = restoreVolume;
6928
- videoRef.current.muted = false;
6929
- setVolume(restoreVolume);
6930
- setIsMuted(false);
6931
- } else {
6932
- videoRef.current.muted = true;
6933
- setIsMuted(true);
6934
- }
7086
+ const video = videoRef.current;
7087
+ if (!video) return;
7088
+ if (isMuted) {
7089
+ const restoreVolume = volume > 0 ? volume : 0.5;
7090
+ video.volume = restoreVolume;
7091
+ video.muted = false;
7092
+ setVolume(restoreVolume);
7093
+ setIsMuted(false);
7094
+ } else {
7095
+ video.muted = true;
7096
+ setIsMuted(true);
6935
7097
  }
6936
7098
  }, [isMuted, volume]);
7099
+ const handleSeek = (0, import_react22.useCallback)((newTime) => {
7100
+ const video = videoRef.current;
7101
+ if (video) {
7102
+ video.currentTime = newTime;
7103
+ }
7104
+ }, []);
6937
7105
  const toggleFullscreen = (0, import_react22.useCallback)(() => {
6938
7106
  const container = videoRef.current?.parentElement;
6939
7107
  if (!container) return;
6940
- if (!isFullscreen) {
6941
- if (container.requestFullscreen) {
6942
- container.requestFullscreen();
6943
- }
6944
- } else if (document.exitFullscreen) {
7108
+ if (!isFullscreen && container.requestFullscreen) {
7109
+ container.requestFullscreen();
7110
+ } else if (isFullscreen && document.exitFullscreen) {
6945
7111
  document.exitFullscreen();
6946
7112
  }
6947
- setIsFullscreen(!isFullscreen);
6948
7113
  }, [isFullscreen]);
6949
7114
  const handleSpeedChange = (0, import_react22.useCallback)((speed) => {
6950
7115
  if (videoRef.current) {
@@ -6962,29 +7127,28 @@ var VideoPlayer = ({
6962
7127
  setShowCaptions(newShowCaptions);
6963
7128
  trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
6964
7129
  }, [showCaptions, subtitles]);
6965
- const handleTimeUpdate = (0, import_react22.useCallback)(() => {
6966
- if (videoRef.current) {
6967
- const current = videoRef.current.currentTime;
6968
- setCurrentTime(current);
6969
- saveProgress();
6970
- onTimeUpdate?.(current);
6971
- if (duration > 0) {
6972
- const progressPercent = current / duration * 100;
6973
- onProgress?.(progressPercent);
6974
- if (progressPercent >= 95 && !hasCompleted) {
6975
- setHasCompleted(true);
6976
- onVideoComplete?.();
6977
- }
7130
+ const checkVideoCompletion = (0, import_react22.useCallback)(
7131
+ (progressPercent) => {
7132
+ if (progressPercent >= 95 && !hasCompleted) {
7133
+ setHasCompleted(true);
7134
+ onVideoComplete?.();
6978
7135
  }
7136
+ },
7137
+ [hasCompleted, onVideoComplete]
7138
+ );
7139
+ const handleTimeUpdate = (0, import_react22.useCallback)(() => {
7140
+ const video = videoRef.current;
7141
+ if (!video) return;
7142
+ const current = video.currentTime;
7143
+ setCurrentTime(current);
7144
+ saveProgress(current);
7145
+ onTimeUpdate?.(current);
7146
+ if (duration > 0) {
7147
+ const progressPercent = current / duration * 100;
7148
+ onProgress?.(progressPercent);
7149
+ checkVideoCompletion(progressPercent);
6979
7150
  }
6980
- }, [
6981
- duration,
6982
- saveProgress,
6983
- onTimeUpdate,
6984
- onProgress,
6985
- onVideoComplete,
6986
- hasCompleted
6987
- ]);
7151
+ }, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
6988
7152
  const handleLoadedMetadata = (0, import_react22.useCallback)(() => {
6989
7153
  if (videoRef.current) {
6990
7154
  setDuration(videoRef.current.duration);
@@ -7013,9 +7177,78 @@ var VideoPlayer = ({
7013
7177
  return () => {
7014
7178
  document.removeEventListener("visibilitychange", handleVisibilityChange);
7015
7179
  window.removeEventListener("blur", handleBlur);
7180
+ clearControlsTimeout();
7181
+ clearMouseMoveTimeout();
7016
7182
  };
7017
- }, [isPlaying]);
7183
+ }, [isPlaying, clearControlsTimeout, clearMouseMoveTimeout]);
7018
7184
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
7185
+ const getTopControlsOpacity = (0, import_react22.useCallback)(() => {
7186
+ if (isFullscreen) {
7187
+ return showControls ? "opacity-100" : "opacity-0";
7188
+ }
7189
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7190
+ }, [isFullscreen, showControls, isPlaying]);
7191
+ const getBottomControlsOpacity = (0, import_react22.useCallback)(() => {
7192
+ if (isFullscreen) {
7193
+ return showControls ? "opacity-100" : "opacity-0";
7194
+ }
7195
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7196
+ }, [isFullscreen, showControls, isPlaying]);
7197
+ const handleVideoKeyDown = (0, import_react22.useCallback)(
7198
+ (e) => {
7199
+ if (e.key) {
7200
+ e.stopPropagation();
7201
+ showControlsWithTimer();
7202
+ }
7203
+ switch (e.key) {
7204
+ case " ":
7205
+ case "Enter":
7206
+ e.preventDefault();
7207
+ togglePlayPause();
7208
+ break;
7209
+ case "ArrowLeft":
7210
+ e.preventDefault();
7211
+ if (videoRef.current) {
7212
+ videoRef.current.currentTime -= 10;
7213
+ }
7214
+ break;
7215
+ case "ArrowRight":
7216
+ e.preventDefault();
7217
+ if (videoRef.current) {
7218
+ videoRef.current.currentTime += 10;
7219
+ }
7220
+ break;
7221
+ case "ArrowUp":
7222
+ e.preventDefault();
7223
+ handleVolumeChange(Math.min(100, volume * 100 + 10));
7224
+ break;
7225
+ case "ArrowDown":
7226
+ e.preventDefault();
7227
+ handleVolumeChange(Math.max(0, volume * 100 - 10));
7228
+ break;
7229
+ case "m":
7230
+ case "M":
7231
+ e.preventDefault();
7232
+ toggleMute();
7233
+ break;
7234
+ case "f":
7235
+ case "F":
7236
+ e.preventDefault();
7237
+ toggleFullscreen();
7238
+ break;
7239
+ default:
7240
+ break;
7241
+ }
7242
+ },
7243
+ [
7244
+ showControlsWithTimer,
7245
+ togglePlayPause,
7246
+ handleVolumeChange,
7247
+ volume,
7248
+ toggleMute,
7249
+ toggleFullscreen
7250
+ ]
7251
+ );
7019
7252
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: cn("flex flex-col", className), children: [
7020
7253
  (title || subtitleText) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-1", children: [
7021
7254
  title && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7042,12 +7275,17 @@ var VideoPlayer = ({
7042
7275
  )
7043
7276
  ] }) }),
7044
7277
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
7045
- "div",
7278
+ "section",
7046
7279
  {
7047
7280
  className: cn(
7048
7281
  "relative w-full bg-background overflow-hidden group",
7049
- title || subtitleText ? "rounded-b-xl" : "rounded-xl"
7282
+ title || subtitleText ? "rounded-b-xl" : "rounded-xl",
7283
+ // Hide cursor when controls are hidden and video is playing in fullscreen
7284
+ isFullscreen && isPlaying && !showControls ? "cursor-none" : "cursor-default"
7050
7285
  ),
7286
+ "aria-label": title ? `Video player: ${title}` : "Video player",
7287
+ onMouseMove: isFullscreen ? handleMouseMove : showControlsWithTimer,
7288
+ onMouseEnter: showControlsWithTimer,
7051
7289
  children: [
7052
7290
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7053
7291
  "video",
@@ -7060,39 +7298,7 @@ var VideoPlayer = ({
7060
7298
  onTimeUpdate: handleTimeUpdate,
7061
7299
  onLoadedMetadata: handleLoadedMetadata,
7062
7300
  onClick: togglePlayPause,
7063
- onKeyDown: (e) => {
7064
- if (e.key) {
7065
- setShowControls(true);
7066
- }
7067
- if (e.key === " " || e.key === "Enter") {
7068
- e.preventDefault();
7069
- togglePlayPause();
7070
- }
7071
- if (e.key === "ArrowLeft" && videoRef.current) {
7072
- e.preventDefault();
7073
- videoRef.current.currentTime -= 10;
7074
- }
7075
- if (e.key === "ArrowRight" && videoRef.current) {
7076
- e.preventDefault();
7077
- videoRef.current.currentTime += 10;
7078
- }
7079
- if (e.key === "ArrowUp") {
7080
- e.preventDefault();
7081
- handleVolumeChange(Math.min(100, volume * 100 + 10));
7082
- }
7083
- if (e.key === "ArrowDown") {
7084
- e.preventDefault();
7085
- handleVolumeChange(Math.max(0, volume * 100 - 10));
7086
- }
7087
- if (e.key === "m" || e.key === "M") {
7088
- e.preventDefault();
7089
- toggleMute();
7090
- }
7091
- if (e.key === "f" || e.key === "F") {
7092
- e.preventDefault();
7093
- toggleFullscreen();
7094
- }
7095
- },
7301
+ onKeyDown: handleVideoKeyDown,
7096
7302
  tabIndex: 0,
7097
7303
  "aria-label": title ? `Video: ${title}` : "Video player",
7098
7304
  children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7122,9 +7328,9 @@ var VideoPlayer = ({
7122
7328
  {
7123
7329
  className: cn(
7124
7330
  "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
7125
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7331
+ getTopControlsOpacity()
7126
7332
  ),
7127
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "ml-auto block", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7333
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7128
7334
  IconButton_default,
7129
7335
  {
7130
7336
  icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.ArrowsOutSimple, { size: 24 }),
@@ -7140,29 +7346,18 @@ var VideoPlayer = ({
7140
7346
  {
7141
7347
  className: cn(
7142
7348
  "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
7143
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7349
+ getBottomControlsOpacity()
7144
7350
  ),
7145
7351
  children: [
7146
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7147
- "input",
7352
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7353
+ ProgressBar2,
7148
7354
  {
7149
- type: "range",
7150
- min: 0,
7151
- max: duration || 100,
7152
- value: currentTime,
7153
- onChange: (e) => {
7154
- const newTime = parseFloat(e.target.value);
7155
- if (videoRef.current) {
7156
- videoRef.current.currentTime = newTime;
7157
- }
7158
- },
7159
- 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",
7160
- "aria-label": "Video progress",
7161
- style: {
7162
- background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
7163
- }
7355
+ currentTime,
7356
+ duration,
7357
+ progressPercentage,
7358
+ onSeek: handleSeek
7164
7359
  }
7165
- ) }),
7360
+ ),
7166
7361
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center justify-between px-4 pb-4", children: [
7167
7362
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-4", children: [
7168
7363
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7174,32 +7369,15 @@ var VideoPlayer = ({
7174
7369
  className: "!bg-transparent !text-white hover:!bg-white/20"
7175
7370
  }
7176
7371
  ),
7177
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
7178
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7179
- IconButton_default,
7180
- {
7181
- icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerHigh, { size: 24 }),
7182
- onClick: toggleMute,
7183
- "aria-label": isMuted ? "Unmute" : "Mute",
7184
- className: "!bg-transparent !text-white hover:!bg-white/20"
7185
- }
7186
- ),
7187
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7188
- "input",
7189
- {
7190
- type: "range",
7191
- min: 0,
7192
- max: 100,
7193
- value: Math.round(volume * 100),
7194
- onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
7195
- className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
7196
- "aria-label": "Volume control",
7197
- style: {
7198
- background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
7199
- }
7200
- }
7201
- )
7202
- ] }),
7372
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7373
+ VolumeControls,
7374
+ {
7375
+ volume,
7376
+ isMuted,
7377
+ onVolumeChange: handleVolumeChange,
7378
+ onToggleMute: toggleMute
7379
+ }
7380
+ ),
7203
7381
  subtitles && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7204
7382
  IconButton_default,
7205
7383
  {
@@ -7218,29 +7396,15 @@ var VideoPlayer = ({
7218
7396
  formatTime(duration)
7219
7397
  ] })
7220
7398
  ] }),
7221
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative", children: [
7222
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7223
- IconButton_default,
7224
- {
7225
- icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.DotsThreeVertical, { size: 24 }),
7226
- onClick: toggleSpeedMenu,
7227
- "aria-label": "Playback speed",
7228
- className: "!bg-transparent !text-white hover:!bg-white/20"
7229
- }
7230
- ),
7231
- showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("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__ */ (0, import_jsx_runtime36.jsxs)(
7232
- "button",
7233
- {
7234
- onClick: () => handleSpeedChange(speed),
7235
- 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"}`,
7236
- children: [
7237
- speed,
7238
- "x"
7239
- ]
7240
- },
7241
- speed
7242
- )) })
7243
- ] }) })
7399
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7400
+ SpeedMenu,
7401
+ {
7402
+ showSpeedMenu,
7403
+ playbackRate,
7404
+ onToggleMenu: toggleSpeedMenu,
7405
+ onSpeedChange: handleSpeedChange
7406
+ }
7407
+ ) })
7244
7408
  ] })
7245
7409
  ]
7246
7410
  }