@bongos/core 1.24.48 → 1.24.50

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 (99) hide show
  1. package/.bongos-core.json +152 -112
  2. package/config/branding.neutral.json +2 -1
  3. package/docs/copy-inventory.md +1108 -1108
  4. package/docs/copy-registry.json +1557 -1557
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +5 -4
  7. package/docs/page-readings.json +681 -681
  8. package/modules/hall-ui/public/ai-studio.html +2 -2
  9. package/modules/hall-ui/public/atlas.html +2 -2
  10. package/modules/hall-ui/public/blockers.html +2 -2
  11. package/modules/hall-ui/public/board-room.html +2 -2
  12. package/modules/hall-ui/public/city.html +1 -1
  13. package/modules/hall-ui/public/collab.html +2 -2
  14. package/modules/hall-ui/public/commands.html +2 -2
  15. package/modules/hall-ui/public/copy-desk.html +2 -2
  16. package/modules/hall-ui/public/deploy.html +2 -2
  17. package/modules/hall-ui/public/goal-map.html +2 -2
  18. package/modules/hall-ui/public/goals.html +2 -2
  19. package/modules/hall-ui/public/government.html +2 -2
  20. package/modules/hall-ui/public/hall-render.js +1 -1
  21. package/modules/hall-ui/public/idea.html +2 -2
  22. package/modules/hall-ui/public/ideas.html +2 -2
  23. package/modules/hall-ui/public/index.html +5 -5
  24. package/modules/hall-ui/public/market-watch-item.html +2 -2
  25. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  26. package/modules/hall-ui/public/market-watch.html +2 -2
  27. package/modules/hall-ui/public/modules.html +2 -2
  28. package/modules/hall-ui/public/money.html +2 -2
  29. package/modules/hall-ui/public/my-agents.html +2 -2
  30. package/modules/hall-ui/public/primer.html +2 -2
  31. package/modules/hall-ui/public/profile.html +2 -2
  32. package/modules/hall-ui/public/project-settings.html +2 -2
  33. package/modules/hall-ui/public/roster.html +2 -2
  34. package/modules/hall-ui/public/settings.html +2 -2
  35. package/modules/hall-ui/public/shell.js +1 -1
  36. package/modules/hall-ui/public/street.html +1 -1
  37. package/modules/hall-ui/public/studio.html +2 -2
  38. package/modules/hall-ui/public/style.css +7 -9
  39. package/modules/hall-ui/public/task.html +2 -2
  40. package/modules/hall-ui/public/thinking.html +2 -2
  41. package/modules/hall-ui/public/tweak-editor.html +2 -2
  42. package/modules/hall-ui/public/watch.html +2 -2
  43. package/modules/hall-ui/public/welcome-boot.css +3 -4
  44. package/modules/hall-ui/public/welcome-boot.js +5 -3
  45. package/modules/hall-ui/public/work.html +2 -2
  46. package/modules/public-landing/public/account.html +1 -5
  47. package/modules/public-landing/public/assets/cosmos.css +7 -6
  48. package/modules/public-landing/public/community.html +1 -5
  49. package/modules/public-landing/public/connections.html +1 -5
  50. package/modules/public-landing/public/contact.html +1 -5
  51. package/modules/public-landing/public/guild.html +1 -5
  52. package/modules/public-landing/public/index.html +5 -8
  53. package/modules/public-landing/public/invitations.html +1 -5
  54. package/modules/public-landing/public/privacy.html +1 -5
  55. package/modules/public-landing/public/projects.html +1 -5
  56. package/modules/public-landing/public/terms.html +1 -5
  57. package/modules/raise/views/gate.html +4 -3
  58. package/modules/raise/views/nda.html +2 -1
  59. package/modules/raise/views/page.html +4 -3
  60. package/modules/status-ui/public/explore.html +14 -4
  61. package/modules/status-ui/public/index.html +26 -126
  62. package/modules/status-ui/public/index.states.json +3 -3
  63. package/modules/status-ui/public/status.js +53 -27
  64. package/modules/status-ui/public/style.css +640 -833
  65. package/modules/ui-design/public/brand/apple-touch-icon.png +0 -0
  66. package/modules/ui-design/public/brand/favicon.svg +1 -0
  67. package/modules/ui-design/public/brand/mark-body.svg +1 -0
  68. package/modules/ui-design/public/brand/mark-dark.svg +1 -0
  69. package/modules/ui-design/public/brand/mark-dot.svg +1 -0
  70. package/modules/ui-design/public/brand/mark-light.svg +1 -0
  71. package/modules/ui-design/public/brand/mark-mono.svg +1 -0
  72. package/modules/ui-design/public/brand/mark.svg +21 -0
  73. package/package-lock.json +2 -2
  74. package/package.json +1 -1
  75. package/release-notes.json +12 -0
  76. package/scripts/bongos/fitness.js +1 -2
  77. package/scripts/bongos/gen-brand.js +99 -0
  78. package/scripts/gds/gen-brand.js +7 -0
  79. package/scripts/status-mirror-sync.js +44 -45
  80. package/src/bongos/platform-visibility-gate.js +6 -3
  81. package/src/bongos/prelaunch-gate.js +6 -3
  82. package/src/bongos/serve-internal.js +9 -7
  83. package/src/brand-mark.js +96 -0
  84. package/src/branding.js +2 -0
  85. package/src/hall-look.js +7 -7
  86. package/src/module-api.js +1 -1
  87. package/tests/brand_mark.mjs +195 -0
  88. package/tests/cloud_bongos_pack.mjs +5 -2
  89. package/tests/hall_loading.mjs +8 -10
  90. package/tests/loading_mark.mjs +7 -13
  91. package/tests/nav_permission_atoms.mjs +2 -1
  92. package/tests/raise.mjs +3 -1
  93. package/tests/status_page_chrome.mjs +195 -0
  94. package/tests/welcome_boot.mjs +5 -2
  95. package/modules/raise/public/favicon.svg +0 -1
  96. package/modules/raise/public/logo-ink.svg +0 -1
  97. package/modules/raise/public/logo-night.svg +0 -1
  98. package/modules/status-ui/public/cursors/arrow.png +0 -0
  99. package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
