@bongos/core 1.24.86 → 1.24.87
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/.bongos-core.json +76 -66
- package/docs/adr/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +15 -0
- package/docs/copy-inventory.md +6 -6
- package/docs/copy-registry.json +6 -6
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +2 -0
- package/docs/page-readings.json +36 -36
- package/modules/hall-ui/public/ai-studio.html +1 -1
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city-draw.js +127 -69
- package/modules/hall-ui/public/city.css +37 -3
- package/modules/hall-ui/public/city.html +16 -5
- package/modules/hall-ui/public/city.js +26 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/home-city-draw.js +88 -54
- package/modules/hall-ui/public/home-city.css +25 -3
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +6 -4
- package/modules/hall-ui/public/market-watch-item.html +1 -1
- package/modules/hall-ui/public/market-watch-rules.html +1 -1
- package/modules/hall-ui/public/market-watch.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/money.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/sky-time.js +206 -0
- package/modules/hall-ui/public/street.html +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +86 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/visor.css +13 -14
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/hall-ui/records/home-city.md +13 -0
- package/modules/hall-ui/records/living-city.md +12 -0
- package/modules/hall-ui/records/visor.md +1 -1
- package/modules/ui-design/public/brand.html +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/hall_city.mjs +72 -22
- package/tests/hall_city_living.mjs +7 -2
- package/tests/hall_sky_time.mjs +361 -0
- package/tests/hall_street.mjs +2 -1
- package/tests/hall_visor.mjs +18 -8
- package/tests/home_city.mjs +54 -5
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
// so tests/hall_city.mjs can pin the model without a browser, and city.js only
|
|
18
18
|
// turns the lists into SVG nodes and spans.
|
|
19
19
|
//
|
|
20
|
-
// THE PALETTE
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// the
|
|
20
|
+
// THE PALETTE (task 1005196: the city in the clouds). isoTheme() names every
|
|
21
|
+
// face and line as one of the hour's sky tokens (--sky-now-*, style.css; set by
|
|
22
|
+
// sky-time.js) or a mix of two, so this file types in no colour and the same
|
|
23
|
+
// path list is right at every hour. There is no ground: a face that stands in
|
|
24
|
+
// the cloud fades to the cloud's colour at its foot (skyDefs: the gradients a
|
|
25
|
+
// page puts in its <defs>), cloud lies between the rows (cloudsFor) and the
|
|
26
|
+
// light comes from below. The geometry is still the prototype's, path for path.
|
|
25
27
|
//
|
|
26
28
|
// REAL DATA, AND WHERE IT HAD TO BEND (each said once, here and in the ship
|
|
27
29
|
// notes):
|
|
@@ -51,39 +53,49 @@
|
|
|
51
53
|
// the living model's one count rule (gov-living.js), so the city never counts for itself
|
|
52
54
|
const Living = root.GovLiving || (typeof require === 'function' ? require('./gov-living.js') : null);
|
|
53
55
|
|
|
54
|
-
// ---------- the iso sub-palette
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
detail: 'color-mix(in srgb, var(--ink) 18%, transparent)', wc: 'color-mix(in srgb, var(--ink) 16%, transparent)', wcLit: 'rgba(214,140,80,0.60)', door: '#cfced3', beacon: 'var(--accent)',
|
|
69
|
-
vacantFill: 'color-mix(in srgb, var(--ink) 2%, transparent)', vacantLine: 'color-mix(in srgb, var(--ink) 45%, transparent)', tree: 'color-mix(in srgb, var(--sea) 22%, transparent)', treeLine: 'rgba(60,80,180,0.45)',
|
|
70
|
-
};
|
|
71
|
-
}
|
|
56
|
+
// ---------- the iso sub-palette: the hour's sky tokens, never a literal ----------
|
|
57
|
+
// now('lit') is the token the hour's blend is written to; mix() is a share of one
|
|
58
|
+
// colour in another (or in nothing: a glaze or a thin line).
|
|
59
|
+
const now = (role) => 'var(--sky-now-' + role + ')';
|
|
60
|
+
const mix = (a, pct, b) => 'color-mix(in srgb, ' + a + ' ' + pct + '%, ' + (b || 'transparent') + ')';
|
|
61
|
+
// A face that stands in the cloud: the page's gradient for that side (skyDefs), then
|
|
62
|
+
// the plain face colour for a drawing that carries no <defs> (an SVG paint's fallback).
|
|
63
|
+
const SIDES = Object.freeze({ l: 'lit', r: 'shade' });
|
|
64
|
+
const hazeId = (t) => (t > 0 ? '-h' + Math.round(t * 100) : '');
|
|
65
|
+
const faceFill = (side, t) => 'url(#sky-face-' + side + hazeId(t) + ') ' + (t > 0 ? mix(now(SIDES[side]), Math.round((1 - t) * 100), now('haze')) : now(SIDES[side]));
|
|
66
|
+
// One palette: the hall is dark only (ADR 0402) and the hour, not a mode, changes it.
|
|
67
|
+
function isoTheme() {
|
|
68
|
+
const roof = now('roof'), lit = now('lit'), shade = now('shade'), line = now('line');
|
|
69
|
+
const edge = mix(roof, 46);
|
|
72
70
|
return {
|
|
73
|
-
civic: { t:
|
|
74
|
-
work: { t: '
|
|
75
|
-
plinth: { t: '
|
|
76
|
-
roof: { t:
|
|
77
|
-
glass: { t:
|
|
78
|
-
scaffold: { t:
|
|
79
|
-
crate: { t:
|
|
80
|
-
sign: { t:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
vacantFill: 'rgba(255,255,255,0.02)', vacantLine: 'rgba(255,255,255,0.4)', tree: 'color-mix(in srgb, var(--sea) 32%, transparent)', treeLine: 'rgba(140,155,230,0.45)',
|
|
71
|
+
civic: { t: roof, l: lit, r: shade, lg: faceFill('l', 0), rg: faceFill('r', 0), s: edge },
|
|
72
|
+
work: { t: mix(roof, 86, now('haze')), l: lit, r: shade, lg: faceFill('l', 0), rg: faceFill('r', 0), s: mix(roof, 36) },
|
|
73
|
+
plinth: { t: mix(roof, 60, now('base')), l: lit, r: shade, lg: faceFill('l', 0), rg: faceFill('r', 0), s: mix(roof, 30) },
|
|
74
|
+
roof: { t: roof, l: mix(lit, 62, roof), r: mix(shade, 78, lit), s: edge },
|
|
75
|
+
glass: { t: mix(line, 16), l: mix(line, 24), r: mix(line, 32), s: mix(line, 62) },
|
|
76
|
+
scaffold: { t: mix(line, 5), l: mix(line, 3), r: mix(line, 3), s: mix(line, 72), dash: '3 3' },
|
|
77
|
+
crate: { t: mix(line, 14), l: mix(line, 9), r: mix(line, 11), s: mix(line, 82), dash: '3 2' },
|
|
78
|
+
sign: { t: lit, l: lit, r: shade, s: mix(line, 62) },
|
|
79
|
+
lane: mix(line, 30), plaza: mix(now('cloud'), 34), plazaLine: mix(line, 34), plot: mix(now('cloud'), 12), plotLine: mix(line, 30), grid: mix(line, 12),
|
|
80
|
+
detail: mix(roof, 34), wc: mix(now('glow'), 58), wcLit: now('glow'), door: shade, beacon: now('lamp'),
|
|
81
|
+
vacantFill: mix(line, 5), vacantLine: mix(line, 72), tree: mix(now('cloud-shade'), 72), treeLine: mix(line, 52),
|
|
85
82
|
};
|
|
86
83
|
}
|
|
84
|
+
// The gradients a page puts in its <defs> for the faces that stand in the cloud: the
|
|
85
|
+
// face colour at the top, the cloud's at the foot. One pair per haze share in use
|
|
86
|
+
// (0 is the clear city). Each is { id, stops: [[offset, colour], …] }, top to bottom.
|
|
87
|
+
function skyDefs(hazes) {
|
|
88
|
+
const seen = {}, out = [];
|
|
89
|
+
[0].concat(Array.isArray(hazes) ? hazes : []).forEach((h) => {
|
|
90
|
+
const t = Math.max(0, Math.min(1, Number(h) || 0)), id = hazeId(t);
|
|
91
|
+
if (seen[id]) return;
|
|
92
|
+
seen[id] = true;
|
|
93
|
+
const far = (c) => (t > 0 ? mix(c, Math.round((1 - t) * 100), now('haze')) : c);
|
|
94
|
+
out.push({ id: 'sky-face-l' + id, stops: [[0, far(now('lit'))], [0.5, far(mix(now('lit'), 84, now('base')))], [1, far(now('base'))]] });
|
|
95
|
+
out.push({ id: 'sky-face-r' + id, stops: [[0, far(now('shade'))], [0.5, far(mix(now('shade'), 86, now('base')))], [1, far(mix(now('base'), 78, now('shade')))]] });
|
|
96
|
+
});
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
87
99
|
|
|
88
100
|
// ---------- the prototype's primitives ----------
|
|
89
101
|
function push(out, d, f, s, dash, w) { out.push({ d: d, f: f, s: s, dash: dash || 'none', w: w || 1 }); }
|
|
@@ -104,8 +116,10 @@
|
|
|
104
116
|
function isoBox(out, k, x0, y0, x1, y1, z0, h, tone, o) {
|
|
105
117
|
o = o || {};
|
|
106
118
|
const P = k.P, z1 = z0 + h, dash = tone.dash || 'none';
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
// a box on the ground stands in the cloud: its two seen faces fade into it
|
|
120
|
+
const inCloud = !(z0 > 0);
|
|
121
|
+
push(out, k.poly([P(x0, y1, z1), P(x1, y1, z1), P(x1, y1, z0), P(x0, y1, z0)]), (inCloud && tone.lg) || tone.l, tone.s, dash);
|
|
122
|
+
push(out, k.poly([P(x1, y1, z1), P(x1, y0, z1), P(x1, y0, z0), P(x1, y1, z0)]), (inCloud && tone.rg) || tone.r, tone.s, dash);
|
|
109
123
|
push(out, k.poly([P(x0, y0, z1), P(x1, y0, z1), P(x1, y1, z1), P(x0, y1, z1)]), tone.t, tone.s, dash);
|
|
110
124
|
let det = '';
|
|
111
125
|
const ix = Math.min(0.08, (x1 - x0) / 6), iy = Math.min(0.08, (y1 - y0) / 6);
|
|
@@ -420,40 +434,34 @@
|
|
|
420
434
|
const HAZE = Object.freeze([0.62, 0.48, 0.34]);
|
|
421
435
|
const ACTIVE_HAZE = 0.4; // a district with an agent at work keeps this share of its band's haze
|
|
422
436
|
const QUIET_HAZE = 0.3; // a civic building with no agent and no ask sits darker
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
const VOID = Object.freeze({ dark: '#000000', light: '#cbd3e4' }); // light: a cooler, deeper sea so the haze reads on paper (task 1004718)
|
|
437
|
+
// The far bands mix toward the haze between sky and cloud (--sky-now-haze), so distance
|
|
438
|
+
// reads as atmosphere at every hour.
|
|
426
439
|
|
|
427
440
|
const clamp01 = (n) => Math.max(0, Math.min(1, Number(n) || 0));
|
|
428
441
|
const r1 = (n) => Math.round(n * 10) / 10;
|
|
429
442
|
|
|
430
|
-
// ---------- haze: a tone mixed toward the
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
443
|
+
// ---------- haze: a tone mixed toward the haze (drawing data, like isoTheme) ----------
|
|
444
|
+
// One tone, a share t of the way into the haze: a face that fades into the cloud takes
|
|
445
|
+
// the hazed gradient of its side, a glaze or a line (anything mixed with nothing) thins
|
|
446
|
+
// out, and a solid face mixes toward the haze.
|
|
447
|
+
function hazed(c, t) {
|
|
448
|
+
const s = String(c);
|
|
449
|
+
if (s === 'none') return s;
|
|
450
|
+
const face = /^url\(#sky-face-([lr])\)/.exec(s);
|
|
451
|
+
if (face) return faceFill(face[1], t);
|
|
452
|
+
const keep = Math.round((1 - t) * 100);
|
|
453
|
+
return s.indexOf('transparent') >= 0 ? mix(s, keep) : mix(s, keep, now('haze'));
|
|
437
454
|
}
|
|
438
|
-
|
|
439
|
-
// A solid face mixes toward the ground; a line or a glaze (alpha) thins out.
|
|
440
|
-
function mixToward(c, target, t) {
|
|
441
|
-
const a = parseColor(c), b = parseColor(target);
|
|
442
|
-
if (!a || !b || !(t > 0)) return c;
|
|
443
|
-
if (a[3] < 1) return 'rgba(' + a[0] + ',' + a[1] + ',' + a[2] + ',' + Math.round(a[3] * (1 - t) * 1000) / 1000 + ')';
|
|
444
|
-
return '#' + hex2(a[0] + (b[0] - a[0]) * t) + hex2(a[1] + (b[1] - a[1]) * t) + hex2(a[2] + (b[2] - a[2]) * t);
|
|
445
|
-
}
|
|
446
|
-
function hazeTheme(I, t, mode) {
|
|
455
|
+
function hazeTheme(I, t) {
|
|
447
456
|
if (!(t > 0)) return I;
|
|
448
|
-
const to = VOID[mode] || VOID.dark;
|
|
449
457
|
const out = {};
|
|
450
458
|
Object.keys(I).forEach((key) => {
|
|
451
459
|
const v = I[key];
|
|
452
460
|
if (v && typeof v === 'object') {
|
|
453
461
|
const o = {};
|
|
454
|
-
Object.keys(v).forEach((f) => { o[f] = f === 'dash' ? v[f] :
|
|
462
|
+
Object.keys(v).forEach((f) => { o[f] = f === 'dash' ? v[f] : hazed(v[f], t); });
|
|
455
463
|
out[key] = o;
|
|
456
|
-
} else out[key] = typeof v === 'string' ?
|
|
464
|
+
} else out[key] = typeof v === 'string' ? hazed(v, t) : v;
|
|
457
465
|
});
|
|
458
466
|
return out;
|
|
459
467
|
}
|
|
@@ -883,11 +891,60 @@
|
|
|
883
891
|
return parts.join(' · ');
|
|
884
892
|
}
|
|
885
893
|
|
|
894
|
+
// ---------- the cloud the city stands in (task 1005196) ----------
|
|
895
|
+
// One cloud: six soft ellipses, as one path, centred on (x, y) at scale s.
|
|
896
|
+
const CLOUD = Object.freeze([[70, 34, 80, 26], [160, 16, 90, 40], [280, 4, 110, 52], [400, 18, 100, 40], [510, 32, 90, 28], [290, 44, 300, 28]]);
|
|
897
|
+
function cloudAt(x, y, s) {
|
|
898
|
+
return CLOUD.map((e) => ellipse(r1(x + (e[0] - 290) * s), r1(y + (e[1] - 20) * s), r1(e[2] * s), r1(e[3] * s))).join('');
|
|
899
|
+
}
|
|
900
|
+
// One puff at a building's foot: a flat ellipse centred on (x, y), r wide. Home's city
|
|
901
|
+
// draws its banks and puffs with cloudAt and this, so the cloud's shape lives here alone.
|
|
902
|
+
function puffAt(x, y, r) { return ellipse(r1(x), r1(y), r, r1(r * 0.36)); }
|
|
903
|
+
// Where the banks lie on the 1440 x 900 plate: far (the sky's own shade, behind the city),
|
|
904
|
+
// back (lit, behind the far corner), then in front of the near corner: lit, shaded, dark.
|
|
905
|
+
const BANKS = Object.freeze({
|
|
906
|
+
far: [[200, 190, 1.1], [1240, 190, 1.1], [720, 70, 1.2]],
|
|
907
|
+
back: [[360, 300, 0.9], [1080, 300, 0.9], [720, 196, 0.8]],
|
|
908
|
+
near: [[300, 600, 0.8], [520, 706, 0.8], [720, 758, 0.9], [920, 706, 0.8], [1140, 600, 0.8]],
|
|
909
|
+
shade: [[170, 700, 1.1], [460, 816, 1.1], [980, 816, 1.1], [1270, 700, 1.1]],
|
|
910
|
+
dark: [[50, 856, 1.5], [720, 916, 1.6], [1390, 856, 1.5]],
|
|
911
|
+
});
|
|
912
|
+
// the two softening filters a page defines for the cloud: { id, blur } (an SVG blur, in px of the plate)
|
|
913
|
+
const SOFT = Object.freeze([Object.freeze({ id: 'sky-soft', blur: 8 }), Object.freeze({ id: 'sky-softer', blur: 14 })]);
|
|
914
|
+
// The cloud as data: the five banks (each one path, its class the layer it is), and the
|
|
915
|
+
// puffs that lie between the rows: one at the foot of every standing building, gathered
|
|
916
|
+
// by depth so the page lays each row's over that row and under the next. The page's sheet
|
|
917
|
+
// gives every layer its colour (a sky token); `soft` names the filter that softens it.
|
|
918
|
+
function cloudsFor(k, feet) {
|
|
919
|
+
// near and row cloud is soft; the far, back, shaded and dark banks softer (SOFT: the page's two filters)
|
|
920
|
+
const bank = (key) => ({ d: BANKS[key].map((c) => cloudAt(c[0], c[1], c[2])).join(''), f: 'none', s: 'none', dash: 'none', w: 1, cls: 'city-cloud city-cloud--' + key, soft: key === 'near' ? SOFT[0] : SOFT[1] });
|
|
921
|
+
const rows = {};
|
|
922
|
+
(feet || []).forEach((f) => {
|
|
923
|
+
const at = k.P(f.x + 0.25, f.y + 0.25, 0), row = Math.floor(f.depth / 2) * 2 + 1;
|
|
924
|
+
rows[row] = (rows[row] || '') + puffAt(at[0], at[1] + 5, f.r);
|
|
925
|
+
});
|
|
926
|
+
return {
|
|
927
|
+
back: [bank('far'), bank('back')],
|
|
928
|
+
rows: Object.keys(rows).map(Number).sort((a, b) => a - b).map((depth) => ({ depth: depth, d: rows[depth], f: 'none', s: 'none', dash: 'none', w: 1, cls: 'city-cloud city-cloud--row', soft: SOFT[0] })),
|
|
929
|
+
front: [bank('near'), bank('shade'), bank('dark')],
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
// The stars: a fixed scatter over the top of the plate, one path. The sheet shows them at
|
|
933
|
+
// the hour's star strength, so they are out at night and gone by day.
|
|
934
|
+
function starField() {
|
|
935
|
+
let d = '';
|
|
936
|
+
for (let i = 0; i < 46; i++) {
|
|
937
|
+
const x = 30 + rnd(i * 3 + 11) * 1380, y = 16 + rnd(i * 5 + 29) * 330, r = 0.7 + rnd(i * 7 + 2) * 0.8;
|
|
938
|
+
d += ellipse(r1(x), r1(y), r1(r), r1(r));
|
|
939
|
+
}
|
|
940
|
+
return { d: d, f: 'none', s: 'none', dash: 'none', w: 1, cls: 'city-stars' };
|
|
941
|
+
}
|
|
942
|
+
|
|
886
943
|
// The whole home view as data.
|
|
887
944
|
// docket the GET /government/docket body, or null when it failed
|
|
888
945
|
// goals GET /goals goals[] (any status; only 'open' ones are drawn)
|
|
889
946
|
// rollups GET /goals/rollups rollups[] (goal_id → task counts)
|
|
890
|
-
// mode
|
|
947
|
+
// mode ignored: the hall is dark only, and the hour (sky-time.js) paints the city
|
|
891
948
|
// now ms since the epoch (a deadline inside a day is urgent)
|
|
892
949
|
// and, for the living city (task 1004685):
|
|
893
950
|
// living GovLiving.livingModel() over the same reads (gov-living.js)
|
|
@@ -896,8 +953,8 @@
|
|
|
896
953
|
function cityModel(input) {
|
|
897
954
|
const opts = input || {};
|
|
898
955
|
const docket = opts.docket && typeof opts.docket === 'object' ? opts.docket : null;
|
|
899
|
-
const mode =
|
|
900
|
-
const I = isoTheme(
|
|
956
|
+
const mode = 'dark';
|
|
957
|
+
const I = isoTheme();
|
|
901
958
|
const counts = docket && docket.counts ? docket.counts : { needs: 0, moving: 0, settled: 0 };
|
|
902
959
|
const living = opts.living && Array.isArray(opts.living.buildings) ? Object.assign({}, opts.living, { byKey: {} }) : null;
|
|
903
960
|
if (living) living.buildings.forEach((b) => { if (b && b.key) living.byKey[b.key] = b; });
|
|
@@ -914,12 +971,11 @@
|
|
|
914
971
|
const k = isoKit(60, 30, 720, 176, 30 / 28, I, evening);
|
|
915
972
|
const P = k.P;
|
|
916
973
|
const kits = {};
|
|
917
|
-
const kitFor = (t) => (t > 0 ? (kits[t] = kits[t] || isoKit(60, 30, 720, 176, 30 / 28, hazeTheme(I, t
|
|
974
|
+
const kitFor = (t) => (t > 0 ? (kits[t] = kits[t] || isoKit(60, 30, 720, 176, 30 / 28, hazeTheme(I, t), evening)) : k);
|
|
918
975
|
|
|
919
|
-
// ground
|
|
976
|
+
// No ground and no road fill: the city stands in cloud. The ring road keeps its lane
|
|
977
|
+
// marks (the parcels and the spend conduit run along them), then the plaza, lit from below.
|
|
920
978
|
const flat = [];
|
|
921
|
-
push(flat, k.poly([P(0, 0), P(16, 0), P(16, 16), P(0, 16)]), I.ground, I.groundLine);
|
|
922
|
-
[[4, 0, 5, 16], [11, 0, 12, 16], [0, 4, 16, 5], [0, 11, 16, 12]].forEach((q) => push(flat, k.poly([P(q[0], q[1]), P(q[2], q[1]), P(q[2], q[3]), P(q[0], q[3])]), I.road, 'none'));
|
|
923
979
|
push(flat, k.seg(P(4.5, 0), P(4.5, 16)) + k.seg(P(11.5, 0), P(11.5, 16)) + k.seg(P(0, 4.5), P(16, 4.5)) + k.seg(P(0, 11.5), P(16, 11.5)), 'none', I.lane, '2 7');
|
|
924
980
|
push(flat, k.poly([P(5, 5), P(11, 5), P(11, 11), P(5, 11)]), I.plaza, I.plazaLine);
|
|
925
981
|
|
|
@@ -973,6 +1029,8 @@
|
|
|
973
1029
|
});
|
|
974
1030
|
});
|
|
975
1031
|
blds.sort((a, b) => a.depth - b.depth);
|
|
1032
|
+
const clouds = cloudsFor(k, CIVIC_SLOTS.map((s) => ({ x: s[1] + 1, y: s[2] + 1, r: 58, depth: s[1] + s[2] + 2 }))
|
|
1033
|
+
.concat(districts.reduce((all, D) => all.concat(D.work.filter((w) => w.kind !== 'trees' && w.kind !== 'slab').map((w) => ({ x: w.gx + 0.5, y: w.gy + 0.5, r: 31, depth: w.gx + w.gy + 1 }))), [])));
|
|
976
1034
|
|
|
977
1035
|
// one pin over each building with a matter, in its most important matter's tier
|
|
978
1036
|
const slotOf = {};
|
|
@@ -1022,7 +1080,7 @@
|
|
|
1022
1080
|
+ (districts.length === 1 ? 'one district' : districts.length + ' districts') + ', one for each open goal'
|
|
1023
1081
|
+ (pins.length ? '. Pins: ' + pinCount.needs + ' need you, ' + pinCount.moving + ' moving, ' + pinCount.settled + ' settled.' : '.');
|
|
1024
1082
|
|
|
1025
|
-
const out = { mode: mode, evening: evening, crate: { fill: I.crate.t, edge: I.crate.s }, flat: flat, blds: blds, pins: pins, parcels: parcels, districts: districts, unplaced: unplaced, line: line, label: label };
|
|
1083
|
+
const out = { mode: mode, evening: evening, crate: { fill: I.crate.t, edge: I.crate.s }, defs: skyDefs(Object.keys(kits).map(Number)), clouds: clouds, stars: starField(), flat: flat, blds: blds, pins: pins, parcels: parcels, districts: districts, unplaced: unplaced, line: line, label: label };
|
|
1026
1084
|
if (!living) return out;
|
|
1027
1085
|
|
|
1028
1086
|
// ---- the living city's three levels (task 1004685) ----
|
|
@@ -1074,14 +1132,14 @@
|
|
|
1074
1132
|
const STREET_HREF = '/builders/street';
|
|
1075
1133
|
|
|
1076
1134
|
const api = {
|
|
1077
|
-
isoTheme: isoTheme, isoKit: isoKit, isoBox: isoBox, isoGable: isoGable, isoHip: isoHip, isoSaw: isoSaw,
|
|
1135
|
+
isoTheme: isoTheme, skyDefs: skyDefs, SOFT: SOFT, cloudsFor: cloudsFor, cloudAt: cloudAt, puffAt: puffAt, starField: starField, isoKit: isoKit, isoBox: isoBox, isoGable: isoGable, isoHip: isoHip, isoSaw: isoSaw,
|
|
1078
1136
|
isoTree: isoTree, isoLight: isoLight, isoInstitution: isoInstitution, isoWork: isoWork, glyph: glyph, rnd: rnd,
|
|
1079
1137
|
lotsFor: lotsFor, districtWork: districtWork, cityModel: cityModel, rulesAtWork: rulesAtWork,
|
|
1080
1138
|
matterTier: matterTier, buildingPin: buildingPin, pinAria: pinAria, TIERS: TIERS, URGENT_MS: URGENT_MS,
|
|
1081
1139
|
CIVIC_SLOTS: CIVIC_SLOTS, ROOF: ROOF, BASE_LOTS: BASE_LOTS, MAX_DISTRICTS: MAX_DISTRICTS,
|
|
1082
1140
|
FALLBACK_LINE: FALLBACK_LINE, STREET_HREF: STREET_HREF,
|
|
1083
1141
|
// the living city (task 1004685)
|
|
1084
|
-
hazeTheme: hazeTheme,
|
|
1142
|
+
hazeTheme: hazeTheme, hazeBand: hazeBand, structureFloors: structureFloors, asksAt: asksAt,
|
|
1085
1143
|
spendRate: spendRate, trunkWidth: trunkWidth, plaqueFor: plaqueFor, ctaFor: ctaFor, livingLine: livingLine,
|
|
1086
1144
|
FLOOR_H: FLOOR_H, BUILD_FLOORS: BUILD_FLOORS, CORE: CORE, BODY: BODY, LANE_PERIOD: LANE_PERIOD, POD_GAP: POD_GAP,
|
|
1087
1145
|
MAX_LANE_PODS: MAX_LANE_PODS, MAX_PODS: MAX_PODS, ACTIVE_HAZE: ACTIVE_HAZE,
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
prototype wrote its theme() values inline; here every colour is one of the
|
|
7
7
|
hall's tokens (style.css + the branding pack), whose values ARE the
|
|
8
8
|
prototype's (bg #000 / #edecee, ink, ink-soft, the rule, the accent
|
|
9
|
-
#fb9569 and its dark ink #2a1305). The drawing's
|
|
10
|
-
|
|
9
|
+
#fb9569 and its dark ink #2a1305). The drawing's faces and the sky behind
|
|
10
|
+
it are the hour's sky tokens (--sky-now-*, task 1005196: the foot of this
|
|
11
|
+
sheet); city-draw.js types in no colour.
|
|
11
12
|
No :root here (tests/hall_reading_world.mjs): page tokens live on the body.
|
|
12
13
|
The two dark blocks below are byte-identical twins (the hall's switch is
|
|
13
14
|
html[data-theme], set by city.js from the same 'hall-theme' preference
|
|
@@ -169,7 +170,7 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
169
170
|
.city-beacon--stem { fill: none; stroke: var(--sea); }
|
|
170
171
|
.city-beacon--ring { fill: none; stroke: color-mix(in srgb, var(--sea) 60%, transparent); }
|
|
171
172
|
.city-sky, .city-hits { position: absolute; left: 0; top: 0; width: 1440px; height: 900px; pointer-events: none; }
|
|
172
|
-
.city-pod { position: absolute; left: 0; top: 0; width: 11px; height: 5px; margin: -2.5px 0 0 -5.5px; box-sizing: border-box; border-radius: 999px; background: linear-gradient(180deg, var(--city-pod-hi), var(--city-pod-lo)); box-shadow: 0 0 0
|
|
173
|
+
.city-pod { position: absolute; left: 0; top: 0; width: 11px; height: 5px; margin: -2.5px 0 0 -5.5px; box-sizing: border-box; border-radius: 999px; background: linear-gradient(180deg, var(--city-pod-hi), var(--city-pod-lo)); box-shadow: 0 0 0 1.5px var(--bg); offset-rotate: auto; offset-distance: var(--pod-at, 30%); }
|
|
173
174
|
|
|
174
175
|
.city-lane__more { position: absolute; transform: translate(-50%, -150%); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink-soft); }
|
|
175
176
|
|
|
@@ -458,3 +459,36 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
458
459
|
.ask-full summary { display: inline-flex; align-items: center; min-height: 24px; cursor: pointer; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--gov-chip-edge-pick); }
|
|
459
460
|
.ask-full__text { margin: 6px 0 0; max-height: 240px; overflow-y: auto; white-space: pre-wrap; line-height: 1.5; overflow-wrap: anywhere; }
|
|
460
461
|
.ask-full summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
462
|
+
|
|
463
|
+
/* ======================================================================
|
|
464
|
+
THE CITY IN THE CLOUDS, AND THE HOUR (task 1005196; ADR 0402)
|
|
465
|
+
The same city, with no ground: it stands in cloud, lit from below, and the
|
|
466
|
+
sky behind it is the hour's (sky-time.js sets --sky-now-*; they rest at
|
|
467
|
+
night). These are art tokens: they paint the scene and nothing you read or
|
|
468
|
+
press. The sky and its cloud run to all four edges of the view at every
|
|
469
|
+
hour: nothing darkens the frame. The wordmark, the plaque, the line and
|
|
470
|
+
the archive note stay Helm night by each standing on a small plate of the
|
|
471
|
+
night (--night-plate), sized to its words.
|
|
472
|
+
====================================================================== */
|
|
473
|
+
.city-frame { background: radial-gradient(ellipse 82% 82% at 50% 56%, var(--sky-now-cloud), var(--sky-now-base) 24%, var(--sky-now-haze) 46%, var(--sky-now-mid) 80%, color-mix(in srgb, var(--sky-now-mid) 60%, var(--sky-now-top))); }
|
|
474
|
+
/* what is drawn over the scene takes the hour's line colour, so it reads on the bright cloud of midday and on the night */
|
|
475
|
+
.city-stage { --city-lane: color-mix(in srgb, var(--sky-now-line) 62%, transparent); --city-crane: color-mix(in srgb, var(--sky-now-line) 86%, transparent); --city-win-lit: var(--sky-now-glow); }
|
|
476
|
+
.city-cloud { stroke: none; }
|
|
477
|
+
/* each bank is softened by one of the stage's two SVG filters (city.html), named on the path; nothing that moves is */
|
|
478
|
+
.city-cloud--row { fill: var(--sky-now-cloud); opacity: .5; }
|
|
479
|
+
.city-cloud--far { fill: color-mix(in srgb, var(--sky-now-mid) 62%, var(--sky-now-cloud-shade)); opacity: .6; }
|
|
480
|
+
.city-cloud--back { fill: var(--sky-now-cloud); opacity: .8; }
|
|
481
|
+
.city-cloud--near { fill: var(--sky-now-cloud); opacity: .95; }
|
|
482
|
+
.city-cloud--shade { fill: var(--sky-now-cloud-shade); }
|
|
483
|
+
.city-cloud--dark { fill: color-mix(in srgb, var(--sky-now-cloud-shade) 62%, var(--sky-now-mid)); }
|
|
484
|
+
.city-stars { fill: var(--sky-now-lamp); stroke: none; opacity: var(--sky-now-stars); }
|
|
485
|
+
.city-stage .city-tally--yes { fill: var(--sky-now-line); }
|
|
486
|
+
.city-stage .city-tally--no { fill: color-mix(in srgb, var(--sky-now-line) 40%, transparent); }
|
|
487
|
+
.city-stage .city-tally--open { stroke: var(--sky-now-line); }
|
|
488
|
+
/* a marker keeps its meaning's colour and gains a night edge, so it reads on any sky */
|
|
489
|
+
.city-pin--moving .city-pin__head, .city-pin--settled .city-pin__head { outline: 2px solid var(--bg); }
|
|
490
|
+
.city-line, .city-mark, .city-plaque, .city-archived { background: var(--night-plate); clip-path: var(--cut-path-sm); }
|
|
491
|
+
.city-line { width: fit-content; margin-inline: auto; padding: 4px 14px; }
|
|
492
|
+
.city-mark { padding: 4px 12px; }
|
|
493
|
+
.city-plaque { padding: 6px 14px; margin-left: -14px; }
|
|
494
|
+
.city-archived { left: 50%; right: auto; width: max-content; max-width: calc(100% - 32px); transform: translateX(-50%); padding: 0 12px; opacity: 1; }
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
links, injected into this head by the server like every hall page's
|
|
12
12
|
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
13
13
|
The prototype carried its own Google Fonts link for the same two faces. -->
|
|
14
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-
|
|
15
|
-
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-
|
|
16
|
-
<link rel="stylesheet" href="/builders/city.css?v=2026-11-11-
|
|
14
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
|
|
15
|
+
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-helm4c">
|
|
16
|
+
<link rel="stylesheet" href="/builders/city.css?v=2026-11-11-helm4c">
|
|
17
17
|
<!-- the ask card in the glass language, shared with the street's deck (task 1004702) -->
|
|
18
18
|
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
|
|
19
19
|
</head>
|
|
@@ -78,8 +78,17 @@
|
|
|
78
78
|
<div class="city-zoom">
|
|
79
79
|
<div class="city-drift">
|
|
80
80
|
<svg class="city-svg" id="citySvg" width="1440" height="900" viewBox="0 0 1440 900" aria-hidden="true" focusable="false">
|
|
81
|
+
<!-- the city in the clouds (task 1005196): the face gradients, the stars and the far
|
|
82
|
+
banks behind the city, and the near banks in front of it -->
|
|
83
|
+
<defs>
|
|
84
|
+
<filter id="sky-soft" x="-0.2" y="-0.8" width="1.4" height="2.6"><feGaussianBlur stdDeviation="8"></feGaussianBlur></filter>
|
|
85
|
+
<filter id="sky-softer" x="-0.2" y="-0.8" width="1.4" height="2.6"><feGaussianBlur stdDeviation="14"></feGaussianBlur></filter>
|
|
86
|
+
</defs>
|
|
87
|
+
<defs id="cityDefs"></defs>
|
|
88
|
+
<g id="citySkyBack"></g>
|
|
81
89
|
<g id="cityFlat"></g>
|
|
82
90
|
<g id="cityBlds" class="city-blds"></g>
|
|
91
|
+
<g id="cityCloudFront"></g>
|
|
83
92
|
<g id="citySkyLanes" class="city-sky-lanes"></g>
|
|
84
93
|
</svg>
|
|
85
94
|
<div class="city-parcels" id="cityParcels" aria-hidden="true"></div>
|
|
@@ -114,7 +123,9 @@
|
|
|
114
123
|
<script src="/builders/palette.js" defer></script>
|
|
115
124
|
<script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
|
|
116
125
|
<script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
|
|
117
|
-
|
|
126
|
+
<!-- task 1005196: the hour of the city's sky (sets the --sky-now-* tokens the drawing spends) -->
|
|
127
|
+
<script src="/builders/sky-time.js?v=2026-11-11-helm4" defer></script>
|
|
128
|
+
<script src="/builders/city-draw.js?v=2026-11-11-helm4b" defer></script>
|
|
118
129
|
<!-- a building's card answers its first ask in place through the deck's own
|
|
119
130
|
route (task 1004702): the desk rows (street-desk.js over the street's kit)
|
|
120
131
|
and the deck's card and sender (street-deck.js) -->
|
|
@@ -126,6 +137,6 @@
|
|
|
126
137
|
<script src="/builders/street-deck.js?v=2026-10-13-glass1" defer></script>
|
|
127
138
|
<script src="/builders/city-decide.js?v=2026-10-23-decide12" defer></script>
|
|
128
139
|
<script src="/builders/city-switchboard.js?v=2026-10-23-board5" defer></script>
|
|
129
|
-
<script src="/builders/city.js?v=2026-11-11-
|
|
140
|
+
<script src="/builders/city.js?v=2026-11-11-helm4" defer></script>
|
|
130
141
|
</body>
|
|
131
142
|
</html>
|
|
@@ -158,6 +158,8 @@
|
|
|
158
158
|
if (p.dash && p.dash !== 'none') el.setAttribute('stroke-dasharray', p.dash);
|
|
159
159
|
el.setAttribute('stroke-width', String(p.w || 1));
|
|
160
160
|
if (p.cls) el.setAttribute('class', p.cls);
|
|
161
|
+
// a bank of cloud is softened by one of the stage's two filters (city.html)
|
|
162
|
+
if (p.soft) el.setAttribute('filter', 'url(#' + p.soft.id + ')');
|
|
161
163
|
return el;
|
|
162
164
|
}
|
|
163
165
|
// A part of a building that moves (or stands at its still frame) is its own group.
|
|
@@ -168,6 +170,19 @@
|
|
|
168
170
|
part.polys.forEach(function (p) { g.appendChild(pathEl(p)); });
|
|
169
171
|
return g;
|
|
170
172
|
}
|
|
173
|
+
// A gradient for the faces that stand in the cloud (city-draw.js skyDefs): top to bottom.
|
|
174
|
+
function gradEl(def) {
|
|
175
|
+
var g = document.createElementNS(SVG_NS, 'linearGradient');
|
|
176
|
+
g.setAttribute('id', def.id);
|
|
177
|
+
g.setAttribute('x1', '0'); g.setAttribute('y1', '0'); g.setAttribute('x2', '0'); g.setAttribute('y2', '1');
|
|
178
|
+
def.stops.forEach(function (st) {
|
|
179
|
+
var s = document.createElementNS(SVG_NS, 'stop');
|
|
180
|
+
s.setAttribute('offset', String(st[0]));
|
|
181
|
+
s.style.setProperty('stop-color', st[1]);
|
|
182
|
+
g.appendChild(s);
|
|
183
|
+
});
|
|
184
|
+
return g;
|
|
185
|
+
}
|
|
171
186
|
function span(cls) { var el = document.createElement('span'); el.className = cls; return el; }
|
|
172
187
|
function node(tag, cls, text) { var el = document.createElement(tag); if (cls) el.className = cls; if (text != null) el.textContent = String(text); return el; }
|
|
173
188
|
|
|
@@ -333,11 +348,20 @@
|
|
|
333
348
|
living: data.living || null, constitution: data.raw.constitution || null, counts: countsNow(),
|
|
334
349
|
});
|
|
335
350
|
var flat = $('cityFlat'), blds = $('cityBlds'), sky = $('citySkyLanes'), parcels = $('cityParcels'), pods = $('citySky'), pins = $('cityPins'), hits = $('cityHits');
|
|
336
|
-
|
|
351
|
+
var defs = $('cityDefs'), back = $('citySkyBack'), front = $('cityCloudFront');
|
|
352
|
+
clear(flat); clear(blds); clear(sky); clear(parcels); clear(pods); clear(pins); clear(hits); clear(defs); clear(back); clear(front);
|
|
353
|
+
// the cloud city (task 1005196): the stars and the far banks behind, a row of cloud
|
|
354
|
+
// after each row of buildings, the near banks in front
|
|
355
|
+
if (defs) (model.defs || []).forEach(function (d) { defs.appendChild(gradEl(d)); });
|
|
356
|
+
var clouds = model.clouds || { back: [], rows: [], front: [] };
|
|
357
|
+
if (back) { if (model.stars) back.appendChild(pathEl(model.stars)); clouds.back.forEach(function (p) { back.appendChild(pathEl(p)); }); }
|
|
358
|
+
if (front) clouds.front.forEach(function (p) { front.appendChild(pathEl(p)); });
|
|
359
|
+
var rows = clouds.rows.slice();
|
|
337
360
|
model.flat.forEach(function (p) { flat.appendChild(pathEl(p)); });
|
|
338
361
|
var civicKeys = {};
|
|
339
362
|
(model.civic || []).forEach(function (c) { civicKeys[c.key] = true; });
|
|
340
363
|
model.blds.forEach(function (b) {
|
|
364
|
+
while (rows.length && rows[0].depth < b.depth) blds.appendChild(pathEl(rows.shift()));
|
|
341
365
|
var g = document.createElementNS(SVG_NS, 'g');
|
|
342
366
|
g.setAttribute('data-key', b.key);
|
|
343
367
|
if (b.quiet) g.setAttribute('data-quiet', '');
|
|
@@ -347,6 +371,7 @@
|
|
|
347
371
|
(b.parts || []).forEach(function (part) { g.appendChild(partEl(part)); });
|
|
348
372
|
blds.appendChild(g);
|
|
349
373
|
});
|
|
374
|
+
rows.forEach(function (p) { blds.appendChild(pathEl(p)); });
|
|
350
375
|
model.parcels.forEach(function (pc) { parcels.appendChild(drawParcel(pc)); });
|
|
351
376
|
if (model.living) {
|
|
352
377
|
// the capsules plug in once, on the first living drawing; a redraw does not replay it
|
|
@@ -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-
|
|
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/collab.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-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
|
|
12
12
|
<link rel="stylesheet" href="/builders/commands.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body data-page="commands" data-page-title="Commands">
|
|
@@ -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-
|
|
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/copy-desk.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-
|
|
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/hall-ask.css">
|
|
14
14
|
<link rel="stylesheet" href="/builders/project-settings.css">
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<!-- The faces (Manrope, JetBrains Mono) come from the branding pack's font
|
|
12
12
|
links, injected into this head by the server like every hall page's
|
|
13
13
|
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet). -->
|
|
14
|
-
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-
|
|
15
|
-
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-
|
|
14
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
|
|
15
|
+
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-11-helm4c">
|
|
16
16
|
<link rel="stylesheet" href="/builders/thinking.css?v=2026-11-10-visor">
|
|
17
17
|
</head>
|
|
18
18
|
<!-- THE PROJECT SKY (task 1004232 / sky part 4 of 5, goal 1000095; ADR 0331,
|
|
@@ -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-
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
|
|
12
12
|
<!-- task 1003470: the family sheet the list's figure band and group heads
|
|
13
13
|
come from (.ov-figs, .ov-group__head), linked before the page's own. -->
|
|
14
14
|
<link rel="stylesheet" href="/builders/oversight.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-
|
|
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/government.css">
|
|
14
14
|
</head>
|