watchparty/client/src/tiles.css
Nicolas ff664f2c2b
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
Replace every blocking alert() with a light toast, fix a mobile grid bug
Every message this app ever showed the client - the host-handoff
notice, every "couldn't start/join a party" error - went through a
blocking alert(), freezing the tab for what's usually just a heads-up.
New toast.ts is a self-mounting singleton (no per-party state, so it
doesn't need Shell.ts wiring like musicBar/MusicBar does) styled after
Player.css's own .requestBanner, with an 'error' variant that's still
just a plain left accent, not a red banner - even a failed click
doesn't need this app to suddenly shout. All ten alert() call sites
across Player/MusicBar/tiles/Home/Series/Playlist/Artist/Album now go
through it.

Also fixes a real reported bug while in the area: the library/artist
grid's minmax(160px, 1fr) floor left no room for a second column once
a real phone's padding was subtracted, unlike everything else mobile
in this app (the sidebar, the music bar) which already got a
narrow-screen treatment. Switched to a fluid clamp(100px, 28vw, 160px)
floor instead of a fixed breakpoint, confirmed at seven real widths via
an iframe (its own independent viewport for vw units, sidestepping
this sandbox's own unreliable viewport-resize emulation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 17:34:10 +02:00

176 lines
5.4 KiB
CSS

/* ---- Tile grid (library/artist pages) + tiles (shared with the home
carousels) - see tiles.ts. ---- */
/* base.css's global `input {}` rule already gives this the right look
(padding/border/background) - this just bounds its width and spaces it
from the grid below, shared by views/library, views/artist, views/album -
see tiles.ts's wireSearchFilter. Sticky, not just top-of-page: asked
for directly, so it stays reachable while scrolling a long grid
instead of scrolling away with the page title above it. Its own
background (base.css's input rule already gives it var(--panel), not
transparent) is what keeps tiles scrolling underneath from showing
through its own bounds once stuck. */
.searchBar {
max-width: 320px;
margin-bottom: 16px;
position: sticky;
top: 0;
z-index: 2;
}
.searchEmpty { margin-top: 8px; }
/* The 160px floor was too wide for a real phone once .mainContent's own
side padding is subtracted - reported directly: a ~360-393px-wide
viewport (a common real Android width, not a rare edge case) only
ever fit one column. clamp(), not a max-width media query breakpoint:
this scales the minimum continuously with the viewport instead of
jumping at one arbitrary width, so it's already right at every size
in between, not just "mobile" and "desktop" - 28vw lands close to
100px on a narrow phone and hits the 160px ceiling (today's exact
desktop value, unchanged) well before any actual desktop width. */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(clamp(100px, 28vw, 160px), 1fr));
gap: 20px 16px;
}
.tile {
position: relative;
display: flex;
flex-direction: column;
gap: 6px;
/* Without this, a long title (or an image's intrinsic size before it
loads) can force this flex/grid item wider than its declared size -
the classic "min-width: auto" flexbox/grid pitfall. Every tile must
stay exactly the track/flex-basis size regardless of content. */
min-width: 0;
}
.tile img {
width: 100%;
aspect-ratio: 2 / 3;
object-fit: cover;
border-radius: 8px;
background: var(--panel);
display: block;
}
.tile img.noImage { visibility: hidden; }
.tileLink {
position: absolute;
inset: 0 0 26px 0;
z-index: 1;
}
.tileLabel {
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Hover-reveal overlay with the two actions, on devices that actually have
hover (mouse/trackpad). Touch devices get an always-visible bar instead -
see the (hover: none) block below, since :hover is unreachable there. */
.tileOverlay {
position: absolute;
inset: 0 0 26px 0;
z-index: 2;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
gap: 8px;
padding: 10px;
background: rgba(0, 0, 0, .55);
border-radius: 8px;
opacity: 0;
transition: opacity .15s ease;
pointer-events: none;
}
.tile:hover .tileOverlay,
.tile:focus-within .tileOverlay {
opacity: 1;
pointer-events: auto;
}
/* Base look is the same solid, high-contrast treatment as every other
button in the app (see base.css's global `button {}` rule) - but that
rule is tag-scoped, so it never applied to "Voir les épisodes", the one
.tileBtn that's an <a>, not a <button> (a plain hash link, so the
browser's native navigation handles it with no JS needed). Left
unstyled beyond padding/text-align, it would render as a bare, small,
default-blue link sitting on the overlay - much less visible than
"Lancer une watch party" (a real <button>) or "Voir sur Emby" (an <a>,
but with the explicit .tileBtnLink look below) right next to it.
Styling .tileBtn itself, tag-agnostic, fixes that - and is the
semantically right call besides: for a Series tile, "Voir les épisodes"
IS the primary action (a Series is never itself directly playable, so
the two never appear on the same tile), not a secondary one. */
.tileBtn {
padding: 8px 10px;
font-size: 12px;
font-weight: 600;
text-align: center;
text-decoration: none;
border-radius: 8px;
background: var(--accent);
color: white;
}
.tileBtn:hover { background: var(--accent-bright); }
/* "Voir sur Emby" only: the one secondary/neutral action among the tile
buttons - overrides just the two properties that make it read as
secondary, on top of .tileBtn's shared sizing/weight/radius above. */
.tileBtnLink {
background: var(--panel);
color: var(--text);
}
.tileBtnLink:hover { background: #333; }
@media (hover: none) {
.tileOverlay {
opacity: 1;
pointer-events: auto;
inset: auto 0 26px 0;
flex-direction: row;
background: linear-gradient(to top, rgba(0,0,0,.75), transparent);
padding: 6px;
}
.tileBtn { font-size: 11px; padding: 6px 4px; }
}
/* Artist/album tiles (tiles.ts's artistTileHtml/albumTileHtml) - an <a>
directly, not a <div> with a separate absolutely-positioned
.tileLink/.tileOverlay on top of it: there's only ever the one
action (open the artist/album page), so the whole tile just being
the link is simpler than reusing machinery built for a second,
hover-revealed set of actions these two never have. */
.tile.tileArtist,
.tile.tileAlbum {
text-decoration: none;
color: var(--text);
}
.tile.tileArtist img,
.tile.tileAlbum img {
aspect-ratio: 1 / 1;
}
.tile.tileArtist img {
border-radius: 50%;
}
.tile.tileArtist .tileLabel {
text-align: center;
}
.tile.tileArtist:hover .tileLabel,
.tile.tileAlbum:hover .tileLabel {
text-decoration: underline;
}