@domandigital/craft 0.11.0 → 0.13.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 +36 -0
- package/CHARACTER.md +100 -1
- package/COPY.md +6 -2
- package/README.md +5 -0
- package/dist/{audit-QIPXL7E4.js → audit-5TYDYRXV.js} +9 -3
- package/dist/audit.cjs +881 -95
- package/dist/audit.d.cts +161 -4
- package/dist/audit.d.ts +161 -4
- package/dist/audit.js +878 -95
- package/dist/{chunk-2AJB27BJ.js → chunk-3VTPI7BU.js} +26 -11
- package/dist/chunk-CZDOMIVS.js +19 -0
- package/dist/chunk-ESCVTKPN.js +217 -0
- package/dist/{chunk-3NGWXZZE.js → chunk-GMVXADJ6.js} +2929 -2082
- package/dist/{chunk-MSQ57PJU.js → chunk-IMB4V34S.js} +31 -18
- package/dist/chunk-JYNXGCYZ.js +75 -0
- package/dist/{chunk-HOHHQB4N.js → chunk-K2BZN4GA.js} +52 -16
- package/dist/{chunk-JP75P2DU.js → chunk-ZLDA2SYB.js} +342 -16
- package/dist/{chunk-JWHC67ZS.js → chunk-ZZ5CKOJP.js} +121 -1
- package/dist/cli-BKJMLD3H.js +18 -0
- package/dist/{cli-U3SZVHSK.js → cli-DRKAMFAH.js} +12 -80
- package/dist/{cli-4ESXOLKV.js → cli-ILH7OO5G.js} +4 -4
- package/dist/{cli-DBZMVYYV.js → cli-J5ECRCD4.js} +17 -17
- package/dist/cli-N2IDXH7W.js +20 -0
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1291 -502
- package/dist/index.d.cts +586 -200
- package/dist/index.d.ts +586 -200
- package/dist/index.js +1263 -502
- package/licences.json +104 -0
- package/package.json +3 -2
- package/word-lists.json +1 -1
- package/dist/chunk-3OZQ4C5R.js +0 -459
- package/dist/chunk-QMYERCBB.js +0 -105
- package/dist/cli-T5RD2BTK.js +0 -17
- package/dist/cli-VREYWCT4.js +0 -16
package/dist/index.d.cts
CHANGED
|
@@ -706,7 +706,13 @@ declare const DENSITY_NAMES: DensityName[];
|
|
|
706
706
|
* to a report and judged again later without a browser. Capturing one needs a
|
|
707
707
|
* browser (see `@domandigital/craft/audit`); judging one does not.
|
|
708
708
|
*/
|
|
709
|
-
|
|
709
|
+
/**
|
|
710
|
+
* 2 adds `role` and `geometry` per section, `rhythmVariance` for the page and
|
|
711
|
+
* the screenshot measures in `visual`. All four are optional: a version 1
|
|
712
|
+
* snapshot reads as version 2 without them (see `readSnapshot`), and nothing
|
|
713
|
+
* that judged a v1 snapshot judges it differently.
|
|
714
|
+
*/
|
|
715
|
+
declare const SNAPSHOT_VERSION = 2;
|
|
710
716
|
interface SnapshotColour {
|
|
711
717
|
/** `rgb(r, g, b)` or `rgba(r, g, b, a)`, as the browser computed it. */
|
|
712
718
|
value: string;
|
|
@@ -745,6 +751,27 @@ interface SnapshotControl {
|
|
|
745
751
|
text: string;
|
|
746
752
|
}
|
|
747
753
|
type SectionKind = "hero" | "logos" | "stats" | "cards" | "marquee" | "text" | "other";
|
|
754
|
+
/**
|
|
755
|
+
* What a section is for, finer than `kind`. `kind` keeps its version 1
|
|
756
|
+
* meaning because the rendered tells, the null models and the estate were
|
|
757
|
+
* calibrated on it; `role` is what component-level comparison reads.
|
|
758
|
+
*/
|
|
759
|
+
type SectionRole = SectionKind | "cta-band" | "footer-cta" | "pricing" | "testimonials" | "faq" | "process" | "features" | "team" | "contact";
|
|
760
|
+
/** How a section is laid out, measured, not judged. */
|
|
761
|
+
interface SectionGeometry {
|
|
762
|
+
/** Share of the section's text blocks that are centred, 0 to 1. */
|
|
763
|
+
centredShare: number;
|
|
764
|
+
/** Content area balance about the vertical centre line: 1 mirror-even, 0 all on one side. */
|
|
765
|
+
mirrorSymmetry: number;
|
|
766
|
+
/** Share of the section's area with no text, image or control in it, 0 to 1. */
|
|
767
|
+
whitespaceRatio: number;
|
|
768
|
+
/** Width the content spans, over the viewport width. */
|
|
769
|
+
contentWidthRatio: number;
|
|
770
|
+
/** The painted background behind the section, as computed. */
|
|
771
|
+
background: string;
|
|
772
|
+
/** Buttons and button-styled links in the section. */
|
|
773
|
+
controls: number;
|
|
774
|
+
}
|
|
748
775
|
interface SnapshotSection {
|
|
749
776
|
top: number;
|
|
750
777
|
height: number;
|
|
@@ -757,6 +784,70 @@ interface SnapshotSection {
|
|
|
757
784
|
iconCards: number;
|
|
758
785
|
/** Hidden at load, waiting for a scroll to reveal it. */
|
|
759
786
|
hiddenAtLoad: boolean;
|
|
787
|
+
/** Version 2. */
|
|
788
|
+
role?: SectionRole;
|
|
789
|
+
/** Version 2. */
|
|
790
|
+
geometry?: SectionGeometry;
|
|
791
|
+
/** Version 2: short pill or label texts in the section ("Most popular", "New"), at most 5. */
|
|
792
|
+
badges?: string[];
|
|
793
|
+
/** Version 2: small round portraits (at most 96px, roughly square, radius 40% or more). */
|
|
794
|
+
avatars?: number;
|
|
795
|
+
/** Version 2: a horizontal scroller, snap track or prev/next control holds the content. */
|
|
796
|
+
carousel?: boolean;
|
|
797
|
+
/** Version 2: large standalone figures ("500+", "98%", "4.9/5") at 24px or more. */
|
|
798
|
+
figures?: number;
|
|
799
|
+
}
|
|
800
|
+
type ImageRole = "photo" | "illustration" | "icon" | "avatar" | "logo";
|
|
801
|
+
/**
|
|
802
|
+
* What the image's own bytes say about where it came from. Only a positive
|
|
803
|
+
* is evidence: most images carry no metadata at all, and a re-encode strips
|
|
804
|
+
* it, so no marker proves nothing.
|
|
805
|
+
*/
|
|
806
|
+
interface ImageProvenance {
|
|
807
|
+
/** The IPTC digital source type found, if any: "trainedAlgorithmicMedia" or "compositeWithTrainedAlgorithmicMedia". */
|
|
808
|
+
digitalSourceType: string | null;
|
|
809
|
+
/** Where the marker sat: an XMP packet, a C2PA manifest, or somewhere else in the file. */
|
|
810
|
+
source: "xmp" | "c2pa" | "other" | null;
|
|
811
|
+
/** The file carries a C2PA manifest (Content Credentials), whatever it says. */
|
|
812
|
+
c2pa: boolean;
|
|
813
|
+
/** Bytes read, from the start of the file. */
|
|
814
|
+
bytes: number;
|
|
815
|
+
}
|
|
816
|
+
/** One image as the visitor sees it: an `img`, or a CSS background large enough to be a picture. */
|
|
817
|
+
interface SnapshotImage {
|
|
818
|
+
/** The URL the browser loaded, unwrapped from an image optimiser (`/_next/image?url=`), or a `data:` URI cut to 64 characters. */
|
|
819
|
+
src: string;
|
|
820
|
+
/** Host of `src`, or null for a `data:` URI. */
|
|
821
|
+
host: string | null;
|
|
822
|
+
width: number;
|
|
823
|
+
height: number;
|
|
824
|
+
top: number;
|
|
825
|
+
role: ImageRole;
|
|
826
|
+
alt: string | null;
|
|
827
|
+
/** Empty alt, `aria-hidden` or `role="presentation"`. */
|
|
828
|
+
decorative: boolean;
|
|
829
|
+
/** Index into `sections`, or null when the image sits outside them. */
|
|
830
|
+
section: number | null;
|
|
831
|
+
/** A CSS `background-image`, not an `img`. */
|
|
832
|
+
background: boolean;
|
|
833
|
+
/** Present when the bytes were read. */
|
|
834
|
+
provenance?: ImageProvenance;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Measures of the rendered pixels, from a full-page screenshot (capped at
|
|
838
|
+
* `height`). None of them passes or fails anything: they describe.
|
|
839
|
+
*/
|
|
840
|
+
interface SnapshotVisual {
|
|
841
|
+
/** Hasler and Süsstrunk (2003) colourfulness, M = σ_rgyb + 0.3 μ_rgyb, on 0-255 channels. Grey is 0. */
|
|
842
|
+
colourfulness: number;
|
|
843
|
+
/** Share of sampled pixels on a luminance edge: a complexity proxy, 0 to 1. */
|
|
844
|
+
edgeDensity: number;
|
|
845
|
+
/** 1 minus the mean luminance difference between each pixel and its left-right mirror, 0 to 1. */
|
|
846
|
+
symmetry: number;
|
|
847
|
+
width: number;
|
|
848
|
+
height: number;
|
|
849
|
+
/** Pixels actually read: large pages are sampled on a grid. */
|
|
850
|
+
sampled: number;
|
|
760
851
|
}
|
|
761
852
|
interface SnapshotGradient {
|
|
762
853
|
/** Colour stops, as computed. */
|
|
@@ -811,6 +902,17 @@ interface Snapshot {
|
|
|
811
902
|
sections: SnapshotSection[];
|
|
812
903
|
effects: SnapshotEffects;
|
|
813
904
|
motion: SnapshotMotion;
|
|
905
|
+
/** Version 2: coefficient of variation of the section heights. Null with fewer than two sections. */
|
|
906
|
+
rhythmVariance?: number | null;
|
|
907
|
+
/** Version 2: absent when the page could not be screenshotted. */
|
|
908
|
+
visual?: SnapshotVisual;
|
|
909
|
+
/**
|
|
910
|
+
* Version 2, craft 0.14 and later: every image on the page, largest first, at
|
|
911
|
+
* most 60. Absent in older snapshots, which the imagery tells never judge.
|
|
912
|
+
*/
|
|
913
|
+
images?: SnapshotImage[];
|
|
914
|
+
/** Set when a version 1 snapshot was read and upgraded in memory. */
|
|
915
|
+
migratedFrom?: 1;
|
|
814
916
|
/** The design-system measures trawl has collected since v7, kept so it can switch to this. */
|
|
815
917
|
metrics: {
|
|
816
918
|
fontSizes: number[];
|
|
@@ -846,7 +948,13 @@ interface Snapshot {
|
|
|
846
948
|
*/
|
|
847
949
|
type Generation = 1 | 2 | 3;
|
|
848
950
|
/** Where a tell is detected first. A source tell may also carry a rendered path. */
|
|
849
|
-
|
|
951
|
+
/**
|
|
952
|
+
* Where a tell is detected first. `rendered` tells exist only on a rendered
|
|
953
|
+
* page (a pricing trio, a centred CTA band): source code cannot show them
|
|
954
|
+
* reliably, so they have no source detector and no source fixtures, only a
|
|
955
|
+
* rendered path with its own flag and pass snapshots.
|
|
956
|
+
*/
|
|
957
|
+
type Surface = "source" | "copy" | "rendered";
|
|
850
958
|
/**
|
|
851
959
|
* `warn` is reported and never fails a run unless `--strict` is passed.
|
|
852
960
|
* `block` fails a run.
|
|
@@ -966,7 +1074,12 @@ interface CopyTell extends TellBase {
|
|
|
966
1074
|
surface: "copy";
|
|
967
1075
|
detect(ctx: CopyContext): Hit[];
|
|
968
1076
|
}
|
|
969
|
-
|
|
1077
|
+
/** A tell with a rendered path only. See `Surface`. */
|
|
1078
|
+
interface RenderedTell extends Omit<TellBase, "fixtures" | "rendered"> {
|
|
1079
|
+
surface: "rendered";
|
|
1080
|
+
rendered: RenderedPath;
|
|
1081
|
+
}
|
|
1082
|
+
type Tell = SourceTell | CopyTell | RenderedTell;
|
|
970
1083
|
/**
|
|
971
1084
|
* A declared reason to keep a tell. Lives in the site's `art-direction.json`.
|
|
972
1085
|
*
|
|
@@ -1031,7 +1144,7 @@ interface CheckReport {
|
|
|
1031
1144
|
* Bumped whenever an entry is added, removed or its detection changes, so a
|
|
1032
1145
|
* report can say which list it was judged against.
|
|
1033
1146
|
*/
|
|
1034
|
-
declare const CATALOGUE_VERSION = "2026.09.
|
|
1147
|
+
declare const CATALOGUE_VERSION = "2026.09.11";
|
|
1035
1148
|
declare const CATALOGUE: readonly Tell[];
|
|
1036
1149
|
declare function tellById(id: string): Tell | undefined;
|
|
1037
1150
|
/**
|
|
@@ -1112,6 +1225,31 @@ interface HouseRule {
|
|
|
1112
1225
|
declare const HOUSE: Readonly<Record<string, HouseRule>>;
|
|
1113
1226
|
/** The house rule for a copy tell. Unknown ids are review, so a new tell never blocks. */
|
|
1114
1227
|
declare function houseRule(id: string, name?: string): HouseRule;
|
|
1228
|
+
/**
|
|
1229
|
+
* What a repo may and may not do to the house gate.
|
|
1230
|
+
*
|
|
1231
|
+
* A repo can raise a tell's severity in `craft.config.json`, or except a
|
|
1232
|
+
* design tell in `art-direction.json`. It cannot lower the blocking tier: a
|
|
1233
|
+
* `warn` or `off` there, or an exception naming a blocking tell, would let one
|
|
1234
|
+
* line and one sentence switch off a rule COPY.md calls not a judgment call,
|
|
1235
|
+
* in every commit that repo makes. `chatbot-residue` is the sharpest case: it
|
|
1236
|
+
* is evidence of a paste, and no client reason makes a paste acceptable.
|
|
1237
|
+
*
|
|
1238
|
+
* A genuine one-off (a style guide quoting an em dash, a testimonial) takes
|
|
1239
|
+
* `copy-ok` on its line: local, in the diff a reviewer reads, and listed in
|
|
1240
|
+
* every report. A rule that is wrong for everyone is changed here.
|
|
1241
|
+
*/
|
|
1242
|
+
declare const 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";
|
|
1243
|
+
declare function holdHouseBlocks<E extends {
|
|
1244
|
+
tell: string;
|
|
1245
|
+
}, O extends {
|
|
1246
|
+
level: string;
|
|
1247
|
+
}>(exceptions: E[], severity: Record<string, O>): {
|
|
1248
|
+
exceptions: E[];
|
|
1249
|
+
severity: Record<string, O>;
|
|
1250
|
+
refused: E[];
|
|
1251
|
+
refusedSeverity: string[];
|
|
1252
|
+
};
|
|
1115
1253
|
|
|
1116
1254
|
/**
|
|
1117
1255
|
* Protected facts: what a rewrite must never lose or invent.
|
|
@@ -1385,6 +1523,71 @@ declare function toJson(data: object): string;
|
|
|
1385
1523
|
|
|
1386
1524
|
declare const RENDERED_PATHS: Record<string, RenderedPath>;
|
|
1387
1525
|
|
|
1526
|
+
/**
|
|
1527
|
+
* Tells that exist only on a rendered page: the stock components.
|
|
1528
|
+
*
|
|
1529
|
+
* A pricing trio with a "Most popular" badge, a centred band with one button,
|
|
1530
|
+
* an avatar carousel of testimonials: none of these is visible in one source
|
|
1531
|
+
* file, and all of them are visible on the page. They read the version 2
|
|
1532
|
+
* snapshot's `role`, `geometry` and section parts, so a version 1 snapshot
|
|
1533
|
+
* never trips them.
|
|
1534
|
+
*
|
|
1535
|
+
* Each changes the look, never the page grammar. The fix for a stock pricing
|
|
1536
|
+
* trio keeps the prices; the fix for a stock band keeps the call to action.
|
|
1537
|
+
*/
|
|
1538
|
+
|
|
1539
|
+
declare const RENDERED_TELLS: RenderedTell[];
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Imagery and provenance: what the pictures on a rendered page say about
|
|
1543
|
+
* where they came from.
|
|
1544
|
+
*
|
|
1545
|
+
* A page built with no assets reaches for three things: a stock library, a
|
|
1546
|
+
* generator, or no pictures at all and a grid of icons where the work should
|
|
1547
|
+
* be. Each is visible in the snapshot's `images`. A snapshot taken before
|
|
1548
|
+
* craft recorded images has none, and none of these tells judges it.
|
|
1549
|
+
*
|
|
1550
|
+
* Each fix keeps the page's job and changes the evidence: a photo of the
|
|
1551
|
+
* client's own work in place of a library one.
|
|
1552
|
+
*/
|
|
1553
|
+
|
|
1554
|
+
/** Stock libraries and placeholder services, by the domain they serve images from. */
|
|
1555
|
+
declare const STOCK_PHOTO_HOSTS: Readonly<Record<string, string>>;
|
|
1556
|
+
/** Services that hand out a face on request: placeholder portraits and generated people. */
|
|
1557
|
+
declare const STOCK_AVATAR_HOSTS: Readonly<Record<string, string>>;
|
|
1558
|
+
/** Where a stock picture came from, by host or by file name, or null. */
|
|
1559
|
+
declare function stockSource(image: Pick<SnapshotImage, "src" | "host">): string | null;
|
|
1560
|
+
/** The placeholder-face service an avatar came from, or a stock library, or null. */
|
|
1561
|
+
declare function stockAvatarSource(image: Pick<SnapshotImage, "src" | "host">): string | null;
|
|
1562
|
+
declare const IMAGERY_TELLS: RenderedTell[];
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Where an image came from, as far as its own bytes say.
|
|
1566
|
+
*
|
|
1567
|
+
* Generators that label their output do it in one of two places: an XMP
|
|
1568
|
+
* packet (Adobe's metadata format, in JPEG, PNG, WebP and AVIF) or a C2PA
|
|
1569
|
+
* manifest (Content Credentials, a JUMBF box). Both name the IPTC digital
|
|
1570
|
+
* source type, and two of its values mean a trained model made the picture:
|
|
1571
|
+
*
|
|
1572
|
+
* http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia
|
|
1573
|
+
* http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia
|
|
1574
|
+
*
|
|
1575
|
+
* This is a byte scan with no parser and no dependency: the IRI is plain text
|
|
1576
|
+
* in XMP and a CBOR text string in C2PA, so it reads the same either way.
|
|
1577
|
+
*
|
|
1578
|
+
* Only a positive is evidence. Most images carry no metadata, every CMS and
|
|
1579
|
+
* image optimiser that re-encodes strips it, and a PNG can keep its metadata
|
|
1580
|
+
* after the pixels, beyond what is read. No marker proves nothing, and this
|
|
1581
|
+
* never says an image is real.
|
|
1582
|
+
*/
|
|
1583
|
+
|
|
1584
|
+
/** Bytes read from the start of a file. JPEG and WebP metadata sit well inside it. */
|
|
1585
|
+
declare const PROVENANCE_SCAN_BYTES: number;
|
|
1586
|
+
/** The IPTC digital source types that mean a trained model made the image, most specific first. */
|
|
1587
|
+
declare const AI_SOURCE_TYPES: readonly ["compositeWithTrainedAlgorithmicMedia", "trainedAlgorithmicMedia"];
|
|
1588
|
+
/** Read an image's leading bytes for an AI digital source type. Pure. */
|
|
1589
|
+
declare function scanProvenance(bytes: Uint8Array): ImageProvenance;
|
|
1590
|
+
|
|
1388
1591
|
/**
|
|
1389
1592
|
* A plain, decided-looking page as a snapshot, for fixtures.
|
|
1390
1593
|
*
|
|
@@ -1411,6 +1614,41 @@ type FontClass = "serif" | "sans" | "mono" | "script" | "system";
|
|
|
1411
1614
|
declare function normaliseFamily(raw: string): string;
|
|
1412
1615
|
declare function fontClass(family: string): FontClass;
|
|
1413
1616
|
|
|
1617
|
+
/**
|
|
1618
|
+
* Read a saved snapshot of any version this craft understands. Pure.
|
|
1619
|
+
*
|
|
1620
|
+
* Version 2 only added optional fields, so a version 1 snapshot is a valid
|
|
1621
|
+
* version 2 snapshot without them. It is marked `migratedFrom: 1` so a report
|
|
1622
|
+
* can say which measures it lacks, rather than reading their absence as zero.
|
|
1623
|
+
*/
|
|
1624
|
+
|
|
1625
|
+
declare const READABLE_SNAPSHOT_VERSIONS: readonly [1, 2];
|
|
1626
|
+
declare function readSnapshot(data: unknown, source?: string): Snapshot;
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Measures of a screenshot's pixels. Pure: RGBA in, numbers out, no decoder
|
|
1630
|
+
* and no browser, so it is tested on synthetic images.
|
|
1631
|
+
*
|
|
1632
|
+
* Only measures that need no model are here, after Miniukovich and De Angeli
|
|
1633
|
+
* (2015, "Computation of interface aesthetics"): colourfulness, an edge-density
|
|
1634
|
+
* complexity proxy, and left-right symmetry. None of them is a verdict. A
|
|
1635
|
+
* sober site and a busy one can both be decided; these say which it is.
|
|
1636
|
+
*/
|
|
1637
|
+
|
|
1638
|
+
interface RgbaImage {
|
|
1639
|
+
width: number;
|
|
1640
|
+
height: number;
|
|
1641
|
+
/** Row-major, 4 bytes a pixel. */
|
|
1642
|
+
rgba: Uint8Array;
|
|
1643
|
+
}
|
|
1644
|
+
/** A luminance step, on 0-255, that counts as an edge. About a 12% contrast change. */
|
|
1645
|
+
declare const EDGE_STEP = 32;
|
|
1646
|
+
/**
|
|
1647
|
+
* Colourfulness, edge density and symmetry. Images over `maxSamples` pixels
|
|
1648
|
+
* are read on a regular grid, and edges are taken between grid neighbours.
|
|
1649
|
+
*/
|
|
1650
|
+
declare function visualMeasures(img: RgbaImage, maxSamples?: number): SnapshotVisual;
|
|
1651
|
+
|
|
1414
1652
|
/**
|
|
1415
1653
|
* A fingerprint: the handful of choices that make a site look like itself,
|
|
1416
1654
|
* reduced to something two sites can be compared on.
|
|
@@ -1425,9 +1663,39 @@ declare function fontClass(family: string): FontClass;
|
|
|
1425
1663
|
* Pure. Built from a Snapshot, so it describes what a visitor gets.
|
|
1426
1664
|
*/
|
|
1427
1665
|
|
|
1428
|
-
|
|
1666
|
+
/**
|
|
1667
|
+
* 2 adds `sections` (role and geometry in running order). A version 1
|
|
1668
|
+
* fingerprint, or one from a version 1 snapshot, has none, and every
|
|
1669
|
+
* comparison involving it uses `layout` exactly as version 1 did, so the null
|
|
1670
|
+
* models and the estate register compare as they were calibrated until they
|
|
1671
|
+
* are re-snapshotted.
|
|
1672
|
+
*/
|
|
1673
|
+
declare const FINGERPRINT_VERSION = 2;
|
|
1674
|
+
/** One section as layout comparison sees it. */
|
|
1675
|
+
interface FingerprintSection {
|
|
1676
|
+
role: SectionRole;
|
|
1677
|
+
/** Share of centred text, 0 to 1. */
|
|
1678
|
+
centred: number;
|
|
1679
|
+
/** Content width over viewport width, 0 to 1. */
|
|
1680
|
+
width: number;
|
|
1681
|
+
/** Left-right balance of the content, 0 to 1. Component comparison only. */
|
|
1682
|
+
symmetry?: number;
|
|
1683
|
+
/** Buttons in the section. */
|
|
1684
|
+
controls?: number;
|
|
1685
|
+
/** Cards side by side. */
|
|
1686
|
+
cards?: number;
|
|
1687
|
+
/** The section's own background, or null when it is the page ground. */
|
|
1688
|
+
background?: Oklch | null;
|
|
1689
|
+
/** Badge texts ("Most popular"), lower-cased. */
|
|
1690
|
+
badges?: string[];
|
|
1691
|
+
/** Small round portraits. */
|
|
1692
|
+
avatars?: number;
|
|
1693
|
+
carousel?: boolean;
|
|
1694
|
+
/** Large standalone figures. */
|
|
1695
|
+
figures?: number;
|
|
1696
|
+
}
|
|
1429
1697
|
interface Fingerprint {
|
|
1430
|
-
version:
|
|
1698
|
+
version: 1 | 2;
|
|
1431
1699
|
/** The most prominent chromatic colour: button fills first, then painted area, then text. */
|
|
1432
1700
|
accent: Oklch | null;
|
|
1433
1701
|
ground: Oklch;
|
|
@@ -1447,6 +1715,8 @@ interface Fingerprint {
|
|
|
1447
1715
|
effects: string[];
|
|
1448
1716
|
/** Section kinds in running order, hero first. */
|
|
1449
1717
|
layout: SectionKind[];
|
|
1718
|
+
/** Version 2: present when every section carries a role and geometry. */
|
|
1719
|
+
sections?: FingerprintSection[];
|
|
1450
1720
|
}
|
|
1451
1721
|
declare function fingerprint(s: Snapshot): Fingerprint;
|
|
1452
1722
|
interface FingerprintDistance {
|
|
@@ -1476,6 +1746,8 @@ declare const DISTANCE_WEIGHTS: {
|
|
|
1476
1746
|
readonly effects: 0.1;
|
|
1477
1747
|
readonly layout: 0.1;
|
|
1478
1748
|
};
|
|
1749
|
+
/** Layout distance, 0 to 1: by role and geometry when both have them, else by kind as in version 1. */
|
|
1750
|
+
declare function layoutDistance(a: Fingerprint, b: Fingerprint): number;
|
|
1479
1751
|
declare function fingerprintDistance(a: Fingerprint, b: Fingerprint): FingerprintDistance;
|
|
1480
1752
|
/**
|
|
1481
1753
|
* The nearest neighbours of a fingerprint in a set, closest first. Typicality
|
|
@@ -1489,6 +1761,270 @@ declare function nearest<T>(target: Fingerprint, set: {
|
|
|
1489
1761
|
distance: FingerprintDistance;
|
|
1490
1762
|
}[];
|
|
1491
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* The estate register: every site the agency has shipped, fingerprinted, so a
|
|
1766
|
+
* new one can be checked against its siblings. Signal 3 of CHARACTER.md.
|
|
1767
|
+
*
|
|
1768
|
+
* An agency's biggest tell is its clients looking like each other. No single
|
|
1769
|
+
* site shows it; only the set does. So the register holds one fingerprint per
|
|
1770
|
+
* site, and `compareToEstate` says which sites a page sits nearest and what
|
|
1771
|
+
* it shares with them.
|
|
1772
|
+
*
|
|
1773
|
+
* The line for "siblings" is not a number picked here. Two different
|
|
1774
|
+
* businesses are siblings when they sit closer together than two pages Claude
|
|
1775
|
+
* builds for the same brief usually do: if a barber and an electrician look
|
|
1776
|
+
* more alike than two drafts of one barber, nobody chose either look. The
|
|
1777
|
+
* median comes from null models when they are given, and from the 2026-09-23
|
|
1778
|
+
* calibration when they are not.
|
|
1779
|
+
*
|
|
1780
|
+
* Pure. The CLI reads and writes the register file.
|
|
1781
|
+
*/
|
|
1782
|
+
|
|
1783
|
+
declare const ESTATE_VERSION = 1;
|
|
1784
|
+
/**
|
|
1785
|
+
* Median fingerprint distance between two null pages built from the same
|
|
1786
|
+
* brief, across seven briefs and 1,330 pairs (calibration/null, 2026-09-23).
|
|
1787
|
+
*/
|
|
1788
|
+
declare const SIBLING_AT = 0.31;
|
|
1789
|
+
interface EstateSite {
|
|
1790
|
+
/** Short kebab-case id: "rmp", "chair-and-blade". */
|
|
1791
|
+
id: string;
|
|
1792
|
+
client: string;
|
|
1793
|
+
url: string;
|
|
1794
|
+
/** Where the site's source lives, if known: "rmp474/RMP-Electrical". */
|
|
1795
|
+
repo?: string;
|
|
1796
|
+
fingerprint: Fingerprint;
|
|
1797
|
+
/** Rendered tells found when it was added, for the record. */
|
|
1798
|
+
tells: string[];
|
|
1799
|
+
addedAt: string;
|
|
1800
|
+
}
|
|
1801
|
+
interface EstateRegister {
|
|
1802
|
+
version: typeof ESTATE_VERSION;
|
|
1803
|
+
sites: EstateSite[];
|
|
1804
|
+
}
|
|
1805
|
+
declare function emptyEstate(): EstateRegister;
|
|
1806
|
+
/** Add a site, or replace the one with the same id. Sorted by id so the file diffs cleanly. */
|
|
1807
|
+
declare function addSite(register: EstateRegister, site: EstateSite): EstateRegister;
|
|
1808
|
+
/**
|
|
1809
|
+
* What two fingerprints effectively share, named the way a person would see
|
|
1810
|
+
* it: "Fraunces headline", "no accent", "pill buttons". A part counts when its
|
|
1811
|
+
* distance is 0.2 or less.
|
|
1812
|
+
*/
|
|
1813
|
+
declare function sharedParts(a: Fingerprint, b: Fingerprint, d?: FingerprintDistance): string[];
|
|
1814
|
+
interface EstateMatch {
|
|
1815
|
+
id: string;
|
|
1816
|
+
distance: number;
|
|
1817
|
+
sibling: boolean;
|
|
1818
|
+
shared: string[];
|
|
1819
|
+
}
|
|
1820
|
+
/** The sites nearest a fingerprint, closest first, leaving out `exclude` (the site itself). */
|
|
1821
|
+
declare function compareToEstate(target: Fingerprint, register: EstateRegister, options?: {
|
|
1822
|
+
exclude?: string;
|
|
1823
|
+
siblingAt?: number;
|
|
1824
|
+
}): EstateMatch[];
|
|
1825
|
+
interface EstatePair {
|
|
1826
|
+
a: string;
|
|
1827
|
+
b: string;
|
|
1828
|
+
distance: number;
|
|
1829
|
+
sibling: boolean;
|
|
1830
|
+
shared: string[];
|
|
1831
|
+
}
|
|
1832
|
+
/** Every pair in the register, closest first. */
|
|
1833
|
+
declare function estatePairs(register: EstateRegister, siblingAt?: number): EstatePair[];
|
|
1834
|
+
/**
|
|
1835
|
+
* The sibling line from null models: the median distance between two pages
|
|
1836
|
+
* built from the same brief. Each inner array is one brief's fingerprints.
|
|
1837
|
+
*/
|
|
1838
|
+
declare function siblingLine(briefs: Fingerprint[][]): number;
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* The counterfactual: what a model builds for this brief when nobody tells it
|
|
1842
|
+
* anything else. Signal 2 of CHARACTER.md.
|
|
1843
|
+
*
|
|
1844
|
+
* A null model is about twenty pages generated from one brief, each reduced to
|
|
1845
|
+
* a fingerprint. A site is typical when it sits as close to those pages as they
|
|
1846
|
+
* sit to each other. That question keeps working when model defaults move,
|
|
1847
|
+
* which a fixed list of tells cannot: ban Inter and the null model moves with
|
|
1848
|
+
* the model, so the comparison moves too.
|
|
1849
|
+
*
|
|
1850
|
+
* `harvest` reads the same pages the other way round: which choices recur
|
|
1851
|
+
* across them that the catalogue does not know yet. Those are the candidates
|
|
1852
|
+
* for the next generation of tells. A person decides; nothing is added here.
|
|
1853
|
+
*
|
|
1854
|
+
* Pure. Generating pages and snapshotting them lives in the CLI.
|
|
1855
|
+
*/
|
|
1856
|
+
|
|
1857
|
+
declare const NULL_VERSION = 1;
|
|
1858
|
+
/**
|
|
1859
|
+
* The prompt a null run gets: the brief, and nothing about how it should look.
|
|
1860
|
+
* Changing this changes every null model built after it, so it is versioned
|
|
1861
|
+
* by being recorded in each one.
|
|
1862
|
+
*/
|
|
1863
|
+
declare function nullPrompt(brief: string): string;
|
|
1864
|
+
interface NullRun {
|
|
1865
|
+
/** The page's file name without extension: "01". */
|
|
1866
|
+
id: string;
|
|
1867
|
+
fingerprint: Fingerprint;
|
|
1868
|
+
/** Catalogue tells found on the page, source and rendered, each once. */
|
|
1869
|
+
tells: string[];
|
|
1870
|
+
/** Every visible string on the page, for the phrase harvest. */
|
|
1871
|
+
copy: string[];
|
|
1872
|
+
}
|
|
1873
|
+
interface NullModel {
|
|
1874
|
+
version: typeof NULL_VERSION;
|
|
1875
|
+
brief: string;
|
|
1876
|
+
prompt: string;
|
|
1877
|
+
/** What was asked for with --model, or "default". */
|
|
1878
|
+
model: string;
|
|
1879
|
+
catalogueVersion: string;
|
|
1880
|
+
builtAt: string;
|
|
1881
|
+
runs: NullRun[];
|
|
1882
|
+
}
|
|
1883
|
+
/** The HTML document in a model's reply, without code fences or commentary. */
|
|
1884
|
+
declare function extractHtml(reply: string): string | null;
|
|
1885
|
+
/** Fewer runs than this and the spread of the null is noise, so nothing is judged. */
|
|
1886
|
+
declare const MIN_RUNS = 5;
|
|
1887
|
+
/** Typical when at least this share of the null's own pages sit further out. */
|
|
1888
|
+
declare const TYPICAL_AT = 0.1;
|
|
1889
|
+
interface Typicality {
|
|
1890
|
+
/**
|
|
1891
|
+
* 0 to 1: the share of the null's own pages that sit further from the rest
|
|
1892
|
+
* than this page sits from them. 1 is more typical than anything the model
|
|
1893
|
+
* built; 0 is further out than every one of them.
|
|
1894
|
+
*/
|
|
1895
|
+
score: number;
|
|
1896
|
+
typical: boolean;
|
|
1897
|
+
/** Mean distance to the k nearest null pages. */
|
|
1898
|
+
distance: number;
|
|
1899
|
+
/** The same measure for each null page against the others, median. */
|
|
1900
|
+
baseline: number;
|
|
1901
|
+
nearest: {
|
|
1902
|
+
id: string;
|
|
1903
|
+
distance: number;
|
|
1904
|
+
}[];
|
|
1905
|
+
/** The choices this page shares with most of the null: why it reads as typical. */
|
|
1906
|
+
shared: {
|
|
1907
|
+
part: string;
|
|
1908
|
+
value: string;
|
|
1909
|
+
runs: number;
|
|
1910
|
+
of: number;
|
|
1911
|
+
}[];
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* How typical a page is against one or more null models (pooled). Every null
|
|
1915
|
+
* page is scored against the others the same way, leaving itself out, so the
|
|
1916
|
+
* threshold comes from the model's own spread and not from a number picked
|
|
1917
|
+
* here: a page drawn from the null is flagged nine times in ten by
|
|
1918
|
+
* construction, and the calibration checks whether real sites are not.
|
|
1919
|
+
*/
|
|
1920
|
+
declare function typicality(target: Fingerprint, runs: NullRun[], k?: number): Typicality;
|
|
1921
|
+
declare function hueName(o: Oklch | null): string;
|
|
1922
|
+
declare function groundName(o: Oklch): string;
|
|
1923
|
+
declare function shapeName(roundness: number | null): string;
|
|
1924
|
+
/** The discrete choices a fingerprint makes, as part → value. What a person would name. */
|
|
1925
|
+
declare function choicesOf(fp: Fingerprint): Record<string, string[]>;
|
|
1926
|
+
type CandidateKind = "face" | "accent" | "ground" | "shape" | "motion" | "opening" | "tell" | "phrase";
|
|
1927
|
+
interface HarvestCandidate {
|
|
1928
|
+
kind: CandidateKind;
|
|
1929
|
+
value: string;
|
|
1930
|
+
/** Null pages that make this choice, across every model given. */
|
|
1931
|
+
runs: number;
|
|
1932
|
+
of: number;
|
|
1933
|
+
/** Distinct briefs it turned up for. A habit shows across briefs; a brief's own words do not. */
|
|
1934
|
+
briefs: number;
|
|
1935
|
+
/** The catalogue tell that already catches it, or null when nothing does. */
|
|
1936
|
+
known: string | null;
|
|
1937
|
+
/** An example, for a phrase: the string it came from. */
|
|
1938
|
+
example?: string;
|
|
1939
|
+
}
|
|
1940
|
+
interface HarvestOptions {
|
|
1941
|
+
/** Report a choice made by at least this share of pages. Default 0.25. */
|
|
1942
|
+
minShare?: number;
|
|
1943
|
+
/** Which copy tell, if any, already catches a phrase. The CLI passes the catalogue's. */
|
|
1944
|
+
copyTell?: (phrase: string) => string | null;
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Choices that recur across null pages, with the tell that already catches
|
|
1948
|
+
* each, if any. The ones with `known: null` are the next generation's
|
|
1949
|
+
* candidates. Nothing here decides: a person reads them, and a candidate only
|
|
1950
|
+
* becomes a tell with a flag case and a pass case like every other.
|
|
1951
|
+
*/
|
|
1952
|
+
declare function harvest(models: Pick<NullModel, "brief" | "runs">[], options?: HarvestOptions): HarvestCandidate[];
|
|
1953
|
+
/** A representative colour for a hue family, for a report. */
|
|
1954
|
+
declare function hueSwatch(runs: NullRun[], hue: string): string | null;
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Component-level sameness: the CTA band, the pricing table, the testimonial
|
|
1958
|
+
* strip, compared one kind at a time.
|
|
1959
|
+
*
|
|
1960
|
+
* The page fingerprint can say two sites look alike overall. It cannot say
|
|
1961
|
+
* "every site in the estate ends on the same centred band with one orange
|
|
1962
|
+
* button". That is the sameness a client notices when they put two of the
|
|
1963
|
+
* agency's sites side by side, and it lives in the components.
|
|
1964
|
+
*
|
|
1965
|
+
* Pure, over the `sections` of version 2 fingerprints. A version 1
|
|
1966
|
+
* fingerprint has none, so it simply has no components to compare.
|
|
1967
|
+
*/
|
|
1968
|
+
|
|
1969
|
+
/** Badge texts that promote one option over the others. */
|
|
1970
|
+
declare const PROMO_BADGE: RegExp;
|
|
1971
|
+
/** The sections of one role in a fingerprint, in running order. */
|
|
1972
|
+
declare function componentsOf(fp: Fingerprint, role: SectionRole): FingerprintSection[];
|
|
1973
|
+
/**
|
|
1974
|
+
* 0 (the same recipe) to 1 (nothing in common), over the parts both
|
|
1975
|
+
* components carry: layout (centring, width, balance), furniture (buttons,
|
|
1976
|
+
* cards, figures, avatars, carousel, a promo badge) and colour (its own
|
|
1977
|
+
* ground or the page's). Different roles are 1: a pricing table and an FAQ
|
|
1978
|
+
* are not the same component however they are laid out.
|
|
1979
|
+
*/
|
|
1980
|
+
declare function componentDistance(a: FingerprintSection, b: FingerprintSection): number;
|
|
1981
|
+
/** What two components share, as a person would name it. */
|
|
1982
|
+
declare function componentShared(a: FingerprintSection, b: FingerprintSection): string[];
|
|
1983
|
+
/** The closest pair of components of one role between two fingerprints. */
|
|
1984
|
+
declare function closestComponents(a: Fingerprint, b: Fingerprint, role: SectionRole): {
|
|
1985
|
+
distance: number;
|
|
1986
|
+
shared: string[];
|
|
1987
|
+
} | null;
|
|
1988
|
+
interface ComponentPair {
|
|
1989
|
+
a: string;
|
|
1990
|
+
b: string;
|
|
1991
|
+
distance: number;
|
|
1992
|
+
shared: string[];
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Every pair of estate sites that both have a component of this role, closest
|
|
1996
|
+
* first. No sibling line yet: that needs a calibration set of real
|
|
1997
|
+
* components, so this ranks and names, and judges nothing.
|
|
1998
|
+
*/
|
|
1999
|
+
declare function estateComponentPairs(register: EstateRegister, role: SectionRole): ComponentPair[];
|
|
2000
|
+
interface ComponentTypicality {
|
|
2001
|
+
role: SectionRole;
|
|
2002
|
+
score: number;
|
|
2003
|
+
typical: boolean;
|
|
2004
|
+
distance: number;
|
|
2005
|
+
baseline: number;
|
|
2006
|
+
/** How many null pages had a component of this role to compare with. */
|
|
2007
|
+
runs: number;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* How typical one component is against the same role in a null model, by
|
|
2011
|
+
* the method `typicality` uses for whole pages: each null component is scored
|
|
2012
|
+
* against the others leaving itself out, and the page's component is placed
|
|
2013
|
+
* in that spread. Null when fewer than `MIN_RUNS` null pages have the role.
|
|
2014
|
+
*/
|
|
2015
|
+
declare function componentTypicality(target: FingerprintSection, runs: NullRun[], k?: number): ComponentTypicality | null;
|
|
2016
|
+
/**
|
|
2017
|
+
* The roles that name a component rather than a generic block: what
|
|
2018
|
+
* `--component` accepts and what `craft audit --null` scores one by one.
|
|
2019
|
+
*/
|
|
2020
|
+
declare const COMPONENT_ROLES: readonly SectionRole[];
|
|
2021
|
+
/**
|
|
2022
|
+
* Typicality for each component on a page that the null model can speak to:
|
|
2023
|
+
* the first section of each component role, skipping roles too few null
|
|
2024
|
+
* pages have.
|
|
2025
|
+
*/
|
|
2026
|
+
declare function componentTypicalities(fp: Fingerprint, runs: NullRun[], k?: number): ComponentTypicality[];
|
|
2027
|
+
|
|
1492
2028
|
/**
|
|
1493
2029
|
* Every colour syntax the estate actually writes, read into OKLCh.
|
|
1494
2030
|
*
|
|
@@ -1545,7 +2081,7 @@ declare function isCream({ l, c, h }: Oklch): boolean;
|
|
|
1545
2081
|
/** The faces a model picks when the brief does not name one. */
|
|
1546
2082
|
declare const REFLEX_FONTS_1: readonly ["Inter", "Roboto", "Open Sans", "Poppins", "Montserrat", "Lato"];
|
|
1547
2083
|
/** What models moved to once Inter was named and banned. */
|
|
1548
|
-
declare const REFLEX_FONTS_2: readonly ["Inter Tight", "DM Sans", "Manrope", "Space Grotesk", "Plus Jakarta Sans", "Outfit", "Geist", "Sora", "Instrument Sans", "Instrument Serif", "Fraunces", "Playfair Display", "Cormorant Garamond", "DM Serif Display"];
|
|
2084
|
+
declare const REFLEX_FONTS_2: readonly ["Inter Tight", "DM Sans", "Manrope", "Space Grotesk", "Plus Jakarta Sans", "Outfit", "Geist", "Cal Sans", "Sora", "Instrument Sans", "Instrument Serif", "Fraunces", "Playfair Display", "Cormorant Garamond", "DM Serif Display"];
|
|
1549
2085
|
/** Above this many scroll reveals in one scan, reveal is the default, not a choice. */
|
|
1550
2086
|
declare const REVEAL_LIMIT = 12;
|
|
1551
2087
|
/** Above this many fully rounded shapes in one scan, the pill is the shape language by accident. */
|
|
@@ -1688,6 +2224,48 @@ declare function initDirection(input: {
|
|
|
1688
2224
|
exceptions?: ArtDirection["exceptions"];
|
|
1689
2225
|
}): ArtDirection;
|
|
1690
2226
|
|
|
2227
|
+
/**
|
|
2228
|
+
* What each face and icon set is licensed for, checked against the licensor's
|
|
2229
|
+
* own page. A face a client site ships without a licence covering that site is
|
|
2230
|
+
* a commercial risk the site owner carries, so `craft direction validate` warns
|
|
2231
|
+
* on a display or body face this register does not know, or knows only as
|
|
2232
|
+
* capped or unverified. Unverified is never a pass.
|
|
2233
|
+
*
|
|
2234
|
+
* This is a register of facts, not a shortlist. Nothing here is proposed to a
|
|
2235
|
+
* site: a face chosen because it is on this list is the agency's own reflex
|
|
2236
|
+
* font (CHARACTER.md, "Distance from the estate"). A face enters the register
|
|
2237
|
+
* after a site has chosen it for a reason and someone has read its licence.
|
|
2238
|
+
*
|
|
2239
|
+
* `licences.json` is generated from this file for tools outside the package.
|
|
2240
|
+
* Regenerate it with `pnpm --filter @domandigital/craft run docs`.
|
|
2241
|
+
*/
|
|
2242
|
+
type LicenceSubject = "face" | "foundry" | "icons";
|
|
2243
|
+
/**
|
|
2244
|
+
* Whether one purchase covers the sites Doman Digital builds for unrelated
|
|
2245
|
+
* clients. `yes`: it does. `capped`: up to `cap` sites. `per-site`: each client
|
|
2246
|
+
* site needs its own licence, in the client's name. `unverified`: nobody has
|
|
2247
|
+
* read the licensor's own terms yet.
|
|
2248
|
+
*/
|
|
2249
|
+
type MultiClient = "yes" | "capped" | "per-site" | "unverified";
|
|
2250
|
+
interface LicenceEntry {
|
|
2251
|
+
/** The family name as CSS sets it, or the foundry or icon set's own name. */
|
|
2252
|
+
name: string;
|
|
2253
|
+
subject: LicenceSubject;
|
|
2254
|
+
licence: string;
|
|
2255
|
+
multiClient: MultiClient;
|
|
2256
|
+
/** How many sites one licence covers, when `multiClient` is `capped`. */
|
|
2257
|
+
cap?: number;
|
|
2258
|
+
/** When the licensor's page was read, ISO date. */
|
|
2259
|
+
checked: string;
|
|
2260
|
+
/** The licensor's own page. Never an aggregator. */
|
|
2261
|
+
source: string;
|
|
2262
|
+
note: string;
|
|
2263
|
+
}
|
|
2264
|
+
declare const LICENCES: readonly LicenceEntry[];
|
|
2265
|
+
/** The register entry for a face, by the family name CSS sets. */
|
|
2266
|
+
declare function faceLicence(family: string): LicenceEntry | undefined;
|
|
2267
|
+
declare function licencesJson(): string;
|
|
2268
|
+
|
|
1691
2269
|
/**
|
|
1692
2270
|
* Proposing an art direction from the client's world.
|
|
1693
2271
|
*
|
|
@@ -1737,198 +2315,6 @@ declare function propose(input: ProposeInput): {
|
|
|
1737
2315
|
direction: ArtDirection;
|
|
1738
2316
|
};
|
|
1739
2317
|
|
|
1740
|
-
/**
|
|
1741
|
-
* The counterfactual: what a model builds for this brief when nobody tells it
|
|
1742
|
-
* anything else. Signal 2 of CHARACTER.md.
|
|
1743
|
-
*
|
|
1744
|
-
* A null model is about twenty pages generated from one brief, each reduced to
|
|
1745
|
-
* a fingerprint. A site is typical when it sits as close to those pages as they
|
|
1746
|
-
* sit to each other. That question keeps working when model defaults move,
|
|
1747
|
-
* which a fixed list of tells cannot: ban Inter and the null model moves with
|
|
1748
|
-
* the model, so the comparison moves too.
|
|
1749
|
-
*
|
|
1750
|
-
* `harvest` reads the same pages the other way round: which choices recur
|
|
1751
|
-
* across them that the catalogue does not know yet. Those are the candidates
|
|
1752
|
-
* for the next generation of tells. A person decides; nothing is added here.
|
|
1753
|
-
*
|
|
1754
|
-
* Pure. Generating pages and snapshotting them lives in the CLI.
|
|
1755
|
-
*/
|
|
1756
|
-
|
|
1757
|
-
declare const NULL_VERSION = 1;
|
|
1758
|
-
/**
|
|
1759
|
-
* The prompt a null run gets: the brief, and nothing about how it should look.
|
|
1760
|
-
* Changing this changes every null model built after it, so it is versioned
|
|
1761
|
-
* by being recorded in each one.
|
|
1762
|
-
*/
|
|
1763
|
-
declare function nullPrompt(brief: string): string;
|
|
1764
|
-
interface NullRun {
|
|
1765
|
-
/** The page's file name without extension: "01". */
|
|
1766
|
-
id: string;
|
|
1767
|
-
fingerprint: Fingerprint;
|
|
1768
|
-
/** Catalogue tells found on the page, source and rendered, each once. */
|
|
1769
|
-
tells: string[];
|
|
1770
|
-
/** Every visible string on the page, for the phrase harvest. */
|
|
1771
|
-
copy: string[];
|
|
1772
|
-
}
|
|
1773
|
-
interface NullModel {
|
|
1774
|
-
version: typeof NULL_VERSION;
|
|
1775
|
-
brief: string;
|
|
1776
|
-
prompt: string;
|
|
1777
|
-
/** What was asked for with --model, or "default". */
|
|
1778
|
-
model: string;
|
|
1779
|
-
catalogueVersion: string;
|
|
1780
|
-
builtAt: string;
|
|
1781
|
-
runs: NullRun[];
|
|
1782
|
-
}
|
|
1783
|
-
/** The HTML document in a model's reply, without code fences or commentary. */
|
|
1784
|
-
declare function extractHtml(reply: string): string | null;
|
|
1785
|
-
/** Fewer runs than this and the spread of the null is noise, so nothing is judged. */
|
|
1786
|
-
declare const MIN_RUNS = 5;
|
|
1787
|
-
/** Typical when at least this share of the null's own pages sit further out. */
|
|
1788
|
-
declare const TYPICAL_AT = 0.1;
|
|
1789
|
-
interface Typicality {
|
|
1790
|
-
/**
|
|
1791
|
-
* 0 to 1: the share of the null's own pages that sit further from the rest
|
|
1792
|
-
* than this page sits from them. 1 is more typical than anything the model
|
|
1793
|
-
* built; 0 is further out than every one of them.
|
|
1794
|
-
*/
|
|
1795
|
-
score: number;
|
|
1796
|
-
typical: boolean;
|
|
1797
|
-
/** Mean distance to the k nearest null pages. */
|
|
1798
|
-
distance: number;
|
|
1799
|
-
/** The same measure for each null page against the others, median. */
|
|
1800
|
-
baseline: number;
|
|
1801
|
-
nearest: {
|
|
1802
|
-
id: string;
|
|
1803
|
-
distance: number;
|
|
1804
|
-
}[];
|
|
1805
|
-
/** The choices this page shares with most of the null: why it reads as typical. */
|
|
1806
|
-
shared: {
|
|
1807
|
-
part: string;
|
|
1808
|
-
value: string;
|
|
1809
|
-
runs: number;
|
|
1810
|
-
of: number;
|
|
1811
|
-
}[];
|
|
1812
|
-
}
|
|
1813
|
-
/**
|
|
1814
|
-
* How typical a page is against one or more null models (pooled). Every null
|
|
1815
|
-
* page is scored against the others the same way, leaving itself out, so the
|
|
1816
|
-
* threshold comes from the model's own spread and not from a number picked
|
|
1817
|
-
* here: a page drawn from the null is flagged nine times in ten by
|
|
1818
|
-
* construction, and the calibration checks whether real sites are not.
|
|
1819
|
-
*/
|
|
1820
|
-
declare function typicality(target: Fingerprint, runs: NullRun[], k?: number): Typicality;
|
|
1821
|
-
declare function hueName(o: Oklch | null): string;
|
|
1822
|
-
declare function groundName(o: Oklch): string;
|
|
1823
|
-
declare function shapeName(roundness: number | null): string;
|
|
1824
|
-
/** The discrete choices a fingerprint makes, as part → value. What a person would name. */
|
|
1825
|
-
declare function choicesOf(fp: Fingerprint): Record<string, string[]>;
|
|
1826
|
-
type CandidateKind = "face" | "accent" | "ground" | "shape" | "motion" | "opening" | "tell" | "phrase";
|
|
1827
|
-
interface HarvestCandidate {
|
|
1828
|
-
kind: CandidateKind;
|
|
1829
|
-
value: string;
|
|
1830
|
-
/** Null pages that make this choice, across every model given. */
|
|
1831
|
-
runs: number;
|
|
1832
|
-
of: number;
|
|
1833
|
-
/** Distinct briefs it turned up for. A habit shows across briefs; a brief's own words do not. */
|
|
1834
|
-
briefs: number;
|
|
1835
|
-
/** The catalogue tell that already catches it, or null when nothing does. */
|
|
1836
|
-
known: string | null;
|
|
1837
|
-
/** An example, for a phrase: the string it came from. */
|
|
1838
|
-
example?: string;
|
|
1839
|
-
}
|
|
1840
|
-
interface HarvestOptions {
|
|
1841
|
-
/** Report a choice made by at least this share of pages. Default 0.25. */
|
|
1842
|
-
minShare?: number;
|
|
1843
|
-
/** Which copy tell, if any, already catches a phrase. The CLI passes the catalogue's. */
|
|
1844
|
-
copyTell?: (phrase: string) => string | null;
|
|
1845
|
-
}
|
|
1846
|
-
/**
|
|
1847
|
-
* Choices that recur across null pages, with the tell that already catches
|
|
1848
|
-
* each, if any. The ones with `known: null` are the next generation's
|
|
1849
|
-
* candidates. Nothing here decides: a person reads them, and a candidate only
|
|
1850
|
-
* becomes a tell with a flag case and a pass case like every other.
|
|
1851
|
-
*/
|
|
1852
|
-
declare function harvest(models: Pick<NullModel, "brief" | "runs">[], options?: HarvestOptions): HarvestCandidate[];
|
|
1853
|
-
/** A representative colour for a hue family, for a report. */
|
|
1854
|
-
declare function hueSwatch(runs: NullRun[], hue: string): string | null;
|
|
1855
|
-
|
|
1856
|
-
/**
|
|
1857
|
-
* The estate register: every site the agency has shipped, fingerprinted, so a
|
|
1858
|
-
* new one can be checked against its siblings. Signal 3 of CHARACTER.md.
|
|
1859
|
-
*
|
|
1860
|
-
* An agency's biggest tell is its clients looking like each other. No single
|
|
1861
|
-
* site shows it; only the set does. So the register holds one fingerprint per
|
|
1862
|
-
* site, and `compareToEstate` says which sites a page sits nearest and what
|
|
1863
|
-
* it shares with them.
|
|
1864
|
-
*
|
|
1865
|
-
* The line for "siblings" is not a number picked here. Two different
|
|
1866
|
-
* businesses are siblings when they sit closer together than two pages Claude
|
|
1867
|
-
* builds for the same brief usually do: if a barber and an electrician look
|
|
1868
|
-
* more alike than two drafts of one barber, nobody chose either look. The
|
|
1869
|
-
* median comes from null models when they are given, and from the 2026-09-23
|
|
1870
|
-
* calibration when they are not.
|
|
1871
|
-
*
|
|
1872
|
-
* Pure. The CLI reads and writes the register file.
|
|
1873
|
-
*/
|
|
1874
|
-
|
|
1875
|
-
declare const ESTATE_VERSION = 1;
|
|
1876
|
-
/**
|
|
1877
|
-
* Median fingerprint distance between two null pages built from the same
|
|
1878
|
-
* brief, across seven briefs and 1,330 pairs (calibration/null, 2026-09-23).
|
|
1879
|
-
*/
|
|
1880
|
-
declare const SIBLING_AT = 0.31;
|
|
1881
|
-
interface EstateSite {
|
|
1882
|
-
/** Short kebab-case id: "rmp", "chair-and-blade". */
|
|
1883
|
-
id: string;
|
|
1884
|
-
client: string;
|
|
1885
|
-
url: string;
|
|
1886
|
-
/** Where the site's source lives, if known: "rmp474/RMP-Electrical". */
|
|
1887
|
-
repo?: string;
|
|
1888
|
-
fingerprint: Fingerprint;
|
|
1889
|
-
/** Rendered tells found when it was added, for the record. */
|
|
1890
|
-
tells: string[];
|
|
1891
|
-
addedAt: string;
|
|
1892
|
-
}
|
|
1893
|
-
interface EstateRegister {
|
|
1894
|
-
version: typeof ESTATE_VERSION;
|
|
1895
|
-
sites: EstateSite[];
|
|
1896
|
-
}
|
|
1897
|
-
declare function emptyEstate(): EstateRegister;
|
|
1898
|
-
/** Add a site, or replace the one with the same id. Sorted by id so the file diffs cleanly. */
|
|
1899
|
-
declare function addSite(register: EstateRegister, site: EstateSite): EstateRegister;
|
|
1900
|
-
/**
|
|
1901
|
-
* What two fingerprints effectively share, named the way a person would see
|
|
1902
|
-
* it: "Fraunces headline", "no accent", "pill buttons". A part counts when its
|
|
1903
|
-
* distance is 0.2 or less.
|
|
1904
|
-
*/
|
|
1905
|
-
declare function sharedParts(a: Fingerprint, b: Fingerprint, d?: FingerprintDistance): string[];
|
|
1906
|
-
interface EstateMatch {
|
|
1907
|
-
id: string;
|
|
1908
|
-
distance: number;
|
|
1909
|
-
sibling: boolean;
|
|
1910
|
-
shared: string[];
|
|
1911
|
-
}
|
|
1912
|
-
/** The sites nearest a fingerprint, closest first, leaving out `exclude` (the site itself). */
|
|
1913
|
-
declare function compareToEstate(target: Fingerprint, register: EstateRegister, options?: {
|
|
1914
|
-
exclude?: string;
|
|
1915
|
-
siblingAt?: number;
|
|
1916
|
-
}): EstateMatch[];
|
|
1917
|
-
interface EstatePair {
|
|
1918
|
-
a: string;
|
|
1919
|
-
b: string;
|
|
1920
|
-
distance: number;
|
|
1921
|
-
sibling: boolean;
|
|
1922
|
-
shared: string[];
|
|
1923
|
-
}
|
|
1924
|
-
/** Every pair in the register, closest first. */
|
|
1925
|
-
declare function estatePairs(register: EstateRegister, siblingAt?: number): EstatePair[];
|
|
1926
|
-
/**
|
|
1927
|
-
* The sibling line from null models: the median distance between two pages
|
|
1928
|
-
* built from the same brief. Each inner array is one brief's fingerprints.
|
|
1929
|
-
*/
|
|
1930
|
-
declare function siblingLine(briefs: Fingerprint[][]): number;
|
|
1931
|
-
|
|
1932
2318
|
/**
|
|
1933
2319
|
* The character report: the three signals and the reason rule, read together.
|
|
1934
2320
|
*
|
|
@@ -2004,4 +2390,4 @@ declare function characterReport(input: CharacterInputs, copyTells?: Set<string>
|
|
|
2004
2390
|
|
|
2005
2391
|
declare function retrofitPlan(report: CharacterReport, direction?: ArtDirection | null): string;
|
|
2006
2392
|
|
|
2007
|
-
export { AI_PHRASES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BASELINE_VERSION, BUZZWORDS, type Baseline, type BaselineEntry, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type ContrastCheck, type ContrastReport, type CopyTell, type CraftConfig, type CraftTokensInput, type CraftTokensResult, DEFAULT_IGNORE, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE, HOUSE_BUDGET, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, type HouseRule, type HouseTier, JSON_SCHEMA_VERSION, LIGHTNESS_CURVE, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROPOSED, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_PHRASES, type SarifOptions, type SarifTell, type SectionKind, type SectionRhythmOptions, type SemanticInput, type SemanticResult, type Severity, type SeverityLevel, type SeverityOverride, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyBaseline, applyHouseGate, applySeverity, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, compareFacts, compareToEstate, craftTokens, createBaseline, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estatePairs, exitDuration, extractHtml, findClaims, findColours, findingKey, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatClaims, formatComparison, formatHex, formatReport, globToRegExp, groundName, harvest, hexToOklch, houseRule, hueName, hueSwatch, ignoreMatcher, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, makeSnapshot, motionTokens, namedSources, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseBaseline, parseColour, parseCraftConfig, parseHex, propose, protectedFacts, ramp, rampFromAnchors, retrofitPlan, runTell, scanSource, sectionRhythm, semantic, severityExceptions, shapeName, sharedParts, shouldAnimate, siblingLine, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, toJson, toSarif, typeFeatureTokens, typicality, validateDirection, visibleText, wcagContrast, wordListsJson };
|
|
2393
|
+
export { AI_PHRASES, AI_SOURCE_TYPES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type Area, type ArtDirection, BASELINE_VERSION, BUZZWORDS, type Baseline, type BaselineEntry, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, COMPONENT_ROLES, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type ComponentPair, type ComponentTypicality, type ContrastCheck, type ContrastReport, type CopyTell, type CraftConfig, type CraftTokensInput, type CraftTokensResult, DEFAULT_IGNORE, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionProblem, type DirectionReport, type DirectionSource, type DurationName, EASE, EASE_TUPLE, EDGE_STEP, ESTATE_VERSION, EXIT_RATIO, type EaseName, type EmitCssOptions, type EstateMatch, type EstatePair, type EstateRegister, type EstateSite, FINGERPRINT_VERSION, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FingerprintSection, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, type Gamut, type Generation, HOUSE, HOUSE_BUDGET, HOUSE_LOCKED, HOUSE_TYPE, type HarvestCandidate, type HarvestOptions, type Hit, type HouseRule, type HouseTier, IMAGERY_TELLS, type ImageProvenance, type ImageRole, JSON_SCHEMA_VERSION, LICENCES, LIGHTNESS_CURVE, type LicenceEntry, type LicenceSubject, MIN_DECIDED, MIN_RUNS, type MotionDecision, type MotionKind, type MultiClient, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PILL_LIMIT, PLAINER_WORDS, PROMO_BADGE, PROPOSED, PROVENANCE_SCAN_BYTES, type PaletteColour, type Proposal, type ProposeInput, RAMP_STEPS, READABLE_SNAPSHOT_VERSIONS, REFLEX_FONTS_1, REFLEX_FONTS_2, RENDERED_PATHS, RENDERED_TELLS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type RenderedTell, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, type RgbaImage, SCALE, SHADCN_LIMIT, SHADCN_PRIMITIVES, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPRING, STOCK_AVATAR_HOSTS, STOCK_PHOTO_HOSTS, STOCK_PHRASES, type SarifOptions, type SarifTell, type SectionGeometry, type SectionKind, type SectionRhythmOptions, type SectionRole, type SemanticInput, type SemanticResult, type Severity, type SeverityLevel, type SeverityOverride, type ShouldAnimateInput, type SignalResult, type Snapshot, type SnapshotColour, type SnapshotControl, type SnapshotEffects, type SnapshotFont, type SnapshotGradient, type SnapshotHeading, type SnapshotImage, type SnapshotMotion, type SnapshotPatch, type SnapshotSection, type SnapshotVisual, type SourceFile, type SourceKind, type SourceTell, type SpaceStep, type Surface, TELL_HEAVY, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type Tell, type TellException, type TypeStep, type Typicality, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyBaseline, applyHouseGate, applySeverity, auditSnapshot, catalogueTable, characterReport, checkCopy, checkPair, checkRestraint, choicesOf, closestComponents, compareFacts, compareToEstate, componentDistance, componentShared, componentTypicalities, componentTypicality, componentsOf, craftTokens, createBaseline, deltaEOk, deltaEOkHex, densityCss, densityTokens, emitCss, emptyEstate, estateComponentPairs, estatePairs, exitDuration, extractHtml, faceLicence, findClaims, findColours, findingKey, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatClaims, formatComparison, formatHex, formatReport, globToRegExp, groundName, harvest, hexToOklch, holdHouseBlocks, houseRule, hueName, hueSwatch, ignoreMatcher, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isCream, layoutDistance, licencesJson, makeSnapshot, motionTokens, namedSources, nearest, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, paletteFromPixels, parseBaseline, parseColour, parseCraftConfig, parseHex, propose, protectedFacts, ramp, rampFromAnchors, readSnapshot, retrofitPlan, runTell, scanProvenance, scanSource, sectionRhythm, semantic, severityExceptions, shapeName, sharedParts, shouldAnimate, siblingLine, stockAvatarSource, stockSource, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, toJson, toSarif, typeFeatureTokens, typicality, validateDirection, visibleText, visualMeasures, wcagContrast, wordListsJson };
|