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>
176 lines
5.4 KiB
CSS
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;
|
|
}
|