awwwards-mcp 1.0.0 → 1.2.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/README.md +251 -8
- package/dist/cli.js +4 -0
- package/dist/parsers.js +6 -3
- package/dist/version-check.js +118 -0
- package/package.json +21 -1
- package/skills/awwwards-doctor/SKILL.md +100 -0
- package/skills/awwwards-inspiration/SKILL.md +28 -9
- package/skills/awwwards-motion-study/SKILL.md +160 -0
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Free, open-source MCP server that gives AI agents design inspiration from
|
|
|
4
4
|
[Awwwards](https://www.awwwards.com/) — the Mobbin-style visual reference loop,
|
|
5
5
|
sourced from the web's best award-winning websites.
|
|
6
6
|
|
|
7
|
+
<a href="https://github.com/INSANE0777/Awwwards-mcp"><img src="assets/demo.gif" alt="awwwards-mcp in action: search results, design DNA, motion filmstrip, band map — real tool output" width="480"></a>
|
|
8
|
+
|
|
7
9
|
Your agent searches in natural language ("dark 3D portfolio sites", "soft pastel
|
|
8
10
|
e-commerce"), sees **real screenshots inline**, and can pull the **design DNA**
|
|
9
11
|
of any site: color palette, tech stack, design elements, award history.
|
|
@@ -22,13 +24,65 @@ of any site: color palette, tech stack, design elements, award history.
|
|
|
22
24
|
|
|
23
25
|
## Setup
|
|
24
26
|
|
|
27
|
+
Any MCP-compatible coding agent can use awwwards-mcp — no API key, no account.
|
|
28
|
+
Requires Node ≥ 22.13 (`node -v` to check). Pick your agent:
|
|
29
|
+
|
|
30
|
+
**Updates**: the server checks the npm registry once a day and prints an
|
|
31
|
+
stderr notice when a newer `awwwards-mcp` exists (stdout stays clean for the
|
|
32
|
+
JSON-RPC channel — your agent sees the notice as a log line). Set
|
|
33
|
+
`AWWWARDS_AUTO_UPDATE=1` in the server's `env` to opt into background
|
|
34
|
+
self-update; restart your agent afterwards to load it. Nothing is fetched
|
|
35
|
+
more than once a day and serving never waits on the check.
|
|
36
|
+
|
|
25
37
|
**Claude Code**
|
|
26
38
|
|
|
27
39
|
```bash
|
|
28
40
|
claude mcp add awwwards -- npx -y awwwards-mcp
|
|
29
41
|
```
|
|
30
42
|
|
|
31
|
-
**
|
|
43
|
+
**Codex CLI** (ChatGPT desktop app and the IDE extension share this config)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codex mcp add awwwards -- npx -y awwwards-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
or in `~/.codex/config.toml` (project-scoped: `.codex/config.toml`):
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[mcp_servers.awwwards]
|
|
53
|
+
command = "npx"
|
|
54
|
+
args = ["-y", "awwwards-mcp"]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**OpenCode** (`opencode.json` — note the command is an array)
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"$schema": "https://opencode.ai/config.json",
|
|
62
|
+
"mcp": {
|
|
63
|
+
"awwwards": {
|
|
64
|
+
"type": "local",
|
|
65
|
+
"command": ["npx", "-y", "awwwards-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**ZCode** (`~/.zcode/cli/config.json` — note servers nest under `"mcp": { "servers": ... }`)
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcp": {
|
|
76
|
+
"servers": {
|
|
77
|
+
"awwwards": { "command": "npx", "args": ["-y", "awwwards-mcp"], "env": {} }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Claude Desktop / Cursor / Windsurf / Gemini CLI / Cline / Continue** — anything
|
|
84
|
+
reading the common `mcpServers` JSON shape (e.g. `~/.claude/claude_desktop_config.json`
|
|
85
|
+
or `~/.gemini/settings.json`):
|
|
32
86
|
|
|
33
87
|
```json
|
|
34
88
|
{
|
|
@@ -38,25 +92,58 @@ claude mcp add awwwards -- npx -y awwwards-mcp
|
|
|
38
92
|
}
|
|
39
93
|
```
|
|
40
94
|
|
|
41
|
-
|
|
95
|
+
**Anything else** — awwwards-mcp is a plain stdio MCP server: point your client
|
|
96
|
+
at `npx -y awwwards-mcp` and it works. To pin a version, use
|
|
97
|
+
`npx -y awwwards-mcp@1.0.0`.
|
|
98
|
+
|
|
99
|
+
**pi coding agent** has no built-in MCP by design — it uses skills and
|
|
100
|
+
extensions instead. Two options:
|
|
101
|
+
|
|
102
|
+
1. Install the awwwards-inspiration skill (below). pi reads skills from
|
|
103
|
+
`~/.pi/agent/skills/` or `~/.agents/skills/` (the latter is shared across
|
|
104
|
+
agents following the Agent Skills standard). The skill teaches the workflow;
|
|
105
|
+
for it to reach the live data, add an MCP-supporting pi extension, or run
|
|
106
|
+
the queries in another agent and paste results.
|
|
107
|
+
2. Skip MCP entirely: ask pi to build you a small CLI wrapper around
|
|
108
|
+
awwwards.com, or use a shared skills directory (`~/.agents/skills/`) so the
|
|
109
|
+
same skill file serves pi and every other agent.
|
|
110
|
+
|
|
111
|
+
Optional full-page captures (needed by `capture_live_site`,
|
|
112
|
+
`analyze_page_structure`, `record_site_motion`):
|
|
42
113
|
|
|
43
114
|
```bash
|
|
44
115
|
npm install -g playwright && npx playwright install chromium
|
|
45
116
|
```
|
|
46
117
|
|
|
118
|
+
`record_site_motion` additionally uses ffmpeg; it resolves the `ffmpeg-static`
|
|
119
|
+
package automatically if present.
|
|
120
|
+
|
|
47
121
|
## Skills
|
|
48
122
|
|
|
49
|
-
This package ships
|
|
50
|
-
|
|
51
|
-
|
|
123
|
+
This package ships three agent skills. Any agent that follows the
|
|
124
|
+
[Agent Skills standard](https://agentskills.io) can load them; copy them into
|
|
125
|
+
your agent's skills directory:
|
|
52
126
|
|
|
53
127
|
```bash
|
|
54
128
|
npm install awwwards-mcp
|
|
55
|
-
mkdir -p ~/.
|
|
129
|
+
mkdir -p ~/.agents/skills && cp -r node_modules/awwwards-mcp/skills/awwwards-inspiration node_modules/awwwards-mcp/skills/awwwards-doctor node_modules/awwwards-mcp/skills/awwwards-motion-study ~/.agents/skills/
|
|
56
130
|
```
|
|
57
131
|
|
|
58
|
-
|
|
59
|
-
|
|
132
|
+
| Skill | What it teaches |
|
|
133
|
+
|-------|-----------------|
|
|
134
|
+
| `awwwards-inspiration` | The inspiration loop: search, judge from screenshots, pull design DNA, state a design direction, capture/motion-first builds. |
|
|
135
|
+
| `awwwards-motion-study` | The full video chain: what to record from a live site (and what to skip), frame-by-frame review (video input or tile-per-element), the motion inventory, and build verification by re-recording. |
|
|
136
|
+
| `awwwards-doctor` | Repair: run `npm run doctor`, apply its fixes, re-anchor parsers after real awwwards.com drift, recover the in-flight task that surfaced the failure. |
|
|
137
|
+
|
|
138
|
+
| Agent | Skills directory |
|
|
139
|
+
|-------|------------------|
|
|
140
|
+
| Claude Code | `~/.claude/skills/` |
|
|
141
|
+
| pi | `~/.pi/agent/skills/` (also reads `~/.agents/skills/`) |
|
|
142
|
+
| ZCode | `~/.zcode/skills/` |
|
|
143
|
+
| Agent Skills-standard agents | `~/.agents/skills/` |
|
|
144
|
+
|
|
145
|
+
Windows: run this from Git Bash, or copy
|
|
146
|
+
`node_modules\awwwards-mcp\skills\awwwards-inspiration` manually.
|
|
60
147
|
|
|
61
148
|
## Indexing (recommended)
|
|
62
149
|
|
|
@@ -94,6 +181,162 @@ and content remain the property of Awwwards and the credited creators — don't
|
|
|
94
181
|
bulk-scrape, redistribute, or republish them. If you use this commercially,
|
|
95
182
|
review awwwards.com's terms yourself.
|
|
96
183
|
|
|
184
|
+
## Built with awwwards-mcp: three real sites
|
|
185
|
+
|
|
186
|
+
Three complete sites were built through the full inspiration loop this MCP
|
|
187
|
+
enables, using nothing but the server's tools plus the shipped
|
|
188
|
+
`awwwards-inspiration` skill. Each one exercised a different corner of the
|
|
189
|
+
loop — and every correction the loop caught on the way became doctrine in the
|
|
190
|
+
skill.
|
|
191
|
+
|
|
192
|
+
> **Built in one shot, by a model that can't watch video.** All three sites
|
|
193
|
+
> were built in a single prompt run on **GLM 5.3-flash** — which does not
|
|
194
|
+
> support video input. The loop's motion study worked entirely from
|
|
195
|
+
> frame-tiled filmstrips (ffmpeg, 1–2 fps per element) instead of watching
|
|
196
|
+
> the recordings. With a video-native model, those same `get_site_elements`
|
|
197
|
+
> videos and `record_site_motion` .webm files could be watched directly —
|
|
198
|
+
> timing, easing and overlap read at full fidelity — and the motion-true
|
|
199
|
+
> results would be better still. The skill's frame-tile doctrine is what
|
|
200
|
+
> closes that gap today.
|
|
201
|
+
|
|
202
|
+
**1. [Fallow Press](fallow-press/index.html)**
|
|
203
|
+
([source](fallow-press/)) — a flat-2D editorial journal, direction
|
|
204
|
+
**Emergence Magazine** (SOTD): pink `#FF9398` on cream and black, torn-paper
|
|
205
|
+
masthead (pure CSS `clip-path`, zero WebGL), giant grotesque display over
|
|
206
|
+
grayscale photography, serif-italic brand, three pages with **separate
|
|
207
|
+
horizontal** projects/about pages (GSAP ScrollTrigger pin +
|
|
208
|
+
`containerAnimation`).
|
|
209
|
+
|
|
210
|
+
| Torn-paper masthead (home) | Horizontal gallery (Fields) | Horizontal chapters (Practices) |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
|  |  |  |
|
|
213
|
+
|
|
214
|
+
The loop as it ran:
|
|
215
|
+
|
|
216
|
+
1. `search_sites` (magazine filters) → shortlist judged from inline
|
|
217
|
+
screenshots → `get_site_details` on Emergence Magazine.
|
|
218
|
+
2. **Capture before building**: `capture_live_site` + `record_site_motion`
|
|
219
|
+
on the live site *first*; full-page PNG and motion .webm kept in
|
|
220
|
+
[fallow-press/ref-motion/](fallow-press/ref-motion/) as the evidence trail.
|
|
221
|
+
3. Build, then verify: full-page capture plus **panel-center pin shots** of
|
|
222
|
+
both horizontal pages (13 stops each, in
|
|
223
|
+
[fallow-press/_qa/](fallow-press/_qa/) — `capture-qa.mjs` is reusable).
|
|
224
|
+
4. The pin shots caught a real bug: horizontal-panel entrances used
|
|
225
|
+
`toggleActions: "play none none reverse"`, and 100vw panels hide content
|
|
226
|
+
at midpoints on the way back — copy disappeared mid-view. Fix
|
|
227
|
+
(one-shot play entrances) is now doctrine: **full-viewport panels get
|
|
228
|
+
one-shot entrances**; QA pin shots land at panel **centers**, not uniform
|
|
229
|
+
fractions, or you photograph empty transition zones.
|
|
230
|
+
|
|
231
|
+
**2. Cerebrium recreation** (`C:/Users/Afjal/cerebrium-recreation/`) — a
|
|
232
|
+
fidelity-first recreation of cerebrium.ai, pixel-checked against the live
|
|
233
|
+
reference: full-page captures of both sides, `analyze_page_structure` band
|
|
234
|
+
compare, and SVG icon/legend fixes until the build matched the reference to
|
|
235
|
+
within 1px of total page height (10,871px vs 10,870px). This is the
|
|
236
|
+
**structure-before-pixels** doctrine at its strictest — band maps compared,
|
|
237
|
+
never just totals.
|
|
238
|
+
|
|
239
|
+

|
|
240
|
+
|
|
241
|
+
**3. The Meridian** (`C:/Users/Afjal/editorial-site/`) — an editorial journal
|
|
242
|
+
built from ORDR/Hearst references: the first build to run the whole loop
|
|
243
|
+
end-to-end. `analyze_page_structure` caught a masthead band bug by comparing
|
|
244
|
+
the build's band map against the reference's; the reference captures,
|
|
245
|
+
motion film, and the reusable pre-scroll capture script live in
|
|
246
|
+
`editorial-site/_qa/`.
|
|
247
|
+
|
|
248
|
+

|
|
249
|
+
|
|
250
|
+

|
|
251
|
+
|
|
252
|
+
## Skills used to build these
|
|
253
|
+
|
|
254
|
+
| Skill | Role in the builds |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `awwwards-inspiration` | The 8-step loop itself (ships with this package): search → judge from screenshots → design DNA → capture/motion study → state direction → build → band-map verify. |
|
|
257
|
+
| `gsap-scrolltrigger` | The horizontal pin + `containerAnimation` pattern (ease `"none"`, one-shot entrances) driving both Fallow Press horizontal pages. |
|
|
258
|
+
| `gsap-core` / `gsap-timeline` | Tween composition and sequenced hero entrances (torn-paper drop, panel copy rises). |
|
|
259
|
+
| `frontend-design` | Typography, palette and layout judgment applied when translating reference DNA into original pages. |
|
|
260
|
+
| `lenis` (library, via skill guidance) | smooth scrolling synced to ScrollTrigger on the Fallow Press home page. |
|
|
261
|
+
| `tailwindcss` / plain CSS | All builds are plain hand-rolled CSS — flat 2D, no frameworks needed. |
|
|
262
|
+
|
|
263
|
+
Reduced-motion, JS-less visits, and capture tools all get graceful fallbacks
|
|
264
|
+
(vertical stacks; progressive-enhancement reveals).
|
|
265
|
+
|
|
266
|
+
**What the verification loop caught** — proof the structure-before-pixels
|
|
267
|
+
doctrine is load-bearing:
|
|
268
|
+
|
|
269
|
+
- Element **posters lie**: the first showcase build was designed from poster
|
|
270
|
+
frames alone and rendered a spinning 3D ring as *floating static cards*.
|
|
271
|
+
Downloading the element videos (`get_site_elements`) and frame-tiling them
|
|
272
|
+
revealed the motion truth — now the skill mandates studying motion before
|
|
273
|
+
animating.
|
|
274
|
+
- Full-page captures of reveal-on-scroll builds showed blank sections: `.reveal`
|
|
275
|
+
animation state vs capture's no-scroll reality. Builds ship
|
|
276
|
+
content-visible-without-JS progressive enhancement.
|
|
277
|
+
- Horizontal-panel copy vanished **mid-view** on the Fallow Press pages:
|
|
278
|
+
`toggleActions` reverse reverts entrances while a 100vw panel is still
|
|
279
|
+
holding the viewport (see above).
|
|
280
|
+
- Band-map compare kept the references' rhythm instead of drifting on
|
|
281
|
+
section heights (Cerebrium, The Meridian).
|
|
282
|
+
|
|
283
|
+
Prompt counts: **3** for the original showcase build (the build ask, the
|
|
284
|
+
motion correction that exposed the poster-lie, the structure pass) and
|
|
285
|
+
**1** for Fallow Press ("create a new website using our MCP and skills… no
|
|
286
|
+
3D websites") — its two follow-ups were caught by the QA loop, not by the
|
|
287
|
+
user. Each correction became doctrine in the shipped `awwwards-inspiration`
|
|
288
|
+
skill: frame-study element videos before animating; judge page architecture
|
|
289
|
+
from the studied passages; tile per element, not one giant filmstrip;
|
|
290
|
+
capture live sites and animation **before** building.
|
|
291
|
+
|
|
292
|
+
## Can awwwards-mcp crawl the sitemap? (robots.txt notes)
|
|
293
|
+
|
|
294
|
+
The awwwards.com `robots.txt` advertises
|
|
295
|
+
`Sitemap: https://www.awwwards.com/sitemap.xml` and — verified live
|
|
296
|
+
2026-09-18 — **that sitemap URL returns a soft-404 HTML page** (as do common
|
|
297
|
+
child names like `/sitemap-websites.xml`). So sitemap discovery isn't
|
|
298
|
+
currently a path to more data; the polite crawl surface is exactly what the
|
|
299
|
+
indexer uses:
|
|
300
|
+
|
|
301
|
+
- **Allowed and used**: `/websites/`, `/websites/<filter>/`, `/sites/<slug>`
|
|
302
|
+
(one filter per URL; deep pagination stays un-crawled).
|
|
303
|
+
- **Disallowed and never fetched**: `/tag/`, `/search-websites`,
|
|
304
|
+
`/websites/?` (query-string pagination), `/elements/*`, `/vote/`,
|
|
305
|
+
favourites/likes/follows, and the rest of the 33 rules.
|
|
306
|
+
- Our client (`src/awwwards.ts` `buildFilterUrl`) constructs **only**
|
|
307
|
+
`/websites/…` paths at 1 request/second — the loop stays inside the
|
|
308
|
+
published rules by construction, not by convention.
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
311
|
+
|
|
312
|
+
PRs welcome! The project especially needs **parser-drift fixes** — when live
|
|
313
|
+
awwwards.com markup changes, a fresh HTML snapshot attached to an issue often
|
|
314
|
+
becomes the new test fixture and the fastest merged PR. See
|
|
315
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the full guide:
|
|
316
|
+
|
|
317
|
+
**Parser-drift is monitored automatically.** A probe script
|
|
318
|
+
([scripts/parser-drift-probe.mjs](scripts/parser-drift-probe.mjs),
|
|
319
|
+
`npm run drift`) checks every markup anchor the parsers depend on — the
|
|
320
|
+
`split`/`indexOf`/regex literals in `src/parsers.ts` — against the live
|
|
321
|
+
listing and detail pages (2 fetches, 1 request/second, same politeness as
|
|
322
|
+
the client). A daily GitHub Action ([.github/workflows/parser-drift.yml](.github/workflows/parser-drift.yml))
|
|
323
|
+
runs it and, on drift, opens/updates a single tracking issue with the exact
|
|
324
|
+
anchors that changed (and auto-closes it when a later run is green). To run
|
|
325
|
+
it yourself: `npm run drift` (live, exit code 0/1/2) or `npm run drift -- --fixture`
|
|
326
|
+
(offline, checks the committed fixtures still feed every anchor). Raw HTML
|
|
327
|
+
is never diffed or stored — anchors only fire when the parsers actually
|
|
328
|
+
break, so there are no false alarms from cosmetic tweaks.
|
|
329
|
+
|
|
330
|
+
- Development setup & project layout (offline fixture-tested, no network in tests)
|
|
331
|
+
- How to create a PR: fork → `fix/`/`feat/`/`docs/` branch → typecheck + tests → PR template
|
|
332
|
+
- The politeness constraints new code must keep (1 req/s, robots.txt paths, light runtime deps)
|
|
333
|
+
|
|
334
|
+
Bugs and feature ideas start as
|
|
335
|
+
[issues](https://github.com/INSANE0777/Awwwards-mcp/issues/new/choose) with
|
|
336
|
+
templates. Security problems go privately — see
|
|
337
|
+
[SECURITY.md](SECURITY.md). By participating you agree to the
|
|
338
|
+
[Code of Conduct](CODE_OF_CONDUCT.md).
|
|
339
|
+
|
|
97
340
|
## Development
|
|
98
341
|
|
|
99
342
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { Cache } from "./cache.js";
|
|
|
12
12
|
import { createHandlers } from "./server.js";
|
|
13
13
|
import { captureLiveSite } from "./capture.js";
|
|
14
14
|
import { runIndexer, shouldAutoIndex } from "./indexer.js";
|
|
15
|
+
import { checkForUpdate } from "./version-check.js";
|
|
15
16
|
// The handlers return ToolResponse, which is structurally identical to the
|
|
16
17
|
// SDK's CallToolResult at runtime ({ content, isError? }). CallToolResult's
|
|
17
18
|
// schema additionally carries a [k: string]: unknown index signature that a
|
|
@@ -100,4 +101,7 @@ if (shouldAutoIndex(cache)) {
|
|
|
100
101
|
console.error(`awwwards-mcp: background index failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
101
102
|
});
|
|
102
103
|
}
|
|
104
|
+
// Update notice: once a day, compare against the npm registry; stderr-only,
|
|
105
|
+
// never blocks serving. AWWWARDS_AUTO_UPDATE=1 opts into background install.
|
|
106
|
+
checkForUpdate(pkgJson.version);
|
|
103
107
|
await server.connect(new StdioServerTransport());
|
package/dist/parsers.js
CHANGED
|
@@ -148,9 +148,12 @@ export function parseElements(html) {
|
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
150
|
const mediaPath = blob?.collectableImage;
|
|
151
|
-
// Only element media (
|
|
152
|
-
//
|
|
153
|
-
|
|
151
|
+
// Only element media; other blobs (site card, collections) must not leak
|
|
152
|
+
// in if the end bound is missing. Media paths come in two schemes: modern
|
|
153
|
+
// sites store them under element/YYYY/MM/..., 2018-era sites under
|
|
154
|
+
// external/YYYY/MM/... (both verified live 2026-09-18 — the CDN serves
|
|
155
|
+
// both prefixes and their _static.jpeg posters).
|
|
156
|
+
if (typeof mediaPath === "string" && /^(element|external)\//.test(mediaPath)) {
|
|
154
157
|
elements.push({
|
|
155
158
|
title: decodeEntities(String(blob.collectableTitle ?? "")),
|
|
156
159
|
mediaPath,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Update notification: once a day, compare the running version against the
|
|
2
|
+
// npm registry and (optionally) self-update. Designed around the constraints
|
|
3
|
+
// of a stdio MCP server:
|
|
4
|
+
// - stdout is the JSON-RPC channel → every notice goes to stderr only
|
|
5
|
+
// - serving must never wait on this → fire-and-forget, 3s timeout,
|
|
6
|
+
// every failure swallowed (registry unreachable / package unpublished)
|
|
7
|
+
// - at most one registry hit per day per machine (state in the cache dir)
|
|
8
|
+
// Auto-update is strictly opt-in: AWWWARDS_AUTO_UPDATE=1 runs
|
|
9
|
+
// `npm install -g awwwards-mcp@<latest>` detached, then asks the user to
|
|
10
|
+
// restart their agent; without the env var, the notice just tells the agent.
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
const REGISTRY_URL = "https://registry.npmjs.org/awwwards-mcp/latest";
|
|
16
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
17
|
+
export function parseVersion(v) {
|
|
18
|
+
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
19
|
+
if (!m)
|
|
20
|
+
return [0, 0, 0];
|
|
21
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
22
|
+
}
|
|
23
|
+
/** 1 when a > b, -1 when a < b, 0 when equal. Unparseable sorts as oldest. */
|
|
24
|
+
export function compareVersions(a, b) {
|
|
25
|
+
const [pa, pb] = [parseVersion(a), parseVersion(b)];
|
|
26
|
+
for (let i = 0; i < 3; i++) {
|
|
27
|
+
if (pa[i] !== pb[i])
|
|
28
|
+
return pa[i] > pb[i] ? 1 : -1;
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
function stateFile() {
|
|
33
|
+
const cacheRoot = process.env.AWWWARDS_CACHE_DIR ?? join(homedir(), ".awwwards-mcp");
|
|
34
|
+
return join(cacheRoot, "version-check.json");
|
|
35
|
+
}
|
|
36
|
+
function readState() {
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(stateFile()))
|
|
39
|
+
return null;
|
|
40
|
+
return JSON.parse(readFileSync(stateFile(), "utf8"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeState(state) {
|
|
47
|
+
try {
|
|
48
|
+
mkdirSync(join(stateFile(), ".."), { recursive: true });
|
|
49
|
+
writeFileSync(stateFile(), JSON.stringify(state));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// notification is best-effort; an unwritable cache dir is fine
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function buildUpdateNotice(current, latest, autoUpdateEnabled) {
|
|
56
|
+
if (compareVersions(latest, current) <= 0)
|
|
57
|
+
return null;
|
|
58
|
+
const lines = [
|
|
59
|
+
`awwwards-mcp: update available — v${latest} (running v${current}).`,
|
|
60
|
+
autoUpdateEnabled
|
|
61
|
+
? `awwwards-mcp: v${latest} is being installed in the background; restart your agent to load it.`
|
|
62
|
+
: `awwwards-mcp: update with \`npm install -g awwwards-mcp@latest\` (or clear your npx cache), or set AWWWARDS_AUTO_UPDATE=1 to self-update on startup.`,
|
|
63
|
+
];
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fire-and-forget update check. Never throws, never blocks the caller:
|
|
68
|
+
* registry errors, timeouts, and unwritable state files are all swallowed.
|
|
69
|
+
*/
|
|
70
|
+
export function checkForUpdate(currentVersion) {
|
|
71
|
+
const auto = process.env.AWWWARDS_AUTO_UPDATE === "1";
|
|
72
|
+
// Skip the daily gate when auto-update is on but no latest is known yet?
|
|
73
|
+
// No: state gates the fetch itself, so a fresh install always has a shot.
|
|
74
|
+
const state = readState();
|
|
75
|
+
if (state && Date.now() - state.checkedAt < CHECK_INTERVAL_MS) {
|
|
76
|
+
// within the interval: reuse the known latest to (re)warn without fetching
|
|
77
|
+
if (state.latest) {
|
|
78
|
+
const notice = buildUpdateNotice(currentVersion, state.latest, auto);
|
|
79
|
+
if (notice)
|
|
80
|
+
console.error(notice);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
void (async () => {
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch(REGISTRY_URL, {
|
|
87
|
+
signal: AbortSignal.timeout(3000),
|
|
88
|
+
headers: { accept: "application/json" },
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok)
|
|
91
|
+
return; // unpublished / registry hiccup → silent
|
|
92
|
+
const data = (await res.json());
|
|
93
|
+
if (!data?.version)
|
|
94
|
+
return;
|
|
95
|
+
writeState({ checkedAt: Date.now(), latest: data.version });
|
|
96
|
+
const notice = buildUpdateNotice(currentVersion, data.version, auto);
|
|
97
|
+
if (notice)
|
|
98
|
+
console.error(notice);
|
|
99
|
+
if (notice && auto)
|
|
100
|
+
startSelfUpdate(data.version);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// offline / timeout / JSON garbage — never surface, never block
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
}
|
|
107
|
+
function startSelfUpdate(version) {
|
|
108
|
+
try {
|
|
109
|
+
const child = spawn(process.platform === "win32" ? "npm.cmd" : "npm", ["install", "-g", `awwwards-mcp@${version}`], { detached: true, stdio: "ignore" });
|
|
110
|
+
child.on("error", () => {
|
|
111
|
+
console.error(`awwwards-mcp: background self-update failed to start — install manually with \`npm install -g awwwards-mcp@${version}\``);
|
|
112
|
+
});
|
|
113
|
+
child.unref();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// self-update is best-effort; the notice already told the user what to do
|
|
117
|
+
}
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awwwards-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Free MCP server giving AI agents design inspiration from Awwwards: search award-winning sites with inline screenshots and extract design DNA.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
"README.md",
|
|
14
14
|
"skills"
|
|
15
15
|
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"awwwards",
|
|
20
|
+
"design",
|
|
21
|
+
"inspiration",
|
|
22
|
+
"web-design",
|
|
23
|
+
"screenshots",
|
|
24
|
+
"agent-tools"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/INSANE0777/Awwwards-mcp.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/INSANE0777/Awwwards-mcp/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/INSANE0777/Awwwards-mcp#readme",
|
|
16
34
|
"engines": {
|
|
17
35
|
"node": ">=22.13.0"
|
|
18
36
|
},
|
|
@@ -22,6 +40,8 @@
|
|
|
22
40
|
"typecheck": "tsc --noEmit",
|
|
23
41
|
"test": "vitest run",
|
|
24
42
|
"smoke": "tsx test/live-smoke.ts",
|
|
43
|
+
"drift": "node scripts/parser-drift-probe.mjs",
|
|
44
|
+
"doctor": "node scripts/doctor.mjs",
|
|
25
45
|
"prepublishOnly": "npm run build"
|
|
26
46
|
},
|
|
27
47
|
"dependencies": {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: awwwards-doctor
|
|
3
|
+
description: Use when awwwards-mcp scraping or tools stop working — BlockedError, "layout may have changed" parser errors, empty results, failed captures, stale searches — or when the user reports the MCP behaving oddly. Guides running the doctor/drift/index scripts, applying their fixes, re-anchoring parsers after real awwwards.com drift, and recovering the in-flight task that surfaced the failure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Awwwards MCP Doctor
|
|
7
|
+
|
|
8
|
+
You are repairing the awwwards-mcp toolchain or working around it mid-task.
|
|
9
|
+
Work in this order: **diagnose → fix mechanically → fix parsers (if drift) →
|
|
10
|
+
recover the user's task**. Never delete user data; the only destructive step
|
|
11
|
+
(cache DB moved aside) preserves the old file in the OS temp dir.
|
|
12
|
+
|
|
13
|
+
## 1. Run the doctor (repo checkout)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run doctor # diagnose only
|
|
17
|
+
npm run doctor -- --fix # diagnose AND apply available fixes
|
|
18
|
+
npm run doctor -- --json # machine-readable report
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The doctor checks five things and knows how to fix the mechanical ones:
|
|
22
|
+
|
|
23
|
+
| Check | Failure meaning | What --fix does |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| network | awwwards.com blocked/challenged the request | nothing to fix locally — wait, retry later; never retry in a loop |
|
|
26
|
+
| parser-drift | awwwards.com markup changed under the parsers | captures the raw live page into `docs/drift-<timestamp>/` (gitignored) as the fixture for the fix |
|
|
27
|
+
| playwright-chromium / ffmpeg-static | optional capture deps missing or broken | installs them (dev deps + `npx playwright install chromium`) |
|
|
28
|
+
| cache | SQLite DB corrupt/locked, or index older than 30 days | moves an unreadable DB aside (kept in temp) and rebuilds via `npm run index` |
|
|
29
|
+
| boot | `dist/cli.js` doesn't start | `npm run build` |
|
|
30
|
+
|
|
31
|
+
Re-run the doctor after fixing; the verdict must be HEALTHY before you claim
|
|
32
|
+
the MCP is repaired.
|
|
33
|
+
|
|
34
|
+
## 2. Fixing real parser drift (the only code-level failure)
|
|
35
|
+
|
|
36
|
+
If the doctor reports parser-drift, the anchor probes say which parser broke.
|
|
37
|
+
Repair sequence — keep it mechanical, verify at each step:
|
|
38
|
+
|
|
39
|
+
1. **Capture the evidence**: the `--fix` snapshot in `docs/drift-*/` IS the
|
|
40
|
+
new truth. (The daily GitHub Action also opens/updates a `parser-drift`
|
|
41
|
+
tracking issue with the drifted anchor names.)
|
|
42
|
+
2. **Locate the new anchors**: open the captured HTML and find the markup
|
|
43
|
+
that replaced the old anchor (search the old anchor's *purpose*, e.g. the
|
|
44
|
+
card JSON blob, the Elements section heading, the og:image meta).
|
|
45
|
+
3. **Re-anchor the parsers** in `src/parsers.ts` — change the minimum needed
|
|
46
|
+
(one split/indexOf/regex literal), not the surrounding logic.
|
|
47
|
+
4. **Update the probe list** in `scripts/parser-drift-probe.mjs`
|
|
48
|
+
(`PROBE_GROUPS`) so the same failure is detectable next time.
|
|
49
|
+
5. **Refresh the fixture**: download the live page with the same UA the
|
|
50
|
+
client uses and replace `test/fixtures/listing.html` (or the detail
|
|
51
|
+
fixture). The offline tests run against fixtures only — a refreshed
|
|
52
|
+
fixture proves the re-anchor against reality.
|
|
53
|
+
6. `npm run typecheck && npm test` — all tests must pass.
|
|
54
|
+
7. Commit as `fix: re-anchor <parser> after awwwards.com markup change`,
|
|
55
|
+
reference the tracking issue.
|
|
56
|
+
|
|
57
|
+
## 3. End users (installed via npm, not a repo checkout)
|
|
58
|
+
|
|
59
|
+
The doctor scripts live in the repo, so for an npm-installed server:
|
|
60
|
+
|
|
61
|
+
1. Check the version notice: stderr at startup prints when a newer
|
|
62
|
+
`awwwards-mcp` exists. Update with `npm install -g awwwards-mcp@latest`
|
|
63
|
+
(or clear the npx cache) and restart the agent — a surprising share of
|
|
64
|
+
"broken" reports are old parsers fixed in a newer release.
|
|
65
|
+
`AWWWARDS_AUTO_UPDATE=1` in the MCP server env opts into background
|
|
66
|
+
self-update on startup.
|
|
67
|
+
2. Reset local state: `rm -rf ~/.awwwards-mcp` is safe (cache + index +
|
|
68
|
+
version-check state rebuild automatically; nothing user-authored lives
|
|
69
|
+
there).
|
|
70
|
+
3. If it still fails, it's upstream (block or drift): open an issue at the
|
|
71
|
+
awwwards-mcp repo attaching the raw HTML of the failing URL.
|
|
72
|
+
|
|
73
|
+
## 4. Recovering the in-flight task (the reason you noticed)
|
|
74
|
+
|
|
75
|
+
The user's build/research doesn't wait for the repair. Degrade gracefully:
|
|
76
|
+
|
|
77
|
+
- **Stale cache is your friend**: `search_sites`/`get_site_details` serve
|
|
78
|
+
cached data when live fetches fail — say so in the reply ("7-day-old
|
|
79
|
+
cached data").
|
|
80
|
+
- **BlockedError means stop, not retry**: the client never retries through
|
|
81
|
+
blocks; tell the user and continue with cached/alternate data.
|
|
82
|
+
- **Capture tools down** (playwright missing): fall back to
|
|
83
|
+
`get_site_details` screenshots (CDN-served, no browser needed) until
|
|
84
|
+
`npm run doctor -- --fix` restores chromium.
|
|
85
|
+
- **Parser drift mid-task**: results may be partially empty (e.g. details
|
|
86
|
+
without palette). Surface which part is missing; don't silently present
|
|
87
|
+
degraded data as complete.
|
|
88
|
+
- After the MCP is healthy again, **re-run the failed calls** and reconcile
|
|
89
|
+
with whatever the fallback produced.
|
|
90
|
+
|
|
91
|
+
## Anti-patterns
|
|
92
|
+
|
|
93
|
+
- Retrying blocked requests in a loop — politeness rules and block
|
|
94
|
+
detection exist precisely so this doesn't hammer awwwards.com.
|
|
95
|
+
- Diffing raw HTML to detect drift — cosmetic page changes would false-alarm
|
|
96
|
+
daily; only parser-anchor probes matter.
|
|
97
|
+
- "Fixing" parsers by loosening them to return empty results quietly — a
|
|
98
|
+
parse that can't find its anchors must error loudly (that is the drift
|
|
99
|
+
signal).
|
|
100
|
+
- Treating the doctor's UNHEALTHY verdict as done because one check passed.
|
|
@@ -44,14 +44,27 @@ Run this loop before building anything visual:
|
|
|
44
44
|
reports a layout-drift error, fall back to judging the shortlisted
|
|
45
45
|
screenshots, `get_site_elements` (which uses a different parser), or a
|
|
46
46
|
`capture_live_site` of the site's URL for a first-hand full-page view.
|
|
47
|
-
6. **
|
|
48
|
-
on shortlisted sites
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
6. **Study MOTION before building (element videos, not posters).** Call
|
|
48
|
+
`get_site_elements` on shortlisted sites — it returns per-element video
|
|
49
|
+
URLs (preloader, page transition, case study, about…). **Download those
|
|
50
|
+
videos and tile them at 1–2 fps BEFORE writing any animation code**:
|
|
51
|
+
`curl -o e.mp4 <video-url> && ffmpeg -i e.mp4 -vf "fps=2,scale=480:-1,tile=6x4" -frames:v 1 tile-e.jpg`,
|
|
52
|
+
then Read the tile. (The full capture→review→build procedure — what to record, what to skip, motion inventories, build verification — is the `awwwards-motion-study` skill.) Element **posters are single frames** — they show
|
|
53
|
+
layout, not motion; a 3D carousel looks like floating static cards in a
|
|
54
|
+
poster (this exact mis-build happened). One tile per element shows you the
|
|
55
|
+
whole animation arc (easing, overlap, entrance order). If a marquee
|
|
56
|
+
animation needs finer study, re-tile that video at higher fps. The motion
|
|
57
|
+
IS the design: every reference animation you ship must trace to frames
|
|
58
|
+
you actually studied, and reference videos pass through
|
|
59
|
+
`showcase/ref-motion/` as the design's evidence trail.
|
|
52
60
|
7. **State the design direction before writing code.** In prose: palette
|
|
53
|
-
(hexes from the references), type mood, layout patterns,
|
|
54
|
-
|
|
61
|
+
(hexes from the references), type mood, layout patterns, page
|
|
62
|
+
architecture (one page vs separate horizontal sections — judge this from
|
|
63
|
+
the reference passages you studied in steps 4–6, not habit), and tech
|
|
64
|
+
choices, each traceable to a reference. Then build. For a site's own
|
|
65
|
+
marquee frontend skills, prefer proven patterns (GSAP ScrollTrigger for
|
|
66
|
+
horizontal scroll chapters, Lenis for smooth scroll) over hand-rolled
|
|
67
|
+
scroll math.
|
|
55
68
|
8. **Verify structure, then polish.** After building, capture your own build
|
|
56
69
|
full-page (`capture_live_site` on its `file://` or served URL) and run
|
|
57
70
|
`analyze_page_structure` on BOTH the reference and the build. Compare band
|
|
@@ -69,8 +82,14 @@ Run this loop before building anything visual:
|
|
|
69
82
|
- **Dumping raw tool output at the user** — curate: show the shortlist, the
|
|
70
83
|
chosen direction, and why.
|
|
71
84
|
- **Designing from a thumbnail or element poster** — thumbnails are hero-only
|
|
72
|
-
crops
|
|
73
|
-
|
|
85
|
+
crops, posters are single video frames, and neither shows real structure OR
|
|
86
|
+
motion. When the reference URL is known, capture it full-page (step 4);
|
|
87
|
+
when an element has motion, tile its video (step 6) — never animate from
|
|
88
|
+
posters.
|
|
89
|
+
- **One filmstrip for everything** — a single N×N tiling of the *whole
|
|
90
|
+
recording* makes late tiles tiny and animation arcs illegible. Tile per
|
|
91
|
+
element/per passage at 1–2 fps instead; re-tile finer passages at higher
|
|
92
|
+
fps when easing or overlap matters.
|
|
74
93
|
- **Padding empty bands to match total height** — if your build's total height
|
|
75
94
|
matches the reference but a spacer/background band is far taller than the
|
|
76
95
|
reference's equivalent, the height was stolen from real content sections.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: awwwards-motion-study
|
|
3
|
+
description: Use when building a site whose reference has animation — before writing any motion code, and again when verifying a build. Teaches what to record from a live site (and what to skip), how to study the recordings frame-by-frame (video input or ffmpeg tile-per-element), how to turn the study into a motion inventory, and how to verify the build's motion against the reference. Complements awwwards-inspiration (step 6) with the full capture→review→build procedure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Awwwards Motion Study — capture, review, build
|
|
7
|
+
|
|
8
|
+
Motion is design: an award-site hero that "looks the same" in a screenshot
|
|
9
|
+
can be a spinning 3D ring, a scrubbed pin, or a staggered reveal. Static
|
|
10
|
+
captures hide it; element posters (single frames) lie about it. This skill
|
|
11
|
+
covers the whole chain: **record → review → inventory → build → re-record**.
|
|
12
|
+
|
|
13
|
+
The one rule everything serves: **never write animation code from memory or
|
|
14
|
+
posters — every shipped animation must trace to frames you actually studied.**
|
|
15
|
+
|
|
16
|
+
## 1. What to capture
|
|
17
|
+
|
|
18
|
+
Record a **motion-through pass**: load, dwell, slow scroll, hover tour.
|
|
19
|
+
Three animation classes must all be on camera:
|
|
20
|
+
|
|
21
|
+
| Class | How it gets captured | Typical duration |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Preloader / entrance | initial dwell after `load` | 5–8 s |
|
|
24
|
+
| Scroll-triggered + pinned/scrubbed | slow stepped scroll (~450 px / ~600 ms) | length of page |
|
|
25
|
+
| Hover / click / micro-interactions | virtual cursor visits interactive elements and dwells | 10–20 s |
|
|
26
|
+
|
|
27
|
+
**Capture these** — they are design:
|
|
28
|
+
|
|
29
|
+
- Preloader/entrance sequences and hero reveals
|
|
30
|
+
- Scroll-triggered reveals, parallax, horizontal pins, scrubbed timelines
|
|
31
|
+
- Hover states (cards, nav, buttons, image rollovers) and click effects
|
|
32
|
+
- Page transitions if the site has them, sticky/nav behaviors on scroll
|
|
33
|
+
- Marquees, tickers, counters — anything time-driven
|
|
34
|
+
|
|
35
|
+
**Skip these** — they are content, noise, or traps:
|
|
36
|
+
|
|
37
|
+
- **Content videos** (film hero backgrounds, stock footage, showreels):
|
|
38
|
+
they're the site's *media*, not its *interaction* — you'd be styling
|
|
39
|
+
someone's footage, not learning the motion design.
|
|
40
|
+
- **Loading states**: capture *after* they resolve. A half-loaded page
|
|
41
|
+
records lazy placeholders as if they were the design. Dwell first, then
|
|
42
|
+
scroll; if a section is still empty in the recording, it's a capture bug,
|
|
43
|
+
not a design feature.
|
|
44
|
+
- Cookie banners / newsletter modals: dismiss them early (unless you're
|
|
45
|
+
specifically building one) or they eat the first seconds of footage.
|
|
46
|
+
- Ads, tracking iframes, user-generated widgets.
|
|
47
|
+
- **Don't redistribute recordings**: captures are local study artifacts
|
|
48
|
+
(keep them in `ref-motion/` or `_qa/`). Awwwards content and site media
|
|
49
|
+
belong to their creators — study them, don't republish them.
|
|
50
|
+
|
|
51
|
+
## 2. How to capture
|
|
52
|
+
|
|
53
|
+
Fast path first, fall back when it can't:
|
|
54
|
+
|
|
55
|
+
1. **`record_site_motion`** (MCP tool): inline filmstrip + saved .webm path.
|
|
56
|
+
Best default. Known ceiling: ~30 s — heavy sites may time out.
|
|
57
|
+
2. **`scripts/record-scrollthrough.mjs`** (repo checkout, playwright):
|
|
58
|
+
`node scripts/record-scrollthrough.mjs <url> <outDir> <name>.webm` —
|
|
59
|
+
full-control recorder: 7 s preloader dwell, 450 px/600 ms stepped scroll,
|
|
60
|
+
injected virtual cursor (playwright video doesn't render the real one)
|
|
61
|
+
that visits and dwells on interactive elements. Use when the MCP tool
|
|
62
|
+
times out or when you need to customize the tour.
|
|
63
|
+
3. **Direct playwright**: `chromium.launch()` + `newContext({ recordVideo })`
|
|
64
|
+
when neither fits (custom viewports, login flows, multi-page tours).
|
|
65
|
+
|
|
66
|
+
Capture rules that prevent re-shoots:
|
|
67
|
+
|
|
68
|
+
- **Capture BEFORE building** — the doctrine step. Review first, code second.
|
|
69
|
+
- Use a **consistent viewport** (1440×900 matches the QA scripts) so your
|
|
70
|
+
reference tiles and build tiles are comparable.
|
|
71
|
+
- **Pre-scroll** to fire lazy content, scroll back to top, then record —
|
|
72
|
+
otherwise reveal-on-scroll sections record as blank boxes.
|
|
73
|
+
- For multi-page sites, record **each page** you'll rebuild (home, projects,
|
|
74
|
+
about), not just the hero.
|
|
75
|
+
- On the 30 s MCP ceiling: fall back to the script; never shorten the scroll
|
|
76
|
+
so much that sections get skipped.
|
|
77
|
+
|
|
78
|
+
## 3. How to review the video
|
|
79
|
+
|
|
80
|
+
Posters are frames; you need frames **in sequence**. Two paths:
|
|
81
|
+
|
|
82
|
+
**Path A — direct video input.** `Read` the .webm first. If your model
|
|
83
|
+
supports video, watching the pass gives timing, easing, and transitions no
|
|
84
|
+
filmstrip can. If the read comes back "media omitted / not supported",
|
|
85
|
+
use Path B.
|
|
86
|
+
|
|
87
|
+
**Path B — tile per element at 1–2 fps.** One tiling *per element/passage*,
|
|
88
|
+
never one mega-strip of the whole recording (late tiles go thumbnail-size
|
|
89
|
+
and easing becomes unreadable — a mis-build happened exactly this way):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ffmpeg -i reference.webm -vf "fps=2,scale=480:-1,tile=6x4" -frames:v 1 tile-hero.jpg
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Cut the video into per-element segments first (by timestamp from watching
|
|
96
|
+
the strip), then tile each segment. **Re-tile finer at higher fps** when
|
|
97
|
+
easing or overlap matters: `fps=6` on a 3 s passage shows whether an ease
|
|
98
|
+
is `power2.out` vs `expo.out`; overlap ordering needs ~200 ms granularity.
|
|
99
|
+
|
|
100
|
+
While reading tiles, extract per element:
|
|
101
|
+
|
|
102
|
+
| Field | What to look for |
|
|
103
|
+
|---|---|
|
|
104
|
+
| trigger | load / scroll-enter / scroll-scrub / hover / click |
|
|
105
|
+
| transform | opacity, y/x, scale, clip-path, blur — which properties move |
|
|
106
|
+
| duration + easing | count frames between first and last movement; fast-out = expo/power4, soft = power2, linear = scrub |
|
|
107
|
+
| stagger | siblings entering in sequence → measure the delay between them |
|
|
108
|
+
| overlap | does the next element start before the previous ends? |
|
|
109
|
+
|
|
110
|
+
Write the result as a **motion inventory** — a table per page — and build
|
|
111
|
+
from the table, not from impressions:
|
|
112
|
+
|
|
113
|
+
```markdown
|
|
114
|
+
| Element | Trigger | Transform | Duration | Easing | Overlap |
|
|
115
|
+
|---|---|---|---|---|---|
|
|
116
|
+
| Hero paper | load | yPercent -104→0 | ~1.2 s | power4.out | photo fades under at -1.0 s |
|
|
117
|
+
| Story cards | scroll 86% | opacity + y 56 | ~0.85 s | power3.out | stagger 0.09 |
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 4. Build from the inventory
|
|
121
|
+
|
|
122
|
+
- Every shipped animation cites an inventory row. If you can't name the
|
|
123
|
+
frames an animation came from, you're inventing — stop and study.
|
|
124
|
+
- Use proven motion primitives, not hand-rolled scroll math: GSAP
|
|
125
|
+
ScrollTrigger (pins, `containerAnimation` with `ease: "none"` for
|
|
126
|
+
horizontal, `scrub` for scrubbed timelines), Lenis for smooth scroll.
|
|
127
|
+
- Full-viewport horizontal panels get **one-shot entrances**
|
|
128
|
+
(`toggleActions: "play none none none"`) — reverse-on-leave hides copy
|
|
129
|
+
mid-view (caught by QA pin shots).
|
|
130
|
+
- Ship `prefers-reduced-motion` fallbacks and progressive enhancement
|
|
131
|
+
(content visible without JS) — a capture tool or JS-less visit must never
|
|
132
|
+
see blank sections.
|
|
133
|
+
|
|
134
|
+
## 5. Close the loop: re-record your build
|
|
135
|
+
|
|
136
|
+
Verification is the same skill pointed at yourself:
|
|
137
|
+
|
|
138
|
+
1. Record your build exactly as you recorded the reference (same viewport,
|
|
139
|
+
same pass shape — `record_site_motion` works on `file://` URLs too).
|
|
140
|
+
2. Tile your build's key passages and **compare tile-to-tile** against the
|
|
141
|
+
reference tiles: same trigger order? similar durations? easing in the
|
|
142
|
+
same family?
|
|
143
|
+
3. QA captures for scroll builds must land at **panel/section centers**, not
|
|
144
|
+
uniform scroll fractions — mid-transition shots photograph empty
|
|
145
|
+
transition zones and look broken when they aren't.
|
|
146
|
+
4. Fix what mismatches, re-record, repeat — one loop, not ten.
|
|
147
|
+
|
|
148
|
+
## Anti-patterns
|
|
149
|
+
|
|
150
|
+
- **Designing from posters or thumbnails** — posters are single frames; the
|
|
151
|
+
3D carousel reads as "floating static cards" (a real mis-build).
|
|
152
|
+
- **One filmstrip for the whole recording** — element-level detail dies in
|
|
153
|
+
an N×N mega-grid.
|
|
154
|
+
- **Capturing only the hero** — the distinctive motion usually lives below
|
|
155
|
+
the fold.
|
|
156
|
+
- **Recording over a half-loaded page** — lazy placeholders captured as
|
|
157
|
+
design.
|
|
158
|
+
- **Studying nothing, animating from vibes** — "it probably fades in" is
|
|
159
|
+
how builds drift from references.
|
|
160
|
+
- **Republishing recordings** — captures are local study evidence.
|