@yojahny/wp-design-library 0.3.0 → 0.5.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.
Files changed (186) hide show
  1. package/.env.example +7 -0
  2. package/README.md +52 -9
  3. package/bin/library.mjs +11 -0
  4. package/entries/ada/entry.md +55 -0
  5. package/entries/ada/strip.webp +0 -0
  6. package/entries/air/captures/2/measure.json +325 -0
  7. package/entries/air/captures/2/strip.webp +0 -0
  8. package/entries/air/entry.md +62 -0
  9. package/entries/air/strip.webp +0 -0
  10. package/entries/ais-community-dark-depth/strip.webp +0 -0
  11. package/entries/apple-home/captures/2/measure.json +91 -0
  12. package/entries/apple-home/captures/2/strip.webp +0 -0
  13. package/entries/apple-home/entry.md +64 -0
  14. package/entries/apple-home/strip.webp +0 -0
  15. package/entries/apple-macbook-neo/captures/2/measure.json +570 -0
  16. package/entries/apple-macbook-neo/captures/2/strip.webp +0 -0
  17. package/entries/apple-macbook-neo/entry.md +62 -0
  18. package/entries/apple-macbook-neo/strip.webp +0 -0
  19. package/entries/apple-watch-ultra-4/entry.md +63 -0
  20. package/entries/apple-watch-ultra-4/measure.json +481 -0
  21. package/entries/apple-watch-ultra-4/strip.webp +0 -0
  22. package/entries/bpco/captures/2/strip.webp +0 -0
  23. package/entries/bpco/captures/3/strip.webp +0 -0
  24. package/entries/bpco/entry.md +81 -0
  25. package/entries/bpco/strip.webp +0 -0
  26. package/entries/column/captures/2/measure.json +996 -0
  27. package/entries/column/captures/2/strip.webp +0 -0
  28. package/entries/column/entry.md +82 -0
  29. package/entries/column/strip.webp +0 -0
  30. package/entries/cthdrl/captures/2/measure.json +251 -0
  31. package/entries/cthdrl/captures/2/strip.webp +0 -0
  32. package/entries/cthdrl/entry.md +86 -0
  33. package/entries/cthdrl/strip.webp +0 -0
  34. package/entries/dala-craftedbygc/entry.md +69 -0
  35. package/entries/dala-craftedbygc/strip.webp +0 -0
  36. package/entries/dock/captures/2/measure.json +206 -0
  37. package/entries/dock/captures/2/strip.webp +0 -0
  38. package/entries/dock/entry.md +62 -0
  39. package/entries/dock/strip.webp +0 -0
  40. package/entries/dub/captures/2/measure.json +37 -0
  41. package/entries/dub/captures/2/strip.webp +0 -0
  42. package/entries/dub/entry.md +61 -0
  43. package/entries/dub/strip.webp +0 -0
  44. package/entries/duolingo/captures/2/measure.json +138 -0
  45. package/entries/duolingo/captures/2/strip.webp +0 -0
  46. package/entries/duolingo/entry.md +65 -0
  47. package/entries/duolingo/strip.webp +0 -0
  48. package/entries/elevenlabs/captures/2/measure.json +160 -0
  49. package/entries/elevenlabs/captures/2/strip.webp +0 -0
  50. package/entries/elevenlabs/entry.md +62 -0
  51. package/entries/elevenlabs/strip.webp +0 -0
  52. package/entries/flying-papers/captures/2/measure.json +291 -0
  53. package/entries/flying-papers/captures/2/strip.webp +0 -0
  54. package/entries/flying-papers/entry.md +61 -0
  55. package/entries/flying-papers/strip.webp +0 -0
  56. package/entries/gsap/captures/2/measure.json +120 -0
  57. package/entries/gsap/captures/2/strip.webp +0 -0
  58. package/entries/gsap/entry.md +62 -0
  59. package/entries/gsap/strip.webp +0 -0
  60. package/entries/letters/captures/2/measure.json +20 -0
  61. package/entries/letters/captures/2/strip.webp +0 -0
  62. package/entries/letters/entry.md +62 -0
  63. package/entries/letters/strip.webp +0 -0
  64. package/entries/micro/captures/2/measure.json +852 -0
  65. package/entries/micro/captures/2/strip.webp +0 -0
  66. package/entries/micro/entry.md +61 -0
  67. package/entries/micro/strip.webp +0 -0
  68. package/entries/monopo-saigon/captures/2/measure.json +145 -0
  69. package/entries/monopo-saigon/captures/2/strip.webp +0 -0
  70. package/entries/monopo-saigon/entry.md +64 -0
  71. package/entries/monopo-saigon/strip.webp +0 -0
  72. package/entries/notion/captures/2/measure.json +338 -0
  73. package/entries/notion/captures/2/strip.webp +0 -0
  74. package/entries/notion/entry.md +66 -0
  75. package/entries/notion/strip.webp +0 -0
  76. package/entries/origin-financial/captures/2/measure.json +169 -0
  77. package/entries/origin-financial/captures/2/strip.webp +0 -0
  78. package/entries/origin-financial/entry.md +65 -0
  79. package/entries/origin-financial/strip.webp +0 -0
  80. package/entries/oryzo-ai/captures/2/measure.json +39 -0
  81. package/entries/oryzo-ai/captures/2/strip.webp +0 -0
  82. package/entries/oryzo-ai/entry.md +76 -0
  83. package/entries/oryzo-ai/strip.webp +0 -0
  84. package/entries/silencio/captures/2/strip.webp +0 -0
  85. package/entries/silencio/entry.md +114 -0
  86. package/entries/silencio/motion/product-story/frame-0.webp +0 -0
  87. package/entries/silencio/motion/product-story/frame-1.webp +0 -0
  88. package/entries/silencio/motion/product-story/frame-2.webp +0 -0
  89. package/entries/silencio/motion/product-story/frame-3.webp +0 -0
  90. package/entries/silencio/motion/product-story/frame-4.webp +0 -0
  91. package/entries/silencio/motion/product-story/frame-5.webp +0 -0
  92. package/entries/silencio/motion/product-story/frame-6.webp +0 -0
  93. package/entries/silencio/motion/product-story/frame-7.webp +0 -0
  94. package/entries/silencio/strip.webp +0 -0
  95. package/entries/steep/captures/2/measure.json +20 -0
  96. package/entries/steep/captures/2/strip.webp +0 -0
  97. package/entries/steep/entry.md +63 -0
  98. package/entries/steep/strip.webp +0 -0
  99. package/entries/stripe/captures/2/measure.json +892 -0
  100. package/entries/stripe/captures/2/strip.webp +0 -0
  101. package/entries/stripe/entry.md +61 -0
  102. package/entries/stripe/strip.webp +0 -0
  103. package/entries/superpower/captures/2/measure.json +179 -0
  104. package/entries/superpower/captures/2/strip.webp +0 -0
  105. package/entries/superpower/entry.md +64 -0
  106. package/entries/superpower/strip.webp +0 -0
  107. package/entries/superr/entry.md +54 -0
  108. package/entries/superr/strip.webp +0 -0
  109. package/entries/svz-weare/captures/2/measure.json +129 -0
  110. package/entries/svz-weare/captures/2/strip.webp +0 -0
  111. package/entries/svz-weare/entry.md +147 -0
  112. package/entries/svz-weare/motion/hero-transition/frame-0.webp +0 -0
  113. package/entries/svz-weare/motion/hero-transition/frame-1.webp +0 -0
  114. package/entries/svz-weare/motion/hero-transition/frame-2.webp +0 -0
  115. package/entries/svz-weare/motion/hero-transition/frame-3.webp +0 -0
  116. package/entries/svz-weare/motion/hero-transition/frame-4.webp +0 -0
  117. package/entries/svz-weare/motion/hero-transition/frame-5.webp +0 -0
  118. package/entries/svz-weare/motion/hero-transition/frame-6.webp +0 -0
  119. package/entries/svz-weare/motion/hero-transition/frame-7.webp +0 -0
  120. package/entries/svz-weare/strip.webp +0 -0
  121. package/entries/thoughtlab/captures/2/measure.json +93 -0
  122. package/entries/thoughtlab/captures/2/strip.webp +0 -0
  123. package/entries/thoughtlab/entry.md +62 -0
  124. package/entries/thoughtlab/strip.webp +0 -0
  125. package/entries/ventriloc/entry.md +54 -0
  126. package/entries/ventriloc/strip.webp +0 -0
  127. package/entries/vivid-and-co/entry.md +58 -0
  128. package/entries/vivid-and-co/strip.webp +0 -0
  129. package/package.json +24 -5
  130. package/src/cli/add.mjs +100 -24
  131. package/src/cli/motion.mjs +168 -0
  132. package/src/cli/refresh.mjs +6 -0
  133. package/src/cli/ui.mjs +2 -1
  134. package/src/entry.mjs +14 -1
  135. package/src/index/build.mjs +9 -3
  136. package/src/index/embed.mjs +1 -1
  137. package/src/ingest/agreement.mjs +33 -0
  138. package/src/ingest/attach.mjs +67 -0
  139. package/src/ingest/draft.mjs +5 -2
  140. package/src/ingest/frames.mjs +20 -6
  141. package/src/ingest/measure.mjs +34 -1
  142. package/src/ingest/observed.mjs +29 -0
  143. package/src/mcp/http.mjs +1 -1
  144. package/src/mcp/instructions.mjs +2 -1
  145. package/src/mcp/prompts.mjs +12 -1
  146. package/src/mcp/resources.mjs +14 -0
  147. package/src/mcp/tools.mjs +48 -5
  148. package/src/motion.mjs +94 -0
  149. package/src/ui/build.mjs +83 -7
  150. package/src/ui/serve.mjs +29 -4
  151. package/src/ui/templates/entry.html +9 -1
  152. package/entries/ais-community-dark-depth/strip.png +0 -0
  153. package/tests/README.md +0 -33
  154. package/tests/checks/cli-check.sh +0 -13
  155. package/tests/checks/docker.sh +0 -10
  156. package/tests/checks/entry-detail.sh +0 -52
  157. package/tests/checks/entry.sh +0 -55
  158. package/tests/checks/export.sh +0 -16
  159. package/tests/checks/fetch-on-start.sh +0 -26
  160. package/tests/checks/frames-dense.sh +0 -27
  161. package/tests/checks/http.sh +0 -22
  162. package/tests/checks/hygiene.sh +0 -29
  163. package/tests/checks/inbox.sh +0 -27
  164. package/tests/checks/index-degrade.sh +0 -32
  165. package/tests/checks/index.sh +0 -36
  166. package/tests/checks/ingest-mp4.sh +0 -47
  167. package/tests/checks/ingest.sh +0 -39
  168. package/tests/checks/instructions.sh +0 -31
  169. package/tests/checks/licence-gate.sh +0 -24
  170. package/tests/checks/mcp-stdout.sh +0 -31
  171. package/tests/checks/measure.sh +0 -79
  172. package/tests/checks/minors.sh +0 -84
  173. package/tests/checks/optional-deps.sh +0 -55
  174. package/tests/checks/prompt-add-entry.sh +0 -28
  175. package/tests/checks/recommend.sh +0 -107
  176. package/tests/checks/resources.sh +0 -47
  177. package/tests/checks/rrf.sh +0 -120
  178. package/tests/checks/seed-sync.sh +0 -22
  179. package/tests/checks/similar.sh +0 -62
  180. package/tests/checks/ui-build.sh +0 -59
  181. package/tests/checks/vocab.sh +0 -30
  182. package/tests/fixtures/entry-ok/entry.md +0 -41
  183. package/tests/fixtures/entry-ok/strip.png +0 -0
  184. package/tests/fixtures/page/index.html +0 -47
  185. package/tests/fixtures/three-frame.webp +0 -0
  186. package/tests/run.sh +0 -16
