player bar: it grows upward, the cursor is marked by the wave itself, and the shape you leave it in survives a restart
Expanding (mock E1+T1, cubic-bezier(.22,1,.36,1) over 350ms to 90px):
`.app.player-expanded` sets --player-height beside the sidebar's own rule
and grid-template-rows joins the transition, so the bar grows into the
content and its bottom edge is pinned without a rule saying so — the
grid's height is the window, the bar's track takes 28px and 1fr gives up
exactly that. Inside the bar four blocks are absolutely placed so the two
layouts can interpolate; flex cannot, because one row to a centred
two-row stack means changing flex-direction, which does not animate. Name
and extras sit at top 50% with a -50% translate and are centred at every
height in both states. --btn-w and --extras-w are measured once rather
than assumed: extras is 180px, not the 170 the layout was drafted with,
and it differs between a stream and a library track. The toggle takes the
download button's seat (the sidebar's .gicon, verbatim) and Save to
library moves into the ... menu under the same isStream gate. Measured:
name and extras vertical error 0 in both states, transport and lane
horizontal error 0 expanded, lane exactly 10px under the transport.
The 1px playhead tick is gone from all three painters. The bar under the
cursor lifts while its neighbours dip (A4): growing alone reads as
nothing, because the median bar is 21.3px of a 24px lane and 87% of bars
have no headroom left to grow into — the dip is what carries the
silhouette. The wash is enter/leave only, one shared presence ramp with
hard span membership, so the edge cannot trail the pointer: 0-bar lag
across five jumps in both directions, and moving inside the lane
schedules no frames at all. The settled painter keeps its cached-Path2D
grouped fills whenever no pointer is on the lane.
The clipped-name edge fade from 2a83615 had never once rendered. Its
classes are applied by classList.toggle inside an action, which the
compiler cannot see, so Svelte deleted the rules as unused — that warning
is a deletion notice, not advice. :global() on the mask class only, with
the anchor left scoped, and all four states now verify against a
narrowed box.
Panel shapes persist in one key, soulstream.panels.v1, holding
nowPlaying, sidebarCollapsed and playerExpanded together so the three
cannot end up half-migrated against each other; it is read per-key so an
older blob contributes what it has. showNowPlaying() exists because
openQueue assigned ui.nowPlaying directly and would have skipped the
write.
This commit is contained in:
1 parent
e976fb88ba
commit
5786269592
3 files changed
+470
-102
No files matched your search
+61
-2
@@ -43,6 +43,46 @@ function readPanelWidth() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Every panel-shape flag, remembered across a reload AND a restart. The
|
||||
* reasoning that already persists `nowPlayingWidth` applies to all of them: a
|
||||
* shape you set by hand and lost on every launch is worse than not being able
|
||||
* to set it. One key holding all three, so the three toggles cannot end up
|
||||
* half-migrated against each other. */
|
||||
const PANEL_KEY = "soulstream.panels.v1";
|
||||
const PANEL_DEFAULTS = { nowPlaying: true, sidebarCollapsed: false, playerExpanded: false };
|
||||
|
||||
function readPanels() {
|
||||
try {
|
||||
const raw = JSON.parse(localStorage.getItem(PANEL_KEY) || "null");
|
||||
if (!raw || typeof raw !== "object") return { ...PANEL_DEFAULTS };
|
||||
const out = { ...PANEL_DEFAULTS };
|
||||
// Per-key, so a stored blob written by an older build (or a hand-edited
|
||||
// one) contributes the flags it does have and defaults the rest.
|
||||
for (const k of Object.keys(PANEL_DEFAULTS)) {
|
||||
if (typeof raw[k] === "boolean") out[k] = raw[k];
|
||||
}
|
||||
return out;
|
||||
} catch {
|
||||
return { ...PANEL_DEFAULTS };
|
||||
}
|
||||
}
|
||||
const panels0 = readPanels();
|
||||
|
||||
function writePanels() {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
PANEL_KEY,
|
||||
JSON.stringify({
|
||||
nowPlaying: ui.nowPlaying,
|
||||
sidebarCollapsed: ui.sidebarCollapsed,
|
||||
playerExpanded: ui.playerExpanded,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// A blocked storage quota must not break the toggle for the session.
|
||||
}
|
||||
}
|
||||
|
||||
export const ui = $state({
|
||||
view: "home",
|
||||
albumId: null, // selected album in Library view
|
||||
@@ -50,8 +90,11 @@ export const ui = $state({
|
||||
// The Now Playing panel is EXPANDED vs COLLAPSED, never gone: collapsing it
|
||||
// leaves the rail (see NowPlaying.svelte), the same way the left nav
|
||||
// collapses to an icon rail rather than disappearing.
|
||||
nowPlaying: true,
|
||||
sidebarCollapsed: false, // left nav collapsed to an icon rail
|
||||
nowPlaying: panels0.nowPlaying,
|
||||
sidebarCollapsed: panels0.sidebarCollapsed, // left nav collapsed to an icon rail
|
||||
// The player bar grows UPWARD into the content — the transport centres and
|
||||
// the lane drops to its own row beneath it. See PlayerBar.svelte.
|
||||
playerExpanded: panels0.playerExpanded,
|
||||
// User-dragged width of the expanded panel. Persisted, unlike the other ui
|
||||
// flags: a width you set by hand and lost on every launch would be worse
|
||||
// than not being able to set it at all.
|
||||
@@ -72,6 +115,21 @@ export const ui = $state({
|
||||
|
||||
export function toggleNowPlaying() {
|
||||
ui.nowPlaying = !ui.nowPlaying;
|
||||
writePanels();
|
||||
}
|
||||
|
||||
/** Reveal the panel (the queue lives in it). Goes through here rather than
|
||||
* assigning `ui.nowPlaying` directly so the shape is still remembered. */
|
||||
export function showNowPlaying() {
|
||||
if (ui.nowPlaying) return;
|
||||
ui.nowPlaying = true;
|
||||
writePanels();
|
||||
}
|
||||
|
||||
/** Grow the player bar upward, or put it back. */
|
||||
export function togglePlayerExpanded() {
|
||||
ui.playerExpanded = !ui.playerExpanded;
|
||||
writePanels();
|
||||
}
|
||||
|
||||
/** Set the expanded panel's width, clamped and remembered. */
|
||||
@@ -87,6 +145,7 @@ export function setNowPlayingWidth(px) {
|
||||
|
||||
export function toggleSidebar() {
|
||||
ui.sidebarCollapsed = !ui.sidebarCollapsed;
|
||||
writePanels();
|
||||
}
|
||||
|
||||
export const libraryStore = $state({
|
||||
|
||||
Reference in new issue
Block a user