diff --git a/app/styles/video-player.css b/app/styles/video-player.css
index a5171c9..65df158 100644
--- a/app/styles/video-player.css
+++ b/app/styles/video-player.css
@@ -415,4 +415,12 @@
background: linear-gradient(90deg, var(--accent-color), #5ac8fa);
border-radius: var(--radius-full);
transition: width 0.1s linear;
+}
+
+/* Ensure control buttons are clickable above slider touch area */
+.btn-icon {
+ position: relative;
+ z-index: 50;
+ cursor: pointer;
+ touch-action: manipulation;
}
\ No newline at end of file
diff --git a/components/player/desktop/DesktopMoreMenu.tsx b/components/player/desktop/DesktopMoreMenu.tsx
index 8e2ba36..fe34519 100644
--- a/components/player/desktop/DesktopMoreMenu.tsx
+++ b/components/player/desktop/DesktopMoreMenu.tsx
@@ -80,59 +80,79 @@ export function DesktopMoreMenu({
};
}, [containerRef]);
- // Calculate menu position with available space awareness
+ // Dual Positioning Strategy
const calculateMenuPosition = React.useCallback(() => {
if (!buttonRef.current || !containerRef.current) return;
- if (!buttonRef.current || !containerRef.current) return;
+ if (!isRotated) {
+ // Normal Mode: Non-rotated, potentially non-fullscreen
+ // Use Viewport Coordinates (getBoundingClientRect) and Portal to Document Body
+ const buttonRect = buttonRef.current.getBoundingClientRect();
+ const viewportHeight = window.innerHeight;
- // Calculate position relative to container using offsetParent loop
- let top = 0;
- let left = 0;
- let el: HTMLElement | null = buttonRef.current;
+ const spaceBelow = viewportHeight - buttonRect.bottom - 10;
+ const spaceAbove = buttonRect.top - 10;
- while (el && el !== containerRef.current) {
- top += el.offsetTop;
- left += el.offsetLeft;
- el = el.offsetParent as HTMLElement;
- }
+ const estimatedMenuHeight = 450;
+ const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
- const buttonHeight = buttonRef.current.offsetHeight;
- const buttonWidth = buttonRef.current.offsetWidth;
- const containerHeight = containerRef.current.offsetHeight;
+ const openUpward = spaceBelow < Math.min(actualMenuHeight, 300) && spaceAbove > spaceBelow;
+ const maxHeight = openUpward
+ ? Math.min(spaceAbove, actualMenuHeight)
+ : Math.min(spaceBelow, viewportHeight * 0.7);
- // Use container dimensions for available space
- const spaceBelow = containerHeight - (top + buttonHeight) - 20;
- const spaceAbove = top - 20;
-
- // Estimate menu height (or use actual if already rendered)
- const estimatedMenuHeight = 450; // approximate height of menu
- const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
-
- // Determine if we should open upward
- const openUpward = spaceBelow < Math.min(actualMenuHeight, 300) && spaceAbove > spaceBelow;
-
- // Calculate max-height based on available space
- const maxHeight = openUpward
- ? Math.min(spaceAbove, actualMenuHeight)
- : Math.min(spaceBelow, containerHeight * 0.7);
-
- if (openUpward) {
setMenuPosition({
- top: top - 10,
- left: left,
+ top: openUpward
+ ? buttonRect.top - 10
+ : buttonRect.bottom + 10,
+ left: buttonRect.right, // Align right edge (transform handled in CSS)
maxHeight: `${maxHeight}px`,
- openUpward: true
+ openUpward: openUpward
});
} else {
- setMenuPosition({
- top: top + buttonHeight + 10,
- left: left,
- maxHeight: `${maxHeight}px`,
- openUpward: false
- });
+ // Rotated Mode: Use Container Coordinates (offset loop) and Portal to Container
+ let top = 0;
+ let left = 0;
+ let el: HTMLElement | null = buttonRef.current;
+
+ while (el && el !== containerRef.current) {
+ top += el.offsetTop;
+ left += el.offsetLeft;
+ el = el.offsetParent as HTMLElement;
+ }
+
+ const buttonHeight = buttonRef.current.offsetHeight;
+ const buttonWidth = buttonRef.current.offsetWidth;
+ const containerHeight = containerRef.current.offsetHeight;
+
+ const spaceBelow = containerHeight - (top + buttonHeight) - 20;
+ const spaceAbove = top - 20;
+
+ const estimatedMenuHeight = 450;
+ const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
+
+ const openUpward = spaceBelow < Math.min(actualMenuHeight, 300) && spaceAbove > spaceBelow;
+ const maxHeight = openUpward
+ ? Math.min(spaceAbove, actualMenuHeight)
+ : Math.min(spaceBelow, containerHeight * 0.7);
+
+ if (openUpward) {
+ setMenuPosition({
+ top: top - 10,
+ left: left + buttonWidth,
+ maxHeight: `${maxHeight}px`,
+ openUpward: true
+ });
+ } else {
+ setMenuPosition({
+ top: top + buttonHeight + 10,
+ left: left + buttonWidth,
+ maxHeight: `${maxHeight}px`,
+ openUpward: false
+ });
+ }
}
- }, [containerRef]);
+ }, [containerRef, isRotated]);
@@ -154,7 +174,7 @@ export function DesktopMoreMenu({
const timer = setTimeout(calculateMenuPosition, 50);
return () => clearTimeout(timer);
}
- }, [showMoreMenu, calculateMenuPosition]);
+ }, [showMoreMenu, calculateMenuPosition, isRotated]);
const handleToggle = () => {
if (!showMoreMenu) {
@@ -166,10 +186,18 @@ export function DesktopMoreMenu({
const MenuContent = (
{/* More Menu Dropdown (Portal) */}
- {showMoreMenu && containerRef.current && createPortal(MenuContent, containerRef.current)}
+ {/* More Menu Dropdown (Portal) */}
+ {showMoreMenu && typeof document !== 'undefined' && createPortal(MenuContent, (isRotated && containerRef.current) ? containerRef.current : document.body)}
);
}
diff --git a/components/player/desktop/DesktopSpeedMenu.tsx b/components/player/desktop/DesktopSpeedMenu.tsx
index dff6357..2e14bc8 100644
--- a/components/player/desktop/DesktopSpeedMenu.tsx
+++ b/components/player/desktop/DesktopSpeedMenu.tsx
@@ -47,65 +47,85 @@ export function DesktopSpeedMenu({
};
}, [containerRef]);
- // Calculate menu position with available space awareness
+ // Dual Positioning Strategy
const calculateMenuPosition = React.useCallback(() => {
if (!buttonRef.current || !containerRef.current) return;
- if (!buttonRef.current || !containerRef.current) return;
+ if (!isRotated) {
+ // Normal Mode: Non-rotated, potentially non-fullscreen
+ // Use Viewport Coordinates (getBoundingClientRect) and Portal to Document Body
+ // This allows the menu to break out of the video container (overflow issue)
+ const buttonRect = buttonRef.current.getBoundingClientRect();
+ // We want it to be positioned relative to the viewport
+ // buttonRect.top/left are already viewport coordinates
+ const viewportHeight = window.innerHeight;
- // Calculate position relative to container using offsetParent loop
- // This works regardless of container rotation because we stay in the local coordinate system
- let top = 0;
- let left = 0;
- let el: HTMLElement | null = buttonRef.current;
+ const spaceBelow = viewportHeight - buttonRect.bottom - 10;
+ const spaceAbove = buttonRect.top - 10;
- while (el && el !== containerRef.current) {
- top += el.offsetTop;
- left += el.offsetLeft;
- el = el.offsetParent as HTMLElement;
- }
+ const estimatedMenuHeight = 250;
+ const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
- const buttonHeight = buttonRef.current.offsetHeight;
- const buttonWidth = buttonRef.current.offsetWidth;
- const containerHeight = containerRef.current.offsetHeight;
+ const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow;
+ const maxHeight = openUpward
+ ? Math.min(spaceAbove, actualMenuHeight)
+ : Math.min(spaceBelow, viewportHeight * 0.7);
- // Use container dimensions for available space
- const spaceBelow = containerHeight - (top + buttonHeight) - 20;
- const spaceAbove = top - 20;
-
- // Estimate menu height (or use actual if already rendered)
- const estimatedMenuHeight = 250; // approximate height of speed menu
- const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
-
- // Determine if we should open upward
- const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow;
-
- // Calculate max-height based on available space
- const maxHeight = openUpward
- ? Math.min(spaceAbove, actualMenuHeight)
- : Math.min(spaceBelow, containerHeight * 0.7);
-
- if (openUpward) {
setMenuPosition({
- top: top - 10,
- left: left + buttonWidth, // Right align? No, original was left: buttonRect.right - containerRect.left
- // Original logic: left = buttonRect.right - containerRect.left.
- // In local coords, buttonRect.right = left + buttonWidth.
- // But we want to align the RIGHT edge of menu with RIGHT edge of button?
- // CSS uses `transform: translateX(-100%)` and `left: ${menuPos.left}`.
- // So left should be the right edge of the button.
+ top: openUpward
+ ? buttonRect.top - 10 // Bottom of menu at top of button
+ : buttonRect.bottom + 10, // Top of menu at bottom of button
+ left: buttonRect.right, // Align right edge (transform handled in CSS)
maxHeight: `${maxHeight}px`,
- openUpward: true
+ openUpward: openUpward
});
} else {
- setMenuPosition({
- top: top + buttonHeight + 10,
- left: left + buttonWidth,
- maxHeight: `${maxHeight}px`,
- openUpward: false
- });
+ // Rotated Mode: Fullscreen/Landscape forced
+ // Use Container Coordinates (offset loop) and Portal to Container
+ // This ensures rotation transforms apply correctly to the menu
+
+ let top = 0;
+ let left = 0;
+ let el: HTMLElement | null = buttonRef.current;
+
+ while (el && el !== containerRef.current) {
+ top += el.offsetTop;
+ left += el.offsetLeft;
+ el = el.offsetParent as HTMLElement;
+ }
+
+ const buttonHeight = buttonRef.current.offsetHeight;
+ const buttonWidth = buttonRef.current.offsetWidth;
+ const containerHeight = containerRef.current.offsetHeight;
+
+ const spaceBelow = containerHeight - (top + buttonHeight) - 20;
+ const spaceAbove = top - 20;
+
+ const estimatedMenuHeight = 250;
+ const actualMenuHeight = menuRef.current?.offsetHeight || estimatedMenuHeight;
+
+ const openUpward = spaceBelow < Math.min(actualMenuHeight, 200) && spaceAbove > spaceBelow;
+ const maxHeight = openUpward
+ ? Math.min(spaceAbove, actualMenuHeight)
+ : Math.min(spaceBelow, containerHeight * 0.7);
+
+ if (openUpward) {
+ setMenuPosition({
+ top: top - 10,
+ left: left + buttonWidth,
+ maxHeight: `${maxHeight}px`,
+ openUpward: true
+ });
+ } else {
+ setMenuPosition({
+ top: top + buttonHeight + 10,
+ left: left + buttonWidth,
+ maxHeight: `${maxHeight}px`,
+ openUpward: false
+ });
+ }
}
- }, [containerRef]);
+ }, [containerRef, isRotated]);
@@ -127,7 +147,7 @@ export function DesktopSpeedMenu({
const timer = setTimeout(calculateMenuPosition, 50);
return () => clearTimeout(timer);
}
- }, [showSpeedMenu, calculateMenuPosition]);
+ }, [showSpeedMenu, calculateMenuPosition, isRotated]);
const handleToggle = () => {
if (!showSpeedMenu) {
@@ -139,10 +159,18 @@ export function DesktopSpeedMenu({
const MenuContent = (
);
}