Small, self-contained visualizers built to understand things properly rather than to read about them. No build step, no dependencies, no framework. Each tool is a single HTML file you can open in a browser, email to someone, or serve from any static host.
Live at https://jsubroto.github.io/playground/
Two instruments on one page.
ASCII to bits:
- Type text and each character renders as a byte, most significant bit first, with place values labelled
- Click any bit to flip it. The glyph, decimal, hex and the text field all update together
- Shortcuts for flipping bit 5 on letters (the case bit), inverting all bits, and resetting
- Live binary, hex and decimal output for the whole string
- Non printable results are labelled (
NUL,LF,SP,DEL) or shown as raw hex
De Morgan's laws:
- Two switches for A and B drive every view on the page at once
- Both identities shown with live TRUE/FALSE values and an equality badge
- Gate schematics where wires light up with signal value, so
ANDthenNOTcan be compared againstNOT, NOTthenOR - A shaded set view: five Venn panels per law tracing both derivation routes to the same region, with a dot marking which region the current switches sit in
- Full truth table with the current switch position highlighted
- Bitwise proof on two toggleable bytes showing
~(A&B),~A|~B,~(A|B)and~A&~Bside by side
The Venn equality is computed by evaluating each expression over the four disjoint regions and comparing the resulting sets, so a wrong expression would report a mismatch rather than silently claiming agreement.
Bubble, insertion, selection, merge and quicksort.
- Every run is recorded as a list of operations before playback starts, so stepping and scrubbing are exact rather than interpolated
- Play, pause, step forward, step back, or drag the timeline. Space toggles playback, arrow keys step
- Five input distributions: random, nearly sorted, reversed, sorted, few unique
- Array size 8 to 80, speed roughly 2 to 900 operations per second
- Bars are coloured by role: comparing, pivot, partially ordered, final position. Merge and quicksort also show the active subrange
- Live comparison, swap and write counters, plus a readout of the current operation
- Per algorithm complexity panel and a side by side comparison table
The counters make the tradeoffs concrete. At n=40, bubble sort drops to 39 comparisons on sorted input because of its early exit, selection sort always does exactly 780 regardless of input, merge sort does a constant 216 writes, and quicksort goes from about 174 comparisons on random input to about 596 on sorted input as its last element pivot degrades.
A 3D platformer in a single HTML file. Hop across a floating path, collect gems, reach the flag.
- WASD or arrow keys to move, relative to the camera
- Space to jump, and press it again in the air for a double jump
- Q and E or mouse drag to rotate the camera
- R to return to the last checkpoint, M to mute
One level of about 20 platforms, including a sliding platform, an elevator and a narrow beam, with 24 gems and 2 checkpoints. Keyboard only, so it loads on phones but can't be played there.
IEEE 754 floating point, bit by bit, in 32-bit and 64-bit.
- Type a decimal and it's rounded to the nearest float, or click any bit to flip it and the decimal follows
- Sign, exponent and mantissa are coloured separately, with hex and grouped binary readouts
- The exact value stored, the shortest decimal that maps back to the same bits, and the rounding error against what you typed
- Next up and next down step to the neighbouring float
- Presets for 0.1, 0.1 + 0.2, the largest float, the smallest normal and subnormal, ±0, ±Infinity and NaN
- A field by field breakdown: bias, hidden leading 1, subnormals, and the formula that turns the bits into the value
Decimal input is converted with exact BigInt arithmetic rather than JavaScript's own parser, so 32-bit results are rounded once, straight from the decimal, instead of passing through a 64-bit value first. A hint under the presets works out live whether 0.1 + 0.2 lands on the float nearest 0.3: it does in 32-bit and misses by one float in 64-bit.
The Web Audio behind Skyhop's sound effects, in three parts.
beep() playground:
- Sliders for each argument of the
beep()function Skyhop uses: starting pitch, length, waveform, volume and pitch slide - A graph of pitch and volume over the length of the sound, and the exact
beep(...)call, both updating as you move the sliders - Every Skyhop sound as a preset, including the multi-note gem, checkpoint and win sounds
- Space plays the current sound
Waveform types:
- One card per
OscillatorNode.type: sine, square, sawtooth and triangle, each with its waveform drawn over two periods - Slow motion mode runs the oscillator below hearing range and uses it to bend the pitch of a sine tone, so you hear the shape as movement while a dot tracks it on the graph
- Tone mode plays the oscillator directly, so the shape comes through as tone colour instead
Overtones:
- Pick triangle, square or sawtooth and add up to 15 overtones with a slider, watching the sum of sines close in on the perfect shape
- Each overtone is drawn on its own, with a bar chart of how loud it is next to the main pitch
- Shows why the shapes sound different: triangle and square use only odd overtones, sawtooth uses all of them, and triangle's fade far faster than the other two
- The square wave's corner ripples get narrower as overtones are added but stay the same height, which is the Gibbs phenomenon
Sin, cos and tan on the unit circle, aimed at programmers.
- Drag the point, click the circle, use the arrow keys (Shift for 15° steps), scrub the slider, or press Play
- cos and sin drawn as the legs of the triangle and tan as the line out to x = 1, each with a signed bar and value
- Degrees or radians, with radians shown as fractions of π where they land on one
- The same angle on unrolled sine and cosine waves, which can be dragged too
- Live code turning a radius and angle into x and y with
Math.cosandMath.sin - Four common traps:
Math.sin(90)returning 0.894 because it takes radians, tan blowing up near 90°,Math.cos(Math.PI / 2)returning about 6e-17 instead of 0, and screen y pointing down
Open index.html in a browser. That is the whole setup.
One caveat: the landing page links to ./bitwise/, ./sorting/, ./skyhop/, ./float/, ./sound/ and ./trig/, and directory URLs do not resolve to index.html over file:// in every browser. To click between pages locally, serve the folder instead:
python3 -m http.server 8000
# then open http://localhost:8000Opening a tool page directly, for example bitwise/index.html, works fine over file://. Skyhop is the exception: Chrome blocks ES module imports over file://, so serve it instead.
GitHub Pages, deploy from a branch:
- Settings, then Pages
- Source: deploy from a branch
- Branch
main, folder/ (root)
No Actions workflow and no .nojekyll file are needed. Jekyll passes plain HTML through untouched and nothing here is underscore prefixed. If a folder ever mysteriously 404s after adding build output, touch .nojekyll is the fix.
.
├── index.html landing page, one card per tool
├── bitwise/
│ └── index.html
├── sorting/
│ └── index.html
├── skyhop/
│ ├── index.html
│ ├── three.module.min.js vendored Three.js r186
│ └── three.core.js imported by three.module.min.js
├── float/
│ └── index.html
├── sound/
│ └── index.html
├── trig/
│ └── index.html
└── README.md
Each tool lives in its own folder named index.html so the URLs stay clean: /bitwise/ rather than /bitwise.html.
Every page is one file containing a single <style> block and a single <script> block. The point is that any page can be copied, shared or opened offline years from now without a toolchain to resurrect.
- Colours are defined as
--artifact-*CSS variables with light and dark values, and elements only reference the variables. A short script in each page's<head>switches to the dark values when the system is in dark mode, and back to light for printing - No external requests at all: no CDN, no web fonts, no images. Typography uses a system font stack
- Every page carries a print stylesheet, so browser print produces a usable PDF
- Tool pages link back to the landing page with a relative
../, which keeps working if the repo is renamed
Skyhop is the exception. As a full screen game it uses its own fixed palette, with no dark mode and no print stylesheet, and it loads a vendored copy of Three.js r186 (three.module.min.js and three.core.js) as an ES module, so it's three files rather than one.
- Create a folder named for the topic, keeping it short:
regex, notregex-visualizer - Write
index.htmlinside it, copying the theme script, token block and print styles from an existing tool - Add a card to the root
index.html - Keep it self-contained
Duplicating the token block across tools is deliberate. At this size, copying 40 lines costs seconds, while a shared stylesheet would introduce relative path handling and break pages opened over file://. Worth revisiting at four or more tools, when a palette change means four edits.
MIT