A timing-based firefly collector built in vanilla JavaScript for js13kGames 2025. Guide fireflies, master shield timing, and keep Nyx Felis curious long enough to survive the night.
Tech Stack: Vanilla JS • Canvas 2D • Web Audio API • Roadroller • Firebase
See also: Orbital Order - the demo game I built as practice.
JS13k's strict size constraints force decisions and keep scope tight. I wanted to build a small game and finish it.
The theme was Black Cats, which made me happy because I love cats. Fireflies were already on my mind from evening walks, so the pairing came naturally. The goal was calm, cozy, a little mysterious. And particles.
The game shifted from a "cozy screensaver" to something with structure when I added the curiosity bar and shield mechanic. Constraints forced choices and kept ideas from sprawling.
Core Loop: Summon fireflies, collect them, shield at the right moment to evolve them, then deliver to keep Nyx Felis curious. Evolution chain progresses through four tiers: Green → Purple → Gold → Rainbow. Perfect shield timing on the third warning flash grants 100% evolution and protection. Survive for 3 minutes to win.
Stack: Vanilla JavaScript organized into modular systems for rendering, audio, timing, and state management. Compressed to 12.5KB for competition, 22KB for enhanced version.
- Rendering: Canvas 2D with composite operations (
lighter,screen) for glow effects without shaders - Audio: Procedural Web Audio (oscillators, filters, envelopes) for all sounds and background music
- Timing: Frame-based shield mechanics with three-phase warning system
- Particles: Lightweight pooled system for trails, explosions, and glow effects
- Cat Rendering: Procedural face with reactive eyes, whiskers, and proximity-based animations
Canvas 2D over WebGL: Gradients and composite operations created glow effects without shaders. Performance stayed at 60fps with hundreds of particles.
Procedural Audio: All sounds generated with Web Audio. No audio files means more space for code.
Frame-Based Timing: requestAnimationFrame loop with frame counters for predictable, repeatable shield windows.
State Caching: Canvas properties cached to minimize redundant API calls during particle rendering.
Four-stage pipeline: Terser → Roadroller → inline script → ZIP. Roadroller reduced minified JS by ~40%. Variable aliasing (Math.sin → sin) and selective font loading kept size under 13KB.
npm install # Install dependencies
npm start # Start dev server (http://localhost:8000)
npm run build # Full build pipeline
npm run size # Check against 13,312 byte limit- Three-flash warning system: Third flash signals optimal shield moment
- Color-coded evolution: Green → Purple → Gold → Rainbow with distinct glows
- Keyboard shortcuts: Space (shield), X (drop), Esc (help), M (audio), L (leaderboard)
- Task-based tutorial: Learn by doing, not reading
- Responsive feedback: Score popups, shield outlines, particle bursts confirm actions
- 60fps target: Maintained with hundreds of particles on modern hardware
Timing Clarity: Changed shield cues so the third flash signals action moment. Some players initially confused warning flashes with attacks.
Canvas Tricks: globalCompositeOperation = 'lighter' + layered gradients + shadow blur created WebGL-like glow. Several people thought it was shader-based.
Constraints Ship Games: 13KB limit forced cuts. Features that didn't serve the core loop were eliminated. This kept scope manageable.
UX > Features: Clarifying shield timing improved gameplay more than adding mechanics.
Missed Deadline: Mixed up CST and CT. Finished enhanced version anyway. Learned to keep scope tight and move on.
- Try Unity for future game projects
- Potentially rebuild one of these js13kGames concepts
- Stars and sparkles will probably show up
MIT License. Built by Afton Gauntlett.