# Design To Pixel

> Rebuild a website (or a single section) from a reference design image so it matches measurably, not approximately — by measuring the reference to derive a numeric spec (viewport, container width, section heights, font sizes, colours, corner radii, gaps), building section by section, then measuring the built DOM and diffing it against the reference until every delta is within a few pixels. Use this skill whenever someone supplies a screenshot, mockup, Figma export, theme demo, or competitor page and wants it "rebuilt", "cloned", "matched", "recreated", "converted to HTML/CSS", or asks for it to look "exactly like", "1:1", "pixel perfect", or "100% the same" — and also when they hand over a design image section by section, or ask you to check whether a build matches a reference. Reach for it even when they just say "make it look like this image", because the measuring loop is what separates a real match from a rough approximation.

- Skill: `abdul977/design-to-pixel` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add abdul977/design-to-pixel`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abdul977/design-to-pixel/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: abdul977 (https://skillmd.com/u/abdul977)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/abdul977/design-to-pixel

---


# Design image → pixel-accurate build

## The core idea

A reference screenshot is **data, not inspiration**. Eyeballing produces an endless
"a bit bigger… now too big" loop with no finish line. Measuring converts the job into a
question that terminates: *is the delta under 5px?*

So the loop is always:

**measure the reference → build to those numbers → measure your own build → diff → fix**

Two habits make this work:

- **Numbers before pixels.** Derive a spec first. Don't start writing CSS from vibes.
- **Verify your own output the same way.** Read your build's DOM with
  `getBoundingClientRect()` and print a `REF | MINE | DELTA` table. Your opinion of whether
  it matches is not evidence; the table is.

`scripts/refmeasure.py` does the reference-side measuring and `scripts/measure_build.js`
does the build-side. Use them rather than rewriting this analysis each time.

---

## Phase 1 — Establish the coordinate system

Nothing else is meaningful until you know what scale the reference is at.

**Use the original image.** If someone sends an AI-upscaled or "enhanced" copy, measure the
original instead. Upscales reframe the content and invent detail — trusting one can put
every vertical position out by tens of pixels while looking sharper and more convincing.

**Find the mockup's real edges.** Promo images often paste a desktop screenshot beside a
narrow mobile column, or add padding. Measure the desktop region only.

**Derive the container and the capture viewport.** Take the nav row and find the leftmost
and rightmost non-background pixel. Equal left/right margins means a centred container, and
`container ÷ page_width` gives the ratio. A ratio near 0.77 with a common container
(1140/1170/1230/1320) tells you the viewport it was captured at — e.g. a 1230 container at
76.9% implies a 1600px viewport.

Then every measurement scales by `target_viewport ÷ mockup_page_width`.

```bash
python scripts/refmeasure.py scale ref.jpg --page-width 380 --target 1600
python scripts/refmeasure.py extents ref.jpg --y0 22 --y1 43   # nav row → container
```

Build at that viewport. Matching a 1600px design while screenshotting at 1440 produces
deltas everywhere that aren't real.

---

## Phase 2 — Extract the spec

Run these against the reference and write the results into a spec table before touching CSS.

| What | How | Command |
|---|---|---|
| Section bands (heights, boundaries) | Scan one column, classify each pixel, print transitions | `bands` |
| Element left/right/width | For a row range, find first/last non-background pixel | `extents` |
| Text line positions & line-height | Count ink pixels per row; groups are lines | `textrows` |
| Colours | **Median** of a flat sample region | `colors` |
| Corner radius / square corners | ASCII pixel map of each corner | `corners` |

```bash
python scripts/refmeasure.py bands    ref.jpg --x 60
python scripts/refmeasure.py textrows ref.jpg --x0 190 --x1 340 --y0 290 --y1 460
python scripts/refmeasure.py colors   ref.jpg --box 200,60,330,75
python scripts/refmeasure.py corners  ref.jpg --box 115,364,181,443
```

### Font size from text width, not cap height

Cap-height estimates are unreliable at low resolution and vary between fonts. Text **width**
is unambiguous. Measure the reference string's width, render the same string in your build
at any size, then scale:

```
correct_size = your_size × ref_width ÷ your_width
```

This self-corrects for the font not being an exact match, because it targets the thing a
viewer actually perceives — how much horizontal space the line occupies.

If a heading needs to look heavier but the width already matches, raise the weight **and**
drop the size a little so the width holds (e.g. 48px/700 → 46px/800). Going heavier alone
overshoots the width.

### Colours: median, never mode or mean

JPEG artifacts skew the mode; antialiasing at edges skews the mean. Take the median of a
flat region away from text and boundaries. If you need to know a text colour, sample the
*darkest* pixels in the glyph area — small text never reaches its true colour because of
antialiasing, so treat the reading as an upper bound on lightness rather than the value.

### Corner radii: print the pixels

A filled corner is square; a staircase is rounded. The pixel map settles arguments instantly
and is worth pasting into your reply when a corner is disputed:

```
TOP-LEFT           BOTTOM-LEFT
.###########       ..##########
.###########       ...#########   ← staircase = rounded
.###########       ....########
   ↑ filled = SQUARE
