Search reads

Find a read to open

Back to Projects
Audio Quote Machine
CompletedHTML5CSS3JavaScript (ES6)+2 more

Audio Quote Machine

An FCC drum-machine build reskinned as a fictional hardware audio interface, swapping drum hits for anime and movie quote clips.

Timeline

Short solo build

Role

Frontend Developer

Team

Solo

Status
Completed

Technology Stack

HTML5
CSS3
JavaScript (ES6)
Bootstrap 5
Vercel

Key Challenges

  • Building convincing "hardware" visual details (status LEDs, scanlines, corner brackets) with layered CSS instead of image assets
  • Handling audio replay cleanly so rapid or repeated key presses restart a clip instead of stacking playback
  • Meeting freeCodeCamp's strict id/structure requirements for #drum-machine, #display, and each .drum-pad without that constraint showing through in the final look

Key Learnings

  • Wiring both click and keydown/keyup input to the same trigger function without duplicating logic
  • Using CSS gradients, box-shadows, and keyframe animation to fake a physical device skin
  • Keeping a small piece of playback state (currentAudio) to coordinate interruption between HTML5 audio elements
  • Working inside an automated test suite (the FCC testable-projects bundle) as a constraint rather than an afterthought

Audio Quote Machine

Overview

This is freeCodeCamp's Front End Libraries "drum machine" project, but reskinned end to end: instead of drum hits, the nine pads trigger quote clips from anime, movies, and a GOT scene, and the whole thing is dressed up as a standalone piece of hardware called the "MXOLISI AUDIO INTERFACE v2.1" rather than a plain grid of buttons on a webpage.

Why I built it

The FCC brief is deliberately narrow: nine pads, unique ids, keyboard and click triggering, a display that updates per pad, and a passing test suite. That narrowness is the point of the exercise, but it also left room to spend the actual effort on making it look like something rather than just satisfying the user stories. I used it as a chance to practice building a convincing visual identity out of nothing but CSS, and to swap in content that actually made me want to press the buttons.

Core features

Quote pads, not drum pads: nine keys (Q, W, E, A, S, D, Z, X, C) each map to a specific line, Erwin Smith from Attack on Titan, Bane's Dark Knight monologue, Cersei Lannister, Alphonse Elric, Itachi and Madara Uchiha from Naruto, Johan Liebert from Monster, and Thorfinn from Vinland Saga, with the display updating to name the source on every trigger.

Dual input: every pad responds to both a click and its mapped key, routed through the same play() function so there's one source of truth for playback and display logic instead of two parallel code paths.

Interrupt-on-repeat playback: pressing a new pad stops and rewinds whatever's currently playing before starting the next clip, so mashing keys doesn't layer overlapping audio.

A device skin, not a webpage: pulsing status lights, a glowing power LED, a scanline sweeping across the display, and bracket details at the container's corners, all built with layered gradients, box-shadows, and CSS keyframes rather than any image assets.

Tech stack

Plain HTML5, CSS3, and ES6 JavaScript, no framework or build step. Bootstrap 5 is pulled in for base layout and its card component, then almost entirely overridden by custom styles for the device look. The freeCodeCamp testable-projects-fcc bundle runs the official test suite against the page. Deployed as a static site on Vercel.

Technical highlights

One trigger function, two inputs

Each pad's onclick and the document-level keyup listener both call the same play(str) function. keyup uppercases whatever key was pressed and checks it against the string "QWEASDZXC" before dispatching, so keyboard and mouse input can never drift out of sync with each other.

A single audio-state variable doing the interruption work

Rather than tracking play state per <audio> element, script.js keeps one currentAudio reference. On every trigger it pauses and resets whatever that variable points to, then plays the new clip and reassigns the reference. It's a small amount of state, but it's exactly enough to stop clips from overlapping without needing to touch every audio element on each keystroke.

Faking hardware with CSS alone

The status lights, power indicator, and corner brackets are all pseudo-elements and small fixed-size divs animated with @keyframes pulse and @keyframes power-pulse. The scanline across the display is a ::before gradient bar animated with translateX. None of it is an image, it's the kind of detail that's cheap to build once you commit to doing it in CSS rather than reaching for an asset.

Challenges

The FCC test suite is strict about structure, specific ids on #drum-machine and #display, a fixed .drum-pad order, ids on the <audio> elements matching their parent pad's inner text, and all of that had to survive being wrapped in a much heavier visual treatment without breaking. Getting the "device" details (LEDs, scanline, brackets) to read as intentional rather than gimmicky took more passes on the gradients and shadow values than the actual JavaScript did.

What I learned

This one was less about JavaScript complexity and more about discipline within a constraint: the interaction logic here is genuinely small, one function, one state variable, one event listener, and the interesting work was in how much visual identity that small amount of logic could support. It was also useful to build against an existing automated test suite rather than just my own manual clicking, since it forces the DOM structure to stay exactly right even as the styling changes underneath it.

Looking ahead

The pad-to-clip mapping is hardcoded in script.js via an if/else chain; pulling that into a small array or object of { key, clip, label } would make it easier to swap in a different quote set without touching the trigger logic. A volume or mute control would also fit the "hardware interface" framing well.

Final thoughts

A constrained exercise is still worth doing properly. The freeCodeCamp brief gives you the bare minimum shape of a working app; everything that makes this one feel like a specific thing, quotes I actually like, an interface that looks like it has weight to it, came from treating the constraint as a floor rather than a ceiling.