@bongos/core 1.19.414 → 1.19.415
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 +166 -21
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +13 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +13 -0
- package/docs/design/mocks/hub/planets.html +127 -3
- package/docs/design/projects-hub-direction-v2.md +10 -0
- package/docs/module-api-changelog.md +2 -0
- package/modules/platform-identity/migrations/platform_identity_013_project_template_materials.sql +31 -0
- package/modules/platform-identity/platform-identity.js +18 -5
- package/modules/platform-identity/routes/projects.js +3 -2
- package/modules/platform-identity/tests/platform-identity.mjs +23 -12
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +47 -1
- package/public-landing/assets/world/r3-frost-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-frost-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-frost-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-frost-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-gold-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-gold-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-bead-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-bead-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-blob-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-blob-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-obsidian-pair-cut.webp +0 -0
- package/public-landing/assets/world/r3-obsidian-pair-cut.webp.json +15 -0
- package/public-landing/assets/world/r3-space-nebula.jpg +0 -0
- package/public-landing/assets/world/r3-space-nebula.jpg.json +13 -0
- package/public-landing/projects.html +23 -14
- package/public-landing/projects.states.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/projects_hub.mjs +68 -18
package/tests/projects_hub.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import assert from 'node:assert/strict';
|
|
|
16
16
|
import fs from 'node:fs';
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { createRequire } from 'node:module';
|
|
19
20
|
|
|
20
21
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
22
|
const HUB = fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.html'), 'utf8');
|
|
@@ -1403,31 +1404,41 @@ test('the joinability in-flight line shares the visibility section’s push pred
|
|
|
1403
1404
|
/* ── the planets and the field (task 1003319) — the map's orbs as the landing's
|
|
1404
1405
|
chrome objects in the project's accent, a per-project template with a
|
|
1405
1406
|
deterministic default walk, and the field brought to the world. ── */
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1407
|
+
const TEMPLATE_RE = /^(blob|pair|bead)-(tint|wash|chrome|(frost|gold|obsidian)-(tint|wash|plain))$/;
|
|
1408
|
+
const partsOf = (t) => { const p = t.split('-'); return p.length === 3 ? p : [p[0], 'chrome', p[1]]; };
|
|
1409
|
+
|
|
1410
|
+
test('the planet templates: shape × material × style; the walk changes all three each step; the catalog and the migration agree (tasks 1003319, 1003337)', () => {
|
|
1411
|
+
const walkList = (SCRIPT.match(/var ORB_WALK = \[([^\]]*)\]/) || [, ''])[1];
|
|
1412
|
+
const walk = [...walkList.matchAll(/'([a-z-]+)'/g)].map((m) => m[1]);
|
|
1413
|
+
assert.equal(walk.length, 12);
|
|
1414
|
+
assert.equal(new Set(walk).size, 12, 'twelve distinct walk positions');
|
|
1415
|
+
for (const t of walk) assert.match(t, TEMPLATE_RE);
|
|
1416
|
+
for (let i = 0; i < walk.length; i++) {
|
|
1417
|
+
const [s0, m0, y0] = partsOf(walk[(i + walk.length - 1) % walk.length]);
|
|
1418
|
+
const [s1, m1, y1] = partsOf(walk[i]);
|
|
1419
|
+
assert.ok(s0 !== s1 && m0 !== m1 && y0 !== y1, `walk step ${i} changes shape, material and style (wrapping)`);
|
|
1416
1420
|
}
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1421
|
+
const P = createRequire(import.meta.url)('../modules/platform-identity/platform-identity.js');
|
|
1422
|
+
assert.deepEqual(P.PROJECT_TEMPLATE_WALK, walk, 'the hub and the catalog walk the same list in the same order');
|
|
1423
|
+
// the client's full set is built from the same grammar the server builds it from
|
|
1424
|
+
assert.match(SCRIPT, /var ORB_SHAPES = \['blob', 'pair', 'bead'\], ORB_MATERIALS = \['frost', 'gold', 'obsidian'\];/);
|
|
1425
|
+
assert.match(SCRIPT, /ORB_SHAPES\.forEach\(function \(s\) \{ \['tint', 'wash', 'chrome'\]\.forEach\(function \(y\) \{ ORB_TEMPLATES\.push\(s \+ '-' \+ y\); \}\); \}\);/);
|
|
1426
|
+
assert.match(SCRIPT, /ORB_SHAPES\.forEach\(function \(s\) \{ ORB_MATERIALS\.forEach\(function \(m\) \{ \['tint', 'wash', 'plain'\]\.forEach\(function \(y\) \{ ORB_TEMPLATES\.push\(s \+ '-' \+ m \+ '-' \+ y\); \}\); \}\); \}\);/);
|
|
1427
|
+
assert.equal(P.PROJECT_TEMPLATES.length, 36);
|
|
1428
|
+
for (const t of P.PROJECT_TEMPLATES) assert.match(t, TEMPLATE_RE);
|
|
1429
|
+
for (const t of walk) assert.ok(P.PROJECT_TEMPLATES.includes(t), `the walk's ${t} is a valid name`);
|
|
1430
|
+
const mig = fs.readFileSync(path.join(ROOT, 'modules', 'platform-identity', 'migrations', 'platform_identity_013_project_template_materials.sql'), 'utf8');
|
|
1431
|
+
for (const t of P.PROJECT_TEMPLATES) assert.ok(mig.includes(`'${t}'`), `migration 013 names ${t}`);
|
|
1422
1432
|
});
|
|
1423
1433
|
|
|
1424
1434
|
test('the walk assigns the template by canonical position beside the accent; a curated template wins; the platform is the pair in chrome', () => {
|
|
1425
|
-
assert.match(SCRIPT, /function assignAccents[\s\S]*?p\._acc = ORB_ACCENTS\[i % ORB_ACCENTS\.length\];\s*p\._tpl =
|
|
1435
|
+
assert.match(SCRIPT, /function assignAccents[\s\S]*?p\._acc = ORB_ACCENTS\[i % ORB_ACCENTS\.length\];\s*p\._tpl = ORB_WALK\[i % ORB_WALK\.length\];/,
|
|
1426
1436
|
'one walk, two assignments');
|
|
1427
|
-
assert.match(SCRIPT, /function templateOf\(p\) \{\s*return ORB_TEMPLATES\.indexOf\(p\.template\) !== -1 \? p\.template : \(p\._tpl \|\|
|
|
1437
|
+
assert.match(SCRIPT, /function templateOf\(p\) \{\s*return ORB_TEMPLATES\.indexOf\(p\.template\) !== -1 \? p\.template : \(p\._tpl \|\| ORB_WALK\[0\]\);/,
|
|
1428
1438
|
'a curated name wins; an unknown name is not a choice');
|
|
1429
1439
|
assert.match(SCRIPT, /_acc: ORB_ACCENTS\[slugAccent\(inst\.slug\)\], _tpl: slugTemplate\(inst\.slug\)/, 'an owned-only entry walks by its slug');
|
|
1430
|
-
assert.match(SCRIPT, /
|
|
1440
|
+
assert.match(SCRIPT, /function slugTemplate\(slug\) \{ return ORB_WALK\[slugHash\(slug\) % ORB_WALK\.length\]; \}/, 'by the walk, not the whole set');
|
|
1441
|
+
assert.match(SCRIPT, /var tpl = templateOf\(p\), tplParts = tpl\.split\('-'\);\s*a\.setAttribute\('data-template', tpl\);\s*a\.setAttribute\('data-shape', tplParts\[0\]\);\s*a\.setAttribute\('data-material', tplParts\.length === 3 \? tplParts\[1\] : 'chrome'\);\s*a\.setAttribute\('data-style', tplParts\[tplParts\.length - 1\]\);/,
|
|
1431
1442
|
'the orb carries its template as data attributes; the sheet does the rest');
|
|
1432
1443
|
assert.match(HUB, /<a class="worldOrb" id="selfOrb" href="\/" data-shape="pair" data-style="chrome" data-template="pair-chrome"/, 'the platform is its own mark');
|
|
1433
1444
|
});
|
|
@@ -1455,6 +1466,43 @@ test("the planet is the landing's object on the landing's crop maths: the three
|
|
|
1455
1466
|
assert.match(COSMOS, /--plate-bongos-cut:url\(\/assets\/world\/r2-bongos-chrome-a-cut\.webp\);/);
|
|
1456
1467
|
});
|
|
1457
1468
|
|
|
1469
|
+
test('the materials (task 1003337): a whole object per plate, the crop recomputed from the cutout\'s alpha box; every plate ships with alpha and a sidecar', () => {
|
|
1470
|
+
// the boxes measured from the shipped cutouts (alpha >= 16 on the 2048² plate); the sheet
|
|
1471
|
+
// pads each side by 1.5% and derives zoom = 1/width, px/py = -u0/-v0 × zoom (square plates)
|
|
1472
|
+
const BBOX = { 'frost-blob': { u0:0.168, u1:0.8223, v0:0.2495, v1:0.7642 }, 'frost-pair': { u0:0.0591, u1:0.9409, v0:0.2764, v1:0.7231 }, 'frost-bead': { u0:0.1685, u1:0.8066, v0:0.1738, v1:0.8198 }, 'gold-blob': { u0:0.1274, u1:0.8584, v0:0.1035, v1:0.9019 }, 'gold-pair': { u0:0.0552, u1:0.9458, v0:0.2739, v1:0.7383 }, 'gold-bead': { u0:0.0542, u1:0.9478, v0:0.0542, v1:0.9434 }, 'obsidian-blob': { u0:0.1885, u1:0.8018, v0:0.1177, v1:0.8979 }, 'obsidian-pair': { u0:0.0249, u1:0.9707, v0:0.3096, v1:0.7158 }, 'obsidian-bead': { u0:0.1416, u1:0.854, v0:0.1431, v1:0.8535 } };
|
|
1473
|
+
const MARGIN = 0.015;
|
|
1474
|
+
const f5 = (n) => { const s = n.toFixed(5); return s.replace(/^(-?)0\./, '$1.').replace(/\.?0+$/, '') || '0'; };
|
|
1475
|
+
for (const [key, b] of Object.entries(BBOX)) {
|
|
1476
|
+
const [material, shape] = key.split('-');
|
|
1477
|
+
const U0 = Math.max(0, b.u0 - MARGIN), U1 = Math.min(1, b.u1 + MARGIN), V0 = Math.max(0, b.v0 - MARGIN), V1 = Math.min(1, b.v1 + MARGIN);
|
|
1478
|
+
const w = U1 - U0, h = V1 - V0, zoom = 1 / w;
|
|
1479
|
+
const rule = `.worldOrb[data-material="${material}"][data-shape="${shape}"]{--tpl-src:var(--plate-${material}-${shape}-cut);--tpl-ar:${f5(w / h)};--tpl-zoom:${f5(zoom)};--tpl-px:${f5(-U0 * zoom)};--tpl-py:${f5(-V0 * zoom)};}`;
|
|
1480
|
+
assert.ok(COSMOS.includes(rule), `the ${key} crop follows from its box: ${rule}`);
|
|
1481
|
+
assert.ok(COSMOS.includes(`--plate-${material}-${shape}-cut:url(/assets/world/r3-${material}-${shape}-cut.webp);`), `the ${key} plate token`);
|
|
1482
|
+
const file = path.join(ROOT, 'public-landing', 'assets', 'world', `r3-${material}-${shape}-cut.webp`);
|
|
1483
|
+
assert.ok(fs.existsSync(file), `${key}: the cutout ships`);
|
|
1484
|
+
assert.ok(fs.existsSync(file + '.json'), `${key}: with its sidecar`);
|
|
1485
|
+
const head = fs.readFileSync(file).subarray(0, 40).toString('latin1');
|
|
1486
|
+
assert.ok(head.startsWith('RIFF') && head.includes('WEBP') && /ALPH|VP8X/.test(head), `${key}: WebP with an alpha chunk`);
|
|
1487
|
+
}
|
|
1488
|
+
assert.match(COSMOS, /\.worldOrb\[data-style="plain"\]\{--tpl-ink:transparent;--tpl-blend:normal;\}/, 'plain is the material as rendered');
|
|
1489
|
+
// the material rules carry two attributes so they outrank the one-attribute chrome crops
|
|
1490
|
+
assert.ok(COSMOS.indexOf('.worldOrb[data-material="frost"][data-shape="blob"]') > COSMOS.indexOf('.worldOrb[data-shape="bead"]{'), 'the materials follow the chrome crops');
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
test('the field carries a rendered backdrop (task 1003337): one token, cover, a slow transform drift, the plate and its sidecar on disk', () => {
|
|
1494
|
+
assert.match(COSMOS, /--field-plate:url\(\/assets\/world\/r3-space-nebula\.jpg\);/, 'the pick is one token');
|
|
1495
|
+
assert.match(COSMOS, /\.field \.ground\{position:absolute;inset:-4%;background:var\(--sky-bg\) var\(--field-plate\) center\/cover no-repeat;\}/);
|
|
1496
|
+
assert.match(COSMOS, /@media \(prefers-reduced-motion:no-preference\)\{\s*\.field \.ground\{animation:groundDrift 90s ease-in-out infinite alternate;\}/, 'the drift only runs where motion is welcome');
|
|
1497
|
+
assert.match(COSMOS, /@keyframes groundDrift\{from\{transform:translate3d\([^)]*\) scale\(1\.02\);\}to\{transform:translate3d\([^)]*\) scale\(1\.06\);\}\}/, 'transform only');
|
|
1498
|
+
const plate = path.join(ROOT, 'public-landing', 'assets', 'world', 'r3-space-nebula.jpg');
|
|
1499
|
+
assert.ok(fs.existsSync(plate) && fs.existsSync(plate + '.json'), 'the plate and its sidecar ship');
|
|
1500
|
+
const side = JSON.parse(fs.readFileSync(plate + '.json', 'utf8'));
|
|
1501
|
+
assert.equal(side.kind, 'backdrop');
|
|
1502
|
+
assert.match(side.prompt, /pure black/i, 'a text-free plate on black');
|
|
1503
|
+
assert.doesNotMatch(COSMOS, /\.field \.ground::after\{content:""/, 'nothing drawn over the plate');
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1458
1506
|
test('the style is a blend on a masked colour layer; the accent never paints the box; the ring and the spotlight follow the silhouette', () => {
|
|
1459
1507
|
assert.match(COSMOS, /\.worldOrb \.disc::before\{content:"";position:absolute;inset:0;\s*background-image:linear-gradient\(var\(--tpl-ink\),var\(--tpl-ink\)\),var\(--tpl-src\);/);
|
|
1460
1508
|
assert.match(COSMOS, /background-blend-mode:var\(--tpl-blend\),normal;\s*-webkit-mask-image:var\(--tpl-src\);mask-image:var\(--tpl-src\);/, 'the mask is the plate itself');
|
|
@@ -1502,9 +1550,11 @@ test('the kit proves the field path: the fixture carries a curated template and
|
|
|
1502
1550
|
const fixture = JSON.parse(fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'featured.json'), 'utf8'));
|
|
1503
1551
|
const otb = fixture.projects.find((p) => p.origin === 'https://amazonprimea.com');
|
|
1504
1552
|
assert.equal(otb.template, 'bead-wash');
|
|
1553
|
+
assert.equal(fixture.projects.find((p) => p.origin === 'https://demo.cloudbongos.com').template, 'pair-gold-wash', 'a material template rides the wire (task 1003337)');
|
|
1505
1554
|
assert.ok(fixture.projects.every((p) => 'template' in p), 'the wire never drops the key');
|
|
1506
1555
|
const states = JSON.parse(fs.readFileSync(path.join(ROOT, 'public-landing', 'projects.states.json'), 'utf8'));
|
|
1507
1556
|
const attrs = states.states['in-map'].expect.attr.map((a) => a.join(' '));
|
|
1508
1557
|
assert.ok(attrs.includes('#selfOrb data-template pair-chrome'));
|
|
1509
1558
|
assert.ok(attrs.some((a) => a.includes('amazonprimea.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
|
|
1559
|
+
assert.ok(attrs.some((a) => a.includes('demo.cloudbongos.com') && a.endsWith('data-material gold')), 'the material reaches the orb as its own attribute');
|
|
1510
1560
|
});
|