People asked to see the Halloween version of NudgeCue, my free classroom app. Here it is:
Two things went into it, and both are easy to copy for your own project: a seasonal theme that switches itself on and off, and a demo video that a coding agent recorded, voiced and edited. This post covers both, with the prompts we used.
Part 1: a seasonal theme that runs itself
Let the calendar do it
The worst version of a Halloween theme is the one you have to remember to switch on, and then forget to switch off on November 3rd.
So the theme reads the date. From October 26 to 31, in the user's own time zone, the app is in Halloween mode. Otherwise it isn't. The whole rule is one small function:
export function seasonForDate(date) {
if (date.getMonth() === 9 && date.getDate() >= 26) return 'halloween';
return null;
}
(Months start at 0 in JavaScript, so 9 is October.)
One detail matters for classroom apps: the screen at the front of the room often stays open for days. So the app checks the date again every minute and whenever the tab comes back into view. A screen left on overnight turns spooky on the 26th and back to normal on November 1st without anyone touching it.
One switch, many small touches
Everything Halloween hangs off a single "season" value that any part of the app can read. In NudgeCue that one value changes:
- the colour theme
- the decorations: bats flying across the top, a ghost bobbing at the left edge, pumpkins in the bottom corners
- the point bursts, which get a 🎃, 👻 or 🦇
- the whole-class confetti, which mixes in pumpkins and bats
- the award and picker sounds, which get spooky versions
- the seat colours on the seating chart
Because it's one value, turning it off turns all of it off. Adding Christmas later is another branch in the same function.
Give people a way out
Not every class wants bats. There's an off switch in the menu, and it's remembered per season and per year, so turning it off this October doesn't quietly hide next October's.
For testing, ?season=halloween in the address previews it before the date, and ?season=none hides it. That's how you check the theme on October 3rd without changing your computer's clock.
Decorations that stay out of the way
The decorations follow four rules:
- They stay at the edges of the screen.
- They can't be clicked, and they sit under pop-ups.
- They hold still for anyone who has reduced motion turned on.
- They never print.
The mistake: a glow that kept the computer busy
The first version of the pumpkins had a soft glowing effect. With nobody touching anything, it kept a slowed-down computer about 31% busy. On a school PC, that's enough to make every pop-up stutter.
The glow animated a CSS drop-shadow filter, which the browser can't hand to the graphics card, so it redraws it on every frame. We moved the glow to its own layer and animated only opacity and transform. It looks the same, and CPU dropped to 6%. The full story is in why your vibe-coded app gets slow.
If you only keep one rule from this post: always-on animations should only change opacity and transform.
Part 2: an agent films the demo
The video above wasn't screen-recorded by hand. A coding agent (Claude Code) did it from a short description, and changing it afterwards took one line.
1. A shot list instead of a screen recording
The video starts as a small file that lists what to do, in order:
[
{ "do": "wait", "ms": 3500, "label": "board" },
{ "do": "tap", "text": "Mina Park", "label": "tap-student" },
{ "do": "tap", "in": "dialog", "button": "Great Teamwork", "label": "pick-reason" },
{ "do": "tap", "text": "Whole Class", "label": "tap-whole-class" },
{ "do": "tap", "in": "dialog", "button": "Great Teamwork", "label": "pick-reason-2" },
{ "do": "tap", "title": "Pick random student", "label": "open-picker" },
{ "do": "key", "key": "Space", "label": "pick" }
]
2. A robot does the tapping
A script opens the app's demo mode in a browser with no window, at phone size. Demo mode uses made-up students, so no real teacher or student ever appears. It runs the steps, records the screen at full resolution and draws a small ring wherever it taps, like a finger would. It also writes down the second each step happened, which matters later.
When we wanted the video in dark mode, the change was one line in the shot list, "theme": "dark", and the robot recorded it again.
3. A voice that lands on the action
The narrator is Puffy, NudgeCue's mascot, a custom voice made with ElevenLabs. The script includes small stage directions, like [spooky whisper] before "bats, ghosts and pumpkins".
A voice take never matches the screen on its own. So the take was cut at its natural pauses, and each sentence was placed at the moment it describes, using the times the robot wrote down: "every point lands with a pumpkin" plays as the pumpkin pops, and "free for teachers" plays on the end card.
4. Music, checked for singing
The music was generated from a text prompt too: a 22-second Halloween track with organ stabs, a theremin-style tune and "not a kids' jingle". Music models sometimes slip in vocals, so the agent ran the track through speech-to-text. An empty transcript means no singing.
5. Putting it together
ffmpeg, a free command-line video tool, stacks the layers: the screen recording, an intro card and end card drawn in a browser, small tap sounds generated in code (nothing to license), the voice and the music. Then it sets the volume to the level short-video apps expect.
What we changed by hand
Our first version had caption bubbles over the app. They covered the very thing we were showing off, so we took them out. Taste is still your job.
The bug worth knowing about
The first dark-mode render quietly stopped before the end card. The end card is a solid image, so its frames were saved without transparency, and that change in the middle of the sequence made ffmpeg end the video early. The fix was one setting that keeps the same pipeline across the switch. If your generated video ever comes out a few seconds short, check whether your overlay frames changed format partway through.
Prompts to copy
The prompt at the top of this post builds the theme. This one builds the video pipeline. It assumes your app has a demo or test account with fake data:
Make me a repeatable demo video for my app. Use a demo or test account with fake data only, never a real user.
1. Write a shot list as JSON: waits, taps (by visible text, button name or title) and key presses, each with a label.
2. Write a Playwright script that opens the app at phone size (390 x 693, scaled to 1080 x 1920), runs the shot list, records the screen, shows a tap ring on each tap, and saves when each step started.
3. Write a compose script with ffmpeg: an intro card and an end card, short tap sounds generated with ffmpeg (no audio files), an optional voiceover and music, loudness normalised to -14 LUFS, 1080 x 1920, H.264 and AAC.
4. Check the finished file's length matches intro + screen + end card, and make a contact sheet of one frame per second so I can see it.
Don't add captions over the app unless I ask.
And for the voice, if you use ElevenLabs:
Write a voiceover script for this shot list, one short sentence per moment, about 2.5 words per second. Then, once I have the audio, find the pauses with ffmpeg silencedetect and place each sentence at its step's time from the recording.
Reuse it next year
The theme switches itself on next October. The video is a shot list and a few settings, so a Christmas version is a new date range, a different voice line and one more recording run.
If you're building your first app and want the whole path from idea to live, there's a free vibe coding starter pack.