Disco Doodle is my hobby site — free, no-signup drawing prompt generators, a division of my little umbrella brand Plaid Labs. Daily Doodle was the original: pick a few categories (animal, occupation, prop, style...), hit spin, get a silly little drawing idea. Sometimes you just need to get past that blank page. More on that here.
A while back I asked Claude to build a second generator, Monster Maker, as a straight-up fork of Daily Doodle's engine but with monster-flavored categories: eyeballs, horns, skin texture, arm style, eyestalks, patterns, a "silly / scary / sleepy" vibe dial. Same beginner/advanced modes, same spin animation, same everything under the hood — just a new skin and a new set of categories.
That part went fast. What I actually want to write about is everything that happened after "it works" — the design back-and-forth, a Firebase security rules rabbit hole, building a share feature that doesn't feel like an ad, and then talking an AI agent into recording its own tutorial video of the thing it just built.
Monsters are different than drawing animals or people. I relate to drawing them because they can't look, "wrong". No one can say your proportions are off, or you forgot to add a nose, or hey that arm is longer than the other...because it's a monster. They only enter our world at nighttime while we're sleeping. Via a network of magical doors... oh wait.
Forking an app by pasting it into a fresh chat
Small but real gotcha worth mentioning: I was working in a fresh Cowork session, and it had no memory of Daily Doodle's code — it lives on my machine / in the repo, not in the AI's head. So the very first thing that happened wasn't code, it was a clarifying question: do you have the file, can I reach your computer, or should I build from scratch?
I pasted the whole daily-doodle.html file in. From there the fork was genuinely mechanical — same CSS custom properties, same category-card component, same slot-machine spin animation, just new data:
const categories = {
eyeballs: { label: "Eyeballs", icon: "👀", color: "card-teal", items: ["No Eyes","One Eye","Two Eyes","Three Eyes","Five Eyes","A Dozen Eyes"] },
horns: { label: "Horns", icon: "😈", color: "card-blue", items: ["No Horns","One Horn","Two Horns","Three Horns","Four Horns","A Crown of Horns"] },
skinTexture: { label: "Skin Texture", icon: "🖐️", color: "card-purple", items: ["Hairy","Slimy","Scaly","Bumpy","Fuzzy","Leathery"] },
// ...armStyle, eyestalks, pattern, vibe
};
const CORE_KEYS = ['eyeballs', 'horns', 'skinTexture']; // beginner mode
The sentence builder was the one piece that actually needed new logic, since "Your monster has Two Eyes and Four Horns, with Hairy skin, wiggly eyestalks..." doesn't follow the same grammar as Daily Doodle's "a [style] drawing of a [job] [animal] wearing [outfit]." Small thing, but it's the difference between a reskin and something that reads like it was actually written for monsters.
The background took longer than the app did
This is the part I'll happily admit: I could not settle on a background, and the AI just... kept building whatever I said next. Light blue polka dots. Then "big scalloped scales, olive green and khaki" (which, credit where due, it built as actual CSS radial-gradient math and got right on basically the first try — no image assets, just gradients tiled in an offset grid). Then "actually just olive green with giant polka dots." Then "remove the polka dots." We landed on flat olive green.
I really just didn't want it to look ugly, but wanted it to be fun when you changed from Daily Doodle to Monster Maker. A few of the tries were exactly what I asked for, and what I asked for was wrong and hideous!
The one genuinely useful thing that came out of that back-and-forth: once the background got dark, the plain gray subtitle/instruction text became almost unreadable against it. Rather than just darkening the text globally, the fix was giving those bits of text the same rounded "pill" treatment the app already used for buttons and badges — so it reads as an intentional design choice instead of a patch:
.subtitle {
display: table; margin: 8px auto 0;
background: rgba(255,255,255,0.8);
padding: 2px 14px; border-radius: 999px;
}
Small detail, but it's the kind of thing that's easy to miss when you're iterating fast — worth actually looking at your own contrast, not just trusting that "it compiles."
A Firebase rules rabbit hole
Both generators share one Firebase Realtime Database for their visitor counters — /counters/dailyDoodle and /counters/monsterMaker, two independent keys in the same JSON tree. That part's simple. What wasn't obvious to me was why the counter worked for one and not the other, and whether I needed a whole second Firebase project.
I ended up screenshotting my actual Firebase console rules tab a couple of times and just asking "does this help?" The short version of what I learned: Realtime Database rules can be scoped to a specific path or left wide open at the root, and my original rules were the default test-mode rules — open, but with a hard expiry date baked in ("now < <timestamp>"). That's the kind of thing that quietly breaks a hobby project months later with zero warning. We replaced it with a permanent rule scoped just to /counters:
{
"rules": {
"counters": {
".read": true,
".write": true
}
}
}
Open where it needs to be, closed everywhere else, no expiry ticking down in the background.
Building a share feature that doesn't feel like an ad
The ask was: after you spin, let people save an image — the result sentence, a counter badge, something you'd actually want to post next to a photo of your drawing — without it screaming "please post my app." That "not overly promotional" constraint mattered more than it sounds like it should.
The whole thing is a <canvas> element, never attached to the page, rendered on demand and downloaded as a PNG:
async function buildShareImageBlob() {
// measure the sentence first on a scratch canvas, so the final
// canvas can size itself to the text instead of leaving dead space
const lines = wrapCanvasText(measureCtx, sentenceText, cardWidth - cardPadX * 2);
const cardHeight = cardPadY * 2 + lines.length * lineHeight;
const H = Math.max(680, cardY + cardHeight + 160);
// ...draw wordmark, badge, card, and a small watermark bottom-right
return new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
}
That "measure first, then size the canvas" step mattered a lot in practice — a short beginner-mode sentence and a long seven-category advanced-mode sentence are wildly different lengths, and a fixed-size card either wastes half the image on empty space or clips the text. Sizing the canvas to the content fixed both at once.
The watermark is one small line of text in a corner, not a banner. That was the actual design decision — the whole point is that someone wants to post this next to their own art, and a self-promotional overlay works against that.
This is a screenshot from the test of the daily doodle verison. I probably misplaced the Monster Maker one.
The part I didn't expect: scripting a tutorial video with a fake mouse cursor
This is the bit I'd actually recommend trying yourself. Once the app worked, I asked for a vertical walkthrough video — and rather than screen-recording a live demo, the approach was to script an actual headless-browser session that performs the demo itself: type into the input, click spin, click share, all timed out, with a synthetic cursor and text captions injected directly into the page so they show up in the recording.
async function tapEl(page, selector) {
const handle = await page.$(selector);
await handle.scrollIntoViewIfNeeded();
const box = await handle.boundingBox();
await moveCursorTo(page, box.x + box.width / 2, box.y + box.height / 2);
await handle.click();
}
The cursor and captions are just DOM elements injected via page.evaluate() before the "recording" starts — a styled <div> that animates to each click target, and a caption pill that updates at each beat ("Add your own ideas to any category," "Tap Spin for an instant idea!"). Since they're real elements on the real page, Playwright's built-in video recorder captures them for free.
Two bugs worth knowing about if you try this:
-
Playwright's video recorder doesn't actually scale to the
sizeyou give it. If yourrecordVideo.sizeis bigger than your CSS viewport, it pastes the unscaled frame into the corner of the bigger canvas instead of stretching it — you get a video that's mostly gray dead space. Fix: record at your real viewport size and letffmpegdo a proper upscale afterward. -
Your page's own background needs to fill the full viewport height, or the same dead-space problem shows up below short pages. A one-line CSS fix (
html { min-height: 100%; background: ...; }) injected just for the recording session solved it without touching the real site's CSS.
ffmpeg -i raw.webm -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
-vf "scale=1080:1920:flags=lanczos,fade=t=in:st=0:d=0.4" \
-c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p \
-c:a aac -shortest -movflags +faststart walkthrough.mp4
(The silent audio track is deliberate — some platforms handle video-with-no-audio-stream oddly, so anullsrc gives it one for free.)
Where the AI actually hit a wall
I asked for a voiceover next, and this is the honest part: there's no ElevenLabs- or OpenAI-quality voice available in that sandbox. What was available: espeak-ng (classic robotic TTS, works instantly, zero setup) and Piper, a free offline neural TTS that's a meaningful step up in naturalness — installable in about thirty seconds, no API key, no network dependency at runtime.
I still haven't posted this video. I'm not sure why. Anyone else get super self-conscious before posting to social media? Me neither!
What I did get out of the AI: a clean, timed caption script broken into beats that match the video almost to the second, ready to paste into whatever TTS tool I wanted:
0:00–0:03 — "Welcome to Monster Maker."
0:03–0:06 — "Add your own ideas to any category."
0:10–0:15 — "Tap Spin for an instant monster!"
0:18–0:20 — "...or share it as an image!"
That felt like the right division of labor, honestly — the AI is great at the mechanical parts (timing, syncing captions to on-screen actions, getting the technical plumbing right) and much less useful the moment "which voice sounds right for my brand" becomes the actual question.
What I'd take away from this
You should absolutely ask to get a video generated on how to use your product. It may not be great for marketing or even an explainer video. You do get to see how someone will use your idea before hitting publish. Who knows, it may give you some insight on things to tweak or change. Ways to really make your idea even shinier.
If you want to see the actual result, both generators are live and free at discodoodle.com — no signup, just pick your categories and hit spin.
While you're there, make a Monster and doodle it up on a post-it. Share Your Art in our Doodle Gallery. We could always use more submissions!

Top comments (0)