forgedock 1.0.16 → 1.0.18

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 (190) hide show
  1. package/README.md +54 -3
  2. package/bin/cross-publish.mjs +344 -0
  3. package/bin/forgedock.mjs +2205 -541
  4. package/bin/github-app-manifest.json.license +2 -0
  5. package/bin/hooks/session-start.mjs +4 -5
  6. package/bin/registry.mjs +20 -0
  7. package/bin/tui.mjs +172 -96
  8. package/commands/adopt.md +433 -0
  9. package/commands/analytics.md +2 -0
  10. package/commands/audit-agents.md +2 -0
  11. package/commands/audit.md +2 -0
  12. package/commands/autopilot.md +77 -0
  13. package/commands/changelog.md +354 -0
  14. package/commands/cleanup.md +2 -0
  15. package/commands/compat-audit.md +330 -0
  16. package/commands/deploy-info.md +6 -5
  17. package/commands/design-bench.md +176 -0
  18. package/commands/design-render-critique-loop.md +103 -0
  19. package/commands/design.md +259 -0
  20. package/commands/diagnose.md +483 -0
  21. package/commands/explain.md +349 -0
  22. package/commands/forge-stats.md +2 -0
  23. package/commands/forgedock-init.md +2 -0
  24. package/commands/geo-audit.md +2 -0
  25. package/commands/incident-response.md +2 -0
  26. package/commands/issue.md +9 -3
  27. package/commands/milestone.md +2 -0
  28. package/commands/optimize.md +411 -0
  29. package/commands/orchestrate.md +239 -0
  30. package/commands/pipeline-health.md +60 -1
  31. package/commands/qa-sweep.md +39 -2
  32. package/commands/quality-gate.md +67 -0
  33. package/commands/replay.md +263 -0
  34. package/commands/resume.md +264 -0
  35. package/commands/review-pr-agents.md +5 -1
  36. package/commands/review-pr-staging.md +160 -2
  37. package/commands/review-pr.md +85 -3
  38. package/commands/rollback.md +2 -0
  39. package/commands/scope.md +280 -0
  40. package/commands/security-audit.md +2 -0
  41. package/commands/status.md +262 -0
  42. package/commands/sync-ecosystem.md +2 -0
  43. package/commands/test-gate.md +929 -0
  44. package/commands/validate.md +2 -0
  45. package/commands/work-on/build/architect.md +128 -18
  46. package/commands/work-on/build/context.md +130 -17
  47. package/commands/work-on/build/implement.md +7 -3
  48. package/commands/work-on/build/validate.md +2 -0
  49. package/commands/work-on/build.md +32 -7
  50. package/commands/work-on/close.md +85 -0
  51. package/commands/work-on/decompose.md +2 -0
  52. package/commands/work-on/investigate.md +30 -3
  53. package/commands/work-on/review.md +13 -1
  54. package/commands/work-on-monolithic.md +2 -0
  55. package/commands/work-on.md +676 -38
  56. package/docs/BRAND.md +51 -0
  57. package/docs/CI.md +177 -0
  58. package/docs/CONFIG.md +94 -0
  59. package/docs/FORGE-PROTOCOL.md +324 -0
  60. package/docs/articles/b-vs-c-baseline.md +210 -0
  61. package/docs/articles/full-abc-benchmark.md +205 -0
  62. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
  63. package/docs/articles/selective-spec-loading-token-savings.md +140 -0
  64. package/docs/claude-breakpoints.json +47 -0
  65. package/docs/design/abc-benchmark.md +195 -0
  66. package/docs/design/design-architect-rationale.md +123 -0
  67. package/docs/design/design-memory.md +61 -0
  68. package/docs/design/design-spec-schema.md +248 -0
  69. package/docs/design/design-system-lint.md +150 -0
  70. package/docs/design/divergent-generation.md +106 -0
  71. package/docs/design/effects-appropriateness.md +133 -0
  72. package/docs/design/fixtures/briefs/README.md +47 -0
  73. package/docs/design/fixtures/briefs/cadence.md +39 -0
  74. package/docs/design/fixtures/briefs/plume.md +39 -0
  75. package/docs/design/fixtures/briefs/slipstream.md +39 -0
  76. package/docs/design/fixtures/briefs/tender.md +39 -0
  77. package/docs/design/fixtures/briefs/voltage.md +39 -0
  78. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
  79. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
  80. package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
  81. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
  82. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
  83. package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
  84. package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
  85. package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
  86. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
  87. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
  88. package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
  89. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
  90. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
  91. package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
  92. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
  93. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
  94. package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
  95. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
  96. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
  97. package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
  98. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
  99. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
  100. package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
  101. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
  102. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
  103. package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
  104. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
  105. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
  106. package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
  107. package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
  108. package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
  109. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
  110. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
  111. package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
  112. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
  113. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
  114. package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
  115. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
  116. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
  117. package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
  118. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
  119. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
  120. package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
  121. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
  122. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
  123. package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
  124. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
  125. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
  126. package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
  127. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
  128. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
  129. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
  130. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
  131. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
  132. package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
  133. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
  134. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
  135. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
  136. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
  137. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
  138. package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
  139. package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
  140. package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
  141. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
  142. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
  143. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
  144. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
  145. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
  146. package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
  147. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
  148. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
  149. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
  150. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
  151. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
  152. package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
  153. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
  154. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
  155. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
  156. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
  157. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
  158. package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
  159. package/docs/design/foundations/brutalist.css +294 -0
  160. package/docs/design/foundations/editorial.css +300 -0
  161. package/docs/design/foundations/minimal-luxury.css +295 -0
  162. package/docs/design/foundations/technical.css +293 -0
  163. package/docs/design/foundations/warm-photo.css +299 -0
  164. package/docs/design/reference-corpus.md +1726 -0
  165. package/docs/design/render-critique-loop.md +57 -0
  166. package/docs/site/getting-started.md +21 -0
  167. package/docs/spec-graph-schema.md +397 -0
  168. package/package.json +3 -3
  169. package/scripts/bench-scorecard.mjs +239 -0
  170. package/scripts/build-spec-graph.mjs +509 -0
  171. package/scripts/classify-lane.sh +138 -0
  172. package/scripts/design-system-lint.mjs +993 -0
  173. package/scripts/graph-query.sh +486 -0
  174. package/scripts/transition-label.sh +169 -0
  175. package/scripts/validate-pr-target.sh +30 -0
  176. package/scripts/validate-spec-graph.sh +423 -0
  177. package/templates/devdocs/README.md +72 -0
  178. package/templates/devdocs/agent/using-forgedock.md +3 -0
  179. package/templates/devdocs/agent/using-github.md +3 -0
  180. package/templates/devdocs/index.yaml +162 -0
  181. package/templates/devdocs/project/architecture.md +3 -0
  182. package/templates/devdocs/project/conventions.md +3 -0
  183. package/templates/devdocs/project/custom-instructions.md +3 -0
  184. package/templates/devdocs/project/glossary.md +3 -0
  185. package/templates/devdocs/project/stack.md +3 -0
  186. package/templates/devdocs/templates/commit-convention.md +3 -0
  187. package/templates/devdocs/templates/issue.md +3 -0
  188. package/templates/devdocs/templates/pull-request.md +3 -0
  189. package/templates/devdocs/templates/review-finding.md +3 -0
  190. package/templates/workflows/forgedock-review.yml +96 -0
