Mr. Grummel Get the app
← All notes
ENGINEERING 9 MIN READ DRAFT — AUGUST 2026

Building a classroom that loads in 300ms

Notes on animating a teacher who has to appear twenty times a day without ever feeling slow.

Every other screen in this app, you chose to open. This one, you didn't — you tapped something else, and the block caught you instead. That difference changes the engineering brief completely. A screen you asked for can take a beat to arrive; nobody's checking a stopwatch. A screen that interrupts you has one shot to not feel like the phone glitched, and it has to make that case in under a third of a second.

The one measurement that matters

Not the length of the intro animation — the gap before it starts. Door, walk-in, floor, ceiling, board, sign, clock: the sequence has a fixed timeline, and every one of those delays was chosen and re-checked by hand. None of it matters if the tap itself has a stutter in front of it. A gorgeous 800ms entrance sitting behind a 400ms blank white frame reads as "this app is slow," full stop — the polish never gets seen, because the verdict already landed on the blank frame.

So the actual target isn't "the animation looks smooth." It's "something appears before the user has time to wonder if the tap registered." That's roughly the same budget as a system app switch — a couple hundred milliseconds — and it's a much stricter bar than most in-app transitions get held to.

Never block the block

The classroom can't wait on the network. Whatever question gets asked has to come from data that was already sitting on the device before you tapped — fetched and cached ahead of time, on your terms, not requested the moment you're already annoyed and staring at a spinner. If that cache is ever empty, the honest failure is a plainer, static question card, not a loading wheel in front of a teacher who hasn't shown up yet.

The same rule applies to assets. A font request or an image fetch on the critical path is a dependency on the network being fast at the exact moment you're mid-interruption, which is precisely when you have the least patience for it. Self-hosting the four fonts this site uses is the same instinct applied to the app: nothing the intro needs should be able to fail because someone's on bad hotel Wi-Fi.

The intro isn't decoration in front of the loading state. It has to be the loading state.

Where the frames actually go

Cheap animation properties only — position and opacity, the kind a phone can move without recalculating layout underneath them, never anything that forces a reflow mid-sequence. A classroom that has to appear this often can't afford even one property that's expensive on an older device, because "older device" isn't an edge case here — it's a large share of who's holding the phone at the exact moment they're reaching for something blocked.

The corollary is that most of what looks like motion in the intro is actually very little motion, staged well. A door that opens, a figure that walks four steps, a board that swings into place — none of it is a lot of pixels changing, it's a lot of *timed* pixels changing, which is a cheaper trick than it looks like from the outside.

What we're still unsure about

Cold start is the honest gap. The very first interruption after the phone reboots or the app has been killed in the background is slower than every one after it, because nothing is warm yet — no cached question, no fonts already parsed, no JS bundle already resident in memory. We don't have a clean fix for that first-tap tax beyond keeping the app as light as we can and hoping the OS doesn't evict it too eagerly. If your very first block of the day feels a beat slower than the rest, that's why, and it's not something we've decided to ignore.

Low-end Android is the other open question — the phones this matters most for, since a two-second stumble is a much bigger fraction of a 300ms budget there than on a flagship. We're building against that constraint, not against the device we happen to be holding.

Draft — not published yet.
Try the pop quiz