@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.js CHANGED
@@ -2742,6 +2742,7 @@ var REFLEX_FONTS_2 = [
2742
2742
  "Plus Jakarta Sans",
2743
2743
  "Outfit",
2744
2744
  "Geist",
2745
+ "Cal Sans",
2745
2746
  "Sora",
2746
2747
  "Instrument Sans",
2747
2748
  "Instrument Serif",
@@ -2963,7 +2964,7 @@ function iconTiles(file) {
2963
2964
  return sized && has(unit, /^rounded-(?:md|lg|xl|2xl|full)$/) && has(unit, /^(?:items-center|place-items-center|place-content-center)$/) && has(unit, /^bg-/);
2964
2965
  });
2965
2966
  }
2966
- var ICON_LIBRARY = /from\s+["'](?:lucide-react|@heroicons\/react[^"']*|react-icons[^"']*|@phosphor-icons\/react|@tabler\/icons-react|@radix-ui\/react-icons)["']/;
2967
+ 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[^"']*)["']/;
2967
2968
  function iconTileStackHits(ctx) {
2968
2969
  const hits = [];
2969
2970
  for (const file of ctx.files) {
@@ -3110,6 +3111,12 @@ function shadcnHits(ctx) {
3110
3111
  const detail = unused.length > 0 ? `; ${unused.length} imported nowhere` : "";
3111
3112
  return [{ path: ui[0].path, offset: 0, message: `${ui.length} stock shadcn primitives${detail}` }];
3112
3113
  }
3114
+ function shadcnCardHits(ctx) {
3115
+ return eachUnit(
3116
+ ctx,
3117
+ (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
3118
+ );
3119
+ }
3113
3120
  function marqueeHits(ctx) {
3114
3121
  const hits = [];
3115
3122
  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;
@@ -3327,6 +3334,23 @@ const inter = Inter({ subsets: ["latin"] });`),
3327
3334
  pass: [f3("src/components/ui/button.tsx", `export function Button() { return null; }`)]
3328
3335
  }
3329
3336
  },
3337
+ {
3338
+ id: "shadcn-card-stock",
3339
+ name: "Stock shadcn Card",
3340
+ generation: 1,
3341
+ severity: "warn",
3342
+ surface: "source",
3343
+ 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.",
3344
+ 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.",
3345
+ detect: shadcnCardHits,
3346
+ fixtures: {
3347
+ flag: [
3348
+ f3("components/ui/card.tsx", `className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}`),
3349
+ 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)}`)
3350
+ ],
3351
+ pass: [f3("components/ui/card.tsx", `className={cn("rounded-sm border-2 border-ink bg-card text-card-foreground", className)}`)]
3352
+ }
3353
+ },
3330
3354
  {
3331
3355
  id: "cream-palette",
3332
3356
  name: "Cream ground",
@@ -3553,6 +3577,17 @@ var HOUSE = {
3553
3577
  function houseRule(id, name = id) {
3554
3578
  return HOUSE[id] ?? { tier: "review", label: name };
3555
3579
  }
3580
+ 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";
3581
+ function holdHouseBlocks(exceptions, severity) {
3582
+ const blocks = (tell) => HOUSE[tell]?.tier === "block";
3583
+ const refusedSeverity = Object.entries(severity).filter(([tell, o]) => blocks(tell) && o.level !== "block").map(([tell]) => tell);
3584
+ return {
3585
+ exceptions: exceptions.filter((e) => !blocks(e.tell)),
3586
+ refused: exceptions.filter((e) => blocks(e.tell)),
3587
+ severity: Object.fromEntries(Object.entries(severity).filter(([tell]) => !refusedSeverity.includes(tell))),
3588
+ refusedSeverity
3589
+ };
3590
+ }
3556
3591
 
3557
3592
  // src/character/parse.ts
3558
3593
  var MARKUP = /\.(tsx|jsx|html?|astro|vue|svelte|mdx)$/i;
@@ -3886,7 +3921,7 @@ function extractStrings(file) {
3886
3921
  }
3887
3922
 
3888
3923
  // src/snapshot/types.ts
3889
- var SNAPSHOT_VERSION = 1;
3924
+ var SNAPSHOT_VERSION = 2;
3890
3925
 
3891
3926
  // src/snapshot/fixture.ts
3892
3927
  function makeSnapshot(patch = {}) {
@@ -4220,7 +4255,7 @@ var RENDERED_PATHS = {
4220
4255
  };
4221
4256
 
4222
4257
  // src/character/check.ts
4223
- var CATALOGUE_VERSION = "2026.09.8";
4258
+ var CATALOGUE_VERSION = "2026.09.9";
4224
4259
  var CATALOGUE = [...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map(
4225
4260
  (t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t
4226
4261
  );
@@ -4787,8 +4822,79 @@ function toJson(data) {
4787
4822
  return JSON.stringify({ schemaVersion: JSON_SCHEMA_VERSION, ...data }, null, 2);
4788
4823
  }
4789
4824
 
4825
+ // src/snapshot/migrate.ts
4826
+ var READABLE_SNAPSHOT_VERSIONS = [1, 2];
4827
+ function readSnapshot(data, source = "snapshot") {
4828
+ const v = data?.version;
4829
+ if (typeof data !== "object" || data === null || !READABLE_SNAPSHOT_VERSIONS.includes(v)) {
4830
+ throw new Error(`${source} is not a snapshot this craft reads (version ${String(v)}; reads ${READABLE_SNAPSHOT_VERSIONS.join(" and ")})`);
4831
+ }
4832
+ const snap = data;
4833
+ if (snap.version === 1) return { ...snap, version: SNAPSHOT_VERSION, migratedFrom: 1 };
4834
+ return snap;
4835
+ }
4836
+
4837
+ // src/snapshot/visual.ts
4838
+ var EDGE_STEP = 32;
4839
+ function visualMeasures(img, maxSamples = 1e6) {
4840
+ const { width: w, height: h, rgba } = img;
4841
+ if (w < 2 || h < 2 || rgba.length < w * h * 4) throw new Error(`visualMeasures: ${w}x${h} image with ${rgba.length} bytes`);
4842
+ const step = Math.max(1, Math.ceil(Math.sqrt(w * h / maxSamples)));
4843
+ const lum = (x, y) => {
4844
+ const i = (y * w + x) * 4;
4845
+ return 0.2126 * rgba[i] + 0.7152 * rgba[i + 1] + 0.0722 * rgba[i + 2];
4846
+ };
4847
+ let n = 0;
4848
+ let sumRg = 0;
4849
+ let sumYb = 0;
4850
+ let sumRg2 = 0;
4851
+ let sumYb2 = 0;
4852
+ let edges = 0;
4853
+ let edgeN = 0;
4854
+ let asym = 0;
4855
+ let asymN = 0;
4856
+ for (let y = 0; y < h; y += step) {
4857
+ for (let x = 0; x < w; x += step) {
4858
+ const i = (y * w + x) * 4;
4859
+ const r = rgba[i];
4860
+ const g = rgba[i + 1];
4861
+ const b = rgba[i + 2];
4862
+ const rg = r - g;
4863
+ const yb = 0.5 * (r + g) - b;
4864
+ sumRg += rg;
4865
+ sumYb += yb;
4866
+ sumRg2 += rg * rg;
4867
+ sumYb2 += yb * yb;
4868
+ n += 1;
4869
+ const l = lum(x, y);
4870
+ if (x + step < w && y + step < h) {
4871
+ if (Math.abs(lum(x + step, y) - l) + Math.abs(lum(x, y + step) - l) > EDGE_STEP) edges += 1;
4872
+ edgeN += 1;
4873
+ }
4874
+ if (x < w / 2) {
4875
+ asym += Math.abs(l - lum(w - 1 - x, y));
4876
+ asymN += 1;
4877
+ }
4878
+ }
4879
+ }
4880
+ const meanRg = sumRg / n;
4881
+ const meanYb = sumYb / n;
4882
+ const sdRg = Math.sqrt(Math.max(0, sumRg2 / n - meanRg * meanRg));
4883
+ const sdYb = Math.sqrt(Math.max(0, sumYb2 / n - meanYb * meanYb));
4884
+ const colourfulness = Math.sqrt(sdRg ** 2 + sdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);
4885
+ const round2 = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
4886
+ return {
4887
+ colourfulness: round2(colourfulness, 1),
4888
+ edgeDensity: round2(edgeN ? edges / edgeN : 0),
4889
+ symmetry: round2(asymN ? 1 - asym / asymN / 255 : 1),
4890
+ width: w,
4891
+ height: h,
4892
+ sampled: n
4893
+ };
4894
+ }
4895
+
4790
4896
  // src/fingerprint/index.ts
4791
- var FINGERPRINT_VERSION = 1;
4897
+ var FINGERPRINT_VERSION = 2;
4792
4898
  var CHROMATIC = 0.03;
4793
4899
  var MIN_ACCENT_AREA = 2e4;
4794
4900
  var oklch = (value) => {
@@ -4843,23 +4949,36 @@ function fingerprint(s) {
4843
4949
  roundness: median(s.controls.map((c) => Math.min(0.5, c.radiusPx / Math.max(1, c.heightPx)))),
4844
4950
  motion: below ? Math.min(1, s.motion.hiddenSections / below) : 0,
4845
4951
  effects: effects.sort(),
4846
- layout: s.sections.map((x) => x.kind)
4952
+ layout: s.sections.map((x) => x.kind),
4953
+ ...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 })) } : {}
4847
4954
  };
4848
4955
  }
4849
4956
  var DISTANCE_WEIGHTS = { accent: 0.25, type: 0.25, ground: 0.1, shape: 0.1, motion: 0.1, effects: 0.1, layout: 0.1 };
4850
- function levenshtein(a, b) {
4957
+ function levenshtein(a, b, cost = (x, y) => x === y ? 0 : 1) {
4851
4958
  const row = Array.from({ length: b.length + 1 }, (_, i) => i);
4852
4959
  for (let i = 1; i <= a.length; i += 1) {
4853
4960
  let prev = row[0];
4854
4961
  row[0] = i;
4855
4962
  for (let j = 1; j <= b.length; j += 1) {
4856
4963
  const cur = row[j];
4857
- row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
4964
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + cost(a[i - 1], b[j - 1]));
4858
4965
  prev = cur;
4859
4966
  }
4860
4967
  }
4861
4968
  return row[b.length];
4862
4969
  }
4970
+ function sectionCost(x, y) {
4971
+ if (x.role !== y.role) return 1;
4972
+ return Math.min(0.5, (Math.abs(x.centred - y.centred) + Math.abs(x.width - y.width)) / 2);
4973
+ }
4974
+ function layoutDistance(a, b) {
4975
+ if (a.sections && b.sections) {
4976
+ const longest2 = Math.max(a.sections.length, b.sections.length);
4977
+ return longest2 === 0 ? 0 : levenshtein(a.sections, b.sections, sectionCost) / longest2;
4978
+ }
4979
+ const longest = Math.max(a.layout.length, b.layout.length);
4980
+ return longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
4981
+ }
4863
4982
  var clamp = (n) => Math.max(0, Math.min(1, n));
4864
4983
  function faceDistance(a, b) {
4865
4984
  if (a.family.toLowerCase() === b.family.toLowerCase()) return 0;
@@ -4874,8 +4993,7 @@ function fingerprintDistance(a, b) {
4874
4993
  const union = /* @__PURE__ */ new Set([...a.effects, ...b.effects]);
4875
4994
  const shared = a.effects.filter((e) => b.effects.includes(e)).length;
4876
4995
  const effects = union.size === 0 ? 0 : 1 - shared / union.size;
4877
- const longest = Math.max(a.layout.length, b.layout.length);
4878
- const layout = longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
4996
+ const layout = layoutDistance(a, b);
4879
4997
  const parts2 = { accent, ground, type, shape, motion, effects, layout };
4880
4998
  const total = Object.keys(DISTANCE_WEIGHTS).reduce((sum, k) => sum + DISTANCE_WEIGHTS[k] * parts2[k], 0);
4881
4999
  return { total: Math.round(total * 1e3) / 1e3, parts: parts2 };
@@ -4903,6 +5021,117 @@ var SOURCE_KINDS = [
4903
5021
  ];
4904
5022
  var CHOICE_KEYS = ["accent", "ground", "display", "body", "shape", "motif", "signature"];
4905
5023
 
5024
+ // src/direction/licences.ts
5025
+ var LICENCES = [
5026
+ {
5027
+ name: "Basteleur",
5028
+ subject: "face",
5029
+ licence: "OFL-1.1",
5030
+ multiClient: "yes",
5031
+ checked: "2026-09-24",
5032
+ source: "https://velvetyne.fr/fonts/basteleur/",
5033
+ note: "Velvetyne, designed by Keussel. Keep the OFL notice with the font files."
5034
+ },
5035
+ {
5036
+ name: "Gulax",
5037
+ subject: "face",
5038
+ licence: "OFL-1.1",
5039
+ multiClient: "yes",
5040
+ checked: "2026-09-24",
5041
+ source: "https://velvetyne.fr/fonts/gulax/",
5042
+ note: "Velvetyne. Keep the OFL notice with the font files."
5043
+ },
5044
+ {
5045
+ name: "Le Murmure",
5046
+ subject: "face",
5047
+ licence: "OFL-1.1",
5048
+ multiClient: "yes",
5049
+ checked: "2026-09-24",
5050
+ source: "https://velvetyne.fr/fonts/le-murmure/",
5051
+ note: "Velvetyne. Keep the OFL notice with the font files."
5052
+ },
5053
+ {
5054
+ name: "Blaze Type",
5055
+ subject: "foundry",
5056
+ licence: "commercial",
5057
+ multiClient: "per-site",
5058
+ cap: 10,
5059
+ checked: "2026-09-24",
5060
+ source: "https://blazetype.eu/eula",
5061
+ 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."
5062
+ },
5063
+ {
5064
+ name: "Power Type",
5065
+ subject: "foundry",
5066
+ licence: "commercial",
5067
+ multiClient: "unverified",
5068
+ checked: "2026-09-24",
5069
+ source: "https://power-type.com/eula",
5070
+ 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."
5071
+ },
5072
+ {
5073
+ name: "Grilli Type",
5074
+ subject: "foundry",
5075
+ licence: "commercial",
5076
+ multiClient: "per-site",
5077
+ checked: "2026-09-24",
5078
+ source: "https://www.grillitype.com/information",
5079
+ 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."
5080
+ },
5081
+ {
5082
+ name: "Phosphor",
5083
+ subject: "icons",
5084
+ licence: "MIT",
5085
+ multiClient: "yes",
5086
+ checked: "2026-09-24",
5087
+ source: "https://github.com/phosphor-icons/core/blob/main/LICENSE",
5088
+ note: "Six weights. The icon-tile tells treat it exactly as they treat Lucide: the library is not the tell, the tinted tile is."
5089
+ },
5090
+ {
5091
+ name: "Tabler Icons",
5092
+ subject: "icons",
5093
+ licence: "MIT",
5094
+ multiClient: "yes",
5095
+ checked: "2026-09-24",
5096
+ source: "https://github.com/tabler/tabler-icons/blob/main/LICENSE",
5097
+ note: "One stroke grid across the set."
5098
+ },
5099
+ {
5100
+ name: "Iconoir",
5101
+ subject: "icons",
5102
+ licence: "MIT",
5103
+ multiClient: "yes",
5104
+ checked: "2026-09-24",
5105
+ source: "https://github.com/iconoir-icons/iconoir/blob/main/LICENSE",
5106
+ note: "Smaller set, no paid tier."
5107
+ },
5108
+ {
5109
+ name: "Unicons",
5110
+ subject: "icons",
5111
+ licence: "IconScout Simple License",
5112
+ multiClient: "yes",
5113
+ checked: "2026-09-24",
5114
+ source: "https://github.com/Iconscout/unicons/blob/master/LICENSE",
5115
+ note: "IconScout's free set, personal and commercial use, attribution optional."
5116
+ },
5117
+ {
5118
+ name: "IconScout premium",
5119
+ subject: "icons",
5120
+ licence: "commercial",
5121
+ multiClient: "per-site",
5122
+ checked: "2026-09-24",
5123
+ source: "https://iconscout.com/licenses",
5124
+ 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."
5125
+ }
5126
+ ];
5127
+ function faceLicence(family) {
5128
+ const name = family.trim().toLowerCase();
5129
+ return LICENCES.find((l) => l.subject === "face" && l.name.toLowerCase() === name);
5130
+ }
5131
+ function licencesJson() {
5132
+ return JSON.stringify({ licences: LICENCES }, null, 2) + "\n";
5133
+ }
5134
+
4906
5135
  // src/direction/validate.ts
4907
5136
  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;
4908
5137
  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;
@@ -5026,6 +5255,18 @@ function validateDirection(input, ctx = {}) {
5026
5255
  ok = false;
5027
5256
  }
5028
5257
  }
5258
+ if ((key === "display" || key === "body") && typeof c.value === "string" && c.value.trim()) {
5259
+ const family = normaliseFamily(c.value);
5260
+ const entry = faceLicence(family);
5261
+ if (!entry) {
5262
+ 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.`);
5263
+ } else if (entry.multiClient === "unverified") {
5264
+ warn(`${at}.value`, `${family}: licence unverified (${entry.note})`);
5265
+ } else if (entry.multiClient === "capped" || entry.multiClient === "per-site") {
5266
+ const scope = entry.multiClient === "capped" ? `covers up to ${entry.cap} sites` : "needs a licence for this site, in the client's name";
5267
+ warn(`${at}.value`, `${family}: ${entry.licence} licence ${scope}. Record the purchase before launch.`);
5268
+ }
5269
+ }
5029
5270
  const fp = ctx.fingerprint;
5030
5271
  if (fp && typeof c.value === "string") {
5031
5272
  if ((key === "accent" || key === "ground") && parseColour(c.value)) {
@@ -5695,13 +5936,16 @@ export {
5695
5936
  DURATION_S,
5696
5937
  EASE,
5697
5938
  EASE_TUPLE,
5939
+ EDGE_STEP,
5698
5940
  ESTATE_VERSION,
5699
5941
  EXIT_RATIO,
5700
5942
  FINGERPRINT_VERSION,
5701
5943
  HOUSE,
5702
5944
  HOUSE_BUDGET,
5945
+ HOUSE_LOCKED,
5703
5946
  HOUSE_TYPE,
5704
5947
  JSON_SCHEMA_VERSION,
5948
+ LICENCES,
5705
5949
  LIGHTNESS_CURVE,
5706
5950
  MIN_DECIDED,
5707
5951
  MIN_RUNS,
@@ -5711,6 +5955,7 @@ export {
5711
5955
  PLAINER_WORDS,
5712
5956
  PROPOSED,
5713
5957
  RAMP_STEPS,
5958
+ READABLE_SNAPSHOT_VERSIONS,
5714
5959
  REFLEX_FONTS_1,
5715
5960
  REFLEX_FONTS_2,
5716
5961
  RENDERED_PATHS,
@@ -5755,6 +6000,7 @@ export {
5755
6000
  estatePairs,
5756
6001
  exitDuration,
5757
6002
  extractHtml,
6003
+ faceLicence,
5758
6004
  findClaims,
5759
6005
  findColours,
5760
6006
  findingKey,
@@ -5772,6 +6018,7 @@ export {
5772
6018
  groundName,
5773
6019
  harvest,
5774
6020
  hexToOklch,
6021
+ holdHouseBlocks,
5775
6022
  houseRule,
5776
6023
  hueName,
5777
6024
  hueSwatch,
@@ -5781,6 +6028,8 @@ export {
5781
6028
  initDirection,
5782
6029
  isAiViolet,
5783
6030
  isCream,
6031
+ layoutDistance,
6032
+ licencesJson,
5784
6033
  makeSnapshot,
5785
6034
  motionTokens,
5786
6035
  namedSources,
@@ -5798,6 +6047,7 @@ export {
5798
6047
  protectedFacts,
5799
6048
  ramp,
5800
6049
  rampFromAnchors,
6050
+ readSnapshot,
5801
6051
  retrofitPlan,
5802
6052
  runTell,
5803
6053
  scanSource,
@@ -5818,6 +6068,7 @@ export {
5818
6068
  typicality,
5819
6069
  validateDirection,
5820
6070
  visibleText,
6071
+ visualMeasures,
5821
6072
  wcagContrast,
5822
6073
  wordListsJson
5823
6074
  };
package/licences.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "licences": [
3
+ {
4
+ "name": "Basteleur",
5
+ "subject": "face",
6
+ "licence": "OFL-1.1",
7
+ "multiClient": "yes",
8
+ "checked": "2026-09-24",
9
+ "source": "https://velvetyne.fr/fonts/basteleur/",
10
+ "note": "Velvetyne, designed by Keussel. Keep the OFL notice with the font files."
11
+ },
12
+ {
13
+ "name": "Gulax",
14
+ "subject": "face",
15
+ "licence": "OFL-1.1",
16
+ "multiClient": "yes",
17
+ "checked": "2026-09-24",
18
+ "source": "https://velvetyne.fr/fonts/gulax/",
19
+ "note": "Velvetyne. Keep the OFL notice with the font files."
20
+ },
21
+ {
22
+ "name": "Le Murmure",
23
+ "subject": "face",
24
+ "licence": "OFL-1.1",
25
+ "multiClient": "yes",
26
+ "checked": "2026-09-24",
27
+ "source": "https://velvetyne.fr/fonts/le-murmure/",
28
+ "note": "Velvetyne. Keep the OFL notice with the font files."
29
+ },
30
+ {
31
+ "name": "Blaze Type",
32
+ "subject": "foundry",
33
+ "licence": "commercial",
34
+ "multiClient": "per-site",
35
+ "cap": 10,
36
+ "checked": "2026-09-24",
37
+ "source": "https://blazetype.eu/eula",
38
+ "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."
39
+ },
40
+ {
41
+ "name": "Power Type",
42
+ "subject": "foundry",
43
+ "licence": "commercial",
44
+ "multiClient": "unverified",
45
+ "checked": "2026-09-24",
46
+ "source": "https://power-type.com/eula",
47
+ "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."
48
+ },
49
+ {
50
+ "name": "Grilli Type",
51
+ "subject": "foundry",
52
+ "licence": "commercial",
53
+ "multiClient": "per-site",
54
+ "checked": "2026-09-24",
55
+ "source": "https://www.grillitype.com/information",
56
+ "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."
57
+ },
58
+ {
59
+ "name": "Phosphor",
60
+ "subject": "icons",
61
+ "licence": "MIT",
62
+ "multiClient": "yes",
63
+ "checked": "2026-09-24",
64
+ "source": "https://github.com/phosphor-icons/core/blob/main/LICENSE",
65
+ "note": "Six weights. The icon-tile tells treat it exactly as they treat Lucide: the library is not the tell, the tinted tile is."
66
+ },
67
+ {
68
+ "name": "Tabler Icons",
69
+ "subject": "icons",
70
+ "licence": "MIT",
71
+ "multiClient": "yes",
72
+ "checked": "2026-09-24",
73
+ "source": "https://github.com/tabler/tabler-icons/blob/main/LICENSE",
74
+ "note": "One stroke grid across the set."
75
+ },
76
+ {
77
+ "name": "Iconoir",
78
+ "subject": "icons",
79
+ "licence": "MIT",
80
+ "multiClient": "yes",
81
+ "checked": "2026-09-24",
82
+ "source": "https://github.com/iconoir-icons/iconoir/blob/main/LICENSE",
83
+ "note": "Smaller set, no paid tier."
84
+ },
85
+ {
86
+ "name": "Unicons",
87
+ "subject": "icons",
88
+ "licence": "IconScout Simple License",
89
+ "multiClient": "yes",
90
+ "checked": "2026-09-24",
91
+ "source": "https://github.com/Iconscout/unicons/blob/master/LICENSE",
92
+ "note": "IconScout's free set, personal and commercial use, attribution optional."
93
+ },
94
+ {
95
+ "name": "IconScout premium",
96
+ "subject": "icons",
97
+ "licence": "commercial",
98
+ "multiClient": "per-site",
99
+ "checked": "2026-09-24",
100
+ "source": "https://iconscout.com/licenses",
101
+ "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."
102
+ }
103
+ ]
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domandigital/craft",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
6
6
  "license": "Apache-2.0",
@@ -50,6 +50,7 @@
50
50
  "README.md",
51
51
  "STANDARD.md",
52
52
  "word-lists.json",
53
+ "licences.json",
53
54
  "css",
54
55
  "dist"
55
56
  ],
@@ -86,6 +87,6 @@
86
87
  "build": "tsup",
87
88
  "typecheck": "tsc --noEmit",
88
89
  "test": "vitest run",
89
- "docs": "tsup && node scripts/write-catalogue.mjs && node scripts/write-word-lists.mjs"
90
+ "docs": "tsup && node scripts/write-catalogue.mjs && node scripts/write-word-lists.mjs && node scripts/write-licences.mjs"
90
91
  }
91
92
  }
package/word-lists.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "catalogueVersion": "2026.09.8",
2
+ "catalogueVersion": "2026.09.9",
3
3
  "lists": {
4
4
  "aiWords": [
5
5
  "tapestry",
@@ -1,17 +0,0 @@
1
- import {
2
- describeFingerprint,
3
- merge,
4
- resolvePages,
5
- runAudit
6
- } from "./chunk-MSQ57PJU.js";
7
- import "./chunk-JP75P2DU.js";
8
- import "./chunk-3OZQ4C5R.js";
9
- import "./chunk-2AJB27BJ.js";
10
- import "./chunk-QMYERCBB.js";
11
- import "./chunk-3NGWXZZE.js";
12
- export {
13
- describeFingerprint,
14
- merge,
15
- resolvePages,
16
- runAudit
17
- };
@@ -1,16 +0,0 @@
1
- import {
2
- formatMatches,
3
- formatPairs,
4
- loadEstate,
5
- runEstate
6
- } from "./chunk-HOHHQB4N.js";
7
- import "./chunk-3OZQ4C5R.js";
8
- import "./chunk-2AJB27BJ.js";
9
- import "./chunk-QMYERCBB.js";
10
- import "./chunk-3NGWXZZE.js";
11
- export {
12
- formatMatches,
13
- formatPairs,
14
- loadEstate,
15
- runEstate
16
- };