cbrowser 20.2.2 → 21.0.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.
Files changed (302) hide show
  1. package/README.md +2 -2
  2. package/dist/analysis/accessibility-empathy.d.ts +29 -1
  3. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  4. package/dist/analysis/accessibility-empathy.js +69 -35
  5. package/dist/analysis/accessibility-empathy.js.map +1 -1
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +11 -7
  8. package/dist/browser.js.map +1 -1
  9. package/dist/cif-score.d.ts +56 -5
  10. package/dist/cif-score.d.ts.map +1 -1
  11. package/dist/cif-score.js +595 -346
  12. package/dist/cif-score.js.map +1 -1
  13. package/dist/cli.js +10 -8
  14. package/dist/cli.js.map +1 -1
  15. package/dist/cognitive/default-traits.d.ts +14 -0
  16. package/dist/cognitive/default-traits.d.ts.map +1 -0
  17. package/dist/cognitive/default-traits.js +36 -0
  18. package/dist/cognitive/default-traits.js.map +1 -0
  19. package/dist/cognitive/emotions.d.ts.map +1 -1
  20. package/dist/cognitive/emotions.js +104 -174
  21. package/dist/cognitive/emotions.js.map +1 -1
  22. package/dist/cognitive/index.d.ts +28 -0
  23. package/dist/cognitive/index.d.ts.map +1 -1
  24. package/dist/cognitive/index.js +353 -287
  25. package/dist/cognitive/index.js.map +1 -1
  26. package/dist/cognitive/temporal.d.ts +0 -24
  27. package/dist/cognitive/temporal.d.ts.map +1 -1
  28. package/dist/cognitive/temporal.js +45 -35
  29. package/dist/cognitive/temporal.js.map +1 -1
  30. package/dist/data/ctc-reference-v2-2026-10-24.d.ts +16 -0
  31. package/dist/data/ctc-reference-v2-2026-10-24.d.ts.map +1 -0
  32. package/dist/data/ctc-reference-v2-2026-10-24.js +1004 -0
  33. package/dist/data/ctc-reference-v2-2026-10-24.js.map +1 -0
  34. package/dist/ideal-profiles-gap.d.ts +1 -0
  35. package/dist/ideal-profiles-gap.d.ts.map +1 -1
  36. package/dist/ideal-profiles-gap.js +9 -1
  37. package/dist/ideal-profiles-gap.js.map +1 -1
  38. package/dist/ideal-profiles.d.ts +8 -0
  39. package/dist/ideal-profiles.d.ts.map +1 -1
  40. package/dist/ideal-profiles.js +28 -24
  41. package/dist/ideal-profiles.js.map +1 -1
  42. package/dist/mcp-server-remote.d.ts.map +1 -1
  43. package/dist/mcp-server-remote.js +7 -8
  44. package/dist/mcp-server-remote.js.map +1 -1
  45. package/dist/mcp-tools/account-personas.d.ts.map +1 -1
  46. package/dist/mcp-tools/account-personas.js +3 -1
  47. package/dist/mcp-tools/account-personas.js.map +1 -1
  48. package/dist/mcp-tools/base/answer-provenance.d.ts +28 -0
  49. package/dist/mcp-tools/base/answer-provenance.d.ts.map +1 -0
  50. package/dist/mcp-tools/base/answer-provenance.js +32 -0
  51. package/dist/mcp-tools/base/answer-provenance.js.map +1 -0
  52. package/dist/mcp-tools/base/audit-tools.d.ts +0 -3
  53. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  54. package/dist/mcp-tools/base/audit-tools.js +152 -84
  55. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  56. package/dist/mcp-tools/base/capture-tools.js +1 -1
  57. package/dist/mcp-tools/base/capture-tools.js.map +1 -1
  58. package/dist/mcp-tools/base/chain-values.d.ts +38 -0
  59. package/dist/mcp-tools/base/chain-values.d.ts.map +1 -0
  60. package/dist/mcp-tools/base/chain-values.js +85 -0
  61. package/dist/mcp-tools/base/chain-values.js.map +1 -0
  62. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  63. package/dist/mcp-tools/base/cognitive-tools.js +126 -120
  64. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  65. package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
  66. package/dist/mcp-tools/base/gif-tools.js +23 -5
  67. package/dist/mcp-tools/base/gif-tools.js.map +1 -1
  68. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  69. package/dist/mcp-tools/base/persona-comparison-tools.js +229 -172
  70. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  71. package/dist/mcp-tools/base/site-familiarity.d.ts +31 -0
  72. package/dist/mcp-tools/base/site-familiarity.d.ts.map +1 -0
  73. package/dist/mcp-tools/base/site-familiarity.js +30 -0
  74. package/dist/mcp-tools/base/site-familiarity.js.map +1 -0
  75. package/dist/mcp-tools/base/site-knowledge-tools.js +1 -1
  76. package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
  77. package/dist/mcp-tools/base/story-narrative.d.ts +50 -0
  78. package/dist/mcp-tools/base/story-narrative.d.ts.map +1 -0
  79. package/dist/mcp-tools/base/story-narrative.js +88 -0
  80. package/dist/mcp-tools/base/story-narrative.js.map +1 -0
  81. package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
  82. package/dist/mcp-tools/base/values-tools.js +19 -5
  83. package/dist/mcp-tools/base/values-tools.js.map +1 -1
  84. package/dist/mcp-tools/base/visual-testing-tools.d.ts +5 -5
  85. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  86. package/dist/mcp-tools/base/visual-testing-tools.js +38 -11
  87. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  88. package/dist/mcp-tools/persona-creation-tools.js +6 -6
  89. package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
  90. package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
  91. package/dist/mcp-tools/persona-lifecycle-tools.js +5 -1
  92. package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
  93. package/dist/mcp-tools/tier-gate.js +1 -1
  94. package/dist/mcp-tools/tool-result-saver.d.ts +7 -0
  95. package/dist/mcp-tools/tool-result-saver.d.ts.map +1 -1
  96. package/dist/mcp-tools/tool-result-saver.js +11 -0
  97. package/dist/mcp-tools/tool-result-saver.js.map +1 -1
  98. package/dist/mcp-tools/trait-provenance.d.ts +109 -0
  99. package/dist/mcp-tools/trait-provenance.d.ts.map +1 -0
  100. package/dist/mcp-tools/trait-provenance.js +187 -0
  101. package/dist/mcp-tools/trait-provenance.js.map +1 -0
  102. package/dist/mcp-tools/ui-resources.d.ts +6 -7
  103. package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
  104. package/dist/mcp-tools/ui-resources.js +162 -12
  105. package/dist/mcp-tools/ui-resources.js.map +1 -1
  106. package/dist/mcp-tools/widget-kit.d.ts +129 -3
  107. package/dist/mcp-tools/widget-kit.d.ts.map +1 -1
  108. package/dist/mcp-tools/widget-kit.js +507 -28
  109. package/dist/mcp-tools/widget-kit.js.map +1 -1
  110. package/dist/performance/metrics.d.ts.map +1 -1
  111. package/dist/performance/metrics.js +4 -1
  112. package/dist/performance/metrics.js.map +1 -1
  113. package/dist/persona-questionnaire.js +1 -1
  114. package/dist/persona-questionnaire.js.map +1 -1
  115. package/dist/personas.d.ts.map +1 -1
  116. package/dist/personas.js +133 -82
  117. package/dist/personas.js.map +1 -1
  118. package/dist/recording/engine.d.ts.map +1 -1
  119. package/dist/recording/engine.js +5 -2
  120. package/dist/recording/engine.js.map +1 -1
  121. package/dist/recording/saliency-overlay.d.ts +8 -3
  122. package/dist/recording/saliency-overlay.d.ts.map +1 -1
  123. package/dist/recording/saliency-overlay.js +14 -5
  124. package/dist/recording/saliency-overlay.js.map +1 -1
  125. package/dist/recording/types.js +1 -1
  126. package/dist/testing/nl-test-suite.d.ts.map +1 -1
  127. package/dist/testing/nl-test-suite.js +2 -1
  128. package/dist/testing/nl-test-suite.js.map +1 -1
  129. package/dist/trait-reference.d.ts +19 -2
  130. package/dist/trait-reference.d.ts.map +1 -1
  131. package/dist/trait-reference.js +19 -2
  132. package/dist/trait-reference.js.map +1 -1
  133. package/dist/types.d.ts +32 -21
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +62 -52
  136. package/dist/types.js.map +1 -1
  137. package/dist/values/persona-completeness.js +1 -1
  138. package/dist/values/persona-completeness.js.map +1 -1
  139. package/dist/visual/ab-comparison.d.ts.map +1 -1
  140. package/dist/visual/ab-comparison.js +4 -1
  141. package/dist/visual/ab-comparison.js.map +1 -1
  142. package/dist/visual/attention-quality.d.ts +1 -1
  143. package/dist/visual/attention-quality.js +1 -1
  144. package/dist/visual/attention-transport.d.ts +74 -10
  145. package/dist/visual/attention-transport.d.ts.map +1 -1
  146. package/dist/visual/attention-transport.js +353 -193
  147. package/dist/visual/attention-transport.js.map +1 -1
  148. package/dist/visual/cognitive-models.d.ts +77 -55
  149. package/dist/visual/cognitive-models.d.ts.map +1 -1
  150. package/dist/visual/cognitive-models.js +231 -104
  151. package/dist/visual/cognitive-models.js.map +1 -1
  152. package/dist/visual/cognitive-transport-chain.d.ts +328 -30
  153. package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
  154. package/dist/visual/cognitive-transport-chain.js +576 -353
  155. package/dist/visual/cognitive-transport-chain.js.map +1 -1
  156. package/dist/visual/cognitive-transport.d.ts +446 -13
  157. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  158. package/dist/visual/cognitive-transport.js +1085 -245
  159. package/dist/visual/cognitive-transport.js.map +1 -1
  160. package/dist/visual/cross-browser.d.ts.map +1 -1
  161. package/dist/visual/cross-browser.js +8 -6
  162. package/dist/visual/cross-browser.js.map +1 -1
  163. package/dist/visual/ctc-bands.d.ts +91 -0
  164. package/dist/visual/ctc-bands.d.ts.map +1 -0
  165. package/dist/visual/ctc-bands.js +117 -0
  166. package/dist/visual/ctc-bands.js.map +1 -0
  167. package/dist/visual/ctc-reference.d.ts +287 -0
  168. package/dist/visual/ctc-reference.d.ts.map +1 -0
  169. package/dist/visual/ctc-reference.js +376 -0
  170. package/dist/visual/ctc-reference.js.map +1 -0
  171. package/dist/visual/demand-laws.d.ts +145 -0
  172. package/dist/visual/demand-laws.d.ts.map +1 -0
  173. package/dist/visual/demand-laws.js +220 -0
  174. package/dist/visual/demand-laws.js.map +1 -0
  175. package/dist/visual/distance-metrics.d.ts +23 -1
  176. package/dist/visual/distance-metrics.d.ts.map +1 -1
  177. package/dist/visual/distance-metrics.js +49 -18
  178. package/dist/visual/distance-metrics.js.map +1 -1
  179. package/dist/visual/dro-bound.d.ts +220 -0
  180. package/dist/visual/dro-bound.d.ts.map +1 -0
  181. package/dist/visual/dro-bound.js +341 -0
  182. package/dist/visual/dro-bound.js.map +1 -0
  183. package/dist/visual/exact-w1.d.ts +60 -0
  184. package/dist/visual/exact-w1.d.ts.map +1 -0
  185. package/dist/visual/exact-w1.js +247 -0
  186. package/dist/visual/exact-w1.js.map +1 -0
  187. package/dist/visual/human-input.d.ts.map +1 -1
  188. package/dist/visual/human-input.js +45 -41
  189. package/dist/visual/human-input.js.map +1 -1
  190. package/dist/visual/index.d.ts +2 -0
  191. package/dist/visual/index.d.ts.map +1 -1
  192. package/dist/visual/index.js +2 -0
  193. package/dist/visual/index.js.map +1 -1
  194. package/dist/visual/law-times-report.d.ts +49 -0
  195. package/dist/visual/law-times-report.d.ts.map +1 -0
  196. package/dist/visual/law-times-report.js +38 -0
  197. package/dist/visual/law-times-report.js.map +1 -0
  198. package/dist/visual/model-provenance.d.ts +151 -0
  199. package/dist/visual/model-provenance.d.ts.map +1 -0
  200. package/dist/visual/model-provenance.js +334 -0
  201. package/dist/visual/model-provenance.js.map +1 -0
  202. package/dist/visual/normal-cdf.d.ts +14 -0
  203. package/dist/visual/normal-cdf.d.ts.map +1 -0
  204. package/dist/visual/normal-cdf.js +26 -0
  205. package/dist/visual/normal-cdf.js.map +1 -0
  206. package/dist/visual/perceptual-transport.d.ts +28 -4
  207. package/dist/visual/perceptual-transport.d.ts.map +1 -1
  208. package/dist/visual/perceptual-transport.js +66 -32
  209. package/dist/visual/perceptual-transport.js.map +1 -1
  210. package/dist/visual/provenance/attention.d.ts +55 -0
  211. package/dist/visual/provenance/attention.d.ts.map +1 -0
  212. package/dist/visual/provenance/attention.js +188 -0
  213. package/dist/visual/provenance/attention.js.map +1 -0
  214. package/dist/visual/provenance/chain.d.ts +41 -0
  215. package/dist/visual/provenance/chain.d.ts.map +1 -0
  216. package/dist/visual/provenance/chain.js +66 -0
  217. package/dist/visual/provenance/chain.js.map +1 -0
  218. package/dist/visual/provenance/cif.d.ts +42 -0
  219. package/dist/visual/provenance/cif.d.ts.map +1 -0
  220. package/dist/visual/provenance/cif.js +116 -0
  221. package/dist/visual/provenance/cif.js.map +1 -0
  222. package/dist/visual/provenance/ctc-reference.d.ts +36 -0
  223. package/dist/visual/provenance/ctc-reference.d.ts.map +1 -0
  224. package/dist/visual/provenance/ctc-reference.js +96 -0
  225. package/dist/visual/provenance/ctc-reference.js.map +1 -0
  226. package/dist/visual/provenance/distance.d.ts +10 -0
  227. package/dist/visual/provenance/distance.d.ts.map +1 -0
  228. package/dist/visual/provenance/distance.js +72 -0
  229. package/dist/visual/provenance/distance.js.map +1 -0
  230. package/dist/visual/provenance/dro.d.ts +10 -0
  231. package/dist/visual/provenance/dro.d.ts.map +1 -0
  232. package/dist/visual/provenance/dro.js +2 -0
  233. package/dist/visual/provenance/dro.js.map +1 -0
  234. package/dist/visual/provenance/emotion.d.ts +26 -0
  235. package/dist/visual/provenance/emotion.d.ts.map +1 -0
  236. package/dist/visual/provenance/emotion.js +99 -0
  237. package/dist/visual/provenance/emotion.js.map +1 -0
  238. package/dist/visual/provenance/human-input.d.ts +19 -0
  239. package/dist/visual/provenance/human-input.d.ts.map +1 -0
  240. package/dist/visual/provenance/human-input.js +84 -0
  241. package/dist/visual/provenance/human-input.js.map +1 -0
  242. package/dist/visual/provenance/journey-group.d.ts +15 -0
  243. package/dist/visual/provenance/journey-group.d.ts.map +1 -0
  244. package/dist/visual/provenance/journey-group.js +15 -0
  245. package/dist/visual/provenance/journey-group.js.map +1 -0
  246. package/dist/visual/provenance/journey-mcp.d.ts +24 -0
  247. package/dist/visual/provenance/journey-mcp.d.ts.map +1 -0
  248. package/dist/visual/provenance/journey-mcp.js +54 -0
  249. package/dist/visual/provenance/journey-mcp.js.map +1 -0
  250. package/dist/visual/provenance/journey.d.ts +34 -0
  251. package/dist/visual/provenance/journey.d.ts.map +1 -0
  252. package/dist/visual/provenance/journey.js +184 -0
  253. package/dist/visual/provenance/journey.js.map +1 -0
  254. package/dist/visual/provenance/laws.d.ts +38 -0
  255. package/dist/visual/provenance/laws.d.ts.map +1 -0
  256. package/dist/visual/provenance/laws.js +40 -0
  257. package/dist/visual/provenance/laws.js.map +1 -0
  258. package/dist/visual/provenance/page-metrics.d.ts +42 -0
  259. package/dist/visual/provenance/page-metrics.d.ts.map +1 -0
  260. package/dist/visual/provenance/page-metrics.js +79 -0
  261. package/dist/visual/provenance/page-metrics.js.map +1 -0
  262. package/dist/visual/provenance/perceptual.d.ts +63 -0
  263. package/dist/visual/provenance/perceptual.d.ts.map +1 -0
  264. package/dist/visual/provenance/perceptual.js +115 -0
  265. package/dist/visual/provenance/perceptual.js.map +1 -0
  266. package/dist/visual/provenance/persona-rates.d.ts +84 -0
  267. package/dist/visual/provenance/persona-rates.d.ts.map +1 -0
  268. package/dist/visual/provenance/persona-rates.js +112 -0
  269. package/dist/visual/provenance/persona-rates.js.map +1 -0
  270. package/dist/visual/provenance/personas.d.ts +122 -0
  271. package/dist/visual/provenance/personas.d.ts.map +1 -0
  272. package/dist/visual/provenance/personas.js +298 -0
  273. package/dist/visual/provenance/personas.js.map +1 -0
  274. package/dist/visual/provenance/profiles.d.ts +39 -0
  275. package/dist/visual/provenance/profiles.d.ts.map +1 -0
  276. package/dist/visual/provenance/profiles.js +37 -0
  277. package/dist/visual/provenance/profiles.js.map +1 -0
  278. package/dist/visual/provenance/temporal.d.ts +21 -0
  279. package/dist/visual/provenance/temporal.d.ts.map +1 -0
  280. package/dist/visual/provenance/temporal.js +56 -0
  281. package/dist/visual/provenance/temporal.js.map +1 -0
  282. package/dist/visual/provenance/visual-distance.d.ts +22 -0
  283. package/dist/visual/provenance/visual-distance.d.ts.map +1 -0
  284. package/dist/visual/provenance/visual-distance.js +30 -0
  285. package/dist/visual/provenance/visual-distance.js.map +1 -0
  286. package/dist/visual/regression.d.ts.map +1 -1
  287. package/dist/visual/regression.js +14 -9
  288. package/dist/visual/regression.js.map +1 -1
  289. package/dist/visual/responsive.d.ts.map +1 -1
  290. package/dist/visual/responsive.js +4 -1
  291. package/dist/visual/responsive.js.map +1 -1
  292. package/dist/visual/scope-prose.js +2 -2
  293. package/dist/visual/scope-prose.js.map +1 -1
  294. package/dist/visual/seeded-random.d.ts +16 -0
  295. package/dist/visual/seeded-random.d.ts.map +1 -0
  296. package/dist/visual/seeded-random.js +25 -0
  297. package/dist/visual/seeded-random.js.map +1 -0
  298. package/dist/visual/stored-accessibility-traits.d.ts +27 -0
  299. package/dist/visual/stored-accessibility-traits.d.ts.map +1 -0
  300. package/dist/visual/stored-accessibility-traits.js +35 -0
  301. package/dist/visual/stored-accessibility-traits.js.map +1 -0
  302. package/package.json +1 -1