package/.env.example CHANGED
@@ -5,3 +5,10 @@ LIBRARY_FETCH_MODELS=0 # 1 = fetch the vector-search models on server start (
5
5
  LIBRARY_MODELS=/data/models # where the models land on the /data volume
6
6
  LIBRARY_EMBED= # empty = real models when present, stub = deterministic vectors for checks, off = FTS only
7
7
  CHROME_SANDBOX=0 # 1 = let Chrome use its own sandbox; only where user namespaces are allowed (not the default container)
8
+
9
+ # Internal motion publisher only; never used by the public UI or MCP readers.
10
+ R2_ACCOUNT_ID=
11
+ R2_ACCESS_KEY_ID=
12
+ R2_SECRET_ACCESS_KEY=
13
+ R2_BUCKET=
14
+ R2_PUBLIC_BASE_URL=
package/README.md CHANGED
@@ -72,7 +72,7 @@ node bin/library.mjs add ~/Desktop/site.webp \
72
72
  | `--license` | free text, required; say what you may do with it |
73
73
  | `--force` | overwrite an existing draft (otherwise a second `add` is refused) |
74
74
 
75
- This writes `entries/<slug>/strip.png` (6–12 sampled frames) and a draft
75
+ This writes `entries/<slug>/strip.webp` (6–12 sampled frames) and a draft
76
76
  `entries/<slug>/entry.md` with the mechanical fields filled and every
