@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.
@@ -1,10 +1,13 @@
1
+ import {
2
+ decodePng
3
+ } from "./chunk-JYNXGCYZ.js";
1
4
  import {
2
5
  fingerprint
3
- } from "./chunk-QMYERCBB.js";
6
+ } from "./chunk-PZ4MF2VV.js";
4
7
  import {
5
8
  SNAPSHOT_VERSION,
6
9
  auditSnapshot
7
- } from "./chunk-3NGWXZZE.js";
10
+ } from "./chunk-AL6MRG6V.js";
8
11
 
9
12
  // src/audit/collect.ts
10
13
  function collectInPage() {
@@ -103,18 +106,26 @@ function collectInPage() {
103
106
  top: Math.round(box(h).top)
104
107
  };
105
108
  });
109
+ const isButtonEl = (el) => el.tagName === "BUTTON" || el.tagName === "INPUT" && /^(submit|button)$/i.test(el.type);
110
+ const isControl = (el) => {
111
+ const isButton = isButtonEl(el);
112
+ if (!isButton && el.tagName !== "A") return false;
113
+ if (!shown(el)) return false;
114
+ const cs = style(el);
115
+ const b = box(el);
116
+ const label = (el.textContent ?? el.value ?? "").replace(/\s+/g, " ").trim();
117
+ if (!label || b.height < 24 || b.height > 90) return false;
118
+ if (!isButton && !(painted(cs.backgroundColor) || px(cs.borderTopWidth) >= 1)) return false;
119
+ if (!isButton && px(cs.paddingLeft) < 8) return false;
120
+ return true;
121
+ };
106
122
  const controls = [];
