chore(skills): sync design-motion-principles from upstream (make update)

Vendored-skill content refreshed by update-all.sh step 8 during the soak
update runs: demo-shell + output-format reworked upstream, new
report-template.html reference. Content-only, no wiring change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
Bastien Chanot
2026-07-08 17:13:07 +02:00
co-authored by Claude Opus 4.8
parent 3049250150
commit 82ce02cf28
3 changed files with 1431 additions and 501 deletions
@@ -1,63 +1,77 @@
<!-- <!--
Demo Shell — design-motion-principles v2.1 Demo Shell — design-motion-principles
=========================================== =====================================
This file is a template the audit agent reads during STEP 3 of the Minimal, isolated reference for a single demo card. The agent reads this
audit workflow (see ../SKILL.md). The agent embeds one .demo-card per during STEP 3 of the audit workflow (see ../SKILL.md) and uses it as the
Critical or Important finding (per R4 in the plan — Opportunities do per-finding template — one .demo block per Critical or Important finding
not get demo cards). (Opportunities don't get demo cards).
How to use this file as the agent The full worked example with five demo cards in context lives in
--------------------------------- references/report-template.html. This file is intentionally minimal:
one card, every contract visible, no report scaffolding.
How the agent uses this file
----------------------------
1. Copy the entire <style> block into the report's <head>. The shell's 1. Copy the entire <style> block into the report's <head>. The shell's
CSS variables, .demo-card layout, loop indicator, and the tokens, .demo layout, stage colors, segmented control, and the
prefers-reduced-motion guard are shared across all demo cards in prefers-reduced-motion guard are SHARED across every demo card in the
the report — they are not duplicated per finding. report and must not be duplicated per finding.
2. For each finding {n} (1-indexed across the whole report): 2. For each finding {n} (1-indexed across the whole report):
a. Replace the MOTION-CODE-SLOT-{n} comment in <style> with the a. Replace the MOTION-CODE-SLOT-{n} comment with the per-finding
per-finding @keyframes block AND any .demo-card-{n}__motion-target @keyframes m{n} block AND the .demo-{n}__mt selector rule.
selector rules. Use the suffix {n} so multiple findings in one Suffix {n} so multiple findings cannot collide on keyframe
report do not collide on keyframe names or target selectors. names or selectors.
b. Replace the DEMO-CARD-MOTION-SLOT-{n} comment in the .demo-card b. Replace the DEMO-CARD-MOTION-SLOT-{n} comment in the .demo__stage
markup with the actual motion-target element. Its class must be markup with the actual motion-target element. Its class must be
.demo-card-{n}__motion-target so it matches the rules above. .demo-{n}__mt so it matches the rules above.
c. Set the .demo-card__header text to a short title for the c. Set .demo__title to a short title for the recommended motion
recommended motion (e.g., "Subtle enter: opacity + translateY + blur"). (e.g., "Quick tab crossfade", "Sheet enter (mirror for exit)").
d. Set the .demo-card__subhead text to the duration plus easing or d. Set .demo__timing to the duration plus easing (e.g.,
other relevant values (e.g., "300ms · ease-out"). The subhead "180ms · ease-out", "300ms · ease-out-quint"). The subhead
ALWAYS renders — the agent populates it for every demo so card ALWAYS renders — populate it for every demo so card heights
heights stay consistent across the report. stay consistent across the report.
e. Renumber the four radio ids from st1-* to st{n}-*, and the
labels' for= attributes to match.
3. Per-finding code MUST honor these contracts: 3. Per-finding code MUST honor these contracts:
- Do not redefine the shell's CSS variables (--bg, --fg, --border, - Do NOT redefine the shell's tokens (--ink, --paper, --surface-2,
--accent, --loop-dim, --card-radius, --card-padding, --gap, --line, --line-strong, --st-bg, --st-fg, --st-line, --st-dim,
--sans, --mono). Use them via var(). --sans, --mono). Use them via var().
- Do not modify the prefers-reduced-motion block. The shell's - Do NOT modify the prefers-reduced-motion block. The shell's
guard collapses all .demo-card-{n}__motion-target animations to guard collapses all .demo-{n}__mt animations to none. The
none. The per-finding @keyframes 100% state must match the per-finding @keyframes 100% state MUST match the motion-target
motion-target's default rendered state so the reduce-motion element's default static rendering so the reduce-motion fallback
fallback shows the correct final visual. shows the correct final visual.
- Per-finding @keyframes use the 0% / 66% / 100% cadence: - Per-finding @keyframes use the 0% / ~60% / 100% cadence:
0% = start state, 66% = motion complete (~2s in), 100% = hold 0% = start state, ~60% = motion complete (~1.8s in),
(~1s). The shell uses animation-duration: 3s. 100% = hold (~1.2s). The shell uses animation-duration: 3s.
4. Demo cards are non-interactive. They have no hover or focus state 4. Motion targets and ANY UI primitives inside the stage (.ui-btn,
beyond the default outline suppression. tabindex="-1" keeps them .ui-card, .ui-row, .ui-check, .ui-num, .ui-label, badge, etc.)
out of keyboard nav order — readers tab through findings, not use --st-bg / --st-fg / --st-line / --st-dim — NEVER --accent or
through demo cards. other page tokens. This guarantees the demo contrasts correctly
when its stage is locked to a different theme than the report.
5. Demo cards are non-interactive. The stage toggle radios are the
only interactive element. tabindex on the radios is fine; the
stage itself stays out of focus order.
Loop pacing Loop pacing
----------- -----------
animation-duration: 3s. Keyframes 0% / 66% / 100%. Motion 0-66% = ~2s, animation-duration: 3s. Keyframes 0% / ~60% / 100%. Motion 0–60% ≈ 1.8s,
hold 66-100% = ~1s, then the animation restarts. Per-finding code hold 60–100% ≈ 1.2s, then restart. Every demo in a report uses this
imitates this cadence so all demos in a report share the same rhythm. cadence so all cards share the same rhythm.
Empty state (this file rendered standalone) Standalone preview
------------------------------------------- ------------------
Opening this file directly in a browser shows one .demo-card with the Opening this file directly in a browser shows one .demo card with the
loop indicator and placeholder content. No motion plays — the agent loop indicator, the segmented Auto/Light/Dark stage toggle, and a
injects motion per finding when this template is embedded in a report. placeholder motion target (a simple shape that fades in). No real
per-finding motion plays — the agent injects motion per finding when
this template is embedded in a report. The shell also honors the
viewer's prefers-color-scheme for standalone rendering.
--> -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -65,144 +79,197 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo Shell — design-motion-principles</title> <title>Demo Shell — design-motion-principles</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Public+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--bg: #ffffff; /* Cool slate-graphite neutrals (hue 255). In the full report these
--fg: #111111; are overridden by a global theme toggle; here the shell flips via
--border: #e5e5e5; prefers-color-scheme for standalone preview. */
--accent: #111111; --ink: oklch(0.155 0.006 255);
--loop-dim: rgba(0, 0, 0, 0.45); --surface: oklch(0.195 0.007 255);
--card-radius: 12px; --surface-2: oklch(0.235 0.008 255);
--card-padding: 20px; --paper: oklch(0.945 0.004 255);
--gap: 12px; --paper-dim: oklch(0.800 0.006 255);
--sans: system-ui, -apple-system, "Segoe UI", sans-serif; --muted: oklch(0.680 0.008 255);
--mono: ui-monospace, "SF Mono", Menlo, monospace; --faint: oklch(0.505 0.008 255);
--line: oklch(0.95 0.01 255 / 0.09);
--line-strong:oklch(0.95 0.01 255 / 0.16);
--display: "Familjen Grotesk", "Hanken Grotesk", system-ui, sans-serif;
--sans: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: light) {
:root { :root {
--bg: #1a1a1a; --ink: oklch(0.985 0.003 255);
--fg: #f0f0f0; --surface: oklch(0.965 0.004 255);
--border: #333333; --surface-2: oklch(0.940 0.005 255);
--accent: #f0f0f0; --paper: oklch(0.180 0.006 255);
--loop-dim: rgba(240, 240, 240, 0.45); --paper-dim: oklch(0.330 0.008 255);
--muted: oklch(0.475 0.010 255);
--faint: oklch(0.640 0.009 255);
--line: oklch(0.180 0.010 255 / 0.12);
--line-strong:oklch(0.180 0.010 255 / 0.22);
} }
} }
* { box-sizing: border-box; }
body { body {
background: var(--bg); background: var(--ink);
color: var(--fg); color: var(--paper);
font-family: var(--sans); font-family: var(--sans);
margin: 0; margin: 0;
padding: 32px; padding: 48px;
}
.demo-card {
position: relative;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
padding: var(--card-padding);
max-width: 360px;
min-width: 280px;
font-family: var(--sans);
outline: none;
}
.demo-card:focus-visible {
outline: none;
}
.demo-card__header {
font-size: 0.875rem;
font-weight: 600;
color: var(--fg);
margin-bottom: 4px;
padding-right: 72px;
}
.demo-card__subhead {
font-family: var(--mono);
font-size: 0.75rem;
color: var(--loop-dim);
margin-bottom: var(--gap);
}
.demo-card__stage {
background: var(--bg);
border: 1px dashed var(--border);
border-radius: 8px;
padding: 24px;
min-height: 100px;
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
} }
.demo-card__loop-indicator { /* visually-hidden (stage-toggle radios) */
position: absolute; .vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
top: 12px;
right: 12px; /* ───────────── Demo card ───────────── */
font-family: var(--mono); .demo { position: relative; width: 380px; }
font-size: 0.7rem; .demo__bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
color: var(--loop-dim); .demo__meta { min-width: 0; }
letter-spacing: 0.02em; .demo__title { display: block; font-size: 13.5px; font-weight: 600; color: var(--paper); line-height: 1.3; }
user-select: none; .demo__timing { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }
.demo__controls { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.demo__loop { font-family: var(--mono); font-size: 12px; color: var(--faint); }
/* segmented Auto / Light / Dark stage toggle (pure CSS).
Auto = follow ambient theme via --surface-2 + --paper.
Light / Dark = locked overrides (hardcoded values, ignore ambient). */
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 100px; overflow: hidden; }
.seg label { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); padding: 4px 9px; cursor: pointer; user-select: none; transition: background 0.15s ease, color 0.15s ease; }
.demo input[id$="-a"]:checked ~ .demo__bar .seg label[for$="-a"],
.demo input[id$="-l"]:checked ~ .demo__bar .seg label[for$="-l"],
.demo input[id$="-d"]:checked ~ .demo__bar .seg label[for$="-d"] {
background: color-mix(in oklch, var(--paper) 12%, transparent);
color: var(--paper);
} }
.demo__stage {
/* AUTO default — stage uses the ambient elevated surface */
--st-bg: var(--surface-2);
--st-fg: var(--paper);
--st-line: var(--line-strong);
--st-dim: color-mix(in oklch, var(--paper) 55%, transparent);
background: var(--st-bg); color: var(--st-fg);
border: 1px solid var(--line); border-radius: 10px;
padding: 32px 24px; min-height: 168px;
display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* Locked Light: hardcoded light, regardless of ambient theme */
.demo input[id$="-l"]:checked ~ .demo__stage {
--st-bg: oklch(0.985 0.003 255);
--st-fg: oklch(0.180 0.006 255);
--st-line: oklch(0.180 0.010 255 / 0.18);
--st-dim: oklch(0.180 0.010 255 / 0.45);
}
/* Locked Dark: hardcoded dark, regardless of ambient theme */
.demo input[id$="-d"]:checked ~ .demo__stage {
--st-bg: oklch(0.180 0.006 255);
--st-fg: oklch(0.985 0.003 255);
--st-line: oklch(0.985 0.010 255 / 0.18);
--st-dim: oklch(0.985 0.010 255 / 0.50);
}
/* Generic UI primitives for use inside motion targets. All theme via
stage tokens, NEVER page tokens — so they contrast correctly when a
stage is locked to a different theme than the report. */
.ui-btn { display: inline-flex; align-items: center; justify-content: center; padding: 9px 18px; border-radius: 8px; background: var(--st-fg); color: var(--st-bg); font-family: var(--sans); font-size: 13px; font-weight: 600; }
.ui-card { background: var(--st-bg); border: 1px solid var(--st-line); border-radius: 10px; padding: 14px 16px; min-width: 180px; box-shadow: 0 8px 24px oklch(0 0 0 / 0.12); }
.ui-row { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--st-fg); }
.ui-check { width: 22px; height: 22px; border-radius: 7px; background: var(--st-fg); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.ui-check svg { width: 13px; height: 13px; stroke: var(--st-bg); }
.ui-num { font-family: var(--display); font-size: 34px; font-weight: 700; color: var(--st-fg); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.ui-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--st-dim); }
/* prefers-reduced-motion — disables every per-finding animation and
hides the loop indicator. The per-finding @keyframes 100% state
must match the motion-target's default rendering so the reduce
fallback shows the correct final visual. */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.demo-card__loop-indicator { .demo__loop { display: none; }
display: none; [class*="__mt"], [class*="__mt"] * { animation: none !important; transition: none !important; }
}
[class*="__motion-target"] {
animation: none !important;
transition: none !important;
}
} }
/* MOTION-CODE-SLOT-{n} /* MOTION-CODE-SLOT-{n}
Per-finding @keyframes and .demo-card-{n}__motion-target rules Per-finding @keyframes and .demo-{n}__mt rules go here. {n} is the
go here. {n} = finding's 1-indexed position across the report. finding's 1-indexed position across the report.
Example shape (replace per finding): Example shape (replace per finding — wrap in
@media (prefers-reduced-motion: no-preference) { ... }):
@keyframes motion-1-enter { @media (prefers-reduced-motion: no-preference) {
0% { opacity: 0; transform: translateY(8px); filter: blur(4px); } @keyframes m1 {
66% { opacity: 1; transform: translateY(0); filter: blur(0); } 0% { opacity: 0; transform: translateY(8px); filter: blur(4px); }
100% { opacity: 1; transform: translateY(0); filter: blur(0); } 60% { opacity: 1; transform: translateY(0); filter: blur(0); }
} 100% { opacity: 1; transform: translateY(0); filter: blur(0); }
.demo-card-1__motion-target { }
animation: motion-1-enter 3s infinite; .demo-1__mt {
display: inline-block; animation: m1 3s cubic-bezier(0.22, 1, 0.36, 1) infinite;
padding: 8px 16px; }
border-radius: 6px;
background: var(--accent);
color: var(--bg);
font-family: var(--sans);
} }
*/ */
/* Placeholder motion for standalone preview only — agent strips this
when composing the report. Demonstrates the 0 / 60 / 100 cadence. */
@media (prefers-reduced-motion: no-preference) {
@keyframes m-placeholder {
0% { opacity: 0; transform: translateY(8px); filter: blur(4px); }
60% { opacity: 1; transform: translateY(0); filter: blur(0); }
100% { opacity: 1; transform: translateY(0); filter: blur(0); }
}
.demo-1__mt {
animation: m-placeholder 3s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
}
</style> </style>
</head> </head>
<body> <body>
<article class="demo-card" tabindex="-1"> <div class="demo">
<div class="demo-card__loop-indicator">↻ looping</div> <input class="vh" type="radio" name="st1" id="st1-a" checked>
<div class="demo-card__header">Recommended motion title</div> <input class="vh" type="radio" name="st1" id="st1-l">
<div class="demo-card__subhead">300ms · ease-out</div> <input class="vh" type="radio" name="st1" id="st1-d">
<div class="demo-card__stage"> <div class="demo__bar">
<div class="demo__meta">
<span class="demo__title">Recommended motion title</span>
<span class="demo__timing">300ms · ease-out</span>
</div>
<div class="demo__controls">
<div class="seg">
<label for="st1-a">Auto</label>
<label for="st1-l">Light</label>
<label for="st1-d">Dark</label>
</div>
<span class="demo__loop">↻</span>
</div>
</div>
<div class="demo__stage">
<!-- DEMO-CARD-MOTION-SLOT-{n} <!-- DEMO-CARD-MOTION-SLOT-{n}
Per-finding motion-target element goes here. Its class must Per-finding motion-target element goes here. Its class must
match the rules in MOTION-CODE-SLOT-{n} above: match the rules in MOTION-CODE-SLOT-{n} above:
<div class="demo-card-{n}__motion-target">...</div> <div class="demo-{n}__mt">...content...</div>
The element's contents are agent-determined (a button shape, The element's contents are agent-determined (a button shape,
a card, an icon, a list of items for stagger demos, etc.). --> a card, an icon, a row of items for stagger demos, a number,
<span style="color: var(--loop-dim); font-family: var(--mono); font-size: 0.75rem;"> a badge, etc.). All inner UI primitives use the stage tokens
(motion preview renders here per finding) (--st-fg / --st-bg / --st-line / --st-dim) — NEVER page tokens. -->
</span> <div class="demo-1__mt">
<div class="ui-card">
<div class="ui-label" style="margin-bottom: 6px;">Placeholder</div>
<div class="ui-row">
<span class="ui-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 13l4 4L19 7"/></svg></span>
<span>(motion preview renders here per finding)</span>
</div>
</div>
</div>
</div> </div>
</article> </div>
</body> </body>
</html> </html>
@@ -1,19 +1,28 @@
# Output Format # Output Format
This file defines the audit's two output modes: The audit produces one of two outputs:
- **HTML mode (default)** — a self-contained `.html` file written to the audited project's `motion-audits/` directory and opened in the user's default browser. Each Critical or Important finding gets an auto-looping CSS demo card beside it. - **HTML mode (default)** — a self-contained `.html` file written to the audited project's `motion-audits/` directory and opened in the user's default browser. Each Critical or Important finding gets a live, looping CSS demo card beside it.
- **Terminal mode (flag-triggered)** — the decorated-markdown report rendered inline in the conversation. Use when the user passes `--terminal`, `--inline`, "show the full report inline," "skip the HTML," or any natural-language equivalent. No HTML file is written. - **Terminal mode (flag-triggered)** — a decorated-markdown report rendered inline in the conversation. Use when the user passes `--terminal`, `--inline`, `--no-html`, "show the full report inline," or any natural-language equivalent. No HTML file is written.
The two modes contain the same audit content; only the rendering differs. Do not summarize — users want full per-lens perspectives. Both modes carry the same audit content; only the rendering differs. Do not summarize — users want full per-lens perspectives.
--- ---
## HTML mode ## HTML mode
### Canonical references
| File | Role |
|---|---|
| `references/report-template.html` | **Source of truth.** Full worked example (fictional "Tally" habit tracker, React + Framer Motion). Every section, every token, every pattern. When in doubt about layout, structure, or styling, READ this file. |
| `references/demo-shell.html` | Minimal isolated example of a single demo card with the per-finding slot pattern. Used as a per-finding template snippet. |
The agent builds the report by reading these two files and adapting them to the audited project — same architecture, audit-specific content.
### File structure ### File structure
The HTML output is a single self-contained `.html` document with everything inlined — no external CSS, no external JS, no external fonts (fonts may degrade gracefully if a CDN reference is used). The file scaffolds: Single self-contained `.html`. All CSS inlined. No external JS. Fonts loaded via Google Fonts CDN (Familjen Grotesk / Public Sans / Geist Mono) with full system-stack fallbacks so the file degrades gracefully offline.
``` ```
<!DOCTYPE html> <!DOCTYPE html>
@@ -22,310 +31,233 @@ The HTML output is a single self-contained `.html` document with everything inli
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{project-name} motion audit — {ISO date}</title> <title>{project-name} motion audit — {ISO date}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Public+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>
/* 1. Demo shell tokens, .demo-card layout, prefers-reduced-motion */ /* 1. :root token block (neutrals, accent aliases, severity, timing ramp, spacing, fonts)
/* — copied from references/demo-shell.html */ 2. :root:has(#theme-light:checked) light-mode token override
3. Layout + component CSS (header, lens-table, timing-figure, lens-sec, finding-row, demo, rec, ref-summary)
/* 2. Report layout tokens: hero, finding-row, perspective-section, 4. Per-finding @keyframes m{n} + .demo-{n}__mt rules, one block per Critical or Important finding,
severity-table, recommendation-summary */ {n} = 1-indexed across the whole report (collision-free) */
/* 3. Per-finding @keyframes and .demo-card-{n}__motion-target
rules, generated per audit — one block per Critical or
Important finding, suffixed by the finding's 1-indexed
position across the report */
</style> </style>
</head> </head>
<body> <body>
<!-- Hero --> <!-- Global theme switch (Dark/Light radios) -->
<!-- Header (eyebrow, title, lede, meta, stats) -->
<!-- Overall Assessment --> <!-- Overall Assessment -->
<!-- Per-lens sections (primary, secondary, selective) --> <!-- 01 · Lens summary table -->
<!-- Combined Recommendations tables --> <!-- 02 · Where the timings land (duration-budget diagram) -->
<!-- Lens Reference Summary --> <!-- 03–05 · Per-lens sections (Jakub, Emil, Jhey — ordered by weighting for the audited context) -->
<!-- 06 · Combined recommendations tables -->
<!-- 07 · Lens reference summary -->
<!-- footer -->
</body> </body>
</html> </html>
``` ```
### Report's own motion posture ### Design system
The report itself has **no** entrance, scroll, or mount animations. No staggered reveals, no fade-in-on-scroll, no motion-on-mount outside the demo cards. The demo cards are the only animated elements in the document — anything else would reproduce the AI-slop patterns the skill audits against. Neutral-default, dual-mode, severity-driven.
### Hero header - **Neutrals.** Cool slate-graphite at hue 255, very low chroma (0.003–0.010). `--ink` is the page background; `--paper` is the foreground text. In light mode the two swap values via the `:root:has(#theme-light:checked)` override — every other token derives from these two and flips automatically.
- **Severity (FIXED, never adaptive).** Red `oklch(0.655 0.185 25)` (critical) · Amber `oklch(0.805 0.125 78)` (important) · Green `oklch(0.745 0.135 152)` (opportunity). Light-mode counterparts deepen L for contrast on white; hues stay constant.
- **Timing-budget ramp (FIXED).** Same hues as severity; used in section 02 only. Instant + responsive = green, deliberate = amber, sluggish = red.
- **Accent (NEUTRAL by default).** `--accent`, `--accent-soft`, `--accent-tint` alias to `--paper`, `--paper-dim`, and a low-alpha paper tint. The report has no chromatic primary color — severity is the only color in the document. An individual audit MAY repoint these three to a sampled brand color, but ONLY if the brand has at least ~40° hue clearance from each of the severity hues and is verified not to fall in the AI-cliché zone (neon cyan, purple-to-blue gradients).
- **Fonts.** Display = Familjen Grotesk, body = Public Sans, mono = Geist Mono. The mono carries timing values (`240ms · ease-out`) and all small labels — never substitute a more generic mono for the timing values.
Top of the document. Project name + ISO date + severity counts row + primary lens label. ### Dual theme
```html Pure-CSS toggle. Two radios (`#theme-dark` default-checked, `#theme-light`) live inside `.theme-switch` at the top of `.wrap`. `:root:has(#theme-light:checked)` overrides every theme-dependent token. No JS. Selector compatibility: `:has()` is Baseline 2023, supported by all modern browsers.
<header class="report-hero">
<h1>{project-name} motion audit</h1> The global toggle's visual control is a segmented `Dark / Light` pill, top-right of the page, styled to match the per-demo stage segmented control.
<p class="report-hero__date">{ISO date}</p>
<p class="report-hero__counts"> ### The report's motion posture
<a href="#critical-findings">🔴 Critical: {N}</a> ·
<a href="#important-findings">🟡 Important: {N}</a> · **The report itself has no entrance, scroll, or mount animation.** No staggered reveals. No fade-in-on-scroll. No motion on mount outside the demo cards. The demo cards are the only animated elements in the document — anything else would reproduce the AI-slop patterns this skill audits against.
<a href="#opportunity-findings">🟢 Opportunities: {N}</a>
</p> The one allowed transition: `border-color 0.2s ease` on lens-table rows and finding-rows for hover feedback. That's it.
<p class="report-hero__primary">Primary: {Designer Name} — {Perspective Handle}</p>
</header> ### Sections (in render order)
#### Global theme switch
First element inside `.wrap`, right-aligned segmented `Dark / Light` pill.
#### Header
```
.eyebrow ("MOTION AUDIT · DESIGN-MOTION-PRINCIPLES")
h1.title ({project name} — {one-line audit framing})
p.lede ({1–2 sentence project description})
.meta-row (what it is · stack)
.stats (Findings · Critical · Important · Opportunities — each is an anchor link to its rec table)
``` ```
The severity counts pair each emoji with a text label (`Critical: N`, not just `🔴 N`) so the severity signal is readable under red-green color vision deficiency. Each count is an anchor link to the corresponding section in the body — this is the navigation affordance for long audits with many findings. Each severity count pairs the number with a text label so the signal is readable under red-green color vision deficiency. Each count is an anchor link (`#rec-crit`, `#rec-imp`, `#rec-opp`) to the corresponding recommendation table.
### Overall Assessment #### Overall Assessment
One short paragraph in larger display type. Does this feel polished? Too much? Too little? What's working, what's not? Wraps in `<section class="assessment">` with a `mono-label` "OVERALL" eyebrow.
One short paragraph in larger type. Does this feel polished? Too much? Too little? What's working, what's not? #### 01 · Lens summary
3-row table, one row per practitioner. Columns: Lens (with name and weight chip) · Verdict (`Strong` / `Concern` / `Problem` / `Mixed` with a colored dot) · One-line read. Weight chips indicate `Primary` / `Secondary` / `Selective` per audit context.
```html #### 02 · Where the timings land — duration-budget diagram
<section class="report-assessment"> Motion-native analog of thumb-first's thumb-zone diagram. A horizontal SVG (`viewBox="0 0 660 300"`) plots Tally's animations as numbered dots on a 0–600ms scale with four zone bands:
<p>{one-paragraph assessment}</p>
</section> | Zone | Range | Color |
|---|---|---|
| Instant | 0–100ms | green (`--t-good`) |
| Responsive | 100–300ms | green (`--t-good`) |
| Deliberate | 300–500ms | amber (`--t-mid`) |
| Sluggish | 500ms+ | red (`--t-slow`) |
Animations with NO transition are plotted as hollow dashed circles at `x=40` (= 0ms). The paired key list to the right carries the action names and durations. A "What's off" block below explains the misalignments.
The SVG uses CSS-class-driven fills (via an inline `<style>` block) so the diagram re-tones with the global theme. Dot label color flips per theme (dark text on lighter dots in dark mode, light text on deeper dots in light mode).
#### 03–05 · Per-lens sections
Three sections, ordered by weighting (primary first). Each section:
```
.lens-sec__head (h3 "Designer — Perspective" + .lens-sec__weight chip)
p.lens-sec__verdict (verdict dot)
.lens-block "What's working well" (ul.lens-list.good with ✓ markers + file refs)
.lens-block "Issues to address" (one .finding-row per Critical/Important finding)
.lens-block "Opportunities" (ul.lens-list.opp with 💡 markers + file refs)
.lens-take ("Through {Designer}'s lens: {1–2 sentence summary}")
``` ```
### Per-lens sections Section heading: `Designer Name — Perspective Handle` (em-dash). Lens take is the documented lens summary, NOT a quote from the person — render as `Through {Designer}'s lens` (apostrophe-s).
Three sections in weighting order: primary, secondary, selective. Each section header pairs the designer name with the perspective handle using an em-dash (`Designer Name — Perspective Handle`): Three perspective handles:
```html | Designer | Handle |
<section class="perspective-section" id="perspective-emil">
<h2>Emil Kowalski — Restraint &amp; Speed</h2>
<div class="perspective-section__working-well">
<h3>What's Working Well</h3>
<ul>
<li>✓ {observation} — <code>{file.tsx:line}</code></li>
</ul>
</div>
<div class="perspective-section__issues" id="emil-issues">
<h3>Issues to Address</h3>
<!-- One .finding-row per Critical or Important finding under this lens -->
</div>
<div class="perspective-section__opportunities">
<h3>Opportunities</h3>
<ul>
<li>💡 {idea} — <code>{file.tsx:line}</code></li>
</ul>
</div>
<p class="perspective-section__quote"><strong>Through Emil's lens:</strong> {1-2 sentence summary}</p>
</section>
```
The three perspective handles:
| Designer | Perspective handle |
|---|---| |---|---|
| Emil Kowalski | Restraint & Speed | | Emil Kowalski | Restraint & Speed |
| Jakub Krehel | Production Polish | | Jakub Krehel | Production Polish |
| Jhey Tompkins | Experimentation & Delight | | Jhey Tompkins | Experimentation & Delight |
Always render section headers as `Designer Name — Perspective` (em-dash). Always close each section with the `Through {Designer}'s lens:` summary — it's a documented lens, not a quote from the person. #### Finding rows (Critical + Important only)
Each Critical or Important finding renders as a `.finding-row` inside its lens's "Issues to address" block:
### Finding rows (Critical + Important only) ```
.finding-row[data-sev="crit|imp"]
.finding-row__prose
.find-tags (severity chip + 1–2 lens chips)
h4.find-title
.find-body
<p><span class="label">What</span>{prose}</p>
<p><span class="label">Why it matters</span>{prose}</p>
<div class="fix"><p><span class="label">Recommended motion</span>{prose}</p></div>
<p class="find-loc"><code>{file:line}</code></p>
.demo
{radios + bar + stage with motion-target}
```
Each Critical or Important finding inside an `Issues to Address` block renders as a `.finding-row` with the issue prose on the left and the demo card on the right (two-column at desktop, stacked at narrow widths): Two-column at desktop (1fr 380px), stacks at narrow widths (≤860px).
Opportunities never render a `.finding-row` and never get a demo card. They appear in the per-lens `.lens-block "Opportunities"` as a `.lens-list.opp` bulleted list.
#### 06 · Combined recommendations
Three severity-grouped tables, in order: `Critical · must fix` (`#rec-crit`) → `Important · should fix` (`#rec-imp`) → `Opportunities · could enhance` (`#rec-opp`). Each has a `.tier-label` with severity-colored mono label, a horizontal rule, and a count. Columns: Issue · File · Fix (or Enhancement · Where · Impact for opportunities).
#### 07 · Lens Reference Summary
Closing `.ref-summary` block. Which lens was referenced most + why + how to lean differently (one line per lens). No new findings here.
#### Footer
Mono micro-row: project name + finding counts.
### Demo cards (the centerpiece)
Each Critical or Important finding gets one demo card. The card is the ONLY animated element in the report.
#### Markup pattern
```html ```html
<div class="finding-row" id="finding-{n}"> <div class="demo">
<div class="finding-row__prose"> <input class="vh" type="radio" name="st{n}" id="st{n}-a" checked>
<p class="finding-row__severity">🔴 Critical</p> <input class="vh" type="radio" name="st{n}" id="st{n}-l">
<h4>{finding title}</h4> <input class="vh" type="radio" name="st{n}" id="st{n}-d">
<p>{finding explanation}</p> <div class="demo__bar">
<p class="finding-row__location"><code>{file.tsx:line}</code></p> <div class="demo__meta">
</div> <span class="demo__title">{recommended motion title}</span>
<article class="demo-card" tabindex="-1"> <span class="demo__timing">{duration} · {easing}</span>
<div class="demo-card__loop-indicator">↻ looping</div>
<div class="demo-card__header">{recommended motion title}</div>
<div class="demo-card__subhead">{duration} · {easing}</div>
<div class="demo-card__stage">
<div class="demo-card-{n}__motion-target">{motion target markup}</div>
</div> </div>
</article> <div class="demo__controls">
<div class="seg">
<label for="st{n}-a">Auto</label>
<label for="st{n}-l">Light</label>
<label for="st{n}-d">Dark</label>
</div>
<span class="demo__loop">↻</span>
</div>
</div>
<div class="demo__stage">
<div class="demo-{n}__mt">{motion target markup}</div>
</div>
</div> </div>
``` ```
`{n}` is the finding's 1-indexed position **across the whole report** (not per-section). This guarantees `@keyframes motion-{n}-...` and `.demo-card-{n}__motion-target` selector names are unique across the document, so concatenating multiple findings' CSS in one `<style>` block does not produce keyframe-name collisions. `{n}` is the finding's 1-indexed position across the whole report (not per-section). This guarantees `@keyframes m{n}` and `.demo-{n}__mt` selectors are unique across the concatenated `<style>` block.
Opportunities never render a demo card. They appear in the per-lens section's `Opportunities` block as a plain bulleted list. #### Stage 3-state toggle
### Demo-shell embedding pattern | State | Behavior |
|---|---|
| **Auto** (default) | Stage colors follow the global theme via `--surface-2` + `--paper` tokens. Matches the rest of the report. |
| **Light** | Hardcoded light stage (`--st-bg: oklch(0.985 0.003 255)`, `--st-fg: oklch(0.180 0.006 255)`). Overrides global. |
| **Dark** | Hardcoded dark stage (inverted). Overrides global. |
The agent reads `references/demo-shell.html` and uses it as a template. For each Critical or Important finding: Pure CSS. Radio inputs precede `.demo__bar` and `.demo__stage` so sibling-combinator selectors (`#stN-l:checked ~ .demo__stage`) work.
1. **Generate the per-finding motion code.** Read the audited code, the relevant lens reference (`emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md` — matching the lens this finding lives under), and `references/motion-cookbook.md` for the concrete recipe (easing, spring config, enter/exit shape). Write a CSS keyframe block + selector rules that demonstrate the recommended motion. Use the 0% / 66% / 100% cadence with `animation-duration: 3s` (~2s motion, ~1s hold, then loop). #### Stage tokens (used by motion targets)
2. **Inject the per-finding code into the report's `<style>` block.** Append a `@keyframes motion-{n}-...` block and a `.demo-card-{n}__motion-target { animation: ...; }` rule. The shell's CSS variables (`--bg`, `--fg`, `--border`, `--accent`, `--loop-dim`, `--sans`, `--mono`) are available — use them via `var()`. Do not redefine them. | Token | Role |
|---|---|
| `--st-bg` | Stage background color |
| `--st-fg` | Foreground / text / "ink" color on the stage |
| `--st-line` | Border / divider color on the stage |
| `--st-dim` | Dimmed text color on the stage |
3. **Inject the motion-target element into the `.demo-card__stage`.** The element's class must be `.demo-card-{n}__motion-target` so it matches the rules from step 2. Motion-target elements (`.ui-btn`, `.ui-card`, `.ui-row`, `.ui-check`, `.ui-num`, `.ui-label`, milestone badge, etc.) use `--st-fg` / `--st-bg` instead of `--accent` / page colors. This guarantees correct contrast even when a stage is locked to a different theme than the page.
4. **Set the demo card's header and subhead.** Header = short title for the recommended motion (e.g., "Subtle enter: opacity + translateY + blur"). Subhead = duration + easing in monospace (e.g., "300ms · ease-out"). The subhead always renders — populate it for every demo. #### Per-finding motion code
5. **Honor the prefers-reduced-motion guard.** The shell's `@media (prefers-reduced-motion: reduce)` block disables all `[class*="__motion-target"]` animations. The per-finding `@keyframes` 100% values MUST match the motion-target element's default static rendering so the reduce-motion fallback shows the correct final visual. Do not write per-finding overrides inside the reduce-motion block. For each Critical or Important finding `{n}`:
### Combined Recommendations tables 1. **Generate motion code.** Read the audited code, the relevant lens reference (`emil-kowalski.md` / `jakub-krehel.md` / `jhey-tompkins.md`), and `references/motion-cookbook.md` for the concrete recipe. Author a `@keyframes m{n}` block and a `.demo-{n}__mt { animation: m{n} 3s {easing} infinite; }` rule.
2. **Loop pacing.** `animation-duration: 3s`. Keyframes at `0%` / `~60%` / `100%`. Motion completes by ~60% (~1.8s), then holds until `100%` (~1.2s) before looping. The `100%` state MUST match the motion-target's default (no-animation) static rendering — this is the `prefers-reduced-motion` fallback contract.
After the three per-lens sections, render severity-grouped tables for quick scanning: 3. **Inject into `<style>`.** Append the `@keyframes m{n}` + `.demo-{n}__mt` block to the report's `<style>`, after the layout CSS, inside a `@media (prefers-reduced-motion: no-preference) { ... }` guard.
4. **Inject demo-card markup.** Append the `.demo` block to the finding's `.finding-row`. Set `.demo__title` to a short motion title (e.g., "Quick tab crossfade"). Set `.demo__timing` to duration + easing (e.g., "180ms · ease-out").
```html 5. **Honor reduced-motion.** The shell's `@media (prefers-reduced-motion: reduce)` block disables all `[class*="__mt"]` animations and hides the `↻` loop indicator. The per-finding `100%` keyframe state must match the motion-target's default static rendering. Do NOT write per-finding overrides inside the reduce-motion block.
<section class="recommendations" id="critical-findings">
<h2>🔴 Critical (Must Fix)</h2>
<table>
<thead>
<tr><th>Issue</th><th>File</th><th>Action</th></tr>
</thead>
<tbody>
<tr>
<td>{issue}</td>
<td><code>{file:line}</code></td>
<td>{fix}</td>
</tr>
</tbody>
</table>
</section>
<section class="recommendations" id="important-findings">
<h2>🟡 Important (Should Fix)</h2>
<!-- same shape -->
</section>
<section class="recommendations" id="opportunity-findings">
<h2>🟢 Opportunities (Could Enhance)</h2>
<!-- same shape -->
</section>
```
The hero's severity counts link to these section IDs (`#critical-findings`, `#important-findings`, `#opportunity-findings`).
### Lens Reference Summary (closing)
```html
<section class="reference-summary">
<h2>Lens Reference Summary</h2>
<p><strong>Which lens was referenced most:</strong> {Designer} — {Perspective}</p>
<p><strong>Why:</strong> {one-line context reason}</p>
<p><strong>If you want to lean differently:</strong></p>
<ul>
<li>To follow Emil more strictly: {specific actions}</li>
<li>To follow Jakub more strictly: {specific actions}</li>
<li>To follow Jhey more strictly: {specific actions}</li>
</ul>
</section>
```
### Empty-state behavior ### Empty-state behavior
When the audit produces zero Critical + zero Important findings: When the audit produces zero Critical + zero Important findings:
- The hero still renders with the severity counts row (showing `Critical: 0 · Important: 0 · Opportunities: N`). - Header still renders with the severity counts (showing `Critical: 0 · Important: 0 · Opportunities: N`).
- Each per-lens section's `Issues to Address` block still renders its header, but the body shows a dimmed-italic line: - Each per-lens "Issues to address" `.lens-block` still renders its `.mono-label`, but the body shows a dimmed-italic line:
```html ```html
<div class="perspective-section__issues" id="emil-issues"> <p class="lens-empty">No issues found at this severity level.</p>
<h3>Issues to Address</h3>
<p class="perspective-section__empty">No issues found at this severity level.</p>
</div>
``` ```
Style: `font-style: italic; color: var(--loop-dim); padding: 12px 0;`. Communicates absence without looking broken.
- No `.finding-row` markup, no demo cards. - No `.finding-row` markup, no demo cards.
- Opportunities still render in text as usual. - Opportunities still render as `.lens-list.opp` lists.
- Combined recommendations tables render with empty `<tbody>` containing a single dimmed-italic row, OR are omitted entirely if their tier has zero findings.
### Responsive behavior ### Responsive behavior
The `.finding-row` two-column layout (prose left, demo right) needs a breakpoint for narrow viewports: - `.finding-row` 2-col → 1-col at ≤860px (demo stacks below prose).
- `.timing-grid` 2-col → 1-col at ≤860px (key list stacks below SVG).
- `.lens-table` → stacked blocks at ≤600px (each row becomes a block, headers hidden).
- `.ref-lean` 2-col → 1-col at ≤600px.
```css ### Absolute bans
.finding-row {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
align-items: start;
margin: 24px 0;
}
@media (max-width: 768px) { - **NO `border-left` or `border-right` >1px as a colored accent stripe** on cards, list items, callouts, or alerts. Use full borders, leading numbers, tinted backgrounds, or no visual indicator instead. (The previous version of this spec had `border-left: 3px solid var(--accent)` on the lens-take block — removed.)
.finding-row { - **NO gradient text** (`background-clip: text` + gradient).
grid-template-columns: 1fr; - **NO pulsing UI** in any demo (looping scale/opacity on status indicators, "live" pulse rings, breathing CTAs). Demo motion is structured one-shot enters that loop, not attention-getting pulses.
} - **NO chromatic accent in the default system.** The neutral-default is the supported configuration. Repointing `--accent` to a sampled brand color is permitted only when severity-hue clearance is verified.
.demo-card {
max-width: 100%;
}
}
```
Below 768px the demo card stacks below the finding prose. The shell's `min-width: 280px` keeps the card from compressing past usability.
### Report layout tokens
These extend the shell's tokens for report-level structure. Append to the `<style>` block after the shell's variables:
```css
body {
max-width: 960px;
margin: 0 auto;
padding: 48px 24px;
}
.report-hero h1 {
font-size: 1.75rem;
margin: 0 0 4px;
}
.report-hero__date {
font-family: var(--mono);
color: var(--loop-dim);
font-size: 0.875rem;
margin: 0 0 16px;
}
.report-hero__counts {
font-size: 1rem;
margin: 0 0 8px;
}
.report-hero__counts a {
color: inherit;
text-decoration: none;
border-bottom: 1px dashed var(--border);
}
.report-hero__counts a:hover {
border-bottom-style: solid;
}
.report-hero__primary {
font-family: var(--mono);
color: var(--loop-dim);
font-size: 0.875rem;
margin: 0;
}
.report-assessment {
margin: 32px 0;
font-size: 1.05rem;
line-height: 1.6;
}
.perspective-section {
margin: 48px 0;
padding-top: 24px;
border-top: 1px solid var(--border);
}
.perspective-section h2 {
font-size: 1.25rem;
margin: 0 0 16px;
}
.perspective-section__quote {
margin-top: 24px;
padding: 12px 16px;
background: var(--bg);
border-left: 3px solid var(--accent);
font-size: 0.95rem;
}
.recommendations {
margin: 48px 0;
}
.recommendations table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.recommendations th,
.recommendations td {
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
```
--- ---
@@ -333,148 +265,73 @@ body {
When the user passes `--terminal` / `--inline` / a natural-language equivalent, do not write an HTML file. Render the decorated-markdown report inline in the conversation. When the user passes `--terminal` / `--inline` / a natural-language equivalent, do not write an HTML file. Render the decorated-markdown report inline in the conversation.
### Quick Summary (Show First) ### Quick Summary (show first)
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 AUDIT SUMMARY 📊 AUDIT SUMMARY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities 🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities
Primary perspective: [Designer(s)] ([context reason]) Primary lens: [Designer] ([context reason])
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
``` ```
### Overall Assessment ### Overall Assessment
One paragraph: Does this feel polished? Too much? Too little? What's working, what's not? One paragraph: Does this feel polished? Too much? Too little? What's working, what's not?
---
### Per-Designer Sections ### Per-Designer Sections
#### Emil's Section For each designer (Emil, Jakub, Jhey — ordered by weighting), use a horizontal-rule header and the body format:
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚡ EMIL KOWALSKI — Restraint & Speed ⚡ EMIL KOWALSKI — Restraint & Speed (Secondary)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
*Weight based on context. Heavy for productivity tools, light for creative/kids apps.* What's Working Well
**What to Check:**
- High-frequency interactions that might not need animation
- Keyboard-initiated actions that animate (generally shouldn't)
- Durations **if this is a productivity context** (Emil prefers under 300ms)
- Animations starting from scale(0) (should be 0.9+)
- Transform-origin on dropdowns/popovers
- CSS keyframes that should be transitions (for interruptibility)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line` - ✓ [Observation] — `file.tsx:line`
**Issues to Address** Issues to Address
- ✗ [Issue] — `file.tsx:line` - ✗ [Issue] — `file.tsx:line`
[Brief explanation] [Brief explanation]
Recommended: [Brief recommendation]
**Through Emil's lens**: [1-2 sentence summary] Through Emil's lens: [1–2 sentence summary]
---
#### Jakub's Section
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 JAKUB KREHEL — Production Polish
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**What to Check:**
- Enter animations (opacity + translateY + blur?)
- Exit animations (subtler than enters? Or missing entirely?)
- **Motion gaps** — Conditional renders without AnimatePresence (from gap analysis)
- **Layout transitions** — Size/position changes that snap instead of animate
- Shadow vs border usage on varied backgrounds
- Optical alignment (buttons with icons, play buttons)
- Hover state transitions (150-200ms minimum)
- Icon swap animations (opacity + scale + blur)
- Spring usage (bounce: 0 for professional, higher for playful)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line`
**Issues to Address**
- ✗ [Issue] — `file.tsx:line`
[Brief explanation]
**Through Jakub's lens**: [1-2 sentence summary]
---
#### Jhey's Section
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✨ JHEY TOMPKINS — Experimentation & Delight
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**What to Check:**
- Could @property enable smoother animations?
- Could linear() provide better easing curves?
- Are stagger effects using optimal techniques?
- Could scroll-driven animations improve the experience?
- What playful touches would enhance engagement?
- Are there celebration moments that need more delight? (streaks, achievements, etc.)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line`
**Opportunities**
- 💡 [Idea] — `file.tsx:line`
[Brief explanation]
**Through Jhey's lens**: [1-2 sentence summary]
---
### Combined Recommendations ### Combined Recommendations
**Critical (Must Fix)** Three severity tables:
| | Issue | File | Action |
|-|-------|------|--------| ```
Critical · Must Fix
| | Issue | File | Fix |
|-|-------|------|-----|
| 🔴 | [Issue] | `file:line` | [Fix] | | 🔴 | [Issue] | `file:line` | [Fix] |
**Important (Should Fix)** Important · Should Fix
| | Issue | File | Action | | | Issue | File | Fix |
|-|-------|------|--------| |-|-------|------|-----|
| 🟡 | [Issue] | `file:line` | [Fix] | | 🟡 | [Issue] | `file:line` | [Fix] |
**Opportunities (Could Enhance)** Opportunities · Could Enhance
| | Enhancement | Where | Impact | | | Enhancement | Where | Impact |
|-|-------------|-------|--------| |-|-------------|-------|--------|
| 🟢 | [Enhancement] | `file:line` | [Impact] | | 🟢 | [Enhancement] | `file:line` | [Impact] |
```
---
### Lens Reference Summary ### Lens Reference Summary
End every terminal audit with: ```
> Lens referenced most: [Designer Name] — [Perspective]
> **Which lens was referenced most**: [Designer Name] — [Perspective]
> >
> **Why**: [Explanation based on the project context] > Why: [Explanation based on the project context]
> >
> **If you want to lean differently**: > If you want to lean differently:
> - To follow Emil more strictly: [specific actions] > - To follow Emil more strictly: [specific actions]
> - To follow Jakub more strictly: [specific actions] > - To follow Jakub more strictly: [specific actions]
> - To follow Jhey more strictly: [specific actions] > - To follow Jhey more strictly: [specific actions]
```
--- ---
@@ -486,4 +343,4 @@ Default to HTML mode. Trigger terminal mode only when the user explicitly signal
- Natural-language equivalent: "show the full report inline," "skip the HTML," "no HTML," "terminal only" - Natural-language equivalent: "show the full report inline," "skip the HTML," "no HTML," "terminal only"
- Any headless or CI environment where opening a browser doesn't apply - Any headless or CI environment where opening a browser doesn't apply
When in doubt, render HTML and mention the terminal-mode flag in the 3-line summary (see `workflows/audit.md` STEP 3) so the user knows the alternative exists. When defaulting to HTML, mention in the 3-line confirmation summary (see `workflows/audit.md`) that `--terminal` is the alternative — so the user knows it exists.
File diff suppressed because it is too large Load Diff