openconstructor is a free, open-source page builder. You describe a page, AI agents build it on a canvas, and you export plain HTML. This update came from watching it fail. A friend asked for "a site about the current strongest LLMs with charts", and the AI:
- listed GPT-4o and Claude 3.7 as the newest models,
- drew filter buttons that did nothing,
- laid a line chart on top of the table,
- and when I asked it to fix that, said "fixed the overlaps" and changed nothing.
Every one of those has the same root cause: the model writes blind. It never sees the page, it never sees today's world, and it gets cut off when a page is long. Here's what I changed, with the code that does it.
TL;DR
- The studio now checks every result for overlapping parts and sends them back to the model
- Requests about anything current trigger a web search before the model writes
- Big pages are edited part by part instead of rewritten whole
- Tabs, filters and accordions work without JavaScript
- 31 AI providers, some with no key at all, and GPT-6.1 Sol free through Pollinations
- A stop button, phone models for screens, real icons and company logos
1. Giving the model eyes: an overlap check after every answer
The model can't see that its chart sits on top of the table. The browser can. After each answer is drawn on the canvas, the studio walks the rendered page and collects everything you'd actually see: text, form controls, and charts (an <svg> with at least three marks). Then it compares every pair that isn't nested.
const w = Math.min(a.box.right, b.box.right) - Math.max(a.box.left, b.box.left); const h = Math.min(a.box.bottom, b.box.bottom) - Math.max(a.box.top, b.box.top); if (w > 2 && h > 2 && w * h >= Math.min(a.area, b.area) * 0.25) found.push(`${describe(a.el)} overlaps ${describe(b.el)}`); If anything is found, the model gets one concrete fix request. That means "<label.chk> “Google” overlaps <select.dev> “All models”", not "please check the layout". It gets one round only, so it can't loop.
The tricky part was false alarms. A giant headline with line-height: .8 "overlapped" the nav under it, though the letters were nowhere near it. A text node's client rect is the font's whole content area, accent room and descender room included. Trimming each line box to roughly the letters fixed it:
const top = q.top + fontSize * 0.16, bottom = q.bottom - fontSize * 0.2; Overlaps that are part of the design pass the check: text on a photo, a collage of prints over a big word, the invisible radios behind custom tabs.
2. The model lived in 2024: fresh facts before writing
Models confidently present what they remember as current. Mine could ask for a web search, but it almost never did. So now the studio searches before the model writes, whenever a request mentions news, prices, "latest", rankings, a year, or AI models:
- The model writes 1–2 search queries.
- The studio runs them, then opens the two best results and reads them in full. Rankings and scores live on those pages, not in snippets.
- For anything about AI models, it also pulls OpenRouter's public model list. It's updated daily and has release dates, context sizes and prices.
- Everything goes to the model, and to every agent of the page swarm, under one rule: take names, numbers and dates from these facts; if a number isn't in them, write "—".
That last rule matters more than it sounds. Before it, the model filled the "score" column with plausible numbers it made up. Now it leaves a dash.
3. Buttons that did nothing: interactivity without JavaScript
The pages are HTML + CSS only. Scripts are off on the canvas and in exports, on purpose. So every "All · Code · Math" filter the AI drew was a picture of a button. Instead of letting it write JS, I gave it one markup pattern and add the CSS myself after the answer:
<div class="oc-tabs"> <input type="radio" name="lb" id="lb-1" checked><label for="lb-1">All</label> <input type="radio" name="lb" id="lb-2"><label for="lb-2">Code</label> <div class="oc-panels"><div>…overall…</div><div>…coding…</div></div> </div> .oc-tabs > .oc-panels > * { display: none } .oc-tabs:has(> input:nth-of-type(2):checked) > .oc-panels > :nth-child(2) { display: block } :has() does the switching: one rule per tab, up to eight. Accordions are <details> and toggles are checkboxes. It works on the canvas, in exports and on published pages, and there's still no script.
4. "Could not apply the response": editing big pages part by part
Asking for one more section on a 7-section page used to make the model return the entire page. That's the CSS of the design system plus every section, and it often ran past the 8,000-token answer limit and got cut off. When it did fit, it took over three minutes.
Now a big page goes to the model with markers, <!-- part 3 -->, and the model answers with only the changes:
<!-- after 3 --> <section class="charts">…</section> <!-- replace 5 --> <footer>…</footer> A small parser applies them. If the answer gets cut off mid-block, the unfinished last block is dropped and the rest still applies. Edits are faster, and the parts nobody asked to change stay the same.
5. Art directions, learned from Awwwards
To push the look up, I studied a dozen recent Awwwards Sites of the Day. I didn't copy their code (most of it is WebGL anyway). I wrote down the moves they share:
- a giant wordmark edge to edge;
- a full-bleed dark photo with tiny mono labels in the corners;
- heavy condensed caps mixed with serif words in one headline;
- an editorial photo collage;
- floating products on a pastel ground;
- one gigantic number as the hero of a section.
These became eight art directions, each with rules and an example hero I wrote from scratch. The planner picks one for the business, and every section agent follows it.
6. 31 providers, and CORS decides who gets in
openconstructor sends your key straight from your browser to the AI service. It never goes through my server. That makes CORS the entry test: if a service's API doesn't answer browser requests, it can't be added. I went through the free-LLM lists on GitHub and probed 26 APIs. Some highlights:
| Works from the browser | Doesn't (no CORS) |
|---|---|
| OVHcloud (no key at all), LLM7 (no key), Pollinations, Vercel AI Gateway, Scaleway, Alibaba, DeepInfra, Fireworks, Nebius, Novita, Chutes | NVIDIA NIM, Kilo Code, Cloudflare, Ollama Cloud, SambaNova, GitHub Models |
The other half of the work was honest "$0" labels. Several services list free and paid models side by side in different formats: LLM7 has "turbo" vs "pro" tiers, Pollinations has a paid_only flag, and some report prices per token while others report them per million. If you get that wrong, someone picks a paid model marked free and hits "insufficient balance". Now only models that actually cost nothing get the $0 badge.
Free GPT-6.1 Sol
The nicest find: Pollinations gives a free key with a daily allowance, and a lot of strong models fall under it. GPT-6.1 Sol, GPT-6 Luna, Kimi K3.1 Pro and DeepSeek V4.1 Flash are all marked as not paid-only. Get a key at enter.pollinations.ai, paste it under ⚙ AI keys, and pick openai/gpt-6.1-sol.
Two caveats so nobody's surprised:
- It's a daily allowance, not unlimited.
- I don't know how Pollinations serves it behind the scenes; it may be routed differently from OpenAI's own API. I haven't benchmarked the two against each other.
For free page-building it's a great option all the same.
If you want zero setup, OVHcloud and LLM7 work with no key or sign-up at all. They're rate-limited, but that's enough to try things.
7. Smaller things
- Stop button. The send button turns into a square "stop" while the AI works. One
AbortControllercovers every request of the turn, including the parallel agents. - Phone and tablet models. iPhone 17 Pro Max, Galaxy S26, Pixel 10, iPad and more are in the page size list, and the preview uses the model you picked.
- Icons and logos. The model writes
<img src="icon:trophy">or<img src="logo:anthropic">, and the studio inlines the real SVG: about 1,500 Lucide icons, plus company logos from Simple Icons. No more empty squares where a logo should be. - Agents that wait their turn. Free tiers often reject a burst of parallel requests with 429. Agents now queue and retry, and if they still fail, the page is built in one go.
openconstructor is free and open source, and it works without an account. Try it at openconstructor.pw and grab the code on GitHub.
If you've solved "the model can't see what it built" differently, I'd love to hear how. The overlap check is crude, and I'm sure there are smarter ways.