/* ─── Psysonic App Shell ─── */ .app-shell { position: relative; display: grid; grid-template-columns: var(--sidebar-width) 1fr var(--queue-width); grid-template-rows: 1fr var(--player-height); grid-template-areas: "sidebar main queue" "player player player"; height: 100vh; /* overflow: hidden keeps the player bar pinned at the bottom even when the window is dragged below the OS minHeight constraint (ignored on some Linux WMs/compositors). Without this, the 1fr row's implicit auto min-height can push the grid taller than 100vh, scrolling the player bar out of view. */ overflow: hidden; background: var(--bg-app); } /* ─── Resizer Handles ─── */ .resizer { position: absolute; top: 0; bottom: var(--player-height); width: 6px; cursor: col-resize; z-index: 50; background: transparent; transition: background var(--transition-fast); } .resizer:hover, .resizer:active { background: var(--accent); } .resizer-queue { right: calc(var(--queue-width) - 3px); } /* ─── Sidebar ─── */ .sidebar { grid-area: sidebar; display: flex; flex-direction: column; background: var(--bg-sidebar); border-right: 1px solid var(--border-subtle); position: relative; z-index: 2; min-height: 0; /* allow 1fr row to shrink freely */ } .sidebar-brand { display: flex; align-items: center; gap: var(--space-3); padding: 0 var(--space-5); height: 64px; box-sizing: border-box; border-bottom: 1px solid var(--border-subtle); } .sidebar-brand .logo-icon { width: 32px; height: 32px; flex-shrink: 0; } .sidebar-brand .brand-name { font-family: var(--font-display); font-size: 20px; font-weight: 700; letter-spacing: -0.02em; background: linear-gradient(135deg, var(--accent), var(--ctp-blue)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .sidebar-nav { flex: 1; padding: var(--space-1) var(--space-3) var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); overflow-y: auto; } .nav-section-label { font-size: 10px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); padding: var(--space-4) var(--space-3) var(--space-2); } .nav-link { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-3); border-radius: var(--radius-md); color: var(--text-secondary); font-size: 14px; font-weight: 500; transition: all var(--transition-fast); cursor: pointer; text-decoration: none; position: relative; } .nav-link:hover { background: var(--bg-hover); color: var(--text-primary); } .nav-link.active { background: var(--accent-dim); color: var(--accent); } .nav-link.active::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 3px; height: 60%; background: var(--accent); border-radius: 0 var(--radius-full) var(--radius-full) 0; } .nav-link svg { flex-shrink: 0; opacity: 0.7; transition: opacity var(--transition-fast); } .nav-link.active svg, .nav-link:hover svg { opacity: 1; } /* Now Playing nav link */ .nav-link-nowplaying { color: var(--accent); background: var(--accent-dim); font-weight: 600; } .nav-link-nowplaying:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); } .nav-link-nowplaying.active { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); } .nav-link-nowplaying svg { opacity: 1; } .nav-np-icon-wrap { position: relative; display: flex; align-items: center; flex-shrink: 0; } .nav-np-dot { position: absolute; top: -3px; right: -4px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: np-dot-pulse 1.8s ease-in-out infinite; } @keyframes np-dot-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(0.7); } } /* Collapsed Sidebar Styles */ .sidebar.collapsed .sidebar-brand { justify-content: center; padding: 0 var(--space-3); } .sidebar.collapsed .nav-link { justify-content: center; padding: var(--space-3) 0; } .collapse-btn { position: absolute; top: calc(50% - var(--player-height) / 2); left: calc(var(--sidebar-width) - 11px); transform: translateY(-50%); z-index: 101; width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--border-subtle); background: var(--bg-card); color: var(--text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; box-shadow: var(--shadow-sm); } .collapse-btn:hover { background: var(--bg-hover); color: var(--text-primary); box-shadow: var(--shadow-md); } .queue-toggle-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: var(--radius-md); border: none; background: transparent; color: var(--accent); cursor: pointer; transition: background 0.15s ease, color 0.15s ease; flex-shrink: 0; } .queue-toggle-btn:hover { background: var(--accent-dim); color: var(--accent); } /* ─── Update Toast ─── */ @keyframes update-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } .update-toast { margin: 0 var(--space-1) var(--space-2); padding: var(--space-3) var(--space-3); border-radius: var(--radius-md); background: var(--accent-dim); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); display: flex; flex-direction: column; gap: 3px; animation: update-toast-in 0.35s ease both; } .update-toast-header { display: flex; align-items: center; gap: var(--space-2); color: var(--accent); } .update-toast-label { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent); } .update-toast-version { font-size: 12px; color: var(--text-secondary); padding-left: 22px; } .update-toast-link { font-size: 11px; font-weight: 600; color: var(--accent); text-decoration: none; padding-left: 22px; opacity: 0.8; transition: opacity var(--transition-fast); } .update-toast-link:hover { opacity: 1; text-decoration: underline; } .update-toast-icon { display: flex; align-items: center; justify-content: center; padding: var(--space-2) 0; color: var(--accent); animation: update-toast-in 0.35s ease both; cursor: default; } /* ─── Sidebar offline download queue ─── */ .sidebar-offline-queue { margin: 4px var(--space-1) 0; padding: 6px 10px; border-radius: var(--radius-md); background: var(--accent-dim); border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent); display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--accent); overflow: hidden; } .sidebar-offline-queue--collapsed { justify-content: center; padding: 6px; } @keyframes spin-slow { to { transform: rotate(360deg); } } .spin-slow { animation: spin-slow 2s linear infinite; flex-shrink: 0; } /* ─── Main Content ─── */ .main-content { grid-area: main; display: flex; flex-direction: column; overflow: hidden; min-width: 0; min-height: 0; /* allow 1fr row to shrink freely */ background: var(--bg-app); z-index: 1; } .content-header { display: flex; align-items: center; gap: var(--space-4); padding: 0 var(--space-6); height: 64px; box-sizing: border-box; border-bottom: 1px solid var(--border-subtle); background: var(--bg-app); position: relative; z-index: 10; } .content-header h1 { font-family: var(--font-display); font-size: 18px; font-weight: 600; color: var(--text-primary); } .content-header .spacer { flex: 1; } .content-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: var(--space-6); contain: paint; } /* ─── Offline Banner ─── */ .offline-banner { display: flex; align-items: center; gap: 8px; padding: 6px 16px; background: color-mix(in srgb, var(--accent) 12%, var(--bg-sidebar)); border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); font-size: 12px; font-weight: 500; flex-shrink: 0; } .offline-banner span { flex: 1; } .offline-banner-retry { display: flex; align-items: center; gap: 4px; background: none; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); border-radius: var(--radius-sm); color: var(--accent); font-size: 11px; padding: 2px 8px; cursor: pointer; transition: background var(--transition-fast); } .offline-banner-retry:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } .offline-banner-retry:disabled { opacity: 0.5; cursor: default; } /* ─── Offline Storage Full Banner ─── */ .offline-storage-full-banner { display: flex; align-items: center; gap: 0.75rem; padding: 10px 16px; margin: 0 0 1rem 0; background: color-mix(in srgb, var(--color-danger, #e53935) 12%, var(--bg-card)); border: 1px solid color-mix(in srgb, var(--color-danger, #e53935) 40%, transparent); border-radius: var(--radius-md); font-size: 13px; color: var(--text-primary); line-height: 1.4; } .offline-storage-full-banner span { flex: 1; } .offline-storage-full-dismiss { background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 18px; line-height: 1; padding: 0 2px; flex-shrink: 0; } .offline-storage-full-dismiss:hover { color: var(--text-primary); } /* ─── Player Bar ─── */ .player-bar { grid-area: player; display: flex; align-items: center; gap: var(--space-6); padding: 0 var(--space-5); background: var(--bg-player); border-top: 1px solid var(--border-subtle); height: var(--player-height); position: relative; z-index: 100; } .player-track-info { display: flex; align-items: center; gap: var(--space-3); flex: 0 0 320px; min-width: 0; overflow: hidden; } .player-album-art { width: 52px; height: 52px; border-radius: var(--radius-md); object-fit: cover; background: var(--bg-card); flex-shrink: 0; box-shadow: var(--shadow-md); } .player-album-art-placeholder { width: 52px; height: 52px; border-radius: var(--radius-md); background: var(--bg-card); flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: var(--text-muted); } .player-album-art-wrap { position: relative; flex-shrink: 0; border-radius: var(--radius-sm); overflow: hidden; } .player-album-art-wrap::after { content: ''; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.3); opacity: 0; transition: opacity var(--transition-fast); pointer-events: none; } .player-album-art-wrap.clickable { cursor: pointer; } .player-album-art-wrap:hover::after { opacity: 1; } /* Expand icon hint that appears on cover hover */ .player-art-expand-hint { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: white; opacity: 0; transition: opacity var(--transition-fast); pointer-events: none; z-index: 1; } .player-album-art-wrap.clickable:hover .player-art-expand-hint { opacity: 1; } .player-track-meta { flex: 1 1 0; min-width: 0; overflow: hidden; } .player-track-name { font-size: 14px; font-weight: 600; color: var(--text-primary); } .player-track-artist { font-size: 12px; color: var(--text-secondary); margin-top: 2px; } /* ── Marquee (PlayerBar track name / artist) ── */ .marquee-wrap { overflow: hidden; white-space: nowrap; width: 100%; } .marquee-scroll { display: inline-block; animation: player-marquee 12s linear infinite; animation-delay: 2s; } @keyframes player-marquee { 0%, 12% { transform: translateX(0); } 78%, 88% { transform: translateX(var(--marquee-amount)); } 88.1%, 100% { transform: translateX(0); } } .player-buttons { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; } .player-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; color: var(--text-muted); transition: all var(--transition-fast); flex-shrink: 0; } .player-btn:hover { background: var(--bg-hover); color: var(--text-primary); transform: scale(1.12); } .player-btn-sm { width: 28px; height: 28px; } .player-btn-primary { width: 46px; height: 46px; background: var(--accent); color: var(--ctp-crust); border-radius: 50%; box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4); flex-shrink: 0; } .player-btn-primary:hover { background: var(--accent); color: var(--ctp-crust); transform: scale(1.06); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4), var(--shadow-glow); filter: brightness(1.12); } /* Waveform seekbar section */ .player-waveform-section { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-2); margin-left: 32px; margin-right: 32px; } .player-waveform-wrap { flex: 1; min-width: 0; } .player-time { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; min-width: 2.5rem; } .player-time:first-child { text-align: right; } .player-time:last-child { text-align: left; } /* Volume section */ .player-volume-section { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; width: 155px; } .player-volume-slider-wrap { flex: 1; min-width: 0; position: relative; } .player-volume-slider { width: 100%; } .player-volume-pct { position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%); font-size: 10px; font-weight: 600; background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-subtle); padding: 2px 6px; border-radius: var(--radius-sm); pointer-events: none; white-space: nowrap; box-shadow: var(--shadow-sm); } .player-eq-btn { color: var(--text-muted); flex-shrink: 0; transition: color 0.15s; } .player-eq-btn:hover, .player-eq-btn.active { color: var(--accent); } /* ─── EQ Popup ─── */ .eq-popup-backdrop { position: fixed; inset: 0; z-index: 200; background: rgba(0, 0, 0, 0.35); backdrop-filter: blur(2px); } .eq-popup { position: fixed; z-index: 201; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(700px, 92vw); background: var(--bg-sidebar); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5); padding: 20px; display: flex; flex-direction: column; gap: 16px; } .eq-popup .eq-wrap { max-width: none; } .eq-popup-header { display: flex; align-items: center; justify-content: space-between; } .eq-popup-title { font-size: 15px; font-weight: 600; color: var(--text-primary); } .eq-popup-close { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 6px; background: var(--bg-hover); border: 1px solid var(--border); color: var(--text-muted); cursor: pointer; transition: background 0.15s, color 0.15s; } .eq-popup-close:hover { background: var(--accent); color: var(--bg-app); } /* ─── Queue Panel ─── */ .queue-panel { grid-area: queue; background: var(--bg-sidebar); border-left: 1px solid var(--border-subtle); display: flex; flex-direction: column; overflow: hidden; min-width: 0; min-height: 0; /* allow 1fr row to shrink freely */ } .queue-panel.queue-drop-active { box-shadow: inset 0 0 0 2px var(--accent); background: linear-gradient(var(--accent-dim), var(--accent-dim)), var(--bg-sidebar); } .queue-header { height: 64px; box-sizing: border-box; padding: 0 var(--space-4); background: var(--bg-app); border-bottom: 1px solid var(--border-subtle); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .queue-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-sm); color: var(--text-muted); cursor: pointer; transition: background var(--transition-fast), color var(--transition-fast); flex-shrink: 0; } .queue-action-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--text-primary); } .queue-action-btn:disabled { opacity: 0.35; cursor: default; } .queue-toolbar { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 0 var(--space-3); height: 44px; flex-shrink: 0; border-bottom: 1px solid var(--border-subtle); background: var(--bg-app); } .crossfade-popover { position: absolute; top: calc(100% + 10px); right: 0; width: 170px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 10px 12px; box-shadow: 0 4px 16px rgba(0,0,0,0.3); z-index: 200; display: flex; flex-direction: column; gap: 8px; } .crossfade-popover-label { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; color: var(--accent); letter-spacing: 0.03em; } .crossfade-popover-value { margin-left: auto; font-size: 12px; font-weight: 700; font-family: monospace; color: var(--text-primary); } .crossfade-popover-slider { width: 100%; accent-color: var(--accent); cursor: pointer; } .crossfade-popover-range { display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted); } .queue-toolbar-sep { width: 1px; height: 18px; background: var(--border-subtle); margin: 0 2px; flex-shrink: 0; } .queue-round-btn { display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 50%; color: var(--text-primary); background: var(--bg-hover); border: none; cursor: pointer; transition: background var(--transition-fast), color var(--transition-fast); flex-shrink: 0; } .queue-round-btn:hover:not(:disabled) { background: var(--border); color: var(--text-primary); } .queue-round-btn:disabled { opacity: 0.35; cursor: default; } .queue-round-btn.active { color: var(--ctp-base); background: var(--accent); } .queue-current-track { display: flex; flex-direction: column; flex-shrink: 0; border-bottom: 1px solid var(--border-subtle); } .queue-current-track-body { padding: var(--space-3) var(--space-4); display: flex; flex-direction: row; align-items: flex-start; gap: var(--space-3); } .queue-current-cover { width: 90px; height: 90px; flex-shrink: 0; border-radius: var(--radius-md); overflow: hidden; background: var(--bg-surface); display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.2); position: relative; } .queue-current-cover img { width: 100%; height: 100%; object-fit: cover; } .queue-current-cover .fallback { color: var(--text-muted); } .queue-current-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; } .queue-current-info h3 { font-size: 13px; font-weight: 700; margin: 0; color: var(--text-primary); } .queue-current-sub { font-size: 12px; color: var(--text-secondary); } .queue-current-tech { width: 100%; font-size: 9px; font-family: monospace; letter-spacing: 0.05em; background: var(--ctp-surface1); color: var(--accent); padding: 3px var(--space-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; backdrop-filter: blur(8px); } .queue-divider { padding: var(--space-3) var(--space-4) 0; flex-shrink: 0; } .queue-list { flex: 1; overflow-y: auto; padding: var(--space-2); } .queue-empty { padding: var(--space-4); color: var(--text-muted); text-align: center; font-size: 14px; } .queue-item { display: flex; align-items: center; justify-content: space-between; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); cursor: pointer; transition: background var(--transition-fast); color: var(--text-secondary); } /* Prevent child elements from stealing dragenter/dragleave events */ .queue-item > * { pointer-events: none; } .queue-item:hover, .queue-item.context-active { background: var(--bg-hover); color: var(--text-primary); } .queue-item.active { background: var(--accent-dim); color: var(--accent); } .queue-item-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; } .queue-item-title { font-size: 12px; font-weight: 500; color: inherit; } .queue-item-artist { font-size: 11px; color: var(--text-muted); } .queue-item-duration { font-size: 11px; color: var(--text-muted); flex-shrink: 0; margin-left: var(--space-3); } .queue-item.active .queue-item-artist, .queue-item.active .queue-item-duration { color: inherit; opacity: 0.8; }