design-build
Produce decks, reports and web layouts that read as designed rather than assembled. The method is not "generate and refine" — it is decide everything that causes revision before rendering anything.
Why this exists
Three client decks in this system took 30, 50 and 76 rounds. Almost none of those rounds were about content.
One 29-page brand deck was counted turn by turn off the transcript. This is where the rounds actually went, and every category is preventable:
| Share | What the turn was | The gate that stops it |
|---|---|---|
| 20% | An asset arriving one at a time — "i have added slide 3 internal image", ×7, plus 24 URLs across six turns | manifest.py — every frame named and requested once, before the build |
| 16% | The user writing the art direction themselves, with ASCII wireframes and split percentages | Arrive with 2–3 real renders and a recommendation. A question is not a proposal. |
| ~13% | A defect visible in the render — "it feels overlapping", "make all the cards same sizes", "its too conjusted", "that line is still there why?" | lint.py — measured, not eyeballed. Never send a render it has not passed. |
None of those needed taste. They needed a list, a proposal, and a measurement.
So the skill turns each silent decision into a question asked up front, gives the answer a fixed vocabulary — 54 named layout patterns and 12 named directions, so "something like this" becomes "C5" — and puts a tool in front of the three failures above, because a rule that relies on remembering is a rule that gets skipped on a deadline.
The sequence
Never skip steps 1–3 to get to rendering faster. That is the mistake this skill exists to prevent.
0 · Measure and research. Before anything else, and without being asked.
Never design against your own taste when a number is available.
python assets/measure.py <any reference> # -> the numbers
python assets/measure.py <reference> --tokens # -> brand.css
python assets/research.py --subject "..." --colour "..." # -> a picked sheet
measure.py reads ground, ink, accent, corner radius, ink coverage,
dark-page ratio and the full type scale off any PDF, image or folder, and
scales the sizes to the canvas being built at. Run it on every reference
in the project folder before writing a line of CSS.
This step exists because a deck was once built to a catalogue while the
client's own reference sat unopened in the same repo, then rebuilt three
times on guesses — and every guess was a number readable off the file in ten
seconds. The ground was #F0F0F0, not white. The radius was 31px, not 8. The
display-to-body contrast was 8.2×, not 2.7×.
research.py builds its own keyword matrix and sweeps it. Generate the
vocabulary; do not wait to be handed it.
The two sources want opposite words, and sending both the same query is why sweeps come back generic:
- Pinterest = direction. Indexed by how designers label their own work —
"editorial spread", "type specimen", "grid system", "brand guidelines". This
is the creative source, and it is creative because of the vocabulary.
--mode direction(the default). - Freepik = asset. A stock library, indexed by literal subject nouns —
"scientist microscope laboratory". Design words return nothing here.
--mode asset. - Neither = the specific requirement. Their scene, this exact crop, the
left third empty. That is
tools/imagegen.py, which compiles the safe area, the measured aspect, the brand hex and the negative list into the prompt. Dry by default;--gospends credits,--copyemits the prompt alone.
Banned search vocabulary — slide, template, powerpoint, infographic, presentation, canva. Measured: those words return template farms, which is the aesthetic being escaped. Enforced in code, not advisory.
1 · Read the source completely
Transcribe the source document to source_doc.txt before designing. Count the
content blocks. Note every table, every set of numbers, every sequence — those
are already telling you their patterns.
2 · Intake — one question at a time
Work through references/intake.md in order, one question per message, not
nine in one. A wall of questions gets "just go" or three answers out of nine;
the other six come back as revisions. Each answer narrows the next, so later
questions get shorter — and Q3 needs no asking at all if Q2 produced a
reference.
Forced choice, defaults stated, and the moment the user shows they are busy: stop asking, state the defaults being taken, and build.
Where art direction is at stake, arrive with two or three real renders and a recommendation. 16% of turns on the last job were the user writing the art direction themselves — that happens when what they were shown was not strong enough to react to. A question is not a substitute for a proposal.
Two questions in that set are never skipped:
- A supplied reference outranks the catalogue. When the user sends a screenshot, board or competitor document, build to that, measured. Do not blend it with the catalogue or improve on it. This holds even when the reference arrives mid-build — rebuild the earlier pages to match rather than shipping a document in two languages.
- Every screenshot gets the two-option question: place it as-is, or rebuild its information as a native infographic. Ask per screenshot. Never decide it silently.
3 · Map, then gate
Assign a pattern ID from references/patterns.md to every page, using the
selection algorithm at the foot of that file. Set it as data-id on each
<section class="slide">.
Then run the Contact Sheet Gate:
python assets/sheet.py <deck>.html --wire
Every page renders as a wireframe — geometry, hierarchy, real content and the brand's base colour, with the finish stripped — laid out as one numbered sheet with each thumbnail labelled by pattern ID. The tool also prints the pattern map and warns when the same pattern runs twice in a row.
Send the sheet. Get approval or corrections on the sheet, before building anything at full fidelity.
This is the highest-leverage step in the process. It moves iteration from the expensive end to the cheap end, and it makes the conversation "page 7 is E3, it should be D8" instead of "I don't like page 7."
Send the asset manifest in the same message as the sheet:
python assets/manifest.py <deck>.html # -> ASSETS-NEEDED.md
python assets/manifest.py <deck>.html --check # what landed, what has not
python assets/manifest.py <deck>.html --stub # grey on-grid placeholders
Every frame numbered, with the exact filename the build will look for, the pixel size, what it must show, and which third stays clear for type. The client drops one folder; nothing gets renamed or re-sent. This is the gate for the largest single category of wasted turns.
4 · Build
Separate the concerns, always, into these files:
| File | Contains | Rule |
|---|---|---|
content.py |
every string, from the source | Sacred. Nothing authored here. One source for all cuts — never forked. |
build.py |
layout only — which pattern each block gets, and where | |
deck.css |
the design system | |
render.py |
HTML → PNG + PDF | |
preview.py |
one page, for tuning | |
audit.py |
dropped/added phrase check, both directions |
5 · Lint — never send a render this has not passed
python assets/lint.py <deck>.html
Measures what the client would otherwise measure by looking: boxes that intersect, siblings whose sizes disagree by a near-miss, type under the 17px floor, content off the canvas, ink coverage over the congestion threshold, and headlines sitting on the loud part of a photograph. Furniture is auto-detected by its recurrence and exempted, so the rail does not get flagged 29 times.
Every check in it exists because a real client sentence is quoted beside it — "it feels overlapping", "make all the cards same sizes", "its too conjusted", "that line is still there why?" A defect found here costs seconds. The same defect found by the client costs a round.
Fails block the PDF. Warnings get read, not ignored.
6 · Audit and measure
Run the content audit both directions. Then measure the design: ink coverage, accent percentage, type-size distribution, and how many pages share a composition. Numbers catch monotony that the eye rationalises.
References
| File | Read it when |
|---|---|
references/intake.md |
Before every project, without exception |
references/patterns.md |
Assigning a layout to every content block |
references/directions.md |
Choosing the art direction at intake |
references/imagery.md |
Deciding what goes in any frame — photo, screenshot, abstract, or nothing |
references/craft.md |
Writing CSS — the defect list and the rendering traps |
assets/ |
Building. tokens.css + patterns.css are the working system; gallery.py holds the markup for all 54; render.py builds the contact sheet. |
The tools, and the failure each one exists to stop
| Tool | Stops |
|---|---|
assets/measure.py |
Designing against taste when a number is readable off the reference |
assets/research.py |
Generic sweeps — Pinterest for direction, Freepik for assets, never the same words to both |
tools/imagegen.py |
Stock standing in for a specific requirement; artwork that fills the third the headline needs |
assets/manifest.py |
Assets arriving one at a time. The largest single category of wasted turns. |
assets/sheet.py |
Full-fidelity rebuilds of structural mistakes |
assets/lint.py |
Sending a render with defects a measurement would have caught |
audit.py (per deck) |
Content silently dropped or authored |
Every pattern ID in references/patterns.md has a matching class in
assets/patterns.css and working markup in assets/gallery.py. Copy from
there rather than writing a layout from scratch — the geometry is already
measured and the defects are already fixed.
The rules — and which register they belong to
These were calibrated on a dense consulting report. Half of them are wrong for a product deck, and shipping them unchanged is how a sales deck ends up reading like a Word document. Measure the reference first (step 0); when a measured number disagrees with a rule below, the number wins.
| Report / strategy | Product / sales | |
|---|---|---|
| Ground | white, dark ≤20% | light system, dark inverted on the pages where the argument turns — cover, the reveal, dividers, closer. ~25–30% is correct. |
| Headline | 56px, weight 400 | 82–199px, weight 800. Display-to-body contrast 6–8×, not 2.7×. |
| Body | 21px | 24–30px |
| Corner radius | 0–8px, hairlines | 18–31px, soft layered shadows, real depth |
| Enclosure | hairlines before borders before fills | cards with fills and depth; a hairline reads as unfinished here |
| Imagery | covers, dividers, closers only | every page, composed individually — placed and bleeding, never a strip in the same position twice |
| Accent | ≤2% of pixels, ink only | large fields, gradients, one filled "hot" tile per comparison |
What holds in both
- Furniture on 100% of pages, identical position. The cheapest way to prove a system exists.
- Type floor 17px @1920. Timid small type reads as unfinished, not restrained.
- Every content block resolves to a table, chart, timeline, list or stat grid. If the source shows it as a table, it stays a table.
- Never the same composition twice in a row — unless the pages are a declared matched pair.
- A real image cropped to the grid, or nothing. Never a dashed placeholder. Abstract earns its place only when it is the information, or when the brand already owns the visual system.
- Nothing bright in the corner a headline occupies. Content starts at y≈70; artwork placed there is illegible regardless of opacity.
- Compute the stage width against the artwork —
live width − band − 70. Eyeballing it puts text under the image. - Content is sacred. Dropping a word to make a number look better ("Only 7%" → "7%") is a content change. The audit catches it; run it.
Never do these
Learned by having them rejected on real work:
- Serif headline swaps and logo watermarks introduced as "improvement." An improvement adds information or fixes a defect. Anything else is a designer signature on someone else's brand, and it gets rejected.
- Abstract generated shapes as image placeholders.
- A second accent colour the brand system doesn't have.
- Numbered version folders. One folder, rebuilt in place — git holds history.