@@ -196,7 +196,8 @@ const CSS = `
196
196
  .act:focus-visible{outline:2px solid #fff;outline-offset:2px}
197
197
  .hero.bare .act{background:var(--brand);color:#fff;border-color:transparent}
198
198
  .facts{display:flex;flex-wrap:wrap;gap:.34rem;margin:.7rem 0 0}
199
- .fact{display:inline-flex;align-items:baseline;gap:.34rem;padding:.24rem .52rem;
199
+ .fact b.rk,.fact .nw{white-space:nowrap}
200
+ .fact{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:.15rem .34rem;padding:.24rem .52rem;
200
201
  border:1px solid rgba(255,255,255,.32);border-radius:6px;background:rgba(255,255,255,.10)}
201
202
  .fact b{font:650 .82rem/1 var(--mono);font-variant-numeric:tabular-nums;color:#fff}
202
203
  .fact span{font-size:.74rem;color:#fff}
@@ -313,6 +314,8 @@ const CSS = `
313
314
  .mgrrow:hover{background:color-mix(in oklch,var(--accent) 12%,transparent)}
314
315
  .mgrrow.on{background:color-mix(in oklch,var(--accent) 22%,transparent);
315
316
  box-shadow:inset 3px 0 0 var(--accent)}
317
+ /* --sub on the selected row's accent tint measured 4.03:1. */
318
+ .mgrrow.on .cap{color:var(--ink)}
316
319
  .mgrrow:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
