Downscale cached cover art, and stop every animation loop under ?pi=1
Covers. Art out of an ID3 APIC frame is sized for a record sleeve: this library averaged 3000x3000 and 580 kB per cover, 140 MB across 284 albums. The browser was decoding nine megapixels - around 36 MB of bitmap - for every cover it painted, to show it in a 185 px card, on a Pi with 2 GB of RAM. The largest any screen in this app asks for is 340 px (the play view), so cache.store_cover now downscales to a 640 px long edge, which leaves room for a tablet at devicePixelRatio 2 and cuts the decode about twentyfold. Pillow was already a hard dependency, for colour extraction. scan_library reuses an album whose fingerprint is unchanged without re-reading its tags, so covers already on disk would never be rewritten - hence shrink_stored_covers(), a pass at the top of a scan. Reading a JPEG's dimensions only parses its header, so after the first run it costs one small read per album. Art already small enough is returned byte-identical rather than re-encoded, so repeated scans cannot slowly grind it down, and anything Pillow cannot read is passed through untouched: a cover that is too big is a performance problem, a cover that is missing is a visible one. Animation. Halving the ambient canvas to a quarter of the pixels at 30fps took it from 53.5% of a core to 25%, and 25% was still not good enough to use. A requestAnimationFrame loop repainting the viewport is a floor you cannot get under while it runs at all, so ?pi=1 now switches it off outright rather than thinning it, along with the decorative CSS loops, the view transitions, the typing game's bubbles and its next-key pulse. The pets stay on screen but hold still, through the same path prefers-reduced-motion already took - taking the animation away is the point, taking away what she earned is not. .stage keeps its own static gradient, so there is still a sea behind everything. Both blurs on the panels stay on. Dropping those measured five times worse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { LOW_POWER } from "../../lib/lowPower";
|
||||
import { creatureById, createSwimmer, pose, stepSwimmer } from "../../lib/tippen/aquarium";
|
||||
import type { CreatureId, Swimmer } from "../../lib/tippen/aquarium";
|
||||
|
||||
@@ -37,8 +38,11 @@ export function AquariumCreatures({ creatures, opacity }: Props) {
|
||||
const present = useRef<ReadonlySet<CreatureId>>(new Set(creatures));
|
||||
|
||||
useEffect(() => {
|
||||
// Reduced motion: every pet is still placed and shown, it just holds still.
|
||||
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
// Reduced motion: every pet is still placed and shown, it just holds still. `?pi=1`
|
||||
// takes the same door: on that device the point is that nothing runs a frame loop,
|
||||
// and the pets are the reward - they should be *there*, they just need not swim.
|
||||
const reducedMotion =
|
||||
LOW_POWER || window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
let frame = 0;
|
||||
let lastTime = performance.now();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** The gradient stage every screen sits on - the app's one full-height container.
|
||||
* `data-blur` is read by app.css to drop the backdrop filters wholesale.
|
||||
* `data-blur` is read by app.css to drop the backdrop filters wholesale, and `data-anim`
|
||||
* by tippen.css to stop the one CSS animation loop that is not a component's to switch
|
||||
* off - the pulse on the next key to press.
|
||||
*
|
||||
* The pets swim here rather than on the home screen, so they stay with her on the lesson
|
||||
* map and during a run too. */
|
||||
@@ -7,6 +9,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import type { CreatureId } from "../../lib/tippen/aquarium";
|
||||
import { LOW_POWER } from "../../lib/lowPower";
|
||||
import { SHOW_AQUARIUM_CREATURES, SHOW_BUBBLES, SHOW_GLASS_BLUR } from "../../lib/tippen/theme";
|
||||
import { AquariumCreatures } from "./AquariumCreatures";
|
||||
import { Bubbles } from "./Bubbles";
|
||||
@@ -21,7 +24,11 @@ interface Props {
|
||||
|
||||
export function Stage({ children, creatures, dimmed }: Props) {
|
||||
return (
|
||||
<div className="tp-stage" data-blur={SHOW_GLASS_BLUR ? "on" : "off"}>
|
||||
<div
|
||||
className="tp-stage"
|
||||
data-blur={SHOW_GLASS_BLUR ? "on" : "off"}
|
||||
data-anim={LOW_POWER ? "off" : "on"}
|
||||
>
|
||||
{SHOW_AQUARIUM_CREATURES && <AquariumCreatures creatures={creatures} opacity={dimmed ? 0.25 : 1} />}
|
||||
{SHOW_BUBBLES && <Bubbles />}
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user