figma-to-html-pixel-perfect 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +143 -1565
- package/package.json +2 -2
- package/references/build-guide.md +472 -0
- package/references/failure-modes.md +17 -0
- package/references/report-guide.md +250 -0
- package/references/setup.md +185 -0
- package/scripts/__pycache__/figma_lint.cpython-313.pyc +0 -0
- package/scripts/figma_discover.py +25 -2
- package/scripts/figma_lint.py +39 -0
- package/scripts/figma_report.py +126 -3
- package/scripts/selftest.py +115 -0
package/SKILL.md
CHANGED
|
@@ -9,1579 +9,157 @@ description: >
|
|
|
9
9
|
accessibility, animations, and visual consistency.
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
# Figma
|
|
12
|
+
# Figma → HTML/CSS, pixel-perfect
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
This file is the protocol. Detail lives in `references/` — **read the named reference at
|
|
15
|
+
each step; they are part of the protocol, not optional background.**
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
guess or skip it.
|
|
17
|
+
## THE NUMBER RULE — the one non-negotiable contract
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**The node JSON is the complete design. Every px value you write into CSS must be a
|
|
20
|
+
number read from the node JSON, or arithmetic on such numbers, written down at the
|
|
21
|
+
moment you write the rule.** There is no legitimate third source of a value.
|
|
20
22
|
|
|
21
|
-
The
|
|
22
|
-
seat-limited like the MCP server, but it IS quota-limited per plan: **every endpoint —
|
|
23
|
-
including `/v1/files` (node JSON) — can 429 with a Retry-After of hours to days** (observed
|
|
24
|
-
live: /files 429 Retry-After 22,734s while the fill-URL endpoint still answered 200).
|
|
25
|
-
Therefore work **cache-first**: pull each node's JSON ONCE to `figma/nodes/`, then every
|
|
26
|
-
script reads the disk cache — never the API. `figma_pull.py` skips nodes already on disk
|
|
27
|
-
(`--force` to refresh). Scarcity order: `/v1/images` (renders) most scarce → `/v1/files`
|
|
28
|
-
also quota'd → `/v1/files/:key/images` (fill URLs) most tolerant → the S3 asset bytes
|
|
29
|
-
themselves are not metered at all.
|
|
23
|
+
The only permitted workflow for creating OR fixing any spacing/size/position:
|
|
30
24
|
|
|
31
|
-
1.
|
|
32
|
-
2.
|
|
33
|
-
3.
|
|
25
|
+
1. Dump the numbers: `python3 scripts/figma_spec.py figma/nodes/<section>.json`
|
|
26
|
+
2. Compute the CSS value from those numbers (`margin-top = child.y − (prev.y + prev.h)`).
|
|
27
|
+
3. Write the rule with the derivation in a comment: `margin-top: 24px; /* 404−(146+248−14lead) */`
|
|
28
|
+
4. Verify with the report once.
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
echo 'YOUR_TOKEN' > ~/.figma_token && chmod 600 ~/.figma_token
|
|
37
|
-
```
|
|
30
|
+
Forbidden, no exceptions, regardless of time pressure:
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
- **Nudge-and-remeasure.** The report prints `[design y…]` beside every delta precisely
|
|
33
|
+
so the correct edit is a subtraction, not an experiment.
|
|
34
|
+
- **Reading only the delta.** `y +8` means: find WHICH gap in the JSON chain is wrong and
|
|
35
|
+
set THAT gap to the design's number — not "subtract 8 somewhere".
|
|
36
|
+
- **Skipping step 1 because you are in a hurry.** Skipping it under time pressure is what
|
|
37
|
+
produced the slowest sessions on record. The fast path IS the extraction path.
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
Tooling enforces this: `figma_report.py` auto-runs the duplicate-selector check (FM123)
|
|
40
|
+
every regen and prints design targets beside deltas; `figma_lint.py` fails on any CSS
|
|
41
|
+
value that exists nowhere in the design. If a guard fires, fixing it is the next action.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
disabled export/copy/share. **No token, seat, plan, duplicate-to-drafts or manual export
|
|
45
|
-
will bypass it.** Only the owner (or someone with edit access) can re-enable
|
|
46
|
-
*"Allow viewers to copy, share and export"* in the Share dialog. Say so and stop.
|
|
43
|
+
## Modes
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Figma renders with the real font; you almost never have it. **Ask for the font file up
|
|
51
|
-
front, as a required input, not a nice-to-have.** It cannot be recovered any other way:
|
|
52
|
-
|
|
53
|
-
- the REST API does not serve font binaries;
|
|
54
|
-
- an SVG export with "Outline text" contains no font data at all;
|
|
55
|
-
- an SVG export *without* outlining only names the family — it does not embed it.
|
|
56
|
-
|
|
57
|
-
So: request `design/fonts/…` at the start (§0.4). Declare `@font-face` pointing there and
|
|
58
|
-
put the real family first in the stack, so the moment the file lands it takes over with no
|
|
59
|
-
code change. Until then, follow §6.5.4 (measure a substitute), disclose it, and never
|
|
60
|
-
claim type fidelity.
|
|
61
|
-
|
|
62
|
-
**You already know exactly which files to ask for.** Every TEXT node exposes
|
|
63
|
-
`style.fontPostScriptName` (§5.3) — that string *is* the file name. Never guess, never
|
|
64
|
-
copy a font list from a previous project: derive it from *this* file, every time.
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
python3 scripts/figma_fonts.py figma/nodes/*.json
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
The script skips `visible:false` subtrees **and** nodes whose cumulative ancestor
|
|
71
|
-
`opacity` is 0, reads per-character overrides, and splits the result into free (load from
|
|
72
|
-
a CDN yourself) and licensed (the user must supply).
|
|
73
|
-
|
|
74
|
-
**Report the result to the user in your first reply**, before writing any code. Fill this
|
|
75
|
-
in from the script's output — do not ship the placeholders:
|
|
76
|
-
|
|
77
|
-
> **Fonts this design needs**
|
|
78
|
-
>
|
|
79
|
-
> **Licensed — please drop these into `design/fonts/`:**
|
|
80
|
-
> `<PostScriptName>.woff2` (or `.otf`) — used for `<where>`
|
|
81
|
-
> …one line per licensed face…
|
|
82
|
-
>
|
|
83
|
-
> **Free — I load these myself, you don't need to send them:** `<Family> <weights>`
|
|
84
|
-
>
|
|
85
|
-
> They cannot be extracted from the Figma API or from an SVG export. Until the licensed
|
|
86
|
-
> files exist I substitute the closest **measured** match (§6.5.4) and the type will not
|
|
87
|
-
> be exact. Once you drop them in, they are picked up automatically — no code change.
|
|
88
|
-
|
|
89
|
-
Then declare `@font-face` for each licensed face pointing at `design/fonts/`, with the
|
|
90
|
-
real family first in the stack, so the page upgrades itself the moment the files land.
|
|
91
|
-
|
|
92
|
-
### 0.4 Project asset conventions (look here FIRST)
|
|
93
|
-
|
|
94
|
-
Everything the user hands over lives in a `design/` folder at the project root. **Check it
|
|
95
|
-
before asking for anything and before spending render quota.**
|
|
96
|
-
|
|
97
|
-
```text
|
|
98
|
-
design/
|
|
99
|
-
├── exports/
|
|
100
|
-
│ ├── page.png # full page frame, PNG @2x ← the visual reference
|
|
101
|
-
│ ├── sections/ # optional: one PNG per section, named <section>.png
|
|
102
|
-
│ └── icons/ # SVG exports: icon-<name>.svg, logo.svg
|
|
103
|
-
└── fonts/ # licensed font files: *.woff2 / *.otf / *.ttf
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Rules:
|
|
107
|
-
|
|
108
|
-
- On start, `ls design/` and use whatever is there. Never re-ask for a file that exists.
|
|
109
|
-
- `design/exports/page.png` replaces the render endpoint entirely (§6.5.2). Slice it into
|
|
110
|
-
sections yourself using the `y`/`height` offsets from the node JSON; write the slices to
|
|
111
|
-
`figma/renders/ref_<section>.png`.
|
|
112
|
-
- `design/fonts/*` means the real font is available — wire it with `@font-face` and **do
|
|
113
|
-
not** substitute (§6.5.4 measuring is only for when this folder is empty).
|
|
114
|
-
- `design/exports/icons/*.svg` means real vector icons are available — use them and delete
|
|
115
|
-
any hand-drawn placeholders.
|
|
116
|
-
|
|
117
|
-
Tell the user exactly this, once:
|
|
118
|
-
|
|
119
|
-
> Create `design/exports/` in the project and drop the page frame there as **`page.png`**
|
|
120
|
-
> (Export → PNG, 2x). If you have the licensed font, put the file in `design/fonts/`.
|
|
121
|
-
> For vector icons, select them → Export → SVG into `design/exports/icons/`.
|
|
122
|
-
> I'll pick everything up from those folders — you don't need to send me anything.
|
|
123
|
-
|
|
124
|
-
### 0.5 Real assets only
|
|
125
|
-
|
|
126
|
-
**What counts as an icon** (`scripts/figma_icons.py` implements all of this; the fidelity
|
|
127
|
-
report applies the same definition, so the two never disagree):
|
|
128
|
-
|
|
129
|
-
| Rule | Why |
|
|
130
|
-
|---|---|
|
|
131
|
-
| It contains a `VECTOR` or `BOOLEAN_OPERATION` | a subtree of bare `ELLIPSE`/`RECTANGLE` is a shape — carousel dots, a ring, a divider — that CSS draws |
|
|
132
|
-
| Nothing in it has an `IMAGE` fill | a shape with a photo fill is a photo, and exporting it sweeps up every path behind it |
|
|
133
|
-
| No later sibling with an opaque fill covers it | component placeholder artwork is routinely buried under a photo; it never renders |
|
|
134
|
-
| Its children are not several disjoint, icon-sized containers | that is a frame of icons — a pager's two arrows, a five-star row — and each is exported separately |
|
|
135
|
-
| Its `viewBox` is the node's box, and `width`/`height` are written into the file | crop to the ink and a 12px glyph fills a 40px button; omit the size and `<img>` falls back to 300×150 |
|
|
136
|
-
|
|
137
|
-
Read **every** shape element out of the page SVG — `path`, `rect`, `circle`, `ellipse`,
|
|
138
|
-
`line`, `polygon`, `polyline`. An icon whose circle is a `<circle>` comes out blank if you
|
|
139
|
-
only look at `<path>`.
|
|
140
|
-
|
|
141
|
-
Placeholders are a reporting state, never a deliverable. Specifically:
|
|
142
|
-
|
|
143
|
-
- **Icons:** never ship hand-drawn approximations if a vector source exists. Extract them
|
|
144
|
-
from the SVG export (Figma sets `id` to the layer name, and coordinates are in page
|
|
145
|
-
space, so wrapping the matching paths in `<svg viewBox="x y w h">` reproduces them
|
|
146
|
-
exactly). Three traps:
|
|
147
|
-
1. Strip the base64 `data:image` payloads first — a full-page export is mostly embedded
|
|
148
|
-
photos and can be hundreds of megabytes; stripped, it is a couple of megabytes and
|
|
149
|
-
only then is parseable.
|
|
150
|
-
2. With `xml.etree`, call `register_namespace("", SVG_NS)` and **do not also pass an
|
|
151
|
-
`xmlns` attribute** — you get a duplicate attribute and every icon fails to render.
|
|
152
|
-
3. Locate icons by the node's bounding box from the JSON, not by layer name; names
|
|
153
|
-
repeat (`icon`, `icon_2`, `Vector`). Then **open the icons and look at them** before
|
|
154
|
-
wiring them in — a bad crop yields a plausible-looking blob.
|
|
155
|
-
4. **Never estimate a path's bounding box by parsing the numbers out of its `d`.**
|
|
156
|
-
Curves and relative commands make that answer wrong, and the failure is silent — a
|
|
157
|
-
logo lockup crops down to just its ornament. Flatten the curves (`scripts/figma_icons.py`
|
|
158
|
-
does this, and matches the browser's `getBBox()` to 0.00px), or measure with `getBBox()`.
|
|
159
|
-
5. **A pure-vector subtree can still be a group of icons.** If one icon rect strictly
|
|
160
|
-
contains another, the outer one is a container — exporting it stacks two icons on top
|
|
161
|
-
of each other. Drop it and keep the inner rects.
|
|
162
|
-
|
|
163
|
-
**You never need the API to get the page's icons.** The page SVG export holds every vector
|
|
164
|
-
on that page in page coordinates; the node JSON says where each icon sits. Intersect them:
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
python3 scripts/figma_icons.py --svg design/exports/page.svg --nodes figma/nodes \
|
|
168
|
-
--out design/exports/icons
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
"The icon library is behind a rate limit" is therefore never a reason to draw an icon by
|
|
172
|
-
hand. Then **open the icons and look at them** before wiring any of them in.
|
|
173
|
-
- **Photos:** always the real `imageRef` bytes, mapped to the right node (§6.5.3).
|
|
174
|
-
- **Logos:** never invent a brand. If the reference shows a wordmark, read it; if it is
|
|
175
|
-
unreadable, use a neutral placeholder and say so.
|
|
176
|
-
- **Fonts:** use the licensed file when supplied; otherwise measure a substitute and
|
|
177
|
-
record the delta. Declare `@font-face` pointing at `design/fonts/` up front and put the
|
|
178
|
-
real family first in the stack — the moment the user drops the file it takes over with
|
|
179
|
-
no code change. Until then the browser logs a 404 per source and falls back; say so
|
|
180
|
-
rather than letting it look like a broken build.
|
|
181
|
-
- **Interactions:** static markup for a carousel/accordion/filter is Mode A. Wiring the
|
|
182
|
-
behaviour is Mode C and needs an explicit request (§3.0).
|
|
183
|
-
|
|
184
|
-
Every remaining placeholder must appear in the difference log with the reason.
|
|
185
|
-
|
|
186
|
-
### 0.6 What to ask the user for, up front
|
|
187
|
-
|
|
188
|
-
- the Figma **frame URL** (must contain `node-id`)
|
|
189
|
-
- confirmation the token is stored
|
|
190
|
-
- the target framework (plain HTML/CSS unless told otherwise)
|
|
191
|
-
- the **licensed font files** — name them exactly, from `scripts/figma_fonts.py` (§0.3);
|
|
192
|
-
do not ask for the free ones
|
|
193
|
-
- a **PNG export** of the page frame if renders turn out to be quota-blocked (§6.5.2)
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 1. Purpose
|
|
198
|
-
|
|
199
|
-
You are a senior frontend engineer, UI engineer, UX reviewer, and design-system specialist.
|
|
200
|
-
|
|
201
|
-
Your responsibilities are to:
|
|
202
|
-
|
|
203
|
-
1. Recreate a supplied Figma design in HTML and CSS as accurately as technically possible.
|
|
204
|
-
2. Preserve the design's visual hierarchy, spacing, typography, color, imagery, component structure, and responsive behavior.
|
|
205
|
-
3. Inspect the design for UX/UI issues and identify opportunities for improvement.
|
|
206
|
-
4. Clearly separate:
|
|
207
|
-
- required implementation based on the source design;
|
|
208
|
-
- inferred behavior where the design is incomplete;
|
|
209
|
-
- optional enhancements that change or extend the design.
|
|
210
|
-
5. Ask the user before applying optional enhancements such as animation, layout changes, new components, modified content hierarchy, or interaction changes.
|
|
211
|
-
6. Produce clean, maintainable, semantic, responsive, and accessible code.
|
|
212
|
-
|
|
213
|
-
Do not claim that an implementation is “100% identical” unless it has been visually compared against the source and no measurable differences remain. Prefer the phrase “pixel-accurate within the available source information.”
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 2. Supported Inputs
|
|
218
|
-
|
|
219
|
-
Accept one or more of the following:
|
|
220
|
-
|
|
221
|
-
- Figma file or Figma Dev Mode link
|
|
222
|
-
- Figma frame link
|
|
223
|
-
- Exported screenshots
|
|
224
|
-
- PNG, JPG, SVG, or PDF design files
|
|
225
|
-
- Design tokens
|
|
226
|
-
- Existing HTML/CSS project
|
|
227
|
-
- Brand guidelines
|
|
228
|
-
- Font files or font names
|
|
229
|
-
- Icons and image assets
|
|
230
|
-
- Desktop, tablet, and mobile references
|
|
231
|
-
- Written UX/UI requirements
|
|
232
|
-
|
|
233
|
-
When a Figma link cannot be accessed, request exported frames or screenshots and any available design specifications.
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## 3. Mandatory Operating Modes
|
|
238
|
-
|
|
239
|
-
Determine the correct mode from the user's request.
|
|
240
|
-
|
|
241
|
-
### 3.0 Which mode should I use? (read this first)
|
|
242
|
-
|
|
243
|
-
| | **Mode A — Exact** | **Mode B — Exact + Suggestions** | **Mode C — Approved Enhancement** |
|
|
45
|
+
| | A — Exact (DEFAULT) | B — Exact + advice | C — Approved changes |
|
|
244
46
|
|---|---|---|---|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
**
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
-
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
-
|
|
361
|
-
-
|
|
362
|
-
|
|
363
|
-
-
|
|
364
|
-
|
|
365
|
-
Do not use arbitrary positioning when a stable Flexbox or CSS Grid solution is available.
|
|
366
|
-
|
|
367
|
-
### 5.3 Typography
|
|
368
|
-
|
|
369
|
-
**How you know which font to use:** every TEXT node in the node JSON carries it.
|
|
370
|
-
|
|
371
|
-
| Field | Gives you |
|
|
372
|
-
|---|---|
|
|
373
|
-
| `style.fontFamily` | the family, e.g. `"<Family>"` |
|
|
374
|
-
| `style.fontPostScriptName` | e.g. `"<Family>-Bold"` ← **the exact file name to ask for** |
|
|
375
|
-
| `style.fontWeight` / `style.italic` | 700 / false |
|
|
376
|
-
| `style.fontSize`, `lineHeightPx`, `letterSpacing`, `textCase` | the rest of the spec |
|
|
377
|
-
| `styleOverrideTable` + `characterStyleOverrides` | **per-character overrides** |
|
|
378
|
-
|
|
379
|
-
Enumerate the whole file before choosing anything:
|
|
380
|
-
|
|
381
|
-
```bash
|
|
382
|
-
# every distinct face the design actually uses, on visible nodes only
|
|
383
|
-
python3 scripts/figma_spec.py figma/nodes/<section>.json | grep -o '\[[^ ]* [0-9]*' | sort -u
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
**Do not read only `style`.** A single text node can mix families. In one real file a hero
|
|
387
|
-
heading's base style was a bold display serif at 78px, while two italic words inside it
|
|
388
|
-
were a *different family* at **82px** — recorded only in
|
|
389
|
-
`styleOverrideTable`/`characterStyleOverrides`. Rendering them as an `<em>` of the base
|
|
390
|
-
family is wrong twice over: wrong face and wrong size.
|
|
391
|
-
|
|
392
|
-
Derive the file names you need directly from `fontPostScriptName` and ask for exactly
|
|
393
|
-
those (§0.3).
|
|
394
|
-
|
|
395
|
-
Capture:
|
|
396
|
-
|
|
397
|
-
- font family;
|
|
398
|
-
- font PostScript name;
|
|
399
|
-
- per-character style overrides;
|
|
400
|
-
- font source;
|
|
401
|
-
- font weight;
|
|
402
|
-
- font size;
|
|
403
|
-
- line height;
|
|
404
|
-
- letter spacing;
|
|
405
|
-
- text transform;
|
|
406
|
-
- text alignment;
|
|
407
|
-
- text color;
|
|
408
|
-
- heading hierarchy;
|
|
409
|
-
- paragraph width;
|
|
410
|
-
- responsive type changes.
|
|
411
|
-
|
|
412
|
-
If the exact font is unavailable, state this clearly and use the closest approved fallback.
|
|
413
|
-
|
|
414
|
-
### 5.4 Visual Tokens
|
|
415
|
-
|
|
416
|
-
Extract or infer:
|
|
417
|
-
|
|
418
|
-
- colors;
|
|
419
|
-
- background colors;
|
|
420
|
-
- borders;
|
|
421
|
-
- border radii;
|
|
422
|
-
- shadows;
|
|
423
|
-
- opacity;
|
|
424
|
-
- blur;
|
|
425
|
-
- gradients;
|
|
426
|
-
- spacing scale;
|
|
427
|
-
- breakpoints;
|
|
428
|
-
- motion duration and easing;
|
|
429
|
-
- icon sizes.
|
|
430
|
-
|
|
431
|
-
Define reusable CSS custom properties, named after the design's own token names where the
|
|
432
|
-
file has them. The values below are only a shape to copy — never these literal colours:
|
|
433
|
-
|
|
434
|
-
```css
|
|
435
|
-
:root {
|
|
436
|
-
--color-background: #ffffff;
|
|
437
|
-
--color-surface: #f7f7f8;
|
|
438
|
-
--color-text: #171717;
|
|
439
|
-
--color-muted: #666666;
|
|
440
|
-
--color-accent: #5b5cf0;
|
|
441
|
-
|
|
442
|
-
--radius-sm: 8px;
|
|
443
|
-
--radius-md: 16px;
|
|
444
|
-
--radius-lg: 28px;
|
|
445
|
-
|
|
446
|
-
--space-1: 4px;
|
|
447
|
-
--space-2: 8px;
|
|
448
|
-
--space-3: 12px;
|
|
449
|
-
--space-4: 16px;
|
|
450
|
-
--space-6: 24px;
|
|
451
|
-
--space-8: 32px;
|
|
452
|
-
--space-12: 48px;
|
|
453
|
-
|
|
454
|
-
--container-width: 1200px;
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
### 5.5 Assets
|
|
459
|
-
|
|
460
|
-
Check:
|
|
461
|
-
|
|
462
|
-
- image dimensions;
|
|
463
|
-
- aspect ratios;
|
|
464
|
-
- object-fit behavior;
|
|
465
|
-
- SVG versus raster use;
|
|
466
|
-
- icon consistency;
|
|
467
|
-
- background images;
|
|
468
|
-
- responsive image needs;
|
|
469
|
-
- image compression;
|
|
470
|
-
- missing assets.
|
|
471
|
-
|
|
472
|
-
Do not invent or replace important visual assets without informing the user.
|
|
473
|
-
|
|
474
|
-
### 5.6 Motion (do this before you decide there is none)
|
|
475
|
-
|
|
476
|
-
Run this **before** writing any CSS. It takes one command:
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
grep -l '"interactions"' figma/nodes/*.json # which sections carry prototype motion
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
If anything comes back, the design specifies motion and reproducing it is **Mode A, not
|
|
483
|
-
optional** (§9). Record, per interaction: `trigger.type`, `trigger.timeout`,
|
|
484
|
-
`transition.type`, `transition.duration` (seconds → ms) and `transition.easing.type`.
|
|
485
|
-
Also record real `effects[]` (`DROP_SHADOW`, `BACKGROUND_BLUR`) — those are design, too.
|
|
486
|
-
|
|
487
|
-
Never write "the design has no animation" without having run this.
|
|
488
|
-
|
|
489
|
-
### 5.7 Interactive States
|
|
490
|
-
|
|
491
|
-
Identify:
|
|
492
|
-
|
|
493
|
-
- hover;
|
|
494
|
-
- focus;
|
|
495
|
-
- active;
|
|
496
|
-
- selected;
|
|
497
|
-
- disabled;
|
|
498
|
-
- loading;
|
|
499
|
-
- success;
|
|
500
|
-
- error;
|
|
501
|
-
- empty;
|
|
502
|
-
- expanded and collapsed states.
|
|
503
|
-
|
|
504
|
-
If states are missing from the design, propose a consistent state system before implementing visually significant behavior.
|
|
505
|
-
|
|
506
|
-
---
|
|
507
|
-
|
|
508
|
-
## 6. Clarification Rules
|
|
509
|
-
|
|
510
|
-
Ask a concise clarification question only when the missing information prevents a reliable implementation, such as:
|
|
511
|
-
|
|
512
|
-
- no design source is available;
|
|
513
|
-
- required assets are missing;
|
|
514
|
-
- the target framework is unknown and affects the output;
|
|
515
|
-
- the design contains contradictory desktop and mobile behavior;
|
|
516
|
-
- an interaction cannot be inferred safely.
|
|
517
|
-
|
|
518
|
-
Do not repeatedly ask questions whose answers are already available.
|
|
519
|
-
|
|
520
|
-
When reasonable implementation can continue safely, proceed and label the assumption.
|
|
521
|
-
|
|
522
|
-
---
|
|
523
|
-
|
|
524
|
-
## 6.5 Figma Extraction & Reference Protocol (MANDATORY when a Figma link is provided)
|
|
525
|
-
|
|
526
|
-
This protocol exists because reconstructing a section from geometry alone **will**
|
|
527
|
-
produce a section that is the right size and completely the wrong design. Every rule
|
|
528
|
-
below was learned from a real failure. Follow them in order.
|
|
529
|
-
|
|
530
|
-
### 6.5.0 The Prime Rule
|
|
531
|
-
|
|
532
|
-
> **Never write code for a section you have not looked at.**
|
|
533
|
-
|
|
534
|
-
A reference image of a section is a *precondition* for implementing it, not a
|
|
535
|
-
nice-to-have. If you cannot obtain one, the section is **BLOCKED** — stop, report it,
|
|
536
|
-
and ask the user for a screenshot. Do not "build it from the JSON and hope".
|
|
537
|
-
|
|
538
|
-
Corollary: **numeric agreement is not visual agreement.** A build can match every
|
|
539
|
-
section's frame height to the pixel and still be the wrong design.
|
|
540
|
-
|
|
541
|
-
### 6.5.0.5 Discovery — find what you are about to ignore (MANDATORY, first)
|
|
542
|
-
|
|
543
|
-
Before the node dump, before the renders, before a line of CSS:
|
|
544
|
-
|
|
545
|
-
```bash
|
|
546
|
-
python3 scripts/figma_discover.py <fileKey> [--nodes figma/nodes] --json figma/discovery.json
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
**Show its output to the user.** It answers four questions that silently decide whether the
|
|
550
|
-
job is honest work or fabrication:
|
|
551
|
-
|
|
552
|
-
| Question | If you skip it |
|
|
553
|
-
|---|---|
|
|
554
|
-
| How many design widths does the file contain? | You *invent* a responsive layout the designer already drew |
|
|
555
|
-
| Is there an icon page / icon components? | You *hand-draw* icons that ship in the same file |
|
|
556
|
-
| Do `interactions[]` point at hover variants? | You *invent* hover states that are specified |
|
|
557
|
-
| How many page-sized frames exist? | You build one screen when the file holds a site |
|
|
558
|
-
|
|
559
|
-
Rules:
|
|
560
|
-
|
|
561
|
-
- **A second design width is a second design.** Run the entire per-section loop against it:
|
|
562
|
-
its own references, its own spec, its own audit. Never derive it from the desktop with
|
|
563
|
-
media queries you made up. If the file has a mobile frame and you write a guessed mobile
|
|
564
|
-
layout, that is a fabrication, not a fallback.
|
|
565
|
-
- The width list is a **heuristic**: cards and scratch frames share widths with screens.
|
|
566
|
-
Present the candidates and ask the user which are real breakpoints. Do not decide alone.
|
|
567
|
-
- **An icon that exists in the file may never be redrawn.** Export it (§0.5).
|
|
568
|
-
- Hover destinations live outside the section subtree; fetch those node ids before you
|
|
569
|
-
write a single `:hover` rule (§9.0).
|
|
570
|
-
- If the file holds several page-sized frames, confirm the scope with the user before
|
|
571
|
-
building one and calling it done.
|
|
572
|
-
|
|
573
|
-
### 6.5.1 Access preflight (1 call, before any planning)
|
|
574
|
-
|
|
575
|
-
Probe access before promising anything:
|
|
576
|
-
|
|
577
|
-
- Fetch one cheap endpoint (e.g. `GET /v1/files/:key?depth=1`).
|
|
578
|
-
- Interpret failures precisely:
|
|
579
|
-
- `403 "File not exportable"` → the file owner disabled export/copy/share. **No API,
|
|
580
|
-
no manual export, no duplicate-to-drafts will work.** Only the owner (or someone
|
|
581
|
-
with edit access) can lift it. Say so and stop.
|
|
582
|
-
- `429` → read the **`Retry-After` header** and report it in human units. If it is
|
|
583
|
-
hours or days, do not plan around "it'll reset soon".
|
|
584
|
-
- MCP seat limits are **separate** from REST API quotas, and file-edit permission is
|
|
585
|
-
separate from both. Do not assume fixing one fixes another.
|
|
586
|
-
|
|
587
|
-
### 6.5.2 Reference capture — spend the scarce budget FIRST
|
|
588
|
-
|
|
589
|
-
Rendering is the rate-limited resource. Asset bytes are not. Spend accordingly.
|
|
590
|
-
|
|
591
|
-
**Do, in this order:**
|
|
592
|
-
|
|
593
|
-
1. Node JSON once — `GET /v1/files/:key/nodes?ids=…` (or MCP `get_metadata`) →
|
|
594
|
-
section inventory (ids, order, sizes) **and** every value you will need later.
|
|
595
|
-
Use `scripts/figma_pull.py`.
|
|
596
|
-
2. `get_variable_defs` once (or read `fills` from the JSON) → colour/spacing tokens.
|
|
597
|
-
3. **One render per section** (`/v1/images?ids=…` or `get_screenshot`). This is the
|
|
598
|
-
budget that matters. Render *every* section before you build *any* of them.
|
|
599
|
-
4. **Open and look at every render.** A render you did not view is worth nothing.
|
|
600
|
-
5. Only now download assets — and only the `imageRef`s actually used by the sections
|
|
601
|
-
you are building.
|
|
602
|
-
|
|
603
|
-
**Never:**
|
|
604
|
-
|
|
605
|
-
- Bulk-download every image fill in the file "to be safe". It burns the shared quota
|
|
606
|
-
that the renders need.
|
|
607
|
-
- Proceed past a failed render. If N sections failed to render, N sections are BLOCKED.
|
|
608
|
-
|
|
609
|
-
**Fail loudly:** run extraction in the foreground, or flush/stream output. A background
|
|
610
|
-
job whose stdout is buffered will hide "N of M renders failed" until it is too late.
|
|
611
|
-
|
|
612
|
-
#### Which endpoint is rate-limited (know this before you panic)
|
|
613
|
-
|
|
614
|
-
Only the **render** endpoint is scarce. Do not assume a `429` blocks everything:
|
|
615
|
-
|
|
616
|
-
| Source | Gives you | Rate-limited? |
|
|
617
|
-
|---|---|---|
|
|
618
|
-
| REST `GET /v1/images/:key?ids=…` | **rendered section PNGs** | **Yes — exhausts first, by far** |
|
|
619
|
-
| REST `GET /v1/files/:key/nodes?ids=…` | node JSON: exact px, auto-layout, tokens, `characters`, `textCase`, `imageRef` | Yes, but a much larger budget |
|
|
620
|
-
| REST `GET /v1/files/:key/images` | the real photos (image fills) | Yes, but a much larger budget |
|
|
621
|
-
| MCP `get_screenshot` / `get_design_context` | same renders, different quota | Yes (per seat) |
|
|
622
|
-
|
|
623
|
-
**Every REST endpoint shares a budget.** The render endpoint runs out first, which makes it
|
|
624
|
-
easy to believe the others are free — they are not; `/files` and `/nodes` will 429 after
|
|
625
|
-
enough calls. **Cache every response to disk on first fetch and read from that cache
|
|
626
|
-
afterwards.** A run that dies because a metadata call was refused is a run that wasted its
|
|
627
|
-
render budget for nothing.
|
|
628
|
-
|
|
629
|
-
So when renders are blocked, you have **not** lost values or assets — only the visual
|
|
630
|
-
ground truth. Get that from the user instead (below).
|
|
631
|
-
|
|
632
|
-
#### Fallback: ask the user to export (when renders are quota-blocked)
|
|
633
|
-
|
|
634
|
-
This is a first-class path, not a last resort. It has no quota.
|
|
635
|
-
|
|
636
|
-
1. **PNG = the visual reference.** Ask for the top-level page frame exported as
|
|
637
|
-
**PNG @2x** (or @1x if the frame is very tall). Slice it locally into sections using
|
|
638
|
-
the `y`/`height` offsets you already have from the node JSON.
|
|
639
|
-
2. **SVG = icons and logo only.** Export those as SVG to stop hand-drawing them.
|
|
640
|
-
3. **JSON = every value.** Keep using the API for geometry, tokens, text and image refs —
|
|
641
|
-
never ask the user to hand you numbers you can fetch.
|
|
642
|
-
|
|
643
|
-
> **If SVG is used as a visual reference, "Outline text" MUST be enabled on export.**
|
|
644
|
-
> Otherwise the SVG carries `font-family="<premium font>"`, your machine substitutes a
|
|
645
|
-
> different face, and the "reference" silently shows wrong glyphs, wrong line breaks and
|
|
646
|
-
> wrong widths — corrupting exactly what you are trying to verify. PNG has no such
|
|
647
|
-
> failure mode; prefer it.
|
|
648
|
-
|
|
649
|
-
Ask for exports with this template:
|
|
650
|
-
|
|
651
|
-
> Renders are quota-blocked (`Retry-After: …`). To continue I need the visual reference:
|
|
652
|
-
> select the page frame → Export → **PNG, 2x**. Optionally select the logo/icons →
|
|
653
|
-
> Export → **SVG**. I already have all values and photos from the API; I only need the
|
|
654
|
-
> rendered image.
|
|
655
|
-
|
|
656
|
-
Cache every response to disk (`figma/nodes/*.json`, `figma/renders/*.png`, `assets/`) so
|
|
657
|
-
a quota reset never forces a refetch.
|
|
658
|
-
|
|
659
|
-
### 6.5.3 Node-JSON reading contract
|
|
660
|
-
|
|
661
|
-
The JSON is trustworthy only if you read these fields. Each bullet is a bug that
|
|
662
|
-
shipped:
|
|
663
|
-
|
|
664
|
-
- **Text content is `characters`, never `name`.** The two routinely disagree: a node whose
|
|
665
|
-
layer `name` was copied from an old label can carry entirely different `characters`.
|
|
666
|
-
Shipping the `name` puts wrong words on the page.
|
|
667
|
-
- **Honour `style.textCase`.** `UPPER` is real. In one file *every* heading, eyebrow and
|
|
668
|
-
button was `UPPER`; rendering them title-case broke the whole page's identity.
|
|
669
|
-
- **Skip `visible: false`** nodes, and skip their subtrees. Hidden variants and stale
|
|
670
|
-
copy live there.
|
|
671
|
-
- **`visible: false` is not the only way a node is hidden.** Multiply `opacity` down the
|
|
672
|
-
ancestor chain and drop anything whose effective opacity is `0`; also respect
|
|
673
|
-
`clipsContent` on ancestors. A node can be `visible: true` and still render nothing.
|
|
674
|
-
This matters beyond layout: an opacity-0 node made one build ask the user for a
|
|
675
|
-
licensed font that the design never actually shows.
|
|
676
|
-
- **`style.textAlignHorizontal` describes alignment *inside the text box*,** not on the
|
|
677
|
-
page. A `LEFT`-aligned box inside a centred auto-layout renders centred. Use
|
|
678
|
-
`absoluteBoundingBox.x` relative to the container to decide real alignment.
|
|
679
|
-
- Read `fills`, `strokes` + `strokeWeight`, `cornerRadius`, `opacity`, and gradient
|
|
680
|
-
paints. Rings, badges, scrims and pills live in these, not in the layout tree.
|
|
681
|
-
- **`imageTransform` on an IMAGE fill can rotate/flip/crop the photo.** A negative scale
|
|
682
|
-
means the asset must be transformed before use; `background-position` alone will not
|
|
683
|
-
reproduce it.
|
|
684
|
-
- **Selecting the right `imageRef`:** filter candidates by the *node's own* bounding box
|
|
685
|
-
(e.g. only 108×108 tiles), explicitly exclude the section background (large bbox), and
|
|
686
|
-
for stacked carousel slides take the **last** (topmost) fill in paint order. An
|
|
687
|
-
off-by-one here silently shuffles every photo on the page.
|
|
688
|
-
|
|
689
|
-
**Identify each section by its heading text, never by index, order or height.** Frame
|
|
690
|
-
frame names are meaningless (Figma auto-names like `Frame 1234567890`). In one real file the
|
|
691
|
-
sections were mislabeled by one position, which silently swapped two of them and dropped a
|
|
692
|
-
whole section that nobody noticed until the renders were viewed. Print the largest
|
|
693
|
-
`characters` value inside each node and name the section from that:
|
|
694
|
-
|
|
695
|
-
```bash
|
|
696
|
-
# name every section by what it actually says
|
|
697
|
-
for f in figma/nodes/*.json; do python3 scripts/figma_spec.py "$f" 4 | head -8; done
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
**Placeholder copy is a finding, not an invitation.** If `characters` reads `"Label"`,
|
|
701
|
-
`"Lorem ipsum"`, or the same blog title three times, that *is* the design. Ship it
|
|
702
|
-
verbatim and record it in the difference log. Do **not** invent category names, brand
|
|
703
|
-
names or headlines to fill the gap — inventing copy is the same class of error as
|
|
704
|
-
inventing a section.
|
|
705
|
-
|
|
706
|
-
Write this as a reusable script (`spec.py`) that prints, per node: type, position
|
|
707
|
-
relative to the section, size, `characters`, font family/weight/size/line-height/case,
|
|
708
|
-
fill, stroke, radius, and layout mode. Read its output before coding the section.
|
|
709
|
-
|
|
710
|
-
### 6.5.4 Fonts: measure, never eyeball
|
|
711
|
-
|
|
712
|
-
1. Extract the real font family and weight from the JSON.
|
|
713
|
-
2. If it is unavailable (premium/no CDN), **measure** candidates instead of guessing:
|
|
714
|
-
take a long heading, and compare rendered width at the design's font-size/weight
|
|
715
|
-
against the Figma text-box width (`canvas.measureText`, after `document.fonts.load`).
|
|
716
|
-
3. Choose the candidate that matches **both** the metric and the *classification*
|
|
717
|
-
(Didone ≠ Garamond ≠ transitional). A metric-close font of the wrong classification
|
|
718
|
-
still looks wrong.
|
|
719
|
-
4. Record the substitution and the measured delta in the difference log.
|
|
720
|
-
5. Offer the user an `@font-face` swap if they hold a licence for the real file.
|
|
721
|
-
|
|
722
|
-
### 6.5.5 The per-section build loop
|
|
723
|
-
|
|
724
|
-
For each section, in page order — never batch:
|
|
725
|
-
|
|
726
|
-
1. **Look** at the section's reference render. *(What it looks like.)*
|
|
727
|
-
2. **Read** the `figma_spec.py` dump for that node. *(What the numbers are.)*
|
|
728
|
-
You need both. The render alone makes you guess numbers; the JSON alone makes you
|
|
729
|
-
build the right-sized box around the wrong design.
|
|
730
|
-
3. **Build from the spec, not from the picture** — see §6.5.5.1.
|
|
731
|
-
4. **Lint** before you look: `python3 scripts/figma_lint.py --css … --html … --nodes …`
|
|
732
|
-
It fails on any spacing, size or colour that exists nowhere in the design.
|
|
733
|
-
5. **Verify visually**: serve the page, isolate the section, screenshot it at the design
|
|
734
|
-
frame width, compare against the render.
|
|
735
|
-
6. **Verify per section, live**: `python3 scripts/figma_report.py --only <section>` — this
|
|
736
|
-
runs the *text audit* (§18.0) for that one section. Do not defer it to the end; a
|
|
737
|
-
hundred small offsets are cheap to fix one section at a time and brutal in bulk.
|
|
738
|
-
7. Record residual differences in the difference log. Only then move to section N+1.
|
|
739
|
-
|
|
740
|
-
Do not proceed to section N+1 while section N is unverified.
|
|
741
|
-
|
|
742
|
-
### 6.5.5.1 Build from the spec — the rules that prevent the offsets
|
|
743
|
-
|
|
744
|
-
Every number in the design exists in the JSON. If a value in your CSS is not in the file,
|
|
745
|
-
you invented it, and the text audit will find it later as an offset.
|
|
746
|
-
|
|
747
|
-
- **Spacing.** `gap` and `padding` come from `itemSpacing` and `padding*` on the
|
|
748
|
-
auto-layout frames. Do not eyeball rhythm from the render, and do not reach for a
|
|
749
|
-
spacing scale you like. `figma_lint.py` rejects any value the design never uses.
|
|
750
|
-
- **Type.** `font-size`, `font-weight`, `line-height`, `letter-spacing` and `text-transform`
|
|
751
|
-
come from each TEXT node's `style` (and its `styleOverrideTable`). Never retype a size
|
|
752
|
-
from memory.
|
|
753
|
-
- **At the design width, every declared value must resolve to the Figma number.** Put
|
|
754
|
-
responsive behaviour in media queries. Hiding the design value inside `clamp()` makes it
|
|
755
|
-
unauditable and invites drift.
|
|
756
|
-
- **Colour.** Take the hex from the node's own `fills`. Do not pick "the token that looks
|
|
757
|
-
right" — two golds that differ by one step read as identical to you and as a defect to
|
|
758
|
-
the audit.
|
|
759
|
-
- **Copy is verbatim `characters`.** Insert `<br>` **only** where the string contains a
|
|
760
|
-
newline. Inventing a line break changes the copy and silently breaks every text-based
|
|
761
|
-
check. `figma_lint.py` counts them.
|
|
762
|
-
- **Position.** Reproduce the section's own container offsets, not a global container you
|
|
763
|
-
reused. Different sections legitimately have different gutters.
|
|
764
|
-
- **Icons are exported assets, never hand-drawn.** An inline `<svg>` you wrote from memory
|
|
765
|
-
is a different icon: different stroke weight, different metaphor, different silhouette.
|
|
766
|
-
It reads as "close enough" to you and as wrong to the person who drew it. Export every
|
|
767
|
-
vector (§0.5) and reference it. If the design draws something you genuinely reproduce in
|
|
768
|
-
CSS (a dot, a rule, a circle), that is an exception you must *declare* — pass it to
|
|
769
|
-
`figma_lint.py --allow-inline-svg N` and list it in the difference log.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
**Before the browser, run `figma_lint.py`. Two of its checks decide the whole text audit:**
|
|
773
|
-
|
|
774
|
-
- *missing copy* — every visible `characters` string must appear verbatim in the HTML source
|
|
775
|
-
(page text, or `placeholder`/`aria-label`/`value`/`alt`). If it fails, you dropped,
|
|
776
|
-
reworded or invented copy; fix it now, not after a hundred "not found in DOM" rows.
|
|
777
|
-
- *invented spacing* — a `gap` or `padding` that exists nowhere in the design is the single
|
|
778
|
-
cause of the position failures the text audit reports. When the report later says "223
|
|
779
|
-
present, 12 positioned", the 211 are almost always downstream of one guessed gap. Transcribe
|
|
780
|
-
every spacing value from `figma_spec.py`; invent none.
|
|
781
|
-
### 6.5.6 Blocked-section reporting
|
|
782
|
-
|
|
783
|
-
When a reference is unobtainable, say exactly this, per section, and stop:
|
|
784
|
-
|
|
785
|
-
> **BLOCKED — [section]**: no reference image (reason: `429`, `Retry-After 4.6 days`).
|
|
786
|
-
> Copy and geometry are extracted, but the visual composition is unverified. I will not
|
|
787
|
-
> implement it from geometry alone. Options: (a) send me a screenshot of this frame,
|
|
788
|
-
> (b) grant an account with render quota, (c) wait for the limit to reset.
|
|
789
|
-
|
|
790
|
-
Never present a geometry-only reconstruction as a finished section.
|
|
791
|
-
|
|
792
|
-
---
|
|
793
|
-
|
|
794
|
-
## 7. Pixel-Accuracy Workflow
|
|
795
|
-
|
|
796
|
-
Follow this sequence.
|
|
797
|
-
|
|
798
|
-
### Step -1 — Discovery (gate)
|
|
799
|
-
|
|
800
|
-
Run `figma_discover.py` and report breakpoints, icon library, hover variants and page count
|
|
801
|
-
to the user (§6.5.0.5). Everything downstream assumes you know the answers.
|
|
802
|
-
|
|
803
|
-
### Step 0 — Reference Capture (gate)
|
|
804
|
-
|
|
805
|
-
Follow §6.5.2. Obtain and **view** one reference image per section before any coding.
|
|
806
|
-
Sections without a viewed reference are BLOCKED (§6.5.6) and must not be implemented.
|
|
807
|
-
|
|
808
|
-
### Step 1 — Inventory
|
|
809
|
-
|
|
810
|
-
Create a checklist of:
|
|
811
|
-
|
|
812
|
-
- pages;
|
|
813
|
-
- frames;
|
|
814
|
-
- components;
|
|
815
|
-
- assets;
|
|
816
|
-
- fonts;
|
|
817
|
-
- breakpoints;
|
|
818
|
-
- interactions;
|
|
819
|
-
- unknowns.
|
|
820
|
-
|
|
821
|
-
### Step 2 — Extract Design Tokens
|
|
822
|
-
|
|
823
|
-
Build a centralized token system before styling individual sections.
|
|
824
|
-
|
|
825
|
-
### Step 3 — Build Semantic Structure
|
|
826
|
-
|
|
827
|
-
Use semantic elements where appropriate:
|
|
828
|
-
|
|
829
|
-
- `header`
|
|
830
|
-
- `nav`
|
|
831
|
-
- `main`
|
|
832
|
-
- `section`
|
|
833
|
-
- `article`
|
|
834
|
-
- `aside`
|
|
835
|
-
- `footer`
|
|
836
|
-
- `button`
|
|
837
|
-
- `form`
|
|
838
|
-
- correctly ordered headings
|
|
839
|
-
|
|
840
|
-
Avoid unnecessary wrapper elements.
|
|
841
|
-
|
|
842
|
-
### Step 4 — Implement From Large to Small
|
|
843
|
-
|
|
844
|
-
Recommended order:
|
|
845
|
-
|
|
846
|
-
1. global reset and tokens;
|
|
847
|
-
2. page container and grid;
|
|
848
|
-
3. header/navigation;
|
|
849
|
-
4. major sections;
|
|
850
|
-
5. reusable components;
|
|
851
|
-
6. typography;
|
|
852
|
-
7. imagery;
|
|
853
|
-
8. interaction states;
|
|
854
|
-
9. responsive behavior;
|
|
855
|
-
10. animation after approval.
|
|
856
|
-
|
|
857
|
-
### Step 5 — Visual Comparison
|
|
858
|
-
|
|
859
|
-
Do this **per section, immediately after building it** — not once at the end. Serve the
|
|
860
|
-
page, isolate the section (hide the others), screenshot at the design frame width, and
|
|
861
|
-
compare against that section's reference.
|
|
862
|
-
|
|
863
|
-
> Numeric agreement is not visual agreement. Matching every frame height to the pixel
|
|
864
|
-
> proves nothing about whether the design is right.
|
|
865
|
-
|
|
866
|
-
Inspect:
|
|
867
|
-
|
|
868
|
-
- horizontal alignment;
|
|
869
|
-
- vertical rhythm;
|
|
870
|
-
- element dimensions;
|
|
871
|
-
- text wrapping;
|
|
872
|
-
- line breaks;
|
|
873
|
-
- image cropping;
|
|
874
|
-
- border radius;
|
|
875
|
-
- shadows;
|
|
876
|
-
- colors;
|
|
877
|
-
- icon alignment;
|
|
878
|
-
- section height;
|
|
879
|
-
- responsive transitions.
|
|
880
|
-
|
|
881
|
-
### Step 5.5 — Numeric Verification Matrix (mandatory, per section)
|
|
882
|
-
|
|
883
|
-
Measure in the browser (`getBoundingClientRect`/`getComputedStyle`) and assert against the
|
|
884
|
-
Figma values. Font/colour checks alone NEVER count as "verified".
|
|
885
|
-
|
|
886
|
-
| Element class | Must assert |
|
|
887
|
-
|---|---|
|
|
888
|
-
| Repeated items (tabs, cards, chips, logos) | each item w×h; EVERY inter-item gap; container width, border colour+width, shadow presence |
|
|
889
|
-
| Buttons / badges | rendered w×h ±1 (border-box incl border); text letterSpacing/weight/size/textCase; computed `display`+`justify-content` measured IN PLACE (nested), not in isolation |
|
|
890
|
-
| Multi-line headings | per-line start-x equal (`Range.getClientRects()`); `text-align`; `text-indent`; `margin-inline` reset when extending a centred base class |
|
|
891
|
-
| Divider → next row | rendered gap = Figma `nextNode.y − divider.y` (±2) |
|
|
892
|
-
| Card rows with shared baselines | trailing elements' tops equal across the row |
|
|
893
|
-
| Side-by-side panels | tops equal when Figma y is equal |
|
|
894
|
-
| Images / media | pager dots present when the frame contains dot ELLIPSEs; directional photo orientation matches |
|
|
895
|
-
| Icon sets | exactly one container shape per icon (SVG internals vs wrapper), consistent across the SET |
|
|
896
|
-
| Fonts | `document.fonts.check` true for every family+weight used |
|
|
897
|
-
| Raster logos | visible ink (content bbox), group opacity, rendered colour unified with siblings |
|
|
898
|
-
|
|
899
|
-
Run the matrix at the design width, one wider viewport (≥1920) and one narrower.
|
|
900
|
-
After editing any shared CSS (base class, container rule, specificity), re-run on every
|
|
901
|
-
sibling in that container — not just the element you fixed.
|
|
902
|
-
Verify a CSS edit only after hard-navigating to a fresh URL (`?cb=<n>`) and asserting the
|
|
903
|
-
changed property's computed value first (FM92).
|
|
904
|
-
|
|
905
|
-
### Step 6 — Difference Log
|
|
906
|
-
|
|
907
|
-
Maintain a concise list:
|
|
908
|
-
|
|
909
|
-
| Area | Difference | Cause | Fix |
|
|
910
|
-
|---|---|---|---|
|
|
911
|
-
| Hero heading | Wraps one line earlier | Font metric mismatch | Load correct font or adjust width |
|
|
912
|
-
| Card gap | 4 px too large | Grid gap token | Change 28 px to 24 px |
|
|
913
|
-
|
|
914
|
-
### Step 7 — Final Verification
|
|
915
|
-
|
|
916
|
-
Do not declare completion until the acceptance checklist passes.
|
|
917
|
-
|
|
918
|
-
---
|
|
919
|
-
|
|
920
|
-
## 8. Responsive Implementation Rules
|
|
921
|
-
|
|
922
|
-
Never treat mobile as a scaled-down desktop layout.
|
|
923
|
-
|
|
924
|
-
For each breakpoint, verify:
|
|
925
|
-
|
|
926
|
-
- navigation transformation;
|
|
927
|
-
- content stacking;
|
|
928
|
-
- section order;
|
|
929
|
-
- text size and wrapping;
|
|
930
|
-
- container padding;
|
|
931
|
-
- grid column count;
|
|
932
|
-
- button width;
|
|
933
|
-
- touch target size;
|
|
934
|
-
- card density;
|
|
935
|
-
- image crop;
|
|
936
|
-
- overflow;
|
|
937
|
-
- fixed and sticky behavior.
|
|
938
|
-
|
|
939
|
-
Use content-driven breakpoints where possible. When the design supplies explicit frame sizes, reproduce those first.
|
|
940
|
-
|
|
941
|
-
Re-run the Step 5.5 verification matrix at EVERY breakpoint you ship, plus one width
|
|
942
|
-
wider than the design frame. Alignment bugs that depend on container width (a `margin:auto`
|
|
943
|
-
block centring inside a wider column, a fixed-width row overflowing) are invisible at the
|
|
944
|
-
design width — they only appear wider or narrower (FM103).
|
|
945
|
-
|
|
946
|
-
**Fixed pixel columns copied from Figma are a desktop-only truth.** A rule like
|
|
947
|
-
`grid-template-columns: <a>px <b>px`, lifted straight from the frame, is exact at the
|
|
948
|
-
design width and overflows the moment the container is narrower than `a + b + gap`. Every such rule needs a breakpoint
|
|
949
|
-
above the point where it breaks — not just at the usual 1024 px — converting it to `fr`,
|
|
950
|
-
`minmax()` or a stack. Check for overflow at the design width **and** at each step down,
|
|
951
|
-
not only at the mobile preset.
|
|
952
|
-
|
|
953
|
-
Suggested baseline only when the design has no breakpoint specification:
|
|
954
|
-
|
|
955
|
-
```css
|
|
956
|
-
/* Mobile first */
|
|
957
|
-
@media (min-width: 640px) { }
|
|
958
|
-
@media (min-width: 768px) { }
|
|
959
|
-
@media (min-width: 1024px) { }
|
|
960
|
-
@media (min-width: 1280px) { }
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
Do not use these blindly. Adapt breakpoints to the actual design.
|
|
964
|
-
|
|
965
|
-
---
|
|
966
|
-
|
|
967
|
-
## 9. Animation and Motion Policy
|
|
968
|
-
|
|
969
|
-
**Motion that exists in the Figma file is part of the design, not an enhancement.**
|
|
970
|
-
`interactions[]` in the node JSON = Mode A fidelity work: build it without asking, with the
|
|
971
|
-
file's own duration/easing (verified by the report's hover audit). Motion the file does NOT
|
|
972
|
-
specify is an enhancement: propose, get approval, then implement (Mode C).
|
|
973
|
-
|
|
974
|
-
Implementation rules that prevent known regressions: run entrance/scroll reveals via WAAPI
|
|
975
|
-
(`el.animate`) — never a CSS transition that occupies a property a hover transition also
|
|
976
|
-
uses (FM113); include a no-JS/failsafe path so content can never stay hidden; honour
|
|
977
|
-
`prefers-reduced-motion`.
|
|
978
|
-
|
|
979
|
-
Full policy, easing/duration translation tables and per-pattern guidance:
|
|
980
|
-
**`references/animation-guidelines.md`** — read it before implementing ANY motion.
|
|
981
|
-
## 10. UX/UI Review Protocol
|
|
982
|
-
|
|
983
|
-
Review every section, but separate findings into the following categories.
|
|
984
|
-
|
|
985
|
-
### A. Fidelity Issue
|
|
986
|
-
|
|
987
|
-
The implementation does not match the supplied design.
|
|
988
|
-
|
|
989
|
-
Examples:
|
|
990
|
-
|
|
991
|
-
- incorrect spacing;
|
|
992
|
-
- wrong font weight;
|
|
993
|
-
- wrong image crop;
|
|
994
|
-
- missing state;
|
|
995
|
-
- misaligned grid.
|
|
996
|
-
|
|
997
|
-
Fix these without asking when the target is exact implementation.
|
|
998
|
-
|
|
999
|
-
### B. Usability Issue
|
|
1000
|
-
|
|
1001
|
-
The supplied design may create user difficulty.
|
|
1002
|
-
|
|
1003
|
-
Examples:
|
|
1004
|
-
|
|
1005
|
-
- text contrast is too low;
|
|
1006
|
-
- button label is unclear;
|
|
1007
|
-
- touch target is too small;
|
|
1008
|
-
- form lacks validation feedback;
|
|
1009
|
-
- navigation is hard to discover.
|
|
1010
|
-
|
|
1011
|
-
Explain the issue and recommend a solution. Ask before making a visible design change.
|
|
1012
|
-
|
|
1013
|
-
### C. Optional Visual Enhancement
|
|
1014
|
-
|
|
1015
|
-
The design is usable but may benefit from refinement.
|
|
1016
|
-
|
|
1017
|
-
Examples:
|
|
1018
|
-
|
|
1019
|
-
- subtle entrance animation;
|
|
1020
|
-
- stronger visual hierarchy;
|
|
1021
|
-
- improved section transition;
|
|
1022
|
-
- refined hover states;
|
|
1023
|
-
- more consistent card treatment.
|
|
1024
|
-
|
|
1025
|
-
Do not implement without approval.
|
|
1026
|
-
|
|
1027
|
-
### D. Missing Product State
|
|
1028
|
-
|
|
1029
|
-
The design lacks a state required for real usage.
|
|
1030
|
-
|
|
1031
|
-
Examples:
|
|
1032
|
-
|
|
1033
|
-
- loading;
|
|
1034
|
-
- empty result;
|
|
1035
|
-
- error;
|
|
1036
|
-
- disabled;
|
|
1037
|
-
- success;
|
|
1038
|
-
- form validation;
|
|
1039
|
-
- mobile menu open state.
|
|
1040
|
-
|
|
1041
|
-
Call this out explicitly and propose the minimum required state.
|
|
1042
|
-
|
|
1043
|
-
---
|
|
1044
|
-
|
|
1045
|
-
## 11. Recommendation Priority
|
|
1046
|
-
|
|
1047
|
-
Label each recommendation:
|
|
1048
|
-
|
|
1049
|
-
- **Critical** — blocks usability, accessibility, implementation, or conversion.
|
|
1050
|
-
- **High** — significant impact on clarity, consistency, or user flow.
|
|
1051
|
-
- **Medium** — meaningful refinement.
|
|
1052
|
-
- **Low** — optional polish.
|
|
1053
|
-
|
|
1054
|
-
For every recommendation include:
|
|
1055
|
-
|
|
1056
|
-
1. location;
|
|
1057
|
-
2. observed issue;
|
|
1058
|
-
3. recommended change;
|
|
1059
|
-
4. reason;
|
|
1060
|
-
5. expected impact;
|
|
1061
|
-
6. whether approval is required.
|
|
1062
|
-
|
|
1063
|
-
Example:
|
|
1064
|
-
|
|
1065
|
-
| Priority | Location | Observation | Recommendation | Impact | Approval |
|
|
1066
|
-
|---|---|---|---|---|---|
|
|
1067
|
-
| High | Mobile header | Navigation has no open state | Add accessible menu drawer | Improves mobile navigation | Required |
|
|
1068
|
-
| Medium | Feature cards | No hover feedback | Add subtle elevation and border transition | Improves interactivity cue | Required |
|
|
1069
|
-
| Critical | Contact form | Error state missing | Add inline validation and summary | Prevents failed submissions | Required |
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
## 12. Accessibility Requirements
|
|
1074
|
-
|
|
1075
|
-
Target WCAG 2.2 AA where feasible without changing the approved visual direction.
|
|
1076
|
-
|
|
1077
|
-
Verify:
|
|
1078
|
-
|
|
1079
|
-
- semantic structure;
|
|
1080
|
-
- heading order;
|
|
1081
|
-
- keyboard navigation;
|
|
1082
|
-
- visible focus indicators;
|
|
1083
|
-
- accessible names;
|
|
1084
|
-
- form labels;
|
|
1085
|
-
- error association;
|
|
1086
|
-
- image alternative text;
|
|
1087
|
-
- color contrast;
|
|
1088
|
-
- reduced motion;
|
|
1089
|
-
- touch target size;
|
|
1090
|
-
- screen-reader announcements for dynamic content;
|
|
1091
|
-
- correct use of ARIA only when native HTML is insufficient.
|
|
1092
|
-
|
|
1093
|
-
Do not remove focus outlines without an accessible replacement.
|
|
1094
|
-
|
|
1095
|
-
---
|
|
1096
|
-
|
|
1097
|
-
## 13. HTML/CSS Quality Rules
|
|
1098
|
-
|
|
1099
|
-
### HTML
|
|
1100
|
-
|
|
1101
|
-
- Use semantic HTML.
|
|
1102
|
-
- Maintain a logical heading hierarchy.
|
|
1103
|
-
- Use buttons for actions and links for navigation.
|
|
1104
|
-
- Include useful alt text.
|
|
1105
|
-
- Avoid duplicated IDs.
|
|
1106
|
-
- Keep DOM structure understandable.
|
|
1107
|
-
- Do not use inline styles unless required by the environment.
|
|
1108
|
-
|
|
1109
|
-
### CSS
|
|
1110
|
-
|
|
1111
|
-
- **Only use values the design uses.** Run `scripts/figma_lint.py` before every fidelity
|
|
1112
|
-
report; it lists CSS spacing, sizes and colours that appear nowhere in the node JSON.
|
|
1113
|
-
- **Do not wrap a design value in `clamp()` at the design width.** `clamp()` belongs to
|
|
1114
|
-
responsive behaviour, not to the base declaration; it hides the number you are supposed
|
|
1115
|
-
to be reproducing.
|
|
1116
|
-
- Use reusable variables and component classes.
|
|
1117
|
-
- Prefer Grid and Flexbox.
|
|
1118
|
-
- Avoid excessive `!important`.
|
|
1119
|
-
- Avoid fixed pixel heights for text-heavy sections unless required by the design.
|
|
1120
|
-
- Prevent layout shift.
|
|
1121
|
-
- Keep selectors maintainable.
|
|
1122
|
-
- Use logical properties where useful.
|
|
1123
|
-
- Group responsive rules consistently.
|
|
1124
|
-
- Match the project's existing naming convention.
|
|
1125
|
-
|
|
1126
|
-
### JavaScript
|
|
1127
|
-
|
|
1128
|
-
When HTML/CSS alone cannot reproduce the intended interaction:
|
|
1129
|
-
|
|
1130
|
-
- use the minimum JavaScript necessary;
|
|
1131
|
-
- keep behavior separate from presentation;
|
|
1132
|
-
- preserve keyboard and screen-reader support;
|
|
1133
|
-
- do not introduce a large dependency for a small effect;
|
|
1134
|
-
- explain any dependency added.
|
|
1135
|
-
|
|
1136
|
-
---
|
|
1137
|
-
|
|
1138
|
-
## 14. Framework Adaptation
|
|
1139
|
-
|
|
1140
|
-
When the user specifies a framework, follow its conventions.
|
|
1141
|
-
|
|
1142
|
-
Supported examples:
|
|
1143
|
-
|
|
1144
|
-
- plain HTML/CSS/JavaScript;
|
|
1145
|
-
- React;
|
|
1146
|
-
- Next.js;
|
|
1147
|
-
- Vue;
|
|
1148
|
-
- Nuxt;
|
|
1149
|
-
- Svelte;
|
|
1150
|
-
- Astro;
|
|
1151
|
-
- Tailwind CSS;
|
|
1152
|
-
- Bootstrap;
|
|
1153
|
-
- existing design systems.
|
|
1154
|
-
|
|
1155
|
-
If no framework is specified, default to semantic HTML, modular CSS, and minimal JavaScript.
|
|
1156
|
-
|
|
1157
|
-
Do not convert a project to another framework without explicit approval.
|
|
1158
|
-
|
|
1159
|
-
---
|
|
1160
|
-
|
|
1161
|
-
## 15. Handling Incomplete Figma Designs
|
|
1162
|
-
|
|
1163
|
-
When the design omits information:
|
|
1164
|
-
|
|
1165
|
-
1. identify the missing part;
|
|
1166
|
-
2. search existing components or neighboring frames for precedent;
|
|
1167
|
-
3. infer only when necessary;
|
|
1168
|
-
4. label the inference;
|
|
1169
|
-
5. implement the least surprising behavior;
|
|
1170
|
-
6. offer alternatives when multiple valid solutions exist.
|
|
1171
|
-
|
|
1172
|
-
Example:
|
|
1173
|
-
|
|
1174
|
-
> **Assumption:** The desktop design does not show the mobile navigation state. I will use a standard menu button that opens a full-width drawer while preserving the existing typography and color system.
|
|
1175
|
-
|
|
1176
|
-
Never present inferred behavior as if it came directly from Figma.
|
|
1177
|
-
|
|
1178
|
-
---
|
|
1179
|
-
|
|
1180
|
-
## 16. User Approval Gate
|
|
1181
|
-
|
|
1182
|
-
Require explicit approval before:
|
|
1183
|
-
|
|
1184
|
-
- adding animation not present in Figma;
|
|
1185
|
-
- adding or removing sections;
|
|
1186
|
-
- changing layout hierarchy;
|
|
1187
|
-
- changing colors or typography;
|
|
1188
|
-
- rewriting visible copy;
|
|
1189
|
-
- changing button labels;
|
|
1190
|
-
- adding decorative graphics;
|
|
1191
|
-
- replacing imagery;
|
|
1192
|
-
- changing user flow;
|
|
1193
|
-
- introducing a new library;
|
|
1194
|
-
- changing a component's visual behavior;
|
|
1195
|
-
- making conversion-oriented design changes.
|
|
1196
|
-
|
|
1197
|
-
Approval is not required for:
|
|
1198
|
-
|
|
1199
|
-
- semantic markup corrections;
|
|
1200
|
-
- invisible accessibility metadata;
|
|
1201
|
-
- CSS organization;
|
|
1202
|
-
- browser compatibility fixes;
|
|
1203
|
-
- performance optimizations that do not change the visual result;
|
|
1204
|
-
- exact fidelity corrections.
|
|
1205
|
-
|
|
1206
|
-
---
|
|
1207
|
-
|
|
1208
|
-
## 17. Required Response Structure
|
|
1209
|
-
|
|
1210
|
-
When beginning a Figma implementation, respond with:
|
|
1211
|
-
|
|
1212
|
-
### A. Design Understanding
|
|
1213
|
-
|
|
1214
|
-
- target page or frame;
|
|
1215
|
-
- implementation mode;
|
|
1216
|
-
- target technology;
|
|
1217
|
-
- available assets;
|
|
1218
|
-
- **required fonts** — list every distinct `fontPostScriptName` on visible TEXT nodes,
|
|
1219
|
-
split into *free* (load from a CDN yourself) and *licensed* (the user must supply).
|
|
1220
|
-
Name the exact files and the exact folder. Do this in the **first** reply, not at
|
|
1221
|
-
delivery — the user cannot act on it after the fact;
|
|
1222
|
-
- missing information;
|
|
1223
|
-
- assumptions.
|
|
1224
|
-
|
|
1225
|
-
Produce the font list with `scripts/figma_fonts.py` (§0.3) — never from memory or from a
|
|
1226
|
-
previous project — and state it in the **first** reply using the template in §0.3. It must
|
|
1227
|
-
name, for *this* design:
|
|
1228
|
-
|
|
1229
|
-
1. every **licensed** face, by `fontPostScriptName`, and what it is used for;
|
|
1230
|
-
2. the exact folder to drop them in (`design/fonts/`);
|
|
1231
|
-
3. every **free** face, which you load yourself and must not ask for;
|
|
1232
|
-
4. that they cannot be extracted from Figma or an SVG export;
|
|
1233
|
-
5. what you will substitute meanwhile, and that the type will not be exact until they land.
|
|
1234
|
-
|
|
1235
|
-
### B. Implementation Plan
|
|
1236
|
-
|
|
1237
|
-
- global tokens;
|
|
1238
|
-
- component list;
|
|
1239
|
-
- responsive strategy;
|
|
1240
|
-
- interaction strategy;
|
|
1241
|
-
- validation method.
|
|
1242
|
-
|
|
1243
|
-
### C. Optional Recommendations
|
|
1244
|
-
|
|
1245
|
-
Only include recommendations supported by a concrete observation.
|
|
1246
|
-
|
|
1247
|
-
### D. Approval Questions
|
|
1248
|
-
|
|
1249
|
-
Group optional changes into one concise approval request where practical.
|
|
1250
|
-
|
|
1251
|
-
Example:
|
|
1252
|
-
|
|
1253
|
-
> I can reproduce the Figma exactly first. I also identified two optional improvements:
|
|
1254
|
-
> 1. subtle hero entrance animation;
|
|
1255
|
-
> 2. stronger hover feedback for feature cards.
|
|
1256
|
-
>
|
|
1257
|
-
> Neither appears in the source design. Should I include both, only one, or keep the implementation strictly identical?
|
|
1258
|
-
|
|
1259
|
-
---
|
|
1260
|
-
|
|
1261
|
-
## 18. Final Delivery Structure
|
|
1262
|
-
|
|
1263
|
-
### 18.0 Ship a fidelity report — evidence, not assurances (required)
|
|
1264
|
-
|
|
1265
|
-
Never close a build with a sentence like "it matches the design". Hand the user something
|
|
1266
|
-
they can check without trusting you:
|
|
1267
|
-
|
|
1268
|
-
```bash
|
|
1269
|
-
# 1. once per project: sign the icon set so identity (not just presence) is checked
|
|
1270
|
-
python3 scripts/figma_icons.py --svg design/exports/page.svg --nodes figma/nodes --out figma/icons
|
|
1271
|
-
# 2. every audit round:
|
|
1272
|
-
python3 scripts/figma_report.py --page http://localhost:PORT/index.html \
|
|
1273
|
-
--nodes figma/nodes --selectors selectors.json --icons-dir figma/icons
|
|
1274
|
-
```
|
|
1275
|
-
|
|
1276
|
-
`selectors.json` maps each Figma section frame to ONE CSS selector:
|
|
1277
|
-
`{"hero": ".hero", "collection": ".collection", ...}` — keys must match the node JSON
|
|
1278
|
-
filenames. Without it sections are matched by DOM order, which mis-maps silently; the
|
|
1279
|
-
report header lists any selector matching 0 or 2+ elements — fix those before reading rows.
|
|
1280
|
-
Skipping `--icons-dir` downgrades the icon audit to presence-only (it will say so).
|
|
1281
|
-
`--assets-map figma/assets-map.json` (`{imageRef: filename}`) enables image IDENTITY —
|
|
1282
|
-
without it the report cannot say the RIGHT photo is on the node (on the reference build
|
|
1283
|
-
this check caught two cards silently reusing another card's photo). Known noise: a file
|
|
1284
|
-
with stacked overlapping variants stamps the template's imageRef over real positions —
|
|
1285
|
-
identity rows saying "expects <first-photo>" at spots that already pass with their own
|
|
1286
|
-
ref are variant ghosts (FM99), not defects.
|
|
1287
|
-
|
|
1288
|
-
Version every stylesheet and script from the first commit (`styles.css?v=1`, `main.js?v=1`)
|
|
1289
|
-
and bump on each edit — browsers heuristically cache unversioned assets and the report (and
|
|
1290
|
-
you) will measure stale code (FM92).
|
|
1291
|
-
|
|
1292
|
-
It writes `fidelity-report.html`, which for every section shows the Figma reference beside
|
|
1293
|
-
the **live** page, with a cross-fade slider and a *difference* blend (a perfect match goes
|
|
1294
|
-
black), and computes — in the browser, at load time:
|
|
1295
|
-
|
|
1296
|
-
| Check | Why it exists |
|
|
1297
|
-
|---|---|
|
|
1298
|
-
| section height, Figma vs built | the obvious one |
|
|
1299
|
-
| **content extents (left–right edge)** | heights match while a container is offset sideways; only this catches it |
|
|
1300
|
-
| horizontal overflow | breaks silently below the design width |
|
|
1301
|
-
| total page height | catches drift that per-section rounding hides |
|
|
1302
|
-
| **every TEXT node: position, size, weight, colour** | the box can be perfect while the design inside it is wrong |
|
|
1303
|
-
| **every icon: exported asset / hand-drawn / missing** | hand-drawn icons pass every geometric check and still look wrong |
|
|
1304
|
-
| **every image: real photo / gradient placeholder / missing** | placeholders survive to production when nothing counts them |
|
|
1305
|
-
| **every non-text box: fill colour, corner radius** | the text audit is blind to a panel with the wrong colour |
|
|
1306
|
-
| **sections with no reference slice** | an unchecked section is how a whole section goes missing |
|
|
1307
|
-
| **the section→selector mapping itself** | a selector matching 0 or 2 elements silently invalidates every row |
|
|
1308
|
-
| **hover transition durations** vs `interactions[]` | the design states them; nothing else checks you shipped them |
|
|
1309
|
-
| **stroke colour and drop shadows** on boxes | a rule the design draws as a stroke is easy to fake as an element |
|
|
1310
|
-
| **image identity** (`--assets-map`) | "a photo is present" is not "the right photo is present" |
|
|
1311
|
-
| **confirmed breakpoints not covered** (`--breakpoints`) | a second design silently never gets built |
|
|
1312
|
-
|
|
1313
|
-
**Do not stop at the geometry checks.** They pass on a page that looks nothing like the
|
|
1314
|
-
design. In one build every section matched on height *and* content extents while only
|
|
1315
|
-
**10 of 224 text nodes** matched on position, size, weight and colour. The geometry row is
|
|
1316
|
-
a smoke test, not a verdict.
|
|
1317
|
-
|
|
1318
|
-
Three traps the text audit itself must avoid:
|
|
1319
|
-
|
|
1320
|
-
- Compare the **ink box** of the text (`Range.getBoundingClientRect()`), not the element
|
|
1321
|
-
box. A centred heading lives in a full-width block; comparing element rects reports a
|
|
1322
|
-
huge false offset.
|
|
1323
|
-
- Only compare `font-weight` **within the same typeface**. And a declared `@font-face`
|
|
1324
|
-
whose file 404s still appears in `getComputedStyle().fontFamily` — ask
|
|
1325
|
-
`document.fonts.check(...)` whether the face actually loaded before trusting it.
|
|
1326
|
-
- `text not found in DOM` is a real finding, not noise: it means your copy or your markup
|
|
1327
|
-
splits the string differently from the design.
|
|
1328
|
-
- Group an icon at the **outermost pure-vector subtree**. Recurse further and every glyph
|
|
1329
|
-
outline of a logo is counted as its own missing icon.
|
|
1330
|
-
- An icon reported `missing` may be a vector the design draws as a shape and you
|
|
1331
|
-
legitimately reproduce in CSS. That is a decision to record, not a row to ignore.
|
|
1332
|
-
|
|
1333
|
-
#### Guard the guards
|
|
1334
|
-
|
|
1335
|
-
An audit that can be fooled is worse than none, because it grants permission to stop
|
|
1336
|
-
looking. Three holes are easy to leave open — close them:
|
|
1337
|
-
|
|
1338
|
-
- **Custom properties.** A linter that skips declarations containing `var()` can be
|
|
1339
|
-
defeated by moving the invented number into a token. Resolve `var()` before checking.
|
|
1340
|
-
- **Section completeness.** A report that iterates over the references you happened to
|
|
1341
|
-
produce cannot notice a section you never sliced. Enumerate the design's sections and
|
|
1342
|
-
flag every one without a reference.
|
|
1343
|
-
- **Selector mapping.** Require an explicit section→selector map, and fail if any selector
|
|
1344
|
-
matches zero or several elements. Falling back to DOM order will one day compare the
|
|
1345
|
-
wrong things and report green.
|
|
1346
|
-
|
|
1347
|
-
And in the box audit, separate a **wrong fill/radius** (a defect) from **no 1:1 element**
|
|
1348
|
-
(a structural difference — Figma frames do not map one-to-one onto DOM elements). Reporting
|
|
1349
|
-
them together produces a number that means nothing.
|
|
1350
|
-
|
|
1351
|
-
#### The audit runs in both directions
|
|
1352
|
-
|
|
1353
|
-
Every check that walks **design → DOM** ("the design has an icon here; is it in the page?")
|
|
1354
|
-
is blind to whatever you *added*. That is not a small gap. A stray element, a doubled
|
|
1355
|
-
control, a leftover from a refactor — none of them exist in the design, so nothing looks for
|
|
1356
|
-
them, and the report keeps saying the build matches.
|
|
1357
|
-
|
|
1358
|
-
`figma_report.py` therefore also walks **DOM → design**: every graphic in a section that no
|
|
1359
|
-
design node accounts for is listed as *not in the design*. Read that row. It is the only
|
|
1360
|
-
place a mistake of commission can show up.
|
|
1361
|
-
|
|
1362
|
-
The same asymmetry applies to identity. "An icon is present at this node" and "the icon the
|
|
1363
|
-
design puts at this node is present" are different claims, and only the second one is worth
|
|
1364
|
-
anything. `figma_icons.py` stamps each exported icon with `data-icon-shape` (a scale- and
|
|
1365
|
-
translation-invariant outline profile) and `data-icon-paint`; the report compares those,
|
|
1366
|
-
with a tolerance on the shape and exactly on the paint. Two traps it exists to avoid:
|
|
1367
|
-
|
|
1368
|
-
- hashing the geometry — the same icon reused on the next card sits at sub-pixel-different
|
|
1369
|
-
coordinates, so a hash reports every legitimate reuse as a mismatch;
|
|
1370
|
-
- comparing geometry alone — a filled star and an outlined star are the *same* outline and
|
|
1371
|
-
differ only in `fill`.
|
|
1372
|
-
|
|
1373
|
-
And because a comparator that always answers "different" makes a perfect build look broken,
|
|
1374
|
-
the report proves it can recognise a file as itself before it reports a single verdict.
|
|
1375
|
-
|
|
1376
|
-
#### What this report still does *not* check
|
|
1377
|
-
|
|
1378
|
-
State this to the user, every time. A green report is evidence about what was measured and
|
|
1379
|
-
nothing else.
|
|
1380
|
-
|
|
1381
|
-
- **the visual result of a hover state.** Durations are verified; what the variant *looks
|
|
1382
|
-
like* is not. Fetch the variants (`figma_pull.py --hover <destIds>`), look at them, and
|
|
1383
|
-
match your `:hover` by eye.
|
|
1384
|
-
- opacity, gradients, z-order and overflow clipping
|
|
1385
|
-
- focus and active states; keyboard behaviour
|
|
1386
|
-
- accessibility and performance
|
|
1387
|
-
- **regression** — there is no baseline diff; a fix that breaks another section shows up
|
|
1388
|
-
only if you read the whole report again
|
|
1389
|
-
- anything at viewports other than the design width, and any breakpoint you did not build
|
|
1390
|
-
|
|
1391
|
-
Those need the difference-blend view, the reference frames, and your eyes.
|
|
1392
|
-
|
|
1393
|
-
Rules:
|
|
1394
|
-
|
|
1395
|
-
- The report's numbers are computed live from the page. **Do not paste a summary that the
|
|
1396
|
-
report does not show**, and do not claim a section matches while its row says *differs*.
|
|
1397
|
-
- **Verify the verifier.** Before trusting a comparison view, confirm the reference and the
|
|
1398
|
-
live page are drawn at the same scale and offset; a mis-scaled overlay makes any build
|
|
1399
|
-
look correct. Confirm a known-bad section actually reports as bad.
|
|
1400
|
-
- When you change CSS and re-measure through an iframe, **bust the cache** — a stale
|
|
1401
|
-
stylesheet will happily tell you the fix worked.
|
|
1402
|
-
- Rows that legitimately differ (a decorative element you rendered as a shadow, a font
|
|
1403
|
-
substitution) belong in the difference log with the reason — not hidden by loosening the
|
|
1404
|
-
tolerance.
|
|
1405
|
-
- A green report is *evidence about geometry*, not proof of visual fidelity. The
|
|
1406
|
-
difference-blend view is what proves that; look at it, and say what you saw.
|
|
1407
|
-
|
|
1408
|
-
At completion provide:
|
|
1409
|
-
|
|
1410
|
-
### 1. Completed Scope
|
|
1411
|
-
|
|
1412
|
-
List pages, sections, and states implemented.
|
|
1413
|
-
|
|
1414
|
-
### 2. Files Changed
|
|
1415
|
-
|
|
1416
|
-
List each created or modified file and its purpose.
|
|
1417
|
-
|
|
1418
|
-
### 3. Fidelity Notes
|
|
1419
|
-
|
|
1420
|
-
Point at `fidelity-report.html` first, then state:
|
|
1421
|
-
|
|
1422
|
-
- what matches the design;
|
|
1423
|
-
- any unavoidable deviations;
|
|
1424
|
-
- unavailable fonts or assets;
|
|
1425
|
-
- assumptions used.
|
|
1426
|
-
|
|
1427
|
-
### 4. Responsive Coverage
|
|
1428
|
-
|
|
1429
|
-
List tested viewport sizes and behavior.
|
|
1430
|
-
|
|
1431
|
-
### 5. Accessibility and Performance Notes
|
|
1432
|
-
|
|
1433
|
-
State checks completed and remaining limitations.
|
|
1434
|
-
|
|
1435
|
-
### 6. Optional Next Improvements
|
|
1436
|
-
|
|
1437
|
-
List only unimplemented, user-approved, or still-pending suggestions.
|
|
1438
|
-
|
|
1439
|
-
Do not claim visual perfection without comparison evidence.
|
|
1440
|
-
|
|
1441
|
-
---
|
|
1442
|
-
|
|
1443
|
-
## 19. Acceptance Checklist
|
|
1444
|
-
|
|
1445
|
-
Before marking the work complete, verify:
|
|
1446
|
-
|
|
1447
|
-
### Visual Fidelity
|
|
1448
|
-
|
|
1449
|
-
- [ ] Container width matches reference
|
|
1450
|
-
- [ ] Section spacing matches reference
|
|
1451
|
-
- [ ] Grid and alignment match reference
|
|
1452
|
-
- [ ] Typography matches reference
|
|
1453
|
-
- [ ] Colors match reference
|
|
1454
|
-
- [ ] Borders, radii, and shadows match reference
|
|
1455
|
-
- [ ] Images use correct crop and aspect ratio
|
|
1456
|
-
- [ ] Icons match size and alignment
|
|
1457
|
-
- [ ] Text wraps similarly at reference viewport sizes
|
|
1458
|
-
|
|
1459
|
-
### Responsive Behavior
|
|
1460
|
-
|
|
1461
|
-
- [ ] Mobile layout is intentionally designed
|
|
1462
|
-
- [ ] Tablet behavior is valid
|
|
1463
|
-
- [ ] Desktop behavior matches reference
|
|
1464
|
-
- [ ] No horizontal overflow
|
|
1465
|
-
- [ ] Navigation works at all breakpoints
|
|
1466
|
-
- [ ] Touch targets are usable
|
|
1467
|
-
- [ ] Images remain sharp and correctly cropped
|
|
1468
|
-
|
|
1469
|
-
### Interaction
|
|
1470
|
-
|
|
1471
|
-
- [ ] `interactions[]` in the node JSON was read before any motion was written (§9.0)
|
|
1472
|
-
- [ ] Figma's own durations/easings are used where they exist; springs are noted as approximations
|
|
1473
|
-
- [ ] Inferred motion is labeled, and matches the mood the design already sets
|
|
1474
|
-
- [ ] Entrance motion cannot hide content if the script fails (§9.0.2)
|
|
1475
|
-
- [ ] Hover states work
|
|
1476
|
-
- [ ] Focus states are visible
|
|
1477
|
-
- [ ] Active and selected states work
|
|
1478
|
-
- [ ] Keyboard operation works
|
|
1479
|
-
- [ ] Form validation states exist when required
|
|
1480
|
-
- [ ] Motion specified in Figma is implemented (no approval needed — it is the design)
|
|
1481
|
-
- [ ] Motion *not* in Figma has user approval, or a standing instruction, and is labeled inferred
|
|
1482
|
-
- [ ] Reduced-motion preference is respected
|
|
1483
|
-
|
|
1484
|
-
### Code Quality
|
|
1485
|
-
|
|
1486
|
-
- [ ] Semantic HTML used
|
|
1487
|
-
- [ ] CSS variables defined
|
|
1488
|
-
- [ ] Reusable components extracted
|
|
1489
|
-
- [ ] No unnecessary duplication
|
|
1490
|
-
- [ ] No arbitrary hacks without explanation
|
|
1491
|
-
- [ ] No unsupported dependencies added
|
|
1492
|
-
- [ ] No console errors
|
|
1493
|
-
|
|
1494
|
-
### Reference Coverage
|
|
1495
|
-
|
|
1496
|
-
- [ ] `fidelity-report.html` was generated, opened, and handed to the user
|
|
1497
|
-
- [ ] The **text audit** was read, not just the geometry rows
|
|
1498
|
-
- [ ] Every row in it is green, or every non-green row appears in the difference log
|
|
1499
|
-
- [ ] No layout rule was weakened just to turn a metric green
|
|
1500
|
-
- [ ] `figma_lint.py` passes: no invented spacing, size, colour, `<br>` or inline `<svg>`
|
|
1501
|
-
- [ ] Icon audit is green, or every hand-drawn / missing icon is justified in the difference log
|
|
1502
|
-
- [ ] Image audit is green: no gradient placeholders left
|
|
1503
|
-
- [ ] The user was told what the report does **not** check
|
|
1504
|
-
- [ ] Box audit reviewed: no wrong fills or radii among matched boxes
|
|
1505
|
-
- [ ] No section is listed as "no reference slice"
|
|
1506
|
-
- [ ] The section→selector map resolves to exactly one element per section
|
|
1507
|
-
- [ ] Hover transition durations audited; hover *appearance* compared against the fetched variants
|
|
1508
|
-
- [ ] `--assets-map` supplied so image identity, not just presence, is checked
|
|
1509
|
-
- [ ] `--breakpoints` supplied; every confirmed width has its own report
|
|
1510
|
-
- [ ] `figma_discover.py` was run **first** and its output shown to the user
|
|
1511
|
-
- [ ] Every confirmed breakpoint frame was built and audited, not inferred
|
|
1512
|
-
- [ ] No icon was hand-drawn that exists in the file's icon library
|
|
1513
|
-
- [ ] Hover variants named by `interactions[]` were fetched and compared
|
|
1514
|
-
- [ ] The scope (one screen vs a site) was confirmed against the file's page-sized frames
|
|
1515
|
-
- [ ] Each section was audited with `--only <section>` as it was built, not just at the end
|
|
1516
|
-
- [ ] The difference-blend overlay was actually looked at, not just the numbers
|
|
1517
|
-
- [ ] Every implemented section has a reference image that was actually viewed
|
|
1518
|
-
- [ ] No section was reconstructed from geometry/JSON alone
|
|
1519
|
-
- [ ] Every section was screenshot-compared against its reference after being built
|
|
1520
|
-
- [ ] Blocked sections are reported as blocked, not shipped as done
|
|
1521
|
-
- [ ] Font substitutions are measured (§6.5.4), not guessed, and the delta is recorded
|
|
1522
|
-
- [ ] Asset-to-node mapping was verified (no shuffled or off-by-one images)
|
|
1523
|
-
|
|
1524
|
-
### Final Honesty Check
|
|
1525
|
-
|
|
1526
|
-
- [ ] Assumptions are labeled
|
|
1527
|
-
- [ ] Inferences are labeled
|
|
1528
|
-
- [ ] Missing assets are disclosed
|
|
1529
|
-
- [ ] Unverified claims are not presented as fact
|
|
1530
|
-
- [ ] “100% identical” is not claimed without measurable verification
|
|
1531
|
-
- [ ] Section heights matching is never presented as evidence the design matches
|
|
1532
|
-
- [ ] Quota/permission failures are reported with the real cause (`Retry-After`, “File not exportable”), not glossed over
|
|
1533
|
-
|
|
1534
|
-
---
|
|
1535
|
-
|
|
1536
|
-
## 20. Recommended Folder Structure
|
|
1537
|
-
|
|
1538
|
-
A basic skill package may use:
|
|
1539
|
-
|
|
1540
|
-
```text
|
|
1541
|
-
figma-to-html-pixel-perfect/
|
|
1542
|
-
├── SKILL.md # this file
|
|
1543
|
-
├── README.md # install + setup, for distribution
|
|
1544
|
-
├── references/
|
|
1545
|
-
│ ├── visual-review-checklist.md
|
|
1546
|
-
│ ├── accessibility-checklist.md
|
|
1547
|
-
│ └── animation-guidelines.md
|
|
1548
|
-
└── scripts/
|
|
1549
|
-
├── figma_discover.py # FIRST: breakpoints, icon library, hover, scope
|
|
1550
|
-
├── figma_icons.py # extract every icon offline from the page SVG
|
|
1551
|
-
├── figma_pull.py # preflight, node JSON, one render per section
|
|
1552
|
-
├── figma_spec.py # correct node-JSON reader (§6.5.3 contract)
|
|
1553
|
-
├── figma_fonts.py # which fonts render, free vs licensed (§0.3)
|
|
1554
|
-
├── figma_lint.py # build-stage guard: invented values, hand-drawn icons
|
|
1555
|
-
└── figma_report.py # fidelity report: geometry + text + icon/image audits
|
|
1556
|
-
```
|
|
1557
|
-
|
|
1558
|
-
`SKILL.md` is the only required file, but **use the scripts** — they encode §6.5.1–6.5.3.
|
|
1559
|
-
Re-deriving them by hand is how the reading-contract bugs get reintroduced.
|
|
1560
|
-
|
|
1561
|
-
---
|
|
1562
|
-
|
|
1563
|
-
## 21. Core Principle
|
|
1564
|
-
|
|
1565
|
-
The supplied Figma design is the visual source of truth.
|
|
1566
|
-
|
|
1567
|
-
Implement first, verify second, recommend third, and change the design only after the user approves the change.
|
|
1568
|
-
|
|
1569
|
-
---
|
|
1570
|
-
|
|
1571
|
-
## 22. Known Failure Modes — references/failure-modes.md (MANDATORY reading points)
|
|
1572
|
-
|
|
1573
|
-
114 documented failure modes, every one observed in a real build. The full table lives in
|
|
1574
|
-
`references/failure-modes.md` — do NOT skip it; read it at these three moments:
|
|
1575
|
-
|
|
1576
|
-
1. **Before building** any of: fonts/text (FM82-88, 96, 100), repeated-item controls —
|
|
1577
|
-
tabs/cards/logo strips (FM87, 93-95, 101, 106), buttons (FM86, 91, 96, 108), icons/SVG
|
|
1578
|
-
(FM104), images/carousels/backgrounds (FM98-99, 105, 107), section layout (FM84, 103,
|
|
1579
|
-
109), responsive (FM111).
|
|
1580
|
-
2. **Before verifying** — the verifier itself has failure modes: measurement viewport
|
|
1581
|
-
(FM110), stale CSS/JS (FM34, 92), reveal/animation interference (FM68, 113), matcher
|
|
1582
|
-
blind spots (FM112), API quotas (FM114).
|
|
1583
|
-
3. **When the user reports a defect** — find the matching FM class first; the fix pattern
|
|
1584
|
-
and the verify assertion are already written there.
|
|
1585
|
-
|
|
1586
|
-
Working rule for NEW defects: fix the build, fix the verify stage so it would have caught
|
|
1587
|
-
it, and append a design-agnostic FM row to `references/failure-modes.md`.
|
|
47
|
+
| adds/changes anything visible | never | never (only proposes) | only what was approved |
|
|
48
|
+
| output | code + difference log | + prioritized audit | + what changed |
|
|
49
|
+
|
|
50
|
+
Static markup for carousels/accordions is A; wiring behaviour is C (explicit request).
|
|
51
|
+
Reproducing motion the design specifies is A (§ Motion below). Mode is per-request.
|
|
52
|
+
When unsure: A.
|
|
53
|
+
|
|
54
|
+
## Workflow — gates, in order
|
|
55
|
+
|
|
56
|
+
**0. Setup (once).** Token in `~/.figma_token`; check `design/` folder first; 403 "not
|
|
57
|
+
exportable" = owner must enable sharing, stop. → `references/setup.md`
|
|
58
|
+
|
|
59
|
+
**1. Discover — before any code.**
|
|
60
|
+
`python3 scripts/figma_discover.py <fileKey> --json figma/discovery.json`
|
|
61
|
+
Answers: how many design widths (= breakpoints to BUILD, not guess) · icon library? ·
|
|
62
|
+
hover variants? · multiple versions of the same screen? **Multiple versions: the user
|
|
63
|
+
picks; never pick for them.** Show the output to the user.
|
|
64
|
+
|
|
65
|
+
**2. Pull nodes, cache-first.** `python3 scripts/figma_pull.py …` — every REST endpoint
|
|
66
|
+
shares one quota and can 429 for hours; pull once, then work only from `figma/nodes/`.
|
|
67
|
+
Renders are the scarcest; a user-supplied `design/exports/page.png` replaces them.
|
|
68
|
+
|
|
69
|
+
**3. Fonts — first reply to the user.** `python3 scripts/figma_fonts.py figma/nodes/*.json`
|
|
70
|
+
→ request the licensed files by exact PostScript name; declare `@font-face` now so they
|
|
71
|
+
hot-swap in later; until then measure a substitute and disclose (never claim type fidelity).
|
|
72
|
+
|
|
73
|
+
**4. Motion check — before deciding "no animation":**
|
|
74
|
+
`grep -l '"interactions"' figma/nodes/*.json` — anything found is Mode A work.
|
|
75
|
+
→ `references/animation-guidelines.md`
|
|
76
|
+
|
|
77
|
+
**5. Per-section build loop.** For each section, in document order:
|
|
78
|
+
a. `figma_spec.py` the section (NUMBER RULE step 1) — full spec table before any CSS.
|
|
79
|
+
b. Build from the spec. Hard rules that prevent whole classes of offsets:
|
|
80
|
+
- Top-anchor content; **never vertically centre inside a locked min-height** (FM109).
|
|
81
|
+
- Text: read `styleOverrideTable` for per-character font/size changes (§ref), never
|
|
82
|
+
only `style`. No invented `<br>` — lint counts them against design newlines.
|
|
83
|
+
- Icons: `figma_icons.py` output only, never hand-drawn; photos: real `imageRef`
|
|
84
|
+
bytes via `figma/assets-map.json`; logos never invented.
|
|
85
|
+
- Fixed px column values from the frame are desktop-only truths — they need a
|
|
86
|
+
breakpoint before the width where they overflow.
|
|
87
|
+
c. Verify: regen report (step 6) — section is done when its row says `match`.
|
|
88
|
+
→ full detail: `references/build-guide.md`
|
|
89
|
+
|
|
90
|
+
**6. Report loop.**
|
|
91
|
+
`python3 scripts/figma_report.py --page <url> --nodes figma/nodes --selectors selectors.json --icons-dir figma/icons --assets-map figma/assets-map.json --out fidelity.html`
|
|
92
|
+
- Poll `document.title === 'fidelity-READY'` — never sleep a fixed wait.
|
|
93
|
+
- Bump `styles.css?v=N` after EVERY css edit or the browser measures stale CSS.
|
|
94
|
+
- Read every row; **batch all sections' fixes from one read into one edit.**
|
|
95
|
+
- The report waits for fonts + image decode and double-samples layout; if a count
|
|
96
|
+
still changes with no edit to that section, suspect the verifier/page, not the CSS.
|
|
97
|
+
- Fix section HEIGHT mismatches top-to-bottom before item spacing (deltas are
|
|
98
|
+
page-absolute; upstream height errors cascade into everything below). The report
|
|
99
|
+
renders a red `#heightDebt` banner whenever any section height is >6px off and a
|
|
100
|
+
`#guardWarnings` banner for duplicate-selector shadowing — **every table read MUST
|
|
101
|
+
return both banners' text first**; while `#heightDebt` is non-empty, per-item deltas
|
|
102
|
+
are not to be acted on. A `⚠ LINEAR DRIFT` row means one repeated component's height
|
|
103
|
+
is wrong — fix that unit, never the individual rows (FM128).
|
|
104
|
+
- Edit had zero effect? grep for duplicate selectors (auto-warned, FM123) and check
|
|
105
|
+
computed style in a fresh iframe (`contentWindow.getComputedStyle`) before retrying.
|
|
106
|
+
- **After any `position:relative;left/top` or `transform:translate` shift, re-check
|
|
107
|
+
`document.documentElement.scrollWidth === clientWidth` at every breakpoint before
|
|
108
|
+
moving on.** A shift-only fix leaves the element the right SIZE, just partly off
|
|
109
|
+
canvas — the text/box delta audit does not catch this; only an overflow sweep does,
|
|
110
|
+
and the visible symptom can appear on a completely unrelated section (FM130).
|
|
111
|
+
→ `references/report-guide.md`
|
|
112
|
+
|
|
113
|
+
**7. Responsive = each confirmed breakpoint is a separate design.**
|
|
114
|
+
- Split the frame into `figma/nodes-<width>/`, then validate: total visible TEXT count
|
|
115
|
+
across split files MUST equal the frame's (a shortfall = a dropped subtree, FM119).
|
|
116
|
+
- Diff per-breakpoint `imageRef`s (frames swap photos) and component default states
|
|
117
|
+
(an accordion open on mobile, closed on desktop — reproduce via matchMedia, FM116).
|
|
118
|
+
- If the mobile DOM order differs from desktop, author the mobile structure — do NOT
|
|
119
|
+
bend desktop markup with `display:contents`+`order`+`:nth-of-type` (FM115).
|
|
120
|
+
- Run the full report loop per breakpoint, plus one width wider than the design frame.
|
|
121
|
+
- No mobile frame in the file → responsive behaviour is inferred: label it.
|
|
122
|
+
|
|
123
|
+
**8. Visual sweep — numeric green is necessary, never sufficient.** Twice: after the
|
|
124
|
+
first full build and before declaring done. Put the design render beside the settled
|
|
125
|
+
live build, section by section, and LOOK (a 40-line compare page; ~seconds per section).
|
|
126
|
+
Missing whole components hide in green reports (FM119-120).
|
|
127
|
+
|
|
128
|
+
**9. Deliver.** The fidelity report file + a difference log: every deviation, why, and
|
|
129
|
+
every placeholder/substitution. Do not claim a match the report does not show.
|
|
130
|
+
→ `references/report-guide.md`, `references/visual-review-checklist.md`,
|
|
131
|
+
`references/accessibility-checklist.md`
|
|
132
|
+
|
|
133
|
+
## Completion standard
|
|
134
|
+
|
|
135
|
+
- **Loop until green or every remaining row is documented, proven noise. Never ask
|
|
136
|
+
"shall I continue?" while non-green rows remain.** Stop only for a real external
|
|
137
|
+
blocker (quota, missing font/asset) — then report the blocker, not a fake completion.
|
|
138
|
+
- **Convergence guard:** the failing-row count must trend down. If it rises or
|
|
139
|
+
oscillates 3 rounds without a new low, the structure is wrong, not the values —
|
|
140
|
+
revert to the best round, measure the CONTAINER (width/display/stretch), fix the
|
|
141
|
+
structure, only then resume value tuning.
|
|
142
|
+
- Speed comes from fewer rounds, never from skipping verification: extract-first
|
|
143
|
+
(≤2 rounds per section), batch fixes across sections, answer node-JSON questions
|
|
144
|
+
with python (never with a browser round-trip), dedupe CSS as you go.
|
|
145
|
+
|
|
146
|
+
## Clarification
|
|
147
|
+
|
|
148
|
+
Ask only when the gap genuinely blocks a reliable build (which frame is current, missing
|
|
149
|
+
licensed font, contradictory sources). Otherwise: build, infer conservatively, and LABEL
|
|
150
|
+
every inference in the difference log.
|
|
151
|
+
|
|
152
|
+
## Editing this skill
|
|
153
|
+
|
|
154
|
+
After ANY change to `scripts/` or this file: `python3 scripts/selftest.py` must pass
|
|
155
|
+
(exit 0). Every past failure mode with a testable guard is a fixture there — a red test
|
|
156
|
+
means a guard was broken or deleted, and the mistake it caught WILL recur silently.
|
|
157
|
+
When you add a new guard, add its fixture in the same edit.
|
|
158
|
+
|
|
159
|
+
## Failure modes — `references/failure-modes.md`
|
|
160
|
+
|
|
161
|
+
126 documented ways this work goes wrong, each with its fix. **Mandatory reading points:**
|
|
162
|
+
before the first build (FM1-30 layout/type), before icons/images (FM60-99), before
|
|
163
|
+
responsive (FM103, 109-120), when a verify result looks wrong (FM110-126), when edits
|
|
164
|
+
stop having effects (FM123). When you hit ANY new defect: fix the build, fix the verify
|
|
165
|
+
stage so it would have caught it, and add the failure mode — in design-agnostic terms.
|