/* Persistent bottom overlay for a music listen party - see MusicBar.ts's header comment for why this is a Shell-level component instead of a route (views/player/Player.css's .playerWrap is the video equivalent, and stays a full route takeover on purpose - a shared audio queue has no image to "theater mode" around, and the whole point here is staying able to browse/add tracks while it plays). Sits above .mainContent, at a lower z-index than .playerWrap (20) so a video party's takeover still correctly covers it - moot in practice since joining a video party calls leaveMusicParty() first (see Player.ts), but cheap to get right. The fixed-to-viewport positioning itself lives one level up, on Shell.css's .bottomStack wrapper - not here - so that on a narrow screen the mobile nav trigger right below it in that wrapper always sits flush above this bar's own actual rendered height (its content can wrap taller on a narrow screen), never a hardcoded guess at it. */ .musicBar { background: var(--bg-header); border-top: 1px solid rgba(255, 255, 255, .08); box-shadow: 0 -4px 16px rgba(0, 0, 0, .3); } /* A thin strip along the bar's own top edge, not inside .musicBarRow - the same "compact player" convention Spotify/YouTube Music use for their mini-players. Click/drag to seek (see MusicBar.ts) - the generous ::before hit-area below (not just the 3px visual track) matters most for drag on a touch screen, same trick views/player/Player.css's own .progressBar::before uses. `position: relative` is what gives that absolutely-positioned ::before something to size itself against. */ .musicBarProgressTrack { position: relative; height: 3px; background: rgba(255, 255, 255, .12); cursor: pointer; touch-action: none; /* this app's own pointer handlers drive the drag, not the browser's default touch scroll/zoom gestures */ } .musicBarProgressTrack::before { content: ''; position: absolute; top: -8px; bottom: -8px; left: 0; right: 0; } .musicBarProgressFill { height: 100%; width: 0%; background: var(--accent); pointer-events: none; } /* ---- Collapsed mobile preview (see MusicBar.html) - display:none by default since #musicBarPanel below is already always visible on desktop, nothing to collapse into a preview of there. Turned on under the 720px breakpoint further down. ---- */ .mobileMiniBar { display: none; align-items: center; gap: 10px; padding: 8px 12px; position: relative; cursor: pointer; } /* Passive - not draggable, unlike #trackProgress inside the panel below. Glanceable progress while collapsed is enough; dragging is one tap away once expanded, same as most real players' own mini-bar. */ .mobileMiniProgress { position: absolute; top: 0; left: 0; right: 0; height: 2px; background: rgba(255, 255, 255, .12); } .mobileMiniProgressFill { height: 100%; width: 0%; background: var(--accent-bright); } .mobileMiniArt { width: 36px; height: 36px; object-fit: cover; border-radius: 6px; background: var(--panel); flex-shrink: 0; } .mobileMiniArt.noImage { visibility: hidden; } .mobileMiniMeta { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; } .mobileMiniMeta strong { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* ---- The actual controls - always in place on desktop (this IS the bar, same as before); a full-screen "now playing" overlay on mobile, opened via #mobileMiniBar above and closed via #panelCloseBtn - see the 720px breakpoint further down for the fixed-overlay/transform part of this, unchanged here. ---- */ .musicBarPanel { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding: 8px 16px; } .panelCloseBtn { display: none; } .musicBarPanelTrack { display: flex; align-items: center; gap: 12px; min-width: 0; } .musicBarPanelTransport { display: flex; align-items: center; justify-content: center; gap: 16px; } .musicBarPanelSide { display: flex; align-items: center; justify-content: flex-end; gap: 10px; } /* Smaller than the video player's presence row (Player.css/presence.css's 36px default) - this bar is a thin strip, not an overlay on top of a large video frame. */ .musicBarPresence .avatar { width: 26px; height: 26px; font-size: 10px; } .musicBarImage { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; background: var(--panel); flex-shrink: 0; } .musicBarImage.noImage { visibility: hidden; } .musicBarInfo { display: flex; flex-direction: column; gap: 2px; min-width: 0; margin-right: auto; } .musicBarInfo strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; } .musicBarPosition { font-variant-numeric: tabular-nums; white-space: nowrap; } /* The one primary transport action, bigger and filled so it clearly outranks the plain small .iconBtn (skip-vote/queue) either side of it - same "obviously the main button" treatment views/player/Player.ts's own controls already get, instead of blending into a row of equally-weighted buttons. Shared between #playPauseBtn (inside .musicBarPanel) and #miniPlayPauseBtn (the collapsed mobile preview) - same look, same size, both wired to the exact same toggle in MusicBar.ts. */ /* .iconBtn.musicBarPlayBig, not just .musicBarPlayBig: both classes are always present together on these two buttons (see MusicBar.html), so this compound selector's higher specificity is what reliably beats .iconBtn's own background/border-radius regardless of which of the two files esbuild happens to bundle later - two plain single-class selectors at equal specificity would otherwise depend on that bundle order, which isn't something to build a visual guarantee on. */ .iconBtn.musicBarPlayBig { width: 34px; height: 34px; border-radius: 50%; background: var(--text); color: var(--bg); display: flex; align-items: center; justify-content: center; padding: 0; flex-shrink: 0; } .iconBtn.musicBarPlayBig:hover { background: var(--accent-bright); color: #fff; } .musicBarPlayBig svg { width: 14px; height: 14px; display: block; } /* #voteSkipBtn's own vote tally (see MusicBar.ts's updateVoteSkipLabel) - a badge instead of the old "Passer (vote) - X/Y" text label, now that the button itself is icon-only like every other transport control here. Hidden (see MusicBar.html) until there's an actual tally to show - matches every other conditionally-shown badge/pill elsewhere in this app rather than showing "0/1" before anyone's voted. */ #voteSkipBtn { position: relative; } .voteBadge { position: absolute; top: -4px; right: -6px; background: var(--accent); color: #fff; font-size: 9px; font-weight: 700; border-radius: 99px; padding: 1px 4px; min-width: 14px; text-align: center; line-height: 1.4; } /* #castBtn while actively casting - same "clearly-on" treatment as views/player/Player.css's own .iconBtn[aria-pressed="true"] (ID-scoped here rather than a shared .iconBtn rule since #playPauseBtn's own "iconBtn" class in this bar has no matching style at all - see MusicBar.html - and never sets aria-pressed, so this couldn't touch it either way, but there's no shared iconBtn.css to hang a rule off yet). */ #castBtn[aria-pressed="true"] { color: #ff9800; background: rgba(255, 152, 0, .18); } /* Pops up ABOVE the bar (same pattern as Player.css's .settingsPanel above its gear button) - collapsed by default, see MusicBar.ts's toggleQueueBtn handler. Spans the full bar width (no max-width) rather than a fixed popup box - a party's queue can run long, and a narrow column wastes the width a desktop viewport already has going spare. display:flex/flex-direction:column here (rather than on .pageHeader/ #queueList individually) is what lets #queueList be the only child that scrolls below - see #queueList's own comment. */ .musicBarQueue { max-height: 50vh; margin: 0 16px; padding: 14px 14px 0; background: var(--bg-header); border: 1px solid rgba(255, 255, 255, .08); border-bottom: none; border-radius: 10px 10px 0 0; display: flex; flex-direction: column; } .musicBarQueue .pageHeader { flex-shrink: 0; } /* The only scrolling region inside .musicBarQueue - "File d'attente" above it stays put instead of scrolling away with the tracks. */ #queueList { overflow-y: auto; } @media (max-width: 720px) { .mobileMiniBar { display: flex; } /* Same fixed-overlay/slide-up-from-bottom pattern as Shell.css's .mobileNavSheet - see docs/architecture.md's "Mobile navigation". display stays flex unconditionally (not toggled) so the transform transition below actually animates instead of jumping. */ .musicBarPanel { position: fixed; inset: 0; z-index: 25; display: flex; flex-direction: column; padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px)); background: var(--bg); transform: translateY(100%); transition: transform .25s ease; overflow-y: auto; } .musicBarPanel.open { transform: translateY(0); } .panelCloseBtn { display: flex; color: var(--muted); margin-bottom: 24px; } .musicBarPanelTrack { flex-direction: column; text-align: center; margin-bottom: 24px; } .musicBarPanelTrack .musicBarImage { width: min(220px, 60vw); height: min(220px, 60vw); border-radius: 12px; } .musicBarInfo { margin-right: 0; align-items: center; } .musicBarInfo strong { font-size: 19px; white-space: normal; } .musicBarPosition { margin-top: 6px; } .musicBarPanelTransport { gap: 28px; margin-bottom: 28px; } .musicBarPanelTransport .musicBarPlayBig { width: 56px; height: 56px; } .musicBarPanelTransport .musicBarPlayBig svg { width: 22px; height: 22px; } .musicBarPanelSide { margin-top: auto; justify-content: center; gap: 20px; } /* Positioned + given a real z-index only here: a normal-flow sibling otherwise loses to .musicBarPanel's fixed overlay above it once that's open (its own z-index applies regardless of DOM order) - opening the queue while the now-playing panel is expanded needs to still show it on top, matching desktop's existing "queue pops up above the bar" behavior. */ .musicBarQueue { position: relative; z-index: 26; margin: 0; border-radius: 0; } } /* One queue entry - a plain flex row (image, then a shrinking/truncating info column, then the duration), NOT views/album's shared .trackRow grid (styles/trackList.css) - that one's 4 fixed columns assume Album.ts's own index/name/duration/button shape, which crushed this list's track names into its 28px index column when reused here. See MusicBar.ts's own comment. */ .queueRow { display: flex; align-items: center; gap: 10px; background: var(--panel); border-radius: 8px; padding: 8px 10px; } .queueRowArt { width: 38px; height: 38px; object-fit: cover; border-radius: 6px; background: var(--bg-header); flex-shrink: 0; } .queueRowArt.noImage { visibility: hidden; } .queueRowInfo { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; } .queueRowName { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Artist/album/"Ajouté par X" condensed onto one secondary line (see MusicBar.ts) rather than each getting their own column - there's no room for 3+ columns once a name needs to read in full, unlike Album.ts's simpler index/name/duration/button row. */ .queueRowMeta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }