@ericrisco/rsc 3.1.4 → 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.
Files changed (96) hide show
  1. package/README.md +7 -6
  2. package/manifest.json +17 -68
  3. package/package.json +6 -1
  4. package/scripts/design-upstream.js +92 -0
  5. package/scripts/doctor.js +0 -24
  6. package/scripts/lib/default-skill-floor.js +5 -1
  7. package/scripts/lib/domains.js +2 -2
  8. package/scripts/lib/recommend.js +1 -3
  9. package/scripts/lib/retired-skills.js +7 -1
  10. package/skills/design/IMPECCABLE-LICENSE.txt +191 -0
  11. package/skills/design/SKILL.md +79 -328
  12. package/skills/design/THIRD_PARTY.md +26 -0
  13. package/skills/design/engine/LICENSE +21 -0
  14. package/skills/design/engine/RUN.md +515 -0
  15. package/skills/design/engine/UPSTREAM.json +4 -0
  16. package/skills/design/engine/assets/app.css +216 -0
  17. package/skills/design/engine/assets/base.css +105 -0
  18. package/skills/design/engine/assets/base.js +37 -0
  19. package/skills/design/engine/assets/charts.js +617 -0
  20. package/skills/design/engine/assets/scenes/stage.css +28 -0
  21. package/skills/design/engine/assets/scenes/stage.js +133 -0
  22. package/skills/design/engine/library/app.json +1327 -0
  23. package/skills/design/engine/library/directions.json +1078 -0
  24. package/skills/design/engine/library/heroes.json +1086 -0
  25. package/skills/design/engine/library/motions.json +92 -0
  26. package/skills/design/engine/library/palettes.json +1485 -0
  27. package/skills/design/engine/library/photos.json +7240 -0
  28. package/skills/design/engine/library/sections.json +4429 -0
  29. package/skills/design/engine/library/sites.json +8350 -0
  30. package/skills/design/engine/library/type.json +2024 -0
  31. package/skills/design/engine/references/app-screens.md +1199 -0
  32. package/skills/design/engine/references/colour.md +141 -0
  33. package/skills/design/engine/references/copy.md +177 -0
  34. package/skills/design/engine/references/critique.md +182 -0
  35. package/skills/design/engine/references/imagery.md +791 -0
  36. package/skills/design/engine/references/layout.md +292 -0
  37. package/skills/design/engine/references/motion.md +980 -0
  38. package/skills/design/engine/references/research.md +356 -0
  39. package/skills/design/engine/references/scenes.md +174 -0
  40. package/skills/design/engine/references/tells.md +115 -0
  41. package/skills/design/engine/references/type.md +170 -0
  42. package/skills/design/engine/scripts/assets.mjs +865 -0
  43. package/skills/design/engine/scripts/draw.mjs +584 -0
  44. package/skills/design/engine/scripts/inspect.mjs +303 -0
  45. package/skills/design/engine/scripts/lib/app-draw.mjs +203 -0
  46. package/skills/design/engine/scripts/lib/cdp.mjs +248 -0
  47. package/skills/design/engine/scripts/lib/probe.mjs +909 -0
  48. package/skills/design/engine/scripts/lib/rules.mjs +243 -0
  49. package/skills/design/engine/scripts/lint.mjs +119 -0
  50. package/skills/design/engine/scripts/scenes.mjs +124 -0
  51. package/skills/design/evals/cases.yaml +35 -46
  52. package/skills/design/references/design-md.md +49 -0
  53. package/skills/design/references/finish-review.md +46 -0
  54. package/skills/design/references/modes.md +51 -0
  55. package/skills/expo/SKILL.md +3 -4
  56. package/skills/marketing/references/landing-copy.md +2 -2
  57. package/skills/react/SKILL.md +2 -2
  58. package/skills/react-native/SKILL.md +3 -3
  59. package/skills/swift-ios/SKILL.md +1 -1
  60. package/skills/ui-engineering/SKILL.md +3 -4
  61. package/skills/ui-engineering/evals/cases.yaml +2 -2
  62. package/skills/variant-explorer/SKILL.md +4 -4
  63. package/skills/variant-explorer/evals/cases.yaml +1 -1
  64. package/targets/clone-bootstrap.mjs +1 -1
  65. package/scripts/lib/design-identity.js +0 -117
  66. package/scripts/lib/starting-point.js +0 -314
  67. package/skills/design/evals/README.md +0 -53
  68. package/skills/design/references/ai-tells.md +0 -75
  69. package/skills/design/references/brand-grounding.md +0 -197
  70. package/skills/design/references/copywriting-frameworks.md +0 -138
  71. package/skills/design/references/design-systems.md +0 -49
  72. package/skills/design/references/inspiration-sources.md +0 -96
  73. package/skills/design/references/landing-anatomy-and-cro.md +0 -202
  74. package/skills/design/references/motion-and-interaction.md +0 -232
  75. package/skills/design/references/research-method.md +0 -154
  76. package/skills/design/references/signature-and-craft.md +0 -148
  77. package/skills/design/references/starting-point.md +0 -75
  78. package/skills/design/references/trends-2026.md +0 -80
  79. package/skills/design/references/visual-system.md +0 -237
  80. package/skills/design/scripts/verify.sh +0 -357
  81. package/skills/design-dna/PROMPT.md +0 -211
  82. package/skills/design-dna/SKILL.md +0 -162
  83. package/skills/design-dna/design-dna.schema.json +0 -230
  84. package/skills/design-dna/evals/README.md +0 -64
  85. package/skills/design-dna/evals/cases.yaml +0 -68
  86. package/skills/design-dna/references/method.md +0 -436
  87. package/skills/design-dna/scripts/emit.py +0 -164
  88. package/skills/design-dna/templates/PROMPT.md.tmpl +0 -42
  89. package/skills/design-dna/templates/SKILL.md.tmpl +0 -42
  90. package/skills/design-dna/templates/check.py.tmpl +0 -143
  91. package/skills/design-loop/SKILL.md +0 -160
  92. package/skills/design-loop/evals/README.md +0 -56
  93. package/skills/design-loop/evals/cases.yaml +0 -60
  94. package/skills/motion-craft/SKILL.md +0 -125
  95. package/skills/motion-craft/evals/cases.yaml +0 -38
  96. 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.