Improve mobile responsiveness for settings and chapters

Adds mobile-first responsive CSS for the custom settings menu and chapters overlay, including adaptive sizing, touch-friendly interactions, and prevention of body scroll when overlays are open. Updates CustomSettingsMenu.js to detect mobile devices, handle touch events for menu items, and manage scroll locking and haptic feedback for a better mobile user experience.
This commit is contained in:
Yiannis Christodoulou 2025-10-11 01:48:26 +03:00
parent 8eb196bf74
commit f787087531
3 changed files with 509 additions and 0 deletions

View File

@ -290,6 +290,11 @@
transition: background-color 0.2s ease;
}
.chapter-close button svg {
width: 18px !important;
height: 18px !important;
}
.chapter-close button:active {
background: rgba(255, 255, 255, 0.2);
transform: scale(0.95);
@ -400,6 +405,11 @@
height: 28px;
}
.chapter-close button svg {
width: 16px !important;
height: 16px !important;
}
.chapter-title h3 a {
font-size: 13px !important;
line-height: 16px !important;
@ -462,6 +472,11 @@
height: 26px;
}
.chapter-close button svg {
width: 14px !important;
height: 14px !important;
}
.chapter-title h3 a {
font-size: 12px !important;
line-height: 15px !important;
@ -525,6 +540,11 @@
height: 28px;
}
.chapter-close button svg {
width: 16px !important;
height: 16px !important;
}
.chapter-title h3 a {
font-size: 13px !important;
line-height: 16px !important;

View File

@ -213,3 +213,313 @@ sup.hd-badge {
padding: 1px 4px;
border-radius: 3px;
}
/* ===== MOBILE RESPONSIVE DESIGN ===== */
/* Mobile-first responsive design for tablets and large phones */
@media (max-width: 767px) {
.custom-settings-overlay {
right: 8px !important;
left: auto !important;
width: 260px !important;
max-width: calc(100vw - 16px) !important;
height: auto !important;
max-height: calc(100vh - 100px) !important;
bottom: 45px !important;
border-radius: 10px !important;
font-size: 13px !important;
}
.settings-header {
padding: 10px 12px;
font-size: 14px;
font-weight: 600;
}
.settings-close-btn {
width: 32px !important;
height: 32px !important;
padding: 0 !important;
}
.settings-close-btn svg {
width: 18px !important;
height: 18px !important;
}
.settings-item {
padding: 10px 12px;
gap: 8px;
min-height: 48px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.settings-item:active {
background: rgba(255, 255, 255, 0.12) !important;
transform: scale(0.98);
}
.settings-left {
gap: 6px;
font-size: 13px;
}
.settings-right {
font-size: 12px;
opacity: 0.9;
}
.submenu-header {
padding: 10px 12px;
font-size: 14px;
min-height: 48px;
}
.submenu-header:active {
background: rgba(255, 255, 255, 0.12) !important;
transform: scale(0.98);
}
.speed-option,
.quality-option,
.subtitle-option {
padding: 10px 12px;
min-height: 44px;
font-size: 13px;
}
.speed-option:active,
.quality-option:active,
.subtitle-option:active {
background: rgba(255, 255, 255, 0.12) !important;
transform: scale(0.98);
}
/* Hide hover effects on touch devices */
.settings-item:hover,
.speed-option:hover,
.quality-option:hover,
.subtitle-option:hover,
.submenu-header:hover {
background: transparent;
}
}
/* Small phones (portrait) - Ultra compact */
@media (max-width: 480px) {
.custom-settings-overlay {
right: 6px !important;
left: auto !important;
width: 240px !important;
max-width: calc(100vw - 12px) !important;
height: auto !important;
max-height: calc(100vh - 80px) !important;
bottom: 35px !important;
border-radius: 8px !important;
font-size: 12px !important;
}
.settings-header {
padding: 8px 10px;
font-size: 13px;
}
.settings-close-btn {
width: 28px !important;
height: 28px !important;
}
.settings-close-btn svg {
width: 16px !important;
height: 16px !important;
}
.settings-item {
padding: 8px 10px;
gap: 6px;
min-height: 44px;
}
.settings-left {
gap: 5px;
font-size: 12px;
}
.settings-right {
font-size: 11px;
}
.submenu-header {
padding: 8px 10px;
font-size: 13px;
min-height: 44px;
}
.speed-option,
.quality-option,
.subtitle-option {
padding: 8px 10px;
min-height: 40px;
font-size: 12px;
}
/* Smaller icons on mobile */
.settings-item-svg svg,
.submenu-header svg {
width: 20px !important;
height: 20px !important;
}
}
/* Very small screens (< 360px) - Maximum compactness */
@media (max-width: 360px) {
.custom-settings-overlay {
right: 4px !important;
left: auto !important;
width: 220px !important;
max-width: calc(100vw - 8px) !important;
height: auto !important;
max-height: calc(100vh - 70px) !important;
bottom: 30px !important;
border-radius: 6px !important;
font-size: 11px !important;
}
.settings-header {
padding: 6px 8px;
font-size: 12px;
}
.settings-close-btn {
width: 26px !important;
height: 26px !important;
}
.settings-close-btn svg {
width: 14px !important;
height: 14px !important;
}
.settings-item {
padding: 6px 8px;
gap: 4px;
min-height: 40px;
}
.settings-left {
gap: 4px;
font-size: 11px;
}
.settings-right {
font-size: 10px;
}
.submenu-header {
padding: 6px 8px;
font-size: 12px;
min-height: 40px;
}
.speed-option,
.quality-option,
.subtitle-option {
padding: 6px 8px;
min-height: 36px;
font-size: 11px;
}
/* Even smaller icons for very small screens */
.settings-item-svg svg,
.submenu-header svg {
width: 18px !important;
height: 18px !important;
}
sup.hd-badge {
font-size: 8px;
padding: 0px 3px;
margin-left: 4px;
}
}
/* Landscape orientation on mobile - Compact for limited height */
@media (max-width: 767px) and (orientation: landscape) {
.custom-settings-overlay {
height: auto !important;
max-height: calc(100vh - 60px) !important;
bottom: 25px !important;
right: 6px !important;
left: auto !important;
width: 250px !important;
max-width: calc(100vw - 12px) !important;
}
.settings-header {
padding: 6px 10px;
font-size: 12px;
}
.settings-close-btn {
width: 28px !important;
height: 28px !important;
}
.settings-close-btn svg {
width: 16px !important;
height: 16px !important;
}
.settings-item {
padding: 7px 10px;
min-height: 38px;
}
.submenu-header {
padding: 7px 10px;
min-height: 38px;
}
.speed-option,
.quality-option,
.subtitle-option {
padding: 6px 10px;
min-height: 36px;
font-size: 11px;
}
}
/* Touch-friendly improvements for all mobile devices */
@media (hover: none) and (pointer: coarse) {
.settings-item,
.speed-option,
.quality-option,
.subtitle-option,
.submenu-header {
-webkit-tap-highlight-color: transparent;
user-select: none;
-webkit-user-select: none;
transition:
background-color 0.2s ease,
transform 0.1s ease;
}
/* Ensure smooth scrolling on touch devices */
.custom-settings-overlay,
.speed-submenu,
.quality-submenu,
.subtitles-submenu {
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
overscroll-behavior-y: contain;
}
/* Remove hover states on touch devices */
.settings-item:hover,
.speed-option:hover,
.quality-option:hover,
.subtitle-option:hover,
.submenu-header:hover {
background: transparent !important;
}
}

View File

@ -22,6 +22,9 @@ class CustomSettingsMenu extends Component {
// Touch scroll detection (mobile)
this.isTouchScrolling = false;
this.touchStartY = 0;
this.isMobile = this.detectMobile();
this.isSmallScreen = window.innerWidth <= 480;
this.touchThreshold = 150; // ms for tap vs scroll detection
// Bind methods
this.createSettingsButton = this.createSettingsButton.bind(this);
@ -35,6 +38,9 @@ class CustomSettingsMenu extends Component {
this.refreshSubtitlesSubmenu = this.refreshSubtitlesSubmenu.bind(this);
this.handleSubtitleChange = this.handleSubtitleChange.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
this.detectMobile = this.detectMobile.bind(this);
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
this.setupResizeListener = this.setupResizeListener.bind(this);
// Initialize after player is ready
this.player().ready(() => {
@ -42,9 +48,101 @@ class CustomSettingsMenu extends Component {
this.createSettingsOverlay();
this.setupEventListeners();
this.restoreSubtitlePreference();
this.setupResizeListener();
});
}
detectMobile() {
return (
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 0) ||
window.matchMedia('(hover: none) and (pointer: coarse)').matches
);
}
handleMobileInteraction(element, callback) {
if (!this.isMobile) return;
let touchStartTime = 0;
let touchMoved = false;
let touchStartY = 0;
element.addEventListener(
'touchstart',
(e) => {
touchStartTime = Date.now();
touchMoved = false;
touchStartY = e.touches[0].clientY;
// Add visual feedback
element.style.transform = 'scale(0.98)';
element.style.transition = 'transform 0.1s ease';
// Add haptic feedback if available
if (navigator.vibrate) {
navigator.vibrate(30);
}
},
{ passive: true }
);
element.addEventListener(
'touchmove',
(e) => {
const touchMoveY = e.touches[0].clientY;
const deltaY = Math.abs(touchMoveY - touchStartY);
const scrollThreshold = this.isSmallScreen ? 5 : 8;
if (deltaY > scrollThreshold) {
touchMoved = true;
// Remove visual feedback when scrolling
element.style.transform = '';
}
},
{ passive: true }
);
element.addEventListener(
'touchend',
(e) => {
const touchEndTime = Date.now();
const touchDuration = touchEndTime - touchStartTime;
// Reset visual feedback
element.style.transform = '';
// Only trigger if it's a quick tap (not a scroll)
const tapThreshold = this.isSmallScreen ? 120 : this.touchThreshold;
if (!touchMoved && touchDuration < tapThreshold) {
e.preventDefault();
callback(e);
}
},
{ passive: false }
);
element.addEventListener(
'touchcancel',
() => {
// Reset visual feedback on cancel
element.style.transform = '';
},
{ passive: true }
);
}
setupResizeListener() {
const handleResize = () => {
this.isSmallScreen = window.innerWidth <= 480;
};
window.addEventListener('resize', handleResize);
window.addEventListener('orientationchange', handleResize);
// Store reference for cleanup
this.resizeHandler = handleResize;
}
createSettingsButton() {
const controlBar = this.player().getChild('controlBar');
@ -236,8 +334,57 @@ class CustomSettingsMenu extends Component {
// Create subtitles submenu (YouTube-like)
this.createSubtitlesSubmenu();
// Add mobile-specific optimizations
if (this.isMobile) {
this.settingsOverlay.style.cssText += `
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
overscroll-behavior-y: contain;
touch-action: pan-y;
`;
// Prevent body scroll when overlay is open on mobile
this.settingsOverlay.addEventListener(
'touchstart',
(e) => {
e.stopPropagation();
},
{ passive: true }
);
}
// Add to control bar
this.player().el().appendChild(this.settingsOverlay);
// Add mobile touch handling to settings items
if (this.isMobile) {
const settingsItems = this.settingsOverlay.querySelectorAll('.settings-item');
settingsItems.forEach((item) => {
this.handleMobileInteraction(item, (e) => {
// Handle the same logic as click events
if (e.target.closest('[data-setting="playback-speed"]')) {
this.speedSubmenu.style.display = 'flex';
this.qualitySubmenu.style.display = 'none';
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
}
if (e.target.closest('[data-setting="quality"]')) {
this.qualitySubmenu.style.display = 'flex';
this.speedSubmenu.style.display = 'none';
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
}
if (e.target.closest('[data-setting="subtitles"]')) {
this.refreshSubtitlesSubmenu();
if (this.subtitlesSubmenu) {
this.subtitlesSubmenu.style.display = 'flex';
this.speedSubmenu.style.display = 'none';
this.qualitySubmenu.style.display = 'none';
}
}
});
});
}
}
createSpeedSubmenu() {
@ -812,9 +959,28 @@ class CustomSettingsMenu extends Component {
if (isVisible) {
this.settingsOverlay.classList.remove('show');
this.settingsOverlay.style.display = 'none';
// Restore body scroll on mobile when closing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
} else {
this.settingsOverlay.classList.add('show');
this.settingsOverlay.style.display = 'block';
// Add haptic feedback on mobile when opening
if (this.isMobile && navigator.vibrate) {
navigator.vibrate(30);
}
// Prevent body scroll on mobile when overlay is open
if (this.isMobile) {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
}
}
this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
@ -1252,6 +1418,19 @@ class CustomSettingsMenu extends Component {
// Remove event listeners
document.removeEventListener('click', this.handleClickOutside);
// Clean up resize listener
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
window.removeEventListener('orientationchange', this.resizeHandler);
}
// Restore body scroll on mobile when disposing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
// Remove DOM elements
if (this.settingsOverlay) {
this.settingsOverlay.remove();