Hackathon prompt: Build something for, about, or inspired by dogs. International Dog Day is August 26.
I decided to answer the prompt with a question:
What if your dog wasn’t a photo — but a physical piece of CSS?
🐾 Meet PawCSS
PawCSS turns any dog photo into a living CSS artwork. Upload a picture, and the app reads every pixel, quantizes the colors, then renders the image again as thousands of individual HTML elements styled with CSS.
No filters. No canvas tricks. No hidden <img> tags behind a blur. The final artwork is literally a grid of DOM nodes — tiles, bevels, bulbs, halftone dots, paper blobs, or ASCII characters — each with its own generated rule.
Live demo: https://pawcss.lovable.app
🎨 The art styles
PawCSS has six CSS “reconstruction” modes. Each one treats the sampled pixels differently:
Style What it does Mosaic Tiles Hand-set ceramic tiles with grout gaps, subtle gradients, and slight per-tile rotation. Pixel Bevel Chunky 8-bit blocks with inset light/shadow bevels for a retro 3D feel. Neon Bulbs Glowing circles on a black stage, sized and blurred by the sampled brightness. Halftone Print Rotated ink dots on paper, usingmix-blend-mode: multiply to feel like print.
Paper Cut
Overlapping organic blobs with randomized organic radii and rotation.
ASCII Terminal
Character-ramp glyphs rendered as colored text cells on a dark terminal background.
You can switch between styles instantly, change the resolution (Bold 32², Balanced 54², Detailed 78²), and drag a Before/After slider to compare the original photo with the CSS reconstruction.
🔍 Why this isn’t just a CSS filter
Most “photo → art” demos hide the original image and throw a filter: blur() or filter: contrast() on top. The photo is still there.
PawCSS deletes the image from the artwork layer:
- The uploaded photo is drawn into a small offscreen
<canvas>. -
getImageData()reads every pixel. - Each pixel becomes a cell with a hex color, luminance, scale, rotation, and style-specific geometry.
- React renders one DOM element per cell inside a CSS Grid.
- The stats panel reports the real element count and unique color count.
A 54 × 54 reconstruction is 2,916 real DOM elements. The export is a standalone HTML file with zero image references that still renders the dog.
🧠 Meet Your Dog — Google AI integration
After the artwork is built, PawCSS sends the downscaled photo to a server function that calls Google Gemini (through the Lovable AI gateway) and generates a playful personality profile:
- Invented name
- Likely breed/mix
- Energy level (1–5)
- Cuddle level (1–5)
- Short vibe
- One-line description
- A funny “dog thought”
The result is shown in a “Meet Your Dog” card. All of this is optional: if the AI key is missing, the rate limit is hit, or the request fails, the app falls back to a friendly placeholder profile. The CSS art pipeline never depends on it, and the API key stays server-side.
That puts the project in the Best use of Google AI prize category.
🔧 Architecture
Built on TanStack Start + React 19 + Tailwind CSS v4.
src/lib/imageProcessing.ts validation, decode, compression, sampling, quantization
src/lib/artwork.ts cell model + per-style CSS rule generation
src/lib/exportArtwork.ts standalone HTML/CSS exporter
src/lib/dogProfile.functions.ts server function → Google Gemini
src/components/pawcss/ UI components (slider, inspector, exporter, etc.)
Enter fullscreen mode Exit fullscreen mode
The image processing layer is plain TypeScript with no React imports, so it is easy to test and reuse. Every number on the stats panel is derived from the generated artwork, not hardcoded.
🎙️ Optional: Let My Dog Speak
The app also has a “Let My Dog Speak” button that reads the AI-generated dog thought aloud using the browser’s built-in SpeechSynthesis API. It works with zero extra keys and zero cost, and the button is hidden when the API is unavailable. Because the voice line is generated from the same AI profile, swapping this to ElevenLabs only requires a server function that returns audio for the same sentence — so the project can also fit the Best use of ElevenLabs category.
🚀 Try it
- Open the live demo.
- Drop a photo of your dog.
- Pick a style and resolution.
- Zoom into the cells, inspect a single generated rule, or export the whole thing as HTML + CSS.
💡 Lessons learned
-
DOM weight matters. A 78 × 78 grid is ~6,000 nodes. We keep things fast with lightweight
<i>elements and scale transforms instead of re-rendering at different sizes. - Export size matters. Per-cell rules would be huge; we deduplicate unique colors into palette classes so the exported file stays reasonable.
- Honest stats matter. It is tempting to fake the numbers. Every stat in PawCSS is computed from the artwork model at render time.
🏆 Prize categories
This submission is entering the Best use of Google AI category (Gemini vision profiling). The optional speech path is designed to slot into the Best use of ElevenLabs category with a single server function swap.
🐕 Final thought
PawCSS was built to celebrate dogs and the weird, wonderful things you can do with the open web. Every tile, bulb, and blob in the final image is a real DOM element — proof that a dog photo can become something more playful than just another filtered image.
Upload your dog. Rebuild them in CSS. 🐾
Built with ❤️ (and a lot of divs) for the DEV International Dog Day Hackathon.