@@ -3,118 +3,33 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>{{worldName}} — the public ledger</title>
6
+ <title>{{productName}} — the public ledger</title>
7
7
  <meta name="description" content="The public ledger of {{worldName}} — {{currency}} spent, works wrought, and the citizens who shape her stones.">
8
- <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='serif'>Α</text></svg>">
9
- <link rel="preconnect" href="https://fonts.googleapis.com">
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&display=swap" rel="stylesheet">
8
+ <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
+ <link rel="apple-touch-icon" href="/modules/ui-design/public/brand/apple-touch-icon.png">
10
+ <!-- The faces are the pack's: the server (and the off-box mirror) splice the pack's
11
+ font links, palette and favicon at the end of <head> (themeMarkup), so no face
12
+ is named here (task 1005089). -->
12
13
  <link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
13
- <link rel="stylesheet" href="/style.css?v=2026-06-12-wip3">
14
+ <link rel="stylesheet" href="/modules/hall-ui/public/controls.css">
15
+ <link rel="stylesheet" href="/style.css">
14
16
  </head>
15
17
  <body>
16
18
 
17
- <!-- task 1475 (idea 280): a fixed, always-reachable "enlarge the page" control
18
- for readability. Wired + rendered by the zoom block in status.js. -->
19
- <button class="ledger-zoom" id="ledger-zoom" type="button" aria-label="Text size"></button>
20
-
21
- <!-- Hidden SVG sprite — referenced via <use href="#id"> below. -->
22
- <svg width="0" height="0" style="position:absolute" aria-hidden="true">
23
- <defs>
24
- <symbol id="orn-map" viewBox="0 0 32 32">
25
- <g fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">
26
- <polygon points="3,4 11,7 21,4 29,7 29,28 21,25 11,28 3,25"/>
27
- <line x1="11" y1="7" x2="11" y2="28"/>
28
- <line x1="21" y1="4" x2="21" y2="25"/>
29
- <path d="M7 14 Q9 11 11 14 Q13 17 15 14"/>
30
- <path d="M21 10 Q23 8 25 10 Q27 12 25 15"/>
31
- </g>
32
- </symbol>
33
-
34
- <symbol id="sigil-amphora" viewBox="0 0 64 64">
35
- <g fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
36
- <path d="M22 12 L42 12 L40 16 Q47 20 47 32 Q47 46 40 54 L24 54 Q17 46 17 32 Q17 20 24 16 Z"/>
37
- <path d="M17 22 Q9 24 9 30 Q9 36 16 40"/>
38
- <path d="M47 22 Q55 24 55 30 Q55 36 48 40"/>
39
- <line x1="20" y1="32" x2="44" y2="32"/>
40
- <line x1="20" y1="36" x2="44" y2="36"/>
41
- <line x1="22" y1="12" x2="42" y2="12" stroke-width="2.2"/>
42
- </g>
43
- <g fill="currentColor">
44
- <ellipse cx="32" cy="44" rx="1" ry="1"/>
45
- </g>
46
- </symbol>
47
-
48
- <symbol id="orn-column" viewBox="0 0 32 32">
49
- <g stroke="currentColor" stroke-width="1.1" stroke-linecap="round">
50
- <rect x="4" y="2" width="24" height="2" fill="currentColor" stroke="none"/>
51
- <rect x="6" y="4" width="20" height="2" fill="none"/>
52
- <line x1="9" y1="6" x2="9" y2="26"/>
53
- <line x1="13" y1="6" x2="13" y2="26"/>
54
- <line x1="16" y1="6" x2="16" y2="26"/>
55
- <line x1="19" y1="6" x2="19" y2="26"/>
56
- <line x1="23" y1="6" x2="23" y2="26"/>
57
- <rect x="6" y="26" width="20" height="2" fill="none"/>
58
- <rect x="4" y="28" width="24" height="2" fill="currentColor" stroke="none"/>
59
- </g>
60
- </symbol>
61
-
62
- <symbol id="orn-coin" viewBox="0 0 32 32">
63
- <g fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">
64
- <circle cx="16" cy="16" r="13"/>
65
- <circle cx="16" cy="16" r="10"/>
66
- <path d="M10 20 Q16 12 22 12"/>
67
- </g>
68
- <g fill="currentColor">
69
- <ellipse cx="13" cy="17.5" rx="1.4" ry="0.7" transform="rotate(-35 13 17.5)"/>
70
- <ellipse cx="15.5" cy="14.5" rx="1.4" ry="0.7" transform="rotate(-15 15.5 14.5)"/>
71
- <ellipse cx="19" cy="13" rx="1.4" ry="0.7" transform="rotate(0 19 13)"/>
72
- </g>
73
- </symbol>
74
-
75
- <symbol id="orn-scroll" viewBox="0 0 32 32">
76
- <g fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">
77
- <path d="M8 9 L24 9"/>
78
- <path d="M8 23 L24 23"/>
79
- <ellipse cx="6" cy="16" rx="2.2" ry="7"/>
80
- <ellipse cx="26" cy="16" rx="2.2" ry="7"/>
81
- <line x1="11" y1="13" x2="22" y2="13"/>
82
- <line x1="11" y1="16" x2="22" y2="16"/>
83
- <line x1="11" y1="19" x2="20" y2="19"/>
84
- </g>
85
- <g fill="currentColor">
86
- <ellipse cx="6" cy="16" rx="0.8" ry="2.6"/>
87
- <ellipse cx="26" cy="16" rx="0.8" ry="2.6"/>
88
- </g>
89
- </symbol>
90
-
91
- <symbol id="orn-laurel" viewBox="0 0 32 32">
92
- <g fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">
93
- <path d="M16 29 Q5 24 5 14 Q5 6 11 3"/>
94
- <path d="M16 29 Q27 24 27 14 Q27 6 21 3"/>
95
- <path d="M14 29 L18 29"/>
96
- </g>
97
- <g fill="currentColor">
98
- <ellipse cx="9" cy="22" rx="2.2" ry="1" transform="rotate(60 9 22)"/>
99
- <ellipse cx="6" cy="18" rx="2.2" ry="1" transform="rotate(80 6 18)"/>
100
- <ellipse cx="6" cy="13" rx="2.2" ry="1" transform="rotate(100 6 13)"/>
101
- <ellipse cx="9" cy="8" rx="2.2" ry="1" transform="rotate(130 9 8)"/>
102
- <ellipse cx="23" cy="22" rx="2.2" ry="1" transform="rotate(120 23 22)"/>
103
- <ellipse cx="26" cy="18" rx="2.2" ry="1" transform="rotate(100 26 18)"/>
104
- <ellipse cx="26" cy="13" rx="2.2" ry="1" transform="rotate(80 26 13)"/>
105
- <ellipse cx="23" cy="8" rx="2.2" ry="1" transform="rotate(50 23 8)"/>
106
- </g>
107
- </symbol>
108
-
109
- <symbol id="orn-diamond" viewBox="0 0 12 12">
110
- <rect x="3" y="3" width="6" height="6" transform="rotate(45 6 6)" fill="currentColor"/>
111
- </symbol>
112
- </defs>
113
- </svg>
19
+ <!-- The one top bar (task 1005089): the shared mark and the product name, with the
20
+ text-size control (task 1475, idea 280; wired + rendered by status.js). -->
21
+ <header class="topbar">
22
+ <div class="topbar__inner">
23
+ <span class="topbar__brand">
24
+ <svg class="topbar__mark" viewBox="0 0 200 200" aria-hidden="true"><use href="/modules/ui-design/public/brand/mark.svg#mark"/></svg>
25
+ <span class="topbar__name">{{productName}}</span>
26
+ </span>
27
+ <button class="ledger-zoom" id="ledger-zoom" type="button" aria-label="Text size"></button>
28
+ </div>
29
+ </header>
114
30
 