77
77
  judgement field blank. The output lists those blanks:
78
78
 
@@ -102,7 +102,7 @@ CHROME_PATH=/usr/bin/google-chrome node bin/library.mjs add https://acme.example
102
102
 
103
103
  | Flag | Meaning |
104
104
  |---|---|
105
- | `--record` | also scroll top to bottom in 12 steps at 1440 and build `strip.png` from those stops (`captured.method: both`); without it, `strip.png` is the single 1440 screenshot and `captured.method: measured` |
105
+ | `--record` | also scroll top to bottom in 12 steps at 1440 and build `strip.webp` from those stops (`captured.method: both`); without it, `strip.webp` is the single 1440 screenshot and `captured.method: measured` |
106
106
 
107
107
  `CHROME_PATH` must point at a Chrome or Chromium binary. Without it (or with a path
108
108
  that does not exist) `add` refuses with `chrome not available: …` and nothing is
@@ -118,7 +118,7 @@ node bin/library.mjs refresh acme-live-hero
118
118
 
119
119
  Re-measures `source.url` with the same system Chrome, rewrites `measure.json` and
120
120
  `captured.at`, and reindexes. Every judgement field (`roles`, `feel`, `palette`,
121
- `type`, `motion`, `body`…) and `strip.png` are left exactly as they were. Refuses an
121
+ `type`, `motion`, `body`…) and `strip.webp` are left exactly as they were. Refuses an
122
122
  entry with no `source.url`, or one whose `captured.method` is `frames` (nothing came
123
123
  from a URL to re-measure). Over MCP, the same as the `refresh` tool: `{ slug }`.
