mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
4ac373a65b
* feat(artists): scoped live search badge replaces page filter Move Artists browse text search into the header Live Search with a page scope badge (Users icon), field-local undo, and double-click/backspace to clear scope. Block the live-search dropdown while scoped so results only filter the Artists grid; mobile overlay follows the same rules. * fix(artists): plain grid for scoped search fixes broken card layout Route the browse grid through VirtualCardGrid, switch to non-virtual CSS grid when live search filters the catalog, reset scroll on filter changes, and skip content-visibility on plain tiles to avoid blank/black cards. * fix(search): scope badge double-Backspace and single clear control Require two Backspaces on an empty scoped field after prior text input; one Backspace still clears the badge when the field was never filled. Move live-search clear/advanced controls inside the field pill, drop the extra outer clear button, and use type=text to avoid native search clears. * fix(search): drop duplicate outer live-search clear button Keep the native in-field clear on type=search and the original pill layout; remove only the extra × control outside the search border. Reset dropdown state when the query is cleared via the native control. * refactor(search): generic scoped browse query helper, drop dead code Rename artistsBrowseSearchQuery to scopedBrowseSearchQuery with an expectedScope argument; wire Artists via useScopedBrowseSearchQuery. Remove unused liveSearchScoped dropdown helper (scoped mode blocks it). * feat(search): ghost scope badge and single-click badge remove After clearing the artists scope on /artists, show a faded ghost chip to restore page-only search while keeping the global search placeholder. Active badge removes on one click; tooltips and styles updated. * feat(search): scoped live search for All Albums and New Releases Wire albums and newReleases scope badges with debounced album title search (local index title-only FTS + filtered search3). Plain grid, scroll reset, and session query restore on album grid browse pages. * fix(browse): preserve scroll restore after album/artist detail back Only reset in-page scroll when filter resetKey changes, not when isScrollRestorePending clears after session restore. * feat(search): scoped live search for Tracks browse Wire /tracks to header live search with wide title/artist/album FTS, hide hero and discovery rails while search is active, and remove the inline search field from the browse list. * fix(search): clear header query when leaving scoped browse pages Prevent global live search from firing on album/detail routes after a scoped browse query; browse session stashes still restore on back. * fix(tracks): restore scroll after back from detail during scoped search Hold stashed song results across fetchSongPage churn, defer leave-stash teardown past AppShell scroll reset, restore tracks scroll after the list is ready, and save scroll snapshot when opening artist from song context menu. * fix(tracks): hide discovery headings during scoped search Hide the page subtitle and "Browse all tracks" section title when tracks search is active, matching hero/rails chrome behavior. * feat(search): scoped live search for Composers browse Wire /composers to header live search with composers scope badge, session stash, scroll restore, and plain grid/list during text filter. Remove the in-page filter input; add i18n and navigation helpers. * docs: CHANGELOG and credits for scoped browse live search (PR #938)
375 lines
7.8 KiB
CSS
375 lines
7.8 KiB
CSS
/* ─ Album Row Container ─ */
|
|
.album-row-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-4);
|
|
position: relative;
|
|
}
|
|
|
|
.album-row-header {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.album-row-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.album-row-nav .nav-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-subtle);
|
|
color: var(--text-secondary);
|
|
transition: all var(--transition-fast);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.album-row-nav .nav-btn:hover:not(.disabled) {
|
|
background: var(--bg-hover);
|
|
color: var(--text-primary);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
.album-row-nav .nav-btn.disabled {
|
|
opacity: 0.3;
|
|
cursor: default;
|
|
}
|
|
|
|
.album-grid-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.album-grid {
|
|
display: flex;
|
|
gap: var(--space-4);
|
|
overflow-x: auto;
|
|
padding-bottom: var(--space-4);
|
|
scroll-snap-type: none;
|
|
scroll-behavior: smooth;
|
|
/* Hide scrollbar for Webkit */
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.album-grid::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.album-grid {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.album-grid-wrap {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
gap: var(--space-4);
|
|
align-items: start;
|
|
}
|
|
|
|
.album-grid-wrap .album-card {
|
|
content-visibility: auto;
|
|
contain-intrinsic-size: 0 220px;
|
|
}
|
|
|
|
/* Same wrap as Albums grid; defer layout/paint for off-screen artist tiles (helps scroll on WebKit/Linux). */
|
|
.album-grid-wrap > .artist-card {
|
|
content-visibility: auto;
|
|
contain-intrinsic-size: 0 220px;
|
|
}
|
|
|
|
/* Text search / plain grid: no deferred paint — stale scroll + content-visibility blanks tiles. */
|
|
.album-grid-wrap--plain > .artist-card {
|
|
content-visibility: visible;
|
|
contain-intrinsic-size: auto;
|
|
}
|
|
|
|
|
|
@media (min-width: 1024px) {
|
|
.album-grid-wrap {
|
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
}
|
|
}
|
|
|
|
.album-grid .album-card,
|
|
.album-grid .artist-card {
|
|
flex: 0 0 clamp(140px, 15vw, 180px);
|
|
}
|
|
|
|
/* Horizontal rails (Home Discover, etc.): WebKitGTK + scroll — no content-visibility:auto,
|
|
no .card transition, hover pinned to cover, dim via overlay background not opacity. */
|
|
.album-grid .album-card {
|
|
contain: none;
|
|
transform: none;
|
|
transition: none;
|
|
}
|
|
|
|
.album-grid .album-card:hover {
|
|
box-shadow: inset 0 0 0 1px var(--border-subtle);
|
|
}
|
|
|
|
.album-grid .album-card-cover {
|
|
contain: layout style;
|
|
transform: none;
|
|
}
|
|
|
|
.album-grid .album-card-cover img {
|
|
transform-origin: center center;
|
|
transform: scale(1);
|
|
transition: opacity 0.15s ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.album-grid .album-card-cover:hover img {
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
.album-grid .album-card .album-card-play-overlay {
|
|
opacity: 1;
|
|
background-color: transparent;
|
|
transition: none;
|
|
}
|
|
|
|
.album-grid .album-card .album-card-details-btn {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: none;
|
|
}
|
|
|
|
.album-grid .album-card-cover:hover .album-card-play-overlay,
|
|
.album-grid .album-card-cover:focus-within .album-card-play-overlay {
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.album-grid .album-card-cover:hover .album-card-details-btn,
|
|
.album-grid .album-card-cover:focus-within .album-card-details-btn {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.album-grid .artist-card {
|
|
content-visibility: auto;
|
|
contain-intrinsic-size: 0 240px;
|
|
}
|
|
|
|
.album-card-more {
|
|
flex: 0 0 clamp(140px, 15vw, 180px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-3);
|
|
background: var(--bg-hover);
|
|
border-radius: var(--radius-lg);
|
|
border: 1px dashed var(--border);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all var(--transition-base);
|
|
}
|
|
|
|
.album-card-more:hover {
|
|
background: var(--ctp-surface0);
|
|
color: var(--text-primary);
|
|
border-color: var(--accent);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.album-card-cover-badges-tr {
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 6px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 4px;
|
|
z-index: 2;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.album-card-offline-badge {
|
|
flex-shrink: 0;
|
|
background: color-mix(in srgb, var(--accent) 85%, transparent);
|
|
color: var(--ctp-crust);
|
|
border-radius: var(--radius-sm);
|
|
padding: 3px 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.album-card-new-badge {
|
|
flex-shrink: 0;
|
|
background: var(--accent);
|
|
color: var(--ctp-crust);
|
|
border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
|
|
border-radius: var(--radius-sm);
|
|
padding: 3px 6px;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.02em;
|
|
line-height: 1;
|
|
}
|
|
|
|
.album-card-play-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity var(--transition-base);
|
|
}
|
|
|
|
.album-card:hover .album-card-play-overlay,
|
|
.album-card:focus-within .album-card-play-overlay,
|
|
.album-card:has(.album-card-details-btn:hover) .album-card-play-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.album-card-details-btn {
|
|
background: var(--accent);
|
|
color: var(--ctp-crust);
|
|
border: none;
|
|
padding: 8px 20px;
|
|
border-radius: var(--radius-full);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
pointer-events: none;
|
|
transition: filter var(--transition-fast), background var(--transition-fast);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.album-card:hover .album-card-details-btn,
|
|
.album-card:focus-within .album-card-details-btn,
|
|
.because-card:hover .album-card-details-btn,
|
|
.because-card:focus-within .album-card-details-btn {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.album-card-details-btn:hover {
|
|
filter: brightness(1.08);
|
|
background: var(--accent);
|
|
}
|
|
|
|
.long-press-play-btn {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.long-press-play-btn__icon {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
@keyframes long-press-slosh {
|
|
0% { transform: translateX(0); }
|
|
100% { transform: translateX(-50%); }
|
|
}
|
|
|
|
.long-press-wave {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
color: currentColor;
|
|
opacity: 0;
|
|
transform: translateY(calc(100% + 15px));
|
|
transition: none;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
.long-press-wave--compact {
|
|
transform: translateY(calc(100% + 10px));
|
|
}
|
|
|
|
.long-press-wave--active {
|
|
opacity: 0.25;
|
|
transform: translateY(0);
|
|
transition: transform 900ms linear;
|
|
}
|
|
|
|
.long-press-wave--compact.long-press-wave--active {
|
|
opacity: 0.35;
|
|
}
|
|
|
|
.long-press-wave__crest {
|
|
position: absolute;
|
|
top: -10px;
|
|
left: 0;
|
|
width: 200%;
|
|
height: 12px;
|
|
animation: none;
|
|
}
|
|
|
|
.long-press-wave--compact .long-press-wave__crest {
|
|
top: -6px;
|
|
height: 8px;
|
|
}
|
|
|
|
.long-press-wave--active .long-press-wave__crest {
|
|
animation: long-press-slosh 1.2s linear infinite;
|
|
}
|
|
|
|
.long-press-wave__fill {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: currentColor;
|
|
}
|
|
|
|
|
|
/* ── Album card selection ── */
|
|
.album-card--selectable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.album-card--selected {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
.album-card-select-check {
|
|
position: absolute;
|
|
top: 6px;
|
|
left: 6px;
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: var(--radius-sm);
|
|
border: 2px solid rgba(255,255,255,0.7);
|
|
background: rgba(0,0,0,0.35);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 3;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.album-card-select-check--on {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: var(--ctp-crust);
|
|
}
|
|
|