115
- <header class="herm">
116
- <svg class="herm__sigil" aria-hidden="true"><use href="#sigil-amphora"/></svg>
117
- <h1>{{worldName}}</h1>
31
+ <section class="herm" aria-labelledby="herm-title">
32
+ <h1 id="herm-title">{{worldName}}</h1>
118
33
  <p class="herm__epigraph">The Public Ledger of the City's Making</p>
119
34
  <p class="herm__sub">A shipping container has washed ashore upon the coast south of Athens. About it a new civilization rises. Set out below: the city's affairs, openly recorded.</p>
120
35
  <p class="herm__meta">
@@ -122,18 +37,12 @@
122
37
  <span class="dot" aria-hidden="true">·</span>
123
38
  <span class="herm__updated" id="last-updated">Tablet being consulted…</span>
124
39
  </p>
125
- </header>
126
-
127
- <div class="rule rule--banner" aria-hidden="true">
128
- <svg><use href="#orn-diamond"/></svg>
129
- <svg><use href="#orn-diamond"/></svg>
130
- <svg><use href="#orn-diamond"/></svg>
131
- </div>
40
+ </section>
132
41
 
133
42
  <!-- Sticky themed section nav — #409. Same design language as the hall
134
43
  (public-builders/style.css .hall-nav__*). Built from a SECTIONS constant
