Pi profile: no glass blur, keep the drift; consolidate the perf knobs
With the ambient canvas off under ?pi=1, the backdrop blur no longer buys anything: it paid for itself by giving each panel a compositing layer above the animating canvas, and with nothing animating underneath it was just cost - enough to make the browse rows visibly sluggish on the device. So the Pi profile now drops every backdrop blur (`glassBlur`, which the music app and the typing game share), and turns the drift and the moving mascot back on: plain CSS transform loops the compositor runs by itself, which measured as noise. The knob set is consolidated to match: one glass toggle instead of panel and card blur, one shadows toggle for cards and tiles, the typing game's drift and view transitions folded into the shared ones, and the backing-store cap fixed by profile rather than a setting. The parent dialog groups them into Darstellung / Abspielen / System and hides the canvas tuning while the canvas is off. Stored settings move to musicmouse.perf.v2, since v1 drafts held the old preset in full. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ import { targetForAlbum } from "./lib/remote";
|
||||
import type { Group, Mode, Results, SongHit } from "./lib/search";
|
||||
import { buildSearchIndex, categoryMatches, groupOf, results as computeResults } from "./lib/search";
|
||||
import { SKIN } from "./lib/skins";
|
||||
import { SHOW_AMBIENCE, SHOW_CARD_BLUR, SHOW_GLASS_BLUR } from "./lib/theme";
|
||||
import { SHOW_AMBIENCE, SHOW_GLASS_BLUR } from "./lib/theme";
|
||||
|
||||
// The room page and the typing game are each a tab away from the player and together a
|
||||
// large share of the bundle; the kiosk parses and compiles only what it opens.
|
||||
@@ -449,7 +449,6 @@ export function App() {
|
||||
className="stage"
|
||||
data-phone={phone || undefined}
|
||||
data-blur={SHOW_GLASS_BLUR ? undefined : "off"}
|
||||
data-cardblur={SHOW_CARD_BLUR ? undefined : "off"}
|
||||
>
|
||||
{state && SHOW_AMBIENCE && (
|
||||
<Ambience
|
||||
|
||||
@@ -766,7 +766,7 @@ export function CategoryTile({
|
||||
: `oklch(95% 0.015 ${GROUP_HUE.music} / .66)`,
|
||||
borderRadius: allBooks ? "6px 18px 18px 6px" : "16px",
|
||||
// One blurred shadow per tile: dropped under `?pi=1`, like `cardShadow`.
|
||||
boxShadow: PERF.tileShadows ? "0 6px 18px var(--shadow)" : "none",
|
||||
boxShadow: PERF.shadows ? "0 6px 18px var(--shadow)" : "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -19,30 +19,46 @@ type NumKey = { [K in keyof PerfSettings]: PerfSettings[K] extends number ? K :
|
||||
|
||||
/** What drifts and how the mascot moves, in the skin's own words. */
|
||||
const DRIFT = SKIN.drift === "leaves" ? "Blätter" : "Blasen";
|
||||
const DRIFT_MOVING = SKIN.drift === "leaves" ? "Fallende Blätter" : "Aufsteigende Blasen";
|
||||
const MASCOT_MOVING = SKIN.mascotMotion === "walk" ? "wanderndes" : "schwimmendes";
|
||||
|
||||
const TOGGLES: [BoolKey, string, string][] = [
|
||||
["ambience", "Animierter Hintergrund", "Canvas im Abspielbildschirm (größter CPU-Verbraucher)"],
|
||||
["cardBlur", "Weichzeichner auf Karten", "Backdrop-Blur je Album-Karte und Titelzeile"],
|
||||
["viewTransitions", "Übergänge", "Ein-/Ausblenden beim Öffnen von Gruppen und Alben"],
|
||||
["decorativeAnimations", "Deko-Animationen", `${DRIFT} im Zimmer, ${MASCOT_MOVING} Maskottchen`],
|
||||
["cardShadows", "Schatten auf Karten", "Schlagschatten unter den Album-Karten"],
|
||||
["tileShadows", "Schatten auf Kacheln", "Schlagschatten unter den Kacheln im Raster"],
|
||||
["petAnimations", "Tippen: Tiere bewegen sich", "Aus: Tiere stehen still"],
|
||||
["tippenBubbles", `Tippen: ${DRIFT}`, `${DRIFT_MOVING} im Hintergrund`],
|
||||
["tippenViewTransitions", "Tippen: Übergänge", "Ein-/Ausblenden der Bildschirme"],
|
||||
["serviceWorker", "Service Worker", "Aus: vorhandener Worker wird entfernt"],
|
||||
["coverWarmup", "Cover vorladen", "Cover im Hintergrund dekodieren"],
|
||||
type Item =
|
||||
| { kind: "toggle"; key: BoolKey; label: string; hint: string }
|
||||
| { kind: "number"; key: NumKey; label: string; hint: string; min: number; max: number; step: number };
|
||||
|
||||
/** Each section's items, in order. An item with `when` only shows while that toggle is on
|
||||
* in the draft - the canvas tuning means nothing with the canvas off. */
|
||||
const SECTIONS: { title: string; items: (Item & { when?: BoolKey })[] }[] = [
|
||||
{
|
||||
title: "Darstellung",
|
||||
items: [
|
||||
{ kind: "toggle", key: "glassBlur", label: "Glaseffekt", hint: "Weichzeichner hinter Reihen, Karten und Leisten" },
|
||||
{ kind: "toggle", key: "shadows", label: "Schatten", hint: "Schlagschatten unter Karten und Kacheln" },
|
||||
{ kind: "toggle", key: "viewTransitions", label: "Übergänge", hint: "Ein-/Ausblenden beim Öffnen und Wechseln" },
|
||||
{ kind: "toggle", key: "decorativeAnimations", label: "Deko-Animationen", hint: `${DRIFT} und ${MASCOT_MOVING} Maskottchen` },
|
||||
{ kind: "toggle", key: "petAnimations", label: "Tippen: Tiere bewegen sich", hint: "Aus: Tiere stehen still" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Abspielen",
|
||||
items: [
|
||||
{ kind: "toggle", key: "ambience", label: "Animierter Hintergrund", hint: "Canvas im Abspielbildschirm - nur zusammen mit Glaseffekt" },
|
||||
{ kind: "number", key: "ambienceScale", when: "ambience", label: "Hintergrund: Auflösung", hint: "Faktor der Canvas-Größe", min: 0.1, max: 1, step: 0.05 },
|
||||
{ kind: "number", key: "ambienceMaxFps", when: "ambience", label: "Hintergrund: max. FPS", hint: "0 = unbegrenzt", min: 0, max: 60, step: 1 },
|
||||
{ kind: "number", key: "ambienceMaxBubbles", when: "ambience", label: `Hintergrund: max. ${DRIFT}`, hint: "0 = unbegrenzt", min: 0, max: 500, step: 5 },
|
||||
{ kind: "number", key: "playbackClockFps", label: "Fortschrittsbalken: Updates/s", hint: "0 = jeder Frame", min: 0, max: 60, step: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "System",
|
||||
items: [
|
||||
{ kind: "toggle", key: "coverWarmup", label: "Cover vorladen", hint: "Cover im Hintergrund dekodieren" },
|
||||
{ kind: "toggle", key: "serviceWorker", label: "Service Worker", hint: "Aus: vorhandener Worker wird entfernt" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const NUMBERS: [NumKey, string, string, number, number, number][] = [
|
||||
["playbackClockFps", "Fortschrittsbalken: Updates/s", "0 = jeder Frame", 0, 60, 1],
|
||||
["ambienceScale", "Hintergrund: Auflösung", "Faktor der Canvas-Größe", 0.1, 1, 0.05],
|
||||
["ambienceMaxPx", "Hintergrund: max. Pixel", "Obergrenze je Achse", 256, 4096, 64],
|
||||
["ambienceMaxFps", "Hintergrund: max. FPS", "0 = unbegrenzt", 0, 60, 1],
|
||||
["ambienceMaxBubbles", `Hintergrund: max. ${DRIFT}`, "0 = unbegrenzt", 0, 500, 5],
|
||||
];
|
||||
const HINT = { fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 var(--hue))" } as const;
|
||||
const LABEL = { fontSize: 14, fontWeight: 800, color: "var(--ink)" } as const;
|
||||
|
||||
const pill = (background: string, color: string) =>
|
||||
({
|
||||
@@ -100,39 +116,49 @@ export function PerfPanel({ onClose }: { onClose: () => void }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
|
||||
{TOGGLES.map(([key, label, hint]) => (
|
||||
<label key={key} style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 6 }}>
|
||||
{SECTIONS.map(({ title, items }) => (
|
||||
<div key={title} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<div
|
||||
style={{
|
||||
...HINT,
|
||||
fontWeight: 900,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: 0.6,
|
||||
marginTop: 14,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{items
|
||||
.filter((item) => !item.when || draft[item.when])
|
||||
.map((item) =>
|
||||
item.kind === "toggle" ? (
|
||||
<label key={item.key} style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft[key]}
|
||||
onChange={(event) => setDraft({ ...draft, [key]: event.target.checked })}
|
||||
checked={draft[item.key]}
|
||||
onChange={(event) => setDraft({ ...draft, [item.key]: event.target.checked })}
|
||||
style={{ width: 22, height: 22 }}
|
||||
/>
|
||||
<span style={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 800, color: "var(--ink)" }}>{label}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 var(--hue))" }}>
|
||||
{hint}
|
||||
</span>
|
||||
<span style={LABEL}>{item.label}</span>
|
||||
<span style={HINT}>{item.hint}</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
|
||||
{NUMBERS.map(([key, label, hint, min, max, step]) => (
|
||||
<label key={key} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 800, color: "var(--ink)" }}>{label}</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 var(--hue))" }}>
|
||||
{hint}
|
||||
</span>
|
||||
) : (
|
||||
<label key={item.key} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<span style={LABEL}>{item.label}</span>
|
||||
<span style={HINT}>{item.hint}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={draft[key]}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
step={item.step}
|
||||
value={draft[item.key]}
|
||||
onChange={(event) => {
|
||||
const value = event.target.valueAsNumber;
|
||||
if (Number.isFinite(value)) setDraft({ ...draft, [key]: value });
|
||||
if (Number.isFinite(value)) setDraft({ ...draft, [item.key]: value });
|
||||
}}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
@@ -143,6 +169,9 @@ export function PerfPanel({ onClose }: { onClose: () => void }) {
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div style={{ display: "flex", gap: 10, marginTop: 6 }}>
|
||||
@@ -154,7 +183,7 @@ export function PerfPanel({ onClose }: { onClose: () => void }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 var(--hue))", lineHeight: 1.5 }}>
|
||||
<div style={{ ...HINT, lineHeight: 1.5 }}>
|
||||
Wird nur in diesem Browser gespeichert und gilt nur mit <code>?pi=1</code>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("perfSettings", () => {
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it("is full-fat without pi, ignoring stored values", async () => {
|
||||
store.set("musicmouse.perf.v1", JSON.stringify({ ambience: false }));
|
||||
store.set("musicmouse.perf.v2", JSON.stringify({ ambience: false }));
|
||||
const m = await load("");
|
||||
expect(m.PERF_ENABLED).toBe(false);
|
||||
expect(m.PERF).toEqual(m.FULL);
|
||||
@@ -30,26 +30,26 @@ describe("perfSettings", () => {
|
||||
});
|
||||
|
||||
it("merges stored overrides over the preset", async () => {
|
||||
store.set("musicmouse.perf.v1", JSON.stringify({ ambience: true, playbackClockFps: 30 }));
|
||||
store.set("musicmouse.perf.v2", JSON.stringify({ ambience: true, playbackClockFps: 30 }));
|
||||
const m = await load("?pi=1");
|
||||
expect(m.PERF.ambience).toBe(true);
|
||||
expect(m.PERF.playbackClockFps).toBe(30);
|
||||
expect(m.PERF.cardBlur).toBe(m.PI.cardBlur);
|
||||
expect(m.PERF.glassBlur).toBe(m.PI.glassBlur);
|
||||
});
|
||||
|
||||
it("drops wrong-typed fields and survives corrupt JSON", async () => {
|
||||
store.set("musicmouse.perf.v1", JSON.stringify({ ambience: "yes", ambienceScale: null }));
|
||||
store.set("musicmouse.perf.v2", JSON.stringify({ ambience: "yes", ambienceScale: null }));
|
||||
expect((await load("?pi=1")).PERF).toEqual((await load("?pi=1")).PI);
|
||||
store.set("musicmouse.perf.v1", "{nope");
|
||||
store.set("musicmouse.perf.v2", "{nope");
|
||||
const m = await load("?pi=1");
|
||||
expect(m.PERF).toEqual(m.PI);
|
||||
});
|
||||
|
||||
it("round-trips through save and reset", async () => {
|
||||
const m = await load("?pi=1");
|
||||
m.savePerfSettings({ ...m.PI, cardBlur: true });
|
||||
expect((await load("?pi=1")).PERF.cardBlur).toBe(true);
|
||||
m.savePerfSettings({ ...m.PI, glassBlur: true });
|
||||
expect((await load("?pi=1")).PERF.glassBlur).toBe(true);
|
||||
m.resetPerfSettings();
|
||||
expect((await load("?pi=1")).PERF.cardBlur).toBe(false);
|
||||
expect((await load("?pi=1")).PERF.glassBlur).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export const cardBackground = (album: Album): string =>
|
||||
* translucent card reads fine and the shadow is a per-card blur render task. Books keep
|
||||
* their two inset "board" bands, which are cheap and are what make them look like books. */
|
||||
export const cardShadow = (album: Album): string =>
|
||||
!PERF.cardShadows
|
||||
!PERF.shadows
|
||||
? isBook(album)
|
||||
? `inset -7px 0 0 oklch(86% 0.09 ${GROUP_HUE.audiobooks} / .7), ` +
|
||||
`inset -11px 0 0 oklch(78% 0.09 ${GROUP_HUE.audiobooks} / .7)`
|
||||
|
||||
@@ -21,21 +21,25 @@
|
||||
* panel to its own compositing layer, so a canvas frame underneath repaints only the
|
||||
* canvas. Take the blur away and the canvas and everything stacked above it collapse
|
||||
* into one layer, and every single frame repaints the whole page - all of the album
|
||||
* grid included. The most expensive-looking CSS in the app is the thing keeping the
|
||||
* rest of it cheap. Do not "optimize" it away without re-running the numbers above.
|
||||
* grid included. While the canvas runs, the most expensive-looking CSS in the app is
|
||||
* the thing keeping the rest of it cheap.
|
||||
*
|
||||
* That got it from 53.5% to 25%, and 25% was still not enough to use. The profile now
|
||||
* goes further and stops the animation instead of thinning it: under `?pi=1` the
|
||||
* ambient canvas is not rendered at all (`SHOW_AMBIENCE`), no bubbles rise anywhere,
|
||||
* the decorative CSS loops are off, the view transitions are off, and the typing game's
|
||||
* pets are placed but held still. A requestAnimationFrame loop that repaints the whole
|
||||
* ambient canvas is not rendered at all (`SHOW_AMBIENCE`), the view transitions are
|
||||
* off, and the typing game's pets are placed but held still. A requestAnimationFrame loop that repaints the whole
|
||||
* viewport is a floor you cannot get under while it runs at all, and on a Pi 4 that
|
||||
* floor is too high. With it gone the browser has nothing to do between one keypress
|
||||
* and the next. `.stage` keeps its own static CSS gradient, so the screen still has a
|
||||
* sea behind it - it just no longer moves or follows the track.
|
||||
*
|
||||
* What stays: both blurs on the panels (see the table - dropping those was five times
|
||||
* worse), and the card blur off, which is a count problem rather than a blur problem.
|
||||
* With the canvas gone, the blur's reason to exist went with it: there is nothing moving
|
||||
* underneath a panel for its layer to shield the page from, and what is left is the cost
|
||||
* of the blur itself - which made the browse rows visibly sluggish on the device. So
|
||||
* `?pi=1` now drops every backdrop blur (`glassBlur`), and turns the drift and the mascot
|
||||
* back on, which are plain CSS transform loops the compositor runs by itself (the
|
||||
* "decorative loops" row above: noise). Turning the canvas back on from the dialog
|
||||
* wants the blur back on with it - the 118% row is that combination.
|
||||
*
|
||||
* Read once at module load, from the URL and nowhere else: no persistence, no
|
||||
* auto-detection. The kiosk points Firefox at `http://localhost:8080/?pi=1` (see the
|
||||
@@ -44,7 +48,7 @@
|
||||
* answerable by editing the address bar.
|
||||
*/
|
||||
|
||||
import { PERF } from "./perfSettings";
|
||||
import { PERF, PERF_ENABLED } from "./perfSettings";
|
||||
|
||||
// Superseded as a single switch: every knob below and in theme.ts now comes from
|
||||
// `PERF` (lib/perfSettings.ts), which `?pi=1` seeds and the settings dialog overrides.
|
||||
@@ -78,7 +82,7 @@ export interface AmbienceQuality {
|
||||
* backing store is what took it from 53.5% to 25%. */
|
||||
export const AMBIENCE_QUALITY: AmbienceQuality = {
|
||||
resolutionScale: PERF.ambienceScale,
|
||||
maxBackingStorePx: PERF.ambienceMaxPx,
|
||||
maxBackingStorePx: PERF_ENABLED ? 1280 : 4096,
|
||||
maxFps: PERF.ambienceMaxFps,
|
||||
maxBubbles: PERF.ambienceMaxBubbles,
|
||||
};
|
||||
|
||||
@@ -9,87 +9,75 @@
|
||||
*
|
||||
* Read once at module load. The knobs feed module-level constants (`SHOW_AMBIENCE`, ...)
|
||||
* and the service worker registration, so applying a change means a page reload.
|
||||
*
|
||||
* `SHOW_GLASS_BLUR` is deliberately not a knob: on the Pi it measured five times *worse*
|
||||
* with the blur off. See the table in lib/lowPower.ts.
|
||||
*/
|
||||
|
||||
export interface PerfSettings {
|
||||
/** Fade/slide animation when entering a group/category or opening the album modal. */
|
||||
/** Backdrop blur on every glass surface: the browse rows, cards, list rows, tab rail,
|
||||
* player bar and the typing game's panels and keys. */
|
||||
glassBlur: boolean;
|
||||
/** Fade/slide animation when entering a group/category, opening the album modal, or
|
||||
* switching screens in the typing game. */
|
||||
viewTransitions: boolean;
|
||||
/** The play view's animated canvas background (gradient + bubbles). */
|
||||
ambience: boolean;
|
||||
/** Real backdrop blur on each repeated glass card/row. */
|
||||
cardBlur: boolean;
|
||||
/** Decorative CSS loops: the room page's bubbles and the dolphin mascot. */
|
||||
/** Decorative CSS loops: the drifting bubbles/leaves (room page, typing game) and the
|
||||
* moving mascot. */
|
||||
decorativeAnimations: boolean;
|
||||
/** Drop shadows under covers in the album cards. */
|
||||
cardShadows: boolean;
|
||||
/** Drop shadows under tiles in the browse grid. */
|
||||
tileShadows: boolean;
|
||||
/** Typing game: pets move (off = placed but held still). */
|
||||
petAnimations: boolean;
|
||||
/** Typing game: rising bubbles. */
|
||||
tippenBubbles: boolean;
|
||||
/** Typing game: fade/slide view transitions. */
|
||||
tippenViewTransitions: boolean;
|
||||
/** Register the service worker (off also unregisters an existing one). */
|
||||
serviceWorker: boolean;
|
||||
/** Pre-decode covers in the background. */
|
||||
coverWarmup: boolean;
|
||||
/** Max playback-position renders per second. 0 = every animation frame. */
|
||||
playbackClockFps: number;
|
||||
/** Drop shadows under album cards and browse tiles. */
|
||||
shadows: boolean;
|
||||
/** The play view's animated canvas background (gradient + bubbles). */
|
||||
ambience: boolean;
|
||||
/** Ambient canvas backing-store scale relative to its on-screen size. */
|
||||
ambienceScale: number;
|
||||
/** Ceiling on either ambient canvas backing-store axis, in px. */
|
||||
ambienceMaxPx: number;
|
||||
/** Ambient canvas frame-rate cap. 0 = whatever the display offers. */
|
||||
ambienceMaxFps: number;
|
||||
/** Ceiling on ambient bubbles alive at once. 0 = unlimited. */
|
||||
ambienceMaxBubbles: number;
|
||||
/** Max playback-position renders per second. 0 = every animation frame. */
|
||||
playbackClockFps: number;
|
||||
/** Pre-decode covers in the background. */
|
||||
coverWarmup: boolean;
|
||||
/** Register the service worker (off also unregisters an existing one). */
|
||||
serviceWorker: boolean;
|
||||
}
|
||||
|
||||
/** Today's behaviour without `?pi=1`. */
|
||||
export const FULL: PerfSettings = {
|
||||
glassBlur: true,
|
||||
viewTransitions: true,
|
||||
ambience: true,
|
||||
cardBlur: true,
|
||||
decorativeAnimations: true,
|
||||
cardShadows: true,
|
||||
tileShadows: true,
|
||||
petAnimations: true,
|
||||
tippenBubbles: true,
|
||||
tippenViewTransitions: true,
|
||||
serviceWorker: true,
|
||||
coverWarmup: false,
|
||||
playbackClockFps: 0,
|
||||
shadows: true,
|
||||
ambience: true,
|
||||
ambienceScale: 1,
|
||||
ambienceMaxPx: 4096,
|
||||
ambienceMaxFps: 0,
|
||||
ambienceMaxBubbles: 0,
|
||||
playbackClockFps: 0,
|
||||
coverWarmup: false,
|
||||
serviceWorker: true,
|
||||
};
|
||||
|
||||
/** Today's behaviour with `?pi=1`. */
|
||||
/** Today's behaviour with `?pi=1`. Glass off, drift and mascot on: with the ambient canvas
|
||||
* gone there is nothing animating underneath the panels any more, so the blur no longer
|
||||
* buys anything (see lib/lowPower.ts) and was what made the browse rows sluggish; the
|
||||
* drift and the mascot are plain CSS transform loops the compositor handles on its own. */
|
||||
export const PI: PerfSettings = {
|
||||
glassBlur: false,
|
||||
viewTransitions: false,
|
||||
ambience: false,
|
||||
cardBlur: false,
|
||||
decorativeAnimations: false,
|
||||
cardShadows: false,
|
||||
tileShadows: false,
|
||||
decorativeAnimations: true,
|
||||
petAnimations: false,
|
||||
tippenBubbles: false,
|
||||
tippenViewTransitions: false,
|
||||
serviceWorker: false,
|
||||
coverWarmup: true,
|
||||
playbackClockFps: 10,
|
||||
shadows: false,
|
||||
ambience: false,
|
||||
ambienceScale: 0.5,
|
||||
ambienceMaxPx: 1280,
|
||||
ambienceMaxFps: 30,
|
||||
ambienceMaxBubbles: 60,
|
||||
playbackClockFps: 10,
|
||||
coverWarmup: true,
|
||||
serviceWorker: false,
|
||||
};
|
||||
|
||||
export const STORAGE_KEY = "musicmouse.perf.v1";
|
||||
/** v2: the knob set was consolidated; v1 drafts stored the old preset in full. */
|
||||
export const STORAGE_KEY = "musicmouse.perf.v2";
|
||||
|
||||
/** True when the page was loaded with `?pi=1`. */
|
||||
export const PERF_ENABLED = readPiFlag();
|
||||
|
||||
@@ -43,14 +43,17 @@ export const ROW_SPACING = 25;
|
||||
* container, not per card, so cost stays flat regardless of grid size. */
|
||||
export const ANIMATE_VIEW_TRANSITIONS = PERF.viewTransitions;
|
||||
|
||||
/** Frost the glass panels/cards/rows with a real backdrop blur.
|
||||
/** Frost every glass surface - the browse rows, cards, list rows, tab rail and player
|
||||
* bar - with a real backdrop blur. Off falls back to each element's own plain
|
||||
* translucent background.
|
||||
*
|
||||
* The obvious thing to cut on weak hardware, and on the Pi that is exactly wrong: it
|
||||
* measured five times *worse* with the blur off (118% of a core against 25%). The blur
|
||||
* is what promotes each panel to its own compositing layer; without it the animated
|
||||
* canvas and the whole album grid above it share one layer and every canvas frame
|
||||
* repaints all of it. See the table in lib/lowPower.ts before touching this. */
|
||||
export const SHOW_GLASS_BLUR = true;
|
||||
* Off under `?pi=1`, which it was not always: while the ambient canvas still ran there,
|
||||
* the blur measured five times *cheaper* on than off, because it gave each panel its own
|
||||
* compositing layer above the animating canvas (see the table in lib/lowPower.ts). With
|
||||
* the canvas gone that trade is gone too, and the blur was just cost - enough to make
|
||||
* the browse rows visibly sluggish. Turning the canvas back on in the dialog wants the
|
||||
* blur back on with it. */
|
||||
export const SHOW_GLASS_BLUR = PERF.glassBlur;
|
||||
|
||||
/** Render the play view's animated canvas background (gradient + bubbles, both
|
||||
* redrawn every frame). Off falls back to `.stage`'s own static CSS gradient, which is
|
||||
@@ -64,22 +67,10 @@ export const SHOW_GLASS_BLUR = true;
|
||||
* so with this off the browser has nothing to do between one keypress and the next. */
|
||||
export const SHOW_AMBIENCE = PERF.ambience;
|
||||
|
||||
/** Frost the *repeated* glass surfaces - every album card in the grid, every row in a
|
||||
* track list - as opposed to the handful of panels wrapped around them.
|
||||
*
|
||||
* Separate from `SHOW_GLASS_BLUR` because the two behave nothing alike, and lumping
|
||||
* them together is what made the first attempt at a Pi profile five times slower. A
|
||||
* panel is one live backdrop copy that buys its whole subtree a compositing layer. A
|
||||
* card is one live backdrop copy out of three hundred sitting directly over the
|
||||
* animated canvas, so every canvas frame re-blurs all of them. Measured on the Pi with
|
||||
* a full album grid on screen, Chromium: see lib/lowPower.ts. */
|
||||
export const SHOW_CARD_BLUR = PERF.cardBlur;
|
||||
|
||||
/** Run the purely decorative CSS animation loops: the room page's bubble field and
|
||||
* the dolphin mascot's bob/swim. Off under `?pi=1`. On their own they measured as
|
||||
* noise, but "nothing on this screen moves by itself" is a property worth having
|
||||
* outright rather than a sum of small wins - a compositor with no animation to service
|
||||
* has nothing to wake up for. */
|
||||
/** Run the purely decorative CSS animation loops: the room page's drift and the
|
||||
* mascot's bob/swim (the typing game's drift reads the same knob, via
|
||||
* lib/tippen/theme.ts). Plain transform loops the compositor runs on its own; on the
|
||||
* Pi they measured as noise, so they stay on under `?pi=1`. */
|
||||
export const SHOW_DECORATIVE_ANIMATIONS = PERF.decorativeAnimations;
|
||||
|
||||
// ------------------------------------------------------------------ colors --
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
|
||||
import { PERF } from "../perfSettings";
|
||||
|
||||
/** Frost the glass panels with a real backdrop blur. Looks like the first thing to cut
|
||||
* on a weak GPU; on the Pi it measured five times worse with it off, because the blur is
|
||||
* what gives each panel its own compositing layer. Same finding as the music app's
|
||||
* matching toggle - see the table in ../lowPower.ts. */
|
||||
export const SHOW_GLASS_BLUR = true;
|
||||
/** Frost the glass panels and keys with a real backdrop blur. Shares the music app's
|
||||
* knob - see `SHOW_GLASS_BLUR` in ../theme.ts. */
|
||||
export const SHOW_GLASS_BLUR = PERF.glassBlur;
|
||||
|
||||
/** The decorative drift behind everything - rising bubbles or falling leaves, by skin. A
|
||||
* dozen elements on a CSS transform loop - off under `?pi=1`, like every other loop in the
|
||||
* app. */
|
||||
export const SHOW_DRIFT = PERF.tippenBubbles;
|
||||
* dozen elements on a CSS transform loop, switched with the music app's other decorative
|
||||
* loops (`SHOW_DECORATIVE_ANIMATIONS` in ../theme.ts). */
|
||||
export const SHOW_DRIFT = PERF.decorativeAnimations;
|
||||
|
||||
/** The earned pets moving behind every screen. The reward that is always in view - off
|
||||
* only to rule it out when chasing a performance problem.
|
||||
@@ -26,7 +24,7 @@ export const SHOW_DRIFT = PERF.tippenBubbles;
|
||||
export const SHOW_PETS = true;
|
||||
|
||||
/** Fade screens in on entry. */
|
||||
export const ANIMATE_VIEW_TRANSITIONS = PERF.tippenViewTransitions;
|
||||
export const ANIMATE_VIEW_TRANSITIONS = PERF.viewTransitions;
|
||||
|
||||
/** Show the on-screen keyboard with the finger colours. "auto" fades it out key by key
|
||||
* as each one is mastered - the scaffold that removes itself, which is the whole point
|
||||
|
||||
@@ -192,17 +192,6 @@ button {
|
||||
box-shadow: 0 4px 14px var(--shadow);
|
||||
}
|
||||
|
||||
/* `SHOW_CARD_BLUR` (`lib/theme.ts`) off sets `data-cardblur="off"` - drop the backdrop
|
||||
blur from the surfaces there are *many* of, and only those. An album grid is up to
|
||||
343 cards, each one a live backdrop copy sitting directly over the animated canvas,
|
||||
so every canvas frame re-blurs all of them. The panels below keep their blur: a
|
||||
panel is one copy, and it is what gives its whole subtree a compositing layer. */
|
||||
.stage[data-cardblur="off"] .card,
|
||||
.stage[data-cardblur="off"] .list-row {
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
/* `SHOW_GLASS_BLUR` (`lib/theme.ts`) off sets `data-blur="off"` on `.stage` - drop
|
||||
the (GPU-expensive) backdrop blur everywhere below and fall back to each
|
||||
element's own plain translucent background. */
|
||||
|
||||
Reference in New Issue
Block a user