317
320
  .mgrname{font-size:.85rem;font-weight:600}
318
321
  .modetabs{display:flex;gap:.3rem;flex-wrap:wrap;margin:.4rem 0 .5rem}
@@ -341,7 +344,7 @@ const CSS = `
341
344
  .editbar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-top:.75rem;
342
345
  padding-top:.75rem;border-top:1px solid var(--line)}
343
346
  .btn{font:inherit;font-size:.8rem;padding:.35rem .7rem;border-radius:.375rem;
344
- border:1px solid var(--line);background:var(--accent);color:#fff;cursor:pointer}
347
+ border:1px solid var(--line);background:var(--accent);color:var(--on-accent);cursor:pointer}
345
348
  .btn:disabled{opacity:.45;cursor:default}
346
349
  .btn.ghost{background:none;color:var(--sub)}
347
350
  .btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
@@ -396,10 +399,12 @@ const CSS = `
396
399
  .chain button.seg:hover .track,.chain button.seg:focus-visible .track{outline:2px solid var(--brand);outline-offset:2px}
397
400
  .chain button.seg:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
398
401
  .chain button.seg[aria-expanded="true"] .lbl{text-decoration:underline}
399
- .chain .noov{flex:0 0 auto;font-size:.7rem;opacity:.55;margin-left:.35rem}
402
+ /* --sub, not opacity: at .55 the tag measured 4.4:1 on a light host. */
403
+ .chain .noov{flex:0 0 auto;font-size:.7rem;color:var(--sub);margin-left:.35rem}
400
404
  .ovpane{margin:.6rem 0 1rem;padding:.6rem;border:1px solid rgba(127,127,127,.28);border-radius:6px}
401
405
  .ovpane img{max-width:100%;height:auto;display:block;border-radius:4px}
