@domandigital/craft 0.11.0 → 0.12.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/dist/index.cjs CHANGED
@@ -37,13 +37,16 @@ __export(index_exports, {
37
37
  DURATION_S: () => DURATION_S,
38
38
  EASE: () => EASE,
39
39
  EASE_TUPLE: () => EASE_TUPLE,
40
+ EDGE_STEP: () => EDGE_STEP,
40
41
  ESTATE_VERSION: () => ESTATE_VERSION,
41
42
  EXIT_RATIO: () => EXIT_RATIO,
42
43
  FINGERPRINT_VERSION: () => FINGERPRINT_VERSION,
43
44
  HOUSE: () => HOUSE,
44
45
  HOUSE_BUDGET: () => HOUSE_BUDGET,
46
+ HOUSE_LOCKED: () => HOUSE_LOCKED,
45
47
  HOUSE_TYPE: () => HOUSE_TYPE,
46
48
  JSON_SCHEMA_VERSION: () => JSON_SCHEMA_VERSION,
49
+ LICENCES: () => LICENCES,
47
50
  LIGHTNESS_CURVE: () => LIGHTNESS_CURVE,
48
51
  MIN_DECIDED: () => MIN_DECIDED,
49
52
  MIN_RUNS: () => MIN_RUNS,
@@ -53,6 +56,7 @@ __export(index_exports, {
53
56
  PLAINER_WORDS: () => PLAINER_WORDS,
54
57
  PROPOSED: () => PROPOSED,
55
58
  RAMP_STEPS: () => RAMP_STEPS,
59
+ READABLE_SNAPSHOT_VERSIONS: () => READABLE_SNAPSHOT_VERSIONS,
56
60
  REFLEX_FONTS_1: () => REFLEX_FONTS_1,
57
61
  REFLEX_FONTS_2: () => REFLEX_FONTS_2,
58
62
  RENDERED_PATHS: () => RENDERED_PATHS,
@@ -97,6 +101,7 @@ __export(index_exports, {
97
101
  estatePairs: () => estatePairs,
98
102
  exitDuration: () => exitDuration,
99
103
  extractHtml: () => extractHtml,
104
+ faceLicence: () => faceLicence,
100
105
  findClaims: () => findClaims,
101
106
  findColours: () => findColours,
102
107
  findingKey: () => findingKey,
@@ -114,6 +119,7 @@ __export(index_exports, {
114
119
  groundName: () => groundName,
115
120
  harvest: () => harvest,
116
121
  hexToOklch: () => hexToOklch,
122
+ holdHouseBlocks: () => holdHouseBlocks,
117
123
  houseRule: () => houseRule,
118
124
  hueName: () => hueName,
119
125
  hueSwatch: () => hueSwatch,
@@ -123,6 +129,8 @@ __export(index_exports, {
123
129
  initDirection: () => initDirection,
124
130
  isAiViolet: () => isAiViolet,
125
131
  isCream: () => isCream,
132
+ layoutDistance: () => layoutDistance,
133
+ licencesJson: () => licencesJson,
126
134
  makeSnapshot: () => makeSnapshot,
127
135
  motionTokens: () => motionTokens,
128
136
  namedSources: () => namedSources,
@@ -140,6 +148,7 @@ __export(index_exports, {
140
148
  protectedFacts: () => protectedFacts,
141
149
  ramp: () => ramp,
142
150
  rampFromAnchors: () => rampFromAnchors,
151
+ readSnapshot: () => readSnapshot,
143
152
  retrofitPlan: () => retrofitPlan,
144
153
  runTell: () => runTell,
145
154
  scanSource: () => scanSource,
@@ -160,6 +169,7 @@ __export(index_exports, {
160
169
  typicality: () => typicality,
161
170
  validateDirection: () => validateDirection,
162
171
  visibleText: () => visibleText,
172
+ visualMeasures: () => visualMeasures,
163
173
  wcagContrast: () => wcagContrast,
164
174
  wordListsJson: () => wordListsJson
165
175
  });
@@ -2909,6 +2919,7 @@ var REFLEX_FONTS_2 = [
2909
2919
  "Plus Jakarta Sans",
2910
2920
  "Outfit",
2911
2921
  "Geist",
2922
+ "Cal Sans",
2912
2923
  "Sora",
2913
2924
  "Instrument Sans",
2914
2925
  "Instrument Serif",
@@ -3130,7 +3141,7 @@ function iconTiles(file) {
3130
3141
  return sized && has(unit, /^rounded-(?:md|lg|xl|2xl|full)$/) && has(unit, /^(?:items-center|place-items-center|place-content-center)$/) && has(unit, /^bg-/);
3131
3142
  });
3132
3143
  }
3133
- var ICON_LIBRARY = /from\s+["'](?:lucide-react|@heroicons\/react[^"']*|react-icons[^"']*|@phosphor-icons\/react|@tabler\/icons-react|@radix-ui\/react-icons)["']/;
3144
+ var ICON_LIBRARY = /from\s+["'](?:lucide-react|@heroicons\/react[^"']*|react-icons[^"']*|@phosphor-icons\/react|@tabler\/icons-react|@radix-ui\/react-icons|@iconscout\/react-unicons[^"']*|@mui\/icons-material[^"']*|react-material-symbols[^"']*)["']/;
3134
3145
  function iconTileStackHits(ctx) {
3135
3146
  const hits = [];
3136
3147
  for (const file of ctx.files) {
@@ -3277,6 +3288,12 @@ function shadcnHits(ctx) {
3277
3288
  const detail = unused.length > 0 ? `; ${unused.length} imported nowhere` : "";
3278
3289
  return [{ path: ui[0].path, offset: 0, message: `${ui.length} stock shadcn primitives${detail}` }];
3279
3290
  }
3291
+ function shadcnCardHits(ctx) {
3292
+ return eachUnit(
3293
+ ctx,
3294
+ (unit) => has(unit, /^bg-card$/) && has(unit, /^text-card-foreground$/) && has(unit, /^border$/) && has(unit, /^rounded-(?:lg|xl)$/) && has(unit, /^shadow(?:-sm)?$/) ? "the registry Card recipe, unchanged" : null
3295
+ );
3296
+ }
3280
3297
  function marqueeHits(ctx) {
3281
3298
  const hits = [];
3282
3299
  const pattern = /@keyframes\s+[\w-]*marquee|animate-(?:marquee|scroll-x|infinite-scroll)\b|<Marquee\b|["']react-fast-marquee["']|ui\/marquee["']|animation(?:-name)?\s*:\s*[^;]*marquee/i;
@@ -3494,6 +3511,23 @@ const inter = Inter({ subsets: ["latin"] });`),
3494
3511
  pass: [f3("src/components/ui/button.tsx", `export function Button() { return null; }`)]
3495
3512
  }
3496
3513
  },
3514
+ {
3515
+ id: "shadcn-card-stock",
3516
+ name: "Stock shadcn Card",
3517
+ generation: 1,
3518
+ severity: "warn",
3519
+ surface: "source",
3520
+ why: "The registry Card (bg-card, a plain border, rounded-lg or rounded-xl, shadow-sm) is the container every generated page is built from. Left as shipped, every card on the site looks like every other shadcn site's.",
3521
+ fix: "Give the card this site's radius and shadow from the restraint budget, or drop the shadow and let a visible border carry it.",
3522
+ detect: shadcnCardHits,
3523
+ fixtures: {
3524
+ flag: [
3525
+ f3("components/ui/card.tsx", `className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}`),
3526
+ f3("components/ui/card.tsx", `className={cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className)}`)
3527
+ ],
3528
+ pass: [f3("components/ui/card.tsx", `className={cn("rounded-sm border-2 border-ink bg-card text-card-foreground", className)}`)]
3529
+ }
3530
+ },
3497
3531
  {
3498
3532
  id: "cream-palette",
3499
3533
  name: "Cream ground",
@@ -3720,6 +3754,17 @@ var HOUSE = {
3720
3754
  function houseRule(id, name = id) {
3721
3755
  return HOUSE[id] ?? { tier: "review", label: name };
3722
3756
  }
3757
+ var HOUSE_LOCKED = "the house blocking tier cannot be lowered by a repo. A genuine one-off takes copy-ok on its line; a rule that is wrong is changed in craft's house.ts and COPY.md";
3758
+ function holdHouseBlocks(exceptions, severity) {
3759
+ const blocks = (tell) => HOUSE[tell]?.tier === "block";
3760
+ const refusedSeverity = Object.entries(severity).filter(([tell, o]) => blocks(tell) && o.level !== "block").map(([tell]) => tell);
3761
+ return {
3762
+ exceptions: exceptions.filter((e) => !blocks(e.tell)),
3763
+ refused: exceptions.filter((e) => blocks(e.tell)),
3764
+ severity: Object.fromEntries(Object.entries(severity).filter(([tell]) => !refusedSeverity.includes(tell))),
3765
+ refusedSeverity
3766
+ };
3767
+ }
3723
3768
 
3724
3769
  // src/character/parse.ts
3725
3770
  var MARKUP = /\.(tsx|jsx|html?|astro|vue|svelte|mdx)$/i;
@@ -4053,7 +4098,7 @@ function extractStrings(file) {
4053
4098
  }
4054
4099
 
4055
4100
  // src/snapshot/types.ts
4056
- var SNAPSHOT_VERSION = 1;
4101
+ var SNAPSHOT_VERSION = 2;
4057
4102
 
4058
4103
  // src/snapshot/fixture.ts
4059
4104
  function makeSnapshot(patch = {}) {
@@ -4387,7 +4432,7 @@ var RENDERED_PATHS = {
4387
4432
  };
4388
4433
 
4389
4434
  // src/character/check.ts
4390
- var CATALOGUE_VERSION = "2026.09.8";
4435
+ var CATALOGUE_VERSION = "2026.09.9";
4391
4436
  var CATALOGUE = [...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map(
4392
4437
  (t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t
4393
4438
  );
@@ -4954,8 +4999,79 @@ function toJson(data) {
4954
4999
  return JSON.stringify({ schemaVersion: JSON_SCHEMA_VERSION, ...data }, null, 2);
4955
5000
  }
4956
5001
 
5002
+ // src/snapshot/migrate.ts
5003
+ var READABLE_SNAPSHOT_VERSIONS = [1, 2];
5004
+ function readSnapshot(data, source = "snapshot") {
5005
+ const v = data?.version;
5006
+ if (typeof data !== "object" || data === null || !READABLE_SNAPSHOT_VERSIONS.includes(v)) {
5007
+ throw new Error(`${source} is not a snapshot this craft reads (version ${String(v)}; reads ${READABLE_SNAPSHOT_VERSIONS.join(" and ")})`);
5008
+ }
5009
+ const snap = data;
5010
+ if (snap.version === 1) return { ...snap, version: SNAPSHOT_VERSION, migratedFrom: 1 };
5011
+ return snap;
5012
+ }
5013
+
5014
+ // src/snapshot/visual.ts
5015
+ var EDGE_STEP = 32;
5016
+ function visualMeasures(img, maxSamples = 1e6) {
5017
+ const { width: w, height: h, rgba } = img;
5018
+ if (w < 2 || h < 2 || rgba.length < w * h * 4) throw new Error(`visualMeasures: ${w}x${h} image with ${rgba.length} bytes`);
5019
+ const step = Math.max(1, Math.ceil(Math.sqrt(w * h / maxSamples)));
5020
+ const lum = (x, y) => {
5021
+ const i = (y * w + x) * 4;
5022
+ return 0.2126 * rgba[i] + 0.7152 * rgba[i + 1] + 0.0722 * rgba[i + 2];
5023
+ };
5024
+ let n = 0;
5025
+ let sumRg = 0;
5026
+ let sumYb = 0;
5027
+ let sumRg2 = 0;
5028
+ let sumYb2 = 0;
5029
+ let edges = 0;
5030
+ let edgeN = 0;
5031
+ let asym = 0;
5032
+ let asymN = 0;
5033
+ for (let y = 0; y < h; y += step) {
5034
+ for (let x = 0; x < w; x += step) {
5035
+ const i = (y * w + x) * 4;
5036
+ const r = rgba[i];
5037
+ const g = rgba[i + 1];
5038
+ const b = rgba[i + 2];
5039
+ const rg = r - g;
5040
+ const yb = 0.5 * (r + g) - b;
5041
+ sumRg += rg;
5042
+ sumYb += yb;
5043
+ sumRg2 += rg * rg;
5044
+ sumYb2 += yb * yb;
5045
+ n += 1;
5046
+ const l = lum(x, y);
5047
+ if (x + step < w && y + step < h) {
5048
+ if (Math.abs(lum(x + step, y) - l) + Math.abs(lum(x, y + step) - l) > EDGE_STEP) edges += 1;
5049
+ edgeN += 1;
5050
+ }
5051
+ if (x < w / 2) {
5052
+ asym += Math.abs(l - lum(w - 1 - x, y));
5053
+ asymN += 1;
5054
+ }
5055
+ }
5056
+ }
5057
+ const meanRg = sumRg / n;
5058
+ const meanYb = sumYb / n;
5059
+ const sdRg = Math.sqrt(Math.max(0, sumRg2 / n - meanRg * meanRg));
5060
+ const sdYb = Math.sqrt(Math.max(0, sumYb2 / n - meanYb * meanYb));
5061
+ const colourfulness = Math.sqrt(sdRg ** 2 + sdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);
5062
+ const round2 = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
5063
+ return {
5064
+ colourfulness: round2(colourfulness, 1),
5065
+ edgeDensity: round2(edgeN ? edges / edgeN : 0),
5066
+ symmetry: round2(asymN ? 1 - asym / asymN / 255 : 1),
5067
+ width: w,
5068
+ height: h,
5069
+ sampled: n
5070
+ };
5071
+ }
5072
+
4957
5073
  // src/fingerprint/index.ts
4958
- var FINGERPRINT_VERSION = 1;
5074
+ var FINGERPRINT_VERSION = 2;
4959
5075
  var CHROMATIC = 0.03;
4960
5076
  var MIN_ACCENT_AREA = 2e4;
4961
5077
  var oklch = (value) => {
@@ -5010,23 +5126,36 @@ function fingerprint(s) {
5010
5126
  roundness: median(s.controls.map((c) => Math.min(0.5, c.radiusPx / Math.max(1, c.heightPx)))),
5011
5127
  motion: below ? Math.min(1, s.motion.hiddenSections / below) : 0,
5012
5128
  effects: effects.sort(),
5013
- layout: s.sections.map((x) => x.kind)
5129
+ layout: s.sections.map((x) => x.kind),
5130
+ ...s.sections.length > 0 && s.sections.every((x) => x.role && x.geometry) ? { sections: s.sections.map((x) => ({ role: x.role, centred: x.geometry.centredShare, width: x.geometry.contentWidthRatio })) } : {}
5014
5131
  };
5015
5132
  }
5016
5133
  var DISTANCE_WEIGHTS = { accent: 0.25, type: 0.25, ground: 0.1, shape: 0.1, motion: 0.1, effects: 0.1, layout: 0.1 };
5017
- function levenshtein(a, b) {
5134
+ function levenshtein(a, b, cost = (x, y) => x === y ? 0 : 1) {
5018
5135
  const row = Array.from({ length: b.length + 1 }, (_, i) => i);
5019
5136
  for (let i = 1; i <= a.length; i += 1) {
5020
5137
  let prev = row[0];
5021
5138
  row[0] = i;
5022
5139
  for (let j = 1; j <= b.length; j += 1) {
5023
5140
  const cur = row[j];
5024
- row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
5141
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + cost(a[i - 1], b[j - 1]));
5025
5142
  prev = cur;
5026
5143
  }
5027
5144
  }
5028
5145
  return row[b.length];
5029
5146
  }
5147
+ function sectionCost(x, y) {
5148
+ if (x.role !== y.role) return 1;
5149
+ return Math.min(0.5, (Math.abs(x.centred - y.centred) + Math.abs(x.width - y.width)) / 2);
5150
+ }
5151
+ function layoutDistance(a, b) {
5152
+ if (a.sections && b.sections) {
5153
+ const longest2 = Math.max(a.sections.length, b.sections.length);
5154
+ return longest2 === 0 ? 0 : levenshtein(a.sections, b.sections, sectionCost) / longest2;
5155
+ }
5156
+ const longest = Math.max(a.layout.length, b.layout.length);
5157
+ return longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
5158
+ }
5030
5159
  var clamp = (n) => Math.max(0, Math.min(1, n));
5031
5160
  function faceDistance(a, b) {
5032
5161
  if (a.family.toLowerCase() === b.family.toLowerCase()) return 0;
@@ -5041,8 +5170,7 @@ function fingerprintDistance(a, b) {
5041
5170
  const union = /* @__PURE__ */ new Set([...a.effects, ...b.effects]);
5042
5171
  const shared = a.effects.filter((e) => b.effects.includes(e)).length;
5043
5172
  const effects = union.size === 0 ? 0 : 1 - shared / union.size;
5044
- const longest = Math.max(a.layout.length, b.layout.length);
5045
- const layout = longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
5173
+ const layout = layoutDistance(a, b);
5046
5174
  const parts2 = { accent, ground, type, shape, motion, effects, layout };
5047
5175
  const total = Object.keys(DISTANCE_WEIGHTS).reduce((sum, k) => sum + DISTANCE_WEIGHTS[k] * parts2[k], 0);
5048
5176
  return { total: Math.round(total * 1e3) / 1e3, parts: parts2 };
@@ -5070,6 +5198,117 @@ var SOURCE_KINDS = [
5070
5198
  ];
5071
5199
  var CHOICE_KEYS = ["accent", "ground", "display", "body", "shape", "motif", "signature"];
5072
5200
 
5201
+ // src/direction/licences.ts
5202
+ var LICENCES = [
5203
+ {
5204
+ name: "Basteleur",
5205
+ subject: "face",
5206
+ licence: "OFL-1.1",
5207
+ multiClient: "yes",
5208
+ checked: "2026-09-24",
5209
+ source: "https://velvetyne.fr/fonts/basteleur/",
5210
+ note: "Velvetyne, designed by Keussel. Keep the OFL notice with the font files."
5211
+ },
5212
+ {
5213
+ name: "Gulax",
5214
+ subject: "face",
5215
+ licence: "OFL-1.1",
5216
+ multiClient: "yes",
5217
+ checked: "2026-09-24",
5218
+ source: "https://velvetyne.fr/fonts/gulax/",
5219
+ note: "Velvetyne. Keep the OFL notice with the font files."
5220
+ },
5221
+ {
5222
+ name: "Le Murmure",
5223
+ subject: "face",
5224
+ licence: "OFL-1.1",
5225
+ multiClient: "yes",
5226
+ checked: "2026-09-24",
5227
+ source: "https://velvetyne.fr/fonts/le-murmure/",
5228
+ note: "Velvetyne. Keep the OFL notice with the font files."
5229
+ },
5230
+ {
5231
+ name: "Blaze Type",
5232
+ subject: "foundry",
5233
+ licence: "commercial",
5234
+ multiClient: "per-site",
5235
+ cap: 10,
5236
+ checked: "2026-09-24",
5237
+ source: "https://blazetype.eu/eula",
5238
+ note: "The EULA sells a professional licence for 1 to 10 websites, and lets an agency buy in a client's name at the listed price. Its /license page says one licence covers every domain; the EULA is the contract, so buy per client."
5239
+ },
5240
+ {
5241
+ name: "Power Type",
5242
+ subject: "foundry",
5243
+ licence: "commercial",
5244
+ multiClient: "unverified",
5245
+ checked: "2026-09-24",
5246
+ source: "https://power-type.com/eula",
5247
+ note: "Research on 2026-09-11 reported a Super License capped at 10 self-hosted domains. The EULA page did not load when checked; read it before relying on the cap."
5248
+ },
5249
+ {
5250
+ name: "Grilli Type",
5251
+ subject: "foundry",
5252
+ licence: "commercial",
5253
+ multiClient: "per-site",
5254
+ checked: "2026-09-24",
5255
+ source: "https://www.grillitype.com/information",
5256
+ note: "Web licences cover any number of domains controlled by the licensee, priced by monthly visitors, so a client site needs the client's own licence. Unlimited tiers start at $10,000 per style."
5257
+ },
5258
+ {
5259
+ name: "Phosphor",
5260
+ subject: "icons",
5261
+ licence: "MIT",
5262
+ multiClient: "yes",
5263
+ checked: "2026-09-24",
5264
+ source: "https://github.com/phosphor-icons/core/blob/main/LICENSE",
5265
+ note: "Six weights. The icon-tile tells treat it exactly as they treat Lucide: the library is not the tell, the tinted tile is."
5266
+ },
5267
+ {
5268
+ name: "Tabler Icons",
5269
+ subject: "icons",
5270
+ licence: "MIT",
5271
+ multiClient: "yes",
5272
+ checked: "2026-09-24",
5273
+ source: "https://github.com/tabler/tabler-icons/blob/main/LICENSE",
5274
+ note: "One stroke grid across the set."
5275
+ },
5276
+ {
5277
+ name: "Iconoir",
5278
+ subject: "icons",
5279
+ licence: "MIT",
5280
+ multiClient: "yes",
5281
+ checked: "2026-09-24",
5282
+ source: "https://github.com/iconoir-icons/iconoir/blob/main/LICENSE",
5283
+ note: "Smaller set, no paid tier."
5284
+ },
5285
+ {
5286
+ name: "Unicons",
5287
+ subject: "icons",
5288
+ licence: "IconScout Simple License",
5289
+ multiClient: "yes",
5290
+ checked: "2026-09-24",
5291
+ source: "https://github.com/Iconscout/unicons/blob/master/LICENSE",
5292
+ note: "IconScout's free set, personal and commercial use, attribution optional."
5293
+ },
5294
+ {
5295
+ name: "IconScout premium",
5296
+ subject: "icons",
5297
+ licence: "commercial",
5298
+ multiClient: "per-site",
5299
+ checked: "2026-09-24",
5300
+ source: "https://iconscout.com/licenses",
5301
+ note: "Each licence covers a single end product. Never in a tool, template or shared kit (create-site, craft, a house component library), even modified. Record the assets each site uses. Assets come from different contributors, so take one site's set from one contributor."
5302
+ }
5303
+ ];
5304
+ function faceLicence(family) {
5305
+ const name = family.trim().toLowerCase();
5306
+ return LICENCES.find((l) => l.subject === "face" && l.name.toLowerCase() === name);
5307
+ }
5308
+ function licencesJson() {
5309
+ return JSON.stringify({ licences: LICENCES }, null, 2) + "\n";
5310
+ }
5311
+
5073
5312
  // src/direction/validate.ts
5074
5313
  var PREFERENCE = /\b(?:client (?:likes|liked|wants|wanted|asked|chose|prefers?)|(?:we|they|i) (?:like|liked|love|loved|prefer)|on[- ]trend|trendy|popular|what (?:everyone|competitors) (?:uses?|does)|brand (?:colou?r|font|guidelines?) (?:is|are|says?))\b/i;
5075
5314
  var MOOD = /\b(?:modern|clean|fresh|professional|trustworthy|premium|elegant|sleek|timeless|minimal(?:ist)?|bold|vibrant|friendly|approachable|luxur(?:y|ious)|sophisticated|contemporary|stylish|warm|inviting|high[- ]end)\b/gi;
@@ -5193,6 +5432,18 @@ function validateDirection(input, ctx = {}) {
5193
5432
  ok = false;
5194
5433
  }
5195
5434
  }
5435
+ if ((key === "display" || key === "body") && typeof c.value === "string" && c.value.trim()) {
5436
+ const family = normaliseFamily(c.value);
5437
+ const entry = faceLicence(family);
5438
+ if (!entry) {
5439
+ warn(`${at}.value`, `${family}: licence not in craft's register. Read the licensor's own terms for use on this client's site, then add it to src/direction/licences.ts.`);
5440
+ } else if (entry.multiClient === "unverified") {
5441
+ warn(`${at}.value`, `${family}: licence unverified (${entry.note})`);
5442
+ } else if (entry.multiClient === "capped" || entry.multiClient === "per-site") {
5443
+ const scope = entry.multiClient === "capped" ? `covers up to ${entry.cap} sites` : "needs a licence for this site, in the client's name";
5444
+ warn(`${at}.value`, `${family}: ${entry.licence} licence ${scope}. Record the purchase before launch.`);
5445
+ }
5446
+ }
5196
5447
  const fp = ctx.fingerprint;
5197
5448
  if (fp && typeof c.value === "string") {
5198
5449
  if ((key === "accent" || key === "ground") && parseColour(c.value)) {
@@ -5863,13 +6114,16 @@ function retrofitPlan(report2, direction) {
5863
6114
  DURATION_S,
5864
6115
  EASE,
5865
6116
  EASE_TUPLE,
6117
+ EDGE_STEP,
5866
6118
  ESTATE_VERSION,
5867
6119
  EXIT_RATIO,
5868
6120
  FINGERPRINT_VERSION,
5869
6121
  HOUSE,
5870
6122
  HOUSE_BUDGET,
6123
+ HOUSE_LOCKED,
5871
6124
  HOUSE_TYPE,
5872
6125
  JSON_SCHEMA_VERSION,
6126
+ LICENCES,
5873
6127
  LIGHTNESS_CURVE,
5874
6128
  MIN_DECIDED,
5875
6129
  MIN_RUNS,
@@ -5879,6 +6133,7 @@ function retrofitPlan(report2, direction) {
5879
6133
  PLAINER_WORDS,
5880
6134
  PROPOSED,
5881
6135
  RAMP_STEPS,
6136
+ READABLE_SNAPSHOT_VERSIONS,
5882
6137
  REFLEX_FONTS_1,
5883
6138
  REFLEX_FONTS_2,
5884
6139
  RENDERED_PATHS,
@@ -5923,6 +6178,7 @@ function retrofitPlan(report2, direction) {
5923
6178
  estatePairs,
5924
6179
  exitDuration,
5925
6180
  extractHtml,
6181
+ faceLicence,
5926
6182
  findClaims,
5927
6183
  findColours,
5928
6184
  findingKey,
@@ -5940,6 +6196,7 @@ function retrofitPlan(report2, direction) {
5940
6196
  groundName,
5941
6197
  harvest,
5942
6198
  hexToOklch,
6199
+ holdHouseBlocks,
5943
6200
  houseRule,
5944
6201
  hueName,
5945
6202
  hueSwatch,
@@ -5949,6 +6206,8 @@ function retrofitPlan(report2, direction) {
5949
6206
  initDirection,
5950
6207
  isAiViolet,
5951
6208
  isCream,
6209
+ layoutDistance,
6210
+ licencesJson,
5952
6211
  makeSnapshot,
5953
6212
  motionTokens,
5954
6213
  namedSources,
@@ -5966,6 +6225,7 @@ function retrofitPlan(report2, direction) {
5966
6225
  protectedFacts,
5967
6226
  ramp,
5968
6227
  rampFromAnchors,
6228
+ readSnapshot,
5969
6229
  retrofitPlan,
5970
6230
  runTell,
5971
6231
  scanSource,
@@ -5986,6 +6246,7 @@ function retrofitPlan(report2, direction) {
5986
6246
  typicality,
5987
6247
  validateDirection,
5988
6248
  visibleText,
6249
+ visualMeasures,
5989
6250
  wcagContrast,
5990
6251
  wordListsJson
5991
6252
  });