@domandigital/craft 0.18.1 → 0.19.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 +12 -0
- package/CHARACTER.md +93 -6
- package/dist/{audit-4SFSLCKY.js → audit-X7LGATAQ.js} +2 -2
- package/dist/audit.cjs +24 -3
- package/dist/audit.d.cts +30 -1
- package/dist/audit.d.ts +30 -1
- package/dist/audit.js +24 -3
- package/dist/{chunk-O5B5ELJH.js → chunk-GNVZGG3E.js} +12 -11
- package/dist/{chunk-WFB6F3BN.js → chunk-IHT6CJOM.js} +106 -8
- package/dist/{chunk-DARKHE6A.js → chunk-J35UYAVV.js} +18 -7
- package/dist/{chunk-RJVG5KA6.js → chunk-LWKMGJSW.js} +38 -10
- package/dist/{chunk-ZUYERAFM.js → chunk-NCBCZFDY.js} +4 -4
- package/dist/{chunk-NTIPJ5U7.js → chunk-OKVHWXYV.js} +3 -3
- package/dist/{chunk-6TSDYETQ.js → chunk-RNHV2SXC.js} +28 -1
- package/dist/{chunk-SL5VZJ2J.js → chunk-W7PMRSDG.js} +3 -2
- package/dist/{cli-BN4IT6N2.js → cli-3WWHRJRG.js} +3 -3
- package/dist/{cli-Y7Y5IROK.js → cli-DKI37OXK.js} +4 -4
- package/dist/{cli-V44QEVS6.js → cli-KXVJJDN5.js} +9 -9
- package/dist/{cli-GXIW7F3E.js → cli-U7WEVPZL.js} +5 -5
- package/dist/{cli-MDW7JTEF.js → cli-YD3VN52R.js} +5 -5
- package/dist/{cli-JZRQBCD4.js → cli-ZGB6MUN2.js} +5 -5
- package/dist/cli.js +2 -2
- package/dist/index.cjs +186 -13
- package/dist/index.d.cts +111 -10
- package/dist/index.d.ts +111 -10
- package/dist/index.js +174 -13
- package/licences.json +27 -0
- package/package.json +3 -2
|
@@ -237,7 +237,7 @@ function fontClass(family) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/fingerprint/index.ts
|
|
240
|
-
var FINGERPRINT_VERSION =
|
|
240
|
+
var FINGERPRINT_VERSION = 3;
|
|
241
241
|
var CHROMATIC = 0.03;
|
|
242
242
|
var MIN_ACCENT_AREA = 2e4;
|
|
243
243
|
var oklch = (value) => {
|
|
@@ -273,6 +273,24 @@ function median(values) {
|
|
|
273
273
|
const mid = Math.floor(sorted.length / 2);
|
|
274
274
|
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
275
275
|
}
|
|
276
|
+
var FULL_WIDTH_IMAGE = 0.8;
|
|
277
|
+
function heroOf(s) {
|
|
278
|
+
const h1 = s.headings.find((h) => h.level === 1);
|
|
279
|
+
const inside = (top, x) => top >= x.top && top < x.top + x.height;
|
|
280
|
+
const index = (() => {
|
|
281
|
+
const withH1 = h1 ? s.sections.findIndex((x) => x.kind === "hero" && inside(h1.top, x)) : -1;
|
|
282
|
+
return withH1 >= 0 ? withH1 : s.sections.findIndex((x) => x.kind === "hero");
|
|
283
|
+
})();
|
|
284
|
+
if (index < 0) return void 0;
|
|
285
|
+
const section = s.sections[index];
|
|
286
|
+
const headline = h1 && inside(h1.top, section) ? { sizePx: h1.sizePx, weight: h1.weight, class: fontClass(normaliseFamily(h1.family)), italic: h1.italic || h1.italicPart } : null;
|
|
287
|
+
if (!s.images) return { headline };
|
|
288
|
+
const picture2 = s.images.filter((i) => (i.role === "photo" || i.role === "illustration") && (i.section === index || i.section === null && inside(i.top, section))).sort((a, b) => b.width * b.height - a.width * a.height)[0];
|
|
289
|
+
if (!picture2) return { headline, image: "none" };
|
|
290
|
+
if (picture2.left === void 0) return { headline };
|
|
291
|
+
const image = picture2.width >= FULL_WIDTH_IMAGE * s.viewport.width ? "full" : picture2.left + picture2.width / 2 < s.viewport.width / 2 ? "left" : "right";
|
|
292
|
+
return { headline, image };
|
|
293
|
+
}
|
|
276
294
|
function fingerprint(s) {
|
|
277
295
|
const effects = [];
|
|
278
296
|
if (s.effects.glass > 0) effects.push("glass-panel");
|
|
@@ -284,6 +302,7 @@ function fingerprint(s) {
|
|
|
284
302
|
if (s.effects.eyebrowChip) effects.push("hero-eyebrow-chip");
|
|
285
303
|
if (s.motion.introOverlay) effects.push("intro-cinematic");
|
|
286
304
|
const below = s.sections.filter((x) => x.top > s.viewport.height).length;
|
|
305
|
+
const hero = heroOf(s);
|
|
287
306
|
return {
|
|
288
307
|
version: FINGERPRINT_VERSION,
|
|
289
308
|
accent: accentOf(s),
|
|
@@ -307,10 +326,28 @@ function fingerprint(s) {
|
|
|
307
326
|
carousel: x.carousel ?? false,
|
|
308
327
|
figures: x.figures ?? 0
|
|
309
328
|
}))
|
|
310
|
-
} : {}
|
|
329
|
+
} : {},
|
|
330
|
+
...hero ? { hero } : {}
|
|
311
331
|
};
|
|
312
332
|
}
|
|
313
|
-
var
|
|
333
|
+
var WARM_HUE = [
|
|
334
|
+
[330, 360],
|
|
335
|
+
[0, 130]
|
|
336
|
+
];
|
|
337
|
+
var COOL_HUE = [[160, 310]];
|
|
338
|
+
var TEMPERATURE_FULL = 6e-3;
|
|
339
|
+
var TEMPERATURE_NAMED = 0.4;
|
|
340
|
+
function groundTemperature(o) {
|
|
341
|
+
const h = (o.h % 360 + 360) % 360;
|
|
342
|
+
const within = (bands) => bands.some(([lo, hi]) => h >= lo && h < hi);
|
|
343
|
+
const strength = Math.min(1, o.c / TEMPERATURE_FULL);
|
|
344
|
+
return within(WARM_HUE) ? strength : within(COOL_HUE) ? -strength : 0;
|
|
345
|
+
}
|
|
346
|
+
function temperatureName(o) {
|
|
347
|
+
const t = groundTemperature(o);
|
|
348
|
+
return t >= TEMPERATURE_NAMED ? "warm" : t <= -TEMPERATURE_NAMED ? "cool" : "neutral";
|
|
349
|
+
}
|
|
350
|
+
var DISTANCE_WEIGHTS = { accent: 0.2, type: 0.2, ground: 0.1, shape: 0.1, motion: 0.05, effects: 0.1, layout: 0.1, rhythm: 0.1, hero: 0.05 };
|
|
314
351
|
function levenshtein(a, b, cost = (x, y) => x === y ? 0 : 1) {
|
|
315
352
|
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
316
353
|
for (let i = 1; i <= a.length; i += 1) {
|
|
@@ -337,18 +374,62 @@ function layoutDistance(a, b) {
|
|
|
337
374
|
return longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
|
|
338
375
|
}
|
|
339
376
|
var clamp = (n) => Math.max(0, Math.min(1, n));
|
|
377
|
+
function bandOf(l) {
|
|
378
|
+
return l < 0.4 ? "dark" : l >= 0.75 ? "light" : "mid";
|
|
379
|
+
}
|
|
380
|
+
function rhythmOf(fp) {
|
|
381
|
+
if (!fp.sections) return null;
|
|
382
|
+
const out = [];
|
|
383
|
+
for (const s of fp.sections) {
|
|
384
|
+
if (s.background === void 0) return null;
|
|
385
|
+
const band = bandOf((s.background ?? fp.ground).l);
|
|
386
|
+
if (out[out.length - 1] !== band) out.push(band);
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
var OPENING_BANDS = 3;
|
|
391
|
+
function rhythmDistance(a, b) {
|
|
392
|
+
const ra = rhythmOf(a);
|
|
393
|
+
const rb = rhythmOf(b);
|
|
394
|
+
if (!ra || !rb) return null;
|
|
395
|
+
const over = (x, y) => {
|
|
396
|
+
const longest = Math.max(x.length, y.length);
|
|
397
|
+
return longest === 0 ? 0 : levenshtein(x, y) / longest;
|
|
398
|
+
};
|
|
399
|
+
return (over(ra.slice(0, OPENING_BANDS), rb.slice(0, OPENING_BANDS)) + over(ra, rb)) / 2;
|
|
400
|
+
}
|
|
401
|
+
var HEADLINE_OCTAVES = 1;
|
|
402
|
+
function heroSection(fp) {
|
|
403
|
+
return fp.sections?.find((s) => s.role === "hero");
|
|
404
|
+
}
|
|
405
|
+
function heroDistance(a, b) {
|
|
406
|
+
const sa = heroSection(a);
|
|
407
|
+
const sb = heroSection(b);
|
|
408
|
+
if (!sa || !sb) return null;
|
|
409
|
+
const parts2 = [Math.abs(sa.centred - sb.centred), clamp(Math.abs(sa.width - sb.width) / 0.5)];
|
|
410
|
+
const ha = a.hero?.headline;
|
|
411
|
+
const hb = b.hero?.headline;
|
|
412
|
+
if (ha && hb) parts2.push(0.5 * (ha.class === hb.class ? 0 : 1) + 0.5 * clamp(Math.abs(Math.log2(ha.sizePx / hb.sizePx)) / HEADLINE_OCTAVES));
|
|
413
|
+
else if (a.hero && b.hero && (ha || hb)) parts2.push(1);
|
|
414
|
+
if (a.hero?.image && b.hero?.image) parts2.push(a.hero.image === b.hero.image ? 0 : 1);
|
|
415
|
+
return parts2.reduce((s, p) => s + p, 0) / parts2.length;
|
|
416
|
+
}
|
|
340
417
|
function faceDistance(a, b) {
|
|
341
418
|
if (a.family.toLowerCase() === b.family.toLowerCase()) return 0;
|
|
342
419
|
return a.class === b.class ? 0.5 : 1;
|
|
343
420
|
}
|
|
344
421
|
var ACCENT_SCALE = 0.3;
|
|
345
422
|
var GROUND_SCALE = 0.15;
|
|
423
|
+
var TEMPERATURE_WEIGHT = 0.6;
|
|
346
424
|
var SHARED_PART = 0.2;
|
|
347
425
|
var ACCENT_SHARED = ACCENT_SCALE * SHARED_PART;
|
|
348
426
|
var GROUND_SHARED = GROUND_SCALE * SHARED_PART;
|
|
427
|
+
function groundDistance(a, b) {
|
|
428
|
+
return clamp(deltaEOk(a, b) / GROUND_SCALE + TEMPERATURE_WEIGHT * Math.abs(groundTemperature(a) - groundTemperature(b)) / 2);
|
|
429
|
+
}
|
|
349
430
|
function fingerprintDistance(a, b) {
|
|
350
431
|
const accent = a.accent && b.accent ? clamp(deltaEOk(a.accent, b.accent) / ACCENT_SCALE) : a.accent || b.accent ? 1 : 0;
|
|
351
|
-
const ground =
|
|
432
|
+
const ground = groundDistance(a.ground, b.ground);
|
|
352
433
|
const type = 0.6 * faceDistance(a.display, b.display) + 0.4 * faceDistance(a.body, b.body);
|
|
353
434
|
const shape = a.roundness === null || b.roundness === null ? a.roundness === b.roundness ? 0 : 0.5 : clamp(Math.abs(a.roundness - b.roundness) / 0.5);
|
|
354
435
|
const motion = clamp(Math.abs(a.motion - b.motion));
|
|
@@ -356,9 +437,18 @@ function fingerprintDistance(a, b) {
|
|
|
356
437
|
const shared = a.effects.filter((e) => b.effects.includes(e)).length;
|
|
357
438
|
const effects = union.size === 0 ? 0 : 1 - shared / union.size;
|
|
358
439
|
const layout = layoutDistance(a, b);
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
|
|
440
|
+
const rhythm = rhythmDistance(a, b);
|
|
441
|
+
const hero = heroDistance(a, b);
|
|
442
|
+
const parts2 = { accent, ground, type, shape, motion, effects, layout, rhythm, hero };
|
|
443
|
+
let sum = 0;
|
|
444
|
+
let weight = 0;
|
|
445
|
+
for (const k of Object.keys(DISTANCE_WEIGHTS)) {
|
|
446
|
+
const p = parts2[k];
|
|
447
|
+
if (p === null) continue;
|
|
448
|
+
sum += DISTANCE_WEIGHTS[k] * p;
|
|
449
|
+
weight += DISTANCE_WEIGHTS[k];
|
|
450
|
+
}
|
|
451
|
+
return { total: Math.round(sum / weight * 1e3) / 1e3, parts: parts2 };
|
|
362
452
|
}
|
|
363
453
|
|
|
364
454
|
// src/character/tells/source.ts
|
|
@@ -1323,6 +1413,12 @@ function groundName(o) {
|
|
|
1323
1413
|
if (o.l >= 0.97 && o.c < 0.012) return "white";
|
|
1324
1414
|
return o.c < 0.02 ? "grey" : "tinted";
|
|
1325
1415
|
}
|
|
1416
|
+
function groundLabel(o) {
|
|
1417
|
+
const name = groundName(o);
|
|
1418
|
+
if (name === "cream" || name === "tinted") return name;
|
|
1419
|
+
const temperature = temperatureName(o);
|
|
1420
|
+
return temperature === "neutral" ? name : `${temperature} ${name}`;
|
|
1421
|
+
}
|
|
1326
1422
|
function shapeName(roundness) {
|
|
1327
1423
|
if (roundness === null) return "no buttons";
|
|
1328
1424
|
if (roundness >= 0.45) return "pill";
|
|
@@ -3057,7 +3153,9 @@ export {
|
|
|
3057
3153
|
holdHouseBlocks,
|
|
3058
3154
|
normaliseFamily,
|
|
3059
3155
|
fingerprint,
|
|
3156
|
+
temperatureName,
|
|
3060
3157
|
levenshtein,
|
|
3158
|
+
rhythmOf,
|
|
3061
3159
|
SHARED_PART,
|
|
3062
3160
|
ACCENT_SHARED,
|
|
3063
3161
|
GROUND_SHARED,
|
|
@@ -3068,7 +3166,7 @@ export {
|
|
|
3068
3166
|
MIN_RUNS,
|
|
3069
3167
|
typicality,
|
|
3070
3168
|
hueName,
|
|
3071
|
-
|
|
3169
|
+
groundLabel,
|
|
3072
3170
|
shapeName,
|
|
3073
3171
|
harvest,
|
|
3074
3172
|
hueSwatch,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
snapshotUrl,
|
|
3
3
|
snapshotUrls
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-W7PMRSDG.js";
|
|
5
5
|
import {
|
|
6
6
|
describeTypicality,
|
|
7
7
|
loadNull
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NCBCZFDY.js";
|
|
9
9
|
import {
|
|
10
10
|
SOURCE_FILE,
|
|
11
11
|
finish,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
parseFlags,
|
|
15
15
|
prepareReport,
|
|
16
16
|
readPaths
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GNVZGG3E.js";
|
|
18
18
|
import {
|
|
19
19
|
toJson
|
|
20
20
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
auditSnapshot,
|
|
26
26
|
componentTypicalities,
|
|
27
27
|
fingerprint,
|
|
28
|
+
rhythmOf,
|
|
28
29
|
scanSource,
|
|
29
30
|
typicality
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-IHT6CJOM.js";
|
|
31
32
|
|
|
32
33
|
// src/audit/cli.ts
|
|
33
34
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -69,9 +70,17 @@ function describeFingerprint(fp) {
|
|
|
69
70
|
` roundness ${fp.roundness === null ? "no buttons" : fp.roundness.toFixed(2)}`,
|
|
70
71
|
` motion ${Math.round(fp.motion * 100)}% of sections below the fold reveal on scroll`,
|
|
71
72
|
` effects ${fp.effects.join(", ") || "none"}`,
|
|
72
|
-
` layout ${fp.layout.join(" > ") || "none"}
|
|
73
|
+
` layout ${fp.layout.join(" > ") || "none"}`,
|
|
74
|
+
` rhythm ${rhythmOf(fp)?.join(" > ") ?? "not measured: sections carry no backgrounds before snapshot version 2"}`,
|
|
75
|
+
` hero ${describeHero(fp)}`
|
|
73
76
|
].join("\n");
|
|
74
77
|
}
|
|
78
|
+
function describeHero(fp) {
|
|
79
|
+
if (!fp.hero) return "none";
|
|
80
|
+
const section = fp.sections?.find((s) => s.role === "hero");
|
|
81
|
+
const parts = [section ? section.centred >= 0.5 ? "centred" : "left-aligned" : null, fp.hero.headline ? `${fp.hero.headline.class} headline at ${Math.round(fp.hero.headline.sizePx)}px` : "no headline", fp.hero.image ? fp.hero.image === "none" ? "no picture" : fp.hero.image === "full" ? "full-width picture" : `picture ${fp.hero.image}` : null];
|
|
82
|
+
return parts.filter((p) => p !== null).join(", ");
|
|
83
|
+
}
|
|
75
84
|
function merge(a, b) {
|
|
76
85
|
const byTell = { ...a.summary.byTell };
|
|
77
86
|
for (const [k, v] of Object.entries(b.summary.byTell)) byTell[k] = (byTell[k] ?? 0) + v;
|
|
@@ -126,18 +135,20 @@ async function runAudit(command, args, io) {
|
|
|
126
135
|
if (flags.pages && target) throw new Error("give a page or --pages, not both");
|
|
127
136
|
if (!target && !flags.pages) throw new Error(`usage: craft ${command} <url${command === "audit" ? " | snapshot.json> | --pages <sitemap.xml | urls.txt>" : ">"}`);
|
|
128
137
|
const viewports = viewportsOf(flags);
|
|
138
|
+
if (flags.scheme && flags.scheme !== "light" && flags.scheme !== "dark") throw new Error("--scheme takes light or dark");
|
|
139
|
+
const colorScheme = flags.scheme;
|
|
129
140
|
const audited = [];
|
|
130
141
|
const failed = [];
|
|
131
142
|
const local = target ? resolve(io.cwd, target) : "";
|
|
132
143
|
if (target && command === "audit" && /\.json$/i.test(target) && existsSync(local)) {
|
|
133
144
|
audited.push({ url: target, viewport: null, snapshot: readSnapshotFile(local) });
|
|
134
145
|
} else if (target && viewports.length === 1) {
|
|
135
|
-
const snapshot = await snapshotUrl(target, { viewport: viewports[0] });
|
|
146
|
+
const snapshot = await snapshotUrl(target, { viewport: viewports[0], colorScheme });
|
|
136
147
|
audited.push({ url: target, viewport: viewports[0] ?? null, snapshot });
|
|
137
148
|
} else {
|
|
138
149
|
const urls = flags.pages ? await resolvePages(flags.pages, io.cwd) : [target];
|
|
139
150
|
for (const viewport of viewports) {
|
|
140
|
-
for (const r of await snapshotUrls(urls, { viewport })) {
|
|
151
|
+
for (const r of await snapshotUrls(urls, { viewport, colorScheme })) {
|
|
141
152
|
if (r.snapshot) audited.push({ url: r.url, viewport: viewport ?? null, snapshot: r.snapshot });
|
|
142
153
|
else failed.push({ url: r.url, viewport: viewport ?? null, error: r.error ?? "no snapshot" });
|
|
143
154
|
}
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
formatDirectionMatches,
|
|
5
5
|
summariseDirection,
|
|
6
6
|
validateDirection
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RNHV2SXC.js";
|
|
8
8
|
import {
|
|
9
9
|
loadNull
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NCBCZFDY.js";
|
|
11
11
|
import {
|
|
12
12
|
parseFlags
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GNVZGG3E.js";
|
|
14
14
|
import {
|
|
15
15
|
toJson
|
|
16
16
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -25,10 +25,12 @@ import {
|
|
|
25
25
|
estateComponentPairs,
|
|
26
26
|
fingerprint,
|
|
27
27
|
fingerprintDistance,
|
|
28
|
-
|
|
28
|
+
groundLabel,
|
|
29
29
|
hueName,
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
rhythmOf,
|
|
31
|
+
shapeName,
|
|
32
|
+
temperatureName
|
|
33
|
+
} from "./chunk-IHT6CJOM.js";
|
|
32
34
|
|
|
33
35
|
// src/estate/cli.ts
|
|
34
36
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -45,19 +47,45 @@ function addSite(register, site) {
|
|
|
45
47
|
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
46
48
|
return { version: ESTATE_VERSION, sites };
|
|
47
49
|
}
|
|
50
|
+
function sharedGround(a, b) {
|
|
51
|
+
const [x, y] = [groundLabel(a.ground), groundLabel(b.ground)];
|
|
52
|
+
if (x === y) return `${x} ground`;
|
|
53
|
+
const temperature = temperatureName(a.ground);
|
|
54
|
+
if (temperature !== "neutral" && temperature === temperatureName(b.ground)) return `${temperature} ground`;
|
|
55
|
+
return `${a.ground.l < 0.3 ? "dark" : "light"} ground`;
|
|
56
|
+
}
|
|
57
|
+
function sharedRhythm(a, b) {
|
|
58
|
+
const bands = rhythmOf(a) ?? [];
|
|
59
|
+
const word = (band) => band;
|
|
60
|
+
if (bands.length <= 1) return `${bands[0] ? word(bands[0]) : "light"} page throughout`;
|
|
61
|
+
const second = bands[1] === "mid" ? "mid-tone band" : `${word(bands[1])} band`;
|
|
62
|
+
return `${word(bands[0])} hero then ${second}`;
|
|
63
|
+
}
|
|
64
|
+
function sharedHero(a, b) {
|
|
65
|
+
const hero = a.sections?.find((s) => s.role === "hero");
|
|
66
|
+
const placement = hero && hero.centred >= 0.5 ? "centred" : "left-aligned";
|
|
67
|
+
const headline = a.hero?.headline && b.hero?.headline && a.hero.headline.class === b.hero.headline.class ? `${a.hero.headline.class} ` : "";
|
|
68
|
+
const image = a.hero?.image && a.hero.image === b.hero?.image && a.hero.image !== "none" ? `, ${a.hero.image === "full" ? "full-width picture" : `photo ${a.hero.image}`}` : "";
|
|
69
|
+
return `${placement} ${headline}hero${image}`;
|
|
70
|
+
}
|
|
48
71
|
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
49
72
|
const out = [];
|
|
50
|
-
const close = (k) =>
|
|
73
|
+
const close = (k) => {
|
|
74
|
+
const p = d.parts[k];
|
|
75
|
+
return p !== null && p <= SHARED_PART;
|
|
76
|
+
};
|
|
51
77
|
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
52
78
|
if (close("type")) {
|
|
53
79
|
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
54
80
|
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
55
81
|
}
|
|
56
|
-
if (close("ground")) out.push(
|
|
82
|
+
if (close("ground")) out.push(sharedGround(a, b));
|
|
57
83
|
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
58
84
|
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
59
85
|
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
60
|
-
if (close("
|
|
86
|
+
if (close("hero")) out.push(sharedHero(a, b));
|
|
87
|
+
if (close("rhythm")) out.push(sharedRhythm(a, b));
|
|
88
|
+
if (close("layout")) out.push("same running order");
|
|
61
89
|
return out;
|
|
62
90
|
}
|
|
63
91
|
function compareToEstate(target, register, options = {}) {
|
|
@@ -105,7 +133,7 @@ async function snapshotOf(target, cwd) {
|
|
|
105
133
|
return snap;
|
|
106
134
|
}
|
|
107
135
|
if (!/^https?:\/\//.test(target)) throw new Error(`${target} is neither a URL, a snapshot file, nor a site in the register`);
|
|
108
|
-
const { snapshotUrl } = await import("./audit-
|
|
136
|
+
const { snapshotUrl } = await import("./audit-X7LGATAQ.js");
|
|
109
137
|
return snapshotUrl(target);
|
|
110
138
|
}
|
|
111
139
|
var line = (distance, sibling, shared) => `${distance.toFixed(2)}${sibling ? " SIBLING" : ""}${shared.length ? ` shares ${shared.join(", ")}` : ""}`;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-OQXTQQAU.js";
|
|
4
4
|
import {
|
|
5
5
|
parseFlags
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GNVZGG3E.js";
|
|
7
7
|
import {
|
|
8
8
|
toJson
|
|
9
9
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
hueSwatch,
|
|
23
23
|
nullPrompt,
|
|
24
24
|
scanSource
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-IHT6CJOM.js";
|
|
26
26
|
import {
|
|
27
27
|
extractStrings
|
|
28
28
|
} from "./chunk-4QWEAL4G.js";
|
|
@@ -120,7 +120,7 @@ async function build(flags, io) {
|
|
|
120
120
|
const toShoot = ids.filter((id) => existsSync(join(pages, `${id}.html`)) && !existsSync(join(shots, `${id}.json`)));
|
|
121
121
|
if (toShoot.length) {
|
|
122
122
|
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"}`);
|
|
123
|
-
const { snapshotUrls } = await import("./audit-
|
|
123
|
+
const { snapshotUrls } = await import("./audit-X7LGATAQ.js");
|
|
124
124
|
const results = await snapshotUrls(toShoot.map((id) => pathToFileURL(join(pages, `${id}.html`)).href));
|
|
125
125
|
results.forEach((r, i) => {
|
|
126
126
|
const id = toShoot[i];
|
|
@@ -241,7 +241,7 @@ async function importPages(flags, io) {
|
|
|
241
241
|
const toShoot = pages.filter((p) => !existsSync(join(shots, `${p.id}.json`)));
|
|
242
242
|
if (toShoot.length) {
|
|
243
243
|
io.out(`craft null: snapshotting ${toShoot.length} page${toShoot.length === 1 ? "" : "s"} from ${flags.builder}`);
|
|
244
|
-
const { snapshotUrls } = await import("./audit-
|
|
244
|
+
const { snapshotUrls } = await import("./audit-X7LGATAQ.js");
|
|
245
245
|
const servers = /* @__PURE__ */ new Map();
|
|
246
246
|
try {
|
|
247
247
|
for (const p of toShoot) if (!servers.has(p.root)) servers.set(p.root, await serveDir(p.root));
|
|
@@ -33,7 +33,7 @@ var PART_AREA = {
|
|
|
33
33
|
effects: { area: "effects", choice: "motif" },
|
|
34
34
|
motion: { area: "motion", choice: "signature" }
|
|
35
35
|
};
|
|
36
|
-
var DEFAULTS = /^(?:ground )?(?:white|grey)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons
|
|
36
|
+
var DEFAULTS = /^(?:ground )?(?:white|grey|light)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons|^light page throughout$/;
|
|
37
37
|
function designFindings(findings, copyTells) {
|
|
38
38
|
return (findings?.findings ?? []).filter((f) => !copyTells.has(f.tell));
|
|
39
39
|
}
|
|
@@ -140,7 +140,7 @@ function areaOfShared(part) {
|
|
|
140
140
|
if (/accent|ground/.test(part)) return "colour";
|
|
141
141
|
if (/buttons/.test(part)) return "shape";
|
|
142
142
|
if (/reveal/.test(part)) return "motion";
|
|
143
|
-
if (/running order/.test(part)) return "layout";
|
|
143
|
+
if (/running order|hero|page throughout/.test(part)) return "layout";
|
|
144
144
|
return "effects";
|
|
145
145
|
}
|
|
146
146
|
function choiceOfShared(part) {
|
|
@@ -150,7 +150,7 @@ function choiceOfShared(part) {
|
|
|
150
150
|
if (/ground/.test(part)) return "ground";
|
|
151
151
|
if (/buttons/.test(part)) return "shape";
|
|
152
152
|
if (/reveal/.test(part)) return "signature";
|
|
153
|
-
return /running order/.test(part) ? void 0 : "motif";
|
|
153
|
+
return /running order|hero|page throughout/.test(part) ? void 0 : "motif";
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
export {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
levenshtein,
|
|
11
11
|
normaliseFamily,
|
|
12
12
|
parseColour
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IHT6CJOM.js";
|
|
14
14
|
import {
|
|
15
15
|
TRADE_NOUNS
|
|
16
16
|
} from "./chunk-4QWEAL4G.js";
|
|
@@ -541,6 +541,33 @@ var LICENCES = [
|
|
|
541
541
|
checked: "2026-09-24",
|
|
542
542
|
source: "https://iconscout.com/licenses",
|
|
543
543
|
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."
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "Saira Semi Condensed",
|
|
547
|
+
subject: "face",
|
|
548
|
+
licence: "OFL-1.1",
|
|
549
|
+
multiClient: "yes",
|
|
550
|
+
checked: "2026-10-09",
|
|
551
|
+
source: "https://github.com/google/fonts/blob/main/ofl/sairasemicondensed/OFL.txt",
|
|
552
|
+
note: "Copyright 2016 The Saira Project Authors (omnibus.type@gmail.com), reserved font name Saira. Keep the OFL notice with the font files; a modified build may not be called Saira. Chosen for Doman Digital's own site (DOM-397, 9 October 2026)."
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "Public Sans",
|
|
556
|
+
subject: "face",
|
|
557
|
+
licence: "OFL-1.1",
|
|
558
|
+
multiClient: "yes",
|
|
559
|
+
checked: "2026-10-09",
|
|
560
|
+
source: "https://github.com/google/fonts/blob/main/ofl/publicsans/OFL.txt",
|
|
561
|
+
note: "Copyright 2015 The Public Sans Project Authors (https://github.com/uswds/public-sans). Keep the OFL notice with the font files. Chosen for Doman Digital's own site (DOM-397, 9 October 2026)."
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: "Martian Mono",
|
|
565
|
+
subject: "face",
|
|
566
|
+
licence: "OFL-1.1",
|
|
567
|
+
multiClient: "yes",
|
|
568
|
+
checked: "2026-10-09",
|
|
569
|
+
source: "https://github.com/google/fonts/blob/main/ofl/martianmono/OFL.txt",
|
|
570
|
+
note: "Copyright 2021 The Martian Mono Project Authors (https://github.com/evilmartians/mono). Keep the OFL notice with the font files. Chosen for Doman Digital's own site (DOM-397, 9 October 2026)."
|
|
544
571
|
}
|
|
545
572
|
];
|
|
546
573
|
function faceLicence(family) {
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
auditSnapshot,
|
|
6
6
|
fingerprint
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IHT6CJOM.js";
|
|
8
8
|
import {
|
|
9
9
|
SNAPSHOT_VERSION
|
|
10
10
|
} from "./chunk-QYBAVU3P.js";
|
|
@@ -505,6 +505,7 @@ function collectInPage() {
|
|
|
505
505
|
width: Math.round(b.width),
|
|
506
506
|
height: Math.round(b.height),
|
|
507
507
|
top: Math.round(b.top),
|
|
508
|
+
left: Math.round(b.left),
|
|
508
509
|
role: roleOfImage(el, src, b.width, b.height, alt),
|
|
509
510
|
alt,
|
|
510
511
|
decorative,
|
|
@@ -752,7 +753,7 @@ async function snapshotUrls(urls, options = {}) {
|
|
|
752
753
|
const out = [];
|
|
753
754
|
try {
|
|
754
755
|
for (const url of urls) {
|
|
755
|
-
const page = await browser.newPage({ viewport: options.viewport ?? { width: 1440, height: 900 } });
|
|
756
|
+
const page = await browser.newPage({ viewport: options.viewport ?? { width: 1440, height: 900 }, ...options.colorScheme ? { colorScheme: options.colorScheme } : {} });
|
|
756
757
|
try {
|
|
757
758
|
const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout: options.timeoutMs ?? 45e3 });
|
|
758
759
|
const status = typeof response?.status === "function" ? response.status() : 200;
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
runHarvest,
|
|
10
10
|
runNull,
|
|
11
11
|
serveDir
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NCBCZFDY.js";
|
|
13
13
|
import "./chunk-OQXTQQAU.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-GNVZGG3E.js";
|
|
15
15
|
import "./chunk-QIEDBL4J.js";
|
|
16
16
|
import "./chunk-WUACK2HX.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-IHT6CJOM.js";
|
|
18
18
|
import "./chunk-4QWEAL4G.js";
|
|
19
19
|
import "./chunk-QYBAVU3P.js";
|
|
20
20
|
export {
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
PROPOSED,
|
|
8
8
|
researchPrompt,
|
|
9
9
|
validateDirection
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-RNHV2SXC.js";
|
|
11
11
|
import {
|
|
12
12
|
parseFlags
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GNVZGG3E.js";
|
|
14
14
|
import {
|
|
15
15
|
toJson
|
|
16
16
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
isCream,
|
|
27
27
|
oklchToRgb,
|
|
28
28
|
parseColour
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-IHT6CJOM.js";
|
|
30
30
|
import "./chunk-4QWEAL4G.js";
|
|
31
31
|
import "./chunk-QYBAVU3P.js";
|
|
32
32
|
|
|
@@ -208,7 +208,7 @@ var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
|
208
208
|
async function snapshotFrom(target, cwd) {
|
|
209
209
|
if (!target) return void 0;
|
|
210
210
|
if (/^https?:\/\//.test(target)) {
|
|
211
|
-
const { snapshotUrl } = await import("./audit-
|
|
211
|
+
const { snapshotUrl } = await import("./audit-X7LGATAQ.js");
|
|
212
212
|
return snapshotUrl(target);
|
|
213
213
|
}
|
|
214
214
|
return readSnapshot(readJson(resolve(cwd, target)), target);
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
characterReport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OKVHWXYV.js";
|
|
4
4
|
import {
|
|
5
5
|
merge
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-J35UYAVV.js";
|
|
7
|
+
import "./chunk-W7PMRSDG.js";
|
|
8
8
|
import "./chunk-JYNXGCYZ.js";
|
|
9
9
|
import {
|
|
10
10
|
SIBLING_AT,
|
|
11
11
|
compareToEstate,
|
|
12
12
|
loadEstate
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-LWKMGJSW.js";
|
|
14
14
|
import {
|
|
15
15
|
PAGE_TYPES,
|
|
16
16
|
validateDirection
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RNHV2SXC.js";
|
|
18
18
|
import {
|
|
19
19
|
loadNull
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-NCBCZFDY.js";
|
|
21
21
|
import "./chunk-OQXTQQAU.js";
|
|
22
22
|
import {
|
|
23
23
|
COPY_FILE,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
loadExceptions,
|
|
29
29
|
parseFlags,
|
|
30
30
|
readPaths
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GNVZGG3E.js";
|
|
32
32
|
import {
|
|
33
33
|
toJson
|
|
34
34
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
fingerprint,
|
|
43
43
|
scanSource,
|
|
44
44
|
typicality
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-IHT6CJOM.js";
|
|
46
46
|
import "./chunk-4QWEAL4G.js";
|
|
47
47
|
import "./chunk-QYBAVU3P.js";
|
|
48
48
|
|
|
@@ -126,7 +126,7 @@ async function runReport(command, args, io) {
|
|
|
126
126
|
if (/\.json$/i.test(target2) && existsSync(local)) {
|
|
127
127
|
snap = readSnapshot(JSON.parse(readFileSync(local, "utf8")), target2);
|
|
128
128
|
} else {
|
|
129
|
-
const { snapshotUrl } = await import("./audit-
|
|
129
|
+
const { snapshotUrl } = await import("./audit-X7LGATAQ.js");
|
|
130
130
|
snap = await snapshotUrl(target2);
|
|
131
131
|
}
|
|
132
132
|
const fp = fingerprint(snap);
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
formatPairs,
|
|
5
5
|
loadEstate,
|
|
6
6
|
runEstate
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-LWKMGJSW.js";
|
|
8
|
+
import "./chunk-RNHV2SXC.js";
|
|
9
|
+
import "./chunk-NCBCZFDY.js";
|
|
10
10
|
import "./chunk-OQXTQQAU.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-GNVZGG3E.js";
|
|
12
12
|
import "./chunk-QIEDBL4J.js";
|
|
13
13
|
import "./chunk-WUACK2HX.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-IHT6CJOM.js";
|
|
15
15
|
import "./chunk-4QWEAL4G.js";
|
|
16
16
|
import "./chunk-QYBAVU3P.js";
|
|
17
17
|
export {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TELL_HEAVY
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OKVHWXYV.js";
|
|
4
4
|
import {
|
|
5
5
|
expandNullTargets,
|
|
6
6
|
loadNull
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-NCBCZFDY.js";
|
|
8
8
|
import "./chunk-OQXTQQAU.js";
|
|
9
9
|
import {
|
|
10
10
|
parseFlags
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GNVZGG3E.js";
|
|
12
12
|
import {
|
|
13
13
|
toJson
|
|
14
14
|
} from "./chunk-QIEDBL4J.js";
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
fingerprint,
|
|
22
22
|
scanSource,
|
|
23
23
|
typicality
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-IHT6CJOM.js";
|
|
25
25
|
import "./chunk-4QWEAL4G.js";
|
|
26
26
|
import {
|
|
27
27
|
SNAPSHOT_VERSION
|
|
@@ -188,7 +188,7 @@ async function runCalibrate(args, io) {
|
|
|
188
188
|
if (toTake.length) {
|
|
189
189
|
io.err(`craft calibrate: taking ${toTake.length} snapshot${toTake.length === 1 ? "" : "s"}`);
|
|
190
190
|
try {
|
|
191
|
-
const { snapshotUrls } = await import("./audit-
|
|
191
|
+
const { snapshotUrls } = await import("./audit-X7LGATAQ.js");
|
|
192
192
|
const results = await snapshotUrls(toTake.map((t) => t.url));
|
|
193
193
|
results.forEach((r, i) => {
|
|
194
194
|
const { entry, keep } = toTake[i];
|