Skip to content

Notes2026.03.17

The page adapts to the ad. What you’ve already read stays put.

A demo store page that picks up the story of the ad you clicked. The model arranges blocks I built, and the code decides what it can change.

Status
Published
Date
2026.03.17

I wanted to see what happens when a landing page picks up the story of the ad you clicked. If you came from an ad about working on the road, the page should start with organization and room for a laptop. If the ad was about travel, it should spend more time on how much fits and how you pack it. And if halfway through you start poking at the price or the warranty, the page should notice.

To try it, I built Atlas One, a demo store for a product that doesn’t exist, with 15 campaigns, a catalog of page blocks and a way for the page to adjust while you’re on it. The hard part was deciding where the model gets a say and where the code keeps the final word. I’m the person who gets bugged when a button jumps 2px on hover, so I wasn’t going to let a model redesign my page on the fly. The part I found most interesting was turning design decisions into a contract the code enforces: which blocks can be used, which facts the page can rely on, and when the screen isn’t allowed to change anymore. Zod schemas spell out exactly what a model is allowed to send back.

via GIPHY

Every campaign has a brief

Each of the 15 campaigns comes with a visual brief that spells out color, type, composition and mood. That’s what ties the ad to the way the page gets built. If the ad image already has text baked into it, a rule keeps the page from stacking a new headline on top. (Two headlines fighting over one picture never looks good.)

The ad is only a first guess about why you’re here. If you do something on the site that says more, like checking the price, that can take over. In the demo, the Facebook and Instagram clicks are made up. There’s no Meta account behind them and no personal profile of anyone.

The model picks from blocks I already built

On the campaign route, the model sends back a structured plan for the page. The content has four jobs: the product’s story, how practical it is, the warranty and the confidence to buy, and the offer itself.

For each job I built six layouts, 24 blocks in all, and every layout can take one of six visual treatments, like a frame, an accent color or extra breathing room.

Four jobs, six blocks each. The model picks one per job.

Google plans the creative and decides what the page should lead with. Then Jev picks a block and a treatment from the approved set. The code checks that the pick fits its job and falls back to a deterministic default when it has to.

I like this setup because the page can change and I can still test and maintain it. If I want a new block, I have to add it to the catalog and build it in the project first. Only then can a model choose it. I also built a scanner that finds candidate components in public repos, and what it finds has to take the same route. None of it is in the live catalog yet.

What you’ve read stays put

A page that rearranges itself while you scroll makes you lose your place. So Atlas One has one hard rule: once you’ve seen a section, its content and its position are locked.

via GIPHY

Under the hood it’s an IntersectionObserver and a list of the section IDs you’ve seen. When a new plan comes in, the merge keeps those sections exactly where they are and only fills the slots that are still open:

const unseen = next.filter(section => !viewed.has(section.id));
let cursor = 0;

return current.map(section =>
  viewed.has(section.id) ? section : unseen[cursor++]
);

So if you start reading about the warranty, the sections you haven’t reached can change, and the part you already read doesn’t move. The hero and the page’s first look stay put too.

Seen sections lock. A new plan can only change what’s below.

It shows up before the model is done

The page loads in two steps. First you get a local plan with approved content, the hero plus the first three sections. The full request runs in the background, and when it lands, it can update the sections you haven’t seen yet and add the last one.

If the provider is down, you just get the local plan and the page still works. Creative plans also stay in server memory for 30 minutes, so two visits from the same campaign don’t make the model do the same work twice.

The facts live in one file

lib/product.ts is the one source of truth for what the product is. On the current campaign route, whatever copy the model writes gets swapped for the approved copy. The model gets to shape how the page is put together, and that’s it. Claims about price, volume and warranty stay with me.

What it doesn’t prove yet

The automated tests cover valid page plans, the path with no model at all, keeping seen sections in place, content limits and color contrast in the local plans. None of that proves an adaptive page converts better. There are no A/B results and no real purchases. The next step is to put a fixed page next to an adaptive one, measure load times and behavior, and see whether adapting helps people make up their minds.