124
124
 
@@ -235,6 +235,12 @@ A ported entry adds `ported/section.html`, `ported/section.css`, a README, and
235
235
  `ported_from: <inspiration slug>`. `save` and `index` refuse `tier: ported` on
236
236
  a `paid` or `public-site` source, and there is no override flag.
237
237
 
238
+ ## Motion previews
239
+
240
+ Entries can include short motion clips with timestamped frames. Entry pages load videos only when played; gallery cards keep their thumbnails. The MCP `get_motion` tool returns observed motion notes, a video URL when published, and timestamped images that agents can inspect without video support. Pass `include_frames: false` for metadata only. Clip titles and notes are included in search and text embeddings.
241
+
242
+ Preparing clips and publishing them to R2 are repository maintenance tasks. See the [internal motion workflow](https://github.com/yojahny55/wp-design-library/blob/main/docs/internal/motion.md) for setup and review steps.
243
+
238
244
  ## Searching
239
245
 
240
246
  The plugin does this for you, but from the CLI:
@@ -247,7 +253,8 @@ node bin/library.mjs check # validate every entry, 1 line per problem
247
253
  Over MCP, seven tools: `search` (keyword + facet filters, aliases resolved),
248
254
  `get_entry` (pass `detail: "summary"` for just the body's first paragraph, instead of
249
255
  the default `"full"`), `similar`, `recommend` (turns a brief into a per-role reference
250
- set in one call), `add`, `refresh`, `save_entry`. `search` and `similar` carry
256
+ set in one call), `add` (pass `attach` to add a second capture — a video beside a live
257
+ URL — to an existing entry instead of refusing it), `refresh`, `save_entry`. `search` and `similar` carry
251
258
  `arms` (which retrieval arms ran; `["fts"]` in Phase 1); any tool may instead respond
252
259
  with `refused` and the reason.
253
260
 
@@ -377,10 +384,11 @@ root only to `chown` an existing (possibly root-owned) `/data` volume, then drop
377
384
 
378
385
  ## Releasing
379
386
 
380
- 1. Bump `version` in `package.json`.
381
- 2. Tag the release: `git tag vX.Y.Z` (the workflow triggers on any `v*` tag).
382
- 3. Push the tag: `git push origin vX.Y.Z`.
383
- 4. `.github/workflows/publish.yml` runs on that push: checks out, installs with
387
+ 1. Add the release's entry to `CHANGELOG.md`.
388
+ 2. Bump `version` in `package.json`.
389
+ 3. Tag the release: `git tag vX.Y.Z` (the workflow triggers on any `v*` tag).
390
+ 4. Push the tag: `git push origin vX.Y.Z`.
391
+ 5. `.github/workflows/publish.yml` runs on that push: checks out, installs with
384
392
  `npm ci`, runs `npm test`, then `npm publish`. Trusted publishing means no npm
385
393
  token lives in this repo — the registry trusts the GitHub Actions run itself via
386
394
  OIDC (`id-token: write`).
@@ -407,6 +415,13 @@ without breaking this workflow, since it never uses a token.
407
415
 
408
416
  ## Known ceilings
409
417
 
418
+ - **A strip is a 2400px WebP, and the full-size sheet is not kept.** A six-column
419
+ contact sheet of 1440-wide frames is 9600x6000; at that size 56 of them are 699 MB
420
+ and npm cannot build a tarball from them at all. 2400px at quality 80 leaves about
421
+ 400px per frame — a headline and a nav stay readable, body copy does not, and
422
+ 1800px loses the headline too. What a session reads off a strip is therefore the
423
+ composition, the palette and the shape of the type, not its small print; the source
424
+ frames are gone, so a re-capture is the only way back to full resolution.
410
425
  - **Chrome runs without its own sandbox inside the container.** Docker's default
411
426
  seccomp profile blocks the user namespaces Chrome's sandbox needs, so
412
427
  `chromiumSandbox: true` would fail to launch there; the non-root container user is
