.playerWrap { /* Fixed + inset so it fills the actual viewport regardless of .mainContent's own padding (which used to add to 100vh and push the whole page taller than the screen, causing an outer scrollbar). Also covers the sidebar while playing, like most video players' theater mode - "Quitter" is how you get back to it. */ position: fixed; inset: 0; z-index: 20; background: #000; } .playerWrap video { width: 100%; height: 100%; object-fit: contain; } /* Two stacked rows, both part of the same top gradient overlay: the action bar (title/members/next/settings/leave) and, below it, the presence avatars - see .presenceOverlay in styles/presence.css. Stacking them in normal flow inside one column means the avatar row always ends up right after the action bar regardless of how tall that bar is (it wraps to two lines on narrow screens), instead of guessing a fixed pixel offset for a second absolutely-positioned block. */ .topOverlay { position: absolute; top: 0; left: 0; right: 0; display: flex; flex-direction: column; gap: 10px; padding: 12px 16px; background: linear-gradient(to bottom, rgba(0,0,0,.7), transparent); z-index: 5; } .actionBar { display: flex; align-items: center; gap: 16px; color: white; font-size: 13px; flex-wrap: wrap; } .actionBar .itemTitle { font-weight: 600; } .actionBar .members { color: var(--muted); flex: 1; } /* Settings: a small gear icon in the action bar, not a permanently-visible block of dropdowns - opens a compact popup on click. */ .settingsWrap { position: relative; } .iconBtn { background: none; padding: 4px 8px; font-size: 16px; line-height: 1; border-radius: 6px; } .iconBtn:hover { background: rgba(255, 255, 255, .12); } /* #castBtn while actively casting - see Player.ts's toggleCast. Same "clearly-on" treatment as .avatarActionRequested's orange, not just a background tint, so it reads at a glance whether the TV or this screen is the one actually playing right now. */ .iconBtn[aria-pressed="true"] { color: #ff9800; background: rgba(255, 152, 0, .18); } .settingsPanel { position: absolute; top: 100%; right: 0; margin-top: 6px; display: flex; flex-direction: column; gap: 10px; background: var(--bg-header); padding: 12px; border-radius: 10px; font-size: 12px; min-width: 180px; box-shadow: 0 8px 24px rgba(0, 0, 0, .4); z-index: 5; } .settingsPanel label { color: var(--muted); margin-bottom: 0; display: flex; flex-direction: column; gap: 4px; } .settingsPanel select { background: var(--panel); color: var(--text); border: 1px solid #3a3a3a; border-radius: 6px; padding: 4px 6px; font-size: 12px; } .settingsPanel select:disabled { opacity: .5; } /* .hint's own bottom margin (base.css) would stack with the panel's own flex `gap` below it - zeroed here so it's spaced exactly like every other row in the column, not a wider gap after this specific one. */ .burnInHint { margin: 0; max-width: 220px; } .loadingNotice { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--muted); font-size: 14px; margin: 0; } .playerError { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 360px; text-align: center; background: rgba(0, 0, 0, .75); padding: 20px; border-radius: 10px; } .playerError p { color: var(--danger); font-size: 14px; margin: 0 0 10px; } /* One column, bottom-anchored, gap between whichever children are actually visible - same "stack in normal flow instead of guessing a fixed pixel offset per absolutely-positioned block" fix .topOverlay's own actionBar+presenceOverlay grouping already needed (see its own comment, and docs/architecture.md for the exact bug this avoided: a
's default margin silently throwing off a hand-picked bottom offset on the element above it - not repeatable once nothing is positioned independently any more). Centered on purpose - this is exactly where a native