```

Radius ≈ how far down the edge travels before reaching full width.

---

## Phase 3 — Build one section at a time

Build a section, verify it, then move to the next. Building everything and comparing at the
end buries small errors and makes them expensive to unpick. It also gives the person you're
working with natural checkpoints to redirect you.

Keep the measured numbers in comments next to the values they justify. Six months later
`/* ref: line 1 = 598px wide @1600 */` explains a `61px` font-size that otherwise looks
arbitrary.

---

## Phase 4 — Verify by measuring your build

Screenshot pixel-reading is noisy; the DOM is exact. Load your page, run
`scripts/measure_build.js`, and print the comparison table.

```
ITEM              REF    MINE  DELTA
topbar h         58.9      58   -0.9
hero h            783     784   +1.0
h1 line1 w      597.9   595.8   -2.1
card top        808.4     806   -2.4
```

**Reading the deltas:**

- Within ~5px on real dimensions → done, move on.
- A large delta on a *top* edge with a tiny delta on the *bottom* edge is usually not a bug:
  a CSS box top sits above the glyph top by half-leading plus the ascent gap. Compare
  bottoms and widths.
- x-positions off by a constant ~7–8px across everything → a scrollbar. Screenshot with
  `--hide-scrollbars` or account for it; don't "fix" the CSS.

---

## Phase 5 — Show a side-by-side

Numbers miss things nobody thought to measure. Scale the reference crop to your build's
width, stack them with labels, and look:

```bash
python scripts/compare_sheet.py ref.jpg build.png out.png --ref-crop 0,7,380,260
```

This is how a corner motif, an off-centre row, or a wrong crop gets caught.

---

## Rendering screenshots

Use real headless Chrome. Editor preview panes often don't composite, so
`requestAnimationFrame` never fires and anything scroll- or animation-driven looks broken
when it is fine.

```bash
chrome --headless=new --disable-gpu --hide-scrollbars \
  --window-size=1600,1100 --screenshot=out.png --virtual-time-budget=15000 URL
```

`--virtual-time-budget` matters — without it you screenshot before fonts and images land.

---

## Traps that cost real time

**Headless Chrome enforces a minimum window width (~500px on Windows.)** Ask for 414 and it
lays out at ~500, then crops to 414 — so a perfectly good responsive page looks badly
broken. Verify narrow layouts by measuring the DOM (`scrollWidth` vs `clientWidth`), not by
screenshot.

**Photo content defeats background detection.** A white wall or a bright window inside a
photo reads as page background, so an automatic edge-finder reports a square corner that
isn't there. When a measurement looks strange, zoom in and *look* before acting on it.

**A composite promo image may not be internally consistent.** If the nav is centred but a
row below is offset, that may be a stitching artifact — or a real design choice. Say which
you think it is and why, and let the person decide rather than silently "correcting" it.

**Verify critique before applying it.** Feedback about geometry — yours or someone else's —
is a hypothesis. Check it against the pixels first. Applying a confident but wrong
correction moves you *away* from the reference while feeling like progress. When your
measurement contradicts the instruction, say so, show the evidence, and ask; if the person
reaffirms, do it their way and note the discrepancy.

---

## Content, not just layout

A reference demo ships with placeholder copy and stock photos. Matching the layout is the
job; shipping the placeholder text usually isn't. Swap in real content, and be careful with
anything that reads as a factual claim — invented statistics ("1900 students", "40 years of
service") on a real organisation's site are a liability. Use only figures you can source, or
leave them clearly marked for the owner to fill in, and say which you did.

Also check supplied photography before it goes live: camera watermarks burned into a corner,
duplicates of the same shot under different filenames, and multi-megabyte originals that
need resizing are all common and all worth flagging.

---

## Files

- `scripts/refmeasure.py` — reference-side measuring (`scale`, `bands`, `extents`,
  `textrows`, `colors`, `corners`). Run with `--help` for options.
- `scripts/measure_build.js` — paste into the browser to measure your build and print the
  `REF | MINE | DELTA` table.
- `scripts/compare_sheet.py` — stacked reference-vs-build image.
- `references/worked-example.md` — a full walkthrough with real numbers, useful when you
  want to see how the phases connect on an actual page.

