@domandigital/craft 0.16.0 → 0.18.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 +17 -0
- package/CHARACTER.md +41 -6
- package/COPY.md +5 -0
- package/README.md +31 -1
- package/REGISTERS.md +163 -0
- package/dist/{audit-UJCUWORL.js → audit-4SFSLCKY.js} +3 -2
- package/dist/audit.cjs +1 -1
- package/dist/audit.d.cts +7 -0
- package/dist/audit.d.ts +7 -0
- package/dist/audit.js +1 -1
- package/dist/chunk-4QWEAL4G.js +1595 -0
- package/dist/{chunk-WAV6AKR4.js → chunk-6TSDYETQ.js} +4 -2
- package/dist/{chunk-TQKDCA4V.js → chunk-DARKHE6A.js} +7 -5
- package/dist/{chunk-LXDNIJFG.js → chunk-O5B5ELJH.js} +28 -20
- package/dist/chunk-QIEDBL4J.js +9 -0
- package/dist/{chunk-CVG5GSMN.js → chunk-RJVG5KA6.js} +8 -6
- package/dist/{chunk-YK2FFL6Z.js → chunk-SL5VZJ2J.js} +1 -1
- package/dist/{chunk-B2MJLGF6.js → chunk-WFB6F3BN.js} +65 -1633
- package/dist/{chunk-LWVGIJ5A.js → chunk-ZUYERAFM.js} +11 -6
- package/dist/{cli-AVYNHBFJ.js → cli-BN4IT6N2.js} +5 -3
- package/dist/{cli-PEATEW57.js → cli-GXIW7F3E.js} +7 -5
- package/dist/cli-JB4X5LAG.js +1573 -0
- package/dist/{cli-52M62QBN.js → cli-JZRQBCD4.js} +7 -5
- package/dist/{cli-3GQROFCB.js → cli-MDW7JTEF.js} +15 -10
- package/dist/{cli-CWORJYOU.js → cli-V44QEVS6.js} +12 -9
- package/dist/{cli-K4LSRYBE.js → cli-Y7Y5IROK.js} +8 -5
- package/dist/cli.js +4 -2
- package/dist/index.cjs +1549 -101
- package/dist/index.d.cts +247 -1
- package/dist/index.d.ts +247 -1
- package/dist/index.js +1529 -101
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
CHOICE_KEYS: () => CHOICE_KEYS,
|
|
34
34
|
COMPONENT_ROLES: () => COMPONENT_ROLES,
|
|
35
35
|
CONSIDERATION: () => CONSIDERATION,
|
|
36
|
+
CONTENT_FEATURES: () => CONTENT_FEATURES,
|
|
36
37
|
CONVENTION: () => CONVENTION,
|
|
37
38
|
CUSTOMER_KINDS: () => CUSTOMER_KINDS,
|
|
38
39
|
DEFAULT_IGNORE: () => DEFAULT_IGNORE,
|
|
@@ -63,11 +64,12 @@ __export(index_exports, {
|
|
|
63
64
|
HUMAN_TYPICAL_TARGET: () => HUMAN_TYPICAL_TARGET,
|
|
64
65
|
IMAGERY_TELLS: () => IMAGERY_TELLS,
|
|
65
66
|
JSON_SCHEMA_VERSION: () => JSON_SCHEMA_VERSION,
|
|
66
|
-
LABELS: () =>
|
|
67
|
+
LABELS: () => LABELS2,
|
|
67
68
|
LEXICAL_FEATURES: () => LEXICAL_FEATURES,
|
|
68
69
|
LICENCES: () => LICENCES,
|
|
69
70
|
LIGHTNESS_CURVE: () => LIGHTNESS_CURVE,
|
|
70
71
|
MIN_DECIDED: () => MIN_DECIDED,
|
|
72
|
+
MIN_PARAGRAPHS: () => MIN_PARAGRAPHS,
|
|
71
73
|
MIN_RUNS: () => MIN_RUNS,
|
|
72
74
|
MOOD: () => MOOD,
|
|
73
75
|
NEGATIVE_REASSURANCE: () => NEGATIVE_REASSURANCE,
|
|
@@ -76,6 +78,9 @@ __export(index_exports, {
|
|
|
76
78
|
PILL_LIMIT: () => PILL_LIMIT,
|
|
77
79
|
PLAINER_WORDS: () => PLAINER_WORDS,
|
|
78
80
|
PREFERENCE: () => PREFERENCE,
|
|
81
|
+
PROFILE: () => PROFILE,
|
|
82
|
+
PROFILE_MEASURED: () => PROFILE_MEASURED,
|
|
83
|
+
PROFILE_PARAGRAPHS: () => PROFILE_PARAGRAPHS,
|
|
79
84
|
PROMO_BADGE: () => PROMO_BADGE,
|
|
80
85
|
PROOF_NAMES: () => PROOF_NAMES,
|
|
81
86
|
PROPOSED: () => PROPOSED,
|
|
@@ -84,10 +89,15 @@ __export(index_exports, {
|
|
|
84
89
|
READABLE_SNAPSHOT_VERSIONS: () => READABLE_SNAPSHOT_VERSIONS,
|
|
85
90
|
REFLEX_FONTS_1: () => REFLEX_FONTS_1,
|
|
86
91
|
REFLEX_FONTS_2: () => REFLEX_FONTS_2,
|
|
92
|
+
REGISTERS: () => REGISTERS,
|
|
93
|
+
REGISTER_BASELINE: () => REGISTER_BASELINE,
|
|
94
|
+
REGISTER_FEATURES: () => REGISTER_FEATURES,
|
|
95
|
+
REGISTER_IDS: () => REGISTER_IDS,
|
|
87
96
|
RENDERED_PATHS: () => RENDERED_PATHS,
|
|
88
97
|
RENDERED_TELLS: () => RENDERED_TELLS,
|
|
89
98
|
REVEAL_LIMIT: () => REVEAL_LIMIT,
|
|
90
99
|
REVIEW_PHRASES: () => REVIEW_PHRASES,
|
|
100
|
+
SAMPLES_PER_SEGMENT: () => SAMPLES_PER_SEGMENT,
|
|
91
101
|
SCALE: () => SCALE,
|
|
92
102
|
SECTION_ROLES: () => SECTION_ROLES,
|
|
93
103
|
SHADCN_LIMIT: () => SHADCN_LIMIT,
|
|
@@ -125,7 +135,9 @@ __export(index_exports, {
|
|
|
125
135
|
checkCopy: () => checkCopy,
|
|
126
136
|
checkOrder: () => checkOrder,
|
|
127
137
|
checkPair: () => checkPair,
|
|
138
|
+
checkPhotoReview: () => checkPhotoReview,
|
|
128
139
|
checkReason: () => checkReason,
|
|
140
|
+
checkRegister: () => checkRegister,
|
|
129
141
|
checkRestraint: () => checkRestraint,
|
|
130
142
|
choicesOf: () => choicesOf,
|
|
131
143
|
clauseDepth: () => clauseDepth,
|
|
@@ -150,6 +162,7 @@ __export(index_exports, {
|
|
|
150
162
|
densityCss: () => densityCss,
|
|
151
163
|
densityTokens: () => densityTokens,
|
|
152
164
|
designTells: () => designTells,
|
|
165
|
+
documentParagraphs: () => documentParagraphs,
|
|
153
166
|
emitCss: () => emitCss,
|
|
154
167
|
emptyEstate: () => emptyEstate,
|
|
155
168
|
estateComponentPairs: () => estateComponentPairs,
|
|
@@ -157,6 +170,7 @@ __export(index_exports, {
|
|
|
157
170
|
exitDuration: () => exitDuration,
|
|
158
171
|
extractHtml: () => extractHtml,
|
|
159
172
|
faceLicence: () => faceLicence,
|
|
173
|
+
featureLabel: () => featureLabel,
|
|
160
174
|
findClaims: () => findClaims,
|
|
161
175
|
findColours: () => findColours,
|
|
162
176
|
findingKey: () => findingKey,
|
|
@@ -173,7 +187,10 @@ __export(index_exports, {
|
|
|
173
187
|
formatDirectionMatches: () => formatDirectionMatches,
|
|
174
188
|
formatHex: () => formatHex,
|
|
175
189
|
formatReport: () => formatReport,
|
|
190
|
+
getRegister: () => getRegister,
|
|
176
191
|
globToRegExp: () => globToRegExp,
|
|
192
|
+
gradientContrast: () => gradientContrast,
|
|
193
|
+
gradientStops: () => gradientStops,
|
|
177
194
|
groundName: () => groundName,
|
|
178
195
|
harvest: () => harvest,
|
|
179
196
|
hedgeCount: () => hedgeCount,
|
|
@@ -219,6 +236,9 @@ __export(index_exports, {
|
|
|
219
236
|
ramp: () => ramp,
|
|
220
237
|
rampFromAnchors: () => rampFromAnchors,
|
|
221
238
|
readSnapshot: () => readSnapshot,
|
|
239
|
+
registerBrief: () => registerBrief,
|
|
240
|
+
registerOf: () => registerOf,
|
|
241
|
+
registersDoc: () => registersDoc,
|
|
222
242
|
renderedOrder: () => renderedOrder,
|
|
223
243
|
researchPrompt: () => researchPrompt,
|
|
224
244
|
retrofitPlan: () => retrofitPlan,
|
|
@@ -570,6 +590,241 @@ function checkPair(text, background) {
|
|
|
570
590
|
};
|
|
571
591
|
}
|
|
572
592
|
|
|
593
|
+
// src/character/color.ts
|
|
594
|
+
var COLOUR = /#[0-9a-fA-F]{8}\b|#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3,4}\b|\b(?:rgba?|hsla?|oklch|oklab)\([^)]*\)|\bcolor\(\s*srgb\b[^)]*\)/g;
|
|
595
|
+
function num(token, percentOf) {
|
|
596
|
+
const t = token.trim();
|
|
597
|
+
if (t.endsWith("%")) return parseFloat(t) / 100 * percentOf;
|
|
598
|
+
return parseFloat(t);
|
|
599
|
+
}
|
|
600
|
+
function parts(fn) {
|
|
601
|
+
const inner = fn.slice(fn.indexOf("(") + 1, -1).trim();
|
|
602
|
+
const [main, slashAlpha] = inner.split("/");
|
|
603
|
+
const channels = main.split(/[\s,]+/).filter(Boolean);
|
|
604
|
+
let alpha = 1;
|
|
605
|
+
if (slashAlpha !== void 0) alpha = num(slashAlpha, 1);
|
|
606
|
+
else if (channels.length === 4) alpha = num(channels.pop(), 1);
|
|
607
|
+
return { channels, alpha: Number.isFinite(alpha) ? alpha : 1 };
|
|
608
|
+
}
|
|
609
|
+
function hslToRgb(h, s, l) {
|
|
610
|
+
const k = (n) => (n + h / 30) % 12;
|
|
611
|
+
const a = s * Math.min(l, 1 - l);
|
|
612
|
+
const f4 = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
613
|
+
return { r: f4(0), g: f4(8), b: f4(4) };
|
|
614
|
+
}
|
|
615
|
+
var clamp012 = (n) => Math.min(1, Math.max(0, n));
|
|
616
|
+
function parseColour(raw) {
|
|
617
|
+
try {
|
|
618
|
+
if (raw.startsWith("#")) {
|
|
619
|
+
const hex2 = raw.slice(1);
|
|
620
|
+
const alpha2 = hex2.length === 8 ? parseInt(hex2.slice(6), 16) / 255 : hex2.length === 4 ? parseInt(hex2[3] + hex2[3], 16) / 255 : 1;
|
|
621
|
+
return { oklch: hexToOklch(raw), alpha: alpha2 };
|
|
622
|
+
}
|
|
623
|
+
const fn = raw.slice(0, raw.indexOf("(")).toLowerCase();
|
|
624
|
+
if (fn === "color") {
|
|
625
|
+
const { channels: channels2, alpha: alpha2 } = parts(raw.replace(/^color\(\s*srgb\s+/i, "srgb("));
|
|
626
|
+
const [r, g, b] = channels2.map((c) => clamp012(num(c, 1)));
|
|
627
|
+
if (![r, g, b].every(Number.isFinite)) return null;
|
|
628
|
+
return { oklch: hexToOklch(formatHex({ r, g, b })), alpha: alpha2 };
|
|
629
|
+
}
|
|
630
|
+
const { channels, alpha } = parts(raw);
|
|
631
|
+
if (channels.length < 3 || channels.some((c) => c.startsWith("var"))) return null;
|
|
632
|
+
if (fn === "oklch") {
|
|
633
|
+
const l = num(channels[0], 1);
|
|
634
|
+
const c = num(channels[1], 0.4);
|
|
635
|
+
const h = parseFloat(channels[2]);
|
|
636
|
+
if (![l, c].every(Number.isFinite)) return null;
|
|
637
|
+
return { oklch: { l, c, h: Number.isFinite(h) ? h : 0 }, alpha };
|
|
638
|
+
}
|
|
639
|
+
if (fn === "oklab") {
|
|
640
|
+
const l = num(channels[0], 1);
|
|
641
|
+
const a = num(channels[1], 0.4);
|
|
642
|
+
const b = num(channels[2], 0.4);
|
|
643
|
+
if (![l, a, b].every(Number.isFinite)) return null;
|
|
644
|
+
const c = Math.sqrt(a * a + b * b);
|
|
645
|
+
const h = c < 1e-7 ? 0 : (Math.atan2(b, a) * 180 / Math.PI % 360 + 360) % 360;
|
|
646
|
+
return { oklch: { l, c, h }, alpha };
|
|
647
|
+
}
|
|
648
|
+
if (fn === "rgb" || fn === "rgba") {
|
|
649
|
+
const [r, g, b] = channels.map((c) => clamp012(num(c, 255) / 255));
|
|
650
|
+
if (![r, g, b].every(Number.isFinite)) return null;
|
|
651
|
+
return { oklch: hexToOklch(formatHex({ r, g, b })), alpha };
|
|
652
|
+
}
|
|
653
|
+
if (fn === "hsl" || fn === "hsla") {
|
|
654
|
+
const h = parseFloat(channels[0]);
|
|
655
|
+
const s = num(channels[1], 1);
|
|
656
|
+
const l = num(channels[2], 1);
|
|
657
|
+
if (![h, s, l].every(Number.isFinite)) return null;
|
|
658
|
+
const rgb = hslToRgb((h % 360 + 360) % 360, clamp012(s), clamp012(l));
|
|
659
|
+
return { oklch: hexToOklch(formatHex(rgb)), alpha };
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
function findColours(value) {
|
|
667
|
+
const out = [];
|
|
668
|
+
for (const m of value.matchAll(COLOUR)) {
|
|
669
|
+
const parsed = parseColour(m[0]);
|
|
670
|
+
if (parsed) out.push({ raw: m[0], ...parsed, index: m.index ?? 0 });
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
var AI_VIOLET = { hueMin: 268, hueMax: 312, minChroma: 0.1, minL: 0.3, maxL: 0.85 };
|
|
675
|
+
function isAiViolet({ l, c, h }) {
|
|
676
|
+
return c >= AI_VIOLET.minChroma && h >= AI_VIOLET.hueMin && h <= AI_VIOLET.hueMax && l >= AI_VIOLET.minL && l <= AI_VIOLET.maxL;
|
|
677
|
+
}
|
|
678
|
+
function isCream({ l, c, h }) {
|
|
679
|
+
return l >= 0.9 && l <= 0.985 && c >= 8e-3 && c <= 0.06 && h >= 55 && h <= 105;
|
|
680
|
+
}
|
|
681
|
+
function isBlueToPurple({ c, h }) {
|
|
682
|
+
return c >= 0.08 && h >= 240 && h <= 330;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/color/gradient.ts
|
|
686
|
+
var KEYWORDS = {
|
|
687
|
+
white: { r: 1, g: 1, b: 1, a: 1 },
|
|
688
|
+
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
689
|
+
transparent: { r: 0, g: 0, b: 0, a: 0 }
|
|
690
|
+
};
|
|
691
|
+
var clamp013 = (n) => Math.min(1, Math.max(0, n));
|
|
692
|
+
function toRgba(raw) {
|
|
693
|
+
const value = raw.trim().toLowerCase();
|
|
694
|
+
if (KEYWORDS[value]) return KEYWORDS[value];
|
|
695
|
+
if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(value)) {
|
|
696
|
+
const h = value.length <= 5 ? [...value.slice(1)].map((c) => c + c).join("") : value.slice(1);
|
|
697
|
+
const { r: r2, g: g2, b: b2 } = parseHex(h.slice(0, 6));
|
|
698
|
+
return { r: r2, g: g2, b: b2, a: h.length === 8 ? parseInt(h.slice(6), 16) / 255 : 1 };
|
|
699
|
+
}
|
|
700
|
+
const rgb = /^rgba?\(([^)]+)\)$/.exec(value);
|
|
701
|
+
if (rgb) {
|
|
702
|
+
const parts2 = rgb[1].split(/[\s,/]+/).filter(Boolean);
|
|
703
|
+
const channel = (p) => p.endsWith("%") ? parseFloat(p) / 100 : parseFloat(p) / 255;
|
|
704
|
+
const alpha = parts2[3] === void 0 ? 1 : parts2[3].endsWith("%") ? parseFloat(parts2[3]) / 100 : parseFloat(parts2[3]);
|
|
705
|
+
const [r2, g2, b2] = parts2.slice(0, 3).map(channel);
|
|
706
|
+
if (parts2.length >= 3 && [r2, g2, b2, alpha].every(Number.isFinite)) return { r: clamp013(r2), g: clamp013(g2), b: clamp013(b2), a: clamp013(alpha) };
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
const parsed = parseColour(value);
|
|
710
|
+
if (!parsed) return null;
|
|
711
|
+
const { r, g, b } = oklchToRgb(parsed.oklch);
|
|
712
|
+
return { r: clamp013(r), g: clamp013(g), b: clamp013(b), a: clamp013(parsed.alpha) };
|
|
713
|
+
}
|
|
714
|
+
var SAMPLES_PER_SEGMENT = 256;
|
|
715
|
+
function mix(a, b, t) {
|
|
716
|
+
const alpha = a.a + (b.a - a.a) * t;
|
|
717
|
+
if (alpha === 0) return { r: 0, g: 0, b: 0, a: 0 };
|
|
718
|
+
const ch = (x, y) => (x * a.a + (y * b.a - x * a.a) * t) / alpha;
|
|
719
|
+
return { r: ch(a.r, b.r), g: ch(a.g, b.g), b: ch(a.b, b.b), a: alpha };
|
|
720
|
+
}
|
|
721
|
+
function composite(top, under) {
|
|
722
|
+
return {
|
|
723
|
+
r: top.r * top.a + under.r * (1 - top.a),
|
|
724
|
+
g: top.g * top.a + under.g * (1 - top.a),
|
|
725
|
+
b: top.b * top.a + under.b * (1 - top.a)
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function gradientStops(gradient) {
|
|
729
|
+
const inner = /gradient\(([\s\S]*)\)\s*$/.exec(gradient.trim());
|
|
730
|
+
if (!inner) throw new Error(`not a CSS gradient: ${gradient}`);
|
|
731
|
+
const parts2 = [];
|
|
732
|
+
let depth = 0;
|
|
733
|
+
let current = "";
|
|
734
|
+
for (const ch of inner[1]) {
|
|
735
|
+
if (ch === "(") depth += 1;
|
|
736
|
+
if (ch === ")") depth -= 1;
|
|
737
|
+
if (ch === "," && depth === 0) {
|
|
738
|
+
parts2.push(current.trim());
|
|
739
|
+
current = "";
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
current += ch;
|
|
743
|
+
}
|
|
744
|
+
if (current.trim()) parts2.push(current.trim());
|
|
745
|
+
const stops = [];
|
|
746
|
+
for (const part of parts2) {
|
|
747
|
+
const colour = part.replace(/(\s+-?[\d.]+(%|px|r?em|deg|turn|vw|vh))+\s*$/i, "").trim();
|
|
748
|
+
if (/^(to\s|circle|ellipse|closest|farthest|at\s|from\s|in\s|-?[\d.]+(deg|turn|rad|grad)$)/i.test(colour)) continue;
|
|
749
|
+
if (!toRgba(colour)) throw new Error(`unreadable gradient stop "${colour}" in ${gradient}`);
|
|
750
|
+
stops.push(colour);
|
|
751
|
+
}
|
|
752
|
+
if (stops.length === 0) throw new Error(`no colour stops in ${gradient}`);
|
|
753
|
+
return stops;
|
|
754
|
+
}
|
|
755
|
+
function gradientContrast(text, gradient, beneath = "#ffffff") {
|
|
756
|
+
const under = toRgba(beneath);
|
|
757
|
+
if (!under || under.a < 1) throw new Error(`beneath must be an opaque colour, got ${beneath}`);
|
|
758
|
+
const fg = toRgba(text);
|
|
759
|
+
if (!fg || fg.a < 1) throw new Error(`text must be an opaque colour, got ${text}`);
|
|
760
|
+
const textHex = formatHex(fg);
|
|
761
|
+
const raw = gradientStops(gradient).map((s) => toRgba(s));
|
|
762
|
+
const stops = raw.map((s) => formatHex(composite(s, under)));
|
|
763
|
+
let worst = stops[0];
|
|
764
|
+
let worstRatio = wcagContrast(textHex, worst);
|
|
765
|
+
for (let i = 0; i + 1 < raw.length; i++) {
|
|
766
|
+
for (let k = 1; k <= SAMPLES_PER_SEGMENT; k++) {
|
|
767
|
+
const point = formatHex(composite(mix(raw[i], raw[i + 1], k / SAMPLES_PER_SEGMENT), under));
|
|
768
|
+
const ratio = wcagContrast(textHex, point);
|
|
769
|
+
if (ratio < worstRatio) [worst, worstRatio] = [point, ratio];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const wcag = wcagContrast(textHex, worst);
|
|
773
|
+
const lc = apcaContrast(textHex, worst);
|
|
774
|
+
return {
|
|
775
|
+
text,
|
|
776
|
+
gradient,
|
|
777
|
+
beneath,
|
|
778
|
+
stops,
|
|
779
|
+
worst,
|
|
780
|
+
wcag,
|
|
781
|
+
lc,
|
|
782
|
+
passesAA: wcag >= 4.5,
|
|
783
|
+
passesAALarge: wcag >= 3,
|
|
784
|
+
note: `${text} on ${gradient} over ${beneath}: worst point ${worst}, ${wcag.toFixed(2)}:1, Lc ${lc.toFixed(1)}`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/photo/review.ts
|
|
789
|
+
function checkPhotoReview(text, options = {}) {
|
|
790
|
+
const minScore = options.minScore ?? 0.7;
|
|
791
|
+
const required = options.required ?? true;
|
|
792
|
+
const failures = [];
|
|
793
|
+
const report2 = [];
|
|
794
|
+
const add = (ok, message) => {
|
|
795
|
+
report2.push(`${ok ? "ok " : "FAIL"} ${message}`);
|
|
796
|
+
if (!ok) failures.push(message);
|
|
797
|
+
};
|
|
798
|
+
const done = () => ({ ok: failures.length === 0, failures, report: report2 });
|
|
799
|
+
if (text === null) {
|
|
800
|
+
if (required) add(false, "no photo review: run `halide review --json`");
|
|
801
|
+
else report2.push("ok no photo review, and none required");
|
|
802
|
+
return done();
|
|
803
|
+
}
|
|
804
|
+
let parsed;
|
|
805
|
+
try {
|
|
806
|
+
parsed = JSON.parse(text);
|
|
807
|
+
} catch (error) {
|
|
808
|
+
add(false, `the photo review is not valid JSON: ${error.message}`);
|
|
809
|
+
return done();
|
|
810
|
+
}
|
|
811
|
+
const images = Array.isArray(parsed.images) ? parsed.images : [];
|
|
812
|
+
const overall = typeof parsed.score === "number" ? parsed.score : null;
|
|
813
|
+
if (overall === null && images.length === 0) {
|
|
814
|
+
add(false, "the photo review has neither a score nor an images list");
|
|
815
|
+
return done();
|
|
816
|
+
}
|
|
817
|
+
if (overall !== null) add(overall >= minScore, `overall score ${overall.toFixed(2)} against a minimum of ${minScore.toFixed(2)}`);
|
|
818
|
+
const below = images.filter((i) => typeof i.score === "number" && i.score < minScore);
|
|
819
|
+
if (below.length > 0) {
|
|
820
|
+
const named = below.slice(0, 5).map((i) => `${i.file ?? i.id ?? "?"} (${i.score.toFixed(2)})`).join(", ");
|
|
821
|
+
add(false, `${below.length} of ${images.length} image(s) below ${minScore.toFixed(2)}: ${named}`);
|
|
822
|
+
} else if (images.length > 0) {
|
|
823
|
+
add(true, `all ${images.length} image(s) at or above ${minScore.toFixed(2)}`);
|
|
824
|
+
}
|
|
825
|
+
return done();
|
|
826
|
+
}
|
|
827
|
+
|
|
573
828
|
// src/color/semantic.ts
|
|
574
829
|
function accentFork(rampSteps, on, options = {}) {
|
|
575
830
|
const minWcag = options.minWcag ?? 4.5;
|
|
@@ -2817,98 +3072,6 @@ ${filler(520, 11)}`)]
|
|
|
2817
3072
|
}
|
|
2818
3073
|
];
|
|
2819
3074
|
|
|
2820
|
-
// src/character/color.ts
|
|
2821
|
-
var COLOUR = /#[0-9a-fA-F]{8}\b|#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{3,4}\b|\b(?:rgba?|hsla?|oklch|oklab)\([^)]*\)|\bcolor\(\s*srgb\b[^)]*\)/g;
|
|
2822
|
-
function num(token, percentOf) {
|
|
2823
|
-
const t = token.trim();
|
|
2824
|
-
if (t.endsWith("%")) return parseFloat(t) / 100 * percentOf;
|
|
2825
|
-
return parseFloat(t);
|
|
2826
|
-
}
|
|
2827
|
-
function parts(fn) {
|
|
2828
|
-
const inner = fn.slice(fn.indexOf("(") + 1, -1).trim();
|
|
2829
|
-
const [main, slashAlpha] = inner.split("/");
|
|
2830
|
-
const channels = main.split(/[\s,]+/).filter(Boolean);
|
|
2831
|
-
let alpha = 1;
|
|
2832
|
-
if (slashAlpha !== void 0) alpha = num(slashAlpha, 1);
|
|
2833
|
-
else if (channels.length === 4) alpha = num(channels.pop(), 1);
|
|
2834
|
-
return { channels, alpha: Number.isFinite(alpha) ? alpha : 1 };
|
|
2835
|
-
}
|
|
2836
|
-
function hslToRgb(h, s, l) {
|
|
2837
|
-
const k = (n) => (n + h / 30) % 12;
|
|
2838
|
-
const a = s * Math.min(l, 1 - l);
|
|
2839
|
-
const f4 = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
2840
|
-
return { r: f4(0), g: f4(8), b: f4(4) };
|
|
2841
|
-
}
|
|
2842
|
-
var clamp012 = (n) => Math.min(1, Math.max(0, n));
|
|
2843
|
-
function parseColour(raw) {
|
|
2844
|
-
try {
|
|
2845
|
-
if (raw.startsWith("#")) {
|
|
2846
|
-
const hex2 = raw.slice(1);
|
|
2847
|
-
const alpha2 = hex2.length === 8 ? parseInt(hex2.slice(6), 16) / 255 : hex2.length === 4 ? parseInt(hex2[3] + hex2[3], 16) / 255 : 1;
|
|
2848
|
-
return { oklch: hexToOklch(raw), alpha: alpha2 };
|
|
2849
|
-
}
|
|
2850
|
-
const fn = raw.slice(0, raw.indexOf("(")).toLowerCase();
|
|
2851
|
-
if (fn === "color") {
|
|
2852
|
-
const { channels: channels2, alpha: alpha2 } = parts(raw.replace(/^color\(\s*srgb\s+/i, "srgb("));
|
|
2853
|
-
const [r, g, b] = channels2.map((c) => clamp012(num(c, 1)));
|
|
2854
|
-
if (![r, g, b].every(Number.isFinite)) return null;
|
|
2855
|
-
return { oklch: hexToOklch(formatHex({ r, g, b })), alpha: alpha2 };
|
|
2856
|
-
}
|
|
2857
|
-
const { channels, alpha } = parts(raw);
|
|
2858
|
-
if (channels.length < 3 || channels.some((c) => c.startsWith("var"))) return null;
|
|
2859
|
-
if (fn === "oklch") {
|
|
2860
|
-
const l = num(channels[0], 1);
|
|
2861
|
-
const c = num(channels[1], 0.4);
|
|
2862
|
-
const h = parseFloat(channels[2]);
|
|
2863
|
-
if (![l, c].every(Number.isFinite)) return null;
|
|
2864
|
-
return { oklch: { l, c, h: Number.isFinite(h) ? h : 0 }, alpha };
|
|
2865
|
-
}
|
|
2866
|
-
if (fn === "oklab") {
|
|
2867
|
-
const l = num(channels[0], 1);
|
|
2868
|
-
const a = num(channels[1], 0.4);
|
|
2869
|
-
const b = num(channels[2], 0.4);
|
|
2870
|
-
if (![l, a, b].every(Number.isFinite)) return null;
|
|
2871
|
-
const c = Math.sqrt(a * a + b * b);
|
|
2872
|
-
const h = c < 1e-7 ? 0 : (Math.atan2(b, a) * 180 / Math.PI % 360 + 360) % 360;
|
|
2873
|
-
return { oklch: { l, c, h }, alpha };
|
|
2874
|
-
}
|
|
2875
|
-
if (fn === "rgb" || fn === "rgba") {
|
|
2876
|
-
const [r, g, b] = channels.map((c) => clamp012(num(c, 255) / 255));
|
|
2877
|
-
if (![r, g, b].every(Number.isFinite)) return null;
|
|
2878
|
-
return { oklch: hexToOklch(formatHex({ r, g, b })), alpha };
|
|
2879
|
-
}
|
|
2880
|
-
if (fn === "hsl" || fn === "hsla") {
|
|
2881
|
-
const h = parseFloat(channels[0]);
|
|
2882
|
-
const s = num(channels[1], 1);
|
|
2883
|
-
const l = num(channels[2], 1);
|
|
2884
|
-
if (![h, s, l].every(Number.isFinite)) return null;
|
|
2885
|
-
const rgb = hslToRgb((h % 360 + 360) % 360, clamp012(s), clamp012(l));
|
|
2886
|
-
return { oklch: hexToOklch(formatHex(rgb)), alpha };
|
|
2887
|
-
}
|
|
2888
|
-
} catch {
|
|
2889
|
-
return null;
|
|
2890
|
-
}
|
|
2891
|
-
return null;
|
|
2892
|
-
}
|
|
2893
|
-
function findColours(value) {
|
|
2894
|
-
const out = [];
|
|
2895
|
-
for (const m of value.matchAll(COLOUR)) {
|
|
2896
|
-
const parsed = parseColour(m[0]);
|
|
2897
|
-
if (parsed) out.push({ raw: m[0], ...parsed, index: m.index ?? 0 });
|
|
2898
|
-
}
|
|
2899
|
-
return out;
|
|
2900
|
-
}
|
|
2901
|
-
var AI_VIOLET = { hueMin: 268, hueMax: 312, minChroma: 0.1, minL: 0.3, maxL: 0.85 };
|
|
2902
|
-
function isAiViolet({ l, c, h }) {
|
|
2903
|
-
return c >= AI_VIOLET.minChroma && h >= AI_VIOLET.hueMin && h <= AI_VIOLET.hueMax && l >= AI_VIOLET.minL && l <= AI_VIOLET.maxL;
|
|
2904
|
-
}
|
|
2905
|
-
function isCream({ l, c, h }) {
|
|
2906
|
-
return l >= 0.9 && l <= 0.985 && c >= 8e-3 && c <= 0.06 && h >= 55 && h <= 105;
|
|
2907
|
-
}
|
|
2908
|
-
function isBlueToPurple({ c, h }) {
|
|
2909
|
-
return c >= 0.08 && h >= 240 && h <= 330;
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
3075
|
// src/character/tells/source.ts
|
|
2913
3076
|
function baseClass(cls) {
|
|
2914
3077
|
let depth = 0;
|
|
@@ -4736,7 +4899,7 @@ var SPECIFICITY_COPY_TELLS = [
|
|
|
4736
4899
|
},
|
|
4737
4900
|
rendered: {
|
|
4738
4901
|
detect: (s) => {
|
|
4739
|
-
if (!s.firstScreenText || !HOME_URL.test(s.url)) return [];
|
|
4902
|
+
if (!s.firstScreenText || !(s.home ?? HOME_URL.test(s.url))) return [];
|
|
4740
4903
|
const text = s.firstScreenText.join("\n");
|
|
4741
4904
|
if (!text.trim() || !specificity(text).generic) return [];
|
|
4742
4905
|
const h1 = s.headings.find((h) => h.level === 1 && h.top < s.viewport.height);
|
|
@@ -6514,6 +6677,1270 @@ function lexicalOf(text, brief) {
|
|
|
6514
6677
|
};
|
|
6515
6678
|
}
|
|
6516
6679
|
|
|
6680
|
+
// src/register/features.ts
|
|
6681
|
+
var REGISTER_FEATURES = [
|
|
6682
|
+
"meanSentenceWords",
|
|
6683
|
+
"contractionsPer100",
|
|
6684
|
+
"youPer100",
|
|
6685
|
+
"firstPersonPer100",
|
|
6686
|
+
"passiveShare",
|
|
6687
|
+
"meanWordLength"
|
|
6688
|
+
];
|
|
6689
|
+
var norm = (w) => w.toLowerCase().replace(/’/g, "'");
|
|
6690
|
+
var YOU = /* @__PURE__ */ new Set(["you", "your", "yours", "yourself", "yourselves", "you'll", "you're", "you've", "you'd"]);
|
|
6691
|
+
var FIRST = /* @__PURE__ */ new Set(["i", "me", "my", "mine", "we", "us", "our", "ours", "i'm", "i'll", "i've", "i'd", "we're", "we'll", "we've", "we'd"]);
|
|
6692
|
+
var CONTRACTION2 = /^[a-z]+(?:n't|'ll|'re|'ve|'d|'m)$|^(?:it|that|there|here|what|who|where|how|he|she|let)'s$/;
|
|
6693
|
+
var BE = "(?:am|is|are|was|were|be|been|being|'s|'re|'m)";
|
|
6694
|
+
var IRREGULAR = "(?:made|done|given|taken|seen|known|shown|written|built|sent|paid|held|told|found|kept|left|brought|bought|thought|taught|caught|sold|put|set|run|cut|chosen|driven|eaten|fallen|forgotten|hidden|broken|spoken|stolen|worn|torn|grown|drawn|thrown|flown|begun|sung|won|lost|met|led|read|heard|meant|spent|built|lent|dealt|felt|sought|fed|bred|laid|said|struck|hung|shut|hit|hurt|let|quit|spread|bound|wound|ground|understood|withdrawn|overseen|undertaken)";
|
|
6695
|
+
var PASSIVE = new RegExp(`\\b${BE}\\s+(?:[a-z]+ly\\s+)?(?:[a-z]+ed|[a-z]+en|${IRREGULAR})\\b`, "i");
|
|
6696
|
+
var ADJECTIVAL = /* @__PURE__ */ new Set([
|
|
6697
|
+
"tired",
|
|
6698
|
+
"interested",
|
|
6699
|
+
"excited",
|
|
6700
|
+
"pleased",
|
|
6701
|
+
"worried",
|
|
6702
|
+
"concerned",
|
|
6703
|
+
"surprised",
|
|
6704
|
+
"bored",
|
|
6705
|
+
"scared",
|
|
6706
|
+
"married",
|
|
6707
|
+
"qualified",
|
|
6708
|
+
"experienced",
|
|
6709
|
+
"based",
|
|
6710
|
+
"located",
|
|
6711
|
+
"committed",
|
|
6712
|
+
"dedicated",
|
|
6713
|
+
"delighted",
|
|
6714
|
+
"disappointed",
|
|
6715
|
+
"involved",
|
|
6716
|
+
"prepared",
|
|
6717
|
+
"supposed",
|
|
6718
|
+
"used",
|
|
6719
|
+
"open",
|
|
6720
|
+
"often",
|
|
6721
|
+
"even",
|
|
6722
|
+
"seven",
|
|
6723
|
+
"eleven",
|
|
6724
|
+
"listen",
|
|
6725
|
+
"happen",
|
|
6726
|
+
"then",
|
|
6727
|
+
"when",
|
|
6728
|
+
"ten",
|
|
6729
|
+
"garden",
|
|
6730
|
+
"kitchen",
|
|
6731
|
+
"children",
|
|
6732
|
+
"women",
|
|
6733
|
+
"men",
|
|
6734
|
+
"red",
|
|
6735
|
+
"bed",
|
|
6736
|
+
"need",
|
|
6737
|
+
"indeed",
|
|
6738
|
+
"shed",
|
|
6739
|
+
"seed",
|
|
6740
|
+
"feed",
|
|
6741
|
+
"speed",
|
|
6742
|
+
"hundred"
|
|
6743
|
+
]);
|
|
6744
|
+
function isPassive(sentence) {
|
|
6745
|
+
const re = new RegExp(PASSIVE.source, "gi");
|
|
6746
|
+
for (const m of sentence.matchAll(re)) {
|
|
6747
|
+
const last = norm(m[0].trim().split(/\s+/).pop() ?? "");
|
|
6748
|
+
if (last === "given" && /given\s+that\b/i.test(sentence.slice(m.index ?? 0))) continue;
|
|
6749
|
+
if (!ADJECTIVAL.has(last)) return true;
|
|
6750
|
+
}
|
|
6751
|
+
return false;
|
|
6752
|
+
}
|
|
6753
|
+
function registerOf(text) {
|
|
6754
|
+
const ss = splitSentences(text);
|
|
6755
|
+
const ws = words(text).map(norm);
|
|
6756
|
+
const n = ws.length;
|
|
6757
|
+
const per100 = (k) => n === 0 ? 0 : 100 * k / n;
|
|
6758
|
+
const letters = ws.reduce((s, w) => s + w.replace(/[^a-z]/g, "").length, 0);
|
|
6759
|
+
return {
|
|
6760
|
+
meanSentenceWords: ss.length === 0 ? 0 : n / ss.length,
|
|
6761
|
+
contractionsPer100: per100(ws.filter((w) => CONTRACTION2.test(w)).length),
|
|
6762
|
+
youPer100: per100(ws.filter((w) => YOU.has(w)).length),
|
|
6763
|
+
firstPersonPer100: per100(ws.filter((w) => FIRST.has(w)).length),
|
|
6764
|
+
passiveShare: ss.length === 0 ? 0 : ss.filter(isPassive).length / ss.length,
|
|
6765
|
+
meanWordLength: n === 0 ? 0 : letters / n
|
|
6766
|
+
};
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
// src/register/registers.ts
|
|
6770
|
+
var GOVUK = "GOV.UK writing guidelines";
|
|
6771
|
+
var MAILCHIMP = "Mailchimp Content Style Guide";
|
|
6772
|
+
var OGILVY = "Ogilvy, Ogilvy on Advertising (1983)";
|
|
6773
|
+
var CAPLES = "Caples, Tested Advertising Methods";
|
|
6774
|
+
var HOUSE2 = "house judgement";
|
|
6775
|
+
var ALWAYS = "Every register keeps the COPY.md blocking tier and the proof rule. Draft first, then run `craft copy <file>` on the draft.";
|
|
6776
|
+
var REGISTERS = [
|
|
6777
|
+
{
|
|
6778
|
+
id: "plain",
|
|
6779
|
+
name: "Plain and trustworthy",
|
|
6780
|
+
useFor: "Forms, policies, terms, instructions, and professional services where the reader must act correctly.",
|
|
6781
|
+
directives: [
|
|
6782
|
+
{ text: "Put the most important information first, then taper down to the detail.", from: GOVUK },
|
|
6783
|
+
{ text: "Split any sentence over 25 words into two.", from: GOVUK },
|
|
6784
|
+
{ text: "Choose the short, common word: buy, help, about.", from: GOVUK },
|
|
6785
|
+
{ text: "Write in the active voice, so the reader sees who does what.", from: GOVUK },
|
|
6786
|
+
{ text: 'Address the reader as "you", as one person you can actually help.', from: GOVUK },
|
|
6787
|
+
{ text: `Use positive contractions such as "you'll", and write negatives out in full: "do not", "cannot".`, from: GOVUK },
|
|
6788
|
+
{ text: "Write headings that say what the section tells the reader, starting with a verb where you can.", from: GOVUK },
|
|
6789
|
+
{ text: "Give each paragraph one job.", from: HOUSE2 }
|
|
6790
|
+
],
|
|
6791
|
+
example: {
|
|
6792
|
+
before: "Should you wish to apply for a residents' parking permit, an application form must be completed and submitted to the council prior to the commencement of the permit period.",
|
|
6793
|
+
after: "Apply for a residents' parking permit before you need it. Fill in the form online. It takes about 10 minutes.",
|
|
6794
|
+
why: "The action comes first, the council is no longer the subject, and each sentence does one thing."
|
|
6795
|
+
},
|
|
6796
|
+
sources: [
|
|
6797
|
+
{
|
|
6798
|
+
name: GOVUK,
|
|
6799
|
+
url: "https://guidance.publishing.service.gov.uk/writing-to-gov-uk-standards/writing-guidelines/",
|
|
6800
|
+
note: 'Clear language (the 25-word split, short words, active voice, contractions), clear structure (most important first, headings) and right tone (address the user as "you"). Checked against the live pages on 2026-09-30.'
|
|
6801
|
+
}
|
|
6802
|
+
]
|
|
6803
|
+
},
|
|
6804
|
+
{
|
|
6805
|
+
id: "persuasive",
|
|
6806
|
+
name: "Persuasive and concise",
|
|
6807
|
+
useFor: "Landing pages, service pages, adverts and sales emails: copy whose job is one action.",
|
|
6808
|
+
directives: [
|
|
6809
|
+
{ text: "Put the promise in the headline: what the reader gets, in words they would use.", from: OGILVY },
|
|
6810
|
+
{ text: "Back each claim with something the reader can check: a number, a name, a date, a price.", from: "COPY.md" },
|
|
6811
|
+
{ text: 'Write to one reader, as "you", about their problem before your service.', from: CAPLES },
|
|
6812
|
+
{ text: "Name the mechanism: how it works, how long it takes, what it costs.", from: "COPY.md" },
|
|
6813
|
+
{ text: "Answer the objection the reader already has, with evidence, in the sentence that follows the claim.", from: HOUSE2 },
|
|
6814
|
+
{ text: "Let the copy run as long as the facts last, and cut each sentence that carries none.", from: OGILVY },
|
|
6815
|
+
{ text: "Close with one specific action and what happens straight after it.", from: "COPY.md" }
|
|
6816
|
+
],
|
|
6817
|
+
example: {
|
|
6818
|
+
before: "Transform your garden with our premium landscaping solutions. Quality you can trust.",
|
|
6819
|
+
after: "We lay a 40 square metre Indian sandstone patio in three days, for \xA34,800 fitted. Every job since 2014 is on our reviews page. Book a free site visit and get a fixed quote within 48 hours.",
|
|
6820
|
+
why: "Size, material, time and price replace the adjectives, the proof sits next to the claim, and the close says what happens next. Fictional business."
|
|
6821
|
+
},
|
|
6822
|
+
sources: [
|
|
6823
|
+
{
|
|
6824
|
+
name: OGILVY,
|
|
6825
|
+
note: "Headlines carry the promise because far more people read the headline than the body; specific, informative copy outsells vague copy. Taken from the widely quoted principles; a page-by-page check against the book is still to do."
|
|
6826
|
+
},
|
|
6827
|
+
{
|
|
6828
|
+
name: CAPLES,
|
|
6829
|
+
note: "Headlines that appeal to the reader's self-interest, and copy judged by tested response rather than taste. A page-by-page check against the book is still to do."
|
|
6830
|
+
},
|
|
6831
|
+
{
|
|
6832
|
+
name: "COPY.md",
|
|
6833
|
+
note: "Writing directives (lead with the fact, name the mechanism, persuade with something checkable) and the CTA rule."
|
|
6834
|
+
}
|
|
6835
|
+
]
|
|
6836
|
+
},
|
|
6837
|
+
{
|
|
6838
|
+
id: "warm",
|
|
6839
|
+
name: "Warm and conversational",
|
|
6840
|
+
useFor: "Customer emails, replies to enquiries, newsletters and small-business updates.",
|
|
6841
|
+
directives: [
|
|
6842
|
+
{ text: "Write the way you would talk to a customer you like: contractions, everyday words.", from: MAILCHIMP },
|
|
6843
|
+
{ text: "Keep words and sentences simple; being clear matters more than being entertaining.", from: MAILCHIMP },
|
|
6844
|
+
{ text: "Say what the reader can do, in positive language.", from: MAILCHIMP },
|
|
6845
|
+
{ text: "Use the active voice.", from: MAILCHIMP },
|
|
6846
|
+
{ text: "Match the tone to how the reader feels right now: lighter for good news, calm and direct for a problem.", from: MAILCHIMP },
|
|
6847
|
+
{ text: "Keep humour dry and occasional, and only where it comes naturally.", from: MAILCHIMP },
|
|
6848
|
+
{ text: "Sign off as a person, with a name.", from: HOUSE2 }
|
|
6849
|
+
],
|
|
6850
|
+
example: {
|
|
6851
|
+
before: "Dear Valued Customer, We wish to inform you that your order has been dispatched and will be delivered in due course.",
|
|
6852
|
+
after: "Hi Sam, your order's on its way. It left our Leicester workshop this morning and should reach you on Thursday. Any questions, just reply to this email. Priya",
|
|
6853
|
+
why: 'A name at each end, a real day and place instead of "in due course", and a next step. Fictional business.'
|
|
6854
|
+
},
|
|
6855
|
+
sources: [
|
|
6856
|
+
{
|
|
6857
|
+
name: MAILCHIMP,
|
|
6858
|
+
url: "https://styleguide.mailchimp.com/voice-and-tone/",
|
|
6859
|
+
note: "Voice and tone (plainspoken, genuine, dry humour; clear before entertaining; adjust tone to the reader's state) and grammar and mechanics (active voice, contractions, positive language). Its allowance for emoji does not apply: emoji block under COPY.md. Checked against the live pages on 2026-09-30."
|
|
6860
|
+
}
|
|
6861
|
+
]
|
|
6862
|
+
},
|
|
6863
|
+
{
|
|
6864
|
+
id: "literary",
|
|
6865
|
+
name: "Elegant and literary",
|
|
6866
|
+
useFor: "Premium brands, makers, hospitality and anything sold on craft and taste rather than price.",
|
|
6867
|
+
directives: [
|
|
6868
|
+
{ text: "Choose one precise noun over two adjectives.", from: HOUSE2 },
|
|
6869
|
+
{ text: "Let sentence length follow the thought: a long sentence to build, a short one to land.", from: "COPY.md" },
|
|
6870
|
+
{ text: "Carry the detail in concrete facts: material, place, process, time.", from: HOUSE2 },
|
|
6871
|
+
{ text: "Trust the reader with one image, state it once, and move on.", from: HOUSE2 },
|
|
6872
|
+
{ text: "Keep claims quiet and exact, and let the facts carry the prestige.", from: HOUSE2 },
|
|
6873
|
+
{ text: "Draw any metaphor from the maker's own work, and hold it for the whole piece.", from: HOUSE2 },
|
|
6874
|
+
{ text: "Read the draft aloud and rework each line that trips the breath.", from: HOUSE2 }
|
|
6875
|
+
],
|
|
6876
|
+
example: {
|
|
6877
|
+
before: "Our luxurious, handcrafted candles create an unforgettable ambience of elegance and sophistication.",
|
|
6878
|
+
after: "Each candle is poured by hand in our Hebden Bridge workshop, forty to a batch. The wax is English rapeseed and the scent is cedar, cut with a little smoke. It burns for sixty hours, and the glass is yours to keep.",
|
|
6879
|
+
why: "Place, material, scent and burn time do the work the adjectives claimed. Fictional business."
|
|
6880
|
+
},
|
|
6881
|
+
sources: [
|
|
6882
|
+
{
|
|
6883
|
+
name: HOUSE2,
|
|
6884
|
+
note: "No public style authority exists for premium-brand prose. These directives are house judgement, to be revised as the estate grows."
|
|
6885
|
+
},
|
|
6886
|
+
{
|
|
6887
|
+
name: "Register separation, 2026-09-29",
|
|
6888
|
+
url: "calibration/copy-shape/report-registers.md",
|
|
6889
|
+
note: "Literary prose (Gutenberg) carries about twice the comma density of the other human registers, the one feature that separated it from all four. Longer, jointed sentences are normal here."
|
|
6890
|
+
}
|
|
6891
|
+
]
|
|
6892
|
+
}
|
|
6893
|
+
];
|
|
6894
|
+
var REGISTER_IDS = REGISTERS.map((r) => r.id);
|
|
6895
|
+
function getRegister(id) {
|
|
6896
|
+
return REGISTERS.find((r) => r.id === id);
|
|
6897
|
+
}
|
|
6898
|
+
function registerBrief(id) {
|
|
6899
|
+
const r = getRegister(id);
|
|
6900
|
+
if (!r) throw new Error(`unknown register "${id}". Registers: ${REGISTER_IDS.join(", ")}`);
|
|
6901
|
+
const lines = [`Register: ${r.name} (${r.id})`, `Use for: ${r.useFor}`, "", "Before you draft:"];
|
|
6902
|
+
r.directives.forEach((d, i) => lines.push(` ${i + 1}. ${d.text} [${d.from}]`));
|
|
6903
|
+
lines.push("", "Example (fictional):", ` Before: ${r.example.before}`, ` After: ${r.example.after}`, ` Why: ${r.example.why}`);
|
|
6904
|
+
lines.push("", "Sources:");
|
|
6905
|
+
for (const s of r.sources) lines.push(` - ${s.name}${s.url ? ` (${s.url})` : ""}: ${s.note}`);
|
|
6906
|
+
lines.push("", `Always: ${ALWAYS}`);
|
|
6907
|
+
return lines.join("\n");
|
|
6908
|
+
}
|
|
6909
|
+
function registersDoc() {
|
|
6910
|
+
const out = [];
|
|
6911
|
+
for (const r of REGISTERS) {
|
|
6912
|
+
out.push(`## ${r.name} (\`${r.id}\`)`, "", `**Use for:** ${r.useFor}`, "", "**Before you draft:**", "");
|
|
6913
|
+
r.directives.forEach((d, i) => out.push(`${i + 1}. ${d.text} *(${d.from})*`));
|
|
6914
|
+
out.push("", "**Example (fictional):**", "", `- Before: ${r.example.before}`, `- After: ${r.example.after}`, `- Why: ${r.example.why}`);
|
|
6915
|
+
out.push("", "**Sources:**", "");
|
|
6916
|
+
for (const s of r.sources) out.push(`- ${s.url ? `[${s.name}](${s.url})` : s.name}: ${s.note}`);
|
|
6917
|
+
out.push("");
|
|
6918
|
+
}
|
|
6919
|
+
out.push(`**Always:** ${ALWAYS}`);
|
|
6920
|
+
return out.join("\n");
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
// src/register/check.ts
|
|
6924
|
+
var REGISTER_BASELINE = {
|
|
6925
|
+
plain: {
|
|
6926
|
+
baseline: "institutional",
|
|
6927
|
+
note: "Compared with Hansard: edited parliamentary debate, the closest free UK source, not plain writing itself."
|
|
6928
|
+
},
|
|
6929
|
+
persuasive: {
|
|
6930
|
+
baseline: "marketing",
|
|
6931
|
+
note: "Compared with 715 passages from UK small-business websites before 2021: a small sample."
|
|
6932
|
+
},
|
|
6933
|
+
warm: {
|
|
6934
|
+
baseline: "business-email",
|
|
6935
|
+
note: "Compared with Enron sent email, 1999 to 2002: corporate and American, not small-business email."
|
|
6936
|
+
},
|
|
6937
|
+
literary: {
|
|
6938
|
+
baseline: "literary",
|
|
6939
|
+
note: "Compared with English fiction and essays, 1726 to 1925: older than any premium brand's copy."
|
|
6940
|
+
}
|
|
6941
|
+
};
|
|
6942
|
+
var MIN_PARAGRAPHS = 5;
|
|
6943
|
+
var CONTENT_FEATURES = /* @__PURE__ */ new Set(["hardPer100", "specificsPer100", "tradePer100", "figures", "names", "generic"]);
|
|
6944
|
+
var usable = (f4, b) => !CONTENT_FEATURES.has(f4) && b.p90 > b.p10;
|
|
6945
|
+
var LABELS = {
|
|
6946
|
+
meanSentenceWords: "Words per sentence",
|
|
6947
|
+
contractionsPer100: "Contractions per 100 words",
|
|
6948
|
+
youPer100: '"You" per 100 words',
|
|
6949
|
+
firstPersonPer100: '"I" and "we" per 100 words',
|
|
6950
|
+
passiveShare: "Share of sentences in the passive",
|
|
6951
|
+
meanWordLength: "Letters per word",
|
|
6952
|
+
commasPer100: "Commas per 100 words",
|
|
6953
|
+
commasPerSentence: "Commas per sentence",
|
|
6954
|
+
clauseDepth: "Clauses per sentence",
|
|
6955
|
+
nominalisationRate: 'Nominalisations ("the provision of") per 100 words',
|
|
6956
|
+
pronounOpenerShare: "Share of sentences opening with a pronoun",
|
|
6957
|
+
impersonalOpenerShare: 'Share of sentences opening impersonally ("It is", "There are")',
|
|
6958
|
+
coordinationRate: 'Paired phrases ("fast and friendly") per 100 words',
|
|
6959
|
+
participialRate: '"-ing" clauses per sentence',
|
|
6960
|
+
tricolons: "Lists of three",
|
|
6961
|
+
sentenceCv: "Spread of sentence lengths",
|
|
6962
|
+
skeletonRepeat: "Repeated sentence patterns",
|
|
6963
|
+
hardPer100: "Hard specifics (names, figures, dates) per 100 words",
|
|
6964
|
+
specificsPer100: "Specifics of any kind per 100 words",
|
|
6965
|
+
tradePer100: "Trade nouns per 100 words",
|
|
6966
|
+
figures: "Figures per paragraph",
|
|
6967
|
+
names: "Names per paragraph",
|
|
6968
|
+
generic: "Share of paragraphs with no specific at all"
|
|
6969
|
+
};
|
|
6970
|
+
var featureLabel = (f4) => LABELS[f4] ?? f4;
|
|
6971
|
+
function documentParagraphs(text) {
|
|
6972
|
+
return text.replace(/\r\n?/g, "\n").split(/\n\s*\n/).map((p) => p.replace(/\s*\n\s*/g, " ").trim()).filter((p) => p && !/^#{1,6}\s/.test(p));
|
|
6973
|
+
}
|
|
6974
|
+
function measure(paragraph) {
|
|
6975
|
+
const out = {};
|
|
6976
|
+
for (const [k, v] of Object.entries(shapeOf(paragraph))) out[k] = typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
6977
|
+
for (const [k, v] of Object.entries(lexicalOf(paragraph))) out[k] = v;
|
|
6978
|
+
for (const [k, v] of Object.entries(registerOf(paragraph))) out[k] = v;
|
|
6979
|
+
return out;
|
|
6980
|
+
}
|
|
6981
|
+
var fmt = (x) => Math.abs(x) >= 10 ? x.toFixed(0) : x.toFixed(Math.abs(x) >= 1 ? 1 : 2);
|
|
6982
|
+
function checkRegister(text, id, profile) {
|
|
6983
|
+
const reg = getRegister(id);
|
|
6984
|
+
if (!reg) throw new Error(`unknown register "${id}"`);
|
|
6985
|
+
const { baseline, note } = REGISTER_BASELINE[id];
|
|
6986
|
+
const target2 = profile[baseline];
|
|
6987
|
+
if (!target2) throw new Error(`no baseline profile for "${baseline}"`);
|
|
6988
|
+
const kept = documentParagraphs(text).filter((p) => isBlurb(shapeOf(p)));
|
|
6989
|
+
const base = { register: id, baseline, paragraphs: kept.length, note };
|
|
6990
|
+
if (kept.length < MIN_PARAGRAPHS) {
|
|
6991
|
+
return {
|
|
6992
|
+
...base,
|
|
6993
|
+
measured: false,
|
|
6994
|
+
means: {},
|
|
6995
|
+
outside: [],
|
|
6996
|
+
nearest: [],
|
|
6997
|
+
summary: `Only ${kept.length} paragraph${kept.length === 1 ? "" : "s"} of 2 to 8 sentences. The registers overlap too much to place anything shorter than ${MIN_PARAGRAPHS}; read it against the brief instead.`
|
|
6998
|
+
};
|
|
6999
|
+
}
|
|
7000
|
+
const rows = kept.map(measure);
|
|
7001
|
+
const features = [...new Set(Object.values(profile).flatMap((p) => Object.keys(p)))];
|
|
7002
|
+
const means = {};
|
|
7003
|
+
for (const f4 of features) {
|
|
7004
|
+
const xs = rows.map((r) => r[f4]).filter((x) => typeof x === "number" && Number.isFinite(x));
|
|
7005
|
+
if (xs.length) means[f4] = xs.reduce((s, x) => s + x, 0) / xs.length;
|
|
7006
|
+
}
|
|
7007
|
+
const outside = [];
|
|
7008
|
+
for (const [f4, b] of Object.entries(target2)) {
|
|
7009
|
+
const v = means[f4];
|
|
7010
|
+
if (v === void 0 || b.separates.length === 0 || !usable(f4, b)) continue;
|
|
7011
|
+
if (v >= b.p10 && v <= b.p90) continue;
|
|
7012
|
+
const direction = v > b.p90 ? "higher" : "lower";
|
|
7013
|
+
outside.push({
|
|
7014
|
+
feature: f4,
|
|
7015
|
+
value: v,
|
|
7016
|
+
band: [b.p10, b.p90],
|
|
7017
|
+
direction,
|
|
7018
|
+
sentence: `${featureLabel(f4)}: ${fmt(v)}. ${reg.name} writing in the baseline runs ${fmt(b.p10)} to ${fmt(b.p90)}, so this is ${direction}.`
|
|
7019
|
+
});
|
|
7020
|
+
}
|
|
7021
|
+
const nearest2 = Object.entries(profile).map(([register, bands]) => {
|
|
7022
|
+
const zs = Object.entries(bands).filter(([f4, b]) => means[f4] !== void 0 && b.sd > 0 && usable(f4, b)).map(([f4, b]) => ((means[f4] - b.p50) / b.sd) ** 2);
|
|
7023
|
+
return { register, distance: zs.length ? Math.sqrt(zs.reduce((s, z) => s + z, 0) / zs.length) : Infinity };
|
|
7024
|
+
}).filter((n) => Number.isFinite(n.distance)).sort((a, b) => a.distance - b.distance);
|
|
7025
|
+
const closest = nearest2[0]?.register;
|
|
7026
|
+
const summary = (outside.length === 0 ? `All ${Object.entries(target2).filter(([f4, b]) => b.separates.length && usable(f4, b)).length} measured features sit inside the usual range for ${reg.name.toLowerCase()} writing.` : `${outside.length} feature${outside.length === 1 ? "" : "s"} outside the usual range for ${reg.name.toLowerCase()} writing.`) + (closest && closest !== baseline ? ` It reads closest to the ${closest} baseline.` : "");
|
|
7027
|
+
return { ...base, measured: true, means, outside, nearest: nearest2, summary };
|
|
7028
|
+
}
|
|
7029
|
+
|
|
7030
|
+
// src/register/profile.ts
|
|
7031
|
+
var PROFILE_PARAGRAPHS = 5;
|
|
7032
|
+
var PROFILE_MEASURED = "2026-09-30";
|
|
7033
|
+
var PROFILE = {
|
|
7034
|
+
"business-email": {
|
|
7035
|
+
"clauseDepth": {
|
|
7036
|
+
"p10": 0.36,
|
|
7037
|
+
"p50": 0.673,
|
|
7038
|
+
"p90": 1.113,
|
|
7039
|
+
"sd": 0.307,
|
|
7040
|
+
"separates": [
|
|
7041
|
+
"explanatory",
|
|
7042
|
+
"institutional",
|
|
7043
|
+
"literary"
|
|
7044
|
+
]
|
|
7045
|
+
},
|
|
7046
|
+
"commasPer100": {
|
|
7047
|
+
"p10": 1.424,
|
|
7048
|
+
"p50": 3.19,
|
|
7049
|
+
"p90": 5.154,
|
|
7050
|
+
"sd": 1.529,
|
|
7051
|
+
"separates": [
|
|
7052
|
+
"institutional",
|
|
7053
|
+
"literary"
|
|
7054
|
+
]
|
|
7055
|
+
},
|
|
7056
|
+
"commasPerSentence": {
|
|
7057
|
+
"p10": 0.2,
|
|
7058
|
+
"p50": 0.467,
|
|
7059
|
+
"p90": 0.826,
|
|
7060
|
+
"sd": 0.262,
|
|
7061
|
+
"separates": [
|
|
7062
|
+
"explanatory",
|
|
7063
|
+
"institutional",
|
|
7064
|
+
"literary",
|
|
7065
|
+
"marketing"
|
|
7066
|
+
]
|
|
7067
|
+
},
|
|
7068
|
+
"coordinationRate": {
|
|
7069
|
+
"p10": 0,
|
|
7070
|
+
"p50": 0.743,
|
|
7071
|
+
"p90": 1.712,
|
|
7072
|
+
"sd": 0.666,
|
|
7073
|
+
"separates": [
|
|
7074
|
+
"marketing"
|
|
7075
|
+
]
|
|
7076
|
+
},
|
|
7077
|
+
"figures": {
|
|
7078
|
+
"p10": 0.2,
|
|
7079
|
+
"p50": 0.8,
|
|
7080
|
+
"p90": 1.8,
|
|
7081
|
+
"sd": 0.725,
|
|
7082
|
+
"separates": [
|
|
7083
|
+
"literary"
|
|
7084
|
+
]
|
|
7085
|
+
},
|
|
7086
|
+
"firstPersonPer100": {
|
|
7087
|
+
"p10": 3.718,
|
|
7088
|
+
"p50": 5.968,
|
|
7089
|
+
"p90": 8.375,
|
|
7090
|
+
"sd": 1.825,
|
|
7091
|
+
"separates": [
|
|
7092
|
+
"explanatory"
|
|
7093
|
+
]
|
|
7094
|
+
},
|
|
7095
|
+
"generic": {
|
|
7096
|
+
"p10": 0,
|
|
7097
|
+
"p50": 0,
|
|
7098
|
+
"p90": 0.2,
|
|
7099
|
+
"sd": 0.138,
|
|
7100
|
+
"separates": [
|
|
7101
|
+
"explanatory"
|
|
7102
|
+
]
|
|
7103
|
+
},
|
|
7104
|
+
"hardPer100": {
|
|
7105
|
+
"p10": 4.289,
|
|
7106
|
+
"p50": 7.127,
|
|
7107
|
+
"p90": 11.308,
|
|
7108
|
+
"sd": 2.843,
|
|
7109
|
+
"separates": [
|
|
7110
|
+
"explanatory",
|
|
7111
|
+
"institutional",
|
|
7112
|
+
"literary"
|
|
7113
|
+
]
|
|
7114
|
+
},
|
|
7115
|
+
"impersonalOpenerShare": {
|
|
7116
|
+
"p10": 0,
|
|
7117
|
+
"p50": 0,
|
|
7118
|
+
"p90": 0.067,
|
|
7119
|
+
"sd": 0.031,
|
|
7120
|
+
"separates": [
|
|
7121
|
+
"explanatory"
|
|
7122
|
+
]
|
|
7123
|
+
},
|
|
7124
|
+
"meanSentenceWords": {
|
|
7125
|
+
"p10": 11.417,
|
|
7126
|
+
"p50": 14.25,
|
|
7127
|
+
"p90": 17.87,
|
|
7128
|
+
"sd": 2.656,
|
|
7129
|
+
"separates": [
|
|
7130
|
+
"explanatory",
|
|
7131
|
+
"literary"
|
|
7132
|
+
]
|
|
7133
|
+
},
|
|
7134
|
+
"meanWordLength": {
|
|
7135
|
+
"p10": 3.858,
|
|
7136
|
+
"p50": 4.16,
|
|
7137
|
+
"p90": 4.459,
|
|
7138
|
+
"sd": 0.231,
|
|
7139
|
+
"separates": [
|
|
7140
|
+
"explanatory"
|
|
7141
|
+
]
|
|
7142
|
+
},
|
|
7143
|
+
"names": {
|
|
7144
|
+
"p10": 1.2,
|
|
7145
|
+
"p50": 2.4,
|
|
7146
|
+
"p90": 3.6,
|
|
7147
|
+
"sd": 0.965,
|
|
7148
|
+
"separates": [
|
|
7149
|
+
"explanatory",
|
|
7150
|
+
"institutional"
|
|
7151
|
+
]
|
|
7152
|
+
},
|
|
7153
|
+
"nominalisationRate": {
|
|
7154
|
+
"p10": 0.625,
|
|
7155
|
+
"p50": 1.93,
|
|
7156
|
+
"p90": 3.831,
|
|
7157
|
+
"sd": 1.264,
|
|
7158
|
+
"separates": [
|
|
7159
|
+
"institutional",
|
|
7160
|
+
"marketing"
|
|
7161
|
+
]
|
|
7162
|
+
},
|
|
7163
|
+
"participialRate": {
|
|
7164
|
+
"p10": 0,
|
|
7165
|
+
"p50": 0,
|
|
7166
|
+
"p90": 0.05,
|
|
7167
|
+
"sd": 0.035,
|
|
7168
|
+
"separates": [
|
|
7169
|
+
"literary"
|
|
7170
|
+
]
|
|
7171
|
+
},
|
|
7172
|
+
"pronounOpenerShare": {
|
|
7173
|
+
"p10": 0,
|
|
7174
|
+
"p50": 0.05,
|
|
7175
|
+
"p90": 0.15,
|
|
7176
|
+
"sd": 0.068,
|
|
7177
|
+
"separates": [
|
|
7178
|
+
"marketing"
|
|
7179
|
+
]
|
|
7180
|
+
},
|
|
7181
|
+
"sentenceCv": {
|
|
7182
|
+
"p10": 0.344,
|
|
7183
|
+
"p50": 0.468,
|
|
7184
|
+
"p90": 0.6,
|
|
7185
|
+
"sd": 0.099,
|
|
7186
|
+
"separates": [
|
|
7187
|
+
"marketing"
|
|
7188
|
+
]
|
|
7189
|
+
},
|
|
7190
|
+
"skeletonRepeat": {
|
|
7191
|
+
"p10": 0,
|
|
7192
|
+
"p50": 0.017,
|
|
7193
|
+
"p90": 0.063,
|
|
7194
|
+
"sd": 0.031,
|
|
7195
|
+
"separates": [
|
|
7196
|
+
"institutional"
|
|
7197
|
+
]
|
|
7198
|
+
},
|
|
7199
|
+
"specificsPer100": {
|
|
7200
|
+
"p10": 4.22,
|
|
7201
|
+
"p50": 7.34,
|
|
7202
|
+
"p90": 11.46,
|
|
7203
|
+
"sd": 2.892,
|
|
7204
|
+
"separates": [
|
|
7205
|
+
"explanatory",
|
|
7206
|
+
"institutional",
|
|
7207
|
+
"literary"
|
|
7208
|
+
]
|
|
7209
|
+
},
|
|
7210
|
+
"tradePer100": {
|
|
7211
|
+
"p10": 0,
|
|
7212
|
+
"p50": 0,
|
|
7213
|
+
"p90": 0.588,
|
|
7214
|
+
"sd": 0.287,
|
|
7215
|
+
"separates": [
|
|
7216
|
+
"marketing"
|
|
7217
|
+
]
|
|
7218
|
+
},
|
|
7219
|
+
"tricolons": {
|
|
7220
|
+
"p10": 0,
|
|
7221
|
+
"p50": 0,
|
|
7222
|
+
"p90": 0.2,
|
|
7223
|
+
"sd": 0.142,
|
|
7224
|
+
"separates": [
|
|
7225
|
+
"institutional",
|
|
7226
|
+
"literary",
|
|
7227
|
+
"marketing"
|
|
7228
|
+
]
|
|
7229
|
+
}
|
|
7230
|
+
},
|
|
7231
|
+
"explanatory": {
|
|
7232
|
+
"clauseDepth": {
|
|
7233
|
+
"p10": 0.78,
|
|
7234
|
+
"p50": 1.194,
|
|
7235
|
+
"p90": 1.733,
|
|
7236
|
+
"sd": 0.383,
|
|
7237
|
+
"separates": [
|
|
7238
|
+
"business-email",
|
|
7239
|
+
"literary"
|
|
7240
|
+
]
|
|
7241
|
+
},
|
|
7242
|
+
"commasPer100": {
|
|
7243
|
+
"p10": 2.579,
|
|
7244
|
+
"p50": 4.164,
|
|
7245
|
+
"p90": 6.089,
|
|
7246
|
+
"sd": 1.423,
|
|
7247
|
+
"separates": [
|
|
7248
|
+
"literary"
|
|
7249
|
+
]
|
|
7250
|
+
},
|
|
7251
|
+
"commasPerSentence": {
|
|
7252
|
+
"p10": 0.448,
|
|
7253
|
+
"p50": 0.77,
|
|
7254
|
+
"p90": 1.2,
|
|
7255
|
+
"sd": 0.309,
|
|
7256
|
+
"separates": [
|
|
7257
|
+
"business-email",
|
|
7258
|
+
"literary"
|
|
7259
|
+
]
|
|
7260
|
+
},
|
|
7261
|
+
"coordinationRate": {
|
|
7262
|
+
"p10": 0.323,
|
|
7263
|
+
"p50": 0.973,
|
|
7264
|
+
"p90": 1.862,
|
|
7265
|
+
"sd": 0.612,
|
|
7266
|
+
"separates": [
|
|
7267
|
+
"marketing"
|
|
7268
|
+
]
|
|
7269
|
+
},
|
|
7270
|
+
"figures": {
|
|
7271
|
+
"p10": 0,
|
|
7272
|
+
"p50": 0.4,
|
|
7273
|
+
"p90": 1.4,
|
|
7274
|
+
"sd": 0.631,
|
|
7275
|
+
"separates": [
|
|
7276
|
+
"literary"
|
|
7277
|
+
]
|
|
7278
|
+
},
|
|
7279
|
+
"firstPersonPer100": {
|
|
7280
|
+
"p10": 0.27,
|
|
7281
|
+
"p50": 1.282,
|
|
7282
|
+
"p90": 3.114,
|
|
7283
|
+
"sd": 1.181,
|
|
7284
|
+
"separates": [
|
|
7285
|
+
"business-email",
|
|
7286
|
+
"literary"
|
|
7287
|
+
]
|
|
7288
|
+
},
|
|
7289
|
+
"generic": {
|
|
7290
|
+
"p10": 0,
|
|
7291
|
+
"p50": 0.4,
|
|
7292
|
+
"p90": 0.6,
|
|
7293
|
+
"sd": 0.214,
|
|
7294
|
+
"separates": [
|
|
7295
|
+
"business-email",
|
|
7296
|
+
"institutional"
|
|
7297
|
+
]
|
|
7298
|
+
},
|
|
7299
|
+
"hardPer100": {
|
|
7300
|
+
"p10": 0.988,
|
|
7301
|
+
"p50": 2.62,
|
|
7302
|
+
"p90": 5.171,
|
|
7303
|
+
"sd": 1.713,
|
|
7304
|
+
"separates": [
|
|
7305
|
+
"business-email",
|
|
7306
|
+
"institutional",
|
|
7307
|
+
"marketing"
|
|
7308
|
+
]
|
|
7309
|
+
},
|
|
7310
|
+
"impersonalOpenerShare": {
|
|
7311
|
+
"p10": 0,
|
|
7312
|
+
"p50": 0.04,
|
|
7313
|
+
"p90": 0.14,
|
|
7314
|
+
"sd": 0.061,
|
|
7315
|
+
"separates": [
|
|
7316
|
+
"business-email"
|
|
7317
|
+
]
|
|
7318
|
+
},
|
|
7319
|
+
"meanSentenceWords": {
|
|
7320
|
+
"p10": 14.973,
|
|
7321
|
+
"p50": 18.31,
|
|
7322
|
+
"p90": 22.533,
|
|
7323
|
+
"sd": 3.013,
|
|
7324
|
+
"separates": [
|
|
7325
|
+
"business-email"
|
|
7326
|
+
]
|
|
7327
|
+
},
|
|
7328
|
+
"meanWordLength": {
|
|
7329
|
+
"p10": 4.207,
|
|
7330
|
+
"p50": 4.466,
|
|
7331
|
+
"p90": 4.752,
|
|
7332
|
+
"sd": 0.213,
|
|
7333
|
+
"separates": [
|
|
7334
|
+
"business-email",
|
|
7335
|
+
"literary"
|
|
7336
|
+
]
|
|
7337
|
+
},
|
|
7338
|
+
"names": {
|
|
7339
|
+
"p10": 0.4,
|
|
7340
|
+
"p50": 1.2,
|
|
7341
|
+
"p90": 2.6,
|
|
7342
|
+
"sd": 0.941,
|
|
7343
|
+
"separates": [
|
|
7344
|
+
"business-email",
|
|
7345
|
+
"institutional"
|
|
7346
|
+
]
|
|
7347
|
+
},
|
|
7348
|
+
"nominalisationRate": {
|
|
7349
|
+
"p10": 0.766,
|
|
7350
|
+
"p50": 1.823,
|
|
7351
|
+
"p90": 3.416,
|
|
7352
|
+
"sd": 1.061,
|
|
7353
|
+
"separates": [
|
|
7354
|
+
"institutional",
|
|
7355
|
+
"marketing"
|
|
7356
|
+
]
|
|
7357
|
+
},
|
|
7358
|
+
"participialRate": {
|
|
7359
|
+
"p10": 0,
|
|
7360
|
+
"p50": 0,
|
|
7361
|
+
"p90": 0.1,
|
|
7362
|
+
"sd": 0.045,
|
|
7363
|
+
"separates": []
|
|
7364
|
+
},
|
|
7365
|
+
"pronounOpenerShare": {
|
|
7366
|
+
"p10": 0,
|
|
7367
|
+
"p50": 0.033,
|
|
7368
|
+
"p90": 0.124,
|
|
7369
|
+
"sd": 0.056,
|
|
7370
|
+
"separates": [
|
|
7371
|
+
"marketing"
|
|
7372
|
+
]
|
|
7373
|
+
},
|
|
7374
|
+
"sentenceCv": {
|
|
7375
|
+
"p10": 0.337,
|
|
7376
|
+
"p50": 0.441,
|
|
7377
|
+
"p90": 0.57,
|
|
7378
|
+
"sd": 0.092,
|
|
7379
|
+
"separates": []
|
|
7380
|
+
},
|
|
7381
|
+
"skeletonRepeat": {
|
|
7382
|
+
"p10": 9e-3,
|
|
7383
|
+
"p50": 0.039,
|
|
7384
|
+
"p90": 0.091,
|
|
7385
|
+
"sd": 0.036,
|
|
7386
|
+
"separates": []
|
|
7387
|
+
},
|
|
7388
|
+
"specificsPer100": {
|
|
7389
|
+
"p10": 1.04,
|
|
7390
|
+
"p50": 2.7,
|
|
7391
|
+
"p90": 5.36,
|
|
7392
|
+
"sd": 1.731,
|
|
7393
|
+
"separates": [
|
|
7394
|
+
"business-email",
|
|
7395
|
+
"institutional",
|
|
7396
|
+
"marketing"
|
|
7397
|
+
]
|
|
7398
|
+
},
|
|
7399
|
+
"tradePer100": {
|
|
7400
|
+
"p10": 0,
|
|
7401
|
+
"p50": 0,
|
|
7402
|
+
"p90": 0.427,
|
|
7403
|
+
"sd": 0.216,
|
|
7404
|
+
"separates": [
|
|
7405
|
+
"marketing"
|
|
7406
|
+
]
|
|
7407
|
+
},
|
|
7408
|
+
"tricolons": {
|
|
7409
|
+
"p10": 0,
|
|
7410
|
+
"p50": 0.2,
|
|
7411
|
+
"p90": 0.6,
|
|
7412
|
+
"sd": 0.222,
|
|
7413
|
+
"separates": []
|
|
7414
|
+
}
|
|
7415
|
+
},
|
|
7416
|
+
"institutional": {
|
|
7417
|
+
"clauseDepth": {
|
|
7418
|
+
"p10": 0.792,
|
|
7419
|
+
"p50": 1.233,
|
|
7420
|
+
"p90": 1.76,
|
|
7421
|
+
"sd": 0.377,
|
|
7422
|
+
"separates": [
|
|
7423
|
+
"business-email",
|
|
7424
|
+
"literary"
|
|
7425
|
+
]
|
|
7426
|
+
},
|
|
7427
|
+
"commasPer100": {
|
|
7428
|
+
"p10": 3.03,
|
|
7429
|
+
"p50": 4.435,
|
|
7430
|
+
"p90": 5.941,
|
|
7431
|
+
"sd": 1.165,
|
|
7432
|
+
"separates": [
|
|
7433
|
+
"business-email",
|
|
7434
|
+
"literary"
|
|
7435
|
+
]
|
|
7436
|
+
},
|
|
7437
|
+
"commasPerSentence": {
|
|
7438
|
+
"p10": 0.61,
|
|
7439
|
+
"p50": 0.96,
|
|
7440
|
+
"p90": 1.375,
|
|
7441
|
+
"sd": 0.307,
|
|
7442
|
+
"separates": [
|
|
7443
|
+
"business-email",
|
|
7444
|
+
"literary"
|
|
7445
|
+
]
|
|
7446
|
+
},
|
|
7447
|
+
"coordinationRate": {
|
|
7448
|
+
"p10": 0.323,
|
|
7449
|
+
"p50": 0.949,
|
|
7450
|
+
"p90": 1.752,
|
|
7451
|
+
"sd": 0.559,
|
|
7452
|
+
"separates": [
|
|
7453
|
+
"marketing"
|
|
7454
|
+
]
|
|
7455
|
+
},
|
|
7456
|
+
"figures": {
|
|
7457
|
+
"p10": 0,
|
|
7458
|
+
"p50": 0.6,
|
|
7459
|
+
"p90": 1.4,
|
|
7460
|
+
"sd": 0.56,
|
|
7461
|
+
"separates": [
|
|
7462
|
+
"literary"
|
|
7463
|
+
]
|
|
7464
|
+
},
|
|
7465
|
+
"generic": {
|
|
7466
|
+
"p10": 0,
|
|
7467
|
+
"p50": 0,
|
|
7468
|
+
"p90": 0.2,
|
|
7469
|
+
"sd": 0.114,
|
|
7470
|
+
"separates": [
|
|
7471
|
+
"explanatory",
|
|
7472
|
+
"literary",
|
|
7473
|
+
"marketing"
|
|
7474
|
+
]
|
|
7475
|
+
},
|
|
7476
|
+
"hardPer100": {
|
|
7477
|
+
"p10": 3.41,
|
|
7478
|
+
"p50": 5.135,
|
|
7479
|
+
"p90": 7.304,
|
|
7480
|
+
"sd": 1.573,
|
|
7481
|
+
"separates": [
|
|
7482
|
+
"business-email",
|
|
7483
|
+
"explanatory",
|
|
7484
|
+
"literary"
|
|
7485
|
+
]
|
|
7486
|
+
},
|
|
7487
|
+
"impersonalOpenerShare": {
|
|
7488
|
+
"p10": 0,
|
|
7489
|
+
"p50": 0.033,
|
|
7490
|
+
"p90": 0.109,
|
|
7491
|
+
"sd": 0.051,
|
|
7492
|
+
"separates": []
|
|
7493
|
+
},
|
|
7494
|
+
"names": {
|
|
7495
|
+
"p10": 2,
|
|
7496
|
+
"p50": 3.2,
|
|
7497
|
+
"p90": 4.8,
|
|
7498
|
+
"sd": 1.113,
|
|
7499
|
+
"separates": [
|
|
7500
|
+
"business-email",
|
|
7501
|
+
"explanatory",
|
|
7502
|
+
"literary",
|
|
7503
|
+
"marketing"
|
|
7504
|
+
]
|
|
7505
|
+
},
|
|
7506
|
+
"nominalisationRate": {
|
|
7507
|
+
"p10": 1.992,
|
|
7508
|
+
"p50": 3.391,
|
|
7509
|
+
"p90": 5.008,
|
|
7510
|
+
"sd": 1.147,
|
|
7511
|
+
"separates": [
|
|
7512
|
+
"business-email",
|
|
7513
|
+
"explanatory",
|
|
7514
|
+
"literary"
|
|
7515
|
+
]
|
|
7516
|
+
},
|
|
7517
|
+
"participialRate": {
|
|
7518
|
+
"p10": 0,
|
|
7519
|
+
"p50": 0,
|
|
7520
|
+
"p90": 0.1,
|
|
7521
|
+
"sd": 0.044,
|
|
7522
|
+
"separates": [
|
|
7523
|
+
"literary"
|
|
7524
|
+
]
|
|
7525
|
+
},
|
|
7526
|
+
"pronounOpenerShare": {
|
|
7527
|
+
"p10": 0,
|
|
7528
|
+
"p50": 0.05,
|
|
7529
|
+
"p90": 0.164,
|
|
7530
|
+
"sd": 0.068,
|
|
7531
|
+
"separates": [
|
|
7532
|
+
"marketing"
|
|
7533
|
+
]
|
|
7534
|
+
},
|
|
7535
|
+
"sentenceCv": {
|
|
7536
|
+
"p10": 0.386,
|
|
7537
|
+
"p50": 0.512,
|
|
7538
|
+
"p90": 0.639,
|
|
7539
|
+
"sd": 0.097,
|
|
7540
|
+
"separates": [
|
|
7541
|
+
"marketing"
|
|
7542
|
+
]
|
|
7543
|
+
},
|
|
7544
|
+
"skeletonRepeat": {
|
|
7545
|
+
"p10": 0.018,
|
|
7546
|
+
"p50": 0.048,
|
|
7547
|
+
"p90": 0.087,
|
|
7548
|
+
"sd": 0.028,
|
|
7549
|
+
"separates": [
|
|
7550
|
+
"business-email"
|
|
7551
|
+
]
|
|
7552
|
+
},
|
|
7553
|
+
"specificsPer100": {
|
|
7554
|
+
"p10": 3.36,
|
|
7555
|
+
"p50": 5.2,
|
|
7556
|
+
"p90": 7.4,
|
|
7557
|
+
"sd": 1.612,
|
|
7558
|
+
"separates": [
|
|
7559
|
+
"business-email",
|
|
7560
|
+
"explanatory",
|
|
7561
|
+
"literary"
|
|
7562
|
+
]
|
|
7563
|
+
},
|
|
7564
|
+
"tradePer100": {
|
|
7565
|
+
"p10": 0,
|
|
7566
|
+
"p50": 0,
|
|
7567
|
+
"p90": 0.157,
|
|
7568
|
+
"sd": 0.101,
|
|
7569
|
+
"separates": [
|
|
7570
|
+
"marketing"
|
|
7571
|
+
]
|
|
7572
|
+
},
|
|
7573
|
+
"tricolons": {
|
|
7574
|
+
"p10": 0,
|
|
7575
|
+
"p50": 0.2,
|
|
7576
|
+
"p90": 0.6,
|
|
7577
|
+
"sd": 0.233,
|
|
7578
|
+
"separates": [
|
|
7579
|
+
"business-email"
|
|
7580
|
+
]
|
|
7581
|
+
}
|
|
7582
|
+
},
|
|
7583
|
+
"literary": {
|
|
7584
|
+
"clauseDepth": {
|
|
7585
|
+
"p10": 1.333,
|
|
7586
|
+
"p50": 2.017,
|
|
7587
|
+
"p90": 2.933,
|
|
7588
|
+
"sd": 0.626,
|
|
7589
|
+
"separates": [
|
|
7590
|
+
"business-email",
|
|
7591
|
+
"explanatory",
|
|
7592
|
+
"institutional",
|
|
7593
|
+
"marketing"
|
|
7594
|
+
]
|
|
7595
|
+
},
|
|
7596
|
+
"commasPer100": {
|
|
7597
|
+
"p10": 5.997,
|
|
7598
|
+
"p50": 8.1,
|
|
7599
|
+
"p90": 10.586,
|
|
7600
|
+
"sd": 1.815,
|
|
7601
|
+
"separates": [
|
|
7602
|
+
"business-email",
|
|
7603
|
+
"explanatory",
|
|
7604
|
+
"institutional",
|
|
7605
|
+
"marketing"
|
|
7606
|
+
]
|
|
7607
|
+
},
|
|
7608
|
+
"commasPerSentence": {
|
|
7609
|
+
"p10": 1.067,
|
|
7610
|
+
"p50": 1.667,
|
|
7611
|
+
"p90": 2.435,
|
|
7612
|
+
"sd": 0.541,
|
|
7613
|
+
"separates": [
|
|
7614
|
+
"business-email",
|
|
7615
|
+
"explanatory",
|
|
7616
|
+
"institutional",
|
|
7617
|
+
"marketing"
|
|
7618
|
+
]
|
|
7619
|
+
},
|
|
7620
|
+
"coordinationRate": {
|
|
7621
|
+
"p10": 0.194,
|
|
7622
|
+
"p50": 0.814,
|
|
7623
|
+
"p90": 1.633,
|
|
7624
|
+
"sd": 0.572,
|
|
7625
|
+
"separates": [
|
|
7626
|
+
"marketing"
|
|
7627
|
+
]
|
|
7628
|
+
},
|
|
7629
|
+
"figures": {
|
|
7630
|
+
"p10": 0,
|
|
7631
|
+
"p50": 0,
|
|
7632
|
+
"p90": 0,
|
|
7633
|
+
"sd": 0.079,
|
|
7634
|
+
"separates": [
|
|
7635
|
+
"business-email",
|
|
7636
|
+
"explanatory",
|
|
7637
|
+
"institutional",
|
|
7638
|
+
"marketing"
|
|
7639
|
+
]
|
|
7640
|
+
},
|
|
7641
|
+
"firstPersonPer100": {
|
|
7642
|
+
"p10": 2.207,
|
|
7643
|
+
"p50": 4.67,
|
|
7644
|
+
"p90": 7.486,
|
|
7645
|
+
"sd": 2.027,
|
|
7646
|
+
"separates": [
|
|
7647
|
+
"explanatory"
|
|
7648
|
+
]
|
|
7649
|
+
},
|
|
7650
|
+
"generic": {
|
|
7651
|
+
"p10": 0,
|
|
7652
|
+
"p50": 0.2,
|
|
7653
|
+
"p90": 0.4,
|
|
7654
|
+
"sd": 0.178,
|
|
7655
|
+
"separates": [
|
|
7656
|
+
"institutional"
|
|
7657
|
+
]
|
|
7658
|
+
},
|
|
7659
|
+
"hardPer100": {
|
|
7660
|
+
"p10": 1.632,
|
|
7661
|
+
"p50": 3.076,
|
|
7662
|
+
"p90": 5.154,
|
|
7663
|
+
"sd": 1.367,
|
|
7664
|
+
"separates": [
|
|
7665
|
+
"business-email",
|
|
7666
|
+
"institutional"
|
|
7667
|
+
]
|
|
7668
|
+
},
|
|
7669
|
+
"impersonalOpenerShare": {
|
|
7670
|
+
"p10": 0,
|
|
7671
|
+
"p50": 0,
|
|
7672
|
+
"p90": 0.067,
|
|
7673
|
+
"sd": 0.039,
|
|
7674
|
+
"separates": []
|
|
7675
|
+
},
|
|
7676
|
+
"meanSentenceWords": {
|
|
7677
|
+
"p10": 15.083,
|
|
7678
|
+
"p50": 20.35,
|
|
7679
|
+
"p90": 27.027,
|
|
7680
|
+
"sd": 4.744,
|
|
7681
|
+
"separates": [
|
|
7682
|
+
"business-email"
|
|
7683
|
+
]
|
|
7684
|
+
},
|
|
7685
|
+
"meanWordLength": {
|
|
7686
|
+
"p10": 3.958,
|
|
7687
|
+
"p50": 4.186,
|
|
7688
|
+
"p90": 4.409,
|
|
7689
|
+
"sd": 0.175,
|
|
7690
|
+
"separates": [
|
|
7691
|
+
"explanatory"
|
|
7692
|
+
]
|
|
7693
|
+
},
|
|
7694
|
+
"names": {
|
|
7695
|
+
"p10": 1,
|
|
7696
|
+
"p50": 1.8,
|
|
7697
|
+
"p90": 3,
|
|
7698
|
+
"sd": 0.816,
|
|
7699
|
+
"separates": [
|
|
7700
|
+
"institutional"
|
|
7701
|
+
]
|
|
7702
|
+
},
|
|
7703
|
+
"nominalisationRate": {
|
|
7704
|
+
"p10": 0.444,
|
|
7705
|
+
"p50": 1.334,
|
|
7706
|
+
"p90": 2.486,
|
|
7707
|
+
"sd": 0.817,
|
|
7708
|
+
"separates": [
|
|
7709
|
+
"institutional",
|
|
7710
|
+
"marketing"
|
|
7711
|
+
]
|
|
7712
|
+
},
|
|
7713
|
+
"participialRate": {
|
|
7714
|
+
"p10": 0,
|
|
7715
|
+
"p50": 0.067,
|
|
7716
|
+
"p90": 0.2,
|
|
7717
|
+
"sd": 0.089,
|
|
7718
|
+
"separates": [
|
|
7719
|
+
"business-email",
|
|
7720
|
+
"institutional"
|
|
7721
|
+
]
|
|
7722
|
+
},
|
|
7723
|
+
"pronounOpenerShare": {
|
|
7724
|
+
"p10": 0,
|
|
7725
|
+
"p50": 0.025,
|
|
7726
|
+
"p90": 0.12,
|
|
7727
|
+
"sd": 0.058,
|
|
7728
|
+
"separates": [
|
|
7729
|
+
"marketing"
|
|
7730
|
+
]
|
|
7731
|
+
},
|
|
7732
|
+
"sentenceCv": {
|
|
7733
|
+
"p10": 0.367,
|
|
7734
|
+
"p50": 0.485,
|
|
7735
|
+
"p90": 0.625,
|
|
7736
|
+
"sd": 0.1,
|
|
7737
|
+
"separates": [
|
|
7738
|
+
"marketing"
|
|
7739
|
+
]
|
|
7740
|
+
},
|
|
7741
|
+
"skeletonRepeat": {
|
|
7742
|
+
"p10": 5e-3,
|
|
7743
|
+
"p50": 0.029,
|
|
7744
|
+
"p90": 0.07,
|
|
7745
|
+
"sd": 0.028,
|
|
7746
|
+
"separates": []
|
|
7747
|
+
},
|
|
7748
|
+
"specificsPer100": {
|
|
7749
|
+
"p10": 1.8,
|
|
7750
|
+
"p50": 3.28,
|
|
7751
|
+
"p90": 5.24,
|
|
7752
|
+
"sd": 1.365,
|
|
7753
|
+
"separates": [
|
|
7754
|
+
"business-email",
|
|
7755
|
+
"institutional",
|
|
7756
|
+
"marketing"
|
|
7757
|
+
]
|
|
7758
|
+
},
|
|
7759
|
+
"tradePer100": {
|
|
7760
|
+
"p10": 0,
|
|
7761
|
+
"p50": 0,
|
|
7762
|
+
"p90": 0.508,
|
|
7763
|
+
"sd": 0.275,
|
|
7764
|
+
"separates": [
|
|
7765
|
+
"marketing"
|
|
7766
|
+
]
|
|
7767
|
+
},
|
|
7768
|
+
"tricolons": {
|
|
7769
|
+
"p10": 0,
|
|
7770
|
+
"p50": 0.4,
|
|
7771
|
+
"p90": 0.8,
|
|
7772
|
+
"sd": 0.314,
|
|
7773
|
+
"separates": [
|
|
7774
|
+
"business-email"
|
|
7775
|
+
]
|
|
7776
|
+
}
|
|
7777
|
+
},
|
|
7778
|
+
"marketing": {
|
|
7779
|
+
"clauseDepth": {
|
|
7780
|
+
"p10": 0.567,
|
|
7781
|
+
"p50": 0.967,
|
|
7782
|
+
"p90": 1.56,
|
|
7783
|
+
"sd": 0.413,
|
|
7784
|
+
"separates": [
|
|
7785
|
+
"literary"
|
|
7786
|
+
]
|
|
7787
|
+
},
|
|
7788
|
+
"commasPer100": {
|
|
7789
|
+
"p10": 2.169,
|
|
7790
|
+
"p50": 4.087,
|
|
7791
|
+
"p90": 6.434,
|
|
7792
|
+
"sd": 1.667,
|
|
7793
|
+
"separates": [
|
|
7794
|
+
"literary"
|
|
7795
|
+
]
|
|
7796
|
+
},
|
|
7797
|
+
"commasPerSentence": {
|
|
7798
|
+
"p10": 0.4,
|
|
7799
|
+
"p50": 0.8,
|
|
7800
|
+
"p90": 1.35,
|
|
7801
|
+
"sd": 0.402,
|
|
7802
|
+
"separates": [
|
|
7803
|
+
"business-email",
|
|
7804
|
+
"literary"
|
|
7805
|
+
]
|
|
7806
|
+
},
|
|
7807
|
+
"coordinationRate": {
|
|
7808
|
+
"p10": 1.087,
|
|
7809
|
+
"p50": 2.236,
|
|
7810
|
+
"p90": 3.473,
|
|
7811
|
+
"sd": 0.931,
|
|
7812
|
+
"separates": [
|
|
7813
|
+
"business-email",
|
|
7814
|
+
"explanatory",
|
|
7815
|
+
"institutional",
|
|
7816
|
+
"literary"
|
|
7817
|
+
]
|
|
7818
|
+
},
|
|
7819
|
+
"figures": {
|
|
7820
|
+
"p10": 0,
|
|
7821
|
+
"p50": 0.4,
|
|
7822
|
+
"p90": 1,
|
|
7823
|
+
"sd": 0.427,
|
|
7824
|
+
"separates": [
|
|
7825
|
+
"literary"
|
|
7826
|
+
]
|
|
7827
|
+
},
|
|
7828
|
+
"generic": {
|
|
7829
|
+
"p10": 0,
|
|
7830
|
+
"p50": 0.2,
|
|
7831
|
+
"p90": 0.6,
|
|
7832
|
+
"sd": 0.206,
|
|
7833
|
+
"separates": [
|
|
7834
|
+
"institutional"
|
|
7835
|
+
]
|
|
7836
|
+
},
|
|
7837
|
+
"hardPer100": {
|
|
7838
|
+
"p10": 1.899,
|
|
7839
|
+
"p50": 4.549,
|
|
7840
|
+
"p90": 8.227,
|
|
7841
|
+
"sd": 2.655,
|
|
7842
|
+
"separates": [
|
|
7843
|
+
"explanatory"
|
|
7844
|
+
]
|
|
7845
|
+
},
|
|
7846
|
+
"impersonalOpenerShare": {
|
|
7847
|
+
"p10": 0,
|
|
7848
|
+
"p50": 0,
|
|
7849
|
+
"p90": 0.1,
|
|
7850
|
+
"sd": 0.047,
|
|
7851
|
+
"separates": []
|
|
7852
|
+
},
|
|
7853
|
+
"names": {
|
|
7854
|
+
"p10": 0.6,
|
|
7855
|
+
"p50": 1.8,
|
|
7856
|
+
"p90": 3.4,
|
|
7857
|
+
"sd": 1.21,
|
|
7858
|
+
"separates": [
|
|
7859
|
+
"institutional"
|
|
7860
|
+
]
|
|
7861
|
+
},
|
|
7862
|
+
"nominalisationRate": {
|
|
7863
|
+
"p10": 1.576,
|
|
7864
|
+
"p50": 3.222,
|
|
7865
|
+
"p90": 5.534,
|
|
7866
|
+
"sd": 1.6,
|
|
7867
|
+
"separates": [
|
|
7868
|
+
"business-email",
|
|
7869
|
+
"explanatory",
|
|
7870
|
+
"literary"
|
|
7871
|
+
]
|
|
7872
|
+
},
|
|
7873
|
+
"participialRate": {
|
|
7874
|
+
"p10": 0,
|
|
7875
|
+
"p50": 0,
|
|
7876
|
+
"p90": 0.133,
|
|
7877
|
+
"sd": 0.062,
|
|
7878
|
+
"separates": []
|
|
7879
|
+
},
|
|
7880
|
+
"pronounOpenerShare": {
|
|
7881
|
+
"p10": 0.067,
|
|
7882
|
+
"p50": 0.213,
|
|
7883
|
+
"p90": 0.4,
|
|
7884
|
+
"sd": 0.136,
|
|
7885
|
+
"separates": [
|
|
7886
|
+
"business-email",
|
|
7887
|
+
"explanatory",
|
|
7888
|
+
"institutional",
|
|
7889
|
+
"literary"
|
|
7890
|
+
]
|
|
7891
|
+
},
|
|
7892
|
+
"sentenceCv": {
|
|
7893
|
+
"p10": 0.275,
|
|
7894
|
+
"p50": 0.377,
|
|
7895
|
+
"p90": 0.52,
|
|
7896
|
+
"sd": 0.096,
|
|
7897
|
+
"separates": [
|
|
7898
|
+
"business-email",
|
|
7899
|
+
"institutional",
|
|
7900
|
+
"literary"
|
|
7901
|
+
]
|
|
7902
|
+
},
|
|
7903
|
+
"skeletonRepeat": {
|
|
7904
|
+
"p10": 0,
|
|
7905
|
+
"p50": 0.031,
|
|
7906
|
+
"p90": 0.081,
|
|
7907
|
+
"sd": 0.038,
|
|
7908
|
+
"separates": []
|
|
7909
|
+
},
|
|
7910
|
+
"specificsPer100": {
|
|
7911
|
+
"p10": 2.54,
|
|
7912
|
+
"p50": 5.2,
|
|
7913
|
+
"p90": 9.18,
|
|
7914
|
+
"sd": 2.733,
|
|
7915
|
+
"separates": [
|
|
7916
|
+
"explanatory",
|
|
7917
|
+
"literary"
|
|
7918
|
+
]
|
|
7919
|
+
},
|
|
7920
|
+
"tradePer100": {
|
|
7921
|
+
"p10": 0,
|
|
7922
|
+
"p50": 0.592,
|
|
7923
|
+
"p90": 1.515,
|
|
7924
|
+
"sd": 0.612,
|
|
7925
|
+
"separates": [
|
|
7926
|
+
"business-email",
|
|
7927
|
+
"explanatory",
|
|
7928
|
+
"institutional",
|
|
7929
|
+
"literary"
|
|
7930
|
+
]
|
|
7931
|
+
},
|
|
7932
|
+
"tricolons": {
|
|
7933
|
+
"p10": 0,
|
|
7934
|
+
"p50": 0.4,
|
|
7935
|
+
"p90": 0.8,
|
|
7936
|
+
"sd": 0.309,
|
|
7937
|
+
"separates": [
|
|
7938
|
+
"business-email"
|
|
7939
|
+
]
|
|
7940
|
+
}
|
|
7941
|
+
}
|
|
7942
|
+
};
|
|
7943
|
+
|
|
6517
7944
|
// src/character/baseline.ts
|
|
6518
7945
|
var BASELINE_VERSION = 1;
|
|
6519
7946
|
function fnv1a(text) {
|
|
@@ -7821,10 +9248,11 @@ function choiceOfShared(part) {
|
|
|
7821
9248
|
}
|
|
7822
9249
|
|
|
7823
9250
|
// src/calibrate/index.ts
|
|
7824
|
-
var
|
|
9251
|
+
var LABELS2 = ["ai", "ai-looking", "human"];
|
|
7825
9252
|
var FLAGGED_TARGET = 0.9;
|
|
7826
9253
|
var HUMAN_TYPICAL_TARGET = 0.1;
|
|
7827
9254
|
var SURFACE = new Map(CATALOGUE.map((t) => [t.id, t.surface]));
|
|
9255
|
+
var GENERATION = new Map(CATALOGUE.map((t) => [t.id, t.generation]));
|
|
7828
9256
|
function designTells(tells) {
|
|
7829
9257
|
return tells.filter((t) => SURFACE.has(t) && SURFACE.get(t) !== "copy");
|
|
7830
9258
|
}
|
|
@@ -7853,15 +9281,15 @@ function flaggedTarget(target2, s, missing) {
|
|
|
7853
9281
|
}
|
|
7854
9282
|
function calibrate(pages, unmeasured = []) {
|
|
7855
9283
|
const typicality2 = pages.length > 0 && pages.every((p) => p.typicality !== void 0);
|
|
7856
|
-
const labels =
|
|
9284
|
+
const labels = LABELS2.map((l) => summarise(l, pages, typicality2));
|
|
7857
9285
|
const byLabel = Object.fromEntries(labels.map((s) => [s.label, s]));
|
|
7858
9286
|
const missing = (l) => unmeasured.filter((u) => u.label === l).length;
|
|
7859
9287
|
const positives = pages.filter((p) => positive(p.label)).length;
|
|
7860
9288
|
const fired = new Set(pages.flatMap((p) => p.tells));
|
|
7861
9289
|
const tells = [...fired].map((tell) => {
|
|
7862
|
-
const hits = Object.fromEntries(
|
|
9290
|
+
const hits = Object.fromEntries(LABELS2.map((l) => [l, pages.filter((p) => p.label === l && p.tells.includes(tell)).length]));
|
|
7863
9291
|
const on = hits.ai + hits["ai-looking"];
|
|
7864
|
-
return { tell, hits, precision: round2(on / (on + hits.human)), recall: positives ? round2(on / positives) : null };
|
|
9292
|
+
return { tell, generation: GENERATION.get(tell) ?? null, hits, precision: round2(on / (on + hits.human)), recall: positives ? round2(on / positives) : null };
|
|
7865
9293
|
}).sort((a, b) => b.hits.ai + b.hits["ai-looking"] + b.hits.human - (a.hits.ai + a.hits["ai-looking"] + a.hits.human) || a.tell.localeCompare(b.tell));
|
|
7866
9294
|
const human = byLabel.human;
|
|
7867
9295
|
const humanNote = missing("human") ? `; ${missing("human")} not measured, counted neither way` : "";
|
|
@@ -7893,18 +9321,18 @@ function formatCalibration(c, notes = []) {
|
|
|
7893
9321
|
lines.push("", `Not measured: ${c.unmeasured.length}. A page not measured never counts as a pass.`);
|
|
7894
9322
|
for (const u of c.unmeasured) lines.push(` ${u.id} (${u.label}): ${u.reason}`);
|
|
7895
9323
|
}
|
|
7896
|
-
lines.push("", "Targets");
|
|
9324
|
+
lines.push("", "Flagged is a look-closer signal, not a verdict that a site is AI-made. Nothing gates on it.", "", "Targets");
|
|
7897
9325
|
for (const t of c.targets) lines.push(` ${t.target.padEnd(50)} ${t.met === null ? "not known" : t.met ? "met" : "NOT MET"}`, ` ${t.detail}`);
|
|
7898
9326
|
lines.push("", ` ${"label".padEnd(11)} ${"pages".padStart(5)} ${"flagged".padStart(8)} ${"typical".padStart(8)} ${"tell-heavy".padStart(11)} ${"blocking".padStart(9)}`);
|
|
7899
9327
|
for (const s of c.labels) {
|
|
7900
9328
|
lines.push(` ${s.label.padEnd(11)} ${String(s.pages).padStart(5)} ${String(s.flagged).padStart(8)} ${String(s.typical ?? "-").padStart(8)} ${String(s.tellHeavy).padStart(11)} ${String(s.blocking).padStart(9)}`);
|
|
7901
9329
|
}
|
|
7902
9330
|
lines.push("", "Per tell. A hit on ai or ai-looking counts for the tell; a hit on human counts against it.");
|
|
7903
|
-
lines.push(` ${"tell".padEnd(28)} ${"ai".padStart(7)} ${"ai-looking".padStart(10)} ${"human".padStart(7)} ${"precision".padStart(9)} ${"recall".padStart(7)}`);
|
|
9331
|
+
lines.push(` ${"tell".padEnd(28)} ${"gen".padStart(3)} ${"ai".padStart(7)} ${"ai-looking".padStart(10)} ${"human".padStart(7)} ${"precision".padStart(9)} ${"recall".padStart(7)}`);
|
|
7904
9332
|
const of = (l) => c.labels.find((s) => s.label === l)?.pages ?? 0;
|
|
7905
9333
|
for (const t of c.tells) {
|
|
7906
9334
|
lines.push(
|
|
7907
|
-
` ${t.tell.padEnd(28)} ${`${t.hits.ai}/${of("ai")}`.padStart(7)} ${`${t.hits["ai-looking"]}/${of("ai-looking")}`.padStart(10)} ${`${t.hits.human}/${of("human")}`.padStart(7)} ${t.precision.toFixed(2).padStart(9)} ${(t.recall === null ? "-" : t.recall.toFixed(2)).padStart(7)}`
|
|
9335
|
+
` ${t.tell.padEnd(28)} ${String(t.generation ?? "-").padStart(3)} ${`${t.hits.ai}/${of("ai")}`.padStart(7)} ${`${t.hits["ai-looking"]}/${of("ai-looking")}`.padStart(10)} ${`${t.hits.human}/${of("human")}`.padStart(7)} ${t.precision.toFixed(2).padStart(9)} ${(t.recall === null ? "-" : t.recall.toFixed(2)).padStart(7)}`
|
|
7908
9336
|
);
|
|
7909
9337
|
}
|
|
7910
9338
|
if (!c.tells.length) lines.push(" no tell fired");
|
|
@@ -8034,6 +9462,7 @@ function retrofitPlan(report2, direction) {
|
|
|
8034
9462
|
CHOICE_KEYS,
|
|
8035
9463
|
COMPONENT_ROLES,
|
|
8036
9464
|
CONSIDERATION,
|
|
9465
|
+
CONTENT_FEATURES,
|
|
8037
9466
|
CONVENTION,
|
|
8038
9467
|
CUSTOMER_KINDS,
|
|
8039
9468
|
DEFAULT_IGNORE,
|
|
@@ -8069,6 +9498,7 @@ function retrofitPlan(report2, direction) {
|
|
|
8069
9498
|
LICENCES,
|
|
8070
9499
|
LIGHTNESS_CURVE,
|
|
8071
9500
|
MIN_DECIDED,
|
|
9501
|
+
MIN_PARAGRAPHS,
|
|
8072
9502
|
MIN_RUNS,
|
|
8073
9503
|
MOOD,
|
|
8074
9504
|
NEGATIVE_REASSURANCE,
|
|
@@ -8077,6 +9507,9 @@ function retrofitPlan(report2, direction) {
|
|
|
8077
9507
|
PILL_LIMIT,
|
|
8078
9508
|
PLAINER_WORDS,
|
|
8079
9509
|
PREFERENCE,
|
|
9510
|
+
PROFILE,
|
|
9511
|
+
PROFILE_MEASURED,
|
|
9512
|
+
PROFILE_PARAGRAPHS,
|
|
8080
9513
|
PROMO_BADGE,
|
|
8081
9514
|
PROOF_NAMES,
|
|
8082
9515
|
PROPOSED,
|
|
@@ -8085,10 +9518,15 @@ function retrofitPlan(report2, direction) {
|
|
|
8085
9518
|
READABLE_SNAPSHOT_VERSIONS,
|
|
8086
9519
|
REFLEX_FONTS_1,
|
|
8087
9520
|
REFLEX_FONTS_2,
|
|
9521
|
+
REGISTERS,
|
|
9522
|
+
REGISTER_BASELINE,
|
|
9523
|
+
REGISTER_FEATURES,
|
|
9524
|
+
REGISTER_IDS,
|
|
8088
9525
|
RENDERED_PATHS,
|
|
8089
9526
|
RENDERED_TELLS,
|
|
8090
9527
|
REVEAL_LIMIT,
|
|
8091
9528
|
REVIEW_PHRASES,
|
|
9529
|
+
SAMPLES_PER_SEGMENT,
|
|
8092
9530
|
SCALE,
|
|
8093
9531
|
SECTION_ROLES,
|
|
8094
9532
|
SHADCN_LIMIT,
|
|
@@ -8126,7 +9564,9 @@ function retrofitPlan(report2, direction) {
|
|
|
8126
9564
|
checkCopy,
|
|
8127
9565
|
checkOrder,
|
|
8128
9566
|
checkPair,
|
|
9567
|
+
checkPhotoReview,
|
|
8129
9568
|
checkReason,
|
|
9569
|
+
checkRegister,
|
|
8130
9570
|
checkRestraint,
|
|
8131
9571
|
choicesOf,
|
|
8132
9572
|
clauseDepth,
|
|
@@ -8151,6 +9591,7 @@ function retrofitPlan(report2, direction) {
|
|
|
8151
9591
|
densityCss,
|
|
8152
9592
|
densityTokens,
|
|
8153
9593
|
designTells,
|
|
9594
|
+
documentParagraphs,
|
|
8154
9595
|
emitCss,
|
|
8155
9596
|
emptyEstate,
|
|
8156
9597
|
estateComponentPairs,
|
|
@@ -8158,6 +9599,7 @@ function retrofitPlan(report2, direction) {
|
|
|
8158
9599
|
exitDuration,
|
|
8159
9600
|
extractHtml,
|
|
8160
9601
|
faceLicence,
|
|
9602
|
+
featureLabel,
|
|
8161
9603
|
findClaims,
|
|
8162
9604
|
findColours,
|
|
8163
9605
|
findingKey,
|
|
@@ -8174,7 +9616,10 @@ function retrofitPlan(report2, direction) {
|
|
|
8174
9616
|
formatDirectionMatches,
|
|
8175
9617
|
formatHex,
|
|
8176
9618
|
formatReport,
|
|
9619
|
+
getRegister,
|
|
8177
9620
|
globToRegExp,
|
|
9621
|
+
gradientContrast,
|
|
9622
|
+
gradientStops,
|
|
8178
9623
|
groundName,
|
|
8179
9624
|
harvest,
|
|
8180
9625
|
hedgeCount,
|
|
@@ -8220,6 +9665,9 @@ function retrofitPlan(report2, direction) {
|
|
|
8220
9665
|
ramp,
|
|
8221
9666
|
rampFromAnchors,
|
|
8222
9667
|
readSnapshot,
|
|
9668
|
+
registerBrief,
|
|
9669
|
+
registerOf,
|
|
9670
|
+
registersDoc,
|
|
8223
9671
|
renderedOrder,
|
|
8224
9672
|
researchPrompt,
|
|
8225
9673
|
retrofitPlan,
|