@bongos/core 1.24.86 → 1.24.88

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 (62) hide show
  1. package/.bongos-core.json +76 -66
  2. package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +15 -0
  3. package/docs/copy-inventory.md +6 -6
  4. package/docs/copy-registry.json +6 -6
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +2 -0
  7. package/docs/page-readings.json +36 -36
  8. package/modules/hall-ui/public/ai-studio.html +1 -1
  9. package/modules/hall-ui/public/atlas.html +1 -1
  10. package/modules/hall-ui/public/blockers.html +1 -1
  11. package/modules/hall-ui/public/board-room.html +1 -1
  12. package/modules/hall-ui/public/city-draw.js +127 -69
  13. package/modules/hall-ui/public/city.css +37 -3
  14. package/modules/hall-ui/public/city.html +16 -5
  15. package/modules/hall-ui/public/city.js +26 -1
  16. package/modules/hall-ui/public/collab.html +1 -1
  17. package/modules/hall-ui/public/commands.html +1 -1
  18. package/modules/hall-ui/public/copy-desk.html +1 -1
  19. package/modules/hall-ui/public/deploy.html +1 -1
  20. package/modules/hall-ui/public/goal-map.html +2 -2
  21. package/modules/hall-ui/public/goals.html +1 -1
  22. package/modules/hall-ui/public/government.html +1 -1
  23. package/modules/hall-ui/public/home-city-draw.js +88 -54
  24. package/modules/hall-ui/public/home-city.css +25 -3
  25. package/modules/hall-ui/public/idea.html +1 -1
  26. package/modules/hall-ui/public/ideas.html +1 -1
  27. package/modules/hall-ui/public/index.html +6 -4
  28. package/modules/hall-ui/public/market-watch-item.html +1 -1
  29. package/modules/hall-ui/public/market-watch-rules.html +1 -1
  30. package/modules/hall-ui/public/market-watch.html +1 -1
  31. package/modules/hall-ui/public/modules.html +1 -1
  32. package/modules/hall-ui/public/money.html +1 -1
  33. package/modules/hall-ui/public/my-agents.html +1 -1
  34. package/modules/hall-ui/public/primer.html +1 -1
  35. package/modules/hall-ui/public/profile.html +1 -1
  36. package/modules/hall-ui/public/project-settings.html +1 -1
  37. package/modules/hall-ui/public/roster.html +1 -1
  38. package/modules/hall-ui/public/settings.html +1 -1
  39. package/modules/hall-ui/public/sky-time.js +206 -0
  40. package/modules/hall-ui/public/street.html +2 -2
  41. package/modules/hall-ui/public/studio.html +1 -1
  42. package/modules/hall-ui/public/style.css +86 -1
  43. package/modules/hall-ui/public/task.html +1 -1
  44. package/modules/hall-ui/public/thinking.html +2 -2
  45. package/modules/hall-ui/public/tweak-editor.html +1 -1
  46. package/modules/hall-ui/public/visor.css +13 -14
  47. package/modules/hall-ui/public/watch.html +1 -1
  48. package/modules/hall-ui/public/work.html +1 -1
  49. package/modules/hall-ui/records/home-city.md +13 -0
  50. package/modules/hall-ui/records/living-city.md +12 -0
  51. package/modules/hall-ui/records/visor.md +1 -1
  52. package/modules/ui-design/public/brand.html +1 -1
  53. package/package-lock.json +2 -2
  54. package/package.json +1 -1
  55. package/release-notes.json +12 -0
  56. package/src/module-api.js +1 -1
  57. package/tests/hall_city.mjs +72 -22
  58. package/tests/hall_city_living.mjs +10 -3
  59. package/tests/hall_sky_time.mjs +361 -0
  60. package/tests/hall_street.mjs +2 -1
  61. package/tests/hall_visor.mjs +18 -8
  62. package/tests/home_city.mjs +54 -5
@@ -37,10 +37,13 @@
37
37
  // shapes, so tests run it in node (the roadmap-timeline.js precedent). The scene
38
38
  // comes back as a tree of plain shape objects, never markup (see citySvg).
39
39
  //
