159 lines
6.2 KiB
TypeScript
159 lines
6.2 KiB
TypeScript
/** Per-knob performance settings, shown under `?pi=1`. Stored in this browser's
|
||
* localStorage (see lib/perfSettings.ts) and applied by a reload, because the knobs are
|
||
* read once at module load. Nothing changes until "Speichern". */
|
||
|
||
import { useState } from "react";
|
||
|
||
import {
|
||
FULL,
|
||
PERF,
|
||
PI,
|
||
resetPerfSettings,
|
||
savePerfSettings,
|
||
type PerfSettings,
|
||
} from "../lib/perfSettings";
|
||
|
||
type BoolKey = { [K in keyof PerfSettings]: PerfSettings[K] extends boolean ? K : never }[keyof PerfSettings];
|
||
type NumKey = { [K in keyof PerfSettings]: PerfSettings[K] extends number ? K : never }[keyof PerfSettings];
|
||
|
||
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", "Blasen im Zimmer, schwimmender Delfin"],
|
||
["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: Blasen", "Aufsteigende Blasen 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"],
|
||
];
|
||
|
||
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. Blasen", "0 = unbegrenzt", 0, 500, 5],
|
||
];
|
||
|
||
const pill = (background: string, color: string) =>
|
||
({
|
||
border: "none",
|
||
cursor: "pointer",
|
||
background,
|
||
color,
|
||
fontSize: 15,
|
||
fontWeight: 800,
|
||
padding: "11px 20px",
|
||
borderRadius: 999,
|
||
}) as const;
|
||
|
||
export function PerfPanel({ onClose }: { onClose: () => void }) {
|
||
const [draft, setDraft] = useState<PerfSettings>({ ...PERF });
|
||
|
||
const save = () => {
|
||
savePerfSettings(draft);
|
||
location.reload();
|
||
};
|
||
const reset = () => {
|
||
resetPerfSettings();
|
||
location.reload();
|
||
};
|
||
|
||
return (
|
||
<div className="overlay" style={{ zIndex: 7, background: "oklch(15% 0.03 210 / .75)" }}>
|
||
<div className="sheet" style={{ padding: 28, width: 480, maxHeight: "100%", overflow: "auto" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||
<div style={{ fontSize: 20, fontWeight: 900, color: "var(--ink)", flex: 1 }}>
|
||
⚡ Leistung
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
aria-label="Schließen"
|
||
style={{
|
||
...pill("oklch(88% 0.02 210)", "var(--ink)"),
|
||
fontSize: 18,
|
||
fontWeight: 900,
|
||
width: 36,
|
||
height: 36,
|
||
padding: 0,
|
||
}}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div style={{ display: "flex", gap: 10, marginTop: 16 }}>
|
||
<button onClick={() => setDraft({ ...PI })} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||
Pi-Vorgabe
|
||
</button>
|
||
<button onClick={() => setDraft({ ...FULL })} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||
Alles an
|
||
</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 }}>
|
||
<input
|
||
type="checkbox"
|
||
checked={draft[key]}
|
||
onChange={(event) => setDraft({ ...draft, [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 210)" }}>
|
||
{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 210)" }}>
|
||
{hint}
|
||
</span>
|
||
<input
|
||
type="number"
|
||
min={min}
|
||
max={max}
|
||
step={step}
|
||
value={draft[key]}
|
||
onChange={(event) => {
|
||
const value = event.target.valueAsNumber;
|
||
if (Number.isFinite(value)) setDraft({ ...draft, [key]: value });
|
||
}}
|
||
style={{
|
||
padding: "8px 12px",
|
||
borderRadius: 10,
|
||
border: "2px solid oklch(85% 0.02 210)",
|
||
fontSize: 16,
|
||
fontWeight: 700,
|
||
}}
|
||
/>
|
||
</label>
|
||
))}
|
||
|
||
<div style={{ display: "flex", gap: 10, marginTop: 6 }}>
|
||
<button onClick={save} style={pill("var(--accent)", "#fff")}>
|
||
Speichern & neu laden
|
||
</button>
|
||
<button onClick={reset} style={pill("oklch(88% 0.02 210)", "var(--ink)")}>
|
||
Zurücksetzen
|
||
</button>
|
||
</div>
|
||
|
||
<div style={{ fontSize: 12, fontWeight: 600, color: "oklch(50% 0.03 210)", lineHeight: 1.5 }}>
|
||
Wird nur in diesem Browser gespeichert und gilt nur mit <code>?pi=1</code>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|