135
44
  in status.js. Empty until populated — [hidden] keeps the strip from
136
- flashing as a blank parchment band before chips arrive. -->
45
+ flashing as an empty band before chips arrive. -->
137
46
  <nav id="status-nav" class="status-nav" aria-label="Sections of the ledger" hidden>
138
47
  <div class="status-nav__inner">
139
48
  <ul class="status-nav__list" id="status-nav-list"></ul>
@@ -170,7 +79,6 @@
170
79
 
171
80
  <section class="tablet" id="leader-tablet" aria-labelledby="leader-heading">
172
81
  <div class="tablet__head">
173
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-laurel"/></svg>
174
82
  <div class="tablet__heading">
175
83
  <h2 id="leader-heading">Architects of {{worldName}}</h2>
176
84
  <p class="tablet__subtitle">Citizens who have set the most stone.</p>
@@ -183,7 +91,6 @@
183
91
 
184
92
  <section class="tablet" id="foundry-tablet" aria-labelledby="foundry-heading">
185
93
  <div class="tablet__head">
186
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-laurel"/></svg>
187
94
  <div class="tablet__heading">
188
95
  <h2 id="foundry-heading">The Foundry</h2>
189
96
  <p class="tablet__subtitle">How the city's craft is changing — works shipped and time per work over the last ninety days. The whole, not the hands.</p>
