Reading a website well enough to build a deck from it
"Paste your URL" is one sentence covering two unrelated jobs. The second — writing the slides, painting the pictures — is the one that gets advertised. The first is reading your site, and it is where a generated deck either becomes yours or becomes a template with your name typed into it.
What follows is what our reader does with an address, and what it refuses to guess. Most of these rules exist because an earlier version got something wrong on a real website, and those wrong answers are here too.
0credits
1
The home page carries the palette, the logo, the navigation and the claims. A second page carries more of the same.
3, up to 1.6 MB
A budget across all three as well as a 1 MB cap on each: the per-file cap stops one enormous sheet stalling the read, the budget stops three of them.
20 seconds
Redirects included. Past it we stop and say so, rather than holding a spinner open.
Reading is not generating, so it is not billed
Fetching one page and parsing three stylesheets costs bandwidth and a few seconds. Writing fourteen slides and painting the eight pictures one timed deck asked for costs real money at a provider. Charging one credit for both would spend your budget before you had seen a slide.
The extraction endpoint never starts a generation, and the route says so in its own source: reading a website is not a generation and must not be counted as one. A deck is 25 credits; the read before it is zero.
The result is cached for an hour, keyed on the address and the kind of document asked for. What you typed in the box beside it is not part of the key — a shared link would otherwise hand the second visitor the first visitor's subject line. A refusal is never cached: a site down for a minute would otherwise be down for an hour.
The address, before it is a website
An endpoint that fetches whatever a stranger types is the textbook shape of server-side
request forgery, so the unglamorous half of "paste your URL" is a guard in front of the
fetch, carrying 36 tests of its own. The scheme must be http or
https. The port must be 80 or 443, because a public host is still a route to
that host's own admin panel. A literal address is decoded properly, so
2130706433, 0177.0.0.1, 0x7f.1 and
::ffff:10.0.0.1 are recognised as what they are. And every hop of a redirect
chain gets the same treatment, not just the address you typed — five hops, eight seconds
each, twenty for the chain, two megabytes maximum.
It does not close the DNS rebinding window. The runtime has no DNS API, so a name is checked by asking a public resolver over HTTPS. Between our lookup and the runtime's own, the record can change — a small, real gap that needs a connect-time hook the platform does not offer. And it does not read robots.txt. A half-parsed robots file is worse than an honest gap, so it is stated as one.
A palette that is theirs, not the first five hex codes in the CSS
The naive version takes the most frequent colours on the page. It always works and is almost
always wrong, because every site has an ink and a paper and only some have a colour they
would call theirs. Filling a brand slot with #111111 because it was the most
common value is exactly the templated look the exercise exists to avoid.
Two real failures shaped the rule. A popular utility CSS framework emits its whole default
scale onto :root — twenty-two hues at eleven steps — on every site built with
it, whether or not one element uses one. On a well-known deployment platform's home page the
first candidate we found was a near-white pink, handed back as that company's primary brand
colour. It was the framework's colour, not theirs.
The second failure was the first fix, which listed that framework's hue names. The
framework's own site then returned a near-white "olive", because olive was not on the list.
Chasing names is endless. What every design system shares is the shape: a swatch in a
scale is written name-step, and a colour a company calls theirs rarely carries a
number unless the name already says which role it plays. So the test became the numbered
step, which keeps --brand-600 working while rejecting the inventory. The cost is
stated rather than hidden — a company whose only brand colour is --blue-600 gets
an unset palette. That is the survivable error.
| Slot | Filled from | Refused |
|---|---|---|
| paper | A background declared on :root,
html or body. A rule beats a theme-color, which
only paints the browser's chrome. | Anything declared inside a component, however full-bleed it looks. |
| ink | A text colour on the same ground. | The same. |
| primary | The strongest surviving candidate: a custom
property whose name claims a brand role, then a coloured
theme-color, then a declared style on a link, button or heading, then any
other custom property. | Greys — saturation under 0.15, or luminance above 0.93 or below 0.02. The page's own ink and paper. Any variable whose name says ink or paper. |
| secondary, accent | The next two survivors. | Additionally, any colour within 0.10 of one already taken. |
| Nothing found | The slot stays empty and the deck is built unbranded. | A house blue. Under no circumstances. |
Both refusals earn their place. A palette of three shades of one blue is a single colour
written down three times, and it produces a deck that looks like a gradient rather than a
brand. And on one real site the variable --ink-2 is a dark navy, saturated just
enough to pass the grey test, and it came back as that company's primary ahead of their
actual cyan. Filtering on the resolved hex could not see that; filtering on the declared role
could.
Light or dark, and why a media query does not decide it
The requirement is easy to state — a deck must not arrive white for a company whose site is
black — and the subtle half is the inverse. A site that is white by default and black
under prefers-color-scheme: dark is a white site. Merging that dark block
in, the obvious way to write the parser, produces a black deck for a white company.
A measured wrong answer sits behind the selector rule too. A national government's site — one
of the plainest white pages on the internet — declares its ground on :root, then
declares a #383f43 background on :root followed by a class, for one
dark component. A pattern that checked only how a selector started matched the
second rule, the later declaration won, and the page came back dark. A selector must now be
the root element and nothing after it.
| Source | Weight | Why it ranks there |
|---|---|---|
| Background colour on the page's own ground | 100 | The page stating what it is. Luminance below 0.45 is dark. |
color-scheme in CSS | 80 | The author naming the page's resting scheme in the one place that is not a guess. Only from an unconditional rule, and only a single word: in CSS, dark light is read as no evidence either way. |
dark or light on an html or body attribute | 70 | Deliberate, but usually toggled by script we never run. |
meta name="color-scheme" | 65 | The same statement, one step further from the pixels — and here dark light does count, because the order is the opinion and the first token is read rather than the presence of the word "dark". |
Paper via theme-color, then a class token containing "dark" | 55, 40 | The weakest things that are still evidence. |
| Nothing said | — | Returns nothing. That is not "light" — it means use your own default rather than a scheme we invented. |
Picking a logo, or picking nothing
The order is by how deliberate the statement is, and every rung is the company saying something rather than us inferring it.
01 The organisation block they publish themselves
Structured data is the one place most sites state their own name, logo and line of work in a form they wrote deliberately. It is not scraped inference, so it outranks everything else.
02 An image they labelled as a mark
An <img> whose source, alt text, class or id contains "logo",
"wordmark", "brandmark" or "lockup". Where a srcset exists the widest
candidate is taken: a mark scaled up from a thumbnail looks worse than none.
03 Their site icon, largest declared size first
Offered and labelled as an icon rather than returned as the mark, because an icon is a logo cropped to a square, which is not the same thing.
04 Nothing, and the deck is built unbranded
Deliberately absent from this ladder: "the first image in the header". A header's first image is a hero photograph about as often as it is a mark, and a deck carrying a stranger's stock photo where the logo should be is worse than one carrying neither.
Only PNG and JPEG are accepted: nothing in the runtime rasterises an SVG, and fetching a file we cannot place is a fetch we should not make. The logo also comes back through the same guarded fetch as everything else — a bare request "just for the image", aimed at an address taken out of a stranger's HTML, is the same security hole one hop further away.
Tone, and how a company frames its own pictures
Tone is measured from the page's prose and refuses to answer more often than it answers. Under 120 words there is nothing to measure. Above that it counts words per sentence, first- and second-person density per thousand words, formal markers and contractions, and reports "formal" or "warm" only when one side scores at least two of its three signals and the other scores none. That is correct rather than timid: falling back to a house default is legitimate — a measured tone that is wrong is not, because it reaches the writer as something the client stated.
Framing is read the same way. Up to 24 content images are sampled — logo and icons dropped first, since the furniture of a page is styled nothing like its photographs — and a treatment is reported only when at least two agree and they are at least 40 per cent of the sample. A single rotated logo does not make them a slanted-frame company.
What we will not guess
In an earlier version, a preference nobody had ever been asked about sat in the intake with a default and reached the writer as a client position on eleven of twelve delivered decks, measured in the database. A crawler is a machine for producing that fault at scale. So every answer the reader fills carries a provenance entry naming the element it came from, and anything without one is left out. Three fields where the temptation is strongest:
- Who the deck is for. This reaches the writer inside a block headed "what the client told us — this is everything you know". An audience we invented is not a defaulted preference; it is a fabricated fact about a stranger's company, printed to the model as theirs.
- What they want the reader to do. The button is the ask. Reading it off a blocklist of navigation words failed three times on real sites: a customer's brand name inside an element whose class contained "button"; a top-level "Pricing" menu item returned as the company's ask, when a page in a menu is a destination and only a button is a request; and a hidden press-kit menu returning "Download Brand Assets", which is what that company wants a journalist to do. It is an allowlist now — an ask is a verb aimed at you.
- Numbers. The field for figures that must be exact is never filled from a crawl. A stale headline statistic reprinted as fact in a client's own deck is the worst thing this pipeline could do with a number.
Where a required question is unanswered it comes back marked missing, carrying the form's own wording for the question rather than an answer, and the screen puts it to you once. No answer is a question on a screen. A wrong answer is a deck built around a stranger's logo wall.
What it costs after the reading
Ten credits are granted on signup, a credit is one US dollar during the alpha, and the catalogue is a slide deck at 25 credits, a branded document at 10, a data report at 15 and a single image at 1. Every price here is an indicative estimate, not a quote. The free grant is deliberately smaller than a deck: enough to read your site, see what it filled and where each field came from, and make an image or a document first.
What happens after you press generate is measured separately — 58 delivered decks, mean 340 seconds, fastest 139, slowest 1,432, and 69 of 79 jobs delivered across every kind. The distribution is in the timing note.
What we could not determine
Whether the palette is right. We can tell you which stylesheet, which rule and which property every hex came from. We cannot tell you it is the colour the company's designer would have named. Provenance is checkable; correctness is a judgement, and the screen is editable for that reason.
One page only. A crawl that follows links is a crawl whose cost a stranger chooses. A brand colour that appears only on an interior page is invisible to us, and so is a fourth stylesheet.
Declared styles, not computed ones. There is no layout engine and no viewport, so
specificity is applied crudely — ids beat classes beat tags, later beats earlier at a tie —
and cascade layers, colour mixing and per-element variable resolution are not modelled. A
selector is read by its last compound only, so .card > img.thumb can match
an image outside the card it was written for. An ancestor test needs a tree; the parser is
a stream.
A hit rate. We have not published what share of sites come back with a full palette, a logo and a measured tone versus how many come back mostly unset. It is the number a buyer would most want from this page. When we have it, it goes here whatever it says.
Where every number on this page comes from
| Figure or rule | Source |
|---|---|
| One page · 3 stylesheets · 1.6 MB budget · 20 s total · 24 images sampled | The declared limits in the extraction module. The per-sheet cap was raised to 1 MB from 700 KB after a real site's 762 KB stylesheet — the one holding its palette — was refused for sitting 62 KB over it. |
| Reading a site costs 0 credits · one-hour cache keyed on the address, not on what you typed | The extraction route, which generates nothing and is not metered, and the cached wrapper around the crawl. |
| Schemes, ports, decoded literal addresses, per-hop redirect checks, 5 hops, 8 s each, 20 s, 2 MB | The URL guard module, which carries 36 tests of its own. |
| Grey test at saturation 0.15 and luminance 0.93 / 0.02 · near-duplicate rejection at 0.10 · the scale-entry rule and the two wrong answers behind it | The colour utilities, the palette resolver and the candidate filter, which records both measured cases in its comments. |
| Light-or-dark weights: 100 / 80 / 70 / 65 / 55 / 40 | The scheme decision function. The root-selector rule beside it records the government site reported as dark. |
| Tone floor of 120 words · framing consensus of 2 images and 40% · PNG and JPEG only | The tone reader, the framing function's defaults, and the accepted image types in the guard. |
| A default reached the writer as a client position on 11 of 12 delivered decks | Recorded in the intake module and measured in the portal's database. It is why every crawled answer now carries provenance. |
| 58 decks, mean 340 s, fastest 139 s, slowest 1,432 s · 69 of 79 jobs delivered | The studio_jobs table in the portal's production database, 23 August 2026. |
| 25 / 10 / 15 / 1 credits, $1 per credit, 10 free on signup | The published catalogue on the product page. Indicative estimates during the alpha, not quotes. |
If you are comparing tools, the question that separates them is not how fast the deck appears. It is what the tool did when your site did not answer something — left the field empty and asked you, or filled it with something reasonable and never mentioned it. The second is invisible until the deck is in front of a client.