402
- .ovpane .cap{font-size:.8rem;opacity:.8;margin:.45rem 0 0}
406
+ /* No opacity on the legend: at .8 it measured 3.59:1 on a light host. */
407
+ .ovpane .cap{font-size:.8rem;margin:.45rem 0 0}
403
408
  @media (max-width:520px){
404
409
  /* The fixed 9.5rem label column took a third of a 393px viewport and left
405
410
  the bars as stubs — the one thing the block exists to show. Labels move
@@ -415,11 +420,16 @@ const CSS = `
415
420
  .ampnote{font-size:.8rem;opacity:.75;margin:.45rem 0 0}
416
421
  .scale{margin:1.5rem 0 .8rem}
417
422
  .bands{display:flex;gap:3px;position:relative}
423
+ /* Inactive bands are dimmed through their fill's alpha, not opacity:
424
+ opacity .42 dimmed the label with the fill, to 1.7-2.1:1. The active
425
+ band's fill is the same in both themes, so its label is not --ink (light
426
+ on an orange fill in dark) but whichever of near-black or near-white
427
+ reads on that fill, chosen in levelsBlock. */
418
428
  .band{flex:1;height:30px;border-radius:5px;display:grid;place-items:center;
419
429
  font:.62rem/1 var(--mono);text-transform:uppercase;letter-spacing:.05em;
420
- color:color-mix(in srgb, var(--ink) 78%, transparent);opacity:.42;
430
+ color:var(--ink);
421
431
  border:2px solid transparent;overflow:hidden;white-space:nowrap;padding:0 .2rem}
422
- .band.on{opacity:1;border-color:var(--ink);font-weight:650}
432
+ .band.on{border-color:var(--ink);font-weight:650}
423
433
  .marker{position:absolute;top:-17px;transform:translateX(-50%);font:600 .64rem/1 var(--mono);
424
434
  color:var(--ink);white-space:nowrap;pointer-events:none}
425
435
  /* Stops at the band's top edge rather than crossing it. */
@@ -467,6 +477,89 @@ const CSS = `
467
477
  .offlist li{padding:.18rem 0;color:var(--sub)}
468
478
  .shot{max-width:100%;height:auto;display:block;border-radius:6px;border:1px solid var(--line)}
469
479
  .cap{font-size:.77rem;color:var(--sub);margin:.35rem 0 0}
480
+ /* Provenance: what an answer's numbers rest on. Status is named in text on
481
+ every row and chip; the colour of a swatch repeats it and never carries
482
+ it alone (WCAG 1.4.1). Chip text is the status hue mixed into --ink, the
483
+ same construction the severity chips use, so it follows the theme. */
484
+ .prov{font-size:.8rem;line-height:1.45;color:var(--sub);margin:.15rem 0 0}
485
+ .prov b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
486
+ .pint{margin:0 0 .6rem;font-size:.85rem}
487
+ .pint b{font-variant-numeric:tabular-nums}
488
+ .pstack{display:flex;height:8px;border-radius:4px;overflow:hidden;background:var(--raise);margin:.2rem 0 .5rem}
489
+ .pstack i{display:block;height:100%}
490
+ .pcounts{list-style:none;padding:0;margin:0 0 .6rem}
491
+ .pcounts li{display:grid;grid-template-columns:auto 2.4rem 1fr;gap:.15rem .6rem;align-items:baseline;
492
+ padding:.32rem 0;border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent)}
493
+ .pcounts li:last-child{border-bottom:0}
494
+ .pnum{font:.82rem/1 var(--mono);text-align:right;font-variant-numeric:tabular-nums}
495
+ .pmean{font-size:.8rem;color:var(--sub)}
496
+ @media (max-width:30rem){
497
+ .pcounts li{grid-template-columns:auto 1fr}
498
+ .pcounts .pnum{text-align:left}
499
+ .pcounts .pmean{grid-column:1/-1}
500
+ }
501
+ .pchip{display:inline-flex;align-items:center;gap:.3rem;font:650 .64rem/1.2 var(--mono);letter-spacing:.05em;
502
+ text-transform:uppercase;padding:.16rem .38rem;border-radius:3px;white-space:nowrap;
503
+ background:var(--raise);color:var(--ink)}
504
+ .pchip .sw{width:8px;height:8px}
505
+ .pchip.hypothesis{background:color-mix(in srgb,var(--warn) 20%,transparent);color:color-mix(in srgb,var(--warn) 62%,var(--ink))}
506
+ .pchip.fitted{background:color-mix(in srgb,var(--ok) 20%,transparent);color:color-mix(in srgb,var(--ok) 55%,var(--ink))}
507
+ .pchip.published{background:color-mix(in srgb,var(--brand) 18%,transparent);color:color-mix(in srgb,var(--brand) 70%,var(--ink))}
508
+ .pchip.definition,.pchip.mixed{background:var(--raise);color:var(--ink)}
509
+ .pchip.custom{background:color-mix(in srgb,var(--brand-2) 20%,transparent);color:color-mix(in srgb,var(--brand-2) 55%,var(--ink))}
510
+ .psw-fitted{background:var(--ok)} .psw-published{background:var(--brand)}
511
+ .psw-definition{background:color-mix(in srgb,var(--ink) 35%,transparent)} .psw-hypothesis{background:var(--warn)}
512
+ .pscope{font-size:.79rem;color:var(--sub);margin:.4rem 0 0}
513
+ details.pids{margin:.6rem 0 0}
514
+ details.pids>summary{cursor:pointer;font-size:.8rem;color:var(--ink);padding:.25rem 0}
515
+ details.pids>summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}
516
+ .pgrp{font:600 .72rem/1.3 var(--mono);color:var(--sub);margin:.55rem 0 .2rem}
517
+ .pidl{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:.2rem .35rem}
518
+ .pidl li{font:.7rem/1.35 var(--mono);color:var(--ink);background:var(--raise);border-radius:3px;
519
+ padding:.08rem .3rem;overflow-wrap:anywhere;max-width:100%}
520
+ /* Trait rows that carry a status: one fixed slot after the value on every
521
+ row, so the tracks stay aligned whether or not a row is marked. */
522
+ .pstatus{display:flex;flex-wrap:wrap;align-items:baseline;gap:.35rem .5rem;margin:0 0 .55rem;font-size:.79rem;color:var(--sub)}
523
+ .pstatus .pchip{flex:0 0 auto}
524
+ .pstatus span.ptext{flex:1 1 14rem;min-width:0}
525
+ .pslot{flex:0 0 4.4rem;display:flex;justify-content:flex-start}
526
+ .ptag{font:650 .6rem/1.2 var(--mono);letter-spacing:.05em;text-transform:uppercase;padding:.14rem .3rem;
527
+ border-radius:3px;border:1px solid var(--line);background:none;color:var(--ink);white-space:nowrap}
528
+ button.ptag{cursor:help;text-decoration:underline dotted currentColor;text-underline-offset:2px}
529
+ button.ptag:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
530
+ @media (max-width:30rem){ .pslot{flex-basis:4.1rem} }
531
+ /* Rank among the real pages: the sentence, then a 0-100% scale with the
532
+ interval shaded and the point marked. The words carry the numbers; the
533
+ scale is the picture of them (role=img, labelled with the same words). */
534
+ .rank{margin:.2rem 0 .3rem}
535
+ .rankh{font-size:1rem;font-weight:600;margin:0 0 .55rem;color:var(--ink)}
536
+ .rscale{position:relative;height:14px;border-radius:7px;background:var(--raise);
537
+ box-shadow:inset 0 0 0 1px var(--line);margin:0 0 .25rem}
538
+ .rband{position:absolute;top:0;bottom:0;border-radius:7px;background:color-mix(in srgb,var(--brand) 38%,transparent)}
539
+ .rpoint{position:absolute;top:-4px;bottom:-4px;width:3px;margin-left:-1.5px;border-radius:2px;background:var(--ink)}
540
+ .rticks{display:flex;justify-content:space-between;font:.68rem/1.2 var(--mono);color:var(--sub);margin:0 0 .45rem}
541
+ .rint{font-size:.8rem;line-height:1.45;color:var(--sub);margin:0}
542
+ /* Seconds against a budget. One ruler for every bar and the budget mark;
543
+ the largest is named in text, never by colour alone (WCAG 1.4.1). */
544
+ .lcap{font-size:.8rem;line-height:1.45;color:var(--sub);margin:0 0 .5rem}
545
+ .lawlist{list-style:none;padding:0;margin:0}
546
+ .law{padding:.45rem 0;border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent)}
547
+ .law:last-child{border-bottom:0}
548
+ .lhead{display:flex;align-items:baseline;gap:.45rem}
549
+ .lname{font-size:.86rem;font-weight:600;color:var(--ink)}
550
+ .ltag{font:650 .6rem/1.2 var(--mono);letter-spacing:.05em;text-transform:uppercase;padding:.12rem .3rem;
551
+ border-radius:3px;border:1px solid var(--ink);color:var(--ink)}
552
+ .ldetail{display:block;font-size:.76rem;line-height:1.4;color:var(--sub);margin:.1rem 0 .3rem}
553
+ .lrow{display:flex;align-items:center;gap:.6rem}
554
+ .ltrack{flex:1;min-width:0;position:relative;height:12px;border-radius:6px;background:var(--raise)}
555
+ .lfill{display:block;height:100%;border-radius:6px;background:var(--brand)}
556
+ .lbudget{position:absolute;top:-4px;bottom:-4px;width:2px;margin-left:-1px;background:var(--ink)}
557
+ .lsec{flex:0 0 3.8rem;text-align:right;font:600 .8rem/1 var(--mono);font-variant-numeric:tabular-nums;color:var(--ink)}
558
+ .ldemand{flex:0 0 5.8rem;font:.74rem/1 var(--mono);font-variant-numeric:tabular-nums;color:var(--sub)}
559
+ .lalt{font-size:.76rem;color:var(--sub);margin:.3rem 0 0}
560
+ .lkey{font-size:.74rem;color:var(--sub);margin:.45rem 0 0}
561
+ .lnote{font-size:.8rem;line-height:1.45;color:var(--sub);margin:.4rem 0 0}
562
+ @media (max-width:30rem){ .lsec{flex-basis:3.4rem} .ldemand{flex-basis:5.2rem;font-size:.7rem} }
470
563
  .foot{padding:0 18px 14px;font-size:.77rem;color:var(--sub)}
471
564
  .msg{padding:16px 18px;font-size:.85rem;color:var(--sub)}
472
565
  @media (max-width:420px){
@@ -713,8 +806,9 @@ const RUNTIME = String.raw `
713
806
  * write to two stores, and a slider dragged across its range would produce a
714
807
  * dozen persona versions of which only the last was meant.
715
808
  */
716
- function editorBlock(rows, b, data) {
809
+ function editorBlock(rows, b, data, prov) {
717
810
  var wrap = el("div", "traits");
811
+ if (prov) wrap.appendChild(traitStatusLine(prov));
718
812
  var personaName = at(data, b.personaField || "persona") || at(data, "persona_name");
719
813
  var edited = {};
720
814
  var controls = [];
@@ -742,7 +836,9 @@ const RUNTIME = String.raw `
742
836
  ? Object.keys(edited).length + " unsaved change(s)" : "";
743
837
  save.disabled = !Object.keys(edited).length;
744
838
  });
745
- line.appendChild(label); line.appendChild(input); line.appendChild(out); line.appendChild(orig);
839
+ line.appendChild(label); line.appendChild(input); line.appendChild(out);
840
+ if (prov) line.appendChild(traitTag(prov, name));
841
+ line.appendChild(orig);
746
842
  wrap.appendChild(line);
747
843
  controls.push({ name: name, input: input, out: out, base: v });
748
844
  });
@@ -839,6 +935,8 @@ const RUNTIME = String.raw `
839
935
  row.appendChild(el("span", "mgrname", String(p.name || "(unnamed)")));
840
936
  var meta = [];
841
937
  if (p.builtin) meta.push("built-in");
938
+ // Where the values come from, on the row that picks the persona.
939
+ if (p.provenance && p.provenance.label) meta.push(String(p.provenance.label));
842
940
  if (p.valuesRoute) meta.push(String(p.valuesRoute));
843
941
  if (meta.length) row.appendChild(el("span", "cap", meta.join(" · ")));
844
942
  row.addEventListener("click", function () {
@@ -859,6 +957,8 @@ const RUNTIME = String.raw `
859
957
  var traits = p.traits || {};
860
958
  var names = Object.keys(traits);
861
959
  var box = el("div", "traits");
960
+ var prov = p.provenance && p.provenance.status ? p.provenance : null;
961
+ if (prov) box.appendChild(traitStatusLine(prov));
862
962
  names.forEach(function (k) {
863
963
  var v = Number(traits[k]);
864
964
  if (!isFinite(v)) return;
@@ -879,6 +979,7 @@ const RUNTIME = String.raw `
879
979
  ? Object.keys(edited).length + " unsaved change(s) on " + p.name : "";
880
980
  });
881
981
  line.appendChild(input); line.appendChild(out);
982
+ if (prov) line.appendChild(traitTag(prov, k));
882
983
  box.appendChild(line);
883
984
  });
884
985
  pane.appendChild(box);
@@ -1100,8 +1201,9 @@ const RUNTIME = String.raw `
1100
1201
  return wrap;
1101
1202
  }
1102
1203
 
1103
- function traitsBlock(rows, b) {
1204
+ function traitsBlock(rows, b, prov) {
1104
1205
  var wrap = el("div", "traits");
1206
+ if (prov) wrap.appendChild(traitStatusLine(prov));
1105
1207
  rows.forEach(function (r) {
1106
1208
  var name = r.name, v = Number(r.value);
1107
1209
  if (!isFinite(v)) return;
@@ -1135,6 +1237,7 @@ const RUNTIME = String.raw `
1135
1237
  track.appendChild(fill);
1136
1238
  line.appendChild(track);
1137
1239
  line.appendChild(el("span", "tval", v.toFixed(2)));
1240
+ if (prov) line.appendChild(traitTag(prov, String(name)));
1138
1241
  wrap.appendChild(line);
1139
1242
  });
