@domandigital/craft 0.6.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 +25 -0
- package/CHARACTER.md +254 -11
- package/dist/audit-YNODYB3U.js +20 -0
- package/dist/audit.cjs +45 -17
- package/dist/audit.d.cts +14 -1
- package/dist/audit.d.ts +14 -1
- package/dist/audit.js +43 -16
- package/dist/{chunk-GWMSSRIQ.js → chunk-3AP4PJMX.js} +480 -670
- 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/{cli-7ME4VIRG.js → chunk-OA7SGPN4.js} +50 -101
- package/dist/{chunk-HYGFP3LB.js → chunk-Q54JSNC5.js} +40 -3
- package/dist/chunk-YYC4EGER.js +249 -0
- package/dist/{cli-F7W4F3ER.js → cli-6Y5E32FS.js} +14 -168
- 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 +2 -1
- package/dist/index.cjs +572 -12
- package/dist/index.d.cts +286 -1
- package/dist/index.d.ts +286 -1
- package/dist/index.js +544 -9
- package/package.json +3 -2
- package/word-lists.json +155 -0
package/dist/index.cjs
CHANGED
|
@@ -3,9 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (
|
|
6
|
+
var __export = (target2, all) => {
|
|
7
7
|
for (var name in all)
|
|
8
|
-
__defProp(
|
|
8
|
+
__defProp(target2, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -35,12 +35,16 @@ __export(index_exports, {
|
|
|
35
35
|
DURATION_S: () => DURATION_S,
|
|
36
36
|
EASE: () => EASE,
|
|
37
37
|
EASE_TUPLE: () => EASE_TUPLE,
|
|
38
|
+
ESTATE_VERSION: () => ESTATE_VERSION,
|
|
38
39
|
EXIT_RATIO: () => EXIT_RATIO,
|
|
39
40
|
FINGERPRINT_VERSION: () => FINGERPRINT_VERSION,
|
|
40
41
|
HOUSE_BUDGET: () => HOUSE_BUDGET,
|
|
41
42
|
HOUSE_TYPE: () => HOUSE_TYPE,
|
|
42
43
|
LIGHTNESS_CURVE: () => LIGHTNESS_CURVE,
|
|
44
|
+
MIN_DECIDED: () => MIN_DECIDED,
|
|
45
|
+
MIN_RUNS: () => MIN_RUNS,
|
|
43
46
|
NEGATIVE_REASSURANCE: () => NEGATIVE_REASSURANCE,
|
|
47
|
+
NULL_VERSION: () => NULL_VERSION,
|
|
44
48
|
PILL_LIMIT: () => PILL_LIMIT,
|
|
45
49
|
PLAINER_WORDS: () => PLAINER_WORDS,
|
|
46
50
|
PROPOSED: () => PROPOSED,
|
|
@@ -53,27 +57,37 @@ __export(index_exports, {
|
|
|
53
57
|
SCALE: () => SCALE,
|
|
54
58
|
SHADCN_LIMIT: () => SHADCN_LIMIT,
|
|
55
59
|
SHADCN_PRIMITIVES: () => SHADCN_PRIMITIVES,
|
|
60
|
+
SIBLING_AT: () => SIBLING_AT,
|
|
56
61
|
SNAPSHOT_VERSION: () => SNAPSHOT_VERSION,
|
|
57
62
|
SOURCE_KINDS: () => SOURCE_KINDS,
|
|
58
63
|
SPACE_STEPS: () => SPACE_STEPS,
|
|
59
64
|
SPRING: () => SPRING,
|
|
60
65
|
STOCK_PHRASES: () => STOCK_PHRASES,
|
|
66
|
+
TELL_HEAVY: () => TELL_HEAVY,
|
|
61
67
|
TYPE_STEPS: () => TYPE_STEPS,
|
|
68
|
+
TYPICAL_AT: () => TYPICAL_AT,
|
|
62
69
|
VAGUE_WORDS: () => VAGUE_WORDS,
|
|
63
70
|
accentFork: () => accentFork,
|
|
71
|
+
addSite: () => addSite,
|
|
64
72
|
apcaContrast: () => apcaContrast,
|
|
65
73
|
auditSnapshot: () => auditSnapshot,
|
|
66
74
|
catalogueTable: () => catalogueTable,
|
|
75
|
+
characterReport: () => characterReport,
|
|
67
76
|
checkCopy: () => checkCopy,
|
|
68
77
|
checkPair: () => checkPair,
|
|
69
78
|
checkRestraint: () => checkRestraint,
|
|
79
|
+
choicesOf: () => choicesOf,
|
|
80
|
+
compareToEstate: () => compareToEstate,
|
|
70
81
|
craftTokens: () => craftTokens,
|
|
71
82
|
deltaEOk: () => deltaEOk,
|
|
72
83
|
deltaEOkHex: () => deltaEOkHex,
|
|
73
84
|
densityCss: () => densityCss,
|
|
74
85
|
densityTokens: () => densityTokens,
|
|
75
86
|
emitCss: () => emitCss,
|
|
87
|
+
emptyEstate: () => emptyEstate,
|
|
88
|
+
estatePairs: () => estatePairs,
|
|
76
89
|
exitDuration: () => exitDuration,
|
|
90
|
+
extractHtml: () => extractHtml,
|
|
77
91
|
findColours: () => findColours,
|
|
78
92
|
fingerprint: () => fingerprint,
|
|
79
93
|
fingerprintDistance: () => fingerprintDistance,
|
|
@@ -83,7 +97,11 @@ __export(index_exports, {
|
|
|
83
97
|
fontClass: () => fontClass,
|
|
84
98
|
formatHex: () => formatHex,
|
|
85
99
|
formatReport: () => formatReport,
|
|
100
|
+
groundName: () => groundName,
|
|
101
|
+
harvest: () => harvest,
|
|
86
102
|
hexToOklch: () => hexToOklch,
|
|
103
|
+
hueName: () => hueName,
|
|
104
|
+
hueSwatch: () => hueSwatch,
|
|
87
105
|
inP3Gamut: () => inP3Gamut,
|
|
88
106
|
inSrgbGamut: () => inSrgbGamut,
|
|
89
107
|
initDirection: () => initDirection,
|
|
@@ -93,6 +111,7 @@ __export(index_exports, {
|
|
|
93
111
|
motionTokens: () => motionTokens,
|
|
94
112
|
nearest: () => nearest,
|
|
95
113
|
normaliseFamily: () => normaliseFamily,
|
|
114
|
+
nullPrompt: () => nullPrompt,
|
|
96
115
|
oklchToHex: () => oklchToHex,
|
|
97
116
|
oklchToRgb: () => oklchToRgb,
|
|
98
117
|
paletteFromPixels: () => paletteFromPixels,
|
|
@@ -101,18 +120,24 @@ __export(index_exports, {
|
|
|
101
120
|
propose: () => propose,
|
|
102
121
|
ramp: () => ramp,
|
|
103
122
|
rampFromAnchors: () => rampFromAnchors,
|
|
123
|
+
retrofitPlan: () => retrofitPlan,
|
|
104
124
|
runTell: () => runTell,
|
|
105
125
|
scanSource: () => scanSource,
|
|
106
126
|
sectionRhythm: () => sectionRhythm,
|
|
107
127
|
semantic: () => semantic,
|
|
128
|
+
shapeName: () => shapeName,
|
|
129
|
+
sharedParts: () => sharedParts,
|
|
108
130
|
shouldAnimate: () => shouldAnimate,
|
|
131
|
+
siblingLine: () => siblingLine,
|
|
109
132
|
tailwindV3Preset: () => tailwindV3Preset,
|
|
110
133
|
tailwindV4Theme: () => tailwindV4Theme,
|
|
111
134
|
tellById: () => tellById,
|
|
112
135
|
toGamut: () => toGamut,
|
|
113
136
|
typeFeatureTokens: () => typeFeatureTokens,
|
|
137
|
+
typicality: () => typicality,
|
|
114
138
|
validateDirection: () => validateDirection,
|
|
115
|
-
wcagContrast: () => wcagContrast
|
|
139
|
+
wcagContrast: () => wcagContrast,
|
|
140
|
+
wordListsJson: () => wordListsJson
|
|
116
141
|
});
|
|
117
142
|
module.exports = __toCommonJS(index_exports);
|
|
118
143
|
|
|
@@ -945,9 +970,9 @@ function resolveValue(value, props, depth = 0) {
|
|
|
945
970
|
if (depth > 8) return null;
|
|
946
971
|
const match = SINGLE_VAR.exec(value.trim());
|
|
947
972
|
if (match === null) return value.trim();
|
|
948
|
-
const
|
|
949
|
-
if (
|
|
950
|
-
return resolveValue(
|
|
973
|
+
const target2 = props.get(match[1].toLowerCase());
|
|
974
|
+
if (target2 !== void 0 && target2 !== "initial")
|
|
975
|
+
return resolveValue(target2, props, depth + 1);
|
|
951
976
|
const fallback = match[2]?.trim();
|
|
952
977
|
return fallback === void 0 || fallback === "" ? null : resolveValue(fallback, props, depth + 1);
|
|
953
978
|
}
|
|
@@ -2502,7 +2527,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2502
2527
|
severity: "warn",
|
|
2503
2528
|
surface: "source",
|
|
2504
2529
|
why: "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.",
|
|
2505
|
-
fix: "
|
|
2530
|
+
fix: "Pick the display face from the client's world (signage, packaging, the trade's lettering) and record why in art-direction.json. If this face really is the right one, declare an exception with the reason and the evidence.",
|
|
2506
2531
|
detect: fontTell(REFLEX_FONTS_2),
|
|
2507
2532
|
fixtures: {
|
|
2508
2533
|
flag: [f2("src/styles.css", `@theme {
|
|
@@ -3250,15 +3275,16 @@ function makeSnapshot(patch = {}) {
|
|
|
3250
3275
|
}
|
|
3251
3276
|
|
|
3252
3277
|
// src/snapshot/fonts.ts
|
|
3278
|
+
var SYSTEM = /^(?:system-ui|-apple-system|BlinkMacSystemFont|ui-sans-serif|ui-serif|ui-monospace|ui-rounded|sans-serif|serif|monospace|cursive)$/i;
|
|
3253
3279
|
function normaliseFamily(raw) {
|
|
3254
3280
|
let name = raw.replace(/["']/g, "").trim();
|
|
3255
3281
|
const next = name.match(/^__(.+?)(?:_Fallback)?_[0-9a-f]{5,8}$/i);
|
|
3256
3282
|
if (next) name = next[1].replace(/_/g, " ");
|
|
3257
3283
|
name = name.replace(/\s+Fallback$/i, "").replace(/\s+Variable$/i, "");
|
|
3284
|
+
if (SYSTEM.test(name)) return "system-ui";
|
|
3258
3285
|
if (!/\s/.test(name) && (/[a-z][A-Z]/.test(name) || /^[a-z]+(?:-[a-z]+)+$/.test(name))) {
|
|
3259
3286
|
name = name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").replace(/\b([a-z])/g, (c) => c.toUpperCase()).replace(/\bDm\b/, "DM");
|
|
3260
3287
|
}
|
|
3261
|
-
if (/^(?:system-ui|-apple-system|BlinkMacSystemFont|ui-sans-serif|ui-serif|ui-monospace|sans-serif|serif|monospace)$/i.test(name)) return "system-ui";
|
|
3262
3288
|
return name;
|
|
3263
3289
|
}
|
|
3264
3290
|
var KNOWN = {
|
|
@@ -3635,6 +3661,25 @@ function catalogueTable() {
|
|
|
3635
3661
|
...rows
|
|
3636
3662
|
].join("\n");
|
|
3637
3663
|
}
|
|
3664
|
+
function wordListsJson() {
|
|
3665
|
+
return JSON.stringify(
|
|
3666
|
+
{
|
|
3667
|
+
catalogueVersion: CATALOGUE_VERSION,
|
|
3668
|
+
lists: {
|
|
3669
|
+
aiWords: AI_WORDS,
|
|
3670
|
+
stockPhrases: STOCK_PHRASES,
|
|
3671
|
+
aiPhrases: AI_PHRASES,
|
|
3672
|
+
plainerWords: PLAINER_WORDS,
|
|
3673
|
+
buzzwords: BUZZWORDS,
|
|
3674
|
+
negativeReassurance: NEGATIVE_REASSURANCE,
|
|
3675
|
+
vagueWords: VAGUE_WORDS,
|
|
3676
|
+
reviewPhrases: REVIEW_PHRASES
|
|
3677
|
+
}
|
|
3678
|
+
},
|
|
3679
|
+
null,
|
|
3680
|
+
2
|
|
3681
|
+
) + "\n";
|
|
3682
|
+
}
|
|
3638
3683
|
|
|
3639
3684
|
// src/character/format.ts
|
|
3640
3685
|
function formatReport(report2, title) {
|
|
@@ -3674,6 +3719,7 @@ function formatReport(report2, title) {
|
|
|
3674
3719
|
// src/fingerprint/index.ts
|
|
3675
3720
|
var FINGERPRINT_VERSION = 1;
|
|
3676
3721
|
var CHROMATIC = 0.03;
|
|
3722
|
+
var MIN_ACCENT_AREA = 2e4;
|
|
3677
3723
|
var oklch = (value) => {
|
|
3678
3724
|
const parsed = parseColour(value);
|
|
3679
3725
|
return parsed && parsed.alpha > 0.5 ? parsed.oklch : null;
|
|
@@ -3686,7 +3732,7 @@ function accentOf(s) {
|
|
|
3686
3732
|
}
|
|
3687
3733
|
const topButton = [...buttons].sort((a, b) => b[1] - a[1])[0];
|
|
3688
3734
|
if (topButton) return oklch(topButton[0]);
|
|
3689
|
-
const bg = s.colours.backgrounds.map((b) => ({ o: oklch(b.value), w: b.area })).filter((x) => x.o && x.o.c >= CHROMATIC).sort((a, b) => b.w - a.w)[0];
|
|
3735
|
+
const bg = s.colours.backgrounds.map((b) => ({ o: oklch(b.value), w: b.area })).filter((x) => x.o && x.o.c >= CHROMATIC && x.w >= MIN_ACCENT_AREA).sort((a, b) => b.w - a.w)[0];
|
|
3690
3736
|
if (bg) return bg.o;
|
|
3691
3737
|
const text = s.colours.text.map((t) => ({ o: oklch(t.value), w: t.chars })).filter((x) => x.o && x.o.c >= CHROMATIC).sort((a, b) => b.w - a.w)[0];
|
|
3692
3738
|
return text ? text.o : null;
|
|
@@ -3763,8 +3809,8 @@ function fingerprintDistance(a, b) {
|
|
|
3763
3809
|
const total = Object.keys(DISTANCE_WEIGHTS).reduce((sum, k) => sum + DISTANCE_WEIGHTS[k] * parts2[k], 0);
|
|
3764
3810
|
return { total: Math.round(total * 1e3) / 1e3, parts: parts2 };
|
|
3765
3811
|
}
|
|
3766
|
-
function nearest(
|
|
3767
|
-
return set.map((item) => ({ id: item.id, distance: fingerprintDistance(
|
|
3812
|
+
function nearest(target2, set, k = set.length) {
|
|
3813
|
+
return set.map((item) => ({ id: item.id, distance: fingerprintDistance(target2, item.fingerprint) })).sort((x, y) => x.distance.total - y.distance.total).slice(0, k);
|
|
3768
3814
|
}
|
|
3769
3815
|
|
|
3770
3816
|
// src/direction/types.ts
|
|
@@ -4071,6 +4117,495 @@ function propose(input) {
|
|
|
4071
4117
|
direction: { version: DIRECTION_VERSION, client: input.client, brief: input.brief, sources: input.sources, choices }
|
|
4072
4118
|
};
|
|
4073
4119
|
}
|
|
4120
|
+
|
|
4121
|
+
// src/null/index.ts
|
|
4122
|
+
var NULL_VERSION = 1;
|
|
4123
|
+
function nullPrompt(brief) {
|
|
4124
|
+
return [
|
|
4125
|
+
`Build the home page for this business: ${brief.trim()}`,
|
|
4126
|
+
"",
|
|
4127
|
+
"Write it as one complete, self-contained HTML file. You may load fonts and libraries from a CDN. Use real copy, not placeholder text. Reply with the HTML only."
|
|
4128
|
+
].join("\n");
|
|
4129
|
+
}
|
|
4130
|
+
function extractHtml(reply) {
|
|
4131
|
+
const start = reply.search(/<!doctype html|<html[\s>]/i);
|
|
4132
|
+
if (start < 0) return null;
|
|
4133
|
+
const end = reply.toLowerCase().lastIndexOf("</html>");
|
|
4134
|
+
const html = end > start ? reply.slice(start, end + "</html>".length) : reply.slice(start).replace(/\s*```\s*$/, "");
|
|
4135
|
+
return html.length > 200 ? html : null;
|
|
4136
|
+
}
|
|
4137
|
+
var MIN_RUNS = 5;
|
|
4138
|
+
var TYPICAL_AT = 0.1;
|
|
4139
|
+
function meanNearest(distances, k) {
|
|
4140
|
+
const sorted = [...distances].sort((a, b) => a - b).slice(0, k);
|
|
4141
|
+
return sorted.reduce((s, d) => s + d, 0) / sorted.length;
|
|
4142
|
+
}
|
|
4143
|
+
function median2(values) {
|
|
4144
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
4145
|
+
const mid = Math.floor(sorted.length / 2);
|
|
4146
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
4147
|
+
}
|
|
4148
|
+
function typicality(target2, runs, k = 3) {
|
|
4149
|
+
if (runs.length < MIN_RUNS) throw new Error(`a null model needs at least ${MIN_RUNS} runs; this one has ${runs.length}`);
|
|
4150
|
+
const kk = Math.min(k, runs.length - 1);
|
|
4151
|
+
const toTarget = runs.map((r) => ({ id: r.id, distance: fingerprintDistance(target2, r.fingerprint).total }));
|
|
4152
|
+
const distance = meanNearest(
|
|
4153
|
+
toTarget.map((t) => t.distance),
|
|
4154
|
+
kk
|
|
4155
|
+
);
|
|
4156
|
+
const loo = runs.map(
|
|
4157
|
+
(r, i) => meanNearest(
|
|
4158
|
+
runs.filter((_, j) => j !== i).map((o) => fingerprintDistance(r.fingerprint, o.fingerprint).total),
|
|
4159
|
+
kk
|
|
4160
|
+
)
|
|
4161
|
+
);
|
|
4162
|
+
const score = loo.filter((d) => d >= distance).length / loo.length;
|
|
4163
|
+
return {
|
|
4164
|
+
score: Math.round(score * 100) / 100,
|
|
4165
|
+
typical: score >= TYPICAL_AT,
|
|
4166
|
+
distance: Math.round(distance * 1e3) / 1e3,
|
|
4167
|
+
baseline: Math.round(median2(loo) * 1e3) / 1e3,
|
|
4168
|
+
nearest: toTarget.sort((a, b) => a.distance - b.distance).slice(0, kk),
|
|
4169
|
+
shared: sharedChoices(target2, runs)
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
var HUES = [
|
|
4173
|
+
["red", 0, 45],
|
|
4174
|
+
["orange", 45, 75],
|
|
4175
|
+
["yellow", 75, 110],
|
|
4176
|
+
["green", 110, 165],
|
|
4177
|
+
["teal", 165, 215],
|
|
4178
|
+
["blue", 215, 265],
|
|
4179
|
+
["violet", 265, 315],
|
|
4180
|
+
["pink", 315, 360]
|
|
4181
|
+
];
|
|
4182
|
+
function hueName(o) {
|
|
4183
|
+
if (!o) return "none";
|
|
4184
|
+
if (o.c < 0.03) return "neutral";
|
|
4185
|
+
return HUES.find(([, lo, hi]) => o.h >= lo && o.h < hi)?.[0] ?? "red";
|
|
4186
|
+
}
|
|
4187
|
+
function groundName(o) {
|
|
4188
|
+
if (o.l < 0.3) return "dark";
|
|
4189
|
+
if (isCream(o)) return "cream";
|
|
4190
|
+
if (o.l >= 0.97 && o.c < 0.012) return "white";
|
|
4191
|
+
return o.c < 0.02 ? "grey" : "tinted";
|
|
4192
|
+
}
|
|
4193
|
+
function shapeName(roundness) {
|
|
4194
|
+
if (roundness === null) return "no buttons";
|
|
4195
|
+
if (roundness >= 0.45) return "pill";
|
|
4196
|
+
return roundness >= 0.15 ? "soft" : "square";
|
|
4197
|
+
}
|
|
4198
|
+
function choicesOf(fp) {
|
|
4199
|
+
return {
|
|
4200
|
+
display: [fp.display.family],
|
|
4201
|
+
body: [fp.body.family],
|
|
4202
|
+
accent: [hueName(fp.accent)],
|
|
4203
|
+
ground: [groundName(fp.ground)],
|
|
4204
|
+
shape: [shapeName(fp.roundness)],
|
|
4205
|
+
motion: [fp.motion >= 0.6 ? "reveals most sections" : fp.motion > 0 ? "reveals some sections" : "no scroll reveals"],
|
|
4206
|
+
effects: fp.effects,
|
|
4207
|
+
opening: [fp.layout.slice(0, 3).join(" > ") || "none"]
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
function sharedChoices(target2, runs) {
|
|
4211
|
+
const mine = choicesOf(target2);
|
|
4212
|
+
const all = runs.map((r) => choicesOf(r.fingerprint));
|
|
4213
|
+
const out = [];
|
|
4214
|
+
for (const [part, values] of Object.entries(mine)) {
|
|
4215
|
+
for (const value of values) {
|
|
4216
|
+
const count2 = all.filter((c) => c[part].includes(value)).length;
|
|
4217
|
+
if (count2 / runs.length >= 0.25) out.push({ part, value, runs: count2, of: runs.length });
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
return out.sort((a, b) => b.runs - a.runs);
|
|
4221
|
+
}
|
|
4222
|
+
var REFLEX_1 = new Set(REFLEX_FONTS_1.map((f3) => f3.toLowerCase()));
|
|
4223
|
+
var REFLEX_2 = new Set(REFLEX_FONTS_2.map((f3) => f3.toLowerCase()));
|
|
4224
|
+
function knownFor(kind, value, sample) {
|
|
4225
|
+
if (kind === "face") return REFLEX_1.has(value.toLowerCase()) ? "reflex-font" : REFLEX_2.has(value.toLowerCase()) ? "reflex-font-2" : null;
|
|
4226
|
+
if (kind === "accent" && value === "violet") return sample.some((f3) => f3.accent && isAiViolet(f3.accent)) ? "ai-violet" : null;
|
|
4227
|
+
if (kind === "ground" && value === "cream") return "cream-palette";
|
|
4228
|
+
if (kind === "shape" && value === "pill") return "pill-everything";
|
|
4229
|
+
if (kind === "motion" && value === "reveals most sections") return "reveal-everywhere";
|
|
4230
|
+
if (kind === "tell") return value;
|
|
4231
|
+
if (kind === "opening" && /^hero > (?:logos|stats)/.test(value)) return "hero-then-proof";
|
|
4232
|
+
return null;
|
|
4233
|
+
}
|
|
4234
|
+
var STOP2 = new Set(
|
|
4235
|
+
"a an the and or but of to in on for with at by from as is are be we our you your us it its this that these those will can all more get has have not no any".split(" ")
|
|
4236
|
+
);
|
|
4237
|
+
function grams(text) {
|
|
4238
|
+
const out = /* @__PURE__ */ new Set();
|
|
4239
|
+
for (const clause of text.toLowerCase().replace(/[’']/g, "'").split(/[.!?;:|()\u2013\u2014\u00b7•]+|,\s/)) {
|
|
4240
|
+
const words2 = clause.match(/[a-z][a-z'-]*/g) ?? [];
|
|
4241
|
+
for (let n = 2; n <= 4; n += 1) {
|
|
4242
|
+
for (let i = 0; i + n <= words2.length; i += 1) {
|
|
4243
|
+
const g = words2.slice(i, i + n);
|
|
4244
|
+
if (STOP2.has(g[0]) || STOP2.has(g[n - 1])) continue;
|
|
4245
|
+
if (g.filter((w) => !STOP2.has(w)).length < 2) continue;
|
|
4246
|
+
out.add(g.join(" "));
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
return out;
|
|
4251
|
+
}
|
|
4252
|
+
var GRAMMAR = /* @__PURE__ */ new Set([
|
|
4253
|
+
"privacy policy",
|
|
4254
|
+
"cookie policy",
|
|
4255
|
+
"rights reserved",
|
|
4256
|
+
"all rights reserved",
|
|
4257
|
+
"terms of service",
|
|
4258
|
+
"terms and conditions",
|
|
4259
|
+
"terms conditions",
|
|
4260
|
+
"how it works",
|
|
4261
|
+
"what we do",
|
|
4262
|
+
"about us",
|
|
4263
|
+
"contact us",
|
|
4264
|
+
"get in touch",
|
|
4265
|
+
"opening hours",
|
|
4266
|
+
"read more",
|
|
4267
|
+
"learn more",
|
|
4268
|
+
"book now",
|
|
4269
|
+
"free quote",
|
|
4270
|
+
"our services",
|
|
4271
|
+
"quick links",
|
|
4272
|
+
"email address",
|
|
4273
|
+
"phone number",
|
|
4274
|
+
"full name",
|
|
4275
|
+
"first name",
|
|
4276
|
+
"last name",
|
|
4277
|
+
"your name",
|
|
4278
|
+
"your email",
|
|
4279
|
+
"send message"
|
|
4280
|
+
]);
|
|
4281
|
+
var LEGAL = /©|©|\bregistered (?:in|office|number)\b|\bcompany (?:no|number|reg)|\bvat (?:no|number|reg)/i;
|
|
4282
|
+
var NOISE = /\S+@\S+|https?:\/\/\S+|\b[\w-]+(?:\.[\w-]+)*\.(?:co\.uk|com|org|net|uk|io)\b|\b\d{1,2}(?:[:.]\d{2})?\s*(?:am|pm)\b|\d+/gi;
|
|
4283
|
+
function textGrams(text) {
|
|
4284
|
+
if (LEGAL.test(text)) return /* @__PURE__ */ new Set();
|
|
4285
|
+
const out = grams(text.replace(NOISE, " | "));
|
|
4286
|
+
for (const g of out) if (GRAMMAR.has(g)) out.delete(g);
|
|
4287
|
+
return out;
|
|
4288
|
+
}
|
|
4289
|
+
function properNouns(brief) {
|
|
4290
|
+
return new Set((brief.match(/\b[A-Z][a-z]+/g) ?? []).map((w) => w.toLowerCase()).filter((w) => !STOP2.has(w)));
|
|
4291
|
+
}
|
|
4292
|
+
function harvest(models, options = {}) {
|
|
4293
|
+
const minShare = options.minShare ?? 0.25;
|
|
4294
|
+
const runs = models.flatMap((m, b) => m.runs.map((r) => ({ ...r, brief: b })));
|
|
4295
|
+
const of = runs.length;
|
|
4296
|
+
if (of === 0) return [];
|
|
4297
|
+
const multiBrief = models.length > 1;
|
|
4298
|
+
const sample = runs.map((r) => r.fingerprint);
|
|
4299
|
+
const tally = /* @__PURE__ */ new Map();
|
|
4300
|
+
const add = (kind, value, run, brief, example) => {
|
|
4301
|
+
const key = `${kind}\0${value}`;
|
|
4302
|
+
const t = tally.get(key) ?? { kind, value, runs: /* @__PURE__ */ new Set(), briefs: /* @__PURE__ */ new Set(), example };
|
|
4303
|
+
t.runs.add(run);
|
|
4304
|
+
t.briefs.add(brief);
|
|
4305
|
+
tally.set(key, t);
|
|
4306
|
+
};
|
|
4307
|
+
runs.forEach((r, i) => {
|
|
4308
|
+
const c = choicesOf(r.fingerprint);
|
|
4309
|
+
for (const f3 of /* @__PURE__ */ new Set([...c.display, ...c.body])) add("face", f3, i, r.brief);
|
|
4310
|
+
add("accent", c.accent[0], i, r.brief);
|
|
4311
|
+
add("ground", c.ground[0], i, r.brief);
|
|
4312
|
+
add("shape", c.shape[0], i, r.brief);
|
|
4313
|
+
add("motion", c.motion[0], i, r.brief);
|
|
4314
|
+
add("opening", c.opening[0], i, r.brief);
|
|
4315
|
+
for (const t of r.tells) add("tell", t, i, r.brief);
|
|
4316
|
+
});
|
|
4317
|
+
const out = [];
|
|
4318
|
+
for (const t of tally.values()) {
|
|
4319
|
+
if (t.runs.size / of < minShare) continue;
|
|
4320
|
+
if (t.kind === "accent" && (t.value === "none" || t.value === "neutral")) continue;
|
|
4321
|
+
if (t.kind === "shape" && t.value === "no buttons") continue;
|
|
4322
|
+
if (t.kind === "ground" && (t.value === "white" || t.value === "grey")) continue;
|
|
4323
|
+
if (t.kind === "motion" && t.value === "no scroll reveals") continue;
|
|
4324
|
+
out.push({ kind: t.kind, value: t.value, runs: t.runs.size, of, briefs: t.briefs.size, known: knownFor(t.kind, t.value, sample) });
|
|
4325
|
+
}
|
|
4326
|
+
const phraseMin = Math.max(3, Math.ceil(of * minShare * 0.4));
|
|
4327
|
+
const names = models.map((m) => properNouns(m.brief));
|
|
4328
|
+
const phrases2 = /* @__PURE__ */ new Map();
|
|
4329
|
+
runs.forEach((r, i) => {
|
|
4330
|
+
for (const text of r.copy) {
|
|
4331
|
+
for (const g of textGrams(text)) {
|
|
4332
|
+
if (g.split(" ").some((w) => names[r.brief].has(w))) continue;
|
|
4333
|
+
const p = phrases2.get(g) ?? { runs: /* @__PURE__ */ new Set(), briefs: /* @__PURE__ */ new Set(), example: text.trim().slice(0, 140) };
|
|
4334
|
+
p.runs.add(i);
|
|
4335
|
+
p.briefs.add(r.brief);
|
|
4336
|
+
phrases2.set(g, p);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
});
|
|
4340
|
+
const kept = [...phrases2].filter(([, p]) => p.runs.size >= phraseMin && (!multiBrief || p.briefs.size >= 2)).sort((a, b) => b[1].runs.size - a[1].runs.size || b[0].length - a[0].length);
|
|
4341
|
+
const maximal = kept.filter(([g, p]) => !kept.some(([h, q]) => h !== g && h.includes(g) && q.runs.size >= 0.8 * p.runs.size));
|
|
4342
|
+
for (const [g, p] of maximal.slice(0, 40)) {
|
|
4343
|
+
out.push({ kind: "phrase", value: g, runs: p.runs.size, of, briefs: p.briefs.size, known: options.copyTell?.(g) ?? null, example: p.example });
|
|
4344
|
+
}
|
|
4345
|
+
const order = ["face", "accent", "ground", "shape", "motion", "opening", "tell", "phrase"];
|
|
4346
|
+
return out.sort((a, b) => order.indexOf(a.kind) - order.indexOf(b.kind) || b.runs - a.runs);
|
|
4347
|
+
}
|
|
4348
|
+
function hueSwatch(runs, hue) {
|
|
4349
|
+
const hits = runs.map((r) => r.fingerprint.accent).filter((o) => !!o && hueName(o) === hue);
|
|
4350
|
+
if (hits.length === 0) return null;
|
|
4351
|
+
const centre = hits.reduce((best, o) => hits.reduce((s, p) => s + deltaEOk(o, p), 0) < hits.reduce((s, p) => s + deltaEOk(best, p), 0) ? o : best);
|
|
4352
|
+
return formatHex(oklchToRgb(centre));
|
|
4353
|
+
}
|
|
4354
|
+
|
|
4355
|
+
// src/estate/index.ts
|
|
4356
|
+
var ESTATE_VERSION = 1;
|
|
4357
|
+
var SIBLING_AT = 0.31;
|
|
4358
|
+
function emptyEstate() {
|
|
4359
|
+
return { version: ESTATE_VERSION, sites: [] };
|
|
4360
|
+
}
|
|
4361
|
+
function addSite(register, site) {
|
|
4362
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
4363
|
+
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
4364
|
+
return { version: ESTATE_VERSION, sites };
|
|
4365
|
+
}
|
|
4366
|
+
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
4367
|
+
const out = [];
|
|
4368
|
+
const close = (k) => d.parts[k] <= 0.2;
|
|
4369
|
+
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
4370
|
+
if (close("type")) {
|
|
4371
|
+
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
4372
|
+
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
4373
|
+
}
|
|
4374
|
+
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
4375
|
+
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
4376
|
+
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
4377
|
+
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
4378
|
+
if (close("layout")) out.push("running order");
|
|
4379
|
+
return out;
|
|
4380
|
+
}
|
|
4381
|
+
function compareToEstate(target2, register, options = {}) {
|
|
4382
|
+
const at = options.siblingAt ?? SIBLING_AT;
|
|
4383
|
+
return register.sites.filter((s) => s.id !== options.exclude).map((s) => {
|
|
4384
|
+
const d = fingerprintDistance(target2, s.fingerprint);
|
|
4385
|
+
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
4386
|
+
}).sort((a, b) => a.distance - b.distance);
|
|
4387
|
+
}
|
|
4388
|
+
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
4389
|
+
const out = [];
|
|
4390
|
+
const s = register.sites;
|
|
4391
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
4392
|
+
for (let j = i + 1; j < s.length; j += 1) {
|
|
4393
|
+
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
4394
|
+
out.push({ a: s[i].id, b: s[j].id, distance: d.total, sibling: d.total < siblingAt, shared: sharedParts(s[i].fingerprint, s[j].fingerprint, d) });
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
return out.sort((x, y) => x.distance - y.distance);
|
|
4398
|
+
}
|
|
4399
|
+
function siblingLine(briefs) {
|
|
4400
|
+
const d = [];
|
|
4401
|
+
for (const runs of briefs) {
|
|
4402
|
+
for (let i = 0; i < runs.length; i += 1) for (let j = i + 1; j < runs.length; j += 1) d.push(fingerprintDistance(runs[i], runs[j]).total);
|
|
4403
|
+
}
|
|
4404
|
+
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
4405
|
+
d.sort((a, b) => a - b);
|
|
4406
|
+
const mid = Math.floor(d.length / 2);
|
|
4407
|
+
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
4408
|
+
return Math.round(m * 1e3) / 1e3;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
// src/report/index.ts
|
|
4412
|
+
var TELL_HEAVY = 3;
|
|
4413
|
+
var MIN_DECIDED = 5;
|
|
4414
|
+
var TELL_AREA = {
|
|
4415
|
+
"reflex-font": { area: "type", choice: "display" },
|
|
4416
|
+
"reflex-font-2": { area: "type", choice: "display" },
|
|
4417
|
+
"italic-serif-display": { area: "type", choice: "display" },
|
|
4418
|
+
"ai-violet": { area: "colour", choice: "accent" },
|
|
4419
|
+
"blue-purple-gradient": { area: "colour", choice: "accent" },
|
|
4420
|
+
"gradient-text": { area: "colour", choice: "accent" },
|
|
4421
|
+
"cream-palette": { area: "colour", choice: "ground" },
|
|
4422
|
+
"pill-everything": { area: "shape", choice: "shape" },
|
|
4423
|
+
"shadcn-dump": { area: "shape", choice: "shape" },
|
|
4424
|
+
"glass-panel": { area: "effects", choice: "motif" },
|
|
4425
|
+
"radial-spotlight-glow": { area: "effects", choice: "motif" },
|
|
4426
|
+
"grid-background": { area: "effects", choice: "motif" },
|
|
4427
|
+
"thin-border-wide-shadow": { area: "effects", choice: "shape" },
|
|
4428
|
+
"icon-tile-stack": { area: "effects", choice: "motif" },
|
|
4429
|
+
"reveal-everywhere": { area: "motion", choice: "signature" },
|
|
4430
|
+
"intro-cinematic": { area: "motion", choice: "signature" },
|
|
4431
|
+
marquee: { area: "motion", choice: "signature" },
|
|
4432
|
+
"hero-then-proof": { area: "layout" },
|
|
4433
|
+
"icon-tile-grid": { area: "layout" },
|
|
4434
|
+
"hero-eyebrow-chip": { area: "layout" },
|
|
4435
|
+
"bento-grid": { area: "layout" }
|
|
4436
|
+
};
|
|
4437
|
+
var PART_AREA = {
|
|
4438
|
+
display: { area: "type", choice: "display" },
|
|
4439
|
+
body: { area: "type", choice: "body" },
|
|
4440
|
+
accent: { area: "colour", choice: "accent" },
|
|
4441
|
+
ground: { area: "colour", choice: "ground" },
|
|
4442
|
+
shape: { area: "shape", choice: "shape" },
|
|
4443
|
+
effects: { area: "effects", choice: "motif" },
|
|
4444
|
+
motion: { area: "motion", choice: "signature" }
|
|
4445
|
+
};
|
|
4446
|
+
var DEFAULTS = /^(?:ground )?(?:white|grey)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons/;
|
|
4447
|
+
function designFindings(findings, copyTells) {
|
|
4448
|
+
return (findings?.findings ?? []).filter((f3) => !copyTells.has(f3.tell));
|
|
4449
|
+
}
|
|
4450
|
+
function characterReport(input, copyTells = /* @__PURE__ */ new Set()) {
|
|
4451
|
+
const actions = [];
|
|
4452
|
+
const signals = [];
|
|
4453
|
+
if (input.findings) {
|
|
4454
|
+
const design = designFindings(input.findings, copyTells);
|
|
4455
|
+
const distinct = [...new Set(design.map((f3) => f3.tell))];
|
|
4456
|
+
signals.push({
|
|
4457
|
+
id: "tells",
|
|
4458
|
+
raised: distinct.length >= TELL_HEAVY,
|
|
4459
|
+
detail: `${distinct.length} distinct design tell${distinct.length === 1 ? "" : "s"}${distinct.length ? `: ${distinct.join(", ")}` : ""}`
|
|
4460
|
+
});
|
|
4461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4462
|
+
for (const f3 of design) {
|
|
4463
|
+
if (seen.has(f3.tell)) continue;
|
|
4464
|
+
seen.add(f3.tell);
|
|
4465
|
+
const map = TELL_AREA[f3.tell] ?? { area: "effects" };
|
|
4466
|
+
const both = design.some((g) => g.tell === f3.tell && g.line === 0) && design.some((g) => g.tell === f3.tell && g.line > 0);
|
|
4467
|
+
actions.push({ area: map.area, what: f3.fix, why: `${f3.name} (${f3.tell}, gen ${f3.generation})${both ? ", in source and on the page" : ""}`, priority: both ? 1 : 2, choice: map.choice });
|
|
4468
|
+
}
|
|
4469
|
+
const copy = (input.findings.findings ?? []).filter((f3) => copyTells.has(f3.tell));
|
|
4470
|
+
const byTell = /* @__PURE__ */ new Map();
|
|
4471
|
+
for (const f3 of copy) byTell.set(f3.tell, [...byTell.get(f3.tell) ?? [], f3]);
|
|
4472
|
+
for (const [tell, list] of byTell) {
|
|
4473
|
+
actions.push({ area: "copy", what: list[0].fix, why: `${list[0].name} (${tell}), ${list.length} place${list.length === 1 ? "" : "s"}, first at ${list[0].path}${list[0].line ? `:${list[0].line}` : ""}`, priority: 3 });
|
|
4474
|
+
}
|
|
4475
|
+
} else {
|
|
4476
|
+
signals.push({ id: "tells", raised: null, detail: "not measured" });
|
|
4477
|
+
}
|
|
4478
|
+
if (input.typicality) {
|
|
4479
|
+
const t = input.typicality;
|
|
4480
|
+
signals.push({ id: "typicality", raised: t.typical, detail: `score ${t.score.toFixed(2)}, ${t.typical ? "typical" : "not typical"} of what Claude builds for this brief` });
|
|
4481
|
+
if (t.typical) {
|
|
4482
|
+
for (const s of t.shared) {
|
|
4483
|
+
const map = PART_AREA[s.part];
|
|
4484
|
+
if (!map || DEFAULTS.test(`${s.part} ${s.value}`)) continue;
|
|
4485
|
+
actions.push({ area: map.area, what: `Move off the model's pick for this brief: ${s.part} ${s.value}.`, why: `${s.runs} of ${s.of} null pages chose it`, priority: 1, choice: map.choice });
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
} else {
|
|
4489
|
+
signals.push({ id: "typicality", raised: null, detail: "not measured: no null model for this brief" });
|
|
4490
|
+
}
|
|
4491
|
+
if (input.estate) {
|
|
4492
|
+
const siblings = input.estate.filter((m) => m.sibling);
|
|
4493
|
+
const nearest2 = input.estate[0];
|
|
4494
|
+
signals.push({
|
|
4495
|
+
id: "estate",
|
|
4496
|
+
raised: siblings.length > 0,
|
|
4497
|
+
detail: siblings.length ? `sibling of ${siblings.map((s) => `${s.id} (${s.distance.toFixed(2)})`).join(", ")}` : nearest2 ? `nearest ${nearest2.id} at ${nearest2.distance.toFixed(2)}, no sibling` : "the register is empty"
|
|
4498
|
+
});
|
|
4499
|
+
for (const s of siblings) {
|
|
4500
|
+
for (const part of s.shared) {
|
|
4501
|
+
if (DEFAULTS.test(part)) continue;
|
|
4502
|
+
actions.push({ area: areaOfShared(part), what: `Differ from ${s.id}: both have ${part}.`, why: `sibling at ${s.distance.toFixed(2)}`, priority: 1, choice: choiceOfShared(part) });
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
} else {
|
|
4506
|
+
signals.push({ id: "estate", raised: null, detail: "not measured: no estate register" });
|
|
4507
|
+
}
|
|
4508
|
+
if (input.direction) {
|
|
4509
|
+
const d = input.direction;
|
|
4510
|
+
const raised2 = !d.valid || d.decided < MIN_DECIDED;
|
|
4511
|
+
signals.push({ id: "reasons", raised: raised2, detail: `${d.decided} of 7 choices decided with a reason${d.valid ? "" : ", art-direction.json has errors"}` });
|
|
4512
|
+
for (const p of d.problems) {
|
|
4513
|
+
const key = p.at.match(/^choices\.(\w+)/)?.[1];
|
|
4514
|
+
if (p.severity === "error" || p.message === "not decided yet") {
|
|
4515
|
+
actions.push({ area: "direction", what: key && p.message === "not decided yet" ? `Decide ${key}, with a source from the client's world.` : `${p.at || "art-direction.json"}: ${p.message}`, why: "the reason rule", priority: 1, choice: key });
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
} else {
|
|
4519
|
+
signals.push({ id: "reasons", raised: null, detail: "not measured: no art-direction.json" });
|
|
4520
|
+
actions.push({ area: "direction", what: "Write art-direction.json: run craft direction init, then give every choice a reason from the client's world.", why: "the reason rule", priority: 1 });
|
|
4521
|
+
}
|
|
4522
|
+
const raised = signals.filter((s) => s.raised === true).length;
|
|
4523
|
+
const unmeasured = signals.filter((s) => s.raised === null).map((s) => s.id);
|
|
4524
|
+
const verdict = raised >= 2 ? "default" : raised === 1 ? "mixed" : unmeasured.length ? "unproven" : "decided";
|
|
4525
|
+
const summary = verdict === "default" ? `${raised} of 4 signals raised: nobody chose how this looks.` : verdict === "mixed" ? `1 of 4 signals raised: ${signals.find((s) => s.raised).id}.` : verdict === "decided" ? "No signal raised, and every choice carries a reason." : `No signal raised, but ${unmeasured.join(", ")} not measured. Unverified is not a pass.`;
|
|
4526
|
+
const order = ["direction", "type", "colour", "shape", "effects", "motion", "layout", "copy"];
|
|
4527
|
+
const merged = [];
|
|
4528
|
+
for (const a of actions) {
|
|
4529
|
+
const same = a.choice && a.area !== "direction" && a.area !== "copy" ? merged.find((m) => m.choice === a.choice && m.area === a.area) : void 0;
|
|
4530
|
+
if (same) {
|
|
4531
|
+
const fromTell = /\(\S+, gen \d\)/.test(a.why);
|
|
4532
|
+
same.why = `${same.why}; ${fromTell ? a.why : `${a.what.replace(/\.$/, "")} (${a.why})`}`;
|
|
4533
|
+
same.priority = Math.min(same.priority, a.priority);
|
|
4534
|
+
} else merged.push({ ...a });
|
|
4535
|
+
}
|
|
4536
|
+
const unique = merged.filter((a, i) => merged.findIndex((b) => b.area === a.area && b.what === a.what) === i);
|
|
4537
|
+
unique.sort((a, b) => a.priority - b.priority || order.indexOf(a.area) - order.indexOf(b.area));
|
|
4538
|
+
return { site: input.site, verdict, summary, signals, actions: unique };
|
|
4539
|
+
}
|
|
4540
|
+
function areaOfShared(part) {
|
|
4541
|
+
if (/headline|body$/.test(part)) return "type";
|
|
4542
|
+
if (/accent|ground/.test(part)) return "colour";
|
|
4543
|
+
if (/buttons/.test(part)) return "shape";
|
|
4544
|
+
if (/reveal/.test(part)) return "motion";
|
|
4545
|
+
if (/running order/.test(part)) return "layout";
|
|
4546
|
+
return "effects";
|
|
4547
|
+
}
|
|
4548
|
+
function choiceOfShared(part) {
|
|
4549
|
+
if (/headline/.test(part)) return "display";
|
|
4550
|
+
if (/body$/.test(part)) return "body";
|
|
4551
|
+
if (/accent/.test(part)) return "accent";
|
|
4552
|
+
if (/ground/.test(part)) return "ground";
|
|
4553
|
+
if (/buttons/.test(part)) return "shape";
|
|
4554
|
+
if (/reveal/.test(part)) return "signature";
|
|
4555
|
+
return /running order/.test(part) ? void 0 : "motif";
|
|
4556
|
+
}
|
|
4557
|
+
|
|
4558
|
+
// src/report/retrofit.ts
|
|
4559
|
+
var HEADINGS = {
|
|
4560
|
+
direction: "Decide first",
|
|
4561
|
+
type: "Type",
|
|
4562
|
+
colour: "Colour",
|
|
4563
|
+
shape: "Shape",
|
|
4564
|
+
effects: "Effects",
|
|
4565
|
+
motion: "Motion",
|
|
4566
|
+
layout: "How sections look (not where they sit)",
|
|
4567
|
+
copy: "Copy"
|
|
4568
|
+
};
|
|
4569
|
+
var ORDER2 = ["direction", "type", "colour", "shape", "effects", "motion", "layout", "copy"];
|
|
4570
|
+
function target(a, direction) {
|
|
4571
|
+
if (!a.choice || a.area === "direction") return "";
|
|
4572
|
+
const c = direction?.choices?.[a.choice];
|
|
4573
|
+
if (c && c.because && !c.because.startsWith("PROPOSED:")) return `
|
|
4574
|
+
- Use: **${c.value}** for ${a.choice}. ${c.because}`;
|
|
4575
|
+
return `
|
|
4576
|
+
- Waits on: deciding **${a.choice}** in art-direction.json.`;
|
|
4577
|
+
}
|
|
4578
|
+
function retrofitPlan(report2, direction) {
|
|
4579
|
+
const lines = [
|
|
4580
|
+
`# Retrofit: ${report2.site}`,
|
|
4581
|
+
"",
|
|
4582
|
+
`**Now:** ${report2.verdict}. ${report2.summary}`,
|
|
4583
|
+
"",
|
|
4584
|
+
"| Signal | Raised | Reading |",
|
|
4585
|
+
"|---|---|---|",
|
|
4586
|
+
...report2.signals.map((s) => `| ${s.id} | ${s.raised === null ? "not measured" : s.raised ? "yes" : "no"} | ${s.detail} |`),
|
|
4587
|
+
"",
|
|
4588
|
+
"**Done when** `craft report` says *decided*: fewer than three design tells, typicality under 0.10 against this brief's null, no sibling in the estate, and at least five of the seven choices decided with a reason.",
|
|
4589
|
+
""
|
|
4590
|
+
];
|
|
4591
|
+
let n = 0;
|
|
4592
|
+
for (const area of ORDER2) {
|
|
4593
|
+
const list = report2.actions.filter((a) => a.area === area);
|
|
4594
|
+
if (list.length === 0) continue;
|
|
4595
|
+
n += 1;
|
|
4596
|
+
lines.push(`## ${n}. ${HEADINGS[area]}`, "");
|
|
4597
|
+
for (const a of list) lines.push(`- [ ] ${a.what} *(${a.why})*${target(a, direction)}`);
|
|
4598
|
+
lines.push("");
|
|
4599
|
+
}
|
|
4600
|
+
if (n === 0) lines.push("Nothing to change.", "");
|
|
4601
|
+
lines.push(
|
|
4602
|
+
"## Leave alone",
|
|
4603
|
+
"",
|
|
4604
|
+
"Navigation, the order of the sections, where the call to action sits, reading order and legibility. Change the look, never the page grammar. A layout tell above is about how a section looks: the icon tiles, the chip, the strip. The section stays where it is.",
|
|
4605
|
+
""
|
|
4606
|
+
);
|
|
4607
|
+
return lines.join("\n");
|
|
4608
|
+
}
|
|
4074
4609
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4075
4610
|
0 && (module.exports = {
|
|
4076
4611
|
AI_PHRASES,
|
|
@@ -4088,12 +4623,16 @@ function propose(input) {
|
|
|
4088
4623
|
DURATION_S,
|
|
4089
4624
|
EASE,
|
|
4090
4625
|
EASE_TUPLE,
|
|
4626
|
+
ESTATE_VERSION,
|
|
4091
4627
|
EXIT_RATIO,
|
|
4092
4628
|
FINGERPRINT_VERSION,
|
|
4093
4629
|
HOUSE_BUDGET,
|
|
4094
4630
|
HOUSE_TYPE,
|
|
4095
4631
|
LIGHTNESS_CURVE,
|
|
4632
|
+
MIN_DECIDED,
|
|
4633
|
+
MIN_RUNS,
|
|
4096
4634
|
NEGATIVE_REASSURANCE,
|
|
4635
|
+
NULL_VERSION,
|
|
4097
4636
|
PILL_LIMIT,
|
|
4098
4637
|
PLAINER_WORDS,
|
|
4099
4638
|
PROPOSED,
|
|
@@ -4106,27 +4645,37 @@ function propose(input) {
|
|
|
4106
4645
|
SCALE,
|
|
4107
4646
|
SHADCN_LIMIT,
|
|
4108
4647
|
SHADCN_PRIMITIVES,
|
|
4648
|
+
SIBLING_AT,
|
|
4109
4649
|
SNAPSHOT_VERSION,
|
|
4110
4650
|
SOURCE_KINDS,
|
|
4111
4651
|
SPACE_STEPS,
|
|
4112
4652
|
SPRING,
|
|
4113
4653
|
STOCK_PHRASES,
|
|
4654
|
+
TELL_HEAVY,
|
|
4114
4655
|
TYPE_STEPS,
|
|
4656
|
+
TYPICAL_AT,
|
|
4115
4657
|
VAGUE_WORDS,
|
|
4116
4658
|
accentFork,
|
|
4659
|
+
addSite,
|
|
4117
4660
|
apcaContrast,
|
|
4118
4661
|
auditSnapshot,
|
|
4119
4662
|
catalogueTable,
|
|
4663
|
+
characterReport,
|
|
4120
4664
|
checkCopy,
|
|
4121
4665
|
checkPair,
|
|
4122
4666
|
checkRestraint,
|
|
4667
|
+
choicesOf,
|
|
4668
|
+
compareToEstate,
|
|
4123
4669
|
craftTokens,
|
|
4124
4670
|
deltaEOk,
|
|
4125
4671
|
deltaEOkHex,
|
|
4126
4672
|
densityCss,
|
|
4127
4673
|
densityTokens,
|
|
4128
4674
|
emitCss,
|
|
4675
|
+
emptyEstate,
|
|
4676
|
+
estatePairs,
|
|
4129
4677
|
exitDuration,
|
|
4678
|
+
extractHtml,
|
|
4130
4679
|
findColours,
|
|
4131
4680
|
fingerprint,
|
|
4132
4681
|
fingerprintDistance,
|
|
@@ -4136,7 +4685,11 @@ function propose(input) {
|
|
|
4136
4685
|
fontClass,
|
|
4137
4686
|
formatHex,
|
|
4138
4687
|
formatReport,
|
|
4688
|
+
groundName,
|
|
4689
|
+
harvest,
|
|
4139
4690
|
hexToOklch,
|
|
4691
|
+
hueName,
|
|
4692
|
+
hueSwatch,
|
|
4140
4693
|
inP3Gamut,
|
|
4141
4694
|
inSrgbGamut,
|
|
4142
4695
|
initDirection,
|
|
@@ -4146,6 +4699,7 @@ function propose(input) {
|
|
|
4146
4699
|
motionTokens,
|
|
4147
4700
|
nearest,
|
|
4148
4701
|
normaliseFamily,
|
|
4702
|
+
nullPrompt,
|
|
4149
4703
|
oklchToHex,
|
|
4150
4704
|
oklchToRgb,
|
|
4151
4705
|
paletteFromPixels,
|
|
@@ -4154,16 +4708,22 @@ function propose(input) {
|
|
|
4154
4708
|
propose,
|
|
4155
4709
|
ramp,
|
|
4156
4710
|
rampFromAnchors,
|
|
4711
|
+
retrofitPlan,
|
|
4157
4712
|
runTell,
|
|
4158
4713
|
scanSource,
|
|
4159
4714
|
sectionRhythm,
|
|
4160
4715
|
semantic,
|
|
4716
|
+
shapeName,
|
|
4717
|
+
sharedParts,
|
|
4161
4718
|
shouldAnimate,
|
|
4719
|
+
siblingLine,
|
|
4162
4720
|
tailwindV3Preset,
|
|
4163
4721
|
tailwindV4Theme,
|
|
4164
4722
|
tellById,
|
|
4165
4723
|
toGamut,
|
|
4166
4724
|
typeFeatureTokens,
|
|
4725
|
+
typicality,
|
|
4167
4726
|
validateDirection,
|
|
4168
|
-
wcagContrast
|
|
4727
|
+
wcagContrast,
|
|
4728
|
+
wordListsJson
|
|
4169
4729
|
});
|