PaintPlay
Project Case Study
PaintPlay
A free browser-based coloring app: upload an image, auto-generate line art, and paint with brush, eraser, and bucket fill. Built as a static Next.js export with hand-written Canvas 2D tooling, no login, no database, and no server.
View Live Project →Overview
PaintPlay is a free, browser-based coloring app. The hero states the whole workflow in one line: "Upload an image, auto-generate line art, and paint with brush, eraser, and bucket fill. Export your result as a PNG."
The architectural position is as important as the feature set: no login, no database, no server actions, no API routes. The entire app is a static Next.js export, and every image the user brings stays on their own device. Nothing is uploaded, because there is nowhere to upload it to.
The Design Constraint
A coloring tool for casual users and children has a short patience budget. Every step between "I have a picture" and "I am painting" is a place to lose someone. That produced three rules:
No account. The app opens straight into the tool.
No upload. Privacy is structural rather than promised — the architecture has no server to send an image to.
No leaking. Bucket fill must respect line art the user never drew, which is the hard part.
Technical Implementation
Stack
| Area | Implementation | Notes |
|---|---|---|
| Framework | Next.js 16.1.1, output: 'export' | Fully static; Turbopack builds |
| UI | React 19.1.0 + Tailwind CSS 4 | Only four runtime dependencies in total |
| State | None (no library) | Hand-rolled stores plus React hooks |
| Graphics | Raw HTML Canvas 2D | No canvas or image library |
| Audio | Web Audio | BGM loop and click sounds |
| Offline | Service worker + generated precache | Registered client-side; offline fallback page |
The dependency list is worth stating plainly: next, react, react-dom, and lucide-react. That is all of it. The drawing engine is written by hand.
Line extraction
Converting a photo into line art runs as pure JavaScript over ImageData — a configurable edge threshold plus a hand-written alpha dilation pass. No OpenCV, no WASM, no model call. It runs on the client, immediately, on whatever device the user already has.
Layered canvases
The painting surface composites separate canvases — source, line, paint, mask, and overlay. Separating them is what makes the tooling tractable:
Bucket fill floods against the thresholded and dilated line mask, so paint stops at the lines instead of leaking through antialiased gaps
Undo/redo is a capped history stack rather than a full-image snapshot per stroke
Export composites paint under the line layer, so strokes never smear the outline, and downloads locally as a PNG
Brushes with texture
Six presets — paint, marker, pencil, crayon, ink, and glow — implemented over pattern textures rather than plain strokes, so the output looks drawn rather than filled.
PWA
A generated precache manifest, a custom service worker, an offline page, and a fullscreen display mode. On a tablet it installs and behaves like a native coloring app, which is the device most of its users are actually holding.
Content structure for discovery
Beyond the tool itself, the app ships themed coloring page galleries with per-theme and per-page static routes, plus intent-targeted landing pages (photo-to-coloring, doodle-to-coloring, free-coloring, online-coloring) and guides. Static export means every one of them is prerendered HTML.
What This Case Study Demonstrates
Building real graphics tooling on the raw Canvas API instead of reaching for a library
Making privacy an architectural property rather than a policy paragraph
Solving the actual hard problem in a coloring app — fill containment against generated line art
Shipping an installable, offline-capable PWA from a static export
Keeping a dependency surface small enough to audit in one sitting
Live: https://paintplay.app/
Role: Solo delivery (line extraction → canvas engine → tooling → PWA → content/SEO)