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:
co-authored by
Claude Opus 4.8
parent
3049250150
commit
82ce02cf28
@@ -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 & 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
Reference in New Issue
Block a user