@@ -0,0 +1,1726 @@
1
+ # Reference Corpus, Grammar & Negatives
2
+
3
+ > **Status:** Committed foundation — corpus + grammar + craft vocabulary + hero motion vocabulary + negatives spec for the UI Taste Harness (milestone #13).
4
+ > Source of the design vocabulary drawn on by the [design-spec-schema](design-spec-schema.md) (#881),
5
+ > the [design-architect rationale](design-architect-rationale.md) (#886), and [design-memory](design-memory.md) (#887).
6
+ > The negatives below feed the deterministic linter (#884) and the vision critic (#882); the effect-usage patterns feed
7
+ > the effects-appropriateness layer (#885); the corpus pages are the benchmark's arm **C** (#878);
8
+ > the craft vocabulary feeds the CSS component library (#1048); the hero motion vocabulary feeds `motion` in the schema (#881).
9
+ >
10
+ > Issues: #880 (initial corpus), #1047 (craft vocabulary extension — `corpus_version` `2026.3`), #1043 (hero motion vocabulary — `corpus_version` `2026.4`), #1046 (scroll-driven narrative vocabulary — `corpus_version` `2026.6`), #1048 (CSS component library foundations — foundation file column in per-archetype table).
11
+
12
+ ## Purpose
13
+
14
+ The harness must not chase "beautiful" (no signal → mode-collapse to the mean = slop) nor copy one product
15
+ ("make it like Linear" → mimicry, no variance). Instead it works from a **curated corpus** of
16
+ currently-converting, well-funded SaaS pages, from which we extract two *separable* things:
17
+
18
+ - **The shared grammar** — what they *all* do. This is the contemporary palette / the *reinforcements* (pull toward).
19
+ - **The axes of variation** — where they *differ*. This is the room for *variance* (the archetypes).
20
+
21
+ The corpus is a **living artifact** — design taste shifts, so this file is revised on a cadence (target: quarterly),
22
+ and every revision bumps a `corpus_version` so [design-memory](design-memory.md) can reason about "what's current
23
+ now vs. then." The `corpus_version` here is the same anchor the [schema](design-spec-schema.md) records in
24
+ `meta.corpus_version` for every generated page.
25
+
26
+ **Current `corpus_version`: `2026.6`**
27
+
28
+ ---
29
+
30
+ ## The corpus (seed set)
31
+
32
+ Each entry is a real page used only as an internal reference (the benchmark's arm **C**, #878) — never shown to
33
+ the generator. Anonymized product names are used in the benchmark briefs (see the benchmark rig, #878).
34
+
35
+ | Reference | Category | Primary archetype it exemplifies | `archetype` id | Notable effect usage |
36
+ |---|---|---|---|---|
37
+ | linear.app | Issue tracking | Refined minimal / dark technical-luxury | `minimal-luxury` | subtle parallax, crisp contrast, restrained motion |
38
+ | stripe.com | Payments API | Polished / enterprise-developer | `technical-dense` | animated gradient (hero only), layered depth |
39
+ | vercel.com | Deploy platform | Stark monochrome / brutalist-minimal | `bold-brutalist` | near-zero effects — restraint *is* the statement |
40
+ | modal.com | Serverless AI compute | Technical / playful | `technical-dense` | technical motion, code-forward hero |
41
+ | resend.com | Email API | Clean / warm-minimal | `warm-photographic` | calm, typographic, almost no motion |
42
+
43
+ Extend deliberately, not reflexively — more entries only when they add a *grammar trait* or *archetype*
44
+ not already represented.
45
+
46
+ ### Why YC-funded companies as the anchor
47
+
48
+ The corpus deliberately idolizes what **highly-funded YC / top-tier-VC-backed companies** ship. This is not
49
+ brand worship — it's a proxy for a specific, observable thing: these companies hire strong design talent and
50
+ ship pages that are *currently converting* at high stakes. They are the leading edge of the contemporary palette.
51
+ "YC-funded" is shorthand for "the current high bar of SaaS taste," not a requirement that a reference literally
52
+ went through YC. The point is to anchor on what the best-resourced teams consider good *right now* — and to keep
53
+ re-anchoring as that bar moves (see [Maintenance](#maintenance)). Lower-tier or dated pages would teach the harness
54
+ yesterday's mean, which is exactly the slop we're escaping.
55
+
56
+ ---
57
+
58
+ ## The shared grammar (contemporary funded-SaaS palette) — the *reinforcements*
59
+
60
+ These are the traits the corpus holds in common. They are what the harness should pull *toward*.
61
+
62
+ - **Confident type hierarchy.** Large display type; deliberate, large jumps between levels; tight tracking on
63
+ headlines. Type does the heavy lifting, not decoration.
64
+ - **Restrained color.** Often near-monochrome plus *one* disciplined accent. Dark-mode-first is increasingly common.
65
+ Color is a tool for emphasis, not surface area.
66
+ - **Intentional whitespace rhythm.** Space is composed, not uniform padding. Negative space creates focus.
67
+ - **Real product imagery.** Interface shots, real screenshots, product-in-context — *not* stock illustration,
68
+ generic 3D blobs, or abstract shapes.
69
+ - **Fine depth.** Hairline (1px) borders; soft, low-spread shadows; a *deliberate* radius system (not rounded-everything).
70
+ - **Motion as polish.** Scroll-triggered reveals, subtle gradients/grain, micro-interactions — never gratuitous,
71
+ always serving attention.
72
+ - **Information confidence.** Not afraid of dense, well-set text. Says real things about the product rather than
73
+ abstract promises.
74
+ - **Non-default typefaces.** A chosen grotesk/geometric sans (often with a serif accent) — never Inter-at-default-weights.
75
+
76
+ Each trait maps to one or more enforced fields in the [schema](design-spec-schema.md): type hierarchy →
77
+ `typography`; restrained color → `color`; whitespace rhythm → `spacing`; fine depth → `radius` + `shadow`;
78
+ motion → `motion`; non-default typefaces → `typography.display_family` (constraint: non-default).
79
+
80
+ ---
81
+
82
+ ## The craft vocabulary — micro-details that signal professional design
83
+
84
+ A page can satisfy all eight grammar traits above and still feel like a template. The grammar operates at
85
+ the macro level (type hierarchy, color system, layout). The craft vocabulary operates at the **element level**:
86
+ the 50+ decisions that professional designers make instinctively but AI models skip because they default to
87
+ framework primitives. These are not optional polish — they are the difference between "AI-generated" and
88
+ "someone designed this."
89
+
90
+ The craft vocabulary is carried in the `craft` object in the [schema](design-spec-schema.md). The
91
+ [per-archetype craft profiles](#per-archetype-craft-profiles) below translate these categories into
92
+ archetype-specific defaults. The negatives N13–N20 enforce them.
93
+
94
+ ### 1. Element-specific radius system
95
+
96
+ **AI default**: `rounded-lg` (or `rounded-2xl`) applied uniformly to every element.
97
+
98
+ **Professional pattern**: Each element type has its own radius, scaled to the element's visual weight and
99
+ function. A radius system uses **at least 3 distinct values**; one-radius-fits-all is an immediate AI tell.
100
+
101
+ ```css
102
+ /* AI default — uniform radius everywhere */
103
+ .card { border-radius: 12px; }
104
+ .button { border-radius: 12px; }
105
+ .badge { border-radius: 12px; }
106
+ .input { border-radius: 12px; }
107
+
108
+ /* Professional — scaled to element size and purpose */
109
+ .card { border-radius: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.04), 0 0 0 1px rgba(0,0,0,.06); }
110
+ .button { border-radius: 8px; } /* smaller than card — deliberate scale relationship */
111
+ .badge { border-radius: 9999px; padding: 2px 10px; } /* pill — categorically different shape */
112
+ .input { border-radius: 6px; border: 1px solid rgba(0,0,0,.1); } /* smallest — most precise */
113
+ ```
114
+
115
+ Constraint: `craft.radius_system` in the schema enforces `distinct_count >= 3`.
116
+
117
+ ### 2. Button craft
118
+
119
+ **AI default**: `bg-blue-600 text-white rounded-lg px-4 py-2` — the Tailwind starter button, flat fill,
120
+ no depth, no state feedback.
121
+
122
+ **Professional patterns**:
123
+ - Depth: subtle inner shadow or very soft gradient that creates dimensional feel (not flat color)
124
+ - Hover: combination of shadow lift + slight scale (`transform: scale(1.01)`) + color shift — not just `hover:bg-blue-700`
125
+ - Active: pressed-in feel — `transform: translateY(1px)` + reduced shadow + slightly darker fill
126
+ - Focus: custom ring matching the button's accent color, not the browser default blue outline
127
+ - Ghost/outline variants: custom border color + matching text — not generic `border-gray-300`
128
+ - Icon alignment: inline icons need `translateY(-1px)` optical correction to sit on the text baseline
129
+
130
+ ```css
131
+ /* Professional primary button */
132
+ .btn-primary {
133
+ background: var(--accent);
134
+ box-shadow: 0 1px 2px rgba(0,0,0,.08), inset 0 1px 0 rgba(255,255,255,.08);
135
+ transition: box-shadow 120ms ease, transform 120ms ease;
136
+ }
137
+ .btn-primary:hover {
138
+ box-shadow: 0 4px 12px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.08);
139
+ transform: translateY(-1px);
140
+ }
141
+ .btn-primary:active {
142
+ box-shadow: 0 1px 2px rgba(0,0,0,.06);
143
+ transform: translateY(1px);
144
+ }
145
+ .btn-primary:focus-visible {
146
+ outline: 2px solid var(--accent);
147
+ outline-offset: 3px;
148
+ }
149
+ ```
150
+
151
+ ### 3. Link and text treatments
152
+
153
+ **AI default**: Browser-default blue underlined links, or unstyled colored text with no decoration.
154
+
155
+ **Professional patterns**:
156
+ - Underline: `text-decoration-thickness: 1px; text-underline-offset: 3px;` — precise, not browser default
157
+ - Animated underline: grow-in via `background-image: linear-gradient(currentColor, currentColor); background-size: 0 1px;` → `hover: background-size: 100% 1px`
158
+ - Nav active indicators: bottom border, background pill, or dot — not bold-weight alone
159
+ - Inline code: tinted background matching brand color at 8–12% opacity + `font-size: 0.875em` + custom padding
160
+
161
+ ```css
162
+ /* Animated underline link */
163
+ .link {
164
+ background-image: linear-gradient(currentColor, currentColor);
165
+ background-position: 0 100%;
166
+ background-size: 0 1px;
167
+ background-repeat: no-repeat;
168
+ transition: background-size 200ms ease;
169
+ text-decoration: none;
170
+ }
171
+ .link:hover { background-size: 100% 1px; }
172
+ ```
173
+
174
+ ### 4. Dividers and separators
175
+
176
+ **AI default**: `<hr>` or `border-b border-gray-200` — a hard line with no thought.
177
+
178
+ **Professional patterns**:
179
+ - Gradient divider: `background: linear-gradient(to right, transparent, rgba(0,0,0,.08) 20%, rgba(0,0,0,.08) 80%, transparent)` — fades at edges
180
+ - Spacing-as-divider: no visible line at all; section rhythm created purely by vertical space and background color shifts
181
+ - Dot/dash decorative separators as section intros (archetype-appropriate — editorial only)
182
+ - Subtle background-color shifts between sections (`#FAFAFA` → `#FFFFFF`) in place of drawn lines
183
+
184
+ ### 5. Icon treatments
185
+
186
+ **AI default**: Lucide/Heroicons dropped in at `w-5 h-5` with the default text color. All icons identical.
187
+
188
+ **Professional patterns**:
189
+ - Tinted containers: icon in a circle or rounded square with `background: rgba(var(--accent-rgb), 0.08)` — color matches brand
190
+ - Size varies by context: feature icons at 24px, nav icons at 18px, inline icons at 16px — not all `w-5 h-5`
191
+ - Color matches text hierarchy: muted (`opacity: 0.5`) for secondary, accent for emphasis, never icon-only accent without label hierarchy support
192
+ - Feature icons use archetype-appropriate container shape: circles for warm/human archetypes, squares for technical
193
+
194
+ ### 6. Form element craft
195
+
196
+ **AI default**: Browser-default inputs with a single border. No custom focus state. Native select, checkbox, radio.
197
+
198
+ **Professional patterns**:
199
+ - Focus: `box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15); border-color: var(--accent);` — branded ring, not browser blue
200
+ - Placeholder: `color: rgba(var(--fg-rgb), 0.35); font-weight: 400; letter-spacing: 0.01em;` — lighter weight and tracking than input text
201
+ - Input groups: icon + input + button flush-joined as one visual unit (`border-radius` only on outer corners)
202
+ - Custom select: arrow replaced with `background-image: url("data:image/svg+xml,…")` matching brand color
203
+ - Toggle/checkbox/radio: always custom-styled — native appearance never ships
204
+
205
+ ```css
206
+ .input:focus {
207
+ outline: none;
208
+ border-color: var(--accent);
209
+ box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.12);
210
+ transition: box-shadow 120ms ease, border-color 120ms ease;
211
+ }
212
+ .input::placeholder {
213
+ color: rgba(var(--fg-rgb), 0.35);
214
+ font-weight: 400;
215
+ letter-spacing: 0.01em;
216
+ }
217
+ ```
218
+
219
+ ### 7. Branded micro-details — the "someone designed this" signals
220
+
221
+ These are the details visible only when you look closely, but collectively signal professional intent:
222
+
223
+ - **Custom text selection**: `::selection { background: rgba(var(--accent-rgb), 0.2); color: inherit; }` — brand color tint, not browser blue
224
+ - **Custom scrollbar** (WebKit): `scrollbar-width: thin; scrollbar-color: rgba(0,0,0,.15) transparent;` — thin, matching theme
225
+ - **Custom cursor feedback**: `cursor: pointer` with `transition: opacity 80ms` on interactive elements — tiny signal of responsiveness
226
+ - **Loading/skeleton states**: branded shimmer gradient (`background: linear-gradient(90deg, …)`) — never a spinner alone
227
+ - **Tooltip styling**: matches design system — correct background, radius from the radius system, `font-size: 0.8125rem`
228
+ - **Custom list bullets**: SVG checkmarks or brand-shaped markers via `list-style: none` + `::before` — never browser disc/circle
229
+ - **Code block styling**: custom syntax theme derived from the brand palette — not default highlight.js or Prism defaults
230
+
231
+ ### 8. Compositional sophistication
232
+
233
+ **AI default**: Every section is a centered column with uniform padding. All cards the same size. No visual tension.
234
+
235
+ **Professional patterns**:
236
+ - **Overlapping elements**: a card or badge that breaks grid boundaries, or a product screenshot that bleeds into the next section
237
+ - **Asymmetric layouts**: deliberate left/right weight — headline left-aligned with right-side visual, not everything centered
238
+ - **Z-depth layering**: background texture/pattern → main content → floating badge/annotation at 3 distinct visual planes
239
+ - **Negative space as design**: intentional "empty" areas that create breathing room and direct the eye — not filled with content
240
+ - **Grid breaks**: one card in a 3-column row is taller, or a full-width row interrupts a card grid — planned asymmetry
241
+
242
+ ---
243
+
244
+ ## Hero motion vocabulary — making the hero feel alive
245
+
246
+ A static hero is the single biggest gap between AI-generated pages and real funded-SaaS pages. The craft vocabulary
247
+ (above) addresses element-level polish; the hero motion vocabulary addresses **section-level vitality** — whether
248
+ the hero *moves*. This layer slots beside the craft vocabulary and is governed by the same principle: match
249
+ technique to archetype, commit deliberately, never add motion for its own sake.
250
+
251
+ The [effects-appropriateness doctrine](effects-appropriateness.md) (#885) already covers when to use *heavy* effects
252
+ (3D/WebGL/parallax). This vocabulary covers the *lighter* tier: CSS-only animations, SVG motion, and video
253
+ placeholders — achievable in a single HTML file with zero or minimal JavaScript. Heavy effects are an upgrade path
254
+ from Tier 2; they are not documented here.
255
+
256
+ The `motion` object in the [schema](design-spec-schema.md) (#881) carries the committed tier and technique.
257
+ The [architect phase](design-architect-rationale.md) (#886) selects both alongside the archetype.
258
+
259
+ ### Tier 1 — CSS-only motion (default; no JS required)
260
+
261
+ These are achievable in a single HTML file. They are the **default motion posture** for the harness — every
262
+ generated page should include at least one Tier 1 technique unless the archetype explicitly calls for restraint
263
+ (see per-archetype motion profiles below).
264
+
265
+ **All Tier 1 patterns MUST include a `prefers-reduced-motion` fallback.**
266
+
267
+ #### 1A. Gradient background animation
268
+
269
+ Animates `background-position` on a large gradient, creating a slow-shifting color field behind the hero.
270
+
271
+ ```css
272
+ /* CSS-only gradient shift — zero JS */
273
+ .hero {
274
+ background: linear-gradient(135deg, var(--bg-1), var(--bg-2), var(--bg-3));
275
+ background-size: 300% 300%;
276
+ animation: gradient-shift 12s ease infinite;
277
+ }
278
+ @keyframes gradient-shift {
279
+ 0% { background-position: 0% 50%; }
280
+ 50% { background-position: 100% 50%; }
281
+ 100% { background-position: 0% 50%; }
282
+ }
283
+ @media (prefers-reduced-motion: reduce) {
284
+ .hero { animation: none; background-position: 0% 50%; }
285
+ }
286
+ ```
287
+
288
+ Archetype fit: `editorial-typographic` (warm gradient shifts), `technical-dense` (dark cool shift),
289
+ `minimal-luxury` (monochrome gradient — very slow, near-imperceptible).
290
+
291
+ #### 1B. Text reveal / entrance animation
292
+
293
+ Staggered entrance of hero headline, subline, and CTA using `clip-path` or `opacity` + `translateY`.
294
+ No JS required — use CSS animation-delay for stagger.
295
+
296
+ ```css
297
+ .hero-headline { animation: reveal-up 600ms cubic-bezier(.2,.0,.0,1) both; }
298
+ .hero-subline { animation: reveal-up 600ms cubic-bezier(.2,.0,.0,1) 120ms both; }
299
+ .hero-cta { animation: reveal-up 600ms cubic-bezier(.2,.0,.0,1) 240ms both; }
300
+
301
+ @keyframes reveal-up {
302
+ from { opacity: 0; transform: translateY(16px); }
303
+ to { opacity: 1; transform: translateY(0); }
304
+ }
305
+ @media (prefers-reduced-motion: reduce) {
306
+ .hero-headline, .hero-subline, .hero-cta { animation: none; }
307
+ }
308
+ ```
309
+
310
+ Archetype fit: all archetypes. Stagger duration scales with archetype energy —
311
+ `bold-brutalist` uses faster, snappier timing (200ms); `minimal-luxury` uses slower, more deliberate timing (800ms).
312
+
313
+ #### 1C. Floating / breathing element
314
+
315
+ A subtle looping translate + scale on a background shape, blob, or decorative element. Creates the impression
316
+ of depth and life without demanding attention.
317
+
318
+ ```css
319
+ .hero-float {
320
+ animation: float 6s ease-in-out infinite;
321
+ }
322
+ @keyframes float {
323
+ 0%, 100% { transform: translateY(0) scale(1); }
324
+ 50% { transform: translateY(-12px) scale(1.02); }
325
+ }
326
+ @media (prefers-reduced-motion: reduce) {
327
+ .hero-float { animation: none; }
328
+ }
329
+ ```
330
+
331
+ Archetype fit: `warm-photographic` (floating soft shapes), `minimal-luxury` (single refined element).
332
+ Not appropriate for `bold-brutalist` (restraint is the statement).
333
+
334
+ #### 1D. Typewriter / typing effect
335
+
336
+ CSS-only typing using `steps()` and `ch` units. A cursor blinks using `border-right` animation.
337
+ No JS — a single static phrase only (multiple phrases require JS and belong in Tier 2).
338
+
339
+ ```css
340
+ .typewriter {
341
+ width: 0;
342
+ overflow: hidden;
343
+ white-space: nowrap;
344
+ border-right: 2px solid var(--accent);
345
+ animation:
346
+ typing 3s steps(30, end) forwards,
347
+ blink 0.7s step-end infinite;
348
+ }
349
+ @keyframes typing { from { width: 0; } to { width: 100%; } }
350
+ @keyframes blink { 50% { border-color: transparent; } }
351
+ @media (prefers-reduced-motion: reduce) {
352
+ .typewriter { width: 100%; animation: none; border-right: none; }
353
+ }
354
+ ```
355
+
356
+ Archetype fit: `technical-dense` (code-typing effect), `editorial-typographic` (elegant phrase reveal).
357
+ Avoid for `minimal-luxury` — too busy for the premium posture.
358
+
359
+ #### 1E. Shimmer / glow on CTA
360
+
361
+ A moving highlight gradient over the primary CTA button, creating a premium luster without JavaScript.
362
+
363
+ ```css
364
+ .btn-primary {
365
+ position: relative;
366
+ overflow: hidden;
367
+ }
368
+ .btn-primary::after {
369
+ content: '';
370
+ position: absolute;
371
+ inset: 0;
372
+ background: linear-gradient(105deg, transparent 40%, rgba(255,255,255,.18) 50%, transparent 60%);
373
+ background-size: 200% 100%;
374
+ animation: shimmer 2.4s linear infinite;
375
+ }
376
+ @keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
377
+ @media (prefers-reduced-motion: reduce) {
378
+ .btn-primary::after { animation: none; }
379
+ }
380
+ ```
381
+
382
+ Archetype fit: `minimal-luxury`, `editorial-typographic`. Skip for `bold-brutalist` (contradicts flat CTA posture).
383
+
384
+ #### 1F. Scroll-triggered fade-in (IntersectionObserver + CSS)
385
+
386
+ Below-the-fold sections use a minimal IntersectionObserver to add an `.is-visible` class, triggering a CSS
387
+ transition. This is Tier 1 because the JS is trivial (~10 lines) and contains no dependencies.
388
+
389
+ ```html
390
+ <!-- Markup: add data-reveal to any section -->
391
+ <section data-reveal>…</section>
392
+ ```
393
+
394
+ ```css
395
+ [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity 500ms ease, transform 500ms ease; }
396
+ [data-reveal].is-visible { opacity: 1; transform: none; }
397
+ @media (prefers-reduced-motion: reduce) {
398
+ [data-reveal] { opacity: 1; transform: none; transition: none; }
399
+ }
400
+ ```
401
+
402
+ ```js
403
+ // ~10 lines, no dependencies
404
+ const io = new IntersectionObserver(entries =>
405
+ entries.forEach(e => e.isIntersecting && e.target.classList.add('is-visible')),
406
+ { threshold: 0.15 }
407
+ );
408
+ document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));
409
+ ```
410
+
411
+ Archetype fit: all archetypes for below-the-fold sections. Not a *hero* technique — use for feature grids,
412
+ social proof, pricing sections below the fold.
413
+
414
+ #### 1G. Staggered grid entrance
415
+
416
+ Feature card grids animate in with staggered delays, creating a ripple of content appearance.
417
+
418
+ ```css
419
+ .feature-grid .card { animation: reveal-up 500ms cubic-bezier(.2,.0,.0,1) both; }
420
+ .feature-grid .card:nth-child(1) { animation-delay: 0ms; }
421
+ .feature-grid .card:nth-child(2) { animation-delay: 80ms; }
422
+ .feature-grid .card:nth-child(3) { animation-delay: 160ms; }
423
+ @media (prefers-reduced-motion: reduce) {
424
+ .feature-grid .card { animation: none; }
425
+ }
426
+ ```
427
+
428
+ Archetype fit: `technical-dense`, `editorial-typographic`. Skip for `minimal-luxury` — stagger reads too busy
429
+ against premium negative space.
430
+
431
+ ---
432
+
433
+ ### Tier 2 — SVG + lightweight JS motion
434
+
435
+ These require SVG or ~50–200 lines of vanilla JS. No framework dependencies.
436
+ Performance budget: `max_js_kb: 50` for Tier 2 additions (within the `lcp_ms: 2000` budget from the schema).
437
+
438
+ **All Tier 2 patterns MUST include a `prefers-reduced-motion` check in JS.**
439
+
440
+ #### 2A. SVG path drawing animation
441
+
442
+ Animates `stroke-dashoffset` to draw a path progressively — a logo tracing, a connector line, or an
443
+ abstract shape drawing itself in.
444
+
445
+ ```css
446
+ .svg-path {
447
+ stroke-dasharray: var(--path-length);
448
+ stroke-dashoffset: var(--path-length);
449
+ animation: draw-path 1.8s cubic-bezier(.4,0,.2,1) forwards;
450
+ }
451
+ @keyframes draw-path { to { stroke-dashoffset: 0; } }
452
+ @media (prefers-reduced-motion: reduce) {
453
+ .svg-path { animation: none; stroke-dashoffset: 0; }
454
+ }
455
+ ```
456
+
457
+ *Note*: `--path-length` is computed at render time via `path.getTotalLength()` (3 lines of JS), or pre-computed
458
+ and set as a CSS custom property in the SVG's `style` attribute.
459
+
460
+ Archetype fit: `editorial-typographic` (logo or editorial mark drawing in), `technical-dense` (connector
461
+ lines between diagram nodes), `minimal-luxury` (single refined mark).
462
+
463
+ #### 2B. CSS clip-path morphing
464
+
465
+ Transitions between two `clip-path: polygon()` shapes on hover or entrance, creating a morphing edge
466
+ effect without JavaScript.
467
+
468
+ ```css
469
+ .hero-shape {
470
+ clip-path: polygon(0 0, 100% 0, 100% 85%, 85% 100%, 0 100%);
471
+ transition: clip-path 600ms cubic-bezier(.4,0,.2,1);
472
+ }
473
+ .hero-shape:hover {
474
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 15%);
475
+ }
476
+ @media (prefers-reduced-motion: reduce) {
477
+ .hero-shape { transition: none; }
478
+ }
479
+ ```
480
+
481
+ Archetype fit: `bold-brutalist` (sharp shape transformations), `technical-dense` (geometric transitions).
482
+
483
+ #### 2C. Animated counter (number roll-up)
484
+
485
+ Numbers count up from 0 to their final value on entrance. Used in social proof ("10M+ deployments"),
486
+ metric showcases, or pricing comparisons.
487
+
488
+ ```js
489
+ // ~30 lines, no dependencies
490
+ function animateCounter(el) {
491
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
492
+ const target = parseInt(el.dataset.target, 10);
493
+ const duration = 1200;
494
+ const start = performance.now();
495
+ const tick = now => {
496
+ const progress = Math.min((now - start) / duration, 1);
497
+ el.textContent = Math.floor(progress * target).toLocaleString();
498
+ if (progress < 1) requestAnimationFrame(tick);
499
+ else el.textContent = target.toLocaleString();
500
+ };
501
+ requestAnimationFrame(tick);
502
+ }
503
+ document.querySelectorAll('[data-counter]').forEach(el => {
504
+ new IntersectionObserver(([e]) => e.isIntersecting && animateCounter(el), { threshold: 0.5 }).observe(el);
505
+ });
506
+ ```
507
+
508
+ Archetype fit: `technical-dense` (metric-forward hero), `editorial-typographic` (impact numbers).
509
+ Not appropriate for `minimal-luxury` (undermines the premium calm).
510
+
511
+ #### 2D. Simple scroll parallax (transform only)
512
+
513
+ A lightweight parallax using `scroll` event + CSS `transform: translateY()` — no library. Moves a
514
+ background layer at a fraction of the scroll rate to create depth.
515
+
516
+ ```js
517
+ // ~15 lines, no dependencies
518
+ const layer = document.querySelector('.parallax-layer');
519
+ if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
520
+ window.addEventListener('scroll', () => {
521
+ layer.style.transform = `translateY(${window.scrollY * 0.3}px)`;
522
+ }, { passive: true });
523
+ }
524
+ ```
525
+
526
+ Performance note: use `transform` only — never animate `top`/`left`/`background-position` in JS (triggers layout).
527
+
528
+ Archetype fit: `warm-photographic` (photo layer parallax), `editorial-typographic` (text + image depth separation).
529
+ Inappropriate for `bold-brutalist` (contradicts zero-decoration posture) and `minimal-luxury` (too active for
530
+ premium restraint).
531
+
532
+ ---
533
+
534
+ ### Tier 3 — Video / asset placeholders (user-supplied content)
535
+
536
+ The harness **cannot generate video content**, but it can scaffold the integration so dropping in a real
537
+ product demo video is a one-line replacement. Tier 3 is appropriate whenever a `warm-photographic` or
538
+ `technical-dense` archetype brief implies a demo or product experience exists.
539
+
540
+ **Default behavior**: the scaffold renders an animated CSS gradient as a poster frame. The visitor sees motion
541
+ before the user has supplied the actual video.
542
+
543
+ #### 3A. Full-bleed video hero scaffold
544
+
545
+ ```html
546
+ <!-- Replace the <source> src with your product demo video -->
547
+ <!-- The .video-poster CSS gradient shows until the video loads -->
548
+ <div class="hero-video-wrap">
549
+ <video
550
+ class="hero-video"
551
+ autoplay
552
+ loop
553
+ muted
554
+ playsinline
555
+ poster=""
556
+ aria-hidden="true">
557
+ <!-- Replace with your product demo video -->
558
+ <source src="" type="video/mp4">
559
+ </video>
560
+ <!-- Animated CSS gradient poster — visible before video loads, or if no src -->
561
+ <div class="video-poster" aria-hidden="true"></div>
562
+ <!-- Hero copy overlay -->
563
+ <div class="hero-content">
564
+ <!-- headline, subline, CTA here -->
565
+ </div>
566
+ </div>
567
+ ```
568
+
569
+ ```css
570
+ .hero-video-wrap {
571
+ position: relative;
572
+ width: 100%;
573
+ aspect-ratio: 16 / 9;
574
+ overflow: hidden;
575
+ background: #000;
576
+ }
577
+ .hero-video {
578
+ position: absolute;
579
+ inset: 0;
580
+ width: 100%;
581
+ height: 100%;
582
+ object-fit: cover;
583
+ }
584
+ /* Animated gradient poster — shows until video src is filled in */
585
+ .video-poster {
586
+ position: absolute;
587
+ inset: 0;
588
+ background: linear-gradient(135deg, var(--bg-1, #0f0f14), var(--bg-2, #1a1a2e), var(--bg-3, #0d1117));
589
+ background-size: 300% 300%;
590
+ animation: gradient-shift 12s ease infinite;
591
+ }
592
+ /* Hide poster when video has loaded */
593
+ .hero-video[src]:not([src=""]) ~ .video-poster { opacity: 0; transition: opacity 400ms ease; }
594
+ .hero-content {
595
+ position: relative;
596
+ z-index: 2;
597
+ /* overlay text — position absolutely over the video */
598
+ }
599
+ @media (prefers-reduced-motion: reduce) {
600
+ .video-poster { animation: none; }
601
+ }
602
+ ```
603
+
604
+ *Drop-in instruction* (left as an HTML comment in generated output):
605
+ ```
606
+ <!-- Replace <source src=""> with your product demo video path.
607
+ Recommended: MP4 H.264, ≤10MB for above-the-fold autoplay.
608
+ The gradient poster above shows until the video loads. -->
609
+ ```
610
+
611
+ Archetype fit: `warm-photographic` (product lifestyle video), `technical-dense` (product demo screencast).
612
+
613
+ #### 3B. Windowed product screenshot with motion overlay
614
+
615
+ For archetypes that use a product screenshot rather than video, scaffold a browser-chrome wrapper with
616
+ a subtle looping CSS animation simulating UI activity (a blinking cursor, a progress bar, a pulsing indicator).
617
+
618
+ ```html
619
+ <!-- Replace the inner content with a real product screenshot or iframe -->
620
+ <div class="product-window">
621
+ <div class="window-chrome" aria-hidden="true">
622
+ <span class="chrome-dot dot-red"></span>
623
+ <span class="chrome-dot dot-yellow"></span>
624
+ <span class="chrome-dot dot-green"></span>
625
+ </div>
626
+ <div class="window-body">
627
+ <!-- Replace with your product screenshot: <img src="screenshot.png" alt="…"> -->
628
+ <!-- Placeholder: animated terminal cursor -->
629
+ <div class="terminal-placeholder" aria-hidden="true">
630
+ <span class="terminal-cursor"></span>
631
+ </div>
632
+ </div>
633
+ </div>
634
+ ```
635
+
636
+ ```css
637
+ .product-window {
638
+ border-radius: 12px;
639
+ overflow: hidden;
640
+ box-shadow: 0 24px 64px rgba(0,0,0,.3), 0 0 0 1px rgba(255,255,255,.06);
641
+ }
642
+ .window-chrome {
643
+ display: flex;
644
+ gap: 6px;
645
+ padding: 12px 16px;
646
+ background: rgba(255,255,255,.04);
647
+ border-bottom: 1px solid rgba(255,255,255,.06);
648
+ }
649
+ .chrome-dot { width: 12px; height: 12px; border-radius: 50%; }
650
+ .dot-red { background: #ff5f57; }
651
+ .dot-yellow { background: #febc2e; }
652
+ .dot-green { background: #28c840; }
653
+ .terminal-cursor {
654
+ display: inline-block;
655
+ width: 8px;
656
+ height: 1.2em;
657
+ background: var(--accent);
658
+ animation: blink 1s step-end infinite;
659
+ vertical-align: text-bottom;
660
+ }
661
+ @keyframes blink { 50% { opacity: 0; } }
662
+ @media (prefers-reduced-motion: reduce) {
663
+ .terminal-cursor { animation: none; }
664
+ }
665
+ ```
666
+
667
+ *Drop-in instruction* (left as an HTML comment in generated output):
668
+ ```
669
+ <!-- Replace .terminal-placeholder with: <img src="screenshot.png" alt="Product interface"> -->
670
+ ```
671
+
672
+ Archetype fit: `technical-dense` (terminal/code UI), `editorial-typographic` (app UI in a clean frame).
673
+
674
+ ---
675
+
676
+ ### Interactive product mock vocabulary <!-- Added: forge#1045 -->
677
+
678
+ A product mock is **not a screenshot**. It is a lightweight simulation of the product's UI — rendered in HTML/CSS
679
+ directly in the hero — that has at least two micro-interactions so it feels like a **living interface**, not a poster.
680
+ This is distinct from Tier 3A (video) and from a plain windowed screenshot (3B baseline). The architect commits
681
+ the mock type in `product_mock.type`; the generator applies the corresponding pattern from this vocabulary.
682
+
683
+ **Restraint rule**: a product mock should have **at most 2 simultaneous interactions**. More reads as demo, not
684
+ polish. The interactions should be subtle — status badges pulse, cards lift on hover, a cursor blinks in an input.
685
+ Never animate primary text or copy.
686
+
687
+ **CSS-only preferred**: all 5 patterns below are CSS-only by default. Lightweight vanilla JS (no dependencies,
688
+ ≤20 lines) is permitted only for timed sequences (auto-typing, looping state transitions) that CSS alone cannot drive.
689
+
690
+ **`prefers-reduced-motion` required**: every animation in this section MUST be wrapped in a
691
+ `@media (prefers-reduced-motion: reduce)` block that disables or replaces the motion.
692
+
693
+ #### Product type: `issue-tracker`
694
+
695
+ Cards with hover lift and a pulsing status badge. Suitable for project management, issue tracking, and task board
696
+ products.
697
+
698
+ **Interactions**: card hover lift (`card-hover-lift`), status badge pulse (`status-badge-pulse`)
699
+
700
+ ```html
701
+ <div class="mock-board" aria-hidden="true">
702
+ <div class="mock-card mock-card--in-progress">
703
+ <span class="mock-badge mock-badge--in-progress">In Progress</span>
704
+ <span class="mock-card-title">Redesign onboarding flow</span>
705
+ </div>
706
+ <div class="mock-card mock-card--review">
707
+ <span class="mock-badge mock-badge--review">In Review</span>
708
+ <span class="mock-card-title">Fix auth token refresh</span>
709
+ </div>
710
+ <div class="mock-card mock-card--done">
711
+ <span class="mock-badge mock-badge--done">Done</span>
712
+ <span class="mock-card-title">Update billing integration</span>
713
+ </div>
714
+ </div>
715
+ ```
716
+
717
+ ```css
718
+ .mock-board {
719
+ display: flex;
720
+ flex-direction: column;
721
+ gap: 8px;
722
+ padding: 16px;
723
+ }
724
+ .mock-card {
725
+ display: flex;
726
+ align-items: center;
727
+ gap: 10px;
728
+ padding: 12px 14px;
729
+ background: rgba(255,255,255,.04);
730
+ border: 1px solid rgba(255,255,255,.07);
731
+ border-radius: 8px;
732
+ transition: transform 160ms cubic-bezier(.2,0,.0,1),
733
+ box-shadow 160ms cubic-bezier(.2,0,.0,1);
734
+ cursor: default;
735
+ }
736
+ .mock-card:hover {
737
+ transform: translateY(-2px);
738
+ box-shadow: 0 8px 24px rgba(0,0,0,.18);
739
+ }
740
+ .mock-badge {
741
+ font-size: 11px;
742
+ font-weight: 500;
743
+ padding: 2px 8px;
744
+ border-radius: 9999px;
745
+ letter-spacing: .02em;
746
+ }
747
+ .mock-badge--in-progress {
748
+ background: rgba(251,191,36,.15);
749
+ color: #fbbf24;
750
+ animation: badge-pulse 2.4s ease-in-out infinite;
751
+ }
752
+ .mock-badge--review {
753
+ background: rgba(139,92,246,.15);
754
+ color: #a78bfa;
755
+ }
756
+ .mock-badge--done {
757
+ background: rgba(34,197,94,.12);
758
+ color: #4ade80;
759
+ }
760
+ @keyframes badge-pulse {
761
+ 0%, 100% { opacity: 1; }
762
+ 50% { opacity: .55; }
763
+ }
764
+ @media (prefers-reduced-motion: reduce) {
765
+ .mock-card { transition: none; }
766
+ .mock-card:hover { transform: none; box-shadow: none; }
767
+ .mock-badge--in-progress { animation: none; }
768
+ }
769
+ ```
770
+
771
+ Archetype fit: `technical-dense`, `editorial-typographic`. Not appropriate for `minimal-luxury` (too much activity
772
+ for a premium calm hero).
773
+
774
+ #### Product type: `payment-fintech`
775
+
776
+ An input field with a typing cursor animation and an amount counter. Suitable for payment, invoicing, and fintech
777
+ products.
778
+
779
+ **Interactions**: typing cursor in input (`input-typing-cursor`), amount fill-in animation (`amount-counter`)
780
+
781
+ ```html
782
+ <div class="mock-payment" aria-hidden="true">
783
+ <div class="mock-input-wrap">
784
+ <span class="mock-input-label">Send to</span>
785
+ <div class="mock-input">
786
+ <span class="mock-input-text" data-typing="alex@acme.com"></span><span class="mock-input-cursor"></span>
787
+ </div>
788
+ </div>
789
+ <div class="mock-amount-wrap">
790
+ <span class="mock-input-label">Amount</span>
791
+ <div class="mock-input">
792
+ <span class="mock-currency">$</span>
793
+ <span class="mock-amount" data-target="2400">0</span>
794
+ </div>
795
+ </div>
796
+ <button class="mock-send-btn" tabindex="-1" aria-hidden="true">Send payment</button>
797
+ </div>
798
+ ```
799
+
800
+ ```css
801
+ .mock-payment {
802
+ display: flex;
803
+ flex-direction: column;
804
+ gap: 12px;
805
+ padding: 20px;
806
+ background: rgba(255,255,255,.04);
807
+ border: 1px solid rgba(255,255,255,.08);
808
+ border-radius: 12px;
809
+ min-width: 260px;
810
+ }
811
+ .mock-input-label {
812
+ display: block;
813
+ font-size: 11px;
814
+ color: rgba(255,255,255,.4);
815
+ margin-bottom: 4px;
816
+ letter-spacing: .04em;
817
+ text-transform: uppercase;
818
+ }
819
+ .mock-input {
820
+ display: flex;
821
+ align-items: center;
822
+ padding: 9px 12px;
823
+ background: rgba(255,255,255,.06);
824
+ border: 1px solid rgba(255,255,255,.1);
825
+ border-radius: 6px;
826
+ font-size: 14px;
827
+ gap: 2px;
828
+ }
829
+ .mock-input-cursor {
830
+ display: inline-block;
831
+ width: 1.5px;
832
+ height: 1.1em;
833
+ background: var(--accent, #818cf8);
834
+ animation: cursor-blink 1.1s step-end infinite;
835
+ vertical-align: text-bottom;
836
+ margin-left: 1px;
837
+ }
838
+ @keyframes cursor-blink { 50% { opacity: 0; } }
839
+ .mock-send-btn {
840
+ padding: 10px;
841
+ background: var(--accent, #818cf8);
842
+ color: #fff;
843
+ border: none;
844
+ border-radius: 8px;
845
+ font-size: 14px;
846
+ font-weight: 500;
847
+ cursor: default;
848
+ transition: transform 120ms, box-shadow 120ms;
849
+ }
850
+ .mock-send-btn:hover {
851
+ transform: translateY(-1px);
852
+ box-shadow: 0 4px 12px rgba(129,140,248,.4);
853
+ }
854
+ @media (prefers-reduced-motion: reduce) {
855
+ .mock-input-cursor { animation: none; }
856
+ .mock-send-btn { transition: none; }
857
+ .mock-send-btn:hover { transform: none; box-shadow: none; }
858
+ }
859
+ ```
860
+
861
+ ```js
862
+ // ~15 lines — auto-types the recipient field and counts up the amount
863
+ // No dependencies. Runs once on load; restarts after a pause to loop.
864
+ (function initPaymentMock() {
865
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
866
+ const textEl = document.querySelector('[data-typing]');
867
+ const amtEl = document.querySelector('[data-target]');
868
+ if (!textEl || !amtEl) return;
869
+ const full = textEl.dataset.typing;
870
+ const target = +amtEl.dataset.target;
871
+ let i = 0;
872
+ const typeInterval = setInterval(() => {
873
+ textEl.textContent = full.slice(0, ++i);
874
+ if (i >= full.length) clearInterval(typeInterval);
875
+ }, 60);
876
+ let count = 0;
877
+ const countInterval = setInterval(() => {
878
+ count = Math.min(count + Math.ceil(target / 30), target);
879
+ amtEl.textContent = count.toLocaleString();
880
+ if (count >= target) clearInterval(countInterval);
881
+ }, 40);
882
+ })();
883
+ ```
884
+
885
+ Archetype fit: `warm-photographic`, `editorial-typographic`. Restrained for `minimal-luxury` — use the cursor only,
886
+ skip the counter.
887
+
888
+ #### Product type: `deploy-infra`
889
+
890
+ Animated deploy pipeline with status indicators that transition from "building" to "deployed". Suitable for CI/CD,
891
+ infrastructure, and platform products.
892
+
893
+ **Interactions**: status indicator pulse while building (`status-building-pulse`), deploy step progress
894
+ (`deploy-step-progress`)
895
+
896
+ ```html
897
+ <div class="mock-pipeline" aria-hidden="true">
898
+ <div class="mock-step mock-step--done">
899
+ <span class="mock-step-dot mock-step-dot--done"></span>
900
+ <span class="mock-step-label">Build</span>
901
+ <span class="mock-step-time">12s</span>
902
+ </div>
903
+ <div class="mock-step mock-step--building">
904
+ <span class="mock-step-dot mock-step-dot--building"></span>
905
+ <span class="mock-step-label">Deploy</span>
906
+ <span class="mock-step-time">running…</span>
907
+ </div>
908
+ <div class="mock-step mock-step--pending">
909
+ <span class="mock-step-dot mock-step-dot--pending"></span>
910
+ <span class="mock-step-label">Health check</span>
911
+ <span class="mock-step-time">—</span>
912
+ </div>
913
+ <div class="mock-progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100">
914
+ <div class="mock-progress-fill"></div>
915
+ </div>
916
+ </div>
917
+ ```
918
+
919
+ ```css
920
+ .mock-pipeline {
921
+ display: flex;
922
+ flex-direction: column;
923
+ gap: 10px;
924
+ padding: 16px 20px;
925
+ background: rgba(255,255,255,.03);
926
+ border: 1px solid rgba(255,255,255,.06);
927
+ border-radius: 10px;
928
+ font-size: 13px;
929
+ font-family: var(--mono, monospace);
930
+ }
931
+ .mock-step {
932
+ display: flex;
933
+ align-items: center;
934
+ gap: 10px;
935
+ }
936
+ .mock-step-dot {
937
+ width: 10px;
938
+ height: 10px;
939
+ border-radius: 50%;
940
+ flex-shrink: 0;
941
+ }
942
+ .mock-step-dot--done { background: #4ade80; }
943
+ .mock-step-dot--building {
944
+ background: #fbbf24;
945
+ animation: building-pulse 1.2s ease-in-out infinite;
946
+ }
947
+ .mock-step-dot--pending { background: rgba(255,255,255,.2); }
948
+ .mock-step-label { flex: 1; color: rgba(255,255,255,.8); }
949
+ .mock-step-time { color: rgba(255,255,255,.35); font-size: 11px; }
950
+ @keyframes building-pulse {
951
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(251,191,36,.5); }
952
+ 50% { box-shadow: 0 0 0 5px rgba(251,191,36,.0); }
953
+ }
954
+ .mock-progress-bar {
955
+ height: 3px;
956
+ background: rgba(255,255,255,.08);
957
+ border-radius: 9999px;
958
+ overflow: hidden;
959
+ margin-top: 4px;
960
+ }
961
+ .mock-progress-fill {
962
+ height: 100%;
963
+ width: 0;
964
+ background: linear-gradient(90deg, var(--accent, #818cf8), #a78bfa);
965
+ border-radius: 9999px;
966
+ animation: progress-fill 3s cubic-bezier(.4,0,.2,1) infinite;
967
+ }
968
+ @keyframes progress-fill {
969
+ 0% { width: 0%; }
970
+ 80% { width: 100%; }
971
+ 100% { width: 100%; }
972
+ }
973
+ @media (prefers-reduced-motion: reduce) {
974
+ .mock-step-dot--building { animation: none; }
975
+ .mock-progress-fill { animation: none; width: 60%; }
976
+ }
977
+ ```
978
+
979
+ Archetype fit: `technical-dense`. Acceptable for `editorial-typographic` with slower timing. Not appropriate for
980
+ `minimal-luxury` or `warm-photographic`.
981
+
982
+ #### Product type: `api-developer`
983
+
984
+ A code editor pane with a syntax-highlighted snippet and a typing animation, plus a response panel that populates.
985
+ Suitable for API, SDK, and developer tool products.
986
+
987
+ **Interactions**: code editor typing animation (`code-typing`), response panel reveal (`response-reveal`)
988
+
989
+ ```html
990
+ <div class="mock-editor" aria-hidden="true">
991
+ <div class="mock-editor-chrome">
992
+ <span class="mock-editor-dot" style="background:#ff5f57"></span>
993
+ <span class="mock-editor-dot" style="background:#febc2e"></span>
994
+ <span class="mock-editor-dot" style="background:#28c840"></span>
995
+ <span class="mock-editor-lang">javascript</span>
996
+ </div>
997
+ <div class="mock-editor-body">
998
+ <pre class="mock-code"><span class="mock-kw">const</span> result = <span class="mock-fn">await</span> forge.<span class="mock-fn">run</span>(<span class="mock-str">"build"</span>, {
999
+ issue: <span class="mock-num">1045</span>,
1000
+ model: <span class="mock-str">"opus"</span>
1001
+ });<span class="mock-cursor-code"></span></pre>
1002
+ </div>
1003
+ <div class="mock-response">
1004
+ <span class="mock-response-label">Response</span>
1005
+ <span class="mock-response-body">{ status: <span class="mock-str">"merged"</span>, pr: <span class="mock-num">1049</span> }</span>
1006
+ </div>
1007
+ </div>
1008
+ ```
1009
+
1010
+ ```css
1011
+ .mock-editor {
1012
+ border-radius: 10px;
1013
+ overflow: hidden;
1014
+ background: #0d1117;
1015
+ border: 1px solid rgba(255,255,255,.08);
1016
+ font-family: var(--mono, 'JetBrains Mono', monospace);
1017
+ font-size: 13px;
1018
+ min-width: 280px;
1019
+ }
1020
+ .mock-editor-chrome {
1021
+ display: flex;
1022
+ align-items: center;
1023
+ gap: 6px;
1024
+ padding: 10px 14px;
1025
+ background: rgba(255,255,255,.03);
1026
+ border-bottom: 1px solid rgba(255,255,255,.06);
1027
+ }
1028
+ .mock-editor-dot { width: 10px; height: 10px; border-radius: 50%; }
1029
+ .mock-editor-lang {
1030
+ margin-left: auto;
1031
+ font-size: 10px;
1032
+ color: rgba(255,255,255,.25);
1033
+ text-transform: uppercase;
1034
+ letter-spacing: .08em;
1035
+ }
1036
+ .mock-editor-body { padding: 16px; line-height: 1.7; }
1037
+ .mock-code { margin: 0; color: rgba(255,255,255,.85); white-space: pre-wrap; }
1038
+ .mock-kw { color: #ff79c6; }
1039
+ .mock-fn { color: #8be9fd; }
1040
+ .mock-str { color: #f1fa8c; }
1041
+ .mock-num { color: #bd93f9; }
1042
+ .mock-cursor-code {
1043
+ display: inline-block;
1044
+ width: 7px;
1045
+ height: 1em;
1046
+ background: rgba(255,255,255,.7);
1047
+ animation: blink 1s step-end infinite;
1048
+ vertical-align: text-bottom;
1049
+ margin-left: 1px;
1050
+ }
1051
+ .mock-response {
1052
+ display: flex;
1053
+ flex-direction: column;
1054
+ gap: 4px;
1055
+ padding: 12px 16px;
1056
+ border-top: 1px solid rgba(255,255,255,.06);
1057
+ background: rgba(255,255,255,.02);
1058
+ animation: response-fade-in 600ms ease 2.5s both;
1059
+ }
1060
+ .mock-response-label {
1061
+ font-size: 10px;
1062
+ color: rgba(255,255,255,.3);
1063
+ text-transform: uppercase;
1064
+ letter-spacing: .06em;
1065
+ }
1066
+ .mock-response-body { color: rgba(255,255,255,.7); }
1067
+ @keyframes response-fade-in {
1068
+ from { opacity: 0; transform: translateY(4px); }
1069
+ to { opacity: 1; transform: translateY(0); }
1070
+ }
1071
+ @media (prefers-reduced-motion: reduce) {
1072
+ .mock-cursor-code { animation: none; }
1073
+ .mock-response { animation: none; }
1074
+ }
1075
+ ```
1076
+
1077
+ Archetype fit: `technical-dense` (primary), `bold-brutalist` (remove color tints, use stark monochrome). Not
1078
+ appropriate for `warm-photographic` or `minimal-luxury`.
1079
+
1080
+ #### Product type: `email-messaging`
1081
+
1082
+ Inbox items that slide in with staggered animation, and a compose area with a blinking cursor. Suitable for
1083
+ email, messaging, and communication products.
1084
+
1085
+ **Interactions**: inbox item staggered reveal (`inbox-item-reveal`), compose cursor blink (`compose-cursor`)
1086
+
1087
+ ```html
1088
+ <div class="mock-inbox" aria-hidden="true">
1089
+ <div class="mock-inbox-header">
1090
+ <span class="mock-inbox-title">Inbox</span>
1091
+ <span class="mock-inbox-count">3 new</span>
1092
+ </div>
1093
+ <div class="mock-inbox-items">
1094
+ <div class="mock-inbox-item mock-inbox-item--unread" style="--delay:0ms">
1095
+ <span class="mock-avatar">A</span>
1096
+ <div class="mock-item-body">
1097
+ <span class="mock-sender">Alex Kim</span>
1098
+ <span class="mock-subject">Re: Q3 roadmap sync</span>
1099
+ </div>
1100
+ <span class="mock-unread-dot"></span>
1101
+ </div>
1102
+ <div class="mock-inbox-item mock-inbox-item--unread" style="--delay:120ms">
1103
+ <span class="mock-avatar">S</span>
1104
+ <div class="mock-item-body">
1105
+ <span class="mock-sender">Sarah Lin</span>
1106
+ <span class="mock-subject">Dashboard redesign feedback</span>
1107
+ </div>
1108
+ <span class="mock-unread-dot"></span>
1109
+ </div>
1110
+ <div class="mock-inbox-item" style="--delay:240ms">
1111
+ <span class="mock-avatar" style="opacity:.5">T</span>
1112
+ <div class="mock-item-body">
1113
+ <span class="mock-sender" style="opacity:.5">Tyler Owens</span>
1114
+ <span class="mock-subject" style="opacity:.4">Shipped: auth refactor</span>
1115
+ </div>
1116
+ </div>
1117
+ </div>
1118
+ <div class="mock-compose">
1119
+ <span class="mock-compose-placeholder">Reply to Alex<span class="mock-compose-cursor"></span></span>
1120
+ </div>
1121
+ </div>
1122
+ ```
1123
+
1124
+ ```css
1125
+ .mock-inbox {
1126
+ display: flex;
1127
+ flex-direction: column;
1128
+ background: rgba(255,255,255,.03);
1129
+ border: 1px solid rgba(255,255,255,.07);
1130
+ border-radius: 12px;
1131
+ overflow: hidden;
1132
+ min-width: 280px;
1133
+ }
1134
+ .mock-inbox-header {
1135
+ display: flex;
1136
+ align-items: center;
1137
+ justify-content: space-between;
1138
+ padding: 12px 16px;
1139
+ border-bottom: 1px solid rgba(255,255,255,.06);
1140
+ font-size: 13px;
1141
+ font-weight: 500;
1142
+ }
1143
+ .mock-inbox-count {
1144
+ font-size: 11px;
1145
+ color: var(--accent, #818cf8);
1146
+ background: rgba(129,140,248,.1);
1147
+ padding: 2px 8px;
1148
+ border-radius: 9999px;
1149
+ }
1150
+ .mock-inbox-items { display: flex; flex-direction: column; }
1151
+ .mock-inbox-item {
1152
+ display: flex;
1153
+ align-items: center;
1154
+ gap: 10px;
1155
+ padding: 10px 16px;
1156
+ border-bottom: 1px solid rgba(255,255,255,.04);
1157
+ animation: item-slide-in 300ms cubic-bezier(.2,0,.0,1) var(--delay, 0ms) both;
1158
+ }
1159
+ .mock-inbox-item--unread { background: rgba(255,255,255,.02); }
1160
+ @keyframes item-slide-in {
1161
+ from { opacity: 0; transform: translateX(-8px); }
1162
+ to { opacity: 1; transform: translateX(0); }
1163
+ }
1164
+ .mock-avatar {
1165
+ width: 28px; height: 28px;
1166
+ border-radius: 50%;
1167
+ background: rgba(129,140,248,.2);
1168
+ color: var(--accent, #818cf8);
1169
+ font-size: 12px; font-weight: 600;
1170
+ display: flex; align-items: center; justify-content: center;
1171
+ flex-shrink: 0;
1172
+ }
1173
+ .mock-item-body { display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
1174
+ .mock-sender { font-size: 13px; font-weight: 500; color: rgba(255,255,255,.85); }
1175
+ .mock-subject { font-size: 12px; color: rgba(255,255,255,.4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1176
+ .mock-unread-dot {
1177
+ width: 6px; height: 6px;
1178
+ background: var(--accent, #818cf8);
1179
+ border-radius: 50%;
1180
+ flex-shrink: 0;
1181
+ margin-left: auto;
1182
+ }
1183
+ .mock-compose {
1184
+ padding: 10px 16px;
1185
+ background: rgba(255,255,255,.02);
1186
+ border-top: 1px solid rgba(255,255,255,.06);
1187
+ font-size: 13px;
1188
+ color: rgba(255,255,255,.3);
1189
+ }
1190
+ .mock-compose-cursor {
1191
+ display: inline-block;
1192
+ width: 1.5px; height: 1em;
1193
+ background: var(--accent, #818cf8);
1194
+ animation: blink 1s step-end infinite;
1195
+ vertical-align: text-bottom;
1196
+ margin-left: 1px;
1197
+ }
1198
+ @media (prefers-reduced-motion: reduce) {
1199
+ .mock-inbox-item { animation: none; }
1200
+ .mock-compose-cursor { animation: none; }
1201
+ }
1202
+ ```
1203
+
1204
+ Archetype fit: `warm-photographic`, `editorial-typographic`. Avoid for `bold-brutalist` (inbox UI conflicts with
1205
+ the brutalist posture).
1206
+
1207
+ ---
1208
+
1209
+ ### Per-product-type mock profiles
1210
+
1211
+ The architect phase commits `product_mock.type` and selects 2 interactions from the available list. This table
1212
+ is the default selection — deviate with explicit rationale.
1213
+
1214
+ | Product type | `type` id | Default interactions | Archetype fit | Not appropriate for |
1215
+ |---|---|---|---|---|
1216
+ | Issue tracker / PM | `issue-tracker` | `card-hover-lift`, `status-badge-pulse` | `technical-dense`, `editorial-typographic` | `minimal-luxury` |
1217
+ | Payment / fintech | `payment-fintech` | `input-typing-cursor`, `amount-counter` | `warm-photographic`, `editorial-typographic` | `bold-brutalist` |
1218
+ | Deploy / infra | `deploy-infra` | `status-building-pulse`, `deploy-step-progress` | `technical-dense` | `minimal-luxury`, `warm-photographic` |
1219
+ | API / developer tool | `api-developer` | `code-typing`, `response-reveal` | `technical-dense`, `bold-brutalist` | `warm-photographic`, `minimal-luxury` |
1220
+ | Email / messaging | `email-messaging` | `inbox-item-reveal`, `compose-cursor` | `warm-photographic`, `editorial-typographic` | `bold-brutalist` |
1221
+
1222
+ **Mock restraint rule**: the mock should feel **alive**, not be a full interactive demo. Select exactly 2 interactions.
1223
+ Never animate primary copy. Mock UI text is placeholder — it should represent the product category authentically
1224
+ but not copy any real product's UI verbatim.
1225
+
1226
+ ---
1227
+
1228
+ ### Per-archetype motion profiles
1229
+
1230
+ Each archetype has a preferred motion tier and technique. The architect phase commits to one; the schema
1231
+ carries it in `motion.tier` and `motion.hero_technique`. The generator applies it.
1232
+
1233
+ | Archetype | Preferred tier | Default hero technique | Notes |
1234
+ |---|---|---|---|
1235
+ | `editorial-typographic` | Tier 1 | `text-reveal` (1B) + optional `gradient-shift` (1A) | Type-led; motion serves the reading experience, never distracts |
1236
+ | `technical-dense` | Tier 1–2 | `typewriter` (1D) or `svg-path-draw` (2A) or `video-scaffold` (3A) | Code/terminal motion earns its place; counters (2C) for metric heroes |
1237
+ | `minimal-luxury` | Tier 1 (minimal) | `gradient-shift` (1A) at very slow timing (20s+), or `text-reveal` (1B) | One technique only; restraint is the signal — two or more = slop |
1238
+ | `bold-brutalist` | Tier 1 (sharp) | `text-reveal` (1B) with fast, hard timing (150–200ms) or none | Hard cuts preferred; looping animations are anti-brutalist |
1239
+ | `warm-photographic` | Tier 1–3 | `float` (1C) + `video-scaffold` (3A), or `scroll-parallax` (2D) | Image and video motion earns its place; gradient poster doubles as animation before video loads |
1240
+
1241
+ **Motion restraint rule**: regardless of tier, a hero should have **at most two simultaneous motion elements**.
1242
+ A gradient-shifting background + a text-reveal is the maximum for Tier 1. Adding floating elements on top
1243
+ of both reads as busy, not polished.
1244
+
1245
+ ---
1246
+
1247
+ ## Below-the-fold narrative vocabulary — scroll-driven full-page structure <!-- Added: forge#1046 -->
1248
+
1249
+ > **Status:** Committed extension — `corpus_version: 2026.6` (#1046).
1250
+ > Defines the scroll-driven narrative grammar for below-the-fold sections. Carried in the
1251
+ > `scroll_narrative` field of the [schema](design-spec-schema.md). Enforced by linter negatives N25–N27.
1252
+ > Committed by the architect at diary step 7.5 in the [rationale](design-architect-rationale.md).
1253
+
1254
+ The hero hooks attention. The page builds a story. Every well-funded SaaS landing page that converts is a
1255
+ **scroll-driven narrative** — a sequence of sections that progressively reveal capability, social proof, and
1256
+ differentiation. A harness that only generates a hero is measuring one sentence of a speech.
1257
+
1258
+ **The canonical narrative arc**:
1259
+
1260
+ ```
1261
+ Hero (hook) → Problem/context → Solution showcase → Social proof → Deep features → Final CTA
1262
+ ```
1263
+
1264
+ This is not the boilerplate skeleton (N7) — the boilerplate is `hero → 3-cards → testimonial → CTA`, a structural
1265
+ shortcut with no narrative logic. The scroll narrative is a **communication hierarchy made spatial**: each section
1266
+ exists because it answers the question raised by the previous one. The sections and their motion transitions are
1267
+ derived from the rationale's step 3 (communication hierarchy) — not chosen as decoration.
1268
+
1269
+ **Below-the-fold motion rule**: all below-fold sections use Technique 1F (scroll-triggered fade-in) as the minimum.
1270
+ The motion vocabulary in `motion.vocabulary` carries the committed below-fold technique tokens; the hero technique
1271
+ in `motion.hero_technique` is separate and does not govern below-fold sections.
1272
+
1273
+ **Minimum section count**: a generated page MUST include at least 4 sections below the fold (i.e., ≥5 sections
1274
+ total including the hero). Fewer sections means the narrative arc is truncated — the page is a hero with an
1275
+ afterthought, not a story.
1276
+
1277
+ ---
1278
+
1279
+ ### Scroll narrative section vocabulary
1280
+
1281
+ Each section type in the narrative arc has a committed purpose and motion pattern. The architect selects from
1282
+ these in diary step 7.5 and records the chosen sequence in `scroll_narrative.section_sequence`.
1283
+
1284
+ #### Section: `problem-context`
1285
+
1286
+ **Purpose**: make the visitor feel seen — name the friction they live with. This is not a list of features;
1287
+ it is a reframe of the visitor's world before they discovered the product. High-density copy is appropriate here;
1288
+ this section earns the visitor's continued scroll.
1289
+
1290
+ **Motion pattern**: `data-reveal` on the section container (Technique 1F). The contrast from the hero's kinetic
1291
+ energy to a sober, high-contrast statement section creates a felt transition — no additional motion needed.
1292
+
1293
+ ```html
1294
+ <section class="section-problem" data-reveal>
1295
+ <div class="problem-statement">
1296
+ <span class="problem-label">The problem</span>
1297
+ <h2 class="problem-heading">…</h2>
1298
+ <p class="problem-body">…</p>
1299
+ </div>
1300
+ </section>
1301
+ ```
1302
+
1303
+ ```css
1304
+ .section-problem { background: var(--bg-alt, rgba(0,0,0,.03)); }
1305
+ .problem-label {
1306
+ display: block;
1307
+ font-size: 0.75rem;
1308
+ font-weight: 600;
1309
+ letter-spacing: 0.08em;
1310
+ text-transform: uppercase;
1311
+ color: var(--accent);
1312
+ margin-bottom: 0.75rem;
1313
+ }
1314
+ ```
1315
+
1316
+ Archetype fit: all archetypes. Not skippable — the problem section is the narrative hinge.
1317
+
1318
+ #### Section: `solution-showcase`
1319
+
1320
+ **Purpose**: product enters. The product mock (committed in `product_mock`) and/or the core capability claim.
1321
+ This section should feel like a reveal — the solution arriving on screen in response to the named problem.
1322
+
1323
+ **Motion pattern**: Staggered entrance for the feature grid (Technique 1G, `data-reveal` with `animation-delay`
1324
+ on each card). If a product mock is present, combine with mock entrance animations.
1325
+
1326
+ ```html
1327
+ <section class="section-solution" data-reveal>
1328
+ <h2 class="solution-heading">…</h2>
1329
+ <div class="feature-grid">
1330
+ <div class="card" style="--card-delay: 0ms">…</div>
1331
+ <div class="card" style="--card-delay: 80ms">…</div>
1332
+ <div class="card" style="--card-delay: 160ms">…</div>
1333
+ <div class="card" style="--card-delay: 240ms">…</div>
1334
+ </div>
1335
+ </section>
1336
+ ```
1337
+
1338
+ ```css
1339
+ .feature-grid .card {
1340
+ animation: card-reveal 500ms cubic-bezier(.2,0,.0,1) var(--card-delay, 0ms) both;
1341
+ }
1342
+ @keyframes card-reveal {
1343
+ from { opacity: 0; transform: translateY(20px); }
1344
+ to { opacity: 1; transform: none; }
1345
+ }
1346
+ @media (prefers-reduced-motion: reduce) {
1347
+ .feature-grid .card { animation: none; }
1348
+ }
1349
+ ```
1350
+
1351
+ Archetype fit: all archetypes. The stagger density scales with archetype — `minimal-luxury` uses 2–3 cards with
1352
+ generous spacing; `technical-dense` may use 4–6 compact rows.
1353
+
1354
+ #### Section: `social-proof`
1355
+
1356
+ **Purpose**: third-party validation. Logos, metrics, or testimonials — depending on the product's sales motion.
1357
+ Developer tools use logos + metrics (concrete, peer-validated); consumer-facing products use testimonials
1358
+ (emotional, relatable).
1359
+
1360
+ **Motion patterns**: three sub-patterns, committed individually in `scroll_narrative.social_proof_type`:
1361
+
1362
+ **Social proof type: `logos`** — a horizontal logo bar. Logos in grayscale on entry, color on hover.
1363
+
1364
+ ```html
1365
+ <section class="section-social-proof" data-reveal>
1366
+ <p class="social-label">Trusted by teams at</p>
1367
+ <div class="logo-bar">
1368
+ <img class="logo" src="logo-a.svg" alt="Company A" width="80" height="24">
1369
+ <img class="logo" src="logo-b.svg" alt="Company B" width="80" height="24">
1370
+ <!-- … -->
1371
+ </div>
1372
+ </section>
1373
+ ```
1374
+
1375
+ ```css
1376
+ .logo-bar { display: flex; align-items: center; gap: 2.5rem; flex-wrap: wrap; }
1377
+ .logo {
1378
+ filter: grayscale(1) opacity(.45);
1379
+ transition: filter 300ms ease;
1380
+ }
1381
+ .logo:hover { filter: grayscale(0) opacity(1); }
1382
+ @media (prefers-reduced-motion: reduce) {
1383
+ .logo { transition: none; }
1384
+ }
1385
+ ```
1386
+
1387
+ **Social proof type: `metrics`** — animated counters (Technique 2C) in a 3-column stat bar. Counters roll up
1388
+ on section entrance.
1389
+
1390
+ ```html
1391
+ <section class="section-social-proof" data-reveal>
1392
+ <div class="metric-bar">
1393
+ <div class="metric">
1394
+ <span class="metric-value" data-counter data-target="50000">0</span>
1395
+ <span class="metric-suffix">+</span>
1396
+ <span class="metric-label">deployments per day</span>
1397
+ </div>
1398
+ <!-- … -->
1399
+ </div>
1400
+ </section>
1401
+ ```
1402
+
1403
+ ```css
1404
+ .metric-bar { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; }
1405
+ .metric-value {
1406
+ font-size: 2.5rem;
1407
+ font-weight: 700;
1408
+ font-variant-numeric: tabular-nums;
1409
+ letter-spacing: -0.03em;
1410
+ color: var(--accent);
1411
+ }
1412
+ .metric-label { font-size: 0.875rem; color: rgba(var(--fg-rgb), .55); }
1413
+ ```
1414
+
1415
+ Counter JS: use Technique 2C from the [hero motion vocabulary](#2c-animated-counter-number-roll-up) — applies
1416
+ identically to below-fold sections via IntersectionObserver. No duplication of implementation.
1417
+
1418
+ **Social proof type: `testimonials`** — a 2-column testimonial grid. Quote + attribution + company logo.
1419
+
1420
+ ```html
1421
+ <section class="section-social-proof" data-reveal>
1422
+ <div class="testimonial-grid">
1423
+ <figure class="testimonial-card">
1424
+ <blockquote class="testimonial-quote">"…"</blockquote>
1425
+ <figcaption class="testimonial-attr">
1426
+ <img class="testimonial-avatar" src="avatar.jpg" alt="" width="32" height="32">
1427
+ <span class="testimonial-name">Name</span>
1428
+ <span class="testimonial-title">Title, Company</span>
1429
+ </figcaption>
1430
+ </figure>
1431
+ <!-- … -->
1432
+ </div>
1433
+ </section>
1434
+ ```
1435
+
1436
+ ```css
1437
+ .testimonial-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }
1438
+ .testimonial-card {
1439
+ padding: 1.5rem;
1440
+ border: 1px solid rgba(var(--fg-rgb), .07);
1441
+ border-radius: var(--radius-card, 16px);
1442
+ }
1443
+ .testimonial-quote {
1444
+ font-size: 1rem;
1445
+ line-height: 1.6;
1446
+ color: rgba(var(--fg-rgb), .8);
1447
+ margin: 0 0 1rem;
1448
+ }
1449
+ .testimonial-attr { display: flex; align-items: center; gap: 0.625rem; }
1450
+ .testimonial-avatar { border-radius: 50%; }
1451
+ .testimonial-name { font-weight: 500; font-size: 0.875rem; }
1452
+ .testimonial-title { font-size: 0.8125rem; color: rgba(var(--fg-rgb), .45); }
1453
+ ```
1454
+
1455
+ Archetype social proof defaults: `technical-dense` → `metrics` + `logos`; `warm-photographic` → `testimonials`;
1456
+ `editorial-typographic` → `testimonials` or `metrics`; `minimal-luxury` → `logos` (restrained, no counters);
1457
+ `bold-brutalist` → `metrics` (hard numbers, no softening quotes).
1458
+
1459
+ #### Section: `deep-features`
1460
+
1461
+ **Purpose**: capability depth for the already-convinced visitor. Higher density, more detail — this section
1462
+ converts the evaluator who scrolled past social proof and needs specifics.
1463
+
1464
+ **Motion pattern**: `data-reveal` on the section; individual feature rows animate with alternating left/right
1465
+ entrance (staggered, 100ms apart). The alternation creates directional rhythm — not the same-direction stagger
1466
+ used in `solution-showcase`.
1467
+
1468
+ ```css
1469
+ [data-reveal-left] {
1470
+ opacity: 0;
1471
+ transform: translateX(-20px);
1472
+ transition: opacity 500ms ease, transform 500ms ease;
1473
+ }
1474
+ [data-reveal-right] {
1475
+ opacity: 0;
1476
+ transform: translateX(20px);
1477
+ transition: opacity 500ms ease, transform 500ms ease;
1478
+ }
1479
+ [data-reveal-left].is-visible,
1480
+ [data-reveal-right].is-visible { opacity: 1; transform: none; }
1481
+ @media (prefers-reduced-motion: reduce) {
1482
+ [data-reveal-left], [data-reveal-right] { opacity: 1; transform: none; transition: none; }
1483
+ }
1484
+ ```
1485
+
1486
+ Archetype fit: `technical-dense` (high density, code examples, spec tables), `editorial-typographic`
1487
+ (prose + image alternating rhythm). Lighter treatment for `minimal-luxury` — 2 features max, generous spacing.
1488
+
1489
+ #### Section: `final-cta`
1490
+
1491
+ **Purpose**: the close. The second CTA placement in the scroll narrative — the visitor who reaches here
1492
+ is the most qualified. This section should feel like a resolved chord: the narrative arc has delivered, and now
1493
+ the invitation is clear and calm.
1494
+
1495
+ **Motion pattern**: background color shift from page background to a tinted block on entrance; the CTA
1496
+ button receives a subtle pulse animation on first entrance only (not looping — looping reads as anxiety, not
1497
+ confidence).
1498
+
1499
+ ```html
1500
+ <section class="section-final-cta" data-reveal>
1501
+ <h2 class="cta-heading">…</h2>
1502
+ <p class="cta-subhead">…</p>
1503
+ <a href="#" class="btn-primary btn-cta-final">Get started</a>
1504
+ </section>
1505
+ ```
1506
+
1507
+ ```css
1508
+ .section-final-cta {
1509
+ background: rgba(var(--accent-rgb), .04);
1510
+ border-top: 1px solid rgba(var(--accent-rgb), .12);
1511
+ }
1512
+ .btn-cta-final {
1513
+ animation: cta-entrance 600ms cubic-bezier(.2,0,.0,1) 300ms both;
1514
+ }
1515
+ @keyframes cta-entrance {
1516
+ from { opacity: 0; transform: scale(.97); }
1517
+ to { opacity: 1; transform: none; }
1518
+ }
1519
+ @media (prefers-reduced-motion: reduce) {
1520
+ .btn-cta-final { animation: none; }
1521
+ }
1522
+ ```
1523
+
1524
+ **CTA rhythm rule**: every generated page MUST have at least 2 CTA placements — one in the hero section
1525
+ and one in the final-cta section. A page with a CTA only in the hero forces the convinced visitor to scroll
1526
+ back to convert. The `scroll_narrative.cta_placements_min` field enforces this constraint.
1527
+
1528
+ ---
1529
+
1530
+ ### Sticky navigation vocabulary
1531
+
1532
+ The nav transforms on scroll: transparent → solid background, optionally with a height reduction and a shadow.
1533
+ This signals page depth and keeps the brand and primary CTA visible at all scroll positions.
1534
+
1535
+ **Pattern**: a `scroll` event listener adds `.nav--scrolled` class when `scrollY > threshold`. The threshold
1536
+ is typically 80px (one viewport header height). CSS handles all the visual transitions — the JS is trivial.
1537
+
1538
+ ```html
1539
+ <nav class="site-nav" id="site-nav">
1540
+ <a class="nav-logo" href="/">Brand</a>
1541
+ <ul class="nav-links">…</ul>
1542
+ <a href="#" class="nav-cta btn-primary">Get started</a>
1543
+ </nav>
1544
+ ```
1545
+
1546
+ ```css
1547
+ .site-nav {
1548
+ position: sticky;
1549
+ top: 0;
1550
+ z-index: 100;
1551
+ padding: 1.25rem 2rem;
1552
+ background: transparent;
1553
+ border-bottom: 1px solid transparent;
1554
+ transition: background 250ms ease, padding 250ms ease, border-color 250ms ease, box-shadow 250ms ease;
1555
+ }
1556
+ .site-nav.nav--scrolled {
1557
+ background: rgba(var(--bg-rgb), .92);
1558
+ backdrop-filter: blur(12px);
1559
+ -webkit-backdrop-filter: blur(12px);
1560
+ border-bottom-color: rgba(var(--fg-rgb), .07);
1561
+ box-shadow: 0 1px 3px rgba(0,0,0,.06);
1562
+ padding: 0.875rem 2rem; /* shrinks on scroll */
1563
+ }
1564
+ @media (prefers-reduced-motion: reduce) {
1565
+ .site-nav { transition: none; }
1566
+ }
1567
+ ```
1568
+
1569
+ ```js
1570
+ // ~10 lines, no dependencies
1571
+ const nav = document.getElementById('site-nav');
1572
+ const THRESHOLD = 80;
1573
+ window.addEventListener('scroll', () => {
1574
+ nav.classList.toggle('nav--scrolled', window.scrollY > THRESHOLD);
1575
+ }, { passive: true });
1576
+ ```
1577
+
1578
+ **Sticky nav decision rule**: sticky nav is appropriate for all archetypes when the page is longer than the
1579
+ viewport (i.e., when `scroll_narrative.section_count >= 5`). For `minimal-luxury` archetype: skip the
1580
+ `backdrop-filter` blur — it reads as glassmorphism, contradicting the clean-surface posture. Use a
1581
+ fully-opaque background instead.
1582
+
1583
+ ---
1584
+
1585
+ ### Per-archetype scroll narrative defaults
1586
+
1587
+ Each archetype has a preferred below-fold narrative structure. These are defaults — deviate with explicit
1588
+ rationale. The architect commits the actual sequence in diary step 7.5.
1589
+
1590
+ | Archetype | Default section sequence | Social proof type | Notes |
1591
+ |---|---|---|---|
1592
+ | `editorial-typographic` | hero → problem → solution → testimonials → deep-features → final-cta | `testimonials` | Narrative-forward; deep-features section is essay-style, not a grid |
1593
+ | `technical-dense` | hero → problem → solution → metrics+logos → deep-features → final-cta | `metrics` + `logos` | Spec and capability depth in deep-features; counters add social proof energy |
1594
+ | `minimal-luxury` | hero → solution → logos → final-cta | `logos` | Shorter is more premium; skip problem section if the brand is already known; ≤4 total sections acceptable |
1595
+ | `bold-brutalist` | hero → problem → solution → metrics → final-cta | `metrics` | No testimonials — softness contradicts the brutalist posture; hard numbers only |
1596
+ | `warm-photographic` | hero → problem → solution → testimonials → deep-features → final-cta | `testimonials` | Human narrative arc; testimonials are the emotional peak |
1597
+
1598
+ ---
1599
+
1600
+ ## The axes of variation — the *archetypes* (this is where variance comes from)
1601
+
1602
+ The corpus is **not one aesthetic**. Variance is achieved by sampling a coherent *archetype* per design and
1603
+ committing to it hard — not by averaging (which yields the mean = slop) and not by copying one product (mimicry).
1604
+ The `archetype` id column below is the exact token committed to `meta.archetype` in the [schema](design-spec-schema.md).
1605
+
1606
+ | Archetype | `archetype` id | Feel | Type | Color | Density | Effects posture |
1607
+ |---|---|---|---|---|---|---|
1608
+ | **Editorial / typographic** | `editorial-typographic` | magazine, considered | serif + sans pairing, big display | warm neutrals + 1 accent | medium | minimal, type-led |
1609
+ | **Technical / dense** | `technical-dense` | dashboard-forward, capable | mono accents, tight sans | dark-first, cool | high | code/data motion welcome |
1610
+ | **Minimal-luxury** | `minimal-luxury` | premium, lots of air | refined sans, generous scale | monochrome + subtle accent | low | restrained micro-motion |
1611
+ | **Bold-brutalist** | `bold-brutalist` | high-contrast, opinionated | heavy grotesk, oversized | stark B/W + 1 loud accent | medium | sharp, near-zero decorative |
1612
+ | **Warm-photographic** | `warm-photographic` | human, approachable | friendly sans | warm palette, photography-led | medium | image-driven, soft motion |
1613
+
1614
+ **Selection rule:** the [architect phase](design-architect-rationale.md) (#886) picks one archetype appropriate to
1615
+ the *product nature* + *audience* (inferred from the brief alone), then commits. Variance between runs = different
1616
+ archetype + different token instantiation; coherence within a run = no drift.
1617
+
1618
+ ### Per-archetype craft profiles
1619
+
1620
+ Each archetype has a specific craft posture across four dimensions. These are the *defaults* — the generator
1621
+ commits to them and deviates only with explicit rationale. The `craft` object in the
1622
+ [schema](design-spec-schema.md) carries these values.
1623
+
1624
+ | Archetype | Radius system | Button style | Divider style | Icon treatment | Foundation file |
1625
+ |---|---|---|---|---|---|
1626
+ | `editorial-typographic` | generous (16px card / 12px input / 8px button) | serif-label text button or outlined; subtle warm shadow | gradient fade to transparent at edges | tinted squares or rectangles at low opacity | [`editorial.css`](foundations/editorial.css) <!-- Added: forge#1048 --> |
1627
+ | `technical-dense` | tight (8px card / 6px input / 4px button) | sharp, mono-label; no gradient; high-contrast border on ghost | hairline 1px solid at 8% opacity | monochrome, small (16px), uniform | [`technical.css`](foundations/technical.css) <!-- Added: forge#1048 --> |
1628
+ | `minimal-luxury` | mixed (20px card / 12px input / 8px button) | ghost or borderless; minimal fill; no visible shadow | spacing only — no visible line between sections | none or near-invisible; no colored backgrounds | [`minimal-luxury.css`](foundations/minimal-luxury.css) <!-- Added: forge#1048 --> |
1629
+ | `bold-brutalist` | zero or max (0px or 9999px — never in between) | high-contrast; heavy border (2px+); flat fill; no shadow | bold solid line (2–3px) or full color-block section break | bold, oversized (32px+); high contrast; no tinted backgrounds | [`brutalist.css`](foundations/brutalist.css) <!-- Added: forge#1048 --> |
1630
+ | `warm-photographic` | soft (12px card / 8px input / 6px button) | rounded; warm-tinted shadow; soft fill matching photo palette | photo-edge bleed or subtle warm-tinted rule | warm-tinted circles; 24px; soft drop shadow | [`warm-photo.css`](foundations/warm-photo.css) <!-- Added: forge#1048 --> |
1631
+
1632
+ ---
1633
+
1634
+ ## The negatives — the anti-slop checklist (machine-checkable)
1635
+
1636
+ These are the AI-generated "tells." They are enforced two ways: a deterministic linter (#884) and the
1637
+ vision critic (#882). Any hit is a finding. Each item is phrased so it maps to a concrete, checkable signal
1638
+ (populated into `negatives` in the [schema](design-spec-schema.md)).
1639
+
1640
+ | # | Negative | Primary check surface |
1641
+ |---|---|---|
1642
+ | N1 | Purple/blue gradient hero on white background | linter (computed hero background) |
1643
+ | N2 | Inter (or system default) at default weights, everywhere | linter (font-family + weight) |
1644
+ | N3 | Everything centered; no asymmetry, no deliberate alignment grid | critic (layout) |
1645
+ | N4 | Three identical feature cards with lucide/emoji icons | linter + critic (repeated card pattern) |
1646
+ | N5 | `rounded-2xl` (or one radius) applied to literally everything | linter (radius-token cardinality) |
1647
+ | N6 | Default Tailwind palette (slate / indigo / gray-50) used as the actual brand color | linter (palette match) |
1648
+ | N7 | The boilerplate skeleton: hero → 3 cards → testimonial → CTA, in that order, every time | linter (section sequence) |
1649
+ | N8 | Stock-illustration people / generic 3D blobs / abstract gradient shapes instead of real product | critic (vision) |
1650
+ | N9 | Glassmorphism overused (frosted panels everywhere) | critic (vision) |
1651
+ | N10 | Equal visual weight across the page — no hierarchy, nothing leads the eye | critic (vision) |
1652
+ | N11 | Abstract promises ("Supercharge your workflow") with no concrete product shown | critic (copy + vision) |
1653
+ | N12 | Decorative effects that serve nothing (see [effect-usage patterns](#effect-usage-patterns-feeds-the-effects-layer-885)) | critic + effects layer (#885) |
1654
+ | N13 | Uniform border-radius: fewer than 3 distinct radius values across card / button / badge / input elements | linter (radius-token cardinality in craft vocabulary; see N5 for the one-class case) |
1655
+ | N14 | Flat primary button: no depth signal — no shadow, no gradient, no hover lift/press state | linter (button shadow/transform rules) + critic |
1656
+ | N15 | Browser-default form elements: unstyled inputs (no custom focus ring), native select/checkbox/radio | linter (CSS property presence) + critic |
1657
+ | N16 | Default icon treatment: all icons same size (`w-5 h-5`), no tinted container, no contextual sizing | critic (vision) |
1658
+ | N17 | No custom link treatment: relies on browser-default underline or bare color-only links | linter (text-decoration-thickness / background-size pattern) |
1659
+ | N18 | Missing branded micro-details: no custom `::selection`, no custom scrollbar, no custom focus rings on interactive elements | linter (CSS rule presence) |
1660
+ | N19 | All sections same visual weight: uniform padding and background — no spacing-as-divider, no color shifts, no grid breaks | critic (layout rhythm) |
1661
+ | N20 | No hover/active/focus states on interactive elements beyond color change | linter (transition + transform properties on interactive selectors) |
1662
+ | N21 | Static hero with no motion or visual interest — a poster, not an experience; inappropriate for archetype | critic (vision) |
1663
+ | N22 | Motion that fights readability: looping animations over primary text, excessive parallax, or more than two simultaneous motion elements in the hero | critic (vision + motion layer) |
1664
+ | N23 | jQuery-era motion effects: bouncing, sliding content in from offscreen, accordion animations everywhere, spinning decorative elements | critic (vision) |
1665
+ | N24 | Static product mock: `product_mock.type` committed in spec but the rendered hero has a browser-chrome wrapper with no CSS animation, hover state, or JS-driven state change | linter (`checkProductMock`) + critic (vision) | <!-- Added: forge#1045 -->
1666
+ | N25 | Truncated narrative: page has ≤1 below-fold section — hero with an afterthought, not a scroll-driven story; `scroll_narrative` committed in spec | linter (`checkScrollNarrative`) <!-- Added: forge#1046 — spec only; .mjs implementation follows --> |
1667
+ | N26 | Missing social proof: `scroll_narrative.social_proof_section` is committed in spec but no social proof section is present in the rendered output | linter (`checkSocialProof`) <!-- Added: forge#1046 — spec only; .mjs implementation follows --> |
1668
+ | N27 | Single CTA: page has only one CTA element — `scroll_narrative.cta_placements_min` is ≥ 2 but only the hero CTA is present | linter (`checkCTACount`) <!-- Added: forge#1046 — spec only; .mjs implementation follows --> |
1669
+
1670
+ ---
1671
+
1672
+ ## Effect-usage patterns (feeds the effects layer, #885)
1673
+
1674
+ Extracted from the corpus: *where* and *how intensely* real pages use heavy techniques. This is how the harness
1675
+ *learns* effect-appropriateness from exemplars rather than inventing it. The governing doctrine that turns these
1676
+ patterns into per-section authoring rules lives in [effects-appropriateness](effects-appropriateness.md) (#885) — the
1677
+ canonical consumer of this table and of N12.
1678
+
1679
+ | Effect | Observed pattern in corpus | Earns its place when… | Slop when… |
1680
+ |---|---|---|---|
1681
+ | 3D / WebGL | rare; only on spatial/visual products, hero centerpiece | product is spatial/visual; one premium hero | bolted onto non-visual B2B; wrecks LCP; no fallback |
1682
+ | Parallax / scroll-driven | subtle, hero + one reveal section | progressive capability reveal, depth | everywhere; jank/CLS; fights readability |
1683
+ | Multi-layer depth (glass/grain/gradient) | hero backdrop, restrained | richness, fg/bg separation | over-glass; muddy contrast |
1684
+ | Contrast (compositional) | pervasive, deliberate | almost always — direct the eye | *low* contrast: flat, equal-weight |
1685
+
1686
+ Note the asymmetry: contrast rewards *more* intention; the others reward *restraint*. The single biggest tell of
1687
+ maturity is matching effect intensity to product nature — e.g. a compute/GPU product may warrant a subtle
1688
+ technical hero visualization, while an email API should stay calm and typographic, and a Vercel-archetype design
1689
+ may make restraint itself the move. The harness must infer this **from the brief alone**.
1690
+
1691
+ ---
1692
+
1693
+ ## Generation Model
1694
+
1695
+ **Validated generation model**: `claude-opus-4-6`
1696
+
1697
+ Benchmark #878 ran the full ABC benchmark with both Sonnet and Opus as the generation model. Results were conclusive:
1698
+
1699
+ | Metric | Sonnet (`claude-sonnet-4-6`) | Opus (`claude-opus-4-6`) |
1700
+ |--------|------------------------------|--------------------------|
1701
+ | A vs B pairwise win-rate | 54.2% | **90.0%** |
1702
+ | A rubric mean | 3.40/5 | **4.11/5** |
1703
+ | A slop count (avg) | 1.8 | **1.2** |
1704
+
1705
+ The gap is a tier difference, not marginal. Opus is the validated generation model for the harness going forward. The Opus run (`docs/design/fixtures/runs/full-abc-opus/`) is the canonical benchmark baseline. The Sonnet run (`docs/design/fixtures/runs/full-abc/`) is preserved as historical comparison data.
1706
+
1707
+ Per the design-bench "same model" rule: both arm A and arm B use the same generation model. Changing the canonical model for a benchmark run requires running both arms with the new model.
1708
+
1709
+ ---
1710
+
1711
+ ## Maintenance
1712
+
1713
+ - Revise quarterly or when a clear taste shift is observed.
1714
+ - Every revision: bump `corpus_version` (above), note what changed, and record it so
1715
+ [design-memory](design-memory.md) (#887) can distinguish "current grammar" from "grammar at time of a past design."
1716
+ - New entries must justify themselves: which grammar trait or archetype do they add?
1717
+
1718
+ ### Revision log
1719
+
1720
+ | `corpus_version` | Date | Change |
1721
+ |---|---|---|
1722
+ | `2026.2` | 2026-06 | Initial committed corpus: 5 seed references, 5 archetypes, 12 negatives, 4 effect-usage patterns. |
1723
+ | `2026.3` | 2026-06 | Added craft vocabulary (8 categories with CSS examples), per-archetype craft profiles table, negatives N13–N20 (#1047). Schema extended with `craft` object and `surface_depth` rubric dimension. |
1724
+ | `2026.4` | 2026-06 | Added hero motion vocabulary (Tier 1 CSS-only, Tier 2 SVG+JS, Tier 3 video placeholders with CSS examples), per-archetype motion profiles table, negatives N21–N23 (#1043). Schema extended with `motion.tier`, `motion.hero_technique`, `motion.video_placeholder` fields and `motion` rubric dimension. |
1725
+ | `2026.5` | 2026-06 | Added interactive product mock vocabulary (5 product types with CSS/JS interaction examples), per-product-type mock profiles table, negative N24 (#1045). Schema extended with `product_mock` field. |
1726
+ | `2026.6` | 2026-06 | Added below-the-fold narrative vocabulary (scroll narrative grammar, canonical section sequence, sticky nav pattern, social proof section types, CTA rhythm rule), per-archetype scroll narrative defaults table, negatives N25–N27 (#1046). Schema extended with `scroll_narrative` object and `scroll_narrative` rubric dimension. |