/* ── Static fullscreen player (lean, media-center layout) ────────────────── No backdrop-filter, no blur, no infinite animations. Background is a sharp image; the darkening gradient + vignette are plain static overlays. Only the seekbar and clock update at runtime, and they own their state. */ .fsp { position: fixed; inset: 0; z-index: 1000; background: #06060e; color: #fff; overflow: hidden; user-select: none; --fsp-cover-size: clamp(150px, 24vh, 250px); } .fsp[data-idle='true'] { cursor: none; } /* ── Background ── */ .fsp-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; /* Bias toward the upper third so faces survive a 16:9 crop of square art. */ object-position: center 30%; /* Eases in once the image has loaded (toggled via `.is-loaded`), so a new background fades up from the empty backdrop instead of hard-cutting. */ opacity: 0; transition: opacity 0.4s ease; } .fsp-bg.is-loaded { opacity: 1; } .fsp-bg--empty { /* The empty/loading backdrop is shown as-is (no image to fade in). */ opacity: 1; background: radial-gradient(circle at 50% 35%, #1a1a2e, #06060e 70%); } /* Static text-contrast scrim + edge vignette — baked look, no runtime effect. */ .fsp-scrim { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(to top, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 38%), linear-gradient(to right, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0) 55%), linear-gradient(to bottom, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 22%); } .fsp-vignette { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(ellipse 75% 75% at 50% 45%, transparent 55%, rgba(0, 0, 0, 0.55) 100%); } /* ── Top bar ── */ .fsp-top { position: absolute; top: 0; left: 0; right: 0; z-index: 3; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) var(--space-6); padding-right: calc(var(--space-6) + 52px); /* leave room for the close button */ } .fsp-nowplaying { display: flex; flex-direction: column; gap: 2px; } .fsp-nowplaying-label { font-size: 14px; font-weight: 500; color: rgba(255, 255, 255, 0.92); } .fsp-nowplaying-pos { font-size: 12px; color: rgba(255, 255, 255, 0.6); } .fsp-clock { font-size: 15px; color: rgba(255, 255, 255, 0.92); font-variant-numeric: tabular-nums; } /* ── Close (fades when idle) ── */ .fsp-close { position: absolute; top: var(--space-4); right: var(--space-4); z-index: 4; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: var(--radius-full); background: rgba(0, 0, 0, 0.35); color: rgba(255, 255, 255, 0.8); cursor: pointer; opacity: 0; transition: opacity var(--transition-base), background var(--transition-fast), color var(--transition-fast); } .fsp[data-idle='false'] .fsp-close, .fsp:hover .fsp-close { opacity: 1; } .fsp-close:hover { background: rgba(0, 0, 0, 0.6); color: #fff; } /* ── Bottom cluster ── */ .fsp-foot { position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; flex-direction: column; gap: var(--space-4); padding: 0 var(--space-6) var(--space-5); /* Soft backing for the control row only — the text has its own hard bar. */ background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.18) 26%, rgba(0, 0, 0, 0) 52%); } /* The black bar: hugs the text + cover with equal padding above and below. The cover is bottom-aligned to the text and pokes above the bar's top. */ .fsp-info-row { position: relative; min-width: 0; /* Span the full width (cancel the foot's side padding); text stays inset. */ margin: 0 calc(-1 * var(--space-6)); padding: var(--space-4) var(--space-6); background: rgba(0, 0, 0, 0.5); } /* Big cover, bottom-flush with the text block; its top pokes above the bar. */ .fsp-cover { position: absolute; left: var(--space-6); bottom: var(--space-4); z-index: 1; width: var(--fsp-cover-size); height: var(--fsp-cover-size); border-radius: var(--radius-md); overflow: hidden; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.75); } .fsp-cover-img { display: block; width: 100%; height: 100%; object-fit: cover; } .fsp-cover-img--empty { background: var(--bg-hover); } .fsp-info-text { min-width: 0; display: flex; flex-direction: column; gap: 5px; /* Clear the overlapping cover on the left. */ padding-left: calc(var(--fsp-cover-size) + var(--space-5)); } .fsp-title { margin: 0; font-size: 46px; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.7); } .fsp-artist { margin: 0; font-size: 25px; font-weight: 500; color: rgba(255, 255, 255, 0.92); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); } .fsp-meta { display: flex; align-items: center; gap: 10px; font-size: 17px; color: rgba(255, 255, 255, 0.78); text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); } .fsp-meta-dot { opacity: 0.55; } .fsp-stars { display: inline-flex; gap: 2px; color: var(--accent); } .fsp-star-btn { display: inline-flex; align-items: center; padding: 0; border: 0; background: none; color: inherit; cursor: pointer; line-height: 0; transition: transform 80ms ease; } .fsp-star-btn:hover { transform: scale(1.15); } .fsp-next { margin: 3px 0 0; font-size: 16px; color: rgba(255, 255, 255, 0.65); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); } /* ── Controls row: transport · time · actions ── */ .fsp-controls { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--space-4); /* Own semi-transparent bar, a bit darker than the info bar above for contrast. Full-width: cancel the foot's side padding like .fsp-info-row. Negative top margin cancels the foot's flex gap so this bar touches the info bar above (the gap to the waveform below stays). */ margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-6)) 0; padding: var(--space-3) var(--space-6); background: rgba(0, 0, 0, 0.88); } .fsp-transport { display: flex; align-items: center; gap: var(--space-2); justify-self: start; } .fsp-actions { display: flex; align-items: center; gap: var(--space-2); justify-self: end; } .fsp-time { justify-self: center; font-size: 18px; color: rgba(255, 255, 255, 0.9); font-variant-numeric: tabular-nums; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); white-space: nowrap; } .fsp-btn { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: none; border-radius: var(--radius-full); background: transparent; color: rgba(255, 255, 255, 0.85); cursor: pointer; transition: background var(--transition-fast), color var(--transition-fast); } .fsp-btn-sm { width: 38px; height: 38px; color: rgba(255, 255, 255, 0.6); } .fsp-btn:hover { background: rgba(255, 255, 255, 0.12); color: #fff; } .fsp-btn.active { color: var(--accent); } /* Bottom-right action cluster: larger tap targets than the transport's small buttons. */ .fsp-actions .fsp-btn-sm { width: 46px; height: 46px; } /* Reused FsPlayBtn (renders .fs-btn.fs-btn-play) — scope its look to this player. */ .fsp .playback-transport-play-wrap { position: relative; display: inline-flex; } /* Match the plain transport buttons — no white circle. */ .fsp .fs-btn-play { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: 0; border: none; border-radius: var(--radius-full); background: transparent; color: rgba(255, 255, 255, 0.85); cursor: pointer; transition: background var(--transition-fast), color var(--transition-fast); } .fsp .fs-btn-play:hover { background: rgba(255, 255, 255, 0.12); color: #fff; } /* ── True waveform seekbar (WaveformSeek) — full-width media-center bar ── Replaces the old thin FsSeekbar. Time stays centered in the control row (FsTimeReadout). The taller canvas makes the foot grow upward, shifting the info/control rows up. */ .fsp .waveform-seek-container { width: 100%; } /* WaveformSeek hardcodes a 24px canvas inline for the player bar; override it here for the fullscreen view. Its ResizeObserver redraws at the new size. */ .fsp .waveform-seek-container canvas { height: clamp(32px, 5vh, 52px) !important; } /* ── "Up next" overlay (FsQueueModal) — semi-transparent popover anchored bottom-right at the queue button, no blur. Transparent backdrop just catches the outside click to close. ── */ .fsq-backdrop { position: absolute; inset: 0; z-index: 5; background: transparent; } .fsq-panel { position: absolute; right: var(--space-6); bottom: 163px; /* flush with the top of the black info frame */ width: min(420px, 40%); max-height: 62vh; display: flex; flex-direction: column; background: rgba(18, 18, 22, 0.82); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: var(--radius-lg); box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6); overflow: hidden; } .fsq-header { display: flex; align-items: center; justify-content: space-between; padding: var(--space-4) var(--space-5); border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .fsq-title { font-size: 15px; font-weight: 600; color: #fff; } .fsq-close { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: var(--radius-full); background: transparent; color: rgba(255, 255, 255, 0.7); cursor: pointer; } .fsq-close:hover { background: rgba(255, 255, 255, 0.12); color: #fff; } .fsq-list { overflow-y: auto; padding: var(--space-2) 0; } .fsq-empty { padding: var(--space-5); text-align: center; color: rgba(255, 255, 255, 0.55); font-size: 13px; } .fsq-item { display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: var(--space-2) var(--space-5); border: none; background: transparent; color: rgba(255, 255, 255, 0.9); cursor: pointer; text-align: left; } .fsq-item:hover { background: rgba(255, 255, 255, 0.08); } .fsq-item-pos { width: 24px; flex-shrink: 0; text-align: right; font-variant-numeric: tabular-nums; font-size: 12px; color: rgba(255, 255, 255, 0.45); } .fsq-item-info { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 1px; } .fsq-item-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; } .fsq-item-artist { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: rgba(255, 255, 255, 0.55); } .fsq-item-dur { flex-shrink: 0; font-variant-numeric: tabular-nums; font-size: 12px; color: rgba(255, 255, 255, 0.5); } /* ── Scrolling lyrics overlay (reuses FsLyricsApple) — semi-transparent, no blur. FsLyricsApple's .fsa-lyrics-container is position:absolute inset:0, so it fills this box. ── */ .fsp-lyrics-overlay { position: absolute; top: var(--space-6); left: 14%; right: 14%; bottom: 360px; /* ends above the song-info / cover block; tune */ z-index: 4; background: rgba(0, 0, 0, 0.88); border-radius: var(--radius-lg); overflow: hidden; } /* Centered lyrics text in the fullscreen overlay (FsLyricsApple defaults to left-aligned for the old layout). */ .fsp-lyrics-overlay .fsa-lyrics-container { text-align: center; } .fsp-lyrics-overlay .fsa-lyric-line { text-align: center; transform-origin: center center; }