@@ -201,7 +108,6 @@
201
108
 
202
109
  <section class="tablet" id="versions-tablet" aria-labelledby="versions-heading">
203
110
  <div class="tablet__head">
204
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-column"/></svg>
205
111
  <div class="tablet__heading">
206
112
  <h2 id="versions-heading">Works in Progress</h2>
207
113
  <p class="tablet__subtitle">The endeavours now underway in the city.</p>
@@ -214,7 +120,6 @@
214
120
 
215
121
  <section class="tablet" id="cost-tablet" aria-labelledby="cost-heading">
216
122
  <div class="tablet__head">
217
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-coin"/></svg>
218
123
  <div class="tablet__heading">
219
124
  <h2 id="cost-heading">The Treasury</h2>
220
125
  <p class="tablet__subtitle">{{Currency}} spent, against the value wrought.</p>
@@ -274,7 +179,6 @@
274
179
 
275
180
  <section class="tablet" id="shipped-tablet" aria-labelledby="shipped-heading">
276
181
  <div class="tablet__head">
277
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-scroll"/></svg>
278
182
  <div class="tablet__heading">
279
183
  <h2 id="shipped-heading">Lately Inscribed</h2>
280
184
  <p class="tablet__subtitle">Newest entries set down upon the ledger.</p>
@@ -293,15 +197,14 @@
293
197
  <li class="empty">No works yet inscribed.</li>
294
198
  </ul>
295
199
  <nav id="shipped-pager" class="shipped-pager" hidden aria-label="Lately Inscribed pages">
296
- <button type="button" id="shipped-prev" class="shipped-pager__btn" aria-label="Previous page">‹ earlier page</button>
200
+ <button type="button" id="shipped-prev" class="btn btn--ghost btn--sm shipped-pager__btn" aria-label="Previous page">‹ earlier page</button>
297
201
  <span id="shipped-pageinfo" class="shipped-pager__info" aria-live="polite">page 1 of 1</span>
298
- <button type="button" id="shipped-next" class="shipped-pager__btn" aria-label="Next page">later page ›</button>
202
+ <button type="button" id="shipped-next" class="btn btn--ghost btn--sm shipped-pager__btn" aria-label="Next page">later page ›</button>
299
203
  </nav>
300
204
  </section>
301
205
 
302
206
  <section class="tablet tablet--wide" id="world-tablet" aria-labelledby="world-heading">
303
207
  <div class="tablet__head">
304
- <svg class="tablet__icon" aria-hidden="true"><use href="#orn-map"/></svg>
305
208
  <div class="tablet__heading">
306
209
  <h2 id="world-heading">The World</h2>
307
210
  <p class="tablet__subtitle">A true-to-life portrait of {{worldName}} as she stands today, rendered from the city's own stones.</p>
@@ -323,9 +226,6 @@
323
226
  </main>
324
227
 
325
228
  <footer class="foot">
326
- <div class="rule rule--small" aria-hidden="true">
327
- <svg><use href="#orn-diamond"/></svg>
328
- </div>
329
229
  <p class="foot__seal">By order of the Senate</p>
