@topy-ai/maggie 0.8.4 → 0.8.6
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-zh-TW.md +1 -1
- package/README.md +2 -2
- package/bundled-references/design-motion-production.md +234 -0
- package/bundled-references/landing-page-visual-defaults.md +101 -0
- package/bundled-skills/catalog.json +8 -0
- package/bundled-skills/maggie-design-motion/SKILL.md +299 -0
- package/bundled-skills/maggie-landing-page/SKILL.md +231 -0
- package/package.json +1 -1
- package/references/design-motion-production.md +234 -0
- package/references/landing-page-visual-defaults.md +101 -0
package/README-zh-TW.md
CHANGED
|
@@ -143,7 +143,7 @@ structure drift 或重複 treatment product 時 fail closed。
|
|
|
143
143
|
deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
|
|
144
144
|
publish 與 production deployment 需要明確確認。
|
|
145
145
|
|
|
146
|
-
目前 npm 版本為 `0.8.4
|
|
146
|
+
目前 npm 版本為 `0.8.6`,補齊兩個設計技能:加入完整的生產 reference(所有可複製的程式碼片段與預設表格),並把設計來源明確為 Figma 非必需(實時 URL、既有程式碼、Figma、或純截圖)。先前 0.8.5 採納兩個設計技能(改編自 MIT 的 `elayadesign/ai-design-skills`):maggie-landing-page(轉化策略 + 視覺系統紀律,具體數值為可被專案 DESIGN.md 覆蓋的預設值;由 maggie-design 生產與驗證)與 maggie-design-motion(靜態設計→像素級動畫 HTML 複刻→匯出 MP4 + GIF)。先前 0.8.4 將技能庫擴展成完整的「一人用 AI 跑一家公司」C-suite,新增八條職能線:maggie-sales(CRM 中立的銷售管線)、maggie-finance(只解釋的 CFO 指標,含確定性 maggie_finance.py 計算器)、maggie-support(面向客戶的客戶成功)、maggie-product(路線圖 + 預註冊實驗)、maggie-fundraising(pitch deck、data room、投資人更新)、maggie-legal(ToS/隱私/DPA/SOC2 readiness)、maggie-monetization(定價/paywall/dunning,金錢操作需人工批准)、maggie-orchestrator(CEO 編排層:將目標拆成帶審批門的 mission 分派到各職能線,含 maggie_orchestrator.py 規劃器)。先前 0.8.3 將 cmo.so/www 開發經驗沉澱進技能庫:新增兩個技能 maggie-skill-authoring(依 catalog 與 MCP 工具白名單撰寫/校驗社區營銷 playbook 技能)與 maggie-feedback-triage(反饋的團隊接收端:決策/定級/分派、PRD + issue 流程、24/7 隊列守護);新增多個 reference(OAuth PKCE 登入、Expo 約定與 Router 骨架、MCP evals 測試框架、conversation cards、WordPress→Firebase 認證遷移手冊);並增補 maggie-assistant-tools(tool≤user 每輪工具過濾、agent loop 安全不變量、preview-only 橋接代理、MCP resources/prompts、注解加固、CLI 與 ChatGPT 插件打包)、maggie-auth-reference(自清理登入測試套件、自簽單次令牌)、maggie-mobile-app(OTA 開機拉取、live==built 部署守衛)、maggie-deployment(pm2 單包發布、live-hash 校驗)與 maggie-ops(template-as-code 郵件同步)。先前 0.8.2 新增 maggie-spirit(為 AI 品牌生成平面像素 spirit/sprite 系統)與 goal-led AI agent 設計參考;0.8.0 為安全優化 roadmap(scan、image-patch、標頭評分、SSVC、GDPR 日誌規則、新 MCP 工具)。先前 0.8.0 為安全優化 roadmap(scan、image-patch、標頭評分、SSVC、GDPR 日誌規則、新 MCP 工具),含 fix-plan 修補風險分級 T1–T4(判斷補丁是否安全套用)與 host-impact 補丁影響面量測(哪些服務需重啟、是否需重開機)、public-check、Maggie Security、batch 31 排程 agent routines、報告收件匣與無人值守 agent 限制、PHP runtime 盤點與主機鎖、pull 網站改寫、query 點子與社群佇列檢查,batch 30 MaggieDash 先裝核心、release privacy gate、feedback 備援、社群連結與圖片檢查、大量頁面產生與 app 檢查,batch 29 預約規則、部署、app QA、助理與社群功能,以及 batch 28 產品 app、assistant tools、mobile、product release、skills catalog 與 bridge host 功能、batch 26、27 feedback 修正與 idempotent `maggie update`與 Booking email-template 合約/CLI validator 同步。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
|
|
147
147
|
dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
|
|
148
148
|
worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
|
|
149
149
|
camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
|
package/README.md
CHANGED
|
@@ -87,9 +87,9 @@ Then invoke the installed skills from your coding agent, for example:
|
|
|
87
87
|
|
|
88
88
|
Maggie keeps the existing project foundation and asks for decisions before
|
|
89
89
|
shared routes, analytics, or publishing boundaries change. The current
|
|
90
|
-
package ships
|
|
90
|
+
package ships 36 installable skills and a local-first MaggieDash foundation.
|
|
91
91
|
|
|
92
|
-
This package is `0.8.
|
|
92
|
+
This package is `0.8.6` — it completes the two adopted design skills with full production references (every copy-ready snippet and default table) and makes the design source Figma-optional (live URL, existing code, Figma, or a screenshot). `0.8.5` adopted the two design skills (adapted, MIT, from `elayadesign/ai-design-skills`): `maggie-landing-page` (conversion strategy + a visual-system discipline whose values defer to the project's DESIGN.md) and `maggie-design-motion` (static design → pixel-exact animated HTML replica → MP4 + GIF export). `0.8.4` expanded the library into a full "one person runs a company with AI" C-suite with eight new function lanes: `maggie-sales` (CRM-neutral pipeline), `maggie-finance` (explain-only CFO metrics + a deterministic `maggie_finance.py` calculator), `maggie-support` (customer success), `maggie-product` (roadmap + pre-registered experiments), `maggie-fundraising` (deck, data room, investor updates), `maggie-legal` (ToS/privacy/DPA/SOC2 readiness), `maggie-monetization` (pricing/paywalls/dunning; money actions human-approved), and `maggie-orchestrator` (the CEO layer that decomposes a goal into gated missions across the lanes, with a `maggie_orchestrator.py` planner). `0.8.3` folded cmo.so/www development learnings in (maggie-skill-authoring, maggie-feedback-triage, and mobile/MCP/auth references); `0.8.2` added `maggie-spirit`; `0.8.0` was the security optimization roadmap.
|
|
93
93
|
|
|
94
94
|
The 0.7.59 release made `maggie security host-audit` (the deep
|
|
95
95
|
lynis audit over SSH) robust on hosts where a storage test can stall: lynis runs
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Design-motion production detail
|
|
2
|
+
|
|
3
|
+
The complete, copy-ready snippets and tables for the `maggie-design-motion` skill.
|
|
4
|
+
The skill carries
|
|
5
|
+
the method in prose; this reference holds the full executable detail so nothing is
|
|
6
|
+
lost. Values are the defaults from the skill's "Make it yours" section. Adapted from
|
|
7
|
+
`elayadesign/ai-design-skills` `design-to-motion` (MIT); see the skill's Credits.
|
|
8
|
+
|
|
9
|
+
## Source alternatives (Figma is optional)
|
|
10
|
+
|
|
11
|
+
The best fidelity comes from a source with real layers and values, but **Figma is
|
|
12
|
+
not required**. In order of "least guessing":
|
|
13
|
+
|
|
14
|
+
1. **Live URL (recommended when the design is already on the web).** The design is
|
|
15
|
+
already rendered - do not replicate pixels. Open it in headless Chrome (the
|
|
16
|
+
project's browser tooling, as `maggie-clone`/`browser-inspection` do), read the
|
|
17
|
+
computed styles, fonts, and bounds from the DOM, and screenshot it for the
|
|
18
|
+
`?still` reference. You get exact values without pixel-lifting.
|
|
19
|
+
2. **Existing code / component.** If the design already exists as HTML/React/etc.,
|
|
20
|
+
skip replication entirely: mount it, add the motion layer (Part B) and the
|
|
21
|
+
capture (Part C) on top. This is the highest fidelity of all.
|
|
22
|
+
3. **Figma REST API.** Exact layers, bounds, fills, effects, radii (A2/A3). Best for
|
|
23
|
+
a design that lives only in Figma.
|
|
24
|
+
4. **Screenshot / flat image.** Always works; every element is rebuilt or lifted
|
|
25
|
+
from pixels (A2). Lowest fidelity, no external dependency at all.
|
|
26
|
+
|
|
27
|
+
Pick the first one that fits what the user actually has. Only paths 3-4 need the
|
|
28
|
+
pixel work below; paths 1-2 jump straight to the motion system.
|
|
29
|
+
|
|
30
|
+
## A3. Layer export placement (Figma)
|
|
31
|
+
|
|
32
|
+
Export each moving element as its own PNG at 3x (batched 20-25 ids):
|
|
33
|
+
|
|
34
|
+
| Case | Placement |
|
|
35
|
+
|---|---|
|
|
36
|
+
| PNG size equals `absoluteRenderBounds` (text always) | Place at the render bounds |
|
|
37
|
+
| PNG larger than render bounds (parent clipped it), even overhang | `x = bbox.x - (pngW - bbox.w)/2`; use `renderBounds.y` unless the top was clipped |
|
|
38
|
+
| Uneven overhang (arrowhead, one square cap) | Use `strokeGeometry`/`fillGeometry` bounds from the `geometry=paths` response |
|
|
39
|
+
| Rotated vectors with a large layer blur | `use_absolute_bounds=true`, place at the bbox; PSNR offset search only if that region < ~45 dB; blobs may store at 1x |
|
|
40
|
+
| Zero-height/width stroked vectors | `use_absolute_bounds=true` returns null; export without it |
|
|
41
|
+
| Mask group with blurred content | Anchor on the edge that was not clipped |
|
|
42
|
+
| Arcs, donuts, partial ellipses | `use_absolute_bounds=true` |
|
|
43
|
+
| Patterns/overlays larger than their surface | `use_absolute_bounds=true`, then crop to the surface rect |
|
|
44
|
+
| Instance children (`I12:34;56:78`) | Often null; export the parent instance |
|
|
45
|
+
| Lines that draw on | Write the centerline yourself and use it as a mask over the PNG (below) |
|
|
46
|
+
| Photo baked in a larger layer | Export the photo node separately; cover the baked photo with a skeleton, put the separate photo on top |
|
|
47
|
+
|
|
48
|
+
Keep Figma z-order; render frame strokes as an overlay above clipped content;
|
|
49
|
+
rebuild a container in CSS when its children must move separately; map drop-shadow
|
|
50
|
+
blur `R` to `box-shadow` blur `R` (or `filter: drop-shadow()` ~`R/2` on a group); a
|
|
51
|
+
group shadow is the shadow of the union; skip hidden layers.
|
|
52
|
+
|
|
53
|
+
### Drawing a line on
|
|
54
|
+
|
|
55
|
+
Read corner points and arc radii from `strokeGeometry` (centerline = halfway across
|
|
56
|
+
the stroke; a rounded corner's centerline radius = inner radius + half the stroke):
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<svg class="ly" viewBox="0 0 1440 990" style="left:0;top:0;width:1440px;height:990px">
|
|
60
|
+
<mask id="draw1">
|
|
61
|
+
<path d="M193 832 V700 A45 45 0 0 1 238 655 H380" fill="none" stroke="#fff" stroke-width="8"
|
|
62
|
+
pathLength="1" stroke-dasharray="1" stroke-dashoffset="1"/>
|
|
63
|
+
</mask>
|
|
64
|
+
<image href="data:image/png;base64,..." x="192" y="653" width="190" height="181" mask="url(#draw1)"/>
|
|
65
|
+
</svg>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Animate `stroke-dashoffset` 1 -> 0 on the JS clock. `pathLength="1"` avoids length
|
|
69
|
+
measuring; the wide mask stroke covers antialiasing. For travelling pulses, read
|
|
70
|
+
positions with `getPointAtLength` on the same path.
|
|
71
|
+
|
|
72
|
+
## A5. Clip pattern
|
|
73
|
+
|
|
74
|
+
Keeps stage coordinates valid inside a clip; for separate cards the card's unit
|
|
75
|
+
wraps its clip so the card and contents move together:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<div class="clip" style="left:113px;top:138px;width:305px;height:907px;border-radius:16px 0 0 16px">
|
|
79
|
+
<div class="cin" style="left:-113px;top:-138px"><!-- units in stage coordinates --></div>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
.clip { position: absolute; overflow: hidden; isolation: isolate; }
|
|
85
|
+
.cin { position: absolute; width: var(--stage-w); height: var(--stage-h); }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## B2. Loading choreography kinds
|
|
89
|
+
|
|
90
|
+
| Kind | Use for | From |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `card` | Separate cards, with shadow | `translateY(24px)`, `scale(0.985)`, `blur(6px)`, opacity 0 |
|
|
93
|
+
| `up` | Text, headers, values, rows | `translateY(10px)`, `blur(4px)`, opacity 0 |
|
|
94
|
+
| `down` | Top toolbars/bars | `translateY(-8px)`, `blur(4px)` |
|
|
95
|
+
| `left` | Sidebar items, list rows, labels | `translateX(-10px)`, `blur(4px)` |
|
|
96
|
+
| `pop` | Icons, badges, avatars, buttons, dots | `scale(0.82-0.88)` from own centre, `blur(4px)` |
|
|
97
|
+
| `photo` | Images | skeleton shimmer, then `blur(14px) saturate(0.6)` to sharp |
|
|
98
|
+
| `wipe` | Bars, fills, highlights | left-to-right `clip-path` reveal in pixels on the full-stage unit |
|
|
99
|
+
| `line`/`grow` | Rules, ticks, timelines | `scaleX(0)`/`scaleY(0)` from the start edge |
|
|
100
|
+
| `donut` | Rings, gauges | conic mask sweeping clockwise from 12 o'clock |
|
|
101
|
+
| `tray`/`rise` | Sheets, trays, folders | slide up 40px from the container's bottom edge, clipped |
|
|
102
|
+
| `fade` | Layers under another layer | opacity only |
|
|
103
|
+
|
|
104
|
+
Containers load in reading order 80-90ms apart; items 30-80ms; total intro ~2-2.5s.
|
|
105
|
+
Never `scale(0)` an object (only lines/bars grow from zero). Covered layers arrive
|
|
106
|
+
last and leave first. Skeletons match the real shape, 1px inside it.
|
|
107
|
+
|
|
108
|
+
## B4. Story + accent tables
|
|
109
|
+
|
|
110
|
+
| Visual | Story |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Stack (leads, notifications, files) | A new item lands on top with blur, others step back a slot, the last leaves |
|
|
113
|
+
| Line/area chart | A scrubber glides across, dots ride the curve, the tooltip counts; or the line redraws and numbers count up |
|
|
114
|
+
| Bar chart, equalizer, grid | Bars breathe like a live feed and settle to design heights; a dot/tooltip rides the active bar |
|
|
115
|
+
| Donut, gauge | Segments ease out one at a time and back; or the ring sweeps again clockwise |
|
|
116
|
+
| Table, list | A cursor inspects rows, the row highlights, a preview loads; or a soft highlight steps down |
|
|
117
|
+
| Calendar, scheduler | A highlight searches for an open slot, the event glides in, then back |
|
|
118
|
+
| Flow, connectors | Pulses travel along connectors in flow direction, nodes light up as a pulse passes |
|
|
119
|
+
| AI, prompt, form | The prompt deletes and retypes, chips evaluate one by one, the result appears with blur |
|
|
120
|
+
| Counters, KPIs | Numbers count up with the chart that drives them |
|
|
121
|
+
| Logo row | A continuous marquee moving exactly one full set per loop |
|
|
122
|
+
| Media player | The handle and progress advance while the play button pulses |
|
|
123
|
+
|
|
124
|
+
| Accent | Use for | Values |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| Pulse ring | Status dots, live indicators, play | 1.8-2.2s period, ring scale 1 -> 2.6, opacity 0.55 -> 0 |
|
|
127
|
+
| Heartbeat | Hearts, likes, sparkles | +20% over 0.35s, every 3s |
|
|
128
|
+
| Gentle bob | Floating objects | +/-2.6px, 2.8s sine |
|
|
129
|
+
| Sheen sweep | Photos, bars, buttons | band 38% width, peak 34% white, 1.5s pass every 3.4s |
|
|
130
|
+
| Skeleton glimmer | Placeholder lines | 2.4s shimmer |
|
|
131
|
+
|
|
132
|
+
### Story track helper
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// [[time, value], ...] eased per segment; values may be numbers or arrays
|
|
136
|
+
function track(keys, t, ease = easeInOut) {
|
|
137
|
+
if (t <= keys[0][0]) return keys[0][1];
|
|
138
|
+
for (let i = 1; i < keys.length; i++) {
|
|
139
|
+
const [t1, v1] = keys[i], [t0, v0] = keys[i - 1];
|
|
140
|
+
if (t <= t1) {
|
|
141
|
+
const k = ease(Math.min(1, (t - t0) / (t1 - t0 || 1)));
|
|
142
|
+
return Array.isArray(v0) ? v0.map((v, j) => v + (v1[j] - v) * k) : v0 + (v1 - v0) * k;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return keys[keys.length - 1][1];
|
|
146
|
+
}
|
|
147
|
+
// a slot that scrubs along a curve and returns to its design position
|
|
148
|
+
slot.style.transform = `translate(${track([[0,[0,0]],[1.2,[46,-18]],[2.6,[46,-18]],[3.8,[0,0]]], t).join('px,')}px)`;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## B5-B6. Cycle and easing
|
|
152
|
+
|
|
153
|
+
| Phase | Default timing |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Build in | 0 -> 2.4s |
|
|
156
|
+
| Stories | 2.4 -> 11.0s |
|
|
157
|
+
| Dissolve | 11.0 -> 11.6s |
|
|
158
|
+
| Empty beat | 11.6 -> 12.0s |
|
|
159
|
+
|
|
160
|
+
| Token | Curve | Use |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `--e` | `cubic-bezier(0.16, 1, 0.3, 1)` | entrance transforms (~1.05s) |
|
|
163
|
+
| `--e2` | `cubic-bezier(0.33, 1, 0.68, 1)` | opacity and blur (0.7-0.8s) |
|
|
164
|
+
| `--io` | `cubic-bezier(0.65, 0, 0.35, 1)` | wipes, fills, sweeps, camera |
|
|
165
|
+
| linear | `linear` | constant motion only (shimmer, playback) |
|
|
166
|
+
|
|
167
|
+
## C1. Deterministic capture (full)
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
await page.clock.install({ time: 0 });
|
|
171
|
+
await page.goto(url + '?capture', { waitUntil: 'load' });
|
|
172
|
+
await page.evaluate(() => document.fonts.ready);
|
|
173
|
+
await page.clock.pauseAt(10000);
|
|
174
|
+
const loopTime = [];
|
|
175
|
+
for (let i = 0; i < frames; i++) {
|
|
176
|
+
// 1. whole milliseconds that average exactly 1000/60 (17,17,16,...); runFor(1000/60) drifts
|
|
177
|
+
const dt = Math.round((i + 1) * 1000 / 60) - Math.round(i * 1000 / 60);
|
|
178
|
+
await page.clock.runFor(dt);
|
|
179
|
+
loopTime.push(await page.evaluate(([i, PRE, dt]) => {
|
|
180
|
+
if (i === PRE) window.__start(); // 2. start inside the same evaluate as the seek
|
|
181
|
+
if (i >= PRE) window.__tick(dt / 1000); // 3. the page clock advances here, never through rAF
|
|
182
|
+
const now = performance.now();
|
|
183
|
+
for (const a of document.getAnimations()) { // CSS transitions follow the fake clock
|
|
184
|
+
a.__m ??= window.__mark;
|
|
185
|
+
a.pause(); a.currentTime = Math.max(0, now - a.__m);
|
|
186
|
+
}
|
|
187
|
+
return i >= PRE ? window.__lt : 0;
|
|
188
|
+
}, [i, PRE, dt]));
|
|
189
|
+
await page.screenshot({ path: `${out}/${String(i).padStart(5, '0')}.png` });
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`PRE` is ~15 settle frames before the start. Viewport = frame size;
|
|
194
|
+
`deviceScaleFactor = min(1920/W, 1200/H)`; the export window starts at frame `PRE-1`
|
|
195
|
+
(loop time 0, empty state) and spans `cycle x 60` frames; record ~10 past it for the
|
|
196
|
+
seam check. Page side in `?capture`: `__start()` adds the `in` class and every state
|
|
197
|
+
change sets `window.__mark = performance.now()`; `__tick(dt)` advances loop time,
|
|
198
|
+
runs the cycle phases, draws stories/accents, sets `window.__lt`; never-ending loops
|
|
199
|
+
(shimmer, marquee) are driven by the JS clock, not infinite CSS keyframes.
|
|
200
|
+
|
|
201
|
+
## C2. Export commands
|
|
202
|
+
|
|
203
|
+
| File | Spec |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `<name>-x.mp4` | H.264, `yuv420p`, 60fps, CRF 14-15, preset slow, `+faststart`, max 1920x1200, even dims |
|
|
206
|
+
| `<name>-loop.gif` | 960px wide, 15-20fps, ~2-3MB |
|
|
207
|
+
| `<name>-800x500.gif` | always delivered; crop to content at 1.6:1 or fit+pad; 20fps (12 if heavy) |
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# MP4 for X
|
|
211
|
+
ffmpeg -start_number $A -framerate 60 -i frames/%05d.png -frames:v $N \
|
|
212
|
+
-vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -c:v libx264 -preset slow -crf 15 \
|
|
213
|
+
-pix_fmt yuv420p -movflags +faststart name-x.mp4
|
|
214
|
+
|
|
215
|
+
# website GIF (960px)
|
|
216
|
+
ffmpeg -start_number $A -framerate 60 -i frames/%05d.png -filter_complex \
|
|
217
|
+
"trim=end_frame=$N,fps=20,scale=960:-2:flags=lanczos,split[a][b];\
|
|
218
|
+
[a]palettegen=max_colors=192:stats_mode=diff[p];\
|
|
219
|
+
[b][p]paletteuse=dither=bayer:bayer_scale=4:diff_mode=rectangle" -loop 0 name-loop.gif
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For the 800x500 GIF, add before `split`:
|
|
223
|
+
`scale=800:500:force_original_aspect_ratio=decrease,pad=800:500:(ow-iw)/2:(oh-ih)/2:color=<page bg>`.
|
|
224
|
+
Keep bayer dithering whenever a photo is in frame (else photos band); if a GIF is
|
|
225
|
+
too heavy, lower frame rate or colours before size.
|
|
226
|
+
|
|
227
|
+
## Make it yours (defaults)
|
|
228
|
+
|
|
229
|
+
Rise 10px (`up`), 8px (`down`/`left`); entrance blur 4px (photos 14px); pop scale
|
|
230
|
+
0.82-0.88; transform 1.05s `--e`; opacity/blur 0.7-0.8s `--e2`; wipe 0.7s (slow bars
|
|
231
|
+
1.1s) `--io`; card rise 24px + `scale(0.985)` + `blur(6px)`; website hero appear 6px
|
|
232
|
+
rise + 2px blur, 50-70ms stagger, ~1.2s; container stagger 80-90ms; typing 45-70ms/
|
|
233
|
+
glyph; story start 2.4s; story length 3-8s; cycle 12s; MP4 60fps CRF 15; GIFs 960px
|
|
234
|
+
and 800x500 at 20fps.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Landing-page visual defaults
|
|
2
|
+
|
|
3
|
+
The complete default value tables for the `maggie-landing-page` skill (Part B). These are
|
|
4
|
+
**defaults**: the user's prompt and the project's own system (a `DESIGN.md`, token
|
|
5
|
+
set, or brand) win over them. Use them to fill gaps or when there is no system yet,
|
|
6
|
+
never to repaint an established brand. Adapted from `elayadesign/ai-design-skills`
|
|
7
|
+
`landing-page-design` (MIT; itself from `Leonxlnx/taste-skill`); see the skill's
|
|
8
|
+
Credits.
|
|
9
|
+
|
|
10
|
+
## Fonts
|
|
11
|
+
|
|
12
|
+
- **Default to:** Geist, Manrope, or Poppins (one typeface per site). A mono (Geist
|
|
13
|
+
Mono) only for code, data, or numeric UI where a monospace is functionally needed.
|
|
14
|
+
- **Avoid defaulting to:** Inter, Roboto, Arial, Open Sans, Helvetica.
|
|
15
|
+
- No italics in the interface; cap weight at bold (no 900/black).
|
|
16
|
+
- The project's font choice always wins.
|
|
17
|
+
|
|
18
|
+
## Type scale (snap every size to a step)
|
|
19
|
+
|
|
20
|
+
Snap to the closest step **below**, taking its paired line height. Never leave
|
|
21
|
+
arbitrary values (`text-[19px]`, `font-size: 22px`, `1.4rem`). Default scale is
|
|
22
|
+
Tailwind's:
|
|
23
|
+
|
|
24
|
+
| Class | Size | Line height |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `text-xs` | 12px (0.75rem) | 16px |
|
|
27
|
+
| `text-sm` | 14px (0.875rem) | 20px |
|
|
28
|
+
| `text-base` | 16px (1rem) | 24px |
|
|
29
|
+
| `text-lg` | 18px (1.125rem) | 28px |
|
|
30
|
+
| `text-xl` | 20px (1.25rem) | 28px |
|
|
31
|
+
| `text-2xl` | 24px (1.5rem) | 32px |
|
|
32
|
+
| `text-3xl` | 30px (1.875rem) | 36px |
|
|
33
|
+
| `text-4xl` | 36px (2.25rem) | 40px |
|
|
34
|
+
| `text-5xl` | 48px (3rem) | 1 |
|
|
35
|
+
| `text-6xl` | 60px (3.75rem) | 1 |
|
|
36
|
+
| `text-7xl` | 72px (4.5rem) | 1 |
|
|
37
|
+
| `text-8xl` | 96px (6rem) | 1 |
|
|
38
|
+
| `text-9xl` | 128px (8rem) | 1 |
|
|
39
|
+
|
|
40
|
+
Buttons: main `text-base` (16px) semibold; smaller header buttons `text-sm` (14px)
|
|
41
|
+
semibold.
|
|
42
|
+
|
|
43
|
+
## Spacing (only these values)
|
|
44
|
+
|
|
45
|
+
| Token | Value | | Token | Value |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| Spacing-0 | 0 | | Spacing-300 | 24px |
|
|
48
|
+
| Spacing-25 | 2px | | Spacing-400 | 32px |
|
|
49
|
+
| Spacing-50 | 4px | | Spacing-500 | 40px |
|
|
50
|
+
| Spacing-75 | 8px | | Spacing-600 | 48px |
|
|
51
|
+
| Spacing-100 | 12px | | Spacing-700 | 64px |
|
|
52
|
+
| Spacing-200 | 16px | | Spacing-800 | 80px |
|
|
53
|
+
| | | | Spacing-900 | 96px |
|
|
54
|
+
|
|
55
|
+
Main buttons: 8px vertical / 12px horizontal padding.
|
|
56
|
+
|
|
57
|
+
## Corner radius
|
|
58
|
+
|
|
59
|
+
Use the token radii. **Nested-radius formula** when a shape sits inside another and
|
|
60
|
+
the gap is under 32px: `inner = outer - gap`, applied only when the result is > 2px
|
|
61
|
+
(else leave square). Example: an outer card at 16px with 8px padding gives the inner
|
|
62
|
+
element an 8px radius.
|
|
63
|
+
|
|
64
|
+
## Backgrounds and hero
|
|
65
|
+
|
|
66
|
+
- Backgrounds are flat - no gradients. The one exception is gradient **text** on the
|
|
67
|
+
hero heading. (A project whose brand uses gradient surfaces overrides this.)
|
|
68
|
+
- Never border only one side of a card (all around or none).
|
|
69
|
+
- Default dark-mode background colours: `#000000`, `#181818`, `#1F1F1F`, `#272727`,
|
|
70
|
+
`#313131`, `#131209`.
|
|
71
|
+
- Hero heading text gradient: dark theme `#FFFFFF` -> `#9B9B9B`; light theme
|
|
72
|
+
`#000000` -> `#666666`, left to right. Cap heading and subheading width at ~680px;
|
|
73
|
+
break lines at meaningful points, never mid-phrase.
|
|
74
|
+
|
|
75
|
+
## Icons
|
|
76
|
+
|
|
77
|
+
Default to Phosphor, Solar, or Iconamoon; avoid Material Icons/Symbols. The
|
|
78
|
+
project's icon set wins; validate it with `maggie-design`'s icon inventory +
|
|
79
|
+
glyph-render gates before release.
|
|
80
|
+
|
|
81
|
+
## Motion
|
|
82
|
+
|
|
83
|
+
- Base curve `cubic-bezier(0.22,1,0.36,1)` with ~500ms for layout/position changes;
|
|
84
|
+
hover colour/opacity 300ms (never under 200ms). Never a browser-default curve
|
|
85
|
+
(`ease`, `linear`, `ease-in-out`).
|
|
86
|
+
- Animate only `transform` and `opacity`; never `top`/`left`/`width`/`height`.
|
|
87
|
+
- Respect `prefers-reduced-motion` (replace movement with a plain opacity change).
|
|
88
|
+
- Scroll reveals rise ~32px and fade over 600-800ms, once, via `IntersectionObserver`
|
|
89
|
+
(never an unthrottled scroll listener).
|
|
90
|
+
- Menu link entrance: items arrive in reading order, each rising ~24px while fading,
|
|
91
|
+
stepped 60ms apart, capped at 400ms total.
|
|
92
|
+
- Tagline reveal: words go from ~25-35% opacity to full colour one at a time in
|
|
93
|
+
reading order as each crosses a trigger line (base curve; `IntersectionObserver`
|
|
94
|
+
per word or an rAF-throttled listener).
|
|
95
|
+
|
|
96
|
+
## Banned filler words (content realism)
|
|
97
|
+
|
|
98
|
+
Do not write "revolutionize", "supercharge", "cutting edge", "effortless",
|
|
99
|
+
"transform your workflow", "take it to the next level", "harness the power", or
|
|
100
|
+
"world class". Say what the product does in plain terms. Sentence case for headings
|
|
101
|
+
and buttons; calm, specific system messages.
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"name": "maggie-design",
|
|
47
47
|
"description": "Design authorized interior pages, review rendered responsive layouts, or explicitly rebrand a packaged homepage/template. Use rebrand only with a named source brand and target brand."
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
"name": "maggie-design-motion",
|
|
51
|
+
"description": "Turn a static design (a Figma frame, screenshot, dashboard, card set, app screen, or a UI on a photo) into a pixel-exact HTML replica that loads from an empty state, then has every visual act out what it does in a seamless loop, and export an MP4 plus GIFs. Use when asked to animate a design, a Figma link, a screenshot, UI cards, a dashboard or hero image, or to make a showcase loop, motion mockup, or a video/GIF of a design. Also for bento/feature cards on a landing page."
|
|
52
|
+
},
|
|
49
53
|
{
|
|
50
54
|
"name": "maggie-feedback",
|
|
51
55
|
"description": "Collect, review, and explicitly submit privacy-safe feedback about a Maggie skill run, workflow result, bug, or feature request."
|
|
@@ -62,6 +66,10 @@
|
|
|
62
66
|
"name": "maggie-fundraising",
|
|
63
67
|
"description": "Fundraising and investor relations for a founder - draft a pitch deck and narrative, assemble a data room, write investor updates, keep cap-table and round hygiene, and prepare SEIS/EIS materials. Use when the user asks to prepare to raise, build a deck or data room, write an investor update, or organise round documents. Factual only; not securities, investment, legal, or tax advice. Pairs with maggie-finance and the TOPY/CEO tools."
|
|
64
68
|
},
|
|
69
|
+
{
|
|
70
|
+
"name": "maggie-landing-page",
|
|
71
|
+
"description": "Build a high-converting landing page - intake, page structure, layout selection, conversion copy, SEO/AEO, plus a visual-system discipline (typography, spacing, radius, backgrounds, hero, icons, motion) whose specific values are defaults the project's DESIGN.md overrides. Use when building, editing, styling, or writing copy for a landing page, marketing page, or a page section meant to convert. Designs the page; maggie-design produces and validates it."
|
|
72
|
+
},
|
|
65
73
|
{
|
|
66
74
|
"name": "maggie-legal",
|
|
67
75
|
"description": "Draft and maintain the routine legal and compliance documents a small company needs - terms of service, privacy policy, cookie/consent notice, DPA, and a SOC2/ISO readiness checklist - jurisdiction-aware and from reviewed templates. Use when the user asks for ToS/privacy/cookie/DPA documents, GDPR/CCPA compliance, or a security-compliance readiness map. Starting points for a qualified lawyer to review; not legal advice. Pairs with maggie-security."
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-design-motion
|
|
3
|
+
description: Turn a static design (a Figma frame, screenshot, dashboard, card set, app screen, or a UI on a photo) into a pixel-exact HTML replica that loads from an empty state, then has every visual act out what it does in a seamless loop, and export an MP4 plus GIFs. Use when asked to animate a design, a Figma link, a screenshot, UI cards, a dashboard or hero image, or to make a showcase loop, motion mockup, or a video/GIF of a design. Also for bento/feature cards on a landing page.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Design Motion
|
|
9
|
+
|
|
10
|
+
A showcase animation is not a redesign. The viewer must see **the exact design**,
|
|
11
|
+
and see that **it is alive**: exact frame -> empty state -> smooth load -> every
|
|
12
|
+
visual acts out its story -> seamless repeat.
|
|
13
|
+
|
|
14
|
+
This is the design-replica + video pipeline. For deterministic brand sprites and
|
|
15
|
+
avatars use [`maggie-spirit`](../maggie-spirit/SKILL.md); when the animation lives
|
|
16
|
+
on a landing page, pair with [`maggie-landing-page`](../maggie-landing-page/SKILL.md)
|
|
17
|
+
and produce through [`maggie-design`](../maggie-design/SKILL.md).
|
|
18
|
+
|
|
19
|
+
Follow the [Maggie Memory Hook](../../references/memory-hook.md) at invocation and
|
|
20
|
+
completion. Timing/distance/curve values live in **Make it yours** (defaults;
|
|
21
|
+
override there, never ad hoc). Adapted from `elayadesign/ai-design-skills` (MIT);
|
|
22
|
+
see Credits.
|
|
23
|
+
|
|
24
|
+
**Figma is optional.** Choose the source with the least guessing for what the user
|
|
25
|
+
actually has: a **live URL** (already rendered - read computed styles + screenshot
|
|
26
|
+
via the project's browser tooling, as `maggie-clone`/`browser-inspection` do; no
|
|
27
|
+
pixel-lifting), **existing code/component** (skip replication, add the motion layer
|
|
28
|
+
on top - highest fidelity), **Figma REST** (exact layers), or a **screenshot** (works
|
|
29
|
+
with zero dependencies, elements rebuilt/lifted from pixels). Only Figma and
|
|
30
|
+
screenshot need the pixel work in Part A; a live URL or existing code jumps to the
|
|
31
|
+
motion system. See [design-motion production detail](../../references/design-motion-production.md)
|
|
32
|
+
for the full source-alternatives list and every copy-ready snippet and table.
|
|
33
|
+
|
|
34
|
+
## Two contexts (decide first)
|
|
35
|
+
|
|
36
|
+
| | Showcase | Website |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| Request | "animate this design", a Figma link alone | building/editing a landing page/site |
|
|
39
|
+
| Animated | the whole frame | only sections with a visual, plus the hero |
|
|
40
|
+
| Start | page load, from empty | when the section scrolls into view, once |
|
|
41
|
+
| After intro | stories run, frame dissolves, cycle repeats | stories run while on screen; no dissolve |
|
|
42
|
+
| Text | exported layers (pixel-exact) | live HTML text (SEO/a11y); only visuals exported |
|
|
43
|
+
| Deliver | page + MP4 + website GIF + 800x500 GIF | the animated sections in the site; exports on request |
|
|
44
|
+
|
|
45
|
+
Inside a site's codebase assume website; with only a design assume showcase; if
|
|
46
|
+
still unclear, ask once. On a website, animate the hero (a small 6px-rise + blur
|
|
47
|
+
appear, reading order, ~1.2s, once on load) and bento/feature cards (intro on
|
|
48
|
+
scroll + a story loop on each visual); leave text-only sections to the page's normal
|
|
49
|
+
scroll reveal. Trigger with `IntersectionObserver`, pause stories off screen,
|
|
50
|
+
respect `prefers-reduced-motion`.
|
|
51
|
+
|
|
52
|
+
# Part A - Fidelity (the finished still must be indistinguishable from the source)
|
|
53
|
+
|
|
54
|
+
## A1. Intake (one batch)
|
|
55
|
+
|
|
56
|
+
Context (showcase/website); source (a Figma link with a node id is best; a
|
|
57
|
+
screenshot works but every layer is rebuilt from pixels); layout (app frame vs
|
|
58
|
+
separate cards, decided from the tree); background (a photo/texture must stay
|
|
59
|
+
exact); which parts are visuals that should act out a story; deliverables; camera
|
|
60
|
+
(default none). State any assumption in one line and continue.
|
|
61
|
+
|
|
62
|
+
## A2. Source of truth
|
|
63
|
+
|
|
64
|
+
- **Figma REST API** with a personal token. **Read the token from a secure store
|
|
65
|
+
(env var or OS keychain); never print, echo, paste, or commit it.**
|
|
66
|
+
`GET /v1/files/:key/nodes?ids=:node&geometry=paths` for the tree/bounds/fills/
|
|
67
|
+
effects/radii; `GET /v1/images/:key?ids=:node&format=png&scale=2` for the
|
|
68
|
+
reference frame; note the frame origin (layer pos = absolute bounds - origin);
|
|
69
|
+
retry CDN downloads and size-check each PNG.
|
|
70
|
+
- **Screenshot / flat image:** find the pixel ratio (image width / common frame
|
|
71
|
+
widths 1440/1280/1920/390, checked against ~14-18px body text). Measure in
|
|
72
|
+
screenshot pixels (pixel `i` centre is `i+0.5`). Make each element by the first
|
|
73
|
+
fitting method: rebuild surfaces/cards/bars in CSS (sample fills as a median
|
|
74
|
+
patch; trace gradients per 10-20px; fit shadows against the edge profile); keep
|
|
75
|
+
website text live HTML; for showcase text, match the font and overlay, else lift
|
|
76
|
+
it out of the pixels (`alpha = (bg - px)/(bg - fg)` per pixel against the rebuilt
|
|
77
|
+
surface); redraw simple icons as SVG; trace lines/charts as SVG; crop only photos/
|
|
78
|
+
avatars/logos to their radius. **What is hidden stays hidden - never invent it
|
|
79
|
+
silently.** Tell the user, in a short list, what was rebuilt / lifted / cropped /
|
|
80
|
+
not separable / guessed before animating.
|
|
81
|
+
|
|
82
|
+
## A3. Layer export (Figma)
|
|
83
|
+
|
|
84
|
+
Export every moving element as its own PNG at 3x (batched 20-25 ids), placed from
|
|
85
|
+
`absoluteRenderBounds` (text always) or the overhang formula / `strokeGeometry`
|
|
86
|
+
bounds for uneven overhang. Keep Figma z-order; render frame strokes as an overlay
|
|
87
|
+
above clipped content; rebuild a container in CSS when its children must move
|
|
88
|
+
separately (so the API does not bake them in); map Figma drop-shadow blur `R` to a
|
|
89
|
+
CSS `box-shadow` blur `R` (or `filter: drop-shadow()` ~`R/2` on a group); a group
|
|
90
|
+
shadow is the shadow of the union; skip hidden layers. For a line that draws on,
|
|
91
|
+
write its centerline as an SVG mask over the exported PNG and animate
|
|
92
|
+
`stroke-dashoffset` 1 -> 0 (`pathLength="1"`).
|
|
93
|
+
|
|
94
|
+
## A4. Background (sacred)
|
|
95
|
+
|
|
96
|
+
When the design sits on a photo/texture, the background is part of the design: it
|
|
97
|
+
never changes, moves, blurs, scales, fades, or recolours, in any variant. Export
|
|
98
|
+
the original image (`GET /v1/files/:key/images`) and apply the fill's scale mode /
|
|
99
|
+
`imageTransform` as CSS. Frosted panels (`backdrop-filter: blur()`) must be a
|
|
100
|
+
sibling above the background with `transform: translateZ(0)` + a `z-index` so
|
|
101
|
+
headless Chrome renders them. Verify the background region by PSNR (aim > 45 dB). If
|
|
102
|
+
you cannot reproduce it, say so before building - never swap in a flat colour
|
|
103
|
+
silently.
|
|
104
|
+
|
|
105
|
+
## A5. Layout: app frame or separate cards
|
|
106
|
+
|
|
107
|
+
- **App frame (joined surfaces):** sidebar + body etc. are present, opaque, and
|
|
108
|
+
still from the first frame; only the elements inside them load. Keep Figma
|
|
109
|
+
stacking where surfaces meet.
|
|
110
|
+
- **Separate cards (bento/widgets):** the cards themselves rise in (the `card` kind
|
|
111
|
+
in B2), staggered in reading order, contents following; the empty state is the
|
|
112
|
+
background alone.
|
|
113
|
+
- Both: each container clips its own contents to its exact radii; image-shaped
|
|
114
|
+
surfaces get an average-colour placeholder; nested content loads like any element.
|
|
115
|
+
|
|
116
|
+
## A6. Page architecture (showcase)
|
|
117
|
+
|
|
118
|
+
One self-contained HTML file: a fixed-size stage at the frame's exact size, scaled
|
|
119
|
+
to fit; images inline as base64 (deduped); wait for `<img>` decode. Modes: `?still`
|
|
120
|
+
(exact frame, no motion, also reduced-motion), `?loop` (repeats like exports),
|
|
121
|
+
`?capture` (rAF off; exposes `window.__start()`, `__tick(dt)`, loop time `__lt` so
|
|
122
|
+
the recorder drives the clock). One JS clock (`requestAnimationFrame`) drives all
|
|
123
|
+
time-based effects from loop time, never wall clock. On a website, build inside the
|
|
124
|
+
site's code instead; the clock/units/modes still apply per section.
|
|
125
|
+
|
|
126
|
+
## A7. Verify the still
|
|
127
|
+
|
|
128
|
+
Render `?still` at 2x and compare with the reference before any motion: PSNR whole
|
|
129
|
+
frame and per container after a light blur (aim > 35 dB overall, > 30 dB per
|
|
130
|
+
container; text-heavy containers sit near 30 dB); look at a blurred amplified diff
|
|
131
|
+
for structural errors (misplaced glow, wrong stacking, missing stroke); for
|
|
132
|
+
screenshot sources also score the rebuilt surfaces with lifted regions masked out.
|
|
133
|
+
Fix structure, not sub-pixel antialiasing.
|
|
134
|
+
|
|
135
|
+
# Part B - Motion system
|
|
136
|
+
|
|
137
|
+
## B1-B3. Start empty, load in, bars
|
|
138
|
+
|
|
139
|
+
The first frame of every load/export is the empty state (never the finished frame).
|
|
140
|
+
Elements **rise from below and resolve blur -> sharp**, staggered. Loading kinds:
|
|
141
|
+
`card` (whole separate cards, with shadow), `up`/`down`/`left` (text, bars, rows,
|
|
142
|
+
sidebars), `pop` (icons/badges/avatars from their own centre), `photo` (skeleton
|
|
143
|
+
shimmer then blur -> sharp), `wipe` (left-to-right `clip-path` reveal in pixels on
|
|
144
|
+
the full-stage unit), `line`/`grow` (draw from the start edge), `donut` (conic
|
|
145
|
+
sweep), `tray`/`rise` (slide up inside the container), `fade` (covered layers).
|
|
146
|
+
Containers load in reading order 80-90ms apart; items 30-80ms. Never `scale(0)` an
|
|
147
|
+
object (only lines/bars grow from zero). Data bars/meters fill like progress
|
|
148
|
+
(left-to-right `clip-path`, 0.7-1.1s). **Never add loading lines not in the design.**
|
|
149
|
+
|
|
150
|
+
## B4. Story loops (the main act)
|
|
151
|
+
|
|
152
|
+
The intro is the opening; the main act is every visual **acting out what it does**,
|
|
153
|
+
on a loop - a lead list receives leads, a chart is read, a calendar finds a slot, a
|
|
154
|
+
prompt is typed. Write each story in one sentence first. Rules: start and end on the
|
|
155
|
+
exact design (at rest the frame equals `?still`); use the design's own elements;
|
|
156
|
+
change text only when it is the point (counters need live text in the matching font;
|
|
157
|
+
typing reveals the exported text layer glyph by glyph, 45-70ms, with a blinking
|
|
158
|
+
caret); obvious not busy (a clear action every 1-2s); readable at the 800x500 GIF
|
|
159
|
+
size; offset cards so attention travels in reading order; one story per card, 3-8s,
|
|
160
|
+
may repeat. Build stories from eased keyframe tracks on the JS clock:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// [[time, value], ...] eased per segment; values may be numbers or arrays
|
|
164
|
+
function track(keys, t, ease = easeInOut) {
|
|
165
|
+
if (t <= keys[0][0]) return keys[0][1];
|
|
166
|
+
for (let i = 1; i < keys.length; i++) {
|
|
167
|
+
const [t1, v1] = keys[i], [t0, v0] = keys[i - 1];
|
|
168
|
+
if (t <= t1) {
|
|
169
|
+
const k = ease(Math.min(1, (t - t0) / (t1 - t0 || 1)));
|
|
170
|
+
return Array.isArray(v0) ? v0.map((v, j) => v + (v1[j] - v) * k) : v0 + (v1 - v0) * k;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return keys[keys.length - 1][1];
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Accents sit on top of stories (pulse ring, heartbeat, gentle bob, sheen sweep,
|
|
178
|
+
skeleton glimmer), starting after the intro lands (~2.4s).
|
|
179
|
+
|
|
180
|
+
## B5-B8. Cycle, easing, camera, perf
|
|
181
|
+
|
|
182
|
+
Showcase cycle (default): build in 0->2.4s, stories 2.4->11.0s, dissolve
|
|
183
|
+
11.0->11.6s, empty beat 11.6->12.0s; the story phase holds at least one full story
|
|
184
|
+
of every card; dissolve back to the empty state (app frame keeps its joined
|
|
185
|
+
surfaces, background never dissolves); reset every JS value too, so the end frame
|
|
186
|
+
equals the start frame. On a website there is no cycle (intro once on scroll,
|
|
187
|
+
stories until off screen). Easing defaults: `--e` `cubic-bezier(0.16,1,0.3,1)`
|
|
188
|
+
(entrance transforms ~1.05s), `--e2` `cubic-bezier(0.33,1,0.68,1)` (opacity/blur
|
|
189
|
+
0.7-0.8s), `--io` `cubic-bezier(0.65,0,0.35,1)` (wipes/fills/sweeps), `linear` only
|
|
190
|
+
for constant motion. No zoom/pan unless asked (then only the app frame scales, the
|
|
191
|
+
background never moves). Animate `transform`/`opacity`/`filter`/`clip-path` only,
|
|
192
|
+
never `top`/`left`/`width`/`height`; keep animated blur <= 14px; reduced motion
|
|
193
|
+
renders the still.
|
|
194
|
+
|
|
195
|
+
## B9. Motion checks (before encoding, on the recorded frames)
|
|
196
|
+
|
|
197
|
+
- **Overflow:** build a mask from the page's real container shapes (exact radii,
|
|
198
|
+
grown 2px). App frame: every pixel outside the joined surfaces equals the empty
|
|
199
|
+
first frame (PSNR inf). Separate cards: during stories, every pixel outside the
|
|
200
|
+
cards (and their shadows) equals `?still`.
|
|
201
|
+
- **Seam:** the first export frame equals the frame one cycle later.
|
|
202
|
+
- **Empty beat:** every empty-beat frame equals the frame before `__start` (catches
|
|
203
|
+
JS state that failed to reset).
|
|
204
|
+
- **Rest:** the last frame before the dissolve equals the landed frame (PSNR inf),
|
|
205
|
+
proving every story returned to the design.
|
|
206
|
+
- **Contact sheet:** tile key moments and look for overflow, ghosting, pops, and
|
|
207
|
+
stories too timid to notice.
|
|
208
|
+
|
|
209
|
+
# Part C - Recording and export
|
|
210
|
+
|
|
211
|
+
## C1. Deterministic capture (never real-time screen recording)
|
|
212
|
+
|
|
213
|
+
Step a fake clock and screenshot every frame. Three details make the loop smooth
|
|
214
|
+
and the seam clean (all in the snippet): whole-millisecond steps that average
|
|
215
|
+
1000/60; `__start()` inside the same evaluate as the seek; the page clock advances
|
|
216
|
+
through `__tick`, never rAF.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
await page.clock.install({ time: 0 });
|
|
220
|
+
await page.goto(url + '?capture', { waitUntil: 'load' });
|
|
221
|
+
await page.evaluate(() => document.fonts.ready);
|
|
222
|
+
await page.clock.pauseAt(10000);
|
|
223
|
+
for (let i = 0; i < frames; i++) {
|
|
224
|
+
const dt = Math.round((i + 1) * 1000 / 60) - Math.round(i * 1000 / 60);
|
|
225
|
+
await page.clock.runFor(dt);
|
|
226
|
+
await page.evaluate(([i, PRE, dt]) => {
|
|
227
|
+
if (i === PRE) window.__start();
|
|
228
|
+
if (i >= PRE) window.__tick(dt / 1000);
|
|
229
|
+
const now = performance.now();
|
|
230
|
+
for (const a of document.getAnimations()) { a.__m ??= window.__mark; a.pause(); a.currentTime = Math.max(0, now - a.__m); }
|
|
231
|
+
}, [i, PRE, dt]);
|
|
232
|
+
await page.screenshot({ path: `${out}/${String(i).padStart(5, '0')}.png` });
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`PRE` is ~15 settle frames before the start. Viewport = frame size;
|
|
237
|
+
`deviceScaleFactor = min(1920/W, 1200/H)`; the export window starts at frame
|
|
238
|
+
`PRE-1` (loop time 0) and spans `cycle x 60` frames, recording ~10 past it for the
|
|
239
|
+
seam check.
|
|
240
|
+
|
|
241
|
+
## C2. Formats
|
|
242
|
+
|
|
243
|
+
- `<name>-x.mp4` - H.264, `yuv420p`, 60fps, CRF 14-15, preset slow, `+faststart`,
|
|
244
|
+
max 1920x1200, even dimensions.
|
|
245
|
+
- `<name>-loop.gif` - 960px wide, 15-20fps, ~2-3MB, for the website.
|
|
246
|
+
- `<name>-800x500.gif` - always delivered; crop to content at 1.6:1 (or fit + pad
|
|
247
|
+
with the page background), 20fps (12 if heavy). Keep bayer dithering whenever a
|
|
248
|
+
photo is in frame.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
ffmpeg -start_number $A -framerate 60 -i frames/%05d.png -frames:v $N \
|
|
252
|
+
-vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -c:v libx264 -preset slow -crf 15 \
|
|
253
|
+
-pix_fmt yuv420p -movflags +faststart name-x.mp4
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
# Make it yours (defaults; edit here, never inline)
|
|
257
|
+
|
|
258
|
+
Rise 10px (`up`), 8px (`down`/`left`); entrance blur 4px (photos 14px); pop scale
|
|
259
|
+
0.82-0.88; transform 1.05s `--e`; opacity/blur 0.7-0.8s `--e2`; wipe 0.7s (slow bars
|
|
260
|
+
1.1s) `--io`; card rise 24px + `scale(0.985)` + `blur(6px)`; website hero appear 6px
|
|
261
|
+
rise + 2px blur, 50-70ms stagger, ~1.2s; container stagger 80-90ms; typing 45-70ms/
|
|
262
|
+
glyph; story start 2.4s; story length 3-8s; cycle 12s; MP4 60fps CRF 15; GIFs 960px
|
|
263
|
+
and 800x500 at 20fps.
|
|
264
|
+
|
|
265
|
+
# Output format
|
|
266
|
+
|
|
267
|
+
Before building: context (showcase/website, with per-section mapping on a website);
|
|
268
|
+
layout (app frame + static surfaces, or separate cards); background (kept exactly or
|
|
269
|
+
what cannot be reproduced); choreography (load order + kind per group); stories (one
|
|
270
|
+
sentence per visual + accents); cycle (length + phases). After building: still
|
|
271
|
+
fidelity (overall + worst-surface PSNR); overflow + seam results; the files with
|
|
272
|
+
size/dimensions/fps/duration; anything not reproduced exactly, named plainly.
|
|
273
|
+
|
|
274
|
+
# Guardrails
|
|
275
|
+
|
|
276
|
+
- The Figma token is read from a secure store and is **never printed, echoed,
|
|
277
|
+
pasted into chat, or committed**; do not store it in Maggie artifacts.
|
|
278
|
+
- Never fake fidelity: never animate the screenshot as one block, move crops that
|
|
279
|
+
carry their old background, or invent hidden areas silently. Report what was
|
|
280
|
+
rebuilt / lifted / cropped / guessed before animating.
|
|
281
|
+
- The background is sacred (A4); every variant keeps it.
|
|
282
|
+
- Local tools only (Figma REST is free, Playwright + ffmpeg run locally); no
|
|
283
|
+
third-party upload of the user's design.
|
|
284
|
+
|
|
285
|
+
# Related skills
|
|
286
|
+
|
|
287
|
+
- `maggie-spirit` - deterministic brand sprites/avatars (a different motion job).
|
|
288
|
+
- `maggie-landing-page` / `maggie-design` - where website motion lives and is
|
|
289
|
+
produced.
|
|
290
|
+
|
|
291
|
+
# Credits
|
|
292
|
+
|
|
293
|
+
Adapted (not transcribed) from
|
|
294
|
+
[`elayadesign/ai-design-skills`](https://github.com/elayadesign/ai-design-skills)
|
|
295
|
+
`design-to-motion` by Elaya (MIT), which adapts general motion rules from
|
|
296
|
+
[`emilkowalski/skills`](https://github.com/emilkowalski/skills),
|
|
297
|
+
[`Owl-Listener/designer-skills`](https://github.com/Owl-Listener/designer-skills),
|
|
298
|
+
and [`dylantarre/animation-principles`](https://github.com/dylantarre/animation-principles)
|
|
299
|
+
(all MIT). Defaults originate there.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: maggie-landing-page
|
|
3
|
+
description: Build a high-converting landing page - intake, page structure, layout selection, conversion copy, SEO/AEO, plus a visual-system discipline (typography, spacing, radius, backgrounds, hero, icons, motion) whose specific values are defaults the project's DESIGN.md overrides. Use when building, editing, styling, or writing copy for a landing page, marketing page, or a page section meant to convert. Designs the page; maggie-design produces and validates it.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Maggie Landing Page
|
|
9
|
+
|
|
10
|
+
A landing page is not a homepage. A homepage serves many intents; a landing page
|
|
11
|
+
wins one: **one offer -> one audience -> one primary action.** This skill decides
|
|
12
|
+
what the page says and how it looks; [`maggie-design`](../maggie-design/SKILL.md)
|
|
13
|
+
produces it and runs the responsive/icon/QA evidence gates, and
|
|
14
|
+
[`maggie-seo-geo`](../maggie-seo-geo/SKILL.md) owns the SEO/AEO content side.
|
|
15
|
+
|
|
16
|
+
Follow the [Maggie Memory Hook](../../references/memory-hook.md) at invocation and
|
|
17
|
+
completion.
|
|
18
|
+
|
|
19
|
+
**Values defer to the project.** The specific numbers and choices in Part B
|
|
20
|
+
(fonts, type scale, spacing, radii, colours, motion curves) are sensible
|
|
21
|
+
**defaults**. Precedence is: the user's explicit prompt, then the project's own
|
|
22
|
+
system (a `DESIGN.md`, token set, or brand - see `maggie-design` and the
|
|
23
|
+
artifact-design fundamentals), then these defaults. Never override a project's
|
|
24
|
+
established palette or type system with the defaults below; use them only to fill
|
|
25
|
+
gaps or when there is no system yet.
|
|
26
|
+
|
|
27
|
+
Adapted from `elayadesign/ai-design-skills` (MIT); see Credits.
|
|
28
|
+
|
|
29
|
+
# Part A - Strategy and structure
|
|
30
|
+
|
|
31
|
+
Work through A before B.
|
|
32
|
+
|
|
33
|
+
## A1. Intake (ask once, in one batch, only what is missing)
|
|
34
|
+
|
|
35
|
+
- **Purpose:** the ONE primary action (trial, demo, buy, waitlist, download); the
|
|
36
|
+
exact offer; what counts as a conversion.
|
|
37
|
+
- **Audience:** the ICP; the problem they are solving; their top three objections;
|
|
38
|
+
the traffic source (ads/search/social/email); what they already know on arrival.
|
|
39
|
+
- **Proof/assets:** logos, testimonials, numbers, case studies; screenshots, demo,
|
|
40
|
+
product GIFs; guarantees and refund/cancel terms.
|
|
41
|
+
- **Constraints:** brand voice; design direction; mobile priority.
|
|
42
|
+
|
|
43
|
+
If the user cannot answer, make a reasonable assumption, state it in one line, and
|
|
44
|
+
continue. Do not stall the build.
|
|
45
|
+
|
|
46
|
+
## A2. Page structure
|
|
47
|
+
|
|
48
|
+
**Above the fold (required):** headline (outcome + audience); subheadline
|
|
49
|
+
(the how + specificity); one primary CTA (verb + what they get); one proof signal
|
|
50
|
+
(logo strip, stat, short testimonial); hero visual.
|
|
51
|
+
|
|
52
|
+
**Mid page (the argument):** problem -> solution; three to five outcome-driven
|
|
53
|
+
benefits; a three-step "how it works"; social proof. Place the mandatory **tagline
|
|
54
|
+
reveal** (B8) here as its own moment, not stacked under the hero.
|
|
55
|
+
|
|
56
|
+
**Bottom (objection handling):** FAQ (six to twelve); risk reversal; a final CTA
|
|
57
|
+
identical to the top.
|
|
58
|
+
|
|
59
|
+
## A3. Layout selection (pick one, say why)
|
|
60
|
+
|
|
61
|
+
| Type | Use when |
|
|
62
|
+
|---|---|
|
|
63
|
+
| A. Classic hero + sections | The product reads from a hero screenshot. Most common. |
|
|
64
|
+
| B. Long-form story | You must educate and overcome skepticism. |
|
|
65
|
+
| C. Minimal conversion page | High-intent traffic, or a short offer (download, waitlist). |
|
|
66
|
+
| D. Comparison page | Search intent includes alternatives ("X vs Y", "best for"). |
|
|
67
|
+
|
|
68
|
+
## A4. Conversion rules
|
|
69
|
+
|
|
70
|
+
- **Match message to source** - mirror the ad/email promise and tone in the hero.
|
|
71
|
+
- **One primary CTA**; never competing CTAs above the fold.
|
|
72
|
+
- **Benefit first** - what the feature means for them, not what it does.
|
|
73
|
+
- **Be specific** - "cut weekly reporting from 4 hours to 15 minutes", not "save
|
|
74
|
+
time and streamline".
|
|
75
|
+
- **Reduce risk** - at least one of: free trial, free plan, no card, cancel
|
|
76
|
+
anytime, money-back.
|
|
77
|
+
- **Objections are a section, not a footnote**; put proof beside the claim it
|
|
78
|
+
supports.
|
|
79
|
+
|
|
80
|
+
## A5. Copywriting
|
|
81
|
+
|
|
82
|
+
- **Headlines:** "{Outcome} without {pain}"; "The {category} for {audience}";
|
|
83
|
+
"Ship {result} in {time}".
|
|
84
|
+
- **Subheadline:** one or two sentences; what it is and who it is for.
|
|
85
|
+
- **CTA:** verb + what they get ("Start free trial", "Book a demo"), never "Learn
|
|
86
|
+
more" or "Submit".
|
|
87
|
+
- **Benefit bullets:** bold benefit, then proof/detail.
|
|
88
|
+
|
|
89
|
+
## A6. Build order (section by section, never rebuild the whole page each pass)
|
|
90
|
+
|
|
91
|
+
Hero -> Benefits -> How it works -> Proof -> FAQ -> Final CTA. This keeps diffs
|
|
92
|
+
reviewable and is how `maggie-design` produces each band.
|
|
93
|
+
|
|
94
|
+
## A7. SEO and AEO
|
|
95
|
+
|
|
96
|
+
`noindex` ad-only or time-bound pages; index evergreen offers where search intent
|
|
97
|
+
matches the promise, with a clear title + meta, internal links, and the FAQ in
|
|
98
|
+
plain Q&A (add FAQ schema if apt). Hand the SEO/AEO specifics to
|
|
99
|
+
[`maggie-seo-geo`](../maggie-seo-geo/SKILL.md).
|
|
100
|
+
|
|
101
|
+
## A8. Pitfalls
|
|
102
|
+
|
|
103
|
+
Too many CTAs above the fold; vague value props; a feature list with no outcomes;
|
|
104
|
+
proof buried; mobile that breaks readability; no clear next step.
|
|
105
|
+
|
|
106
|
+
# Part B - Visual system (defaults; the project's DESIGN.md wins)
|
|
107
|
+
|
|
108
|
+
Resolve every visual value through a rule, never ad hoc. When the project has a
|
|
109
|
+
token system, use it; the values below are the starting defaults. The **complete
|
|
110
|
+
default tables** (the full type scale, spacing tokens, dark-mode colours, hero
|
|
111
|
+
gradients, motion curves and durations, banned filler words) are in
|
|
112
|
+
[landing-page visual defaults](../../references/landing-page-visual-defaults.md); the
|
|
113
|
+
summary below is the working subset.
|
|
114
|
+
|
|
115
|
+
## B1. Typography
|
|
116
|
+
|
|
117
|
+
- **Default fonts:** a clean geometric/grotesk (the upstream default set is Geist /
|
|
118
|
+
Manrope / Poppins, plus a mono only for code/numeric UI). Avoid defaulting to
|
|
119
|
+
Inter/Roboto/Arial. No italics in UI; one typeface per site unless asked; cap
|
|
120
|
+
weight at bold (no 900/black). The **project's font choice always wins.**
|
|
121
|
+
- **Copy:** no hyphens inside sentences (rewrite the phrase); no orphaned last-line
|
|
122
|
+
words (`text-wrap: balance` for headings, `pretty` for body).
|
|
123
|
+
- **Type scale:** snap every size to a defined scale step (the default is Tailwind's
|
|
124
|
+
scale); never leave arbitrary `text-[19px]`/`1.4rem`. Snap to the closest step
|
|
125
|
+
below, taking its paired line height.
|
|
126
|
+
|
|
127
|
+
## B2. Spacing
|
|
128
|
+
|
|
129
|
+
Use one spacing token set (default: 0, 2, 4, 8, 12, 16, 24, 32, 40, 48, 64, 80,
|
|
130
|
+
96 px); nothing between or outside it. Main buttons: 8px vertical / 12px horizontal
|
|
131
|
+
padding.
|
|
132
|
+
|
|
133
|
+
## B3. Corner radius
|
|
134
|
+
|
|
135
|
+
Use the token radii. **Nested-radius formula** when a shape sits inside another and
|
|
136
|
+
the gap is under 32px: `inner = outer - gap`, applied only when the result is > 2px
|
|
137
|
+
(else leave square).
|
|
138
|
+
|
|
139
|
+
## B4. Borders and backgrounds
|
|
140
|
+
|
|
141
|
+
Never border only one side of a card (all around or none). Backgrounds are flat -
|
|
142
|
+
no gradients (the one exception is gradient *text* on the hero heading). This is a
|
|
143
|
+
default; a project whose brand uses gradient surfaces overrides it.
|
|
144
|
+
|
|
145
|
+
## B5. Hero
|
|
146
|
+
|
|
147
|
+
Hero heading may use a left-to-right text gradient (light->mid). Cap heading and
|
|
148
|
+
subheading width (default ~680px) and break lines at meaningful points, never
|
|
149
|
+
mid-phrase.
|
|
150
|
+
|
|
151
|
+
## B6. Icons
|
|
152
|
+
|
|
153
|
+
Default to a consistent modern set (upstream: Phosphor / Solar / Iconamoon); avoid
|
|
154
|
+
Material by default. The project's icon set wins; validate it with `maggie-design`'s
|
|
155
|
+
icon inventory + glyph-render gates before release.
|
|
156
|
+
|
|
157
|
+
## B7. Motion
|
|
158
|
+
|
|
159
|
+
Weighted, decelerating motion; never a browser-default curve. Default base curve
|
|
160
|
+
`cubic-bezier(0.22,1,0.36,1)` ~500ms for layout/position; 300ms (never under 200ms)
|
|
161
|
+
for hover colour/opacity. Animate only `transform`/`opacity`. Respect
|
|
162
|
+
`prefers-reduced-motion` (replace movement with a plain opacity change). Scroll
|
|
163
|
+
reveals rise ~32px and fade over 600-800ms, once, via `IntersectionObserver` (never
|
|
164
|
+
an unthrottled scroll listener). If the animation becomes a showcase/video, hand it
|
|
165
|
+
to [`maggie-design-motion`](../maggie-design-motion/SKILL.md).
|
|
166
|
+
|
|
167
|
+
## B8. Content, states, ship, tagline reveal
|
|
168
|
+
|
|
169
|
+
- **Content realism:** real draft copy (no lorem, no "Your headline here");
|
|
170
|
+
believable, non-repeating names; industry-fitting invented brands; non-round
|
|
171
|
+
numbers; sentence case; calm specific system messages. Banned filler:
|
|
172
|
+
"revolutionize", "supercharge", "cutting edge", "effortless", "transform your
|
|
173
|
+
workflow", "world class", and the like.
|
|
174
|
+
- **States:** every interactive element ships hover / active / **focus ring** /
|
|
175
|
+
loading (skeletons shaped like the layout, not spinners) / empty (a composed
|
|
176
|
+
getting-started view) / error (inline, specific, never `alert()`). No dead links;
|
|
177
|
+
the current nav item is indicated.
|
|
178
|
+
- **Ship requirements:** privacy/terms links, branded 404, client-side form
|
|
179
|
+
validation, skip-to-content, cookie consent where required (see `maggie-legal`),
|
|
180
|
+
branded favicon, `<title>`/meta/`og:image`, alt text, semantic HTML, a way back
|
|
181
|
+
from every page.
|
|
182
|
+
- **Tagline reveal (mandatory):** one large-type section (default `text-4xl`..`6xl`)
|
|
183
|
+
stating the core benefit, min two lines, width-capped, separate from the hero.
|
|
184
|
+
Each word transitions from a muted tone (~25-35% opacity) to full colour in
|
|
185
|
+
reading order as it crosses a trigger line (base curve, `IntersectionObserver`
|
|
186
|
+
per word or an rAF-throttled listener - never unthrottled).
|
|
187
|
+
|
|
188
|
+
# Output format
|
|
189
|
+
|
|
190
|
+
Before code, return: page outline; hero copy (headline/subheadline/CTA/proof);
|
|
191
|
+
three to five benefits; three "how it works" steps; six to twelve FAQ Q&A;
|
|
192
|
+
SEO/AEO (index or noindex + title/meta); layout recommendation (A/B/C/D) and why.
|
|
193
|
+
Then build section by section per A6, through `maggie-design`.
|
|
194
|
+
|
|
195
|
+
# Quality bar
|
|
196
|
+
|
|
197
|
+
- One offer / one audience / one primary action; no competing CTAs above the fold;
|
|
198
|
+
specific numbers; at least one risk reversal; proof beside its claim.
|
|
199
|
+
- Every visual value resolves through a rule or the project's token system, never
|
|
200
|
+
ad hoc; the project's brand/palette/type is respected over the defaults.
|
|
201
|
+
- All six states present; no dead links; 404/legal/validation/favicon/meta/alt done.
|
|
202
|
+
- Tagline reveal present; motion uses the base curve and IntersectionObserver.
|
|
203
|
+
|
|
204
|
+
# Guardrails
|
|
205
|
+
|
|
206
|
+
- Values here are defaults; the user's prompt and the project's DESIGN.md/tokens
|
|
207
|
+
win. Never repaint an established brand with the defaults.
|
|
208
|
+
- Real content only; never invent testimonials, customer logos, or metrics as if
|
|
209
|
+
genuine - draft clearly-sample copy, and mark placeholder proof as placeholder.
|
|
210
|
+
- Production, responsive review, icon gates, and QA evidence run through
|
|
211
|
+
`maggie-design`; this skill does not bypass them.
|
|
212
|
+
- Legal/consent wording belongs to `maggie-legal`; SEO specifics to
|
|
213
|
+
`maggie-seo-geo`.
|
|
214
|
+
|
|
215
|
+
# Related skills
|
|
216
|
+
|
|
217
|
+
- `maggie-design` - produces and validates the page (bands, icon inventory,
|
|
218
|
+
responsive/QA evidence).
|
|
219
|
+
- `maggie-seo-geo` - SEO/AEO content and measurement.
|
|
220
|
+
- `maggie-design-motion` - when a section becomes an animated showcase or a
|
|
221
|
+
video/GIF.
|
|
222
|
+
- `maggie-spirit` - brand marks and avatars; `maggie-legal` - consent/terms.
|
|
223
|
+
|
|
224
|
+
# Credits
|
|
225
|
+
|
|
226
|
+
Adapted (not transcribed) from
|
|
227
|
+
[`elayadesign/ai-design-skills`](https://github.com/elayadesign/ai-design-skills)
|
|
228
|
+
`landing-page-design` by Elaya (MIT), which itself adapts content-realism and
|
|
229
|
+
motion ideas from [`Leonxlnx/taste-skill`](https://github.com/Leonxlnx/taste-skill)
|
|
230
|
+
by Leon Lin (MIT). The specific numeric defaults originate there; our version makes
|
|
231
|
+
them overridable and defers to the project's design system.
|
package/package.json
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Design-motion production detail
|
|
2
|
+
|
|
3
|
+
The complete, copy-ready snippets and tables for the `maggie-design-motion` skill.
|
|
4
|
+
The skill carries
|
|
5
|
+
the method in prose; this reference holds the full executable detail so nothing is
|
|
6
|
+
lost. Values are the defaults from the skill's "Make it yours" section. Adapted from
|
|
7
|
+
`elayadesign/ai-design-skills` `design-to-motion` (MIT); see the skill's Credits.
|
|
8
|
+
|
|
9
|
+
## Source alternatives (Figma is optional)
|
|
10
|
+
|
|
11
|
+
The best fidelity comes from a source with real layers and values, but **Figma is
|
|
12
|
+
not required**. In order of "least guessing":
|
|
13
|
+
|
|
14
|
+
1. **Live URL (recommended when the design is already on the web).** The design is
|
|
15
|
+
already rendered - do not replicate pixels. Open it in headless Chrome (the
|
|
16
|
+
project's browser tooling, as `maggie-clone`/`browser-inspection` do), read the
|
|
17
|
+
computed styles, fonts, and bounds from the DOM, and screenshot it for the
|
|
18
|
+
`?still` reference. You get exact values without pixel-lifting.
|
|
19
|
+
2. **Existing code / component.** If the design already exists as HTML/React/etc.,
|
|
20
|
+
skip replication entirely: mount it, add the motion layer (Part B) and the
|
|
21
|
+
capture (Part C) on top. This is the highest fidelity of all.
|
|
22
|
+
3. **Figma REST API.** Exact layers, bounds, fills, effects, radii (A2/A3). Best for
|
|
23
|
+
a design that lives only in Figma.
|
|
24
|
+
4. **Screenshot / flat image.** Always works; every element is rebuilt or lifted
|
|
25
|
+
from pixels (A2). Lowest fidelity, no external dependency at all.
|
|
26
|
+
|
|
27
|
+
Pick the first one that fits what the user actually has. Only paths 3-4 need the
|
|
28
|
+
pixel work below; paths 1-2 jump straight to the motion system.
|
|
29
|
+
|
|
30
|
+
## A3. Layer export placement (Figma)
|
|
31
|
+
|
|
32
|
+
Export each moving element as its own PNG at 3x (batched 20-25 ids):
|
|
33
|
+
|
|
34
|
+
| Case | Placement |
|
|
35
|
+
|---|---|
|
|
36
|
+
| PNG size equals `absoluteRenderBounds` (text always) | Place at the render bounds |
|
|
37
|
+
| PNG larger than render bounds (parent clipped it), even overhang | `x = bbox.x - (pngW - bbox.w)/2`; use `renderBounds.y` unless the top was clipped |
|
|
38
|
+
| Uneven overhang (arrowhead, one square cap) | Use `strokeGeometry`/`fillGeometry` bounds from the `geometry=paths` response |
|
|
39
|
+
| Rotated vectors with a large layer blur | `use_absolute_bounds=true`, place at the bbox; PSNR offset search only if that region < ~45 dB; blobs may store at 1x |
|
|
40
|
+
| Zero-height/width stroked vectors | `use_absolute_bounds=true` returns null; export without it |
|
|
41
|
+
| Mask group with blurred content | Anchor on the edge that was not clipped |
|
|
42
|
+
| Arcs, donuts, partial ellipses | `use_absolute_bounds=true` |
|
|
43
|
+
| Patterns/overlays larger than their surface | `use_absolute_bounds=true`, then crop to the surface rect |
|
|
44
|
+
| Instance children (`I12:34;56:78`) | Often null; export the parent instance |
|
|
45
|
+
| Lines that draw on | Write the centerline yourself and use it as a mask over the PNG (below) |
|
|
46
|
+
| Photo baked in a larger layer | Export the photo node separately; cover the baked photo with a skeleton, put the separate photo on top |
|
|
47
|
+
|
|
48
|
+
Keep Figma z-order; render frame strokes as an overlay above clipped content;
|
|
49
|
+
rebuild a container in CSS when its children must move separately; map drop-shadow
|
|
50
|
+
blur `R` to `box-shadow` blur `R` (or `filter: drop-shadow()` ~`R/2` on a group); a
|
|
51
|
+
group shadow is the shadow of the union; skip hidden layers.
|
|
52
|
+
|
|
53
|
+
### Drawing a line on
|
|
54
|
+
|
|
55
|
+
Read corner points and arc radii from `strokeGeometry` (centerline = halfway across
|
|
56
|
+
the stroke; a rounded corner's centerline radius = inner radius + half the stroke):
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<svg class="ly" viewBox="0 0 1440 990" style="left:0;top:0;width:1440px;height:990px">
|
|
60
|
+
<mask id="draw1">
|
|
61
|
+
<path d="M193 832 V700 A45 45 0 0 1 238 655 H380" fill="none" stroke="#fff" stroke-width="8"
|
|
62
|
+
pathLength="1" stroke-dasharray="1" stroke-dashoffset="1"/>
|
|
63
|
+
</mask>
|
|
64
|
+
<image href="data:image/png;base64,..." x="192" y="653" width="190" height="181" mask="url(#draw1)"/>
|
|
65
|
+
</svg>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Animate `stroke-dashoffset` 1 -> 0 on the JS clock. `pathLength="1"` avoids length
|
|
69
|
+
measuring; the wide mask stroke covers antialiasing. For travelling pulses, read
|
|
70
|
+
positions with `getPointAtLength` on the same path.
|
|
71
|
+
|
|
72
|
+
## A5. Clip pattern
|
|
73
|
+
|
|
74
|
+
Keeps stage coordinates valid inside a clip; for separate cards the card's unit
|
|
75
|
+
wraps its clip so the card and contents move together:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<div class="clip" style="left:113px;top:138px;width:305px;height:907px;border-radius:16px 0 0 16px">
|
|
79
|
+
<div class="cin" style="left:-113px;top:-138px"><!-- units in stage coordinates --></div>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
.clip { position: absolute; overflow: hidden; isolation: isolate; }
|
|
85
|
+
.cin { position: absolute; width: var(--stage-w); height: var(--stage-h); }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## B2. Loading choreography kinds
|
|
89
|
+
|
|
90
|
+
| Kind | Use for | From |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `card` | Separate cards, with shadow | `translateY(24px)`, `scale(0.985)`, `blur(6px)`, opacity 0 |
|
|
93
|
+
| `up` | Text, headers, values, rows | `translateY(10px)`, `blur(4px)`, opacity 0 |
|
|
94
|
+
| `down` | Top toolbars/bars | `translateY(-8px)`, `blur(4px)` |
|
|
95
|
+
| `left` | Sidebar items, list rows, labels | `translateX(-10px)`, `blur(4px)` |
|
|
96
|
+
| `pop` | Icons, badges, avatars, buttons, dots | `scale(0.82-0.88)` from own centre, `blur(4px)` |
|
|
97
|
+
| `photo` | Images | skeleton shimmer, then `blur(14px) saturate(0.6)` to sharp |
|
|
98
|
+
| `wipe` | Bars, fills, highlights | left-to-right `clip-path` reveal in pixels on the full-stage unit |
|
|
99
|
+
| `line`/`grow` | Rules, ticks, timelines | `scaleX(0)`/`scaleY(0)` from the start edge |
|
|
100
|
+
| `donut` | Rings, gauges | conic mask sweeping clockwise from 12 o'clock |
|
|
101
|
+
| `tray`/`rise` | Sheets, trays, folders | slide up 40px from the container's bottom edge, clipped |
|
|
102
|
+
| `fade` | Layers under another layer | opacity only |
|
|
103
|
+
|
|
104
|
+
Containers load in reading order 80-90ms apart; items 30-80ms; total intro ~2-2.5s.
|
|
105
|
+
Never `scale(0)` an object (only lines/bars grow from zero). Covered layers arrive
|
|
106
|
+
last and leave first. Skeletons match the real shape, 1px inside it.
|
|
107
|
+
|
|
108
|
+
## B4. Story + accent tables
|
|
109
|
+
|
|
110
|
+
| Visual | Story |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Stack (leads, notifications, files) | A new item lands on top with blur, others step back a slot, the last leaves |
|
|
113
|
+
| Line/area chart | A scrubber glides across, dots ride the curve, the tooltip counts; or the line redraws and numbers count up |
|
|
114
|
+
| Bar chart, equalizer, grid | Bars breathe like a live feed and settle to design heights; a dot/tooltip rides the active bar |
|
|
115
|
+
| Donut, gauge | Segments ease out one at a time and back; or the ring sweeps again clockwise |
|
|
116
|
+
| Table, list | A cursor inspects rows, the row highlights, a preview loads; or a soft highlight steps down |
|
|
117
|
+
| Calendar, scheduler | A highlight searches for an open slot, the event glides in, then back |
|
|
118
|
+
| Flow, connectors | Pulses travel along connectors in flow direction, nodes light up as a pulse passes |
|
|
119
|
+
| AI, prompt, form | The prompt deletes and retypes, chips evaluate one by one, the result appears with blur |
|
|
120
|
+
| Counters, KPIs | Numbers count up with the chart that drives them |
|
|
121
|
+
| Logo row | A continuous marquee moving exactly one full set per loop |
|
|
122
|
+
| Media player | The handle and progress advance while the play button pulses |
|
|
123
|
+
|
|
124
|
+
| Accent | Use for | Values |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| Pulse ring | Status dots, live indicators, play | 1.8-2.2s period, ring scale 1 -> 2.6, opacity 0.55 -> 0 |
|
|
127
|
+
| Heartbeat | Hearts, likes, sparkles | +20% over 0.35s, every 3s |
|
|
128
|
+
| Gentle bob | Floating objects | +/-2.6px, 2.8s sine |
|
|
129
|
+
| Sheen sweep | Photos, bars, buttons | band 38% width, peak 34% white, 1.5s pass every 3.4s |
|
|
130
|
+
| Skeleton glimmer | Placeholder lines | 2.4s shimmer |
|
|
131
|
+
|
|
132
|
+
### Story track helper
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// [[time, value], ...] eased per segment; values may be numbers or arrays
|
|
136
|
+
function track(keys, t, ease = easeInOut) {
|
|
137
|
+
if (t <= keys[0][0]) return keys[0][1];
|
|
138
|
+
for (let i = 1; i < keys.length; i++) {
|
|
139
|
+
const [t1, v1] = keys[i], [t0, v0] = keys[i - 1];
|
|
140
|
+
if (t <= t1) {
|
|
141
|
+
const k = ease(Math.min(1, (t - t0) / (t1 - t0 || 1)));
|
|
142
|
+
return Array.isArray(v0) ? v0.map((v, j) => v + (v1[j] - v) * k) : v0 + (v1 - v0) * k;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return keys[keys.length - 1][1];
|
|
146
|
+
}
|
|
147
|
+
// a slot that scrubs along a curve and returns to its design position
|
|
148
|
+
slot.style.transform = `translate(${track([[0,[0,0]],[1.2,[46,-18]],[2.6,[46,-18]],[3.8,[0,0]]], t).join('px,')}px)`;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## B5-B6. Cycle and easing
|
|
152
|
+
|
|
153
|
+
| Phase | Default timing |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Build in | 0 -> 2.4s |
|
|
156
|
+
| Stories | 2.4 -> 11.0s |
|
|
157
|
+
| Dissolve | 11.0 -> 11.6s |
|
|
158
|
+
| Empty beat | 11.6 -> 12.0s |
|
|
159
|
+
|
|
160
|
+
| Token | Curve | Use |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `--e` | `cubic-bezier(0.16, 1, 0.3, 1)` | entrance transforms (~1.05s) |
|
|
163
|
+
| `--e2` | `cubic-bezier(0.33, 1, 0.68, 1)` | opacity and blur (0.7-0.8s) |
|
|
164
|
+
| `--io` | `cubic-bezier(0.65, 0, 0.35, 1)` | wipes, fills, sweeps, camera |
|
|
165
|
+
| linear | `linear` | constant motion only (shimmer, playback) |
|
|
166
|
+
|
|
167
|
+
## C1. Deterministic capture (full)
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
await page.clock.install({ time: 0 });
|
|
171
|
+
await page.goto(url + '?capture', { waitUntil: 'load' });
|
|
172
|
+
await page.evaluate(() => document.fonts.ready);
|
|
173
|
+
await page.clock.pauseAt(10000);
|
|
174
|
+
const loopTime = [];
|
|
175
|
+
for (let i = 0; i < frames; i++) {
|
|
176
|
+
// 1. whole milliseconds that average exactly 1000/60 (17,17,16,...); runFor(1000/60) drifts
|
|
177
|
+
const dt = Math.round((i + 1) * 1000 / 60) - Math.round(i * 1000 / 60);
|
|
178
|
+
await page.clock.runFor(dt);
|
|
179
|
+
loopTime.push(await page.evaluate(([i, PRE, dt]) => {
|
|
180
|
+
if (i === PRE) window.__start(); // 2. start inside the same evaluate as the seek
|
|
181
|
+
if (i >= PRE) window.__tick(dt / 1000); // 3. the page clock advances here, never through rAF
|
|
182
|
+
const now = performance.now();
|
|
183
|
+
for (const a of document.getAnimations()) { // CSS transitions follow the fake clock
|
|
184
|
+
a.__m ??= window.__mark;
|
|
185
|
+
a.pause(); a.currentTime = Math.max(0, now - a.__m);
|
|
186
|
+
}
|
|
187
|
+
return i >= PRE ? window.__lt : 0;
|
|
188
|
+
}, [i, PRE, dt]));
|
|
189
|
+
await page.screenshot({ path: `${out}/${String(i).padStart(5, '0')}.png` });
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`PRE` is ~15 settle frames before the start. Viewport = frame size;
|
|
194
|
+
`deviceScaleFactor = min(1920/W, 1200/H)`; the export window starts at frame `PRE-1`
|
|
195
|
+
(loop time 0, empty state) and spans `cycle x 60` frames; record ~10 past it for the
|
|
196
|
+
seam check. Page side in `?capture`: `__start()` adds the `in` class and every state
|
|
197
|
+
change sets `window.__mark = performance.now()`; `__tick(dt)` advances loop time,
|
|
198
|
+
runs the cycle phases, draws stories/accents, sets `window.__lt`; never-ending loops
|
|
199
|
+
(shimmer, marquee) are driven by the JS clock, not infinite CSS keyframes.
|
|
200
|
+
|
|
201
|
+
## C2. Export commands
|
|
202
|
+
|
|
203
|
+
| File | Spec |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `<name>-x.mp4` | H.264, `yuv420p`, 60fps, CRF 14-15, preset slow, `+faststart`, max 1920x1200, even dims |
|
|
206
|
+
| `<name>-loop.gif` | 960px wide, 15-20fps, ~2-3MB |
|
|
207
|
+
| `<name>-800x500.gif` | always delivered; crop to content at 1.6:1 or fit+pad; 20fps (12 if heavy) |
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# MP4 for X
|
|
211
|
+
ffmpeg -start_number $A -framerate 60 -i frames/%05d.png -frames:v $N \
|
|
212
|
+
-vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" -c:v libx264 -preset slow -crf 15 \
|
|
213
|
+
-pix_fmt yuv420p -movflags +faststart name-x.mp4
|
|
214
|
+
|
|
215
|
+
# website GIF (960px)
|
|
216
|
+
ffmpeg -start_number $A -framerate 60 -i frames/%05d.png -filter_complex \
|
|
217
|
+
"trim=end_frame=$N,fps=20,scale=960:-2:flags=lanczos,split[a][b];\
|
|
218
|
+
[a]palettegen=max_colors=192:stats_mode=diff[p];\
|
|
219
|
+
[b][p]paletteuse=dither=bayer:bayer_scale=4:diff_mode=rectangle" -loop 0 name-loop.gif
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For the 800x500 GIF, add before `split`:
|
|
223
|
+
`scale=800:500:force_original_aspect_ratio=decrease,pad=800:500:(ow-iw)/2:(oh-ih)/2:color=<page bg>`.
|
|
224
|
+
Keep bayer dithering whenever a photo is in frame (else photos band); if a GIF is
|
|
225
|
+
too heavy, lower frame rate or colours before size.
|
|
226
|
+
|
|
227
|
+
## Make it yours (defaults)
|
|
228
|
+
|
|
229
|
+
Rise 10px (`up`), 8px (`down`/`left`); entrance blur 4px (photos 14px); pop scale
|
|
230
|
+
0.82-0.88; transform 1.05s `--e`; opacity/blur 0.7-0.8s `--e2`; wipe 0.7s (slow bars
|
|
231
|
+
1.1s) `--io`; card rise 24px + `scale(0.985)` + `blur(6px)`; website hero appear 6px
|
|
232
|
+
rise + 2px blur, 50-70ms stagger, ~1.2s; container stagger 80-90ms; typing 45-70ms/
|
|
233
|
+
glyph; story start 2.4s; story length 3-8s; cycle 12s; MP4 60fps CRF 15; GIFs 960px
|
|
234
|
+
and 800x500 at 20fps.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Landing-page visual defaults
|
|
2
|
+
|
|
3
|
+
The complete default value tables for the `maggie-landing-page` skill (Part B). These are
|
|
4
|
+
**defaults**: the user's prompt and the project's own system (a `DESIGN.md`, token
|
|
5
|
+
set, or brand) win over them. Use them to fill gaps or when there is no system yet,
|
|
6
|
+
never to repaint an established brand. Adapted from `elayadesign/ai-design-skills`
|
|
7
|
+
`landing-page-design` (MIT; itself from `Leonxlnx/taste-skill`); see the skill's
|
|
8
|
+
Credits.
|
|
9
|
+
|
|
10
|
+
## Fonts
|
|
11
|
+
|
|
12
|
+
- **Default to:** Geist, Manrope, or Poppins (one typeface per site). A mono (Geist
|
|
13
|
+
Mono) only for code, data, or numeric UI where a monospace is functionally needed.
|
|
14
|
+
- **Avoid defaulting to:** Inter, Roboto, Arial, Open Sans, Helvetica.
|
|
15
|
+
- No italics in the interface; cap weight at bold (no 900/black).
|
|
16
|
+
- The project's font choice always wins.
|
|
17
|
+
|
|
18
|
+
## Type scale (snap every size to a step)
|
|
19
|
+
|
|
20
|
+
Snap to the closest step **below**, taking its paired line height. Never leave
|
|
21
|
+
arbitrary values (`text-[19px]`, `font-size: 22px`, `1.4rem`). Default scale is
|
|
22
|
+
Tailwind's:
|
|
23
|
+
|
|
24
|
+
| Class | Size | Line height |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `text-xs` | 12px (0.75rem) | 16px |
|
|
27
|
+
| `text-sm` | 14px (0.875rem) | 20px |
|
|
28
|
+
| `text-base` | 16px (1rem) | 24px |
|
|
29
|
+
| `text-lg` | 18px (1.125rem) | 28px |
|
|
30
|
+
| `text-xl` | 20px (1.25rem) | 28px |
|
|
31
|
+
| `text-2xl` | 24px (1.5rem) | 32px |
|
|
32
|
+
| `text-3xl` | 30px (1.875rem) | 36px |
|
|
33
|
+
| `text-4xl` | 36px (2.25rem) | 40px |
|
|
34
|
+
| `text-5xl` | 48px (3rem) | 1 |
|
|
35
|
+
| `text-6xl` | 60px (3.75rem) | 1 |
|
|
36
|
+
| `text-7xl` | 72px (4.5rem) | 1 |
|
|
37
|
+
| `text-8xl` | 96px (6rem) | 1 |
|
|
38
|
+
| `text-9xl` | 128px (8rem) | 1 |
|
|
39
|
+
|
|
40
|
+
Buttons: main `text-base` (16px) semibold; smaller header buttons `text-sm` (14px)
|
|
41
|
+
semibold.
|
|
42
|
+
|
|
43
|
+
## Spacing (only these values)
|
|
44
|
+
|
|
45
|
+
| Token | Value | | Token | Value |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| Spacing-0 | 0 | | Spacing-300 | 24px |
|
|
48
|
+
| Spacing-25 | 2px | | Spacing-400 | 32px |
|
|
49
|
+
| Spacing-50 | 4px | | Spacing-500 | 40px |
|
|
50
|
+
| Spacing-75 | 8px | | Spacing-600 | 48px |
|
|
51
|
+
| Spacing-100 | 12px | | Spacing-700 | 64px |
|
|
52
|
+
| Spacing-200 | 16px | | Spacing-800 | 80px |
|
|
53
|
+
| | | | Spacing-900 | 96px |
|
|
54
|
+
|
|
55
|
+
Main buttons: 8px vertical / 12px horizontal padding.
|
|
56
|
+
|
|
57
|
+
## Corner radius
|
|
58
|
+
|
|
59
|
+
Use the token radii. **Nested-radius formula** when a shape sits inside another and
|
|
60
|
+
the gap is under 32px: `inner = outer - gap`, applied only when the result is > 2px
|
|
61
|
+
(else leave square). Example: an outer card at 16px with 8px padding gives the inner
|
|
62
|
+
element an 8px radius.
|
|
63
|
+
|
|
64
|
+
## Backgrounds and hero
|
|
65
|
+
|
|
66
|
+
- Backgrounds are flat - no gradients. The one exception is gradient **text** on the
|
|
67
|
+
hero heading. (A project whose brand uses gradient surfaces overrides this.)
|
|
68
|
+
- Never border only one side of a card (all around or none).
|
|
69
|
+
- Default dark-mode background colours: `#000000`, `#181818`, `#1F1F1F`, `#272727`,
|
|
70
|
+
`#313131`, `#131209`.
|
|
71
|
+
- Hero heading text gradient: dark theme `#FFFFFF` -> `#9B9B9B`; light theme
|
|
72
|
+
`#000000` -> `#666666`, left to right. Cap heading and subheading width at ~680px;
|
|
73
|
+
break lines at meaningful points, never mid-phrase.
|
|
74
|
+
|
|
75
|
+
## Icons
|
|
76
|
+
|
|
77
|
+
Default to Phosphor, Solar, or Iconamoon; avoid Material Icons/Symbols. The
|
|
78
|
+
project's icon set wins; validate it with `maggie-design`'s icon inventory +
|
|
79
|
+
glyph-render gates before release.
|
|
80
|
+
|
|
81
|
+
## Motion
|
|
82
|
+
|
|
83
|
+
- Base curve `cubic-bezier(0.22,1,0.36,1)` with ~500ms for layout/position changes;
|
|
84
|
+
hover colour/opacity 300ms (never under 200ms). Never a browser-default curve
|
|
85
|
+
(`ease`, `linear`, `ease-in-out`).
|
|
86
|
+
- Animate only `transform` and `opacity`; never `top`/`left`/`width`/`height`.
|
|
87
|
+
- Respect `prefers-reduced-motion` (replace movement with a plain opacity change).
|
|
88
|
+
- Scroll reveals rise ~32px and fade over 600-800ms, once, via `IntersectionObserver`
|
|
89
|
+
(never an unthrottled scroll listener).
|
|
90
|
+
- Menu link entrance: items arrive in reading order, each rising ~24px while fading,
|
|
91
|
+
stepped 60ms apart, capped at 400ms total.
|
|
92
|
+
- Tagline reveal: words go from ~25-35% opacity to full colour one at a time in
|
|
93
|
+
reading order as each crosses a trigger line (base curve; `IntersectionObserver`
|
|
94
|
+
per word or an rAF-throttled listener).
|
|
95
|
+
|
|
96
|
+
## Banned filler words (content realism)
|
|
97
|
+
|
|
98
|
+
Do not write "revolutionize", "supercharge", "cutting edge", "effortless",
|
|
99
|
+
"transform your workflow", "take it to the next level", "harness the power", or
|
|
100
|
+
"world class". Say what the product does in plain terms. Sentence case for headings
|
|
101
|
+
and buttons; calm, specific system messages.
|