@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.js
CHANGED
|
@@ -827,9 +827,9 @@ function resolveValue(value, props, depth = 0) {
|
|
|
827
827
|
if (depth > 8) return null;
|
|
828
828
|
const match = SINGLE_VAR.exec(value.trim());
|
|
829
829
|
if (match === null) return value.trim();
|
|
830
|
-
const
|
|
831
|
-
if (
|
|
832
|
-
return resolveValue(
|
|
830
|
+
const target2 = props.get(match[1].toLowerCase());
|
|
831
|
+
if (target2 !== void 0 && target2 !== "initial")
|
|
832
|
+
return resolveValue(target2, props, depth + 1);
|
|
833
833
|
const fallback = match[2]?.trim();
|
|
834
834
|
return fallback === void 0 || fallback === "" ? null : resolveValue(fallback, props, depth + 1);
|
|
835
835
|
}
|
|
@@ -2384,7 +2384,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2384
2384
|
severity: "warn",
|
|
2385
2385
|
surface: "source",
|
|
2386
2386
|
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.",
|
|
2387
|
-
fix: "
|
|
2387
|
+
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.",
|
|
2388
2388
|
detect: fontTell(REFLEX_FONTS_2),
|
|
2389
2389
|
fixtures: {
|
|
2390
2390
|
flag: [f2("src/styles.css", `@theme {
|
|
@@ -3132,15 +3132,16 @@ function makeSnapshot(patch = {}) {
|
|
|
3132
3132
|
}
|
|
3133
3133
|
|
|
3134
3134
|
// src/snapshot/fonts.ts
|
|
3135
|
+
var SYSTEM = /^(?:system-ui|-apple-system|BlinkMacSystemFont|ui-sans-serif|ui-serif|ui-monospace|ui-rounded|sans-serif|serif|monospace|cursive)$/i;
|
|
3135
3136
|
function normaliseFamily(raw) {
|
|
3136
3137
|
let name = raw.replace(/["']/g, "").trim();
|
|
3137
3138
|
const next = name.match(/^__(.+?)(?:_Fallback)?_[0-9a-f]{5,8}$/i);
|
|
3138
3139
|
if (next) name = next[1].replace(/_/g, " ");
|
|
3139
3140
|
name = name.replace(/\s+Fallback$/i, "").replace(/\s+Variable$/i, "");
|
|
3141
|
+
if (SYSTEM.test(name)) return "system-ui";
|
|
3140
3142
|
if (!/\s/.test(name) && (/[a-z][A-Z]/.test(name) || /^[a-z]+(?:-[a-z]+)+$/.test(name))) {
|
|
3141
3143
|
name = name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").replace(/\b([a-z])/g, (c) => c.toUpperCase()).replace(/\bDm\b/, "DM");
|
|
3142
3144
|
}
|
|
3143
|
-
if (/^(?:system-ui|-apple-system|BlinkMacSystemFont|ui-sans-serif|ui-serif|ui-monospace|sans-serif|serif|monospace)$/i.test(name)) return "system-ui";
|
|
3144
3145
|
return name;
|
|
3145
3146
|
}
|
|
3146
3147
|
var KNOWN = {
|
|
@@ -3517,6 +3518,25 @@ function catalogueTable() {
|
|
|
3517
3518
|
...rows
|
|
3518
3519
|
].join("\n");
|
|
3519
3520
|
}
|
|
3521
|
+
function wordListsJson() {
|
|
3522
|
+
return JSON.stringify(
|
|
3523
|
+
{
|
|
3524
|
+
catalogueVersion: CATALOGUE_VERSION,
|
|
3525
|
+
lists: {
|
|
3526
|
+
aiWords: AI_WORDS,
|
|
3527
|
+
stockPhrases: STOCK_PHRASES,
|
|
3528
|
+
aiPhrases: AI_PHRASES,
|
|
3529
|
+
plainerWords: PLAINER_WORDS,
|
|
3530
|
+
buzzwords: BUZZWORDS,
|
|
3531
|
+
negativeReassurance: NEGATIVE_REASSURANCE,
|
|
3532
|
+
vagueWords: VAGUE_WORDS,
|
|
3533
|
+
reviewPhrases: REVIEW_PHRASES
|
|
3534
|
+
}
|
|
3535
|
+
},
|
|
3536
|
+
null,
|
|
3537
|
+
2
|
|
3538
|
+
) + "\n";
|
|
3539
|
+
}
|
|
3520
3540
|
|
|
3521
3541
|
// src/character/format.ts
|
|
3522
3542
|
function formatReport(report2, title) {
|
|
@@ -3556,6 +3576,7 @@ function formatReport(report2, title) {
|
|
|
3556
3576
|
// src/fingerprint/index.ts
|
|
3557
3577
|
var FINGERPRINT_VERSION = 1;
|
|
3558
3578
|
var CHROMATIC = 0.03;
|
|
3579
|
+
var MIN_ACCENT_AREA = 2e4;
|
|
3559
3580
|
var oklch = (value) => {
|
|
3560
3581
|
const parsed = parseColour(value);
|
|
3561
3582
|
return parsed && parsed.alpha > 0.5 ? parsed.oklch : null;
|
|
@@ -3568,7 +3589,7 @@ function accentOf(s) {
|
|
|
3568
3589
|
}
|
|
3569
3590
|
const topButton = [...buttons].sort((a, b) => b[1] - a[1])[0];
|
|
3570
3591
|
if (topButton) return oklch(topButton[0]);
|
|
3571
|
-
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];
|
|
3592
|
+
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];
|
|
3572
3593
|
if (bg) return bg.o;
|
|
3573
3594
|
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];
|
|
3574
3595
|
return text ? text.o : null;
|
|
@@ -3645,8 +3666,8 @@ function fingerprintDistance(a, b) {
|
|
|
3645
3666
|
const total = Object.keys(DISTANCE_WEIGHTS).reduce((sum, k) => sum + DISTANCE_WEIGHTS[k] * parts2[k], 0);
|
|
3646
3667
|
return { total: Math.round(total * 1e3) / 1e3, parts: parts2 };
|
|
3647
3668
|
}
|
|
3648
|
-
function nearest(
|
|
3649
|
-
return set.map((item) => ({ id: item.id, distance: fingerprintDistance(
|
|
3669
|
+
function nearest(target2, set, k = set.length) {
|
|
3670
|
+
return set.map((item) => ({ id: item.id, distance: fingerprintDistance(target2, item.fingerprint) })).sort((x, y) => x.distance.total - y.distance.total).slice(0, k);
|
|
3650
3671
|
}
|
|
3651
3672
|
|
|
3652
3673
|
// src/direction/types.ts
|
|
@@ -3953,6 +3974,495 @@ function propose(input) {
|
|
|
3953
3974
|
direction: { version: DIRECTION_VERSION, client: input.client, brief: input.brief, sources: input.sources, choices }
|
|
3954
3975
|
};
|
|
3955
3976
|
}
|
|
3977
|
+
|
|
3978
|
+
// src/null/index.ts
|
|
3979
|
+
var NULL_VERSION = 1;
|
|
3980
|
+
function nullPrompt(brief) {
|
|
3981
|
+
return [
|
|
3982
|
+
`Build the home page for this business: ${brief.trim()}`,
|
|
3983
|
+
"",
|
|
3984
|
+
"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."
|
|
3985
|
+
].join("\n");
|
|
3986
|
+
}
|
|
3987
|
+
function extractHtml(reply) {
|
|
3988
|
+
const start = reply.search(/<!doctype html|<html[\s>]/i);
|
|
3989
|
+
if (start < 0) return null;
|
|
3990
|
+
const end = reply.toLowerCase().lastIndexOf("</html>");
|
|
3991
|
+
const html = end > start ? reply.slice(start, end + "</html>".length) : reply.slice(start).replace(/\s*```\s*$/, "");
|
|
3992
|
+
return html.length > 200 ? html : null;
|
|
3993
|
+
}
|
|
3994
|
+
var MIN_RUNS = 5;
|
|
3995
|
+
var TYPICAL_AT = 0.1;
|
|
3996
|
+
function meanNearest(distances, k) {
|
|
3997
|
+
const sorted = [...distances].sort((a, b) => a - b).slice(0, k);
|
|
3998
|
+
return sorted.reduce((s, d) => s + d, 0) / sorted.length;
|
|
3999
|
+
}
|
|
4000
|
+
function median2(values) {
|
|
4001
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
4002
|
+
const mid = Math.floor(sorted.length / 2);
|
|
4003
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
4004
|
+
}
|
|
4005
|
+
function typicality(target2, runs, k = 3) {
|
|
4006
|
+
if (runs.length < MIN_RUNS) throw new Error(`a null model needs at least ${MIN_RUNS} runs; this one has ${runs.length}`);
|
|
4007
|
+
const kk = Math.min(k, runs.length - 1);
|
|
4008
|
+
const toTarget = runs.map((r) => ({ id: r.id, distance: fingerprintDistance(target2, r.fingerprint).total }));
|
|
4009
|
+
const distance = meanNearest(
|
|
4010
|
+
toTarget.map((t) => t.distance),
|
|
4011
|
+
kk
|
|
4012
|
+
);
|
|
4013
|
+
const loo = runs.map(
|
|
4014
|
+
(r, i) => meanNearest(
|
|
4015
|
+
runs.filter((_, j) => j !== i).map((o) => fingerprintDistance(r.fingerprint, o.fingerprint).total),
|
|
4016
|
+
kk
|
|
4017
|
+
)
|
|
4018
|
+
);
|
|
4019
|
+
const score = loo.filter((d) => d >= distance).length / loo.length;
|
|
4020
|
+
return {
|
|
4021
|
+
score: Math.round(score * 100) / 100,
|
|
4022
|
+
typical: score >= TYPICAL_AT,
|
|
4023
|
+
distance: Math.round(distance * 1e3) / 1e3,
|
|
4024
|
+
baseline: Math.round(median2(loo) * 1e3) / 1e3,
|
|
4025
|
+
nearest: toTarget.sort((a, b) => a.distance - b.distance).slice(0, kk),
|
|
4026
|
+
shared: sharedChoices(target2, runs)
|
|
4027
|
+
};
|
|
4028
|
+
}
|
|
4029
|
+
var HUES = [
|
|
4030
|
+
["red", 0, 45],
|
|
4031
|
+
["orange", 45, 75],
|
|
4032
|
+
["yellow", 75, 110],
|
|
4033
|
+
["green", 110, 165],
|
|
4034
|
+
["teal", 165, 215],
|
|
4035
|
+
["blue", 215, 265],
|
|
4036
|
+
["violet", 265, 315],
|
|
4037
|
+
["pink", 315, 360]
|
|
4038
|
+
];
|
|
4039
|
+
function hueName(o) {
|
|
4040
|
+
if (!o) return "none";
|
|
4041
|
+
if (o.c < 0.03) return "neutral";
|
|
4042
|
+
return HUES.find(([, lo, hi]) => o.h >= lo && o.h < hi)?.[0] ?? "red";
|
|
4043
|
+
}
|
|
4044
|
+
function groundName(o) {
|
|
4045
|
+
if (o.l < 0.3) return "dark";
|
|
4046
|
+
if (isCream(o)) return "cream";
|
|
4047
|
+
if (o.l >= 0.97 && o.c < 0.012) return "white";
|
|
4048
|
+
return o.c < 0.02 ? "grey" : "tinted";
|
|
4049
|
+
}
|
|
4050
|
+
function shapeName(roundness) {
|
|
4051
|
+
if (roundness === null) return "no buttons";
|
|
4052
|
+
if (roundness >= 0.45) return "pill";
|
|
4053
|
+
return roundness >= 0.15 ? "soft" : "square";
|
|
4054
|
+
}
|
|
4055
|
+
function choicesOf(fp) {
|
|
4056
|
+
return {
|
|
4057
|
+
display: [fp.display.family],
|
|
4058
|
+
body: [fp.body.family],
|
|
4059
|
+
accent: [hueName(fp.accent)],
|
|
4060
|
+
ground: [groundName(fp.ground)],
|
|
4061
|
+
shape: [shapeName(fp.roundness)],
|
|
4062
|
+
motion: [fp.motion >= 0.6 ? "reveals most sections" : fp.motion > 0 ? "reveals some sections" : "no scroll reveals"],
|
|
4063
|
+
effects: fp.effects,
|
|
4064
|
+
opening: [fp.layout.slice(0, 3).join(" > ") || "none"]
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
function sharedChoices(target2, runs) {
|
|
4068
|
+
const mine = choicesOf(target2);
|
|
4069
|
+
const all = runs.map((r) => choicesOf(r.fingerprint));
|
|
4070
|
+
const out = [];
|
|
4071
|
+
for (const [part, values] of Object.entries(mine)) {
|
|
4072
|
+
for (const value of values) {
|
|
4073
|
+
const count2 = all.filter((c) => c[part].includes(value)).length;
|
|
4074
|
+
if (count2 / runs.length >= 0.25) out.push({ part, value, runs: count2, of: runs.length });
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
return out.sort((a, b) => b.runs - a.runs);
|
|
4078
|
+
}
|
|
4079
|
+
var REFLEX_1 = new Set(REFLEX_FONTS_1.map((f3) => f3.toLowerCase()));
|
|
4080
|
+
var REFLEX_2 = new Set(REFLEX_FONTS_2.map((f3) => f3.toLowerCase()));
|
|
4081
|
+
function knownFor(kind, value, sample) {
|
|
4082
|
+
if (kind === "face") return REFLEX_1.has(value.toLowerCase()) ? "reflex-font" : REFLEX_2.has(value.toLowerCase()) ? "reflex-font-2" : null;
|
|
4083
|
+
if (kind === "accent" && value === "violet") return sample.some((f3) => f3.accent && isAiViolet(f3.accent)) ? "ai-violet" : null;
|
|
4084
|
+
if (kind === "ground" && value === "cream") return "cream-palette";
|
|
4085
|
+
if (kind === "shape" && value === "pill") return "pill-everything";
|
|
4086
|
+
if (kind === "motion" && value === "reveals most sections") return "reveal-everywhere";
|
|
4087
|
+
if (kind === "tell") return value;
|
|
4088
|
+
if (kind === "opening" && /^hero > (?:logos|stats)/.test(value)) return "hero-then-proof";
|
|
4089
|
+
return null;
|
|
4090
|
+
}
|
|
4091
|
+
var STOP2 = new Set(
|
|
4092
|
+
"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(" ")
|
|
4093
|
+
);
|
|
4094
|
+
function grams(text) {
|
|
4095
|
+
const out = /* @__PURE__ */ new Set();
|
|
4096
|
+
for (const clause of text.toLowerCase().replace(/[’']/g, "'").split(/[.!?;:|()\u2013\u2014\u00b7•]+|,\s/)) {
|
|
4097
|
+
const words2 = clause.match(/[a-z][a-z'-]*/g) ?? [];
|
|
4098
|
+
for (let n = 2; n <= 4; n += 1) {
|
|
4099
|
+
for (let i = 0; i + n <= words2.length; i += 1) {
|
|
4100
|
+
const g = words2.slice(i, i + n);
|
|
4101
|
+
if (STOP2.has(g[0]) || STOP2.has(g[n - 1])) continue;
|
|
4102
|
+
if (g.filter((w) => !STOP2.has(w)).length < 2) continue;
|
|
4103
|
+
out.add(g.join(" "));
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
return out;
|
|
4108
|
+
}
|
|
4109
|
+
var GRAMMAR = /* @__PURE__ */ new Set([
|
|
4110
|
+
"privacy policy",
|
|
4111
|
+
"cookie policy",
|
|
4112
|
+
"rights reserved",
|
|
4113
|
+
"all rights reserved",
|
|
4114
|
+
"terms of service",
|
|
4115
|
+
"terms and conditions",
|
|
4116
|
+
"terms conditions",
|
|
4117
|
+
"how it works",
|
|
4118
|
+
"what we do",
|
|
4119
|
+
"about us",
|
|
4120
|
+
"contact us",
|
|
4121
|
+
"get in touch",
|
|
4122
|
+
"opening hours",
|
|
4123
|
+
"read more",
|
|
4124
|
+
"learn more",
|
|
4125
|
+
"book now",
|
|
4126
|
+
"free quote",
|
|
4127
|
+
"our services",
|
|
4128
|
+
"quick links",
|
|
4129
|
+
"email address",
|
|
4130
|
+
"phone number",
|
|
4131
|
+
"full name",
|
|
4132
|
+
"first name",
|
|
4133
|
+
"last name",
|
|
4134
|
+
"your name",
|
|
4135
|
+
"your email",
|
|
4136
|
+
"send message"
|
|
4137
|
+
]);
|
|
4138
|
+
var LEGAL = /©|©|\bregistered (?:in|office|number)\b|\bcompany (?:no|number|reg)|\bvat (?:no|number|reg)/i;
|
|
4139
|
+
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;
|
|
4140
|
+
function textGrams(text) {
|
|
4141
|
+
if (LEGAL.test(text)) return /* @__PURE__ */ new Set();
|
|
4142
|
+
const out = grams(text.replace(NOISE, " | "));
|
|
4143
|
+
for (const g of out) if (GRAMMAR.has(g)) out.delete(g);
|
|
4144
|
+
return out;
|
|
4145
|
+
}
|
|
4146
|
+
function properNouns(brief) {
|
|
4147
|
+
return new Set((brief.match(/\b[A-Z][a-z]+/g) ?? []).map((w) => w.toLowerCase()).filter((w) => !STOP2.has(w)));
|
|
4148
|
+
}
|
|
4149
|
+
function harvest(models, options = {}) {
|
|
4150
|
+
const minShare = options.minShare ?? 0.25;
|
|
4151
|
+
const runs = models.flatMap((m, b) => m.runs.map((r) => ({ ...r, brief: b })));
|
|
4152
|
+
const of = runs.length;
|
|
4153
|
+
if (of === 0) return [];
|
|
4154
|
+
const multiBrief = models.length > 1;
|
|
4155
|
+
const sample = runs.map((r) => r.fingerprint);
|
|
4156
|
+
const tally = /* @__PURE__ */ new Map();
|
|
4157
|
+
const add = (kind, value, run, brief, example) => {
|
|
4158
|
+
const key = `${kind}\0${value}`;
|
|
4159
|
+
const t = tally.get(key) ?? { kind, value, runs: /* @__PURE__ */ new Set(), briefs: /* @__PURE__ */ new Set(), example };
|
|
4160
|
+
t.runs.add(run);
|
|
4161
|
+
t.briefs.add(brief);
|
|
4162
|
+
tally.set(key, t);
|
|
4163
|
+
};
|
|
4164
|
+
runs.forEach((r, i) => {
|
|
4165
|
+
const c = choicesOf(r.fingerprint);
|
|
4166
|
+
for (const f3 of /* @__PURE__ */ new Set([...c.display, ...c.body])) add("face", f3, i, r.brief);
|
|
4167
|
+
add("accent", c.accent[0], i, r.brief);
|
|
4168
|
+
add("ground", c.ground[0], i, r.brief);
|
|
4169
|
+
add("shape", c.shape[0], i, r.brief);
|
|
4170
|
+
add("motion", c.motion[0], i, r.brief);
|
|
4171
|
+
add("opening", c.opening[0], i, r.brief);
|
|
4172
|
+
for (const t of r.tells) add("tell", t, i, r.brief);
|
|
4173
|
+
});
|
|
4174
|
+
const out = [];
|
|
4175
|
+
for (const t of tally.values()) {
|
|
4176
|
+
if (t.runs.size / of < minShare) continue;
|
|
4177
|
+
if (t.kind === "accent" && (t.value === "none" || t.value === "neutral")) continue;
|
|
4178
|
+
if (t.kind === "shape" && t.value === "no buttons") continue;
|
|
4179
|
+
if (t.kind === "ground" && (t.value === "white" || t.value === "grey")) continue;
|
|
4180
|
+
if (t.kind === "motion" && t.value === "no scroll reveals") continue;
|
|
4181
|
+
out.push({ kind: t.kind, value: t.value, runs: t.runs.size, of, briefs: t.briefs.size, known: knownFor(t.kind, t.value, sample) });
|
|
4182
|
+
}
|
|
4183
|
+
const phraseMin = Math.max(3, Math.ceil(of * minShare * 0.4));
|
|
4184
|
+
const names = models.map((m) => properNouns(m.brief));
|
|
4185
|
+
const phrases2 = /* @__PURE__ */ new Map();
|
|
4186
|
+
runs.forEach((r, i) => {
|
|
4187
|
+
for (const text of r.copy) {
|
|
4188
|
+
for (const g of textGrams(text)) {
|
|
4189
|
+
if (g.split(" ").some((w) => names[r.brief].has(w))) continue;
|
|
4190
|
+
const p = phrases2.get(g) ?? { runs: /* @__PURE__ */ new Set(), briefs: /* @__PURE__ */ new Set(), example: text.trim().slice(0, 140) };
|
|
4191
|
+
p.runs.add(i);
|
|
4192
|
+
p.briefs.add(r.brief);
|
|
4193
|
+
phrases2.set(g, p);
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
});
|
|
4197
|
+
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);
|
|
4198
|
+
const maximal = kept.filter(([g, p]) => !kept.some(([h, q]) => h !== g && h.includes(g) && q.runs.size >= 0.8 * p.runs.size));
|
|
4199
|
+
for (const [g, p] of maximal.slice(0, 40)) {
|
|
4200
|
+
out.push({ kind: "phrase", value: g, runs: p.runs.size, of, briefs: p.briefs.size, known: options.copyTell?.(g) ?? null, example: p.example });
|
|
4201
|
+
}
|
|
4202
|
+
const order = ["face", "accent", "ground", "shape", "motion", "opening", "tell", "phrase"];
|
|
4203
|
+
return out.sort((a, b) => order.indexOf(a.kind) - order.indexOf(b.kind) || b.runs - a.runs);
|
|
4204
|
+
}
|
|
4205
|
+
function hueSwatch(runs, hue) {
|
|
4206
|
+
const hits = runs.map((r) => r.fingerprint.accent).filter((o) => !!o && hueName(o) === hue);
|
|
4207
|
+
if (hits.length === 0) return null;
|
|
4208
|
+
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);
|
|
4209
|
+
return formatHex(oklchToRgb(centre));
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
// src/estate/index.ts
|
|
4213
|
+
var ESTATE_VERSION = 1;
|
|
4214
|
+
var SIBLING_AT = 0.31;
|
|
4215
|
+
function emptyEstate() {
|
|
4216
|
+
return { version: ESTATE_VERSION, sites: [] };
|
|
4217
|
+
}
|
|
4218
|
+
function addSite(register, site) {
|
|
4219
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
4220
|
+
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
4221
|
+
return { version: ESTATE_VERSION, sites };
|
|
4222
|
+
}
|
|
4223
|
+
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
4224
|
+
const out = [];
|
|
4225
|
+
const close = (k) => d.parts[k] <= 0.2;
|
|
4226
|
+
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
4227
|
+
if (close("type")) {
|
|
4228
|
+
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
4229
|
+
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
4230
|
+
}
|
|
4231
|
+
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
4232
|
+
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
4233
|
+
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
4234
|
+
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
4235
|
+
if (close("layout")) out.push("running order");
|
|
4236
|
+
return out;
|
|
4237
|
+
}
|
|
4238
|
+
function compareToEstate(target2, register, options = {}) {
|
|
4239
|
+
const at = options.siblingAt ?? SIBLING_AT;
|
|
4240
|
+
return register.sites.filter((s) => s.id !== options.exclude).map((s) => {
|
|
4241
|
+
const d = fingerprintDistance(target2, s.fingerprint);
|
|
4242
|
+
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
4243
|
+
}).sort((a, b) => a.distance - b.distance);
|
|
4244
|
+
}
|
|
4245
|
+
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
4246
|
+
const out = [];
|
|
4247
|
+
const s = register.sites;
|
|
4248
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
4249
|
+
for (let j = i + 1; j < s.length; j += 1) {
|
|
4250
|
+
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
4251
|
+
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) });
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
return out.sort((x, y) => x.distance - y.distance);
|
|
4255
|
+
}
|
|
4256
|
+
function siblingLine(briefs) {
|
|
4257
|
+
const d = [];
|
|
4258
|
+
for (const runs of briefs) {
|
|
4259
|
+
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);
|
|
4260
|
+
}
|
|
4261
|
+
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
4262
|
+
d.sort((a, b) => a - b);
|
|
4263
|
+
const mid = Math.floor(d.length / 2);
|
|
4264
|
+
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
4265
|
+
return Math.round(m * 1e3) / 1e3;
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
// src/report/index.ts
|
|
4269
|
+
var TELL_HEAVY = 3;
|
|
4270
|
+
var MIN_DECIDED = 5;
|
|
4271
|
+
var TELL_AREA = {
|
|
4272
|
+
"reflex-font": { area: "type", choice: "display" },
|
|
4273
|
+
"reflex-font-2": { area: "type", choice: "display" },
|
|
4274
|
+
"italic-serif-display": { area: "type", choice: "display" },
|
|
4275
|
+
"ai-violet": { area: "colour", choice: "accent" },
|
|
4276
|
+
"blue-purple-gradient": { area: "colour", choice: "accent" },
|
|
4277
|
+
"gradient-text": { area: "colour", choice: "accent" },
|
|
4278
|
+
"cream-palette": { area: "colour", choice: "ground" },
|
|
4279
|
+
"pill-everything": { area: "shape", choice: "shape" },
|
|
4280
|
+
"shadcn-dump": { area: "shape", choice: "shape" },
|
|
4281
|
+
"glass-panel": { area: "effects", choice: "motif" },
|
|
4282
|
+
"radial-spotlight-glow": { area: "effects", choice: "motif" },
|
|
4283
|
+
"grid-background": { area: "effects", choice: "motif" },
|
|
4284
|
+
"thin-border-wide-shadow": { area: "effects", choice: "shape" },
|
|
4285
|
+
"icon-tile-stack": { area: "effects", choice: "motif" },
|
|
4286
|
+
"reveal-everywhere": { area: "motion", choice: "signature" },
|
|
4287
|
+
"intro-cinematic": { area: "motion", choice: "signature" },
|
|
4288
|
+
marquee: { area: "motion", choice: "signature" },
|
|
4289
|
+
"hero-then-proof": { area: "layout" },
|
|
4290
|
+
"icon-tile-grid": { area: "layout" },
|
|
4291
|
+
"hero-eyebrow-chip": { area: "layout" },
|
|
4292
|
+
"bento-grid": { area: "layout" }
|
|
4293
|
+
};
|
|
4294
|
+
var PART_AREA = {
|
|
4295
|
+
display: { area: "type", choice: "display" },
|
|
4296
|
+
body: { area: "type", choice: "body" },
|
|
4297
|
+
accent: { area: "colour", choice: "accent" },
|
|
4298
|
+
ground: { area: "colour", choice: "ground" },
|
|
4299
|
+
shape: { area: "shape", choice: "shape" },
|
|
4300
|
+
effects: { area: "effects", choice: "motif" },
|
|
4301
|
+
motion: { area: "motion", choice: "signature" }
|
|
4302
|
+
};
|
|
4303
|
+
var DEFAULTS = /^(?:ground )?(?:white|grey)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons/;
|
|
4304
|
+
function designFindings(findings, copyTells) {
|
|
4305
|
+
return (findings?.findings ?? []).filter((f3) => !copyTells.has(f3.tell));
|
|
4306
|
+
}
|
|
4307
|
+
function characterReport(input, copyTells = /* @__PURE__ */ new Set()) {
|
|
4308
|
+
const actions = [];
|
|
4309
|
+
const signals = [];
|
|
4310
|
+
if (input.findings) {
|
|
4311
|
+
const design = designFindings(input.findings, copyTells);
|
|
4312
|
+
const distinct = [...new Set(design.map((f3) => f3.tell))];
|
|
4313
|
+
signals.push({
|
|
4314
|
+
id: "tells",
|
|
4315
|
+
raised: distinct.length >= TELL_HEAVY,
|
|
4316
|
+
detail: `${distinct.length} distinct design tell${distinct.length === 1 ? "" : "s"}${distinct.length ? `: ${distinct.join(", ")}` : ""}`
|
|
4317
|
+
});
|
|
4318
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4319
|
+
for (const f3 of design) {
|
|
4320
|
+
if (seen.has(f3.tell)) continue;
|
|
4321
|
+
seen.add(f3.tell);
|
|
4322
|
+
const map = TELL_AREA[f3.tell] ?? { area: "effects" };
|
|
4323
|
+
const both = design.some((g) => g.tell === f3.tell && g.line === 0) && design.some((g) => g.tell === f3.tell && g.line > 0);
|
|
4324
|
+
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 });
|
|
4325
|
+
}
|
|
4326
|
+
const copy = (input.findings.findings ?? []).filter((f3) => copyTells.has(f3.tell));
|
|
4327
|
+
const byTell = /* @__PURE__ */ new Map();
|
|
4328
|
+
for (const f3 of copy) byTell.set(f3.tell, [...byTell.get(f3.tell) ?? [], f3]);
|
|
4329
|
+
for (const [tell, list] of byTell) {
|
|
4330
|
+
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 });
|
|
4331
|
+
}
|
|
4332
|
+
} else {
|
|
4333
|
+
signals.push({ id: "tells", raised: null, detail: "not measured" });
|
|
4334
|
+
}
|
|
4335
|
+
if (input.typicality) {
|
|
4336
|
+
const t = input.typicality;
|
|
4337
|
+
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` });
|
|
4338
|
+
if (t.typical) {
|
|
4339
|
+
for (const s of t.shared) {
|
|
4340
|
+
const map = PART_AREA[s.part];
|
|
4341
|
+
if (!map || DEFAULTS.test(`${s.part} ${s.value}`)) continue;
|
|
4342
|
+
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 });
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
} else {
|
|
4346
|
+
signals.push({ id: "typicality", raised: null, detail: "not measured: no null model for this brief" });
|
|
4347
|
+
}
|
|
4348
|
+
if (input.estate) {
|
|
4349
|
+
const siblings = input.estate.filter((m) => m.sibling);
|
|
4350
|
+
const nearest2 = input.estate[0];
|
|
4351
|
+
signals.push({
|
|
4352
|
+
id: "estate",
|
|
4353
|
+
raised: siblings.length > 0,
|
|
4354
|
+
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"
|
|
4355
|
+
});
|
|
4356
|
+
for (const s of siblings) {
|
|
4357
|
+
for (const part of s.shared) {
|
|
4358
|
+
if (DEFAULTS.test(part)) continue;
|
|
4359
|
+
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) });
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
} else {
|
|
4363
|
+
signals.push({ id: "estate", raised: null, detail: "not measured: no estate register" });
|
|
4364
|
+
}
|
|
4365
|
+
if (input.direction) {
|
|
4366
|
+
const d = input.direction;
|
|
4367
|
+
const raised2 = !d.valid || d.decided < MIN_DECIDED;
|
|
4368
|
+
signals.push({ id: "reasons", raised: raised2, detail: `${d.decided} of 7 choices decided with a reason${d.valid ? "" : ", art-direction.json has errors"}` });
|
|
4369
|
+
for (const p of d.problems) {
|
|
4370
|
+
const key = p.at.match(/^choices\.(\w+)/)?.[1];
|
|
4371
|
+
if (p.severity === "error" || p.message === "not decided yet") {
|
|
4372
|
+
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 });
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
} else {
|
|
4376
|
+
signals.push({ id: "reasons", raised: null, detail: "not measured: no art-direction.json" });
|
|
4377
|
+
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 });
|
|
4378
|
+
}
|
|
4379
|
+
const raised = signals.filter((s) => s.raised === true).length;
|
|
4380
|
+
const unmeasured = signals.filter((s) => s.raised === null).map((s) => s.id);
|
|
4381
|
+
const verdict = raised >= 2 ? "default" : raised === 1 ? "mixed" : unmeasured.length ? "unproven" : "decided";
|
|
4382
|
+
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.`;
|
|
4383
|
+
const order = ["direction", "type", "colour", "shape", "effects", "motion", "layout", "copy"];
|
|
4384
|
+
const merged = [];
|
|
4385
|
+
for (const a of actions) {
|
|
4386
|
+
const same = a.choice && a.area !== "direction" && a.area !== "copy" ? merged.find((m) => m.choice === a.choice && m.area === a.area) : void 0;
|
|
4387
|
+
if (same) {
|
|
4388
|
+
const fromTell = /\(\S+, gen \d\)/.test(a.why);
|
|
4389
|
+
same.why = `${same.why}; ${fromTell ? a.why : `${a.what.replace(/\.$/, "")} (${a.why})`}`;
|
|
4390
|
+
same.priority = Math.min(same.priority, a.priority);
|
|
4391
|
+
} else merged.push({ ...a });
|
|
4392
|
+
}
|
|
4393
|
+
const unique = merged.filter((a, i) => merged.findIndex((b) => b.area === a.area && b.what === a.what) === i);
|
|
4394
|
+
unique.sort((a, b) => a.priority - b.priority || order.indexOf(a.area) - order.indexOf(b.area));
|
|
4395
|
+
return { site: input.site, verdict, summary, signals, actions: unique };
|
|
4396
|
+
}
|
|
4397
|
+
function areaOfShared(part) {
|
|
4398
|
+
if (/headline|body$/.test(part)) return "type";
|
|
4399
|
+
if (/accent|ground/.test(part)) return "colour";
|
|
4400
|
+
if (/buttons/.test(part)) return "shape";
|
|
4401
|
+
if (/reveal/.test(part)) return "motion";
|
|
4402
|
+
if (/running order/.test(part)) return "layout";
|
|
4403
|
+
return "effects";
|
|
4404
|
+
}
|
|
4405
|
+
function choiceOfShared(part) {
|
|
4406
|
+
if (/headline/.test(part)) return "display";
|
|
4407
|
+
if (/body$/.test(part)) return "body";
|
|
4408
|
+
if (/accent/.test(part)) return "accent";
|
|
4409
|
+
if (/ground/.test(part)) return "ground";
|
|
4410
|
+
if (/buttons/.test(part)) return "shape";
|
|
4411
|
+
if (/reveal/.test(part)) return "signature";
|
|
4412
|
+
return /running order/.test(part) ? void 0 : "motif";
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
// src/report/retrofit.ts
|
|
4416
|
+
var HEADINGS = {
|
|
4417
|
+
direction: "Decide first",
|
|
4418
|
+
type: "Type",
|
|
4419
|
+
colour: "Colour",
|
|
4420
|
+
shape: "Shape",
|
|
4421
|
+
effects: "Effects",
|
|
4422
|
+
motion: "Motion",
|
|
4423
|
+
layout: "How sections look (not where they sit)",
|
|
4424
|
+
copy: "Copy"
|
|
4425
|
+
};
|
|
4426
|
+
var ORDER2 = ["direction", "type", "colour", "shape", "effects", "motion", "layout", "copy"];
|
|
4427
|
+
function target(a, direction) {
|
|
4428
|
+
if (!a.choice || a.area === "direction") return "";
|
|
4429
|
+
const c = direction?.choices?.[a.choice];
|
|
4430
|
+
if (c && c.because && !c.because.startsWith("PROPOSED:")) return `
|
|
4431
|
+
- Use: **${c.value}** for ${a.choice}. ${c.because}`;
|
|
4432
|
+
return `
|
|
4433
|
+
- Waits on: deciding **${a.choice}** in art-direction.json.`;
|
|
4434
|
+
}
|
|
4435
|
+
function retrofitPlan(report2, direction) {
|
|
4436
|
+
const lines = [
|
|
4437
|
+
`# Retrofit: ${report2.site}`,
|
|
4438
|
+
"",
|
|
4439
|
+
`**Now:** ${report2.verdict}. ${report2.summary}`,
|
|
4440
|
+
"",
|
|
4441
|
+
"| Signal | Raised | Reading |",
|
|
4442
|
+
"|---|---|---|",
|
|
4443
|
+
...report2.signals.map((s) => `| ${s.id} | ${s.raised === null ? "not measured" : s.raised ? "yes" : "no"} | ${s.detail} |`),
|
|
4444
|
+
"",
|
|
4445
|
+
"**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.",
|
|
4446
|
+
""
|
|
4447
|
+
];
|
|
4448
|
+
let n = 0;
|
|
4449
|
+
for (const area of ORDER2) {
|
|
4450
|
+
const list = report2.actions.filter((a) => a.area === area);
|
|
4451
|
+
if (list.length === 0) continue;
|
|
4452
|
+
n += 1;
|
|
4453
|
+
lines.push(`## ${n}. ${HEADINGS[area]}`, "");
|
|
4454
|
+
for (const a of list) lines.push(`- [ ] ${a.what} *(${a.why})*${target(a, direction)}`);
|
|
4455
|
+
lines.push("");
|
|
4456
|
+
}
|
|
4457
|
+
if (n === 0) lines.push("Nothing to change.", "");
|
|
4458
|
+
lines.push(
|
|
4459
|
+
"## Leave alone",
|
|
4460
|
+
"",
|
|
4461
|
+
"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.",
|
|
4462
|
+
""
|
|
4463
|
+
);
|
|
4464
|
+
return lines.join("\n");
|
|
4465
|
+
}
|
|
3956
4466
|
export {
|
|
3957
4467
|
AI_PHRASES,
|
|
3958
4468
|
AI_VIOLET,
|
|
@@ -3969,12 +4479,16 @@ export {
|
|
|
3969
4479
|
DURATION_S,
|
|
3970
4480
|
EASE,
|
|
3971
4481
|
EASE_TUPLE,
|
|
4482
|
+
ESTATE_VERSION,
|
|
3972
4483
|
EXIT_RATIO,
|
|
3973
4484
|
FINGERPRINT_VERSION,
|
|
3974
4485
|
HOUSE_BUDGET,
|
|
3975
4486
|
HOUSE_TYPE,
|
|
3976
4487
|
LIGHTNESS_CURVE,
|
|
4488
|
+
MIN_DECIDED,
|
|
4489
|
+
MIN_RUNS,
|
|
3977
4490
|
NEGATIVE_REASSURANCE,
|
|
4491
|
+
NULL_VERSION,
|
|
3978
4492
|
PILL_LIMIT,
|
|
3979
4493
|
PLAINER_WORDS,
|
|
3980
4494
|
PROPOSED,
|
|
@@ -3987,27 +4501,37 @@ export {
|
|
|
3987
4501
|
SCALE,
|
|
3988
4502
|
SHADCN_LIMIT,
|
|
3989
4503
|
SHADCN_PRIMITIVES,
|
|
4504
|
+
SIBLING_AT,
|
|
3990
4505
|
SNAPSHOT_VERSION,
|
|
3991
4506
|
SOURCE_KINDS,
|
|
3992
4507
|
SPACE_STEPS,
|
|
3993
4508
|
SPRING,
|
|
3994
4509
|
STOCK_PHRASES,
|
|
4510
|
+
TELL_HEAVY,
|
|
3995
4511
|
TYPE_STEPS,
|
|
4512
|
+
TYPICAL_AT,
|
|
3996
4513
|
VAGUE_WORDS,
|
|
3997
4514
|
accentFork,
|
|
4515
|
+
addSite,
|
|
3998
4516
|
apcaContrast,
|
|
3999
4517
|
auditSnapshot,
|
|
4000
4518
|
catalogueTable,
|
|
4519
|
+
characterReport,
|
|
4001
4520
|
checkCopy,
|
|
4002
4521
|
checkPair,
|
|
4003
4522
|
checkRestraint,
|
|
4523
|
+
choicesOf,
|
|
4524
|
+
compareToEstate,
|
|
4004
4525
|
craftTokens,
|
|
4005
4526
|
deltaEOk,
|
|
4006
4527
|
deltaEOkHex,
|
|
4007
4528
|
densityCss,
|
|
4008
4529
|
densityTokens,
|
|
4009
4530
|
emitCss,
|
|
4531
|
+
emptyEstate,
|
|
4532
|
+
estatePairs,
|
|
4010
4533
|
exitDuration,
|
|
4534
|
+
extractHtml,
|
|
4011
4535
|
findColours,
|
|
4012
4536
|
fingerprint,
|
|
4013
4537
|
fingerprintDistance,
|
|
@@ -4017,7 +4541,11 @@ export {
|
|
|
4017
4541
|
fontClass,
|
|
4018
4542
|
formatHex,
|
|
4019
4543
|
formatReport,
|
|
4544
|
+
groundName,
|
|
4545
|
+
harvest,
|
|
4020
4546
|
hexToOklch,
|
|
4547
|
+
hueName,
|
|
4548
|
+
hueSwatch,
|
|
4021
4549
|
inP3Gamut,
|
|
4022
4550
|
inSrgbGamut,
|
|
4023
4551
|
initDirection,
|
|
@@ -4027,6 +4555,7 @@ export {
|
|
|
4027
4555
|
motionTokens,
|
|
4028
4556
|
nearest,
|
|
4029
4557
|
normaliseFamily,
|
|
4558
|
+
nullPrompt,
|
|
4030
4559
|
oklchToHex,
|
|
4031
4560
|
oklchToRgb,
|
|
4032
4561
|
paletteFromPixels,
|
|
@@ -4035,16 +4564,22 @@ export {
|
|
|
4035
4564
|
propose,
|
|
4036
4565
|
ramp,
|
|
4037
4566
|
rampFromAnchors,
|
|
4567
|
+
retrofitPlan,
|
|
4038
4568
|
runTell,
|
|
4039
4569
|
scanSource,
|
|
4040
4570
|
sectionRhythm,
|
|
4041
4571
|
semantic,
|
|
4572
|
+
shapeName,
|
|
4573
|
+
sharedParts,
|
|
4042
4574
|
shouldAnimate,
|
|
4575
|
+
siblingLine,
|
|
4043
4576
|
tailwindV3Preset,
|
|
4044
4577
|
tailwindV4Theme,
|
|
4045
4578
|
tellById,
|
|
4046
4579
|
toGamut,
|
|
4047
4580
|
typeFeatureTokens,
|
|
4581
|
+
typicality,
|
|
4048
4582
|
validateDirection,
|
|
4049
|
-
wcagContrast
|
|
4583
|
+
wcagContrast,
|
|
4584
|
+
wordListsJson
|
|
4050
4585
|
};
|