From 72520d56e6045f9f33c441918a123e86ce63e0c8 Mon Sep 17 00:00:00 2001 From: hibna Date: Thu, 30 Oct 2025 03:31:07 +0300 Subject: [PATCH] Refactor video player CSS for improved subtitles and layout Major CSS refactor for video player components: improves subtitle styling and cross-browser positioning, restructures ControlsLayer and VideoElement styles for clarity and responsiveness, and updates custom properties for better subtitle/controls separation. Adds a local settings file for permissions. --- .claude/settings.local.json | 10 + src/components/ControlsLayer.css | 101 ++++++-- src/components/VideoElement.css | 382 +++++++++++++++++++++++++------ src/components/VideoPlayer.css | 66 ++++-- src/styles/variables.css | 62 +++-- 5 files changed, 503 insertions(+), 118 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..5f9aadd --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(find:*)", + "Bash(cat:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/src/components/ControlsLayer.css b/src/components/ControlsLayer.css index e1f2cd8..dfeb8f8 100644 --- a/src/components/ControlsLayer.css +++ b/src/components/ControlsLayer.css @@ -1,46 +1,97 @@ +/* ============================================ + CONTROLS LAYER - Overlay Container + ============================================ */ + .controls-layer { + /* Positioning */ position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; + + /* Stacking */ z-index: var(--player-z-controls); + + /* Layout - Push controls to bottom */ display: flex; flex-direction: column; justify-content: flex-end; - transition: opacity var(--player-transition-normal) ease; + + /* Interaction */ cursor: default; + pointer-events: auto; + + /* Animation */ + opacity: 1; + transition: opacity var(--player-transition-normal) ease; } +/* ============================================ + VISIBILITY STATES + ============================================ */ + +/* Visible state (explicit for clarity) */ +.controls-layer.visible { + opacity: 1; +} + +/* Hidden while playing */ .controls-layer.hidden.playing { opacity: 0; } +/* Hide cursor in fullscreen when controls are hidden */ .controls-layer.fullscreen.hidden.playing { cursor: none; } -/* Allow clicks to pass through to video when controls are hidden */ +/* ============================================ + CONTROLS BAR - Bottom Control Panel + ============================================ */ + +.controls-bar { + /* Layout */ + display: flex; + flex-direction: column; + + /* Spacing - CRITICAL: Leave room for subtitles */ + padding-top: var(--player-spacing-xl); + padding-bottom: var(--player-spacing-lg); + padding-left: var(--player-spacing-lg); + padding-right: var(--player-spacing-lg); + + /* Background gradient */ + background: linear-gradient( + to top, + var(--player-bg-controls) 0%, + var(--player-bg-controls) 60%, + transparent 100% + ); + + /* Animation */ + transform: translateY(0); + transition: transform var(--player-transition-normal) ease; + + /* Interaction */ + pointer-events: auto; +} + +/* Slide controls down when hidden (allows clicks to pass through) */ .controls-layer.hidden.playing .controls-bar { transform: translateY(100%); pointer-events: none; } -.controls-layer.visible { - opacity: 1; -} - -.controls-bar { - background: linear-gradient(to top, var(--player-bg-controls) 0%, transparent 100%); - padding: var(--player-spacing-xl) var(--player-spacing-lg) var(--player-spacing-lg); - transition: transform var(--player-transition-normal) ease; - transform: translateY(0); -} +/* ============================================ + PROGRESS CONTAINER + ============================================ */ .progress-container { margin-bottom: var(--player-spacing-md); } +/* ============================================ + CONTROLS ROW - Button Container + ============================================ */ + .controls-row { display: flex; align-items: center; @@ -48,21 +99,31 @@ gap: var(--player-spacing-sm); } -.controls-left, -.controls-right { +/* Left side controls group */ +.controls-left { display: flex; align-items: center; gap: var(--player-spacing-sm); } +/* Right side controls group */ .controls-right { + display: flex; + align-items: center; + gap: var(--player-spacing-sm); margin-left: auto; } -/* Mobile responsiveness */ +/* ============================================ + MOBILE RESPONSIVE + ============================================ */ + @media (max-width: 640px) { .controls-bar { - padding: var(--player-spacing-lg) var(--player-spacing-md) var(--player-spacing-md); + padding-top: var(--player-spacing-lg); + padding-bottom: var(--player-spacing-md); + padding-left: var(--player-spacing-md); + padding-right: var(--player-spacing-md); } .controls-row { diff --git a/src/components/VideoElement.css b/src/components/VideoElement.css index 3353779..77d99af 100644 --- a/src/components/VideoElement.css +++ b/src/components/VideoElement.css @@ -1,18 +1,33 @@ +/* ============================================ + VIDEO CONTAINER + ============================================ */ + .video-container { position: relative; width: 100%; height: 100%; z-index: var(--player-z-video); + overflow: visible; + pointer-events: none; } +/* ============================================ + VIDEO ELEMENT + ============================================ */ + .video-element { width: 100%; height: 100%; - object-fit: contain; display: block; + object-fit: contain; + pointer-events: auto; } -/* Hide native controls */ +/* ============================================ + HIDE NATIVE MEDIA CONTROLS + ============================================ */ + +/* Chrome/Safari/Edge */ .video-element::-webkit-media-controls { display: none !important; } @@ -25,93 +40,328 @@ display: none !important; } -/* Subtitle Styling */ +/* Firefox */ +.video-element::-moz-media-controls { + display: none !important; +} + +/* ============================================ + SUBTITLE STYLING - Modern & Cross-Browser + Optimized for Chromium and Firefox + ============================================ */ + +/* ==================== BASE CUE STYLES ==================== */ + +/* All browsers - Base subtitle appearance */ +::cue { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif !important; + font-size: 1.5rem !important; + font-weight: 700 !important; + line-height: 1.5 !important; + color: #ffffff !important; + background-color: rgba(0, 0, 0, 0.9) !important; + padding: 0.4em 0.8em !important; + border-radius: 6px !important; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 1), + 0 2px 8px rgba(0, 0, 0, 0.8) !important; + white-space: pre-line !important; +} + +/* Chromium specific */ .video-element::cue { - font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - font-size: clamp(1.05rem, 2vw, 1.9rem); - font-weight: 600; - line-height: 1.45; - letter-spacing: 0.015em; - text-align: center; - color: var(--player-subtitle-color); - background-color: var(--player-subtitle-bg); - padding: 0.45em 0.9em; - border-radius: 14px; - box-shadow: var(--player-subtitle-shadow), 0 0 0 1px rgba(255, 255, 255, 0.08); - text-shadow: 0 6px 20px rgba(0, 0, 0, 0.55); - white-space: pre-line; - word-break: break-word; - text-wrap: balance; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif !important; + font-size: 1.5rem !important; + font-weight: 700 !important; + line-height: 1.5 !important; + color: #ffffff !important; + background-color: rgba(0, 0, 0, 0.9) !important; + padding: 0.4em 0.8em !important; + border-radius: 6px !important; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 1), + 0 2px 8px rgba(0, 0, 0, 0.8) !important; + white-space: pre-line !important; } -/* Fullscreen subtitle adjustments */ -:fullscreen .video-element::cue, -.video-element:fullscreen::cue { - font-size: clamp(1.35rem, 2.4vw, 2.35rem); - padding: 0.55em 1.1em; +/* Firefox specific - Base styles */ +::-moz-cue { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif !important; + font-size: 1.5rem !important; + font-weight: 700 !important; + line-height: 1.5 !important; + color: #ffffff !important; + background-color: rgba(0, 0, 0, 0.9) !important; + padding: 0.4em 0.8em !important; + border-radius: 6px !important; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 1), + 0 2px 8px rgba(0, 0, 0, 0.8) !important; + white-space: pre-line !important; } -/* Ensure text tracks are properly positioned - above controls */ -.video-element::-webkit-media-text-track-container { - overflow: visible !important; - position: absolute !important; - bottom: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: var(--player-z-subtitle) !important; - display: flex !important; - flex-direction: column !important; - justify-content: flex-end !important; - align-items: center !important; - padding-bottom: var(--player-subtitle-bottom-offset) !important; - padding-bottom: calc(var(--player-subtitle-bottom-offset) + env(safe-area-inset-bottom)) !important; - pointer-events: none !important; - gap: 12px !important; +video::-moz-cue { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif !important; + font-size: 1.5rem !important; + font-weight: 700 !important; + line-height: 1.5 !important; + color: #ffffff !important; + background-color: rgba(0, 0, 0, 0.9) !important; + padding: 0.4em 0.8em !important; + border-radius: 6px !important; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 1), + 0 2px 8px rgba(0, 0, 0, 0.8) !important; + white-space: pre-line !important; } -.video-element::-webkit-media-text-track-display { - overflow: visible !important; - width: 100% !important; - max-width: var(--player-subtitle-max-width, 88%) !important; - text-align: center !important; +.video-element::-moz-cue { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif !important; + font-size: 1.5rem !important; + font-weight: 700 !important; + line-height: 1.5 !important; + color: #ffffff !important; + background-color: rgba(0, 0, 0, 0.9) !important; + padding: 0.4em 0.8em !important; + border-radius: 6px !important; + text-shadow: + 0 1px 2px rgba(0, 0, 0, 1), + 0 2px 8px rgba(0, 0, 0, 0.8) !important; + white-space: pre-line !important; } -/* Multi-line subtitle support */ -.video-element::cue-region { - width: min(var(--player-subtitle-max-width, 88%), 60ch); - margin: 0 auto; +/* ==================== TEXT FORMATTING ==================== */ + +::cue(b), ::cue(strong) { + font-weight: 900 !important; } -/* Better contrast for different cue types */ -.video-element::cue(b) { - font-weight: 900; +::cue(i), ::cue(em) { + font-style: italic !important; } -.video-element::cue(i) { - font-style: italic; +::cue(u) { + text-decoration: underline !important; +} + +.video-element::cue(b), +.video-element::cue(strong) { + font-weight: 900 !important; +} + +.video-element::cue(i), +.video-element::cue(em) { + font-style: italic !important; } .video-element::cue(u) { - text-decoration: underline; + text-decoration: underline !important; } -/* Responsive adjustments */ -@media (max-width: 640px) { +/* Firefox text formatting */ +::-moz-cue(b), ::-moz-cue(strong) { + font-weight: 900 !important; +} + +::-moz-cue(i), ::-moz-cue(em) { + font-style: italic !important; +} + +::-moz-cue(u) { + text-decoration: underline !important; +} + +/* ==================== CHROMIUM POSITIONING ==================== */ + +/* Container - Chromium */ +.video-element::-webkit-media-text-track-container { + position: absolute !important; + bottom: 40px !important; + left: 0 !important; + right: 0 !important; + width: 100% !important; + height: auto !important; + + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + + padding: 0 !important; + margin: 0 !important; + + overflow: visible !important; + z-index: 10 !important; + pointer-events: none !important; +} + +/* Display wrapper - Chromium */ +.video-element::-webkit-media-text-track-display { + width: 100% !important; + max-width: 100% !important; + + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + + margin: 0 !important; + padding: 0 20px !important; + + text-align: center !important; +} + +/* ==================== FIREFOX POSITIONING ==================== */ + +/* Firefox - Position subtitles using video wrapper approach */ +@-moz-document url-prefix() { + .video-container { + position: relative; + } + + .video-element { + position: relative; + } + + /* Firefox subtitle container positioning */ .video-element::cue { - font-size: clamp(1rem, 3.4vw, 1.35rem); - padding: 0.35em 0.75em; + position: relative; + } +} + +/* Firefox - Use text track display positioning */ +video::-moz-text-track-display { + position: absolute !important; + bottom: 40px !important; + left: 0 !important; + right: 0 !important; + width: 100% !important; + + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + + padding: 0 20px !important; + margin: 0 !important; + + text-align: center !important; + z-index: 10 !important; + pointer-events: none !important; +} + +.video-element::-moz-text-track-display { + position: absolute !important; + bottom: 40px !important; + left: 0 !important; + right: 0 !important; + width: 100% !important; + + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + + padding: 0 20px !important; + margin: 0 !important; + + text-align: center !important; + z-index: 10 !important; + pointer-events: none !important; +} + +/* ==================== FULLSCREEN ==================== */ + +.video-element:fullscreen::cue, +:fullscreen .video-element::cue { + font-size: 2rem !important; + padding: 0.5em 1em !important; +} + +::-moz-cue:fullscreen, +video:fullscreen::-moz-cue, +.video-element:fullscreen::-moz-cue, +:fullscreen ::-moz-cue, +:fullscreen video::-moz-cue, +:fullscreen .video-element::-moz-cue { + font-size: 2rem !important; + padding: 0.5em 1em !important; +} + +.video-element:fullscreen::-webkit-media-text-track-container, +:fullscreen .video-element::-webkit-media-text-track-container { + bottom: 120px !important; +} + +video:fullscreen::-moz-text-track-display, +.video-element:fullscreen::-moz-text-track-display, +:fullscreen video::-moz-text-track-display, +:fullscreen .video-element::-moz-text-track-display { + bottom: 120px !important; +} + +/* ==================== RESPONSIVE ==================== */ + +@media (max-width: 1024px) { + ::cue, + .video-element::cue, + ::-moz-cue, + video::-moz-cue, + .video-element::-moz-cue { + font-size: 1.35rem !important; } .video-element::-webkit-media-text-track-container { - padding-bottom: 72px !important; - padding-bottom: calc(72px + env(safe-area-inset-bottom)) !important; + bottom: 80px !important; } - :fullscreen .video-element::cue, - .video-element:fullscreen::cue { - font-size: clamp(1.2rem, 3.6vw, 1.8rem); + video::-moz-text-track-display, + .video-element::-moz-text-track-display { + bottom: 80px !important; + } +} + +@media (max-width: 640px) { + ::cue, + .video-element::cue, + ::-moz-cue, + video::-moz-cue, + .video-element::-moz-cue { + font-size: 1.15rem !important; + padding: 0.35em 0.7em !important; + } + + .video-element::-webkit-media-text-track-container { + bottom: 80px !important; + padding: 0 12px !important; + } + + .video-element::-webkit-media-text-track-display { + max-width: 92% !important; + } + + video::-moz-text-track-display, + .video-element::-moz-text-track-display { + bottom: 80px !important; + padding: 0 12px !important; + } +} + +@media (max-width: 375px) { + ::cue, + .video-element::cue, + ::-moz-cue, + video::-moz-cue, + .video-element::-moz-cue { + font-size: 1rem !important; + } + + .video-element::-webkit-media-text-track-container { + bottom: 60px !important; + } + + video::-moz-text-track-display, + .video-element::-moz-text-track-display { + bottom: 60px !important; } } diff --git a/src/components/VideoPlayer.css b/src/components/VideoPlayer.css index 8b177c0..9896a92 100644 --- a/src/components/VideoPlayer.css +++ b/src/components/VideoPlayer.css @@ -1,3 +1,7 @@ +/* ============================================ + VIDEO PLAYER - Main Container + ============================================ */ + .video-player { position: relative; width: 100%; @@ -12,12 +16,40 @@ -webkit-user-select: none; } -.video-player *, -.video-player *::before, -.video-player *::after { +/* Apply box-sizing to all child elements */ +.video-player * { box-sizing: border-box; } +/* Reset video element box-sizing to preserve native subtitle rendering */ +.video-player video { + box-sizing: content-box; +} + +/* ============================================ + ASPECT RATIO - 16:9 Intrinsic Ratio + ============================================ */ + +/* Use padding-top hack for aspect ratio */ +.video-player::before { + content: ''; + display: block; + padding-top: 56.25%; /* 16:9 aspect ratio */ +} + +/* Position all direct children absolutely within aspect ratio container */ +.video-player > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +/* ============================================ + FULLSCREEN SUPPORT + ============================================ */ + .video-player:fullscreen { width: 100vw; height: 100vh; @@ -38,22 +70,10 @@ height: 100vh; } -/* Aspect ratio container */ -.video-player::before { - content: ''; - display: block; - padding-top: 56.25%; /* 16:9 aspect ratio */ -} +/* ============================================ + NATIVE CONTROLS - Hide completely + ============================================ */ -.video-player > * { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -/* Remove default video controls */ .video-player video::-webkit-media-controls { display: none !important; } @@ -61,3 +81,13 @@ .video-player video::-webkit-media-controls-enclosure { display: none !important; } + +.video-player video::-webkit-media-controls-panel { + display: none !important; +} + +/* Ensure subtitle text tracks are visible */ +.video-player video::-webkit-media-text-track-container, +.video-player video::-webkit-media-text-track-display { + display: block !important; +} diff --git a/src/styles/variables.css b/src/styles/variables.css index 8d60f66..20e0f45 100644 --- a/src/styles/variables.css +++ b/src/styles/variables.css @@ -1,5 +1,11 @@ +/* ============================================ + VIDEO PLAYER - CSS Custom Properties + ============================================ */ + :root { - /* Colors - Red Theme */ + /* ==================== COLORS ==================== */ + + /* Primary Theme - Red */ --player-primary: #ef4444; --player-primary-hover: #dc2626; --player-primary-active: #b91c1c; @@ -15,58 +21,86 @@ --player-text: #ffffff; --player-text-secondary: #d1d5db; --player-text-muted: #9ca3af; - --player-subtitle-color: #f9fafb; - --player-subtitle-bg: rgba(15, 15, 18, 0.78); - --player-subtitle-shadow: 0 12px 28px rgba(0, 0, 0, 0.55); - --player-subtitle-max-width: 88%; - --player-subtitle-bottom-offset: clamp(88px, 11vh, 160px); /* Border & Divider */ --player-border: #374151; --player-divider: rgba(255, 255, 255, 0.1); - /* Buffered & Progress */ + /* Progress Bar */ --player-buffered: rgba(239, 68, 68, 0.3); --player-progress-bg: rgba(255, 255, 255, 0.3); - /* Shadows */ + /* ==================== SUBTITLES ==================== */ + + /* Subtitle Appearance */ + --player-subtitle-color: #f9fafb; + --player-subtitle-bg: rgba(15, 15, 18, 0.78); + --player-subtitle-shadow: 0 12px 28px rgba(0, 0, 0, 0.55); + --player-subtitle-max-width: 88%; + + /* Subtitle Positioning + CRITICAL: This value MUST be larger than the controls bar height + to prevent subtitles from overlapping with controls. + + Controls height calculation: + - Progress bar: ~20px + - Progress margin-bottom: 14px (--player-spacing-md) + - Controls padding-top: 28px (--player-spacing-xl) + - Controls padding-bottom: 20px (--player-spacing-lg) + - Controls row (buttons): ~36-40px + - Total: ~118-122px + + We use 132px minimum (122px + 10px safety margin) + */ + --player-subtitle-bottom-offset: clamp(132px, 13vh, 180px); + + /* ==================== SHADOWS ==================== */ + --player-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --player-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); --player-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); - /* Transitions */ + /* ==================== TRANSITIONS ==================== */ + --player-transition-fast: 150ms; --player-transition-normal: 250ms; --player-transition-slow: 400ms; - /* Z-index */ + /* ==================== Z-INDEX LAYERS ==================== */ + --player-z-video: 1; --player-z-subtitle: 10; --player-z-controls: 20; --player-z-menu: 30; --player-z-loading: 40; - /* Spacing */ + /* ==================== SPACING ==================== */ + --player-spacing-xs: 6px; --player-spacing-sm: 10px; --player-spacing-md: 14px; --player-spacing-lg: 20px; --player-spacing-xl: 28px; - /* Border Radius */ + /* ==================== BORDER RADIUS ==================== */ + --player-radius-sm: 4px; --player-radius-md: 6px; --player-radius-lg: 8px; --player-radius-full: 9999px; - /* Icon Sizes */ + /* ==================== ICON SIZES ==================== */ + --player-icon-sm: 20px; --player-icon-md: 28px; --player-icon-lg: 36px; --player-icon-xl: 56px; } -/* Animations */ +/* ============================================ + ANIMATIONS + ============================================ */ + @keyframes spin { from { transform: rotate(0deg);