@bongos/core 1.24.48 → 1.24.49
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 +142 -97
- package/config/branding.neutral.json +2 -1
- package/docs/copy-inventory.md +1028 -1028
- package/docs/copy-registry.json +1448 -1448
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +625 -625
- package/modules/hall-ui/public/ai-studio.html +2 -2
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +1 -1
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +5 -5
- package/modules/hall-ui/public/market-watch-item.html +2 -2
- package/modules/hall-ui/public/market-watch-rules.html +2 -2
- package/modules/hall-ui/public/market-watch.html +2 -2
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/money.html +2 -2
- package/modules/hall-ui/public/my-agents.html +2 -2
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +7 -9
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/welcome-boot.css +3 -4
- package/modules/hall-ui/public/welcome-boot.js +5 -3
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/public-landing/public/account.html +1 -5
- package/modules/public-landing/public/assets/cosmos.css +7 -6
- package/modules/public-landing/public/community.html +1 -5
- package/modules/public-landing/public/connections.html +1 -5
- package/modules/public-landing/public/contact.html +1 -5
- package/modules/public-landing/public/guild.html +1 -5
- package/modules/public-landing/public/index.html +5 -8
- package/modules/public-landing/public/invitations.html +1 -5
- package/modules/public-landing/public/privacy.html +1 -5
- package/modules/public-landing/public/projects.html +1 -5
- package/modules/public-landing/public/terms.html +1 -5
- package/modules/raise/views/gate.html +4 -3
- package/modules/raise/views/nda.html +2 -1
- package/modules/raise/views/page.html +4 -3
- package/modules/status-ui/public/index.html +1 -1
- package/modules/status-ui/public/style.css +2 -2
- package/modules/ui-design/public/brand/apple-touch-icon.png +0 -0
- package/modules/ui-design/public/brand/favicon.svg +1 -0
- package/modules/ui-design/public/brand/mark-body.svg +1 -0
- package/modules/ui-design/public/brand/mark-dark.svg +1 -0
- package/modules/ui-design/public/brand/mark-dot.svg +1 -0
- package/modules/ui-design/public/brand/mark-light.svg +1 -0
- package/modules/ui-design/public/brand/mark-mono.svg +1 -0
- package/modules/ui-design/public/brand/mark.svg +21 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/bongos/fitness.js +1 -2
- package/scripts/bongos/gen-brand.js +99 -0
- package/scripts/gds/gen-brand.js +7 -0
- package/src/bongos/platform-visibility-gate.js +6 -3
- package/src/bongos/prelaunch-gate.js +6 -3
- package/src/bongos/serve-internal.js +1 -0
- package/src/brand-mark.js +96 -0
- package/src/branding.js +2 -0
- package/src/hall-look.js +7 -7
- package/src/module-api.js +1 -1
- package/tests/brand_mark.mjs +195 -0
- package/tests/cloud_bongos_pack.mjs +5 -2
- package/tests/hall_loading.mjs +8 -10
- package/tests/loading_mark.mjs +7 -13
- package/tests/nav_permission_atoms.mjs +2 -1
- package/tests/raise.mjs +3 -1
- package/tests/welcome_boot.mjs +5 -2
- package/modules/raise/public/favicon.svg +0 -1
- package/modules/raise/public/logo-ink.svg +0 -1
- package/modules/raise/public/logo-night.svg +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// gen-brand.js — the logo and favicon set, written from the one mark (task 1005088).
|
|
4
|
+
//
|
|
5
|
+
// The mark's one source is a shipped module's public/brand/mark.svg (src/brand-mark.js finds
|
|
6
|
+
// it). Pages that can colour it themselves reference it with <use>; everything that cannot —
|
|
7
|
+
// an <img>, a CSS mask, a browser tab, a home-screen icon — gets a fixed-colour file written
|
|
8
|
+
// here, beside the source, in the neutral pack's colours (config/branding.neutral.json):
|
|
9
|
+
//
|
|
10
|
+
// mark-light.svg the mark in Ink with the accent moon (light grounds)
|
|
11
|
+
// mark-dark.svg the mark in the Night ink with the accent moon (dark grounds)
|
|
12
|
+
// mark-mono.svg the whole mark in one ink (black), for a print or a stamp
|
|
13
|
+
// mark-body.svg the ring and planet only, one ink (the loaders' mask shapes:
|
|
14
|
+
// mark-dot.svg the moon only, one ink the ring breathes, the moon turns)
|
|
15
|
+
// favicon.svg the small cut, Ink by day and the Night ink by night
|
|
16
|
+
// apple-touch-icon.png 180x180, the light mark on the pack's ground (--png; needs a browser)
|
|
17
|
+
//
|
|
18
|
+
// Deterministic text, so `--check` is a clean gate (tests/brand_mark.mjs runs it). The PNG is
|
|
19
|
+
// rendered by the system browser and only written with --png.
|
|
20
|
+
//
|
|
21
|
+
// Usage:
|
|
22
|
+
// node scripts/bongos/gen-brand.js # write the SVG variants
|
|
23
|
+
// node scripts/bongos/gen-brand.js --png # … and render apple-touch-icon.png
|
|
24
|
+
// node scripts/bongos/gen-brand.js --check # write nothing; exit 2 if a variant is stale
|
|
25
|
+
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const { NIGHT_INK, readMarkSource, markParts, markFile, faviconSvg } = require('../../src/brand-mark');
|
|
29
|
+
|
|
30
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
31
|
+
|
|
32
|
+
function brandDir() {
|
|
33
|
+
const modulesDir = path.join(ROOT, 'modules');
|
|
34
|
+
const hit = fs.readdirSync(modulesDir).sort()
|
|
35
|
+
.map((m) => path.join(modulesDir, m, 'public', 'brand'))
|
|
36
|
+
.find((d) => fs.existsSync(path.join(d, 'mark.svg')));
|
|
37
|
+
if (!hit) throw new Error('no module ships public/brand/mark.svg');
|
|
38
|
+
return hit;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function packColors() {
|
|
42
|
+
const pack = JSON.parse(fs.readFileSync(path.join(ROOT, 'config', 'branding.neutral.json'), 'utf8'));
|
|
43
|
+
return pack.theme.ui.colors;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// { filename: text } for every SVG variant. Pure given the source and the colours.
|
|
47
|
+
function brandFiles(src, colors) {
|
|
48
|
+
const parts = markParts(src);
|
|
49
|
+
if (!parts) throw new Error('mark.svg is missing #mark-ring, #mark-disc or #mark-dot');
|
|
50
|
+
const mono = '#000000';
|
|
51
|
+
return {
|
|
52
|
+
'mark-light.svg': markFile(parts, { ink: colors.ink, dot: colors.accent, label: 'logo' }),
|
|
53
|
+
'mark-dark.svg': markFile(parts, { ink: NIGHT_INK, dot: colors.accent, label: 'logo' }),
|
|
54
|
+
'mark-mono.svg': markFile(parts, { ink: mono, dot: mono, label: 'logo' }),
|
|
55
|
+
'mark-body.svg': markFile(parts, { ink: mono, dot: mono, only: ['ring', 'disc'] }),
|
|
56
|
+
'mark-dot.svg': markFile(parts, { ink: mono, dot: mono, only: ['dot'] }),
|
|
57
|
+
'favicon.svg': faviconSvg({ ink: colors.ink, accent: colors.accent }, src) + '\n',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function renderTouchIcon(dir, colors) {
|
|
62
|
+
const { launchBrowser } = require('../../modules/ui-design/kit/lib'); // eslint-disable-line global-require
|
|
63
|
+
const svg = fs.readFileSync(path.join(dir, 'mark-light.svg'), 'utf8');
|
|
64
|
+
const browser = await launchBrowser();
|
|
65
|
+
try {
|
|
66
|
+
const page = await browser.newPage({ viewport: { width: 180, height: 180 }, deviceScaleFactor: 1 });
|
|
67
|
+
await page.setContent(`<html><body style="margin:0;background:${colors.bg}">` +
|
|
68
|
+
`<img src="data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}" style="display:block;width:128px;height:128px;margin:26px">` +
|
|
69
|
+
'</body></html>');
|
|
70
|
+
await page.waitForLoadState('load');
|
|
71
|
+
await page.screenshot({ path: path.join(dir, 'apple-touch-icon.png'), omitBackground: false });
|
|
72
|
+
} finally { await browser.close(); }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
const args = process.argv.slice(2);
|
|
77
|
+
const dir = brandDir();
|
|
78
|
+
const colors = packColors();
|
|
79
|
+
const files = brandFiles(readMarkSource({ root: ROOT }), colors);
|
|
80
|
+
const stale = [];
|
|
81
|
+
for (const [name, text] of Object.entries(files)) {
|
|
82
|
+
const file = path.join(dir, name);
|
|
83
|
+
const cur = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
84
|
+
if (cur === text) continue;
|
|
85
|
+
stale.push(name);
|
|
86
|
+
if (!args.includes('--check')) fs.writeFileSync(file, text);
|
|
87
|
+
}
|
|
88
|
+
if (args.includes('--check')) {
|
|
89
|
+
if (stale.length) { process.stderr.write(`gen-brand: stale — ${stale.join(', ')}. Run: node scripts/bongos/gen-brand.js\n`); process.exit(2); }
|
|
90
|
+
process.stdout.write('gen-brand: the logo and favicon set match mark.svg\n');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (args.includes('--png')) await renderTouchIcon(dir, colors);
|
|
94
|
+
process.stdout.write(`gen-brand: ${stale.length ? `wrote ${stale.join(', ')}` : 'nothing to write'}${args.includes('--png') ? ' + apple-touch-icon.png' : ''}\n`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (require.main === module) main().catch((e) => { process.stderr.write(`${e.message}\n`); process.exit(1); });
|
|
98
|
+
|
|
99
|
+
module.exports = { brandFiles, packColors };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/gen-brand.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
+
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
+
// The real script is scripts/bongos/gen-brand.js. This forwarder keeps callers that still name
|
|
5
|
+
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
+
'use strict';
|
|
7
|
+
require('../bongos/forwarder.js').forward(module, 'gen-brand.js');
|
|
@@ -179,6 +179,9 @@ function relativeReturn(raw) {
|
|
|
179
179
|
|
|
180
180
|
// The one server escaper (task 1004876); it also escapes ' , which this copy did not.
|
|
181
181
|
const { escapeHtml: escAttr } = require('../html-escape');
|
|
182
|
+
// The mark, inlined from its one source (task 1005088) so the door still loads nothing;
|
|
183
|
+
// coloured by .mark's --mark-ink / --mark-dot below.
|
|
184
|
+
const { markSvg } = require('../brand-mark');
|
|
182
185
|
|
|
183
186
|
// The door. Self-contained on purpose (the pre-launch gate's shape): it is
|
|
184
187
|
// served to someone who may not see the project, so it pulls no stylesheet, no
|
|
@@ -198,8 +201,8 @@ body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
|
|
|
198
201
|
background:#edecee;color:#17181a}
|
|
199
202
|
@media(prefers-color-scheme:dark){body{background:#000000;color:#ffffff}}
|
|
200
203
|
.box{width:min(380px,100%);text-align:center}
|
|
201
|
-
.mark{display:block;width:48px;height:48px;margin:0 auto 22px}
|
|
202
|
-
@media(prefers-color-scheme:dark){.mark
|
|
204
|
+
.mark{display:block;width:48px;height:48px;margin:0 auto 22px;--mark-ink:#17181a;--mark-dot:#fb9569}
|
|
205
|
+
@media(prefers-color-scheme:dark){.mark{--mark-ink:#e2e1e4}}
|
|
203
206
|
h1{font-size:20px;font-weight:700;margin:0 0 6px}
|
|
204
207
|
p{margin:0 0 20px;opacity:.72;font-size:14px}
|
|
205
208
|
/* dark ink on the accent fill: the chrome world's salmon carries white at 2.19:1 */
|
|
@@ -207,7 +210,7 @@ a.btn{display:inline-block;padding:11px 22px;border-radius:999px;background:#fb9
|
|
|
207
210
|
font:700 14px/1 inherit;text-decoration:none}
|
|
208
211
|
a.btn:focus{outline:2px solid #a3481a;outline-offset:2px}
|
|
209
212
|
</style></head><body><div class="box">
|
|
210
|
-
|
|
213
|
+
${markSvg({ className: 'mark' })}
|
|
211
214
|
<h1>${safe}</h1>
|
|
212
215
|
<p>This project’s pages are open to its members. Sign in to continue.</p>
|
|
213
216
|
<a class="btn" href="${escAttr(signIn)}">Sign in</a>
|
|
@@ -180,6 +180,9 @@ function relativeReturn(raw) {
|
|
|
180
180
|
|
|
181
181
|
// The one server escaper (task 1004876); it also escapes ' , which this copy did not.
|
|
182
182
|
const { escapeHtml: escAttr } = require('../html-escape');
|
|
183
|
+
// The mark, inlined from its one source (task 1005088) so the form still loads nothing;
|
|
184
|
+
// coloured by .mark's --mark-ink / --mark-dot below.
|
|
185
|
+
const { markSvg } = require('../brand-mark');
|
|
183
186
|
|
|
184
187
|
// The form. Self-contained and token-free on purpose: it is served BEFORE the
|
|
185
188
|
// visitor is allowed to see anything, so it must not pull the branding bundle,
|
|
@@ -198,8 +201,8 @@ body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
|
|
|
198
201
|
background:#edecee;color:#17181a}
|
|
199
202
|
@media(prefers-color-scheme:dark){body{background:#000000;color:#ffffff}}
|
|
200
203
|
.box{width:min(380px,100%);text-align:center}
|
|
201
|
-
.mark{display:block;width:48px;height:48px;margin:0 auto 22px}
|
|
202
|
-
@media(prefers-color-scheme:dark){.mark
|
|
204
|
+
.mark{display:block;width:48px;height:48px;margin:0 auto 22px;--mark-ink:#17181a;--mark-dot:#fb9569}
|
|
205
|
+
@media(prefers-color-scheme:dark){.mark{--mark-ink:#e2e1e4}}
|
|
203
206
|
h1{font-size:20px;font-weight:700;margin:0 0 6px}
|
|
204
207
|
p{margin:0 0 20px;opacity:.72;font-size:14px}
|
|
205
208
|
form{display:flex;gap:8px}
|
|
@@ -213,7 +216,7 @@ button{padding:11px 20px;border:0;border-radius:999px;background:#fb9569;color:#
|
|
|
213
216
|
.err{margin:14px 0 0;color:#b3261e;font-size:13.5px}
|
|
214
217
|
@media(prefers-color-scheme:dark){.err{color:#ffb4a8}}
|
|
215
218
|
</style></head><body><div class="box">
|
|
216
|
-
|
|
219
|
+
${markSvg({ className: 'mark' })}
|
|
217
220
|
<h1>${name}</h1>
|
|
218
221
|
<p>Not open to the world yet. Enter the password to have a look.</p>
|
|
219
222
|
<form method="POST" action="${SUBMIT_PATH}">
|
|
@@ -133,6 +133,7 @@ function themeMarkup(theme) {
|
|
|
133
133
|
// before anything reads the URL, so the neutral pack's inline `data:image/svg+xml,
|
|
134
134
|
// <svg …>` favicon resolves to exactly the same bytes it does today.
|
|
135
135
|
if (theme.favicon) out += `<link rel="icon" href="${esc(theme.favicon)}">`;
|
|
136
|
+
if (theme.appleTouchIcon) out += `<link rel="apple-touch-icon" href="${esc(theme.appleTouchIcon)}">`;
|
|
136
137
|
return out;
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/brand-mark.js — the Bongos mark for the server, read from its one source (task 1005088).
|
|
2
|
+
//
|
|
3
|
+
// The ring-and-dot mark lives in exactly one file, a shipped module's public/brand/mark.svg.
|
|
4
|
+
// A browser page draws it by reference (<use href="…/brand/mark.svg#mark">); the server needs
|
|
5
|
+
// the markup itself in two places that load nothing — the pre-launch and members-only gates,
|
|
6
|
+
// which are self-contained on purpose — and in a looked hall's favicon, drawn in the look's own
|
|
7
|
+
// colours. This file reads that source and hands those out, and scripts/bongos/gen-brand.js
|
|
8
|
+
// writes the static variants (light, dark, mono, the favicon) from the same functions.
|
|
9
|
+
//
|
|
10
|
+
// The source is found by its SHAPE — any shipped module's public/brand/mark.svg — never by a
|
|
11
|
+
// module key, which core may not name (ADR 0083 §Decision #4), as src/hall-look.js finds the
|
|
12
|
+
// style library. PURE given the source text; the default reader is the only I/O.
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
// The Night ink: the chrome world's ink on a dark ground, which every dark twin of the mark uses.
|
|
19
|
+
const NIGHT_INK = '#e2e1e4';
|
|
20
|
+
// The two custom properties the source colours itself by, exactly as written there.
|
|
21
|
+
const INK_VAR = 'var(--mark-ink,currentColor)';
|
|
22
|
+
const DOT_VAR = 'var(--mark-dot,var(--accent,currentColor))';
|
|
23
|
+
// The favicon cut: at 16px the display weights blur, so the tab icon draws a heavier ring, a
|
|
24
|
+
// slightly smaller planet and a larger moon. Same geometry, other weights.
|
|
25
|
+
const SMALL_CUT = { ring: { 'stroke-width': '24' }, disc: { r: '50' }, dot: { r: '18' } };
|
|
26
|
+
|
|
27
|
+
let _source = null;
|
|
28
|
+
function findSource(root) {
|
|
29
|
+
const base = root || require('./instance-config').resolveCoreRoot(); // eslint-disable-line global-require
|
|
30
|
+
const modulesDir = path.join(base, 'modules');
|
|
31
|
+
return fs.readdirSync(modulesDir).sort()
|
|
32
|
+
.map((m) => path.join(modulesDir, m, 'public', 'brand', 'mark.svg'))
|
|
33
|
+
.find((f) => fs.existsSync(f)) || null;
|
|
34
|
+
}
|
|
35
|
+
// The source text, read once. null when no shipped module carries it.
|
|
36
|
+
function readMarkSource({ root } = {}) {
|
|
37
|
+
if (_source !== null && !root) return _source;
|
|
38
|
+
let text = '';
|
|
39
|
+
try { const file = findSource(root); text = file ? fs.readFileSync(file, 'utf8') : ''; } catch { text = ''; }
|
|
40
|
+
if (!root) _source = text;
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The three parts as the source writes them, ids dropped (an inline copy must not collide
|
|
45
|
+
// with the source's ids on a page). null when the source is missing or misshapen.
|
|
46
|
+
function markParts(src = readMarkSource()) {
|
|
47
|
+
const pick = (id) => {
|
|
48
|
+
const m = String(src || '').match(new RegExp(`<(path|circle)\\s+id="mark-${id}"([^>]*?)\\s*/>`));
|
|
49
|
+
return m ? `<${m[1]}${m[2]}/>` : null;
|
|
50
|
+
};
|
|
51
|
+
const parts = { ring: pick('ring'), disc: pick('disc'), dot: pick('dot') };
|
|
52
|
+
return parts.ring && parts.disc && parts.dot ? parts : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setAttrs(el, attrs) {
|
|
56
|
+
let out = el;
|
|
57
|
+
for (const [k, v] of Object.entries(attrs || {})) out = out.replace(new RegExp(`\\s${k}="[^"]*"`), ` ${k}="${v}"`);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The parts' markup with the two colours set: each a CSS value (a hex, or a var()).
|
|
62
|
+
function partsMarkup(parts, { ink = INK_VAR, dot = DOT_VAR, cut = null, only = null } = {}) {
|
|
63
|
+
const names = only || ['ring', 'disc', 'dot'];
|
|
64
|
+
return names.map((n) => setAttrs(parts[n], cut && cut[n]).split(INK_VAR).join(ink).split(DOT_VAR).join(dot)).join('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The mark as inline markup for a server-rendered page. Coloured by --mark-ink / --mark-dot on
|
|
68
|
+
// the element (or its ancestors), exactly as a page's <use> reference is.
|
|
69
|
+
function markSvg({ className = 'mark', src } = {}) {
|
|
70
|
+
const parts = markParts(src === undefined ? readMarkSource() : src);
|
|
71
|
+
if (!parts) return '';
|
|
72
|
+
return `<svg class="${className}" viewBox="0 0 200 200" aria-hidden="true">${partsMarkup(parts)}</svg>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A standalone SVG file of the mark in fixed colours (the light, dark and mono logos, and the
|
|
76
|
+
// two mask shapes the loaders cut from it).
|
|
77
|
+
function markFile(parts, { ink, dot, only, label = '' } = {}) {
|
|
78
|
+
const a11y = label ? ` role="img" aria-label="${label}"` : ' aria-hidden="true"';
|
|
79
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"${a11y}>${partsMarkup(parts, { ink, dot, only })}</svg>\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The favicon: the small cut, in the pack's ink by day and the Night ink by night.
|
|
83
|
+
function faviconSvg({ ink, accent, night = NIGHT_INK }, src = readMarkSource()) {
|
|
84
|
+
const parts = markParts(src);
|
|
85
|
+
if (!parts) return '';
|
|
86
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">' +
|
|
87
|
+
`<style>svg{--i:${ink};--d:${accent}}@media (prefers-color-scheme:dark){svg{--i:${night}}}</style>` +
|
|
88
|
+
partsMarkup(parts, { ink: 'var(--i)', dot: 'var(--d)', cut: SMALL_CUT }) + '</svg>';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// An SVG as a data: URI for a <link rel="icon"> (single quotes, # escaped), or '' for ''.
|
|
92
|
+
function svgDataUri(svg) {
|
|
93
|
+
return svg ? 'data:image/svg+xml;utf8,' + svg.replace(/"/g, "'").replace(/#/g, '%23') : '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { NIGHT_INK, readMarkSource, markParts, markSvg, markFile, faviconSvg, svgDataUri };
|
package/src/branding.js
CHANGED
|
@@ -268,6 +268,8 @@ function clientBranding(b = branding()) {
|
|
|
268
268
|
links: Array.isArray(ui.fonts?.links) ? ui.fonts.links : [],
|
|
269
269
|
},
|
|
270
270
|
favicon: ui.favicon || '',
|
|
271
|
+
// the home-screen icon (a 180x180 PNG; task 1005088), '' when the pack names none
|
|
272
|
+
appleTouchIcon: ui.appleTouchIcon || '',
|
|
271
273
|
backdrop: ui.backdrop || '',
|
|
272
274
|
// The look (ADR 0358): which one this hall wears ('' = its own pack), whether
|
|
273
275
|
// its night is tinted (only ever true with a look), and its logo address.
|
package/src/hall-look.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
const fs = require('node:fs');
|
|
25
25
|
const path = require('node:path');
|
|
26
|
+
const { faviconSvg, svgDataUri } = require('./brand-mark');
|
|
26
27
|
|
|
27
28
|
const HEX = /^#[0-9a-f]{6}$/i;
|
|
28
29
|
const LOOK_NAME = /^[a-z][a-z-]{0,39}$/;
|
|
@@ -53,13 +54,10 @@ function readLibraryLook(name, { root } = {}) {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// The favicon a looked hall shows without a logo: the Bongos mark (task 1004846) in the
|
|
56
|
-
// look's own ink with its accent as the moon, and the Night ink twin in dark mode.
|
|
57
|
+
// look's own ink with its accent as the moon, and the Night ink twin in dark mode. Drawn from
|
|
58
|
+
// the mark's one source (task 1005088); the colours are the look's validated hex.
|
|
57
59
|
function lookFavicon(colors) {
|
|
58
|
-
|
|
59
|
-
return "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'>" +
|
|
60
|
-
`<style>.r{stroke:${c('ink')}}.p{fill:${c('ink')}}@media (prefers-color-scheme:dark){.r{stroke:%23e2e1e4}.p{fill:%23e2e1e4}}</style>` +
|
|
61
|
-
"<path class='r' d='M137 31A78 78 0 1 0 170 66' fill='none' stroke-width='24' stroke-linecap='round'/>" +
|
|
62
|
-
`<circle class='p' cx='100' cy='100' r='50'/><circle cx='156' cy='46' r='18' fill='${c('accent')}'/></svg>`;
|
|
60
|
+
return svgDataUri(faviconSvg({ ink: colors.ink, accent: colors.accent }));
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
// What the hall wears: { name, ui } or null for its own pack. PURE given readLook.
|
|
@@ -91,8 +89,10 @@ function applyLook(resolved, readLook = readLibraryLook) {
|
|
|
91
89
|
ui.look = look.name;
|
|
92
90
|
ui.nightTint = project.nightTint !== 'off';
|
|
93
91
|
ui.favicon = lookFavicon(ui.colors);
|
|
92
|
+
// the home-screen icon is the pack's own colours; a hall in other colours goes without
|
|
93
|
+
ui.appleTouchIcon = '';
|
|
94
94
|
}
|
|
95
|
-
if (logo) { ui.logo = logo; ui.favicon = logo; }
|
|
95
|
+
if (logo) { ui.logo = logo; ui.favicon = logo; ui.appleTouchIcon = ''; }
|
|
96
96
|
resolved.theme.ui = ui;
|
|
97
97
|
return resolved;
|
|
98
98
|
}
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.49'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// tests/brand_mark.mjs — one logo and one favicon set, served from one place (task 1005088,
|
|
2
|
+
// goal 1000138 "One Cloud Bongos design system").
|
|
3
|
+
//
|
|
4
|
+
// The ring-and-dot mark used to be pasted ~22 times (the landing pages, the gates, the hall
|
|
5
|
+
// shell, hall-render, welcome-boot, raise, three loaders' masks) beside four favicon schemes.
|
|
6
|
+
// Now it lives in modules/ui-design/public/brand/mark.svg and nothing else draws it by hand.
|
|
7
|
+
// What this pins, DB-free and browser-free:
|
|
8
|
+
// * grep finds ONE source: no file outside the brand folder carries the mark's geometry
|
|
9
|
+
// (comments stripped), and the brand folder's variants are exactly what
|
|
10
|
+
// scripts/bongos/gen-brand.js writes from the source (so they are outputs, not copies);
|
|
11
|
+
// * every page uses it: the landing and hub pages, the hall shell, hall-render and the
|
|
12
|
+
// welcome reference mark.svg; the three loaders mask with mark-body/mark-dot; raise shows
|
|
13
|
+
// mark-light/mark-dark; the gates and a looked hall's favicon draw it through
|
|
14
|
+
// src/brand-mark.js; every page's fallback favicon and the pack's favicon are the one
|
|
15
|
+
// favicon.svg, with the apple-touch-icon beside it;
|
|
16
|
+
// * a project's own logo (theme.logo) still takes the mark's place.
|
|
17
|
+
//
|
|
18
|
+
// Run: node --test tests/brand_mark.mjs
|
|
19
|
+
|
|
20
|
+
import { test } from 'node:test';
|
|
21
|
+
import assert from 'node:assert/strict';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { createRequire } from 'node:module';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
+
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf8');
|
|
30
|
+
const BRAND = '/modules/ui-design/public/brand/';
|
|
31
|
+
const BRAND_DIR = path.join('modules', 'ui-design', 'public', 'brand');
|
|
32
|
+
|
|
33
|
+
// The mark's fingerprints: the ring's arc and the moon's centre, in any quoting or spacing.
|
|
34
|
+
const FINGERPRINTS = [/A\s*78[\s,]+78\b/, /cx=\\?['"]156\\?['"]\s+cy=\\?['"]46\\?['"]/, /\bcx%3D%27156/];
|
|
35
|
+
// Strip comments so a doc line naming the arc is not a copy (block, line and HTML comments).
|
|
36
|
+
const decomment = (s) => s
|
|
37
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
38
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
39
|
+
.replace(/(^|[^:'"\\])\/\/.*$/gm, '$1');
|
|
40
|
+
|
|
41
|
+
const SCAN_ROOTS = ['src', 'modules', 'scripts', 'config', 'public', 'public-docs', 'server.js'];
|
|
42
|
+
const TEXT = /\.(js|mjs|cjs|html|css|json|svg)$/;
|
|
43
|
+
function walk(rel, out = []) {
|
|
44
|
+
const abs = path.join(ROOT, rel);
|
|
45
|
+
if (!fs.existsSync(abs)) return out;
|
|
46
|
+
const st = fs.statSync(abs);
|
|
47
|
+
if (st.isFile()) { if (TEXT.test(rel)) out.push(rel); return out; }
|
|
48
|
+
for (const name of fs.readdirSync(abs)) {
|
|
49
|
+
if (name === 'node_modules' || name.startsWith('.')) continue;
|
|
50
|
+
walk(path.join(rel, name), out);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Pure: the files (outside the brand folder) that carry the mark's geometry.
|
|
56
|
+
function pastedCopies(files, readFile) {
|
|
57
|
+
return files.filter((f) => !f.startsWith(BRAND_DIR + path.sep))
|
|
58
|
+
.filter((f) => { const code = decomment(readFile(f)); return FINGERPRINTS.some((re) => re.test(code)); });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('grep finds one source for the mark: no pasted copy outside the brand folder', () => {
|
|
62
|
+
const files = SCAN_ROOTS.flatMap((r) => walk(r));
|
|
63
|
+
assert.ok(files.length > 500, 'the scan reads the tree');
|
|
64
|
+
assert.deepEqual(pastedCopies(files, (f) => read(f)), [],
|
|
65
|
+
'draw the mark by reference to modules/ui-design/public/brand/mark.svg (or src/brand-mark.js on the server), never a pasted copy');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('the guard is real: a pasted copy in any of the three spellings is found', () => {
|
|
69
|
+
const fake = {
|
|
70
|
+
'modules/x/public/a.html': '<svg><path d="M137 31 A78 78 0 1 0 170 66"/></svg>',
|
|
71
|
+
'modules/x/public/b.css': ".l{--s:url(\"data:image/svg+xml,%3Csvg%3E%3Ccircle cx='156' cy='46' r='12'/%3E%3C/svg%3E\")}",
|
|
72
|
+
'src/c.js': "const s = \"<path class='r' d='M137 31A78 78 0 1 0 170 66'/>\";",
|
|
73
|
+
'src/d.js': '// the arc A78 78 is the ring\nconst ok = 1;',
|
|
74
|
+
[path.join(BRAND_DIR, 'mark.svg')]: '<path d="M137 31 A78 78 0 1 0 170 66"/>',
|
|
75
|
+
};
|
|
76
|
+
assert.deepEqual(pastedCopies(Object.keys(fake), (f) => fake[f]).sort(),
|
|
77
|
+
['modules/x/public/a.html', 'modules/x/public/b.css', 'src/c.js']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('the source carries the three parts and their two colours', () => {
|
|
81
|
+
const src = read(BRAND_DIR, 'mark.svg');
|
|
82
|
+
for (const id of ['mark', 'mark-ring', 'mark-disc', 'mark-dot']) assert.match(src, new RegExp(`id="${id}"`));
|
|
83
|
+
// A browser reads a referenced SVG as XML: a comment holding "--" (say, a custom property's
|
|
84
|
+
// name) makes the whole file unreadable and every page's mark silently vanishes.
|
|
85
|
+
for (const [, body] of src.matchAll(/<!--([\s\S]*?)-->/g)) assert.ok(!body.includes('--'), 'no "--" inside an XML comment');
|
|
86
|
+
assert.equal((src.match(/<!--/g) || []).length, (src.match(/-->/g) || []).length, 'every comment closes');
|
|
87
|
+
const { markParts } = require('../src/brand-mark.js');
|
|
88
|
+
const parts = markParts(src);
|
|
89
|
+
assert.ok(parts, 'src/brand-mark.js reads the parts');
|
|
90
|
+
assert.match(parts.ring + parts.disc, /var\(--mark-ink,currentColor\)/);
|
|
91
|
+
assert.match(parts.dot, /var\(--mark-dot,var\(--accent,currentColor\)\)/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('the brand folder holds exactly what gen-brand.js writes from the source (light, dark, mono, favicon)', () => {
|
|
95
|
+
const { brandFiles, packColors } = require('../scripts/bongos/gen-brand.js');
|
|
96
|
+
const files = brandFiles(read(BRAND_DIR, 'mark.svg'), packColors());
|
|
97
|
+
for (const [name, text] of Object.entries(files)) {
|
|
98
|
+
assert.equal(read(BRAND_DIR, name), text, `${name} is stale — run node scripts/bongos/gen-brand.js`);
|
|
99
|
+
}
|
|
100
|
+
const c = packColors();
|
|
101
|
+
assert.match(files['mark-light.svg'], new RegExp(c.ink));
|
|
102
|
+
assert.match(files['mark-light.svg'], new RegExp(c.accent));
|
|
103
|
+
assert.match(files['mark-dark.svg'], /#e2e1e4/);
|
|
104
|
+
assert.doesNotMatch(files['mark-mono.svg'], new RegExp(c.accent), 'mono is one ink');
|
|
105
|
+
assert.match(files['favicon.svg'], /prefers-color-scheme:dark/, 'the tab icon has its night twin');
|
|
106
|
+
const png = fs.readFileSync(path.join(ROOT, BRAND_DIR, 'apple-touch-icon.png'));
|
|
107
|
+
assert.equal(png.toString('ascii', 1, 4), 'PNG');
|
|
108
|
+
assert.deepEqual([png.readUInt32BE(16), png.readUInt32BE(20)], [180, 180], 'the home-screen icon is 180x180');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('every landing and hub page draws the mark by reference', () => {
|
|
112
|
+
const dir = path.join('modules', 'public-landing', 'public');
|
|
113
|
+
const pages = fs.readdirSync(path.join(ROOT, dir)).filter((n) => n.endsWith('.html'))
|
|
114
|
+
.filter((n) => read(dir, n).includes('class="bongosMark"'));
|
|
115
|
+
assert.ok(pages.length >= 10, `the landing pages with a lockup (${pages.length})`);
|
|
116
|
+
for (const n of pages) {
|
|
117
|
+
assert.match(read(dir, n), new RegExp(`<svg class="bongosMark"[^>]*><use href="${BRAND}mark\\.svg#mark"/></svg>`), n);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('the hall shell, hall-render and the welcome reference the mark, and a project logo still wins', () => {
|
|
122
|
+
const shell = read('modules', 'hall-ui', 'public', 'shell.js');
|
|
123
|
+
assert.ok(shell.includes(`<use href="${BRAND}mark.svg#mark"/>`), 'the sidebar mark');
|
|
124
|
+
assert.match(shell, /brand\.theme && brand\.theme\.logo\s*\?\s*'<img class="brand-mark__logo"/, 'the logo override');
|
|
125
|
+
const render = read('modules', 'hall-ui', 'public', 'hall-render.js');
|
|
126
|
+
assert.ok(render.includes(`<use href="${BRAND}mark.svg#mark"/>`), 'the signed-out card mark');
|
|
127
|
+
assert.match(render, /logo\s*\?\s*`<img class="landing__logo"/, 'the logo override');
|
|
128
|
+
const wb = read('modules', 'hall-ui', 'public', 'welcome-boot.js');
|
|
129
|
+
assert.ok(wb.includes(`var MARK = '${BRAND}mark.svg'`), 'the welcome banner');
|
|
130
|
+
for (const part of ['#mark-ring', '#mark-disc', '#mark-dot']) assert.ok(wb.includes(part), `the welcome animates ${part}`);
|
|
131
|
+
assert.match(wb, /logo \? '<img class="wb__logo"/, 'the logo override');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('the three loaders cut their shapes from the brand files', () => {
|
|
135
|
+
for (const [file, v] of [
|
|
136
|
+
[['modules', 'hall-ui', 'public', 'style.css'], '--hl-shape'],
|
|
137
|
+
[['modules', 'status-ui', 'public', 'style.css'], '--lm-shape'],
|
|
138
|
+
[['modules', 'public-landing', 'public', 'assets', 'cosmos.css'], '--lm-shape'],
|
|
139
|
+
]) {
|
|
140
|
+
const css = read(...file);
|
|
141
|
+
assert.ok(new RegExp(`${v}:\\s*url\\("${BRAND}mark-body\\.svg"\\)`).test(css), `${file.join('/')} ring and planet`);
|
|
142
|
+
assert.ok(new RegExp(`${v}:\\s*url\\("${BRAND}mark-dot\\.svg"\\)`).test(css), `${file.join('/')} moon`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('raise shows the shared logo pair and favicon', () => {
|
|
147
|
+
for (const v of ['gate', 'page', 'nda']) {
|
|
148
|
+
const html = read('modules', 'raise', 'views', `${v}.html`);
|
|
149
|
+
assert.ok(html.includes(`<link rel="icon" href="${BRAND}favicon.svg"`), `${v}: favicon`);
|
|
150
|
+
assert.ok(html.includes(`<link rel="apple-touch-icon" href="${BRAND}apple-touch-icon.png">`), `${v}: touch icon`);
|
|
151
|
+
if (html.includes('class="logo')) {
|
|
152
|
+
assert.ok(html.includes(`src="${BRAND}mark-light.svg"`) && html.includes(`src="${BRAND}mark-dark.svg"`), `${v}: logo pair`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('the gates draw the mark from the source and still load nothing', () => {
|
|
158
|
+
const { markSvg } = require('../src/brand-mark.js');
|
|
159
|
+
const svg = markSvg({ className: 'mark' });
|
|
160
|
+
assert.match(svg, /^<svg class="mark" viewBox="0 0 200 200" aria-hidden="true"><path [^>]*\/><circle [^>]*\/><circle [^>]*\/><\/svg>$/);
|
|
161
|
+
assert.doesNotMatch(svg, /\sid=/, 'an inline copy carries no ids');
|
|
162
|
+
for (const f of ['prelaunch-gate.js', 'platform-visibility-gate.js']) {
|
|
163
|
+
const src = read('src', 'bongos', f);
|
|
164
|
+
assert.ok(src.includes("${markSvg({ className: 'mark' })}"), `${f} draws the mark`);
|
|
165
|
+
assert.match(src, /\.mark\{[^}]*--mark-ink:#17181a;--mark-dot:#fb9569\}/, `${f} colours it`);
|
|
166
|
+
assert.match(src, /prefers-color-scheme:dark\)\{\.mark\{--mark-ink:#e2e1e4\}\}/, `${f} night twin`);
|
|
167
|
+
assert.doesNotMatch(src, /<use href=/, `${f} references nothing — it is served before any asset`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('one favicon: every page fallback and the pack point at favicon.svg; a look or a logo overrides', () => {
|
|
172
|
+
for (const dir of [['modules', 'hall-ui', 'public'], ['modules', 'status-ui', 'public']]) {
|
|
173
|
+
for (const n of fs.readdirSync(path.join(ROOT, ...dir)).filter((x) => x.endsWith('.html'))) {
|
|
174
|
+
const icons = read(...dir, n).match(/<link rel="icon"[^>]*>/g) || [];
|
|
175
|
+
for (const l of icons) assert.ok(l.includes(`href="${BRAND}favicon.svg"`), `${dir.join('/')}/${n}: ${l.slice(0, 80)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const pack = JSON.parse(read('config', 'branding.neutral.json')).theme.ui;
|
|
179
|
+
assert.equal(pack.favicon, `${BRAND}favicon.svg`);
|
|
180
|
+
assert.equal(pack.appleTouchIcon, `${BRAND}apple-touch-icon.png`);
|
|
181
|
+
|
|
182
|
+
const { themeMarkup } = require('../src/bongos/serve-internal.js');
|
|
183
|
+
const m = themeMarkup({ favicon: pack.favicon, appleTouchIcon: pack.appleTouchIcon, fonts: { links: [] } });
|
|
184
|
+
assert.ok(m.includes(`<link rel="icon" href="${BRAND}favicon.svg">`));
|
|
185
|
+
assert.ok(m.includes(`<link rel="apple-touch-icon" href="${BRAND}apple-touch-icon.png">`));
|
|
186
|
+
assert.ok(!themeMarkup({ favicon: 'x', fonts: { links: [] } }).includes('apple-touch-icon'), 'no touch icon unless the pack names one');
|
|
187
|
+
|
|
188
|
+
const { applyLook } = require('../src/hall-look.js');
|
|
189
|
+
const looked = applyLook({ theme: { ui: { ...pack } }, project: { look: 'custom', lookAccent: '#3366cc' } },
|
|
190
|
+
() => ({ colors: { ink: '#101010', accent: '#fb9569' }, fonts: {} }));
|
|
191
|
+
assert.match(looked.theme.ui.favicon, /^data:image\/svg\+xml;utf8,<svg /);
|
|
192
|
+
assert.match(looked.theme.ui.favicon, /%233366cc/, 'drawn in the look\'s accent');
|
|
193
|
+
assert.match(looked.theme.ui.favicon, /stroke-width='24'/, 'the small cut');
|
|
194
|
+
assert.equal(looked.theme.ui.appleTouchIcon, '', 'the pack-coloured touch icon steps aside');
|
|
195
|
+
});
|
|
@@ -43,7 +43,9 @@ test('R74: the vanilla pack carries the Cloud Bongos LOOK (chrome palette, Manro
|
|
|
43
43
|
assert.match(c.theme.fonts.display, /Manrope/);
|
|
44
44
|
assert.match(c.theme.fonts.body, /Manrope/);
|
|
45
45
|
assert.ok(c.theme.fonts.links.some((l) => /JetBrains\+Mono/.test(l) && /Manrope/.test(l)), 'loads Manrope + JetBrains Mono');
|
|
46
|
-
|
|
46
|
+
// the one favicon set (task 1005088): the moon-in-the-gap mark, served from the brand folder
|
|
47
|
+
assert.equal(c.theme.favicon, '/modules/ui-design/public/brand/favicon.svg', 'the moon-in-the-gap favicon, not the Greek Α');
|
|
48
|
+
assert.equal(c.theme.appleTouchIcon, '/modules/ui-design/public/brand/apple-touch-icon.png');
|
|
47
49
|
assert.doesNotMatch(c.theme.favicon, /font-family='serif'>Α/);
|
|
48
50
|
// the backdrop is the TOKEN, not a hex: the served body follows the mode,
|
|
49
51
|
// because the sheets' dark blocks are (0,2,0) and outrank the injected :root.
|
|
@@ -60,7 +62,8 @@ test('R74: serving the hall on a vanilla boot injects the Cloud Bongos chrome th
|
|
|
60
62
|
// palette override + the token-valued body backdrop + bongo favicon + the type links
|
|
61
63
|
assert.match(out, /:root\{[^}]*--accent:#fb9569;/);
|
|
62
64
|
assert.match(out, /body\{background:var\(--bg\);\}/);
|
|
63
|
-
assert.match(out, /<link rel="icon" href="
|
|
65
|
+
assert.match(out, /<link rel="icon" href="\/modules\/ui-design\/public\/brand\/favicon\.svg">/);
|
|
66
|
+
assert.match(out, /<link rel="apple-touch-icon" href="\/modules\/ui-design\/public\/brand\/apple-touch-icon\.png">/);
|
|
64
67
|
assert.match(out, /fonts\.googleapis\.com\/css2\?family=JetBrains\+Mono/);
|
|
65
68
|
// and it lands AFTER the stylesheet so the Cloud Bongos look wins
|
|
66
69
|
assert.ok(out.indexOf('/builders/style.css') < out.indexOf('--accent:#fb9569'));
|
package/tests/hall_loading.mjs
CHANGED
|
@@ -36,13 +36,11 @@ test('the loader is drawn from the Bongos mark, in three sizes', () => {
|
|
|
36
36
|
for (const sel of ['.hall-loading {', '.hall-loading::before {', '.hall-loading::after {', '.hall-loading--page {', '.hall-loading--inline {']) {
|
|
37
37
|
assert.ok(LOADER.includes(sel), `${sel} is declared`);
|
|
38
38
|
}
|
|
39
|
-
// The
|
|
40
|
-
|
|
41
|
-
assert.ok(
|
|
42
|
-
assert.ok(
|
|
43
|
-
assert.ok(LOADER.includes(
|
|
44
|
-
assert.ok(SHELL.includes('<circle cx="156" cy="46" r="12" fill="#fb9569"/>'), 'the brand mark\'s dot');
|
|
45
|
-
assert.ok(LOADER.includes("circle cx='156' cy='46' r='12'"), 'is the loader\'s dot');
|
|
39
|
+
// The shell's brand mark and the loader are cut from the one mark (task 1005088), so the
|
|
40
|
+
// loader IS the logo: the shell references mark.svg, the masks are its ring-and-core and dot.
|
|
41
|
+
assert.ok(SHELL.includes('<use href="/modules/ui-design/public/brand/mark.svg#mark"/>'), 'the brand mark is the one mark');
|
|
42
|
+
assert.ok(LOADER.includes('--hl-shape: url("/modules/ui-design/public/brand/mark-body.svg")'), 'the loader\'s ring and core');
|
|
43
|
+
assert.ok(LOADER.includes('--hl-shape: url("/modules/ui-design/public/brand/mark-dot.svg")'), 'the loader\'s dot');
|
|
46
44
|
});
|
|
47
45
|
|
|
48
46
|
test('it moves in CSS only, on transform and filter only, and rests on the logo', () => {
|
|
@@ -74,9 +72,9 @@ test('every colour is a token; the black in the masks is alpha, not paint', () =
|
|
|
74
72
|
for (const p of paints) assert.match(p, /^(var\(--[a-z-]+\)|color-mix\(in srgb, var\(--ink\) \d+%, var\(--bg-card\)\))$/, `${p} is a token or a mix of one`);
|
|
75
73
|
// The one accent is the dot: the live thing on a waiting page (the One Warm Thing Rule).
|
|
76
74
|
assert.equal((css.match(/var\(--accent\)/g) || []).length, 1, 'the accent paints the dot alone');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
// The two masks are the brand folder's one-ink shapes (task 1005088): only their alpha counts.
|
|
76
|
+
const masks = [...css.matchAll(/url\("\/modules\/ui-design\/public\/brand\/(mark-[a-z]+\.svg)"\)/g)].map((m) => m[1]);
|
|
77
|
+
assert.deepEqual(masks, ['mark-body.svg', 'mark-dot.svg'], 'two masks: the ring and core, and the dot');
|
|
80
78
|
});
|
|
81
79
|
|
|
82
80
|
test('the page loaders step aside by themselves, and never show without :has()', () => {
|