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.
- package/README.md +2 -2
- package/dist/analysis/accessibility-empathy.d.ts +29 -1
- package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +69 -35
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +11 -7
- package/dist/browser.js.map +1 -1
- package/dist/cif-score.d.ts +56 -5
- package/dist/cif-score.d.ts.map +1 -1
- package/dist/cif-score.js +595 -346
- package/dist/cif-score.js.map +1 -1
- package/dist/cli.js +10 -8
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/default-traits.d.ts +14 -0
- package/dist/cognitive/default-traits.d.ts.map +1 -0
- package/dist/cognitive/default-traits.js +36 -0
- package/dist/cognitive/default-traits.js.map +1 -0
- package/dist/cognitive/emotions.d.ts.map +1 -1
- package/dist/cognitive/emotions.js +104 -174
- package/dist/cognitive/emotions.js.map +1 -1
- package/dist/cognitive/index.d.ts +28 -0
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +353 -287
- package/dist/cognitive/index.js.map +1 -1
- package/dist/cognitive/temporal.d.ts +0 -24
- package/dist/cognitive/temporal.d.ts.map +1 -1
- package/dist/cognitive/temporal.js +45 -35
- package/dist/cognitive/temporal.js.map +1 -1
- package/dist/data/ctc-reference-v2-2026-10-24.d.ts +16 -0
- package/dist/data/ctc-reference-v2-2026-10-24.d.ts.map +1 -0
- package/dist/data/ctc-reference-v2-2026-10-24.js +1004 -0
- package/dist/data/ctc-reference-v2-2026-10-24.js.map +1 -0
- package/dist/ideal-profiles-gap.d.ts +1 -0
- package/dist/ideal-profiles-gap.d.ts.map +1 -1
- package/dist/ideal-profiles-gap.js +9 -1
- package/dist/ideal-profiles-gap.js.map +1 -1
- package/dist/ideal-profiles.d.ts +8 -0
- package/dist/ideal-profiles.d.ts.map +1 -1
- package/dist/ideal-profiles.js +28 -24
- package/dist/ideal-profiles.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +7 -8
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-tools/account-personas.d.ts.map +1 -1
- package/dist/mcp-tools/account-personas.js +3 -1
- package/dist/mcp-tools/account-personas.js.map +1 -1
- package/dist/mcp-tools/base/answer-provenance.d.ts +28 -0
- package/dist/mcp-tools/base/answer-provenance.d.ts.map +1 -0
- package/dist/mcp-tools/base/answer-provenance.js +32 -0
- package/dist/mcp-tools/base/answer-provenance.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +0 -3
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +152 -84
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +1 -1
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/chain-values.d.ts +38 -0
- package/dist/mcp-tools/base/chain-values.d.ts.map +1 -0
- package/dist/mcp-tools/base/chain-values.js +85 -0
- package/dist/mcp-tools/base/chain-values.js.map +1 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +126 -120
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/gif-tools.js +23 -5
- package/dist/mcp-tools/base/gif-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +229 -172
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/site-familiarity.d.ts +31 -0
- package/dist/mcp-tools/base/site-familiarity.d.ts.map +1 -0
- package/dist/mcp-tools/base/site-familiarity.js +30 -0
- package/dist/mcp-tools/base/site-familiarity.js.map +1 -0
- package/dist/mcp-tools/base/site-knowledge-tools.js +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
- package/dist/mcp-tools/base/story-narrative.d.ts +50 -0
- package/dist/mcp-tools/base/story-narrative.d.ts.map +1 -0
- package/dist/mcp-tools/base/story-narrative.js +88 -0
- package/dist/mcp-tools/base/story-narrative.js.map +1 -0
- package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/values-tools.js +19 -5
- package/dist/mcp-tools/base/values-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts +5 -5
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +38 -11
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +6 -6
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.js +5 -1
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
- package/dist/mcp-tools/tier-gate.js +1 -1
- package/dist/mcp-tools/tool-result-saver.d.ts +7 -0
- package/dist/mcp-tools/tool-result-saver.d.ts.map +1 -1
- package/dist/mcp-tools/tool-result-saver.js +11 -0
- package/dist/mcp-tools/tool-result-saver.js.map +1 -1
- package/dist/mcp-tools/trait-provenance.d.ts +109 -0
- package/dist/mcp-tools/trait-provenance.d.ts.map +1 -0
- package/dist/mcp-tools/trait-provenance.js +187 -0
- package/dist/mcp-tools/trait-provenance.js.map +1 -0
- package/dist/mcp-tools/ui-resources.d.ts +6 -7
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
- package/dist/mcp-tools/ui-resources.js +162 -12
- package/dist/mcp-tools/ui-resources.js.map +1 -1
- package/dist/mcp-tools/widget-kit.d.ts +129 -3
- package/dist/mcp-tools/widget-kit.d.ts.map +1 -1
- package/dist/mcp-tools/widget-kit.js +507 -28
- package/dist/mcp-tools/widget-kit.js.map +1 -1
- package/dist/performance/metrics.d.ts.map +1 -1
- package/dist/performance/metrics.js +4 -1
- package/dist/performance/metrics.js.map +1 -1
- package/dist/persona-questionnaire.js +1 -1
- package/dist/persona-questionnaire.js.map +1 -1
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +133 -82
- package/dist/personas.js.map +1 -1
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +5 -2
- package/dist/recording/engine.js.map +1 -1
- package/dist/recording/saliency-overlay.d.ts +8 -3
- package/dist/recording/saliency-overlay.d.ts.map +1 -1
- package/dist/recording/saliency-overlay.js +14 -5
- package/dist/recording/saliency-overlay.js.map +1 -1
- package/dist/recording/types.js +1 -1
- package/dist/testing/nl-test-suite.d.ts.map +1 -1
- package/dist/testing/nl-test-suite.js +2 -1
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/trait-reference.d.ts +19 -2
- package/dist/trait-reference.d.ts.map +1 -1
- package/dist/trait-reference.js +19 -2
- package/dist/trait-reference.js.map +1 -1
- package/dist/types.d.ts +32 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +62 -52
- package/dist/types.js.map +1 -1
- package/dist/values/persona-completeness.js +1 -1
- package/dist/values/persona-completeness.js.map +1 -1
- package/dist/visual/ab-comparison.d.ts.map +1 -1
- package/dist/visual/ab-comparison.js +4 -1
- package/dist/visual/ab-comparison.js.map +1 -1
- package/dist/visual/attention-quality.d.ts +1 -1
- package/dist/visual/attention-quality.js +1 -1
- package/dist/visual/attention-transport.d.ts +74 -10
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +353 -193
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/cognitive-models.d.ts +77 -55
- package/dist/visual/cognitive-models.d.ts.map +1 -1
- package/dist/visual/cognitive-models.js +231 -104
- package/dist/visual/cognitive-models.js.map +1 -1
- package/dist/visual/cognitive-transport-chain.d.ts +328 -30
- package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
- package/dist/visual/cognitive-transport-chain.js +576 -353
- package/dist/visual/cognitive-transport-chain.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +446 -13
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +1085 -245
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/cross-browser.d.ts.map +1 -1
- package/dist/visual/cross-browser.js +8 -6
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/ctc-bands.d.ts +91 -0
- package/dist/visual/ctc-bands.d.ts.map +1 -0
- package/dist/visual/ctc-bands.js +117 -0
- package/dist/visual/ctc-bands.js.map +1 -0
- package/dist/visual/ctc-reference.d.ts +287 -0
- package/dist/visual/ctc-reference.d.ts.map +1 -0
- package/dist/visual/ctc-reference.js +376 -0
- package/dist/visual/ctc-reference.js.map +1 -0
- package/dist/visual/demand-laws.d.ts +145 -0
- package/dist/visual/demand-laws.d.ts.map +1 -0
- package/dist/visual/demand-laws.js +220 -0
- package/dist/visual/demand-laws.js.map +1 -0
- package/dist/visual/distance-metrics.d.ts +23 -1
- package/dist/visual/distance-metrics.d.ts.map +1 -1
- package/dist/visual/distance-metrics.js +49 -18
- package/dist/visual/distance-metrics.js.map +1 -1
- package/dist/visual/dro-bound.d.ts +220 -0
- package/dist/visual/dro-bound.d.ts.map +1 -0
- package/dist/visual/dro-bound.js +341 -0
- package/dist/visual/dro-bound.js.map +1 -0
- package/dist/visual/exact-w1.d.ts +60 -0
- package/dist/visual/exact-w1.d.ts.map +1 -0
- package/dist/visual/exact-w1.js +247 -0
- package/dist/visual/exact-w1.js.map +1 -0
- package/dist/visual/human-input.d.ts.map +1 -1
- package/dist/visual/human-input.js +45 -41
- package/dist/visual/human-input.js.map +1 -1
- package/dist/visual/index.d.ts +2 -0
- package/dist/visual/index.d.ts.map +1 -1
- package/dist/visual/index.js +2 -0
- package/dist/visual/index.js.map +1 -1
- package/dist/visual/law-times-report.d.ts +49 -0
- package/dist/visual/law-times-report.d.ts.map +1 -0
- package/dist/visual/law-times-report.js +38 -0
- package/dist/visual/law-times-report.js.map +1 -0
- package/dist/visual/model-provenance.d.ts +151 -0
- package/dist/visual/model-provenance.d.ts.map +1 -0
- package/dist/visual/model-provenance.js +334 -0
- package/dist/visual/model-provenance.js.map +1 -0
- package/dist/visual/normal-cdf.d.ts +14 -0
- package/dist/visual/normal-cdf.d.ts.map +1 -0
- package/dist/visual/normal-cdf.js +26 -0
- package/dist/visual/normal-cdf.js.map +1 -0
- package/dist/visual/perceptual-transport.d.ts +28 -4
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +66 -32
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/dist/visual/provenance/attention.d.ts +55 -0
- package/dist/visual/provenance/attention.d.ts.map +1 -0
- package/dist/visual/provenance/attention.js +188 -0
- package/dist/visual/provenance/attention.js.map +1 -0
- package/dist/visual/provenance/chain.d.ts +41 -0
- package/dist/visual/provenance/chain.d.ts.map +1 -0
- package/dist/visual/provenance/chain.js +66 -0
- package/dist/visual/provenance/chain.js.map +1 -0
- package/dist/visual/provenance/cif.d.ts +42 -0
- package/dist/visual/provenance/cif.d.ts.map +1 -0
- package/dist/visual/provenance/cif.js +116 -0
- package/dist/visual/provenance/cif.js.map +1 -0
- package/dist/visual/provenance/ctc-reference.d.ts +36 -0
- package/dist/visual/provenance/ctc-reference.d.ts.map +1 -0
- package/dist/visual/provenance/ctc-reference.js +96 -0
- package/dist/visual/provenance/ctc-reference.js.map +1 -0
- package/dist/visual/provenance/distance.d.ts +10 -0
- package/dist/visual/provenance/distance.d.ts.map +1 -0
- package/dist/visual/provenance/distance.js +72 -0
- package/dist/visual/provenance/distance.js.map +1 -0
- package/dist/visual/provenance/dro.d.ts +10 -0
- package/dist/visual/provenance/dro.d.ts.map +1 -0
- package/dist/visual/provenance/dro.js +2 -0
- package/dist/visual/provenance/dro.js.map +1 -0
- package/dist/visual/provenance/emotion.d.ts +26 -0
- package/dist/visual/provenance/emotion.d.ts.map +1 -0
- package/dist/visual/provenance/emotion.js +99 -0
- package/dist/visual/provenance/emotion.js.map +1 -0
- package/dist/visual/provenance/human-input.d.ts +19 -0
- package/dist/visual/provenance/human-input.d.ts.map +1 -0
- package/dist/visual/provenance/human-input.js +84 -0
- package/dist/visual/provenance/human-input.js.map +1 -0
- package/dist/visual/provenance/journey-group.d.ts +15 -0
- package/dist/visual/provenance/journey-group.d.ts.map +1 -0
- package/dist/visual/provenance/journey-group.js +15 -0
- package/dist/visual/provenance/journey-group.js.map +1 -0
- package/dist/visual/provenance/journey-mcp.d.ts +24 -0
- package/dist/visual/provenance/journey-mcp.d.ts.map +1 -0
- package/dist/visual/provenance/journey-mcp.js +54 -0
- package/dist/visual/provenance/journey-mcp.js.map +1 -0
- package/dist/visual/provenance/journey.d.ts +34 -0
- package/dist/visual/provenance/journey.d.ts.map +1 -0
- package/dist/visual/provenance/journey.js +184 -0
- package/dist/visual/provenance/journey.js.map +1 -0
- package/dist/visual/provenance/laws.d.ts +38 -0
- package/dist/visual/provenance/laws.d.ts.map +1 -0
- package/dist/visual/provenance/laws.js +40 -0
- package/dist/visual/provenance/laws.js.map +1 -0
- package/dist/visual/provenance/page-metrics.d.ts +42 -0
- package/dist/visual/provenance/page-metrics.d.ts.map +1 -0
- package/dist/visual/provenance/page-metrics.js +79 -0
- package/dist/visual/provenance/page-metrics.js.map +1 -0
- package/dist/visual/provenance/perceptual.d.ts +63 -0
- package/dist/visual/provenance/perceptual.d.ts.map +1 -0
- package/dist/visual/provenance/perceptual.js +115 -0
- package/dist/visual/provenance/perceptual.js.map +1 -0
- package/dist/visual/provenance/persona-rates.d.ts +84 -0
- package/dist/visual/provenance/persona-rates.d.ts.map +1 -0
- package/dist/visual/provenance/persona-rates.js +112 -0
- package/dist/visual/provenance/persona-rates.js.map +1 -0
- package/dist/visual/provenance/personas.d.ts +122 -0
- package/dist/visual/provenance/personas.d.ts.map +1 -0
- package/dist/visual/provenance/personas.js +298 -0
- package/dist/visual/provenance/personas.js.map +1 -0
- package/dist/visual/provenance/profiles.d.ts +39 -0
- package/dist/visual/provenance/profiles.d.ts.map +1 -0
- package/dist/visual/provenance/profiles.js +37 -0
- package/dist/visual/provenance/profiles.js.map +1 -0
- package/dist/visual/provenance/temporal.d.ts +21 -0
- package/dist/visual/provenance/temporal.d.ts.map +1 -0
- package/dist/visual/provenance/temporal.js +56 -0
- package/dist/visual/provenance/temporal.js.map +1 -0
- package/dist/visual/provenance/visual-distance.d.ts +22 -0
- package/dist/visual/provenance/visual-distance.d.ts.map +1 -0
- package/dist/visual/provenance/visual-distance.js +30 -0
- package/dist/visual/provenance/visual-distance.js.map +1 -0
- package/dist/visual/regression.d.ts.map +1 -1
- package/dist/visual/regression.js +14 -9
- package/dist/visual/regression.js.map +1 -1
- package/dist/visual/responsive.d.ts.map +1 -1
- package/dist/visual/responsive.js +4 -1
- package/dist/visual/responsive.js.map +1 -1
- package/dist/visual/scope-prose.js +2 -2
- package/dist/visual/scope-prose.js.map +1 -1
- package/dist/visual/seeded-random.d.ts +16 -0
- package/dist/visual/seeded-random.d.ts.map +1 -0
- package/dist/visual/seeded-random.js +25 -0
- package/dist/visual/seeded-random.js.map +1 -0
- package/dist/visual/stored-accessibility-traits.d.ts +27 -0
- package/dist/visual/stored-accessibility-traits.d.ts.map +1 -0
- package/dist/visual/stored-accessibility-traits.js +35 -0
- package/dist/visual/stored-accessibility-traits.js.map +1 -0
- 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{
|
|
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
|
|
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
|
-
.
|
|
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
|
-
|
|
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:
|
|
430
|
+
color:var(--ink);
|
|
421
431
|
border:2px solid transparent;overflow:hidden;white-space:nowrap;padding:0 .2rem}
|
|
422
|
-
.band.on{
|
|
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);
|
|
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) &&
|
|
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
|
-
//
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
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 =
|
|
1283
|
-
var ratio =
|
|
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
|
|
1301
|
-
//
|
|
1302
|
-
//
|
|
1303
|
-
// the
|
|
1304
|
-
// answer this, and saying so beats answering
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1944
|
-
|
|
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);
|