Files
Psychotoxical-psysonic/src/styles/components.css
T
Frank Stellmacher 20a083a9a6 feat(player): preview indicator in player bar + smart stop semantics (#394)
* feat(player): preview-active state on play button (ring + stop icon)

Checkpoint: play button mirrors the inline preview button from tracklists
during preview playback — hollow circle, accent ring depleting over the
preview duration, Square (stop) icon. Click still resumes main playback,
which the Rust audio engine cancels the preview for.

i18n key player.previewActive in all 8 locales for tooltip + aria-label.


* feat(player): show preview track in player bar + smart stop semantics

The player-bar info cell (cover, title, artist) now mirrors the previewing
track during preview playback, with a small accent "Preview" pill above
the title and an accent top-border on the bar. Rating, fullscreen hint
and album/artist link clicks are suppressed while previewing — they
target the queued track, not the preview.

Stop semantics for the two transport buttons during preview:
- Big play button (Square+ring visual): stops preview, main auto-resumes
  if it was playing before. Matches the tracklist preview-button behaviour.
- Small Stop button: new audio_preview_stop_silent Rust command — stops
  preview AND leaves main paused, so "Stop = silence" actually goes silent.

previewStore now stores the full PreviewingTrack (id, title, artist,
coverArt) — the seven startPreview call sites pass it through.
i18n key player.previewLabel in all 8 locales.
2026-05-01 12:57:34 +02:00

13245 lines
283 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ─── Pages & Components CSS ─── */
/* ─ Hero ─ */
.hero {
position: relative;
width: 100%;
height: 360px;
overflow: hidden;
flex-shrink: 0;
}
.hero-placeholder {
width: 100%;
height: 360px;
background: linear-gradient(135deg, var(--ctp-surface0), var(--ctp-mantle));
flex-shrink: 0;
}
.hero-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
transition: opacity 0.8s ease;
}
.hero-dots {
position: absolute;
bottom: var(--space-4);
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 6px;
z-index: 10;
}
.hero-dot {
width: 8px;
height: 8px;
border-radius: var(--radius-full);
background: rgba(255, 255, 255, 0.35);
border: none;
padding: 0;
cursor: pointer;
transition: all 0.3s ease;
}
.hero-dot:hover:not(.hero-dot-active) {
background: rgba(255, 255, 255, 0.6);
}
.hero-dot-active {
width: 24px;
background: rgba(255, 255, 255, 0.9);
}
.hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(to right,
rgba(0, 0, 0, 0.78) 0%,
rgba(0, 0, 0, 0.45) 50%,
rgba(0, 0, 0, 0.18) 100%),
linear-gradient(to top,
rgba(0, 0, 0, 0.88) 0%,
transparent 60%);
}
.hero-content {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
gap: 2rem;
padding: var(--space-8);
}
.hero-cover {
width: 220px;
height: 220px;
border-radius: var(--radius-md);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6);
object-fit: cover;
flex-shrink: 0;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.hero-text {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.hero-eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: var(--space-2);
}
.hero-title {
font-family: var(--font-display);
font-size: clamp(24px, 3vw, 36px);
font-weight: 700;
color: #ffffff;
margin-bottom: var(--space-1);
line-height: 1.2;
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
max-width: 500px;
}
.hero-artist {
font-size: 16px;
color: rgba(255, 255, 255, 0.72);
margin-bottom: var(--space-3);
}
.hero-meta {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.hero-play-btn {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-5);
background: var(--accent);
color: var(--ctp-crust);
border-radius: var(--radius-full);
font-size: 14px;
font-weight: 600;
transition: all var(--transition-fast);
width: fit-content;
}
.hero-play-btn:hover {
background: var(--accent);
filter: brightness(1.1);
transform: scale(1.02);
box-shadow: var(--shadow-glow);
}
/* ─ Page header row (title + actions) ─ */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
flex-wrap: wrap;
margin-bottom: var(--space-6);
}
.page-header .page-title {
margin-bottom: 0;
}
/* ─ Section titles ─ */
.section-title,
.page-title {
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--space-4);
}
.section-title-link {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
cursor: pointer;
transition: color var(--transition-fast);
}
.section-title-link:hover {
color: var(--accent);
}
.section-title-chevron {
opacity: 0.45;
transition: opacity var(--transition-fast), transform var(--transition-fast);
flex-shrink: 0;
}
.section-title-link:hover .section-title-chevron {
opacity: 1;
transform: translateX(2px);
}
/* ─ Album Card ─ */
.album-card {
position: relative;
cursor: pointer;
background: var(--bg-card);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: inset 0 0 0 1px var(--border-subtle);
transition: transform var(--transition-base), box-shadow var(--transition-base);
}
.album-card:hover {
transform: translateY(-3px);
box-shadow: inset 0 0 0 1px var(--border), var(--shadow-md);
}
.album-card-cover {
position: relative;
aspect-ratio: 1;
overflow: hidden;
background: var(--bg-hover);
}
.album-card-cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform var(--transition-slow);
}
.album-card:hover .album-card-cover img {
transform: scale(1.04);
}
.album-card-cover-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
}
/* ─ Artist Card (Grid View) ─ */
.artist-card {
display: flex;
flex-direction: column;
background: var(--bg-card);
border-radius: var(--radius-lg);
border: 1px solid var(--border-subtle);
cursor: pointer;
overflow: hidden;
transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}
.artist-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--border);
}
.artist-card-avatar {
position: relative;
aspect-ratio: 1;
overflow: hidden;
background: var(--bg-hover);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
}
.artist-card-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform var(--transition-slow);
}
.artist-card:hover .artist-card-avatar img {
transform: scale(1.04);
}
.artist-card-avatar-initial {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 50% !important;
border: 2px solid;
width: calc(100% - 2 * var(--space-4));
margin: var(--space-3) auto var(--space-3);
}
.artist-card-avatar-initial span {
font-size: 3rem;
font-weight: 800;
font-family: var(--font-display);
line-height: 1;
user-select: none;
}
.artist-card-info {
padding: var(--space-3) var(--space-3) var(--space-2);
display: flex;
flex-direction: column;
gap: 2px;
}
.artist-card-name {
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.artist-card-meta {
font-size: 12px;
color: var(--text-secondary);
}
/* ─ 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: 50%;
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: x mandatory;
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;
}
@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);
scroll-snap-align: start;
}
.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;
scroll-snap-align: start;
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-offline-badge {
position: absolute;
top: 6px;
right: 6px;
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;
z-index: 2;
pointer-events: none;
}
.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;
transition: opacity var(--transition-base);
}
.album-card: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;
transition: transform var(--transition-fast), background var(--transition-fast);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
}
.album-card-details-btn:hover {
transform: scale(1.05);
background: var(--accent);
}
/* ── 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);
}
/* ─── Artist Card Selection Checkbox ─── */
.artist-card {
position: relative;
}
.artist-card-select-check {
position: absolute;
top: 6px;
left: 6px;
width: 20px;
height: 20px;
border-radius: var(--radius-sm);
border: 2px solid var(--border-subtle);
background: var(--bg-elevated);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
pointer-events: none;
}
.artist-card-select-check--on {
background: var(--accent);
border-color: var(--accent);
color: var(--ctp-crust);
}
/* ── Albums selection bar (portal) ── */
.albums-selection-bar {
position: fixed;
bottom: calc(var(--player-height) + 12px);
left: 50%;
transform: translateX(-50%);
z-index: 9000;
display: flex;
align-items: center;
gap: 0.75rem;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 8px 14px;
box-shadow: var(--shadow-lg);
white-space: nowrap;
}
.albums-selection-count {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
padding-right: 4px;
border-right: 1px solid var(--border-subtle);
}
.albums-selection-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.albums-selection-action-btn {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
}
.album-card-info {
padding: var(--space-3) var(--space-4) var(--space-4);
}
.album-card-title {
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.album-card-artist {
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.album-card-year {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.album-card-rating-row {
display: flex;
align-items: center;
gap: 5px;
margin-top: 4px;
}
.album-card-rating-stars {
font-size: 11px;
color: var(--accent);
letter-spacing: 1px;
line-height: 1;
}
/* ─ Live Search ─ */
.live-search {
position: relative;
min-width: 280px;
max-width: 420px;
flex: 1;
}
.live-search-input-wrap {
position: relative;
display: flex;
align-items: center;
}
.live-search-icon {
position: absolute;
left: 12px;
color: var(--text-muted);
pointer-events: none;
display: flex;
align-items: center;
}
.live-search-field {
padding-left: 36px !important;
padding-right: 58px !important;
border-radius: var(--radius-full) !important;
}
.live-search-clear {
position: absolute;
right: 8px;
font-size: 18px;
color: var(--text-muted);
line-height: 1;
transition: color var(--transition-fast);
}
.live-search-clear:hover {
color: var(--text-primary);
}
.live-search-adv-btn {
position: absolute;
right: 30px;
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
background: none;
border: none;
cursor: pointer;
color: var(--text-muted);
opacity: 0.55;
transition: opacity var(--transition-fast), color var(--transition-fast);
border-radius: var(--radius-sm);
padding: 0;
}
.live-search-adv-btn:hover {
opacity: 1;
color: var(--accent);
}
.live-search-dropdown {
position: absolute;
top: calc(100% + 8px);
left: 0;
right: 0;
background: var(--ctp-surface0);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
max-height: 420px;
overflow-y: auto;
z-index: 1000;
animation: fadeIn 150ms ease both;
}
.search-section {
padding: var(--space-2) 0;
}
.search-section+.search-section {
border-top: 1px solid var(--border-subtle);
}
.search-section-label {
display: flex;
align-items: center;
gap: 6px;
padding: var(--space-2) var(--space-4);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.search-result-item {
display: flex;
align-items: center;
gap: var(--space-3);
width: 100%;
padding: var(--space-2) var(--space-4);
text-align: left;
transition: background var(--transition-fast);
border-radius: 0;
}
.search-result-item:hover,
.search-result-item.active,
.search-result-item.context-active {
background: var(--bg-hover);
}
.search-result-icon {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
background: var(--bg-hover);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
flex-shrink: 0;
}
.search-result-thumb {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
object-fit: cover;
flex-shrink: 0;
}
.search-result-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.search-result-sub {
font-size: 11px;
color: var(--text-secondary);
margin-top: 2px;
}
.search-empty {
padding: var(--space-4);
text-align: center;
color: var(--text-muted);
font-size: 13px;
}
/* ─ Album Detail ─ */
/* ─── Offline Library ─── */
.offline-library {
padding: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-6);
}
.offline-library-header {
display: flex;
align-items: center;
gap: 14px;
color: var(--accent);
}
.offline-library-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.offline-library-count {
font-size: 13px;
color: var(--text-secondary);
margin: 2px 0 0;
}
.offline-library-card .album-card-info {
gap: 3px;
}
.offline-library-card-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 2px;
}
.offline-library-enqueue {
background: none;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-muted);
font-size: 10px;
padding: 2px 6px;
cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast);
}
.offline-library-enqueue:hover {
color: var(--accent);
border-color: var(--accent);
}
.offline-library-tracks {
font-size: 10px;
color: var(--text-muted);
}
.offline-library-delete {
background: none;
border: none;
padding: 2px 4px;
cursor: pointer;
color: var(--text-muted);
display: flex;
align-items: center;
border-radius: var(--radius-sm);
transition: color var(--transition-fast);
}
.offline-library-delete:hover {
color: var(--color-error, #e05050);
}
/* Filter tabs */
.offline-filter-tabs {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.offline-filter-tab {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 20px;
border: 1px solid var(--border);
background: none;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.offline-filter-tab:hover {
color: var(--text-primary);
border-color: var(--accent);
}
.offline-filter-tab.active {
background: var(--accent);
color: var(--ctp-base);
border-color: var(--accent);
font-weight: 600;
}
.offline-filter-tab-count {
font-size: 11px;
opacity: 0.75;
}
.offline-filter-tab.active .offline-filter-tab-count {
opacity: 0.85;
}
/* Artist discography groups */
.offline-artist-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.offline-artist-group-heading {
font-size: 16px;
font-weight: 700;
color: var(--text-primary);
padding-bottom: 6px;
border-bottom: 1px solid var(--border);
}
.album-detail {
display: flex;
flex-direction: column;
gap: 0;
position: relative;
}
.album-detail-header {
position: relative;
padding: var(--space-6) var(--space-6) 0;
overflow: hidden;
}
.album-detail-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
z-index: 0;
opacity: 0.35;
transition: opacity var(--transition-slow);
}
.album-detail-overlay {
position: absolute;
inset: 0;
background: linear-gradient(to bottom,
rgba(30, 30, 46, 0.5) 0%,
var(--bg-app) 100%);
z-index: 0;
}
.album-detail-hero {
display: flex;
gap: var(--space-6);
align-items: flex-start;
padding: var(--space-4) 0 var(--space-6);
}
.album-detail-cover-btn {
padding: 0;
border-radius: var(--radius-lg);
flex-shrink: 0;
cursor: zoom-in;
position: relative;
overflow: hidden;
display: block;
width: clamp(120px, 15vw, 200px);
height: clamp(120px, 15vw, 200px);
}
.album-detail-cover-btn:hover .album-detail-cover {
transform: scale(1.04);
}
.album-detail-cover {
width: 100%;
height: 100%;
border-radius: var(--radius-lg);
object-fit: cover;
box-shadow: var(--shadow-lg);
display: block;
transition: transform 0.2s ease;
}
/* ─── Cover Lightbox ─── */
.cover-lightbox-overlay {
position: fixed;
inset: 0;
z-index: 9000;
background: rgba(0, 0, 0, 0.88);
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
box-sizing: border-box;
cursor: zoom-out;
backdrop-filter: blur(6px);
}
.cover-lightbox-img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: var(--radius-lg);
box-shadow: 0 32px 80px rgba(0, 0, 0, 0.8);
cursor: default;
}
.cover-lightbox-close {
position: fixed;
top: 24px;
right: 24px;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background var(--transition-fast);
z-index: 1;
}
.cover-lightbox-close:hover {
background: rgba(255, 255, 255, 0.24);
}
.album-cover-placeholder {
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-card);
font-size: 48px;
color: var(--text-muted);
}
/* ── Playlist 2×2 cover grid ── */
.playlist-cover-grid {
flex-shrink: 0;
width: clamp(120px, 15vw, 200px);
height: clamp(120px, 15vw, 200px);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-lg);
}
/* In playlist cards, the 2x2 grid must fill the full cover area. */
.album-card-cover > .playlist-cover-grid {
width: 100%;
height: 100%;
border-radius: 0;
box-shadow: none;
}
.playlist-cover-cell {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.playlist-cover-cell--empty {
background: var(--bg-hover);
}
.album-detail-meta {
min-width: 0;
flex: 1;
}
.album-detail-title {
font-family: var(--font-display);
font-size: clamp(20px, 3vw, 32px);
font-weight: 800;
color: var(--text-primary);
line-height: 1.1;
margin: var(--space-2) 0 var(--space-1);
overflow-wrap: break-word;
}
.album-detail-artist {
font-size: clamp(13px, 1.5vw, 16px);
color: var(--accent);
font-weight: 600;
}
.album-detail-artist-link {
background: none;
border: none;
padding: 0;
color: var(--accent);
font-size: inherit;
font-weight: inherit;
cursor: pointer;
text-decoration: none;
transition: color var(--transition-fast), text-decoration var(--transition-fast);
}
.album-detail-artist-link:hover {
color: var(--text-primary);
text-decoration: underline;
}
.album-detail-info {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
color: var(--text-muted);
font-size: 13px;
margin: var(--space-2) 0 var(--space-2);
}
.album-detail-entity-rating {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-3);
margin-bottom: var(--space-4);
}
/* Inline with label: do not stretch to full row width */
.album-detail-entity-rating .star-rating {
width: auto;
justify-content: flex-start;
}
.album-detail-entity-rating-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.artist-detail-entity-rating {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
}
.artist-detail-entity-rating .star-rating {
width: auto;
justify-content: flex-start;
}
.artist-detail-entity-rating-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.album-detail-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
row-gap: var(--space-2);
}
.album-detail-actions-primary {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.album-detail-content {
position: relative;
z-index: 1;
}
.album-detail-badge {
margin-bottom: 0.5rem;
background: var(--accent);
color: #fff;
}
.album-detail-back {
margin-bottom: 1rem;
gap: 6px;
}
.album-info-dot {
margin: 0 4px;
}
.album-related {
padding: 0 var(--space-6) var(--space-8);
}
.album-related-divider {
border-top: 1px solid var(--border-subtle);
margin-bottom: 2rem;
}
.album-related-title {
margin-bottom: 1rem;
}
.download-hint {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--text-secondary);
background: rgba(203, 166, 247, 0.08);
border: 1px solid rgba(203, 166, 247, 0.2);
border-radius: var(--radius-full);
padding: 3px 10px 3px 8px;
cursor: default;
white-space: nowrap;
}
.download-progress-wrap {
display: flex;
align-items: center;
gap: var(--space-2);
padding: 0 var(--space-3);
height: 36px;
border-radius: var(--radius-md);
background: var(--surface-1);
border: 1px solid var(--border);
min-width: 180px;
color: var(--text-secondary);
font-size: 12px;
}
.download-progress-bar {
flex: 1;
height: 4px;
background: var(--surface-2);
border-radius: 2px;
overflow: hidden;
}
.download-progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--ctp-mauve), var(--ctp-blue));
border-radius: 2px;
transition: width 0.2s ease;
}
.download-progress-pct {
min-width: 28px;
text-align: right;
font-variant-numeric: tabular-nums;
}
/* ─ Offline cache button ─ */
.offline-cache-btn {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
}
.offline-cache-btn--cached {
color: var(--color-star-active, var(--accent));
border-color: var(--color-star-active, var(--accent));
}
.offline-cache-btn--progress {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
opacity: 0.75;
padding: 6px 10px;
}
/* ─ Download folder modal ─ */
.download-folder-pick-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-input, var(--bg-sidebar));
margin-bottom: 12px;
}
.download-folder-path {
flex: 1;
font-size: 13px;
color: var(--text-primary);
word-break: break-all;
min-width: 0;
}
.download-folder-path:empty::before,
.download-folder-path[data-empty]::before {
color: var(--text-muted);
}
.download-remember-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--text-secondary);
cursor: pointer;
user-select: none;
margin-bottom: 4px;
}
.download-modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 12px;
border-top: 1px solid var(--border);
margin-top: 4px;
}
/* ─ Song Info Modal ─ */
.song-info-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9990;
}
.song-info-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9991;
width: 460px;
max-width: calc(100vw - 32px);
max-height: 80vh;
background: var(--bg-card, var(--bg-secondary));
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
overflow: hidden;
}
.song-info-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px 12px;
border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.song-info-title {
font-size: 14px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: 0.02em;
}
.song-info-close {
padding: 4px;
color: var(--text-muted);
}
.song-info-body {
overflow-y: auto;
padding: 12px 4px 16px;
}
.song-info-loading {
text-align: center;
color: var(--text-muted);
font-size: 13px;
padding: 24px;
}
.song-info-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.song-info-label {
color: var(--text-muted);
padding: 4px 16px 4px 16px;
white-space: nowrap;
vertical-align: top;
width: 140px;
font-size: 12px;
}
.song-info-value {
color: var(--text-primary);
padding: 4px 16px 4px 8px;
word-break: break-all;
}
/* Double-click copies; avoid browser selecting word on dblclick */
.song-info-value--no-select {
user-select: none;
-webkit-user-select: none;
}
.song-info-divider {
padding: 6px 0;
border-bottom: 1px solid var(--border-subtle);
}
.song-info-path {
font-family: var(--font-mono, monospace);
font-size: 11px;
color: var(--text-secondary);
word-break: break-all;
}
/* ─ Tracklist ─ */
.tracklist {
padding: 0 var(--space-6) var(--space-6);
overflow-x: auto;
}
.tracklist-header-wrapper {
position: relative;
}
.col-center {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.tracklist-header {
display: grid;
gap: var(--space-3);
align-items: center;
padding: var(--space-2) var(--space-3);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
border-bottom: 1px solid var(--border-subtle);
margin-bottom: var(--space-2);
}
.track-row {
display: grid;
gap: var(--space-3);
align-items: center;
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
cursor: pointer;
transition: background var(--transition-fast);
user-select: none;
contain: layout style;
overflow: hidden;
}
.tracklist-total {
display: grid;
align-items: center;
border-top: 1px solid var(--border-subtle);
padding: var(--space-2) var(--space-3);
margin-top: var(--space-1);
}
.tracklist-total-label {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
text-align: right;
}
.tracklist-total-value {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
text-align: center;
}
/* ── Column resize handle ── */
/* Positioned flush with the right edge of its header cell.
The ::after pseudo-element renders the visible 1 px divider line. */
.col-resize-handle {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
z-index: 2;
}
.col-resize-handle::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 20%;
bottom: 20%;
width: 1px;
border-radius: 1px;
background: var(--ctp-surface1);
transition: background 0.15s;
}
.col-resize-handle:hover::after,
.col-resize-handle:active::after {
background: var(--accent);
}
/* ── Column visibility picker ── */
.tracklist-col-picker-wrapper {
display: flex;
justify-content: flex-end;
padding: 0 var(--space-6);
margin-bottom: 4px;
z-index: 50;
}
.tracklist-col-picker {
position: relative;
display: flex;
align-items: center;
}
.tracklist-col-picker-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
opacity: 0.5;
transition: opacity 0.15s, background 0.15s;
}
.tracklist-col-picker-btn:hover {
opacity: 1;
background: var(--ctp-surface0);
color: var(--text-primary);
}
.tracklist-col-picker-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
background: var(--bg-card);
border: 1px solid var(--ctp-surface1);
border-radius: var(--radius-md);
padding: 6px;
min-width: 160px;
z-index: 200;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
}
.tracklist-col-picker-label {
font-size: 10px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.07em;
padding: 4px 8px 6px;
}
.tracklist-col-picker-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 5px 8px;
border-radius: var(--radius-sm);
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 13px;
text-align: left;
transition: background 0.1s;
}
.tracklist-col-picker-item:hover {
background: var(--ctp-surface0);
color: var(--text-primary);
}
.tracklist-col-picker-item.active {
color: var(--accent);
}
.tracklist-col-picker-check {
width: 16px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
}
.tracklist-col-picker-divider {
height: 1px;
background: var(--ctp-surface1);
margin: 4px 0;
}
.tracklist-col-picker-reset {
display: flex;
width: 100%;
align-items: center;
gap: 8px;
padding: 6px 8px;
background: none;
border: none;
border-radius: var(--radius-sm);
font-size: 12px;
color: var(--text-muted);
cursor: pointer;
text-align: left;
transition: background var(--transition-fast), color var(--transition-fast);
}
.tracklist-col-picker-reset:hover {
background: var(--ctp-surface0);
color: var(--text-primary);
}
/* Delete button in playlist row */
.playlist-row-delete-cell {
display: flex;
align-items: center;
justify-content: center;
}
.playlist-row-delete-btn {
background: none;
border: none;
cursor: pointer;
color: var(--text-muted);
padding: 4px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
opacity: 0;
transition: opacity var(--transition-fast), color var(--transition-fast);
}
.track-row:hover .playlist-row-delete-btn,
.track-row.context-active .playlist-row-delete-btn {
opacity: 1;
}
.playlist-row-delete-btn:hover {
color: var(--danger);
}
/* In suggestion rows the + btn is always visible at muted opacity */
.playlist-suggestions .playlist-row-delete-btn {
opacity: 0.45;
}
.playlist-suggestions .track-row:hover .playlist-row-delete-btn {
opacity: 1;
color: var(--accent);
}
/* Suggestion-row action buttons — sit left of the track title */
.track-info-suggestion {
display: flex;
align-items: center;
gap: 6px;
}
.playlist-suggestion-play-btn {
flex: 0 0 auto;
width: 24px;
height: 24px;
padding: 0;
border: none;
border-radius: 50%;
background: var(--accent);
color: var(--ctp-crust);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0.85;
transition: opacity var(--transition-fast), transform var(--transition-fast);
}
.playlist-suggestion-play-btn:hover {
opacity: 1;
transform: scale(1.06);
}
.playlist-suggestion-play-icon {
display: block;
/* optical center for right-weighted triangle */
transform: translateX(0.5px);
}
.playlist-suggestion-preview-btn {
position: relative;
flex: 0 0 auto;
width: 24px;
height: 24px;
padding: 0;
border: none;
background: transparent;
border-radius: 50%;
cursor: pointer;
color: var(--text-muted);
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity var(--transition-fast), color var(--transition-fast);
}
.playlist-suggestions .track-row:hover .playlist-suggestion-preview-btn,
.playlist-suggestion-preview-btn:hover,
.playlist-suggestion-preview-btn.is-previewing {
opacity: 1;
color: var(--accent);
}
.playlist-suggestion-preview-ring {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transform: rotate(-90deg);
}
.playlist-suggestion-preview-ring-track {
fill: none;
stroke: var(--ctp-overlay0);
stroke-width: 1.5;
}
.playlist-suggestion-preview-ring-progress {
fill: none;
stroke: var(--accent);
stroke-width: 1.5;
/* circumference = 2π × 10.5 ≈ 65.97 */
stroke-dasharray: 65.97;
stroke-dashoffset: 65.97;
stroke-linecap: round;
}
.playlist-suggestion-preview-icon {
display: block;
position: relative;
z-index: 1;
}
/* Play triangle is right-weighted; nudge for optical center */
.playlist-suggestion-preview-icon-play {
transform: translateX(0.5px);
}
.playlist-suggestion-preview-btn.is-previewing .playlist-suggestion-preview-ring-progress {
animation: playlist-preview-progress var(--preview-duration, 30s) linear forwards;
}
/* Settings opt-out: a single root attribute hides every inline preview button
without forcing every tracklist to plumb the flag through props. Master
wins — when off, every preview button is hidden regardless of per-location
state. */
html[data-track-previews="off"] .playlist-suggestion-preview-btn {
display: none;
}
/* Per-location hide: each tracklist container marks its scope via
`data-preview-loc="<id>"`. When the matching root attribute is "off", only
buttons inside that scope disappear. Attribute names are stored lowercase
by the DOM so `randomMix` becomes `randommix`. */
html[data-track-previews-suggestions="off"] [data-preview-loc="suggestions"] .playlist-suggestion-preview-btn,
html[data-track-previews-albums="off"] [data-preview-loc="albums"] .playlist-suggestion-preview-btn,
html[data-track-previews-playlists="off"] [data-preview-loc="playlists"] .playlist-suggestion-preview-btn,
html[data-track-previews-favorites="off"] [data-preview-loc="favorites"] .playlist-suggestion-preview-btn,
html[data-track-previews-artist="off"] [data-preview-loc="artist"] .playlist-suggestion-preview-btn,
html[data-track-previews-randommix="off"] [data-preview-loc="randomMix"] .playlist-suggestion-preview-btn {
display: none;
}
@keyframes playlist-preview-progress {
from { stroke-dashoffset: 65.97; }
to { stroke-dashoffset: 0; }
}
.track-row:hover,
.track-row.context-active {
background: var(--bg-hover);
}
.track-row.active {
background: var(--accent-dim);
animation: track-pulse 3s ease-in-out infinite;
}
.track-row.active:hover {
background: var(--accent-dim);
animation: none;
}
@keyframes track-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.track-row>* {
padding-top: 6px;
padding-bottom: 6px;
}
.track-num {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.track-num .bulk-check {
position: absolute;
left: 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.1s;
}
.track-num .bulk-check.bulk-check-visible {
opacity: 1;
pointer-events: auto;
}
.track-row:hover .track-num .bulk-check {
opacity: 1;
pointer-events: auto;
}
/* Default: show track number */
.track-num-number {
font-size: 13px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
text-align: center;
}
/* Play icon: hidden by default */
.track-num-play {
display: none;
align-items: center;
color: var(--text-primary);
}
/* Eq-bars wrapper */
.track-num-eq {
display: flex;
align-items: center;
justify-content: flex-end;
}
/* ── Hover (non-active row): hide number, show play icon ── */
.track-row:hover .track-num-number { display: none; }
.track-row:hover .track-num-play { display: flex; }
/* ── Active (currently playing): hide number, show eq-bars ── */
.track-num.track-num-active .track-num-number { display: none; }
.track-num.track-num-active .track-num-number { color: var(--accent); } /* kept for when visible */
/* ── Active + hover: hide eq-bars, show play icon ── */
.track-row:hover .track-num.track-num-active .track-num-eq { display: none; }
.track-row:hover .track-num.track-num-active .track-num-play { display: flex; }
/* Paused state: show play icon statically (no hover needed) */
.track-num.track-num-paused .track-num-play { display: flex; opacity: 0.5; }
.track-row:hover .track-num.track-num-paused .track-num-play { opacity: 1; }
/* ── Tracklists with inline action buttons (AlbumTrackList & co.) ──────────
Track number stays static instead of swapping to a play icon on hover.
The dedicated Play + Preview buttons in the title cell take over both
click-to-play and click-to-preview. Active+playing rows still show the
eq-bars, active+paused rows fall back to the static number.
These rules MUST come after the legacy `.track-row:hover` swap rules so
the cascade overrides them. */
.track-row-with-actions:hover .track-num-number { display: inline; }
.track-row-with-actions .track-num { cursor: default; }
.track-row-with-actions .track-num.track-num-active .track-num-number { display: inline; }
.track-row-with-actions:hover .track-num.track-num-active .track-num-eq { display: flex; }
/* Mirror the suggestion preview-button hover-highlight here so the icon
reaches full opacity / accent colour when the row is hovered. */
.track-row-with-actions:hover .playlist-suggestion-preview-btn {
opacity: 1;
color: var(--accent);
}
/* Equalizer bars — shown for the currently playing track */
.eq-bars {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 2px;
width: 14px;
height: 13px;
}
.eq-bar {
flex: 1;
height: 100%;
background: var(--accent);
border-radius: 1px;
transform-origin: bottom;
transform: scaleY(0.25);
animation: eq-bounce 1.1s ease-in-out infinite;
}
.eq-bar:nth-child(1) {
animation-delay: 0s;
animation-duration: 1.0s;
}
.eq-bar:nth-child(2) {
animation-delay: 0.18s;
animation-duration: 1.3s;
}
.eq-bar:nth-child(3) {
animation-delay: 0.35s;
animation-duration: 0.9s;
}
@keyframes eq-bounce {
0%,
100% {
transform: scaleY(0.25);
}
50% {
transform: scaleY(1);
}
}
/* CD / Disc separator */
.disc-header {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4) var(--space-3) var(--space-2);
font-family: var(--font-display);
font-size: 13px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--accent);
margin-top: var(--space-3);
border-top: 1px solid var(--border-subtle);
}
.disc-header:first-child {
border-top: none;
margin-top: 0;
}
.disc-icon {
font-size: 16px;
}
.track-info {
min-width: 0;
}
.track-title {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
overflow-wrap: break-word;
word-break: break-word;
}
.track-artist-cell {
min-width: 0;
display: flex;
align-items: flex-start;
}
.track-artist {
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.track-artist-link {
cursor: pointer;
}
.track-artist-link:hover {
color: var(--accent);
text-decoration: underline;
}
.track-artist-sep {
color: var(--text-muted);
user-select: none;
}
.track-codec {
display: block;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--text-muted);
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.track-size {
color: var(--text-muted);
}
.track-duration {
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.track-meta {
display: flex;
align-items: center;
}
.track-meta .track-codec {
margin-top: 0;
}
.track-genre {
font-size: 11px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.track-star-cell {
display: flex;
justify-content: center;
}
.track-star-btn {
padding: 4px;
height: auto;
min-height: unset;
color: var(--text-muted);
transition: color var(--transition-fast);
}
.track-star-btn:hover,
.track-star-btn.is-starred {
color: var(--color-star-active, var(--accent));
}
.album-detail-star-btn {
transition: color var(--transition-fast), border-color var(--transition-fast) !important;
}
.album-detail-star-btn:hover {
color: var(--color-star-active, var(--accent)) !important;
}
.album-detail-star-btn.is-starred {
color: var(--color-star-active, var(--accent)) !important;
border: 1px solid var(--color-star-active, var(--accent)) !important;
}
/* ── Random Mix — clickable artist name ── */
.rm-artist-btn {
background: none;
border: none;
padding: 0;
margin: 0;
font-size: 12px;
color: var(--text-secondary);
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
text-align: left;
text-decoration: underline;
text-decoration-style: dotted;
text-decoration-color: transparent;
transition: color 0.15s, text-decoration-color 0.15s;
}
.rm-artist-btn:hover {
color: var(--accent);
text-decoration-color: var(--accent);
}
.rm-artist-btn.is-blocked {
color: var(--danger);
text-decoration-color: var(--danger);
cursor: default;
}
.rm-artist-btn.just-added {
color: var(--accent);
text-decoration-color: var(--accent);
}
/* ── Random Mix — genre pill chip ── */
.rm-genre-chip {
display: inline-block;
background: var(--bg-hover);
border: none;
border-radius: var(--radius-sm);
padding: 2px 8px;
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
transition: background 0.15s, color 0.15s;
}
.rm-genre-chip:hover {
background: color-mix(in srgb, var(--accent) 20%, transparent);
color: var(--accent);
}
.rm-genre-chip.is-blocked {
background: color-mix(in srgb, var(--danger) 15%, transparent);
color: var(--danger);
cursor: default;
}
.rm-genre-chip.just-added {
background: color-mix(in srgb, var(--accent) 20%, transparent);
color: var(--accent);
}
/* ─ Modal ─ */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(17, 17, 27, 0.85);
backdrop-filter: blur(8px);
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 10vh;
z-index: 9999;
animation: fadeIn 150ms ease both;
}
.modal-content {
background: var(--ctp-surface0);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
padding: var(--space-8);
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
position: relative;
animation: fadeIn 200ms ease both;
box-shadow: var(--shadow-lg);
}
/* Modal CSV Import - siempre centrado */
.modal-overlay--csv {
position: fixed !important;
inset: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding-top: 0 !important;
}
.modal-close {
position: absolute;
top: var(--space-4);
right: var(--space-4);
color: var(--text-muted);
}
.modal-close:hover {
color: var(--text-primary);
}
.modal-title {
margin-bottom: 1rem;
font-family: var(--font-display);
}
/* ─ Playback delay (sleep / delayed start) modal ─ */
.playback-delay-modal {
max-height: min(85vh, 560px);
padding: var(--space-6);
position: relative;
overflow: hidden;
animation: playback-delay-modal-in 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes playback-delay-modal-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* Mood-tint: subtle gradient overlay behind content, differs per mode. */
.playback-delay-modal::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.6;
background: radial-gradient(
ellipse at top right,
color-mix(in srgb, var(--pd-accent, var(--accent)) 14%, transparent) 0%,
transparent 55%
);
z-index: 0;
}
/* Raise content above the ::before mood tint, but do NOT touch .modal-close
— it must keep its absolute top-right positioning. */
.playback-delay-modal > *:not(.modal-close) { position: relative; z-index: 1; }
/* Scoped polish for the close button in this modal: circular glass chip with
subtle hover lift + rotation. Does not touch the global .modal-close, so
other modals keep their existing look. */
.playback-delay-modal > .modal-close {
z-index: 2;
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
color: var(--text-muted);
transition:
background 180ms ease,
border-color 180ms ease,
color 180ms ease,
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.playback-delay-modal > .modal-close:hover {
background: color-mix(in srgb, var(--pd-accent, var(--accent)) 16%, transparent);
border-color: color-mix(in srgb, var(--pd-accent, var(--accent)) 40%, transparent);
color: var(--pd-accent, var(--accent));
transform: rotate(90deg);
}
.playback-delay-modal > .modal-close:focus-visible {
outline: 2px solid color-mix(in srgb, var(--pd-accent, var(--accent)) 60%, transparent);
outline-offset: 2px;
}
.playback-delay-modal > .modal-close:active {
transform: rotate(90deg) scale(0.94);
}
.playback-delay-modal--pause { --pd-accent: var(--ctp-lavender, #b4befe); }
.playback-delay-modal--start { --pd-accent: var(--ctp-peach, #fab387); }
.playback-delay-modal--idle { --pd-accent: var(--ctp-overlay1, #7f849c); }
.playback-delay-modal__head {
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 var(--space-4);
padding-right: 2rem;
}
.playback-delay-modal__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 999px;
background: color-mix(in srgb, var(--pd-accent, var(--accent)) 18%, transparent);
color: var(--pd-accent, var(--accent));
flex-shrink: 0;
}
.playback-delay-modal__title {
margin: 0;
font-family: var(--font-display);
font-size: 1.05rem;
font-weight: 600;
letter-spacing: -0.01em;
}
.playback-delay-modal-overlay--anchored {
padding: 0;
}
.playback-transport-play-wrap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
}
/* ── Preview-active state on the player bar ─────────────────────────
Combo of two cues so it's unmistakable that a preview is sounding:
1) accent top-border on the bar itself
2) tiny "Preview" pill above the title in the track-info cell */
.player-bar.is-previewing {
box-shadow: inset 0 2px 0 0 var(--accent), 0 -1px 0 0 var(--bg-border);
}
.player-preview-label {
display: inline-block;
align-self: flex-start;
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ctp-crust);
background: var(--accent);
padding: 1px 6px;
border-radius: 4px;
margin-bottom: 3px;
line-height: 1.3;
user-select: none;
}
/* ── Preview-active state on the play button ────────────────────────
While a track preview is sounding, the player-bar play button mirrors
the inline preview button used in tracklists: hollow circle + chevron
icon + countdown ring. Click still resumes main playback (which the
Rust audio engine cancels the preview for, see audio.rs). */
.player-btn-primary.is-previewing {
background: transparent;
color: var(--accent);
box-shadow: none;
}
.player-btn-primary.is-previewing:hover {
background: var(--bg-hover, transparent);
color: var(--accent);
box-shadow: none;
}
.player-btn-preview-ring {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
transform: rotate(-90deg);
z-index: 1;
}
.player-btn-preview-ring-track {
fill: none;
stroke: var(--ctp-overlay0);
stroke-width: 4;
}
.player-btn-preview-ring-progress {
fill: none;
stroke: var(--accent);
stroke-width: 4;
stroke-linecap: round;
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: player-preview-progress var(--preview-duration, 30s) linear forwards;
}
@keyframes player-preview-progress {
from { stroke-dashoffset: 100; }
to { stroke-dashoffset: 0; }
}
/* ── Custom-minutes input with inline "min" suffix ──────────────────── */
.playback-delay-custom__field {
position: relative;
flex: 1 1 100px;
min-width: 0;
display: flex;
}
.playback-delay-custom__field .playback-delay-custom__input {
flex: 1 1 auto;
padding-right: 2.4rem;
}
.playback-delay-custom__suffix {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 0.78rem;
color: var(--text-muted);
pointer-events: none;
opacity: 0.7;
letter-spacing: 0.02em;
}
/* ── Live preview: "Pauses at 23:47" ────────────────────────────────── */
.playback-delay-preview {
display: flex;
align-items: baseline;
gap: 6px;
margin-top: var(--space-3);
min-height: 1.35rem;
line-height: 1.35rem;
font-size: 0.82rem;
color: var(--text-muted);
transition: opacity 150ms ease;
white-space: nowrap;
}
.playback-delay-preview[data-empty="true"] { opacity: 0; }
.playback-delay-preview[data-empty="false"] { opacity: 1; }
.playback-delay-preview__label { letter-spacing: 0.01em; }
.playback-delay-preview__time {
font-weight: 600;
font-variant-numeric: tabular-nums;
color: var(--pd-accent, var(--accent));
letter-spacing: -0.01em;
}
/* ── Circular progress ring around play/pause ────────────────────────── */
.playback-schedule-ring {
display: block;
}
.playback-schedule-ring__svg {
display: block;
overflow: visible;
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 42%, transparent));
}
.playback-schedule-ring__track {
stroke: color-mix(in srgb, var(--text-primary) 10%, transparent);
}
.playback-schedule-ring__fill {
transition: stroke-dashoffset 480ms linear;
}
/* Unified accent gradient for both modes — the Moon / Sunrise icon in the
button carries the mode distinction, so the ring stays consistent with
the rest of the app. */
.playback-schedule-ring__grad-a { stop-color: var(--accent); }
.playback-schedule-ring__grad-b { stop-color: var(--ctp-lavender, #b4befe); }
/* Replaces the Play/Pause icon while a schedule timer is armed. Two-line
stack: mode icon (Moon for sleep, Sunrise for delayed-start) above the
countdown text. The mode icon + ring colour jointly signal which timer
is running. Three size variants mirror the three player views. */
.player-btn-schedule-stack {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
line-height: 1;
}
.player-btn-schedule-time {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.82rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
line-height: 1;
min-width: 2.2em;
text-align: center;
color: currentColor;
}
.player-btn-schedule-stack--fs { gap: 3px; }
.player-btn-schedule-time--fs { font-size: 0.92rem; }
.player-btn-schedule-stack--mobile { gap: 3px; }
.player-btn-schedule-time--mobile { font-size: 1rem; }
.playback-delay-section__head--tight {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
margin-bottom: var(--space-2);
min-height: 0;
}
.playback-delay-inline-cancel {
flex-shrink: 0;
padding: 4px 10px;
font-size: 0.8rem;
}
.playback-delay-chips--compact {
margin-bottom: var(--space-4);
gap: 5px;
}
.playback-delay-custom--inline {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
margin-top: 0;
}
.playback-delay-custom--inline .playback-delay-custom__input {
flex: 1 1 100px;
min-width: 0;
}
.playback-delay-idle {
margin-bottom: var(--space-2);
}
.playback-delay-idle .playback-delay-muted:first-child {
margin-top: 0;
}
.playback-delay-section {
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-4);
background: color-mix(in srgb, var(--ctp-base) 40%, transparent);
}
.playback-delay-section--disabled {
opacity: 0.55;
pointer-events: none;
}
.playback-delay-section__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-3);
margin-bottom: var(--space-3);
}
.playback-delay-section__title {
font-weight: 600;
font-size: 0.95rem;
}
.playback-delay-section__countdown {
font-size: 0.8rem;
font-variant-numeric: tabular-nums;
color: var(--accent);
white-space: nowrap;
}
.playback-delay-muted {
margin: 0;
font-size: 0.85rem;
color: var(--text-muted);
line-height: 1.4;
}
.playback-delay-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: var(--space-3);
}
.playback-delay-chip {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 7px 12px;
font-size: 0.82rem;
font-weight: 500;
background: var(--ctp-surface1);
color: var(--text-primary);
cursor: pointer;
transition:
background 150ms ease,
border-color 150ms ease,
color 150ms ease,
transform 120ms ease,
box-shadow 150ms ease;
}
.playback-delay-chip:hover {
border-color: color-mix(in srgb, var(--pd-accent, var(--accent)) 55%, var(--border));
background: color-mix(in srgb, var(--pd-accent, var(--accent)) 14%, var(--ctp-surface1));
color: var(--pd-accent, var(--accent));
transform: translateY(-1px);
box-shadow: 0 3px 10px color-mix(in srgb, var(--pd-accent, var(--accent)) 22%, transparent);
}
.playback-delay-chip:active { transform: translateY(0); }
.playback-delay-chip--on {
border-color: var(--pd-accent, var(--accent));
background: color-mix(in srgb, var(--pd-accent, var(--accent)) 22%, var(--ctp-surface1));
color: var(--pd-accent, var(--accent));
}
.playback-delay-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
}
.playback-delay-custom__label {
display: block;
font-size: 0.85rem;
font-weight: 500;
margin-bottom: 6px;
color: var(--text-muted);
}
.playback-delay-custom__row {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-items: center;
}
.playback-delay-custom__input {
flex: 1 1 120px;
min-width: 0;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px 10px;
font-size: 0.9rem;
background: var(--ctp-base);
color: var(--text-primary);
}
.playback-delay-custom__input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}
/* ─ Lucky Mix pips (shared by the inline queue-lucky-cube indicator) ─ */
.lucky-mix-pip {
position: absolute;
width: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.lucky-mix-pip--tl { top: 16px; left: 16px; }
.lucky-mix-pip--tr { top: 16px; right: 16px; }
.lucky-mix-pip--bl { bottom: 16px; left: 16px; }
.lucky-mix-pip--br { bottom: 16px; right: 16px; }
.lucky-mix-pip--center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.queue-lucky-loading {
margin: 2px 10px 4px;
display: flex;
justify-content: center;
padding: 7px 10px;
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
cursor: pointer;
width: calc(100% - 20px);
transition: background 140ms ease, border-color 140ms ease;
}
.queue-lucky-loading:hover {
background: color-mix(in srgb, var(--danger) 8%, transparent);
border-color: color-mix(in srgb, var(--danger) 28%, transparent);
}
.queue-lucky-loading:hover .queue-lucky-cube {
opacity: 0.55;
}
.queue-lucky-loading__dice {
position: relative;
width: 56px;
height: 30px;
flex: 0 0 auto;
}
.queue-lucky-cube {
position: absolute;
width: 18px;
height: 18px;
border-radius: 5px;
border: 1px solid color-mix(in srgb, #fff 65%, var(--accent) 35%);
background: linear-gradient(
160deg,
color-mix(in srgb, var(--accent) 66%, #fff 34%) 0%,
color-mix(in srgb, var(--accent) 84%, #000 16%) 100%
);
box-shadow:
0 4px 9px rgba(0, 0, 0, 0.28),
inset -2px -3px 5px rgba(0, 0, 0, 0.18),
inset 2px 2px 4px rgba(255, 255, 255, 0.18);
}
.queue-lucky-cube .lucky-mix-pip {
width: 3px;
height: 3px;
}
.queue-lucky-cube .lucky-mix-pip--tl { top: 4px; left: 4px; }
.queue-lucky-cube .lucky-mix-pip--tr { top: 4px; right: 4px; }
.queue-lucky-cube .lucky-mix-pip--bl { bottom: 4px; left: 4px; }
.queue-lucky-cube .lucky-mix-pip--br { bottom: 4px; right: 4px; }
.queue-lucky-cube--a {
left: 0;
top: 11px;
animation: queueLuckyCubeA 760ms ease-in-out infinite;
}
.queue-lucky-cube--b {
left: 18px;
top: 3px;
animation: queueLuckyCubeB 690ms ease-in-out infinite;
}
.queue-lucky-cube--c {
left: 36px;
top: 11px;
animation: queueLuckyCubeC 830ms ease-in-out infinite;
}
@keyframes queueLuckyCubeA {
0% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-6px) rotate(-10deg); }
100% { transform: translateY(0) rotate(0deg); }
}
@keyframes queueLuckyCubeB {
0% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-8px) rotate(10deg); }
100% { transform: translateY(0) rotate(0deg); }
}
@keyframes queueLuckyCubeC {
0% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-5px) rotate(12deg); }
100% { transform: translateY(0) rotate(0deg); }
}
/* ─ Playlist edit modal ─ */
.playlist-edit-modal {
max-width: 560px;
}
.playlist-edit-body {
display: flex;
gap: 20px;
align-items: flex-start;
margin-bottom: 20px;
}
/* Clickable cover wrap in the hero (opens modal) */
.playlist-hero-cover {
position: relative;
display: inline-block;
cursor: pointer;
border-radius: var(--radius-md);
overflow: hidden;
}
.playlist-hero-cover-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
opacity: 0;
transition: opacity 150ms ease;
border-radius: inherit;
}
.playlist-hero-cover:hover .playlist-hero-cover-overlay {
opacity: 1;
}
/* Cover area inside the modal */
.playlist-edit-cover-wrap {
position: relative;
width: 180px;
height: 180px;
flex-shrink: 0;
border-radius: var(--radius-md);
overflow: hidden;
cursor: pointer;
}
.playlist-edit-cover-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 150ms ease;
}
.playlist-edit-cover-wrap:hover .playlist-edit-cover-overlay {
opacity: 1;
}
.playlist-edit-cover-menu {
display: flex;
flex-direction: column;
gap: 6px;
text-align: center;
}
.playlist-edit-cover-menu-item {
background: none;
border: none;
color: #fff;
font-size: 13px;
font-weight: 600;
cursor: pointer;
padding: 6px 12px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
transition: background 120ms ease;
}
.playlist-edit-cover-menu-item:hover {
background: rgba(255, 255, 255, 0.15);
}
.playlist-edit-cover-menu-item--danger {
color: var(--ctp-red, #f38ba8);
}
/* Right side fields */
.playlist-edit-fields {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.playlist-edit-name-input {
font-size: 18px;
font-weight: 700;
}
.playlist-edit-desc-input {
resize: none;
min-height: 80px;
font-size: 13px;
}
/* Footer */
.playlist-edit-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 16px;
border-top: 1px solid var(--border);
gap: 12px;
}
.artist-bio {
font-size: 14px;
line-height: 1.7;
color: var(--text-secondary);
user-select: text;
}
.artist-bio a {
color: var(--accent);
text-decoration: underline;
}
/* Artist Detail Page */
.artist-detail-header {
display: flex;
align-items: flex-start;
gap: 2rem;
margin-bottom: 2rem;
}
.artist-detail-avatar {
width: 160px;
height: 160px;
border-radius: 50%;
overflow: hidden;
background: var(--bg-card);
flex-shrink: 0;
box-shadow: var(--shadow-md);
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-subtle);
}
.artist-detail-avatar-btn {
display: block;
width: 100%;
height: 100%;
padding: 0;
border: none;
background: none;
cursor: zoom-in;
transition: transform 0.2s ease, filter 0.2s ease;
}
.artist-detail-avatar-btn:hover {
transform: scale(1.04);
filter: brightness(1.08);
}
/* Camera overlay for uploading a new artist image */
.artist-avatar-upload-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 44px;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
cursor: pointer;
opacity: 0;
transition: opacity 150ms ease;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
z-index: 2;
}
.artist-detail-avatar:hover .artist-avatar-upload-overlay {
opacity: 1;
}
.artist-avatar-upload-overlay:hover {
background: rgba(0, 0, 0, 0.7);
}
.artist-detail-meta {
flex: 1;
min-width: 0;
padding-top: 0.5rem;
}
.artist-detail-links {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.artist-ext-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: var(--radius-full);
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-secondary);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
}
.artist-ext-link:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--border-subtle);
}
/* Bio section */
/* Artist Detail bio — reuses np-info-card + np-artist-bio-row */
.artist-bio-card {
margin-bottom: 2rem;
}
/* ─ Artists Page ─ */
.letter-heading {
font-size: 13px;
font-weight: 700;
color: var(--text-muted);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: var(--space-2);
padding: var(--space-2) 0;
border-bottom: 1px solid var(--border-subtle);
}
.artist-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.artist-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
border-radius: var(--radius-md);
transition: background var(--transition-fast);
width: 100%;
text-align: left;
}
.artist-row:hover {
background: var(--bg-hover);
}
.artist-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--accent-dim);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
flex-shrink: 0;
}
.artist-avatar-initial {
background: var(--bg-card);
border: 2px solid;
}
.artist-avatar-initial span {
font-size: 1rem;
font-weight: 700;
font-family: var(--font-display);
line-height: 1;
user-select: none;
}
.artist-name {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
.artist-meta {
font-size: 12px;
color: var(--text-muted);
margin-top: 2px;
}
/* ─ Years Page ─ */
.decade-heading {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--space-3);
}
.year-section {
margin-bottom: var(--space-3);
}
.year-toggle {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
border-radius: var(--radius-md);
width: 100%;
text-align: left;
color: var(--text-secondary);
font-size: 15px;
font-weight: 500;
transition: background var(--transition-fast);
}
.year-toggle:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.year-label {
font-variant-numeric: tabular-nums;
}
/* ─ Settings ─ */
.settings-tabs {
display: flex;
gap: 4px;
padding: 4px;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
margin-bottom: 2rem;
flex-wrap: wrap;
}
.settings-tab {
display: flex;
align-items: center;
gap: 7px;
padding: 7px 16px;
border-radius: calc(var(--radius-lg) - 4px);
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
background: none;
border: none;
cursor: pointer;
transition: color var(--transition-fast), background var(--transition-fast);
white-space: nowrap;
}
.settings-tab:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.settings-tab.active {
color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.settings-section {
margin-bottom: var(--space-5);
}
.settings-section-header {
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--accent);
margin-bottom: var(--space-2);
}
.settings-section-header h2 {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
}
/* ── Settings Sub-Section (Accordion) ──────────────────────────────────────
Gruppiert verwandte Einstellungen innerhalb eines Tabs. Nutzt <details>
fuer gratis Keyboard + ARIA. */
.settings-sub-section {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
margin-bottom: var(--space-3);
overflow: hidden;
}
@keyframes settings-sub-section-flash {
0% { border-color: var(--border-subtle); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
20% { border-color: var(--accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 35%, transparent); }
100% { border-color: var(--border-subtle); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
.settings-sub-section--flash {
animation: settings-sub-section-flash 1.4s ease-out;
}
.settings-sub-section-summary {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3);
cursor: pointer;
list-style: none;
user-select: none;
transition: background 120ms ease;
}
.settings-sub-section-summary::-webkit-details-marker {
display: none;
}
.settings-sub-section-summary:hover {
background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.settings-sub-section-icon {
display: inline-flex;
color: var(--accent);
flex-shrink: 0;
}
.settings-sub-section-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
flex: 1;
min-width: 0;
}
.settings-sub-section-action {
display: inline-flex;
align-items: center;
flex-shrink: 0;
}
.settings-sub-section-chevron {
color: var(--text-muted);
flex-shrink: 0;
transition: transform 180ms ease;
}
.settings-sub-section[open] > .settings-sub-section-summary .settings-sub-section-chevron {
transform: rotate(180deg);
}
.settings-sub-section-desc {
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
margin: 0;
padding: 0 var(--space-3) var(--space-2);
}
.settings-sub-section-content {
padding: var(--space-2) var(--space-3) var(--space-3);
}
/* Wenn das Accordion eine <section class="settings-section"> wrappt (Legacy-
Content, der noch seine eigenen Header traegt), blenden wir den internen
Section-Header aus — der Accordion-Summary uebernimmt dessen Rolle. */
.settings-sub-section-content > .settings-section {
margin-bottom: 0;
}
.settings-sub-section-content > .settings-section > .settings-section-header {
display: none;
}
/* ── In-Page-Suche fuer Settings ─────────────────────────────────────────── */
.settings-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
margin-bottom: 1.5rem;
}
.settings-header .page-title {
margin-bottom: 0;
}
.settings-search {
display: flex;
align-items: center;
flex-shrink: 0;
}
.settings-search-wrap {
position: relative;
display: flex;
align-items: center;
width: 240px;
}
.settings-search-icon {
position: absolute;
left: 10px;
color: var(--text-muted);
pointer-events: none;
}
.settings-search-input {
width: 100%;
padding: 6px 32px;
font-size: 13px;
}
.settings-search-clear {
position: absolute;
right: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 50%;
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.settings-search-clear:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.settings-search-empty {
padding: var(--space-3) var(--space-4);
font-size: 13px;
color: var(--text-muted);
border-radius: var(--radius-md);
background: var(--bg-card);
border: 1px dashed var(--border-subtle);
text-align: center;
}
.settings-search-results {
list-style: none;
margin: var(--space-4) 0 var(--space-6);
padding: var(--space-3);
border-radius: var(--radius-md);
background: var(--bg-card);
border: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
gap: 2px;
}
.settings-search-result-item {
width: 100%;
display: flex;
align-items: center;
gap: var(--space-2);
padding: 8px 10px;
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
color: var(--text-primary);
text-align: left;
cursor: pointer;
font-size: 13px;
transition: background 0.12s, border-color 0.12s;
}
.settings-search-result-item:hover,
.settings-search-result-item[data-selected="true"] {
background: var(--bg-hover);
border-color: var(--border);
}
.settings-search-result-item[data-selected="true"] {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 8%, var(--bg-hover));
}
.settings-search-result-badge {
flex-shrink: 0;
font-size: 11px;
font-weight: 500;
padding: 2px 8px;
border-radius: 999px;
background: color-mix(in srgb, var(--accent) 15%, transparent);
color: var(--accent);
}
.settings-search-result-title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ── Mitwirkende: Card-Grid mit klappbaren Beitragslisten ────────────────── */
.contributors-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: var(--space-2);
}
.contributor-card {
background: var(--bg-elevated);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
overflow: hidden;
transition: border-color 120ms ease, background 120ms ease;
}
.contributor-card[open] {
grid-column: 1 / -1;
background: color-mix(in srgb, var(--accent) 4%, var(--bg-elevated));
border-color: color-mix(in srgb, var(--accent) 35%, var(--border-subtle));
}
.contributor-card-summary {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
cursor: pointer;
list-style: none;
user-select: none;
}
.contributor-card-summary::-webkit-details-marker {
display: none;
}
.contributor-card-summary:hover {
background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.contributor-card-avatar {
border-radius: 50%;
flex-shrink: 0;
}
.contributor-card-meta {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.contributor-card-name {
color: var(--accent);
font-weight: 600;
font-size: 13px;
cursor: pointer;
width: fit-content;
}
.contributor-card-name:hover {
text-decoration: underline;
}
.contributor-card-sub {
display: flex;
gap: 6px;
align-items: center;
font-size: 11px;
color: var(--text-muted);
}
.contributor-card-since {
background: var(--accent-dim);
color: var(--accent);
padding: 1px 6px;
border-radius: 99px;
font-weight: 600;
}
.contributor-card-chevron {
color: var(--text-muted);
flex-shrink: 0;
transition: transform 180ms ease;
}
.contributor-card[open] .contributor-card-chevron {
transform: rotate(180deg);
}
.contributor-card-list {
margin: 0;
padding: 0 var(--space-3) var(--space-3) calc(var(--space-3) + 40px);
font-size: 12px;
color: var(--text-secondary);
line-height: 1.7;
list-style: none;
}
.contributor-card-list li {
position: relative;
padding-left: 14px;
}
.contributor-card-list li::before {
content: '•';
position: absolute;
left: 0;
top: 0;
color: var(--accent);
font-weight: 700;
line-height: 1.7;
}
/* ── Datenschutz-Hinweis-Banner (z.B. oben im Integrations-Tab) ──────────── */
.settings-privacy-notice {
display: flex;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
margin-bottom: var(--space-4);
border-radius: var(--radius-lg);
background: color-mix(in srgb, var(--color-warning, #f59e0b) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--color-warning, #f59e0b) 40%, transparent);
color: var(--text-primary);
}
.settings-privacy-notice-icon {
color: var(--color-warning, #f59e0b);
flex-shrink: 0;
margin-top: 2px;
}
.settings-privacy-notice-title {
font-weight: 600;
font-size: 13px;
margin-bottom: var(--space-1);
}
.settings-privacy-notice-body {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.55;
}
.settings-privacy-notice-body strong {
color: var(--text-primary);
font-weight: 600;
}
.settings-card {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-3);
}
.settings-card.user-row {
transition: background 120ms ease, border-color 120ms ease;
}
.settings-card.user-row:hover {
background: color-mix(in srgb, var(--accent) 10%, var(--bg-card));
border-color: color-mix(in srgb, var(--accent) 45%, var(--border-subtle));
}
.settings-card.user-row:focus-visible {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent);
}
.settings-hint {
font-size: 0.8rem;
padding: 0.5rem 0.75rem;
border-radius: var(--radius-md);
line-height: 1.45;
}
.settings-hint-info {
background: color-mix(in srgb, var(--accent) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
color: var(--text-secondary);
}
/* ─ Help Page ─ */
.help-list {
display: flex;
flex-direction: column;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
overflow: hidden;
}
/* ── Theme Accordion ─────────────────────────────────────────────────────── */
.theme-accordion {
display: flex;
flex-direction: column;
}
.theme-accordion-item {
border-bottom: 1px solid var(--border-subtle);
}
.theme-accordion-item:last-child {
border-bottom: none;
}
.theme-accordion-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
width: 100%;
padding: 10px var(--space-4);
text-align: left;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
background: var(--bg-card);
border-left: 3px solid transparent;
transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.theme-accordion-header:hover {
background: var(--bg-hover);
color: var(--text-secondary);
}
.theme-accordion-open .theme-accordion-header {
color: var(--accent);
background: var(--bg-hover);
border-left: 3px solid var(--accent);
padding-left: calc(var(--space-5) + 3px);
}
.theme-accordion-chevron {
flex-shrink: 0;
color: var(--text-muted);
transition: transform 0.2s ease;
}
.theme-accordion-open .theme-accordion-chevron {
transform: rotate(180deg);
color: var(--accent);
}
.theme-accordion-content {
padding: 14px var(--space-4) var(--space-4) calc(var(--space-4) + 3px);
background: var(--bg-app);
border-top: 1px solid var(--border-subtle);
border-left: 3px solid var(--accent);
}
.theme-accordion-active-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
margin-left: 8px;
vertical-align: middle;
opacity: 0.8;
}
/* ── Theme Family Header (subgroup label inside an accordion) ───────────── */
.theme-family-header {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
padding: 4px 0 6px;
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 2px;
}
/* ── Theme Card (individual theme preview button) ───────────────────────── */
.theme-card-btn {
background: none;
border: none;
padding: 0;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.theme-card-preview {
width: 100%;
height: 46px;
border-radius: 8px;
overflow: hidden;
outline: 2px solid transparent;
outline-offset: 2px;
position: relative;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
transition: outline-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
/* Hover: dim accent ring + subtle lift */
.theme-card-btn:hover .theme-card-preview {
outline-color: var(--accent-glow);
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
transform: scale(1.04);
}
/* Active: full accent ring + glow */
.theme-card-preview.is-active {
outline-color: var(--accent);
box-shadow: 0 0 8px var(--accent-glow, rgba(0,0,0,0.2));
}
/* Active + hover: keep strong ring, keep lift */
.theme-card-btn:hover .theme-card-preview.is-active {
outline-color: var(--accent);
box-shadow: 0 0 12px var(--accent-glow, rgba(0,0,0,0.3));
transform: scale(1.04);
}
.theme-card-label {
font-size: 11px;
color: var(--text-secondary);
font-weight: 400;
text-align: center;
line-height: 1.2;
word-break: break-word;
transition: color var(--transition-fast), font-weight 0s;
}
.theme-card-btn:hover .theme-card-label {
color: var(--text-primary);
}
.theme-card-label.is-active {
color: var(--text-primary);
font-weight: 600;
}
/* ── Queue Tab Bar ────────────────────────────────────────────────────────── */
.queue-tab-bar {
display: flex;
border-top: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.queue-tab-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 8px;
background: none;
border: none;
cursor: pointer;
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
transition: color 0.15s, background 0.15s;
}
.queue-tab-btn:hover { color: var(--text-primary); background: var(--bg-hover); }
.queue-tab-btn.active { color: var(--accent); }
/* ── Lyrics Pane (sidebar) ────────────────────────────────────────────────── */
.lyrics-pane {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
/* scroll-behavior intentionally left to auto — JS drives smooth scroll imperatively */
padding: 16px 16px 8px;
scrollbar-width: thin;
}
.lyrics-pane-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.lyrics-status {
text-align: center;
color: var(--text-muted);
font-size: 13px;
padding: 40px 0;
}
.lyrics-synced {
display: flex;
flex-direction: column;
gap: 2px;
padding: 24px 0 40px; /* top pad lets first line start below centre, bottom lets last line scroll up */
}
.lyrics-line {
font-size: 15px;
font-weight: 500;
color: var(--text-muted);
text-align: center;
padding: 5px 8px;
border-radius: 6px;
line-height: 1.55;
cursor: default;
word-break: break-word;
overflow-wrap: break-word;
min-width: 0;
transform-origin: center center;
/* All transitions are compositor-friendly */
transition:
color 350ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
transform 350ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
opacity 350ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
text-shadow 350ms ease;
transform: scale(0.92);
opacity: 0.75;
}
.lyrics-line.completed {
color: var(--text-muted);
transform: scale(0.88);
opacity: 0.55;
}
.lyrics-line.active {
color: var(--text-primary);
font-weight: 700;
transform: scale(1);
opacity: 1;
text-shadow: 0 0 20px color-mix(in srgb, var(--accent) 45%, transparent);
}
.lyrics-line:hover:not(.active) {
color: var(--text-secondary);
opacity: 0.9;
}
/* Word-sync: neutralise line colour so only individual words pop */
.lyrics-word-synced .lyrics-line {
color: var(--text-muted);
}
.lyrics-word-synced .lyrics-line.active {
color: var(--text-muted);
text-shadow: none;
}
.lyrics-word-synced .lyrics-line.completed {
color: var(--text-muted);
}
.lyrics-word {
display: inline;
transition: color 160ms ease, text-shadow 160ms ease;
color: inherit;
white-space: pre-wrap;
}
.lyrics-word.played {
color: var(--text-secondary);
}
.lyrics-word.active {
color: var(--text-primary);
text-shadow: 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent);
}
.lyrics-plain {
padding: 4px 0 16px;
}
.lyrics-plain-line {
font-size: 13px;
line-height: 1.85;
color: var(--text-secondary);
text-align: center;
margin: 0;
}
.lyrics-source {
font-size: 11px;
color: var(--text-muted);
text-align: center;
margin: 8px 0 4px;
opacity: 0.6;
}
/* ── Help ─────────────────────────────────────────────────────────────────── */
.help-item {
border-bottom: 1px solid var(--border-subtle);
}
.help-item:last-child {
border-bottom: none;
}
.help-question {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
width: 100%;
padding: 10px var(--space-4);
text-align: left;
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
background: var(--bg-card);
border-left: 3px solid transparent;
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.help-question:hover {
background: var(--bg-hover);
}
.help-item-open .help-question {
color: var(--accent);
background: var(--bg-hover);
border-left: 3px solid var(--accent);
padding-left: calc(var(--space-5) + 3px);
}
.help-chevron {
flex-shrink: 0;
color: var(--text-muted);
transition: transform 0.2s ease;
}
.help-item-open .help-chevron {
transform: rotate(180deg);
color: var(--accent);
}
.help-answer {
padding: 10px var(--space-4) var(--space-4) calc(var(--space-4) + 3px);
font-size: 13px;
color: var(--text-secondary);
line-height: 1.65;
background: var(--bg-app);
border-top: 1px solid var(--border-subtle);
border-left: 3px solid var(--accent);
}
.settings-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
padding: 2px var(--space-3);
margin: 0 calc(var(--space-3) * -1);
border-radius: var(--radius-sm);
transition: background 120ms ease;
}
.settings-toggle-row:hover {
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* ─ Normalization block (per-mode sub-section) ─ */
.settings-segmented {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.settings-segmented > .btn {
flex: 1 1 0;
min-width: 0;
font-size: 13px;
padding: 8px 16px;
}
.settings-segmented > .btn-ghost {
border: 1px solid var(--border-subtle);
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
}
.settings-segmented > .btn-ghost:hover {
border-color: color-mix(in srgb, var(--accent) 40%, var(--border-subtle));
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.settings-norm-block .btn-ghost {
border: 1px solid var(--border-subtle);
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
}
.settings-norm-block .btn-ghost:hover {
border-color: color-mix(in srgb, var(--accent) 40%, var(--border-subtle));
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.settings-norm-block {
display: flex;
flex-direction: column;
gap: 1.1rem;
padding: 1.2rem 1.25rem;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.settings-norm-field {
display: flex;
flex-direction: column;
gap: 0.45rem;
}
.settings-norm-row {
display: flex;
align-items: center;
gap: 0.9rem;
flex-wrap: wrap;
}
.settings-norm-row > input[type="range"] {
flex: 1;
min-width: 120px;
}
.settings-norm-label {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
min-width: 160px;
}
.settings-norm-value {
font-size: 12px;
color: var(--text-muted);
min-width: 56px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.settings-norm-help {
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
max-width: 720px;
}
.settings-norm-note {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
padding: 0.75rem 0.9rem;
border-left: 2px solid var(--accent);
background: color-mix(in srgb, var(--accent) 7%, transparent);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
margin-top: 0.3rem;
}
@media (max-width: 640px) {
.settings-norm-label {
min-width: 0;
flex-basis: 100%;
}
}
.settings-section-divider {
border: none;
border-top: 1px solid var(--border);
margin: var(--space-2) 0;
}
/* ─ Sidebar Customizer ─ */
.sidebar-customizer-row {
display: flex;
align-items: center;
gap: var(--space-3);
padding: 8px var(--space-4);
border-radius: var(--radius-sm);
transition: background var(--transition-fast), opacity var(--transition-fast);
cursor: default;
}
.sidebar-customizer-row:hover {
background: var(--bg-hover);
}
.sidebar-customizer-row.drag-over {
background: var(--accent-dim);
outline: 1px solid var(--accent);
outline-offset: -1px;
}
.sidebar-customizer-row.dragging {
opacity: 0.4;
}
.sidebar-customizer-grip {
cursor: grab;
color: var(--text-muted);
display: flex;
align-items: center;
flex-shrink: 0;
-webkit-user-drag: none;
}
.sidebar-customizer-grip:active {
cursor: grabbing;
}
.sidebar-customizer-block-label {
padding: 6px var(--space-4) 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.sidebar-customizer-fixed-hint {
padding: 8px var(--space-4) 6px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 4px;
}
.settings-about {
display: flex;
flex-direction: column;
}
.settings-about-header {
display: flex;
align-items: center;
gap: var(--space-4);
}
.about-psysonic-logo-lol-hit {
flex-shrink: 0;
margin: 0;
padding: 0;
border: none;
background: none;
cursor: pointer;
border-radius: 14px;
line-height: 0;
}
.about-psysonic-logo-lol-hit:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.about-psysonic-lol-close {
position: fixed;
top: max(12px, env(safe-area-inset-top, 0px));
right: max(12px, env(safe-area-inset-right, 0px));
z-index: 100001;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
margin: 0;
padding: 0;
border: 2px solid #e2e8f0;
border-radius: var(--radius-full);
background: #1e293b;
color: #f8fafc;
cursor: pointer;
box-shadow:
0 0 0 1px #0f172a,
0 6px 24px rgba(0, 0, 0, 0.5);
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.about-psysonic-lol-close:hover {
background: #334155;
border-color: #f8fafc;
color: #fff;
}
.about-psysonic-lol-close:focus-visible {
outline: 3px solid #38bdf8;
outline-offset: 2px;
}
.about-psysonic-lol-overlay {
position: fixed;
inset: 0;
z-index: 100000;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
box-sizing: border-box;
isolation: isolate;
background: rgba(0, 0, 0, 0.82);
}
/* Opaque panel + fixed palette so contrast holds on every app theme. */
.about-psysonic-lol-panel {
width: min(26rem, calc(100vw - 2.5rem));
max-width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
text-align: center;
padding: var(--space-5) var(--space-6) var(--space-6);
border-radius: 18px;
border: 2px solid #334155;
background: #0f172a;
color: #f8fafc;
box-shadow:
0 24px 80px rgba(0, 0, 0, 0.55),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
--logo-color-start: #a78bfa;
--logo-color-end: #38bdf8;
}
.about-psysonic-lol-logo-slot {
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-1) 0 var(--space-4);
margin: 0;
border: none;
background: transparent;
box-shadow: none;
}
.about-psysonic-lol-logo-mark {
flex-shrink: 0;
filter: drop-shadow(0 2px 16px rgba(0, 0, 0, 0.55));
}
.about-psysonic-lol-copy {
display: flex;
flex-direction: column;
gap: var(--space-3);
color: #f8fafc;
}
.about-psysonic-lol-title {
margin: 0;
font-family: var(--font-display);
font-size: clamp(1.5rem, 4.5vw, 1.9rem);
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.02em;
color: #ffffff;
}
.about-psysonic-lol-lede {
margin: 0;
font-size: clamp(0.9rem, 2.4vw, 1rem);
line-height: 1.55;
color: #e2e8f0;
max-width: 24em;
margin-inline: auto;
text-wrap: balance;
}
.about-psysonic-lol-inline-link {
display: inline;
margin: 0;
padding: 0;
border: none;
background: none;
cursor: pointer;
font: inherit;
font-weight: 700;
color: #38bdf8;
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 3px;
}
.about-psysonic-lol-inline-link:hover {
color: #7dd3fc;
}
.about-psysonic-lol-signoff {
margin: 0;
font-size: 0.875rem;
line-height: 1.45;
color: #cbd5e1;
font-style: italic;
}
.about-psysonic-lol-ps {
margin: 0;
font-size: 0.8125rem;
line-height: 1.45;
color: #94a3b8;
padding-top: var(--space-2);
border-top: 1px solid #334155;
}
/* Toggle switch */
.toggle-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
flex-shrink: 0;
cursor: pointer;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-track {
position: absolute;
inset: 0;
background: var(--ctp-surface2);
border-radius: var(--radius-full);
transition: background var(--transition-base);
}
.toggle-track::before {
content: '';
position: absolute;
left: 3px;
top: 3px;
width: 18px;
height: 18px;
border-radius: 50%;
background: white;
transition: transform var(--transition-base);
box-shadow: var(--shadow-sm);
}
.toggle-switch:hover .toggle-track {
background: var(--ctp-overlay0);
}
.toggle-switch input:checked+.toggle-track {
background: var(--accent);
}
.toggle-switch:hover input:checked+.toggle-track {
filter: brightness(1.15);
}
.toggle-switch input:checked+.toggle-track::before {
transform: translateX(20px);
}
/* ─ Login Page ─ */
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
background: var(--bg-app);
}
.login-bg {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at 20% 50%, rgba(203, 166, 247, 0.08) 0%, transparent 60%),
radial-gradient(ellipse at 80% 20%, rgba(137, 180, 250, 0.06) 0%, transparent 50%);
pointer-events: none;
}
.login-card {
width: 100%;
max-width: 520px;
background: var(--ctp-mantle);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-xl);
padding: var(--space-8);
box-shadow: var(--shadow-lg);
position: relative;
z-index: 1;
}
.login-logo {
display: flex;
justify-content: center;
margin-bottom: var(--space-5);
}
.login-lang-picker {
position: absolute;
top: var(--space-3);
right: var(--space-3);
display: flex;
align-items: center;
gap: var(--space-1);
color: var(--text-muted);
z-index: 2;
}
.login-lang-picker > button {
font-size: 12px;
padding: 4px 8px;
background: transparent;
border: 1px solid transparent;
color: var(--text-muted);
width: auto;
min-width: 110px;
}
.login-lang-picker > button:hover {
background: var(--bg-hover);
border-color: var(--border-subtle);
color: var(--text-primary);
}
.login-title {
text-align: center;
font-family: var(--font-display);
font-size: 28px;
font-weight: 800;
background: linear-gradient(135deg, var(--ctp-mauve), var(--ctp-blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.login-subtitle {
text-align: center;
color: var(--text-muted);
font-size: 13px;
margin: var(--space-1) 0 var(--space-6);
}
.login-form {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.form-group {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.form-group label {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.03em;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
}
.login-status {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
font-size: 13px;
}
.login-status--testing {
background: var(--accent-dim);
color: var(--accent);
}
.login-status--ok {
background: rgba(166, 227, 161, 0.15);
color: var(--positive);
}
.login-status--error {
background: rgba(243, 139, 168, 0.15);
color: var(--danger);
}
.login-saved-servers {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.login-saved-label {
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
letter-spacing: 0.05em;
text-transform: uppercase;
margin-bottom: var(--space-1);
}
.login-server-btn {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
text-align: left;
width: 100%;
}
.login-divider {
display: flex;
align-items: center;
gap: var(--space-3);
color: var(--text-muted);
font-size: 12px;
margin: var(--space-2) 0;
}
.login-divider::before,
.login-divider::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
/* ─ Loading / Empty ─ */
.loading-center {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.empty-state {
text-align: center;
padding: var(--space-10);
color: var(--text-muted);
}
/* ─────────────────────────────────────────
Fullscreen Player — Adaptive Portrait
──────────────────────────────────────────── */
.fs-player {
position: fixed;
inset: 0;
z-index: 9000;
overflow: hidden;
contain: layout style paint;
animation: fsIn 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
background: #06060e;
/* Promote to own compositor layer immediately on mount */
will-change: transform, opacity;
}
@keyframes fsIn {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes mesh-aura-a {
0% { transform: translate(0, 0 ); }
33% { transform: translate(5%, -6% ); }
66% { transform: translate(-3%, 2% ); }
100% { transform: translate(0, 0 ); }
}
@keyframes mesh-aura-b {
0% { transform: translate(0, 0 ); }
50% { transform: translate(-6%, 5% ); }
100% { transform: translate(0, 0 ); }
}
@keyframes portrait-drift {
0%, 100% { transform: translateY(0 ); }
50% { transform: translateY(-10px); }
}
.fs-mesh-bg {
position: absolute;
inset: 0;
z-index: 0;
background: #06060e;
overflow: hidden;
pointer-events: none;
}
/* Blobs are real divs — no will-change/filter to avoid software compositing layers */
.fs-mesh-blob {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
.fs-mesh-blob-a {
/* Oversized so panning never exposes the dark base underneath */
width: 130%;
height: 130%;
left: -35%;
bottom: -35%;
/* opacity on the element avoids color-mix(…, transparent) WebKit artifacts —
interpolating to the CSS keyword 'transparent' (rgba(0,0,0,0)) produces a
visible halo ring at the gradient boundary on WebKitGTK. */
background: radial-gradient(ellipse,
var(--dynamic-fs-accent, var(--accent)) 0%,
transparent 70%);
opacity: 0.14;
animation: mesh-aura-a 26s ease-in-out infinite;
animation-delay: 350ms;
transition: opacity 400ms ease-in-out;
}
.fs-mesh-blob-b {
width: 100%;
height: 110%;
right: -25%;
top: -25%;
background: radial-gradient(ellipse,
var(--dynamic-fs-accent, var(--accent)) 0%,
transparent 70%);
opacity: 0.08;
animation: mesh-aura-b 20s ease-in-out infinite;
animation-delay: 350ms;
transition: opacity 400ms ease-in-out;
}
/* ── Artist portrait — right half, object-fit: contain ── */
.fs-portrait-wrap {
position: absolute;
right: 4vw;
top: 4vw;
bottom: 8vw;
width: 46%;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
/* No overflow:hidden / border-radius — object-fit:contain never overflows, and
rounding the wrap visually clips wide images. */
transform: translateZ(0);
/* Thin left-edge fade to blend into the scrim — keep image fully visible */
-webkit-mask-image: linear-gradient(
to right,
transparent 0%,
rgba(0, 0, 0, 0.6) 6%,
#000 16%
);
mask-image: linear-gradient(
to right,
transparent 0%,
rgba(0, 0, 0, 0.6) 6%,
#000 16%
);
}
.fs-portrait {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
filter: drop-shadow(-24px 0 48px rgba(0, 0, 0, 0.85));
transition: opacity 1000ms ease-in-out;
will-change: transform;
animation: portrait-drift 22s ease-in-out infinite;
animation-delay: 350ms;
display: block;
}
/* Subtle darkening overlay — plain div paint, no GPU cost.
Alpha driven by --fs-portrait-dim (set via inline style on .fs-player). */
.fs-portrait-wrap::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background: rgba(0, 0, 0, var(--fs-portrait-dim, 0.28));
}
/* ── Horizontal scrim — dark left edge for text legibility ── */
.fs-scrim {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
background: linear-gradient(
to right,
rgba(6, 6, 14, 0.88) 0%,
rgba(6, 6, 14, 0.65) 32%,
rgba(6, 6, 14, 0.18) 58%,
transparent 78%
);
}
/* ── Close button ── */
.fs-close {
position: absolute;
top: 20px;
left: 24px;
z-index: 10;
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(8px);
color: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-fast);
cursor: pointer;
}
.fs-close:hover {
background: rgba(255, 255, 255, 0.18);
color: #ffffff;
transform: translateY(-1px);
}
/* ── Info cluster — bottom-left quadrant ── */
.fs-cluster {
position: absolute;
bottom: 72px;
left: clamp(28px, 4vw, 64px);
z-index: 4;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 85vw;
}
/* Album art — small, rounded, glowing */
.fs-art-wrap {
position: relative; /* stacking context for crossfade layers */
width: clamp(120px, 10vw, 180px);
height: clamp(120px, 10vw, 180px);
border-radius: 12px;
overflow: hidden;
flex-shrink: 0;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.7),
0 0 0 1px rgba(255, 255, 255, 0.07),
0 0 28px color-mix(in srgb, var(--dynamic-fs-accent, var(--accent)), transparent 30%);
transition: box-shadow 200ms ease-in-out;
}
/* Each layer is absolutely stacked — layers crossfade via opacity */
.fs-art {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 300ms ease;
}
.fs-art-placeholder {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.06);
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.3);
}
/* Track title — massive, font-black, primary — now on top */
.fs-track-title {
font-family: var(--font-display);
font-size: clamp(28px, 4.5vw, 68px);
font-weight: 900;
color: var(--dynamic-fs-accent, var(--accent));
margin: 0;
line-height: 1.05;
text-shadow: 0 2px 24px rgba(0, 0, 0, 0.5), 0 0 40px var(--dynamic-fs-accent, var(--accent-glow, var(--accent)));
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
transition: color 200ms ease-in-out, text-shadow 200ms ease-in-out;
}
/* Artist name — secondary, below track title */
.fs-artist-name {
font-family: var(--font-display);
font-size: clamp(13px, 1.5vw, 22px);
font-weight: 400;
color: rgba(255, 255, 255, 0.55);
margin: 0;
line-height: 1.3;
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Metadata row */
.fs-meta {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
font-size: clamp(11px, 1vw, 13px);
color: rgba(255, 255, 255, 0.5);
margin-top: 4px;
}
.fs-meta-dot {
opacity: 0.4;
}
/* Controls row — left-aligned */
.fs-controls {
display: flex;
align-items: center;
gap: 6px;
margin-top: 4px;
}
.fs-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
color: rgba(255, 255, 255, 0.65);
cursor: pointer;
transition: all var(--transition-fast), color 200ms ease-in-out, background-color 200ms ease-in-out;
background: transparent;
}
.fs-btn:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
transform: scale(1.1);
}
.fs-btn.active {
color: var(--dynamic-fs-accent, var(--accent));
}
.fs-btn-sm {
width: 28px;
height: 28px;
}
.fs-btn-play {
width: 52px;
height: 52px;
background: var(--dynamic-fs-accent, var(--accent));
color: var(--ctp-crust);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5), 0 0 20px var(--dynamic-fs-accent, var(--accent-glow, var(--accent)));
}
.fs-btn-play:hover {
background: var(--dynamic-fs-accent, var(--accent));
color: var(--ctp-crust);
transform: scale(1.1);
filter: brightness(1.12);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.6), 0 0 28px var(--dynamic-fs-accent, var(--accent-glow, var(--accent)));
}
.fs-btn-heart:hover,
.fs-btn-heart.active {
color: var(--dynamic-fs-accent, var(--color-star-active, var(--accent)));
background: rgba(255, 255, 255, 0.08);
}
/* ── Full-width seekbar — pinned to bottom edge ── */
.fs-seekbar-wrap {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 4;
}
.fs-seekbar-times {
display: flex;
justify-content: space-between;
padding: 0 16px 5px;
font-size: 10px;
color: rgba(255, 255, 255, 0.35);
font-variant-numeric: tabular-nums;
pointer-events: none;
user-select: none;
}
.fs-seekbar {
position: relative;
height: 3px;
cursor: pointer;
transition: height 120ms ease;
}
.fs-seekbar:hover {
height: 5px;
}
.fs-seekbar-bg {
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.08);
}
.fs-seekbar-buf {
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.18);
pointer-events: none;
}
.fs-seekbar-played {
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: var(--dynamic-fs-accent, var(--accent));
box-shadow: 0 0 15px var(--dynamic-fs-accent, var(--accent-glow, var(--accent)));
pointer-events: none;
transition: background-color 200ms ease-in-out, box-shadow 200ms ease-in-out;
}
.fs-seekbar input[type="range"] {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
margin: 0;
padding: 0;
-webkit-appearance: none;
appearance: none;
}
/* ── Idle-fade — only the close button hides; cluster + seekbar always visible ── */
.fs-player[data-idle="true"] .fs-close {
opacity: 0.05;
pointer-events: none;
transition: opacity 900ms ease;
}
.fs-player[data-idle="false"] .fs-close {
opacity: 1;
pointer-events: auto;
transition: opacity 200ms ease;
}
/* ── Apple Music-style fullscreen lyrics ─────────────────────────────────────
Full-screen scrollable list. Active line scrolls to ~35% from top.
User scroll pauses auto-scroll for 4 s. Word-sync via imperative DOM updates. */
.fsa-lyrics-container {
position: absolute;
inset: 0;
z-index: 3;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
padding: 0 clamp(28px, 5vw, 72px);
overscroll-behavior-x: none;
/* pointer-events on children only — container itself scrollable */
pointer-events: auto;
}
.fsa-lyrics-container::-webkit-scrollbar {
display: none;
}
/* Top padding — clears the close button (44 px) + some breathing room */
.fsa-lyrics-top-pad {
height: 80px;
pointer-events: none;
}
/* Bottom padding — lets last line scroll to ~35% from top even on short tracks */
.fsa-lyrics-bottom-pad {
height: 60vh;
pointer-events: none;
}
/* ── Line base styles ── */
.fsa-lyric-line {
/* Fixed font-size avoids layout reflow on active-line switch.
Visual "size" difference is done via transform: scale below. */
font-size: clamp(1.4rem, 3.2vh, 3rem);
font-weight: 600;
line-height: 1.45;
color: rgba(255, 255, 255, 0.28);
margin-bottom: 0.45em;
cursor: pointer;
user-select: none;
word-break: break-word;
overflow-wrap: break-word;
min-width: 0;
max-width: 100%;
transform-origin: left center;
/* All visual changes are compositor-friendly (color, text-shadow, transform) */
transition:
color 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
text-shadow 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
transform 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
opacity 380ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform: scale(0.9);
opacity: 0.9;
}
.fsa-lyric-line:hover {
color: rgba(255, 255, 255, 0.55);
}
/* Past: subtly smaller and more faded */
.fsa-lyric-line.fsal-past {
color: rgba(255, 255, 255, 0.16);
transform: scale(0.86);
opacity: 0.7;
}
/* Active: full scale, bright, glowing — font-weight snaps (no layout cost on same size) */
.fsa-lyric-line.fsal-active {
font-weight: 700;
color: rgba(255, 255, 255, 0.96);
transform: scale(1);
opacity: 1;
text-shadow:
0 0 48px color-mix(in srgb, var(--dynamic-fs-accent, var(--accent)), transparent 25%),
0 2px 18px rgba(0, 0, 0, 0.55);
}
/* Word-sync — active line dims base so only lit words pop */
.fsa-lyric-line.fsal-active .fsa-lyric-word {
color: rgba(255, 255, 255, 0.28);
text-shadow: none;
}
.fsa-lyric-word {
display: inline;
white-space: pre-wrap; /* preserves trailing spaces from lyricsplus tokens, allows wrapping */
transition: color 160ms ease, text-shadow 160ms ease;
}
.fsa-lyric-line.fsal-active .fsa-lyric-word.played {
color: rgba(255, 255, 255, 0.72);
}
.fsa-lyric-line.fsal-active .fsa-lyric-word.active {
color: #ffffff;
text-shadow: 0 0 32px color-mix(in srgb, var(--dynamic-fs-accent, var(--accent)), transparent 10%);
}
/* Plain (unsynced) lyrics */
.fsa-plain-lyrics {
pointer-events: none;
}
/* Fade the bottom of the scroll viewport when showing plain lyrics */
.fsa-lyrics-container--plain {
-webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
}
.fsa-plain-line {
font-size: clamp(1.1rem, 2.5vh, 2.2rem);
font-weight: 500;
line-height: 1.55;
color: rgba(255, 255, 255, 0.72);
margin: 0 0 0.2em;
word-break: break-word;
}
/* ── Gradient fades — top and bottom chrome ──────────────────────────────── */
/* Top fade: covers close button area; pointer-events none so button stays clickable */
.fsa-fade-top {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 110px;
z-index: 5;
pointer-events: none;
background: linear-gradient(
to bottom,
rgba(6, 6, 14, 0.92) 0%,
rgba(6, 6, 14, 0.5) 55%,
transparent 100%
);
}
/* Bottom fade: sits above lyrics (same z-index, later in DOM) but under cluster */
.fsa-fade-bottom {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 45vh;
z-index: 3;
pointer-events: none;
background: linear-gradient(
to bottom,
transparent 0%,
rgba(6, 6, 14, 0.55) 40%,
rgba(6, 6, 14, 0.88) 72%,
rgba(6, 6, 14, 0.96) 100%
);
}
/* ── Layout overrides when lyrics mode is active ────────────────────────── */
/* Hide portrait — lyrics use the full width */
.fs-player[data-lyrics] .fs-portrait-wrap {
display: none;
}
/* Scrim is redundant without the portrait — hide it */
.fs-player[data-lyrics] .fs-scrim {
display: none;
}
/* ── Lyrics settings popover (above mic button) ─────────────────────────────── */
/* The panel itself — floated above the mic button.
Rendered inside position:relative wrapper (the button wrapper).
z-index: 9 sits above the backdrop (8). */
.fslm-panel {
position: absolute;
bottom: calc(100% + 12px);
right: 0;
width: 230px;
z-index: 9;
background: #14141d;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 14px;
padding: 14px;
box-shadow: 0 12px 44px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.04);
animation: fslmIn 160ms cubic-bezier(0.22, 1, 0.36, 1) both;
transform-origin: bottom right;
}
@keyframes fslmIn {
from { opacity: 0; transform: scale(0.88) translateY(6px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
/* Toggle row */
.fslm-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.fslm-label {
font-size: 13px;
font-weight: 500;
color: rgba(255, 255, 255, 0.85);
}
/* Style selector */
.fslm-style-row {
display: flex;
gap: 7px;
transition: opacity 200ms ease;
}
.fslm-disabled .fslm-style-row,
.fslm-style-row.fslm-disabled {
opacity: 0.35;
pointer-events: none;
}
.fslm-style-btn {
flex: 1;
padding: 8px 10px;
border-radius: 9px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.78);
cursor: pointer;
text-align: left;
transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
}
.fslm-style-btn:hover {
border-color: rgba(255, 255, 255, 0.3);
color: #fff;
background: rgba(255, 255, 255, 0.1);
}
.fslm-style-active {
/* accent colors set inline via accentColor prop */
}
.fslm-style-name {
display: block;
font-size: 12px;
font-weight: 600;
line-height: 1.2;
}
.fslm-style-desc {
display: block;
font-size: 10px;
margin-top: 3px;
opacity: 0.7;
line-height: 1.3;
}
/* Small arrow pointing down toward the button */
.fslm-arrow {
position: absolute;
bottom: -6px;
right: 10px;
width: 12px;
height: 12px;
background: rgba(18, 18, 28, 0.88);
border-right: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transform: rotate(45deg);
}
/* ── Classic 5-line rail (Rail style) ───────────────────────────────────────── */
.fsr-lyrics-overlay {
position: absolute;
top: 15vh;
left: clamp(28px, 4vw, 64px);
right: 52%;
height: 30vh;
z-index: 3;
overflow: hidden;
pointer-events: none;
contain: layout style;
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 80%, transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 80%, transparent 100%);
}
.fsr-lyrics-rail {
position: absolute;
top: 0; left: 0; right: 0;
will-change: transform;
transition: transform 500ms ease-out;
}
.fsr-lyric-line {
height: 6vh;
display: flex;
align-items: center;
overflow: hidden;
white-space: normal;
overflow-wrap: break-word;
word-break: break-word;
font-size: 2vh;
line-height: 1.4;
color: rgba(255, 255, 255, 0.22);
font-weight: 400;
cursor: pointer;
pointer-events: auto;
transition: color 280ms ease, text-shadow 280ms ease, transform 280ms ease;
transform-origin: left center;
user-select: none;
}
.fsr-lyric-line:hover { color: rgba(255, 255, 255, 0.5); }
.fsr-lyric-line.fsrl-past { color: rgba(255, 255, 255, 0.1); }
.fsr-lyric-line.fsrl-active {
font-weight: 600;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 0 28px var(--accent-glow, var(--accent)), 0 1px 8px rgba(0, 0, 0, 0.6);
transform: scaleX(1.015);
}
.fsr-lyric-line.fsrl-active .fsr-lyric-word {
color: rgba(255, 255, 255, 0.35);
text-shadow: none;
}
.fsr-lyric-word {
display: inline;
white-space: pre;
transition: color 0.18s ease, text-shadow 0.18s ease;
}
.fsr-lyric-line.fsrl-active .fsr-lyric-word.played { color: rgba(255, 255, 255, 0.75); }
.fsr-lyric-line.fsrl-active .fsr-lyric-word.active {
color: #ffffff;
text-shadow: 0 0 28px var(--accent-glow, var(--accent)), 0 1px 8px rgba(0, 0, 0, 0.6);
}
html.no-compositing .fsr-lyrics-overlay { -webkit-mask-image: none; mask-image: none; }
html.no-compositing .fsr-lyrics-rail { will-change: auto; }
/* ── no-compositing overrides ─────────────────────────────────────────────────
Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc).
Replaces GPU-only effects (backdrop-filter, CSS filter, mask-image) with
software-friendly equivalents so the fullscreen player stays responsive. */
/* fs-player: remove will-change — without GPU compositor it only wastes RAM */
html.no-compositing .fs-player {
will-change: auto;
}
/* Close button: replace frosted-glass blur with flat semi-opaque background */
html.no-compositing .fs-close {
backdrop-filter: none;
background: rgba(0, 0, 0, 0.45);
}
/* Portrait: remove CSS filter (drop-shadow), will-change and drift animation */
html.no-compositing .fs-portrait {
filter: none;
will-change: auto;
animation: none;
}
/* Portrait wrap: replace mask-image fade with a pseudo-element overlay gradient.
pointer-events: none on wrap means the overlay never blocks clicks. */
html.no-compositing .fs-portrait-wrap {
-webkit-mask-image: none;
mask-image: none;
}
html.no-compositing .fs-portrait-wrap::before {
content: '';
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background: linear-gradient(
to right,
#06060e 0%,
rgba(6, 6, 14, 0.4) 6%,
transparent 16%
);
}
/* Lyrics fade overlays: gradient-only, no backdrop-filter — fine in no-compositing */
/* Mesh blobs: stop animations — in software mode each frame composites surfaces
larger than the screen (blob-a is 130 × 130 % of the viewport); on high-DPI
displays this saturates the CPU and drops the player to ~10 FPS.
Static gradients are preserved; only the slow pan animation is removed. */
html.no-compositing .fs-mesh-blob {
animation: none;
}
/* Seekbar played bar: remove box-shadow — its width changes on every playback
tick; in software rendering each change triggers a box-shadow repaint. */
html.no-compositing .fs-seekbar-played {
box-shadow: none;
}
/* Chat */
.chat-popup {
position: absolute;
width: 340px;
height: 480px;
background: var(--bg-card);
backdrop-filter: blur(12px);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
z-index: 9999;
overflow: hidden;
animation: slideInDown 0.2s ease-out;
}
@keyframes slideInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.chat-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: var(--surface1);
}
.chat-popup-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.chat-message-row {
display: flex;
flex-direction: column;
}
.chat-message-row.me {
align-items: flex-end;
}
.chat-message-row.other {
align-items: flex-start;
}
.chat-message-bubble {
max-width: 85%;
padding: 8px 12px;
border-radius: 12px;
font-size: 0.95rem;
line-height: 1.4;
word-break: break-word;
}
.chat-message-row.me .chat-message-bubble {
background: var(--accent);
color: var(--mantle);
border-bottom-right-radius: 4px;
}
.chat-message-row.other .chat-message-bubble {
background: var(--surface2);
color: var(--text);
border-bottom-left-radius: 4px;
}
.chat-message-author {
font-size: 0.75rem;
font-weight: 600;
margin-bottom: 2px;
opacity: 0.8;
}
.chat-message-time {
font-size: 0.7rem;
margin-top: 4px;
text-align: right;
opacity: 0.7;
}
.chat-popup-input {
display: flex;
padding: 12px;
border-top: 1px solid var(--border);
background: var(--surface1);
gap: 8px;
}
.chat-popup-input input {
flex: 1;
background: var(--crust);
border: 1px solid var(--border);
border-radius: 20px;
padding: 8px 16px;
color: var(--text);
outline: none;
transition: border-color 0.2s;
}
.chat-popup-input input:focus {
border-color: var(--accent);
}
.chat-popup-input button {
background: var(--accent);
color: var(--mantle);
border: none;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: opacity 0.2s;
}
.chat-popup-input button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ─ Context Menu ─ */
.context-menu {
position: fixed;
z-index: 10000;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-1) 0;
min-width: 200px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
backdrop-filter: blur(16px);
color: var(--text-primary);
font-size: 13px;
outline: none;
}
.context-menu-item {
padding: var(--space-2) var(--space-4);
display: flex;
align-items: center;
gap: var(--space-2);
cursor: pointer;
transition: background var(--transition-fast);
}
.context-menu-item:hover {
background: var(--surface-2);
color: var(--accent);
}
.context-menu-item.context-menu-keyboard-active,
.context-menu-rating-row.context-menu-keyboard-active {
background: var(--surface-2);
color: var(--accent);
}
.context-menu-item.context-menu-keyboard-active {
box-shadow: inset 2px 0 0 var(--accent), inset 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
}
.context-menu-rating-row.context-menu-keyboard-active {
background: color-mix(in srgb, var(--accent) 18%, var(--surface-2));
box-shadow: inset 3px 0 0 var(--accent), inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent);
}
.context-menu-rating-row.context-menu-keyboard-active .context-menu-rating-icon {
color: var(--accent);
opacity: 1;
}
.context-menu-divider {
height: 1px;
background: var(--border-subtle);
margin: var(--space-1) 0;
}
.context-menu-rating-row {
/* Match .context-menu-item padding so the row lines up with other entries */
padding: var(--space-2) var(--space-4);
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--text-primary);
}
.context-menu-rating-row .context-menu-rating-icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
color: var(--text-muted);
opacity: 0.95;
}
.context-menu-rating-row .context-menu-rating-icon svg {
display: block;
}
.context-menu-rating-row .star-rating {
flex: 1;
min-width: 0;
width: auto;
justify-content: flex-start;
align-items: center;
line-height: 1;
}
.context-menu-rating-row .star-rating .star {
padding: 0;
margin: 0;
line-height: 1;
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1em;
min-height: 1em;
}
/* ─ CSS Tooltips ─ */
/* Tooltips are handled by TooltipPortal (React portal) — no CSS pseudo-elements needed */
[data-tooltip] {
position: relative;
}
/* ─ Playlists Page ─ */
.playlist-page-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.playlist-page-header .page-title {
margin-bottom: 0;
}
.playlist-filter-input {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text-primary);
font-size: 13px;
padding: 6px 12px;
width: 220px;
outline: none;
transition: border-color 0.15s;
}
.playlist-filter-input:focus {
border-color: var(--accent);
}
.playlist-filter-input::placeholder {
color: var(--text-muted);
}
.playlist-list {
display: flex;
flex-direction: column;
}
.playlist-list-header {
display: grid;
grid-template-columns: 36px 1fr 90px 90px 44px;
gap: var(--space-3);
padding: 6px var(--space-3);
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 4px;
}
.playlist-sort-btn {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
background: none;
border: none;
cursor: pointer;
padding: 0;
transition: color 0.15s;
}
.playlist-sort-btn:hover,
.playlist-sort-btn.active {
color: var(--accent);
}
.playlist-row {
display: grid;
grid-template-columns: 36px 1fr 90px 90px 44px;
gap: var(--space-3);
align-items: center;
padding: 6px var(--space-3);
border-radius: var(--radius-md);
transition: background 0.15s;
user-select: none;
}
.playlist-row:hover {
background: var(--bg-hover);
}
.playlist-play-icon {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--accent);
color: var(--ctp-base);
border: none;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s, transform 0.15s;
flex-shrink: 0;
}
.playlist-row:hover .playlist-play-icon {
opacity: 1;
}
.playlist-play-icon:hover {
transform: scale(1.1);
}
.playlist-name {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
.playlist-meta {
font-size: 12px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.playlist-delete-btn {
opacity: 0;
color: var(--ctp-red) !important;
padding: 4px;
height: auto;
min-height: unset;
transition: opacity 0.15s;
justify-self: center;
}
.playlist-row:hover .playlist-delete-btn {
opacity: 1;
}
.fav-remove-btn {
opacity: 0;
color: var(--text-muted);
background: none;
border: none;
padding: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
transition: opacity 0.15s, color 0.15s;
}
.fav-remove-btn:hover {
color: var(--ctp-red);
}
.track-row:hover .fav-remove-btn,
.track-row.context-active .fav-remove-btn {
opacity: 1;
}
/* ─ Statistics Page ─ */
.stats-page {
display: flex;
flex-direction: column;
gap: 3rem;
}
.stats-overview {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
}
@media (max-width: 600px) {
.stats-overview {
grid-template-columns: repeat(2, 1fr);
}
}
.stats-card {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-5) var(--space-4);
display: flex;
flex-direction: column;
gap: 4px;
align-items: center;
text-align: center;
}
.stats-card-value {
font-family: var(--font-display);
font-size: 2rem;
font-weight: 800;
color: var(--accent);
line-height: 1;
}
.stats-card-label {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
}
.stats-card-label[data-tooltip] {
cursor: help;
text-decoration: underline;
text-decoration-style: dotted;
text-underline-offset: 3px;
}
/* Genre chart */
.genre-chart {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.genre-row {
display: flex;
flex-direction: column;
gap: 6px;
}
.genre-row-header {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--space-3);
}
.genre-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.genre-counts {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
flex-shrink: 0;
}
.genre-bar-track {
width: 100%;
height: 6px;
background: var(--bg-hover);
border-radius: 3px;
overflow: hidden;
}
.genre-bar-fill {
height: 100%;
background: var(--accent);
border-radius: 3px;
transition: width 0.8s ease-out;
}
/* ─ Connection Indicator ─ */
.connection-indicator-host {
position: relative;
flex-shrink: 0;
}
.connection-indicator-dropdown-panel {
position: absolute;
top: calc(100% + 6px);
right: 0;
min-width: 220px;
max-width: min(320px, 85vw);
}
.connection-indicator-chevron--open {
transform: rotate(180deg);
transition: transform var(--transition-fast, 0.15s ease);
}
.connection-indicator {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.connection-led {
width: 9px;
height: 9px;
border-radius: 50%;
flex-shrink: 0;
transition: background 0.4s ease, box-shadow 0.4s ease;
}
.connection-led--connected {
background: #a6e3a1;
box-shadow: 0 0 6px 2px rgba(166, 227, 161, 0.55);
}
.connection-led--disconnected {
background: #f38ba8;
box-shadow: 0 0 6px 2px rgba(243, 139, 168, 0.55);
}
.connection-led--checking {
background: var(--text-muted);
box-shadow: none;
animation: led-pulse 1.2s ease-in-out infinite;
}
@keyframes led-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
.connection-meta {
display: flex;
flex-direction: column;
line-height: 1.2;
}
.connection-type {
font-size: 11px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: 0.03em;
}
.connection-server {
font-size: 10px;
color: var(--text-muted);
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ─ Offline Overlay ─ */
.offline-overlay {
position: absolute;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.offline-overlay-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 40px 48px;
background: var(--bg-app);
border: 1px solid var(--border-subtle);
border-radius: 16px;
text-align: center;
max-width: 360px;
}
.offline-icon {
color: var(--text-muted);
opacity: 0.7;
}
.offline-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.offline-subtitle {
font-size: 14px;
color: var(--text-muted);
margin: 0;
line-height: 1.5;
}
.offline-retry {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* ─ Now Playing Page ─ */
/* ─── Now Playing Page ─────────────────────────────────────── */
.np-page {
position: relative;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
background:
radial-gradient(ellipse at 20% 0%, color-mix(in srgb, var(--accent) 10%, var(--bg-main)) 0%, transparent 60%),
radial-gradient(ellipse at 80% 100%, color-mix(in srgb, var(--ctp-blue) 8%, var(--bg-main)) 0%, transparent 60%),
var(--bg-main);
}
/* Main scrollable content */
.np-main {
flex: 1;
overflow-y: auto;
padding: 28px 28px 40px;
display: flex;
flex-direction: column;
gap: 18px;
min-width: 0;
position: relative;
z-index: 1;
}
/* ── Hero card: 3-column (cover+info | EQ | tag cloud) ── */
.np-hero-card {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 28px;
padding: 32px;
background: rgba(0, 0, 0, 0.30);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: var(--radius-lg);
border: 1px solid rgba(255, 255, 255, 0.08);
flex-shrink: 0;
}
/* Left column: meta info, big text */
.np-hero-left {
min-width: 0;
display: flex;
align-items: center;
}
.np-hero-cover-wrap {
flex-shrink: 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.np-hero-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 14px;
justify-content: center;
}
/* Right column: tag cloud */
.np-tag-cloud {
min-width: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
gap: 0;
}
.np-tag-cloud-tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
justify-content: flex-end;
align-items: center;
overflow: visible;
}
.np-tag-cloud-header {
width: 100%;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(255, 255, 255, 0.4);
margin-bottom: 10px;
text-align: right;
}
.np-tag {
font-size: 13px;
font-weight: 600;
padding: 6px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.10);
color: rgba(255, 255, 255, 0.75);
letter-spacing: 0.03em;
white-space: nowrap;
}
.np-tag-accent {
background: color-mix(in srgb, var(--accent) 22%, transparent);
color: var(--accent);
}
.np-tag-clickable {
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.np-tag-clickable:hover {
background: color-mix(in srgb, var(--accent) 30%, transparent);
color: white;
}
/* ── Info cards (artist / album) ── */
.np-info-card {
background: rgba(0, 0, 0, 0.22);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: var(--radius-lg);
border: 1px solid rgba(255, 255, 255, 0.07);
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 14px;
}
.np-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.np-card-title {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: rgba(255, 255, 255, 0.70);
margin: 0;
}
.np-card-link {
background: none;
border: none;
cursor: pointer;
font-size: 12px;
color: var(--accent);
display: flex;
align-items: center;
gap: 4px;
padding: 0;
flex-shrink: 0;
transition: opacity 0.15s;
}
.np-card-link:hover { opacity: 0.75; }
/* ── Artist bio ── */
.np-artist-bio-row {
display: flex;
gap: 16px;
align-items: flex-start;
}
.np-artist-thumb {
width: 80px;
height: 80px;
border-radius: var(--radius-md);
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.np-bio-wrap {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
padding-right: 1.5rem;
}
.np-bio-text {
font-size: 13px;
line-height: 1.65;
color: rgba(255, 255, 255, 0.72);
user-select: text;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
.np-bio-text.expanded {
display: block;
-webkit-line-clamp: unset;
overflow-y: auto;
max-height: 300px;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: var(--ctp-teal) transparent;
}
.np-bio-text.expanded::-webkit-scrollbar {
width: 4px;
}
.np-bio-text.expanded::-webkit-scrollbar-track {
background: transparent;
}
.np-bio-text.expanded::-webkit-scrollbar-thumb {
background: var(--ctp-teal);
border-radius: 2px;
}
.np-bio-text a { color: var(--accent); }
.np-bio-toggle {
background: none;
border: none;
cursor: pointer;
font-size: 12px;
color: var(--accent);
padding: 0;
align-self: flex-start;
opacity: 0.8;
}
.np-bio-toggle:hover { opacity: 1; }
.app-shell[data-mobile] .np-artist-bio-row {
flex-direction: column;
}
.app-shell[data-mobile] .np-artist-thumb {
width: 56px;
height: 56px;
}
.app-shell[data-mobile] .np-bio-wrap {
padding-right: 0;
}
/* ── Album tracklist ── */
.np-album-tracklist {
display: flex;
flex-direction: column;
gap: 1px;
}
.np-album-track {
display: grid;
grid-template-columns: 28px 1fr 44px;
align-items: center;
gap: 8px;
padding: 7px 8px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.12s;
}
.np-album-track:hover {
background: rgba(255, 255, 255, 0.06);
}
.np-album-track.active {
background: rgba(255, 255, 255, 0.08);
}
.np-album-track-num {
font-size: 12px;
color: rgba(255, 255, 255, 0.55);
text-align: right;
font-variant-numeric: tabular-nums;
display: flex;
align-items: center;
justify-content: flex-end;
}
.np-album-track.active .np-album-track-num {
color: var(--accent);
}
.np-album-track-title {
font-size: 13px;
color: rgba(255, 255, 255, 0.80);
}
.np-album-track.active .np-album-track-title {
color: var(--accent);
font-weight: 600;
}
.np-album-track-dur {
font-size: 12px;
color: rgba(255, 255, 255, 0.65);
font-variant-numeric: tabular-nums;
text-align: right;
}
/* ── Queue Sidebar ── */
.np-queue-sidebar {
width: 260px;
flex-shrink: 0;
display: flex;
flex-direction: column;
overflow: hidden;
border-left: 1px solid rgba(255, 255, 255, 0.07);
background: rgba(0, 0, 0, 0.28);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
position: relative;
z-index: 1;
}
.np-qs-header {
padding: 16px 14px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.07);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-shrink: 0;
}
.np-qs-title {
font-size: 13px;
font-weight: 700;
color: white;
}
.np-qs-meta {
font-size: 11px;
color: rgba(255, 255, 255, 0.40);
margin-top: 2px;
}
.np-qs-actions {
display: flex;
gap: 2px;
flex-shrink: 0;
}
.np-qs-list {
flex: 1;
overflow-y: auto;
padding: 6px 0;
}
.np-qs-item {
display: grid;
grid-template-columns: 26px 1fr 36px 22px;
align-items: center;
gap: 6px;
padding: 6px 10px 6px 12px;
cursor: pointer;
transition: background 0.1s;
}
.np-qs-item:hover,
.np-qs-item.context-active {
background: rgba(255, 255, 255, 0.06);
}
.np-qs-item.active {
background: rgba(255, 255, 255, 0.08);
}
.np-qs-num {
font-size: 11px;
color: rgba(255, 255, 255, 0.30);
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
font-variant-numeric: tabular-nums;
}
.np-qs-info {
min-width: 0;
}
.np-qs-track-title {
font-size: 12px;
color: rgba(255, 255, 255, 0.80);
line-height: 1.3;
}
.np-qs-track-title.active {
color: var(--accent);
font-weight: 600;
}
.np-qs-track-artist {
font-size: 11px;
color: rgba(255, 255, 255, 0.35);
margin-top: 1px;
}
.np-qs-dur {
font-size: 11px;
color: rgba(255, 255, 255, 0.30);
font-variant-numeric: tabular-nums;
text-align: right;
}
/* ── Shared np- classes (kept) ── */
/* Hero (old, kept for NpBg) */
.np-hero {
position: relative;
min-height: 280px;
display: flex;
align-items: center;
flex-shrink: 0;
}
.np-hero-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 16px;
padding: 28px 56px 32px;
width: 100%;
box-sizing: border-box;
}
/* Cover */
.np-cover-wrap {
flex-shrink: 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.np-cover {
position: relative;
z-index: 1;
width: 280px;
height: 280px;
border-radius: 16px;
object-fit: cover;
}
.np-cover-fallback {
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-hover);
color: var(--text-muted);
}
/* Info column (legacy, kept for compat) */
.np-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
color: white;
max-width: 600px;
}
.np-title {
font-family: var(--font-display);
font-size: 36px;
font-weight: 700;
line-height: 1.15;
color: white;
}
.np-artist-album {
font-size: 19px;
color: rgba(255, 255, 255, 0.75);
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 6px;
align-items: center;
}
.np-link:hover {
color: white;
text-decoration: underline;
}
.np-sep {
opacity: 0.4;
}
.np-tech-row {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 8px;
align-items: center;
margin-top: 4px;
}
.np-badge {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.85);
letter-spacing: 0.04em;
}
.np-star-btn {
background: none;
border: none;
cursor: pointer;
padding: 2px;
display: flex;
align-items: center;
color: white;
transition: transform 0.15s;
}
.np-star-btn:hover {
transform: scale(1.2);
}
/* Progress */
.np-progress-wrap {
display: flex;
align-items: center;
gap: 10px;
margin-top: 4px;
}
.np-waveform {
flex: 1;
}
.np-time {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
font-variant-numeric: tabular-nums;
flex-shrink: 0;
min-width: 36px;
}
/* Controls */
.np-controls {
display: flex;
align-items: center;
gap: 16px;
margin-top: 4px;
}
.np-ctrl-btn {
background: none;
border: none;
cursor: pointer;
color: rgba(255, 255, 255, 0.75);
display: flex;
align-items: center;
padding: 6px;
border-radius: 50%;
transition: color 0.15s, background 0.15s;
}
.np-ctrl-btn:hover {
color: white;
background: rgba(255, 255, 255, 0.1);
}
.np-ctrl-btn:disabled {
opacity: 0.3;
cursor: default;
}
.np-ctrl-btn.np-ctrl-active {
color: var(--accent);
}
.np-ctrl-lg {
color: rgba(255, 255, 255, 0.9);
}
.np-ctrl-play {
width: 60px;
height: 60px;
border-radius: 50%;
background: white;
color: var(--bg-app);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.15s, box-shadow 0.15s;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.np-ctrl-play:hover {
transform: scale(1.07);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
}
/* Volume */
.np-volume-row {
display: flex;
align-items: center;
gap: 8px;
}
.np-volume-slider {
width: 120px;
accent-color: white;
opacity: 0.7;
cursor: pointer;
}
.np-volume-slider:hover {
opacity: 1;
}
.np-empty-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: rgba(255, 255, 255, 0.5);
padding: 40px 0;
}
/* ─ Radio NowPlaying section ─ */
.np-radio-section {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
}
.np-badge-live {
background: rgba(239, 68, 68, 0.25);
color: #f87171;
display: inline-flex;
align-items: center;
}
.np-badge-azuracast {
background: rgba(99, 102, 241, 0.25);
color: #a5b4fc;
}
.np-radio-progress-wrap {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.np-radio-time {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
min-width: 32px;
}
.np-radio-progress-bar {
flex: 1;
height: 4px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
overflow: hidden;
}
.np-radio-progress-fill {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 1s linear;
}
.np-radio-next-track {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 0;
}
.np-radio-track-art {
width: 48px;
height: 48px;
border-radius: 4px;
object-fit: cover;
flex-shrink: 0;
}
.np-radio-track-art--sm {
width: 32px;
height: 32px;
}
.np-radio-track-info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.np-radio-track-title {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.np-radio-track-artist {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Radio progress bar in PlayerBar */
.radio-progress-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
overflow: hidden;
}
.radio-progress-fill {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 1s linear;
}
/* Listener count in PlayerBar */
.player-radio-listeners {
font-size: 10px;
color: var(--text-muted);
margin-top: 1px;
}
/* Queue section */
.np-queue-section {
flex: 1;
padding: 144px 56px 40px;
display: flex;
flex-direction: column;
gap: 0;
}
.np-queue-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 4px;
}
.np-queue-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.np-queue-meta {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.np-queue-actions {
display: flex;
align-items: center;
gap: 4px;
}
.np-action-btn {
background: none;
border: none;
cursor: pointer;
color: var(--text-muted);
padding: 6px 8px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
transition: color 0.15s, background 0.15s;
}
.np-action-btn:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.np-action-btn:disabled {
opacity: 0.3;
cursor: default;
}
/* Queue list */
.np-queue-list {
display: flex;
flex-direction: column;
}
.np-queue-item {
display: grid;
grid-template-columns: 36px 1fr auto 28px;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: var(--radius-md);
cursor: pointer;
transition: background 0.12s;
user-select: none;
}
.np-queue-item:hover,
.np-queue-item.context-active {
background: var(--bg-hover);
}
.np-queue-item.active {
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.np-queue-num {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
min-width: 24px;
}
.np-queue-item-info {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.np-queue-item-title {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.np-queue-item-active {
color: var(--accent);
}
.np-queue-item-artist {
font-size: 11px;
color: var(--text-muted);
}
.np-queue-item-duration {
font-size: 12px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
.np-queue-remove {
background: none;
border: none;
cursor: pointer;
color: var(--text-muted);
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
width: 24px;
height: 24px;
opacity: 0;
transition: opacity 0.12s, color 0.12s, background 0.12s;
}
.np-queue-item:hover .np-queue-remove {
opacity: 1;
}
.np-queue-remove:hover {
color: var(--ctp-red);
background: var(--bg-hover);
}
/* ─ Equalizer ─ */
.eq-wrap {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 660px;
}
.eq-controls-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.eq-toggle-label {
display: flex;
align-items: center;
gap: 0;
font-size: 13px;
color: var(--text-secondary);
}
.eq-preset-row {
display: flex;
align-items: center;
gap: 6px;
}
.eq-preset-select {
padding: 5px 10px;
font-size: 12px;
min-width: 140px;
}
/* ─ Custom Select ─ */
.custom-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--bg-input);
border: 1px solid var(--border-dropdown, var(--border));
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
text-align: left;
transition: border-color 0.15s, background 0.15s;
}
.custom-select-trigger:hover {
border-color: var(--accent);
background: var(--bg-hover);
}
.custom-select-trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.custom-select-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-select-chevron {
flex-shrink: 0;
color: var(--text-muted);
transition: transform 0.2s ease;
}
.custom-select-chevron.open {
transform: rotate(180deg);
}
.custom-select-dropdown {
background: var(--bg-card);
border: 1px solid var(--border-dropdown, var(--border));
border-radius: var(--radius-md);
box-shadow: 0 8px 32px var(--shadow-dropdown, rgba(0, 0, 0, 0.4));
overflow-y: auto;
overscroll-behavior: contain;
}
.custom-select-option {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
transition: background 0.1s;
}
.custom-select-option:hover {
background: var(--bg-hover);
}
.custom-select-option.selected {
color: var(--accent);
font-weight: 600;
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.custom-select-option.disabled {
color: var(--text-muted);
cursor: default;
}
.custom-select-group-label {
padding: 0.375rem 0.75rem 0.25rem;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
border-top: 1px solid var(--border-subtle);
margin-top: 0.25rem;
}
.custom-select-group-label:first-child {
border-top: none;
margin-top: 0;
}
.eq-ctrl-btn {
background: var(--bg-hover);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
transition: background 0.15s, color 0.15s;
flex-shrink: 0;
}
.eq-ctrl-btn:hover {
background: var(--accent);
color: var(--bg-app);
}
.eq-save-row {
display: flex;
align-items: center;
gap: 8px;
}
/* Main EQ panel — canvas area + faders */
.eq-panel {
background: var(--bg-app);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
transition: opacity 0.2s;
}
.eq-panel--off {
opacity: 0.4;
pointer-events: none;
}
.eq-canvas {
display: block;
width: 100%;
height: 120px;
margin-bottom: 12px;
}
.eq-faders {
display: flex;
align-items: stretch;
padding: 12px 8px 12px;
gap: 0;
height: 155px;
border-top: 1px solid var(--border);
}
/* dB scale on left */
.eq-db-scale {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0 6px 0 0;
width: 28px;
flex-shrink: 0;
}
.eq-db-tick {
font-size: 9px;
color: var(--text-muted);
font-family: monospace;
text-align: right;
line-height: 1;
}
/* Individual band column */
.eq-band {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
flex: 1;
min-width: 0;
}
.eq-gain-val {
font-size: 9px;
color: var(--text-muted);
font-family: monospace;
font-variant-numeric: tabular-nums;
line-height: 1;
height: 12px;
}
/* Fader track — the rotated slider is absolute, so this only needs
enough height for the visual fader length. */
.eq-fader-track {
position: relative;
flex: 1;
min-height: 110px;
width: 100%;
}
/* Zero-dB center mark — horizontal line at 50% = 0 dB position */
.eq-zero-mark {
position: absolute;
left: 10%;
right: 10%;
height: 1px;
background: var(--text-muted);
pointer-events: none;
top: 50%;
transform: translateY(-50%);
}
/* Custom div-based vertical fader — no native range input, full pixel control */
.eq-fader-custom {
position: absolute;
inset: 0;
user-select: none;
touch-action: none;
}
/* The thin vertical track line */
.eq-track-line {
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 3px;
transform: translateX(-50%);
background: var(--border);
border-radius: 2px;
pointer-events: none;
}
/* The draggable handle */
.eq-thumb {
position: absolute;
left: 50%;
width: 20px;
height: 8px;
transform: translateX(-50%) translateY(-50%);
background: var(--accent);
border-radius: 3px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.18);
pointer-events: none;
transition: opacity 0.15s;
}
.eq-freq-label {
font-size: 9px;
color: var(--text-muted);
white-space: nowrap;
}
/* Pre-gain row */
.eq-pregain-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px 10px;
border-top: 1px solid var(--border-subtle);
}
.eq-pregain-label {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
min-width: 72px;
}
.eq-pregain-slider {
flex: 1;
accent-color: var(--accent);
height: 4px;
cursor: pointer;
}
.eq-pregain-val {
font-size: 11px;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
min-width: 52px;
text-align: right;
}
/* AutoEQ section */
.eq-autoeq-section {
border: 1px solid var(--border-subtle);
border-radius: 8px;
overflow: hidden;
}
.eq-autoeq-toggle {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 8px 12px;
background: var(--bg-card);
border: none;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
text-align: left;
}
.eq-autoeq-toggle:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.eq-autoeq-toggle span {
flex: 1;
}
.eq-autoeq-body {
padding: 10px 12px 12px;
background: var(--bg-app);
display: flex;
flex-direction: column;
gap: 8px;
}
.eq-autoeq-search-row {
display: flex;
align-items: center;
gap: 6px;
}
.eq-autoeq-status {
font-size: 11px;
color: var(--text-muted);
padding: 2px 0;
}
.eq-autoeq-error {
color: var(--danger);
}
.eq-autoeq-applied {
color: var(--accent);
}
.eq-autoeq-results {
display: flex;
flex-direction: column;
gap: 2px;
max-height: 180px;
overflow-y: auto;
}
.eq-autoeq-result-btn {
text-align: left;
padding: 5px 10px;
border-radius: 5px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
}
.eq-autoeq-result-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.eq-autoeq-result-btn {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.eq-autoeq-result-source {
font-size: 10px;
color: var(--text-muted);
white-space: nowrap;
flex-shrink: 0;
}
/* ─── Changelog ────────────────────────────────────────────────────────────── */
.changelog-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.changelog-entry {
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
background: var(--bg-card);
overflow: hidden;
}
.changelog-summary {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
cursor: pointer;
list-style: none;
user-select: none;
color: var(--text-primary);
}
.changelog-summary::-webkit-details-marker { display: none; }
.changelog-summary::before {
content: '▶';
font-size: 9px;
color: var(--text-muted);
transition: transform 0.15s;
flex-shrink: 0;
}
.changelog-entry[open] > .changelog-summary::before {
transform: rotate(90deg);
}
.changelog-version {
font-size: 13px;
font-weight: 600;
color: var(--accent);
font-variant-numeric: tabular-nums;
}
.changelog-date {
font-size: 11px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.changelog-body {
padding: 4px 14px 12px 28px;
display: flex;
flex-direction: column;
gap: 2px;
}
.changelog-h3 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent);
margin-top: 10px;
margin-bottom: 2px;
}
.changelog-h4 {
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
margin-top: 6px;
}
.changelog-item {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
padding-left: 12px;
position: relative;
}
.changelog-item::before {
content: '·';
position: absolute;
left: 2px;
color: var(--text-muted);
}
.changelog-text {
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
}
.changelog-code {
font-family: var(--font-mono, monospace);
font-size: 11px;
background: var(--bg-hover);
color: var(--accent);
padding: 1px 4px;
border-radius: 3px;
}
/* ─ Genre Filter Bar ─ */
.genre-filter-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
margin-left: 0.35rem;
border-radius: 9px;
background: var(--accent);
color: var(--ctp-crust);
font-size: 0.7rem;
font-weight: 700;
line-height: 1;
}
.genre-filter-popover {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: 10px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
display: flex;
flex-direction: column;
overflow: hidden;
overscroll-behavior: contain;
}
.genre-filter-popover__search {
padding: 0.55rem 0.6rem 0.4rem;
border-bottom: 1px solid var(--border-subtle);
}
.genre-filter-popover__search input {
width: 100%;
border: 1px solid var(--border-subtle);
background: var(--bg-app);
color: var(--text-primary);
border-radius: 6px;
padding: 0.35rem 0.55rem;
font-size: 0.85rem;
outline: none;
}
.genre-filter-popover__search input:focus {
border-color: var(--accent);
}
.genre-filter-popover__list {
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0.25rem 0;
}
.genre-filter-popover__option {
display: flex;
align-items: center;
gap: 0.55rem;
padding: 0.4rem 0.7rem;
font-size: 0.85rem;
color: var(--text-primary);
cursor: pointer;
user-select: none;
}
.genre-filter-popover__option:hover {
background: var(--bg-hover);
}
.genre-filter-popover__option--selected {
color: var(--accent);
font-weight: 600;
}
.genre-filter-popover__check {
width: 16px;
height: 16px;
border: 1.5px solid var(--border-subtle);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--ctp-crust);
background: transparent;
}
.genre-filter-popover__option--selected .genre-filter-popover__check {
background: var(--accent);
border-color: var(--accent);
}
.genre-filter-popover__empty {
padding: 0.75rem;
font-size: 0.82rem;
color: var(--text-muted);
text-align: center;
}
.genre-filter-popover__footer {
padding: 0.4rem 0.5rem;
border-top: 1px solid var(--border-subtle);
display: flex;
justify-content: flex-end;
}
/* ─ Genre Tag Cloud ─ */
.genre-cloud {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.55rem 0.6rem;
padding-bottom: 1rem;
}
.genre-pill {
appearance: none;
border: 1px solid color-mix(in srgb, var(--genre-color, var(--accent)) 22%, transparent);
background: color-mix(in srgb, var(--genre-color, var(--accent)) 6%, transparent);
color: var(--text-primary);
border-radius: 999px;
padding: 0.28em 0.78em;
cursor: pointer;
font-weight: 600;
line-height: 1.15;
white-space: nowrap;
user-select: none;
font-family: inherit;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.12s ease;
}
.genre-pill:hover {
background: color-mix(in srgb, var(--genre-color, var(--accent)) 14%, transparent);
border-color: color-mix(in srgb, var(--genre-color, var(--accent)) 45%, transparent);
color: var(--genre-color, var(--accent));
transform: translateY(-1px);
}
.genre-pill:focus-visible {
outline: 2px solid var(--genre-color, var(--accent));
outline-offset: 2px;
}
/* ─ Random Landing ─ */
.random-landing {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100%;
padding: var(--space-8);
box-sizing: border-box;
}
.random-landing-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: clamp(var(--space-4), 2vw, var(--space-6));
width: 100%;
max-width: min(860px, 90vw);
}
.mix-pick-card {
position: relative;
overflow: hidden;
display: flex;
align-items: flex-end;
min-height: clamp(200px, 22vh, 320px);
padding: var(--space-6);
border-radius: 16px;
background: var(--ctp-surface0);
border: 1px solid rgba(255, 255, 255, 0.06);
cursor: pointer;
text-align: left;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
color: inherit;
}
.mix-pick-card:hover {
transform: translateY(-4px) scale(1.015);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.35), 0 0 0 1px var(--accent), 0 0 24px -4px var(--accent);
border-color: var(--accent);
}
.mix-pick-card-bg-icon {
position: absolute;
top: 50%;
right: -16px;
transform: translateY(-50%);
width: clamp(140px, 14vw, 200px);
height: clamp(140px, 14vw, 200px);
color: var(--text-primary);
opacity: 0.07;
pointer-events: none;
flex-shrink: 0;
}
.mix-pick-card-content {
position: relative;
display: flex;
flex-direction: column;
gap: var(--space-2);
z-index: 1;
}
.mix-pick-card-icon {
color: var(--accent);
margin-bottom: var(--space-1);
}
.mix-pick-card-label {
font-family: var(--font-display);
font-size: 17px;
font-weight: 700;
color: var(--text-primary);
line-height: 1.2;
}
.mix-pick-card-desc {
font-size: 12px;
color: var(--text-muted);
line-height: 1.45;
max-width: 22ch;
}
@media (max-width: 640px) {
.random-landing {
padding: var(--space-5) var(--space-4);
justify-content: flex-start;
}
.random-landing-grid {
grid-template-columns: 1fr;
}
.mix-pick-card {
min-height: 160px;
}
}
/* ─ Playlists overview header ─ */
.playlists-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
flex-wrap: wrap;
margin-bottom: var(--space-6);
}
/* ─ Playlist Card (Grid Overview) ─ */
.playlist-card-icon {
color: var(--accent);
opacity: 0.6;
}
/* Playlist card actions (edit/delete) — visible on hover */
.playlist-card-actions {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 6px;
opacity: 0;
transform: translateY(-2px);
transition: opacity var(--transition-fast), transform var(--transition-fast);
z-index: 6;
}
.album-card:hover .playlist-card-actions {
opacity: 1;
transform: translateY(0);
}
.playlist-card-action {
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(0, 0, 0, 0.55);
color: rgba(255, 255, 255, 0.75);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}
.playlist-card-action--edit:hover {
background: rgba(90, 120, 255, 0.85);
color: #fff;
}
.playlist-card-action--delete:hover {
background: rgba(220, 60, 60, 0.85);
color: #fff;
}
.playlist-card-action--delete-confirm {
background: #dc3c3c !important;
color: #fff !important;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.45);
animation: delete-confirm-pulse 0.7s ease-in-out infinite alternate;
}
@keyframes delete-confirm-pulse {
from { background: #dc3c3c; }
to { background: #ff2222; }
}
/* ─ Internet Radio ─ */
.radio-card-active {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.radio-live-overlay {
position: absolute;
top: 6px;
left: 6px;
z-index: 3;
pointer-events: none;
}
.radio-live-badge {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
background: #e03030;
color: #fff;
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.06em;
line-height: 1.4;
}
/* ─ Radio Toolbar ─ */
.radio-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.radio-toolbar-chips {
display: flex;
gap: 6px;
overflow-x: auto;
flex: 1;
scrollbar-width: none;
}
.radio-toolbar-chips::-webkit-scrollbar { display: none; }
.radio-filter-chip {
display: inline-flex;
align-items: center;
padding: 4px 14px;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
background: var(--bg-card);
color: var(--text-secondary);
border: 1px solid var(--ctp-surface1);
white-space: nowrap;
flex-shrink: 0;
transition: all 0.15s;
}
.radio-filter-chip:hover { background: var(--bg-hover); color: var(--text-primary); }
.radio-filter-chip.active { background: var(--accent); color: var(--ctp-crust); border-color: var(--accent); }
.radio-card-drop-before {
box-shadow: -3px 0 0 0 var(--accent);
}
.radio-card-drop-after {
box-shadow: 3px 0 0 0 var(--accent);
}
/* ─ Radio card edit chip ─ */
.radio-card-chip {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
background: var(--text-secondary);
color: var(--ctp-surface1);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.03em;
transition: background 0.15s, color 0.15s;
white-space: nowrap;
}
.radio-card-chip:hover {
background: var(--accent);
color: var(--ctp-crust);
}
/* Radio card: buttons row always pinned to bottom */
.radio-card {
display: flex;
flex-direction: column;
}
.radio-card .album-card-info {
flex: 1;
display: flex;
flex-direction: column;
}
.radio-card .album-card-artist {
margin-top: auto;
}
/* ─ Alphabet Filter Bar ─ */
.alphabet-filter-bar {
display: flex;
flex-wrap: wrap;
gap: 3px;
margin-bottom: 14px;
}
.alphabet-filter-btn {
min-width: 26px;
padding: 4px 7px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.03em;
color: var(--text-primary);
background: transparent;
border: none;
cursor: pointer;
transition: color 0.12s, background 0.12s;
}
.alphabet-filter-btn:hover { background: var(--bg-hover); }
.alphabet-filter-btn.active { background: var(--accent); color: var(--ctp-crust); }
/* ─ Artists page alphabet bar ─ */
.artists-alpha-btn {
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 600;
border: 1px solid var(--border-subtle);
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.artists-alpha-btn:not(.artists-alpha-btn--active):hover {
background: var(--accent);
color: var(--ctp-crust);
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}
.artists-alpha-btn--active {
background: var(--accent);
color: var(--ctp-crust);
border-color: var(--accent);
}
.alphabet-filter-btn.empty { opacity: 0.28; pointer-events: none; }
/* ─ Radio favorite star ─ */
.radio-favorite-btn { color: rgba(255, 255, 255, 0.35); }
.radio-favorite-btn:hover { color: rgba(255, 255, 255, 0.8); }
.radio-favorite-btn.active { color: var(--accent); }
/* ─ Radio Browser Directory ─ */
.radio-browser-result {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 6px;
border-radius: var(--radius);
transition: background 0.12s;
cursor: default;
}
.radio-browser-result.clickable { cursor: pointer; }
.radio-browser-result.clickable:hover { background: var(--bg-hover); }
.radio-browser-result.added { opacity: 0.6; }
.radio-browser-action {
flex-shrink: 0;
width: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.radio-browser-favicon {
width: 40px;
height: 40px;
border-radius: var(--radius-sm);
object-fit: cover;
flex-shrink: 0;
}
.radio-browser-favicon--placeholder {
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-card);
color: var(--text-muted);
}
.radio-browser-info {
flex: 1;
min-width: 0;
}
.radio-browser-name {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.radio-browser-tags {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
}
/* ─ Bulk Select ─ */
.bulk-action-bar {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
background: var(--accent-dim, color-mix(in srgb, var(--accent) 15%, transparent));
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
border-radius: var(--radius);
margin-bottom: var(--space-2);
}
.bulk-action-count {
font-size: 13px;
font-weight: 600;
color: var(--accent);
margin-right: auto;
}
.bulk-pl-picker-wrap {
position: relative;
}
.bulk-check {
display: inline-block;
width: 14px;
height: 14px;
border: 1.5px solid var(--text-muted);
border-radius: 3px;
background: transparent;
vertical-align: middle;
flex-shrink: 0;
transition: border-color 0.1s, background 0.1s;
}
.bulk-check.checked {
background: var(--accent);
border-color: var(--accent);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6l3 3 5-5' stroke='%23fff' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-size: 10px 10px;
background-repeat: no-repeat;
background-position: center;
}
.track-row.bulk-selected {
background: color-mix(in srgb, var(--accent) 10%, transparent) !important;
}
.track-row.bulk-selected:hover {
background: color-mix(in srgb, var(--accent) 16%, transparent) !important;
}
/* Drop indicator line */
.playlist-drop-indicator {
height: 2px;
background: var(--accent);
border-radius: 1px;
margin: 1px 0;
pointer-events: all;
}
/* ─ Playlist Search Panel ─ */
.playlist-search-panel {
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-3) var(--space-2);
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-bottom: var(--space-4);
animation: fade-in-down 0.15s ease;
}
@keyframes fade-in-down {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.playlist-search-input-wrap {
position: relative;
display: flex;
align-items: center;
}
.playlist-search-input-wrap .input {
width: 100%;
padding-right: 36px;
}
.playlist-search-input-wrap .live-search-clear {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
}
.playlist-search-row {
display: grid;
grid-template-columns: 18px 36px 1fr auto 52px 28px;
align-items: center;
gap: var(--space-2);
padding: 3px var(--space-1);
border-radius: var(--radius-sm);
transition: background var(--transition-fast);
}
.playlist-search-row:hover {
background: var(--bg-hover);
}
.playlist-search-row--selected {
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.playlist-search-checkbox {
width: 14px;
height: 14px;
accent-color: var(--accent);
cursor: pointer;
flex-shrink: 0;
}
.playlist-search-thumb {
width: 36px;
height: 36px;
object-fit: cover;
border-radius: var(--radius-xs);
background: var(--bg-hover);
flex-shrink: 0;
}
.playlist-search-info {
display: flex;
flex-direction: column;
min-width: 0;
}
.playlist-search-title {
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
}
.playlist-search-artist {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.playlist-search-album {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.playlist-search-duration {
font-size: 12px;
color: var(--text-muted);
text-align: right;
font-variant-numeric: tabular-nums;
}
.playlist-search-add-btn {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
transition: background var(--transition-fast), color var(--transition-fast);
flex-shrink: 0;
}
.playlist-search-add-btn:hover {
background: var(--accent);
color: var(--ctp-base);
}
.playlist-suggestions {
margin-top: var(--space-6);
}
.playlist-suggestions-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding: 0 var(--space-3);
margin-bottom: var(--space-1);
}
.playlist-suggestions-title {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.playlist-suggestions-hint {
font-size: 11px;
color: var(--text-muted);
line-height: 1.3;
}
/* ─ Context Menu Submenu ─ */
.context-menu-item--submenu {
position: relative;
}
.context-menu-item--submenu.active,
.context-menu-item--submenu:hover {
background: var(--surface-2);
color: var(--accent);
}
.context-submenu {
position: absolute;
top: -4px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-1) 0;
min-width: 190px;
max-width: 250px;
max-height: 280px;
overflow-y: auto;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
z-index: 10001;
}
.context-submenu-empty {
padding: var(--space-2) var(--space-4);
color: var(--text-muted);
font-size: 12px;
}
.context-submenu-new {
color: var(--accent);
font-weight: 500;
}
.context-submenu-create {
display: flex;
align-items: center;
gap: 4px;
padding: var(--space-1) var(--space-2);
}
.context-submenu-input {
flex: 1;
min-width: 0;
background: var(--bg-surface);
border: 1px solid var(--accent);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 12px;
padding: 3px 7px;
outline: none;
}
.context-submenu-input::placeholder {
color: var(--text-muted);
}
.context-submenu-create-btn {
flex-shrink: 0;
background: var(--accent);
border: none;
border-radius: var(--radius-sm);
color: var(--ctp-crust);
padding: 4px 6px;
cursor: pointer;
display: flex;
align-items: center;
transition: opacity var(--transition-fast);
}
.context-submenu-create-btn:hover {
opacity: 0.85;
}
/* ═══════════════════════════════════════════════════════════════════════════
MOBILE COMPONENT OVERRIDES (< 800px)
═══════════════════════════════════════════════════════════════════════════ */
/* ─── Tracklist — compact Title + Artist rows ─── */
.app-shell[data-mobile] .tracklist-header {
display: none;
}
.app-shell[data-mobile] .track-row {
display: grid !important;
grid-template-columns: 44px 1fr 50px !important;
grid-template-rows: auto auto;
column-gap: 8px;
row-gap: 0;
min-height: 52px;
padding: 6px 0;
}
/* play button / number */
.app-shell[data-mobile] .track-row > :nth-child(1) {
grid-column: 1;
grid-row: 1 / span 2;
align-self: center;
}
/* title cell */
.app-shell[data-mobile] .track-row > :nth-child(2) {
grid-column: 2;
grid-row: 1;
align-self: end;
padding-bottom: 1px;
}
/* artist cell — shown as subtitle */
.app-shell[data-mobile] .track-row > :nth-child(3) {
grid-column: 2;
grid-row: 2;
align-self: start;
overflow: hidden;
}
/* duration — last child, right column */
.app-shell[data-mobile] .track-row > :last-child {
grid-column: 3;
grid-row: 1 / span 2;
align-self: center;
justify-self: end;
display: flex !important;
}
/* hide album, genre, star and any extra columns */
.app-shell[data-mobile] .track-row > :nth-child(n+4):not(:last-child) {
display: none !important;
}
/* artist button/span as subtitle */
.app-shell[data-mobile] .track-row .track-artist-cell .rm-artist-btn,
.app-shell[data-mobile] .track-row .track-artist-cell .track-artist {
font-size: 12px !important;
color: var(--text-secondary) !important;
padding: 0 !important;
background: none !important;
border: none !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: 100%;
text-align: left;
cursor: default;
}
/* ─── Artist Detail page ─── */
.app-shell[data-mobile] .artist-detail-header {
flex-direction: column;
align-items: center;
text-align: center;
}
.app-shell[data-mobile] .artist-detail-avatar {
width: 150px;
height: 150px;
flex-shrink: 0;
}
.app-shell[data-mobile] .artist-detail-meta {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding-top: 0;
}
.app-shell[data-mobile] .artist-detail-entity-rating {
justify-content: center;
}
.app-shell[data-mobile] .artist-detail-links {
justify-content: center;
}
.app-shell[data-mobile] .album-grid-wrap--artist {
grid-template-columns: repeat(2, 1fr);
}
/* ─── Album / Artist Grid — fewer columns ─── */
.app-shell[data-mobile] .album-grid-wrap {
grid-template-columns: repeat(auto-fill, minmax(min(140px, 30%), 1fr));
gap: var(--space-3);
}
/* Horizontal scroll rows — smaller cards */
.app-shell[data-mobile] .album-grid .album-card,
.app-shell[data-mobile] .album-grid .artist-card {
flex: 0 0 130px;
}
.app-shell[data-mobile] .album-card-more {
flex: 0 0 130px;
}
/* ─── Hero — mobile ─── */
.app-shell[data-mobile] .hero,
.app-shell[data-mobile] .hero-placeholder {
height: 290px;
}
/* Stronger bottom gradient so white text stays readable without a cover */
.app-shell[data-mobile] .hero-overlay {
background:
linear-gradient(to top,
rgba(0, 0, 0, 0.90) 0%,
rgba(0, 0, 0, 0.55) 45%,
rgba(0, 0, 0, 0.22) 100%);
}
.app-shell[data-mobile] .hero-content {
padding: var(--space-4);
padding-bottom: 44px; /* keep buttons clear of the pagination dots */
gap: 0;
align-items: flex-end;
}
.app-shell[data-mobile] .hero-title {
font-size: clamp(17px, 5vw, 22px);
max-width: 100%;
}
/* Circular action buttons on mobile hero */
.hero-actions-mobile {
display: flex;
gap: 12px;
margin-top: 12px;
}
/* ─── Live Search — full width ─── */
.app-shell[data-mobile] .live-search {
max-width: none;
min-width: 0;
}
/* ─── Section / Page titles ─── */
.app-shell[data-mobile] .section-title,
.app-shell[data-mobile] .page-title,
.app-shell[data-mobile] .section-title-link {
font-size: 17px;
}
/* ─── Album Detail Header — stack vertically on mobile ─── */
.app-shell[data-mobile] .album-detail-header-inner {
flex-direction: column;
align-items: flex-start;
}
.app-shell[data-mobile] .album-detail-cover {
width: 160px;
height: 160px;
}
/* ─── Page padding ─── */
.app-shell[data-mobile] .content-body {
padding: var(--space-4) !important;
}
/* ─── Hide desktop search on mobile ─── */
.app-shell[data-mobile] .live-search {
display: none;
}
/* ═══════════════════════════════════════════════════════════════════════════
MOBILE SEARCH OVERLAY
═══════════════════════════════════════════════════════════════════════════ */
@keyframes mobile-search-in {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.mobile-search-overlay {
position: fixed;
inset: 0;
z-index: 200;
background: var(--bg-app);
display: flex;
flex-direction: column;
animation: mobile-search-in 0.2s ease both;
}
/* ─── Search bar ─── */
.mobile-search-bar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px 10px 12px;
flex-shrink: 0;
}
.mobile-search-field {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
background: var(--bg-card);
border-radius: 20px;
padding: 0 12px;
height: 40px;
min-width: 0;
}
.mobile-search-icon {
color: var(--text-muted);
flex-shrink: 0;
}
.mobile-search-spinner {
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.7s linear infinite;
flex-shrink: 0;
}
.mobile-search-input {
flex: 1;
background: none;
border: none;
outline: none;
font-size: 17px;
color: var(--text-primary);
caret-color: var(--accent);
min-width: 0;
}
.mobile-search-input::placeholder {
color: var(--text-muted);
}
.mobile-search-input::-webkit-search-cancel-button {
display: none;
}
.mobile-search-clear {
display: flex;
align-items: center;
justify-content: center;
background: var(--text-muted);
border: none;
color: var(--bg-app);
cursor: pointer;
padding: 0;
width: 18px;
height: 18px;
border-radius: 50%;
flex-shrink: 0;
opacity: 0.7;
}
.mobile-search-cancel {
background: none;
border: none;
color: var(--accent);
font-size: 15px;
font-weight: 500;
cursor: pointer;
padding: 4px 0;
flex-shrink: 0;
white-space: nowrap;
}
/* ─── Results area ─── */
.mobile-search-results {
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
}
.mobile-search-noresults {
padding: 48px 20px;
text-align: center;
color: var(--text-muted);
font-size: 15px;
}
/* ─── Empty state ─── */
.mobile-search-empty-state {
display: flex;
flex-direction: column;
}
.mobile-search-hint {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
padding: 48px 20px 32px;
}
.mobile-search-hint-icon {
color: var(--text-muted);
opacity: 0.25;
}
.mobile-search-hint-text {
font-size: 16px;
color: var(--text-muted);
text-align: center;
}
/* ─── Category chips ─── */
.mobile-search-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 6px 16px 16px;
}
.mobile-search-chip {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 20px;
padding: 7px 14px;
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
cursor: pointer;
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.mobile-search-chip:active,
.mobile-search-chip:hover {
background: var(--bg-hover);
border-color: var(--accent);
color: var(--accent);
}
/* ─── Recent searches ─── */
.mobile-search-recent-remove {
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
color: var(--text-muted);
padding: 6px;
cursor: pointer;
flex-shrink: 0;
opacity: 0.6;
}
.mobile-search-recent-remove:hover {
opacity: 1;
}
/* ─── Section headers ─── */
.mobile-search-section {
padding-bottom: 8px;
}
.mobile-search-section-label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
padding: 16px 16px 6px;
}
/* ─── Result items ─── */
.mobile-search-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 10px 16px;
background: none;
border: none;
cursor: pointer;
text-align: left;
transition: background var(--transition-fast);
}
.mobile-search-item:active {
background: var(--bg-hover);
}
.mobile-search-thumb {
width: 46px;
height: 46px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
}
.mobile-search-avatar {
width: 46px;
height: 46px;
border-radius: 8px;
background: var(--bg-card);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
flex-shrink: 0;
}
.mobile-search-avatar--circle {
border-radius: 50%;
}
.mobile-search-item-info {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
flex: 1;
}
.mobile-search-item-title {
font-size: 15px;
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-search-item-sub {
font-size: 13px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-search-item-chevron {
color: var(--text-muted);
opacity: 0.4;
flex-shrink: 0;
}
/* ─── Album Header — Mobile Actions ─── */
.album-detail-actions-mobile {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
margin-top: 10px;
width: 100%;
}
.album-actions-row {
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
gap: 20px;
}
.album-actions-row--secondary {
gap: 16px;
}
/* Base icon button */
.album-icon-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.1);
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast), filter var(--transition-fast);
flex-shrink: 0;
}
.album-icon-btn:hover {
background: rgba(255, 255, 255, 0.14);
color: var(--text-primary);
}
/* Play — largest, accent fill */
.album-icon-btn--play {
width: 54px;
height: 54px;
background: var(--accent);
border-color: transparent;
color: #fff;
}
.album-icon-btn--play:hover {
background: var(--accent);
filter: brightness(1.12);
color: #fff;
}
/* Queue — medium, slightly more prominent than secondary */
.album-icon-btn--queue {
width: 46px;
height: 46px;
background: rgba(255, 255, 255, 0.13);
border-color: rgba(255, 255, 255, 0.18);
color: var(--text-primary);
}
/* Secondary (smaller) icon buttons */
.album-icon-btn--sm {
width: 36px;
height: 36px;
}
.album-icon-btn.is-starred {
color: var(--color-star-active, var(--accent));
}
.album-icon-btn.album-icon-btn--active {
color: var(--accent);
background: rgba(255, 255, 255, 0.1);
}
/* Download progress inline */
.album-icon-btn.album-icon-btn--progress {
width: auto;
border-radius: 21px;
padding: 0 12px;
gap: 6px;
color: var(--text-muted);
cursor: default;
}
.album-icon-btn-pct {
font-size: 12px;
font-variant-numeric: tabular-nums;
}
/* ─── Album Tracklist — Mobile ─── */
.tracklist-mobile {
padding: 0 var(--space-4) var(--space-6);
}
.tracklist-mobile-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding: 12px 0;
border-bottom: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.06));
cursor: pointer;
transition: background var(--transition-fast);
border-radius: 6px;
}
.tracklist-mobile-row:last-child {
border-bottom: none;
}
.tracklist-mobile-row.active .tracklist-mobile-title {
color: var(--accent);
}
.tracklist-mobile-row.context-active {
background: var(--bg-hover);
}
.tracklist-mobile-main {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
flex: 1;
}
.tracklist-mobile-num {
font-size: 12px;
color: var(--text-muted);
min-width: 18px;
text-align: right;
flex-shrink: 0;
font-variant-numeric: tabular-nums;
}
.tracklist-mobile-eq {
flex-shrink: 0;
display: flex;
align-items: center;
min-width: 18px;
}
.tracklist-mobile-title {
font-size: 15px;
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tracklist-mobile-duration {
font-size: 13px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
/* ─── Most Played page ───────────────────────────────────────────────────────── */
.mp-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 0.75rem;
}
.mp-header-left {
display: flex;
align-items: center;
gap: 0.6rem;
}
.mp-header-icon {
color: var(--accent);
}
.mp-title {
font-size: clamp(22px, 2.5vw, 32px);
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.mp-sort-btn {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
}
.mp-section {
margin-bottom: 2.5rem;
}
.mp-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.mp-filter-btn {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
padding: 4px 10px;
opacity: 0.6;
}
.mp-filter-btn:hover { opacity: 1; }
.mp-filter-btn--active {
opacity: 1;
color: var(--accent);
border-color: var(--accent);
}
.mp-section-title {
font-size: 15px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 0;
margin: 0 0 1rem;
}
.mp-loading {
display: flex;
justify-content: center;
padding: 3rem;
}
/* ── Top Artists grid ── */
.mp-artist-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.6rem;
}
.mp-artist-card {
display: flex;
align-items: center;
gap: 0.65rem;
padding: 0.5rem 0.75rem;
border-radius: var(--radius);
background: var(--bg-card);
cursor: pointer;
text-align: left;
transition: background var(--transition-fast);
border: 1px solid transparent;
}
.mp-artist-card:hover {
background: var(--bg-hover);
border-color: var(--border);
}
.mp-rank {
font-size: 12px;
font-weight: 700;
color: var(--accent);
min-width: 18px;
text-align: right;
}
.mp-artist-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.mp-artist-avatar--placeholder {
background: var(--bg-surface);
}
.mp-artist-info {
display: flex;
flex-direction: column;
min-width: 0;
}
.mp-artist-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.mp-artist-plays {
font-size: 11px;
color: var(--text-muted);
margin-top: 1px;
}
/* ── Album list ── */
.mp-album-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.mp-album-row {
display: grid;
grid-template-columns: 28px 44px 1fr auto auto;
align-items: center;
gap: 0.75rem;
padding: 0.35rem 0.5rem;
border-radius: var(--radius-sm, 6px);
cursor: pointer;
transition: background var(--transition-fast);
}
.mp-album-row:hover {
background: var(--bg-hover);
}
.mp-album-rank {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
text-align: right;
font-variant-numeric: tabular-nums;
}
.mp-album-cover {
width: 44px;
height: 44px;
border-radius: 4px;
object-fit: cover;
flex-shrink: 0;
}
.mp-album-cover--placeholder {
background: var(--bg-surface);
}
.mp-album-meta {
display: flex;
flex-direction: column;
min-width: 0;
}
.mp-album-name {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
}
.mp-album-artist {
font-size: 12px;
color: var(--text-muted);
margin-top: 1px;
}
.mp-album-year {
font-size: 12px;
color: var(--text-muted);
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.mp-album-plays {
font-size: 13px;
font-weight: 600;
color: var(--accent);
white-space: nowrap;
font-variant-numeric: tabular-nums;
min-width: 36px;
text-align: right;
}
.mp-load-more {
margin-top: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* ─── UI Scale Slider ─── */
.ui-scale-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background: var(--border-subtle);
outline: none;
cursor: pointer;
}
.ui-scale-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.ui-scale-slider::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 20%, transparent);
}
/* ── Folder Browser (Miller Columns) ──────────────────────────────────────── */
.folder-browser {
display: flex;
flex-direction: column;
padding: 0;
height: 100%;
}
.folder-browser-title {
padding: 1.5rem 1.5rem 0.75rem;
flex-shrink: 0;
}
.folder-browser-columns {
flex: 1;
min-height: 0;
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
/* height fallback for browsers that don't support flex: 1 correctly here */
height: calc(100vh - var(--player-height, 88px) - var(--titlebar-height, 0px) - 70px);
border-top: 1px solid var(--border-subtle);
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--accent) 35%, var(--text-muted)) transparent;
}
.folder-browser-columns:focus,
.folder-browser-columns:focus-visible {
outline: none;
}
.folder-browser-columns::-webkit-scrollbar {
height: 8px;
}
.folder-browser-columns::-webkit-scrollbar-track {
background: transparent;
}
.folder-browser-columns::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--accent) 35%, var(--text-muted));
border-radius: 999px;
border: 2px solid transparent;
background-clip: content-box;
}
.folder-col {
flex: 1 1 220px;
min-width: 200px;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
border-right: 1px solid var(--border-subtle);
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--accent) 35%, var(--text-muted)) transparent;
transition: min-width 0.18s ease, max-width 0.18s ease, flex-basis 0.18s ease, flex-grow 0.18s ease;
}
.folder-browser-columns--compact .folder-col {
flex: 0 0 var(--folder-col-width, 220px);
min-width: var(--folder-col-width, 220px);
max-width: var(--folder-col-width, 220px);
}
.folder-col.folder-col--compact {
flex-basis: 56px;
min-width: 56px;
max-width: 56px;
}
.folder-col.folder-col--compact .folder-col-row {
justify-content: center;
gap: 0;
padding-left: 0.2rem;
padding-right: 0.2rem;
}
.folder-col.folder-col--compact .folder-col-name,
.folder-col.folder-col--compact .folder-col-chevron {
display: none;
}
.folder-col.folder-col--compact .folder-col-icon {
opacity: 0.95;
}
.folder-col::-webkit-scrollbar {
width: 8px;
}
.folder-col::-webkit-scrollbar-track {
background: transparent;
}
.folder-col::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--accent) 35%, var(--text-muted));
border-radius: 999px;
border: 2px solid transparent;
background-clip: content-box;
}
.folder-col::-webkit-scrollbar-thumb:hover {
background: color-mix(in srgb, var(--accent) 55%, var(--text-primary));
}
.folder-col-status {
display: flex;
align-items: center;
justify-content: center;
height: 80px;
color: var(--text-muted);
font-size: 0.8rem;
padding: 1rem;
text-align: center;
}
.folder-col-filter {
position: sticky;
top: 0;
z-index: 2;
padding: 0.35rem 0.45rem;
background: color-mix(in srgb, var(--bg-app) 92%, transparent);
border-bottom: 1px solid var(--border-subtle);
}
.folder-col-filter-input {
width: 100%;
padding: 0.35rem 0.5rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border-subtle);
background: var(--bg-card);
color: var(--text-primary);
font-size: 0.78rem;
}
.folder-col-filter-input:focus {
outline: none;
border-color: color-mix(in srgb, var(--accent) 70%, var(--border-subtle));
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
}
.folder-col.folder-col--compact .folder-col-filter {
display: none;
}
.folder-col-error {
color: var(--danger, #f38ba8);
}
.folder-col-row {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.38rem 0.6rem 0.38rem 0.75rem;
background: none;
border: none;
border-radius: 0;
cursor: pointer;
color: var(--text-primary);
font-size: 0.8rem;
text-align: left;
transition: background 0.1s;
}
.folder-col-row:hover {
background: var(--bg-hover, color-mix(in srgb, var(--accent) 8%, transparent));
}
.folder-browser-columns.keyboard-nav-active .folder-col-row:hover:not(.selected):not(.context-active):not(.keyboard-active) {
background: none;
}
/* Context-menu anchor only — does not replace Miller “open” selection (accent). */
.folder-col-row.context-active:not(.selected) {
background: var(--bg-hover, color-mix(in srgb, var(--accent) 8%, transparent));
}
.folder-col-row.keyboard-active:not(.selected) {
background: var(--bg-hover, color-mix(in srgb, var(--accent) 8%, transparent));
}
.folder-col-row.selected {
background: var(--accent);
color: oklch(from var(--accent) clamp(0, (0.58 - l) * 100, 1) 0 h);
}
.folder-col-row.selected .folder-col-chevron {
color: oklch(from var(--accent) clamp(0, (0.58 - l) * 100, 1) 0 h / 0.7);
}
.folder-col-icon {
flex-shrink: 0;
opacity: 0.75;
display: flex;
align-items: center;
}
.folder-col-row.selected .folder-col-icon {
opacity: 1;
}
.folder-col-row.now-playing .folder-col-name {
font-weight: 600;
}
.folder-col-playing-icon {
animation: folderTrackPulse 1.2s ease-in-out infinite;
transform-origin: center;
}
.folder-col-path-playing-icon {
animation: folderTrackPulse 1.2s ease-in-out infinite;
transform-origin: center;
}
.folder-col-path-playing-icon svg {
stroke-width: 2.6;
filter: saturate(1.08) contrast(1.06);
}
@keyframes folderTrackPulse {
0% { transform: scale(1); opacity: 0.92; }
50% { transform: scale(1.16); opacity: 1; }
100% { transform: scale(1); opacity: 0.92; }
}
.folder-col-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.folder-col-chevron {
flex-shrink: 0;
color: var(--text-muted);
margin-left: auto;
}
/* ─── ZIP Download Overlay ─── */
.zip-dl-overlay {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 9000;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: none;
}
.zip-dl-item {
pointer-events: all;
width: 280px;
padding: 10px 12px;
border-radius: var(--radius-md);
background: var(--bg-sidebar);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
display: flex;
flex-direction: column;
gap: 6px;
animation: zip-dl-slide-in 0.2s ease;
}
@keyframes zip-dl-slide-in {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.zip-dl-item.zip-dl-done {
border-color: color-mix(in srgb, var(--accent) 20%, transparent);
opacity: 0.8;
}
.zip-dl-item.zip-dl-error {
border-color: color-mix(in srgb, var(--danger, #e05555) 40%, transparent);
opacity: 0.85;
}
.zip-dl-header {
display: flex;
align-items: center;
gap: 7px;
color: var(--accent);
font-size: 12px;
}
.zip-dl-item.zip-dl-error .zip-dl-header {
color: var(--danger, #e05555);
}
.zip-dl-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-primary);
font-size: 12px;
}
.zip-dl-close {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
padding: 0;
border-radius: 50%;
transition: color 0.15s, background 0.15s;
}
.zip-dl-close:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.zip-dl-info {
font-size: 11px;
color: var(--text-muted);
padding-left: 20px;
}
.zip-dl-track {
height: 3px;
border-radius: 2px;
background: var(--bg-hover);
overflow: hidden;
position: relative;
}
.zip-dl-fill {
height: 100%;
border-radius: 2px;
background: var(--accent);
transition: width 0.4s linear;
}
/* Indeterminate sliding animation */
.zip-dl-indeterminate::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 40%;
border-radius: 2px;
background: var(--accent);
animation: zip-dl-shimmer 1.4s ease-in-out infinite;
}
@keyframes zip-dl-shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(350%); }
}
/* ─── Device Sync ─────────────────────────────────────────────────────────── */
.device-sync-page {
display: flex;
flex-direction: column;
height: 100%;
padding: 24px 28px;
overflow: hidden;
}
/* ── Header ── */
.device-sync-header {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 20px;
flex-shrink: 0;
}
.device-sync-header-title {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
}
.device-sync-header-title h1 {
font-size: 1.3rem;
font-weight: 600;
margin: 0;
}
/* ── Config Row (Template + Drive Layout) ── */
.device-sync-config-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
gap: 30px;
min-height: 0;
width: 100%;
}
.device-sync-template-section {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
max-width: 600px;
}
.device-sync-schema-section {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
max-width: 600px;
}
.device-sync-schema-code {
display: inline-block;
background: var(--bg-glass);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 6px 10px;
font-size: 12px;
color: var(--accent);
font-family: monospace;
user-select: all;
align-self: flex-start;
}
.device-sync-schema-hint {
font-size: 11px;
color: var(--text-muted);
line-height: 1.5;
max-width: 520px;
}
.device-sync-migrate-btn {
align-self: flex-start;
font-size: 11px;
padding: 4px 10px;
margin-top: 2px;
}
/* ── Migration modal ──────────────────────────────────────────────────── */
.device-sync-migrate-modal {
min-width: 440px;
max-width: 560px;
}
.device-sync-migrate-body {
display: flex;
flex-direction: column;
gap: 12px;
margin: 12px 0;
}
.device-sync-migrate-loading {
display: flex;
align-items: center;
gap: 10px;
padding: 16px 4px;
color: var(--text-secondary);
font-size: 13px;
}
.device-sync-migrate-nothing {
padding: 12px 4px;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.5;
}
.device-sync-migrate-summary {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
}
.device-sync-migrate-summary .muted { color: var(--text-muted); font-size: 12px; }
.device-sync-migrate-warning {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 10px;
background: var(--accent-dim);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
color: var(--warning, var(--accent));
font-size: 12px;
line-height: 1.4;
}
.device-sync-migrate-warning svg { flex-shrink: 0; margin-top: 2px; }
.device-sync-migrate-preview-note {
font-size: 11px;
color: var(--text-muted);
font-family: monospace;
padding: 6px 10px;
background: var(--bg-glass);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
word-break: break-all;
}
.device-sync-migrate-result { display: flex; flex-direction: column; gap: 6px; }
.device-sync-migrate-result-line {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.device-sync-migrate-result-line .positive { color: var(--positive, var(--accent)); }
.device-sync-migrate-result-line .danger { color: var(--danger); }
.device-sync-migrate-errors {
font-size: 11px;
color: var(--text-muted);
margin-top: 6px;
}
.device-sync-migrate-errors summary { cursor: pointer; color: var(--accent); }
.device-sync-migrate-errors ul { max-height: 180px; overflow-y: auto; padding-left: 18px; margin: 6px 0 0; }
.device-sync-migrate-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 10px;
}
.device-sync-target-section {
display: flex;
flex-direction: column;
gap: 6px;
align-items: flex-end; /* Flush right alignment */
flex: 1; /* allow block expansion */
}
.device-sync-folder-path {
max-width: 260px;
font-size: 0.8rem;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 5px 8px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
}
/* ── Drive layout ── */
.device-sync-drive-layout {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.device-sync-drive-controls {
display: flex;
align-items: center;
gap: 8px;
}
.device-sync-drive-meta {
font-size: 0.75rem;
color: var(--text-muted);
text-align: right;
padding-right: 2px;
}
.device-sync-drive-icon {
color: var(--accent);
flex-shrink: 0;
}
.device-sync-drive-select {
min-width: 200px;
max-width: 320px;
font-size: 0.95rem;
height: 40px;
padding: 0 10px;
line-height: normal;
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
cursor: pointer;
}
.device-sync-drive-controls .btn-ghost {
height: 40px;
width: 40px;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.device-sync-no-drives {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.78rem;
color: var(--warning, #f59e0b);
padding: 5px 10px;
background: color-mix(in srgb, var(--warning, #f59e0b) 10%, transparent);
border-radius: var(--radius-sm);
}
.device-sync-label-inline {
font-size: 0.8rem;
font-weight: 500;
color: var(--text-secondary);
white-space: nowrap;
}
.device-sync-template-presets {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.device-sync-template-preset-btn {
padding: 4px 10px;
font-size: 0.78rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-input);
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.device-sync-template-preset-btn:hover {
background: var(--ctp-surface0);
color: var(--text-primary);
}
.device-sync-template-preset-btn.active {
border-color: var(--accent);
color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.device-sync-template-input-wrap {
display: flex;
flex-direction: column;
gap: 6px;
position: relative;
}
.device-sync-template-input-row {
position: relative;
display: flex;
align-items: center;
}
.device-sync-template-input {
width: 100%;
font-family: monospace;
font-size: 0.95rem;
height: 40px;
padding: 0 32px 0 10px;
line-height: normal;
}
.device-sync-template-clear {
position: absolute;
right: 8px;
background: none;
border: none;
cursor: pointer;
color: var(--text-muted);
display: flex;
align-items: center;
padding: 2px;
border-radius: var(--radius-sm);
transition: color var(--transition-fast);
}
.device-sync-template-clear:hover {
color: var(--text-primary);
}
.device-sync-template-tokens {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.device-sync-template-token {
padding: 2px 7px;
font-size: 0.75rem;
font-family: monospace;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--ctp-surface0);
color: var(--text-secondary);
cursor: pointer;
transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
line-height: 1.6;
}
.device-sync-template-token:hover {
background: color-mix(in srgb, var(--accent) 15%, var(--ctp-surface0));
border-color: var(--accent);
color: var(--accent);
}
.device-sync-template-preview {
font-size: 0.72rem;
color: var(--text-muted);
font-family: monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 2px;
opacity: 0.75;
margin-bottom: 10px;
}
/* ── Main area (device panel + browser) ── */
.device-sync-main {
display: flex;
flex: 1;
min-height: 0;
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
/* ── Device panel ── */
.device-sync-device-panel {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
background: var(--bg-card);
overflow: hidden;
}
.device-sync-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
height: 52px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.device-sync-panel-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-primary);
}
.device-sync-panel-actions {
display: flex;
align-items: center;
gap: 6px;
}
.device-sync-add-btn {
display: flex;
align-items: center;
gap: 5px;
}
.device-sync-empty {
font-size: 0.82rem;
color: var(--text-secondary);
padding: 20px 14px;
margin: 0;
}
/* ── Device list ── */
.device-sync-list-header {
display: flex;
align-items: center;
gap: 8px;
height: 52px;
padding: 0 14px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.device-sync-check-label {
display: flex;
align-items: center;
flex-shrink: 0;
cursor: pointer;
}
.device-sync-list-col-name {
flex: 1;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.device-sync-list-col-type {
width: 70px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
flex-shrink: 0;
}
.device-sync-device-list {
flex: 1;
overflow-y: auto;
}
.device-sync-device-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
cursor: pointer;
transition: background 0.1s;
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
color: var(--text-primary);
}
.device-sync-device-row:last-child { border-bottom: none; }
.device-sync-source-row:hover {
background: var(--bg-hover);
}
.device-sync-source-row.deletion, .device-sync-row.deletion {
text-decoration: line-through;
opacity: 0.5;
}
.device-sync-row-name {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.device-sync-row-artist {
color: var(--text-muted);
font-weight: 400;
margin-left: 0.2em;
}
.device-sync-device-row.checked { background: var(--accent-dim); }
.device-sync-source-type {
width: 70px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--accent);
flex-shrink: 0;
}
/* ── Status summary badges ── */
.device-sync-status-summary {
display: flex;
align-items: center;
gap: 8px;
height: 52px;
padding: 0 14px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.device-sync-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.02em;
}
.device-sync-badge.synced {
background: color-mix(in srgb, var(--success, #4ade80) 15%, transparent);
color: var(--success, #4ade80);
}
.device-sync-badge.pending {
background: color-mix(in srgb, var(--warning, #f59e0b) 15%, transparent);
color: var(--warning, #f59e0b);
}
.device-sync-badge.deletion {
background: color-mix(in srgb, var(--danger, #f38ba8) 15%, transparent);
color: var(--danger, #f38ba8);
}
/* ── Status column ── */
.device-sync-list-col-status {
width: 50px;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
flex-shrink: 0;
text-align: center;
}
.device-sync-list-col-actions {
width: 32px;
flex-shrink: 0;
}
/* ── Status icons per row ── */
.device-sync-status-icon {
width: 50px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.device-sync-status-icon.synced { color: var(--success, #4ade80); }
.device-sync-status-icon.pending { color: var(--warning, #f59e0b); }
.device-sync-status-icon.deletion { color: var(--danger, #f38ba8); }
/* ── Per-row action buttons ── */
.device-sync-row-actions {
width: 32px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.device-sync-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: transparent;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.15s, color 0.15s, transform 0.1s;
opacity: 0;
}
.device-sync-device-row:hover .device-sync-action-btn {
opacity: 1;
}
.device-sync-action-btn.danger {
color: var(--danger, #f38ba8);
}
.device-sync-action-btn.danger:hover {
background: color-mix(in srgb, var(--danger, #f38ba8) 15%, transparent);
transform: scale(1.1);
}
.device-sync-action-btn.muted {
color: var(--text-muted);
}
.device-sync-action-btn.muted:hover {
background: var(--bg-hover);
color: var(--text-primary);
transform: scale(1.1);
}
.device-sync-action-btn.undo {
color: var(--accent);
opacity: 1;
}
.device-sync-action-btn.undo:hover {
background: color-mix(in srgb, var(--accent) 15%, transparent);
transform: scale(1.1);
}
/* ── Row status variants ── */
.device-sync-device-row.synced {}
.device-sync-device-row.pending {}
.device-sync-device-row.deletion {
opacity: 0.55;
}
.device-sync-device-row.deletion .device-sync-row-name {
text-decoration: line-through;
text-decoration-color: var(--danger, #f38ba8);
}
/* ── Background sync progress (non-blocking) ── */
.device-sync-bg-progress {
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px 14px;
border-top: 1px solid var(--border);
flex-shrink: 0;
background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.device-sync-bg-progress.done {
flex-direction: row;
align-items: center;
justify-content: space-between;
background: color-mix(in srgb, var(--success, #4ade80) 4%, transparent);
}
.device-sync-bg-progress-bar-wrap {
height: 3px;
background: var(--bg-input);
border-radius: 2px;
overflow: hidden;
}
.device-sync-bg-progress-bar {
height: 100%;
background: var(--accent);
border-radius: 2px;
transition: width 0.3s ease;
}
.device-sync-bg-progress-text {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.78rem;
color: var(--text-secondary);
}
.device-sync-stat-error { color: var(--danger); display: flex; align-items: center; gap: 3px; }
.color-success { color: var(--success, #4ade80); }
/* ── Browser panel ── */
.device-sync-browser {
display: flex;
flex-direction: column;
flex: 1;
background: var(--bg-card);
overflow: hidden;
}
.device-sync-tabs {
display: flex;
height: 52px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.device-sync-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 0 8px;
font-size: 0.8rem;
font-weight: 500;
color: var(--text-secondary);
background: transparent;
border: none;
cursor: pointer;
transition: color 0.15s, background 0.15s;
}
.device-sync-tab:hover { color: var(--text-primary); background: var(--bg-hover); }
.device-sync-tab.active { color: var(--accent); border-bottom: 2px solid var(--accent); }
.device-sync-search-wrap {
display: flex;
align-items: center;
height: 52px;
padding: 0 10px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.device-sync-search-wrap .input {
flex: 1;
}
.device-sync-live-badge {
display: flex;
align-items: center;
gap: 3px;
flex-shrink: 0;
margin-left: 8px;
padding: 2px 7px;
border-radius: 999px;
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.03em;
color: var(--accent);
background: var(--accent-dim);
white-space: nowrap;
}
.device-sync-section-label {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 10px 4px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-muted);
}
.device-sync-list {
flex: 1;
overflow-y: auto;
}
.device-sync-loading {
display: flex;
justify-content: center;
padding: 24px;
color: var(--text-secondary);
}
.device-sync-browser-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
text-align: left;
background: transparent;
border: none;
cursor: pointer;
transition: background 0.12s;
color: var(--text-primary);
}
.device-sync-browser-row:hover { background: var(--bg-hover); }
.device-sync-browser-row.selected { background: var(--accent-dim); }
.device-sync-browser-row.indent { padding-left: 36px; background: var(--bg-secondary); }
.device-sync-browser-row.indent:hover { background: var(--bg-hover); }
.device-sync-browser-row.indent.selected { background: var(--accent-dim); }
.device-sync-artist-row {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
font-size: 0.85rem;
color: var(--text-primary);
border-bottom: 1px solid var(--border);
}
.device-sync-expand-btn {
display: flex;
align-items: center;
background: transparent;
border: none;
cursor: pointer;
color: var(--text-secondary);
padding: 2px;
flex-shrink: 0;
transition: color 0.12s;
}
.device-sync-expand-btn:hover { color: var(--text-primary); }
.device-sync-row-check { color: var(--accent); display: flex; flex-shrink: 0; }
.device-sync-row-circle {
width: 15px;
height: 15px;
border-radius: 50%;
border: 1.5px solid var(--border);
display: block;
}
.device-sync-row-name { flex: 1; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.device-sync-row-meta { font-size: 0.75rem; color: var(--text-secondary); flex-shrink: 0; }
/* ── Pause CSS animations when the window is hidden / minimized ────────────
- `data-app-hidden`: App.tsx `visibilitychange` → `document.hidden` (all platforms).
- `data-psy-native-hidden`: Rust inject on Tauri `win.hide()` / show (WebView2
often keeps `document.hidden === false` when the native window is hidden).
Either flag pauses infinite animations including portaled nodes and ::pseudo,
without touching Rust/audio threads. */
/* ─ What's New — banner + page ────────────────────────────────────────────
Fixed neutral palette so it reads identically on every theme (light and
dark). The banner sits in the sidebar just above Now Playing; the page
opens in the main content area. */
.sidebar-bottom-spacer {
margin-top: auto;
flex-shrink: 0;
}
.whats-new-banner {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
margin: 0 0 6px 0;
background: #1f2233;
color: #dfe3f6;
border: 1px solid #3a3f5a;
border-radius: 8px;
font: inherit;
font-size: 12px;
text-align: left;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
position: relative;
}
.whats-new-banner:hover {
background: #272b40;
border-color: #4a5072;
}
.whats-new-banner__icon {
flex-shrink: 0;
color: #8ec8ff;
}
.whats-new-banner__text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
line-height: 1.25;
}
.whats-new-banner__title {
font-weight: 600;
color: #f1f3fb;
}
.whats-new-banner__version {
font-size: 10.5px;
color: #9aa0bd;
font-variant-numeric: tabular-nums;
}
.whats-new-banner__dismiss {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 4px;
color: #9aa0bd;
cursor: pointer;
}
.whats-new-banner__dismiss:hover {
background: rgba(255, 255, 255, 0.08);
color: #e4e7f5;
}
.whats-new-banner--collapsed {
width: 34px;
height: 34px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 6px;
}
/* ─── Page ─── */
.whats-new {
max-width: 880px;
margin: 0 auto;
padding: 28px 32px 48px;
color: #e4e7f5;
background: #16182a;
border-radius: 12px;
box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 8px 24px rgba(0,0,0,0.25);
border: 1px solid #2a2d44;
}
.whats-new__header {
padding-bottom: 16px;
margin-bottom: 20px;
border-bottom: 1px solid #2a2d44;
}
.whats-new__title-row {
display: flex;
align-items: center;
gap: 12px;
}
.whats-new__close {
margin-left: auto;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
border: 1px solid #3a3f5a;
background: transparent;
color: #9aa0bd;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.whats-new__close:hover {
background: rgba(255,255,255,0.06);
color: #e4e7f5;
border-color: #4a5072;
}
.whats-new__icon {
color: #8ec8ff;
flex-shrink: 0;
}
.whats-new__title {
font-size: 22px;
font-weight: 700;
margin: 0;
color: #f1f3fb;
line-height: 1.2;
}
.whats-new__subtitle {
font-size: 13px;
color: #9aa0bd;
margin-top: 2px;
}
.whats-new__date {
font-variant-numeric: tabular-nums;
}
.whats-new__body {
font-size: 14px;
line-height: 1.65;
}
.whats-new__empty {
color: #9aa0bd;
font-style: italic;
}
.whats-new-h3 {
font-size: 16px;
font-weight: 700;
color: #f1f3fb;
margin: 22px 0 10px;
padding-bottom: 6px;
border-bottom: 1px solid #2a2d44;
}
.whats-new-h4 {
font-size: 14px;
font-weight: 700;
color: #dfe3f6;
margin: 14px 0 6px;
}
.whats-new-p {
margin: 6px 0;
}
.whats-new-list {
list-style: disc;
padding-left: 22px;
margin: 6px 0 10px;
}
.whats-new-list li {
margin-bottom: 8px;
padding-left: 2px;
}
.whats-new-list li::marker {
color: #6b7192;
}
.whats-new-quote {
border-left: 3px solid #3a3f5a;
padding: 6px 12px;
margin: 10px 0;
color: #b6bcd8;
background: rgba(255,255,255,0.02);
border-radius: 0 4px 4px 0;
}
.whats-new-hr {
border: none;
border-top: 1px solid #2a2d44;
margin: 18px 0;
}
.whats-new-code {
background: #262a3e;
color: #d4d7f0;
padding: 1px 5px;
border-radius: 3px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 12.5px;
}
.whats-new-link {
color: #8ec8ff;
text-decoration: none;
border-bottom: 1px dashed #4a5072;
cursor: pointer;
}
.whats-new-link:hover {
color: #b6dbff;
border-bottom-color: #8ec8ff;
}
/* ─ Mini Player window ───────────────────────────────────────────────────── */
/* ── Mini player shell ── outermost wrapper that fills the window ── */
.mini-player-shell {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-app);
color: var(--text-primary);
user-select: none;
-webkit-user-select: none;
overflow: hidden;
box-sizing: border-box;
}
/* Belt-and-suspenders: WebKit/WebView2 occasionally lets descendants pick up
user-select: auto from UA defaults (especially inside <button>). Force it
off for every node in the mini so neither mouse-drag nor Ctrl+A can ever
highlight track meta or titlebar text. */
.mini-player-shell,
.mini-player-shell * {
user-select: none;
-webkit-user-select: none;
}
/* ── Custom in-page titlebar.
Win/Linux: full bar with drag region, track title, and action buttons.
macOS: slim transparent bar holding only the action buttons; the
native titlebar above already shows title + close. ── */
.mini-player__titlebar {
flex-shrink: 0;
height: 26px;
display: flex;
align-items: center;
gap: 6px;
padding: 0 4px 0 10px;
background: color-mix(in srgb, var(--bg-app) 85%, var(--bg-card) 15%);
border-bottom: 1px solid var(--border-subtle);
}
.mini-player__titlebar--mac {
height: 22px;
padding: 0 6px;
background: transparent;
border-bottom: none;
justify-content: flex-end;
}
.mini-player__titlebar-title {
flex: 1;
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: 0.02em;
}
.mini-player__titlebar-spacer {
flex: 1;
}
.mini-player__titlebar-btn {
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--text-muted);
border-radius: 4px;
cursor: pointer;
transition: background 0.12s, color 0.12s;
}
.mini-player__titlebar-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.mini-player__titlebar-btn--active {
color: var(--accent);
}
.mini-player__titlebar-btn--close:hover {
background: var(--danger);
color: var(--ctp-base);
}
.mini-player {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 12px;
overflow: hidden;
box-sizing: border-box;
min-height: 0;
}
.mini-player__meta {
display: flex;
gap: 12px;
align-items: stretch;
flex-shrink: 0;
}
.mini-player__art {
flex: 0 0 84px;
width: 84px;
height: 84px;
aspect-ratio: 1;
background: var(--bg-card);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}
.mini-player__art img,
.mini-player__art-fallback {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.mini-player__art-fallback {
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-card));
}
.mini-player__meta-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
}
.mini-player__title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mini-player__artist,
.mini-player__album,
.mini-player__year {
font-size: 11px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mini-player__toolbar {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 4px 0 10px;
flex-shrink: 0;
border-bottom: 1px solid var(--border-subtle);
}
.mini-player__toolbar-sep {
width: 1px;
height: 18px;
background: var(--border-subtle);
margin: 0 2px;
flex-shrink: 0;
}
.mini-player__volume-wrap {
position: relative;
display: inline-flex;
}
.mini-player__volume-popover {
/* Positioned via inline `style` (createPortal'd to document.body, so
`position: fixed` + computed left/top from the trigger button's rect).
Falls back gracefully if inline style is missing. */
position: fixed;
z-index: 99998;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: 8px;
padding: 10px 6px 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
}
.mini-player__volume-pct {
font-size: 10px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
min-width: 28px;
text-align: center;
}
.mini-player__volume-bar {
position: relative;
width: 5px;
height: 110px;
background: var(--ctp-surface1);
border-radius: 3px;
overflow: hidden;
cursor: pointer;
/* Visual hit-box wider than the strip so dragging is forgiving. */
padding: 0 6px;
background-clip: content-box;
box-sizing: content-box;
}
.mini-player__volume-bar-fill {
position: absolute;
bottom: 0;
left: 6px;
width: 5px;
background: var(--volume-accent, var(--accent));
border-radius: 3px;
pointer-events: none;
}
.mini-player__bottom {
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 8px;
/* When the queue is closed there is no flex-grow sibling to fill the
extra space — push the bottom to the actual bottom of the player so
the controls + progress always sit on the floor. When the queue IS
open, queue takes flex: 1 and this margin collapses to 0. */
margin-top: auto;
}
.mini-player__controls {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.mini-player__btn {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
background: transparent;
color: var(--text-primary);
cursor: pointer;
transition: background 0.12s;
}
.mini-player__btn:hover {
background: var(--bg-hover);
}
.mini-player__btn--primary {
width: 36px;
height: 36px;
background: var(--accent);
color: var(--ctp-crust);
}
.mini-player__btn--primary:hover {
background: var(--accent);
filter: brightness(1.08);
}
.mini-player__progress {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
color: var(--text-muted);
}
.mini-player__progress-time {
font-variant-numeric: tabular-nums;
min-width: 30px;
}
.mini-player__progress-track {
flex: 1;
height: 3px;
border-radius: 2px;
background: var(--ctp-surface1);
overflow: hidden;
}
.mini-player__progress-fill {
height: 100%;
background: var(--accent);
transition: width 0.25s linear;
}
.mini-queue-wrap.overlay-scroll {
flex: 1 1 auto;
background: var(--bg-card);
border-radius: 8px;
margin-top: 10px;
}
.mini-queue.overlay-scroll__viewport {
padding: 4px;
display: flex;
flex-direction: column;
gap: 1px;
box-sizing: border-box;
}
.mini-queue__empty {
padding: 16px;
text-align: center;
color: var(--text-muted);
font-size: 12px;
}
.mini-queue__item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 8px;
border: none;
background: transparent;
color: var(--text-primary);
text-align: left;
border-radius: 4px;
cursor: pointer;
min-width: 0;
}
.mini-queue__item:hover {
background: var(--bg-hover);
}
.mini-queue__item--ctx {
background: var(--bg-hover);
}
.mini-queue__item--current {
background: var(--accent-dim);
color: var(--accent);
}
.mini-queue__num {
flex-shrink: 0;
width: 20px;
font-size: 10px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
text-align: right;
}
.mini-queue__item--current .mini-queue__num {
color: var(--accent);
font-weight: 600;
}
.mini-queue__meta {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
}
.mini-queue__title {
font-size: 12px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mini-queue__artist {
font-size: 10px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mini-queue__item--current .mini-queue__artist {
color: var(--accent);
opacity: 0.8;
}
.mini-player__tool {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
background: var(--bg-hover);
color: var(--text-primary);
cursor: pointer;
flex-shrink: 0;
transition: background var(--transition-fast), color var(--transition-fast);
}
.mini-player__tool:hover:not(:disabled) {
background: var(--border);
color: var(--accent);
}
.mini-player__tool:disabled {
opacity: 0.35;
cursor: default;
}
.mini-player__tool--active {
color: var(--ctp-base);
background: var(--accent);
}
.mini-player__tool--active:hover:not(:disabled) {
color: var(--ctp-base);
background: var(--accent);
}
html[data-app-hidden="true"] *,
html[data-app-hidden="true"] *::before,
html[data-app-hidden="true"] *::after,
html[data-psy-native-hidden="true"] *,
html[data-psy-native-hidden="true"] *::before,
html[data-psy-native-hidden="true"] *::after {
animation-play-state: paused !important;
}
/* ── Now Playing Info panel ──────────────────────────────────────────────── */
.np-info {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 6px 16px 20px;
display: block;
color: var(--text-primary);
text-align: left;
min-width: 0;
}
.np-info-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
color: var(--text-muted);
font-size: 13px;
text-align: center;
}
.np-info-section {
display: block;
padding: 18px 0;
border-bottom: 1px solid color-mix(in srgb, var(--border-subtle, var(--ctp-surface0)) 70%, transparent);
}
.np-info-section > * + * { margin-top: 12px; }
.np-info-section:last-child {
border-bottom: none;
padding-bottom: 4px;
}
.np-info-section:first-child {
padding-top: 10px;
}
.np-info-section-title {
font-size: 10.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
text-align: left;
}
/* Artist card */
.np-info-artist-image-wrap {
width: 100%;
aspect-ratio: 16 / 10;
border-radius: 12px;
overflow: hidden;
background: var(--bg-elevated);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.np-info-artist-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.np-info-artist-body {
display: block;
}
.np-info-artist-body > * + * { margin-top: 8px; }
.np-info-artist-name {
font-size: 22px;
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.01em;
text-align: left;
}
.np-info-artist-bio {
margin: 0;
font-size: 13px;
line-height: 1.55;
color: var(--text-secondary);
text-align: left;
}
.np-info-artist-bio.is-clamped {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.np-info-link-btn {
align-self: flex-start;
background: none;
border: none;
padding: 4px 0;
margin: -2px 0 0;
font-size: 12px;
font-weight: 600;
color: var(--accent);
cursor: pointer;
text-align: left;
}
.np-info-link-btn:hover { text-decoration: underline; }
/* Credits / contributors — stacked: name prominent, role muted underneath */
.np-info-credits {
list-style: none;
margin: 0;
padding: 0;
display: block;
}
.np-info-credit-row {
display: block;
padding: 0;
text-align: left;
}
.np-info-credit-row + .np-info-credit-row { margin-top: 10px; }
.np-info-credit-names {
display: block;
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
text-align: left;
line-height: 1.3;
}
.np-info-credit-role {
display: block;
font-size: 11.5px;
color: var(--text-muted);
text-transform: capitalize;
text-align: left;
margin-top: 1px;
}
/* Tour */
.np-info-tour {
list-style: none;
margin: 0;
padding: 0;
display: block;
}
.np-info-tour > .np-info-tour-item + .np-info-tour-item { margin-top: 6px; }
.np-info-tour-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: var(--bg-elevated);
border-radius: 10px;
cursor: pointer;
transition: background 0.15s;
border: none;
text-align: left;
width: 100%;
min-width: 0; /* allow children to shrink past their min-content */
box-sizing: border-box;
overflow: hidden;
}
.np-info-tour-item:hover {
background: color-mix(in srgb, var(--accent) 10%, var(--bg-elevated));
}
.np-info-tour-date {
flex-shrink: 0;
width: 48px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 0 5px;
border-radius: 8px;
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--accent);
line-height: 1;
}
.np-info-tour-date-month {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.np-info-tour-date-day {
font-size: 19px;
font-weight: 700;
margin-top: 3px;
font-variant-numeric: tabular-nums;
}
.np-info-tour-meta {
/* `flex: 1 1 0` + `min-width: 0` is the canonical fix for the flex
* truncation bug — without `flex-basis: 0` the meta column inherits
* its intrinsic width from the longest venue/place text, which then
* overflows the parent ul on WebKit. */
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
overflow: hidden;
}
.np-info-tour-venue {
font-size: 13.5px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.np-info-tour-place {
font-size: 11.5px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.np-info-tour-when { color: var(--text-secondary); }
.np-info-tour-sep { opacity: 0.55; margin: 0 2px; }
.np-info-tour-empty {
font-size: 13px;
color: var(--text-muted);
padding: 8px 0;
text-align: left;
}
.np-info-tour-credit {
font-size: 10.5px;
color: var(--text-muted);
margin-top: 10px;
text-align: right;
opacity: 0.55;
letter-spacing: 0.02em;
}
.np-info-tour-more {
align-self: center;
margin-top: 4px;
padding: 6px 14px;
background: none;
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
border-radius: 999px;
color: var(--accent);
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
.np-info-tour-more:hover {
background: color-mix(in srgb, var(--accent) 12%, transparent);
border-color: var(--accent);
}
/* Bandsintown opt-in prompt — shown in place of tour list when toggle is off */
.np-info-bandsintown-prompt {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px 16px;
border-radius: 12px;
background: color-mix(in srgb, var(--accent) 8%, var(--bg-elevated));
border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
.np-info-bandsintown-prompt-title {
display: flex;
align-items: center;
gap: 6px;
font-size: 13.5px;
font-weight: 600;
color: var(--text-primary);
}
.np-info-bandsintown-prompt-info {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
cursor: help;
border-radius: 50%;
padding: 2px;
transition: color 0.15s, background 0.15s;
}
.np-info-bandsintown-prompt-info:hover,
.np-info-bandsintown-prompt-info:focus-visible {
color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
outline: none;
}
.np-info-bandsintown-prompt-desc {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.45;
}
.np-info-bandsintown-prompt-btn {
align-self: flex-start;
margin-top: 6px;
padding: 6px 16px;
background: var(--accent);
border: none;
border-radius: 999px;
color: var(--ctp-base, #1e1e2e);
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
transition: filter 0.12s;
}
.np-info-bandsintown-prompt-btn:hover { filter: brightness(1.08); }
/* ────────────────────────────────────────────────────────────────────────── */
/* Now Playing Dashboard (full-page redesign) */
/* ────────────────────────────────────────────────────────────────────────── */
.np-dash {
display: flex;
flex-direction: column;
gap: 18px;
min-width: 0;
}
.np-dash-hero {
display: grid;
grid-template-columns: auto 1fr;
gap: 28px;
padding: 28px;
background: rgba(0, 0, 0, 0.30);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: var(--radius-lg);
border: 1px solid rgba(255, 255, 255, 0.08);
align-items: center;
}
.np-dash-hero-cover {
width: 260px;
height: 260px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
}
.np-dash-hero-cover .np-cover {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.np-dash-hero-body {
min-width: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.np-dash-hero-title {
font-size: 30px;
font-weight: 700;
letter-spacing: -0.015em;
line-height: 1.15;
color: var(--accent);
overflow-wrap: anywhere;
}
.np-dash-hero-sub {
font-size: 15px;
color: rgba(255, 255, 255, 0.78);
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: baseline;
}
.np-dash-hero-sub .np-link { color: inherit; }
.np-dash-hero-sub .np-link:hover { color: var(--accent); }
.np-dash-hero-badges {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.np-badge-hires {
background: color-mix(in srgb, var(--ctp-yellow) 30%, transparent);
color: var(--ctp-yellow);
font-weight: 700;
letter-spacing: 0.05em;
}
.np-dash-hero-actions {
display: flex;
align-items: center;
gap: 8px;
margin-top: 2px;
}
.np-dash-icon-btn {
background: none;
border: none;
cursor: pointer;
padding: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.65);
border-radius: var(--radius-sm);
transition: color 0.15s, background 0.15s;
}
.np-dash-icon-btn:hover {
color: var(--accent);
background: rgba(255, 255, 255, 0.06);
}
.np-dash-lfm-btn.is-loved {
color: var(--accent);
}
.np-stars-inline {
display: inline-flex;
gap: 3px;
align-items: center;
margin-left: 4px;
}
.np-dash-hero-stat {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
opacity: 0.85;
}
/* Last.fm stats pulled up into the hero */
.np-dash-hero-lfm {
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 8px;
padding: 12px 14px;
background: rgba(255, 255, 255, 0.04);
border-radius: var(--radius-md);
border-left: 3px solid var(--accent);
}
.np-dash-hero-lfm-heading {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 2px;
}
.np-dash-hero-lfm-badge {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
}
.np-dash-hero-lfm-row {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 6px;
font-size: 13px;
color: rgba(255, 255, 255, 0.78);
font-variant-numeric: tabular-nums;
}
.np-dash-hero-lfm-scope {
font-weight: 600;
color: rgba(255, 255, 255, 0.92);
}
.np-dash-hero-lfm-sep {
color: rgba(255, 255, 255, 0.35);
padding: 0 2px;
}
.np-dash-hero-lfm-dot {
color: rgba(255, 255, 255, 0.30);
padding: 0 3px;
}
.np-dash-hero-lfm-you {
color: var(--accent);
font-weight: 600;
}
/* Widget layout toolbar (layout / hidden-cards menu) */
.np-dash-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: -4px;
}
.np-dash-toolbar-menu-wrap {
position: relative;
}
.np-dash-toolbar-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius-sm);
padding: 6px 10px;
font-size: 12px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.np-dash-toolbar-btn:hover {
background: rgba(255, 255, 255, 0.10);
color: var(--accent);
}
.np-dash-toolbar-badge {
background: var(--accent);
color: var(--ctp-base, #1e1e2e);
font-size: 10px;
font-weight: 700;
padding: 1px 6px;
border-radius: 999px;
min-width: 14px;
text-align: center;
line-height: 1.3;
}
.np-dash-toolbar-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 220px;
background: var(--ctp-base, #1e1e2e);
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: var(--radius-md);
padding: 6px;
z-index: 50;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.np-dash-toolbar-section {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(255, 255, 255, 0.45);
padding: 6px 8px 4px;
}
.np-dash-toolbar-empty {
padding: 8px 10px;
font-size: 12px;
color: rgba(255, 255, 255, 0.50);
font-style: italic;
}
.np-dash-toolbar-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
background: none;
border: none;
cursor: pointer;
color: rgba(255, 255, 255, 0.85);
font-size: 12.5px;
padding: 7px 10px;
border-radius: var(--radius-sm);
text-align: left;
transition: background 0.1s;
}
.np-dash-toolbar-item:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--accent);
}
.np-dash-toolbar-divider {
height: 1px;
margin: 4px 0;
background: rgba(255, 255, 255, 0.08);
}
/* Widget wrapper — makes each card a psyDnD drag source */
.np-dash-card-wrap {
position: relative;
cursor: grab;
}
.np-dash-col.is-dnd-active .np-dash-card-wrap {
transition: transform 0.15s;
}
.np-dash-col.is-drop-target {
background: rgba(255, 255, 255, 0.02);
border-radius: var(--radius-lg);
outline: 1px dashed rgba(255, 255, 255, 0.10);
outline-offset: 4px;
}
.np-dash-toolbar-item-label { flex: 1; min-width: 0; }
.np-dash-toolbar-item.is-hidden .np-dash-toolbar-item-label { opacity: 0.55; }
/* Drop indicator — accent line between cards while dragging */
.np-dash-drop-indicator {
height: 3px;
border-radius: 2px;
background: var(--accent);
box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
margin: -8px 0;
}
/* Empty column placeholder */
.np-dash-col-empty {
padding: 28px 18px;
text-align: center;
font-size: 12.5px;
color: rgba(255, 255, 255, 0.40);
border: 1px dashed rgba(255, 255, 255, 0.10);
border-radius: var(--radius-lg);
font-style: italic;
}
/* Two flex columns — left stack, right stack */
.np-dash-grid {
display: flex;
gap: 18px;
align-items: stretch; /* equal-height columns so drops below the last card still hit the column */
}
.np-dash-col {
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
gap: 18px;
/* Ensures the area below the last card still belongs to this column — needed
so the user can drop a card "at the very bottom" even when the other
column is taller. */
min-height: 120px;
}
.np-dash-card {
min-width: 0;
}
/* Expanded bio grows the card naturally instead of turning into a scroll box */
.np-dash-artist-text .np-bio-text.expanded {
max-height: none;
overflow: visible;
}
/* Artist card */
.np-dash-artist-body {
display: flex;
gap: 16px;
align-items: flex-start;
}
.np-dash-artist-image {
width: 92px;
height: 92px;
border-radius: var(--radius-md);
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.np-dash-artist-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.np-dash-artist-name {
font-size: 16px;
font-weight: 600;
color: rgba(255, 255, 255, 0.92);
}
.np-dash-similar {
margin-top: 4px;
}
.np-dash-chip-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.np-chip {
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.75);
cursor: pointer;
transition: background 0.15s, color 0.15s;
white-space: nowrap;
}
.np-chip:hover {
background: color-mix(in srgb, var(--accent) 30%, transparent);
color: white;
}
/* Album card meta line */
.np-dash-album-meta {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
font-size: 12.5px;
color: rgba(255, 255, 255, 0.70);
}
.np-dash-album-name {
font-weight: 600;
color: rgba(255, 255, 255, 0.88);
}
.np-dash-album-stats {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
align-items: baseline;
}
/* Top songs card */
.np-dash-top-list {
display: flex;
flex-direction: column;
gap: 2px;
}
.np-dash-top-row {
display: grid;
grid-template-columns: 28px 1fr auto 16px;
align-items: center;
gap: 10px;
padding: 8px 10px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.12s;
}
.np-dash-top-row:hover {
background: rgba(255, 255, 255, 0.07);
}
.np-dash-top-row.active {
background: rgba(255, 255, 255, 0.10);
}
.np-dash-top-rank {
font-size: 12px;
font-weight: 700;
color: rgba(255, 255, 255, 0.45);
font-variant-numeric: tabular-nums;
text-align: right;
}
.np-dash-top-row.active .np-dash-top-rank {
color: var(--accent);
}
.np-dash-top-body {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
}
.np-dash-top-title {
font-size: 13px;
color: rgba(255, 255, 255, 0.88);
}
.np-dash-top-row.active .np-dash-top-title {
color: var(--accent);
font-weight: 600;
}
.np-dash-top-sub {
font-size: 11px;
color: rgba(255, 255, 255, 0.50);
}
.np-dash-top-dur {
font-size: 12px;
font-variant-numeric: tabular-nums;
color: rgba(255, 255, 255, 0.60);
}
.np-dash-top-play {
color: rgba(255, 255, 255, 0.35);
transition: color 0.15s;
}
.np-dash-top-row:hover .np-dash-top-play {
color: var(--accent);
}
.np-dash-top-credit {
font-size: 11px;
color: rgba(255, 255, 255, 0.40);
margin-top: 2px;
}
/* Info-only tracklist inside the dashboard: no cursor, no hover background */
.np-dash-card .np-album-track { cursor: default; }
.np-dash-card .np-album-track:hover:not(.active) { background: transparent; }
/* Tracklist expand toggle */
.np-dash-tracklist-more {
background: none;
border: none;
cursor: pointer;
font-size: 12px;
color: var(--accent);
padding: 6px 8px;
align-self: flex-start;
opacity: 0.85;
transition: opacity 0.15s;
}
.np-dash-tracklist-more:hover { opacity: 1; }
/* Hero age + ReplayGain badge */
.np-dash-hero-age {
font-style: italic;
opacity: 0.65;
}
/* Discography grid — compact contact-sheet of covers, title/year shown on hover via tooltip */
.np-dash-disc-grid {
display: grid;
grid-template-columns: repeat(10, minmax(0, 1fr));
gap: 6px;
}
.np-dash-disc-tile {
cursor: pointer;
min-width: 0;
transition: transform 0.15s;
}
.np-dash-disc-tile:hover { transform: translateY(-2px); }
.np-dash-disc-cover {
aspect-ratio: 1 / 1;
border-radius: var(--radius-sm);
overflow: hidden;
background: rgba(255, 255, 255, 0.04);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
position: relative;
}
.np-dash-disc-tile.active .np-dash-disc-cover {
box-shadow: 0 0 0 2px var(--accent), 0 3px 10px rgba(0, 0, 0, 0.4);
}
.np-dash-disc-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.np-dash-disc-fallback {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.30);
}
/* Last.fm stats */
.np-dash-stats-row {
display: flex;
flex-direction: column;
gap: 6px;
}
.np-dash-stats-row + .np-dash-stats-row {
margin-top: 4px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.np-dash-stats-label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: rgba(255, 255, 255, 0.45);
}
.np-dash-stats-values {
display: flex;
gap: 22px;
flex-wrap: wrap;
}
.np-dash-stat {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 60px;
}
.np-dash-stat-value {
font-size: 20px;
font-weight: 700;
color: rgba(255, 255, 255, 0.92);
letter-spacing: -0.01em;
font-variant-numeric: tabular-nums;
}
.np-dash-stat-you .np-dash-stat-value {
color: var(--accent);
}
.np-dash-stat-sub {
font-size: 11px;
color: rgba(255, 255, 255, 0.50);
text-transform: lowercase;
}
.np-dash-stats-links {
display: flex;
gap: 16px;
flex-wrap: wrap;
margin-top: 2px;
}
/* Responsive: single column on narrow viewports */
@media (max-width: 900px) {
.np-dash-grid { flex-direction: column; }
.np-dash-hero { grid-template-columns: 1fr; }
.np-dash-hero-cover { width: 180px; height: 180px; margin: 0 auto; }
.np-dash-hero-title { font-size: 24px; }
}
.app-shell[data-mobile] .np-dash-grid { flex-direction: column; }
.app-shell[data-mobile] .np-dash-hero { grid-template-columns: 1fr; }
.app-shell[data-mobile] .np-dash-hero-cover { width: 160px; height: 160px; margin: 0 auto; }
.app-shell[data-mobile] .np-dash-hero-title { font-size: 22px; }
.app-shell[data-mobile] .np-dash-artist-body { flex-direction: column; }
.app-shell[data-mobile] .np-dash-artist-image { width: 72px; height: 72px; }
.app-shell[data-mobile] .np-dash-disc-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 1100px) {
.np-dash-disc-grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
.np-dash-disc-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.np-dash-stats-values { gap: 16px; }
.np-dash-stat-value { font-size: 18px; }
}
/* ─────────────────────────────────────────────────────────────────────
Orbit — session top strip
───────────────────────────────────────────────────────────────────── */
.orbit-bar {
position: relative;
z-index: 10;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 12px;
padding: 6px 16px;
margin: 0 12px 8px;
background: color-mix(in srgb, var(--accent) 8%, var(--bg-elevated, rgba(255,255,255,0.04)));
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
border-radius: var(--radius-md, 10px);
color: var(--text-primary);
font-size: 12px;
letter-spacing: 0.01em;
animation: orbit-bar-in 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
flex-shrink: 0;
}
@keyframes orbit-bar-in {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
.orbit-bar__left {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.orbit-bar__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 70%, transparent);
animation: orbit-pulse 2s ease-in-out infinite;
flex-shrink: 0;
}
@keyframes orbit-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(0.85); }
}
.orbit-bar__name {
font-weight: 600;
color: var(--accent);
overflow: hidden;
text-overflow: ellipsis;
}
.orbit-bar__sep {
color: rgba(255, 255, 255, 0.35);
flex-shrink: 0;
}
.orbit-bar__count,
.orbit-bar__host {
color: var(--text-muted);
flex-shrink: 0;
}
.orbit-bar__center {
display: flex;
align-items: center;
justify-content: center;
}
.orbit-bar__shuffle {
display: inline-flex;
align-items: center;
gap: 7px;
font-variant-numeric: tabular-nums;
color: var(--text-muted);
font-weight: 500;
padding: 4px 12px;
border-radius: 999px;
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
white-space: nowrap;
}
.orbit-bar__shuffle-icon {
color: var(--accent);
flex-shrink: 0;
}
.orbit-bar__shuffle-time {
color: var(--text-primary);
font-weight: 600;
}
.orbit-bar__right {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.orbit-bar__catchup {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 999px;
background: var(--accent);
border: 1px solid var(--accent);
color: #fff;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.02em;
cursor: pointer;
transition: filter 150ms ease, transform 120ms ease, box-shadow 150ms ease;
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);
animation: orbit-catchup-pulse 1.8s ease-in-out infinite;
}
.orbit-bar__catchup:hover {
filter: brightness(1.08);
transform: translateY(-1px);
}
.orbit-bar__catchup svg {
animation: orbit-catchup-spin 2.4s linear infinite;
}
@keyframes orbit-catchup-pulse {
0%, 100% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
}
50% {
box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 0%, transparent);
}
}
@keyframes orbit-catchup-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.orbit-bar__settings {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 50%;
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
color: var(--text-muted);
cursor: pointer;
transition: color 150ms ease, background 150ms ease, border-color 150ms ease;
}
.orbit-bar__settings svg {
transition: transform 300ms ease;
}
.orbit-bar__settings:hover {
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}
.orbit-bar__settings:hover svg {
transform: rotate(45deg);
}
.orbit-bar__exit {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 50%;
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
color: var(--text-muted);
cursor: pointer;
transition: color 150ms ease, background 150ms ease, transform 180ms ease;
}
.orbit-bar__exit:hover {
color: var(--ctp-red, #f38ba8);
background: color-mix(in srgb, var(--ctp-red, #f38ba8) 12%, transparent);
transform: rotate(90deg);
}
/* (Previously pushed the shell down for a fixed top-bar. Not needed — bar is
now an inline element within .main-content.) */
/* Participant-count button (the "4/10" in the bar) */
.orbit-bar .orbit-bar__count {
background: none;
border: none;
color: var(--text-muted);
font: inherit;
letter-spacing: inherit;
padding: 2px 6px;
border-radius: 999px;
cursor: pointer;
transition: color 150ms ease, background 150ms ease;
}
.orbit-bar .orbit-bar__count:hover {
color: var(--text-primary);
background: color-mix(in srgb, var(--text-primary) 6%, transparent);
}
/* ── Participants popover ──────────────────────────────────────────── */
.orbit-participants-pop {
min-width: 280px;
max-width: 380px;
padding: 6px;
background: var(--ctp-base, #1e1e2e);
border: 1px solid color-mix(in srgb, var(--accent) 22%, rgba(255,255,255,0.1));
border-radius: var(--radius-md);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
/* Share popover (host-only) — standalone counterpart to the participants popover. */
.orbit-share-pop {
min-width: 320px;
max-width: 380px;
padding: 12px 14px 14px;
background: var(--ctp-base, #1e1e2e);
border: 1px solid color-mix(in srgb, var(--accent) 22%, rgba(255,255,255,0.1));
border-radius: var(--radius-md);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.orbit-share-pop__label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 8px;
}
.orbit-share-pop__row {
display: flex;
align-items: center;
gap: 6px;
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
border-radius: var(--radius-sm);
padding: 6px 6px 6px 8px;
}
.orbit-share-pop__link {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: var(--font-mono, monospace);
font-size: 11px;
color: var(--text-primary);
background: transparent;
border: none;
padding: 0;
user-select: all;
}
.orbit-share-pop__copy {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
background: var(--accent);
color: var(--bg-primary, #0b0b13);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: filter 0.15s ease;
}
.orbit-share-pop__copy:hover { filter: brightness(1.1); }
.orbit-participants-pop__head {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
padding: 6px 8px 4px;
}
.orbit-participants-pop__row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: var(--radius-sm);
font-size: 13px;
color: var(--text-primary);
}
.orbit-participants-pop__row:hover { background: color-mix(in srgb, var(--text-primary) 5%, transparent); }
.orbit-participants-pop__row--host { color: var(--accent); }
.orbit-participants-pop__row--host svg { color: var(--accent); }
/* Guest rows — give the user icon a quieter tone so the host's crown stays
the accent focal point. */
.orbit-participants-pop__row:not(.orbit-participants-pop__row--host) > svg {
color: var(--text-muted);
flex-shrink: 0;
}
.orbit-participants-pop__name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
.orbit-participants-pop__meta {
font-size: 11px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
.orbit-participants-pop__empty {
padding: 10px 8px;
font-size: 12px;
color: var(--text-muted);
font-style: italic;
text-align: center;
}
.orbit-participants-pop__actions {
display: inline-flex;
align-items: center;
gap: 4px;
}
.orbit-participants-pop__kick {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 50%;
background: transparent;
border: 1px solid transparent;
color: var(--text-muted);
cursor: pointer;
transition: color 140ms ease, background 140ms ease, border-color 140ms ease;
}
.orbit-participants-pop__kick:hover {
color: var(--ctp-yellow, #f9e2af);
background: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 12%, transparent);
border-color: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 35%, transparent);
}
.orbit-participants-pop__kick--ban:hover {
color: var(--ctp-red, #f38ba8);
background: color-mix(in srgb, var(--ctp-red, #f38ba8) 12%, transparent);
border-color: color-mix(in srgb, var(--ctp-red, #f38ba8) 35%, transparent);
}
.orbit-participants-pop__kick.is-active {
color: var(--ctp-peach, #fab387);
background: color-mix(in srgb, var(--ctp-peach, #fab387) 16%, transparent);
border-color: color-mix(in srgb, var(--ctp-peach, #fab387) 45%, transparent);
}
/* ── Exit modal ────────────────────────────────────────────────────── */
.orbit-exit-overlay {
align-items: center;
justify-content: center;
}
.orbit-exit-modal {
max-width: 400px;
padding: 24px 26px 20px;
text-align: center;
}
.orbit-exit-modal__title {
margin: 0 0 10px;
font-size: 16px;
font-weight: 700;
color: var(--text-primary);
}
.orbit-exit-modal__body {
margin: 0 0 20px;
font-size: 13px;
color: var(--text-muted);
line-height: 1.5;
}
.orbit-exit-modal__actions {
display: flex;
justify-content: center;
}
/* ── Topbar "start Orbit" trigger — same form as Live, accent-tinted ── */
.orbit-start-trigger {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 45%, var(--ctp-surface1));
transition: color 150ms ease, border-color 150ms ease, background 150ms ease, box-shadow 200ms ease;
}
.orbit-start-trigger__spin { transition: transform 260ms ease; }
.orbit-start-trigger:hover {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 70%, transparent);
background: color-mix(in srgb, var(--accent) 10%, var(--bg-hover));
box-shadow: 0 0 12px -3px color-mix(in srgb, var(--accent) 55%, transparent);
}
.orbit-start-trigger:hover .orbit-start-trigger__spin { transform: rotate(45deg); }
/* Launch popover — three-option menu anchored below the topbar trigger. */
.orbit-launch-pop {
min-width: 220px;
padding: 6px;
background: var(--ctp-base, #1e1e2e);
border: 1px solid color-mix(in srgb, var(--accent) 22%, rgba(255,255,255,0.1));
border-radius: var(--radius-md);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
display: flex;
flex-direction: column;
gap: 2px;
}
.orbit-launch-pop__item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 9px 10px;
background: transparent;
border: none;
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 13px;
font-weight: 500;
text-align: left;
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.orbit-launch-pop__item svg {
color: var(--accent);
flex-shrink: 0;
}
.orbit-launch-pop__item:hover:not(:disabled) {
background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.orbit-launch-pop__item:disabled {
opacity: 0.45;
cursor: not-allowed;
}
/* Orbit help modal — 9 accordions on SettingsSubSection, scrollable body. */
.orbit-help-modal {
max-width: 560px;
width: min(560px, calc(100vw - 32px));
max-height: 85vh;
padding: 22px 26px 20px;
display: flex;
flex-direction: column;
position: relative;
}
.orbit-help-modal__title {
margin: 0 0 8px;
padding-right: 1.5rem;
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.orbit-help-modal__intro {
margin: 0 0 14px;
font-size: 13px;
color: var(--text-muted);
line-height: 1.55;
}
.orbit-help-modal__body {
flex: 1;
overflow-y: auto;
margin: 0 -4px;
padding: 0 4px;
}
.orbit-help-modal__body p {
margin: 0 0 8px;
font-size: 12.5px;
line-height: 1.55;
color: var(--text-secondary, var(--text-primary));
}
.orbit-help-modal__body p:last-child { margin-bottom: 0; }
/* Keyboard focus indicator for accordion summaries — the default outline
is too subtle against the modal's dark background. Uses focus-visible
so mouse clicks don't leave a lingering ring. */
.orbit-help-modal__body summary:focus-visible {
outline: none;
background: color-mix(in srgb, var(--accent) 14%, transparent);
box-shadow: inset 3px 0 0 var(--accent);
}
.orbit-help-modal__warn {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 10px;
padding: 10px 12px;
background: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--ctp-yellow, #f9e2af) 40%, transparent);
border-radius: var(--radius-sm);
font-size: 12px;
line-height: 1.5;
color: var(--text-primary);
}
.orbit-help-modal__warn strong {
color: var(--ctp-yellow, #f9e2af);
font-weight: 700;
}
/* Account picker — modal for multi-account server switch on Orbit join. */
.orbit-account-picker {
max-width: 420px;
width: min(420px, calc(100vw - 32px));
padding: 22px 26px 20px;
position: relative;
}
.orbit-account-picker__title {
margin: 0 0 6px;
padding-right: 1.5rem;
font-size: 17px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.orbit-account-picker__sub {
margin: 0 0 14px;
font-size: 13px;
color: var(--text-muted);
line-height: 1.5;
}
.orbit-account-picker__list {
list-style: none;
padding: 0;
margin: 0 0 14px;
display: flex;
flex-direction: column;
gap: 4px;
}
.orbit-account-picker__item {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 12px;
font-size: 13px;
text-align: left;
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
border-radius: var(--radius-sm);
color: var(--text-primary);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease;
}
.orbit-account-picker__item svg {
color: var(--accent);
flex-shrink: 0;
}
.orbit-account-picker__item:hover,
.orbit-account-picker__item.is-active {
background: color-mix(in srgb, var(--accent) 12%, transparent);
border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}
.orbit-account-picker__user {
font-weight: 600;
}
.orbit-account-picker__name {
color: var(--text-muted);
font-weight: 400;
}
.orbit-account-picker__actions {
display: flex;
justify-content: flex-end;
}
/* ── Start-session modal ────────────────────────────────────────── */
.orbit-start-overlay {
align-items: center;
justify-content: center;
}
.orbit-start-modal {
max-width: 480px;
width: min(480px, calc(100vw - 32px));
padding: 18px 26px 16px;
position: relative;
}
.orbit-start-modal__hero {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0 0 10px;
margin-bottom: 12px;
border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
}
.orbit-start-modal__hero-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 50%;
background: color-mix(in srgb, var(--accent) 16%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
color: var(--accent);
margin-bottom: 8px;
box-shadow: 0 0 24px -6px color-mix(in srgb, var(--accent) 50%, transparent);
}
.orbit-start-modal__hero-icon--live {
animation: orbit-hero-pulse 2.2s ease-in-out infinite;
}
@keyframes orbit-hero-pulse {
0%, 100% { box-shadow: 0 0 24px -6px color-mix(in srgb, var(--accent) 45%, transparent); }
50% { box-shadow: 0 0 32px -4px color-mix(in srgb, var(--accent) 75%, transparent); }
}
.orbit-start-modal__title {
margin: 0 0 6px;
padding-right: 1.5rem;
font-size: 17px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.orbit-start-modal__hero .orbit-start-modal__title {
padding-right: 0;
}
.orbit-start-modal__brand {
color: var(--accent);
font-weight: 700;
letter-spacing: -0.005em;
}
.orbit-start-modal__sub {
margin: 0;
font-size: 13px;
color: var(--text-muted);
line-height: 1.55;
}
.orbit-start-modal__sub strong {
color: var(--text-primary);
font-weight: 600;
}
.orbit-start-modal__tip {
display: grid;
grid-template-columns: 18px 1fr;
align-items: start;
gap: 10px;
padding: 10px 12px;
margin-bottom: 14px;
font-size: 12px;
line-height: 1.5;
color: var(--text-secondary, var(--text-primary));
background: color-mix(in srgb, var(--accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
border-radius: var(--radius-sm);
}
.orbit-start-modal__tip svg {
margin-top: 2px;
color: var(--accent);
flex-shrink: 0;
}
.orbit-start-modal__tip strong {
color: var(--text-primary);
font-weight: 600;
}
.orbit-start-modal__tip--warn {
background: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 14%, transparent);
border-color: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 45%, transparent);
color: var(--text-primary);
}
.orbit-start-modal__tip--warn svg {
color: var(--ctp-yellow, #f9e2af);
}
.orbit-start-modal__tip--warn strong {
color: var(--ctp-yellow, #f9e2af);
}
.orbit-start-modal__field {
margin-bottom: 14px;
}
.orbit-start-modal__field:last-of-type {
margin-bottom: 16px;
}
.orbit-start-modal__label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: 0.02em;
margin-bottom: 6px;
}
.orbit-start-modal__label strong {
font-size: 13px;
color: var(--accent);
font-weight: 700;
margin-left: 4px;
}
.orbit-start-modal__helper {
margin-top: 6px;
font-size: 11.5px;
color: var(--text-muted);
line-height: 1.4;
}
.orbit-start-modal__toggle-row {
display: flex;
align-items: center;
gap: 14px;
cursor: pointer;
}
.orbit-start-modal__toggle-text {
flex: 1;
min-width: 0;
}
.orbit-start-modal__toggle-row .orbit-start-modal__label {
margin-bottom: 2px;
}
.orbit-start-modal__toggle-row .orbit-start-modal__helper {
margin-top: 0;
}
.orbit-start-modal__note {
display: grid;
grid-template-columns: 18px 1fr;
align-items: start;
gap: 10px;
padding: 10px 12px;
margin-bottom: 10px;
font-size: 12px;
line-height: 1.5;
color: var(--text-secondary, var(--text-primary));
background: color-mix(in srgb, var(--accent) 9%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
border-radius: var(--radius-sm);
}
.orbit-start-modal__note svg {
margin-top: 2px;
color: var(--accent);
flex-shrink: 0;
}
.orbit-start-modal__note--muted {
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
border-color: color-mix(in srgb, var(--text-primary) 10%, transparent);
}
.orbit-start-modal__note--muted svg {
color: var(--text-muted);
}
.orbit-start-modal__note kbd {
display: inline-block;
padding: 1px 5px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 10.5px;
font-weight: 600;
color: var(--text-primary);
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 18%, transparent);
border-radius: 3px;
line-height: 1.2;
vertical-align: baseline;
}
.orbit-start-modal__kbd-sep {
display: inline-block;
margin: 0 3px;
color: var(--text-muted);
}
.orbit-start-modal__input-row {
display: flex;
align-items: stretch;
gap: 6px;
}
.orbit-start-modal__input {
display: block;
width: 100%;
flex: 1;
min-width: 0;
padding: 9px 12px;
background: var(--ctp-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 14px;
box-sizing: border-box;
outline: none;
}
.orbit-start-modal__input:focus {
border-color: var(--accent);
}
.orbit-start-modal__reshuffle {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 38px;
background: color-mix(in srgb, var(--accent) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
border-radius: var(--radius-sm);
color: var(--accent);
cursor: pointer;
transition: background 150ms ease, border-color 150ms ease, transform 260ms ease;
}
.orbit-start-modal__reshuffle:hover {
background: color-mix(in srgb, var(--accent) 22%, transparent);
border-color: color-mix(in srgb, var(--accent) 52%, transparent);
}
.orbit-start-modal__reshuffle:active {
transform: rotate(20deg);
}
/* ── Host settings popover (from the gear in the Orbit bar) ──────── */
.orbit-settings-pop {
min-width: 320px;
max-width: 380px;
padding: 14px;
background: var(--ctp-base, #1e1e2e);
border: 1px solid color-mix(in srgb, var(--accent) 22%, rgba(255,255,255,0.1));
border-radius: var(--radius-md);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.orbit-settings-pop__head {
padding: 2px 4px 12px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
margin-bottom: 12px;
}
.orbit-settings-pop__row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 8px;
margin-bottom: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 120ms ease;
}
.orbit-settings-pop__row + .orbit-settings-pop__row {
margin-top: 0;
}
.orbit-settings-pop__row:hover {
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
}
.orbit-settings-pop__text {
flex: 1;
min-width: 0;
}
.orbit-settings-pop__label {
font-size: 12.5px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 3px;
}
.orbit-settings-pop__hint {
font-size: 11px;
color: var(--text-muted);
line-height: 1.45;
}
.orbit-settings-pop .toggle-switch {
flex-shrink: 0;
margin-top: 2px;
}
/* Row-variant for controls that want the control *under* the label rather
than beside it (preset pickers etc.). Non-interactive container: the
hover-background is suppressed here so the buttons keep focus. */
.orbit-settings-pop__row--stacked {
flex-direction: column;
align-items: stretch;
gap: 8px;
cursor: default;
}
.orbit-settings-pop__row--stacked:hover {
background: transparent;
}
.orbit-settings-pop__preset-group {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.orbit-settings-pop__preset {
flex: 1 1 0;
min-width: 44px;
padding: 6px 8px;
font-size: 11.5px;
font-weight: 600;
color: var(--text-secondary, var(--text-primary));
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.orbit-settings-pop__preset:hover:not(:disabled) {
background: color-mix(in srgb, var(--accent) 10%, transparent);
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
color: var(--text-primary);
}
.orbit-settings-pop__preset.is-active {
background: color-mix(in srgb, var(--accent) 22%, transparent);
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
color: var(--accent);
}
.orbit-settings-pop__preset:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.orbit-settings-pop__action {
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
margin-top: 12px;
padding: 10px 12px;
background: color-mix(in srgb, var(--accent) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
border-radius: var(--radius-sm);
color: var(--accent);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
cursor: pointer;
transition: background 150ms ease, border-color 150ms ease, transform 180ms ease;
}
.orbit-settings-pop__action:hover {
background: color-mix(in srgb, var(--accent) 22%, transparent);
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.orbit-settings-pop__action:active { transform: scale(0.98); }
.orbit-settings-pop__action svg { color: var(--accent); }
/* ── Guest queue view (replaces QueuePanel body while role = guest) ── */
.queue-panel--orbit-guest { padding: 0; }
.orbit-guest-queue {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
}
.orbit-queue-head {
padding: 14px 14px 10px;
border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.orbit-queue-head__title-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
min-width: 0;
}
.orbit-queue-head__title {
margin: 0;
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.orbit-queue-head__presence {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.02em;
border-radius: 999px;
border-width: 1px;
border-style: solid;
}
.orbit-queue-head__presence--online {
color: var(--ctp-green, #a6e3a1);
background: color-mix(in srgb, var(--ctp-green, #a6e3a1) 14%, transparent);
border-color: color-mix(in srgb, var(--ctp-green, #a6e3a1) 40%, transparent);
}
.orbit-queue-head__presence--online svg {
color: var(--ctp-green, #a6e3a1);
}
.orbit-queue-head__presence--away {
color: var(--ctp-yellow, #f9e2af);
background: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 14%, transparent);
border-color: color-mix(in srgb, var(--ctp-yellow, #f9e2af) 40%, transparent);
}
.orbit-queue-head__presence--away svg {
color: var(--ctp-yellow, #f9e2af);
}
.orbit-queue-head__meta {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--text-muted);
min-width: 0;
}
.orbit-queue-head__meta svg { color: var(--accent); flex-shrink: 0; }
.orbit-queue-head__names {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.orbit-guest-queue__current {
position: relative;
margin: 12px 10px 6px;
padding: 10px;
background: color-mix(in srgb, var(--accent) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
border-radius: var(--radius-md, 10px);
flex-shrink: 0;
}
.orbit-guest-queue__live-badge {
position: absolute;
top: 8px;
right: 10px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 7px;
font-size: 9.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ctp-crust, #11111b);
background: var(--accent);
border-radius: 999px;
}
.orbit-guest-queue__live-badge svg { animation: orbit-live-pulse 2s ease-in-out infinite; }
@keyframes orbit-live-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.orbit-guest-queue__current-body {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.orbit-guest-queue__cover {
flex-shrink: 0;
width: 44px;
height: 44px;
border-radius: var(--radius-sm);
object-fit: cover;
background: color-mix(in srgb, var(--text-primary) 6%, transparent);
}
.orbit-guest-queue__cover--lg { width: 54px; height: 54px; }
.orbit-guest-queue__cover--ph {
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
}
.orbit-guest-queue__info {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1;
gap: 1px;
}
.orbit-guest-queue__track-title {
font-size: 12.5px;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.orbit-guest-queue__track-artist {
font-size: 11.5px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.orbit-guest-queue__note {
margin-top: 2px;
font-size: 10.5px;
color: var(--accent);
font-weight: 600;
letter-spacing: 0.02em;
}
.orbit-guest-queue__section-head {
padding: 14px 14px 6px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.orbit-guest-queue__count {
padding: 1px 6px;
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
border-radius: 999px;
font-size: 10px;
color: var(--text-primary);
}
.orbit-guest-queue__list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 2px 6px 8px;
}
.orbit-guest-queue__item {
display: flex;
align-items: center;
gap: 9px;
padding: 6px 8px;
border-radius: var(--radius-sm);
transition: background 120ms ease;
}
.orbit-guest-queue__item:hover {
background: color-mix(in srgb, var(--text-primary) 5%, transparent);
}
.orbit-guest-queue__submitter {
margin-top: 2px;
font-size: 10.5px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.orbit-guest-queue__submitter strong {
color: var(--accent);
font-weight: 600;
}
/* Host manual-approval strip — rendered right below OrbitQueueHead when
autoApprove is off and guest suggestions are waiting. Prominent accent
colour so the host can't miss queued approvals. */
.host-approval {
margin: 0 10px 10px;
padding: 10px;
background: color-mix(in srgb, var(--accent) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
border-radius: var(--radius-md, 10px);
}
.host-approval__head {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
}
.host-approval__head svg { color: var(--accent); flex-shrink: 0; }
.host-approval__count {
margin-left: auto;
padding: 1px 7px;
background: var(--accent);
color: var(--bg-primary, #0b0b13);
border-radius: 999px;
font-size: 10px;
letter-spacing: 0;
text-transform: none;
}
.host-approval__list {
display: flex;
flex-direction: column;
gap: 4px;
}
.host-approval__item {
display: flex;
align-items: center;
gap: 9px;
padding: 6px;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--text-primary) 4%, transparent);
}
.host-approval__cover {
width: 36px;
height: 36px;
border-radius: 4px;
object-fit: cover;
flex-shrink: 0;
}
.host-approval__cover--ph {
background: color-mix(in srgb, var(--text-primary) 8%, transparent);
}
.host-approval__info {
flex: 1;
min-width: 0;
overflow: hidden;
}
.host-approval__title {
font-size: 12.5px;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.host-approval__artist {
font-size: 11px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.host-approval__submitter {
margin-top: 2px;
font-size: 10.5px;
color: var(--accent);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.host-approval__actions {
display: inline-flex;
gap: 4px;
flex-shrink: 0;
}
.host-approval__btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--text-primary) 18%, transparent);
background: transparent;
color: var(--text-primary);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.host-approval__btn--approve:hover {
background: color-mix(in srgb, var(--ctp-green, #a6e3a1) 28%, transparent);
border-color: var(--ctp-green, #a6e3a1);
color: var(--ctp-green, #a6e3a1);
}
.host-approval__btn--decline:hover {
background: color-mix(in srgb, var(--danger, #f38ba8) 28%, transparent);
border-color: var(--danger, #f38ba8);
color: var(--danger, #f38ba8);
}
/* Pending suggestions — tracks the guest has submitted but the host
hasn't merged yet. Looks like the regular queue section, with a subtle
yellow accent and a clock icon in the heading. */
.orbit-guest-queue__pending {
padding: 0 6px;
margin-bottom: 4px;
border-bottom: 1px solid var(--border-subtle);
padding-bottom: 6px;
}
.orbit-guest-queue__section-head--pending {
color: var(--ctp-yellow, #f9e2af);
}
.orbit-guest-queue__section-head--pending svg {
color: var(--ctp-yellow, #f9e2af);
}
.orbit-guest-queue__item--pending {
opacity: 0.85;
}
.orbit-guest-queue__pending-hint {
margin-top: 2px;
font-size: 10.5px;
color: var(--text-muted);
font-style: italic;
}
.orbit-guest-queue__empty {
padding: 18px 16px;
font-size: 11.5px;
color: var(--text-muted);
line-height: 1.5;
text-align: center;
}
.orbit-guest-queue__more {
padding: 8px 14px;
font-size: 11px;
color: var(--text-muted);
text-align: center;
font-style: italic;
}
.orbit-guest-queue__footer {
padding: 8px 14px 10px;
font-size: 10.5px;
color: var(--text-muted);
text-align: center;
border-top: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.orbit-start-modal__range {
display: block;
width: 100%;
margin: 14px 0 10px;
accent-color: var(--accent);
}
.orbit-start-modal__error {
margin-bottom: 12px;
padding: 8px 10px;
font-size: 12px;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--ctp-red, #f38ba8) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--ctp-red, #f38ba8) 35%, transparent);
color: var(--ctp-red, #f38ba8);
}
.orbit-start-modal__actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 10px;
}
.orbit-start-modal__link {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 18px;
padding: 10px 12px;
background: var(--ctp-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-family: var(--font-mono, ui-monospace, 'Courier New', monospace);
font-size: 11.5px;
color: var(--accent);
word-break: break-all;
}
.orbit-start-modal__link code {
flex: 1;
min-width: 0;
background: none;
padding: 0;
color: inherit;
}
.orbit-start-modal__copy {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--accent) 14%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
color: var(--accent);
cursor: pointer;
transition: background 150ms ease, border-color 150ms ease;
}
.orbit-start-modal__copy:hover {
background: color-mix(in srgb, var(--accent) 26%, transparent);
border-color: color-mix(in srgb, var(--accent) 50%, transparent);
}