1140
1243
  return wrap;
@@ -1157,6 +1260,319 @@ const RUNTIME = String.raw `
1157
1260
  return [];
1158
1261
  }
1159
1262
 
1263
+ // One sentence from an object: the template for the value of its by key
1264
+ // ("*" the fallback), with {key} filled from the object. A perceptual
1265
+ // profile is {resolution, name}, and "default (default)" says nothing that
1266
+ // "none: the generic default" does not say better.
1267
+ function fillTemplate(obj, by, templates) {
1268
+ if (!obj || typeof obj !== "object" || !templates) return null;
1269
+ var key = by ? String(obj[by]) : "*";
1270
+ var t = Object.prototype.hasOwnProperty.call(templates, key) ? templates[key] : templates["*"];
1271
+ if (typeof t !== "string") return null;
1272
+ return t.replace(/\{(\w+)\}/g, function (_m, k) {
1273
+ return obj[k] === undefined || obj[k] === null ? "" : String(obj[k]);
1274
+ });
1275
+ }
1276
+
1277
+ // A percentile among the real reference pages, in the verdict's words.
1278
+ // The numbers come from the payload's figures when it has them: the whole
1279
+ // percents the verdict sentence states (cognitive_effort's rankFigures).
1280
+ // The decimals beside them are rounded to three places, and recomputed
1281
+ // from those a rank can differ by a point (0.8098 is stated 80; its 0.81
1282
+ // reads 81). Without figures, rounded exactly as ctcRank (ctc-bands.ts)
1283
+ // rounds: N and the interval's low end down, its high end up; at either end
1284
+ // the rank says what it is and no interval is stated. The decimals still
1285
+ // place the point and the band on the scale.
1286
+ // tests/effort-view-laws.test.ts checks these numbers against ctcRank's.
1287
+ function isPct(x) { return typeof x === "number" && isFinite(x) && x >= 0 && x <= 1; }
1288
+ function pctDown(x) { return Math.floor(x * 100 + 1e-9); }
1289
+ function pctUp(x) { return Math.ceil(x * 100 - 1e-9); }
1290
+ function rankWords(t, iv, figures) {
1291
+ if (!isPct(t)) return null;
1292
+ var whole = function (x) { return typeof x === "number" && isFinite(x) && x >= 0 && x <= 100; };
1293
+ var have = figures && typeof figures === "object" && (figures.end === "cheapest" || figures.end === "costliest" || whole(figures.percent));
1294
+ var end = have ? (figures.end === "cheapest" || figures.end === "costliest" ? figures.end : null)
1295
+ : t === 0 ? "cheapest" : t === 1 ? "costliest" : null;
1296
+ var n = have ? figures.percent : pctDown(t);
1297
+ var nText = n === 0 ? "under 1" : String(n);
1298
+ var head = end === "cheapest" ? "At or below the cheapest real page we measured"
1299
+ : end === "costliest" ? "At least as costly as the costliest real page we measured"
1300
+ : "Costlier than " + nText + "% of the real pages we measured";
1301
+ var chip = end === "cheapest" ? "at or below the cheapest" : end === "costliest" ? "as costly as the costliest" : "costlier than " + nText + "%";
1302
+ var interval = null;
1303
+ if (!end) {
1304
+ if (have) {
1305
+ var fi = figures.interval;
1306
+ interval = Array.isArray(fi) && fi.length === 2 && whole(fi[0]) && whole(fi[1]) ? [fi[0], fi[1]] : null;
1307
+ } else if (Array.isArray(iv) && iv.length === 2 && isPct(iv[0]) && isPct(iv[1])) {
1308
+ interval = [pctDown(iv[0]), pctUp(iv[1])];
1309
+ }
1310
+ }
1311
+ var drawn = interval && Array.isArray(iv) && iv.length === 2 && isPct(iv[0]) && isPct(iv[1]) ? iv : null;
1312
+ return { t: t, iv: drawn, head: head, chip: chip, interval: interval };
1313
+ }
1314
+ function pctStyle(x) { return (Math.max(0, Math.min(1, x)) * 100).toFixed(4) + "%"; }
1315
+
1316
+ function rankBlock(data, b) {
1317
+ var w = rankWords(at(data, b.field), b.intervalField ? at(data, b.intervalField) : null,
1318
+ b.figuresField ? at(data, b.figuresField) : null);
1319
+ if (!w) return null;
1320
+ var level = b.level || 95;
1321
+ var wrap = el("div", "rank");
1322
+ wrap.appendChild(el("p", "rankh", w.head));
1323
+ var scale = el("div", "rscale");
1324
+ scale.setAttribute("role", "img");
1325
+ scale.setAttribute("aria-label", w.head + (w.interval
1326
+ ? "; " + level + "% interval " + w.interval[0] + "% to " + w.interval[1] + "%" : ""));
1327
+ if (w.interval && w.iv) {
1328
+ var band = el("span", "rband");
1329
+ band.style.left = pctStyle(w.iv[0]);
1330
+ band.style.width = pctStyle(w.iv[1] - w.iv[0]);
1331
+ scale.appendChild(band);
1332
+ }
1333
+ var point = el("span", "rpoint");
1334
+ point.style.left = pctStyle(w.t);
1335
+ scale.appendChild(point);
1336
+ wrap.appendChild(scale);
1337
+ var ticks = el("div", "rticks");
1338
+ ticks.setAttribute("aria-hidden", "true");
1339
+ ["0%", "50%", "100%"].forEach(function (x) { ticks.appendChild(el("span", null, x)); });
1340
+ wrap.appendChild(ticks);
1341
+ if (w.interval) {
1342
+ wrap.appendChild(el("p", "rint", level + "% interval: " + w.interval[0] + "% to " + w.interval[1]
1343
+ + "% of the real pages. That is how firmly the reference pages fix this rank, not whether the cost is right."));
1344
+ }
1345
+ return wrap;
1346
+ }
1347
+
1348
+ // Seconds against one budget: every bar and the budget mark on one ruler,
1349
+ // the largest named in text.
1350
+ function timesBlock(data, b) {
1351
+ var o = at(data, b.field);
1352
+ if (!o || typeof o !== "object") return null;
1353
+ var budget = Number(o[b.budgetKey]);
1354
+ var rows = (b.rows || []).map(function (r) {
1355
+ var x = o[r.key];
1356
+ return x && typeof x === "object" && isFinite(Number(x.seconds)) ? { r: r, x: x, sec: Number(x.seconds) } : null;
1357
+ }).filter(function (e) { return e; });
1358
+ if (!rows.length || !(budget > 0)) return null;
1359
+ var max = Math.max.apply(null, rows.map(function (e) { return e.sec; }).concat([budget]));
1360
+ var top = rows.reduce(function (a, e) { return e.sec > a.sec ? e : a; }, rows[0]);
1361
+ var wrap = el("div", "laws");
1362
+ var bud = num(budget);
1363
+ wrap.appendChild(el("p", "lcap", "Seconds of work this page asks of a typical first-time visitor, by law, against a "
1364
+ + bud + " s time budget. Each is a demand d = T / (T + " + bud + " s): a law that takes the whole budget makes a demand of 0.5."));
1365
+ var ul = el("ul", "lawlist");
1366
+ rows.forEach(function (e) {
1367
+ var li = el("li", "law");
1368
+ var head = el("div", "lhead");
1369
+ head.appendChild(el("span", "lname", e.r.label));
1370
+ if (e === top) head.appendChild(el("span", "ltag", "largest"));
1371
+ li.appendChild(head);
1372
+ li.appendChild(el("span", "ldetail", rowDetail(e.r, e.x)));
1373
+ var row = el("div", "lrow");
1374
+ var track = el("div", "ltrack");
1375
+ track.setAttribute("aria-hidden", "true");
1376
+ var fill = el("i", "lfill");
1377
+ fill.style.width = pctStyle(e.sec / max);
1378
+ var mark = el("span", "lbudget");
1379
+ mark.style.left = pctStyle(budget / max);
1380
+ track.appendChild(fill);
1381
+ track.appendChild(mark);
1382
+ row.appendChild(track);
1383
+ row.appendChild(el("span", "lsec", e.sec.toFixed(1) + " s"));
1384
+ row.appendChild(el("span", "ldemand", "demand " + Number(e.x.demand).toFixed(2)));
1385
+ li.appendChild(row);
1386
+ var alt = e.r.alt;
1387
+ if (alt && isFinite(Number(e.x[alt.secondsKey]))) {
1388
+ li.appendChild(el("p", "lalt", alt.label + " (" + alt.fractionLabel + " " + num(Number(e.x[alt.fractionKey])) + "): "
1389
+ + Number(e.x[alt.secondsKey]).toFixed(1) + " s, demand " + Number(e.x[alt.demandKey]).toFixed(2)));
1390
+ }
1391
+ ul.appendChild(li);
1392
+ });
1393
+ wrap.appendChild(ul);
1394
+ wrap.appendChild(el("p", "lkey", "The mark across each bar is the " + bud + " s budget."));
1395
+ if (b.caption && typeof b.caption.text === "string") {
1396
+ var hv = b.caption.field ? at(data, b.caption.field) : undefined;
1397
+ var here = hv === undefined || hv === null || hv === "" ? "" : String(b.caption.here || "").split("{value}").join(String(hv));
1398
+ wrap.appendChild(el("p", "lnote", b.caption.text.split("{here}").join(here)));
1399
+ }
1400
+ return wrap;
1401
+ }
1402
+
1403
+ // A row's detail: its template when every {key} in it is a number on the
1404
+ // row's entry, else the plain detail, so a missing field never prints as a
1405
+ // placeholder or falls back to a copied number.
1406
+ function rowDetail(r, x) {
1407
+ var t = r.detailTemplate;
1408
+ if (typeof t !== "string") return r.detail;
1409
+ var keys = (t.match(/\{(\w+)\}/g) || []).map(function (m) { return m.slice(1, -1); });
1410
+ if (!keys.every(function (k) { return x && typeof x[k] === "number" && isFinite(x[k]); })) return r.detail;
1411
+ return t.replace(/\{(\w+)\}/g, function (_m, k) { return num(x[k]); });
1412
+ }
1413
+
1414
+ // What an answer's numbers rest on (model-provenance.ts). The statuses in
1415
+ // reading order, from what data or the literature backs to what is set by
1416
+ // hand, each with what the registry means by it. Through 21.0 the views
1417
+ // drew these answers' numbers bare, and the summary fell into the rest dump
1418
+ // with its hypothesis ids printed as one joined string.
1419
+ var PROV_ORDER = ["fitted", "published", "definition", "hypothesis"];
1420
+ var PROV_MEANING = {
1421
+ fitted: "estimated from named data, with a held-out score",
1422
+ published: "taken from a cited law or measurement in the literature",
1423
+ definition: "a choice of units, scale or encoding; no empirical claim",
1424
+ hypothesis: "set by hand; nothing in data or the literature backs the value yet",
1425
+ };
1426
+ var PROV_PHRASE = {
1427
+ fitted: "fitted to data", published: "published",
1428
+ definition: "definitions", hypothesis: "set by hand (hypotheses)",
1429
+ };
1430
+ function isProvenance(p) {
1431
+ return !!(p && typeof p === "object" && p.counts && typeof p.counts === "object");
1432
+ }
1433
+ function provTotal(p) {
1434
+ return PROV_ORDER.reduce(function (s, k) { return s + (Number(p.counts[k]) || 0); }, 0);
1435
+ }
1436
+
1437
+ // One sentence of counts, every status named even at zero: "0 fitted to
1438
+ // data" is the finding for a score, not an omission.
1439
+ function provenanceLine(p, subject, seeAlso, qualifier) {
1440
+ var line = el("p", "prov");
1441
+ var total = provTotal(p);
1442
+ line.appendChild(document.createTextNode("This " + (subject || "answer") + " reads "));
1443
+ line.appendChild(el("b", null, String(total)));
1444
+ line.appendChild(document.createTextNode(" registered constant" + (total === 1 ? "" : "s")
1445
+ + (qualifier ? " " + qualifier : "") + ": "));
1446
+ PROV_ORDER.forEach(function (k, i) {
1447
+ if (i) line.appendChild(document.createTextNode(i === PROV_ORDER.length - 1 ? ", and " : ", "));
1448
+ line.appendChild(el("b", null, String(Number(p.counts[k]) || 0)));
1449
+ line.appendChild(document.createTextNode(" " + PROV_PHRASE[k]));
1450
+ });
1451
+ line.appendChild(document.createTextNode("." + (seeAlso ? ' What each means is under "' + seeAlso + '".' : "")));
1452
+ return line;
1453
+ }
1454
+
1455
+ function provenanceFull(p, b, data) {
1456
+ var wrap = el("div");
1457
+ // The interval comes first when the payload has one: it bounds the
1458
+ // number itself, where the counts below say what the number is made of.
1459
+ var iv = b.intervalField ? at(data, b.intervalField) : null;
1460
+ if (Array.isArray(iv) && iv.length === 2 && isFinite(iv[0]) && isFinite(iv[1])) {
1461
+ var pi = el("p", "pint");
1462
+ pi.appendChild(el("b", null, (b.intervalLabel || "95% interval") + ": "));
1463
+ pi.appendChild(document.createTextNode(b.intervalFormat === "percent"
1464
+ ? pctDown(Number(iv[0])) + "% to " + pctUp(Number(iv[1])) + "%"
1465
+ : num(Number(iv[0])) + " to " + num(Number(iv[1]))));
1466
+ wrap.appendChild(pi);
1467
+ }
1468
+ wrap.appendChild(provenanceLine(p, b.subject, null));
1469
+ var total = provTotal(p);
1470
+ if (total > 0) {
1471
+ // A picture of the shares. Hidden from assistive tech: the list under
1472
+ // it says the same thing in text.
1473
+ var stack = el("div", "pstack");
1474
+ stack.setAttribute("aria-hidden", "true");
1475
+ PROV_ORDER.forEach(function (k) {
1476
+ var n = Number(p.counts[k]) || 0;
1477
+ if (!n) return;
1478
+ var seg = el("i", "psw-" + k);
1479
+ seg.style.width = (n / total * 100) + "%";
1480
+ stack.appendChild(seg);
1481
+ });
1482
+ wrap.appendChild(stack);
1483
+ }
1484
+ var ul = el("ul", "pcounts");
1485
+ PROV_ORDER.forEach(function (k) {
1486
+ var li = el("li");
1487
+ var chip = el("span", "pchip " + k);
1488
+ chip.appendChild(el("span", "sw psw-" + k));
1489
+ chip.appendChild(document.createTextNode(k));
1490
+ li.appendChild(chip);
1491
+ li.appendChild(el("span", "pnum", String(Number(p.counts[k]) || 0)));
1492
+ li.appendChild(el("span", "pmean", PROV_MEANING[k]));
1493
+ ul.appendChild(li);
1494
+ });
1495
+ wrap.appendChild(ul);
1496
+ if (p.scope) wrap.appendChild(el("p", "pscope", String(p.scope)));
1497
+ if (p.note) wrap.appendChild(el("p", "pscope", String(p.note)));
1498
+ var ids = Array.isArray(p.hypotheses) ? p.hypotheses : [];
1499
+ if (ids.length) {
1500
+ // Grouped by area (the id's first segment), behind a disclosure: a
1501
+ // hundred ids are the evidence, not the summary.
1502
+ var d = el("details", "pids");
1503
+ d.appendChild(el("summary", null, "The " + ids.length + " hand-set constant" + (ids.length === 1 ? "" : "s")
1504
+ + " it read, by area"));
1505
+ var groups = {}, order = [];
1506
+ ids.forEach(function (id) {
1507
+ var g = String(id).split(".")[0];
1508
+ if (!groups[g]) { groups[g] = []; order.push(g); }
1509
+ groups[g].push(String(id));
1510
+ });
1511
+ order.forEach(function (g) {
1512
+ d.appendChild(el("p", "pgrp", g + " (" + groups[g].length + ")"));
1513
+ var l = el("ul", "pidl");
1514
+ groups[g].forEach(function (id) { l.appendChild(el("li", null, id)); });
1515
+ d.appendChild(l);
1516
+ });
1517
+ wrap.appendChild(d);
1518
+ }
1519
+ return wrap;
1520
+ }
1521
+
1522
+ // Where a group of trait values comes from (trait-provenance.ts): a status
1523
+ // line over the meters, and on a row, a tag where that value differs from
1524
+ // the line: the model's default, a citation, or another registry status.
1525
+ function traitProv(data, b) {
1526
+ if (!b.provenanceField) return null;
1527
+ used[b.provenanceField.split(".")[0]] = true;
1528
+ var p = at(data, b.provenanceField);
1529
+ return p && typeof p === "object" && p.status ? p : null;
1530
+ }
1531
+ function traitStatusLine(p) {
1532
+ var box = el("div", "pstatus");
1533
+ box.appendChild(el("span", "pchip " + String(p.status), String(p.label || p.status)));
1534
+ box.appendChild(el("span", "ptext", String(p.note || "")));
1535
+ var marks = [];
1536
+ if (p.anchored && Object.keys(p.anchored).length) marks.push("anchored: the value is the percentile a published population mean implies on a normed instrument, still a hypothesis (select the tag for the source)");
1537
+ if (p.cited && Object.keys(p.cited).length) marks.push("cited: a finding is cited for the direction of this value, not for its number (select the tag for the source)");
1538
+ if (Array.isArray(p.unset) && p.unset.length) marks.push("default: the persona does not set this trait, so the model's default is shown");
1539
+ if (p.statuses && Object.keys(p.statuses).length) marks.push("a status name: that value's registry status differs from the line above");
1540
+ if (marks.length) box.appendChild(el("span", "ptext", "Marked on a row: " + marks.join("; ") + "."));
1541
+ return box;
1542
+ }
1543
+ function traitTag(p, name) {
1544
+ var slot = el("span", "pslot");
1545
+ if (!p) return slot;
1546
+ var cited = p.cited && p.cited[name];
1547
+ var anch = p.anchored && p.anchored[name];
1548
+ var st = p.statuses && p.statuses[name];
1549
+ if (Array.isArray(p.unset) && p.unset.indexOf(name) >= 0) {
1550
+ slot.appendChild(el("span", "ptag", "default"));
1551
+ } else if (anch || cited) {
1552
+ var word = anch ? "anchored" : "cited";
1553
+ var btn = el("button", "ptag", word);
1554
+ btn.type = "button";
1555
+ btn.setAttribute("aria-describedby", "kit-tip");
1556
+ btn.setAttribute("aria-label", word + ": source for " + titleize(name));
1557
+ var iv = anch && Array.isArray(anch.interval) ? anch.interval : null;
1558
+ var info = anch
1559
+ ? { label: "Norm-anchored" + (st ? " (" + st + ")" : " (hypothesis)"),
1560
+ description: String(anch.cite) + ". " + String(anch.reported) + "."
1561
+ + (iv ? " Value interval " + iv[0] + " to " + iv[1] + "." : "")
1562
+ + " Mapping the instrument onto this trait is cbrowser's choice, so the value stays a hypothesis." }
1563
+ : { label: "Cited for direction" + (st ? " (" + st + ")" : ""),
1564
+ description: String(cited.cite) + ", for " + String(cited["for"]) + ". That supports the direction of this persona's difference from typical users, not the number." };
1565
+ btn.addEventListener("mouseenter", function () { showTip(btn, info); });
1566
+ btn.addEventListener("focus", function () { showTip(btn, info); });
1567
+ btn.addEventListener("mouseleave", hideTip);
1568
+ btn.addEventListener("blur", hideTip);
1569
+ slot.appendChild(btn);
1570
+ } else if (st) {
1571
+ slot.appendChild(el("span", "ptag", String(st)));
1572
+ }
1573
+ return slot;
1574
+ }
1575
+
1160
1576
  async function chainBlock(data, b) {
1161
1577
  var layers = at(data, b.field);
1162
1578
  if (!isObjArray(layers) || !layers.length) return null;
@@ -1243,6 +1659,11 @@ const RUNTIME = String.raw `
1243
1659
  node.alt = label + " overlay for this page: " + (ov.legend || "");