330
230
  <!-- task 1002853: every company field is optional, so each clause drops whole when its pack field is empty -->
331
231
  <!--brand-if:companyName--><p>{{worldName}} is the work of <!--brand-if:companySite--><a href="{{companySite}}" target="_blank" rel="noopener"><!--/brand-if:companySite--><strong>{{companyName}}</strong><!--brand-if:companySite--></a><!--/brand-if:companySite--><!--brand-if:companyTagline-->, {{companyTagline}}<!--/brand-if:companyTagline-->.</p><!--/brand-if:companyName-->
@@ -333,7 +233,7 @@
333
233
  </footer>
334
234
 
335
235
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
336
- <script src="/status.js?v=2026-06-12-wip3"></script>
236
+ <script src="/status.js"></script>
337
237
 
338
238
  </body>
339
239
  </html>
@@ -1,10 +1,10 @@
1
1
  {
2
- "_": "The status page's states for modules/ui-design/kit/render.js (task 1005053, its first): the ledger once its reads have answered, from the stub's public fixtures. The status sheet has no dark rules, so `modes` names light alone and there is no query to walk. The stub ships no world snapshots, so the World tablet keeps its still line: its loader stopped when the snapshot list 404'd, which is the failure path this state pictures. The waiting state itself is looked at with the stub's LATENCY=<ms> (serve.js).",
2
+ "_": "The status page's states for modules/ui-design/kit/render.js (task 1005053, its first): the ledger once its reads have answered, from the stub's public fixtures. Since task 1005089 the sheet is on the chrome world with its dark tier written twice (html[data-theme=\"dark\"] and the prefers-color-scheme twin), so `modes` names both; the page has no switch of its own, so there is no query to walk. The stub ships no world snapshots, so the World tablet keeps its still line: its loader stopped when the snapshot list 404'd, which is the failure path this state pictures. The stub ships no per-version done-when fixture either, and status.js reads each version's criteria best-effort (a failed read leaves the row without them), so those 404s are expected. The waiting state itself is looked at with the stub's LATENCY=<ms> (serve.js).",
3
3
  "page": "/",
4
4
  "surface": "status-ui",
5
- "modes": ["light"],
5
+ "modes": ["dark", "light"],
6
6
  "modeQuery": false,
7
- "ignoreRequests": ["/snapshots/manifest\\.json$", "/api/(gds|bongos)/public/(grades|analytics/ecosystem)", "cdn\\.jsdelivr\\.net"],
7
+ "ignoreRequests": ["/snapshots/manifest\\.json$", "/api/(gds|bongos)/public/(grades|analytics/ecosystem)", "/api/(gds|bongos)/versions/[^/]+/done-when$", "cdn\\.jsdelivr\\.net"],
8
8
  "ignoreConsole": ["status of 404", "snapshots", "ecosystem", "Failed to load resource"],
9
9
  "states": {
10
10
  "loaded": { "auth": false, "actions": [["wait", 600]], "expect": { "visible": ["#versions", "#world-snapshot"], "count": [["#versions .loading-mark", 0], ["#world-snapshot .loading-mark", 0]] } }
@@ -643,6 +643,10 @@ async function loadCosts() {
643
643
 
644
644
  if (!window.Chart) return; // Chart.js failed to load — skip
645
645
 
646
+ // The chart paints with the page's own tokens (task 1005089): a canvas cannot
647
+ // read var(), so the resolved values are read once per draw, in whichever mode
648
+ // the page is in. Spend is ink, value the --info tone; the faces are the pack's.
649
+ const t = chartTokens();
646
650
  const ctx = els.costChart.getContext('2d');
647
651
  if (costChart) costChart.destroy();
648
652
  costChart = new Chart(ctx, {
@@ -654,23 +658,23 @@ async function loadCosts() {
654
658
  label: `${curLabel()} spent`,
655
659
  data: cumCost,
656
660
  fill: true,
657
- borderColor: '#2a2110',
658
- backgroundColor: 'rgba(139, 111, 44, 0.16)',
661
+ borderColor: t.ink,
662
+ backgroundColor: t.inkWash,
659
663
  tension: 0.2,
660
664
  pointRadius: 2.5,
661
- pointBackgroundColor: '#2a2110',
665
+ pointBackgroundColor: t.ink,
662
666
  borderWidth: 1.5,
663
667
  },
664
668
  {
665
669
  label: 'value wrought',
666
670
  data: cumValue,
667
671
  fill: false,
668
- borderColor: '#2f5a3f',
669
- backgroundColor: 'rgba(74, 125, 92, 0.18)',
672
+ borderColor: t.info,
673
+ backgroundColor: t.info,
670
674
  borderDash: [5, 4],
671
675
  tension: 0.2,
672
676
  pointRadius: 2.5,
673
- pointBackgroundColor: '#2f5a3f',
677
+ pointBackgroundColor: t.info,
674
678
  borderWidth: 1.5,
675
679
  },
676
680
  ],
@@ -678,7 +682,7 @@ async function loadCosts() {
678
682
  options: {
679
683
  responsive: true,
680
684
  maintainAspectRatio: false,
681
- font: { family: '"EB Garamond", Georgia, serif' },
685
+ font: { family: t.body },
682
686
  plugins: {
683
687
  legend: {
684
688
  display: true,
@@ -686,14 +690,16 @@ async function loadCosts() {
686
690
  labels: {
687
691
  boxWidth: 22,
688
692
  padding: 18,
689
- font: { family: '"EB Garamond", Georgia, serif', size: 13, style: 'italic' },
690
- color: '#6e5b3a',
693
+ font: { family: t.body, size: 13 },
694
+ color: t.inkSoft,
691
695
  },
692
696
  },
693
697
  tooltip: {
694
- backgroundColor: 'rgba(42, 33, 16, 0.92)',
695
- titleFont: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
696
- bodyFont: { family: '"EB Garamond", Georgia, serif', size: 13 },
698
+ backgroundColor: t.ink,
699
+ titleColor: t.card,
700
+ bodyColor: t.card,
701
+ titleFont: { family: t.body, size: 12, weight: '500' },
702
+ bodyFont: { family: t.body, size: 13 },
697
703
  padding: 10,
698
704
  callbacks: {
699
705
  label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })} dr.`,
@@ -706,28 +712,28 @@ async function loadCosts() {
706
712
  title: {
707
713
  display: true,
708
714
  text: `${curLabel()} (cumulative)`,
709
- font: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
710
- color: '#735f3d',
715
+ font: { family: t.body, size: 12, weight: '500' },
716
+ color: t.inkSoft,
711
717
  },
712
718
  ticks: {
713
719
  callback: (v) => `${v} dr.`,
714
- font: { family: '"EB Garamond", Georgia, serif', size: 12 },
715
- color: '#6e5b3a',
720
+ font: { family: t.body, size: 12 },
721
+ color: t.inkSoft,
716
722
  },
717
- grid: { color: 'rgba(139, 111, 44, 0.18)', drawBorder: false },
723
+ grid: { color: t.rule, drawBorder: false },
718
724
  },
719
725
  x: {
720
726
  title: {
721
727
  display: true,
722
728
  text: 'date',
723
- font: { family: '"Cinzel", Georgia, serif', size: 11, weight: '500' },
724
- color: '#735f3d',
729
+ font: { family: t.body, size: 12, weight: '500' },
730
+ color: t.inkSoft,
725
731
  },
726
732
  grid: { display: false },
727
733
  ticks: {
728
734
  maxTicksLimit: 10,
729
- font: { family: '"EB Garamond", Georgia, serif', size: 12 },
730
- color: '#6e5b3a',
735
+ font: { family: t.body, size: 12 },
736
+ color: t.inkSoft,
731
737
  },
732
738
  },
733
739
  },
@@ -735,6 +741,23 @@ async function loadCosts() {
735
741
  });
736
742
  }
737
743
 
744
+ // The resolved values of the tokens the cost chart paints with, read from the
745
+ // page (a canvas cannot take var()). An unset token reads '' and Chart.js keeps
746
+ // its own default for it.
747
+ function chartTokens() {
748
+ const cs = window.getComputedStyle(document.body);
749
+ const v = (name) => cs.getPropertyValue(name).trim();
750
+ return {
751
+ ink: v('--ink'),
752
+ inkSoft: v('--ink-soft'),
753
+ inkWash: v('--glass-strong'),
754
+ info: v('--info'),
755
+ rule: v('--rule-soft'),
756
+ card: v('--bg-card'),
757
+ body: v('--font-body') || 'sans-serif',
758
+ };
759
+ }
760
+
738
761
  function toDayKey(d) {
739
762
  // Postgres returns 'YYYY-MM-DD' as a string OR an ISO timestamp depending
740
763
  // on transport — normalise to YYYY-MM-DD so dedupe works across formats.
@@ -1050,7 +1073,7 @@ function stampRefreshTime() {
1050
1073
  // kinds-of-work fingerprint. Fed by /api/bongos/public/analytics/ecosystem.
1051
1074
  // Inline SVG sparklines — no chart library for this tile.
1052
1075
 
1053
- function foundrySparkline(values, color) {
1076
+ function foundrySparkline(values, tone) {
1054
1077
  const W = 320, H = 54, pad = 4;
1055
1078
  const nums = (values || []).map((v) => (Number.isFinite(Number(v)) ? Number(v) : 0));
1056
1079
  if (nums.length === 0) return '';
@@ -1060,9 +1083,12 @@ function foundrySparkline(values, color) {
1060
1083
  const y = (v) => H - pad - ((v - min) / span) * (H - 2 * pad);
1061
1084
  const line = nums.map((v, i) => `${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(' ');
1062
1085
  const area = `${pad},${H - pad} ${line} ${(W - pad)},${H - pad}`;
1063
- return `<svg class="foundry-spark" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="trend">
1064
- <polyline class="foundry-spark__area" points="${area}" fill="${color}" />
1065
- <polyline class="foundry-spark__line" points="${line}" fill="none" stroke="${color}" />
1086
+ // The colour is the sheet's (.foundry-spark, and --ink for the ink tone): a
1087
+ // token, never a literal handed in from here (task 1005089).
1088
+ const cls = tone === 'ink' ? 'foundry-spark foundry-spark--ink' : 'foundry-spark';
1089
+ return `<svg class="${cls}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="trend">
1090
+ <polyline class="foundry-spark__area" points="${area}" />
1091
+ <polyline class="foundry-spark__line" points="${line}" />
1066
1092
  </svg>`;
1067
1093
  }
1068
1094
 
@@ -1095,8 +1121,8 @@ async function loadEcosystem() {
1095
1121
  const shipVals = (data.ship_rate || []).map((r) => Number(r.n));
1096
1122
  const durVals = (data.duration_trend || []).map((r) => Number(r.avg_actual));
1097
1123
  chartsEl.innerHTML =
1098
- `<div class="foundry__chart"><div class="foundry__chart-title">Works shipped / day</div>${foundrySparkline(shipVals, '#2f5a3f') || '<p class="empty">no data</p>'}</div>` +
1099
- `<div class="foundry__chart"><div class="foundry__chart-title">Avg time per work (min)</div>${foundrySparkline(durVals, '#2d6e7e') || '<p class="empty">no data</p>'}</div>`;
1124
+ `<div class="foundry__chart"><div class="foundry__chart-title">Works shipped / day</div>${foundrySparkline(shipVals, 'ink') || '<p class="empty">no data</p>'}</div>` +
1125
+ `<div class="foundry__chart"><div class="foundry__chart-title">Avg time per work (min)</div>${foundrySparkline(durVals, 'info') || '<p class="empty">no data</p>'}</div>`;
1100
1126
 
1101
1127
  const kinds = (data.top_kinds || []).slice(0, 6);
1102
1128
  if (kindsEl) {