40
- // THE PALETTE is the city's own drawing data (city-draw.js isoTheme, trimmed to
41
- // what this scene spends: the dark blocks in the black, the white paper model in
42
- // light). Everything around the drawing takes the hall's tokens; salmon
43
- // (var(--accent)) is spent only on what waits on the viewer.
40
+ // THE PALETTE (task 1005196: the city in the clouds) is the hour's sky tokens
41
+ // (--sky-now-*, style.css; set by sky-time.js) and mixes of them, as city-draw.js
42
+ // isoTheme is: this file types in no colour. There is no ground; a face that stands in
43
+ // the cloud fades into it (the gradients are city-draw.js skyDefs, put in this scene's
44
+ // <defs>), and cloud lies behind the city, at each tower's foot and in front of it.
45
+ // A face pin, the artists' glass and the salmon of what waits on the viewer are the
46
+ // hall's own tokens, so they mean the same at every hour.
44
47
  (function (root) {
45
48
  'use strict';
46
49
 
@@ -67,50 +70,64 @@
67
70
  const LOOSE = { cx: 690, cy: 312, rx: 150, ry: 54 };
68
71
  const MOON = { x: 905, y: 70, r: 28 };
69
72
  // the museum as the skyline sees it: the marble block with its portico, the atrium behind
73
+ // Where the banks of cloud lie about the city's back corner (dx, dy from it, scale): far
74
+ // and back behind it, near in front. The cloud's shape is city-draw.js's (cloudAt, puffAt).
75
+ const BANKS = {
76
+ far: [[-250, 50, 0.55], [250, 40, 0.55], [0, -60, 0.7]],
77
+ back: [[-200, 130, 0.55], [220, 120, 0.55], [0, 20, 0.5]],
78
+ near: [[-330, 322, 0.45], [215, 336, 0.45], [-60, 392, 0.5]],
79
+ };
70
80
  const MUSEUM_SIL = [{ k: 'museum', x0: 4.9, y0: 12.4, x1: 11.1, y1: 13.7, h: 43, portico: true }, { k: 'museum', x0: 5.6, y0: 10.9, x1: 10.4, y1: 12.3, h: 40 }];
71
81
 
72
82
  const r1 = (n) => Math.round(n * 10) / 10;
73
83
  const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0);
74
84
  const sum = (a) => (Array.isArray(a) ? a.reduce((s, v) => s + num(v), 0) : 0);
75
85
 
76
- // The sky's own drawing data, as theme() is the city's: night over black, dusk over paper.
77
- function skyTheme(mode) {
78
- if (mode === 'light') {
79
- return { star: '#b8732f', glow: 'rgba(214,140,70,0.28)', line: 'rgba(150,95,40,0.38)', label: 'rgba(23,24,26,0.58)', near: '#b9b6c2', far: '#d4d2dc', civic: '#c9bfb2', win: 'rgba(214,140,70,0.85)', moon: '#3b3f52', shade: '#e8eaf3', streak: 'rgba(214,140,70,0.9)', field: 'rgba(23,24,26,0.2)', beacon: '#fb9569', outline: '#8a5523' };
80
- }
81
- return { star: '#ffe2c2', glow: 'rgba(255,214,170,0.35)', line: 'rgba(255,214,170,0.32)', label: 'rgba(255,255,255,0.5)', near: '#060609', far: '#14141d', civic: '#0d0c12', win: 'rgba(255,214,160,0.9)', moon: '#f4ead8', shade: '#08080e', streak: 'rgba(255,226,194,0.95)', field: 'rgba(255,255,255,0.18)', beacon: '#fff4e6', outline: '#ffe2c2' };
86
+ // now('lit') is the token the hour's blend is written to; mix() is a share of one colour
87
+ // in another (or in nothing: a glaze or a thin line).
88
+ // The banks and the sky gradients never change with the week, and citySvg runs once a
89
+ // frame while the slider glides, so both are worked out once and kept (per scene origin).
90
+ const bankPaths = new Map();
91
+ const bankPath = (Gov, key, at, ox, oy) => {
92
+ const k = key + '|' + ox + '|' + oy;
93
+ if (!bankPaths.has(k)) bankPaths.set(k, at.map((c) => Gov.cloudAt(ox + c[0], oy + c[1], c[2])).join(''));
94
+ return bankPaths.get(k);
95
+ };
96
+ let gradDefs = null;
97
+ const skyGrads = (Gov, node) => {
98
+ if (!(Gov && typeof Gov.skyDefs === 'function')) return [];
99
+ if (!gradDefs) gradDefs = Gov.skyDefs([]);
100
+ return gradDefs.map((g) => node('linearGradient', { id: g.id, x1: 0, y1: 0, x2: 0, y2: 1 }, g.stops.map((st) => node('stop', { offset: st[0], 'stop-color': st[1] }))));
101
+ };
102
+ const now = (role) => 'var(--sky-now-' + role + ')';
103
+ const mix = (a, pct, b) => 'color-mix(in srgb, ' + a + ' ' + pct + '%, ' + (b || 'transparent') + ')';
104
+ // a face that stands in the cloud: the scene's gradient for that side (city-draw.js
105
+ // skyDefs), then the plain face colour for a scene drawn without them
106
+ const FOOT_L = 'url(#sky-face-l) ' + now('lit'), FOOT_R = 'url(#sky-face-r) ' + now('shade');
107
+ const SEA = 'var(--sea)';
108
+
109
+ // The sky's drawing data: the hour's tokens (the labels are words, so they are the hall's ink).
110
+ function skyTheme() {
111
+ const win = now('lamp');
112
+ return { star: win, glow: mix(win, 32), line: mix(win, 32), label: 'var(--ink-soft)', near: now('shade'), far: now('lit'), civic: mix(now('shade'), 70, now('top')), win: mix(win, 88), moon: now('cloud'), shade: now('mid'), streak: mix(win, 92), field: mix(now('line'), 30), beacon: win, outline: win };
82
113
  }
83
114
 
84
- function theme(mode) {
85
- if (mode === 'light') {
86
- return {
87
- work: { t: '#f6f5f8', l: '#e6e5e9', r: '#d2d1d6', s: 'rgba(23,24,26,0.22)' },
88
- plinth: { t: '#efeef1', l: '#e2e1e5', r: '#cfced3', s: 'rgba(23,24,26,0.22)' },
89
- glassL: 'rgba(91,111,208,0.30)', glassR: 'rgba(91,111,208,0.42)', glassT: 'rgba(91,111,208,0.22)',
90
- floor: 'rgba(23,24,26,0.10)', steel: 'rgba(23,24,26,0.62)', scaffold: 'rgba(23,24,26,0.40)', lit: 'rgba(226,150,90,0.55)',
91
- slab: { t: '#d9d8dd', l: '#c4c3c9', r: '#b2b1b8', s: 'rgba(23,24,26,0.4)' },
92
- ground: 'rgba(23,24,26,0.035)', groundLine: 'rgba(23,24,26,0.08)',
93
- orb: '#d98a4e', orbGlow: 'rgba(217,138,78,0.30)', ideaLine: 'rgba(217,138,78,0.55)',
94
- av: '#e4e3e7', avAgent: '#d3d8f2', avInk: '#17181a',
95
- star: '#b8732f',
96
- marble: { t: '#fbfaf7', l: '#ece9e2', r: '#d8d4cb', s: 'rgba(23,24,26,0.28)' }, granite: { t: '#cfcdd2', l: '#bcbac0', r: '#a9a7ae', s: 'rgba(23,24,26,0.25)' },
97
- mglass: { t: 'rgba(91,111,208,0.16)', l: 'rgba(91,111,208,0.22)', r: 'rgba(91,111,208,0.3)', s: 'rgba(60,80,180,0.5)' },
98
- grass: '#c9ddbf', grassLine: 'rgba(40,70,40,0.2)', path: '#ece6da', water: '#a9cbe3', tree: ['#8fb27c', '#7aa36b', '#9dbd89'], col: '#ffffff', dark: 'rgba(23,24,26,0.35)',
99
- };
100
- }
115
+ // One palette: the hall is dark only (ADR 0402) and the hour, not a mode, changes it.
116
+ function theme() {
117
+ const roof = now('roof'), lit = now('lit'), shade = now('shade'), line = now('line');
101
118
  return {
102
- work: { t: '#34343a', l: '#1c1c20', r: '#111114', s: 'rgba(255,255,255,0.12)' },
103
- plinth: { t: '#27272c', l: '#141417', r: '#0b0b0d', s: 'rgba(255,255,255,0.08)' },
104
- glassL: 'rgba(110,130,230,0.30)', glassR: 'rgba(110,130,230,0.20)', glassT: 'rgba(150,170,255,0.30)',
105
- floor: 'rgba(255,255,255,0.06)', steel: 'rgba(255,255,255,0.70)', scaffold: 'rgba(255,255,255,0.40)', lit: 'rgba(255,200,150,0.55)',
106
- slab: { t: '#5a5a62', l: '#3a3a40', r: '#2a2a2f', s: 'rgba(255,255,255,0.2)' },
107
- ground: 'rgba(255,255,255,0.018)', groundLine: 'rgba(255,255,255,0.05)',
108
- orb: '#ffe2c2', orbGlow: 'rgba(255,214,170,0.35)', ideaLine: 'rgba(255,214,170,0.45)',
109
- av: '#2c2c32', avAgent: '#2b3157', avInk: '#ffffff',
110
- star: '#ffe2c2',
111
- marble: { t: '#dcd8d0', l: '#bfbab0', r: '#9f9a90', s: 'rgba(255,255,255,0.22)' }, granite: { t: '#3c3c42', l: '#28282d', r: '#1c1c20', s: 'rgba(255,255,255,0.12)' },
112
- mglass: { t: 'rgba(150,170,255,0.24)', l: 'rgba(120,140,230,0.2)', r: 'rgba(100,120,210,0.16)', s: 'rgba(170,190,255,0.45)' },
113
- grass: '#273f2d', grassLine: 'rgba(160,220,160,0.10)', path: '#3d3a36', water: '#34577a', tree: ['#4f7a4c', '#43683f', '#5b8656'], col: '#e8e4dc', dark: 'rgba(0,0,0,0.55)',
119
+ work: { t: roof, l: lit, r: shade, lg: FOOT_L, rg: FOOT_R, s: mix(roof, 40) },
120
+ plinth: { t: mix(roof, 60, now('base')), l: lit, r: shade, lg: FOOT_L, rg: FOOT_R, s: mix(roof, 30) },
121
+ glassL: mix(SEA, 36), glassR: mix(SEA, 26), glassT: mix(SEA, 36),
122
+ floor: mix(roof, 24), steel: mix(line, 90), scaffold: mix(line, 60), lit: mix(now('lamp'), 72),
123
+ slab: { t: roof, l: mix(lit, 70, roof), r: lit, s: mix(line, 40) },
124
+ orbGlow: mix(now('lamp'), 35),
125
+ av: 'var(--bg-card)', avAgent: mix(SEA, 26, 'var(--bg-card)'), avInk: 'var(--ink)',
126
+ star: now('lamp'),
127
+ marble: { t: roof, l: mix(lit, 42, roof), r: mix(shade, 46, roof), s: mix(line, 36) }, granite: { t: mix(roof, 70, lit), l: lit, r: shade, lg: FOOT_L, rg: FOOT_R, s: mix(roof, 30) },
128
+ mglass: { t: mix(SEA, 20), l: mix(SEA, 26), r: mix(SEA, 34), s: mix(SEA, 55) },
129
+ grass: mix(now('cloud-shade'), 34), grassLine: mix(line, 14), path: mix(now('cloud'), 46), water: mix(now('mid'), 46), tree: [mix(lit, 70, now('cloud-shade')), mix(lit, 86, now('cloud-shade')), mix(lit, 55, now('cloud-shade'))], col: roof, dark: mix(shade, 72),
130
+ plaza: mix(now('cloud'), 34), plazaLine: mix(line, 34),
114
131
  };
115
132
  }
116
133
 
@@ -138,11 +155,11 @@
138
155
  const row = (k) => Array.from({ length: W }, (_, i) => num(p[k] && p[k][i]));
139
156
  people[String(p.id)] = { id: String(p.id), login: p.login || '', name: p.name || p.login || 'Builder', avatar: p.avatar_url || null, agent: !!p.agent, build: row('build'), art: row('art'), idea: row('idea'), gov: row('gov'), vote: row('vote') };
140
157
  });
141
- const now = d.now || {};
158
+ const nowAt = d.now || {};
142
159
  const sites = {};
143
- (Array.isArray(now.sites) ? now.sites : []).forEach((s) => { if (s && s.goal_id != null && s.builder_id != null) sites[String(s.goal_id)] = { builder: String(s.builder_id), agent: !!s.agent }; });
160
+ (Array.isArray(nowAt.sites) ? nowAt.sites : []).forEach((s) => { if (s && s.goal_id != null && s.builder_id != null) sites[String(s.goal_id)] = { builder: String(s.builder_id), agent: !!s.agent }; });
144
161
  const waiting = {};
145
- (Array.isArray(now.waiting) ? now.waiting : []).forEach((w) => { if (w && w.goal_id != null && num(w.count) > 0) waiting[String(w.goal_id)] = num(w.count); });
162
+ (Array.isArray(nowAt.waiting) ? nowAt.waiting : []).forEach((w) => { if (w && w.goal_id != null && num(w.count) > 0) waiting[String(w.goal_id)] = num(w.count); });
146
163
  const ideas = (Array.isArray(d.ideas) ? d.ideas : []).map((x) => ({
147
164
  id: String(x.id), week: num(x.week), by: x.by != null ? String(x.by) : null, title: x.title == null ? 'A private idea' : String(x.title),
148
165
  goal: x.goal_id != null ? String(x.goal_id) : null,
@@ -217,7 +234,7 @@
217
234
  // attribute value or a text node, which is also why this file needs no escaper.
218
235
  function citySvg(m, t, opts) {
219
236
  const o = opts || {};
220
- const I = theme(o.mode);
237
+ const I = theme();
221
238
  const Gov = o.gov || null;
222
239
  const vb = viewBoxOf(m);
223
240
  const i = Math.max(0, Math.min(m.W - 1, Math.floor(t + 1e-6)));
@@ -229,28 +246,41 @@
229
246
  const line = (a, b, s, w, dash) => node('line', Object.assign({ x1: r1(a[0]), y1: r1(a[1]), x2: r1(b[0]), y2: r1(b[1]), stroke: s, 'stroke-width': w || 1, 'stroke-linecap': 'round' }, dash ? { 'stroke-dasharray': dash } : {}));
230
247
  const circle = (cx, cy, r, attrs) => node('circle', Object.assign({ cx: r1(cx), cy: r1(cy), r }, attrs));
231
248
  const box = (x0, y0, x1, y1, z0, h, tn) => {
232
- const z1 = z0 + h;
249
+ // a box on the ground stands in the cloud: its two seen faces fade into it
250
+ const z1 = z0 + h, inCloud = !(z0 > 0);
233
251
  return [
234
- face([P(x0, y1, z1), P(x1, y1, z1), P(x1, y1, z0), P(x0, y1, z0)], tn.l, tn.s),
235
- face([P(x1, y1, z1), P(x1, y0, z1), P(x1, y0, z0), P(x1, y1, z0)], tn.r, tn.s),
252
+ face([P(x0, y1, z1), P(x1, y1, z1), P(x1, y1, z0), P(x0, y1, z0)], (inCloud && tn.lg) || tn.l, tn.s),
253
+ face([P(x1, y1, z1), P(x1, y0, z1), P(x1, y0, z0), P(x1, y1, z0)], (inCloud && tn.rg) || tn.r, tn.s),
236
254
  face([P(x0, y0, z1), P(x1, y0, z1), P(x1, y1, z1), P(x0, y1, z1)], tn.t, tn.s),
237
255
  ];
238
256
  };
239
257
  const quiet = (key) => (o.focus === key ? '' : ' hc-quiet');
240
258
 
259
+ // No ground: the city stands in cloud (task 1005196). The face gradients and the cloud's
260
+ // shape are the Governor City's own (Gov.skyDefs, Gov.cloudAt, Gov.puffAt), never copied:
261
+ // a scene drawn without it has plain faces and no cloud.
262
+ const cloudy = !!(Gov && typeof Gov.cloudAt === 'function' && typeof Gov.puffAt === 'function');
263
+ const bank = (key, at) => node('path', { d: bankPath(Gov, key, at, vb.OX, vb.OY), class: 'hc-cloud hc-cloud--' + key, filter: 'url(#hc-soft' + (key === 'near' ? '' : 'er') + ')', 'aria-hidden': 'true' });
264
+ const puff = (x, y, r) => { const c = P(x, y, 0); return node('path', { d: Gov.puffAt(c[0], c[1] + 4, r), class: 'hc-cloud hc-cloud--row', filter: 'url(#hc-soft)', 'aria-hidden': 'true' }); };
265
+ const grads = skyGrads(Gov, node);
266
+ // the two filters that soften the cloud (nothing that moves is blurred)
267
+ const soft = (id, blur) => node('filter', { id, x: -0.2, y: -0.8, width: 1.4, height: 2.6 }, [node('feGaussianBlur', { stdDeviation: blur })]);
241
268
  const out = [
242
- node('defs', {}, [node('radialGradient', { id: 'hc-orb' }, [node('stop', { offset: 0, 'stop-color': I.orbGlow }), node('stop', { offset: 1, 'stop-color': 'transparent' })])]),
243
- face([P(0, 0), P(m.width, 0), P(m.width, m.depth), P(0, m.depth)], I.ground, I.groundLine),
269
+ node('defs', {}, [node('radialGradient', { id: 'hc-orb' }, [node('stop', { offset: 0, 'stop-color': I.orbGlow }), node('stop', { offset: 1, 'stop-color': 'transparent' })]), soft('hc-soft', 7), soft('hc-softer', 12)].concat(grads)),
244
270
  ];
271
+ if (cloudy) out.push(bank('far', BANKS.far), bank('back', BANKS.back));
245
272
 
246
273
  // the ideas: a thin band of stars high above the city, apart from it — one door to the sky
247
274
  const seen = m.ideas.filter((x) => x.week <= i);
248
275
  const fresh = m.ideas.filter((x) => x.week === i).length;
249
- const band = [node('rect', { x: 60, y: 8, width: 880, height: SKY_H - 16, fill: 'transparent' })];
276
+ // the stars sit in one group the sheet shows at the hour's star strength (out at night,
277
+ // gone by day); the door under them is there at every hour
278
+ const stars = [];
250
279
  seen.forEach((x) => {
251
280
  const h = hash(x.id), recent = i - x.week <= 1;
252
- band.push(circle(80 + (h % 1000) / 1000 * 840, 14 + (Math.floor(h / 1000) % 1000) / 1000 * (SKY_H - 34), recent ? 1.7 : 1.1, Object.assign({ fill: I.star, opacity: recent ? 0.75 : 0.28 }, x.week === i ? { class: 'hc-twinkle' } : {})));
281
+ stars.push(circle(80 + (h % 1000) / 1000 * 840, 14 + (Math.floor(h / 1000) % 1000) / 1000 * (SKY_H - 34), recent ? 1.7 : 1.1, Object.assign({ fill: I.star, opacity: recent ? 0.75 : 0.28 }, x.week === i ? { class: 'hc-twinkle' } : {})));
253
282
  });
283
+ const band = [node('rect', { x: 60, y: 8, width: 880, height: SKY_H - 16, fill: 'transparent' }), node('g', { class: 'hc-stars' }, stars)];
254
284
  out.push(node('a', { href: '/thinking', class: 'hc-door hc-sky', 'aria-label': `The ideas: ${fresh} new this week. Open the sky.`, 'data-tip': 'Ideas', 'data-tip-body': `${fresh} new this week. Click to look up at the sky.` }, band));
255
285
 
256
286
  // every building on the ground, back to front; the civic core draws as one district
@@ -306,7 +336,11 @@
306
336
  const shipped = Math.round(d.build + d.art), byArt = Math.round(d.art);
307
337
  const tip = `${shipped} shipped${byArt ? ` · ${byArt} by artists` : ''}${who ? ` · ${agent ? `${who.name}'s agent` : who.name} on site` : ''} · click to open the goal`;
308
338
  out.push(node('a', { href: `/goals#/goal/${encodeURIComponent(a.id)}`, class: 'hc-tower', 'aria-label': `${a.title}: ${shipped} shipped. Open the goal.`, 'data-tip': a.title, 'data-tip-body': tip }, g));
339
+ // the cloud at its foot, over this tower and under the next one forward
340
+ if (cloudy) out.push(puff(x1 - 0.5, y1 - 0.5, 62));
309
341
  });
342
+ // the near banks, either side of the garden and under the front corner
343
+ if (cloudy) out.push(bank('near', BANKS.near));
310
344
 
311
345
  over.forEach((v, n) => {
312
346
  if (v.k === 'face') { out.push(avatarNode(v.p, v.who, v.agent, v.a, I, n, node, line, circle)); return; }
@@ -321,9 +355,9 @@
321
355
  // The government district: the plaza and the nine civic buildings, drawn by
322
356
  // city-draw.js's own recipes in its own palette — one hover, one door, to the street.
323
357
  function coreNode() {
324
- const CI = Gov.isoTheme(o.mode === 'light' ? 'light' : 'dark');
325
- const k = Gov.isoKit(TW, TH, vb.OX, vb.OY, CIVIC_S, CI, o.mode !== 'light');
326
- const kids = [face([P(CORE.x0, CORE.y0), P(CORE.x1, CORE.y0), P(CORE.x1, CORE.y1), P(CORE.x0, CORE.y1)], CI.plaza, CI.plazaLine)];
358
+ const CI = Gov.isoTheme();
359
+ const k = Gov.isoKit(TW, TH, vb.OX, vb.OY, CIVIC_S, CI, true);
360
+ const kids = [face([P(CORE.x0, CORE.y0), P(CORE.x1, CORE.y0), P(CORE.x1, CORE.y1), P(CORE.x0, CORE.y1)], I.plaza, I.plazaLine)];
327
361
  Gov.CIVIC_SLOTS.slice().sort((p, q) => (p[1] + p[2]) - (q[1] + q[2])).forEach((sl) => {
328
362
  const list = [];
329
363
  Gov.isoInstitution(list, k, sl[0], sl[1] - 0.6, sl[2] - 0.6, {});
@@ -424,7 +458,7 @@
424
458
  // The sky for week t as shapes (citySvg's contract: a tree of plain nodes, never markup).
425
459
  function skySvg(m, t, opts) {
426
460
  const o = opts || {};
427
- const C = skyTheme(o.mode);
461
+ const C = skyTheme();
428
462
  const Gov = o.gov || null;
429
463
  const i = Math.max(0, Math.min(m.W - 1, Math.floor(t + 1e-6)));
430
464
  const latest = t >= m.W - 1 - 1e-3;
@@ -1,14 +1,36 @@
1
1
  /* modules/hall-ui/public/home-city.css — Home's project city (task 1005054; v16, task 1005058).
2
2
  The scene card, the ladder of builders beside it and the time slider under it
3
3
  (home-city.js; record modules/hall-ui/records/home-city.md). Every colour is a
4
- hall token or a mix of one; the tower faces are drawing data in
5
- home-city-draw.js. One sky for both the city and the ladder, so the two read
4
+ hall token or a mix of one; the tower faces are the hour's sky tokens, named
5
+ in home-city-draw.js. One sky for both the city and the ladder, so the two read
6
6
  as one scene. No :root here; motion is CSS and the hall's reduced-motion rule (style.css, `prefers-reduced-motion: reduce`)
7
7
  stops it. */
8
8
 
9
9
  .home-city { padding: 0; overflow: hidden; background: linear-gradient(var(--bg-card), var(--bg)); }
10
10
  .hc-stage { display: flex; align-items: center; gap: 4px; }
11
11
  .hc-svg { display: block; flex: 1; min-width: 0; height: auto; }
12
+ /* THE CITY IN THE CLOUDS, AND THE HOUR (task 1005196; ADR 0402). The sky is the hour's
13
+ (--sky-now-*, set by sky-time.js; night at rest) and it is art. It runs edge to edge and
14
+ nothing darkens it: the far sky above, and the light from below behind the city (the
15
+ scene's own glow, so it sits under the city at any width). With the bars up the sky fills
16
+ the city's panel; in game mode it is the ground of the whole Home view, behind the visor,
17
+ and the panel is clear. What you read or press on it stands on a plate of the night
18
+ (--night-plate): the ladder, and in game mode the greeting; the slider keeps its own strip. */
19
+ .hc-stage { background: linear-gradient(to bottom, var(--sky-now-top), var(--sky-now-mid) 46%, var(--sky-now-haze)); }
20
+ .hc-svg { background: radial-gradient(ellipse closest-side at 46% 62%, var(--sky-now-cloud), var(--sky-now-base) 30%, var(--sky-now-haze) 62%, transparent); }
21
+ .hc-svg .hc-stars { opacity: var(--sky-now-stars); }
22
+ /* the ground is the hall's own plate seam (style.css body::before: fixed, the whole view, under the page), with no veil over it */
23
+ html[data-game="on"] body[data-page="home"] { --plate: linear-gradient(to bottom, var(--sky-now-top), var(--sky-now-mid) 46%, var(--sky-now-haze)); --plate-veil: none; --plate-blend: normal; }
24
+ html[data-game="on"] body[data-page="home"] .home-city, html[data-game="on"] body[data-page="home"] .hc-stage { background: none; }
25
+ html[data-game="on"] body[data-page="home"] .page-head--hall .page-head__h { width: fit-content; padding: var(--ds-space-hair) var(--ds-space-xs); background: var(--night-plate); clip-path: var(--cut-path-sm); }
26
+ /* the ladder's plate: the night between two strong hairlines, sized to the rungs */
27
+ .hc-ladder { isolation: isolate; }
28
+ .hc-ladder::before { content: ""; position: absolute; inset: calc(-1 * var(--ds-space-xs)) calc(-1 * var(--ds-space-sm)); z-index: -1; background: var(--night-plate); border: 0 solid var(--rule); border-left-width: var(--line-strong); border-right-width: var(--line-strong); }
29
+ .hc-cloud { stroke: none; pointer-events: none; }
30
+ .hc-cloud--row { fill: var(--sky-now-cloud); opacity: .5; }
31
+ .hc-cloud--far { fill: color-mix(in srgb, var(--sky-now-mid) 62%, var(--sky-now-cloud-shade)); opacity: .6; }
32
+ .hc-cloud--back { fill: var(--sky-now-cloud); opacity: .8; }
33
+ .hc-cloud--near { fill: var(--sky-now-cloud); opacity: .95; }
12
34
  .hc-svg .hc-wait__n { font: 700 11px var(--font-display); fill: var(--accent-on); }
13
35
  .hc-svg .hc-av__i { font: 600 13px var(--font-display); }
14
36
  .hc-svg [data-tip] { cursor: default; }
@@ -18,7 +40,7 @@
18
40
  .hc-svg .hc-tower:hover .hc-face { filter: brightness(1.3); }
19
41
  /* the core and the museum stay quiet on the builder's Home so the new buildings lead;
20
42
  each comes back to full colour under the pointer or the keyboard */
21
- .hc-svg .hc-quiet { opacity: .3; filter: saturate(0) brightness(.85); transition: opacity .3s, filter .3s; }
43
+ .hc-svg .hc-quiet { opacity: .62; filter: saturate(.3); transition: opacity .3s, filter .3s; }
22
44
  .hc-svg .hc-quiet:hover, .hc-svg .hc-quiet:focus-visible { opacity: .95; filter: none; }
23
45
  .hc-svg .hc-core:hover .hc-face, .hc-svg .hc-museum:hover .hc-face { filter: brightness(1.15); }
24
46
  .hc-svg .hc-sky:hover circle, .hc-svg .hc-sky:focus-visible circle { opacity: 1; }
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <!-- The record archetype, shared with task.html (task 1003305). idea.css below
13
13
  carries only what an idea record has and a task record does not. -->
14
14
  <link rel="stylesheet" href="/builders/record.css">
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com">
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
15
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
16
16
  <link rel="stylesheet" href="/builders/ideas.css">
17
17
  </head>
18
18
  <body data-page="ideas" data-page-title="Ideas">
@@ -18,12 +18,12 @@
18
18
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
19
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
22
- <link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-helm3">
21
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
22
+ <link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-helm4c">
23
23
  <link rel="stylesheet" href="/builders/welcome-boot.css?v=2026-11-07-brand-mark">
24
24
  <link rel="stylesheet" href="/builders/shell-game.css?v=2026-11-11-view2">
25
25
  <link rel="stylesheet" href="/builders/hall-ask.css">
26
- <link rel="stylesheet" href="/builders/home-city.css?v=2026-11-11-helm3">
26
+ <link rel="stylesheet" href="/builders/home-city.css?v=2026-11-11-helm4c">
27
27
  <link rel="stylesheet" href="/builders/home-gallery.css">
28
28
  <link rel="stylesheet" href="/builders/home-idea-box.css">
29
29
  </head>
@@ -136,7 +136,9 @@
136
136
  <!-- task 1005054: the project city — the drawing (pure) and the widget — in the
137
137
  home-stats zone; the Goals card (goals.js) left Home with it. -->
138
138
  <script src="/builders/city-draw.js" defer></script>
139
- <script src="/builders/home-city-draw.js" defer></script>
139
+ <!-- task 1005196: the hour of the city's sky (sets the --sky-now-* tokens the city spends) -->
140
+ <script src="/builders/sky-time.js?v=2026-11-11-helm4" defer></script>
141
+ <script src="/builders/home-city-draw.js?v=2026-11-11-helm4d" defer></script>
140
142
  <script src="/builders/home-gallery.js" defer></script>
141
143
  <script src="/builders/home-idea-box.js" defer></script>
142
144
  <script src="/builders/home-city.js?v=2026-11-10-visor" defer></script>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/market-watch.css">
13
13
  </head>
14
14
  <body data-page="market-watch-item" data-page-title="Market Watch">
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/hall-ask.css">
13
13
  <link rel="stylesheet" href="/builders/market-watch-rules.css">
14
14
  </head>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/market-watch.css">
13
13
  <link rel="stylesheet" href="/builders/market-watch-settings.css">
14
14
  </head>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <!-- task 1004366: a store version's how-to reads as a document, so it borrows the
13
13
  reading room's article card (room.css is scoped to its own primer- classes). -->
14
14
  <link rel="stylesheet" href="/builders/room.css">
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/oversight.css">
13
13
  <link rel="stylesheet" href="/builders/project-settings.css?v=2026-11-02-outflow">
14
14
  </head>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <!-- The oversight family's archetype: a titled row with facts and actions, the
13
13
  group head and the foot line (records/oversight-family.md). This page's
14
14
  repeated thing is that row at the desk's one-line density. -->
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/room.css">
13
13
  <link rel="stylesheet" href="/builders/primer.css?v=2026-07-02-shell1">
14
14
  <link rel="stylesheet" href="/builders/diagram-viewer.css?v=2026-11-04-reading-room">
@@ -14,7 +14,7 @@
14
14
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
15
15
  <link rel="preconnect" href="https://fonts.googleapis.com">
16
16
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
17
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
18
18
  <!-- The PANEL archetype (task 1003308) before the page's own sheet. -->
19
19
  <link rel="stylesheet" href="/builders/panel.css">
20
20
  <link rel="stylesheet" href="/builders/hall-ask.css">
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <link rel="stylesheet" href="/builders/oversight.css">
13
13
  <link rel="stylesheet" href="/builders/project-settings.css?v=2026-11-02-outflow">
14
14
  </head>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
11
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
12
12
  <!-- task 1003441: the identity cell (avatar, name, login). It was shared with
13
13
  the cross-project /people page until task 1003444 deleted it. -->
14
14
  <link rel="stylesheet" href="/builders/roster.css">
@@ -11,7 +11,7 @@
11
11
  <link rel="icon" href="/modules/ui-design/public/brand/favicon.svg" type="image/svg+xml">
12
12
  <link rel="preconnect" href="https://fonts.googleapis.com">
13
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
- <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm3">
14
+ <link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
15
15
  <!-- The PANEL archetype (task 1003308): the row, the switch, the field, the
16
16
  chip control and the family's voices, shared with profile and pair.
17
17
  settings.css then holds only what this page