From 91a5c81a254d4598ebf35ba091a320a51f90d906 Mon Sep 17 00:00:00 2001 From: VinnyNC Date: Mon, 29 Sep 2025 21:29:03 -0400 Subject: [PATCH] Complete 3.3.3: Mobile Navigation - Implement bottom navigation pattern for mobile with chat controls --- assets/js/ui-controls.js | 40 +++++++++++++ index.php | 29 ++++++++++ static/css/components.css | 119 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) diff --git a/assets/js/ui-controls.js b/assets/js/ui-controls.js index 84d815f..710a0db 100644 --- a/assets/js/ui-controls.js +++ b/assets/js/ui-controls.js @@ -414,6 +414,33 @@ } }; + // Mobile navigation action handler + function handleMobileNavAction(action) { + switch (action) { + case 'toggle-chat': + toggleChat(); + break; + case 'refresh-stream': + performPullToRefresh(); + break; + case 'toggle-fullscreen': + toggleFullscreen(); + break; + case 'toggle-picture-in-picture': + togglePictureInPicture(); + break; + case 'toggle-quality': + // Toggle quality selector visibility + const qualitySelector = document.getElementById('qualitySelector'); + if (qualitySelector) { + qualitySelector.style.display = qualitySelector.style.display === 'none' ? 'block' : 'none'; + } + break; + default: + AppLogger.warn('Unknown mobile nav action:', action); + } + } + // Initialize event listeners function initializeEventListeners() { // Keyboard shortcuts @@ -442,6 +469,18 @@ DOMUtils.addEvent(document, 'touchmove', handlePullToRefreshTouchMove, { passive: false }); DOMUtils.addEvent(document, 'touchend', handlePullToRefreshTouchEnd, { passive: true }); + // Mobile navigation buttons + const mobileNav = document.getElementById('mobileNav'); + if (mobileNav) { + mobileNav.addEventListener('click', function(event) { + const button = event.target.closest('.mobile-nav-btn'); + if (button && button.dataset.action) { + event.preventDefault(); + handleMobileNavAction(button.dataset.action); + } + }); + } + AppLogger.log('UI controls event listeners initialized'); } @@ -456,6 +495,7 @@ updateConnectionStatus: updateConnectionStatus, updateNotificationBadge: updateNotificationBadge, playNotificationSound: playNotificationSound, + handleMobileNavAction: handleMobileNavAction, DOMUtils: DOMUtils }; diff --git a/index.php b/index.php index 13c51d4..932f321 100644 --- a/index.php +++ b/index.php @@ -401,6 +401,35 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
+ + + diff --git a/static/css/components.css b/static/css/components.css index 6f5dace..b034016 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -1401,3 +1401,122 @@ font-size: 20px; background: rgba(0,90,156,0.9); } + +/* ================================================================= + MOBILE BOTTOM NAVIGATION + ================================================================= */ + +.mobile-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(135deg, var(--dodgers-blue-700), var(--dodgers-blue-600)); + border-top: 1px solid rgba(255,255,255,0.2); + box-shadow: var(--elevation-8); + backdrop-filter: blur(10px); + padding: var(--spacing-3) var(--spacing-4); + display: none; /* Hidden by default, shown only on mobile */ + z-index: var(--z-fixed); + flex-wrap: wrap; + justify-content: space-around; + align-items: center; +} + +.mobile-nav.active { + display: flex; +} + +.mobile-nav-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-1); + padding: var(--spacing-2) var(--spacing-1); + min-height: var(--min-tap-target-size, 44px); + min-width: var(--min-tap-target-size, 44px); + border: none; + background: transparent; + color: rgba(255,255,255,0.9); + border-radius: var(--border-radius-lg); + cursor: pointer; + transition: all var(--transition-fast); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + text-align: center; +} + +.mobile-nav-btn:active { + transform: scale(0.95); + background: rgba(255,255,255,0.1); +} + +.mobile-nav-btn:hover { + color: white; + background: rgba(255,255,255,0.15); +} + +.mobile-nav-btn.active { + background: rgba(255,255,255,0.2); + color: white; +} + +.mobile-nav-btn .icon { + font-size: 20px; + width: 24px; + height: 24px; +} + +.mobile-nav-btn span { + font-size: 10px; + line-height: 1; + opacity: 0.9; +} + +/* Mobile navigation responsive behavior */ +@media (max-width: calc(var(--breakpoint-md) - 1px)) { + .mobile-nav { + display: flex; + } +} + +@media (max-width: calc(var(--breakpoint-sm) - 1px)) { + .mobile-nav { + padding: var(--spacing-2) var(--spacing-3); + gap: var(--spacing-3); + } + + .mobile-nav-btn { + padding: var(--spacing-1); + gap: 2px; + min-width: 48px; + } + + .mobile-nav-btn .icon { + width: 20px; + height: 20px; + font-size: 18px; + } + + .mobile-nav-btn span { + font-size: 9px; + } +} + +/* Safe area support for devices with notches */ +@supports (padding-bottom: max(0px)) { + .mobile-nav { + padding-bottom: max(var(--spacing-3), env(safe-area-inset-bottom)); + } + + body.mobile-nav-active { + padding-bottom: 80px; /* Space for bottom nav */ + } + + @media (max-width: calc(var(--breakpoint-md) - 1px)) { + body.mobile-nav-active { + padding-bottom: max(70px, calc(70px + env(safe-area-inset-bottom))); + } + } +}