2026-03-08|5 min read|--flashbee--devlog--nextjs--web-audio-api--indie-maker

Building FlashBee: Games, Routing Bugs, and Web Audio API

Today was one of those days where you sit down to add one feature and end up untangling three different problems before lunch. That's FlashBee for you.

## What I Was Building

FlashBee is a flashcard app for Vietnamese kids learning English. The core loop is simple: show a card, hear the word, remember it. But "simple" gets boring fast, especially for kids. So today's agenda was adding proper game modes — not just one, but multiple, each aimed at a different age group.

The deck detail page used to have a flat list of buttons: Study, Quiz, Bubble Pop. Functional. Forgettable. I wanted something that felt more like choosing a game at an arcade — each mode with its own personality, color, and a small note explaining who it's for.

## The New Game Grid

I rebuilt the deck page around a 2×2 game card grid:

  • >Học thẻ (Flashcards) — flip cards, auto-pronunciation. Every age.
  • >Quiz hình ảnh (Image Quiz) — hear the English word, pick the right picture from 4 choices. Kindergarten to Grade 3.
  • >Quiz dịch nghĩa (Translation Quiz) — read the English word, tap the correct Vietnamese meaning. Grade 4 and up.
  • >Bubble Pop — floating bubbles with images, pop the right one. Grade 1 to Grade 5.

Each card shows a small badge: Mẫu giáo – Lớp 3, Lớp 4 trở lên, Mọi lứa tuổi. The idea is that a parent hands the tablet to their kid and doesn't have to explain anything. The UI does the explaining.

Cards with not enough words show a 🔒 and a "Need 4+ cards" note instead of the audience label. Disabled gracefully.

## The Translation Quiz

This was the new game I built from scratch today. The mechanic is straightforward but surprisingly effective for older kids: a large English word sits in the center of the screen with a "Listen" button. Four Vietnamese translations appear below as tappable buttons. Pick the right one.

After answering, a toast pops up — green for correct (✅ "sky" = "bầu trời"), red for wrong (❌ Correct answer: "bầu trời"). Then it auto-advances after a short pause. No tapping needed. Just read, listen, choose, repeat.

I added TTS auto-play when each question loads — the word reads itself out loud 400ms after the card appears. The listen button is still there if you want to hear it again. For older kids who are already reading, the combination of seeing the word + hearing its pronunciation is where retention actually happens.

## A Routing Bug That Wasn't Where I Thought

After building the game grid, I tested it. Clicked Quiz hình ảnh. Nothing happened.

No error. No navigation. The button was there, it wasn't disabled, React showed onClick was registered. But the page didn't move.

I checked the DOM — no disabled attribute. I fired the React onClick handler manually through the browser console — it ran without error. I tried window.location.href manually — it navigated instantly.

The culprit: I was using button + router.push() from Next.js App Router's useRouter. For reasons I still don't fully understand, router.push() was silently doing nothing. No error thrown, no console warning. Just... nothing.

The fix was switching every game card from <button onClick={() => router.push(href)}> to <Link href={href}> wrapping a styled <div>. That's the correct App Router pattern anyway — <Link> for navigation, router.push() for programmatic redirects after async actions. Lesson re-learned.

For disabled cards, I wrapped with <div> instead of <Link>. Pointer events blocked at the CSS level, no routing attempted.

## Sound in Bubble Pop

This one wasn't mine to build — I found it already done when I opened the file. Clean Web Audio API implementation using an AudioContext singleton:

Correct answer: three ascending sine wave notes (C5 → E5 → G5), each 80ms apart, a cheerful arpeggio that fades out in 200ms.

Wrong answer: a single oscillator that frequency-ramps from 400Hz down to 200Hz over 300ms. The classic wah of disappointment.

No libraries. No sound files to load. Just math and oscillators. The AudioContext gets created once and reused — important because browsers only allow AudioContext creation after a user gesture, and creating too many causes memory issues on mobile.

What I like about this approach: it works offline, loads instantly, and the sounds are small enough to not feel intrusive. Kids get feedback without headphones being required.

## What's Next

The four-game grid is live. The Translation Quiz is live. Bubble Pop now sounds like a game instead of a silent slideshow.

Still on the list:

  • >Sync these new sound patterns into QuizGame and TranslationQuiz
  • >Maybe a fifth game mode — something typing-based for older kids
  • >Performance pass before production deploy

One day of work. Three problems solved, one new feature shipped, one old bug fixed, one sound system inherited and documented. Not bad.