107
123
  for (const el of all) {
108
- const tag = el.tagName;
109
- const isButton = tag === "BUTTON" || tag === "INPUT" && /^(submit|button)$/i.test(el.type);
110
- if (!isButton && tag !== "A") continue;
111
- if (!shown(el)) continue;
124
+ if (!isControl(el)) continue;
125
+ const isButton = isButtonEl(el);
112
126
  const cs = style(el);
113
127
  const b = box(el);
114
128
  const label = (el.textContent ?? el.value ?? "").replace(/\s+/g, " ").trim();
115
- if (!label || b.height < 24 || b.height > 90) continue;
116
- if (!isButton && !(painted(cs.backgroundColor) || px(cs.borderTopWidth) >= 1)) continue;
117
- if (!isButton && px(cs.paddingLeft) < 8) continue;
118
129
  const r = cs.borderTopLeftRadius;
119
130
  const radius = r.endsWith("%") ? parseFloat(r) / 100 * b.height : px(r);
120
131
  controls.push({
@@ -194,7 +205,80 @@ function collectInPage() {
194
205
  }
195
206
  return best;
196
207
  };
197
- const sections = blocks.slice(0, 40).map((el, index) => {
208
+ const backgroundOf = (el) => {
209
+ for (let n = el; n && n !== document.documentElement; n = n.parentElement) {
210
+ const c = style(n).backgroundColor;
211
+ if (painted(c)) return c;
212
+ }
213
+ return ground;
214
+ };
215
+ const geometryOf = (el, b) => {
216
+ const leaves = Array.from(el.querySelectorAll("*")).slice(0, 1500).filter((n) => (ownText(n).length > 1 || /^(IMG|SVG|VIDEO|PICTURE|BUTTON|INPUT|TEXTAREA|SELECT)$/i.test(n.tagName)) && shown(n)).slice(0, 400);
217
+ const centreX = b.left + b.width / 2;
218
+ let left = 0;
219
+ let right = 0;
220
+ let covered = 0;
221
+ let minX = Infinity;
222
+ let maxX = -Infinity;
223
+ let textBlocks = 0;
224
+ let centred = 0;
225
+ for (const n of leaves) {
226
+ const r = box(n);
227
+ const area = r.width * r.height;
228
+ covered += area;
229
+ minX = Math.min(minX, r.left);
230
+ maxX = Math.max(maxX, r.left + r.width);
231
+ const split = Math.max(0, Math.min(r.width, centreX - r.left));
232
+ left += split * r.height;
233
+ right += (r.width - split) * r.height;
234
+ if (ownText(n).length > 1) {
235
+ textBlocks += 1;
236
+ const mid = r.left + r.width / 2;
237
+ if (style(n).textAlign === "center" || Math.abs(mid - centreX) < vw * 0.03 && r.width < b.width * 0.8) centred += 1;
238
+ }
239
+ }
240
+ const round = (v) => Math.round(v * 1e3) / 1e3;
241
+ return {
242
+ centredShare: textBlocks ? round(centred / textBlocks) : 0,
243
+ mirrorSymmetry: left + right > 0 ? round(1 - Math.abs(left - right) / (left + right)) : 1,
244
+ whitespaceRatio: round(Math.max(0, 1 - Math.min(1, covered / Math.max(1, b.width * b.height)))),
245
+ contentWidthRatio: leaves.length ? round(Math.min(1, (maxX - minX) / vw)) : 0,
246
+ background: backgroundOf(el),
247
+ controls: Array.from(el.querySelectorAll("a, button, input")).slice(0, 200).filter(isControl).length
248
+ };
249
+ };
250
+ const CURRENCY = /[£$€]\s?\d/;
251
+ const roleOf = (el, kind, geo, b, cards, iconCards, last) => {
252
+ if (kind === "marquee" || kind === "hero") return kind;
253
+ const heading = (el.querySelector("h1, h2, h3")?.textContent ?? "").toLowerCase();
254
+ const leaves = Array.from(el.querySelectorAll("*")).slice(0, 1500);
255
+ const own = leaves.map(ownText);
256
+ const questions = own.filter((t) => /\?$/.test(t) && t.length > 8).length;
257
+ if (el.querySelectorAll("details").length >= 3 || el.querySelectorAll("[aria-expanded]").length >= 3 || /\bfaq|frequently asked|questions\b/.test(heading) && questions >= 3) return "faq";
258
+ const prices = own.filter((t) => CURRENCY.test(t) && t.length < 40).length;
259
+ if (prices >= 2 && (cards >= 2 || /pric|plan|package/.test(heading))) return "pricing";
260
+ const quotes = el.querySelectorAll("blockquote, q").length;
261
+ if (quotes >= 2 || /testimonial|review|what (?:our )?(?:clients|customers|people) say/.test(heading)) return "testimonials";
262
+ const steps = Math.max(0, ...Array.from(el.querySelectorAll("ol")).map((ol) => ol.children.length));
263
+ const numbered = own.filter((t) => /^(?:step\s*)?0?[1-9][.:]?$/i.test(t)).length;
264
+ if (steps >= 3 || numbered >= 3 || /how it works|our process|\bsteps?\b/.test(heading)) return "process";
265
+ const form = el.querySelector("form");
266
+ const fields = form ? form.querySelectorAll("input:not([type=hidden]):not([type=submit]), textarea, select").length : 0;
267
+ if (fields >= 2 || /contact|get in touch/.test(heading)) return "contact";
268
+ const portraits = Array.from(el.querySelectorAll("img")).filter((i) => {
269
+ const r = box(i);
270
+ return r.width >= 80 && r.width <= 420 && Math.abs(r.width - r.height) < r.width * 0.25;
271
+ }).length;
272
+ if (portraits >= 3 && /team|people|meet|who we are/.test(heading)) return "team";
273
+ if (kind === "stats" || kind === "logos") return kind;
274
+ const words = (el.textContent ?? "").replace(/\s+/g, " ").trim().length;
275
+ const setApart = geo.background !== ground || geo.centredShare >= 0.6;
276
+ if (setApart && heading && b.height <= vh * 0.9 && geo.controls >= 1 && geo.controls <= 2 && words < 400 && cards < 3) return last ? "footer-cta" : "cta-band";
277
+ if (cards >= 3 && iconCards >= 2) return "features";
278
+ return kind;
279
+ };
280
+ const kept = blocks.slice(0, 40);
281
+ const sections = kept.map((el, index) => {
198
282
  const b = box(el);
199
283
  const heading = el.querySelector("h1, h2, h3");
200
284
  const label = (heading?.textContent ?? el.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
@@ -208,6 +292,7 @@ function collectInPage() {
208
292
  else if (images.length >= 4 && textChars < 300) kind = "logos";
209
293
  else if (numbers >= 3 && textChars < 600) kind = "stats";
210
294
  else if (cards >= 3) kind = "cards";
295
+ const geometry = geometryOf(el, b);
211
296
  return {
212
297
  top: Math.round(b.top),
213
298
  height: Math.round(b.height),
@@ -215,7 +300,9 @@ function collectInPage() {
215
300
  label,
216
301
  cards,
217
302
  iconCards,
218
- hiddenAtLoad: b.top > vh && hiddenAtLoad(el)
303
+ hiddenAtLoad: b.top > vh && hiddenAtLoad(el),
304
+ role: roleOf(el, kind, geometry, b, cards, iconCards, index === kept.length - 1),
305
+ geometry
219
306
  };
220
307
  });
221
308
  if (header && sections.length === 0) {
@@ -308,6 +395,13 @@ function collectInPage() {
308
395
  headings,
309
396
  controls,
310
397
  sections,
398
+ rhythmVariance: (() => {
399
+ const hs = sections.map((x) => x.height);
400
+ if (hs.length < 2) return null;
401
+ const mean = hs.reduce((t, v) => t + v, 0) / hs.length;
402
+ const sd = Math.sqrt(hs.reduce((t, v) => t + (v - mean) ** 2, 0) / hs.length);
403
+ return mean > 0 ? Math.round(sd / mean * 1e3) / 1e3 : null;
404
+ })(),
311
405
  effects,
312
406
  motion: {
313
407
  hiddenSections: sections.filter((s) => s.hiddenAtLoad).length,
@@ -341,7 +435,67 @@ function overlayInPage() {
341
435
  return false;
342
436
  }
343
437
 
438
+ // src/snapshot/visual.ts
439
+ var EDGE_STEP = 32;
440
+ function visualMeasures(img, maxSamples = 1e6) {
441
+ const { width: w, height: h, rgba } = img;
442
+ if (w < 2 || h < 2 || rgba.length < w * h * 4) throw new Error(`visualMeasures: ${w}x${h} image with ${rgba.length} bytes`);
443
+ const step = Math.max(1, Math.ceil(Math.sqrt(w * h / maxSamples)));
444
+ const lum = (x, y) => {
445
+ const i = (y * w + x) * 4;
446
+ return 0.2126 * rgba[i] + 0.7152 * rgba[i + 1] + 0.0722 * rgba[i + 2];
447
+ };
448
+ let n = 0;
449
+ let sumRg = 0;
450
+ let sumYb = 0;
451
+ let sumRg2 = 0;
452
+ let sumYb2 = 0;
453
+ let edges = 0;
454
+ let edgeN = 0;
455
+ let asym = 0;
456
+ let asymN = 0;
457
+ for (let y = 0; y < h; y += step) {
458
+ for (let x = 0; x < w; x += step) {
459
+ const i = (y * w + x) * 4;
460
+ const r = rgba[i];
461
+ const g = rgba[i + 1];
462
+ const b = rgba[i + 2];
463
+ const rg = r - g;
464
+ const yb = 0.5 * (r + g) - b;
465
+ sumRg += rg;
466
+ sumYb += yb;
467
+ sumRg2 += rg * rg;
468
+ sumYb2 += yb * yb;
469
+ n += 1;
470
+ const l = lum(x, y);
471
+ if (x + step < w && y + step < h) {
472
+ if (Math.abs(lum(x + step, y) - l) + Math.abs(lum(x, y + step) - l) > EDGE_STEP) edges += 1;
473
+ edgeN += 1;
474
+ }
475
+ if (x < w / 2) {
476
+ asym += Math.abs(l - lum(w - 1 - x, y));
477
+ asymN += 1;
478
+ }
479
+ }
480
+ }
481
+ const meanRg = sumRg / n;
482
+ const meanYb = sumYb / n;
483
+ const sdRg = Math.sqrt(Math.max(0, sumRg2 / n - meanRg * meanRg));
484
+ const sdYb = Math.sqrt(Math.max(0, sumYb2 / n - meanYb * meanYb));
485
+ const colourfulness = Math.sqrt(sdRg ** 2 + sdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);
486
+ const round = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
487
+ return {
488
+ colourfulness: round(colourfulness, 1),
489
+ edgeDensity: round(edgeN ? edges / edgeN : 0),
490
+ symmetry: round(asymN ? 1 - asym / asymN / 255 : 1),
491
+ width: w,
492
+ height: h,
493
+ sampled: n
494
+ };
495
+ }
496
+
344
497
  // src/audit/index.ts
498
+ var VISUAL_MAX_HEIGHT = 6e3;
345
499
  async function loadPlaywright() {
346
500
  for (const name of ["playwright", "playwright-core"]) {
347
501
  try {
@@ -359,12 +513,23 @@ async function snapshotPage(page, url, options = {}) {
359
513
  await page.waitForTimeout(options.introWindowMs ?? 5e3);
360
514
  const late = await page.evaluate(overlayInPage).catch(() => false);
361
515
  const data = await page.evaluate(collectInPage);
516
+ let visual;
517
+ if (page.screenshot && options.visual !== false) {
518
+ try {
519
+ const height = Math.min(data.pageHeight, VISUAL_MAX_HEIGHT);
520
+ const png = await page.screenshot({ fullPage: true, type: "png", clip: { x: 0, y: 0, width: data.viewport.width, height } });
521
+ visual = visualMeasures(decodePng(png));
522
+ } catch {
523
+ visual = void 0;
524
+ }
525
+ }
362
526
  return {
363
527
  version: SNAPSHOT_VERSION,
364
528
  url,
365
529
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
366
530
  ...data,
367
- motion: { ...data.motion, introOverlay: early && !late }
531
+ motion: { ...data.motion, introOverlay: early && !late },
532
+ ...visual ? { visual } : {}
368
533
  };
369
534
  }
370
535
  async function launch(pw, url, options) {
@@ -413,6 +578,7 @@ async function audit(url, options = {}) {
413
578
  export {
414
579
  collectInPage,
415
580
  overlayInPage,
581
+ VISUAL_MAX_HEIGHT,
416
582
  loadPlaywright,
417
583
  snapshotPage,
418
584
  snapshotUrl,
@@ -0,0 +1,19 @@
1
+ import {
2
+ SNAPSHOT_VERSION
3
+ } from "./chunk-AL6MRG6V.js";
4
+
5
+ // src/snapshot/migrate.ts
6
+ var READABLE_SNAPSHOT_VERSIONS = [1, 2];
7
+ function readSnapshot(data, source = "snapshot") {
8
+ const v = data?.version;
9
+ if (typeof data !== "object" || data === null || !READABLE_SNAPSHOT_VERSIONS.includes(v)) {
10
+ throw new Error(`${source} is not a snapshot this craft reads (version ${String(v)}; reads ${READABLE_SNAPSHOT_VERSIONS.join(" and ")})`);
11
+ }
12
+ const snap = data;
13
+ if (snap.version === 1) return { ...snap, version: SNAPSHOT_VERSION, migratedFrom: 1 };
14
+ return snap;
15
+ }
16
+
17
+ export {
18
+ readSnapshot
19
+ };
@@ -0,0 +1,17 @@
1
+ import {
2
+ formatMatches,
3
+ formatPairs,
4
+ loadEstate,
5
+ runEstate
6
+ } from "./chunk-CDAJUS5U.js";
7
+ import "./chunk-LHGY43LL.js";
8
+ import "./chunk-ZKVJ7K6X.js";
9
+ import "./chunk-J3QUROJ5.js";
10
+ import "./chunk-PZ4MF2VV.js";
11
+ import "./chunk-AL6MRG6V.js";
12
+ export {
13
+ formatMatches,
14
+ formatPairs,
15
+ loadEstate,
16
+ runEstate
17
+ };
@@ -6,10 +6,11 @@ import {
6
6
  nullPath,
7
7
  runHarvest,
8
8
  runNull
9
- } from "./chunk-3OZQ4C5R.js";
10
- import "./chunk-2AJB27BJ.js";
11
- import "./chunk-QMYERCBB.js";
12
- import "./chunk-3NGWXZZE.js";
9
+ } from "./chunk-LHGY43LL.js";
10
+ import "./chunk-ZKVJ7K6X.js";
11
+ import "./chunk-J3QUROJ5.js";
12
+ import "./chunk-PZ4MF2VV.js";
13
+ import "./chunk-AL6MRG6V.js";
13
14
  export {
14
15
  describeTypicality,
15
16
  formatHarvest,
@@ -0,0 +1,19 @@
1
+ import {
2
+ describeFingerprint,
3
+ merge,
4
+ resolvePages,
5
+ runAudit
6
+ } from "./chunk-PU5G5DUC.js";
7
+ import "./chunk-ULPI335S.js";
8
+ import "./chunk-JYNXGCYZ.js";
9
+ import "./chunk-LHGY43LL.js";
10
+ import "./chunk-ZKVJ7K6X.js";
11
+ import "./chunk-J3QUROJ5.js";
12
+ import "./chunk-PZ4MF2VV.js";
13
+ import "./chunk-AL6MRG6V.js";
14
+ export {
15
+ describeFingerprint,
16
+ merge,
17
+ resolvePages,
18
+ runAudit
19
+ };
@@ -1,19 +1,23 @@
1
1
  import {
2
2
  validateDirection
3
- } from "./chunk-JWHC67ZS.js";
3
+ } from "./chunk-S3NZAVU6.js";
4
4
  import {
5
5
  merge
6
- } from "./chunk-MSQ57PJU.js";
7
- import "./chunk-JP75P2DU.js";
6
+ } from "./chunk-PU5G5DUC.js";
7
+ import "./chunk-ULPI335S.js";
8
+ import "./chunk-JYNXGCYZ.js";
8
9
  import {
9
10
  SIBLING_AT,
10
11
  compareToEstate,
11
12
  loadEstate
12
- } from "./chunk-HOHHQB4N.js";
13
+ } from "./chunk-CDAJUS5U.js";
13
14
  import {
14
15
  loadNull,
15
16
  typicality
16
- } from "./chunk-3OZQ4C5R.js";
17
+ } from "./chunk-LHGY43LL.js";
18
+ import {
19
+ readSnapshot
20
+ } from "./chunk-ZKVJ7K6X.js";
17
21
  import {
18
22
  COPY_FILE,
19
23
  NOT_COPY_DIR,
@@ -24,17 +28,16 @@ import {
24
28
  parseFlags,
25
29
  readPaths,
26
30
  toJson
27
- } from "./chunk-2AJB27BJ.js";
31
+ } from "./chunk-J3QUROJ5.js";
28
32
  import {
29
33
  fingerprint
30
- } from "./chunk-QMYERCBB.js";
34
+ } from "./chunk-PZ4MF2VV.js";
31
35
  import {
32
36
  CATALOGUE,
33
- SNAPSHOT_VERSION,
34
37
  auditSnapshot,
35
38
  checkCopy,
36
39
  scanSource
37
- } from "./chunk-3NGWXZZE.js";
40
+ } from "./chunk-AL6MRG6V.js";
38
41
 
39
42
  // src/report/cli.ts
40
43
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -259,10 +262,9 @@ async function runReport(command, args, io) {
259
262
  const local = resolve(io.cwd, target2);
260
263
  let snap;
261
264
  if (/\.json$/i.test(target2) && existsSync(local)) {
262
- snap = JSON.parse(readFileSync(local, "utf8"));
263
- if (snap.version !== SNAPSHOT_VERSION) throw new Error(`${target2} is not a snapshot`);
265
+ snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target2);
264
266
  } else {
265
- const { snapshotUrl } = await import("./audit-QIPXL7E4.js");
267
+ const { snapshotUrl } = await import("./audit-5QWFLZ6L.js");
266
268
  snap = await snapshotUrl(target2);
267
269
  }
268
270
  const fp = fingerprint(snap);
@@ -1,14 +1,20 @@
1
1
  import {
2
2
  DIRECTION_VERSION,
3
3
  validateDirection
4
- } from "./chunk-JWHC67ZS.js";
4
+ } from "./chunk-S3NZAVU6.js";
5
+ import {
6
+ decodePng
7
+ } from "./chunk-JYNXGCYZ.js";
8
+ import {
9
+ readSnapshot
10
+ } from "./chunk-ZKVJ7K6X.js";
5
11
  import {
6
12
  parseFlags,
7
13
  toJson
8
- } from "./chunk-2AJB27BJ.js";
14
+ } from "./chunk-J3QUROJ5.js";
9
15
  import {
10
16
  fingerprint
11
- } from "./chunk-QMYERCBB.js";
17
+ } from "./chunk-PZ4MF2VV.js";
12
18
  import {
13
19
  deltaEOk,
14
20
  formatHex,
@@ -17,7 +23,7 @@ import {
17
23
  isCream,
18
24
  oklchToRgb,
19
25
  parseColour
20
- } from "./chunk-3NGWXZZE.js";
26
+ } from "./chunk-AL6MRG6V.js";
21
27
 
22
28
  // src/direction/cli.ts
23
29
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
@@ -50,78 +56,6 @@ function initDirection(input) {
50
56
  };
51
57
  }
52
58
 
53
- // src/direction/png.ts
54
- import { inflateSync } from "zlib";
55
- function decodePng(buf) {
56
- const sig = [137, 80, 78, 71, 13, 10, 26, 10];
57
- if (!sig.every((b, i) => buf[i] === b)) throw new Error("not a PNG");
58
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
59
- let pos = 8;
60
- let width = 0;
61
- let height = 0;
62
- let depth = 0;
63
- let colour = 0;
64
- let interlace = 0;
65
- const idat = [];
66
- while (pos < buf.length) {
67
- const len = view.getUint32(pos);
68
- const type = String.fromCharCode(...buf.subarray(pos + 4, pos + 8));
69
- const data = buf.subarray(pos + 8, pos + 8 + len);
70
- if (type === "IHDR") {
71
- width = view.getUint32(pos + 8);
72
- height = view.getUint32(pos + 12);
73
- depth = data[8];
74
- colour = data[9];
75
- interlace = data[12];
76
- } else if (type === "IDAT") idat.push(data);
77
- else if (type === "IEND") break;
78
- pos += 12 + len;
79
- }
80
- const channels = { 0: 1, 2: 3, 4: 2, 6: 4 }[colour];
81
- if (depth !== 8 || !channels || interlace !== 0) throw new Error(`unsupported PNG (depth ${depth}, colour type ${colour}${interlace ? ", interlaced" : ""}); export it as 8-bit RGB`);
82
- const raw = inflateSync(Buffer.concat(idat.map((d) => Buffer.from(d))));
83
- const stride = width * channels;
84
- const out = new Uint8Array(width * height * 4);
85
- const prev = new Uint8Array(stride);
86
- const line = new Uint8Array(stride);
87
- for (let y = 0; y < height; y += 1) {
88
- const filter = raw[y * (stride + 1)];
89
- const src = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
90
- for (let x = 0; x < stride; x += 1) {
91
- const a = x >= channels ? line[x - channels] : 0;
92
- const b = prev[x];
93
- const c = x >= channels ? prev[x - channels] : 0;
94
- let v = src[x];
95
- if (filter === 1) v += a;
96
- else if (filter === 2) v += b;
97
- else if (filter === 3) v += a + b >> 1;
98
- else if (filter === 4) {
99
- const p = a + b - c;
100
- const pa = Math.abs(p - a);
101
- const pb = Math.abs(p - b);
102
- const pc = Math.abs(p - c);
103
- v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
104
- }
105
- line[x] = v & 255;
106
- }
107
- for (let x = 0; x < width; x += 1) {
108
- const i = x * channels;
109
- const o = (y * width + x) * 4;
110
- if (channels >= 3) {
111
- out[o] = line[i];
112
- out[o + 1] = line[i + 1];
113
- out[o + 2] = line[i + 2];
114
- out[o + 3] = channels === 4 ? line[i + 3] : 255;
115
- } else {
116
- out[o] = out[o + 1] = out[o + 2] = line[i];
117
- out[o + 3] = channels === 2 ? line[i + 1] : 255;
118
- }
119
- }
120
- prev.set(line);
121
- }
122
- return { width, height, rgba: out };
123
- }
124
-
125
59
  // src/direction/propose.ts
126
60
  var PROPOSED = "PROPOSED:";
127
61
  function srgbToLinear(c) {
@@ -253,8 +187,8 @@ function estateFrom(dir, cwd) {
253
187
  }
254
188
  return readdirSync(root).filter((f) => f.endsWith(".json")).flatMap((f) => {
255
189
  try {
256
- const snap = readJson(join(root, f));
257
- return snap.version === 1 && snap.fonts ? [{ id: f.replace(/(\.snapshot)?\.json$/, ""), fingerprint: fingerprint(snap) }] : [];
190
+ const snap = readSnapshot(readJson(join(root, f)), f);
191
+ return snap.fonts ? [{ id: f.replace(/(\.snapshot)?\.json$/, ""), fingerprint: fingerprint(snap) }] : [];
258
192
  } catch {
259
193
  return [];
260
194
  }
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-2AJB27BJ.js";
5
- import "./chunk-3NGWXZZE.js";
4
+ } from "./chunk-J3QUROJ5.js";
5
+ import "./chunk-AL6MRG6V.js";
6
6
 
7
7
  // src/cli.ts
8
8
  process.stdout.on("error", (error) => {