A 2 days ago I launched openconstructor, a free, open-source browser editor: you describe an interface, an AI builds it on a canvas, and you keep editing by hand or ask the AI to change one part. It got 562 visitors in its first two days with no ads.
The whole thing runs on a Cloudflare Worker, Supabase and free open models. Here are the parts that turned out more interesting than I expected.
1. Small free models can't design, so I stopped asking them to
The built-in AI runs on open models (GPT-OSS 120B, Llama 3.3 70B) through Workers AI. They write valid HTML, but ask for an "award-winning cinematic website" and you get something different every time: sometimes great, often a plain hero and three cards.
Prompt engineering got me maybe 20% further. What worked was splitting the job:
- the design is a set of hand-made sections (hero with a CSS 3D object, scroll-lit statement, services, case studies, logo wall…);
- the model only writes the content, as JSON.
// the model gets a schema with defaults and returns only this { "brand": "Ember", "hue": 28, "titleA": "Roasted by fire.", "titleB": "Poured with care.", "services": [["Roast", "Small batches on a 1960s drum roaster."], ...], "heroArt": "amber neon cube over dark sand dunes, cinematic 3d render" } The page is then assembled from the sections. hue rotates the template's accent colours, so a coffee roaster gets amber and an AI lab gets violet. Same model, but the result went from "sometimes nice" to consistently polished, in about 6 seconds.
Lesson: with weak models, move the taste into code and let the model fill in the blanks.
2. Published pages run zero JavaScript
Users publish pages to openconstructor.pw/p/<name>. Since anyone can publish, every page is served with:
Content-Security-Policy: sandbox allow-forms ...; script-src 'none' No scripts at all. So how do pages still feel alive? Modern CSS covers more than I thought:
@supports (animation-timeline: view()) { @media (prefers-reduced-motion: no-preference) { .section { animation: rise linear both; animation-timeline: view(); animation-range: entry 0% cover 30%; } } } @keyframes rise { from { opacity: 0; transform: translateY(64px); } } Scroll-driven animations, perspective + preserve-3d for floating 3D objects, marquees with mask fades: all without a single line of JS. Browsers without support just show the content.
3. Screenshots without a server
The sandbox has a side effect: the page has an opaque origin, so the gallery can't measure or screenshot it in an iframe. Headless Chrome on the server would be costly.
Instead, the editor takes the screenshot itself at publish time:
- lay the page out in a hidden iframe to measure its height;
- put the HTML into an SVG
<foreignObject>; - draw that SVG onto a
<canvas>and export JPEG.
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"> <foreignObject width="100%" height="100%">${xhtml}</foreignObject></svg>`; img.src = 'data:image/svg+xml,' + encodeURIComponent(svg); ctx.drawImage(img, 0, 0); canvas.toBlob(upload, 'image/jpeg', 0.82); The worker stores two images in KV: a 1200×630 one for link previews (og:image) and a full-page one for the gallery, which scrolls through it on hover. About 40 KB each.
4. My biggest mistake: a sign-up wall I didn't notice
After launch I looked at the numbers: hundreds of visitors, two accounts. It turned out the free AI required signing in. So most people opened the editor, typed a prompt and hit a login form.
Now visitors get 3 free generations a day without an account (counted in KV per hashed IP, the IP itself isn't stored). An account gives 20 a day, cloud projects and publishing. A friendly offer appears after the first good result, not before.
Lesson: test your product signed out, in a private window, before every launch post.
Try it / read the code
It also exports to React, Vue, Svelte and even Roblox UI and LÖVE 2D. I'd love feedback, especially on where the AI output falls apart.