@domandigital/craft 0.15.0 → 0.17.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 +18 -0
- package/CHARACTER.md +51 -0
- package/COPY.md +5 -0
- package/README.md +8 -0
- package/REGISTERS.md +163 -0
- package/dist/{audit-MTUC24XU.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-YDURAOYR.js → chunk-6TSDYETQ.js} +4 -2
- package/dist/{chunk-2IQ5OXFR.js → chunk-DARKHE6A.js} +7 -5
- package/dist/{cli-FCKHYAVZ.js → chunk-NTIPJ5U7.js} +2 -185
- package/dist/{chunk-MUVFECFC.js → chunk-O5B5ELJH.js} +46 -21
- package/dist/chunk-QIEDBL4J.js +9 -0
- package/dist/{chunk-QU6Q2QZZ.js → chunk-RJVG5KA6.js} +8 -6
- package/dist/{chunk-OUQ25LY2.js → chunk-SL5VZJ2J.js} +1 -1
- package/dist/{chunk-LFNSB6W7.js → chunk-WFB6F3BN.js} +66 -1633
- package/dist/{chunk-RXXRXV3V.js → chunk-ZUYERAFM.js} +178 -18
- package/dist/{cli-FLG3ZCAF.js → cli-BN4IT6N2.js} +13 -5
- package/dist/{cli-22TSWAX6.js → cli-GXIW7F3E.js} +7 -5
- package/dist/cli-JB4X5LAG.js +1573 -0
- package/dist/{cli-5ACKICBO.js → cli-JZRQBCD4.js} +7 -5
- package/dist/cli-MDW7JTEF.js +246 -0
- package/dist/cli-V44QEVS6.js +193 -0
- package/dist/{cli-7OZCYOQY.js → cli-Y7Y5IROK.js} +8 -5
- package/dist/cli.js +4 -2
- package/dist/index.cjs +1597 -101
- package/dist/index.d.cts +311 -2
- package/dist/index.d.ts +311 -2
- package/dist/index.js +1569 -101
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -757,10 +757,10 @@ function stripLayers(css, ignored) {
|
|
|
757
757
|
const names = new Set(ignored.map((n) => n.trim().toLowerCase()));
|
|
758
758
|
let out = "";
|
|
759
759
|
let i = 0;
|
|
760
|
-
const
|
|
760
|
+
const pattern2 = /@layer\s+([a-zA-Z0-9_-]+)\s*\{/gi;
|
|
761
761
|
while (i < css.length) {
|
|
762
|
-
|
|
763
|
-
const match =
|
|
762
|
+
pattern2.lastIndex = i;
|
|
763
|
+
const match = pattern2.exec(css);
|
|
764
764
|
if (match === null) {
|
|
765
765
|
out += css.slice(i);
|
|
766
766
|
break;
|
|
@@ -790,12 +790,12 @@ var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
790
790
|
]);
|
|
791
791
|
function atRuleBodies(css, prelude) {
|
|
792
792
|
const out = [];
|
|
793
|
-
const
|
|
793
|
+
const pattern2 = new RegExp(
|
|
794
794
|
prelude.source,
|
|
795
795
|
prelude.flags.includes("g") ? prelude.flags : `${prelude.flags}g`
|
|
796
796
|
);
|
|
797
797
|
let match;
|
|
798
|
-
while ((match =
|
|
798
|
+
while ((match = pattern2.exec(css)) !== null) {
|
|
799
799
|
const start = match.index + match[0].length;
|
|
800
800
|
let depth = 1;
|
|
801
801
|
let i = start;
|
|
@@ -805,7 +805,7 @@ function atRuleBodies(css, prelude) {
|
|
|
805
805
|
i += 1;
|
|
806
806
|
}
|
|
807
807
|
out.push(css.slice(start, i - 1));
|
|
808
|
-
|
|
808
|
+
pattern2.lastIndex = i;
|
|
809
809
|
}
|
|
810
810
|
return out;
|
|
811
811
|
}
|
|
@@ -838,9 +838,9 @@ function isVarOnlyList(value) {
|
|
|
838
838
|
return parts2.length > 1 && parts2.every((part) => /^\s*var\(\s*--[\w-]+\s*\)\s*$/.test(part));
|
|
839
839
|
}
|
|
840
840
|
function distinctValues(css, property, props, unresolved) {
|
|
841
|
-
const
|
|
841
|
+
const pattern2 = new RegExp(`(?:^|[;{\\s])${property}\\s*:\\s*([^;}]+)`, "gi");
|
|
842
842
|
const seen = /* @__PURE__ */ new Set();
|
|
843
|
-
for (const match of css.matchAll(
|
|
843
|
+
for (const match of css.matchAll(pattern2)) {
|
|
844
844
|
const raw = match[1].trim().replace(/\s+/g, " ").toLowerCase();
|
|
845
845
|
if (CSS_WIDE_KEYWORDS.has(raw)) continue;
|
|
846
846
|
const value = unwrapShadowColor(raw);
|
|
@@ -1259,14 +1259,14 @@ function linkSpans(text) {
|
|
|
1259
1259
|
return [...text.matchAll(LINK)].map((m) => [m.index ?? 0, (m.index ?? 0) + m[0].length]);
|
|
1260
1260
|
}
|
|
1261
1261
|
var insideLink = (spans, at) => spans.some(([a, b]) => at >= a && at < b);
|
|
1262
|
-
function phrases(
|
|
1262
|
+
function phrases(patterns2, label, source = (ctx) => ctx.blocks, skip = () => false) {
|
|
1263
1263
|
return (ctx) => {
|
|
1264
1264
|
const hits = [];
|
|
1265
1265
|
for (const block of source(ctx)) {
|
|
1266
1266
|
const spans = [];
|
|
1267
1267
|
const links = linkSpans(block.text);
|
|
1268
|
-
for (const
|
|
1269
|
-
const global = new RegExp(
|
|
1268
|
+
for (const pattern2 of patterns2) {
|
|
1269
|
+
const global = new RegExp(pattern2.source, pattern2.flags.includes("g") ? pattern2.flags : `${pattern2.flags}g`);
|
|
1270
1270
|
for (const m of block.text.matchAll(global)) {
|
|
1271
1271
|
if (skip(block, m[0], m.index ?? 0)) continue;
|
|
1272
1272
|
if (insideLink(links, m.index ?? 0)) continue;
|
|
@@ -2653,11 +2653,11 @@ function baseClass(cls) {
|
|
|
2653
2653
|
function bases(unit) {
|
|
2654
2654
|
return unit.classes.map(baseClass);
|
|
2655
2655
|
}
|
|
2656
|
-
function has(unit,
|
|
2657
|
-
return bases(unit).some((c) =>
|
|
2656
|
+
function has(unit, pattern2) {
|
|
2657
|
+
return bases(unit).some((c) => pattern2.test(c));
|
|
2658
2658
|
}
|
|
2659
|
-
function find(unit,
|
|
2660
|
-
return bases(unit).find((c) =>
|
|
2659
|
+
function find(unit, pattern2) {
|
|
2660
|
+
return bases(unit).find((c) => pattern2.test(c));
|
|
2661
2661
|
}
|
|
2662
2662
|
function arbitrary(cls) {
|
|
2663
2663
|
const m = cls.match(/\[(.*)\]$/);
|
|
@@ -2673,9 +2673,9 @@ function eachUnit(ctx, fn) {
|
|
|
2673
2673
|
}
|
|
2674
2674
|
return hits;
|
|
2675
2675
|
}
|
|
2676
|
-
function firstMatch(text,
|
|
2677
|
-
const m =
|
|
2678
|
-
|
|
2676
|
+
function firstMatch(text, pattern2) {
|
|
2677
|
+
const m = pattern2.exec(text);
|
|
2678
|
+
pattern2.lastIndex = 0;
|
|
2679
2679
|
return m ? m.index : -1;
|
|
2680
2680
|
}
|
|
2681
2681
|
var FRAMEWORK_PALETTE = /^--(?:color-)?(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+$|^--tw-/;
|
|
@@ -3119,9 +3119,9 @@ function shadcnCardHits(ctx) {
|
|
|
3119
3119
|
}
|
|
3120
3120
|
function marqueeHits(ctx) {
|
|
3121
3121
|
const hits = [];
|
|
3122
|
-
const
|
|
3122
|
+
const pattern2 = /@keyframes\s+[\w-]*marquee|animate-(?:marquee|scroll-x|infinite-scroll)\b|<Marquee\b|["']react-fast-marquee["']|ui\/marquee["']|animation(?:-name)?\s*:\s*[^;]*marquee/i;
|
|
3123
3123
|
for (const file of ctx.files) {
|
|
3124
|
-
const at = firstMatch(file.text,
|
|
3124
|
+
const at = firstMatch(file.text, pattern2);
|
|
3125
3125
|
if (at !== -1) hits.push({ path: file.path, offset: at, message: "scrolling marquee" });
|
|
3126
3126
|
}
|
|
3127
3127
|
return hits;
|
|
@@ -3963,8 +3963,8 @@ function protectedFacts(text) {
|
|
|
3963
3963
|
const mask = (start, length) => {
|
|
3964
3964
|
rest = rest.slice(0, start) + " ".repeat(length) + rest.slice(start + length);
|
|
3965
3965
|
};
|
|
3966
|
-
for (const [kind,
|
|
3967
|
-
for (const m of [...rest.matchAll(
|
|
3966
|
+
for (const [kind, pattern2] of PATTERNS) {
|
|
3967
|
+
for (const m of [...rest.matchAll(pattern2)]) {
|
|
3968
3968
|
const value = m[0].replace(/[.,;:]+$/, "");
|
|
3969
3969
|
facts.push({ kind, value, key: `${kind}:${normalise(kind, value)}` });
|
|
3970
3970
|
mask(m.index ?? 0, m[0].length);
|
|
@@ -4290,7 +4290,7 @@ function side(page, other, brief) {
|
|
|
4290
4290
|
function compareCompetitor(ours, competitor, brief) {
|
|
4291
4291
|
return { ours: side(ours, competitor, brief), competitor: side(competitor, ours, brief), brief: Boolean(brief) };
|
|
4292
4292
|
}
|
|
4293
|
-
var pctOf = (
|
|
4293
|
+
var pctOf = (share2) => share2 === null ? "no sentences" : `${Math.round(share2 * 100)}%`;
|
|
4294
4294
|
function formatCompetitor(result) {
|
|
4295
4295
|
const row = (label, s) => ` ${label.padEnd(11)} ${s.interchangeable.length} of ${s.sentences} (${pctOf(s.share)}), ${s.perHundred} specifics per 100 words`;
|
|
4296
4296
|
const lines = [
|
|
@@ -4466,7 +4466,7 @@ var SPECIFICITY_COPY_TELLS = [
|
|
|
4466
4466
|
},
|
|
4467
4467
|
rendered: {
|
|
4468
4468
|
detect: (s) => {
|
|
4469
|
-
if (!s.firstScreenText || !HOME_URL.test(s.url)) return [];
|
|
4469
|
+
if (!s.firstScreenText || !(s.home ?? HOME_URL.test(s.url))) return [];
|
|
4470
4470
|
const text = s.firstScreenText.join("\n");
|
|
4471
4471
|
if (!text.trim() || !specificity(text).generic) return [];
|
|
4472
4472
|
const h1 = s.headings.find((h) => h.level === 1 && h.top < s.viewport.height);
|
|
@@ -4666,11 +4666,11 @@ function reflexFonts(list) {
|
|
|
4666
4666
|
for (const font of s.fonts) {
|
|
4667
4667
|
const name = wanted.get(normaliseFamily(font.family).toLowerCase());
|
|
4668
4668
|
if (!name || seen.has(name)) continue;
|
|
4669
|
-
const
|
|
4669
|
+
const share2 = font.chars / total;
|
|
4670
4670
|
const displayShare = font.displayChars / display;
|
|
4671
|
-
if (
|
|
4671
|
+
if (share2 >= 0.15 || displayShare >= 0.3) {
|
|
4672
4672
|
seen.add(name);
|
|
4673
|
-
hits.push(hit(s, `${name} sets ${pct(
|
|
4673
|
+
hits.push(hit(s, `${name} sets ${pct(share2)} of the text on the page${displayShare >= 0.3 ? ` and ${pct(displayShare)} of the headings` : ""}`));
|
|
4674
4674
|
}
|
|
4675
4675
|
}
|
|
4676
4676
|
return hits;
|
|
@@ -5873,8 +5873,8 @@ var IMPERATIVE = /^(?:Add|Use|Check|Make|Get|Set|Include|Create|Update|List|Keep
|
|
|
5873
5873
|
var atStart = (sentence, index) => /^[\s*_"“”’(>#|-]*(?:\d{1,3}[.)]\s*)?[\s*_]*(?:[\w ]+:\**\s*)?$/.test(sentence.slice(0, index)) || /\|\s*\**$/.test(sentence.slice(0, index));
|
|
5874
5874
|
function namedSources(sentence) {
|
|
5875
5875
|
const found = /* @__PURE__ */ new Set();
|
|
5876
|
-
ATTRIBUTION.forEach((
|
|
5877
|
-
for (const m of sentence.matchAll(
|
|
5876
|
+
ATTRIBUTION.forEach((pattern2, i) => {
|
|
5877
|
+
for (const m of sentence.matchAll(pattern2)) {
|
|
5878
5878
|
const raw = m[1].trim();
|
|
5879
5879
|
const word = !/\s/.test(raw) && !/[’']s$/.test(m[0].split(/\s+/)[0]);
|
|
5880
5880
|
if (i === 2 && word && atStart(sentence, m.index ?? 0)) continue;
|
|
@@ -6118,7 +6118,7 @@ function shapeOf(text) {
|
|
|
6118
6118
|
const ss = splitSentences(text);
|
|
6119
6119
|
const wordCount = words(text).length;
|
|
6120
6120
|
const per100 = (n) => wordCount === 0 ? 0 : 100 * n / wordCount;
|
|
6121
|
-
const
|
|
6121
|
+
const share2 = (re) => ss.length === 0 ? 0 : ss.filter((s) => re.test(s)).length / ss.length;
|
|
6122
6122
|
const tri = ss.flatMap(tricolons);
|
|
6123
6123
|
const commas = (text.match(/,/g) ?? []).length;
|
|
6124
6124
|
return {
|
|
@@ -6132,9 +6132,9 @@ function shapeOf(text) {
|
|
|
6132
6132
|
hedgeRate: per100(hedgeCount(text)),
|
|
6133
6133
|
hedgedClose: hedgedClose(ss),
|
|
6134
6134
|
nominalisationRate: per100(nominalisations(text)),
|
|
6135
|
-
pronounOpenerShare:
|
|
6136
|
-
transitionOpenerShare:
|
|
6137
|
-
impersonalOpenerShare:
|
|
6135
|
+
pronounOpenerShare: share2(PRONOUN_OPENER),
|
|
6136
|
+
transitionOpenerShare: share2(TRANSITION_OPENER),
|
|
6137
|
+
impersonalOpenerShare: share2(IMPERSONAL_OPENER),
|
|
6138
6138
|
commasPerSentence: ss.length === 0 ? 0 : commas / ss.length,
|
|
6139
6139
|
commasPer100: per100(commas),
|
|
6140
6140
|
participialRate: ss.length === 0 ? 0 : ss.reduce((s, x) => s + participialClauses(x), 0) / ss.length,
|
|
@@ -6161,6 +6161,1353 @@ function isBlurb(f4) {
|
|
|
6161
6161
|
return f4.sentences >= 2 && f4.sentences <= 8 && f4.words >= 20 && f4.words <= 160;
|
|
6162
6162
|
}
|
|
6163
6163
|
|
|
6164
|
+
// src/character/lexical.ts
|
|
6165
|
+
var JUZEK = "Juzek and Ward, arXiv:2412.11385, Appendix A (PubMed abstracts, 2020 to 2024)";
|
|
6166
|
+
var STUDY_WORDS = [
|
|
6167
|
+
"delves",
|
|
6168
|
+
"delved",
|
|
6169
|
+
"delving",
|
|
6170
|
+
"delve",
|
|
6171
|
+
"showcasing",
|
|
6172
|
+
"boasts",
|
|
6173
|
+
"underscores",
|
|
6174
|
+
"underscoring",
|
|
6175
|
+
"comprehending",
|
|
6176
|
+
"intricacies",
|
|
6177
|
+
"surpassing",
|
|
6178
|
+
"intricate"
|
|
6179
|
+
].map((word) => ({ word, source: JUZEK }));
|
|
6180
|
+
function vocabulary() {
|
|
6181
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6182
|
+
const out = [];
|
|
6183
|
+
const add = (word, source) => {
|
|
6184
|
+
const w = word.toLowerCase();
|
|
6185
|
+
if (seen.has(w)) return;
|
|
6186
|
+
seen.add(w);
|
|
6187
|
+
out.push({ word: w, source });
|
|
6188
|
+
};
|
|
6189
|
+
for (const s of STUDY_WORDS) add(s.word, s.source);
|
|
6190
|
+
for (const w of AI_WORDS) add(w, "AI_WORDS");
|
|
6191
|
+
for (const w of PLAINER_WORDS) add(w, "PLAINER_WORDS");
|
|
6192
|
+
for (const w of AI_PHRASES) add(w, "AI_PHRASES");
|
|
6193
|
+
for (const w of STOCK_PHRASES) add(w, "STOCK_PHRASES");
|
|
6194
|
+
for (const w of BUZZWORDS) add(w, "BUZZWORDS");
|
|
6195
|
+
return out;
|
|
6196
|
+
}
|
|
6197
|
+
var escape4 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6198
|
+
var pattern = (w) => new RegExp(`(?<![\\w-])${escape4(w).replace(/\s+/g, "\\s+")}(?![\\w-])`, "gi");
|
|
6199
|
+
var compiled = null;
|
|
6200
|
+
var patterns = () => compiled ?? (compiled = vocabulary().map((v) => ({ ...v, re: pattern(v.word) })));
|
|
6201
|
+
var plain = (text) => text.replace(/[’‘]/g, "'").replace(/\s+/g, " ");
|
|
6202
|
+
function vocabularyCounts(text) {
|
|
6203
|
+
const t = plain(text);
|
|
6204
|
+
const out = /* @__PURE__ */ new Map();
|
|
6205
|
+
for (const { word, re } of patterns()) {
|
|
6206
|
+
const n = (t.match(re) ?? []).length;
|
|
6207
|
+
if (n) out.set(word, n);
|
|
6208
|
+
}
|
|
6209
|
+
return out;
|
|
6210
|
+
}
|
|
6211
|
+
var LEXICAL_FEATURES = [
|
|
6212
|
+
"listRate",
|
|
6213
|
+
"studyRate",
|
|
6214
|
+
"hardPer100",
|
|
6215
|
+
"tradePer100",
|
|
6216
|
+
"specificsPer100",
|
|
6217
|
+
"generic",
|
|
6218
|
+
"figures",
|
|
6219
|
+
"names"
|
|
6220
|
+
];
|
|
6221
|
+
var STUDY = new Set(STUDY_WORDS.map((s) => s.word));
|
|
6222
|
+
var LISTED = new Set([...AI_WORDS, ...PLAINER_WORDS, ...AI_PHRASES, ...STOCK_PHRASES, ...BUZZWORDS].map((w) => w.toLowerCase()));
|
|
6223
|
+
var r3 = (n) => Math.round(n * 1e3) / 1e3;
|
|
6224
|
+
function lexicalOf(text, brief) {
|
|
6225
|
+
const words3 = Math.max(1, countWords2(text));
|
|
6226
|
+
const counts = vocabularyCounts(text);
|
|
6227
|
+
let list = 0;
|
|
6228
|
+
let study = 0;
|
|
6229
|
+
for (const [w, n] of counts) {
|
|
6230
|
+
if (STUDY.has(w)) study += n;
|
|
6231
|
+
if (LISTED.has(w)) list += n;
|
|
6232
|
+
}
|
|
6233
|
+
const s = specificity(text, brief);
|
|
6234
|
+
const kind = (...k) => s.specifics.filter((x) => k.includes(x.kind)).length;
|
|
6235
|
+
return {
|
|
6236
|
+
listRate: r3(list / words3 * 100),
|
|
6237
|
+
studyRate: r3(study / words3 * 100),
|
|
6238
|
+
hardPer100: r3(s.hard / words3 * 100),
|
|
6239
|
+
tradePer100: r3(s.trade / words3 * 100),
|
|
6240
|
+
specificsPer100: r3(s.perHundred),
|
|
6241
|
+
generic: s.generic ? 1 : 0,
|
|
6242
|
+
figures: kind("money", "date", "time", "number"),
|
|
6243
|
+
names: kind("name", "brief")
|
|
6244
|
+
};
|
|
6245
|
+
}
|
|
6246
|
+
|
|
6247
|
+
// src/register/features.ts
|
|
6248
|
+
var REGISTER_FEATURES = [
|
|
6249
|
+
"meanSentenceWords",
|
|
6250
|
+
"contractionsPer100",
|
|
6251
|
+
"youPer100",
|
|
6252
|
+
"firstPersonPer100",
|
|
6253
|
+
"passiveShare",
|
|
6254
|
+
"meanWordLength"
|
|
6255
|
+
];
|
|
6256
|
+
var norm = (w) => w.toLowerCase().replace(/’/g, "'");
|
|
6257
|
+
var YOU = /* @__PURE__ */ new Set(["you", "your", "yours", "yourself", "yourselves", "you'll", "you're", "you've", "you'd"]);
|
|
6258
|
+
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"]);
|
|
6259
|
+
var CONTRACTION2 = /^[a-z]+(?:n't|'ll|'re|'ve|'d|'m)$|^(?:it|that|there|here|what|who|where|how|he|she|let)'s$/;
|
|
6260
|
+
var BE = "(?:am|is|are|was|were|be|been|being|'s|'re|'m)";
|
|
6261
|
+
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)";
|
|
6262
|
+
var PASSIVE = new RegExp(`\\b${BE}\\s+(?:[a-z]+ly\\s+)?(?:[a-z]+ed|[a-z]+en|${IRREGULAR})\\b`, "i");
|
|
6263
|
+
var ADJECTIVAL = /* @__PURE__ */ new Set([
|
|
6264
|
+
"tired",
|
|
6265
|
+
"interested",
|
|
6266
|
+
"excited",
|
|
6267
|
+
"pleased",
|
|
6268
|
+
"worried",
|
|
6269
|
+
"concerned",
|
|
6270
|
+
"surprised",
|
|
6271
|
+
"bored",
|
|
6272
|
+
"scared",
|
|
6273
|
+
"married",
|
|
6274
|
+
"qualified",
|
|
6275
|
+
"experienced",
|
|
6276
|
+
"based",
|
|
6277
|
+
"located",
|
|
6278
|
+
"committed",
|
|
6279
|
+
"dedicated",
|
|
6280
|
+
"delighted",
|
|
6281
|
+
"disappointed",
|
|
6282
|
+
"involved",
|
|
6283
|
+
"prepared",
|
|
6284
|
+
"supposed",
|
|
6285
|
+
"used",
|
|
6286
|
+
"open",
|
|
6287
|
+
"often",
|
|
6288
|
+
"even",
|
|
6289
|
+
"seven",
|
|
6290
|
+
"eleven",
|
|
6291
|
+
"listen",
|
|
6292
|
+
"happen",
|
|
6293
|
+
"then",
|
|
6294
|
+
"when",
|
|
6295
|
+
"ten",
|
|
6296
|
+
"garden",
|
|
6297
|
+
"kitchen",
|
|
6298
|
+
"children",
|
|
6299
|
+
"women",
|
|
6300
|
+
"men",
|
|
6301
|
+
"red",
|
|
6302
|
+
"bed",
|
|
6303
|
+
"need",
|
|
6304
|
+
"indeed",
|
|
6305
|
+
"shed",
|
|
6306
|
+
"seed",
|
|
6307
|
+
"feed",
|
|
6308
|
+
"speed",
|
|
6309
|
+
"hundred"
|
|
6310
|
+
]);
|
|
6311
|
+
function isPassive(sentence) {
|
|
6312
|
+
const re = new RegExp(PASSIVE.source, "gi");
|
|
6313
|
+
for (const m of sentence.matchAll(re)) {
|
|
6314
|
+
const last = norm(m[0].trim().split(/\s+/).pop() ?? "");
|
|
6315
|
+
if (last === "given" && /given\s+that\b/i.test(sentence.slice(m.index ?? 0))) continue;
|
|
6316
|
+
if (!ADJECTIVAL.has(last)) return true;
|
|
6317
|
+
}
|
|
6318
|
+
return false;
|
|
6319
|
+
}
|
|
6320
|
+
function registerOf(text) {
|
|
6321
|
+
const ss = splitSentences(text);
|
|
6322
|
+
const ws = words(text).map(norm);
|
|
6323
|
+
const n = ws.length;
|
|
6324
|
+
const per100 = (k) => n === 0 ? 0 : 100 * k / n;
|
|
6325
|
+
const letters = ws.reduce((s, w) => s + w.replace(/[^a-z]/g, "").length, 0);
|
|
6326
|
+
return {
|
|
6327
|
+
meanSentenceWords: ss.length === 0 ? 0 : n / ss.length,
|
|
6328
|
+
contractionsPer100: per100(ws.filter((w) => CONTRACTION2.test(w)).length),
|
|
6329
|
+
youPer100: per100(ws.filter((w) => YOU.has(w)).length),
|
|
6330
|
+
firstPersonPer100: per100(ws.filter((w) => FIRST.has(w)).length),
|
|
6331
|
+
passiveShare: ss.length === 0 ? 0 : ss.filter(isPassive).length / ss.length,
|
|
6332
|
+
meanWordLength: n === 0 ? 0 : letters / n
|
|
6333
|
+
};
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
// src/register/registers.ts
|
|
6337
|
+
var GOVUK = "GOV.UK writing guidelines";
|
|
6338
|
+
var MAILCHIMP = "Mailchimp Content Style Guide";
|
|
6339
|
+
var OGILVY = "Ogilvy, Ogilvy on Advertising (1983)";
|
|
6340
|
+
var CAPLES = "Caples, Tested Advertising Methods";
|
|
6341
|
+
var HOUSE2 = "house judgement";
|
|
6342
|
+
var ALWAYS = "Every register keeps the COPY.md blocking tier and the proof rule. Draft first, then run `craft copy <file>` on the draft.";
|
|
6343
|
+
var REGISTERS = [
|
|
6344
|
+
{
|
|
6345
|
+
id: "plain",
|
|
6346
|
+
name: "Plain and trustworthy",
|
|
6347
|
+
useFor: "Forms, policies, terms, instructions, and professional services where the reader must act correctly.",
|
|
6348
|
+
directives: [
|
|
6349
|
+
{ text: "Put the most important information first, then taper down to the detail.", from: GOVUK },
|
|
6350
|
+
{ text: "Split any sentence over 25 words into two.", from: GOVUK },
|
|
6351
|
+
{ text: "Choose the short, common word: buy, help, about.", from: GOVUK },
|
|
6352
|
+
{ text: "Write in the active voice, so the reader sees who does what.", from: GOVUK },
|
|
6353
|
+
{ text: 'Address the reader as "you", as one person you can actually help.', from: GOVUK },
|
|
6354
|
+
{ text: `Use positive contractions such as "you'll", and write negatives out in full: "do not", "cannot".`, from: GOVUK },
|
|
6355
|
+
{ text: "Write headings that say what the section tells the reader, starting with a verb where you can.", from: GOVUK },
|
|
6356
|
+
{ text: "Give each paragraph one job.", from: HOUSE2 }
|
|
6357
|
+
],
|
|
6358
|
+
example: {
|
|
6359
|
+
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.",
|
|
6360
|
+
after: "Apply for a residents' parking permit before you need it. Fill in the form online. It takes about 10 minutes.",
|
|
6361
|
+
why: "The action comes first, the council is no longer the subject, and each sentence does one thing."
|
|
6362
|
+
},
|
|
6363
|
+
sources: [
|
|
6364
|
+
{
|
|
6365
|
+
name: GOVUK,
|
|
6366
|
+
url: "https://guidance.publishing.service.gov.uk/writing-to-gov-uk-standards/writing-guidelines/",
|
|
6367
|
+
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.'
|
|
6368
|
+
}
|
|
6369
|
+
]
|
|
6370
|
+
},
|
|
6371
|
+
{
|
|
6372
|
+
id: "persuasive",
|
|
6373
|
+
name: "Persuasive and concise",
|
|
6374
|
+
useFor: "Landing pages, service pages, adverts and sales emails: copy whose job is one action.",
|
|
6375
|
+
directives: [
|
|
6376
|
+
{ text: "Put the promise in the headline: what the reader gets, in words they would use.", from: OGILVY },
|
|
6377
|
+
{ text: "Back each claim with something the reader can check: a number, a name, a date, a price.", from: "COPY.md" },
|
|
6378
|
+
{ text: 'Write to one reader, as "you", about their problem before your service.', from: CAPLES },
|
|
6379
|
+
{ text: "Name the mechanism: how it works, how long it takes, what it costs.", from: "COPY.md" },
|
|
6380
|
+
{ text: "Answer the objection the reader already has, with evidence, in the sentence that follows the claim.", from: HOUSE2 },
|
|
6381
|
+
{ text: "Let the copy run as long as the facts last, and cut each sentence that carries none.", from: OGILVY },
|
|
6382
|
+
{ text: "Close with one specific action and what happens straight after it.", from: "COPY.md" }
|
|
6383
|
+
],
|
|
6384
|
+
example: {
|
|
6385
|
+
before: "Transform your garden with our premium landscaping solutions. Quality you can trust.",
|
|
6386
|
+
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.",
|
|
6387
|
+
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."
|
|
6388
|
+
},
|
|
6389
|
+
sources: [
|
|
6390
|
+
{
|
|
6391
|
+
name: OGILVY,
|
|
6392
|
+
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."
|
|
6393
|
+
},
|
|
6394
|
+
{
|
|
6395
|
+
name: CAPLES,
|
|
6396
|
+
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."
|
|
6397
|
+
},
|
|
6398
|
+
{
|
|
6399
|
+
name: "COPY.md",
|
|
6400
|
+
note: "Writing directives (lead with the fact, name the mechanism, persuade with something checkable) and the CTA rule."
|
|
6401
|
+
}
|
|
6402
|
+
]
|
|
6403
|
+
},
|
|
6404
|
+
{
|
|
6405
|
+
id: "warm",
|
|
6406
|
+
name: "Warm and conversational",
|
|
6407
|
+
useFor: "Customer emails, replies to enquiries, newsletters and small-business updates.",
|
|
6408
|
+
directives: [
|
|
6409
|
+
{ text: "Write the way you would talk to a customer you like: contractions, everyday words.", from: MAILCHIMP },
|
|
6410
|
+
{ text: "Keep words and sentences simple; being clear matters more than being entertaining.", from: MAILCHIMP },
|
|
6411
|
+
{ text: "Say what the reader can do, in positive language.", from: MAILCHIMP },
|
|
6412
|
+
{ text: "Use the active voice.", from: MAILCHIMP },
|
|
6413
|
+
{ text: "Match the tone to how the reader feels right now: lighter for good news, calm and direct for a problem.", from: MAILCHIMP },
|
|
6414
|
+
{ text: "Keep humour dry and occasional, and only where it comes naturally.", from: MAILCHIMP },
|
|
6415
|
+
{ text: "Sign off as a person, with a name.", from: HOUSE2 }
|
|
6416
|
+
],
|
|
6417
|
+
example: {
|
|
6418
|
+
before: "Dear Valued Customer, We wish to inform you that your order has been dispatched and will be delivered in due course.",
|
|
6419
|
+
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",
|
|
6420
|
+
why: 'A name at each end, a real day and place instead of "in due course", and a next step. Fictional business.'
|
|
6421
|
+
},
|
|
6422
|
+
sources: [
|
|
6423
|
+
{
|
|
6424
|
+
name: MAILCHIMP,
|
|
6425
|
+
url: "https://styleguide.mailchimp.com/voice-and-tone/",
|
|
6426
|
+
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."
|
|
6427
|
+
}
|
|
6428
|
+
]
|
|
6429
|
+
},
|
|
6430
|
+
{
|
|
6431
|
+
id: "literary",
|
|
6432
|
+
name: "Elegant and literary",
|
|
6433
|
+
useFor: "Premium brands, makers, hospitality and anything sold on craft and taste rather than price.",
|
|
6434
|
+
directives: [
|
|
6435
|
+
{ text: "Choose one precise noun over two adjectives.", from: HOUSE2 },
|
|
6436
|
+
{ text: "Let sentence length follow the thought: a long sentence to build, a short one to land.", from: "COPY.md" },
|
|
6437
|
+
{ text: "Carry the detail in concrete facts: material, place, process, time.", from: HOUSE2 },
|
|
6438
|
+
{ text: "Trust the reader with one image, state it once, and move on.", from: HOUSE2 },
|
|
6439
|
+
{ text: "Keep claims quiet and exact, and let the facts carry the prestige.", from: HOUSE2 },
|
|
6440
|
+
{ text: "Draw any metaphor from the maker's own work, and hold it for the whole piece.", from: HOUSE2 },
|
|
6441
|
+
{ text: "Read the draft aloud and rework each line that trips the breath.", from: HOUSE2 }
|
|
6442
|
+
],
|
|
6443
|
+
example: {
|
|
6444
|
+
before: "Our luxurious, handcrafted candles create an unforgettable ambience of elegance and sophistication.",
|
|
6445
|
+
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.",
|
|
6446
|
+
why: "Place, material, scent and burn time do the work the adjectives claimed. Fictional business."
|
|
6447
|
+
},
|
|
6448
|
+
sources: [
|
|
6449
|
+
{
|
|
6450
|
+
name: HOUSE2,
|
|
6451
|
+
note: "No public style authority exists for premium-brand prose. These directives are house judgement, to be revised as the estate grows."
|
|
6452
|
+
},
|
|
6453
|
+
{
|
|
6454
|
+
name: "Register separation, 2026-09-29",
|
|
6455
|
+
url: "calibration/copy-shape/report-registers.md",
|
|
6456
|
+
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."
|
|
6457
|
+
}
|
|
6458
|
+
]
|
|
6459
|
+
}
|
|
6460
|
+
];
|
|
6461
|
+
var REGISTER_IDS = REGISTERS.map((r) => r.id);
|
|
6462
|
+
function getRegister(id) {
|
|
6463
|
+
return REGISTERS.find((r) => r.id === id);
|
|
6464
|
+
}
|
|
6465
|
+
function registerBrief(id) {
|
|
6466
|
+
const r = getRegister(id);
|
|
6467
|
+
if (!r) throw new Error(`unknown register "${id}". Registers: ${REGISTER_IDS.join(", ")}`);
|
|
6468
|
+
const lines = [`Register: ${r.name} (${r.id})`, `Use for: ${r.useFor}`, "", "Before you draft:"];
|
|
6469
|
+
r.directives.forEach((d, i) => lines.push(` ${i + 1}. ${d.text} [${d.from}]`));
|
|
6470
|
+
lines.push("", "Example (fictional):", ` Before: ${r.example.before}`, ` After: ${r.example.after}`, ` Why: ${r.example.why}`);
|
|
6471
|
+
lines.push("", "Sources:");
|
|
6472
|
+
for (const s of r.sources) lines.push(` - ${s.name}${s.url ? ` (${s.url})` : ""}: ${s.note}`);
|
|
6473
|
+
lines.push("", `Always: ${ALWAYS}`);
|
|
6474
|
+
return lines.join("\n");
|
|
6475
|
+
}
|
|
6476
|
+
function registersDoc() {
|
|
6477
|
+
const out = [];
|
|
6478
|
+
for (const r of REGISTERS) {
|
|
6479
|
+
out.push(`## ${r.name} (\`${r.id}\`)`, "", `**Use for:** ${r.useFor}`, "", "**Before you draft:**", "");
|
|
6480
|
+
r.directives.forEach((d, i) => out.push(`${i + 1}. ${d.text} *(${d.from})*`));
|
|
6481
|
+
out.push("", "**Example (fictional):**", "", `- Before: ${r.example.before}`, `- After: ${r.example.after}`, `- Why: ${r.example.why}`);
|
|
6482
|
+
out.push("", "**Sources:**", "");
|
|
6483
|
+
for (const s of r.sources) out.push(`- ${s.url ? `[${s.name}](${s.url})` : s.name}: ${s.note}`);
|
|
6484
|
+
out.push("");
|
|
6485
|
+
}
|
|
6486
|
+
out.push(`**Always:** ${ALWAYS}`);
|
|
6487
|
+
return out.join("\n");
|
|
6488
|
+
}
|
|
6489
|
+
|
|
6490
|
+
// src/register/check.ts
|
|
6491
|
+
var REGISTER_BASELINE = {
|
|
6492
|
+
plain: {
|
|
6493
|
+
baseline: "institutional",
|
|
6494
|
+
note: "Compared with Hansard: edited parliamentary debate, the closest free UK source, not plain writing itself."
|
|
6495
|
+
},
|
|
6496
|
+
persuasive: {
|
|
6497
|
+
baseline: "marketing",
|
|
6498
|
+
note: "Compared with 715 passages from UK small-business websites before 2021: a small sample."
|
|
6499
|
+
},
|
|
6500
|
+
warm: {
|
|
6501
|
+
baseline: "business-email",
|
|
6502
|
+
note: "Compared with Enron sent email, 1999 to 2002: corporate and American, not small-business email."
|
|
6503
|
+
},
|
|
6504
|
+
literary: {
|
|
6505
|
+
baseline: "literary",
|
|
6506
|
+
note: "Compared with English fiction and essays, 1726 to 1925: older than any premium brand's copy."
|
|
6507
|
+
}
|
|
6508
|
+
};
|
|
6509
|
+
var MIN_PARAGRAPHS = 5;
|
|
6510
|
+
var CONTENT_FEATURES = /* @__PURE__ */ new Set(["hardPer100", "specificsPer100", "tradePer100", "figures", "names", "generic"]);
|
|
6511
|
+
var usable = (f4, b) => !CONTENT_FEATURES.has(f4) && b.p90 > b.p10;
|
|
6512
|
+
var LABELS = {
|
|
6513
|
+
meanSentenceWords: "Words per sentence",
|
|
6514
|
+
contractionsPer100: "Contractions per 100 words",
|
|
6515
|
+
youPer100: '"You" per 100 words',
|
|
6516
|
+
firstPersonPer100: '"I" and "we" per 100 words',
|
|
6517
|
+
passiveShare: "Share of sentences in the passive",
|
|
6518
|
+
meanWordLength: "Letters per word",
|
|
6519
|
+
commasPer100: "Commas per 100 words",
|
|
6520
|
+
commasPerSentence: "Commas per sentence",
|
|
6521
|
+
clauseDepth: "Clauses per sentence",
|
|
6522
|
+
nominalisationRate: 'Nominalisations ("the provision of") per 100 words',
|
|
6523
|
+
pronounOpenerShare: "Share of sentences opening with a pronoun",
|
|
6524
|
+
impersonalOpenerShare: 'Share of sentences opening impersonally ("It is", "There are")',
|
|
6525
|
+
coordinationRate: 'Paired phrases ("fast and friendly") per 100 words',
|
|
6526
|
+
participialRate: '"-ing" clauses per sentence',
|
|
6527
|
+
tricolons: "Lists of three",
|
|
6528
|
+
sentenceCv: "Spread of sentence lengths",
|
|
6529
|
+
skeletonRepeat: "Repeated sentence patterns",
|
|
6530
|
+
hardPer100: "Hard specifics (names, figures, dates) per 100 words",
|
|
6531
|
+
specificsPer100: "Specifics of any kind per 100 words",
|
|
6532
|
+
tradePer100: "Trade nouns per 100 words",
|
|
6533
|
+
figures: "Figures per paragraph",
|
|
6534
|
+
names: "Names per paragraph",
|
|
6535
|
+
generic: "Share of paragraphs with no specific at all"
|
|
6536
|
+
};
|
|
6537
|
+
var featureLabel = (f4) => LABELS[f4] ?? f4;
|
|
6538
|
+
function documentParagraphs(text) {
|
|
6539
|
+
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));
|
|
6540
|
+
}
|
|
6541
|
+
function measure(paragraph) {
|
|
6542
|
+
const out = {};
|
|
6543
|
+
for (const [k, v] of Object.entries(shapeOf(paragraph))) out[k] = typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
6544
|
+
for (const [k, v] of Object.entries(lexicalOf(paragraph))) out[k] = v;
|
|
6545
|
+
for (const [k, v] of Object.entries(registerOf(paragraph))) out[k] = v;
|
|
6546
|
+
return out;
|
|
6547
|
+
}
|
|
6548
|
+
var fmt = (x) => Math.abs(x) >= 10 ? x.toFixed(0) : x.toFixed(Math.abs(x) >= 1 ? 1 : 2);
|
|
6549
|
+
function checkRegister(text, id, profile) {
|
|
6550
|
+
const reg = getRegister(id);
|
|
6551
|
+
if (!reg) throw new Error(`unknown register "${id}"`);
|
|
6552
|
+
const { baseline, note } = REGISTER_BASELINE[id];
|
|
6553
|
+
const target2 = profile[baseline];
|
|
6554
|
+
if (!target2) throw new Error(`no baseline profile for "${baseline}"`);
|
|
6555
|
+
const kept = documentParagraphs(text).filter((p) => isBlurb(shapeOf(p)));
|
|
6556
|
+
const base = { register: id, baseline, paragraphs: kept.length, note };
|
|
6557
|
+
if (kept.length < MIN_PARAGRAPHS) {
|
|
6558
|
+
return {
|
|
6559
|
+
...base,
|
|
6560
|
+
measured: false,
|
|
6561
|
+
means: {},
|
|
6562
|
+
outside: [],
|
|
6563
|
+
nearest: [],
|
|
6564
|
+
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.`
|
|
6565
|
+
};
|
|
6566
|
+
}
|
|
6567
|
+
const rows = kept.map(measure);
|
|
6568
|
+
const features = [...new Set(Object.values(profile).flatMap((p) => Object.keys(p)))];
|
|
6569
|
+
const means = {};
|
|
6570
|
+
for (const f4 of features) {
|
|
6571
|
+
const xs = rows.map((r) => r[f4]).filter((x) => typeof x === "number" && Number.isFinite(x));
|
|
6572
|
+
if (xs.length) means[f4] = xs.reduce((s, x) => s + x, 0) / xs.length;
|
|
6573
|
+
}
|
|
6574
|
+
const outside = [];
|
|
6575
|
+
for (const [f4, b] of Object.entries(target2)) {
|
|
6576
|
+
const v = means[f4];
|
|
6577
|
+
if (v === void 0 || b.separates.length === 0 || !usable(f4, b)) continue;
|
|
6578
|
+
if (v >= b.p10 && v <= b.p90) continue;
|
|
6579
|
+
const direction = v > b.p90 ? "higher" : "lower";
|
|
6580
|
+
outside.push({
|
|
6581
|
+
feature: f4,
|
|
6582
|
+
value: v,
|
|
6583
|
+
band: [b.p10, b.p90],
|
|
6584
|
+
direction,
|
|
6585
|
+
sentence: `${featureLabel(f4)}: ${fmt(v)}. ${reg.name} writing in the baseline runs ${fmt(b.p10)} to ${fmt(b.p90)}, so this is ${direction}.`
|
|
6586
|
+
});
|
|
6587
|
+
}
|
|
6588
|
+
const nearest2 = Object.entries(profile).map(([register, bands]) => {
|
|
6589
|
+
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);
|
|
6590
|
+
return { register, distance: zs.length ? Math.sqrt(zs.reduce((s, z) => s + z, 0) / zs.length) : Infinity };
|
|
6591
|
+
}).filter((n) => Number.isFinite(n.distance)).sort((a, b) => a.distance - b.distance);
|
|
6592
|
+
const closest = nearest2[0]?.register;
|
|
6593
|
+
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.` : "");
|
|
6594
|
+
return { ...base, measured: true, means, outside, nearest: nearest2, summary };
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
// src/register/profile.ts
|
|
6598
|
+
var PROFILE_PARAGRAPHS = 5;
|
|
6599
|
+
var PROFILE_MEASURED = "2026-09-30";
|
|
6600
|
+
var PROFILE = {
|
|
6601
|
+
"business-email": {
|
|
6602
|
+
"clauseDepth": {
|
|
6603
|
+
"p10": 0.36,
|
|
6604
|
+
"p50": 0.673,
|
|
6605
|
+
"p90": 1.113,
|
|
6606
|
+
"sd": 0.307,
|
|
6607
|
+
"separates": [
|
|
6608
|
+
"explanatory",
|
|
6609
|
+
"institutional",
|
|
6610
|
+
"literary"
|
|
6611
|
+
]
|
|
6612
|
+
},
|
|
6613
|
+
"commasPer100": {
|
|
6614
|
+
"p10": 1.424,
|
|
6615
|
+
"p50": 3.19,
|
|
6616
|
+
"p90": 5.154,
|
|
6617
|
+
"sd": 1.529,
|
|
6618
|
+
"separates": [
|
|
6619
|
+
"institutional",
|
|
6620
|
+
"literary"
|
|
6621
|
+
]
|
|
6622
|
+
},
|
|
6623
|
+
"commasPerSentence": {
|
|
6624
|
+
"p10": 0.2,
|
|
6625
|
+
"p50": 0.467,
|
|
6626
|
+
"p90": 0.826,
|
|
6627
|
+
"sd": 0.262,
|
|
6628
|
+
"separates": [
|
|
6629
|
+
"explanatory",
|
|
6630
|
+
"institutional",
|
|
6631
|
+
"literary",
|
|
6632
|
+
"marketing"
|
|
6633
|
+
]
|
|
6634
|
+
},
|
|
6635
|
+
"coordinationRate": {
|
|
6636
|
+
"p10": 0,
|
|
6637
|
+
"p50": 0.743,
|
|
6638
|
+
"p90": 1.712,
|
|
6639
|
+
"sd": 0.666,
|
|
6640
|
+
"separates": [
|
|
6641
|
+
"marketing"
|
|
6642
|
+
]
|
|
6643
|
+
},
|
|
6644
|
+
"figures": {
|
|
6645
|
+
"p10": 0.2,
|
|
6646
|
+
"p50": 0.8,
|
|
6647
|
+
"p90": 1.8,
|
|
6648
|
+
"sd": 0.725,
|
|
6649
|
+
"separates": [
|
|
6650
|
+
"literary"
|
|
6651
|
+
]
|
|
6652
|
+
},
|
|
6653
|
+
"firstPersonPer100": {
|
|
6654
|
+
"p10": 3.718,
|
|
6655
|
+
"p50": 5.968,
|
|
6656
|
+
"p90": 8.375,
|
|
6657
|
+
"sd": 1.825,
|
|
6658
|
+
"separates": [
|
|
6659
|
+
"explanatory"
|
|
6660
|
+
]
|
|
6661
|
+
},
|
|
6662
|
+
"generic": {
|
|
6663
|
+
"p10": 0,
|
|
6664
|
+
"p50": 0,
|
|
6665
|
+
"p90": 0.2,
|
|
6666
|
+
"sd": 0.138,
|
|
6667
|
+
"separates": [
|
|
6668
|
+
"explanatory"
|
|
6669
|
+
]
|
|
6670
|
+
},
|
|
6671
|
+
"hardPer100": {
|
|
6672
|
+
"p10": 4.289,
|
|
6673
|
+
"p50": 7.127,
|
|
6674
|
+
"p90": 11.308,
|
|
6675
|
+
"sd": 2.843,
|
|
6676
|
+
"separates": [
|
|
6677
|
+
"explanatory",
|
|
6678
|
+
"institutional",
|
|
6679
|
+
"literary"
|
|
6680
|
+
]
|
|
6681
|
+
},
|
|
6682
|
+
"impersonalOpenerShare": {
|
|
6683
|
+
"p10": 0,
|
|
6684
|
+
"p50": 0,
|
|
6685
|
+
"p90": 0.067,
|
|
6686
|
+
"sd": 0.031,
|
|
6687
|
+
"separates": [
|
|
6688
|
+
"explanatory"
|
|
6689
|
+
]
|
|
6690
|
+
},
|
|
6691
|
+
"meanSentenceWords": {
|
|
6692
|
+
"p10": 11.417,
|
|
6693
|
+
"p50": 14.25,
|
|
6694
|
+
"p90": 17.87,
|
|
6695
|
+
"sd": 2.656,
|
|
6696
|
+
"separates": [
|
|
6697
|
+
"explanatory",
|
|
6698
|
+
"literary"
|
|
6699
|
+
]
|
|
6700
|
+
},
|
|
6701
|
+
"meanWordLength": {
|
|
6702
|
+
"p10": 3.858,
|
|
6703
|
+
"p50": 4.16,
|
|
6704
|
+
"p90": 4.459,
|
|
6705
|
+
"sd": 0.231,
|
|
6706
|
+
"separates": [
|
|
6707
|
+
"explanatory"
|
|
6708
|
+
]
|
|
6709
|
+
},
|
|
6710
|
+
"names": {
|
|
6711
|
+
"p10": 1.2,
|
|
6712
|
+
"p50": 2.4,
|
|
6713
|
+
"p90": 3.6,
|
|
6714
|
+
"sd": 0.965,
|
|
6715
|
+
"separates": [
|
|
6716
|
+
"explanatory",
|
|
6717
|
+
"institutional"
|
|
6718
|
+
]
|
|
6719
|
+
},
|
|
6720
|
+
"nominalisationRate": {
|
|
6721
|
+
"p10": 0.625,
|
|
6722
|
+
"p50": 1.93,
|
|
6723
|
+
"p90": 3.831,
|
|
6724
|
+
"sd": 1.264,
|
|
6725
|
+
"separates": [
|
|
6726
|
+
"institutional",
|
|
6727
|
+
"marketing"
|
|
6728
|
+
]
|
|
6729
|
+
},
|
|
6730
|
+
"participialRate": {
|
|
6731
|
+
"p10": 0,
|
|
6732
|
+
"p50": 0,
|
|
6733
|
+
"p90": 0.05,
|
|
6734
|
+
"sd": 0.035,
|
|
6735
|
+
"separates": [
|
|
6736
|
+
"literary"
|
|
6737
|
+
]
|
|
6738
|
+
},
|
|
6739
|
+
"pronounOpenerShare": {
|
|
6740
|
+
"p10": 0,
|
|
6741
|
+
"p50": 0.05,
|
|
6742
|
+
"p90": 0.15,
|
|
6743
|
+
"sd": 0.068,
|
|
6744
|
+
"separates": [
|
|
6745
|
+
"marketing"
|
|
6746
|
+
]
|
|
6747
|
+
},
|
|
6748
|
+
"sentenceCv": {
|
|
6749
|
+
"p10": 0.344,
|
|
6750
|
+
"p50": 0.468,
|
|
6751
|
+
"p90": 0.6,
|
|
6752
|
+
"sd": 0.099,
|
|
6753
|
+
"separates": [
|
|
6754
|
+
"marketing"
|
|
6755
|
+
]
|
|
6756
|
+
},
|
|
6757
|
+
"skeletonRepeat": {
|
|
6758
|
+
"p10": 0,
|
|
6759
|
+
"p50": 0.017,
|
|
6760
|
+
"p90": 0.063,
|
|
6761
|
+
"sd": 0.031,
|
|
6762
|
+
"separates": [
|
|
6763
|
+
"institutional"
|
|
6764
|
+
]
|
|
6765
|
+
},
|
|
6766
|
+
"specificsPer100": {
|
|
6767
|
+
"p10": 4.22,
|
|
6768
|
+
"p50": 7.34,
|
|
6769
|
+
"p90": 11.46,
|
|
6770
|
+
"sd": 2.892,
|
|
6771
|
+
"separates": [
|
|
6772
|
+
"explanatory",
|
|
6773
|
+
"institutional",
|
|
6774
|
+
"literary"
|
|
6775
|
+
]
|
|
6776
|
+
},
|
|
6777
|
+
"tradePer100": {
|
|
6778
|
+
"p10": 0,
|
|
6779
|
+
"p50": 0,
|
|
6780
|
+
"p90": 0.588,
|
|
6781
|
+
"sd": 0.287,
|
|
6782
|
+
"separates": [
|
|
6783
|
+
"marketing"
|
|
6784
|
+
]
|
|
6785
|
+
},
|
|
6786
|
+
"tricolons": {
|
|
6787
|
+
"p10": 0,
|
|
6788
|
+
"p50": 0,
|
|
6789
|
+
"p90": 0.2,
|
|
6790
|
+
"sd": 0.142,
|
|
6791
|
+
"separates": [
|
|
6792
|
+
"institutional",
|
|
6793
|
+
"literary",
|
|
6794
|
+
"marketing"
|
|
6795
|
+
]
|
|
6796
|
+
}
|
|
6797
|
+
},
|
|
6798
|
+
"explanatory": {
|
|
6799
|
+
"clauseDepth": {
|
|
6800
|
+
"p10": 0.78,
|
|
6801
|
+
"p50": 1.194,
|
|
6802
|
+
"p90": 1.733,
|
|
6803
|
+
"sd": 0.383,
|
|
6804
|
+
"separates": [
|
|
6805
|
+
"business-email",
|
|
6806
|
+
"literary"
|
|
6807
|
+
]
|
|
6808
|
+
},
|
|
6809
|
+
"commasPer100": {
|
|
6810
|
+
"p10": 2.579,
|
|
6811
|
+
"p50": 4.164,
|
|
6812
|
+
"p90": 6.089,
|
|
6813
|
+
"sd": 1.423,
|
|
6814
|
+
"separates": [
|
|
6815
|
+
"literary"
|
|
6816
|
+
]
|
|
6817
|
+
},
|
|
6818
|
+
"commasPerSentence": {
|
|
6819
|
+
"p10": 0.448,
|
|
6820
|
+
"p50": 0.77,
|
|
6821
|
+
"p90": 1.2,
|
|
6822
|
+
"sd": 0.309,
|
|
6823
|
+
"separates": [
|
|
6824
|
+
"business-email",
|
|
6825
|
+
"literary"
|
|
6826
|
+
]
|
|
6827
|
+
},
|
|
6828
|
+
"coordinationRate": {
|
|
6829
|
+
"p10": 0.323,
|
|
6830
|
+
"p50": 0.973,
|
|
6831
|
+
"p90": 1.862,
|
|
6832
|
+
"sd": 0.612,
|
|
6833
|
+
"separates": [
|
|
6834
|
+
"marketing"
|
|
6835
|
+
]
|
|
6836
|
+
},
|
|
6837
|
+
"figures": {
|
|
6838
|
+
"p10": 0,
|
|
6839
|
+
"p50": 0.4,
|
|
6840
|
+
"p90": 1.4,
|
|
6841
|
+
"sd": 0.631,
|
|
6842
|
+
"separates": [
|
|
6843
|
+
"literary"
|
|
6844
|
+
]
|
|
6845
|
+
},
|
|
6846
|
+
"firstPersonPer100": {
|
|
6847
|
+
"p10": 0.27,
|
|
6848
|
+
"p50": 1.282,
|
|
6849
|
+
"p90": 3.114,
|
|
6850
|
+
"sd": 1.181,
|
|
6851
|
+
"separates": [
|
|
6852
|
+
"business-email",
|
|
6853
|
+
"literary"
|
|
6854
|
+
]
|
|
6855
|
+
},
|
|
6856
|
+
"generic": {
|
|
6857
|
+
"p10": 0,
|
|
6858
|
+
"p50": 0.4,
|
|
6859
|
+
"p90": 0.6,
|
|
6860
|
+
"sd": 0.214,
|
|
6861
|
+
"separates": [
|
|
6862
|
+
"business-email",
|
|
6863
|
+
"institutional"
|
|
6864
|
+
]
|
|
6865
|
+
},
|
|
6866
|
+
"hardPer100": {
|
|
6867
|
+
"p10": 0.988,
|
|
6868
|
+
"p50": 2.62,
|
|
6869
|
+
"p90": 5.171,
|
|
6870
|
+
"sd": 1.713,
|
|
6871
|
+
"separates": [
|
|
6872
|
+
"business-email",
|
|
6873
|
+
"institutional",
|
|
6874
|
+
"marketing"
|
|
6875
|
+
]
|
|
6876
|
+
},
|
|
6877
|
+
"impersonalOpenerShare": {
|
|
6878
|
+
"p10": 0,
|
|
6879
|
+
"p50": 0.04,
|
|
6880
|
+
"p90": 0.14,
|
|
6881
|
+
"sd": 0.061,
|
|
6882
|
+
"separates": [
|
|
6883
|
+
"business-email"
|
|
6884
|
+
]
|
|
6885
|
+
},
|
|
6886
|
+
"meanSentenceWords": {
|
|
6887
|
+
"p10": 14.973,
|
|
6888
|
+
"p50": 18.31,
|
|
6889
|
+
"p90": 22.533,
|
|
6890
|
+
"sd": 3.013,
|
|
6891
|
+
"separates": [
|
|
6892
|
+
"business-email"
|
|
6893
|
+
]
|
|
6894
|
+
},
|
|
6895
|
+
"meanWordLength": {
|
|
6896
|
+
"p10": 4.207,
|
|
6897
|
+
"p50": 4.466,
|
|
6898
|
+
"p90": 4.752,
|
|
6899
|
+
"sd": 0.213,
|
|
6900
|
+
"separates": [
|
|
6901
|
+
"business-email",
|
|
6902
|
+
"literary"
|
|
6903
|
+
]
|
|
6904
|
+
},
|
|
6905
|
+
"names": {
|
|
6906
|
+
"p10": 0.4,
|
|
6907
|
+
"p50": 1.2,
|
|
6908
|
+
"p90": 2.6,
|
|
6909
|
+
"sd": 0.941,
|
|
6910
|
+
"separates": [
|
|
6911
|
+
"business-email",
|
|
6912
|
+
"institutional"
|
|
6913
|
+
]
|
|
6914
|
+
},
|
|
6915
|
+
"nominalisationRate": {
|
|
6916
|
+
"p10": 0.766,
|
|
6917
|
+
"p50": 1.823,
|
|
6918
|
+
"p90": 3.416,
|
|
6919
|
+
"sd": 1.061,
|
|
6920
|
+
"separates": [
|
|
6921
|
+
"institutional",
|
|
6922
|
+
"marketing"
|
|
6923
|
+
]
|
|
6924
|
+
},
|
|
6925
|
+
"participialRate": {
|
|
6926
|
+
"p10": 0,
|
|
6927
|
+
"p50": 0,
|
|
6928
|
+
"p90": 0.1,
|
|
6929
|
+
"sd": 0.045,
|
|
6930
|
+
"separates": []
|
|
6931
|
+
},
|
|
6932
|
+
"pronounOpenerShare": {
|
|
6933
|
+
"p10": 0,
|
|
6934
|
+
"p50": 0.033,
|
|
6935
|
+
"p90": 0.124,
|
|
6936
|
+
"sd": 0.056,
|
|
6937
|
+
"separates": [
|
|
6938
|
+
"marketing"
|
|
6939
|
+
]
|
|
6940
|
+
},
|
|
6941
|
+
"sentenceCv": {
|
|
6942
|
+
"p10": 0.337,
|
|
6943
|
+
"p50": 0.441,
|
|
6944
|
+
"p90": 0.57,
|
|
6945
|
+
"sd": 0.092,
|
|
6946
|
+
"separates": []
|
|
6947
|
+
},
|
|
6948
|
+
"skeletonRepeat": {
|
|
6949
|
+
"p10": 9e-3,
|
|
6950
|
+
"p50": 0.039,
|
|
6951
|
+
"p90": 0.091,
|
|
6952
|
+
"sd": 0.036,
|
|
6953
|
+
"separates": []
|
|
6954
|
+
},
|
|
6955
|
+
"specificsPer100": {
|
|
6956
|
+
"p10": 1.04,
|
|
6957
|
+
"p50": 2.7,
|
|
6958
|
+
"p90": 5.36,
|
|
6959
|
+
"sd": 1.731,
|
|
6960
|
+
"separates": [
|
|
6961
|
+
"business-email",
|
|
6962
|
+
"institutional",
|
|
6963
|
+
"marketing"
|
|
6964
|
+
]
|
|
6965
|
+
},
|
|
6966
|
+
"tradePer100": {
|
|
6967
|
+
"p10": 0,
|
|
6968
|
+
"p50": 0,
|
|
6969
|
+
"p90": 0.427,
|
|
6970
|
+
"sd": 0.216,
|
|
6971
|
+
"separates": [
|
|
6972
|
+
"marketing"
|
|
6973
|
+
]
|
|
6974
|
+
},
|
|
6975
|
+
"tricolons": {
|
|
6976
|
+
"p10": 0,
|
|
6977
|
+
"p50": 0.2,
|
|
6978
|
+
"p90": 0.6,
|
|
6979
|
+
"sd": 0.222,
|
|
6980
|
+
"separates": []
|
|
6981
|
+
}
|
|
6982
|
+
},
|
|
6983
|
+
"institutional": {
|
|
6984
|
+
"clauseDepth": {
|
|
6985
|
+
"p10": 0.792,
|
|
6986
|
+
"p50": 1.233,
|
|
6987
|
+
"p90": 1.76,
|
|
6988
|
+
"sd": 0.377,
|
|
6989
|
+
"separates": [
|
|
6990
|
+
"business-email",
|
|
6991
|
+
"literary"
|
|
6992
|
+
]
|
|
6993
|
+
},
|
|
6994
|
+
"commasPer100": {
|
|
6995
|
+
"p10": 3.03,
|
|
6996
|
+
"p50": 4.435,
|
|
6997
|
+
"p90": 5.941,
|
|
6998
|
+
"sd": 1.165,
|
|
6999
|
+
"separates": [
|
|
7000
|
+
"business-email",
|
|
7001
|
+
"literary"
|
|
7002
|
+
]
|
|
7003
|
+
},
|
|
7004
|
+
"commasPerSentence": {
|
|
7005
|
+
"p10": 0.61,
|
|
7006
|
+
"p50": 0.96,
|
|
7007
|
+
"p90": 1.375,
|
|
7008
|
+
"sd": 0.307,
|
|
7009
|
+
"separates": [
|
|
7010
|
+
"business-email",
|
|
7011
|
+
"literary"
|
|
7012
|
+
]
|
|
7013
|
+
},
|
|
7014
|
+
"coordinationRate": {
|
|
7015
|
+
"p10": 0.323,
|
|
7016
|
+
"p50": 0.949,
|
|
7017
|
+
"p90": 1.752,
|
|
7018
|
+
"sd": 0.559,
|
|
7019
|
+
"separates": [
|
|
7020
|
+
"marketing"
|
|
7021
|
+
]
|
|
7022
|
+
},
|
|
7023
|
+
"figures": {
|
|
7024
|
+
"p10": 0,
|
|
7025
|
+
"p50": 0.6,
|
|
7026
|
+
"p90": 1.4,
|
|
7027
|
+
"sd": 0.56,
|
|
7028
|
+
"separates": [
|
|
7029
|
+
"literary"
|
|
7030
|
+
]
|
|
7031
|
+
},
|
|
7032
|
+
"generic": {
|
|
7033
|
+
"p10": 0,
|
|
7034
|
+
"p50": 0,
|
|
7035
|
+
"p90": 0.2,
|
|
7036
|
+
"sd": 0.114,
|
|
7037
|
+
"separates": [
|
|
7038
|
+
"explanatory",
|
|
7039
|
+
"literary",
|
|
7040
|
+
"marketing"
|
|
7041
|
+
]
|
|
7042
|
+
},
|
|
7043
|
+
"hardPer100": {
|
|
7044
|
+
"p10": 3.41,
|
|
7045
|
+
"p50": 5.135,
|
|
7046
|
+
"p90": 7.304,
|
|
7047
|
+
"sd": 1.573,
|
|
7048
|
+
"separates": [
|
|
7049
|
+
"business-email",
|
|
7050
|
+
"explanatory",
|
|
7051
|
+
"literary"
|
|
7052
|
+
]
|
|
7053
|
+
},
|
|
7054
|
+
"impersonalOpenerShare": {
|
|
7055
|
+
"p10": 0,
|
|
7056
|
+
"p50": 0.033,
|
|
7057
|
+
"p90": 0.109,
|
|
7058
|
+
"sd": 0.051,
|
|
7059
|
+
"separates": []
|
|
7060
|
+
},
|
|
7061
|
+
"names": {
|
|
7062
|
+
"p10": 2,
|
|
7063
|
+
"p50": 3.2,
|
|
7064
|
+
"p90": 4.8,
|
|
7065
|
+
"sd": 1.113,
|
|
7066
|
+
"separates": [
|
|
7067
|
+
"business-email",
|
|
7068
|
+
"explanatory",
|
|
7069
|
+
"literary",
|
|
7070
|
+
"marketing"
|
|
7071
|
+
]
|
|
7072
|
+
},
|
|
7073
|
+
"nominalisationRate": {
|
|
7074
|
+
"p10": 1.992,
|
|
7075
|
+
"p50": 3.391,
|
|
7076
|
+
"p90": 5.008,
|
|
7077
|
+
"sd": 1.147,
|
|
7078
|
+
"separates": [
|
|
7079
|
+
"business-email",
|
|
7080
|
+
"explanatory",
|
|
7081
|
+
"literary"
|
|
7082
|
+
]
|
|
7083
|
+
},
|
|
7084
|
+
"participialRate": {
|
|
7085
|
+
"p10": 0,
|
|
7086
|
+
"p50": 0,
|
|
7087
|
+
"p90": 0.1,
|
|
7088
|
+
"sd": 0.044,
|
|
7089
|
+
"separates": [
|
|
7090
|
+
"literary"
|
|
7091
|
+
]
|
|
7092
|
+
},
|
|
7093
|
+
"pronounOpenerShare": {
|
|
7094
|
+
"p10": 0,
|
|
7095
|
+
"p50": 0.05,
|
|
7096
|
+
"p90": 0.164,
|
|
7097
|
+
"sd": 0.068,
|
|
7098
|
+
"separates": [
|
|
7099
|
+
"marketing"
|
|
7100
|
+
]
|
|
7101
|
+
},
|
|
7102
|
+
"sentenceCv": {
|
|
7103
|
+
"p10": 0.386,
|
|
7104
|
+
"p50": 0.512,
|
|
7105
|
+
"p90": 0.639,
|
|
7106
|
+
"sd": 0.097,
|
|
7107
|
+
"separates": [
|
|
7108
|
+
"marketing"
|
|
7109
|
+
]
|
|
7110
|
+
},
|
|
7111
|
+
"skeletonRepeat": {
|
|
7112
|
+
"p10": 0.018,
|
|
7113
|
+
"p50": 0.048,
|
|
7114
|
+
"p90": 0.087,
|
|
7115
|
+
"sd": 0.028,
|
|
7116
|
+
"separates": [
|
|
7117
|
+
"business-email"
|
|
7118
|
+
]
|
|
7119
|
+
},
|
|
7120
|
+
"specificsPer100": {
|
|
7121
|
+
"p10": 3.36,
|
|
7122
|
+
"p50": 5.2,
|
|
7123
|
+
"p90": 7.4,
|
|
7124
|
+
"sd": 1.612,
|
|
7125
|
+
"separates": [
|
|
7126
|
+
"business-email",
|
|
7127
|
+
"explanatory",
|
|
7128
|
+
"literary"
|
|
7129
|
+
]
|
|
7130
|
+
},
|
|
7131
|
+
"tradePer100": {
|
|
7132
|
+
"p10": 0,
|
|
7133
|
+
"p50": 0,
|
|
7134
|
+
"p90": 0.157,
|
|
7135
|
+
"sd": 0.101,
|
|
7136
|
+
"separates": [
|
|
7137
|
+
"marketing"
|
|
7138
|
+
]
|
|
7139
|
+
},
|
|
7140
|
+
"tricolons": {
|
|
7141
|
+
"p10": 0,
|
|
7142
|
+
"p50": 0.2,
|
|
7143
|
+
"p90": 0.6,
|
|
7144
|
+
"sd": 0.233,
|
|
7145
|
+
"separates": [
|
|
7146
|
+
"business-email"
|
|
7147
|
+
]
|
|
7148
|
+
}
|
|
7149
|
+
},
|
|
7150
|
+
"literary": {
|
|
7151
|
+
"clauseDepth": {
|
|
7152
|
+
"p10": 1.333,
|
|
7153
|
+
"p50": 2.017,
|
|
7154
|
+
"p90": 2.933,
|
|
7155
|
+
"sd": 0.626,
|
|
7156
|
+
"separates": [
|
|
7157
|
+
"business-email",
|
|
7158
|
+
"explanatory",
|
|
7159
|
+
"institutional",
|
|
7160
|
+
"marketing"
|
|
7161
|
+
]
|
|
7162
|
+
},
|
|
7163
|
+
"commasPer100": {
|
|
7164
|
+
"p10": 5.997,
|
|
7165
|
+
"p50": 8.1,
|
|
7166
|
+
"p90": 10.586,
|
|
7167
|
+
"sd": 1.815,
|
|
7168
|
+
"separates": [
|
|
7169
|
+
"business-email",
|
|
7170
|
+
"explanatory",
|
|
7171
|
+
"institutional",
|
|
7172
|
+
"marketing"
|
|
7173
|
+
]
|
|
7174
|
+
},
|
|
7175
|
+
"commasPerSentence": {
|
|
7176
|
+
"p10": 1.067,
|
|
7177
|
+
"p50": 1.667,
|
|
7178
|
+
"p90": 2.435,
|
|
7179
|
+
"sd": 0.541,
|
|
7180
|
+
"separates": [
|
|
7181
|
+
"business-email",
|
|
7182
|
+
"explanatory",
|
|
7183
|
+
"institutional",
|
|
7184
|
+
"marketing"
|
|
7185
|
+
]
|
|
7186
|
+
},
|
|
7187
|
+
"coordinationRate": {
|
|
7188
|
+
"p10": 0.194,
|
|
7189
|
+
"p50": 0.814,
|
|
7190
|
+
"p90": 1.633,
|
|
7191
|
+
"sd": 0.572,
|
|
7192
|
+
"separates": [
|
|
7193
|
+
"marketing"
|
|
7194
|
+
]
|
|
7195
|
+
},
|
|
7196
|
+
"figures": {
|
|
7197
|
+
"p10": 0,
|
|
7198
|
+
"p50": 0,
|
|
7199
|
+
"p90": 0,
|
|
7200
|
+
"sd": 0.079,
|
|
7201
|
+
"separates": [
|
|
7202
|
+
"business-email",
|
|
7203
|
+
"explanatory",
|
|
7204
|
+
"institutional",
|
|
7205
|
+
"marketing"
|
|
7206
|
+
]
|
|
7207
|
+
},
|
|
7208
|
+
"firstPersonPer100": {
|
|
7209
|
+
"p10": 2.207,
|
|
7210
|
+
"p50": 4.67,
|
|
7211
|
+
"p90": 7.486,
|
|
7212
|
+
"sd": 2.027,
|
|
7213
|
+
"separates": [
|
|
7214
|
+
"explanatory"
|
|
7215
|
+
]
|
|
7216
|
+
},
|
|
7217
|
+
"generic": {
|
|
7218
|
+
"p10": 0,
|
|
7219
|
+
"p50": 0.2,
|
|
7220
|
+
"p90": 0.4,
|
|
7221
|
+
"sd": 0.178,
|
|
7222
|
+
"separates": [
|
|
7223
|
+
"institutional"
|
|
7224
|
+
]
|
|
7225
|
+
},
|
|
7226
|
+
"hardPer100": {
|
|
7227
|
+
"p10": 1.632,
|
|
7228
|
+
"p50": 3.076,
|
|
7229
|
+
"p90": 5.154,
|
|
7230
|
+
"sd": 1.367,
|
|
7231
|
+
"separates": [
|
|
7232
|
+
"business-email",
|
|
7233
|
+
"institutional"
|
|
7234
|
+
]
|
|
7235
|
+
},
|
|
7236
|
+
"impersonalOpenerShare": {
|
|
7237
|
+
"p10": 0,
|
|
7238
|
+
"p50": 0,
|
|
7239
|
+
"p90": 0.067,
|
|
7240
|
+
"sd": 0.039,
|
|
7241
|
+
"separates": []
|
|
7242
|
+
},
|
|
7243
|
+
"meanSentenceWords": {
|
|
7244
|
+
"p10": 15.083,
|
|
7245
|
+
"p50": 20.35,
|
|
7246
|
+
"p90": 27.027,
|
|
7247
|
+
"sd": 4.744,
|
|
7248
|
+
"separates": [
|
|
7249
|
+
"business-email"
|
|
7250
|
+
]
|
|
7251
|
+
},
|
|
7252
|
+
"meanWordLength": {
|
|
7253
|
+
"p10": 3.958,
|
|
7254
|
+
"p50": 4.186,
|
|
7255
|
+
"p90": 4.409,
|
|
7256
|
+
"sd": 0.175,
|
|
7257
|
+
"separates": [
|
|
7258
|
+
"explanatory"
|
|
7259
|
+
]
|
|
7260
|
+
},
|
|
7261
|
+
"names": {
|
|
7262
|
+
"p10": 1,
|
|
7263
|
+
"p50": 1.8,
|
|
7264
|
+
"p90": 3,
|
|
7265
|
+
"sd": 0.816,
|
|
7266
|
+
"separates": [
|
|
7267
|
+
"institutional"
|
|
7268
|
+
]
|
|
7269
|
+
},
|
|
7270
|
+
"nominalisationRate": {
|
|
7271
|
+
"p10": 0.444,
|
|
7272
|
+
"p50": 1.334,
|
|
7273
|
+
"p90": 2.486,
|
|
7274
|
+
"sd": 0.817,
|
|
7275
|
+
"separates": [
|
|
7276
|
+
"institutional",
|
|
7277
|
+
"marketing"
|
|
7278
|
+
]
|
|
7279
|
+
},
|
|
7280
|
+
"participialRate": {
|
|
7281
|
+
"p10": 0,
|
|
7282
|
+
"p50": 0.067,
|
|
7283
|
+
"p90": 0.2,
|
|
7284
|
+
"sd": 0.089,
|
|
7285
|
+
"separates": [
|
|
7286
|
+
"business-email",
|
|
7287
|
+
"institutional"
|
|
7288
|
+
]
|
|
7289
|
+
},
|
|
7290
|
+
"pronounOpenerShare": {
|
|
7291
|
+
"p10": 0,
|
|
7292
|
+
"p50": 0.025,
|
|
7293
|
+
"p90": 0.12,
|
|
7294
|
+
"sd": 0.058,
|
|
7295
|
+
"separates": [
|
|
7296
|
+
"marketing"
|
|
7297
|
+
]
|
|
7298
|
+
},
|
|
7299
|
+
"sentenceCv": {
|
|
7300
|
+
"p10": 0.367,
|
|
7301
|
+
"p50": 0.485,
|
|
7302
|
+
"p90": 0.625,
|
|
7303
|
+
"sd": 0.1,
|
|
7304
|
+
"separates": [
|
|
7305
|
+
"marketing"
|
|
7306
|
+
]
|
|
7307
|
+
},
|
|
7308
|
+
"skeletonRepeat": {
|
|
7309
|
+
"p10": 5e-3,
|
|
7310
|
+
"p50": 0.029,
|
|
7311
|
+
"p90": 0.07,
|
|
7312
|
+
"sd": 0.028,
|
|
7313
|
+
"separates": []
|
|
7314
|
+
},
|
|
7315
|
+
"specificsPer100": {
|
|
7316
|
+
"p10": 1.8,
|
|
7317
|
+
"p50": 3.28,
|
|
7318
|
+
"p90": 5.24,
|
|
7319
|
+
"sd": 1.365,
|
|
7320
|
+
"separates": [
|
|
7321
|
+
"business-email",
|
|
7322
|
+
"institutional",
|
|
7323
|
+
"marketing"
|
|
7324
|
+
]
|
|
7325
|
+
},
|
|
7326
|
+
"tradePer100": {
|
|
7327
|
+
"p10": 0,
|
|
7328
|
+
"p50": 0,
|
|
7329
|
+
"p90": 0.508,
|
|
7330
|
+
"sd": 0.275,
|
|
7331
|
+
"separates": [
|
|
7332
|
+
"marketing"
|
|
7333
|
+
]
|
|
7334
|
+
},
|
|
7335
|
+
"tricolons": {
|
|
7336
|
+
"p10": 0,
|
|
7337
|
+
"p50": 0.4,
|
|
7338
|
+
"p90": 0.8,
|
|
7339
|
+
"sd": 0.314,
|
|
7340
|
+
"separates": [
|
|
7341
|
+
"business-email"
|
|
7342
|
+
]
|
|
7343
|
+
}
|
|
7344
|
+
},
|
|
7345
|
+
"marketing": {
|
|
7346
|
+
"clauseDepth": {
|
|
7347
|
+
"p10": 0.567,
|
|
7348
|
+
"p50": 0.967,
|
|
7349
|
+
"p90": 1.56,
|
|
7350
|
+
"sd": 0.413,
|
|
7351
|
+
"separates": [
|
|
7352
|
+
"literary"
|
|
7353
|
+
]
|
|
7354
|
+
},
|
|
7355
|
+
"commasPer100": {
|
|
7356
|
+
"p10": 2.169,
|
|
7357
|
+
"p50": 4.087,
|
|
7358
|
+
"p90": 6.434,
|
|
7359
|
+
"sd": 1.667,
|
|
7360
|
+
"separates": [
|
|
7361
|
+
"literary"
|
|
7362
|
+
]
|
|
7363
|
+
},
|
|
7364
|
+
"commasPerSentence": {
|
|
7365
|
+
"p10": 0.4,
|
|
7366
|
+
"p50": 0.8,
|
|
7367
|
+
"p90": 1.35,
|
|
7368
|
+
"sd": 0.402,
|
|
7369
|
+
"separates": [
|
|
7370
|
+
"business-email",
|
|
7371
|
+
"literary"
|
|
7372
|
+
]
|
|
7373
|
+
},
|
|
7374
|
+
"coordinationRate": {
|
|
7375
|
+
"p10": 1.087,
|
|
7376
|
+
"p50": 2.236,
|
|
7377
|
+
"p90": 3.473,
|
|
7378
|
+
"sd": 0.931,
|
|
7379
|
+
"separates": [
|
|
7380
|
+
"business-email",
|
|
7381
|
+
"explanatory",
|
|
7382
|
+
"institutional",
|
|
7383
|
+
"literary"
|
|
7384
|
+
]
|
|
7385
|
+
},
|
|
7386
|
+
"figures": {
|
|
7387
|
+
"p10": 0,
|
|
7388
|
+
"p50": 0.4,
|
|
7389
|
+
"p90": 1,
|
|
7390
|
+
"sd": 0.427,
|
|
7391
|
+
"separates": [
|
|
7392
|
+
"literary"
|
|
7393
|
+
]
|
|
7394
|
+
},
|
|
7395
|
+
"generic": {
|
|
7396
|
+
"p10": 0,
|
|
7397
|
+
"p50": 0.2,
|
|
7398
|
+
"p90": 0.6,
|
|
7399
|
+
"sd": 0.206,
|
|
7400
|
+
"separates": [
|
|
7401
|
+
"institutional"
|
|
7402
|
+
]
|
|
7403
|
+
},
|
|
7404
|
+
"hardPer100": {
|
|
7405
|
+
"p10": 1.899,
|
|
7406
|
+
"p50": 4.549,
|
|
7407
|
+
"p90": 8.227,
|
|
7408
|
+
"sd": 2.655,
|
|
7409
|
+
"separates": [
|
|
7410
|
+
"explanatory"
|
|
7411
|
+
]
|
|
7412
|
+
},
|
|
7413
|
+
"impersonalOpenerShare": {
|
|
7414
|
+
"p10": 0,
|
|
7415
|
+
"p50": 0,
|
|
7416
|
+
"p90": 0.1,
|
|
7417
|
+
"sd": 0.047,
|
|
7418
|
+
"separates": []
|
|
7419
|
+
},
|
|
7420
|
+
"names": {
|
|
7421
|
+
"p10": 0.6,
|
|
7422
|
+
"p50": 1.8,
|
|
7423
|
+
"p90": 3.4,
|
|
7424
|
+
"sd": 1.21,
|
|
7425
|
+
"separates": [
|
|
7426
|
+
"institutional"
|
|
7427
|
+
]
|
|
7428
|
+
},
|
|
7429
|
+
"nominalisationRate": {
|
|
7430
|
+
"p10": 1.576,
|
|
7431
|
+
"p50": 3.222,
|
|
7432
|
+
"p90": 5.534,
|
|
7433
|
+
"sd": 1.6,
|
|
7434
|
+
"separates": [
|
|
7435
|
+
"business-email",
|
|
7436
|
+
"explanatory",
|
|
7437
|
+
"literary"
|
|
7438
|
+
]
|
|
7439
|
+
},
|
|
7440
|
+
"participialRate": {
|
|
7441
|
+
"p10": 0,
|
|
7442
|
+
"p50": 0,
|
|
7443
|
+
"p90": 0.133,
|
|
7444
|
+
"sd": 0.062,
|
|
7445
|
+
"separates": []
|
|
7446
|
+
},
|
|
7447
|
+
"pronounOpenerShare": {
|
|
7448
|
+
"p10": 0.067,
|
|
7449
|
+
"p50": 0.213,
|
|
7450
|
+
"p90": 0.4,
|
|
7451
|
+
"sd": 0.136,
|
|
7452
|
+
"separates": [
|
|
7453
|
+
"business-email",
|
|
7454
|
+
"explanatory",
|
|
7455
|
+
"institutional",
|
|
7456
|
+
"literary"
|
|
7457
|
+
]
|
|
7458
|
+
},
|
|
7459
|
+
"sentenceCv": {
|
|
7460
|
+
"p10": 0.275,
|
|
7461
|
+
"p50": 0.377,
|
|
7462
|
+
"p90": 0.52,
|
|
7463
|
+
"sd": 0.096,
|
|
7464
|
+
"separates": [
|
|
7465
|
+
"business-email",
|
|
7466
|
+
"institutional",
|
|
7467
|
+
"literary"
|
|
7468
|
+
]
|
|
7469
|
+
},
|
|
7470
|
+
"skeletonRepeat": {
|
|
7471
|
+
"p10": 0,
|
|
7472
|
+
"p50": 0.031,
|
|
7473
|
+
"p90": 0.081,
|
|
7474
|
+
"sd": 0.038,
|
|
7475
|
+
"separates": []
|
|
7476
|
+
},
|
|
7477
|
+
"specificsPer100": {
|
|
7478
|
+
"p10": 2.54,
|
|
7479
|
+
"p50": 5.2,
|
|
7480
|
+
"p90": 9.18,
|
|
7481
|
+
"sd": 2.733,
|
|
7482
|
+
"separates": [
|
|
7483
|
+
"explanatory",
|
|
7484
|
+
"literary"
|
|
7485
|
+
]
|
|
7486
|
+
},
|
|
7487
|
+
"tradePer100": {
|
|
7488
|
+
"p10": 0,
|
|
7489
|
+
"p50": 0.592,
|
|
7490
|
+
"p90": 1.515,
|
|
7491
|
+
"sd": 0.612,
|
|
7492
|
+
"separates": [
|
|
7493
|
+
"business-email",
|
|
7494
|
+
"explanatory",
|
|
7495
|
+
"institutional",
|
|
7496
|
+
"literary"
|
|
7497
|
+
]
|
|
7498
|
+
},
|
|
7499
|
+
"tricolons": {
|
|
7500
|
+
"p10": 0,
|
|
7501
|
+
"p50": 0.4,
|
|
7502
|
+
"p90": 0.8,
|
|
7503
|
+
"sd": 0.309,
|
|
7504
|
+
"separates": [
|
|
7505
|
+
"business-email"
|
|
7506
|
+
]
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
};
|
|
7510
|
+
|
|
6164
7511
|
// src/character/baseline.ts
|
|
6165
7512
|
var BASELINE_VERSION = 1;
|
|
6166
7513
|
function fnv1a(text) {
|
|
@@ -6261,23 +7608,23 @@ function parseCraftConfig(data, knownTells, source = "craft.config.json") {
|
|
|
6261
7608
|
return { ignore, severity, ...data.copyPaths ? { copyPaths: data.copyPaths } : {} };
|
|
6262
7609
|
}
|
|
6263
7610
|
function globToRegExp(glob) {
|
|
6264
|
-
const
|
|
7611
|
+
const pattern2 = glob.replace(/^\.\//, "").replace(/\/$/, "").replace(/\/\*\*$/, "");
|
|
6265
7612
|
let body = "";
|
|
6266
|
-
for (let i = 0; i <
|
|
6267
|
-
const c =
|
|
6268
|
-
if (c === "*" &&
|
|
6269
|
-
const slash =
|
|
7613
|
+
for (let i = 0; i < pattern2.length; i += 1) {
|
|
7614
|
+
const c = pattern2[i];
|
|
7615
|
+
if (c === "*" && pattern2[i + 1] === "*") {
|
|
7616
|
+
const slash = pattern2[i + 2] === "/";
|
|
6270
7617
|
body += slash ? "(?:.*/)?" : ".*";
|
|
6271
7618
|
i += slash ? 2 : 1;
|
|
6272
7619
|
} else if (c === "*") body += "[^/]*";
|
|
6273
7620
|
else if (c === "?") body += "[^/]";
|
|
6274
7621
|
else body += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6275
7622
|
}
|
|
6276
|
-
const anywhere = !
|
|
7623
|
+
const anywhere = !pattern2.includes("/");
|
|
6277
7624
|
return new RegExp(`^${anywhere ? "(?:.*/)?" : ""}${body}(?:/.*)?$`);
|
|
6278
7625
|
}
|
|
6279
|
-
function ignoreMatcher(
|
|
6280
|
-
const res =
|
|
7626
|
+
function ignoreMatcher(patterns2) {
|
|
7627
|
+
const res = patterns2.map(globToRegExp);
|
|
6281
7628
|
return (path) => {
|
|
6282
7629
|
const p = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6283
7630
|
return res.some((re) => re.test(p));
|
|
@@ -6444,11 +7791,11 @@ function visualMeasures(img, maxSamples = 1e6) {
|
|
|
6444
7791
|
const sdRg = Math.sqrt(Math.max(0, sumRg2 / n - meanRg * meanRg));
|
|
6445
7792
|
const sdYb = Math.sqrt(Math.max(0, sumYb2 / n - meanYb * meanYb));
|
|
6446
7793
|
const colourfulness = Math.sqrt(sdRg ** 2 + sdYb ** 2) + 0.3 * Math.sqrt(meanRg ** 2 + meanYb ** 2);
|
|
6447
|
-
const
|
|
7794
|
+
const round3 = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
|
|
6448
7795
|
return {
|
|
6449
|
-
colourfulness:
|
|
6450
|
-
edgeDensity:
|
|
6451
|
-
symmetry:
|
|
7796
|
+
colourfulness: round3(colourfulness, 1),
|
|
7797
|
+
edgeDensity: round3(edgeN ? edges / edgeN : 0),
|
|
7798
|
+
symmetry: round3(asymN ? 1 - asym / asymN / 255 : 1),
|
|
6452
7799
|
width: w,
|
|
6453
7800
|
height: h,
|
|
6454
7801
|
sampled: n
|
|
@@ -7312,62 +8659,6 @@ function propose(input) {
|
|
|
7312
8659
|
};
|
|
7313
8660
|
}
|
|
7314
8661
|
|
|
7315
|
-
// src/estate/index.ts
|
|
7316
|
-
var ESTATE_VERSION = 1;
|
|
7317
|
-
var SIBLING_AT = 0.31;
|
|
7318
|
-
function emptyEstate() {
|
|
7319
|
-
return { version: ESTATE_VERSION, sites: [] };
|
|
7320
|
-
}
|
|
7321
|
-
function addSite(register, site) {
|
|
7322
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
7323
|
-
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
7324
|
-
return { version: ESTATE_VERSION, sites };
|
|
7325
|
-
}
|
|
7326
|
-
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
7327
|
-
const out = [];
|
|
7328
|
-
const close = (k) => d.parts[k] <= SHARED_PART;
|
|
7329
|
-
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
7330
|
-
if (close("type")) {
|
|
7331
|
-
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
7332
|
-
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
7333
|
-
}
|
|
7334
|
-
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
7335
|
-
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
7336
|
-
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
7337
|
-
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
7338
|
-
if (close("layout")) out.push("running order");
|
|
7339
|
-
return out;
|
|
7340
|
-
}
|
|
7341
|
-
function compareToEstate(target2, register, options = {}) {
|
|
7342
|
-
const at = options.siblingAt ?? SIBLING_AT;
|
|
7343
|
-
return register.sites.filter((s) => s.id !== options.exclude && s.fingerprint).map((s) => {
|
|
7344
|
-
const d = fingerprintDistance(target2, s.fingerprint);
|
|
7345
|
-
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
7346
|
-
}).sort((a, b) => a.distance - b.distance);
|
|
7347
|
-
}
|
|
7348
|
-
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
7349
|
-
const out = [];
|
|
7350
|
-
const s = register.sites.filter((site) => site.fingerprint);
|
|
7351
|
-
for (let i = 0; i < s.length; i += 1) {
|
|
7352
|
-
for (let j = i + 1; j < s.length; j += 1) {
|
|
7353
|
-
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
7354
|
-
out.push({ a: s[i].id, b: s[j].id, distance: d.total, sibling: d.total < siblingAt, shared: sharedParts(s[i].fingerprint, s[j].fingerprint, d) });
|
|
7355
|
-
}
|
|
7356
|
-
}
|
|
7357
|
-
return out.sort((x, y) => x.distance - y.distance);
|
|
7358
|
-
}
|
|
7359
|
-
function siblingLine(briefs) {
|
|
7360
|
-
const d = [];
|
|
7361
|
-
for (const runs of briefs) {
|
|
7362
|
-
for (let i = 0; i < runs.length; i += 1) for (let j = i + 1; j < runs.length; j += 1) d.push(fingerprintDistance(runs[i], runs[j]).total);
|
|
7363
|
-
}
|
|
7364
|
-
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
7365
|
-
d.sort((a, b) => a - b);
|
|
7366
|
-
const mid = Math.floor(d.length / 2);
|
|
7367
|
-
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
7368
|
-
return Math.round(m * 1e3) / 1e3;
|
|
7369
|
-
}
|
|
7370
|
-
|
|
7371
8662
|
// src/report/index.ts
|
|
7372
8663
|
var TELL_HEAVY = 3;
|
|
7373
8664
|
var MIN_DECIDED = 5;
|
|
@@ -7523,6 +8814,155 @@ function choiceOfShared(part) {
|
|
|
7523
8814
|
return /running order/.test(part) ? void 0 : "motif";
|
|
7524
8815
|
}
|
|
7525
8816
|
|
|
8817
|
+
// src/calibrate/index.ts
|
|
8818
|
+
var LABELS2 = ["ai", "ai-looking", "human"];
|
|
8819
|
+
var FLAGGED_TARGET = 0.9;
|
|
8820
|
+
var HUMAN_TYPICAL_TARGET = 0.1;
|
|
8821
|
+
var SURFACE = new Map(CATALOGUE.map((t) => [t.id, t.surface]));
|
|
8822
|
+
var GENERATION = new Map(CATALOGUE.map((t) => [t.id, t.generation]));
|
|
8823
|
+
function designTells(tells) {
|
|
8824
|
+
return tells.filter((t) => SURFACE.has(t) && SURFACE.get(t) !== "copy");
|
|
8825
|
+
}
|
|
8826
|
+
function isFlagged(page) {
|
|
8827
|
+
return Boolean(page.typicality?.typical) || designTells(page.tells).length >= TELL_HEAVY;
|
|
8828
|
+
}
|
|
8829
|
+
var positive = (label) => label !== "human";
|
|
8830
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
8831
|
+
var share = (n, of) => `${n} of ${of} (${Math.round(100 * n / of)}%)`;
|
|
8832
|
+
function summarise(label, pages, typicality2) {
|
|
8833
|
+
const mine = pages.filter((p) => p.label === label);
|
|
8834
|
+
return {
|
|
8835
|
+
label,
|
|
8836
|
+
pages: mine.length,
|
|
8837
|
+
// A set measured partly against the null would count the same page two ways.
|
|
8838
|
+
flagged: mine.filter((p) => typicality2 ? isFlagged(p) : designTells(p.tells).length >= TELL_HEAVY).length,
|
|
8839
|
+
typical: typicality2 ? mine.filter((p) => p.typicality?.typical).length : null,
|
|
8840
|
+
tellHeavy: mine.filter((p) => designTells(p.tells).length >= TELL_HEAVY).length,
|
|
8841
|
+
blocking: mine.reduce((s, p) => s + p.blocking, 0)
|
|
8842
|
+
};
|
|
8843
|
+
}
|
|
8844
|
+
function flaggedTarget(target2, s, missing) {
|
|
8845
|
+
const note = missing ? `; ${missing} not measured, counted neither way` : "";
|
|
8846
|
+
if (s.pages === 0) return { target: target2, met: null, detail: `no page measured${note}` };
|
|
8847
|
+
return { target: target2, met: s.flagged / s.pages >= FLAGGED_TARGET, detail: `${share(s.flagged, s.pages)} flagged, target ${FLAGGED_TARGET * 100}% or more${note}` };
|
|
8848
|
+
}
|
|
8849
|
+
function calibrate(pages, unmeasured = []) {
|
|
8850
|
+
const typicality2 = pages.length > 0 && pages.every((p) => p.typicality !== void 0);
|
|
8851
|
+
const labels = LABELS2.map((l) => summarise(l, pages, typicality2));
|
|
8852
|
+
const byLabel = Object.fromEntries(labels.map((s) => [s.label, s]));
|
|
8853
|
+
const missing = (l) => unmeasured.filter((u) => u.label === l).length;
|
|
8854
|
+
const positives = pages.filter((p) => positive(p.label)).length;
|
|
8855
|
+
const fired = new Set(pages.flatMap((p) => p.tells));
|
|
8856
|
+
const tells = [...fired].map((tell) => {
|
|
8857
|
+
const hits = Object.fromEntries(LABELS2.map((l) => [l, pages.filter((p) => p.label === l && p.tells.includes(tell)).length]));
|
|
8858
|
+
const on = hits.ai + hits["ai-looking"];
|
|
8859
|
+
return { tell, generation: GENERATION.get(tell) ?? null, hits, precision: round2(on / (on + hits.human)), recall: positives ? round2(on / positives) : null };
|
|
8860
|
+
}).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));
|
|
8861
|
+
const human = byLabel.human;
|
|
8862
|
+
const humanNote = missing("human") ? `; ${missing("human")} not measured, counted neither way` : "";
|
|
8863
|
+
const targets = [
|
|
8864
|
+
flaggedTarget("1. Generated pages get caught", byLabel.ai, missing("ai")),
|
|
8865
|
+
human.pages === 0 ? { target: "2. Good human sites don't get caught", met: null, detail: `no page measured${humanNote}` } : {
|
|
8866
|
+
target: "2. Good human sites don't get caught",
|
|
8867
|
+
// A block hit fails it outright; without null models the other half is not known.
|
|
8868
|
+
met: human.blocking > 0 ? false : human.typical === null ? null : human.typical / human.pages <= HUMAN_TYPICAL_TARGET,
|
|
8869
|
+
detail: `${human.blocking} block hits, target 0; ${human.typical === null ? "typicality not measured (no null models)" : `${share(human.typical, human.pages)} typical, target ${HUMAN_TYPICAL_TARGET * 100}% or fewer`}${humanNote}`
|
|
8870
|
+
},
|
|
8871
|
+
flaggedTarget("3. Sites a person labelled AI-looking get caught", byLabel["ai-looking"], missing("ai-looking"))
|
|
8872
|
+
];
|
|
8873
|
+
return {
|
|
8874
|
+
pages: pages.length,
|
|
8875
|
+
typicality: typicality2,
|
|
8876
|
+
labels,
|
|
8877
|
+
tells,
|
|
8878
|
+
silent: CATALOGUE.map((t) => t.id).filter((id) => !fired.has(id)),
|
|
8879
|
+
targets,
|
|
8880
|
+
unmeasured
|
|
8881
|
+
};
|
|
8882
|
+
}
|
|
8883
|
+
function formatCalibration(c, notes = []) {
|
|
8884
|
+
const counts = c.labels.map((s) => `${s.pages} ${s.label}`).join(", ");
|
|
8885
|
+
const lines = [`craft calibrate: ${c.pages} pages measured (${counts})`, ...notes];
|
|
8886
|
+
if (!c.typicality) lines.push("No null models: flagged means tell-heavy only.");
|
|
8887
|
+
if (c.unmeasured.length) {
|
|
8888
|
+
lines.push("", `Not measured: ${c.unmeasured.length}. A page not measured never counts as a pass.`);
|
|
8889
|
+
for (const u of c.unmeasured) lines.push(` ${u.id} (${u.label}): ${u.reason}`);
|
|
8890
|
+
}
|
|
8891
|
+
lines.push("", "Flagged is a look-closer signal, not a verdict that a site is AI-made. Nothing gates on it.", "", "Targets");
|
|
8892
|
+
for (const t of c.targets) lines.push(` ${t.target.padEnd(50)} ${t.met === null ? "not known" : t.met ? "met" : "NOT MET"}`, ` ${t.detail}`);
|
|
8893
|
+
lines.push("", ` ${"label".padEnd(11)} ${"pages".padStart(5)} ${"flagged".padStart(8)} ${"typical".padStart(8)} ${"tell-heavy".padStart(11)} ${"blocking".padStart(9)}`);
|
|
8894
|
+
for (const s of c.labels) {
|
|
8895
|
+
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)}`);
|
|
8896
|
+
}
|
|
8897
|
+
lines.push("", "Per tell. A hit on ai or ai-looking counts for the tell; a hit on human counts against it.");
|
|
8898
|
+
lines.push(` ${"tell".padEnd(28)} ${"gen".padStart(3)} ${"ai".padStart(7)} ${"ai-looking".padStart(10)} ${"human".padStart(7)} ${"precision".padStart(9)} ${"recall".padStart(7)}`);
|
|
8899
|
+
const of = (l) => c.labels.find((s) => s.label === l)?.pages ?? 0;
|
|
8900
|
+
for (const t of c.tells) {
|
|
8901
|
+
lines.push(
|
|
8902
|
+
` ${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)}`
|
|
8903
|
+
);
|
|
8904
|
+
}
|
|
8905
|
+
if (!c.tells.length) lines.push(" no tell fired");
|
|
8906
|
+
lines.push("", `Never fired on this set: ${c.silent.length} of ${CATALOGUE.length} catalogue tells.`);
|
|
8907
|
+
return lines.join("\n");
|
|
8908
|
+
}
|
|
8909
|
+
|
|
8910
|
+
// src/estate/index.ts
|
|
8911
|
+
var ESTATE_VERSION = 1;
|
|
8912
|
+
var SIBLING_AT = 0.31;
|
|
8913
|
+
function emptyEstate() {
|
|
8914
|
+
return { version: ESTATE_VERSION, sites: [] };
|
|
8915
|
+
}
|
|
8916
|
+
function addSite(register, site) {
|
|
8917
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(site.id)) throw new Error(`site id "${site.id}" must be short kebab-case`);
|
|
8918
|
+
const sites = [...register.sites.filter((s) => s.id !== site.id), site].sort((a, b) => a.id.localeCompare(b.id));
|
|
8919
|
+
return { version: ESTATE_VERSION, sites };
|
|
8920
|
+
}
|
|
8921
|
+
function sharedParts(a, b, d = fingerprintDistance(a, b)) {
|
|
8922
|
+
const out = [];
|
|
8923
|
+
const close = (k) => d.parts[k] <= SHARED_PART;
|
|
8924
|
+
if (close("accent")) out.push(a.accent || b.accent ? `${hueName(a.accent)} accent` : "no accent");
|
|
8925
|
+
if (close("type")) {
|
|
8926
|
+
if (a.display.family === b.display.family) out.push(`${a.display.family} headline`);
|
|
8927
|
+
if (a.body.family === b.body.family) out.push(`${a.body.family} body`);
|
|
8928
|
+
}
|
|
8929
|
+
if (close("ground")) out.push(`${groundName(a.ground)} ground`);
|
|
8930
|
+
if (close("shape")) out.push(`${shapeName(a.roundness)} buttons`);
|
|
8931
|
+
if (close("motion")) out.push(a.motion > 0 || b.motion > 0 ? "scroll reveals" : "no reveals");
|
|
8932
|
+
if (close("effects") && a.effects.length) out.push(a.effects.filter((e) => b.effects.includes(e)).join(", "));
|
|
8933
|
+
if (close("layout")) out.push("running order");
|
|
8934
|
+
return out;
|
|
8935
|
+
}
|
|
8936
|
+
function compareToEstate(target2, register, options = {}) {
|
|
8937
|
+
const at = options.siblingAt ?? SIBLING_AT;
|
|
8938
|
+
return register.sites.filter((s) => s.id !== options.exclude && s.fingerprint).map((s) => {
|
|
8939
|
+
const d = fingerprintDistance(target2, s.fingerprint);
|
|
8940
|
+
return { id: s.id, distance: d.total, sibling: d.total < at, shared: sharedParts(target2, s.fingerprint, d) };
|
|
8941
|
+
}).sort((a, b) => a.distance - b.distance);
|
|
8942
|
+
}
|
|
8943
|
+
function estatePairs(register, siblingAt = SIBLING_AT) {
|
|
8944
|
+
const out = [];
|
|
8945
|
+
const s = register.sites.filter((site) => site.fingerprint);
|
|
8946
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
8947
|
+
for (let j = i + 1; j < s.length; j += 1) {
|
|
8948
|
+
const d = fingerprintDistance(s[i].fingerprint, s[j].fingerprint);
|
|
8949
|
+
out.push({ a: s[i].id, b: s[j].id, distance: d.total, sibling: d.total < siblingAt, shared: sharedParts(s[i].fingerprint, s[j].fingerprint, d) });
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8952
|
+
return out.sort((x, y) => x.distance - y.distance);
|
|
8953
|
+
}
|
|
8954
|
+
function siblingLine(briefs) {
|
|
8955
|
+
const d = [];
|
|
8956
|
+
for (const runs of briefs) {
|
|
8957
|
+
for (let i = 0; i < runs.length; i += 1) for (let j = i + 1; j < runs.length; j += 1) d.push(fingerprintDistance(runs[i], runs[j]).total);
|
|
8958
|
+
}
|
|
8959
|
+
if (d.length === 0) throw new Error("no null pages to measure the sibling line from");
|
|
8960
|
+
d.sort((a, b) => a - b);
|
|
8961
|
+
const mid = Math.floor(d.length / 2);
|
|
8962
|
+
const m = d.length % 2 ? d[mid] : (d[mid - 1] + d[mid]) / 2;
|
|
8963
|
+
return Math.round(m * 1e3) / 1e3;
|
|
8964
|
+
}
|
|
8965
|
+
|
|
7526
8966
|
// src/report/retrofit.ts
|
|
7527
8967
|
var HEADINGS = {
|
|
7528
8968
|
direction: "Decide first",
|
|
@@ -7588,6 +9028,7 @@ export {
|
|
|
7588
9028
|
CHOICE_KEYS,
|
|
7589
9029
|
COMPONENT_ROLES,
|
|
7590
9030
|
CONSIDERATION,
|
|
9031
|
+
CONTENT_FEATURES,
|
|
7591
9032
|
CONVENTION,
|
|
7592
9033
|
CUSTOMER_KINDS,
|
|
7593
9034
|
DEFAULT_IGNORE,
|
|
@@ -7604,6 +9045,7 @@ export {
|
|
|
7604
9045
|
ESTATE_VERSION,
|
|
7605
9046
|
EXIT_RATIO,
|
|
7606
9047
|
FINGERPRINT_VERSION,
|
|
9048
|
+
FLAGGED_TARGET,
|
|
7607
9049
|
FUNCTION_WORDS,
|
|
7608
9050
|
GROUND_SCALE,
|
|
7609
9051
|
GROUND_SHARED,
|
|
@@ -7614,11 +9056,15 @@ export {
|
|
|
7614
9056
|
HOUSE_BUDGET,
|
|
7615
9057
|
HOUSE_LOCKED,
|
|
7616
9058
|
HOUSE_TYPE,
|
|
9059
|
+
HUMAN_TYPICAL_TARGET,
|
|
7617
9060
|
IMAGERY_TELLS,
|
|
7618
9061
|
JSON_SCHEMA_VERSION,
|
|
9062
|
+
LABELS2 as LABELS,
|
|
9063
|
+
LEXICAL_FEATURES,
|
|
7619
9064
|
LICENCES,
|
|
7620
9065
|
LIGHTNESS_CURVE,
|
|
7621
9066
|
MIN_DECIDED,
|
|
9067
|
+
MIN_PARAGRAPHS,
|
|
7622
9068
|
MIN_RUNS,
|
|
7623
9069
|
MOOD,
|
|
7624
9070
|
NEGATIVE_REASSURANCE,
|
|
@@ -7627,6 +9073,9 @@ export {
|
|
|
7627
9073
|
PILL_LIMIT,
|
|
7628
9074
|
PLAINER_WORDS,
|
|
7629
9075
|
PREFERENCE,
|
|
9076
|
+
PROFILE,
|
|
9077
|
+
PROFILE_MEASURED,
|
|
9078
|
+
PROFILE_PARAGRAPHS,
|
|
7630
9079
|
PROMO_BADGE,
|
|
7631
9080
|
PROOF_NAMES,
|
|
7632
9081
|
PROPOSED,
|
|
@@ -7635,6 +9084,10 @@ export {
|
|
|
7635
9084
|
READABLE_SNAPSHOT_VERSIONS,
|
|
7636
9085
|
REFLEX_FONTS_1,
|
|
7637
9086
|
REFLEX_FONTS_2,
|
|
9087
|
+
REGISTERS,
|
|
9088
|
+
REGISTER_BASELINE,
|
|
9089
|
+
REGISTER_FEATURES,
|
|
9090
|
+
REGISTER_IDS,
|
|
7638
9091
|
RENDERED_PATHS,
|
|
7639
9092
|
RENDERED_TELLS,
|
|
7640
9093
|
REVEAL_LIMIT,
|
|
@@ -7656,6 +9109,7 @@ export {
|
|
|
7656
9109
|
STOCK_FIGURES,
|
|
7657
9110
|
STOCK_PHOTO_HOSTS,
|
|
7658
9111
|
STOCK_PHRASES,
|
|
9112
|
+
STUDY_WORDS,
|
|
7659
9113
|
TELL_HEAVY,
|
|
7660
9114
|
TRADE_NOUNS,
|
|
7661
9115
|
TYPE_STEPS,
|
|
@@ -7669,12 +9123,14 @@ export {
|
|
|
7669
9123
|
applySeverity,
|
|
7670
9124
|
auditSnapshot,
|
|
7671
9125
|
briefTerms,
|
|
9126
|
+
calibrate,
|
|
7672
9127
|
catalogueTable,
|
|
7673
9128
|
characterReport,
|
|
7674
9129
|
checkCopy,
|
|
7675
9130
|
checkOrder,
|
|
7676
9131
|
checkPair,
|
|
7677
9132
|
checkReason,
|
|
9133
|
+
checkRegister,
|
|
7678
9134
|
checkRestraint,
|
|
7679
9135
|
choicesOf,
|
|
7680
9136
|
clauseDepth,
|
|
@@ -7698,6 +9154,8 @@ export {
|
|
|
7698
9154
|
deltaEOkHex,
|
|
7699
9155
|
densityCss,
|
|
7700
9156
|
densityTokens,
|
|
9157
|
+
designTells,
|
|
9158
|
+
documentParagraphs,
|
|
7701
9159
|
emitCss,
|
|
7702
9160
|
emptyEstate,
|
|
7703
9161
|
estateComponentPairs,
|
|
@@ -7705,6 +9163,7 @@ export {
|
|
|
7705
9163
|
exitDuration,
|
|
7706
9164
|
extractHtml,
|
|
7707
9165
|
faceLicence,
|
|
9166
|
+
featureLabel,
|
|
7708
9167
|
findClaims,
|
|
7709
9168
|
findColours,
|
|
7710
9169
|
findingKey,
|
|
@@ -7714,12 +9173,14 @@ export {
|
|
|
7714
9173
|
fluidSpace,
|
|
7715
9174
|
fluidType,
|
|
7716
9175
|
fontClass,
|
|
9176
|
+
formatCalibration,
|
|
7717
9177
|
formatClaims,
|
|
7718
9178
|
formatComparison,
|
|
7719
9179
|
formatCompetitor,
|
|
7720
9180
|
formatDirectionMatches,
|
|
7721
9181
|
formatHex,
|
|
7722
9182
|
formatReport,
|
|
9183
|
+
getRegister,
|
|
7723
9184
|
globToRegExp,
|
|
7724
9185
|
groundName,
|
|
7725
9186
|
harvest,
|
|
@@ -7738,8 +9199,10 @@ export {
|
|
|
7738
9199
|
isBlurb,
|
|
7739
9200
|
isCategoryLabel,
|
|
7740
9201
|
isCream,
|
|
9202
|
+
isFlagged,
|
|
7741
9203
|
jobWords,
|
|
7742
9204
|
layoutDistance,
|
|
9205
|
+
lexicalOf,
|
|
7743
9206
|
licencesJson,
|
|
7744
9207
|
makeSnapshot,
|
|
7745
9208
|
motionTokens,
|
|
@@ -7764,6 +9227,9 @@ export {
|
|
|
7764
9227
|
ramp,
|
|
7765
9228
|
rampFromAnchors,
|
|
7766
9229
|
readSnapshot,
|
|
9230
|
+
registerBrief,
|
|
9231
|
+
registerOf,
|
|
9232
|
+
registersDoc,
|
|
7767
9233
|
renderedOrder,
|
|
7768
9234
|
researchPrompt,
|
|
7769
9235
|
retrofitPlan,
|
|
@@ -7806,6 +9272,8 @@ export {
|
|
|
7806
9272
|
validateJob,
|
|
7807
9273
|
visibleText,
|
|
7808
9274
|
visualMeasures,
|
|
9275
|
+
vocabulary,
|
|
9276
|
+
vocabularyCounts,
|
|
7809
9277
|
wcagContrast,
|
|
7810
9278
|
wordListsJson
|
|
7811
9279
|
};
|