1244
1660
  pane.appendChild(node);
1245
1661
  if (ov.legend) pane.appendChild(el("p", "cap", String(ov.legend)));
1662
+ // The map's own block: the attention model's constants, which the
1663
+ // score's block leaves out because the map is context, not input.
1664
+ if (isProvenance(ov.provenance)) {
1665
+ pane.appendChild(provenanceLine(ov.provenance, "map", null, "of the attention model, counted apart from the score"));
1666
+ }
1246
1667
  } catch (e) {
1247
1668
  pane.innerHTML = "";
1248
1669
  pane.appendChild(el("p", "cap", "Could not load the " + label + " overlay: " + (e && e.message ? e.message : e)));
@@ -1269,18 +1690,20 @@ const RUNTIME = String.raw `
1269
1690
  });
1270
1691
  wrap.appendChild(pane);
1271
1692
 
1272
- if (isFinite(additive) && isFinite(total) && additive > 0) {
1693
+ if (isFinite(additive) && additive > 0) {
1273
1694
  var amp = el("div", "amp");
1274
1695
  // Computed BEFORE the bars that read it. var hoists, so declaring this
1275
1696
  // below its first use left the guard comparing undefined !== null, which
1276
1697
  // is true -- the guard passed and the bar was drawn with no value.
1277
1698
  //
1278
- // raw, not total: total is a sigmoid of raw, so plotting additive against
1279
- // it drew a 1.034 bar beside a 0.410 bar and told the 60%-less story in
1280
- // pixels while the caption underneath said +2%. Both bars are in
1699
+ // The in-sequence total comes from totalField, which must be in
1700
+ // layer-cost units like additive: cognitive_effort points it at raw. Its
1701
+ // total was a logistic of raw and is now a percentile; plotting additive
1702
+ // against it drew a 1.034 bar beside a 0.410 bar and told the 60%-less
1703
+ // story in pixels while the caption underneath said +2%. Both bars are in
1281
1704
  // layer-cost units or the comparison is omitted.
1282
- var rawTotal = at(data, "cognitiveTransportCost.raw");
1283
- var ratio = typeof rawTotal === "number" ? rawTotal / additive : null;
1705
+ var rawTotal = isFinite(total) ? total : null;
1706
+ var ratio = rawTotal === null ? null : rawTotal / additive;
1284
1707
  var pct = ratio === null ? 0 : Math.round(Math.abs(1 - ratio) * 100);
1285
1708
  if (ratio !== null) {
1286
1709
  amp.appendChild(bar("sum of layers", additive, 205, false, null));
@@ -1297,11 +1720,12 @@ const RUNTIME = String.raw `
1297
1720
  // The mock payload used to build it had additive < total, which is the
1298
1721
  // one shape the tool has never actually produced. (2026-08-02)
1299
1722
  amp.appendChild(el("p", "ampnote",
1300
- // No raw total, no claim. Falling back to the sigmoid-squashed total
1301
- // here is what produced "about 60% less" on a chain that actually adds
1302
- // ~1%: total is a sigmoid and additive is not, so their ratio measures
1303
- // the squash, not the chain. A payload from before raw existed cannot
1304
- // answer this, and saying so beats answering it wrongly.
1723
+ // No raw total, no claim. Falling back to the 0-1 total here is what
1724
+ // produced "about 60% less" on a chain that actually adds ~1%: total
1725
+ // was then a logistic of raw (now a percentile) and additive is a sum,
1726
+ // so their ratio measures the mapping, not the chain. A payload from
1727
+ // before raw existed cannot answer this, and saying so beats answering
1728
+ // it wrongly.
1305
1729
  ratio === null
1306
1730
  ? "This result predates the raw cost being reported, so the chain's effect cannot be computed from it. The bars above are still exact; only the comparison below them is unavailable."
1307
1731
  : ratio > 1.0005
@@ -1331,10 +1755,15 @@ const RUNTIME = String.raw `
1331
1755
  levels.forEach(function (lv) {
1332
1756
  var band = el("div", "band", String(lv.label || ""));
1333
1757
  var v = Number(lv.value);
1334
- if (isFinite(v)) band.style.backgroundColor = "hsl(" + (v * 120) + ", 65%, 50%)";
1335
1758
  // Active band chosen by nearest level, since the queried value rarely
1336
1759
  // lands exactly on a band's own value.
1337
- if (label && String(lv.label) === String(label)) band.classList.add("on");
1760
+ var on = !!label && String(lv.label) === String(label);
1761
+ if (isFinite(v)) {
1762
+ var fill = hslHex(v * 120, 0.65, 0.5);
1763
+ if (on) { band.style.backgroundColor = fill; band.style.color = readableOn(fill); }
1764
+ else band.style.backgroundColor = "hsla(" + (v * 120) + ", 65%, 50%, .42)";
1765
+ }
1766
+ if (on) band.classList.add("on");
1338
1767
  band.title = String(lv.label || "");
1339
1768
  bands.appendChild(band);
1340
1769
  });
@@ -1460,6 +1889,18 @@ const RUNTIME = String.raw `
1460
1889
  minor: "#2563eb", low: "#2563eb", info: "#64748b", notice: "#64748b",
1461
1890
  };
1462
1891
 
1892
+ // hsl (degrees, 0-1, 0-1) as #rrggbb, so a fill drawn as hsl can be given
1893
+ // a readable label by readableOn below.
1894
+ function hslHex(h, s, l) {
1895
+ var k = function (n) { return (n + h / 30) % 12; };
1896
+ var a = s * Math.min(l, 1 - l);
1897
+ var f = function (n) {
1898
+ var c = l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
1899
+ return ("0" + Math.round(c * 255).toString(16)).slice(-2);
1900
+ };
1901
+ return "#" + f(0) + f(8) + f(4);
1902
+ }
1903
+
1463
1904
  // Whichever of near-white / near-black contrasts better with a fill, by WCAG
1464
1905
  // relative luminance rather than by eye.
1465
1906
  function readableOn(hex) {
@@ -1770,7 +2211,8 @@ const RUNTIME = String.raw `
1770
2211
  if (b.type === "traits") {
1771
2212
  used[b.field.split(".")[0]] = true;
1772
2213
  var rows = normaliseTraits(at(data, b.field), b);
1773
- return rows.length ? { node: traitsBlock(rows, b), count: rows.length } : null;
2214
+ var tprov = traitProv(data, b);
2215
+ return rows.length ? { node: traitsBlock(rows, b, tprov), count: rows.length } : null;
1774
2216
  }
1775
2217
  if (b.type === "manager") {
1776
2218
  used[b.field.split(".")[0]] = true;
@@ -1781,7 +2223,8 @@ const RUNTIME = String.raw `
1781
2223
  used[b.field.split(".")[0]] = true;
1782
2224
  if (b.personaField) used[b.personaField.split(".")[0]] = true;
1783
2225
  var erows = normaliseTraits(at(data, b.field), b);
1784
- return erows.length ? { node: editorBlock(erows, b, data), count: erows.length } : null;
2226
+ var eprov = traitProv(data, b);
2227
+ return erows.length ? { node: editorBlock(erows, b, data, eprov), count: erows.length } : null;
1785
2228
  }
1786
2229
  if (b.type === "chain") {
1787
2230
  [b.field, b.bottleneckField, b.additiveField, b.totalField, b.overlaysField].forEach(function (f) {
@@ -1810,8 +2253,30 @@ const RUNTIME = String.raw `
1810
2253
  if (b.type === "note") {
1811
2254
  used[b.field.split(".")[0]] = true;
1812
2255
  var t = at(data, b.field);
2256
+ if (b.templates && t && typeof t === "object") {
2257
+ var said = fillTemplate(t, b.by, b.templates);
2258
+ return said ? { node: el("p", null, said), count: null } : null;
2259
+ }
1813
2260
  return t ? { node: el("p", null, fmt(t)), count: null } : null;
1814
2261
  }
2262
+ if (b.type === "rank") {
2263
+ used[b.field.split(".")[0]] = true;
2264
+ var rnode = rankBlock(data, b);
2265
+ return rnode ? { node: rnode, count: null } : null;
2266
+ }
2267
+ if (b.type === "times") {
2268
+ used[b.field.split(".")[0]] = true;
2269
+ var tnode = timesBlock(data, b);
2270
+ return tnode ? { node: tnode, count: null } : null;
2271
+ }
2272
+ if (b.type === "provenance") {
2273
+ used[b.field.split(".")[0]] = true;
2274
+ var pv = at(data, b.field);
2275
+ if (!isProvenance(pv)) return null;
2276
+ return b.variant === "full"
2277
+ ? { node: provenanceFull(pv, b, data), count: provTotal(pv) }
2278
+ : { node: provenanceLine(pv, b.subject, b.seeAlso), count: null };
2279
+ }
1815
2280
  if (b.type === "rest") {
1816
2281
  var rest = [];
1817
2282
  Object.keys(data).forEach(function (k) {
@@ -1833,7 +2298,7 @@ const RUNTIME = String.raw `
1833
2298
  var b = blocks[i];
1834
2299
  if (b.type === "drawer") continue;
1835
2300
  var built = await buildBlock(data, b);
1836
- if (built) out.push(section(b.title || titleize(b.type), built.count, built.node, false));
2301
+ if (built) out.push(section(b.title || titleize(b.type), built.count, built.node, !!b.open));
1837
2302
  }
1838
2303
  return out;
1839
2304
  }
@@ -1923,12 +2388,22 @@ const RUNTIME = String.raw `
1923
2388
  hero.facts.forEach(function (f) {
1924
2389
  var v = at(data, f.field);
1925
2390
  var value = null, attn = false;
1926
- if (f.countFlag && isObjArray(v)) {
2391
+ var label = f.label, range = null;
2392
+ if (f.format === "rank") {
2393
+ var w = rankWords(v, f.intervalField ? at(data, f.intervalField) : null, f.figuresField ? at(data, f.figuresField) : null);
2394
+ if (w) {
2395
+ value = w.chip;
2396
+ label = "of real pages" + (w.interval ? ", " + (f.level || 95) + "% interval " : "");
2397
+ if (w.interval) range = w.interval[0] + "-" + w.interval[1] + "%";
2398
+ }
2399
+ } else if (f.countFlag && isObjArray(v)) {
1927
2400
  var ok = v.filter(function (x) { return x[f.countFlag]; }).length;
1928
2401
  value = ok + "/" + v.length;
1929
2402
  attn = !!f.attnWhenShort && ok < v.length;
1930
2403
  } else if (Array.isArray(v)) {
1931
2404
  value = String(v.length);
2405
+ } else if (f.templates && v && typeof v === "object") {
2406
+ value = fillTemplate(v, f.by, f.templates);
1932
2407
  } else if (v && typeof v === "object") {
1933
2408
  // A bag of named numbers is a countable collection too. Without this
1934
2409
  // a fact pointing at a traits object silently produced nothing, which
@@ -1940,8 +2415,12 @@ const RUNTIME = String.raw `
1940
2415
  if (value === null) return;
1941
2416
  used[f.field.split(".")[0]] = true;
1942
2417
  var chip = el("span", "fact" + (attn ? " attn" : ""));
1943
- chip.appendChild(el("b", null, value));
1944
- chip.appendChild(el("span", null, f.label));
2418
+ // A rank is one phrase: its value never breaks, and its range is
2419
+ // kept whole ("78-" / "84%" read as two numbers at 393 px).
2420
+ chip.appendChild(el("b", f.format === "rank" ? "rk" : null, value));
2421
+ var lab = el("span", null, label);
2422
+ if (range) lab.appendChild(el("span", "nw", range));
2423
+ chip.appendChild(lab);
1945
2424
  strip.appendChild(chip);
1946
2425
  });
1947
2426
  if (strip.childNodes.length) band.appendChild(strip);