watchparty/client/src/musicBar/MusicBar.css
Nicolas 5ceceb262d
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
Fix the music bar progress bar not spanning full width on desktop
trackProgress is musicBarPanel own first child inside a 3-column grid
(track/transport/side), with no grid-column of its own - auto-placement
dropped it into column 1 of row 1 like any other item, squeezing it to
that columns width and wrapping musicBarPanelSide onto a second row
underneath instead of spanning the full bar with everything else in
one row below, as its own comment already said it should. Fixed with
grid-column: 1 / -1, which pushes every sibling after it into row 2
across the full width with no changes needed to any of them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 22:55:51 +02:00

440 lines
14 KiB
CSS

/* 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 {
position: relative; /* containing block for .musicBarQueue's own position: absolute - see its comment */
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.
`grid-column: 1 / -1` is the fix, not decoration: #trackProgress is
.musicBarPanel's first child, and without an explicit span a 3-column
grid places it in column 1 alone - squeezing it down to the width of
.musicBarPanelTrack's own column and wrapping .musicBarPanelSide onto
a second row underneath it, instead of the intended full-width strip
with everything else in one row below. Reported directly - never
looked obviously wrong at a glance since .musicBarPanelTrack's own
content (a small image + short text) left column 1 wide enough to
mostly hide the squeeze. Spanning this row is what pushes every
sibling after it into row 2, across all 3 columns, with no change
needed to them. */
.musicBarProgressTrack {
grid-column: 1 / -1;
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 - position: absolute against .musicBar's own
position: relative, bottom: 100% instead of that one's top: 100%/
right: 0, since this floats up from a bottom bar rather than down from
a top-corner icon) - collapsed by default, see MusicBar.ts's
toggleQueueBtn handler. `position: absolute` (not left in normal flow)
is what stops opening this from silently growing .bottomStack's own
fixed-to-viewport height and shoving the actual controls upward
instead of floating over them - reported directly as the desktop bar
looking broken the moment the queue was opened. 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 {
position: absolute;
left: 0;
right: 0;
bottom: 100%;
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; }
/* Always reachable regardless of z-index/positioning below (see
MusicBar.ts's own click-outside handler for the other way to close
this on desktop) - reported directly that, in mobile's full-screen
layout, the *only* previous way to close this (tap the same
#toggleQueueBtn again) sat visually underneath the now-open queue
panel itself once it covered the screen, making it unreachable. */
.queueCloseBtn {
background: none;
color: var(--muted);
font-size: 12px;
font-weight: 400;
padding: 2px;
flex-shrink: 0;
}
.queueCloseBtn:hover { background: none; color: var(--text); }
/* 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; }
/* A full-screen overlay of its own here, not desktop's floating popup
(there's no "above the bar" to float over once .musicBarPanel is
itself a full-screen overlay) - z-index: 26 (above .musicBarPanel's
25) is still what puts it on top, but `position: relative` used to
leave it in .musicBar's own normal flow, sized by `max-height: 50vh`
inherited from the base rule above and positioned whichever the
surrounding layout happened to allow - overlapping the very
#toggleQueueBtn used to open it, with no other way to close a
`position: relative` sibling that isn't just a small popup here.
`inset: 0` + `max-height: none` makes it genuinely full-screen like
.musicBarPanel itself, so its own #queueCloseBtn is always reachable
regardless of queue length. */
.musicBarQueue {
position: fixed;
inset: 0;
z-index: 26;
max-height: none;
margin: 0;
border-radius: 0;
padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px));
}
}
/* 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;
}