@@ -431,12 +446,40 @@ without breaking this workflow, since it never uses a token.
431
446
  - **`detail: "summary"` skips leading headings and takes the first prose paragraph.** An
432
447
  entry whose opening paragraph says nothing useful summarises badly. The fix is a better
433
448
  entry, not a cleverer splitter.
449
+ - **Dominant colour measures screen time, not design intent.** A strip is frames stitched
450
+ together, so a colour filling eight seconds of an 8.4-second video dominates whether or not a
451
+ viewer would call it the palette. `agreement.palette.observed` is a pixel-frequency histogram.
452
+ - **"Address the deltas" is prose.** A check can assert the `## Sources` section exists, never
453
+ that it is honest. The same ceiling every contract here carries.
454
+ - **`agreement` records what was measured at attach time.** If the session later hand-edits
455
+ `palette`, `agreement.palette.declared` goes stale: it is a record of the capture, not of the
456
+ entry's current claim. `observed` goes stale the same way after a re-capture: it is recomputed
457
+ only on attach, never by `refresh`, which rewrites `measure.json` and bumps `captured.at` while
458
+ leaving the whole block alone. And `captured.method` has no writer but attach, so hand-removing
459
+ a capture leaves `method: both` on an entry that now holds only frames captures — the one
460
+ staleness that changes behaviour rather than only a record, since `method` is what `refresh`
461
+ gates on.
462
+ - **A frames capture cannot be refreshed.** `refresh` still only re-measures a URL; a newer
463
+ video is attached, not refreshed. It also refuses outright once an entry has more than one
464
+ capture: it writes into the entry root, capture 1's slot, and cannot know that is the capture
465
+ `source.url` describes.
466
+ - **Capture 1 is privileged by the path layout** — its files sit at the entry root — and there
467
+ is no way to promote capture 2. Two consumers inherit that: image embeddings index the root
468
+ `strip.webp` only, so `similar` can never match on an attached capture's strip; and the entry
469
+ page reads the root `measure.json` only, so a frames-first entry with a measured capture
470
+ attached lists that capture in the Captures row and still shows no "Measured tokens" block.
471
+ - **The page links a URL it never re-checks.** `source.url` is recorded at capture time and
472
+ rendered thereafter; a link that later rots or changes hands is shown exactly as captured.
473
+ - **Cross-`sharp`-version stability is untested.** `package.json` allows `^0.34.3`, so a minor
474
+ libvips bump could shift the low-count edge buckets that `observedColours` reports. Entries
475
+ persist for months, so an `agreement` block recomputed later could legitimately differ from
476
+ one recorded earlier, with no defect involved.
434
477
 
435
478
  ## Layout
436
479
 
