Polish playlist layout

This commit is contained in:
pleb 2025-11-03 11:35:36 -08:00
parent 5caf656baf
commit 2cae4ad3f6

View File

@ -206,26 +206,26 @@ function togglePlaylist(id: number) {
{/if}
</div>
<div class="mt-6 space-y-3">
<div class="mt-6 playlists-grid">
{#each playlists as playlist (playlist.playlistId)}
<article class="playlist-row card-surface">
<button class="row-header" type="button" on:click={() => togglePlaylist(playlist.playlistId)}>
<div class="row-cover">
<article class:expanded={Boolean(expanded[playlist.playlistId])} class="playlist-tile card-surface">
<button class="tile-header" type="button" on:click={() => togglePlaylist(playlist.playlistId)}>
<div class="tile-cover">
<img src={playlist.playlistImage} alt={`Playlist cover for ${playlist.name}`} loading="lazy" />
</div>
<div class="row-main">
<div class="row-title">
<div class="tile-main">
<div class="tile-title">
<a href={playlistLink(playlist.playlistId)} target="_blank" rel="noopener noreferrer" on:click|stopPropagation>
{playlist.name}
</a>
<span class="map-count">{playlist.stats?.totalMaps ?? 0} maps</span>
</div>
{#if isFiniteScore(playlist.stats?.avgScore)}
<div class="row-score">
<div class="tile-score">
<ScoreBar value={playlist.stats?.avgScore ?? null} size="sm" />
</div>
{/if}
<div class="row-sub">
<div class="tile-sub">
<span>
by
{#if playlist.owner?.name}
@ -238,7 +238,7 @@ function togglePlaylist(id: number) {
</span>
</div>
</div>
<div class:arrow-expanded={Boolean(expanded[playlist.playlistId])} class="row-arrow" aria-hidden="true">
<div class:arrow-expanded={Boolean(expanded[playlist.playlistId])} class="tile-arrow" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" role="presentation">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
@ -247,11 +247,11 @@ function togglePlaylist(id: number) {
{#if expanded[playlist.playlistId]}
{@const state = playlistState[playlist.playlistId]}
<div class="row-body">
<div class="tile-body">
{#if state?.loading}
<div class="row-status">Loading songs…</div>
<div class="tile-status">Loading songs…</div>
{:else if state?.error}
<div class="row-status error">{state.error}</div>
<div class="tile-status error">{state.error}</div>
{:else if state?.maps?.length}
<div class="songs-grid">
{#each state.maps as card (card.id)}
@ -280,7 +280,7 @@ function togglePlaylist(id: number) {
{/each}
</div>
{#if state.total && state.total > SONGS_PER_PAGE}
<div class="row-pagination">
<div class="tile-pagination">
<button
class="pager-btn"
on:click={() => loadPlaylistMaps(playlist.playlistId, Math.max(0, (state.offset ?? 0) - SONGS_PER_PAGE))}
@ -288,7 +288,7 @@ function togglePlaylist(id: number) {
>
Prev {SONGS_PER_PAGE}
</button>
<span class="row-status">
<span class="tile-status">
{#if state.total === 0}
No songs
{:else}
@ -314,7 +314,7 @@ function togglePlaylist(id: number) {
</div>
{/if}
{:else}
<div class="row-status">No songs found.</div>
<div class="tile-status">No songs found.</div>
{/if}
</div>
{/if}
@ -336,18 +336,30 @@ function togglePlaylist(id: number) {
</section>
<style>
.playlist-row {
padding: 0;
overflow: hidden;
.playlists-grid {
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.row-header {
.playlist-tile {
display: flex;
flex-direction: column;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.playlist-tile.expanded {
grid-column: 1 / -1;
}
.tile-header {
width: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
align-items: center;
padding: 1rem 1.25rem;
gap: 1rem;
padding: 1rem 1.25rem 0.85rem;
background: transparent;
color: inherit;
border: none;
@ -355,43 +367,42 @@ function togglePlaylist(id: number) {
text-align: left;
}
.row-cover {
height: 3em;
width: auto;
display: flex;
align-items: center;
justify-content: center;
.tile-cover {
position: relative;
width: 88px;
height: 88px;
border-radius: 0.6rem;
overflow: hidden;
border-radius: 0.5rem;
background: rgba(15, 23, 42, 0.6);
}
.row-cover img {
.tile-cover img {
width: 100%;
height: 100%;
width: auto;
object-fit: cover;
display: block;
}
.row-main {
.tile-main {
display: flex;
flex-direction: column;
gap: 0.4rem;
gap: 0.5rem;
}
.row-title {
.tile-title {
display: flex;
align-items: center;
gap: 0.75rem;
flex-direction: column;
gap: 0.45rem;
}
.tile-title a {
color: white;
font-size: 1.05rem;
font-weight: 600;
}
.row-title a {
color: white;
text-decoration: none;
}
.row-title a:hover {
.tile-title a:hover {
text-decoration: underline;
}
@ -400,7 +411,18 @@ function togglePlaylist(id: number) {
color: rgba(148, 163, 184, 0.85);
}
.row-sub {
.tile-score {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.tile-score :global(.score-meter) {
width: 100%;
max-width: 280px;
}
.tile-sub {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
@ -408,52 +430,61 @@ function togglePlaylist(id: number) {
color: rgba(148, 163, 184, 0.95);
}
.row-sub a {
.tile-sub a {
color: var(--color-neon);
text-decoration: none;
}
.row-sub a:hover {
.tile-sub a:hover {
text-decoration: underline;
color: color-mix(in srgb, var(--color-neon) 80%, white 20%);
}
.row-score {
display: flex;
flex-direction: column;
gap: 0.4rem;
max-width: 220px;
.tile-arrow {
align-self: center;
justify-self: center;
color: rgba(148, 163, 184, 0.75);
transition: transform 0.2s ease, color 0.2s ease;
}
.row-score :global(.score-meter) {
width: 100%;
}
.row-arrow {
transition: transform 0.2s ease;
color: rgba(148, 163, 184, 0.8);
}
.row-arrow.arrow-expanded {
.tile-arrow.arrow-expanded {
transform: rotate(180deg);
color: var(--color-neon);
}
.row-body {
.tile-body {
border-top: 1px solid rgba(148, 163, 184, 0.08);
padding: 1rem 1.25rem 1.25rem;
}
.row-status {
.tile-status {
color: rgba(148, 163, 184, 0.95);
font-size: 0.9rem;
}
.row-status.error {
@media (max-width: 640px) {
.tile-header {
grid-template-columns: minmax(64px, 1fr);
grid-template-rows: auto auto;
justify-items: stretch;
}
.tile-cover {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
.tile-arrow {
justify-self: end;
}
}
.tile-status.error {
color: #f87171;
}
.row-pagination {
.tile-pagination {
margin-top: 1rem;
display: flex;
align-items: center;