diff --git a/src/main.tsx b/src/main.tsx index ff61e0b9..d3c454de 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,7 +5,7 @@ import { runPreReactBootstrap } from './app/bootstrap'; import './i18n'; import './styles/themes/index.css'; import './styles/layout.css'; -import './styles/components.css'; +import './styles/components/index.css'; import './styles/tracks.css'; runPreReactBootstrap(); diff --git a/src/styles/components.css b/src/styles/components.css deleted file mode 100644 index d81bebcc..00000000 --- a/src/styles/components.css +++ /dev/null @@ -1,14205 +0,0 @@ -/* ─── 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 { - 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; - transform-origin: center center; - transform: scale(1); - transition: opacity 0.15s ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1); -} - -.album-card:hover .album-card-cover img { - transform: scale(1.02); -} - -.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 { - 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; - transform-origin: center center; - transform: scale(1); - transition: opacity 0.15s ease, transform 350ms cubic-bezier(0.16, 1, 0.3, 1); -} - -.artist-card:hover .artist-card-avatar img { - transform: scale(1.02); -} - -.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); -} - -/* ─ Composer Grid (text-only, compact) ─ - * Composers carry no useful imagery — visual is just noise. Strip the avatar, - * keep the name as the visual anchor with the participation count below. */ -.composer-grid-wrap { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: var(--space-2); - align-items: stretch; -} - -.composer-grid-wrap > .composer-card { - content-visibility: auto; - contain-intrinsic-size: 0 78px; -} - -.composer-card { - display: flex; - flex-direction: column; - justify-content: center; - gap: 2px; - background: var(--bg-card); - border-radius: var(--radius-md); - border: 1px solid var(--border-subtle); - padding: var(--space-3) var(--space-3); - cursor: pointer; - text-align: left; - transition: - transform 200ms cubic-bezier(0.16, 1, 0.3, 1), - border-color 200ms ease, - background 200ms ease, - box-shadow 200ms ease; -} - -.composer-card:hover { - border-color: var(--accent); - background: var(--bg-hover); - transform: translateY(-1px); - box-shadow: 0 4px 12px -6px rgba(0, 0, 0, 0.4); -} - -.composer-card-name { - font-size: 14px; - font-weight: 600; - color: var(--text-primary); - line-height: 1.25; - white-space: normal; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - /* Reserve two name lines so the participation row stays vertically aligned - * across the grid, regardless of whether the name wraps or not. */ - min-height: calc(14px * 1.25 * 2); -} - -.composer-card-meta { - font-size: 11px; - color: var(--text-secondary); - line-height: 1.3; -} - -/* ─ 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; -} - -/* Same wrap as Albums grid; defer layout/paint for off-screen artist tiles (helps scroll on WebKit/Linux). */ -.album-grid-wrap > .artist-card { - content-visibility: auto; - contain-intrinsic-size: 0 220px; -} - - -@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; -} - -/* Horizontal rails can hold many off-screen cards; skip their paint/layout until visible. */ -.album-grid .album-card { - content-visibility: auto; - contain-intrinsic-size: 0 240px; -} - -.album-grid .artist-card { - content-visibility: auto; - contain-intrinsic-size: 0 240px; -} - -.album-card-more { - flex: 0 0 clamp(140px, 15vw, 180px); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--space-3); - background: var(--bg-hover); - border-radius: var(--radius-lg); - border: 1px dashed var(--border); - color: var(--text-secondary); - cursor: pointer; - 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-cover-badges-tr { - position: absolute; - top: 6px; - right: 6px; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 4px; - z-index: 2; - pointer-events: none; -} - -.album-card-offline-badge { - flex-shrink: 0; - background: color-mix(in srgb, var(--accent) 85%, transparent); - color: var(--ctp-crust); - border-radius: var(--radius-sm); - padding: 3px 4px; - display: flex; - align-items: center; - justify-content: center; -} - -.album-card-new-badge { - flex-shrink: 0; - background: var(--accent); - color: var(--ctp-crust); - border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent); - border-radius: var(--radius-sm); - padding: 3px 6px; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.02em; - line-height: 1; -} - -.album-card-play-overlay { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.4); - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - opacity: 0; - 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: 240px; - max-width: 420px; - flex: 1 1 320px; -} - -/* JS-computed collapse mode (budget-based, with hysteresis) */ -.live-search[data-collapsed] { - min-width: 34px; - max-width: 34px; - flex: 0 0 34px; -} - -.live-search[data-collapsed]:not([data-active]) .live-search-input-wrap { - width: 34px; - height: 34px; - border-radius: var(--radius-full); - border: 1px solid var(--border-subtle); - background: color-mix(in srgb, var(--bg-card) 84%, transparent); - cursor: text; -} - -.live-search[data-collapsed]:not([data-active]) .live-search-icon { - left: 50%; - transform: translateX(-50%); -} - -.live-search[data-collapsed]:not([data-active]) .live-search-field { - width: 0 !important; - min-width: 0 !important; - padding: 0 !important; - border: none !important; - opacity: 0; - pointer-events: none; -} - -.live-search[data-collapsed][data-active] { - min-width: 34px; - max-width: 34px; - flex: 0 0 34px; -} - -.live-search[data-collapsed][data-active] .live-search-input-wrap { - position: absolute; - top: 50%; - left: 0; - transform: translateY(-50%); - width: min(420px, calc(100vw - 2 * var(--space-4))); - z-index: 25; -} - -.live-search[data-collapsed][data-active] .live-search-field { - width: 100%; - opacity: 1; - pointer-events: auto; -} - -.live-search[data-collapsed]:not([data-active]) .live-search-clear, -.live-search[data-collapsed]:not([data-active]) .live-search-adv-btn { - opacity: 0; - pointer-events: none; -} - -.live-search[data-collapsed][data-active] .live-search-dropdown { - left: 0; - right: auto; - width: min(420px, calc(100vw - 2 * var(--space-4))); -} - -.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: var(--ctp-crust); -} - -.album-detail-back { - margin-bottom: 1rem; - gap: 6px; -} - -.album-track-toolbar { - display: flex; - gap: 8px; - align-items: center; - padding: 0 16px 8px; - flex-wrap: wrap; -} - -.album-track-toolbar-filter { - position: relative; - flex: 1 1 160px; - max-width: 260px; -} - -.album-track-toolbar-actions { - margin-left: auto; - display: flex; - align-items: center; - gap: var(--space-2); - min-height: 34px; -} - -.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); -} - -/* Hide track preview controls inside an active Orbit session — preview - playback shares the audio engine with the orbit-driven main player and - the two would step on each other. The defensive guard in - `previewStore.startPreview` catches keyboard / programmatic callers. */ -[data-orbit-active] .playlist-suggestion-preview-btn { - display: none; -} - -/* Guest in an Orbit session: seekbar is read-only — sync follows the host. - `pointer-events: none` blocks click / drag / wheel / hover; the dimmed - look + not-allowed cursor (set on the parent so it shows when hovered - over the now-inert child) signal "this control is not yours". */ -[data-orbit-role="guest"] .waveform-seek-container { - cursor: not-allowed; - opacity: 0.55; -} -[data-orbit-role="guest"] .waveform-seek-container > * { - pointer-events: none; -} - -.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); -} - -/* Loading state: the engine is still downloading/decoding/seeking. A short - rotating arc (~25% of the circumference) signals "pending" without - pretending to be progress. 150 ms delay suppresses the spinner for - small/cached files where audio starts almost instantly. */ -.playlist-suggestion-preview-btn.is-previewing:not(.audio-started) .playlist-suggestion-preview-ring-progress { - /* circumference = 2π × 10.5 ≈ 65.97 → 25% arc + 75% gap */ - stroke-dasharray: 16.5 49.5; - stroke-dashoffset: 0; - animation: playlist-preview-loading 1.1s linear infinite; - animation-delay: 150ms; -} - -@keyframes playlist-preview-loading { - from { stroke-dashoffset: 0; } - to { stroke-dashoffset: -65.97; } -} - -/* Animation runs only after `audio-started` is added — i.e. once the engine - emitted `audio:preview-start` for this track. Prevents the ring from - sprinting ahead of audio during the engine's download/decode/seek warmup. */ -.playlist-suggestion-preview-btn.audio-started .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=""`. 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); -} - -.track-row.active:hover { - background: var(--accent-dim); -} - -.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); -} - -/* "Now playing" indicator — Lucide AudioLines icon coloured with the active - * accent. Was an animated three-bar transform setup; the per-frame opacity + - * scaleY repaints fell back to software composite on WebKitGTK without - * compositing (NVIDIA proprietary + WEBKIT_DISABLE_COMPOSITING_MODE=1) and - * pegged the WebProcess at ~80% CPU on AlbumDetail with a long tracklist. */ -.eq-bars { - color: var(--accent); - display: block; -} - -/* 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; -} - -/* Perf probe must not blur/composite the whole app while profiling. */ -.modal-overlay--perf-probe { - backdrop-filter: none !important; - -webkit-backdrop-filter: none !important; - background: rgba(17, 17, 27, 0.96); - animation: none; -} - -.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); -} - -.sidebar-perf-modal { - max-width: 560px; -} - -.sidebar-perf-modal__hint { - margin: 0 0 12px; - font-size: 13px; - color: var(--text-muted); -} - -.sidebar-perf-modal__item { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 0; - font-size: 13px; - color: var(--text-primary); -} - -.sidebar-perf-modal__item input[type="checkbox"] { - accent-color: var(--accent); - width: 15px; - height: 15px; -} - -.sidebar-perf-modal__phase { - margin-top: 10px; - border: 1px solid color-mix(in srgb, var(--text-muted) 22%, transparent); - border-radius: 10px; - padding: 8px 10px 10px; - background: color-mix(in srgb, var(--bg-card, var(--ctp-base)) 78%, transparent); -} - -.sidebar-perf-modal__phase--nested { - margin-left: 10px; - margin-right: 2px; - padding-top: 6px; - background: color-mix(in srgb, var(--bg-card, var(--ctp-base)) 68%, transparent); -} - -.sidebar-perf-modal__phase-title { - cursor: pointer; - font-size: 12px; - font-weight: 600; - color: var(--text-primary); - user-select: none; - list-style: none; -} - -.sidebar-perf-modal__phase-title::-webkit-details-marker { - display: none; -} - -.sidebar-perf-modal__cpu { - margin: 0 0 10px; - padding: 10px 12px; - border: 1px solid color-mix(in srgb, var(--text-muted) 22%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--bg-card, var(--ctp-base)) 80%, transparent); -} - -.sidebar-perf-modal__cpu-title { - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.08em; - font-weight: 600; - color: var(--text-muted); - margin-bottom: 6px; -} - -.sidebar-perf-modal__cpu-row { - font-size: 13px; - color: var(--text-primary); - line-height: 1.45; -} - -.sidebar-perf-modal__subhead { - margin-top: 10px; - margin-bottom: 2px; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.08em; - font-weight: 600; - color: var(--text-muted); -} - -.sidebar-perf-modal__actions { - margin-top: 14px; - display: flex; - justify-content: flex-end; - gap: 8px; -} - -/* ─ 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; -} - -/* Loading spinner — same logic as the tracklist ring above. The SVG uses - pathLength="100", so the circumference is normalised: 25 unit arc + 75 - unit gap, with the offset rolling from 0 to -100. */ -.player-bar.is-previewing:not(.audio-started) .player-btn-preview-ring-progress { - stroke-dasharray: 25 75; - stroke-dashoffset: 0; - animation: player-preview-loading 1.1s linear infinite; - animation-delay: 150ms; -} - -@keyframes player-preview-loading { - from { stroke-dashoffset: 0; } - to { stroke-dashoffset: -100; } -} - -/* Animation gated on the player-bar `audio-started` flag (set after the - engine emits `audio:preview-start`). Same rationale as the tracklist - preview ring above. */ -.player-bar.audio-started .player-btn-preview-ring-progress { - 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
- 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
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-columns { - columns: 2; - column-gap: 1.25rem; -} - -@media (max-width: 1200px) { - .help-columns { - columns: 1; - } -} - -.help-search { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - background: var(--bg-card); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - width: 280px; - max-width: 100%; - transition: border-color var(--transition-fast); -} - -.help-search:focus-within { - border-color: var(--accent); -} - -.help-search-icon { - color: var(--text-muted); - flex-shrink: 0; -} - -.help-search-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - padding: 2px 0; - color: var(--text-primary); - font-size: 0.9rem; - font-family: inherit; -} - -.help-search-input::placeholder { - color: var(--text-muted); -} - -.help-search-clear { - background: transparent; - border: none; - color: var(--text-muted); - cursor: pointer; - padding: 0; - display: inline-flex; - align-items: center; - flex-shrink: 0; - transition: color var(--transition-fast); -} - -.help-search-clear:hover { - color: var(--text-primary); -} - -.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; - min-height: 0; -} - -.lyrics-pane__viewport { - overflow-x: hidden; - /* scroll-behavior intentionally left to auto — JS drives smooth scroll imperatively */ - padding: 16px 16px 8px; -} - -.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(auto-fit, minmax(170px, 1fr)); - gap: var(--space-4); -} - -@media (max-width: 600px) { - .stats-overview { - grid-template-columns: repeat(auto-fit, minmax(140px, 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: clamp(1.45rem, 3.4vw, 2rem); - font-weight: 800; - color: var(--accent); - line-height: 1; - overflow-wrap: anywhere; - word-break: break-word; -} - -.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; -} - -/* Server switch menu is portaled to body (ConnectionIndicator); rules kept for any inline-less use. */ -.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; - container-type: inline-size; - 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; - min-height: 0; -} - -.np-main__viewport { - 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: 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; -} - -.artist-release-group { - margin-bottom: 1.5rem; -} - -.artist-release-group__header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 0.75rem; -} - -.artist-release-group__header h3 { - margin: 0; - font-size: 15px; - font-weight: 600; -} - -.artist-release-group__count { - font-size: 12px; - color: var(--text-muted); - background: var(--bg-card); - border-radius: 10px; - padding: 1px 8px; -} - -.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-thumb--artist-round { - border-radius: 50%; -} - -.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-name-row { - display: flex; - align-items: center; - gap: 0.5rem; - min-width: 0; -} - -.mp-album-plays-pill { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 8px; - border-radius: 999px; - background: var(--bg-card); - color: var(--accent); - font-size: 12px; - font-weight: 600; - white-space: nowrap; - flex-shrink: 0; - font-variant-numeric: tabular-nums; - letter-spacing: 0.02em; -} - -.mp-album-actions { - display: flex; - gap: 4px; -} - -.mp-album-action-btn { - width: 28px; - height: 28px; - display: inline-flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - border-radius: var(--radius-sm); - color: var(--text-muted); - cursor: pointer; - transition: background var(--transition-fast), color var(--transition-fast); -} - -.mp-album-action-btn:hover { - background: var(--bg-card); - color: var(--text-primary); -} - -.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-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