Overview of 977: We built a CSS Challenge platform
Wes Bos and Scott Tolinski walk through building MadCSS — a real-time CSS coding battle platform used to host a March tournament with top CSS devs. They explain what a CSS battle is, demo the user experience (real-time diff, overlay, competitor views), and deep-dive into the stack, sync design, in-browser diffing algorithm, sandboxing, and the engineering challenges they hit while shipping quickly.
Key points / main takeaways
- MadCSS is a fast, real-time platform where two competitors recreate a target UI using HTML/CSS. Scoring is objective, based on an image diff between the target and each competitor’s render.
- The UI allows competitors to work in their local editor (VS Code), then syncs their file changes to the platform instantly.
- The system uses Zero (a sync engine/platform), SvelteKit, Drizzle for schema handling, Postgres (hosted on Supabase in their setup), and Cloudflare Workers for the main app. The sync server runs as a long-lived process (they used fly.io).
- Diffing is done client-side by rendering HTML to image (using an HTML→SVG foreignObject → canvas approach via a library they called “snapdom” / html-to-image technique) and then comparing pixels with tuned algorithms and thresholds to provide useful scoring (not strictly pixel-perfect).
- Major engineering challenges: tuning the diff algorithm, auth token expiry causing "unauthorized" sync errors, script sandboxing (CSP + iframe sandbox), and getting file-system-to-browser sync right.
Platform / Stack (high level)
- Frontend / app framework: SvelteKit (Cloudflare Workers adapter).
- Real-time sync engine: Zero (referred to throughout as “Zero/Zero Sync”). Wes also wrote Svelte bindings for Zero.
- Schemas & DB: Drizzle schemas; Drizzle → Zero schema translator; Postgres as the source DB (they used Supabase to host Postgres).
- Sync server: Long-running container process (they ran on fly.io). Zero supports server implementations including Cloudflare Durable Objects (not used here but mentioned as an option).
- Hosting: App/UI on Cloudflare Workers; sync server on fly.io (with plans/possibility to move to Cloudflare Durable Objects).
- Auth: BetterAuth (used for Google OAuth) plus middleware in SvelteKit to pass auth context to Zero routes.
- CSS utilities: Graffiti utilities (stacks, splitting, color tokens) and Tailwind support via CDN (Tailwind logic runs in the sandboxed iframe).
How the real-time sync works (Zero summary)
- Zero provides client-side libraries (queries & client-side mutation code run instantly), a long-running sync server, and server-side handling.
- Workflow:
- Client-side mutations apply immediately to local state.
- Changes are patched and sent to the Zero sync server over WebSocket.
- The sync server keeps a replica (it can replicate Postgres → SQLite), computes diffs/patches, and informs apps which rows changed since last update.
- The app’s API routes receive those changes and run server-side logic (auth, permissions, final writes).
- Important notes:
- Zero is not a standalone database — writes is ultimately persisted via your own DB/ORM code (they use Drizzle).
- Zero became easier to adopt because they changed patterns: instead of encoding auth/permissions in the schema file, you can now handle mutations via your own API routes and reuse your app’s auth logic.
- Zero is very fast because it stores/loads local data (IndexedDB), sends small patch diffs, and syncs only deltas.
File-system integration & UX
- Users select a folder and work with their normal editor (VS Code). The platform stores a File System Access handle in IndexedDB and exposes the local files in the in-browser UI.
- Saves in the local editor trigger a mutation that Zero picks up; sync server reconciles and broadcasts updates. Changes appear immediately in the battle preview (near-instant).
- This design preserves user ergonomics (shortcuts, completions, editor extensions) while enabling real-time sharing.
Diffing & scoring (how they made it feel fair)
- Goal: fair, consistent scoring without running Chromium servers. So they render both target and candidate in the same browser and diff the resulting images.
- Rendering approach: convert an HTML element to an image using an HTML→SVG foreignObject → canvas technique (they used a library described as “snapdom” / html-to-image style).
- Diffing algorithm:
- Tried multiple approaches: vectorization, SSIM (Structural Similarity Index), etc. None were perfect for their use-case.
- Final solution: a composite system with multiple algorithms, weighted thresholds, and heuristics (color weighting, opacity weighting, background exclusion).
- Special handling for background: background color often dominates pixel counts — they subtract/punch out background pixels from scoring to avoid trivial wins.
- Results visualization: a diff viewer (red = major difference, green/blue = minor) and an overlay with opacity and drag control so competitors can visually target fixes.
- Tuning the algorithm required many tests and iterative adjustments — small knob changes affected many cases.
Security & sandboxing
- Competitor HTML/CSS is rendered in sandboxed iframes.
- They allowed the Tailwind CDN script but restricted inline scripts and other origins via a Content Security Policy (CSP) header so contestants couldn’t execute arbitrary scripts on other clients.
- This combination of iframe sandbox attributes + strict CSP allowed Tailwind to run while preventing harmful cross-client script execution.
Major bugs & lessons learned
- Auth expiry bug: the JWT they used expired after 15 minutes, which caused sync server to intermittently report unauthorized. Fix: lengthened token lifetime as a short-term workaround; better long-term fix would be safe refresh flow without tearing down the Zero client instance mid-battle.
- Hosting pain: fly.io worked but was clumsy to manage for logs/deploys. They plan to move the sync server to Cloudflare Durable Objects or another long-running host.
- Diffing edge cases: many odd test cases revealed issues (e.g., divs mimicking backgrounds), requiring additional heuristics.
- Time pressure: many fixes were discovered under tight schedule; heavy user-testing and iterative fixes were crucial (they ran daily usage sessions and fixed bugs).
Tournament & launch details
- MadCSS tournament (Mad CSS / March MadCSS) hosted on madcss.com and their YouTube channel.
- Format: two competitors per match, objective scoring; bracket picking feature and prizes planned.
- Competitors included well-known CSS/Frontend devs (as announced): Kevin Powell, Adam Argyle, Adam Wathan, Cassidy Williams, Ania Kubów (Ania Kubow), Josh Comeau, Kyle Cook (Web Dev Simplified), Ben Hong, Bree Hall, Chris Coyier, Jason Lengstorf, Shonda Pearson, Amy Dutton, and others.
- They built tournament UX (referee view, lobby, spectator/commentator overlays, badges, merch, jackets).
Notable quotes / insights
- “I hate writing code in some little text box on a website… use your own editor.” — core UX philosophy: let users use their tools, sync changes in real time.
- “Zero is incredibly fast because it stores your data locally and sends patch messages.” — summary of why Zero fits this use-case.
- Diff tuning insight: a single small algorithm change can move many outcomes — iterative, case-driven testing is essential.
Recommendations / action items for developers building something similar
- Use a client-first sync engine (Zero) for low-latency real-time experiences, but plan auth and token refresh carefully.
- If you need deterministic scoring, render both target and candidate in the same runtime (browser) to avoid cross-environment differences.
- Use iframe sandbox + CSP to safely allow third-party utilities (Tailwind CDN) while preventing code execution risks.
- Keep users in their editor by integrating the File System Access API and persist file handles in IndexedDB — UX wins are worth the implementation complexity.
- Expect to iterate heavily on image-diff thresholds; automate or maintain an extensive test-suite of representative cases.
- Consider hosting tradeoffs: long-running processes are required for some sync servers — Cloudflare Durable Objects are an interesting alternative to containers (fly/Coolify).
Where to find it
- MadCSS site: madcss.com
- Tournament and videos: Syntax YouTube channel (search MadCSS / March MadCSS)
If you want a one-line summary: they built a fast, real-time CSS battle platform that lets competitors use their local editors, syncs edits instantly via Zero, performs in-browser image diffing with a carefully tuned algorithm, and shipped it in a short, intense sprint to power a public tournament.
