@domandigital/craft 0.5.0 → 0.7.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/CHANGELOG.md +110 -0
- package/CHARACTER.md +578 -0
- package/PRINCIPLES.md +14 -0
- package/README.md +36 -1
- package/STANDARD.md +4 -1
- package/art-direction.schema.json +59 -0
- package/dist/audit-YNODYB3U.js +20 -0
- package/dist/audit.cjs +2472 -0
- package/dist/audit.d.cts +389 -0
- package/dist/audit.d.ts +389 -0
- package/dist/audit.js +2439 -0
- package/dist/chunk-3AP4PJMX.js +2352 -0
- package/dist/chunk-CU5TZHOV.js +163 -0
- package/dist/chunk-GV3QOTQU.js +458 -0
- package/dist/chunk-IQTZWNEK.js +112 -0
- package/dist/chunk-J2BOAIHJ.js +175 -0
- package/dist/chunk-OA7SGPN4.js +419 -0
- package/dist/chunk-Q54JSNC5.js +105 -0
- package/dist/chunk-YYC4EGER.js +249 -0
- package/dist/cli-6Y5E32FS.js +334 -0
- package/dist/cli-IQT6QZ6U.js +16 -0
- package/dist/cli-MW3JAGX3.js +21 -0
- package/dist/cli-PD43TWX7.js +15 -0
- package/dist/cli-TM2HWYN7.js +312 -0
- package/dist/cli.js +22 -0
- package/dist/index.cjs +3385 -27
- package/dist/index.d.cts +1011 -1
- package/dist/index.d.ts +1011 -1
- package/dist/index.js +3311 -24
- package/package.json +28 -4
- package/word-lists.json +155 -0
package/CHARACTER.md
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# The character standard
|
|
2
|
+
|
|
3
|
+
[STANDARD.md](./STANDARD.md) asks whether a surface is well made. This document
|
|
4
|
+
asks a second question: **did anyone decide how it looks, or is it the answer a
|
|
5
|
+
model gives when nobody told it otherwise?**
|
|
6
|
+
|
|
7
|
+
A site can pass every craft number and still read as generated. Indigo accent,
|
|
8
|
+
Inter, a glass card over the hero, a strip of logos, three icon cards, every
|
|
9
|
+
section fading up on scroll. None of those is wrong. Together they say nobody
|
|
10
|
+
chose anything.
|
|
11
|
+
|
|
12
|
+
## Change the look, not the page grammar
|
|
13
|
+
|
|
14
|
+
First impressions favour pages that look typical for their category (Tuch et
|
|
15
|
+
al., 2012). So this standard **never rewards strangeness**. Navigation, where
|
|
16
|
+
the call to action sits, reading order and legibility stay conventional. They
|
|
17
|
+
remain the job of STANDARD.md and the page audit.
|
|
18
|
+
|
|
19
|
+
Character goes into the surface: the display face, the accent, the shape
|
|
20
|
+
language, the motif, the one signature moment. That is where a model's defaults
|
|
21
|
+
show, and where a real business has something of its own to draw on.
|
|
22
|
+
|
|
23
|
+
## Three signals, and a reason behind every choice
|
|
24
|
+
|
|
25
|
+
Any fixed list of tells goes stale. Ban Inter and a model moves to Space
|
|
26
|
+
Grotesk. So the list is one signal of three, and the other two keep working as
|
|
27
|
+
model defaults move.
|
|
28
|
+
|
|
29
|
+
| Signal | Question | Status |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| 1. Known tells | Does it use a choice on the catalogue below? | **Shipped**: source, copy, and on the rendered page for 18 tells (`craft audit`). |
|
|
32
|
+
| 2. Counterfactual typicality | Would Claude have built this anyway, for this brief? | **Shipped**: `craft null build` generates about 20 pages from the brief; `craft audit --null` scores a page against them. |
|
|
33
|
+
| 3. Distance from the estate | Does it look like the agency's other sites? | **Shipped**: `craft estate add` fingerprints each shipped site into `estate.json`; `craft estate compare` marks siblings. The agency's biggest tell is its clients looking like each other. |
|
|
34
|
+
|
|
35
|
+
**The reason rule.** Every expressive choice records a `because`, with evidence
|
|
36
|
+
from the client's real world: the shopfront, the van, the trade's own look, the
|
|
37
|
+
place, the materials. A token with no reason is a default. This is the part that
|
|
38
|
+
adds character rather than only removing tells. It lives in `art-direction.json`.
|
|
39
|
+
|
|
40
|
+
## Tells are tagged by generation
|
|
41
|
+
|
|
42
|
+
| Gen | What it is |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| 1 | The first wave: indigo, blue-to-purple gradients, Inter, glass, three icon cards. |
|
|
45
|
+
| 2 | What models moved to once the first wave was named: cream grounds and italic serifs, eyebrow chips, bento grids, marquees, intro cinematics. |
|
|
46
|
+
| 3 | Whatever `craft tells harvest` finds next in the null models, once a person has read it. |
|
|
47
|
+
|
|
48
|
+
A report counts findings by generation, so it can say *which* wave a site was
|
|
49
|
+
built in, not only that it has tells.
|
|
50
|
+
|
|
51
|
+
## Every tell ships as a warning
|
|
52
|
+
|
|
53
|
+
Nothing blocks until its hits across the estate have been read by a person and
|
|
54
|
+
the rule tuned against them. A rule that blocks on day one gets switched off by
|
|
55
|
+
day three. `--strict` fails on any finding, for a surface that has been
|
|
56
|
+
cleaned and should stay clean.
|
|
57
|
+
|
|
58
|
+
## Declaring the direction
|
|
59
|
+
|
|
60
|
+
`art-direction.json` at the site root records the seven expressive choices
|
|
61
|
+
(accent, ground, display face, body face, shape, motif, signature moment), the
|
|
62
|
+
sources in the client's world they come from, and why. Layout and navigation
|
|
63
|
+
are not choices here: they stay conventional.
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"version": 1,
|
|
68
|
+
"client": "RMP Electrical",
|
|
69
|
+
"brief": "A two-person electrical contractor in Brackley doing rewires and EV chargers.",
|
|
70
|
+
"sources": [
|
|
71
|
+
{ "id": "van", "kind": "livery", "note": "The Transit van, bottle green with cream sign-writing.", "path": "brand/van.png" }
|
|
72
|
+
],
|
|
73
|
+
"choices": {
|
|
74
|
+
"accent": {
|
|
75
|
+
"value": "#1f4d3a",
|
|
76
|
+
"because": "The bottle green is the van's own paint, and the van is what people in Brackley recognise.",
|
|
77
|
+
"evidence": ["van"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`craft direction validate` applies the reason rule. A choice is decided only
|
|
84
|
+
when its reason is at least a sentence, cites a declared source, and mentions
|
|
85
|
+
what that source shows (naming only the colour does not count). It rejects a
|
|
86
|
+
preference ("the client likes it"), a mood board (two or more of modern,
|
|
87
|
+
clean, premium, elegant and the rest), and a value on the tell catalogue
|
|
88
|
+
without an exception carrying the same reason. Given a snapshot, it warns
|
|
89
|
+
where the page does not show what the file declares.
|
|
90
|
+
|
|
91
|
+
`craft direction init` writes what the site does today with every reason
|
|
92
|
+
empty, so the first thing it produces is the list of things nobody decided.
|
|
93
|
+
Run on RMP's live home page (2026-09-23) it records Fraunces, Manrope, a cream
|
|
94
|
+
ground and pill buttons, three of them on the catalogue, and zero of seven
|
|
95
|
+
choices decided.
|
|
96
|
+
|
|
97
|
+
`craft direction propose` reads the dominant colours off each source's PNG
|
|
98
|
+
photo, ranks accent candidates away from the reflex violet band and from the
|
|
99
|
+
rest of the estate (`--estate` a folder of snapshots), and drafts choices.
|
|
100
|
+
Every draft reason starts `PROPOSED:`, which validate rejects: a person looks
|
|
101
|
+
at the van, agrees the green is the van's green, and says so in their own
|
|
102
|
+
words.
|
|
103
|
+
|
|
104
|
+
The editor schema ships as `@domandigital/craft/art-direction.schema.json`.
|
|
105
|
+
A file from phase A that holds only exceptions stays valid.
|
|
106
|
+
|
|
107
|
+
## Declaring an exception
|
|
108
|
+
|
|
109
|
+
Doman Digital's own brand is violet. A hue rule with no way to say so flags the
|
|
110
|
+
agency's own site on every commit. So an exception is declared, with its reason,
|
|
111
|
+
in `art-direction.json` at the site root:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"exceptions": [
|
|
116
|
+
{
|
|
117
|
+
"tell": "ai-violet",
|
|
118
|
+
"because": "Violet has been the Doman Digital mark since 2019: the van, the cards, the signage.",
|
|
119
|
+
"evidence": "brand/van-livery.jpg"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
An exception with no `because`, or one shorter than a sentence, is not applied
|
|
126
|
+
and is reported. An applied exception is still listed in every report with the
|
|
127
|
+
count it silenced, so it stays visible.
|
|
128
|
+
|
|
129
|
+
## Using it
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
craft scan # markup, components and stylesheets under .
|
|
133
|
+
craft scan --staged # the git index, for a pre-commit hook
|
|
134
|
+
craft copy app content # the prose a visitor reads
|
|
135
|
+
craft tells list # the catalogue this build judges against
|
|
136
|
+
craft audit https://example.co.uk --repo . # the rendered page and its source, one report
|
|
137
|
+
craft snapshot https://example.co.uk --out home.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`--json` gives the full report. Exit code is 0 on warnings, 1 on a block (or
|
|
141
|
+
any finding under `--strict`), 2 on a usage error.
|
|
142
|
+
|
|
143
|
+
`scan` reads classes the way Tailwind does: `className="..."`, the literals
|
|
144
|
+
inside `cn()`, `clsx()` and `twMerge()`, and each `cva()` variant separately.
|
|
145
|
+
It reads colour in hex, `rgb()`, `hsl()` and `oklch()`, in raw CSS, Tailwind v4
|
|
146
|
+
`@theme` blocks and arbitrary values, and judges hue in OKLCh. A framework's
|
|
147
|
+
own palette definitions are ignored: a colour counts when a site uses it, not
|
|
148
|
+
when the framework defines it.
|
|
149
|
+
|
|
150
|
+
`copy` reads Markdown outside code, text between tags (including text that
|
|
151
|
+
runs into an expression, such as a label followed by `{count}`), string literals that
|
|
152
|
+
read as sentences, and the text inside HTML held in a string, such as an email
|
|
153
|
+
template. Short strings are read too, for the tells that fit in a label: an em
|
|
154
|
+
dash, an emoji, a "No catch." badge. Comments are never copy. It reads `.json`
|
|
155
|
+
and `.jsonl`, so a Sanity export can be checked a document per line. It skips
|
|
156
|
+
READMEs, changelogs and `_`-prefixed folders when walking a directory, because
|
|
157
|
+
those are for the people who build the site.
|
|
158
|
+
|
|
159
|
+
A real person's own words, a review or a testimonial, must never be edited to
|
|
160
|
+
pass. Mark the line, or the line above it, with `copy-ok` or `craft-ok`. Marked
|
|
161
|
+
findings are listed in every report with where they are, never hidden. The
|
|
162
|
+
same markers work for `craft scan`.
|
|
163
|
+
|
|
164
|
+
## The rendered page
|
|
165
|
+
|
|
166
|
+
`craft audit` opens the page in Chromium, waits five seconds, and records what
|
|
167
|
+
a visitor gets as a snapshot: the faces that set the text and the headline,
|
|
168
|
+
the colours by painted area, the buttons' shape, the sections in running
|
|
169
|
+
order and which ones wait for a scroll, and the effects (glass, glows,
|
|
170
|
+
marquees, an intro that covers the page at load). The same tells then judge
|
|
171
|
+
it, so a report can say "Inter in source and on the page", or catch the
|
|
172
|
+
violet a CMS sets that no source file names.
|
|
173
|
+
|
|
174
|
+
It needs Playwright, as an optional peer dependency: `npm i -D playwright`.
|
|
175
|
+
The core import never loads a browser. A saved snapshot can be judged again
|
|
176
|
+
anywhere with `craft audit home.json`. `HTTPS_PROXY` is honoured, and a
|
|
177
|
+
`localhost` or `NO_PROXY` host is reached directly.
|
|
178
|
+
|
|
179
|
+
Every audit also prints a **fingerprint**: accent and ground in OKLCh, the
|
|
180
|
+
display and body faces, button roundness, reveal density, the effects and the
|
|
181
|
+
running order. `fingerprintDistance` compares two of them, 0 to 1, weighted
|
|
182
|
+
towards accent and type. Signals 2 and 3 are both this distance, measured
|
|
183
|
+
against different sets.
|
|
184
|
+
|
|
185
|
+
Known limit: a computed font family is the family the stylesheet asked for.
|
|
186
|
+
A face that failed to load is still reported under its name.
|
|
187
|
+
|
|
188
|
+
## The counterfactual
|
|
189
|
+
|
|
190
|
+
A list of tells goes stale; the model does not. So signal 2 asks the model
|
|
191
|
+
directly: given this brief and nothing else, what would you build?
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
craft null build --brief "RMP Electrical, electricians based in Uxbridge ..." --out null/rmp
|
|
195
|
+
craft audit https://www.rmp-electrical.co.uk --null null/rmp
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`null build` runs `claude -p` about 20 times (`--runs`), from an empty folder
|
|
199
|
+
with no tools, settings, skills or MCP servers, so each page is what the model
|
|
200
|
+
builds from the brief alone. The prompt is recorded in `null.json` and says
|
|
201
|
+
nothing about how the page should look. Each page is rendered, snapshotted and
|
|
202
|
+
fingerprinted like any other. A reply that is a note instead of a page is kept
|
|
203
|
+
as `NN.reply.txt` and generated again. The build is resumable: run the same
|
|
204
|
+
command to fill in anything that failed. `--brief` can come from
|
|
205
|
+
`art-direction.json` with `--direction`.
|
|
206
|
+
|
|
207
|
+
**Typicality** is a page's mean fingerprint distance to its three nearest null
|
|
208
|
+
pages, ranked against the same measure for every null page against the
|
|
209
|
+
others. A score of 0.40 means 40% of the model's own pages sit further from
|
|
210
|
+
the rest than this one does. A page is typical at 0.10 or above, so a page the
|
|
211
|
+
model built is flagged nine times in ten by construction, and the threshold
|
|
212
|
+
comes from the model's own spread rather than a number picked here. The report
|
|
213
|
+
lists what the page shares with most of the null ("display Inter, 14 of 20"),
|
|
214
|
+
which is where to start changing it.
|
|
215
|
+
|
|
216
|
+
**The harvest** reads null models the other way round. `craft tells harvest
|
|
217
|
+
calibration/null` lists the choices that recur across the null pages (faces,
|
|
218
|
+
accent families, grounds, button shapes, openings, phrases) and says which
|
|
219
|
+
tell already catches each. The ones nothing catches are the candidates for
|
|
220
|
+
generation 3. A phrase has to turn up across more than one brief, so a trade's
|
|
221
|
+
own words do not count, and navigation labels, form labels and the legal footer
|
|
222
|
+
are left out as page grammar. Nothing is added to the catalogue automatically:
|
|
223
|
+
a candidate becomes a tell with a flag case and a pass case like every other.
|
|
224
|
+
|
|
225
|
+
Known limits: a user-level `CLAUDE.md` on the machine that builds the null
|
|
226
|
+
still reaches the model. The null is only as current as its build date; rebuild
|
|
227
|
+
it when the model changes.
|
|
228
|
+
|
|
229
|
+
## The estate
|
|
230
|
+
|
|
231
|
+
The agency's biggest tell is not on any one site. It is its clients looking
|
|
232
|
+
like each other. `estate.json` holds one fingerprint per shipped site:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
craft estate add https://www.rmp-electrical.co.uk --id rmp --client "RMP Electrical"
|
|
236
|
+
craft estate compare # every pair, closest first
|
|
237
|
+
craft estate compare rmp # one site against the rest
|
|
238
|
+
craft estate compare https://staging.example.test # a new build before it ships
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Siblings** are two sites closer together than two pages Claude builds for
|
|
242
|
+
the same brief usually are: the median of that distance over the null models,
|
|
243
|
+
0.31 on 2026-09-23 (`--null` measures it again from any null models given).
|
|
244
|
+
If a barber and an electrician look more alike than two drafts of one barber,
|
|
245
|
+
neither look was chosen. The report says what the pair shares in plain words
|
|
246
|
+
("Fraunces headline", "no accent", "pill buttons"), which is where to start
|
|
247
|
+
pulling them apart. A sibling is a warning; `--strict` exits 1 on one, for a
|
|
248
|
+
pipeline that has decided to hold the line.
|
|
249
|
+
|
|
250
|
+
`craft direction propose --estate estate.json` reads the register too, so a
|
|
251
|
+
proposed accent steers away from one a sibling already uses.
|
|
252
|
+
|
|
253
|
+
## The character report
|
|
254
|
+
|
|
255
|
+
`craft report` reads one site against everything above and gives a verdict:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
craft report https://www.rmp-electrical.co.uk --repo . --null null/rmp --estate ../estate.json
|
|
259
|
+
craft retrofit https://www.rmp-electrical.co.uk --repo . --null null/rmp --estate ../estate.json --out RETROFIT.md
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Verdict | When |
|
|
263
|
+
|---|---|
|
|
264
|
+
| default | Two or more signals raised: three or more design tells, typical of the brief's null, a sibling in the estate, or fewer than five choices decided with a reason. |
|
|
265
|
+
| mixed | One raised. |
|
|
266
|
+
| decided | None raised, and all four measured. |
|
|
267
|
+
| unproven | None raised, but something was not measured. Unverified is never a pass. |
|
|
268
|
+
|
|
269
|
+
The actions come in the order a retrofit works: decide first, then type,
|
|
270
|
+
colour, shape, effects, motion, how sections look, and copy. Where a tell, the
|
|
271
|
+
null model and a sibling all point at one choice, they are one action with
|
|
272
|
+
three reasons. Sharing a white page or a still one with the null is never
|
|
273
|
+
an action: that would reward strangeness.
|
|
274
|
+
|
|
275
|
+
`craft retrofit` writes the same as a checklist. Each change names the
|
|
276
|
+
art-direction choice that settles it, and shows the decided value and its
|
|
277
|
+
reason, or says it waits on that decision. It ends with what to leave alone:
|
|
278
|
+
navigation, the order of sections, where the call to action sits.
|
|
279
|
+
|
|
280
|
+
## The house copy rules live here
|
|
281
|
+
|
|
282
|
+
Since catalogue `2026.09.2` the rule lists of claude-kit's
|
|
283
|
+
`house-style/copy-rules.md` are carried by tells in this catalogue:
|
|
284
|
+
`ai-phrase`, `plainer-word`, `plain-english`, `buzzword`,
|
|
285
|
+
`negative-reassurance`, `em-dash`, `emoji`, `not-just-but`, `no-x-no-y` and
|
|
286
|
+
`no-x-badge` are its blocking tier; `review-phrase` and `vague-word` its review
|
|
287
|
+
tier. craft ships all of them as `warn`. `copy-check` in claude-kit is a thin
|
|
288
|
+
wrapper over `craft copy --json` and decides which tells fail a commit, so the
|
|
289
|
+
lists exist once, here, with a test per entry.
|
|
290
|
+
|
|
291
|
+
The swap was checked against the old checker on 2,570 files across the seven
|
|
292
|
+
client repos (2026-09-23). The old checker found 193 issues, craft finds every
|
|
293
|
+
one of them that is visitor copy. The rest, 29, were the old checker reading
|
|
294
|
+
code comments, asset keys (`brows-hero-seamless`), a regex that strips em
|
|
295
|
+
dashes, and three words (`truly`, `genuinely`, `deeply`) that the house rules
|
|
296
|
+
keep in the review tier and the old list had drifted into blocking. Four real
|
|
297
|
+
misses in craft were fixed on the way: em dashes in JSX text next to an
|
|
298
|
+
expression, a lone dash placeholder in a table cell, prose inside HTML held in
|
|
299
|
+
a string, and the contrastive negation in a live welcome email that is the
|
|
300
|
+
reason that rule exists.
|
|
301
|
+
|
|
302
|
+
## Calibration
|
|
303
|
+
|
|
304
|
+
The targets, recorded here with the date once each has been run:
|
|
305
|
+
|
|
306
|
+
- **AI set** (pages generated by `claude -p` from about 20 briefs): 90% or more
|
|
307
|
+
flagged as typical or tell-heavy. **First run 2026-09-23: 19 of 20 (95%)**, below.
|
|
308
|
+
- **Human set** (the ten references in claude-kit's `reference-set.md`): zero
|
|
309
|
+
block hits and low typicality. **First run 2026-09-23: zero block hits, none
|
|
310
|
+
typical**, below.
|
|
311
|
+
- **Estate baseline** (all seven client repos and live URLs): every hit read by
|
|
312
|
+
a person before any rule blocks. **First run 2026-09-23**, below.
|
|
313
|
+
|
|
314
|
+
A first read of one internal repo (dd-library, 157 source files, 2026-09-22)
|
|
315
|
+
found Inter Tight and Fraunces in the base template, 21 scroll reveals and 31
|
|
316
|
+
pills, and one false positive: Tailwind's `amber-50`, the stock warning-notice
|
|
317
|
+
fill, read as a cream page ground. That rule now matches named cream tokens and
|
|
318
|
+
measured colour only.
|
|
319
|
+
|
|
320
|
+
### Estate baseline, 2026-09-23
|
|
321
|
+
|
|
322
|
+
`craft scan` over each repo and `craft audit` on each live home page at
|
|
323
|
+
1440 x 900. Snapshots and the table are in `calibration/estate/2026-09-23/`.
|
|
324
|
+
Cells read *source findings / R if seen on the rendered page*. Nothing here
|
|
325
|
+
blocks; it is for a person to read.
|
|
326
|
+
|
|
327
|
+
| Tell | DD | MMM | Sensphere | Harrison James | HJ Beauty | Chair and Blade | RMP |
|
|
328
|
+
|---|---|---|---|---|---|---|---|
|
|
329
|
+
| `reflex-font` | 15 / · | 2 / · | 3 / R | 2 / R | 2 / · | 1 / · | |
|
|
330
|
+
| `reflex-font-2` | 7 / R | 34 / R | 2 / · | 1 / R | 3 / R | 4 / R | 12 / R |
|
|
331
|
+
| `ai-violet` | 36 / · | 9 / · | 18 / R | | | | |
|
|
332
|
+
| `gradient-text` | | 2 / · | | | | | |
|
|
333
|
+
| `glass-panel` | 9 / · | 45 / · | 13 / · | | 1 / · | | |
|
|
334
|
+
| `hero-then-proof` | 9 / · | 1 / R | · / R | | 1 / · | 1 / · | 2 / R |
|
|
335
|
+
| `icon-tile-grid` | 1 / R | 9 / · | · / R | | · / R | | 1 / R |
|
|
336
|
+
| `reveal-everywhere` | 1 / · | 1 / · | | | | · / R | |
|
|
337
|
+
| `pill-everything` | 1 / R | 1 / · | 1 / · | | | | 1 / R |
|
|
338
|
+
| `shadcn-dump` | | 1 / · | | | | | |
|
|
339
|
+
| `cream-palette` | | 3 / · | | | | | 18 / R |
|
|
340
|
+
| `italic-serif-display` | | 4 / · | | | | 8 / · | 1 / R |
|
|
341
|
+
| `hero-eyebrow-chip` | 1 / · | 1 / · | | | | | · / R |
|
|
342
|
+
| `icon-tile-stack` | 1 / · | 93 / · | 4 / · | | | | 18 / · |
|
|
343
|
+
| `radial-spotlight-glow` | 6 / R | 14 / R | 5 / R | | | | |
|
|
344
|
+
| `grid-background` | | 2 / · | | | | | |
|
|
345
|
+
| `marquee` | 1 / · | | | | 3 / R | · / R | |
|
|
346
|
+
| `thin-border-wide-shadow` | | 2 / · | 4 / R | | | | |
|
|
347
|
+
| `intro-cinematic` | | · / R | | | | 2 / · | |
|
|
348
|
+
|
|
349
|
+
What it shows:
|
|
350
|
+
|
|
351
|
+
- **The three survey findings reproduce where they can.** Sensphere's violet
|
|
352
|
+
is found in source (18) and on its buttons; MMM's intro, glows and glass are
|
|
353
|
+
in source and its intro and glows on the page; MMM reveals 60% of the
|
|
354
|
+
sections below its fold on scroll, all three of them, one short of the
|
|
355
|
+
four the rendered rule needs, and its source is over the reveal limit. Rise & Bloom's glass
|
|
356
|
+
hero and icon grid could not be checked: that repo is not attached to this
|
|
357
|
+
session.
|
|
358
|
+
- **Every site ships a reflex face.** Twelve of fourteen site-face pairs are
|
|
359
|
+
on one of the two lists. Fraunces sets the headline on DD, HJ Beauty and RMP.
|
|
360
|
+
- **DD and RMP were read as the closest pair** (0.46) on the first run, with
|
|
361
|
+
a green accent in common. DD's "green" was its floating WhatsApp button,
|
|
362
|
+
48px square, the only saturated paint on a near-black and white page. The
|
|
363
|
+
fingerprint now needs a panel's worth of area before a painted colour
|
|
364
|
+
counts as the accent. The corrected pairs are under *The estate*, below.
|
|
365
|
+
- **DD's violet is its brand.** Its 36 source hits wait for the exception in
|
|
366
|
+
its `art-direction.json`.
|
|
367
|
+
|
|
368
|
+
False positives found and fixed in this run:
|
|
369
|
+
|
|
370
|
+
- `glass-panel` on the page fired on five of seven sites. Every one was a
|
|
371
|
+
sticky or fixed nav bar with a blurred background, which is ordinary chrome.
|
|
372
|
+
The rendered path now ignores anything in a `header` or `nav`, fixed or
|
|
373
|
+
sticky, and anything smaller than a panel.
|
|
374
|
+
- `next/font` and CSS-variable family names (`__Inter_d65c78`,
|
|
375
|
+
`cormorantGaramond`) are now read back to the face's name, or every Next.js
|
|
376
|
+
site would have passed the font tells.
|
|
377
|
+
|
|
378
|
+
- The rendered reveal check read only a section and its direct children.
|
|
379
|
+
Found through the null models (below): a reveal on the cards two levels
|
|
380
|
+
down read as a still page. It now reads the text blocks inside a section
|
|
381
|
+
too, ignoring anything fixed. Re-run the same day: Chair and Blade, which
|
|
382
|
+
has a `RevealWrapper`, now shows `reveal-everywhere` on the page (six of
|
|
383
|
+
nine sections below the fold).
|
|
384
|
+
|
|
385
|
+
Not yet read by a person: the source counts for `icon-tile-stack` (MMM 93)
|
|
386
|
+
and `glass-panel` (MMM 45) look high and are the first to check.
|
|
387
|
+
|
|
388
|
+
### Signal 2 first read, 2026-09-23
|
|
389
|
+
|
|
390
|
+
Seven null models, one per estate brief, 20 pages each (140 pages), built
|
|
391
|
+
with `craft null build` from the briefs in `calibration/briefs.json`. Eight
|
|
392
|
+
replies were notes instead of pages and were generated again (kept as
|
|
393
|
+
`*.reply.txt`). Everything is in `calibration/null/`; `score.mjs` reproduces
|
|
394
|
+
the numbers and writes `results.json`.
|
|
395
|
+
|
|
396
|
+
**AI set.** Twenty briefs the null never saw (a dentist, a bakery, a
|
|
397
|
+
landscaper and so on), one page each, scored against all 140 null pages.
|
|
398
|
+
Flagged means typical (0.10 or above) or tell-heavy (three or more distinct
|
|
399
|
+
design tells), a rule fixed before any result was read.
|
|
400
|
+
|
|
401
|
+
| | Pages | Share |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| Flagged | 19 of 20 | 95% (target 90%) |
|
|
404
|
+
| Typical on its own | 13 of 20 | 65% |
|
|
405
|
+
| Tell-heavy on its own | 16 of 20 | 80% |
|
|
406
|
+
|
|
407
|
+
The one miss is the landscaper: dark forest green on a cream ground,
|
|
408
|
+
Cormorant Garamond over Karla, square buttons. Two tells (`cream-palette`,
|
|
409
|
+
`reflex-font-2`), one short of tell-heavy, and a score of 0.01 against the
|
|
410
|
+
pooled null because no estate brief is a garden business. Neither signal alone reaches the
|
|
411
|
+
target; together they do, which is the reason for having more than one.
|
|
412
|
+
|
|
413
|
+
**The model's look depends on the brief.** Each brief's null pages scored
|
|
414
|
+
against the other six briefs' pages are typical only 2 to 13 times in 20.
|
|
415
|
+
A barber and an electrician get different pages. So a site is scored against
|
|
416
|
+
the null built from its own brief, and pooling briefs is a weaker test.
|
|
417
|
+
|
|
418
|
+
**The estate against its own brief's null.** Score, then what it shares with
|
|
419
|
+
most of the null.
|
|
420
|
+
|
|
421
|
+
| Site | Score | Typical | Shares with the null |
|
|
422
|
+
|---|---|---|---|
|
|
423
|
+
| DD | 0.15 | yes | pill buttons (19 of 20), Fraunces headline (13 of 20) |
|
|
424
|
+
| Harrison James | 0.10 | yes, at the line | Inter body (18 of 20), yellow accent (16 of 20), white ground |
|
|
425
|
+
| MMM | 0.05 | no | white ground, radial glow (13 of 20), Cormorant Garamond (9 of 20) |
|
|
426
|
+
| HJ Beauty | 0.05 | no | white ground, Fraunces (5 of 20), marquee (5 of 20) |
|
|
427
|
+
| Chair and Blade | 0.05 | no | square buttons (18 of 20), Bebas Neue (9 of 20), dark ground |
|
|
428
|
+
| RMP | 0.05 | no | eyebrow chip (14 of 20), Manrope body |
|
|
429
|
+
| Sensphere | 0.00 | no | radial glow (10 of 20), Inter body |
|
|
430
|
+
|
|
431
|
+
Read with care: "not typical" says the site is not what Claude builds from
|
|
432
|
+
the brief today. It does not say the site was decided. Sensphere scores 0.00
|
|
433
|
+
and still carries five catalogue tells on the page, violet among them: Claude
|
|
434
|
+
no longer builds that look for this brief, but it is still a known tell. That
|
|
435
|
+
is what signal 1 is for. DD and Harrison James are the
|
|
436
|
+
two a model would most nearly have built unprompted.
|
|
437
|
+
|
|
438
|
+
**The harvest** (`calibration/null/harvest.txt`). No face outside the two
|
|
439
|
+
reflex lists is chosen by a quarter of the null pages, so the font lists still
|
|
440
|
+
cover what the model picks today: Inter 69 of 140, Fraunces 51, Poppins 36.
|
|
441
|
+
The catalogue tells the model still produces most: `reflex-font-2` 103 of
|
|
442
|
+
140, `glass-panel` 100, `reflex-font` 94, `cream-palette` 81,
|
|
443
|
+
`pill-everything` 79. Candidates nothing catches yet, for a person to read:
|
|
444
|
+
|
|
445
|
+
- A coral or terracotta accent (around `#e77a63`): 66 of 140, five briefs.
|
|
446
|
+
- Phrases across five or more briefs: "done properly" (33 pages, six briefs),
|
|
447
|
+
"exactly what" (27, all seven), "who actually" (24, five), "within one
|
|
448
|
+
working day" (32, five), "rather than" (27, six).
|
|
449
|
+
- The opening "hero > hero > cards" (54 of 140) is how the section reader
|
|
450
|
+
splits a nav band from the hero, not a model habit. Not a candidate.
|
|
451
|
+
|
|
452
|
+
`glass-panel` at 100 of 140 includes source hits on blurred sticky navs, the
|
|
453
|
+
same false positive fixed on the rendered path. The source rule needs the same
|
|
454
|
+
fix before that count means anything.
|
|
455
|
+
|
|
456
|
+
### Signal 3 first read, 2026-09-23
|
|
457
|
+
|
|
458
|
+
`craft estate add` on the seven live snapshots, then `craft estate compare
|
|
459
|
+
--null calibration/null/*` (the line measured again: 0.31). Register and output
|
|
460
|
+
in `calibration/estate/`.
|
|
461
|
+
|
|
462
|
+
| Pair | Distance | Shares |
|
|
463
|
+
|---|---|---|
|
|
464
|
+
| DD + HJ Beauty | **0.28, siblings** | no accent, Fraunces headline, no reveals |
|
|
465
|
+
| DD + RMP | 0.49 | Fraunces headline, grey ground, pill buttons |
|
|
466
|
+
| Harrison James + Sensphere | 0.50 | white ground, square buttons, no reveals |
|
|
467
|
+
| HJ Beauty + RMP | 0.56 | Fraunces headline |
|
|
468
|
+
| Chair and Blade + anything | 0.60 to 0.86 | at most a marquee (with HJ Beauty) |
|
|
469
|
+
|
|
470
|
+
One sibling pair in twenty-one, and it is the agency's own site with one of
|
|
471
|
+
its clients: the same Fraunces headline over a near-black and white page with
|
|
472
|
+
no colour of its own. Fraunces sets the headline on three of the seven. Chair
|
|
473
|
+
and Blade shares nothing with anyone but a marquee. Not yet read by a person.
|
|
474
|
+
|
|
475
|
+
### Human set, 2026-09-23
|
|
476
|
+
|
|
477
|
+
The references in claude-kit's `reference-set.md`, live home pages, scored as
|
|
478
|
+
the AI set is: typicality against all seven null models pooled, and the
|
|
479
|
+
design tells on the rendered page. The tenth reference is "any
|
|
480
|
+
generated-looking site" and is left out by definition. Snapshots and
|
|
481
|
+
`score.mjs` in `calibration/human/`.
|
|
482
|
+
|
|
483
|
+
| Site | Score | Tells on the page | Flagged |
|
|
484
|
+
|---|---|---|---|
|
|
485
|
+
| Linear | 0.00 | 7: violet, grid, icon grid, marquee, glow, Inter, hairline shadow | tell-heavy |
|
|
486
|
+
| Stripe | 0.00 | 5: violet, blue-to-purple, glass, icon grid, glow | tell-heavy |
|
|
487
|
+
| DD | 0.00 | 4: icon grid, pills, glow, Fraunces | tell-heavy |
|
|
488
|
+
| animations.dev | 0.00 | 2: marquee, Inter | no |
|
|
489
|
+
| Utopia | 0.00 | 2: hero then proof, pills | no |
|
|
490
|
+
| APCA (Myndex) | 0.00 | 0 | no |
|
|
491
|
+
| rauno.me | 0.01 | 0 | no |
|
|
492
|
+
| StarrLuxury | 0.01 | not measured: a preview gate, not the site | |
|
|
493
|
+
| candy-aesthetics | 0.05 | not measured: "This preview has ended" | |
|
|
494
|
+
|
|
495
|
+
- **Zero block hits and none typical**: both targets met. Every score is 0.05
|
|
496
|
+
or under.
|
|
497
|
+
- **Three of the seven measured are tell-heavy.** Linear and Stripe are where
|
|
498
|
+
the first-wave look came from: the indigo, the glow, the gradient were
|
|
499
|
+
theirs before they were every model's. The tell list describes them too,
|
|
500
|
+
which is the reason no single signal decides the verdict.
|
|
501
|
+
- **The comparison is not like for like.** The null models are small-business
|
|
502
|
+
briefs; these are software and portfolio sites, so a low score is easier to
|
|
503
|
+
reach. A human set of small-business sites nobody generated is the better
|
|
504
|
+
test and is still to find.
|
|
505
|
+
- Both house demos had expired or gated previews on the day. They are not
|
|
506
|
+
measured, not clean.
|
|
507
|
+
|
|
508
|
+
### Character report first read, 2026-09-23
|
|
509
|
+
|
|
510
|
+
`craft report` on each live snapshot with its repo, its own brief's null and
|
|
511
|
+
the register. Reports and retrofit plans are in `calibration/report/`.
|
|
512
|
+
|
|
513
|
+
| Site | Verdict | Raised | Actions |
|
|
514
|
+
|---|---|---|---|
|
|
515
|
+
| DD | default | tells (12), typical (0.15), sibling of HJ Beauty | 26 |
|
|
516
|
+
| HJ Beauty | default | tells (6), sibling of DD | 18 |
|
|
517
|
+
| MMM | mixed | tells (18) | 27 |
|
|
518
|
+
| Sensphere | mixed | tells (9) | 18 |
|
|
519
|
+
| Chair and Blade | mixed | tells (7) | 11 |
|
|
520
|
+
| RMP | mixed | tells (8) | 18 |
|
|
521
|
+
| Harrison James | mixed | typical (0.10) | 9 |
|
|
522
|
+
|
|
523
|
+
No site has an `art-direction.json`, so the reasons are not measured on any of
|
|
524
|
+
them and none can be *decided* yet. The first change after deciding is the
|
|
525
|
+
same on all seven: the display face. It is the estate's common default, and
|
|
526
|
+
the one choice the retrofits (phase G) should settle first. Not yet read by a
|
|
527
|
+
person.
|
|
528
|
+
|
|
529
|
+
## The catalogue
|
|
530
|
+
|
|
531
|
+
Generated from the package. Run `pnpm --filter @domandigital/craft run docs`
|
|
532
|
+
after changing an entry; a test fails until you do.
|
|
533
|
+
|
|
534
|
+
<!-- craft:catalogue:start -->
|
|
535
|
+
Catalogue version `2026.09.3`, 39 tells.
|
|
536
|
+
|
|
537
|
+
| Id | Gen | Surface | Severity | Tell | Why it is a default |
|
|
538
|
+
| --- | --- | --- | --- | --- | --- |
|
|
539
|
+
| `reflex-font` | 1 | source + rendered | warn | Reflex font | Inter, Roboto, Poppins and friends are what a model sets when nothing in the brief names a face. Every site in the estate ships one. |
|
|
540
|
+
| `reflex-font-2` | 2 | source + rendered | warn | Second-wave reflex font | Ban Inter and a model reaches for Inter Tight, DM Sans, Manrope, Space Grotesk or an Instrument/Fraunces serif. A swap inside the reflex list is not a decision. |
|
|
541
|
+
| `ai-violet` | 1 | source + rendered | warn | Indigo-violet accent | Tailwind's indigo and violet are the accent a model picks for any brand it knows nothing about. Hue is read in OKLCh, so a hex violet is caught as well as a class name. |
|
|
542
|
+
| `blue-purple-gradient` | 1 | source + rendered | warn | Blue-to-purple gradient | The first-wave hero background. It says 'software product' on a plumber's site. |
|
|
543
|
+
| `gradient-text` | 1 | source + rendered | warn | Gradient-filled heading | Gradient text is the model's way of making a heading look designed without deciding what it should look like. |
|
|
544
|
+
| `glass-panel` | 1 | source + rendered | warn | Glass panel | A translucent blurred card over a busy background is the stock way to put text on a hero image. It costs contrast and says nothing about the client. |
|
|
545
|
+
| `hero-then-proof` | 1 | source + rendered | warn | Hero straight into a trust strip | Six of seven estate sites go hero, then logos or stats. It is the landing-page template's order, not the client's story. |
|
|
546
|
+
| `icon-tile-grid` | 1 | source + rendered | warn | Three-column icon-card grid | Three cards, each an icon, a heading and two lines, is what a model builds for any list of services. |
|
|
547
|
+
| `reveal-everywhere` | 1 | source + rendered | warn | Reveal on every section | When every block fades up on scroll, the motion stops meaning anything and the page feels slow. MMM had 70, DD 40, sen-sphere 27. |
|
|
548
|
+
| `pill-everything` | 1 | source + rendered | warn | Pills everywhere | rounded-full on every button, badge and tag is the framework's friendliest default. MMM had 162, DD 125. A shape used everywhere is not a shape language. |
|
|
549
|
+
| `shadcn-dump` | 1 | source | warn | Stock component dump | A generator exports the whole shadcn/ui kit whether the site uses it or not. Rise & Bloom ships a full set under its own book, vine and petal assets. |
|
|
550
|
+
| `cream-palette` | 2 | source + rendered | warn | Cream ground | Once white-and-violet was named, models moved to a warm off-white: parchment, linen, oat. It reads as 'tasteful' in the same way everywhere. |
|
|
551
|
+
| `italic-serif-display` | 2 | source + rendered | warn | Italic serif display | A serif heading with one italic word ('Beauty, reimagined') is the second wave's signature move. |
|
|
552
|
+
| `hero-eyebrow-chip` | 2 | source + rendered | warn | Eyebrow chip above the hero | A small bordered pill ('New · Now booking') above the headline is in almost every generated hero. |
|
|
553
|
+
| `icon-tile-stack` | 2 | source | warn | Icon in a tinted tile | A library icon in a soft rounded square, stacked above a heading, is the stock card header. |
|
|
554
|
+
| `bento-grid` | 2 | source | warn | Bento grid | Mixed-size tiles in a grid became the default 'features' layout of the second wave. |
|
|
555
|
+
| `radial-spotlight-glow` | 2 | source + rendered | warn | Radial glow | A soft blurred blob or radial spotlight behind the hero is atmosphere without a subject. |
|
|
556
|
+
| `grid-background` | 2 | source + rendered | warn | Graph-paper background | A faint 1px line or dot grid behind the hero is the developer-tool look, borrowed by everyone. |
|
|
557
|
+
| `marquee` | 2 | source + rendered | warn | Scrolling marquee | An endless strip of logos or words is the second wave's trust strip. It moves so it looks alive. |
|
|
558
|
+
| `thin-border-wide-shadow` | 2 | source + rendered | warn | Hairline border, wide shadow | A near-invisible border under a large soft shadow is the stock 'elevated card'. |
|
|
559
|
+
| `intro-cinematic` | 2 | source + rendered | warn | Intro cinematic | A logo animation that runs before the page is something a model adds to make a site feel premium. Three estate sites open with one. It delays the page for every visitor. |
|
|
560
|
+
| `ai-vocabulary` | 1 | copy | warn | AI vocabulary | Tapestry, elevate, nestled, unparalleled: words that appear in generated copy far more than in anything a business owner writes. |
|
|
561
|
+
| `stock-phrase` | 1 | copy | warn | Stock phrase | Phrases every generated services page uses. They fill space where a fact should be. |
|
|
562
|
+
| `ai-phrase` | 1 | copy | warn | AI phrase | Let's dive in, here's the thing, at its core, seamless, rest assured: the phrase list of the house copy rules. A reader has seen each one in a thousand generated pages. |
|
|
563
|
+
| `plainer-word` | 1 | copy | warn | A plainer word exists | Empower, leverage, harness, delve: each stands in for a plainer verb, and the swap is a reliable sign nobody chose the word. |
|
|
564
|
+
| `plain-english` | 2 | copy | warn | 'Plain English' | Told to avoid jargon, a model announces that it is avoiding jargon. 'Explained plainly' swaps a synonym and keeps the tell. |
|
|
565
|
+
| `buzzword` | 1 | copy | warn | Buzzword | Cutting-edge, world-class, reach out, turnkey: corporate filler older than any model, and still the first thing a model writes about a business it knows nothing about. |
|
|
566
|
+
| `negative-reassurance` | 1 | copy | warn | Negative reassurance | 'No hidden fees', 'no surprises', 'never locked in' reassure by naming the fear, and plant it in a reader who did not have it. |
|
|
567
|
+
| `vague-word` | 1 | copy | warn | Vague word | Innovative, scalable, end-to-end, solutions: each can be true, and each is used where the writer had nothing specific to say. |
|
|
568
|
+
| `review-phrase` | 1 | copy | warn | Review-tier phrase | Truly, genuinely, ultimately, 'the single most', 'bar none': normal English once, and a tell when they stack. The house rules keep them for review, never blocking. |
|
|
569
|
+
| `hollow-imperative` | 1 | copy | warn | Hollow imperative | 'Discover', 'Experience', 'Transform your' as the opening verb of a heading or button is a call to action with nothing in it. |
|
|
570
|
+
| `rhetorical-opener` | 1 | copy | warn | Rhetorical question opener | 'Looking for...?', 'Tired of...?', 'Ready to...?' opens with the model guessing at the reader instead of telling them something. |
|
|
571
|
+
| `em-dash` | 2 | copy | warn | Em dash | Generated copy leans on the em dash to join clauses. House copy does not use it. |
|
|
572
|
+
| `emoji` | 1 | copy | warn | Emoji in copy | A sparkle or a rocket beside a heading is decoration a model adds to seem friendly. It dates the page and reads as a social post. |
|
|
573
|
+
| `not-just-but` | 2 | copy | warn | Contrastive negation | 'It's not just a haircut, it's an experience', 'dispatched from here, not shipped in': the contrast-and-reveal template is the most recognisable construction in generated copy. |
|
|
574
|
+
| `no-x-no-y` | 2 | copy | warn | 'No X, no Y' list | 'No obligation, no spam.' Defining the business by what it is not, itemised, is the same template as contrastive negation. |
|
|
575
|
+
| `no-x-badge` | 2 | copy | warn | 'No X' badge | 'No catch.', 'NO JARGON GUIDE': a short standalone 'No X' reads as a slapped-on kicker, and worse when the same one is reused across pieces. |
|
|
576
|
+
| `staccato-triplet` | 2 | copy | warn | Staccato triplet | Three fragments in a row ('Fast. Friendly. Local.' or 'No fuss. No jargon. Just results.') is the second wave's favourite rhythm. |
|
|
577
|
+
| `where-x-meets-y` | 2 | copy | warn | 'Where X meets Y' | 'Where luxury meets comfort' is a tagline shape that fits every business and so describes none. |
|
|
578
|
+
<!-- craft:catalogue:end -->
|
package/PRINCIPLES.md
CHANGED
|
@@ -91,3 +91,17 @@ instead: a piecewise-linear map pinned at 0 and 1 that passes through
|
|
|
91
91
|
(curve value, actual value) for the anchor. Monotonic by construction, and every
|
|
92
92
|
other step keeps its relative position. The anchors define the ramp; the curve
|
|
93
93
|
only sets the spacing between them.
|
|
94
|
+
|
|
95
|
+
## Well made is not the same as decided
|
|
96
|
+
|
|
97
|
+
A surface can clear every number in the standard and still read as generated.
|
|
98
|
+
The craft rules measure quality; they cannot see that the accent, the face and
|
|
99
|
+
the section order are the ones a model picks for every brief it knows nothing
|
|
100
|
+
about. That is a different question, so it is a different document:
|
|
101
|
+
[CHARACTER.md](./CHARACTER.md).
|
|
102
|
+
|
|
103
|
+
Two things keep it from becoming a taste police. It only ever changes the look,
|
|
104
|
+
never the page grammar, because a page that looks typical for its category is
|
|
105
|
+
the one visitors trust first. And it never asks for novelty. It asks for a
|
|
106
|
+
reason: every expressive choice records where it came from in the client's own
|
|
107
|
+
world, and a choice with no reason is the one flagged.
|
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ The Doman Digital house craft standard, as numbers.
|
|
|
4
4
|
|
|
5
5
|
OKLCh colour ramps that snap to colours you already ship, semantic tokens derived
|
|
6
6
|
with measured WCAG **and** APCA contrast, one motion vocabulary shared by CSS and
|
|
7
|
-
JS,
|
|
7
|
+
JS, a base stylesheet, and a scanner that finds the AI look in source and copy.
|
|
8
|
+
Zero runtime dependencies.
|
|
8
9
|
|
|
9
10
|
Read [PRINCIPLES.md](./PRINCIPLES.md) for why each rule is a rule.
|
|
10
11
|
|
|
@@ -71,6 +72,37 @@ rule cited, including a section on what is deliberately *not* a rule. Its
|
|
|
71
72
|
canonical numbers are compared against the code on every test run, so the
|
|
72
73
|
document cannot drift from what ships.
|
|
73
74
|
|
|
75
|
+
## Character: finding the AI look
|
|
76
|
+
|
|
77
|
+
The standard asks whether a surface is well made. [CHARACTER.md](./CHARACTER.md)
|
|
78
|
+
asks whether anyone decided how it looks, or whether it is what a model builds
|
|
79
|
+
when nobody told it otherwise.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx craft scan # markup, components, stylesheets
|
|
83
|
+
npx craft scan --staged # pre-commit: the git index only
|
|
84
|
+
npx craft copy app content
|
|
85
|
+
npx craft tells list
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every finding names the tell, its generation (1: indigo, Inter, glass; 2: cream,
|
|
89
|
+
italic serif, eyebrow chips, bento), the line, and what to do instead. Every
|
|
90
|
+
tell ships as a warning. A brand that really is violet declares it, with a
|
|
91
|
+
reason, in `art-direction.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{ "exceptions": [{ "tell": "ai-violet", "because": "Violet is on the van, the cards and the fascia." }] }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The same checks run in code:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { checkCopy, formatReport, scanSource } from "@domandigital/craft";
|
|
101
|
+
|
|
102
|
+
const report = scanSource([{ path: "app/page.tsx", text }]);
|
|
103
|
+
console.log(formatReport(report, "craft scan"));
|
|
104
|
+
```
|
|
105
|
+
|
|
74
106
|
## The anchor guarantee
|
|
75
107
|
|
|
76
108
|
Every hex you pass in comes back out unchanged — the same string, byte for byte.
|
|
@@ -99,6 +131,9 @@ checkable rather than asserted.
|
|
|
99
131
|
| Restraint | `checkRestraint`, `HOUSE_BUDGET` |
|
|
100
132
|
| Tailwind | `tailwindV3Preset` (v3), `tailwindV4Theme` / `craft.tailwind.css` (v4) |
|
|
101
133
|
| Emit | `craftTokens`, `emitCss`, `motionTokens` |
|
|
134
|
+
| Character | `scanSource`, `checkCopy`, `formatReport`, `CATALOGUE`, `CATALOGUE_VERSION`, `catalogueTable`, `runTell`, `tellById` |
|
|
135
|
+
| Character colour | `parseColour`, `findColours`, `isAiViolet`, `isCream`, `AI_VIOLET` |
|
|
136
|
+
| Character lists | `REFLEX_FONTS_1`, `REFLEX_FONTS_2`, `SHADCN_PRIMITIVES`, `AI_WORDS`, `STOCK_PHRASES`, `REVEAL_LIMIT`, `PILL_LIMIT`, `SHADCN_LIMIT` |
|
|
102
137
|
|
|
103
138
|
## Licence
|
|
104
139
|
|