@ericrisco/rsc 3.1.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/manifest.json +43 -69
- package/package.json +6 -1
- package/scripts/design-upstream.js +92 -0
- package/scripts/doctor.js +0 -24
- package/scripts/lib/default-skill-floor.js +5 -1
- package/scripts/lib/domains.js +2 -2
- package/scripts/lib/recommend.js +1 -3
- package/scripts/lib/retired-skills.js +7 -1
- package/skills/ai-media/SKILL.md +2 -2
- package/skills/design/IMPECCABLE-LICENSE.txt +191 -0
- package/skills/design/SKILL.md +79 -328
- package/skills/design/THIRD_PARTY.md +26 -0
- package/skills/design/engine/LICENSE +21 -0
- package/skills/design/engine/RUN.md +515 -0
- package/skills/design/engine/UPSTREAM.json +4 -0
- package/skills/design/engine/assets/app.css +216 -0
- package/skills/design/engine/assets/base.css +105 -0
- package/skills/design/engine/assets/base.js +37 -0
- package/skills/design/engine/assets/charts.js +617 -0
- package/skills/design/engine/assets/scenes/stage.css +28 -0
- package/skills/design/engine/assets/scenes/stage.js +133 -0
- package/skills/design/engine/library/app.json +1327 -0
- package/skills/design/engine/library/directions.json +1078 -0
- package/skills/design/engine/library/heroes.json +1086 -0
- package/skills/design/engine/library/motions.json +92 -0
- package/skills/design/engine/library/palettes.json +1485 -0
- package/skills/design/engine/library/photos.json +7240 -0
- package/skills/design/engine/library/sections.json +4429 -0
- package/skills/design/engine/library/sites.json +8350 -0
- package/skills/design/engine/library/type.json +2024 -0
- package/skills/design/engine/references/app-screens.md +1199 -0
- package/skills/design/engine/references/colour.md +141 -0
- package/skills/design/engine/references/copy.md +177 -0
- package/skills/design/engine/references/critique.md +182 -0
- package/skills/design/engine/references/imagery.md +791 -0
- package/skills/design/engine/references/layout.md +292 -0
- package/skills/design/engine/references/motion.md +980 -0
- package/skills/design/engine/references/research.md +356 -0
- package/skills/design/engine/references/scenes.md +174 -0
- package/skills/design/engine/references/tells.md +115 -0
- package/skills/design/engine/references/type.md +170 -0
- package/skills/design/engine/scripts/assets.mjs +865 -0
- package/skills/design/engine/scripts/draw.mjs +584 -0
- package/skills/design/engine/scripts/inspect.mjs +303 -0
- package/skills/design/engine/scripts/lib/app-draw.mjs +203 -0
- package/skills/design/engine/scripts/lib/cdp.mjs +248 -0
- package/skills/design/engine/scripts/lib/probe.mjs +909 -0
- package/skills/design/engine/scripts/lib/rules.mjs +243 -0
- package/skills/design/engine/scripts/lint.mjs +119 -0
- package/skills/design/engine/scripts/scenes.mjs +124 -0
- package/skills/design/evals/cases.yaml +35 -46
- package/skills/design/references/design-md.md +49 -0
- package/skills/design/references/finish-review.md +46 -0
- package/skills/design/references/modes.md +51 -0
- package/skills/expo/SKILL.md +3 -4
- package/skills/marketing/references/landing-copy.md +2 -2
- package/skills/react/SKILL.md +2 -2
- package/skills/react-native/SKILL.md +3 -3
- package/skills/scroll-film/SKILL.md +220 -0
- package/skills/scroll-film/assets/page-template.html +128 -0
- package/skills/scroll-film/assets/scrub-engine.js +352 -0
- package/skills/scroll-film/evals/README.md +26 -0
- package/skills/scroll-film/evals/cases.yaml +81 -0
- package/skills/scroll-film/references/chain.md +218 -0
- package/skills/scroll-film/references/engine.md +132 -0
- package/skills/scroll-film/scripts/verify.sh +158 -0
- package/skills/swift-ios/SKILL.md +1 -1
- package/skills/ui-engineering/SKILL.md +3 -4
- package/skills/ui-engineering/evals/cases.yaml +2 -2
- package/skills/variant-explorer/SKILL.md +4 -4
- package/skills/variant-explorer/evals/cases.yaml +1 -1
- package/targets/clone-bootstrap.mjs +1 -1
- package/scripts/lib/design-identity.js +0 -117
- package/scripts/lib/starting-point.js +0 -314
- package/skills/design/evals/README.md +0 -53
- package/skills/design/references/ai-tells.md +0 -75
- package/skills/design/references/brand-grounding.md +0 -197
- package/skills/design/references/copywriting-frameworks.md +0 -138
- package/skills/design/references/design-systems.md +0 -49
- package/skills/design/references/inspiration-sources.md +0 -96
- package/skills/design/references/landing-anatomy-and-cro.md +0 -202
- package/skills/design/references/motion-and-interaction.md +0 -228
- package/skills/design/references/research-method.md +0 -154
- package/skills/design/references/signature-and-craft.md +0 -148
- package/skills/design/references/starting-point.md +0 -75
- package/skills/design/references/trends-2026.md +0 -80
- package/skills/design/references/visual-system.md +0 -237
- package/skills/design/scripts/verify.sh +0 -357
- package/skills/design-dna/PROMPT.md +0 -211
- package/skills/design-dna/SKILL.md +0 -162
- package/skills/design-dna/design-dna.schema.json +0 -230
- package/skills/design-dna/evals/README.md +0 -64
- package/skills/design-dna/evals/cases.yaml +0 -68
- package/skills/design-dna/references/method.md +0 -436
- package/skills/design-dna/scripts/emit.py +0 -164
- package/skills/design-dna/templates/PROMPT.md.tmpl +0 -42
- package/skills/design-dna/templates/SKILL.md.tmpl +0 -42
- package/skills/design-dna/templates/check.py.tmpl +0 -143
- package/skills/design-loop/SKILL.md +0 -160
- package/skills/design-loop/evals/README.md +0 -56
- package/skills/design-loop/evals/cases.yaml +0 -60
- package/skills/motion-craft/SKILL.md +0 -125
- package/skills/motion-craft/evals/cases.yaml +0 -38
- package/skills/motion-craft/scripts/verify.sh +0 -75
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# verify.sh — design-review gate for the `design` skill.
|
|
4
|
-
#
|
|
5
|
-
# WHAT IT DOES
|
|
6
|
-
# 1. If a dev server is reachable AND Lighthouse is available, runs a
|
|
7
|
-
# performance + accessibility audit and checks Core Web Vitals against the
|
|
8
|
-
# skill's hard thresholds (LCP < 2.5s, CLS < 0.1, INP proxy via TBT < 200ms,
|
|
9
|
-
# a11y score >= 0.9).
|
|
10
|
-
# 2. Always runs static, network-free design-review checks, in three groups:
|
|
11
|
-
# - four judgement-shaped ones (more than one <h1>, image without alt,
|
|
12
|
-
# hardcoded hex despite a token system, animation with no reduced-motion
|
|
13
|
-
# guard) — see static_checks();
|
|
14
|
-
# - the tell registry: one row per checkable AI tell, iterated generically
|
|
15
|
-
# (dashes in rendered copy, scroll cues, numbered eyebrows, build stamps,
|
|
16
|
-
# unstable viewport heights, middot chains, transition-all, hype words)
|
|
17
|
-
# — see tell_table(); every row has a fixture in the repo's test suite;
|
|
18
|
-
# - three counters that need a ratio rather than a match (eyebrows against
|
|
19
|
-
# sections, double-bordered rows, radius scales) — see counter_checks().
|
|
20
|
-
# Everything here WARNS. Nothing here fails, unless --strict is passed.
|
|
21
|
-
# 3. If Lighthouse did not run, prints the manual QA checklist. For a graded
|
|
22
|
-
# 0-10 critique, use the visual-audit rubric in SKILL.md instead.
|
|
23
|
-
#
|
|
24
|
-
# HOW TO RUN (inside YOUR project, not the skills repo)
|
|
25
|
-
# ./verify.sh # static checks + Lighthouse if localhost:3000 is up
|
|
26
|
-
# ./verify.sh --url http://localhost:3001
|
|
27
|
-
# ./verify.sh --strict # treat warnings as failures (exit 1 on any warn)
|
|
28
|
-
#
|
|
29
|
-
# EXIT CODES
|
|
30
|
-
# 0 no real failures (warnings allowed unless --strict)
|
|
31
|
-
# 1 a real failure (failed Lighthouse threshold), or --strict with any warning
|
|
32
|
-
# 2 bad usage
|
|
33
|
-
#
|
|
34
|
-
# Missing tools are SKIPPED with a yellow notice, never failed.
|
|
35
|
-
|
|
36
|
-
set -euo pipefail
|
|
37
|
-
|
|
38
|
-
# --- portability: runs on stock macOS bash 3.2 ------------------------------
|
|
39
|
-
# This script intentionally avoids bash 4+ features (no `mapfile`/`readarray`,
|
|
40
|
-
# no associative arrays, no `${arr[@]}` under set -u). It uses only scalar
|
|
41
|
-
# counters and `read` loops, so it degrades gracefully on the default macOS
|
|
42
|
-
# /bin/bash (3.2). If somehow run under an older/non-bash shell, warn and exit
|
|
43
|
-
# cleanly rather than crashing.
|
|
44
|
-
if [ -z "${BASH_VERSION:-}" ]; then
|
|
45
|
-
printf 'This script requires bash (any version >= 3.2). Run: bash %s\n' "$0" >&2
|
|
46
|
-
exit 2
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# --- color helpers (no escape codes when not a TTY) -------------------------
|
|
50
|
-
if [ -t 1 ]; then
|
|
51
|
-
RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; NC=$'\033[0m'
|
|
52
|
-
else
|
|
53
|
-
RED=''; GREEN=''; YELLOW=''; NC=''
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
ok_count=0; skip_count=0; warn_count=0; fail_count=0
|
|
57
|
-
|
|
58
|
-
ok() { printf '%s[ ok ]%s %s\n' "$GREEN" "$NC" "$*"; ok_count=$((ok_count + 1)); }
|
|
59
|
-
skip() { printf '%s[skip]%s %s\n' "$YELLOW" "$NC" "$*"; skip_count=$((skip_count + 1)); }
|
|
60
|
-
warn() { printf '%s[warn]%s %s\n' "$YELLOW" "$NC" "$*"; warn_count=$((warn_count + 1)); }
|
|
61
|
-
fail() { printf '%s[fail]%s %s\n' "$RED" "$NC" "$*"; fail_count=$((fail_count + 1)); }
|
|
62
|
-
|
|
63
|
-
usage() {
|
|
64
|
-
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
# --- arg parse --------------------------------------------------------------
|
|
68
|
-
URL="http://localhost:3000"
|
|
69
|
-
STRICT=0
|
|
70
|
-
while [ $# -gt 0 ]; do
|
|
71
|
-
case "$1" in
|
|
72
|
-
--url) URL="${2:?--url needs a value}"; shift 2 ;;
|
|
73
|
-
--strict) STRICT=1; shift ;;
|
|
74
|
-
-h|--help) usage; exit 0 ;;
|
|
75
|
-
*) printf '%sUnknown argument: %s%s\n\n' "$RED" "$1" "$NC"; usage; exit 2 ;;
|
|
76
|
-
esac
|
|
77
|
-
done
|
|
78
|
-
|
|
79
|
-
have() { command -v "$1" >/dev/null 2>&1; }
|
|
80
|
-
|
|
81
|
-
# search wrapper: ripgrep if present, else portable grep -rnE. Both print file:line.
|
|
82
|
-
# Excludes build/vendor dirs and this script itself so it never matches its own
|
|
83
|
-
# source (e.g. the `transition: all` strings in its comments). Patterns passed
|
|
84
|
-
# here MUST be POSIX-ERE (no PCRE lookaheads) so the grep fallback behaves the
|
|
85
|
-
# same as the rg path — see note on the img-alt check below.
|
|
86
|
-
SELF_NAME="$(basename "$0")"
|
|
87
|
-
search() {
|
|
88
|
-
[ "$#" -gt 0 ] || return 1
|
|
89
|
-
# VERIFY_FORCE_GREP=1 pins the grep branch. It exists so the test suite can prove BOTH
|
|
90
|
-
# engines agree on the multibyte patterns (em-dash, middot) on any machine, instead of
|
|
91
|
-
# only exercising whichever one happens to be installed.
|
|
92
|
-
if have rg && [ -z "${VERIFY_FORCE_GREP:-}" ]; then
|
|
93
|
-
# rg already ignores .gitignore'd paths (node_modules, .next, dist); also skip self.
|
|
94
|
-
# Always name the search root. run_tell_table feeds its registry through stdin;
|
|
95
|
-
# without an explicit path ripgrep treats that stdin as the search corpus and
|
|
96
|
-
# silently checks the registry text instead of the project files.
|
|
97
|
-
rg -n --no-heading --glob "!$SELF_NAME" "$@" .
|
|
98
|
-
else
|
|
99
|
-
# last arg is the pattern; everything before are paths/globs we ignore for grep.
|
|
100
|
-
# `${*: -1}` (last positional) is supported on bash 3.2; the space before -1 is required.
|
|
101
|
-
local pattern="${*: -1}"
|
|
102
|
-
grep -rnE \
|
|
103
|
-
--exclude="$SELF_NAME" \
|
|
104
|
-
--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=.next \
|
|
105
|
-
--exclude-dir=dist --exclude-dir=build --exclude-dir=.next \
|
|
106
|
-
"$pattern" . 2>/dev/null
|
|
107
|
-
fi
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
LH_RAN=0
|
|
111
|
-
|
|
112
|
-
# --- Lighthouse step (guarded) ---------------------------------------------
|
|
113
|
-
run_lighthouse() {
|
|
114
|
-
if ! { have lighthouse || have npx; }; then
|
|
115
|
-
skip "lighthouse not found — skipping perf/a11y run"
|
|
116
|
-
return
|
|
117
|
-
fi
|
|
118
|
-
if ! have curl || ! curl -sf --max-time 3 "$URL" >/dev/null 2>&1; then
|
|
119
|
-
skip "no dev server at $URL — start it (e.g. npm run dev) to run Lighthouse"
|
|
120
|
-
return
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
local tmp; tmp="$(mktemp -d)"
|
|
124
|
-
trap 'rm -rf "$tmp"' RETURN
|
|
125
|
-
|
|
126
|
-
local out="$tmp/lh.json"
|
|
127
|
-
printf 'Running Lighthouse against %s ...\n' "$URL"
|
|
128
|
-
if have lighthouse; then
|
|
129
|
-
lighthouse "$URL" --quiet --chrome-flags="--headless=new" \
|
|
130
|
-
--only-categories=performance,accessibility \
|
|
131
|
-
--output=json --output-path="$out" >/dev/null 2>&1 || true
|
|
132
|
-
else
|
|
133
|
-
npx --no-install lighthouse "$URL" --quiet --chrome-flags="--headless=new" \
|
|
134
|
-
--only-categories=performance,accessibility \
|
|
135
|
-
--output=json --output-path="$out" >/dev/null 2>&1 || true
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
if [ ! -s "$out" ]; then
|
|
139
|
-
skip "Lighthouse produced no report (Chrome missing?) — skipping perf/a11y"
|
|
140
|
-
return
|
|
141
|
-
fi
|
|
142
|
-
LH_RAN=1
|
|
143
|
-
|
|
144
|
-
# metric extractor: jq preferred, node fallback
|
|
145
|
-
metric() {
|
|
146
|
-
local jqpath="$1" nodeexpr="$2"
|
|
147
|
-
if have jq; then
|
|
148
|
-
jq -r "$jqpath // empty" "$out" 2>/dev/null
|
|
149
|
-
elif have node; then
|
|
150
|
-
node -e "const d=require('$out');const v=$nodeexpr;process.stdout.write(v==null?'':String(v))" 2>/dev/null
|
|
151
|
-
fi
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
local lcp cls tbt a11y
|
|
155
|
-
lcp="$(metric '.audits["largest-contentful-paint"].numericValue' 'd.audits["largest-contentful-paint"].numericValue')"
|
|
156
|
-
cls="$(metric '.audits["cumulative-layout-shift"].numericValue' 'd.audits["cumulative-layout-shift"].numericValue')"
|
|
157
|
-
tbt="$(metric '.audits["total-blocking-time"].numericValue' 'd.audits["total-blocking-time"].numericValue')"
|
|
158
|
-
a11y="$(metric '.categories.accessibility.score' 'd.categories.accessibility.score')"
|
|
159
|
-
|
|
160
|
-
if [ -n "$lcp" ]; then
|
|
161
|
-
if awk "BEGIN{exit !($lcp < 2500)}"; then ok "LCP ${lcp}ms < 2500ms"; else fail "LCP ${lcp}ms >= 2500ms"; fi
|
|
162
|
-
else
|
|
163
|
-
skip "LCP not reported"
|
|
164
|
-
fi
|
|
165
|
-
if [ -n "$cls" ]; then
|
|
166
|
-
if awk "BEGIN{exit !($cls < 0.1)}"; then ok "CLS ${cls} < 0.1"; else fail "CLS ${cls} >= 0.1"; fi
|
|
167
|
-
else
|
|
168
|
-
skip "CLS not reported"
|
|
169
|
-
fi
|
|
170
|
-
if [ -n "$tbt" ]; then
|
|
171
|
-
if awk "BEGIN{exit !($tbt < 200)}"; then ok "INP proxy (TBT) ${tbt}ms < 200ms"; else fail "INP proxy (TBT) ${tbt}ms >= 200ms"; fi
|
|
172
|
-
else
|
|
173
|
-
skip "TBT (INP proxy) not reported"
|
|
174
|
-
fi
|
|
175
|
-
if [ -n "$a11y" ]; then
|
|
176
|
-
if awk "BEGIN{exit !($a11y >= 0.9)}"; then ok "a11y score ${a11y} >= 0.9"; else fail "a11y score ${a11y} < 0.9"; fi
|
|
177
|
-
else
|
|
178
|
-
skip "a11y score not reported"
|
|
179
|
-
fi
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
# --- the tell registry ------------------------------------------------------
|
|
183
|
-
# One row per checkable AI tell. A row is DATA, not code, so the test suite can enumerate
|
|
184
|
-
# every row and demand a fixture for each: a check nobody has ever seen fire cannot ship.
|
|
185
|
-
# Add a tell by adding a row plus tests/fixtures/design-tells/tells/<id>.tsx — nothing else.
|
|
186
|
-
#
|
|
187
|
-
# Columns, delimited by %% (the pattern column needs `|` for ERE alternation):
|
|
188
|
-
# id %% sev %% extensions %% POSIX-ERE pattern %% what to do instead / the legit exception
|
|
189
|
-
#
|
|
190
|
-
# Patterns are POSIX ERE: no lookahead, no \b, no \s — they must behave identically under rg
|
|
191
|
-
# and under grep -E. Part of the corpus is adapted from Leonxlnx/taste-skill (MIT).
|
|
192
|
-
tell_table() {
|
|
193
|
-
cat <<'TELLS'
|
|
194
|
-
em-dash%%warn%%tsx,jsx,html,vue,svelte,mdx%%—|–%%em-dash or en-dash in rendered copy is the loudest AI tell. Use a period, a comma, a colon or parentheses instead
|
|
195
|
-
scroll-cue%%warn%%tsx,jsx,html,vue,svelte%%[Ss]croll to (explore|walk|discover|see|begin)|↓ *[Ss]croll%%a scroll cue explains scrolling to someone already looking at the hero. Delete it, or use a real anchor link instead
|
|
196
|
-
numbered-eyebrow%%warn%%tsx,jsx,html,vue,svelte%%(^|[^0-9A-Za-z])[0-9]{2,3} +(·|/) +[A-Za-z]%%numbered section eyebrow (001 · Capabilities). Name the topic in plain language instead
|
|
197
|
-
version-stamp%%warn%%tsx,jsx,html,vue,svelte%%v[0-9]+\.[0-9]+\.[0-9]+|Build [0-9]{3,4}%%a build or version stamp is devtool furniture on a marketing page. Delete it. Legitimate exception: a real devtool footer, a changelog or a release page
|
|
198
|
-
viewport-unit%%warn%%tsx,jsx,html,vue,svelte,css%%h-screen%%h-screen jumps when mobile browser chrome slides away. Use min-h-[100dvh] instead
|
|
199
|
-
middot-chain%%warn%%tsx,jsx,html,vue,svelte%%·[^·]*·%%more than one middot on a line is decoration pretending to be metadata. Use line breaks, columns or a hairline instead
|
|
200
|
-
transition-all%%warn%%tsx,jsx,css,html,vue,svelte%%transition:[[:space:]]*all|transition-all%%transition-all animates layout properties and janks. List the exact properties instead
|
|
201
|
-
ban-list%%warn%%tsx,jsx,html,md,mdx%%revolutionary|game-?changer|cutting-edge|supercharge|seamless|unlock%%hype words carry no value prop. Use the concrete benefit and a real number instead
|
|
202
|
-
TELLS
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
run_tell_table() {
|
|
206
|
-
local line id sev globs pat msg hits exts rest
|
|
207
|
-
# NOT `IFS='%%'`: IFS is a set of characters, so it would split on a single `%` and put an
|
|
208
|
-
# empty field between every pair. Parameter expansion splits on the two-character
|
|
209
|
-
# delimiter exactly, which is what the pattern column needs to keep its ERE `|`.
|
|
210
|
-
while IFS= read -r line; do
|
|
211
|
-
case "$line" in ''|'#'*) continue ;; esac
|
|
212
|
-
id="${line%%"%%"*}"; rest="${line#*"%%"}"
|
|
213
|
-
sev="${rest%%"%%"*}"; rest="${rest#*"%%"}"
|
|
214
|
-
globs="${rest%%"%%"*}"; rest="${rest#*"%%"}"
|
|
215
|
-
pat="${rest%%"%%"*}"; msg="${rest#*"%%"}"
|
|
216
|
-
exts="${globs//,/|}"
|
|
217
|
-
# Two post-filters, both learned from a real run over this repo:
|
|
218
|
-
# 1. keep only the extensions the row declares (anchored before the first colon, so it
|
|
219
|
-
# behaves the same under rg and grep and never matches content that names a file);
|
|
220
|
-
# 2. drop lines whose match sits in a code comment. A dash in `/* tokens — lifted */`
|
|
221
|
-
# is not rendered copy, and flagging it trains the reader to ignore the tool.
|
|
222
|
-
hits="$(search "$pat" 2>/dev/null \
|
|
223
|
-
| grep -E "^[^:]*\.($exts):" \
|
|
224
|
-
| grep -vE "^[^:]*:[0-9]+:[[:space:]]*(//|/\*|\*[^a-zA-Z]|#|<!--|\{/\*)" || true)"
|
|
225
|
-
if [ -n "$hits" ]; then
|
|
226
|
-
# The sev column is real, not decoration: anything other than `warn` breaks the build.
|
|
227
|
-
case "$sev" in
|
|
228
|
-
warn) warn "$id: $msg" ;;
|
|
229
|
-
*) fail "$id: $msg" ;;
|
|
230
|
-
esac
|
|
231
|
-
printf '%s\n' "$hits" | head -n 5
|
|
232
|
-
fi
|
|
233
|
-
done <<EOF
|
|
234
|
-
$(tell_table)
|
|
235
|
-
EOF
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
# --- counters: the tells that are a ratio or a repetition, not a pattern -----
|
|
239
|
-
# These cannot be table rows because one match proves nothing — it takes a count. Each one
|
|
240
|
-
# carries its own named test instead of riding the generic row mechanism.
|
|
241
|
-
counter_checks() {
|
|
242
|
-
local eyebrows sections ceiling hits variants v
|
|
243
|
-
|
|
244
|
-
# Eyebrow ceiling. Threshold ceil(sections/3) is BORROWED from taste-skill and has never
|
|
245
|
-
# been calibrated against a page of ours — the warning says so rather than implying rigour.
|
|
246
|
-
eyebrows="$(search 'uppercase[^"]*tracking|tracking[^"]*uppercase' 2>/dev/null | grep -cE '^[^:]*\.(tsx|jsx|html|vue|svelte):' || true)"
|
|
247
|
-
sections="$(search '<section' 2>/dev/null | grep -cE '^[^:]*\.(tsx|jsx|html|vue|svelte):' || true)"
|
|
248
|
-
[ -z "$eyebrows" ] && eyebrows=0
|
|
249
|
-
[ -z "$sections" ] && sections=0
|
|
250
|
-
if [ "$sections" -eq 0 ]; then
|
|
251
|
-
if [ "$eyebrows" -gt 0 ]; then
|
|
252
|
-
skip "eyebrow ceiling: no <section> found, so there is no denominator — not guessing one"
|
|
253
|
-
fi
|
|
254
|
-
else
|
|
255
|
-
ceiling=$(( (sections + 2) / 3 ))
|
|
256
|
-
if [ "$eyebrows" -gt "$ceiling" ]; then
|
|
257
|
-
warn "eyebrow count $eyebrows exceeds the ceiling $ceiling for $sections sections (uncalibrated threshold, borrowed): drop the uppercase micro-labels, or let the headline carry it instead"
|
|
258
|
-
fi
|
|
259
|
-
fi
|
|
260
|
-
|
|
261
|
-
# A hairline above AND below every row of a long list is the laziest possible layout.
|
|
262
|
-
hits="$(search 'border-t[^"]*border-b|border-b[^"]*border-t' 2>/dev/null | grep -cE '^[^:]*\.(tsx|jsx|html|vue|svelte):' || true)"
|
|
263
|
-
[ -z "$hits" ] && hits=0
|
|
264
|
-
if [ "$hits" -gt 3 ]; then
|
|
265
|
-
warn "double border on $hits list rows: pick one edge, or use spacing instead of hairlines"
|
|
266
|
-
fi
|
|
267
|
-
|
|
268
|
-
# More than two radius scales in one tree means no radius system at all.
|
|
269
|
-
variants=0
|
|
270
|
-
for v in none sm md lg xl 2xl 3xl full; do
|
|
271
|
-
if search "rounded-$v" >/dev/null 2>&1; then variants=$((variants + 1)); fi
|
|
272
|
-
done
|
|
273
|
-
if [ "$variants" -gt 2 ]; then
|
|
274
|
-
warn "$variants different radius scales in use: one radius system per project — use a --radius-* token instead"
|
|
275
|
-
fi
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
# --- static design-review checks (always run, no network) -------------------
|
|
279
|
-
static_checks() {
|
|
280
|
-
local hits
|
|
281
|
-
|
|
282
|
-
# 1. more than one <h1> in any single file
|
|
283
|
-
if have rg; then
|
|
284
|
-
while IFS= read -r line; do
|
|
285
|
-
[ -n "$line" ] && warn "multiple <h1> in one file: $line"
|
|
286
|
-
done < <(rg -c '<h1' --glob "!$SELF_NAME" --glob '*.{tsx,jsx,html,vue,svelte}' . 2>/dev/null | awk -F: '$NF>1' || true)
|
|
287
|
-
else
|
|
288
|
-
while IFS= read -r f; do
|
|
289
|
-
[ -z "$f" ] && continue
|
|
290
|
-
case "$f" in */"$SELF_NAME"|"./$SELF_NAME") continue ;; esac
|
|
291
|
-
if [ "$(grep -c '<h1' "$f" 2>/dev/null || echo 0)" -gt 1 ]; then warn "multiple <h1> in one file: $f"; fi
|
|
292
|
-
done < <(grep -rl --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=.next '<h1' . 2>/dev/null || true)
|
|
293
|
-
fi
|
|
294
|
-
|
|
295
|
-
# 2. (moved) `transition: all` is now a row in tell_table() — it is a plain pattern, so it
|
|
296
|
-
# gets the generic mechanism and its fixture for free.
|
|
297
|
-
|
|
298
|
-
# 3. hardcoded hex when a token system exists (\b is not portable in ERE; a
|
|
299
|
-
# 3-8 char hex run after # is a sufficient heuristic without the boundary).
|
|
300
|
-
if search '@theme|--color-' >/dev/null 2>&1; then
|
|
301
|
-
hits="$(search '#[0-9a-fA-F]{3,8}' 2>/dev/null | grep -E '\.(tsx|jsx|css)' || true)"
|
|
302
|
-
if [ -n "$hits" ]; then warn "hardcoded hex colors despite a token system (heuristic):"; printf '%s\n' "$hits" | head -n 5; fi
|
|
303
|
-
fi
|
|
304
|
-
|
|
305
|
-
# 4. <img> / <Image without alt.
|
|
306
|
-
# POSIX ERE (the grep fallback) has NO lookahead, so we do it in two
|
|
307
|
-
# lookahead-free stages that behave identically under rg and grep:
|
|
308
|
-
# find single-line image tags, then drop the ones that carry alt=.
|
|
309
|
-
# Heuristic: tags split across multiple lines are not caught (acceptable —
|
|
310
|
-
# this is a fast lint, not an AST), and it never errors on either engine.
|
|
311
|
-
hits="$(search '<(img|Image)[^>]' 2>/dev/null | grep -iE '\.(tsx|jsx|html|vue|svelte)' | grep -vE '\balt=' || true)"
|
|
312
|
-
if [ -n "$hits" ]; then warn "image without alt= found (single-line tags):"; printf '%s\n' "$hits" | head -n 5; fi
|
|
313
|
-
|
|
314
|
-
# 5. animations present but no prefers-reduced-motion anywhere
|
|
315
|
-
if search '@keyframes|animation:|animate-' >/dev/null 2>&1; then
|
|
316
|
-
if ! search 'prefers-reduced-motion' >/dev/null 2>&1; then
|
|
317
|
-
warn "animations present but no prefers-reduced-motion guard found"
|
|
318
|
-
fi
|
|
319
|
-
fi
|
|
320
|
-
|
|
321
|
-
# 6. (moved) the marketing ban-list is now a row in tell_table(), same reason as #2.
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
# --- fallback manual checklist ----------------------------------------------
|
|
325
|
-
print_checklist() {
|
|
326
|
-
cat <<'EOF'
|
|
327
|
-
|
|
328
|
-
Manual design-review checklist (Lighthouse did not run):
|
|
329
|
-
[ ] Value prop legible in 5 seconds above the fold.
|
|
330
|
-
[ ] Text contrast >= 4.5:1 (3:1 for large text / UI).
|
|
331
|
-
[ ] Visible focus state on all interactive elements.
|
|
332
|
-
[ ] Touch targets 44x44px (recommended); never below the 24x24px WCAG 2.2 AA floor (SC 2.5.8).
|
|
333
|
-
[ ] prefers-reduced-motion honored.
|
|
334
|
-
[ ] Exactly one <h1> on the page.
|
|
335
|
-
[ ] Semantic landmarks present (header/nav/main/section/footer).
|
|
336
|
-
[ ] LCP image has priority.
|
|
337
|
-
[ ] Fonts use next/font (no CLS / FOUT swap shift).
|
|
338
|
-
[ ] No transition: all / transition-all.
|
|
339
|
-
[ ] Tokens used (no magic hex / px).
|
|
340
|
-
[ ] Ban-list words absent from copy.
|
|
341
|
-
[ ] Text fits at 360px and desktop without overflow.
|
|
342
|
-
[ ] Empty / loading / hover / error states designed.
|
|
343
|
-
EOF
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
# --- run --------------------------------------------------------------------
|
|
347
|
-
run_lighthouse
|
|
348
|
-
static_checks
|
|
349
|
-
run_tell_table
|
|
350
|
-
counter_checks
|
|
351
|
-
[ "$LH_RAN" -eq 0 ] && print_checklist
|
|
352
|
-
|
|
353
|
-
printf '\nok=%d skip=%d warn=%d fail=%d\n' "$ok_count" "$skip_count" "$warn_count" "$fail_count"
|
|
354
|
-
|
|
355
|
-
if [ "$fail_count" -gt 0 ]; then exit 1; fi
|
|
356
|
-
if [ "$STRICT" -eq 1 ] && [ "$warn_count" -gt 0 ]; then exit 1; fi
|
|
357
|
-
exit 0
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
<!-- design-dna: the standalone prompt.
|
|
2
|
-
Paste into any session and attach the winning design. Nothing else is needed.
|
|
3
|
-
Kept verbatim from the source method; edit only if you also edit ../SKILL.md. -->
|
|
4
|
-
|
|
5
|
-
You are a design forensics analyst. I am going to give you one design I love.
|
|
6
|
-
|
|
7
|
-
Your job is not to praise it, describe it, or make something like it. Your job is
|
|
8
|
-
to CODIFY it: reduce it to the smallest set of rules that will reproduce its
|
|
9
|
-
identity on completely different content, forever, across any medium.
|
|
10
|
-
|
|
11
|
-
The output is a reusable skill. Treat the design in front of you as evidence,
|
|
12
|
-
not as a brief.
|
|
13
|
-
|
|
14
|
-
## THE STANDARD YOU ARE HELD TO
|
|
15
|
-
|
|
16
|
-
A specification that cannot fail is not a specification.
|
|
17
|
-
|
|
18
|
-
If every rule you write is one a bad output could still satisfy, you have written
|
|
19
|
-
a mood board. Every rule must be checkable against a finished piece, and capable
|
|
20
|
-
of returning FAIL.
|
|
21
|
-
|
|
22
|
-
## WHAT ACTUALLY CARRIES A DESIGN'S IDENTITY
|
|
23
|
-
|
|
24
|
-
Read this before you start. Getting it wrong is the standard failure.
|
|
25
|
-
|
|
26
|
-
1. RELATIONSHIPS, NOT VALUES. "The headline is 96px" is nearly worthless. "The
|
|
27
|
-
headline is 8x the body, never under 6x" is the identity. Style lives in the
|
|
28
|
-
ratios between elements. A list of measurements is exactly that information
|
|
29
|
-
thrown away.
|
|
30
|
-
2. PROPORTIONS OF COLOUR, NOT JUST COLOUR. The same three hex codes at 60/30/10
|
|
31
|
-
and at 90/8/2 are two unrelated designs. Always record coverage.
|
|
32
|
-
3. ONE BREAK IN THE SYSTEM. Almost every design worth copying has exactly one
|
|
33
|
-
deliberate exception: type crossing an image, a rule overshooting its margin,
|
|
34
|
-
a numeral clipped by the canvas edge. It is the highest-information element
|
|
35
|
-
present and the first thing a mechanical extraction loses, because extraction
|
|
36
|
-
looks for systems and this is a break from the system. Find it. Name it.
|
|
37
|
-
4. REFUSALS, NOT JUST PERMISSIONS. Listing six colours tells a model it may use
|
|
38
|
-
six. If the reference uses one accent on 3% of the canvas, the design's real
|
|
39
|
-
content is a refusal. Write the refusals down.
|
|
40
|
-
5. ABSENCE IS DESIGN. No shadows. No icons. No curves. Nothing centred. Record
|
|
41
|
-
what is missing with the same care as what is present.
|
|
42
|
-
6. STRUCTURE, NOT ONLY STYLING. If a style has no named layouts, output #8 will
|
|
43
|
-
not sit beside output #1 as a set. You get consistent styling and
|
|
44
|
-
inconsistent structure, which reads as sloppiness.
|
|
45
|
-
|
|
46
|
-
## RUN THIS IN SEVEN STEPS. SHOW YOUR WORK AT EACH.
|
|
47
|
-
|
|
48
|
-
### STEP 1 - OBSERVE. Do not interpret.
|
|
49
|
-
|
|
50
|
-
Produce a flat inventory of literal observations. Measure, do not describe.
|
|
51
|
-
|
|
52
|
-
Sample real colours. Estimate what share of the canvas each covers. Count the
|
|
53
|
-
type sizes, the weights, the accent uses. Measure the ratio between largest and
|
|
54
|
-
smallest type. Note margins as a percentage of the canvas, not in pixels. Record
|
|
55
|
-
texture, grain, edges, image treatment. Write down what is absent.
|
|
56
|
-
|
|
57
|
-
No judgements yet. Interpretation here is how you end up specifying a design
|
|
58
|
-
that is not the one in front of you.
|
|
59
|
-
|
|
60
|
-
### STEP 2 - DEBATE IT WITH YOURSELF. Two loops, honestly opposed.
|
|
61
|
-
|
|
62
|
-
LOOP A, THE MAXIMALIST. Argue: anything not written down will be improvised, and
|
|
63
|
-
improvisation is where consistency dies. List every property exhaustively.
|
|
64
|
-
Miss nothing.
|
|
65
|
-
|
|
66
|
-
LOOP B, THE MINIMALIST. Argue: a copy can match every value and still look
|
|
67
|
-
generic. Identity lives in a handful of moves and a wall of refusals. Name the
|
|
68
|
-
3-9 moves that actually carry it, and the bans that keep output off the average.
|
|
69
|
-
Attack Loop A's list. Say which entries are load-bearing and which are trivia.
|
|
70
|
-
|
|
71
|
-
ADJUDICATE. For every property, ask:
|
|
72
|
-
|
|
73
|
-
If I changed this value, would the output stop looking like the reference?
|
|
74
|
-
|
|
75
|
-
Yes, it is load-bearing. No, it is trivia, and trivia dilutes attention.
|
|
76
|
-
|
|
77
|
-
Then resolve the debate the right way, which is NOT a compromise. Both sides are
|
|
78
|
-
correct about different documents:
|
|
79
|
-
|
|
80
|
-
- The EXHAUSTIVE record is for me and for build tools. Any size, because a
|
|
81
|
-
compiler does not sample.
|
|
82
|
-
- The PROMPT PAYLOAD is for a model with a finite attention budget. Hard cap:
|
|
83
|
-
2KB of text. Compliance drops as rules pile up, and material in the middle of
|
|
84
|
-
a long document gets recovered far worse than material at either end.
|
|
85
|
-
|
|
86
|
-
### STEP 3 - CODIFY. Write dna.json.
|
|
87
|
-
|
|
88
|
-
Fill these keys. Omit any that genuinely do not apply.
|
|
89
|
-
|
|
90
|
-
meta name, slug, source files, date, medium of origin,
|
|
91
|
-
not_copied (any real logo, wordmark or licensed image -
|
|
92
|
-
reproduce the SYSTEM, never the MARKS)
|
|
93
|
-
soul one_line (max 160 chars, concrete nouns, no marketing words),
|
|
94
|
-
3-5 adjectives, lineage (Swiss editorial / 90s flyer / brutalist),
|
|
95
|
-
read_distance (thumb | arms-length | desk | room | street),
|
|
96
|
-
energy: density, variance, contrast, warmth - each 1-10
|
|
97
|
-
palette colors[]: role, hex, descriptive name ("dusty plum", never
|
|
98
|
-
"accent-500" - image and video models cannot read token names)
|
|
99
|
-
coverage: percentage of canvas per role, summing to ~100
|
|
100
|
-
banned: colour behaviours forbidden
|
|
101
|
-
type families[]: role, family, REQUIRED fallback, weights, licence risk
|
|
102
|
-
scale: display_to_body_ratio (the typographic signature),
|
|
103
|
-
steps, max_sizes_per_frame
|
|
104
|
-
treatment: tracking, leading, case, measure, numerals
|
|
105
|
-
space grid, margin_pct, gutter_pct, alignment, rhythm,
|
|
106
|
-
negative_space (where the emptiness is and how big),
|
|
107
|
-
safe_area per platform
|
|
108
|
-
USE PERCENTAGES so one spec drives a 1080x1350 carousel and a
|
|
109
|
-
1920x1080 slide without a rewrite
|
|
110
|
-
surface texture (kind, opacity, scale, stacking order), edges, elevation,
|
|
111
|
-
imagery treatment, iconography
|
|
112
|
-
signatures[] 3-9. move (name it like a technique), how (AS A RATIO OR
|
|
113
|
-
RELATIONSHIP), when it appears, never (how it gets used wrong)
|
|
114
|
-
weird_move the single break in the system. Its own key, deliberately.
|
|
115
|
-
archetypes[] named layouts: id, purpose in a sequence, anatomy, content budget
|
|
116
|
-
motion easing curves, durations, entrances, what NEVER moves,
|
|
117
|
-
reduced-motion fallback. Omit for static work.
|
|
118
|
-
voice register, sentence length, headline shape, banned words.
|
|
119
|
-
Copy is part of the design.
|
|
120
|
-
bans[] minimum 5, written as absolutes
|
|
121
|
-
tests[] minimum 8. See Step 4.
|
|
122
|
-
|
|
123
|
-
### STEP 4 - WRITE TESTS THAT CAN FAIL. Eight to twelve.
|
|
124
|
-
|
|
125
|
-
Binary and measurable:
|
|
126
|
-
|
|
127
|
-
- "Accent colour covers under 8% of the canvas."
|
|
128
|
-
- "No more than 3 type sizes in a single frame."
|
|
129
|
-
- "Largest-to-smallest type ratio is above 6:1."
|
|
130
|
-
- "Smallest type is at least 28px at 1080px canvas width."
|
|
131
|
-
- "The weird move is present, exactly once."
|
|
132
|
-
- "Body copy stays under 65 characters per line."
|
|
133
|
-
- "Squint from three metres. Does the mass distribution match the reference?"
|
|
134
|
-
|
|
135
|
-
Not tests: "feels premium", "looks clean". If two people could disagree about
|
|
136
|
-
the answer, it is not a test. Mark which ones a script could decide.
|
|
137
|
-
|
|
138
|
-
### STEP 5 - RECONSTRUCT AND DIFF. Do not skip this.
|
|
139
|
-
|
|
140
|
-
Rebuild the original reference using only dna.json. Close the reference. Work
|
|
141
|
-
from the spec alone.
|
|
142
|
-
|
|
143
|
-
Put your rebuild beside the original. List every single difference.
|
|
144
|
-
|
|
145
|
-
Every difference is a field the spec forgot. Fold each one back in. Record what
|
|
146
|
-
was missing. Go again. Repeat until a stranger could not pick the copy from the
|
|
147
|
-
original.
|
|
148
|
-
|
|
149
|
-
This is the only honest test of completeness that exists. A spec that has never
|
|
150
|
-
been used to rebuild its own source has never been tested. Expect two or three
|
|
151
|
-
passes. Expect the gaps to be things you were sure were obvious.
|
|
152
|
-
|
|
153
|
-
### STEP 6 - EMIT THE SKILL.
|
|
154
|
-
|
|
155
|
-
Write a folder named after meta.slug:
|
|
156
|
-
|
|
157
|
-
<slug>/
|
|
158
|
-
SKILL.md how to use this style
|
|
159
|
-
PROMPT.md the 2KB payload. THIS is what goes in a context window.
|
|
160
|
-
dna.json the full record. NEVER pasted into a prompt.
|
|
161
|
-
reference/ the original, kept forever
|
|
162
|
-
example/ one worked output, the canonical proof
|
|
163
|
-
tools/check.py the automatable tests, exits non-zero on failure
|
|
164
|
-
|
|
165
|
-
PROMPT.md is the file that matters. Order it exactly like this, because
|
|
166
|
-
attention is strongest at the two ends and weakest in the middle:
|
|
167
|
-
|
|
168
|
-
1. The reference image, attached and named first
|
|
169
|
-
2. soul.one_line
|
|
170
|
-
3. The weird move, alone, unmissable
|
|
171
|
-
4. The 3-9 signature moves, as ratios
|
|
172
|
-
5. The bans, as absolutes
|
|
173
|
-
6. Palette and type - roles and coverage only, not a full ramp
|
|
174
|
-
7. Archetype names and when to use each
|
|
175
|
-
8. The self-check, last
|
|
176
|
-
|
|
177
|
-
Nothing else. If it does not change what the output looks like from three metres
|
|
178
|
-
away, it belongs in dna.json, not here. To add a tenth signature you must delete
|
|
179
|
-
one. The cap is the point: it forces you to decide, and an uncapped spec just
|
|
180
|
-
ships your indecision.
|
|
181
|
-
|
|
182
|
-
End PROMPT.md with this line:
|
|
183
|
-
|
|
184
|
-
Before returning any output, run every test in the self-check. Name each test
|
|
185
|
-
and its result. If any fails, repair the output and run them again. Never
|
|
186
|
-
return output with a failing test and a note explaining it away.
|
|
187
|
-
|
|
188
|
-
### STEP 7 - TELL ME WHAT YOU ARE UNSURE ABOUT.
|
|
189
|
-
|
|
190
|
-
List every value you inferred rather than measured, and every rule you are under
|
|
191
|
-
70% confident in. These are where the style will drift first, and I would rather
|
|
192
|
-
know now.
|
|
193
|
-
|
|
194
|
-
## RULES FOR YOU, THE ANALYST
|
|
195
|
-
|
|
196
|
-
- Never invent a value you could measure. If you cannot measure it, say so and
|
|
197
|
-
mark it inferred.
|
|
198
|
-
- Never copy a real logo, wordmark, licensed photograph or proprietary typeface.
|
|
199
|
-
Put them in meta.not_copied and substitute. Reproduce the system, never the
|
|
200
|
-
marks.
|
|
201
|
-
- Descriptive colour names, not systematic ones. "Dusty plum" travels to an
|
|
202
|
-
image model. "accent-500" does not.
|
|
203
|
-
- Every font family needs a fallback. Silent Arial substitution is the most
|
|
204
|
-
common way a reproduction dies quietly.
|
|
205
|
-
- When you are tempted to add a rule because output looks generic, add a ban
|
|
206
|
-
instead. A prohibition steers harder than a permission, and one ban can rule
|
|
207
|
-
out an entire space in a single line.
|
|
208
|
-
- One skill per style. Never merge two identities into one spec. The average of
|
|
209
|
-
two good designs is a bad design.
|
|
210
|
-
|
|
211
|
-
Begin at Step 1. Show your work.
|