@tractiontactics/tt-fidelity 0.2.1
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/AGENTS.md +41 -0
- package/README.md +150 -0
- package/bin/tt-fidelity.js +2 -0
- package/fidelity-diff.mjs +7 -0
- package/fidelity.schema.json +77 -0
- package/fixtures/draft-opaque.html +57 -0
- package/fixtures/draft.html +61 -0
- package/fixtures/pages/cluster-a-draft.html +19 -0
- package/fixtures/pages/cluster-a-proto.html +19 -0
- package/fixtures/pages/cluster-b-draft.html +19 -0
- package/fixtures/pages/cluster-b-proto.html +19 -0
- package/fixtures/pages/cluster-pages.json +12 -0
- package/fixtures/pixel-diverge.html +28 -0
- package/fixtures/pixel-identical.html +21 -0
- package/fixtures/proto.html +51 -0
- package/fixtures/roles-opaque.json +13 -0
- package/fixtures/self-test.mjs +111 -0
- package/package.json +55 -0
- package/src/capture.mjs +114 -0
- package/src/cli.mjs +124 -0
- package/src/constants.mjs +73 -0
- package/src/fonts.mjs +61 -0
- package/src/index.mjs +6 -0
- package/src/pixel.mjs +197 -0
- package/src/plan.mjs +410 -0
- package/src/rank.mjs +52 -0
- package/src/run.mjs +433 -0
- package/src/schema.mjs +62 -0
- package/src/sections.mjs +119 -0
- package/src/styles.mjs +356 -0
- package/src/tt-map.mjs +17 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Agent contract — `@tractiontactics/tt-fidelity`
|
|
2
|
+
|
|
3
|
+
You are measuring a prototype against a TT WP Theme draft. **You are not the grader.**
|
|
4
|
+
|
|
5
|
+
## Hard rules
|
|
6
|
+
|
|
7
|
+
1. **Run the checker.** Paste `QUEUE.md` or the WORK PLAN from stdout / `fidelity.json`. Do not invent a fidelity report from memory or from a successful API write.
|
|
8
|
+
2. **Exit 0 means measured pixel parity** for compared sections at requested viewports (`--mode full` or `pixel`). `--mode styles` exit 0 is triage only — never claim the page is done.
|
|
9
|
+
3. **"Matched" means measured.** Unverifiable claims must be labelled as such.
|
|
10
|
+
4. **One WORK PLAN task**, then re-run with `--round` incremented. Do not batch speculative fixes.
|
|
11
|
+
5. **Never apply a rule site-wide** until `instances_per_page` (in clusters / QUEUE) shows it is a real default. Blanket CSS from a rare seam creates hundreds of false findings.
|
|
12
|
+
6. **Cache:** the tool cache-busts by default. If draft and live disagree, purge host/CDN and re-run — do not “fix” ghosts.
|
|
13
|
+
7. **Escalate** tokens → block Style/Advanced → minimal `PUT /custom-css` → theme change → HTML template. Cite `ttRowId` / `ttBlockIds` when present.
|
|
14
|
+
|
|
15
|
+
## Loop
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @tractiontactics/tt-fidelity \
|
|
19
|
+
--proto "<PROTOTYPE_URL>" \
|
|
20
|
+
--draft "<DRAFT_PREVIEW_URL>" \
|
|
21
|
+
--viewports 1280,768,390 \
|
|
22
|
+
--out ./fidelity \
|
|
23
|
+
--mode full \
|
|
24
|
+
--round N \
|
|
25
|
+
--max-rounds 6
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Exit **0** → page (or chrome scope) done for this URL pair.
|
|
29
|
+
- Exit **1** → do TASK 1 from the plan; re-measure.
|
|
30
|
+
- Exit **2** → fix install / auth / `--roles`; do not guess missing sections.
|
|
31
|
+
- Exit **3** → stop; report residuals; consider history restore.
|
|
32
|
+
|
|
33
|
+
## Large sites
|
|
34
|
+
|
|
35
|
+
Compare one prototype page to its draft at a time, or pass `--pages`. Use `--scope chrome` then `--scope content`. Prefer `--only-failing` after the first full pass.
|
|
36
|
+
|
|
37
|
+
## Forbidden
|
|
38
|
+
|
|
39
|
+
- Certifying “100% parity” without exit 0 from the pixel gate.
|
|
40
|
+
- Substituting fetch-only CSS analysis for the main path.
|
|
41
|
+
- Ignoring section count mismatches (structural first).
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @tractiontactics/tt-fidelity
|
|
2
|
+
|
|
3
|
+
Section-first visual fidelity for prototype → TT WP Theme builds.
|
|
4
|
+
|
|
5
|
+
**Pixel / perceptual section screenshots are the acceptance gate.** Computed styles explain *what* to change. Exit `0` means every compared section is under the clean threshold — not that an agent believed the page matched.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -D playwright && npx playwright install chromium
|
|
9
|
+
|
|
10
|
+
npx @tractiontactics/tt-fidelity \
|
|
11
|
+
--proto "https://prototype.example.com/page" \
|
|
12
|
+
--draft "https://site.example.com/?p=12&preview=true" \
|
|
13
|
+
--viewports 1280,768,390 \
|
|
14
|
+
--out ./fidelity \
|
|
15
|
+
--mode full
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Monorepo:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
node tools/fidelity-diff/fidelity-diff.mjs --proto … --draft … --out ./fidelity
|
|
22
|
+
npm run fidelity:test # self-tests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Publishing to npm
|
|
26
|
+
|
|
27
|
+
Do **not** use a 2FA-bypass granular access token (deprecated / blocked for publish).
|
|
28
|
+
|
|
29
|
+
**First publish** (creates the package). npm requires **account 2FA** or a granular token that can publish — a plain login session is not enough.
|
|
30
|
+
|
|
31
|
+
1. Enable 2FA on your npm account (if not already):
|
|
32
|
+
https://www.npmjs.com/settings/~/account/security → Authenticator app
|
|
33
|
+
(or `npm profile enable-2fa auth-and-writes` and follow prompts)
|
|
34
|
+
2. Create a **Granular Access Token** (not classic):
|
|
35
|
+
https://www.npmjs.com/settings/~/tokens → Generate new token → Granular
|
|
36
|
+
- Permissions: **Read and write**
|
|
37
|
+
- Packages / scopes: `@tractiontactics` (or “All packages” for the org)
|
|
38
|
+
- **Bypass 2FA:** leave **off** if your account 2FA works with `--otp`; turn **on** only for a one-shot first publish if interactive OTP still fails (token is being deprecated for publish ~2027 — replace with Trusted Publishing after)
|
|
39
|
+
3. Point npm at that token and publish with a **real** 6-digit authenticator code (not the placeholder `XXXXXX`):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd tools/fidelity-diff
|
|
43
|
+
npm config set //registry.npmjs.org/:_authToken=npm_YOUR_TOKEN_HERE
|
|
44
|
+
npm whoami
|
|
45
|
+
npm publish --access public --otp=123456 # real code from your authenticator
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Later releases:** prefer GitHub Actions trusted publishing (OIDC).
|
|
49
|
+
|
|
50
|
+
1. On [npmjs.com](https://www.npmjs.com) → package → **Settings → Trusted Publisher**
|
|
51
|
+
- GitHub repo: `tractiontactics/tt-wp-platform`
|
|
52
|
+
- Workflow filename: `publish-tt-fidelity.yml`
|
|
53
|
+
2. Bump `version` in this `package.json`, commit, tag, push:
|
|
54
|
+
```bash
|
|
55
|
+
git tag tt-fidelity-v0.2.2
|
|
56
|
+
git push origin tt-fidelity-v0.2.2
|
|
57
|
+
```
|
|
58
|
+
Workflow: [`.github/workflows/publish-tt-fidelity.yml`](../../.github/workflows/publish-tt-fidelity.yml) — no `NPM_TOKEN`.
|
|
59
|
+
|
|
60
|
+
Until the package is published, agents should use the monorepo path above.
|
|
61
|
+
|
|
62
|
+
## Exit codes
|
|
63
|
+
|
|
64
|
+
| Code | Meaning |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| **0** | Pixel gate clean (or styles clean in `--mode styles` only) |
|
|
67
|
+
| **1** | Differences remain — continue the loop |
|
|
68
|
+
| **2** | Could not run / low landmark coverage |
|
|
69
|
+
| **3** | `--max-rounds` reached with diffs still present |
|
|
70
|
+
|
|
71
|
+
## Modes
|
|
72
|
+
|
|
73
|
+
| Mode | Exit 0 from | Styles |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `full` (default) | Section pixel scores | Explainer + work plan enrichment |
|
|
76
|
+
| `pixel` | Section pixel scores | Skipped |
|
|
77
|
+
| `styles` | Computed-style diffs (triage only) | Authoritative for this mode only |
|
|
78
|
+
|
|
79
|
+
**Do not treat `--mode styles` exit 0 as visual parity.** Gradients, borders via `background-image`, and inline SVG are invisible to a property census.
|
|
80
|
+
|
|
81
|
+
## Severity bands (pixel score = differing pixels / area)
|
|
82
|
+
|
|
83
|
+
| Band | Score | Meaning |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| clean | < 0.10 | Pass |
|
|
86
|
+
| fix | 0.10–0.40 | Fix settings / tokens |
|
|
87
|
+
| rebuild | ≥ 0.40 | Rebuild section |
|
|
88
|
+
|
|
89
|
+
Override fail cut with `--threshold` (default `0.10`).
|
|
90
|
+
|
|
91
|
+
## CLI
|
|
92
|
+
|
|
93
|
+
| Flag | Purpose |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `--proto` / `--draft` | Single URL pair |
|
|
96
|
+
| `--pages <file>` | JSON `[{id,proto,draft}]` or `proto\|draft` lines |
|
|
97
|
+
| `--out <dir>` | `fidelity.json`, `QUEUE.md`, `shots/sections/…` |
|
|
98
|
+
| `--mode full\|pixel\|styles` | See above |
|
|
99
|
+
| `--viewports` | Default `1280,768,390` |
|
|
100
|
+
| `--scope full\|chrome\|content` | Section / landmark scope |
|
|
101
|
+
| `--cache-bust` / `--no-cache-bust` | Default **on** (`tt_nocache=`); logs cache headers |
|
|
102
|
+
| `--concurrency <n>` | Parallel pages |
|
|
103
|
+
| `--only-failing` | Skip sections clean in prior `fidelity.json` |
|
|
104
|
+
| `--roles` | Landmark map when defaults miss |
|
|
105
|
+
| `--round` / `--max-rounds` | Agent loop |
|
|
106
|
+
| `--json` | Extra copy of the document |
|
|
107
|
+
| Auth / cookies | `--auth`, `--draft-auth`, `--cookie` |
|
|
108
|
+
|
|
109
|
+
## Programmatic API
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
import { runFidelity } from '@tractiontactics/tt-fidelity';
|
|
113
|
+
|
|
114
|
+
const result = await runFidelity({
|
|
115
|
+
proto: 'https://…',
|
|
116
|
+
draft: 'https://…',
|
|
117
|
+
viewports: [1280, 390],
|
|
118
|
+
mode: 'full',
|
|
119
|
+
outDir: './fidelity'
|
|
120
|
+
});
|
|
121
|
+
// result.exitCode, result.summary, result.pages, result.clusters, result.document, result.text
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Schema: [`fidelity.schema.json`](./fidelity.schema.json) (`schema_version: 2`).
|
|
125
|
+
|
|
126
|
+
## How measurement works
|
|
127
|
+
|
|
128
|
+
1. Load both URLs at the same viewport (cache-busted by default).
|
|
129
|
+
2. Discover **sections**: draft prefers `.tt-pb-row` / `data-tt-row`; prototype prefers `main > section`.
|
|
130
|
+
3. Align by document order; count mismatch → structural task.
|
|
131
|
+
4. Screenshot each pair, normalize canvas, `pixelmatch` → score + band.
|
|
132
|
+
5. Optionally capture landmark computed styles (explainer).
|
|
133
|
+
6. Cluster failing headings across `--pages` → `QUEUE.md` leverage list.
|
|
134
|
+
7. Emit TT ids (`data-tt-row` / `data-tt-block`) when present for `PUT /pages/{id}/layout`.
|
|
135
|
+
|
|
136
|
+
## Agent contract
|
|
137
|
+
|
|
138
|
+
See **[AGENTS.md](./AGENTS.md)**. Short version:
|
|
139
|
+
|
|
140
|
+
1. Run the checker; paste `QUEUE.md` / JSON — do not self-author “100% parity.”
|
|
141
|
+
2. Exit 0 = pixel gate. Styles alone ≠ done.
|
|
142
|
+
3. One WORK PLAN task → re-measure → increment `--round`.
|
|
143
|
+
4. Never apply a finding site-wide until `instances_per_page` justifies it.
|
|
144
|
+
5. Trust `--cache-bust` (default on).
|
|
145
|
+
|
|
146
|
+
Platform docs: [fidelity-measurement.md](../../WordPress%20Theme/Plugin/docs/fidelity-measurement.md), [prototype-to-site.md](../../WordPress%20Theme/Plugin/docs/prototype-to-site.md).
|
|
147
|
+
|
|
148
|
+
## Theme hooks (0.15.3+)
|
|
149
|
+
|
|
150
|
+
Builders emit additive `data-tt-row` / `data-tt-block` with layout JSON ids (stable even when `advanced.custom_id` overrides HTML `id`). Paint unchanged.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://tractiontactics.com/schemas/fidelity.v2.json",
|
|
4
|
+
"title": "TT Fidelity Report",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema_version", "mode", "exit_code", "pages"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema_version": { "const": 2 },
|
|
9
|
+
"package_version": { "type": "string" },
|
|
10
|
+
"mode": { "enum": ["full", "pixel", "styles"] },
|
|
11
|
+
"threshold": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"clean": { "type": "number" },
|
|
15
|
+
"fix": { "type": "number" },
|
|
16
|
+
"rebuild": { "type": "number" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"round": { "type": "integer" },
|
|
20
|
+
"max_rounds": { "type": "integer" },
|
|
21
|
+
"exit_code": { "type": "integer", "enum": [0, 1, 2, 3] },
|
|
22
|
+
"summary": { "type": "object" },
|
|
23
|
+
"clusters": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"key": { "type": "string" },
|
|
29
|
+
"label": { "type": "string" },
|
|
30
|
+
"pages": { "type": "integer" },
|
|
31
|
+
"avgScore": { "type": "number" },
|
|
32
|
+
"instancesPerPage": { "type": "number" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"work_plan": { "type": "array" },
|
|
37
|
+
"pages": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"required": ["id", "proto", "draft", "viewports"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"id": { "type": "string" },
|
|
44
|
+
"proto": { "type": "string" },
|
|
45
|
+
"draft": { "type": "string" },
|
|
46
|
+
"viewports": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"width": { "type": "integer" },
|
|
52
|
+
"sections": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"index": { "type": "integer" },
|
|
58
|
+
"protoSelector": { "type": "string" },
|
|
59
|
+
"draftSelector": { "type": "string" },
|
|
60
|
+
"ttRowId": { "type": ["string", "null"] },
|
|
61
|
+
"ttBlockIds": { "type": "array", "items": { "type": "string" } },
|
|
62
|
+
"score": { "type": "number" },
|
|
63
|
+
"band": { "enum": ["clean", "fix", "rebuild"] },
|
|
64
|
+
"shotPaths": { "type": "object" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"cacheHeaders": { "type": "object" },
|
|
69
|
+
"coverage": { "type": "object" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Fixture draft — opaque markup</title>
|
|
7
|
+
<!--
|
|
8
|
+
Same visual page as proto.html but with markup that defeats convention-based
|
|
9
|
+
selectors: no <header>/<nav>/<main>/<footer>, no ARIA roles, and hashed
|
|
10
|
+
class names of the kind a CSS-modules or utility-first build emits.
|
|
11
|
+
|
|
12
|
+
Used to prove fidelity-diff does NOT report a false "clean" when its role
|
|
13
|
+
selectors match nothing. It should report low coverage loudly instead.
|
|
14
|
+
-->
|
|
15
|
+
<style>
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
body { margin: 0; font-family: system-ui, sans-serif; color: #0f172a; }
|
|
18
|
+
.Hdr_a1b2 { display: flex; justify-content: space-between; align-items: center;
|
|
19
|
+
padding: 16px 24px; background: #fff; border-bottom: 1px solid #e2e8f0; }
|
|
20
|
+
.Nav_c3d4 { display: flex; gap: 16px; }
|
|
21
|
+
.Nav_c3d4 > .Lnk_e5f6 { font-size: 14px; font-weight: 600; color: #0f172a;
|
|
22
|
+
text-decoration: none; padding: 8px 4px; }
|
|
23
|
+
.Wrap_g7h8 { max-width: 1280px; margin: 0 auto; padding: 0 40px; }
|
|
24
|
+
.Ttl_i9j0 { font-size: 36px; line-height: 1.1; margin: 64px 0 24px; }
|
|
25
|
+
.Cta_k1l2 { display: inline-block; background: #059669; color: #fff;
|
|
26
|
+
padding: 12px 24px; border-radius: 4px; font-size: 18px; font-weight: 700;
|
|
27
|
+
text-decoration: none; }
|
|
28
|
+
.Blk_m3n4 { padding: 40px 0; }
|
|
29
|
+
.Grd_o5p6 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px; }
|
|
30
|
+
.Itm_q7r8 { padding: 20px; border-radius: 8px; background: #f8fafc; }
|
|
31
|
+
.Ftr_s9t0 { padding: 48px 40px; background: #0f172a; color: #fff; margin-top: 80px; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="Hdr_a1b2">
|
|
36
|
+
<div class="Brand_u1v2">Acme</div>
|
|
37
|
+
<div class="Nav_c3d4">
|
|
38
|
+
<a class="Lnk_e5f6" href="/">Home</a>
|
|
39
|
+
<a class="Lnk_e5f6" href="/services">Services</a>
|
|
40
|
+
<a class="Lnk_e5f6" href="/about">About</a>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="Wrap_g7h8">
|
|
44
|
+
<div class="Ttl_i9j0">Trusted local experts</div>
|
|
45
|
+
<a class="Cta_k1l2" href="/quote">Get a quote</a>
|
|
46
|
+
<div class="Blk_m3n4">
|
|
47
|
+
<div class="Sub_w3x4">What we do</div>
|
|
48
|
+
<div class="Grd_o5p6">
|
|
49
|
+
<div class="Itm_q7r8"><div>Repairs</div><p>Fast and tidy.</p></div>
|
|
50
|
+
<div class="Itm_q7r8"><div>Installs</div><p>Done properly.</p></div>
|
|
51
|
+
<div class="Itm_q7r8"><div>Servicing</div><p>On schedule.</p></div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="Ftr_s9t0">© Acme</div>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Fixture draft</title>
|
|
7
|
+
<!--
|
|
8
|
+
Deliberate drift from proto.html, used to verify fidelity-diff reports the
|
|
9
|
+
right numbers. Expected findings:
|
|
10
|
+
fonts : body/nav/h1 family Georgia -> system-ui; h1 48px -> 36px; nav 16px -> 14px
|
|
11
|
+
menus : header padding 24/40 -> 16/24; nav gap 32px -> 16px; nav-item count 4 -> 3
|
|
12
|
+
colours : .btn background #047857 -> #059669; .btn radius 8px -> 4px; card radius 12px -> 8px
|
|
13
|
+
padding : .btn 16/32 -> 12/24; .card 32px -> 20px; section 64px -> 40px
|
|
14
|
+
flow : .cards grid 3 cols -> 2 cols
|
|
15
|
+
gaps : .cards gap 24px -> 32px
|
|
16
|
+
widths : main max-width 1120px -> 1280px
|
|
17
|
+
-->
|
|
18
|
+
<style>
|
|
19
|
+
* { box-sizing: border-box; }
|
|
20
|
+
body { margin: 0; font-family: system-ui, sans-serif; color: #0f172a; }
|
|
21
|
+
header { display: flex; justify-content: space-between; align-items: center;
|
|
22
|
+
padding: 16px 24px; background: #ffffff; border-bottom: 1px solid #e2e8f0; }
|
|
23
|
+
nav { display: flex; gap: 16px; }
|
|
24
|
+
nav a { font-family: system-ui, sans-serif; font-size: 14px; font-weight: 600;
|
|
25
|
+
color: #0f172a; text-decoration: none; padding: 8px 4px; }
|
|
26
|
+
main { max-width: 1280px; margin: 0 auto; padding: 0 40px; }
|
|
27
|
+
h1 { font-size: 36px; line-height: 1.1; letter-spacing: -0.02em; margin: 64px 0 24px; }
|
|
28
|
+
.btn { display: inline-block; background: #059669; color: #ffffff;
|
|
29
|
+
padding: 12px 24px; border-radius: 4px; font-size: 18px; font-weight: 700;
|
|
30
|
+
text-decoration: none; }
|
|
31
|
+
section { padding: 40px 0; }
|
|
32
|
+
.cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px; }
|
|
33
|
+
.card { padding: 20px; border-radius: 8px; background: #f8fafc;
|
|
34
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
|
|
35
|
+
footer { padding: 48px 40px; background: #0f172a; color: #ffffff; margin-top: 80px; }
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<header>
|
|
40
|
+
<div class="logo">Acme</div>
|
|
41
|
+
<nav>
|
|
42
|
+
<a href="/">Home</a>
|
|
43
|
+
<a href="/services">Services</a>
|
|
44
|
+
<a href="/about">About</a>
|
|
45
|
+
</nav>
|
|
46
|
+
</header>
|
|
47
|
+
<main>
|
|
48
|
+
<h1>Trusted local experts</h1>
|
|
49
|
+
<a class="btn" href="/quote">Get a quote</a>
|
|
50
|
+
<section>
|
|
51
|
+
<h2>What we do</h2>
|
|
52
|
+
<div class="cards">
|
|
53
|
+
<div class="card"><h3>Repairs</h3><p>Fast and tidy.</p></div>
|
|
54
|
+
<div class="card"><h3>Installs</h3><p>Done properly.</p></div>
|
|
55
|
+
<div class="card"><h3>Servicing</h3><p>On schedule.</p></div>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
</main>
|
|
59
|
+
<footer>© Acme</footer>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Cluster page A draft</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; font-family: system-ui, sans-serif; }
|
|
9
|
+
section { padding: 40px; background: #f1f5f9; }
|
|
10
|
+
.cta { padding: 40px; background: #94a3b8; color: #fff; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<section><h2>Welcome A</h2></section>
|
|
16
|
+
<section class="cta"><h2>Prefer to talk it through?</h2></section>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Cluster page A</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; font-family: system-ui, sans-serif; }
|
|
9
|
+
section { padding: 40px; background: #f1f5f9; }
|
|
10
|
+
.cta { padding: 40px; background: #047857; color: #fff; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<section><h2>Welcome A</h2></section>
|
|
16
|
+
<section class="cta"><h2>Prefer to talk it through?</h2></section>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Cluster page B draft</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; font-family: system-ui, sans-serif; }
|
|
9
|
+
section { padding: 40px; background: #fff; }
|
|
10
|
+
.cta { padding: 40px; background: #cbd5e1; color: #fff; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<section><h2>Welcome B</h2></section>
|
|
16
|
+
<section class="cta"><h2>Prefer to talk it through?</h2></section>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Cluster page B</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; font-family: system-ui, sans-serif; }
|
|
9
|
+
section { padding: 40px; background: #fff; }
|
|
10
|
+
.cta { padding: 40px; background: #047857; color: #fff; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main>
|
|
15
|
+
<section><h2>Welcome B</h2></section>
|
|
16
|
+
<section class="cta"><h2>Prefer to talk it through?</h2></section>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Pixel diverge draft — gradient only</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* Same computed colour/padding as proto for sampled landmarks, but hero paints via gradient */
|
|
9
|
+
body { margin: 0; font-family: Georgia, serif; background: #fff; color: #111; }
|
|
10
|
+
main { max-width: 800px; margin: 0 auto; }
|
|
11
|
+
section { padding: 48px 24px; }
|
|
12
|
+
h2 { margin: 0 0 12px; font-size: 28px; }
|
|
13
|
+
p { margin: 0; line-height: 1.5; }
|
|
14
|
+
/* background-image is invisible to the colours bucket (background-color only) */
|
|
15
|
+
section:first-child {
|
|
16
|
+
background-color: #ffffff;
|
|
17
|
+
background-image: linear-gradient(135deg, #047857 0%, #0ea5e9 100%);
|
|
18
|
+
color: #fff;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<main>
|
|
24
|
+
<section><h2>Intro</h2><p>Same paint on both sides.</p></section>
|
|
25
|
+
<section><h2>Details</h2><p>Second section content.</p></section>
|
|
26
|
+
</main>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Pixel identical proto</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; font-family: Georgia, serif; background: #fff; color: #111; }
|
|
9
|
+
main { max-width: 800px; margin: 0 auto; }
|
|
10
|
+
section { padding: 48px 24px; }
|
|
11
|
+
h2 { margin: 0 0 12px; font-size: 28px; }
|
|
12
|
+
p { margin: 0; line-height: 1.5; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<main>
|
|
17
|
+
<section><h2>Intro</h2><p>Same paint on both sides.</p></section>
|
|
18
|
+
<section><h2>Details</h2><p>Second section content.</p></section>
|
|
19
|
+
</main>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Fixture prototype</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; }
|
|
9
|
+
body { margin: 0; font-family: Georgia, serif; color: #0f172a; }
|
|
10
|
+
header { display: flex; justify-content: space-between; align-items: center;
|
|
11
|
+
padding: 24px 40px; background: #ffffff; border-bottom: 1px solid #e2e8f0; }
|
|
12
|
+
nav { display: flex; gap: 32px; }
|
|
13
|
+
nav a { font-family: Georgia, serif; font-size: 16px; font-weight: 600;
|
|
14
|
+
color: #0f172a; text-decoration: none; padding: 8px 4px; }
|
|
15
|
+
main { max-width: 1120px; margin: 0 auto; padding: 0 40px; }
|
|
16
|
+
h1 { font-size: 48px; line-height: 1.1; letter-spacing: -0.02em; margin: 64px 0 24px; }
|
|
17
|
+
.btn { display: inline-block; background: #047857; color: #ffffff;
|
|
18
|
+
padding: 16px 32px; border-radius: 8px; font-size: 18px; font-weight: 700;
|
|
19
|
+
text-decoration: none; }
|
|
20
|
+
section { padding: 64px 0; }
|
|
21
|
+
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
|
|
22
|
+
.card { padding: 32px; border-radius: 12px; background: #f8fafc;
|
|
23
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
|
|
24
|
+
footer { padding: 48px 40px; background: #0f172a; color: #ffffff; margin-top: 80px; }
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<header>
|
|
29
|
+
<div class="logo">Acme</div>
|
|
30
|
+
<nav>
|
|
31
|
+
<a href="/">Home</a>
|
|
32
|
+
<a href="/services">Services</a>
|
|
33
|
+
<a href="/about">About</a>
|
|
34
|
+
<a href="/contact">Contact</a>
|
|
35
|
+
</nav>
|
|
36
|
+
</header>
|
|
37
|
+
<main>
|
|
38
|
+
<h1>Trusted local experts</h1>
|
|
39
|
+
<a class="btn" href="/quote">Get a quote</a>
|
|
40
|
+
<section>
|
|
41
|
+
<h2>What we do</h2>
|
|
42
|
+
<div class="cards">
|
|
43
|
+
<div class="card"><h3>Repairs</h3><p>Fast and tidy.</p></div>
|
|
44
|
+
<div class="card"><h3>Installs</h3><p>Done properly.</p></div>
|
|
45
|
+
<div class="card"><h3>Servicing</h3><p>On schedule.</p></div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
</main>
|
|
49
|
+
<footer>© Acme</footer>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[
|
|
2
|
+
["header", [".Hdr_a1b2", "header"], 1],
|
|
3
|
+
["logo", [".Brand_u1v2", ".logo"], 1],
|
|
4
|
+
["nav", [".Nav_c3d4", "nav"], 1],
|
|
5
|
+
["nav-item", [".Nav_c3d4 > a", "nav a"], 8],
|
|
6
|
+
["main", [".Wrap_g7h8", "main"], 1],
|
|
7
|
+
["h1", [".Ttl_i9j0", "h1"], 1],
|
|
8
|
+
["h2", [".Sub_w3x4", "h2"], 4],
|
|
9
|
+
["button", [".Cta_k1l2", "a.button", ".btn"], 6],
|
|
10
|
+
["section", [".Blk_m3n4", "main > section"], 8],
|
|
11
|
+
["card", [".Itm_q7r8", ".card"], 6],
|
|
12
|
+
["footer", [".Ftr_s9t0", "footer"], 1]
|
|
13
|
+
]
|