437
480
  ```
438
481
  entries/<slug>/entry.md frontmatter + prose (the corpus, reviewable in PRs)
439
- entries/<slug>/strip.png contact sheet
482
+ entries/<slug>/strip.webp contact sheet, capped at 2400px wide (WebP q80)
440
483
  vocab.yaml controlled vocabulary, five facets, aliases
441
484
  src/ingest/ frames, draft, save
442
485
  src/index/ SQLite FTS5 build and query
package/bin/library.mjs CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+
2
5
  const cmds = {
3
6
  check: () => import('../src/cli/check.mjs'),
4
7
  index: () => import('../src/cli/index.mjs'),
@@ -9,7 +12,15 @@ const cmds = {
9
12
  export: () => import('../src/cli/export.mjs'),
10
13
  ui: () => import('../src/cli/ui.mjs'),
11
14
  };
15
+ // Refero discovery is a repository-maintenance tool. The npm package deliberately
16
+ // omits its module, so it is only available from a checkout that contains it.
17
+ if (fs.existsSync(fileURLToPath(new URL('../src/cli/refero.mjs', import.meta.url)))) {
18
+ cmds.refero = () => import('../src/cli/refero.mjs');
19
+ }
12
20
  const [cmd, ...args] = process.argv.slice(2);
21
+ if (fs.existsSync(fileURLToPath(new URL('../src/cli/motion.mjs', import.meta.url)))) {
22
+ cmds.motion = () => import('../src/cli/motion.mjs');
23
+ }
13
24
  if (!cmds[cmd]) {
14
25
  process.stderr.write(`usage: library <${Object.keys(cmds).join('|')}> [args]\n`);
15
26
  process.exit(64);
@@ -0,0 +1,55 @@
1
+ ---
2
+ slug: ada
3
+ title: 'Ada: agentic customer experience platform'
4
+ tier: inspiration
5
+ source:
6
+ kind: public-site
7
+ url: https://www.ada.cx/
8
+ license: Captured from a Refero-hosted recording and the live site; description only, no redistribution of the source.
9
+ captured:
10
+ method: frames
11
+ at: '2026-09-16'
12
+ tool: wp-design-library@0.4.0
13
+ captures:
14
+ - method: frames
15
+ input: /tmp/ada-refero.mp4
16
+ artifacts:
17
+ - strip.webp
18
+ frames: 36
19
+ duration_ms: 22133
20
+ media:
21
+ frames: 36
22
+ duration_ms: 22133
23
+ roles: [hero, proof, feature, explainer]
24
+ feel: [light, premium, saas, depth]
25
+ palette:
26
+ canvas: '#8a7d3f'
27
+ ink: '#000000'
28
+ accent: '#0051c3'
29
+ type:
30
+ display: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", Arial, sans-serif'
31
+ body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", Arial, sans-serif'
32
+ motion:
33
+ devices: [reveal, pan, count]
34
+ notes: Eyeballed from 36 sampled frames, not measured. The blurred landscape hero holds behind a live agent conversation, then reveals a logo row and proof statistics before an operating-model explainer. The metric values appear to count in, but their trigger is not visible.
35
+ ported_from: null
36
+ ---
37
+
38
+ ## What it does
39
+
40
+ Ada presents an agentic customer-experience platform through a live conversation, proof metrics, and operating-model explanation.
41
+
42
+ ## Section roster
43
+
44
+ 1. Hero with agent conversation card over a blurred landscape.
45
+ 2. Customer-logo proof row.
46
+ 3. AI agent performance metrics.
47
+ 4. Customer-experience operating-model explainer.
48
+
49
+ ## Why it works
50
+
51
+ The moving landscape gives the product a more human atmosphere than a conventional software hero, while the white agent panel supplies a concrete product cue. The large background treatment can reduce contrast around the initial message.
52
+
53
+ ## Sources
54
+
55
+ The Refero MP4 supplies the sampled motion sequence. https://www.ada.cx/ remains the source URL; its blocked live response was not retained as a capture.
Binary file
@@ -0,0 +1,325 @@
1
+ {
2
+ "url": "https://air.inc/",
3
+ "at": "2026-09-17",
4
+ "viewport": [
5
+ 1440,
6
+ 390
7
+ ],
8
+ "tokens": {
9
+ "canvas": "#13396a",
10
+ "ink": "#000000",
11
+ "accent": "#1b1b1b",
12
+ "fontDisplay": "\"Control Compressed\", Arial, Helvetica, sans-serif",
13
+ "fontBody": "Control, Arial, Helvetica, sans-serif"
14
+ },
15
+ "sections": [
16
+ {
17
+ "index": 0,
18
+ "tag": "header",
19
+ "id": "",
20
+ "class": "Header_root__hagAA",
21
+ "top": 0,
22
+ "height": 72,
23
+ "animations": []
24
+ },
25
+ {
26
+ "index": 1,
27
+ "tag": "div",
28
+ "id": "",
29
+ "class": "canvas canvas___loaded",
30
+ "top": 0,
31
+ "height": 900,
32
+ "animations": []
33
+ },
34
+ {
35
+ "index": 2,
36
+ "tag": "section",
37
+ "id": "Hero",
38
+ "class": "Section_root__XZMWH p-0! Section_root___spacingDefault__lCxmv",
39
+ "top": 0,
40
+ "height": 1020,
41
+ "animations": []
42
+ },
43
+ {
44
+ "index": 3,
45
+ "tag": "section",
46
+ "id": "Agentic Cards",
47
+ "class": "Section_root__XZMWH p-0! Section_root___spacingDefault__lCxmv bg-center bg-contain bg-no-repeat",
48
+ "top": 1020,
49
+ "height": 1021,
50
+ "animations": []
51
+ },
52
+ {
53
+ "index": 4,
54
+ "tag": "section",
55
+ "id": "",
56
+ "class": "flex w-full flex-col gap-12",
57
+ "top": 1282,
58
+ "height": 631,
59
+ "animations": []
60
+ },
61
+ {
62
+ "index": 5,
63
+ "tag": "section",
64
+ "id": "Logos",
65
+ "class": "Section_root__XZMWH Section_root___spacingSmall__u_8bU",
66
+ "top": 2041,
67
+ "height": 502,
68
+ "animations": []
69
+ },
70
+ {
71
+ "index": 6,
72
+ "tag": "section",
73
+ "id": "agent-waitlist",
74
+ "class": "Section_root__XZMWH p-0! Section_root___spacingDefault__lCxmv bg-center bg- bg-no-repeat",
75
+ "top": 2543,
76
+ "height": 858,
77
+ "animations": [
78
+ {
79
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
80
+ "timeline": "document",
81
+ "range": null,
82
+ "duration": 10000
83
+ },
84
+ {
85
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
86
+ "timeline": "document",
87
+ "range": null,
88
+ "duration": 10000
89
+ }
90
+ ]
91
+ },
92
+ {
93
+ "index": 7,
94
+ "tag": "section",
95
+ "id": "Spacing",
96
+ "class": "Section_root__XZMWH Section_root___spacingDefault__lCxmv bg-center bg-contain bg-no-repeat",
97
+ "top": 3401,
98
+ "height": 240,
99
+ "animations": []
100
+ },
101
+ {
102
+ "index": 8,
103
+ "tag": "section",
104
+ "id": "Supersize Text",
105
+ "class": "Section_root__XZMWH Section_root___spacingDefault__lCxmv",
106
+ "top": 3641,
107
+ "height": 949,
108
+ "animations": []
109
+ },
110
+ {
111
+ "index": 9,
112
+ "tag": "section",
113
+ "id": "Primary Product Demo",
114
+ "class": "Section_root__XZMWH p-0! Section_root___spacingDefault__lCxmv",
115
+ "top": 4590,
116
+ "height": 638,
117
+ "animations": []
118
+ },
119
+ {
120
+ "index": 10,
121
+ "tag": "section",
122
+ "id": "Features",
123
+ "class": "Section_root__XZMWH p-0! Section_root___spacingLarge__acfRQ",
124
+ "top": 5228,
125
+ "height": 1416,
126
+ "animations": [
127
+ {
128
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
129
+ "timeline": "document",
130
+ "range": null,
131
+ "duration": 10000
132
+ },
133
+ {
134
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
135
+ "timeline": "document",
136
+ "range": null,
137
+ "duration": 10000
138
+ },
139
+ {
140
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
141
+ "timeline": "document",
142
+ "range": null,
143
+ "duration": 10000
144
+ },
145
+ {
146
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
147
+ "timeline": "document",
148
+ "range": null,
149
+ "duration": 10000
150
+ },
151
+ {
152
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
153
+ "timeline": "document",
154
+ "range": null,
155
+ "duration": 10000
156
+ },
157
+ {
158
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
159
+ "timeline": "document",
160
+ "range": null,
161
+ "duration": 10000
162
+ },
163
+ {
164
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
165
+ "timeline": "document",
166
+ "range": null,
167
+ "duration": 10000
168
+ }
169
+ ]
170
+ },
171
+ {
172
+ "index": 11,
173
+ "tag": "section",
174
+ "id": "Models",
175
+ "class": "Section_root__XZMWH p-0! Section_root___spacingDefault__lCxmv bg-center bg-contain bg-no-repeat",
176
+ "top": 6645,
177
+ "height": 256,
178
+ "animations": [
179
+ {
180
+ "target": "div.CTACard_root_orbs_inner__sJJHM",
181
+ "timeline": "document",
182
+ "range": null,
183
+ "duration": 10000
184
+ },
185
+ {
186
+ "target": "div.CTACard_root_orbs_item__ocG9A",
187
+ "timeline": "document",
188
+ "range": null,
189
+ "duration": 6000
190
+ },
191
+ {
192
+ "target": "div.CTACard_root_orbs_item__ocG9A",
193
+ "timeline": "document",
194
+ "range": null,
195
+ "duration": 6000
196
+ },
197
+ {
198
+ "target": "div.CTACard_root_orbs_item__ocG9A",
199
+ "timeline": "document",
200
+ "range": null,
201
+ "duration": 6000
202
+ },
203
+ {
204
+ "target": "div.AnimatedBorder_root_stroke__5hoiG",
205
+ "timeline": "document",
206
+ "range": null,
207
+ "duration": 10000
208
+ }
209
+ ]
210
+ },
211
+ {
212
+ "index": 12,
213
+ "tag": "section",
214
+ "id": "Integrations",
215
+ "class": "Section_root__XZMWH Section_root___headerPadding__9L15p Section_root___spacingSmall__u_8bU bg-center bg-contain bg-no-repeat",
216
+ "top": 6901,
217
+ "height": 439,
218
+ "animations": []
219
+ },
220
+ {
221
+ "index": 13,
222
+ "tag": "section",
223
+ "id": "Storytelling",
224
+ "class": "Section_root__XZMWH Section_root___spacingDefault__lCxmv bg-center bg-contain bg-no-repeat",
225
+ "top": 7340,
226
+ "height": 3888,
227
+ "animations": []
228
+ },
229
+ {
230
+ "index": 14,
231
+ "tag": "section",
232
+ "id": "Book a Demo Form",
233
+ "class": "Section_root__XZMWH Section_root___spacingDefault__lCxmv",
234
+ "top": 11228,
235
+ "height": 1162,
236
+ "animations": [
237
+ {
238
+ "target": "h1.u-h2-large",
239
+ "timeline": "document",
240
+ "range": null,
241
+ "duration": 1000
242
+ },
243
+ {
244
+ "target": "h1.u-h2-large",
245
+ "timeline": "document",
246
+ "range": null,
247
+ "duration": 1000
248
+ },
249
+ {
250
+ "target": "span",
251
+ "timeline": "document",
252
+ "range": null,
253
+ "duration": 1000
254
+ },
255
+ {
256
+ "target": "span",
257
+ "timeline": "document",
258
+ "range": null,
259
+ "duration": 1000
260
+ },
261
+ {
262
+ "target": "span",
263
+ "timeline": "document",
264
+ "range": null,
265
+ "duration": 1000
266
+ },
267
+ {
268
+ "target": "span",
269
+ "timeline": "document",
270
+ "range": null,
271
+ "duration": 1000
272
+ },
273
+ {
274
+ "target": "span",
275
+ "timeline": "document",
276
+ "range": null,
277
+ "duration": 1000
278
+ },
279
+ {
280
+ "target": "span",
281
+ "timeline": "document",
282
+ "range": null,
283
+ "duration": 1000
284
+ },
285
+ {
286
+ "target": "span",
287
+ "timeline": "document",
288
+ "range": null,
289
+ "duration": 1000
290
+ },
291
+ {
292
+ "target": "span",
293
+ "timeline": "document",
294
+ "range": null,
295
+ "duration": 1000
296
+ },
297
+ {
298
+ "target": "p",
299
+ "timeline": "document",
300
+ "range": null,
301
+ "duration": 1000
302
+ },
303
+ {
304
+ "target": "p",
305
+ "timeline": "document",
306
+ "range": null,
307
+ "duration": 1000
308
+ }
309
+ ]
310
+ },
311
+ {
312
+ "index": 15,
313
+ "tag": "footer",
314
+ "id": "Footer",
315
+ "class": "Section_root__XZMWH p-0! Footer_root__g9BuT",
316
+ "top": 12390,
317
+ "height": 1350,
318
+ "animations": []
319
+ }
320
+ ],
321
+ "screenshots": {
322
+ "390": "screen-390.png",
323
+ "1440": "screen-1440.png"
324
+ }
325
+ }
@@ -0,0 +1,62 @@
1
+ ---
2
+ slug: air
3
+ title: 'Air: visual asset workspace'
4
+ tier: inspiration
5
+ source:
6
+ kind: public-site
7
+ url: https://air.inc/
8
+ license: Captured from a Refero-hosted recording and the live site; description only, no redistribution of the source.
9
+ captured:
10
+ method: both
11
+ at: '2026-09-17'
12
+ tool: wp-design-library@0.4.0
13
+ captures:
14
+ - method: frames
15
+ input: /tmp/air-refero.mp4
16
+ artifacts:
17
+ - strip.webp
18
+ frames: 36
19
+ duration_ms: 29583
20
+ - method: both
21
+ input: https://air.inc/
22
+ artifacts:
23
+ - captures/2/strip.webp
24
+ - captures/2/measure.json
25
+ - captures/2/screen-1440.png
26
+ - captures/2/screen-390.png
27
+ media:
28
+ frames: 36
29
+ duration_ms: 29583
30
+ roles: [hero, explainer, capability, feature, proof]
31
+ feel: [light, premium, playful, saas, depth]
32
+ palette:
33
+ canvas: '#13396a'
34
+ ink: '#000000'
35
+ accent: '#1b1b1b'
36
+ type:
37
+ display: '"Control Compressed", Arial, Helvetica, sans-serif'
38
+ body: Control, Arial, Helvetica, sans-serif
39
+ motion:
40
+ devices: [reveal, pan, parallax, drift]
41
+ notes: Eyeballed from 36 sampled frames, not measured. The blue-sky hero gives way to floating asset cards, workspace views, and large statement transitions as the recording moves downward. Cards drift independently over the background; individual triggers are not visible.
42
+ ported_from: null
43
+ ---
44
+
45
+ ## What it does
46
+
47
+ Air presents a visual workspace for collecting, organizing, and collaborating around creative assets.
48
+
49
+ ## Section roster
50
+
51
+ 1. Sky-toned hero and product promise.
52
+ 2. Asset-card and workspace demonstration.
53
+ 3. Large organizing statement with content examples.
54
+ 4. Collaboration and library capability scenes.
55
+
56
+ ## Why it works
57
+
58
+ The airy background gives a dense creative tool room to breathe, while the moving cards make the organizing promise visible. The page relies on a long visual sequence, so its supporting explanation is easy to skim past.
59
+
60
+ ## Sources
61
+
62
+ The Refero MP4 supplies the sampled motion sequence. The live capture at https://air.inc/ supplies the current site URL and layout reference.
Binary file