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.
- package/README.md +54 -3
- package/bin/cross-publish.mjs +344 -0
- package/bin/forgedock.mjs +2205 -541
- package/bin/github-app-manifest.json.license +2 -0
- package/bin/hooks/session-start.mjs +4 -5
- package/bin/registry.mjs +20 -0
- package/bin/tui.mjs +172 -96
- package/commands/adopt.md +433 -0
- package/commands/analytics.md +2 -0
- package/commands/audit-agents.md +2 -0
- package/commands/audit.md +2 -0
- package/commands/autopilot.md +77 -0
- package/commands/changelog.md +354 -0
- package/commands/cleanup.md +2 -0
- package/commands/compat-audit.md +330 -0
- package/commands/deploy-info.md +6 -5
- package/commands/design-bench.md +176 -0
- package/commands/design-render-critique-loop.md +103 -0
- package/commands/design.md +259 -0
- package/commands/diagnose.md +483 -0
- package/commands/explain.md +349 -0
- package/commands/forge-stats.md +2 -0
- package/commands/forgedock-init.md +2 -0
- package/commands/geo-audit.md +2 -0
- package/commands/incident-response.md +2 -0
- package/commands/issue.md +9 -3
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +239 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +39 -2
- package/commands/quality-gate.md +67 -0
- package/commands/replay.md +263 -0
- package/commands/resume.md +264 -0
- package/commands/review-pr-agents.md +5 -1
- package/commands/review-pr-staging.md +160 -2
- package/commands/review-pr.md +85 -3
- package/commands/rollback.md +2 -0
- package/commands/scope.md +280 -0
- package/commands/security-audit.md +2 -0
- package/commands/status.md +262 -0
- package/commands/sync-ecosystem.md +2 -0
- package/commands/test-gate.md +929 -0
- package/commands/validate.md +2 -0
- package/commands/work-on/build/architect.md +128 -18
- package/commands/work-on/build/context.md +130 -17
- package/commands/work-on/build/implement.md +7 -3
- package/commands/work-on/build/validate.md +2 -0
- package/commands/work-on/build.md +32 -7
- package/commands/work-on/close.md +85 -0
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on/investigate.md +30 -3
- package/commands/work-on/review.md +13 -1
- package/commands/work-on-monolithic.md +2 -0
- package/commands/work-on.md +676 -38
- package/docs/BRAND.md +51 -0
- package/docs/CI.md +177 -0
- package/docs/CONFIG.md +94 -0
- package/docs/FORGE-PROTOCOL.md +324 -0
- package/docs/articles/b-vs-c-baseline.md +210 -0
- package/docs/articles/full-abc-benchmark.md +205 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/articles/selective-spec-loading-token-savings.md +140 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/design/abc-benchmark.md +195 -0
- package/docs/design/design-architect-rationale.md +123 -0
- package/docs/design/design-memory.md +61 -0
- package/docs/design/design-spec-schema.md +248 -0
- package/docs/design/design-system-lint.md +150 -0
- package/docs/design/divergent-generation.md +106 -0
- package/docs/design/effects-appropriateness.md +133 -0
- package/docs/design/fixtures/briefs/README.md +47 -0
- package/docs/design/fixtures/briefs/cadence.md +39 -0
- package/docs/design/fixtures/briefs/plume.md +39 -0
- package/docs/design/fixtures/briefs/slipstream.md +39 -0
- package/docs/design/fixtures/briefs/tender.md +39 -0
- package/docs/design/fixtures/briefs/voltage.md +39 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/runs.json +175 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/scorecard.json +138 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/b-vs-c-baseline/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-1.html +562 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-2.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-a/run-3.html +618 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-1.html +364 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-2.html +420 -0
- package/docs/design/fixtures/runs/full-abc/cadence/arm-b/run-3.html +506 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-1.html +527 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-2.html +445 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-a/run-3.html +483 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-1.html +611 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-2.html +594 -0
- package/docs/design/fixtures/runs/full-abc/plume/arm-b/run-3.html +817 -0
- package/docs/design/fixtures/runs/full-abc/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-1.html +496 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-2.html +447 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-a/run-3.html +564 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-1.html +373 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-2.html +398 -0
- package/docs/design/fixtures/runs/full-abc/slipstream/arm-b/run-3.html +595 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-1.html +622 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-2.html +475 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-a/run-3.html +660 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-1.html +531 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-2.html +565 -0
- package/docs/design/fixtures/runs/full-abc/tender/arm-b/run-3.html +674 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-1.html +525 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-a/run-3.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-1.html +388 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-2.html +463 -0
- package/docs/design/fixtures/runs/full-abc/voltage/arm-b/run-3.html +568 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-1.html +1068 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-2.html +1423 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-a/run-3.html +1245 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-1.html +305 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-2.html +327 -0
- package/docs/design/fixtures/runs/full-abc-opus/cadence/arm-b/run-3.html +428 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-1.html +1270 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-2.html +1172 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-a/run-3.html +1532 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-1.html +315 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-2.html +342 -0
- package/docs/design/fixtures/runs/full-abc-opus/plume/arm-b/run-3.html +353 -0
- package/docs/design/fixtures/runs/full-abc-opus/runs.json +190 -0
- package/docs/design/fixtures/runs/full-abc-opus/scorecard.json +144 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-1.html +345 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-a/run-3.html +502 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-1.html +408 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-2.html +446 -0
- package/docs/design/fixtures/runs/full-abc-opus/slipstream/arm-b/run-3.html +496 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-1.html +813 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-2.html +711 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-a/run-3.html +721 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-1.html +280 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-2.html +291 -0
- package/docs/design/fixtures/runs/full-abc-opus/tender/arm-b/run-3.html +366 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-1.html +1010 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-2.html +836 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-a/run-3.html +930 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-1.html +407 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-2.html +396 -0
- package/docs/design/fixtures/runs/full-abc-opus/voltage/arm-b/run-3.html +529 -0
- package/docs/design/foundations/brutalist.css +294 -0
- package/docs/design/foundations/editorial.css +300 -0
- package/docs/design/foundations/minimal-luxury.css +295 -0
- package/docs/design/foundations/technical.css +293 -0
- package/docs/design/foundations/warm-photo.css +299 -0
- package/docs/design/reference-corpus.md +1726 -0
- package/docs/design/render-critique-loop.md +57 -0
- package/docs/site/getting-started.md +21 -0
- package/docs/spec-graph-schema.md +397 -0
- package/package.json +3 -3
- package/scripts/bench-scorecard.mjs +239 -0
- package/scripts/build-spec-graph.mjs +509 -0
- package/scripts/classify-lane.sh +138 -0
- package/scripts/design-system-lint.mjs +993 -0
- package/scripts/graph-query.sh +486 -0
- package/scripts/transition-label.sh +169 -0
- package/scripts/validate-pr-target.sh +30 -0
- package/scripts/validate-spec-graph.sh +423 -0
- package/templates/devdocs/README.md +72 -0
- package/templates/devdocs/agent/using-forgedock.md +3 -0
- package/templates/devdocs/agent/using-github.md +3 -0
- package/templates/devdocs/index.yaml +162 -0
- package/templates/devdocs/project/architecture.md +3 -0
- package/templates/devdocs/project/conventions.md +3 -0
- package/templates/devdocs/project/custom-instructions.md +3 -0
- package/templates/devdocs/project/glossary.md +3 -0
- package/templates/devdocs/project/stack.md +3 -0
- package/templates/devdocs/templates/commit-convention.md +3 -0
- package/templates/devdocs/templates/issue.md +3 -0
- package/templates/devdocs/templates/pull-request.md +3 -0
- package/templates/devdocs/templates/review-finding.md +3 -0
- 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. |
|