
</ascii cam>
Your webcam as live ASCII art. 30fps, zero deps, nothing leaves the device.
30+ fps
live ASCII render
1 file
zero dependencies
0 bytes
leave the device
SPECIFICATIONS
| ROLE | SOLO BUILD |
|---|---|
| YEAR | 2026 |
| TYPE | BROWSER TOY |
| STATUS | LIVE |
| STACK | vanilla js · getusermedia · canvas 2d · ascii · real-time · zero deps |
| LINKS | — |
| AVAILABILITY | in development |
“The portfolio aesthetic is mono-and-ASCII-coded, and the most honest way to extend that into something interactive is to let the visitor become part of the ASCII layer.”
A browser toy that turns your webcam into live ASCII art.The frame stream is sampled in a canvas, mapped to a glyph density ramp, and printed into a `<pre>` at 30+ fps.Two character ramps (simple and dense), invert toggle, mirror toggle.
== WHAT IS THIS ==
A browser toy that turns your webcam into live ASCII art. The frame stream is sampled in a canvas, mapped to a glyph density ramp, and printed into a `<pre>` at 30+ fps. Two character ramps (simple and dense), invert toggle, mirror toggle. Nothing leaves the device.
== </the problem> ==
The portfolio aesthetic is mono-and-ASCII-coded, and the most honest way to extend that into something interactive is to let the visitor become part of the ASCII layer. But a public webcam toy is only okay to publish if privacy is a provable property, not a promise.
== </my approach> ==
I built it as a single HTML file with zero dependencies: getUserMedia opens the camera into a hidden video element, a hidden canvas samples each frame into a low-res buffer matched to the character grid, and a luminance ramp picks one glyph per pixel. The whole frame is written as one string to a <pre> via textContent on every animation frame, hitting 30+ fps. No server, no upload, no recording, no analytics call.
== </the story> ==
ASCII CAM is a single-file browser toy. Open it, give it camera permission, and your live webcam feed is sampled, luminance-mapped, and reprinted as ASCII glyphs at 30+ fps. The stream never leaves the device. There is no server, no upload, no recording, no analytics call. The whole thing is one HTML file with no dependencies.
It exists because the portfolio aesthetic is mono-and-ASCII-coded, and the most honest way to extend that aesthetic into something interactive is to actually let the visitor become part of the ASCII layer.
== </architecture> ==
Vanilla JavaScript, no framework, no build step. getUserMedia opens the camera into a hidden video element. A second hidden canvas draws each frame into a low-res buffer matched to the available character grid. getImageData reads the pixels back, and a luminance ramp ((0.2126·R + 0.7152·G + 0.0722·B) / 255) picks one glyph per pixel from a ramp string. The result is concatenated into a string and assigned to a <pre> via textContent on every animation frame.
Two ramps ship: a 10-char simple ramp for high-contrast readability, and a 60-char dense ramp for richer luminance gradient. Mirror, invert, and density mode are stateful toggles. A resize observer recomputes the column count from the available rect so the renderer always fills the stage.
The whole experience is sandboxed inside the portfolio's embed frame with back, home, and open-in-new controls.
== </key features> ==
Live 30+ fps ASCII render
Each frame is luminance-mapped ((0.2126·R + 0.7152·G + 0.0722·B) / 255) and reprinted as glyphs in real time.
Two character ramps
A 10-char simple ramp for high-contrast readability and a 60-char dense ramp for a richer luminance gradient.
Mirror, invert, and density toggles
Stateful controls that reshape the render without restarting the stream.
Responsive glyph grid
A resize observer recomputes the column count from the available rect so the renderer always fills the stage.
Nothing leaves the device
The camera permission grant is the only network-relevant action. There is no server and nothing comes back over the wire.
Opt-in gate page
A clear pre-permission UI explains what is about to happen before the camera request ever fires.
== </key decisions> ==
DECISION 01
Run client-side, no upload, ever. The page is built so the camera permission grant is the only network-relevant action and nothing comes back over the wire. That property is the one thing that makes a public webcam toy okay to publish.
DECISION 02
Use textContent on a <pre>, not the DOM, not canvas drawText. Writing the frame as one string per tick is the cheapest possible render path and the browser's text renderer is faster than anything I would write into a canvas for this size of grid.
DECISION 03
Provide an opt-in gate page before the camera request fires. Browsers will ask anyway, but a clear pre-permission UI explaining what is about to happen avoids surprise.
== </what i learned> ==
The cheapest render path wins: writing the frame as one string to a <pre> via textContent beats anything I would draw into a canvas at this grid size · the browser's text renderer is faster.
Client-side-only isn't a nice-to-have for a webcam toy, it's the one property that makes publishing it okay at all.
Browsers will ask for camera permission anyway, but a pre-permission gate that explains what's coming avoids surprise entirely.
vanilla js · getusermedia · canvas 2d · ascii · real-time · zero deps
interactive demo · loads on demand
LIVE