Overview

Craptronics Plc brings you the Junk-Phonic 9000 is a single photograph of a boombox that has been talked into working. There's no 3D model, no canvas-drawn cabinet, no sprite sheet — just a real product photo, sliced apart in the places where buttons and dials already exist, and rebuilt as a living interface using nothing but HTML, CSS, and the Web Audio API.

Load an mp3 into it and it plays through a cassette deck with genuine wow & flutter — not a filter that makes things sound "vintage," but a continuous playback-rate wobble that behaves like an actual worn tape transport, pitch drift and all. Tune the internet radio and the VU needles sweep with real programme level. Turn up the bass and the woofers — cut from the same photo, layered back on top of it — visibly vibrate to the actual waveform, not just a generic "louder = bigger" pulse. Press Crap Bass Boost, which despite the name is a boxy telephone-band filter that removes bass, and the woofers audibly and visibly calm down, because that's genuinely what's happening to the signal.

It was built by Craptronics Plc, who also produced the Crap-turon 5000, and the tape currently loaded in the deck is a mix called Hits 1982, which has never existed and never will.

How it's built

The photo is the UI

Every control is an absolutely-positioned HTML element sitting directly on top of the source photograph, with its coordinates measured from the actual image pixels rather than guessed. Concretely: the photo is loaded into Python/PIL, then thresholded and scanned for the distinctive colours or brightness signatures of each control — the yellow REC button, the amber VU meter glow, the dark cassette window, the chrome speaker rims — to pull out exact bounding boxes in pixel space. Those get converted to percentages of the image's width/height, which is what lets every overlay track correctly regardless of how large the photo is rendered (the whole page scales as one fluid unit, from a phone screen up to a 1600px-wide desktop layout).

The two speaker cones use the same trick in reverse: they're crops of the original photo's own woofers, repositioned in a <div> on top of the now-cone-less base image, so they can be independently scaled and brightened without touching the rest of the photo.

Where a control needed to fit into genuinely tiny real estate (the tape transport buttons sit in a strip of chassis roughly 40px wide at native resolution), the UI shrinks to icon-only pill buttons rather than breaking the photo's geometry. Where a piece of text was baked directly into the photo — the brand logo — it was inpainted out (a clean patch of the panel stretched over the old logo) and redrawn with new text, rather than faked with an HTML overlay.

Audio architecture

Two independent AudioContexts run side by side, exactly mirroring the two physical audio sources:

Tape A (Web Audio graph, per file loaded):

<audio> element ──▶ highpass (80Hz) ──▶ lowpass (8kHz) ──▶ compressor ──▶ tone stage ──▶ destination                                                              │                                                              ▶ analyser (level meter)

Radio (Web Audio graph, per stream):

<audio> element ──▶ loudness gain ──▶ tone filters (Crap Bass Boost) ──▶ analyser ──▶ master gain ──▶ destination

Bass-driven woofer motion

This went through a couple of iterations. The first pass drove the cones off a slice of frequency-bin energy from the existing spectrum analyser — simple, but at the FFT size in use that "bass" bucket actually covered a broad low-to-mid range, and it was reading loudness, not bass.

The current version taps a dedicated pair of cascaded 150Hz lowpass filters (-24dB/octave combined) sitting off to the side of the audible signal path — connected, but never routed to destination, so they don't affect what you actually hear. Each animation frame reads the time-domain waveform of that filtered band directly (getByteTimeDomainData), rather than an energy average, and uses:

At a 60fps render loop this necessarily aliases against anything much above ~30Hz, but that aliasing is doing useful work here — it's what makes a 60Hz bassline read as a buzz/vibration rather than a smooth swell, which is closer to how a real cone actually behaves at that frequency.

Because Loudness and Crap Bass Boost are wired in before this tap (not after), both toggles genuinely change what the woofers do, not just what you hear: Loudness's flat gain boost scales the visible vibration up, and Crap Bass Boost — which despite the name squeezes the signal down to a 300Hz–3kHz telephone band — chokes off the entire sub-150Hz range the woofer tap is looking at, so the cones visibly settle down along with the bass actually disappearing from the mix.

VU meters

The two analog dials in the photo already have needles painted on them at rest; the overlay needles pivot from a point measured directly off the photo (a few percent left of the dial's visual centre, matching where the real needle hinges) and sweep across a fixed angular range based on a broader-spectrum loudness read — VU meters are supposed to track overall programme level, not just bass, so this deliberately uses a different (wider) frequency slice than the woofer tap. Each channel gets a small independent random jitter so left and right don't move in perfect lockstep, the way real stereo meters don't either.

Stack

Vanilla HTML/CSS/JavaScript, zero build step, zero dependencies. Runs as a single .html file — open it and it works. Audio is entirely Web Audio API; visuals are CSS transforms and <canvas>-free DOM overlays; the only network calls at runtime are to radio-browser.info and to whichever stream URL you tune to.