@domandigital/craft 0.18.2 → 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/dist/index.d.ts CHANGED
@@ -914,6 +914,8 @@ interface SnapshotImage {
914
914
  width: number;
915
915
  height: number;
916
916
  top: number;
917
+ /** Version 2, craft 0.19 and later: distance from the viewport's left edge, so a hero can say which side its picture sits. */
918
+ left?: number;
917
919
  role: ImageRole;
918
920
  alt: string | null;
919
921
  /** Empty alt, `aria-hidden` or `role="presentation"`. */
@@ -2228,8 +2230,14 @@ declare function visualMeasures(img: RgbaImage, maxSamples?: number): SnapshotVi
2228
2230
  * comparison involving it uses `layout` exactly as version 1 did, so the null
2229
2231
  * models and the estate register compare as they were calibrated until they
2230
2232
  * are re-snapshotted.
2233
+ *
2234
+ * 3 adds `hero` (the headline's size and where the hero's picture sits).
2235
+ * Rhythm, the sequence of light and dark bands, is read from the version 2
2236
+ * `sections`, so a version 2 fingerprint measures it without a fresh
2237
+ * snapshot. A part neither side carries is left out of the distance rather
2238
+ * than counted as different (see `fingerprintDistance`).
2231
2239
  */
2232
- declare const FINGERPRINT_VERSION = 2;
2240
+ declare const FINGERPRINT_VERSION = 3;
2233
2241
  /** One section as layout comparison sees it. */
2234
2242
  interface FingerprintSection {
2235
2243
  role: SectionRole;
@@ -2253,8 +2261,26 @@ interface FingerprintSection {
2253
2261
  /** Large standalone figures. */
2254
2262
  figures?: number;
2255
2263
  }
2264
+ /** Where the hero's largest picture sits, relative to the headline. */
2265
+ type HeroImage = "left" | "right" | "full" | "none";
2266
+ /** The hero's composition: the first thing two sites are compared on by eye. */
2267
+ interface FingerprintHero {
2268
+ /** The headline (the first h1 inside the hero), or null when the hero has none. */
2269
+ headline: {
2270
+ sizePx: number;
2271
+ weight: number;
2272
+ class: FontClass;
2273
+ italic: boolean;
2274
+ } | null;
2275
+ /**
2276
+ * The largest photo or illustration in the hero: beside the text (left or
2277
+ * right of the viewport's centre line), `full` when it spans the width,
2278
+ * `none` without one. Absent when the snapshot predates image positions.
2279
+ */
2280
+ image?: HeroImage;
2281
+ }
2256
2282
  interface Fingerprint {
2257
- version: 1 | 2;
2283
+ version: 1 | 2 | 3;
2258
2284
  /** The most prominent chromatic colour: button fills first, then painted area, then text. */
2259
2285
  accent: Oklch | null;
2260
2286
  ground: Oklch;
@@ -2276,10 +2302,29 @@ interface Fingerprint {
2276
2302
  layout: SectionKind[];
2277
2303
  /** Version 2: present when every section carries a role and geometry. */
2278
2304
  sections?: FingerprintSection[];
2305
+ /** Version 3: present when the page has a hero section. */
2306
+ hero?: FingerprintHero;
2279
2307
  }
2280
2308
  declare function fingerprint(s: Snapshot): Fingerprint;
2309
+ /**
2310
+ * Chroma at which a ground's tint is unmistakable. RMP's cream sits at 0.010
2311
+ * and Bellerose's stone at 0.007, both plainly warm paper; Bellerose's
2312
+ * porcelain (#F1F3F4) at 0.003 is a cool hint. A page ground is the largest
2313
+ * area on the page and the eye adapts to it, so a tint far below what reads
2314
+ * as a difference between two buttons still reads as warm or cool paper.
2315
+ */
2316
+ declare const TEMPERATURE_FULL = 0.006;
2317
+ /** A ground is named warm or cool from this temperature, about chroma 0.0024. Below it, the name stays neutral. */
2318
+ declare const TEMPERATURE_NAMED = 0.4;
2319
+ /**
2320
+ * How warm or cool a colour reads, -1 (fully cool) through 0 (neutral) to 1
2321
+ * (fully warm): the chroma over `TEMPERATURE_FULL`, signed by the hue.
2322
+ */
2323
+ declare function groundTemperature(o: Oklch): number;
2324
+ type Temperature = "warm" | "neutral" | "cool";
2325
+ declare function temperatureName(o: Oklch): Temperature;
2281
2326
  interface FingerprintDistance {
2282
- /** 0 identical, 1 nothing in common. A weighted mean of the parts. */
2327
+ /** 0 identical, 1 nothing in common. A weighted mean of the parts both sides measure. */
2283
2328
  total: number;
2284
2329
  parts: {
2285
2330
  accent: number;
@@ -2289,34 +2334,78 @@ interface FingerprintDistance {
2289
2334
  motion: number;
2290
2335
  effects: number;
2291
2336
  layout: number;
2337
+ /** Null when either side has no sections (version 1). */
2338
+ rhythm: number | null;
2339
+ /** Null when either side has no hero. */
2340
+ hero: number | null;
2292
2341
  };
2293
2342
  }
2294
2343
  /**
2295
2344
  * How much each part counts. Accent and type are where a model's defaults
2296
2345
  * show first and where a real business has something of its own, so they
2297
- * carry half the weight between them.
2346
+ * carry the most. Structure (the running order, the rhythm of light and dark
2347
+ * bands, and the hero's composition) carries a quarter between its three
2348
+ * parts: two sites that open the same way read as the same site before any
2349
+ * colour is compared. A part only one side measures is left out and the
2350
+ * rest are weighed over what remains.
2298
2351
  */
2299
2352
  declare const DISTANCE_WEIGHTS: {
2300
- readonly accent: 0.25;
2301
- readonly type: 0.25;
2353
+ readonly accent: 0.2;
2354
+ readonly type: 0.2;
2302
2355
  readonly ground: 0.1;
2303
2356
  readonly shape: 0.1;
2304
- readonly motion: 0.1;
2357
+ readonly motion: 0.05;
2305
2358
  readonly effects: 0.1;
2306
2359
  readonly layout: 0.1;
2360
+ readonly rhythm: 0.1;
2361
+ readonly hero: 0.05;
2307
2362
  };
2308
2363
  /** Layout distance, 0 to 1: by role and geometry when both have them, else by kind as in version 1. */
2309
2364
  declare function layoutDistance(a: Fingerprint, b: Fingerprint): number;
2365
+ /** A section's band by the lightness of what it is painted on. */
2366
+ type LightnessBand = "light" | "mid" | "dark";
2367
+ declare function bandOf(l: number): LightnessBand;
2368
+ /**
2369
+ * The page's rhythm: its bands of light and dark in running order, each run
2370
+ * of same-lightness sections merged into one. "Light hero, white logo strip,
2371
+ * near-black band, light features" is light, dark, light: the strip is part
2372
+ * of the light opening, which is how a visitor sees it. Null for a version 1
2373
+ * fingerprint, which has no section backgrounds.
2374
+ */
2375
+ declare function rhythmOf(fp: Fingerprint): LightnessBand[] | null;
2376
+ /** The first bands count double: the opening is what two sites are compared on before anyone scrolls. */
2377
+ declare const OPENING_BANDS = 3;
2378
+ /**
2379
+ * Rhythm distance, 0 to 1: the edit distance over the opening bands and over
2380
+ * the whole sequence, averaged. Null when either side has no sections.
2381
+ */
2382
+ declare function rhythmDistance(a: Fingerprint, b: Fingerprint): number | null;
2383
+ /**
2384
+ * Hero distance, 0 to 1, over the parts both sides measure: where the text
2385
+ * sits (centred or left), how wide it runs, the headline's class and size,
2386
+ * and which side the picture is on. Null when either side has no hero.
2387
+ */
2388
+ declare function heroDistance(a: Fingerprint, b: Fingerprint): number | null;
2310
2389
  /** ΔE in OKLab. 0.02 is barely visible; by 0.3 two accents read as different colours. */
2311
2390
  declare const ACCENT_SCALE = 0.3;
2312
2391
  /** Grounds sit near white, where a smaller step already reads as a different paper. */
2313
2392
  declare const GROUND_SCALE = 0.15;
2393
+ /**
2394
+ * How much a difference in temperature adds to the ground distance, on top
2395
+ * of ΔE. Cream against porcelain is ΔE 0.013, a tenth of `GROUND_SCALE`, and
2396
+ * nobody takes them for the same paper: the temperature carries what ΔE
2397
+ * cannot at this chroma. Warm against cool adds 0.6; warm against neutral
2398
+ * white adds 0.3; two warm papers add nothing.
2399
+ */
2400
+ declare const TEMPERATURE_WEIGHT = 0.6;
2314
2401
  /** A part at or below this distance counts as shared between two sites. */
2315
2402
  declare const SHARED_PART = 0.2;
2316
2403
  /** Two accents within this ΔE_OK read as the same colour family: about 6 on the familiar ΔE scale. */
2317
2404
  declare const ACCENT_SHARED: number;
2318
2405
  /** Two grounds within this ΔE_OK read as the same paper. */
2319
2406
  declare const GROUND_SHARED: number;
2407
+ /** Ground distance, 0 to 1: ΔE over `GROUND_SCALE`, plus the temperature gap. */
2408
+ declare function groundDistance(a: Oklch, b: Oklch): number;
2320
2409
  declare function fingerprintDistance(a: Fingerprint, b: Fingerprint): FingerprintDistance;
2321
2410
  /**
2322
2411
  * The nearest neighbours of a fingerprint in a set, closest first. Typicality
@@ -2582,8 +2671,9 @@ declare function emptyEstate(): EstateRegister;
2582
2671
  declare function addSite(register: EstateRegister, site: EstateSite): EstateRegister;
2583
2672
  /**
2584
2673
  * What two fingerprints effectively share, named the way a person would see
2585
- * it: "Fraunces headline", "no accent", "pill buttons". A part counts when its
2586
- * distance is `SHARED_PART` or less.
2674
+ * it: "Fraunces headline", "no accent", "pill buttons", "cream ground",
2675
+ * "light hero then dark band". A part counts when its distance is
2676
+ * `SHARED_PART` or less; a part neither side measures never counts.
2587
2677
  */
2588
2678
  declare function sharedParts(a: Fingerprint, b: Fingerprint, d?: FingerprintDistance): string[];
2589
2679
  interface EstateMatch {
@@ -2703,7 +2793,18 @@ interface Typicality {
2703
2793
  */
2704
2794
  declare function typicality(target: Fingerprint, runs: NullRun[], k?: number): Typicality;
2705
2795
  declare function hueName(o: Oklch | null): string;
2796
+ /**
2797
+ * The coarse name the null's choice counts use ("white", "cream"), kept
2798
+ * stable so a harvest compares with earlier runs. `groundLabel` is the one a
2799
+ * person reads.
2800
+ */
2706
2801
  declare function groundName(o: Oklch): string;
2802
+ /**
2803
+ * The ground as a person would name it, with its temperature: "cream",
2804
+ * "cool grey", "warm white", "cool dark". Every light neutral page used to be
2805
+ * "grey", which put RMP's cream and Bellerose's porcelain under one word.
2806
+ */
2807
+ declare function groundLabel(o: Oklch): string;
2707
2808
  declare function shapeName(roundness: number | null): string;
2708
2809
  /** The discrete choices a fingerprint makes, as part → value. What a person would name. */
2709
2810
  declare function choicesOf(fp: Fingerprint): Record<string, string[]>;
@@ -3345,4 +3446,4 @@ declare function characterReport(input: CharacterInputs, copyTells?: Set<string>
3345
3446
 
3346
3447
  declare function retrofitPlan(report: CharacterReport, direction?: ArtDirection | null): string;
3347
3448
 
3348
- export { ACCENT_SCALE, ACCENT_SHARED, AI_PHRASES, AI_SOURCE_TYPES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type ActionPosition, type Area, type ArtDirection, BASELINE_VERSION, BUZZWORDS, type Band, type Baseline, type BaselineEntry, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, COMPONENT_ROLES, CONSIDERATION, CONTENT_FEATURES, CONVENTION, CUSTOMER_KINDS, type Calibration, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type CompetitorComparison, type ComponentPair, type ComponentTypicality, type Consideration, type ContrastCheck, type ContrastReport, type CopySide, type CopyTell, type CraftConfig, type CraftTokensInput, type CraftTokensResult, type CustomerKind, DEFAULT_IGNORE, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DIRECTION_VERSIONS, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionComparison, type DirectionMatch, type DirectionProblem, type DirectionReport, type DirectionSource, type DirectionSummary, 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, FLAGGED_TARGET, FUNCTION_WORDS, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FingerprintSection, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, GROUND_SCALE, GROUND_SHARED, type Gamut, type Generation, type GradientContrast, HIERARCHY_FIELDS, HOME_FILE, HOME_URL, HOUSE, HOUSE_BUDGET, HOUSE_LOCKED, HOUSE_TYPE, HUMAN_TYPICAL_TARGET, type HarvestCandidate, type HarvestOptions, type Hit, type HouseRule, type HouseTier, IMAGERY_TELLS, type ImageProvenance, type ImageRole, JSON_SCHEMA_VERSION, type JobContext, type JobMap, type JobPart, LABELS, LEXICAL_FEATURES, LICENCES, LIGHTNESS_CURVE, type Label, type LabelSummary, type LexicalFeatures, type LicenceEntry, type LicenceSubject, MIN_DECIDED, MIN_PARAGRAPHS, MIN_RUNS, MOOD, type MeasuredPage, type MotionDecision, type MotionKind, type MultiClient, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, type Oklch, type OklchToHexOptions, PAGE_TYPES, PILL_LIMIT, PLAINER_WORDS, PREFERENCE, PROFILE, PROFILE_MEASURED, PROFILE_PARAGRAPHS, PROMO_BADGE, PROOF_NAMES, PROPOSED, PROVENANCE_SCAN_BYTES, type PageCopy, type PageHierarchy, type PageResult, type PageType, type PaletteColour, type PhotoReviewOptions, type PhotoReviewResult, type Proposal, type ProposeInput, RAMP_STEPS, READABLE_SNAPSHOT_VERSIONS, REFLEX_FONTS_1, REFLEX_FONTS_2, REGISTERS, REGISTER_BASELINE, REGISTER_FEATURES, REGISTER_IDS, RENDERED_PATHS, RENDERED_TELLS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type ReasonInput, type Register, type RegisterCheck, type RegisterFeatures, type RegisterId, type RegisterProfile, type RenderedTell, type ResearchInput, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, type RgbaImage, SAMPLES_PER_SEGMENT, SCALE, SECTION_ROLES, SHADCN_LIMIT, SHADCN_PRIMITIVES, SHARED_PART, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPECIFICITY_COPY_TELLS, SPECIFICITY_RENDERED_TELLS, SPRING, STANDING_CLAIM, STOCK_AVATAR_HOSTS, STOCK_FIGURES, STOCK_PHOTO_HOSTS, STOCK_PHRASES, STUDY_WORDS, type SarifOptions, type SarifTell, type SectionGeometry, type SectionKind, type SectionRhythmOptions, type SectionRole, type SemanticInput, type SemanticResult, type Severity, type SeverityLevel, type SeverityOverride, type ShapeFeatures, 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 Specific, type SpecificKind, type Specificity, type StudyWord, type Surface, TELL_HEAVY, TRADE_NOUNS, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type TargetCheck, type Tell, type TellException, type TellRate, type Tricolon, type TypeStep, type Typicality, type UnmeasuredPage, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyBaseline, applyHouseGate, applySeverity, auditSnapshot, briefTerms, calibrate, catalogueTable, characterReport, checkCopy, checkOrder, checkPair, checkPhotoReview, checkReason, checkRegister, checkRestraint, choicesOf, clauseDepth, closestComponents, compareCompetitor, compareDirections, compareFacts, compareToDeclared, compareToEstate, componentDistance, componentShared, componentTypicalities, componentTypicality, componentsOf, contentWords, countWords, craftTokens, createBaseline, cv, deltaEOk, deltaEOkHex, densityCss, densityTokens, designTells, documentParagraphs, emitCss, emptyEstate, estateComponentPairs, estatePairs, exitDuration, extractHtml, faceLicence, featureLabel, findClaims, findColours, findingKey, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatCalibration, formatClaims, formatComparison, formatCompetitor, formatDirectionMatches, formatHex, formatReport, getRegister, globToRegExp, gradientContrast, gradientStops, groundName, harvest, hedgeCount, hedgedClose, hexToOklch, holdHouseBlocks, houseRule, hueName, hueSwatch, ignoreMatcher, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isBlurb, isCategoryLabel, isCream, isFlagged, jobWords, layoutDistance, lexicalOf, licencesJson, makeSnapshot, motionTokens, namedSources, nearest, nominalisations, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, pageCopy, paletteFromPixels, paragraphUniformity, parseBaseline, parseColour, parseCraftConfig, parseHex, participialClauses, phrasalCoordinations, propose, protectedFacts, ramp, rampFromAnchors, readSnapshot, registerBrief, registerOf, registersDoc, renderedOrder, researchPrompt, retrofitPlan, runTell, scanProvenance, scanSource, sectionRhythm, semantic, sentencesOf, severityExceptions, shapeMoves, shapeName, shapeOf, words as shapeWords, sharedParts, shouldAnimate, siblingLine, skeleton, skeletonRepeat, specificity, specifics, splitSentences, stackedConditional, stockAvatarSource, stockSource, summariseDirection, tailwindV3Preset, tailwindV4Theme, tellById, toGamut, toJson, toSarif, tradeOf, tricolons, typeFeatureTokens, typicality, unprovenClaims, validateDirection, validateHierarchy, validateJob, visibleText, visualMeasures, vocabulary, vocabularyCounts, wcagContrast, wordListsJson };
3449
+ export { ACCENT_SCALE, ACCENT_SHARED, AI_PHRASES, AI_SOURCE_TYPES, AI_VIOLET, AI_WORDS, type AccentFork, type AccentForkOptions, type Action, type ActionPosition, type Area, type ArtDirection, BASELINE_VERSION, BUZZWORDS, type Band, type Baseline, type BaselineEntry, CATALOGUE, CATALOGUE_VERSION, CHOICE_KEYS, COMPONENT_ROLES, CONSIDERATION, CONTENT_FEATURES, CONVENTION, CUSTOMER_KINDS, type Calibration, type CandidateKind, type CharacterInputs, type CharacterReport, type CheckOptions, type CheckReport, type CheckRestraintInput, type ChoiceKey, type Claim, type ClaimsReport, type CompetitorComparison, type ComponentPair, type ComponentTypicality, type Consideration, type ContrastCheck, type ContrastReport, type CopySide, type CopyTell, type CraftConfig, type CraftTokensInput, type CraftTokensResult, type CustomerKind, DEFAULT_IGNORE, DENSITIES, DENSITY_NAMES, DIRECTION_VERSION, DIRECTION_VERSIONS, DISTANCE_WEIGHTS, DURATION_MS, DURATION_S, type DensityCssOptions, type DensityName, type DirectionChoice, type DirectionComparison, type DirectionMatch, type DirectionProblem, type DirectionReport, type DirectionSource, type DirectionSummary, 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, FLAGGED_TARGET, FUNCTION_WORDS, type Fact, type FactComparison, type FactKind, type Finding, type Fingerprint, type FingerprintDistance, type FingerprintHero, type FingerprintSection, type FluidSpaceOptions, type FluidTypeOptions, type FluidTypeResult, type FontClass, GROUND_SCALE, GROUND_SHARED, type Gamut, type Generation, type GradientContrast, HIERARCHY_FIELDS, HOME_FILE, HOME_URL, HOUSE, HOUSE_BUDGET, HOUSE_LOCKED, HOUSE_TYPE, HUMAN_TYPICAL_TARGET, type HarvestCandidate, type HarvestOptions, type HeroImage, type Hit, type HouseRule, type HouseTier, IMAGERY_TELLS, type ImageProvenance, type ImageRole, JSON_SCHEMA_VERSION, type JobContext, type JobMap, type JobPart, LABELS, LEXICAL_FEATURES, LICENCES, LIGHTNESS_CURVE, type Label, type LabelSummary, type LexicalFeatures, type LicenceEntry, type LicenceSubject, type LightnessBand, MIN_DECIDED, MIN_PARAGRAPHS, MIN_RUNS, MOOD, type MeasuredPage, type MotionDecision, type MotionKind, type MultiClient, NEGATIVE_REASSURANCE, NULL_VERSION, type NullModel, type NullRun, OPENING_BANDS, type Oklch, type OklchToHexOptions, PAGE_TYPES, PILL_LIMIT, PLAINER_WORDS, PREFERENCE, PROFILE, PROFILE_MEASURED, PROFILE_PARAGRAPHS, PROMO_BADGE, PROOF_NAMES, PROPOSED, PROVENANCE_SCAN_BYTES, type PageCopy, type PageHierarchy, type PageResult, type PageType, type PaletteColour, type PhotoReviewOptions, type PhotoReviewResult, type Proposal, type ProposeInput, RAMP_STEPS, READABLE_SNAPSHOT_VERSIONS, REFLEX_FONTS_1, REFLEX_FONTS_2, REGISTERS, REGISTER_BASELINE, REGISTER_FEATURES, REGISTER_IDS, RENDERED_PATHS, RENDERED_TELLS, REVEAL_LIMIT, REVIEW_PHRASES, type Ramp, type RampFromAnchorsOptions, type RampOptions, type RampStep, type ReasonInput, type Register, type RegisterCheck, type RegisterFeatures, type RegisterId, type RegisterProfile, type RenderedTell, type ResearchInput, type RestraintBudget, type RestraintReport, type RestraintSeverity, type RestraintViolation, type Rgb, type RgbaImage, SAMPLES_PER_SEGMENT, SCALE, SECTION_ROLES, SHADCN_LIMIT, SHADCN_PRIMITIVES, SHARED_PART, SIBLING_AT, SNAPSHOT_VERSION, SOURCE_KINDS, SPACE_STEPS, SPECIFICITY_COPY_TELLS, SPECIFICITY_RENDERED_TELLS, SPRING, STANDING_CLAIM, STOCK_AVATAR_HOSTS, STOCK_FIGURES, STOCK_PHOTO_HOSTS, STOCK_PHRASES, STUDY_WORDS, type SarifOptions, type SarifTell, type SectionGeometry, type SectionKind, type SectionRhythmOptions, type SectionRole, type SemanticInput, type SemanticResult, type Severity, type SeverityLevel, type SeverityOverride, type ShapeFeatures, 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 Specific, type SpecificKind, type Specificity, type StudyWord, type Surface, TELL_HEAVY, TEMPERATURE_FULL, TEMPERATURE_NAMED, TEMPERATURE_WEIGHT, TRADE_NOUNS, TYPE_STEPS, TYPICAL_AT, type TailwindPreset, type TargetCheck, type Tell, type TellException, type TellRate, type Temperature, type Tricolon, type TypeStep, type Typicality, type UnmeasuredPage, VAGUE_WORDS, type ValidateContext, type Verdict, accentFork, addSite, apcaContrast, applyBaseline, applyHouseGate, applySeverity, auditSnapshot, bandOf, briefTerms, calibrate, catalogueTable, characterReport, checkCopy, checkOrder, checkPair, checkPhotoReview, checkReason, checkRegister, checkRestraint, choicesOf, clauseDepth, closestComponents, compareCompetitor, compareDirections, compareFacts, compareToDeclared, compareToEstate, componentDistance, componentShared, componentTypicalities, componentTypicality, componentsOf, contentWords, countWords, craftTokens, createBaseline, cv, deltaEOk, deltaEOkHex, densityCss, densityTokens, designTells, documentParagraphs, emitCss, emptyEstate, estateComponentPairs, estatePairs, exitDuration, extractHtml, faceLicence, featureLabel, findClaims, findColours, findingKey, fingerprint, fingerprintDistance, fluidClamp, fluidSpace, fluidType, fontClass, formatCalibration, formatClaims, formatComparison, formatCompetitor, formatDirectionMatches, formatHex, formatReport, getRegister, globToRegExp, gradientContrast, gradientStops, groundDistance, groundLabel, groundName, groundTemperature, harvest, hedgeCount, hedgedClose, heroDistance, hexToOklch, holdHouseBlocks, houseRule, hueName, hueSwatch, ignoreMatcher, inP3Gamut, inSrgbGamut, initDirection, isAiViolet, isBlurb, isCategoryLabel, isCream, isFlagged, jobWords, layoutDistance, lexicalOf, licencesJson, makeSnapshot, motionTokens, namedSources, nearest, nominalisations, normaliseFamily, nullPrompt, oklchToHex, oklchToRgb, pageCopy, paletteFromPixels, paragraphUniformity, parseBaseline, parseColour, parseCraftConfig, parseHex, participialClauses, phrasalCoordinations, propose, protectedFacts, ramp, rampFromAnchors, readSnapshot, registerBrief, registerOf, registersDoc, renderedOrder, researchPrompt, retrofitPlan, rhythmDistance, rhythmOf, runTell, scanProvenance, scanSource, sectionRhythm, semantic, sentencesOf, severityExceptions, shapeMoves, shapeName, shapeOf, words as shapeWords, sharedParts, shouldAnimate, siblingLine, skeleton, skeletonRepeat, specificity, specifics, splitSentences, stackedConditional, stockAvatarSource, stockSource, summariseDirection, tailwindV3Preset, tailwindV4Theme, tellById, temperatureName, toGamut, toJson, toSarif, tradeOf, tricolons, typeFeatureTokens, typicality, unprovenClaims, validateDirection, validateHierarchy, validateJob, visibleText, visualMeasures, vocabulary, vocabularyCounts, wcagContrast, wordListsJson };
package/dist/index.js CHANGED
@@ -5031,7 +5031,7 @@ var RENDERED_PATHS = {
5031
5031
  };
5032
5032
 
5033
5033
  // src/fingerprint/index.ts
5034
- var FINGERPRINT_VERSION = 2;
5034
+ var FINGERPRINT_VERSION = 3;
5035
5035
  var CHROMATIC = 0.03;
5036
5036
  var MIN_ACCENT_AREA = 2e4;
5037
5037
  var oklch = (value) => {
@@ -5067,6 +5067,24 @@ function median(values) {
5067
5067
  const mid = Math.floor(sorted.length / 2);
5068
5068
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
5069
5069
  }
5070
+ var FULL_WIDTH_IMAGE = 0.8;
5071
+ function heroOf(s) {
5072
+ const h1 = s.headings.find((h) => h.level === 1);
5073
+ const inside = (top, x) => top >= x.top && top < x.top + x.height;
5074
+ const index = (() => {
5075
+ const withH1 = h1 ? s.sections.findIndex((x) => x.kind === "hero" && inside(h1.top, x)) : -1;
5076
+ return withH1 >= 0 ? withH1 : s.sections.findIndex((x) => x.kind === "hero");
5077
+ })();
5078
+ if (index < 0) return void 0;
5079
+ const section = s.sections[index];
5080
+ const headline = h1 && inside(h1.top, section) ? { sizePx: h1.sizePx, weight: h1.weight, class: fontClass(normaliseFamily(h1.family)), italic: h1.italic || h1.italicPart } : null;
5081
+ if (!s.images) return { headline };
5082
+ 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];
5083
+ if (!picture2) return { headline, image: "none" };
5084
+ if (picture2.left === void 0) return { headline };
5085
+ const image = picture2.width >= FULL_WIDTH_IMAGE * s.viewport.width ? "full" : picture2.left + picture2.width / 2 < s.viewport.width / 2 ? "left" : "right";
5086
+ return { headline, image };
5087
+ }
5070
5088
  function fingerprint(s) {
5071
5089
  const effects = [];
5072
5090
  if (s.effects.glass > 0) effects.push("glass-panel");
@@ -5078,6 +5096,7 @@ function fingerprint(s) {
5078
5096
  if (s.effects.eyebrowChip) effects.push("hero-eyebrow-chip");
5079
5097
  if (s.motion.introOverlay) effects.push("intro-cinematic");
5080
5098
  const below = s.sections.filter((x) => x.top > s.viewport.height).length;
5099
+ const hero = heroOf(s);
5081
5100
  return {
5082
5101
  version: FINGERPRINT_VERSION,
5083
5102
  accent: accentOf(s),
@@ -5101,10 +5120,28 @@ function fingerprint(s) {
5101
5120
  carousel: x.carousel ?? false,
5102
5121
  figures: x.figures ?? 0
5103
5122
  }))
5104
- } : {}
5123
+ } : {},
5124
+ ...hero ? { hero } : {}
5105
5125
  };
5106
5126
  }
5107
- var DISTANCE_WEIGHTS = { accent: 0.25, type: 0.25, ground: 0.1, shape: 0.1, motion: 0.1, effects: 0.1, layout: 0.1 };
5127
+ var WARM_HUE = [
5128
+ [330, 360],
5129
+ [0, 130]
5130
+ ];
5131
+ var COOL_HUE = [[160, 310]];
5132
+ var TEMPERATURE_FULL = 6e-3;
5133
+ var TEMPERATURE_NAMED = 0.4;
5134
+ function groundTemperature(o) {
5135
+ const h = (o.h % 360 + 360) % 360;
5136
+ const within = (bands) => bands.some(([lo, hi]) => h >= lo && h < hi);
5137
+ const strength = Math.min(1, o.c / TEMPERATURE_FULL);
5138
+ return within(WARM_HUE) ? strength : within(COOL_HUE) ? -strength : 0;
5139
+ }
5140
+ function temperatureName(o) {
5141
+ const t = groundTemperature(o);
5142
+ return t >= TEMPERATURE_NAMED ? "warm" : t <= -TEMPERATURE_NAMED ? "cool" : "neutral";
5143
+ }
5144
+ 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 };
5108
5145
  function levenshtein(a, b, cost = (x, y) => x === y ? 0 : 1) {
5109
5146
  const row = Array.from({ length: b.length + 1 }, (_, i) => i);
5110
5147
  for (let i = 1; i <= a.length; i += 1) {
@@ -5131,18 +5168,62 @@ function layoutDistance(a, b) {
5131
5168
  return longest === 0 ? 0 : levenshtein(a.layout, b.layout) / longest;
5132
5169
  }
5133
5170
  var clamp = (n) => Math.max(0, Math.min(1, n));
5171
+ function bandOf(l) {
5172
+ return l < 0.4 ? "dark" : l >= 0.75 ? "light" : "mid";
5173
+ }
5174
+ function rhythmOf(fp) {
5175
+ if (!fp.sections) return null;
5176
+ const out = [];
5177
+ for (const s of fp.sections) {
5178
+ if (s.background === void 0) return null;
5179
+ const band = bandOf((s.background ?? fp.ground).l);
5180
+ if (out[out.length - 1] !== band) out.push(band);
5181
+ }
5182
+ return out;
5183
+ }
5184
+ var OPENING_BANDS = 3;
5185
+ function rhythmDistance(a, b) {
5186
+ const ra = rhythmOf(a);
5187
+ const rb = rhythmOf(b);
5188
+ if (!ra || !rb) return null;
5189
+ const over = (x, y) => {
5190
+ const longest = Math.max(x.length, y.length);
5191
+ return longest === 0 ? 0 : levenshtein(x, y) / longest;
5192
+ };
5193
+ return (over(ra.slice(0, OPENING_BANDS), rb.slice(0, OPENING_BANDS)) + over(ra, rb)) / 2;
5194
+ }
5195
+ var HEADLINE_OCTAVES = 1;
5196
+ function heroSection(fp) {
5197
+ return fp.sections?.find((s) => s.role === "hero");
5198
+ }
5199
+ function heroDistance(a, b) {
5200
+ const sa = heroSection(a);
5201
+ const sb = heroSection(b);
5202
+ if (!sa || !sb) return null;
5203
+ const parts2 = [Math.abs(sa.centred - sb.centred), clamp(Math.abs(sa.width - sb.width) / 0.5)];
5204
+ const ha = a.hero?.headline;
5205
+ const hb = b.hero?.headline;
5206
+ 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));
5207
+ else if (a.hero && b.hero && (ha || hb)) parts2.push(1);
5208
+ if (a.hero?.image && b.hero?.image) parts2.push(a.hero.image === b.hero.image ? 0 : 1);
5209
+ return parts2.reduce((s, p) => s + p, 0) / parts2.length;
5210
+ }
5134
5211
  function faceDistance(a, b) {
5135
5212
  if (a.family.toLowerCase() === b.family.toLowerCase()) return 0;
5136
5213
  return a.class === b.class ? 0.5 : 1;
5137
5214
  }
5138
5215
  var ACCENT_SCALE = 0.3;
5139
5216
  var GROUND_SCALE = 0.15;
5217
+ var TEMPERATURE_WEIGHT = 0.6;
5140
5218
  var SHARED_PART = 0.2;
5141
5219
  var ACCENT_SHARED = ACCENT_SCALE * SHARED_PART;
5142
5220
  var GROUND_SHARED = GROUND_SCALE * SHARED_PART;
5221
+ function groundDistance(a, b) {
5222
+ return clamp(deltaEOk(a, b) / GROUND_SCALE + TEMPERATURE_WEIGHT * Math.abs(groundTemperature(a) - groundTemperature(b)) / 2);
5223
+ }
5143
5224
  function fingerprintDistance(a, b) {
5144
5225
  const accent = a.accent && b.accent ? clamp(deltaEOk(a.accent, b.accent) / ACCENT_SCALE) : a.accent || b.accent ? 1 : 0;
5145
- const ground = clamp(deltaEOk(a.ground, b.ground) / GROUND_SCALE);
5226
+ const ground = groundDistance(a.ground, b.ground);
5146
5227
  const type = 0.6 * faceDistance(a.display, b.display) + 0.4 * faceDistance(a.body, b.body);
5147
5228
  const shape = a.roundness === null || b.roundness === null ? a.roundness === b.roundness ? 0 : 0.5 : clamp(Math.abs(a.roundness - b.roundness) / 0.5);
5148
5229
  const motion = clamp(Math.abs(a.motion - b.motion));
@@ -5150,9 +5231,18 @@ function fingerprintDistance(a, b) {
5150
5231
  const shared = a.effects.filter((e) => b.effects.includes(e)).length;
5151
5232
  const effects = union.size === 0 ? 0 : 1 - shared / union.size;
5152
5233
  const layout = layoutDistance(a, b);
5153
- const parts2 = { accent, ground, type, shape, motion, effects, layout };
5154
- const total = Object.keys(DISTANCE_WEIGHTS).reduce((sum, k) => sum + DISTANCE_WEIGHTS[k] * parts2[k], 0);
5155
- return { total: Math.round(total * 1e3) / 1e3, parts: parts2 };
5234
+ const rhythm = rhythmDistance(a, b);
5235
+ const hero = heroDistance(a, b);
5236
+ const parts2 = { accent, ground, type, shape, motion, effects, layout, rhythm, hero };
5237
+ let sum = 0;
5238
+ let weight = 0;
5239
+ for (const k of Object.keys(DISTANCE_WEIGHTS)) {
5240
+ const p = parts2[k];
5241
+ if (p === null) continue;
5242
+ sum += DISTANCE_WEIGHTS[k] * p;
5243
+ weight += DISTANCE_WEIGHTS[k];
5244
+ }
5245
+ return { total: Math.round(sum / weight * 1e3) / 1e3, parts: parts2 };
5156
5246
  }
5157
5247
  function nearest(target2, set, k = set.length) {
5158
5248
  return set.map((item) => ({ id: item.id, distance: fingerprintDistance(target2, item.fingerprint) })).sort((x, y) => x.distance.total - y.distance.total).slice(0, k);
@@ -5230,6 +5320,12 @@ function groundName(o) {
5230
5320
  if (o.l >= 0.97 && o.c < 0.012) return "white";
5231
5321
  return o.c < 0.02 ? "grey" : "tinted";
5232
5322
  }
5323
+ function groundLabel(o) {
5324
+ const name = groundName(o);
5325
+ if (name === "cream" || name === "tinted") return name;
5326
+ const temperature = temperatureName(o);
5327
+ return temperature === "neutral" ? name : `${temperature} ${name}`;
5328
+ }
5233
5329
  function shapeName(roundness) {
5234
5330
  if (roundness === null) return "no buttons";
5235
5331
  if (roundness >= 0.45) return "pill";
@@ -8504,6 +8600,33 @@ var LICENCES = [
8504
8600
  checked: "2026-09-24",
8505
8601
  source: "https://iconscout.com/licenses",
8506
8602
  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."
8603
+ },
8604
+ {
8605
+ name: "Saira Semi Condensed",
8606
+ subject: "face",
8607
+ licence: "OFL-1.1",
8608
+ multiClient: "yes",
8609
+ checked: "2026-10-09",
8610
+ source: "https://github.com/google/fonts/blob/main/ofl/sairasemicondensed/OFL.txt",
8611
+ 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)."
8612
+ },
8613
+ {
8614
+ name: "Public Sans",
8615
+ subject: "face",
8616
+ licence: "OFL-1.1",
8617
+ multiClient: "yes",
8618
+ checked: "2026-10-09",
8619
+ source: "https://github.com/google/fonts/blob/main/ofl/publicsans/OFL.txt",
8620
+ 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)."
8621
+ },
8622
+ {
8623
+ name: "Martian Mono",
8624
+ subject: "face",
8625
+ licence: "OFL-1.1",
8626
+ multiClient: "yes",
8627
+ checked: "2026-10-09",
8628
+ source: "https://github.com/google/fonts/blob/main/ofl/martianmono/OFL.txt",
8629
+ 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)."
8507
8630
  }
8508
8631
  ];
8509
8632
  function faceLicence(family) {
@@ -8870,7 +8993,7 @@ var PART_AREA = {
8870
8993
  effects: { area: "effects", choice: "motif" },
8871
8994
  motion: { area: "motion", choice: "signature" }
8872
8995
  };
8873
- var DEFAULTS = /^(?:ground )?(?:white|grey)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons/;
8996
+ var DEFAULTS = /^(?:ground )?(?:white|grey|light)(?: ground)?$|no scroll reveals|no reveals|accent (?:none|neutral)|^no buttons|shape no buttons|^light page throughout$/;
8874
8997
  function designFindings(findings, copyTells) {
8875
8998
  return (findings?.findings ?? []).filter((f4) => !copyTells.has(f4.tell));
8876
8999
  }
@@ -8977,7 +9100,7 @@ function areaOfShared(part) {
8977
9100
  if (/accent|ground/.test(part)) return "colour";
8978
9101
  if (/buttons/.test(part)) return "shape";
8979
9102
  if (/reveal/.test(part)) return "motion";
8980
- if (/running order/.test(part)) return "layout";
9103
+ if (/running order|hero|page throughout/.test(part)) return "layout";
8981
9104
  return "effects";
8982
9105
  }
8983
9106
  function choiceOfShared(part) {
@@ -8987,7 +9110,7 @@ function choiceOfShared(part) {
8987
9110
  if (/ground/.test(part)) return "ground";
8988
9111
  if (/buttons/.test(part)) return "shape";
8989
9112
  if (/reveal/.test(part)) return "signature";
8990
- return /running order/.test(part) ? void 0 : "motif";
9113
+ return /running order|hero|page throughout/.test(part) ? void 0 : "motif";
8991
9114
  }
8992
9115
 
8993
9116
  // src/calibrate/index.ts
@@ -9094,19 +9217,45 @@ function addSite(register, site) {
9094
9217
  const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
9095
9218
  return { version: ESTATE_VERSION, sites };
9096
9219
  }
9220
+ function sharedGround(a, b) {
9221
+ const [x, y] = [groundLabel(a.ground), groundLabel(b.ground)];
9222
+ if (x === y) return `${x} ground`;
9223
+ const temperature = temperatureName(a.ground);
9224
+ if (temperature !== "neutral" && temperature === temperatureName(b.ground)) return `${temperature} ground`;
9225
+ return `${a.ground.l < 0.3 ? "dark" : "light"} ground`;
9226
+ }
9227
+ function sharedRhythm(a, b) {
9228
+ const bands = rhythmOf(a) ?? [];
9229
+ const word = (band) => band;
9230
+ if (bands.length <= 1) return `${bands[0] ? word(bands[0]) : "light"} page throughout`;
9231
+ const second = bands[1] === "mid" ? "mid-tone band" : `${word(bands[1])} band`;
9232
+ return `${word(bands[0])} hero then ${second}`;
9233
+ }
9234
+ function sharedHero(a, b) {
9235
+ const hero = a.sections?.find((s) => s.role === "hero");
9236
+ const placement = hero && hero.centred >= 0.5 ? "centred" : "left-aligned";
9237
+ const headline = a.hero?.headline && b.hero?.headline && a.hero.headline.class === b.hero.headline.class ? `${a.hero.headline.class} ` : "";
9238
+ 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}`}` : "";
9239
+ return `${placement} ${headline}hero${image}`;
9240
+ }
9097
9241
  function sharedParts(a, b, d = fingerprintDistance(a, b)) {
9098
9242
  const out = [];
9099
- const close = (k) => d.parts[k] <= SHARED_PART;
9243
+ const close = (k) => {
9244
+ const p = d.parts[k];
9245
+ return p !== null && p <= SHARED_PART;
9246
+ };
9100
9247
  if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
9101
9248
  if (close("type")) {
9102
9249
  if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
9103
9250
  if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
9104
9251
  }
9105
- if (close("ground")) out.push(`${groundName(a.ground)} ground`);
9252
+ if (close("ground")) out.push(sharedGround(a, b));
9106
9253
  if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
9107
9254
  if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
9108
9255
  if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
9109
- if (close("layout")) out.push("running order");
9256
+ if (close("hero")) out.push(sharedHero(a, b));
9257
+ if (close("rhythm")) out.push(sharedRhythm(a, b));
9258
+ if (close("layout")) out.push("same running order");
9110
9259
  return out;
9111
9260
  }
9112
9261
  function compareToEstate(target2, register, options = {}) {
@@ -9245,6 +9394,7 @@ export {
9245
9394
  MOOD,
9246
9395
  NEGATIVE_REASSURANCE,
9247
9396
  NULL_VERSION,
9397
+ OPENING_BANDS,
9248
9398
  PAGE_TYPES,
9249
9399
  PILL_LIMIT,
9250
9400
  PLAINER_WORDS,
@@ -9288,6 +9438,9 @@ export {
9288
9438
  STOCK_PHRASES,
9289
9439
  STUDY_WORDS,
9290
9440
  TELL_HEAVY,
9441
+ TEMPERATURE_FULL,
9442
+ TEMPERATURE_NAMED,
9443
+ TEMPERATURE_WEIGHT,
9291
9444
  TRADE_NOUNS,
9292
9445
  TYPE_STEPS,
9293
9446
  TYPICAL_AT,
@@ -9299,6 +9452,7 @@ export {
9299
9452
  applyHouseGate,
9300
9453
  applySeverity,
9301
9454
  auditSnapshot,
9455
+ bandOf,
9302
9456
  briefTerms,
9303
9457
  calibrate,
9304
9458
  catalogueTable,
@@ -9362,10 +9516,14 @@ export {
9362
9516
  globToRegExp,
9363
9517
  gradientContrast,
9364
9518
  gradientStops,
9519
+ groundDistance,
9520
+ groundLabel,
9365
9521
  groundName,
9522
+ groundTemperature,
9366
9523
  harvest,
9367
9524
  hedgeCount,
9368
9525
  hedgedClose,
9526
+ heroDistance,
9369
9527
  hexToOklch,
9370
9528
  holdHouseBlocks,
9371
9529
  houseRule,
@@ -9413,6 +9571,8 @@ export {
9413
9571
  renderedOrder,
9414
9572
  researchPrompt,
9415
9573
  retrofitPlan,
9574
+ rhythmDistance,
9575
+ rhythmOf,
9416
9576
  runTell,
9417
9577
  scanProvenance,
9418
9578
  scanSource,
@@ -9439,6 +9599,7 @@ export {
9439
9599
  tailwindV3Preset,
9440
9600
  tailwindV4Theme,
9441
9601
  tellById,
9602
+ temperatureName,
9442
9603
  toGamut,
9443
9604
  toJson,
9444
9605
  toSarif,
package/licences.json CHANGED
@@ -99,6 +99,33 @@
99
99
  "checked": "2026-09-24",
100
100
  "source": "https://iconscout.com/licenses",
101
101
  "note": "Each licence covers a single end product. Never in a tool, template or shared kit (create-site, craft, a house component library), even modified. Record the assets each site uses. Assets come from different contributors, so take one site's set from one contributor."
102
+ },
103
+ {
104
+ "name": "Saira Semi Condensed",
105
+ "subject": "face",
106
+ "licence": "OFL-1.1",
107
+ "multiClient": "yes",
108
+ "checked": "2026-10-09",
109
+ "source": "https://github.com/google/fonts/blob/main/ofl/sairasemicondensed/OFL.txt",
110
+ "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)."
111
+ },
112
+ {
113
+ "name": "Public Sans",
114
+ "subject": "face",
115
+ "licence": "OFL-1.1",
116
+ "multiClient": "yes",
117
+ "checked": "2026-10-09",
118
+ "source": "https://github.com/google/fonts/blob/main/ofl/publicsans/OFL.txt",
119
+ "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)."
120
+ },
121
+ {
122
+ "name": "Martian Mono",
123
+ "subject": "face",
124
+ "licence": "OFL-1.1",
125
+ "multiClient": "yes",
126
+ "checked": "2026-10-09",
127
+ "source": "https://github.com/google/fonts/blob/main/ofl/martianmono/OFL.txt",
128
+ "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)."
102
129
  }
103
130
  ]
104
131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domandigital/craft",
3
- "version": "0.18.2",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "description": "The Doman Digital house craft standard as numbers: OKLCh colour ramps that snap to existing anchors, derived semantic tokens with measured WCAG and APCA contrast, one shared motion vocabulary for CSS and JS, and a base stylesheet. Zero dependencies.",
6
6
  "license": "Apache-2.0",