# My AI page builder couldn't see its own pages. Here's how I gave it eyes (and free GPT-6.1 Sol)

> Source: <https://dev.to/timofeus_248d45d3902a91a3/my-ai-page-builder-couldnt-see-its-own-pages-heres-how-i-gave-it-eyes-and-free-gpt-61-sol-16e7>
> Published: 2026-11-15 21:10:48+00:00

[openconstructor](https://openconstructor.pw) 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:

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 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.

``` js
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:

``` js
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.

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:

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.

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.

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:

``` php
<!-- 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.

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:

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.

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.

The nicest find: **[Pollinations](https://pollinations.ai)** 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:

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.

`AbortController` covers every request of the turn, including the parallel agents.`<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.
openconstructor is free and open source, and it works without an account. Try it at **[openconstructor.pw](https://openconstructor.pw)** and grab the code on **[GitHub](https://github.com/Kuloka/openconstructor)**.

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.
