@bongos/core 1.19.407 → 1.19.409
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 +47 -27
- package/clients/bongos-client/index.d.ts +2 -2
- package/docs/api/openapi.json +5 -1
- package/docs/api-reference.md +2 -2
- package/docs/copy-inventory.md +172 -172
- package/docs/copy-registry.json +219 -219
- package/docs/design/mocks/hub/planets.html +232 -0
- package/docs/design/projects-hub-direction-v2.md +10 -0
- package/modules/platform-identity/migrations/platform_identity_012_project_template.sql +26 -0
- package/modules/platform-identity/migrations/platform_identity_012_recent_ships.sql +40 -0
- package/modules/platform-identity/platform-identity.js +105 -14
- package/modules/platform-identity/routes/projects.js +4 -0
- package/modules/platform-identity/routes/sso.js +10 -0
- package/modules/platform-identity/tests/platform-identity.mjs +36 -2
- 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 +107 -68
- package/public-landing/projects.html +40 -40
- package/public-landing/projects.states.json +1 -1
- package/src/bongos/auth-admission.js +89 -8
- package/src/bongos/routes/auth.js +21 -0
- package/src/module-api.js +14 -19
- package/tests/hub_recent_ships.mjs +373 -0
- package/tests/projects_hub.mjs +109 -0
|
@@ -226,6 +226,35 @@ await ta('upsertProject carries the declared type, COALESCE-preserved like tagli
|
|
|
226
226
|
assert.ok(!pool2.calls[0].params.includes('spacefaring'), 'unknown types never reach the SQL');
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
await ta('upsertProject carries the planet template, COALESCE-preserved; an unknown name is not a choice (task 1003319)', async () => {
|
|
230
|
+
const pool = mockPool([{ rows: [{ id: '1', origin: 'https://p', template: 'bead-wash' }] }]);
|
|
231
|
+
await P.upsertProject({ origin: 'https://p', name: 'Proj', template: 'bead-wash' }, { pool });
|
|
232
|
+
const c = pool.calls[0];
|
|
233
|
+
assert.match(c.text, /template\s*=\s*COALESCE/, 'a later templateless upsert must not erase a curated template');
|
|
234
|
+
assert.ok(c.params.includes('bead-wash'), 'the template value is parameterized');
|
|
235
|
+
const pool2 = mockPool([{ rows: [{ id: '2' }] }]);
|
|
236
|
+
await P.upsertProject({ origin: 'https://q', name: 'Q', template: 'saucer-neon' }, { pool: pool2 });
|
|
237
|
+
assert.ok(!pool2.calls[0].params.includes('saucer-neon'), 'unknown templates never reach the SQL');
|
|
238
|
+
// the vocabulary: the world's three whole objects × the three ways the accent lands,
|
|
239
|
+
// in the walk's order (consecutive positions change shape AND style)
|
|
240
|
+
assert.equal(P.PROJECT_TEMPLATES.length, 9);
|
|
241
|
+
assert.equal(new Set(P.PROJECT_TEMPLATES).size, 9, 'nine distinct templates');
|
|
242
|
+
for (const t of P.PROJECT_TEMPLATES) assert.match(t, /^(blob|pair|bead)-(tint|wash|chrome)$/);
|
|
243
|
+
for (let i = 1; i < P.PROJECT_TEMPLATES.length; i++) {
|
|
244
|
+
const [s0, y0] = P.PROJECT_TEMPLATES[i - 1].split('-');
|
|
245
|
+
const [s1, y1] = P.PROJECT_TEMPLATES[i].split('-');
|
|
246
|
+
assert.ok(s0 !== s1 && y0 !== y1, `walk step ${i} changes both shape and style`);
|
|
247
|
+
}
|
|
248
|
+
// the migration pins the same nine
|
|
249
|
+
const mig = readFileSync(new URL('../migrations/platform_identity_012_project_template.sql', import.meta.url), 'utf8');
|
|
250
|
+
for (const t of P.PROJECT_TEMPLATES) assert.ok(mig.includes(`'${t}'`), `migration 012 names ${t}`);
|
|
251
|
+
// and the public projection carries it, so the sky can read a curated choice
|
|
252
|
+
const pool3 = mockPool([{ rows: [] }]);
|
|
253
|
+
await P.listFeaturedProjects({ pool: pool3 });
|
|
254
|
+
const sel = (pool3.calls[0].text.match(/SELECT([\s\S]*?)FROM/i) || [, ''])[1];
|
|
255
|
+
assert.match(sel, /\btemplate\b/, 'the featured projection carries the template');
|
|
256
|
+
});
|
|
257
|
+
|
|
229
258
|
await ta('listFeaturedProjects returns a PUBLIC projection (featured+active only, no owner/status)', async () => {
|
|
230
259
|
const pool = mockPool([{ rows: [{ origin: 'https://p', name: 'Proj', tagline: 't', art_url: null, created_at: '2026-08-01T00:00:00Z' }] }]);
|
|
231
260
|
const out = await P.listFeaturedProjects({ pool });
|
|
@@ -465,7 +494,10 @@ await ta('upsertActivity upserts per (client_id, github_id) with the talent snap
|
|
|
465
494
|
const c = pool.calls[0];
|
|
466
495
|
assert.match(c.text, /INSERT INTO platform_identity_builder_project_activity/);
|
|
467
496
|
assert.match(c.text, /ON CONFLICT \(client_id, github_id\) DO UPDATE/);
|
|
468
|
-
|
|
497
|
+
// recent_ships (task 1002803) rides the same row as a jsonb STRING param —
|
|
498
|
+
// an empty list when the sender reported none, never NULL. Its own cap,
|
|
499
|
+
// shape and link bound are covered by tests/hub_recent_ships.mjs.
|
|
500
|
+
assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['engineer'], '[]']);
|
|
469
501
|
});
|
|
470
502
|
|
|
471
503
|
await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-ladder rank → null, disciplines validated + capped', async () => {
|
|
@@ -568,7 +600,9 @@ await ta('getRollupForBuilder aggregates un-fanned + names only publicly-catalog
|
|
|
568
600
|
assert.deepEqual(out.totals, { projects: 2, credits: 150, works_shipped: 7, karma: 4 });
|
|
569
601
|
assert.deepEqual(out.disciplines, ['engineer', 'ui']);
|
|
570
602
|
assert.equal(out.projects.length, 1, 'only the public catalog row is named');
|
|
571
|
-
|
|
603
|
+
// recent_ships rides the project row it belongs to (task 1002803), so a work
|
|
604
|
+
// title can only surface where the project name already may.
|
|
605
|
+
assert.deepEqual(out.projects[0], { name: 'Public Proj', origin: 'https://pub.example', rank_held: 'metic', recent_ships: [] });
|
|
572
606
|
assert.equal(out.more_projects, 1, 'the non-public project is counted, never named');
|
|
573
607
|
// The whitelist holds structurally: client_id never appears in the shape.
|
|
574
608
|
assert.ok(!JSON.stringify(out).includes('client_id'), 'client_id must not cross the port');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null}]}
|
|
1
|
+
{"projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","template":"bead-wash","created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"template":null,"created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"template":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null}]}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.409",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.409",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.409",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
/* flat, not a gradient to white: one ground from the top bar to the footer.
|
|
70
70
|
A fade-out at the footer would reintroduce the fog. */
|
|
71
71
|
--sky-bg:var(--bg);
|
|
72
|
-
--star-display:none;
|
|
73
72
|
--chrome-ink:var(--ink);
|
|
74
73
|
--chrome-ink-dim:var(--ink-soft);
|
|
75
74
|
--chrome-accent:var(--accent-deep);
|
|
@@ -109,7 +108,6 @@
|
|
|
109
108
|
--on-ok:#0b2e18;
|
|
110
109
|
--page-bg:var(--bg);
|
|
111
110
|
--sky-bg:var(--bg);
|
|
112
|
-
--star-display:block;
|
|
113
111
|
--chrome-ink:var(--ink);
|
|
114
112
|
--chrome-ink-dim:var(--ink-soft);
|
|
115
113
|
--chrome-accent:var(--accent-deep);
|
|
@@ -132,6 +130,7 @@
|
|
|
132
130
|
--label-space:.1em;
|
|
133
131
|
/* the view masthead's object (task 1003332): dark drops the contact shadow */
|
|
134
132
|
--mast-shadow:none;
|
|
133
|
+
--orb-shadow:opacity(1);
|
|
135
134
|
color-scheme:dark;
|
|
136
135
|
}
|
|
137
136
|
@media (prefers-color-scheme: dark){
|
|
@@ -146,7 +145,6 @@
|
|
|
146
145
|
--on-ok:#0b2e18;
|
|
147
146
|
--page-bg:var(--bg);
|
|
148
147
|
--sky-bg:var(--bg);
|
|
149
|
-
--star-display:block;
|
|
150
148
|
--chrome-ink:var(--ink);
|
|
151
149
|
--chrome-ink-dim:var(--ink-soft);
|
|
152
150
|
--chrome-accent:var(--accent-deep);
|
|
@@ -169,6 +167,7 @@
|
|
|
169
167
|
--label-space:.1em;
|
|
170
168
|
/* the view masthead's object (task 1003332): dark drops the contact shadow */
|
|
171
169
|
--mast-shadow:none;
|
|
170
|
+
--orb-shadow:opacity(1);
|
|
172
171
|
color-scheme:dark;
|
|
173
172
|
}
|
|
174
173
|
}
|
|
@@ -259,7 +258,16 @@
|
|
|
259
258
|
Root-absolute path: a url() in a custom property resolves against the
|
|
260
259
|
DOCUMENT. Light gives the object the world's one contact shadow. */
|
|
261
260
|
--plate-set-cut:url(/assets/world/r2-chrome-blobs-set-cut.webp);
|
|
261
|
+
/* the sky's planets take the world's other two whole objects (task 1003319):
|
|
262
|
+
the round blob and the bongo pair, alpha cutouts in both modes for the
|
|
263
|
+
masthead's reason. */
|
|
264
|
+
--plate-bead-cut:url(/assets/world/r2-chrome-blob-one-cut.webp);
|
|
265
|
+
--plate-bongos-cut:url(/assets/world/r2-bongos-chrome-a-cut.webp);
|
|
262
266
|
--mast-shadow:drop-shadow(0 26px 30px color-mix(in srgb,var(--ink) 20%,transparent));
|
|
267
|
+
/* the planet's contact shadow, scaled to a 60-130px object. Dark's twin is
|
|
268
|
+
a NO-OP filter (opacity(1), not none) so the search spotlight's
|
|
269
|
+
drop-shadow can compose after it in one filter list. */
|
|
270
|
+
--orb-shadow:drop-shadow(0 12px 14px color-mix(in srgb,var(--ink) 22%,transparent));
|
|
263
271
|
/* the footer's measured height per width — the sky is 100vh minus the bar and
|
|
264
272
|
this, so the map fits the viewport with no scrollbar (T2's rule); the
|
|
265
273
|
toolbar row that used to be in that sum is gone (task 1003300) */
|
|
@@ -287,12 +295,11 @@
|
|
|
287
295
|
html{scroll-behavior:smooth;}
|
|
288
296
|
body{font-family:var(--font-body);color:var(--ink);background:var(--page-bg);min-height:100vh;-webkit-font-smoothing:antialiased;overflow-x:hidden;}
|
|
289
297
|
|
|
290
|
-
/* ── the sky:
|
|
291
|
-
|
|
298
|
+
/* ── the sky: the page's fixed ground layer, the page ground in both modes.
|
|
299
|
+
Nothing is drawn on it: the twinkling stars left with task 1003319 (the world
|
|
300
|
+
draws no atmosphere, and a 3.2s loop read as a pulse). Fixed layer, never
|
|
292
301
|
fixed-attachment on body. ── */
|
|
293
302
|
.sky{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;background:var(--sky-bg);}
|
|
294
|
-
.star{position:absolute;display:var(--star-display);border-radius:50%;background:#fff;box-shadow:0 0 3px rgba(255,255,255,.5);animation:twinkle 3.2s ease-in-out infinite;}
|
|
295
|
-
@keyframes twinkle{0%,100%{opacity:.22}50%{opacity:.95}}
|
|
296
303
|
|
|
297
304
|
/* ── header: mode-aware chrome (ink on sunrise, white + glow in space) ── */
|
|
298
305
|
.top{position:relative;z-index:2;display:flex;align-items:center;justify-content:space-between;padding:20px clamp(18px,4vw,40px);}
|
|
@@ -721,55 +728,88 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
721
728
|
.qWrap.is-open .qCount{display:none;}
|
|
722
729
|
}
|
|
723
730
|
|
|
724
|
-
/* ── the
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
731
|
+
/* ── THE FIELD: the void (task 1003319; the owner's ask at the hub pick,
|
|
732
|
+
docs/design/projects-hub-direction-v2.md §7, the round in §11). Nothing is
|
|
733
|
+
drawn on the ground — no grid, no stars: DESIGN.md's world draws no
|
|
734
|
+
atmosphere, and its depth comes from the rendered objects themselves. The
|
|
735
|
+
ground is the page ground in both modes (true black / paper), closed by the
|
|
736
|
+
bar's hairline above and the footer's below. Where things sit is still the
|
|
737
|
+
slot layout; how the sky moves is task 1002762's camera. Component-scoped
|
|
738
|
+
custom properties only — a page-local or component `:root` would outrank
|
|
739
|
+
the branding pack spliced after this file. ── */
|
|
729
740
|
.fieldWrap{padding:0;max-width:none;}
|
|
730
|
-
.field{position:relative;height:calc(100vh - var(--top-h) - var(--foot-h));min-height:340px;overflow:hidden;
|
|
731
|
-
--grid-ink:color-mix(in srgb,var(--rule) 92%,transparent);}
|
|
741
|
+
.field{position:relative;height:calc(100vh - var(--top-h) - var(--foot-h));min-height:340px;overflow:hidden;}
|
|
732
742
|
.field .ground{position:absolute;inset:0;background:var(--sky-bg);}
|
|
733
|
-
/* the gravity sheet, flattened: two gradients under a radial mask */
|
|
734
|
-
.field .ground::after{content:"";position:absolute;inset:-10%;
|
|
735
|
-
background-image:linear-gradient(var(--grid-ink) 1px,transparent 1px),
|
|
736
|
-
linear-gradient(90deg,var(--grid-ink) 1px,transparent 1px);
|
|
737
|
-
background-size:74px 74px;
|
|
738
|
-
-webkit-mask-image:radial-gradient(ellipse 62% 58% at 50% 50%,#000 12%,transparent 78%);
|
|
739
|
-
mask-image:radial-gradient(ellipse 62% 58% at 50% 50%,#000 12%,transparent 78%);}
|
|
740
|
-
.field .stars{position:absolute;inset:0;display:var(--star-display);}
|
|
741
|
-
.field .stars i{position:absolute;width:2px;height:2px;border-radius:50%;background:#fff;opacity:.55;}
|
|
742
743
|
|
|
743
|
-
/* a project rendered as
|
|
744
|
-
|
|
744
|
+
/* ── THE PLANET: a project rendered as one of the world's three whole chrome
|
|
745
|
+
objects in the project's accent (task 1003319; DESIGN.md "Project orbs" and
|
|
746
|
+
"Planet templates"). The orb's box is --d wide (the physics' diameter) by
|
|
747
|
+
the object's own aspect; the object is the disc's ::before — a colour layer
|
|
748
|
+
over the plate, both clipped by the plate's alpha as a MASK, so the accent
|
|
749
|
+
never paints the box. SHAPE (data-shape) is a plate + one rectangle on the
|
|
750
|
+
landing's crop maths; STYLE (data-style) is how the accent lands: tint = a
|
|
751
|
+
`color` blend (the accent's hue and chroma, the chrome's light), wash =
|
|
752
|
+
`overlay` (the chrome lit by the accent, its iridescence kept), chrome =
|
|
753
|
+
the plate as rendered. Colour comes from tokens, never a literal, so every
|
|
754
|
+
accent follows a re-theme. The relationship ring is a circle BEHIND the
|
|
755
|
+
object (the rail dot's grammar), never a box-shadow: a mask clips a shadow
|
|
756
|
+
away, and the world has no shadow vocabulary for UI. ── */
|
|
745
757
|
.worldOrb{position:absolute;transform:translate(-50%,-50%);display:flex;flex-direction:column;
|
|
746
|
-
align-items:center;gap:
|
|
747
|
-
.worldOrb .disc{
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
.worldOrb .disc::
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
.
|
|
765
|
-
|
|
766
|
-
.
|
|
767
|
-
|
|
768
|
-
.worldOrb[data-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
758
|
+
align-items:center;gap:10px;cursor:pointer;background:none;border:none;padding:0;text-decoration:none;}
|
|
759
|
+
.worldOrb .disc{--dd:calc(var(--d,96px) * var(--orb-scale,1));width:var(--dd);aspect-ratio:var(--tpl-ar,1);position:relative;isolation:isolate;
|
|
760
|
+
filter:var(--orb-shadow);transition:translate .4s cubic-bezier(.16,1,.3,1);}
|
|
761
|
+
/* the landing's hover: the object rises, 400ms on the world's exponential ease-out */
|
|
762
|
+
.worldOrb:hover .disc{translate:0 -6px;}
|
|
763
|
+
.worldOrb .disc::before{content:"";position:absolute;inset:0;
|
|
764
|
+
background-image:linear-gradient(var(--tpl-ink),var(--tpl-ink)),var(--tpl-src);
|
|
765
|
+
background-repeat:no-repeat;
|
|
766
|
+
background-size:100% 100%,calc(var(--dd) * var(--tpl-zoom)) auto;
|
|
767
|
+
background-position:0 0,calc(var(--dd) * var(--tpl-px)) calc(var(--dd) * var(--tpl-py));
|
|
768
|
+
background-blend-mode:var(--tpl-blend),normal;
|
|
769
|
+
-webkit-mask-image:var(--tpl-src);mask-image:var(--tpl-src);
|
|
770
|
+
-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
|
|
771
|
+
-webkit-mask-size:calc(var(--dd) * var(--tpl-zoom)) auto;mask-size:calc(var(--dd) * var(--tpl-zoom)) auto;
|
|
772
|
+
-webkit-mask-position:calc(var(--dd) * var(--tpl-px)) calc(var(--dd) * var(--tpl-py));
|
|
773
|
+
mask-position:calc(var(--dd) * var(--tpl-px)) calc(var(--dd) * var(--tpl-py));}
|
|
774
|
+
/* the shapes — the same rectangles the landing's .o1/.o2/.o3 declare
|
|
775
|
+
(tests/projects_hub.mjs pins the numbers equal):
|
|
776
|
+
blob r2-chrome-blob-one u[.14,.865] v[.172,.808] the round blob, whole
|
|
777
|
+
pair r2-bongos-chrome-a the whole plate the chrome pair
|
|
778
|
+
bead r2-chrome-blobs-set u[.810,.955] v[.556,.840] the set's small bead */
|
|
779
|
+
.worldOrb[data-shape="blob"]{--tpl-src:var(--plate-bead-cut);--tpl-ar:1.13994;--tpl-zoom:1.37931;--tpl-px:-.19310;--tpl-py:-.23724;}
|
|
780
|
+
.worldOrb[data-shape="pair"]{--tpl-src:var(--plate-bongos-cut);--tpl-ar:1;--tpl-zoom:1;--tpl-px:0;--tpl-py:0;}
|
|
781
|
+
.worldOrb[data-shape="bead"]{--tpl-src:var(--plate-set-cut);--tpl-ar:.91476;--tpl-zoom:6.89655;--tpl-px:-5.58621;--tpl-py:-2.14014;}
|
|
782
|
+
/* the styles */
|
|
783
|
+
.worldOrb[data-style="tint"]{--tpl-ink:var(--acc);--tpl-blend:color;}
|
|
784
|
+
.worldOrb[data-style="wash"]{--tpl-ink:var(--acc);--tpl-blend:overlay;}
|
|
785
|
+
.worldOrb[data-style="chrome"]{--tpl-ink:transparent;--tpl-blend:normal;}
|
|
786
|
+
/* the relationship ring: yours takes the accent (the one live thing about the
|
|
787
|
+
orb); building there is a hairline of it */
|
|
788
|
+
.worldOrb[data-rel="owner"] .disc::after,.worldOrb[data-rel="builder"] .disc::after{content:"";position:absolute;
|
|
789
|
+
left:50%;top:50%;width:calc(100% + 18px);aspect-ratio:1;translate:-50% -50%;border-radius:50%;z-index:-1;}
|
|
790
|
+
.worldOrb[data-rel="owner"] .disc::after{border:1.5px solid var(--accent);}
|
|
791
|
+
.worldOrb[data-rel="builder"] .disc::after{border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);}
|
|
792
|
+
/* the labels, in the world's type: the name in the ledger's title voice, the
|
|
793
|
+
sub-line as a label; the halo is the world's black one, dark only */
|
|
794
|
+
.worldOrb .nm{font-family:var(--font-body);font-weight:500;font-size:15px;letter-spacing:-.015em;color:var(--ink);
|
|
795
|
+
text-shadow:var(--type-halo);white-space:nowrap;}
|
|
796
|
+
.worldOrb .sub{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);
|
|
797
|
+
text-transform:uppercase;color:var(--ink-faint);white-space:nowrap;}
|
|
798
|
+
/* the badge states a fact (yours / you build here), so it is a hairline pill
|
|
799
|
+
in the label face, not an accent fill (the One Warm Thing Rule); z-index 3
|
|
800
|
+
keeps it over the object it labels */
|
|
801
|
+
.worldOrb .badge{position:absolute;top:-14px;left:50%;transform:translateX(-50%);z-index:3;
|
|
802
|
+
font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;
|
|
803
|
+
font-weight:400;padding:3px 10px;border-radius:999px;white-space:nowrap;
|
|
804
|
+
background:var(--bg);color:var(--ink-faint);border:1px solid var(--rule);}
|
|
805
|
+
.worldOrb[data-rel="owner"] .badge{color:var(--ink);border-color:var(--ink-faint);}
|
|
772
806
|
.worldOrb .chip{transform:scale(.88);white-space:nowrap;}
|
|
807
|
+
/* narrow: the drawn object is four fifths of its box while the physics keeps
|
|
808
|
+
the whole box for spacing — at 390 the labels and the badges need the air
|
|
809
|
+
more than the object needs the size */
|
|
810
|
+
@media (max-width:900px){
|
|
811
|
+
.field{--orb-scale:.8;}
|
|
812
|
+
}
|
|
773
813
|
|
|
774
814
|
/* what the only-mine filter shows when you are connected to nothing. Not a
|
|
775
815
|
hypothetical — GET /my-projects returns { owned: [], building: [] } for the
|
|
@@ -777,16 +817,15 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
777
817
|
.fieldEmpty{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;
|
|
778
818
|
justify-content:center;gap:14px;text-align:center;padding:26px;z-index:4;}
|
|
779
819
|
.field.isEmpty .fieldEmpty{display:flex;}
|
|
780
|
-
.fieldEmpty p{font-size:
|
|
781
|
-
|
|
782
|
-
.fieldEmpty .sub{font-size:13.5px;font-weight:600;color:var(--glass-ink);}
|
|
820
|
+
.fieldEmpty p{font-size:15.5px;font-weight:400;color:var(--ink);max-width:44ch;line-height:1.5;}
|
|
821
|
+
.fieldEmpty .sub{font-size:14px;font-weight:400;color:var(--ink-soft);}
|
|
783
822
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
823
|
+
/* the hint is one label line on the ground — no pill, no accent: it is
|
|
824
|
+
neither pressable nor live (task 1003319) */
|
|
825
|
+
.hintbar{position:absolute;left:50%;bottom:16px;transform:translateX(-50%);display:flex;gap:18px;
|
|
826
|
+
flex-wrap:wrap;justify-content:center;font-family:var(--label-face);font-size:var(--label-size);
|
|
827
|
+
letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);max-width:92%;}
|
|
828
|
+
.hintbar kbd{font-family:inherit;font-size:inherit;color:var(--ink-soft);}
|
|
790
829
|
|
|
791
830
|
/* ── THE TABLE IN THE WORLD (task 1003332; owner's review of the shipped chrome).
|
|
792
831
|
The table is a VIEW of the same world as the sky, not a panel beside it: a
|
|
@@ -877,16 +916,16 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
877
916
|
/* a dimmed orb is backdrop — no pointer, no focus (the JS drops it from the
|
|
878
917
|
tab order); at .25 opacity its label could not carry interaction honestly */
|
|
879
918
|
.worldOrb.dim{opacity:.25;filter:saturate(.35);pointer-events:none;}
|
|
880
|
-
/* the spotlight
|
|
881
|
-
|
|
882
|
-
box-shadow
|
|
883
|
-
|
|
884
|
-
|
|
919
|
+
/* the spotlight is a drop-shadow in the accent on the disc's own filter — a
|
|
920
|
+
glow that follows the silhouette (the object is a mask, so an outline or a
|
|
921
|
+
box-shadow would be clipped away). Composed after the contact shadow, which
|
|
922
|
+
dark states as a no-op filter for exactly this. */
|
|
923
|
+
.worldOrb.match .disc{filter:var(--orb-shadow) drop-shadow(0 0 16px color-mix(in srgb,var(--acc,var(--accent)) 60%,transparent));}
|
|
885
924
|
.field .hoverCard{position:absolute;z-index:6;pointer-events:none;display:flex;flex-direction:column;gap:2px;
|
|
886
|
-
background:var(--bg-card);border:
|
|
925
|
+
background:var(--bg-card);border:1px solid var(--rule);border-radius:var(--radius-sm);
|
|
887
926
|
padding:9px 13px;max-width:240px;}
|
|
888
|
-
.field .hoverCard .hn{font-family:var(--font-
|
|
889
|
-
.field .hoverCard .ht{font-size:
|
|
927
|
+
.field .hoverCard .hn{font-family:var(--font-body);font-weight:500;font-size:14px;letter-spacing:-.015em;color:var(--ink);white-space:nowrap;}
|
|
928
|
+
.field .hoverCard .ht{font-size:12.5px;color:var(--ink-soft);font-weight:400;}
|
|
890
929
|
|
|
891
930
|
/* ── the project card ──
|
|
892
931
|
An overlay INSIDE the map, not a destination: opening a project must not cost
|
|
@@ -327,23 +327,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
327
327
|
</style>
|
|
328
328
|
</head>
|
|
329
329
|
<body>
|
|
330
|
-
<div class="sky" aria-hidden="true">
|
|
331
|
-
<span class="star" style="top:2%;left:12%;width:2px;height:2px;"></span>
|
|
332
|
-
<span class="star" style="top:5%;left:28%;width:1.5px;height:1.5px;animation-delay:.7s;"></span>
|
|
333
|
-
<span class="star" style="top:3%;left:47%;width:2px;height:2px;animation-delay:1.4s;"></span>
|
|
334
|
-
<span class="star" style="top:8%;left:63%;width:1.5px;height:1.5px;animation-delay:.3s;"></span>
|
|
335
|
-
<span class="star" style="top:4%;left:78%;width:2.5px;height:2.5px;animation-delay:2s;"></span>
|
|
336
|
-
<span class="star" style="top:10%;left:88%;width:1.5px;height:1.5px;animation-delay:1s;"></span>
|
|
337
|
-
<span class="star" style="top:14%;left:20%;width:1.5px;height:1.5px;animation-delay:1.8s;"></span>
|
|
338
|
-
<span class="star" style="top:22%;left:55%;width:2px;height:2px;animation-delay:.5s;"></span>
|
|
339
|
-
<span class="star" style="top:34%;left:36%;width:1.5px;height:1.5px;animation-delay:2.4s;"></span>
|
|
340
|
-
<span class="star" style="top:30%;left:78%;width:1.5px;height:1.5px;animation-delay:1.2s;"></span>
|
|
341
|
-
<span class="star" style="top:48%;left:8%;width:2px;height:2px;animation-delay:.9s;"></span>
|
|
342
|
-
<span class="star" style="top:55%;left:90%;width:1.5px;height:1.5px;animation-delay:1.6s;"></span>
|
|
343
|
-
<span class="star" style="top:68%;left:24%;width:1.5px;height:1.5px;animation-delay:2.1s;"></span>
|
|
344
|
-
<span class="star" style="top:76%;left:64%;width:2px;height:2px;animation-delay:.4s;"></span>
|
|
345
|
-
<span class="star" style="top:88%;left:42%;width:1.5px;height:1.5px;animation-delay:1.9s;"></span>
|
|
346
|
-
</div>
|
|
330
|
+
<div class="sky" aria-hidden="true"></div>
|
|
347
331
|
|
|
348
332
|
<!-- ══ THE BAR ═══════════════════════════════════════════════════════════════
|
|
349
333
|
A header of the PAGE, not of the orb field, so it never drifts, scales or
|
|
@@ -471,7 +455,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
471
455
|
<div class="fieldWrap" id="sub-sky-body">
|
|
472
456
|
<div class="field" id="orbField">
|
|
473
457
|
<div class="ground"></div>
|
|
474
|
-
<div class="stars" id="mapStars" aria-hidden="true"></div>
|
|
475
458
|
<!-- The camera (task 1002762). Pan and zoom are ONE transform on this
|
|
476
459
|
layer; orbs write only their own physics delta, so the two compose
|
|
477
460
|
for free and the ground, stars, empty state and hintbar stay put.
|
|
@@ -479,12 +462,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
479
462
|
sentinel spans, and a tag opened inside one would leave the child
|
|
480
463
|
instance an unbalanced tree. -->
|
|
481
464
|
<div class="skyCam" id="skyCam">
|
|
482
|
-
<!-- The platform's own orb
|
|
465
|
+
<!-- The platform's own orb: the bongo pair in chrome, the platform's own
|
|
466
|
+
mark (task 1003319). Static markup, not JS-built, precisely so the
|
|
483
467
|
apex-only sentinels can remove it: a child instance has no "this
|
|
484
468
|
platform" to point at, and stripApexOnly() is a string transform that
|
|
485
469
|
cannot reach anything a script renders later. -->
|
|
486
470
|
<!--apex-only:start-->
|
|
487
|
-
<a class="worldOrb" id="selfOrb" href="/" style="--acc:var(--accent);--d:
|
|
471
|
+
<a class="worldOrb" id="selfOrb" href="/" data-shape="pair" data-style="chrome" data-template="pair-chrome" style="--acc:var(--accent);--d:132px;left:50%;top:44%">
|
|
488
472
|
<span class="disc"></span>
|
|
489
473
|
<span class="nm" data-brand-product-inline>Cloud Bongos</span>
|
|
490
474
|
<span class="sub">this platform</span>
|
|
@@ -1282,9 +1266,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1282
1266
|
The orbs are placed on fixed slots: where things sit is this pass; how the
|
|
1283
1267
|
sky moves is goal 1000031. ══ */
|
|
1284
1268
|
|
|
1269
|
+
/* the third number is the orb's width (--d, the physics' diameter). A
|
|
1270
|
+
cutout's silhouette reads a quarter smaller than a filled disc of the
|
|
1271
|
+
same box, so the slots grew by that with the planets (task 1003319). */
|
|
1285
1272
|
var ORB_SLOTS = [
|
|
1286
|
-
[22, 30,
|
|
1287
|
-
[13, 58,
|
|
1273
|
+
[22, 30, 92], [78, 66, 75], [33, 74, 70], [70, 26, 80],
|
|
1274
|
+
[13, 58, 65], [88, 42, 62], [50, 80, 68], [62, 48, 72]
|
|
1288
1275
|
];
|
|
1289
1276
|
/* ONE accent palette for every orb on the page — the map's spheres and the
|
|
1290
1277
|
manage cards' — so a project reads the same colour in both views.
|
|
@@ -1294,6 +1281,16 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1294
1281
|
deriving it from contract tokens is what makes it survive a re-brand. */
|
|
1295
1282
|
var ORB_ACCENTS = ['var(--sea)', 'var(--terracotta)', 'var(--gold-soft)', 'var(--sun-lit)', 'var(--accent)'];
|
|
1296
1283
|
var ORB_GLOWS = ORB_ACCENTS.map(function (t) { return 'color-mix(in srgb,' + t + ' 45%,transparent)'; });
|
|
1284
|
+
/* THE PLANET TEMPLATES (task 1003319). A project's orb is one of the world's
|
|
1285
|
+
three whole chrome objects (blob / pair / bead, on the landing's own crop
|
|
1286
|
+
maths) in the project's accent, styled tint (the accent replaces the
|
|
1287
|
+
chrome's colour), wash (the accent lights it) or chrome (as rendered). A
|
|
1288
|
+
curated `template` on the catalog row wins; otherwise the same canonical
|
|
1289
|
+
walk that assigns the accent assigns the template, and the order here IS
|
|
1290
|
+
the walk: consecutive positions change both the shape and the style, and
|
|
1291
|
+
nine templates against five accents repeat only every forty-five projects.
|
|
1292
|
+
Mirrors platform-identity's PROJECT_TEMPLATES (the DB CHECK). */
|
|
1293
|
+
var ORB_TEMPLATES = ['blob-tint', 'pair-wash', 'bead-chrome', 'blob-wash', 'pair-chrome', 'bead-tint', 'blob-chrome', 'pair-tint', 'bead-wash'];
|
|
1297
1294
|
var catalog = [];
|
|
1298
1295
|
var mineOrigins = {}; /* normalized host → 'owner' | 'builder' */
|
|
1299
1296
|
var ownedInstances = []; /* the viewer's OWN provisioning rows (never torn_down) */
|
|
@@ -1329,16 +1326,26 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1329
1326
|
function assignAccents() {
|
|
1330
1327
|
catalog.slice()
|
|
1331
1328
|
.sort(function (a, b) { return String(a.origin).localeCompare(String(b.origin)); })
|
|
1332
|
-
.forEach(function (p, i) {
|
|
1329
|
+
.forEach(function (p, i) {
|
|
1330
|
+
p._acc = ORB_ACCENTS[i % ORB_ACCENTS.length];
|
|
1331
|
+
p._tpl = ORB_TEMPLATES[i % ORB_TEMPLATES.length];
|
|
1332
|
+
});
|
|
1333
1333
|
}
|
|
1334
1334
|
function accentOf(p) { return p._acc || ORB_ACCENTS[0]; }
|
|
1335
|
+
/* a curated template wins over the walk; an unknown name is not a choice */
|
|
1336
|
+
function templateOf(p) {
|
|
1337
|
+
return ORB_TEMPLATES.indexOf(p.template) !== -1 ? p.template : (p._tpl || ORB_TEMPLATES[0]);
|
|
1338
|
+
}
|
|
1335
1339
|
/* the manage cards' accent rule — one project, one colour, in the sky AND
|
|
1336
1340
|
the list. Shared with orbFor(), which used to carry this hash inline. */
|
|
1337
|
-
function
|
|
1341
|
+
function slugHash(slug) {
|
|
1338
1342
|
var h = 0, s = String(slug || '');
|
|
1339
1343
|
for (var i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
|
|
1340
|
-
return h
|
|
1344
|
+
return h;
|
|
1341
1345
|
}
|
|
1346
|
+
function slugAccent(slug) { return slugHash(slug) % ORB_ACCENTS.length; }
|
|
1347
|
+
/* an owned-only entry has no catalog row to carry a template: its slug walks */
|
|
1348
|
+
function slugTemplate(slug) { return ORB_TEMPLATES[slugHash(slug) % ORB_TEMPLATES.length]; }
|
|
1342
1349
|
function relOf(p) {
|
|
1343
1350
|
/* an instance the viewer provisioned is a server-resolved authority too —
|
|
1344
1351
|
own-scoped by owner_builder_id, no client inference (direction §2
|
|
@@ -1431,6 +1438,11 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1431
1438
|
a.setAttribute('data-generated', '');
|
|
1432
1439
|
var rel = relOf(p);
|
|
1433
1440
|
a.setAttribute('data-rel', rel);
|
|
1441
|
+
/* the planet: shape and style as data attributes, cosmos.css does the rest */
|
|
1442
|
+
var tpl = templateOf(p), tplParts = tpl.split('-');
|
|
1443
|
+
a.setAttribute('data-template', tpl);
|
|
1444
|
+
a.setAttribute('data-shape', tplParts[0]);
|
|
1445
|
+
a.setAttribute('data-style', tplParts[1]);
|
|
1434
1446
|
var inst = p._inst;
|
|
1435
1447
|
var href = safeHttpUrl(p.origin);
|
|
1436
1448
|
if (inst) {
|
|
@@ -1533,7 +1545,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1533
1545
|
name: inst.slug, tagline: inst.domain || '',
|
|
1534
1546
|
origin: inst.domain ? 'https://' + inst.domain : '',
|
|
1535
1547
|
builders: null, tags: null, type: inst.type || null,
|
|
1536
|
-
_inst: inst, _acc: ORB_ACCENTS[slugAccent(inst.slug)]
|
|
1548
|
+
_inst: inst, _acc: ORB_ACCENTS[slugAccent(inst.slug)], _tpl: slugTemplate(inst.slug)
|
|
1537
1549
|
});
|
|
1538
1550
|
});
|
|
1539
1551
|
var list = browse.filter(function (p) {
|
|
@@ -2213,7 +2225,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2213
2225
|
that must not interpenetrate — sits above it; collisions measure from
|
|
2214
2226
|
the disc's own center */
|
|
2215
2227
|
var disc = el.querySelector('.disc');
|
|
2216
|
-
|
|
2228
|
+
/* a planet's box is not square (task 1003319): its own height, not r */
|
|
2229
|
+
var discOff = disc ? (disc.offsetTop + disc.offsetHeight / 2) - el.offsetHeight / 2 : 0;
|
|
2217
2230
|
/* reduced motion IS the mock: no carry-over, every orb sits exactly on
|
|
2218
2231
|
its slot (C9) — carrying frozen positions across a re-sort would
|
|
2219
2232
|
leave zero-motion orbs parked off their own anchors forever */
|
|
@@ -2491,19 +2504,6 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2491
2504
|
} else { stopSky(); hideHover(); }
|
|
2492
2505
|
}
|
|
2493
2506
|
|
|
2494
|
-
/* the starfield only paints where --star-display is block (dark mode) */
|
|
2495
|
-
(function stars() {
|
|
2496
|
-
var frag = document.createDocumentFragment();
|
|
2497
|
-
for (var i = 0; i < 90; i++) {
|
|
2498
|
-
var s = document.createElement('i');
|
|
2499
|
-
s.style.left = ((i * 37 + 11) % 100) + '%';
|
|
2500
|
-
s.style.top = ((i * 61 + 7) % 100) + '%';
|
|
2501
|
-
s.style.opacity = (0.25 + ((i * 13) % 50) / 100).toFixed(2);
|
|
2502
|
-
frag.appendChild(s);
|
|
2503
|
-
}
|
|
2504
|
-
$('mapStars').appendChild(frag);
|
|
2505
|
-
})();
|
|
2506
|
-
|
|
2507
2507
|
/* ══ THE CREATE WIZARD (task 1002763) — absorbed whole from /create, which
|
|
2508
2508
|
now 302s here. An inner scope so its ~40 helpers stay out of the hub's
|
|
2509
2509
|
namespace; it shares the hub's $, esc, API and `me`, so there is one
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"ignoreControls": [".skip", ".vh"],
|
|
9
9
|
"states": {
|
|
10
10
|
"out-map": { "auth": false, "expect": { "expanded": false } },
|
|
11
|
-
"in-map": { "auth": true, "expect": { "expanded": false } },
|
|
11
|
+
"in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 4]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-chrome"]] } },
|
|
12
12
|
"in-map-projects": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProjects" } },
|
|
13
13
|
"in-map-profile": { "auth": true, "actions": [["click", "#who"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProfile" } },
|
|
14
14
|
"in-map-kbd": { "auth": true, "actions": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 350], ["press", "ArrowDown"], ["wait", 120]], "expect": { "expanded": true, "activeText": "Table" } },
|