@bongos/core 1.19.413 → 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.
Files changed (63) hide show
  1. package/.bongos-core.json +190 -40
  2. package/docs/adr/0204-the-apex-is-dark-only.md +36 -0
  3. package/docs/adr/README.md +1 -0
  4. package/docs/copy-inventory.md +310 -312
  5. package/docs/copy-registry.json +394 -412
  6. package/docs/design/landing-page-direction-v2.md +2 -0
  7. package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
  8. package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +13 -0
  9. package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
  10. package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +13 -0
  11. package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
  12. package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +13 -0
  13. package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
  14. package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +13 -0
  15. package/docs/design/mocks/hub/planets.html +127 -3
  16. package/docs/design/projects-hub-direction-v2.md +14 -0
  17. package/docs/module-api-changelog.md +4 -0
  18. package/docs/recipes/ui-look-before-you-ship.md +4 -1
  19. package/modules/platform-identity/migrations/platform_identity_013_project_template_materials.sql +31 -0
  20. package/modules/platform-identity/platform-identity.js +18 -5
  21. package/modules/platform-identity/routes/projects.js +3 -2
  22. package/modules/platform-identity/tests/platform-identity.mjs +23 -12
  23. package/modules/ui-design/kit/fixtures/featured.json +1 -1
  24. package/modules/ui-design/kit/lib.js +3 -1
  25. package/modules/ui-design/kit/render.js +5 -4
  26. package/package-lock.json +2 -2
  27. package/package.json +1 -1
  28. package/public-landing/assets/cosmos.css +50 -18
  29. package/public-landing/assets/world/r3-frost-bead-cut.webp +0 -0
  30. package/public-landing/assets/world/r3-frost-bead-cut.webp.json +15 -0
  31. package/public-landing/assets/world/r3-frost-blob-cut.webp +0 -0
  32. package/public-landing/assets/world/r3-frost-blob-cut.webp.json +15 -0
  33. package/public-landing/assets/world/r3-frost-pair-cut.webp +0 -0
  34. package/public-landing/assets/world/r3-frost-pair-cut.webp.json +15 -0
  35. package/public-landing/assets/world/r3-gold-bead-cut.webp +0 -0
  36. package/public-landing/assets/world/r3-gold-bead-cut.webp.json +15 -0
  37. package/public-landing/assets/world/r3-gold-blob-cut.webp +0 -0
  38. package/public-landing/assets/world/r3-gold-blob-cut.webp.json +15 -0
  39. package/public-landing/assets/world/r3-gold-pair-cut.webp +0 -0
  40. package/public-landing/assets/world/r3-gold-pair-cut.webp.json +15 -0
  41. package/public-landing/assets/world/r3-obsidian-bead-cut.webp +0 -0
  42. package/public-landing/assets/world/r3-obsidian-bead-cut.webp.json +15 -0
  43. package/public-landing/assets/world/r3-obsidian-blob-cut.webp +0 -0
  44. package/public-landing/assets/world/r3-obsidian-blob-cut.webp.json +15 -0
  45. package/public-landing/assets/world/r3-obsidian-pair-cut.webp +0 -0
  46. package/public-landing/assets/world/r3-obsidian-pair-cut.webp.json +15 -0
  47. package/public-landing/assets/world/r3-space-nebula.jpg +0 -0
  48. package/public-landing/assets/world/r3-space-nebula.jpg.json +13 -0
  49. package/public-landing/contact.html +2 -6
  50. package/public-landing/index.html +5 -44
  51. package/public-landing/index.states.json +3 -2
  52. package/public-landing/privacy.html +2 -6
  53. package/public-landing/projects.html +27 -55
  54. package/public-landing/projects.probes.json +0 -3
  55. package/public-landing/projects.states.json +4 -3
  56. package/public-landing/terms.html +2 -6
  57. package/scripts/gds/worktree.js +11 -0
  58. package/src/module-api.js +1 -1
  59. package/tests/landing_legal_pages.mjs +10 -0
  60. package/tests/landing_page.mjs +16 -19
  61. package/tests/projects_hub.mjs +69 -19
  62. package/tests/projects_hub_pre_uat.mjs +4 -3
  63. package/tests/ui_design_kit.mjs +10 -0
@@ -16,8 +16,9 @@
16
16
  · dark mode — a true-black ground with the card lifted just off it, white
17
17
  ink, and the accent pair flipping direction so the link tone goes BRIGHTER
18
18
 
19
- Mode resolution: prefers-color-scheme by default; an explicit choice
20
- sets data-mode="light|dark" on <html> (persisted by the page script).
19
+ Mode resolution: the apex pages pin data-mode="dark" on <html> (task 1003336,
20
+ ADR 0204) — dark is their only mode. The light tier below stays until its
21
+ own removal (the light-pair contrast tests still measure it).
21
22
 
22
23
  TOKEN TIERS:
23
24
  · CONTRACT tier — names the branding pack overrides (themeMarkup()
@@ -263,6 +264,22 @@
263
264
  masthead's reason. */
264
265
  --plate-bead-cut:url(/assets/world/r2-chrome-blob-one-cut.webp);
265
266
  --plate-bongos-cut:url(/assets/world/r2-bongos-chrome-a-cut.webp);
267
+ /* the materials round (task 1003337): each material is its own whole-object plate
268
+ per shape — the alpha cutout, masked and blended exactly like the chrome ones.
269
+ Sidecars beside each file carry the prompt and the take. */
270
+ --plate-frost-blob-cut:url(/assets/world/r3-frost-blob-cut.webp);
271
+ --plate-frost-pair-cut:url(/assets/world/r3-frost-pair-cut.webp);
272
+ --plate-frost-bead-cut:url(/assets/world/r3-frost-bead-cut.webp);
273
+ --plate-gold-blob-cut:url(/assets/world/r3-gold-blob-cut.webp);
274
+ --plate-gold-pair-cut:url(/assets/world/r3-gold-pair-cut.webp);
275
+ --plate-gold-bead-cut:url(/assets/world/r3-gold-bead-cut.webp);
276
+ --plate-obsidian-blob-cut:url(/assets/world/r3-obsidian-blob-cut.webp);
277
+ --plate-obsidian-pair-cut:url(/assets/world/r3-obsidian-pair-cut.webp);
278
+ --plate-obsidian-bead-cut:url(/assets/world/r3-obsidian-bead-cut.webp);
279
+ /* the field's ground: a rendered deep-space plate on the dark-only apex (task 1003337,
280
+ the owner's ask on the planets review). One token, so the pick is one line; the
281
+ candidates sit beside it in .impeccable/mocks/world-assets/. */
282
+ --field-plate:url(/assets/world/r3-space-nebula.jpg);
266
283
  --mast-shadow:drop-shadow(0 26px 30px color-mix(in srgb,var(--ink) 20%,transparent));
267
284
  /* the planet's contact shadow, scaled to a 60-130px object. Dark's twin is
268
285
  a NO-OP filter (opacity(1), not none) so the search spotlight's
@@ -585,19 +602,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
585
602
  .who{gap:9px;}
586
603
  .who img{width:26px;height:26px;border-radius:var(--radius-pill);object-fit:cover;flex:none;
587
604
  border:1px solid var(--rule);}
588
- .modeBtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;
589
- cursor:pointer;background:none;border:0;border-radius:var(--radius-pill);color:var(--ink-soft);
590
- transition:color .2s cubic-bezier(.2,.7,.3,1);}
591
- .modeBtn:hover{color:var(--ink);}
592
- .modeBtn svg{width:20px;height:20px;}
593
- /* the glyph shows the mode you would GET: the moon in light, the sun in dark */
594
- .modeBtn .sun{display:none;}
595
- :root[data-mode="dark"] .modeBtn .sun{display:block;}
596
- :root[data-mode="dark"] .modeBtn .moon{display:none;}
597
- @media (prefers-color-scheme:dark){
598
- :root:not([data-mode="light"]) .modeBtn .sun{display:block;}
599
- :root:not([data-mode="light"]) .modeBtn .moon{display:none;}
600
- }
601
605
  /* the one filled shape on a bar with no pills: the landing's sign-in door */
602
606
  .signinDoor{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 26px;
603
607
  border-radius:var(--radius-pill);background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);
@@ -685,8 +689,6 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
685
689
  .qWrap{flex:1 1 90px;max-width:480px;gap:10px;}
686
690
  .trig{font-size:13.5px;}
687
691
  .who>span{display:none;}
688
- .modeBtn{width:40px;height:40px;}
689
- .modeBtn svg{width:18px;height:18px;}
690
692
  .signinDoor{padding:0 16px;font-size:13.5px;}
691
693
  .bandIn{padding:0 12px 16px;}
692
694
  .bandRow{grid-template-columns:1fr;gap:0;}
@@ -739,7 +741,14 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
739
741
  the branding pack spliced after this file. ── */
740
742
  .fieldWrap{padding:0;max-width:none;}
741
743
  .field{position:relative;height:calc(100vh - var(--top-h) - var(--foot-h));min-height:340px;overflow:hidden;}
742
- .field .ground{position:absolute;inset:0;background:var(--sky-bg);}
744
+ /* the ground is a rendered plate (task 1003337): the page ground under a deep-space
745
+ render at cover, drifting a few percent over 90s (transform only, dead under
746
+ reduced motion); the inset is the room the drift needs. */
747
+ .field .ground{position:absolute;inset:-4%;background:var(--sky-bg) var(--field-plate) center/cover no-repeat;}
748
+ @media (prefers-reduced-motion:no-preference){
749
+ .field .ground{animation:groundDrift 90s ease-in-out infinite alternate;}
750
+ @keyframes groundDrift{from{transform:translate3d(-1.2%,-.8%,0) scale(1.02);}to{transform:translate3d(1.2%,.8%,0) scale(1.06);}}
751
+ }
743
752
 
744
753
  /* ── THE PLANET: a project rendered as one of the world's three whole chrome
745
754
  objects in the project's accent (task 1003319; DESIGN.md "Project orbs" and
@@ -783,6 +792,29 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
783
792
  .worldOrb[data-style="tint"]{--tpl-ink:var(--acc);--tpl-blend:color;}
784
793
  .worldOrb[data-style="wash"]{--tpl-ink:var(--acc);--tpl-blend:overlay;}
785
794
  .worldOrb[data-style="chrome"]{--tpl-ink:transparent;--tpl-blend:normal;}
795
+ .worldOrb[data-style="plain"]{--tpl-ink:transparent;--tpl-blend:normal;}
796
+ /* the materials (task 1003337): a whole object per plate, the rectangle measured from the
797
+ cutout's alpha (bbox at alpha>=16 on the 2048² plate, a 1.5% margin each side; zoom = 1/width,
798
+ px/py = -u0/-v0 × zoom — tests/projects_hub.mjs recomputes these from the boxes):
799
+ frost-blob u[0.168,0.8223] v[0.2495,0.7642]
800
+ frost-pair u[0.0591,0.9409] v[0.2764,0.7231]
801
+ frost-bead u[0.1685,0.8066] v[0.1738,0.8198]
802
+ gold-blob u[0.1274,0.8584] v[0.1035,0.9019]
803
+ gold-pair u[0.0552,0.9458] v[0.2739,0.7383]
804
+ gold-bead u[0.0542,0.9478] v[0.0542,0.9434]
805
+ obsidian-blob u[0.1885,0.8018] v[0.1177,0.8979]
806
+ obsidian-pair u[0.0249,0.9707] v[0.3096,0.7158]
807
+ obsidian-bead u[0.1416,0.854] v[0.1431,0.8535]
808
+ Two attributes outrank the one-attribute chrome crops above. */
809
+ .worldOrb[data-material="frost"][data-shape="blob"]{--tpl-src:var(--plate-frost-blob-cut);--tpl-ar:1.25629;--tpl-zoom:1.46135;--tpl-px:-.22359;--tpl-py:-.34269;}
810
+ .worldOrb[data-material="frost"][data-shape="pair"]{--tpl-src:var(--plate-frost-pair-cut);--tpl-ar:1.91273;--tpl-zoom:1.09673;--tpl-px:-.04837;--tpl-py:-.28669;}
811
+ .worldOrb[data-material="frost"][data-shape="bead"]{--tpl-src:var(--plate-frost-bead-cut);--tpl-ar:.98831;--tpl-zoom:1.49678;--tpl-px:-.22976;--tpl-py:-.23769;}
812
+ .worldOrb[data-material="gold"][data-shape="blob"]{--tpl-src:var(--plate-gold-blob-cut);--tpl-ar:.91864;--tpl-zoom:1.31406;--tpl-px:-.1477;--tpl-py:-.11629;}
813
+ .worldOrb[data-material="gold"][data-shape="pair"]{--tpl-src:var(--plate-gold-pair-cut);--tpl-ar:1.86206;--tpl-zoom:1.08625;--tpl-px:-.04367;--tpl-py:-.28123;}
814
+ .worldOrb[data-material="gold"][data-shape="bead"]{--tpl-src:var(--plate-gold-bead-cut);--tpl-ar:1.00479;--tpl-zoom:1.08272;--tpl-px:-.04244;--tpl-py:-.04244;}
815
+ .worldOrb[data-material="obsidian"][data-shape="blob"]{--tpl-src:var(--plate-obsidian-blob-cut);--tpl-ar:.794;--tpl-zoom:1.55448;--tpl-px:-.2697;--tpl-py:-.15965;}
816
+ .worldOrb[data-material="obsidian"][data-shape="pair"]{--tpl-src:var(--plate-obsidian-pair-cut);--tpl-ar:2.23705;--tpl-zoom:1.0248;--tpl-px:-.01015;--tpl-py:-.30191;}
817
+ .worldOrb[data-material="obsidian"][data-shape="bead"]{--tpl-src:var(--plate-obsidian-bead-cut);--tpl-ar:1.0027;--tpl-zoom:1.34698;--tpl-px:-.17053;--tpl-py:-.17255;}
786
818
  /* the relationship ring: yours takes the accent (the one live thing about the
787
819
  orb); building there is a hairline of it */
788
820
  .worldOrb[data-rel="owner"] .disc::after,.worldOrb[data-rel="builder"] .disc::after{content:"";position:absolute;
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "frosted glass",
10
+ "object": "the small bead",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-frost-bead: the small bead in frosted glass. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-frost-bead-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in frosted glass: --plate-frost-bead-cut, masked and blended by [data-material=\"frost\"][data-shape=\"bead\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "frosted glass",
10
+ "object": "the round blob",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-frost-blob: the round blob in frosted glass. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-frost-blob-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in frosted glass: --plate-frost-blob-cut, masked and blended by [data-material=\"frost\"][data-shape=\"blob\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "frosted glass",
10
+ "object": "the bongo pair",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-frost-pair: the bongo pair in frosted glass. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-frost-pair-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in frosted glass: --plate-frost-pair-cut, masked and blended by [data-material=\"frost\"][data-shape=\"pair\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "brushed gold",
10
+ "object": "the small bead",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-gold-bead: the small bead in brushed gold. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-gold-bead-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in brushed gold: --plate-gold-bead-cut, masked and blended by [data-material=\"gold\"][data-shape=\"bead\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "brushed gold",
10
+ "object": "the round blob",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-gold-blob: the round blob in brushed gold. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-gold-blob-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in brushed gold: --plate-gold-blob-cut, masked and blended by [data-material=\"gold\"][data-shape=\"blob\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "brushed gold",
10
+ "object": "the bongo pair",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-gold-pair: the bongo pair in brushed gold. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-gold-pair-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in brushed gold: --plate-gold-pair-cut, masked and blended by [data-material=\"gold\"][data-shape=\"pair\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "polished obsidian",
10
+ "object": "the small bead",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-obsidian-bead: the small bead in polished obsidian. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-obsidian-bead-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in polished obsidian: --plate-obsidian-bead-cut, masked and blended by [data-material=\"obsidian\"][data-shape=\"bead\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "polished obsidian",
10
+ "object": "the round blob",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-obsidian-blob: the round blob in polished obsidian. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-obsidian-blob-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in polished obsidian: --plate-obsidian-blob-cut, masked and blended by [data-material=\"obsidian\"][data-shape=\"blob\"] in cosmos.css"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "kind": "cutout",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "1:1",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "material": "polished obsidian",
10
+ "object": "the bongo pair",
11
+ "prompt": "Background removed (Higgsfield image_background_remover) from r3-obsidian-pair: the bongo pair in polished obsidian. Cut out so the planet can be masked and colour-blended in the project's accent (the hub's template grammar, task 1003319); alpha verified with ffmpeg alphaextract (min 0, max 255).",
12
+ "source": ".impeccable/mocks/world-assets/r3-obsidian-pair-cut.webp",
13
+ "encoded": "ffmpeg libwebp quality 88, alpha kept (yuva420p), 1600px (task 1003337)",
14
+ "use": "the hub's planet in polished obsidian: --plate-obsidian-pair-cut, masked and blended by [data-material=\"obsidian\"][data-shape=\"pair\"] in cosmos.css"
15
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "kind": "backdrop",
3
+ "model": "nano_banana_2 (requested as nano_banana_pro; the catalog served nano_banana_2)",
4
+ "aspect_ratio": "16:9",
5
+ "resolution": "2k",
6
+ "generated": "2026-08-29T15:37Z",
7
+ "round": 3,
8
+ "task": 1003337,
9
+ "prompt": "Deep-space photograph on a pure black ground: a faint wide nebula wash in muted violet and deep blue with a soft bronze warmth along one edge, very sparse pin-sharp stars of varied brightness and size, gentle depth, most of the frame near-black, nothing else, no planets, no ground plane, no horizon, no lens flare, no text, no watermark. Pure black dominates.",
10
+ "source": ".impeccable/mocks/world-assets/r3-space-nebula.jpg",
11
+ "encoded": "ffmpeg JPEG -q:v 4, 2752×1536 (task 1003337)",
12
+ "use": "the field's ground on /projects: --field-plate, `cover`, a 90s transform drift dead under reduced motion (the builder's recommendation; the owner may swap any candidate)"
13
+ }
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-mode="dark">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -17,11 +17,7 @@
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
18
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
19
19
  <link rel="stylesheet" href="/assets/cosmos.css">
20
- <script>
21
- /* Mode, resolved before first paint — the same contract as every landing page. */
22
- (function(){try{var m=localStorage.getItem('cb-color-mode');
23
- if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
24
- </script>
20
+ <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
25
21
  <style>
26
22
  /* The reading column only. No :root block — the pack's injected theme owns the
27
23
  tokens (landing direction C1); everything below reads them. */
@@ -1,13 +1,11 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-mode="dark">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <script>
6
- /* Mode, resolved before first paint so an explicit choice never flashes. ?mode=
7
- wins (it is how a reviewer walks both modes on one URL), then the stored
8
- choice, then the OS preference. */
9
- (function(){try{var q=new URLSearchParams(location.search).get('mode');var m=(q==='light'||q==='dark')?q:localStorage.getItem('cb-color-mode');if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
10
- </script>
5
+ <!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
6
+ <html> above — no pre-paint script, no stored choice, no OS preference, no
7
+ ?mode=. Light is the builders' hall's. The light tier of world.css stays
8
+ until its own removal. -->
11
9
  <meta name="viewport" content="width=device-width, initial-scale=1">
12
10
  <!-- ── S0 · HEAD METADATA — SERVER-STAMPED ──────────────────────────────────
13
11
  The {{…}} tokens below are replaced by landingPage() (src/platform-server.js)
@@ -168,11 +166,6 @@ main:focus{outline:none;box-shadow:none}
168
166
  .who{gap:9px}
169
167
  .who img{width:26px;height:26px;border-radius:var(--radius-pill);object-fit:cover;flex:none;border:1px solid var(--rule)}
170
168
  .who-name{max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
171
- .modeBtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;cursor:pointer;
172
- background:none;border:0;border-radius:var(--radius-pill);color:var(--ink-soft);transition:color .2s cubic-bezier(.2,.7,.3,1)}
173
- .modeBtn:hover{color:var(--ink)}
174
- .modeBtn svg{width:20px;height:20px}
175
- .sun{display:none}
176
169
  /* the sign-in door: the one filled shape on a bar with no pills — inverted ink, lowercase */
177
170
  .pill{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 22px;
178
171
  border-radius:var(--radius-pill);font-family:inherit;font-size:15px;color:var(--ink);cursor:pointer;
@@ -180,10 +173,6 @@ main:focus{outline:none;box-shadow:none}
180
173
  .signin{background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);border:0;
181
174
  text-transform:lowercase;font-weight:500;padding:0 26px}
182
175
  .signin:hover{background:var(--ink);color:var(--bg)}
183
- :root[data-mode="dark"] .sun{display:block}
184
- :root[data-mode="dark"] .moon{display:none}
185
- @media (prefers-color-scheme:dark){:root:not([data-mode="light"]) .sun{display:block}
186
- :root:not([data-mode="light"]) .moon{display:none}}
187
176
 
188
177
  /* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, a LAYER
189
178
  over the page under the bar — the page does not reflow (owner, 2026-08-28; task 1003332). Cells in a
@@ -491,8 +480,6 @@ body{
491
480
  .mark-word{display:none}
492
481
  .trig{font-size:13.5px}
493
482
  .who-name{display:none}
494
- .modeBtn{width:40px;height:40px}
495
- .modeBtn svg{width:18px;height:18px}
496
483
  .pill{min-height:42px;font-size:13.5px}
497
484
  .signin{padding:0 16px}
498
485
  .bandIn{padding:0 12px 16px}
@@ -683,17 +670,6 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
683
670
  </div>
684
671
  <div class="barRight">
685
672
  <button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
686
- <button class="modeBtn" id="mode" type="button" aria-label="Toggle light or dark">
687
- <svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
688
- stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
689
- <path d="M16.2 12.4A6.8 6.8 0 0 1 7.6 3.8a6.8 6.8 0 1 0 8.6 8.6z"/>
690
- </svg>
691
- <svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
692
- stroke-width="1.4" stroke-linecap="round">
693
- <circle cx="10" cy="10" r="3.9"/>
694
- <path d="M10 1.6v2.1M10 16.3v2.1M1.6 10h2.1M16.3 10h2.1M4.1 4.1l1.5 1.5M14.4 14.4l1.5 1.5M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5"/>
695
- </svg>
696
- </button>
697
673
  <!-- the identity slot: the sign-in door signed out; the chip (the profile trigger) once
698
674
  the session resolves — swapped client-side, never a wrong-state flash -->
699
675
  <span class="whoSlot" id="who">
@@ -933,24 +909,9 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
933
909
 
934
910
  <script>
935
911
  (function () {
936
- var root = document.documentElement;
937
912
  var API = '/api/bongos';
938
913
  var GDS = '/api/gds';
939
914
 
940
- /* ── the light/dark switch. The pre-paint script in <head> already applied the
941
- stored choice; this only flips it. The icon swap is CSS. ── */
942
- function isDark() {
943
- var m = root.getAttribute('data-mode');
944
- if (m === 'dark') return true;
945
- if (m === 'light') return false;
946
- return matchMedia('(prefers-color-scheme: dark)').matches;
947
- }
948
- document.getElementById('mode').addEventListener('click', function () {
949
- var next = isDark() ? 'light' : 'dark';
950
- root.setAttribute('data-mode', next);
951
- try { localStorage.setItem('cb-color-mode', next); } catch (e) {}
952
- });
953
-
954
915
  /* ── the tagline. It is ONE server-stamped string, but the composition wants it
955
916
  on two lines, so the two halves are baked as separate spans. A host whose
956
917
  tagline is not those two halves gets it back as one wrapping line rather
@@ -1,8 +1,9 @@
1
1
  {
2
- "_": "The landing's states for modules/ui-design/kit/render.js (task 1003320): the bar and its two bands, signed out and in.",
2
+ "_": "The landing's states for modules/ui-design/kit/render.js (task 1003320): the bar and its two bands, signed out and in. Dark only (task 1003336): the apex pins data-mode=\"dark\", so `modes` names the one mode and there is no query to walk.",
3
3
  "page": "/",
4
4
  "surface": "public-landing",
5
- "modeQuery": "mode",
5
+ "modes": ["dark"],
6
+ "modeQuery": false,
6
7
  "ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/provisioning/"],
7
8
  "ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "Unauthorized"],
8
9
  "ignoreControls": [".skip", ".vh"],
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-mode="dark">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -15,11 +15,7 @@
15
15
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
16
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
17
17
  <link rel="stylesheet" href="/assets/cosmos.css">
18
- <script>
19
- /* Mode, resolved before first paint — the same contract as every landing page. */
20
- (function(){try{var m=localStorage.getItem('cb-color-mode');
21
- if(m==='light'||m==='dark')document.documentElement.setAttribute('data-mode',m);}catch(e){}})();
22
- </script>
18
+ <!-- Dark only, like every apex page (task 1003336, ADR 0204): data-mode="dark" is fixed on <html>. -->
23
19
  <style>
24
20
  /* The reading column only. No :root block — the pack's injected theme owns the
25
21
  tokens (landing direction C1); everything below reads them. */
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-mode="dark">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -20,11 +20,9 @@
20
20
  re-injects the branding :root AFTER stylesheets, so the pack stays
21
21
  authoritative. Design contract: docs/design/projects-surfaces-redesign-direction.md -->
22
22
  <link rel="stylesheet" href="/assets/cosmos.css">
23
- <script>
24
- /* apply the saved color mode BEFORE first paint so there's no flash;
25
- no saved choice = follow the OS (prefers-color-scheme) */
26
- try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')document.documentElement.setAttribute('data-mode',_m);}catch(e){}
27
- </script>
23
+ <!-- The apex is dark only (task 1003336, ADR 0204): data-mode="dark" is fixed on
24
+ <html> above — no pre-paint script, no stored choice, no OS preference. Light
25
+ is the builders' hall's. cosmos.css keeps its light tier until its own removal. -->
28
26
  <style>
29
27
  /* Page-local layout only. /projects is the project HUB (task 1002743): one
30
28
  surface holding the map, your projects, and creating a new one. Every
@@ -381,19 +379,6 @@ summary{min-height:24px;padding:3px 0;}
381
379
  </div>
382
380
  <div class="barRight">
383
381
  <button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
384
- <!-- the glyph shows the mode you would GET: the moon in light, the sun in
385
- dark, swapped by cosmos.css on data-mode; the script labels it -->
386
- <button type="button" class="modeBtn" id="modeToggle" aria-label="Switch light or dark mode">
387
- <svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
388
- stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
389
- <path d="M16.2 12.4A6.8 6.8 0 0 1 7.6 3.8a6.8 6.8 0 1 0 8.6 8.6z"/>
390
- </svg>
391
- <svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
392
- stroke-width="1.4" stroke-linecap="round">
393
- <circle cx="10" cy="10" r="3.9"/>
394
- <path d="M10 1.6v2.1M10 16.3v2.1M1.6 10h2.1M16.3 10h2.1M4.1 4.1l1.5 1.5M14.4 14.4l1.5 1.5M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5"/>
395
- </svg>
396
- </button>
397
382
  <!-- the identity chip IS the profile trigger; renderWho fills it. Signed
398
383
  out it does not exist and the sign-in door takes its slot. -->
399
384
  <button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden></button>
@@ -1098,28 +1083,6 @@ summary{min-height:24px;padding:3px 0;}
1098
1083
 
1099
1084
  function $(id) { return document.getElementById(id); }
1100
1085
 
1101
- /* ── light/dark: the sky is constant, the mode governs the cards. No saved
1102
- choice = follow the OS; the toggle stores an explicit one. ── */
1103
- function resolvedMode() {
1104
- var m = document.documentElement.getAttribute('data-mode');
1105
- if (m === 'light' || m === 'dark') return m;
1106
- return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
1107
- }
1108
- /* the glyph shows the mode you would GET (the moon in light, the sun in
1109
- dark — cosmos.css swaps them on data-mode), so the label must say the same.
1110
- No aria-pressed — this swaps a mode, it is not a two-state toggle button. */
1111
- function paintModeToggle() {
1112
- var dark = resolvedMode() === 'dark';
1113
- var el = $('modeToggle');
1114
- el.setAttribute('aria-label', dark ? 'Switch to light mode' : 'Switch to dark mode');
1115
- }
1116
- $('modeToggle').addEventListener('click', function () {
1117
- var next = resolvedMode() === 'dark' ? 'light' : 'dark';
1118
- document.documentElement.setAttribute('data-mode', next);
1119
- try { localStorage.setItem('cb-color-mode', next); } catch (e) {}
1120
- paintModeToggle();
1121
- });
1122
- paintModeToggle();
1123
1086
  function esc(s) {
1124
1087
  return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
1125
1088
  return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
@@ -1281,16 +1244,23 @@ summary{min-height:24px;padding:3px 0;}
1281
1244
  deriving it from contract tokens is what makes it survive a re-brand. */
1282
1245
  var ORB_ACCENTS = ['var(--sea)', 'var(--terracotta)', 'var(--gold-soft)', 'var(--sun-lit)', 'var(--accent)'];
1283
1246
  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
1247
+ /* THE PLANET TEMPLATES (task 1003319; the materials, task 1003337). A project's
1248
+ orb is one of the world's three whole objects (blob / pair / bead) in one of
1249
+ four materials (chrome — the first round, named by omission — frost, gold,
1250
+ obsidian) in the project's accent, styled tint (the accent replaces the
1251
+ material's colour), wash (the accent lights it) or plain (as rendered; the
1252
+ chrome material's plain is spelled `chrome`, the first round's name). A
1288
1253
  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'];
1254
+ walk that assigns the accent assigns the template: ORB_WALK, whose
1255
+ consecutive positions change shape, material AND style, so twelve against
1256
+ five accents repeat only every sixty projects. ORB_TEMPLATES is every valid
1257
+ name — platform-identity's PROJECT_TEMPLATES (the DB CHECK); ORB_WALK mirrors
1258
+ its PROJECT_TEMPLATE_WALK. */
1259
+ var ORB_SHAPES = ['blob', 'pair', 'bead'], ORB_MATERIALS = ['frost', 'gold', 'obsidian'];
1260
+ var ORB_TEMPLATES = [];
1261
+ ORB_SHAPES.forEach(function (s) { ['tint', 'wash', 'chrome'].forEach(function (y) { ORB_TEMPLATES.push(s + '-' + y); }); });
1262
+ ORB_SHAPES.forEach(function (s) { ORB_MATERIALS.forEach(function (m) { ['tint', 'wash', 'plain'].forEach(function (y) { ORB_TEMPLATES.push(s + '-' + m + '-' + y); }); }); });
1263
+ var ORB_WALK = ['blob-tint', 'pair-gold-wash', 'bead-obsidian-plain', 'blob-frost-wash', 'pair-chrome', 'bead-gold-tint', 'blob-obsidian-wash', 'pair-frost-tint', 'bead-wash', 'blob-gold-plain', 'pair-obsidian-tint', 'bead-frost-wash'];
1294
1264
  var catalog = [];
1295
1265
  var mineOrigins = {}; /* normalized host → 'owner' | 'builder' */
1296
1266
  var ownedInstances = []; /* the viewer's OWN provisioning rows (never torn_down) */
@@ -1328,13 +1298,13 @@ summary{min-height:24px;padding:3px 0;}
1328
1298
  .sort(function (a, b) { return String(a.origin).localeCompare(String(b.origin)); })
1329
1299
  .forEach(function (p, i) {
1330
1300
  p._acc = ORB_ACCENTS[i % ORB_ACCENTS.length];
1331
- p._tpl = ORB_TEMPLATES[i % ORB_TEMPLATES.length];
1301
+ p._tpl = ORB_WALK[i % ORB_WALK.length];
1332
1302
  });
1333
1303
  }
1334
1304
  function accentOf(p) { return p._acc || ORB_ACCENTS[0]; }
1335
1305
  /* a curated template wins over the walk; an unknown name is not a choice */
1336
1306
  function templateOf(p) {
1337
- return ORB_TEMPLATES.indexOf(p.template) !== -1 ? p.template : (p._tpl || ORB_TEMPLATES[0]);
1307
+ return ORB_TEMPLATES.indexOf(p.template) !== -1 ? p.template : (p._tpl || ORB_WALK[0]);
1338
1308
  }
1339
1309
  /* the manage cards' accent rule — one project, one colour, in the sky AND
1340
1310
  the list. Shared with orbFor(), which used to carry this hash inline. */
@@ -1345,7 +1315,7 @@ summary{min-height:24px;padding:3px 0;}
1345
1315
  }
1346
1316
  function slugAccent(slug) { return slugHash(slug) % ORB_ACCENTS.length; }
1347
1317
  /* 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]; }
1318
+ function slugTemplate(slug) { return ORB_WALK[slugHash(slug) % ORB_WALK.length]; }
1349
1319
  function relOf(p) {
1350
1320
  /* an instance the viewer provisioned is a server-resolved authority too —
1351
1321
  own-scoped by owner_builder_id, no client inference (direction §2
@@ -1438,11 +1408,13 @@ summary{min-height:24px;padding:3px 0;}
1438
1408
  a.setAttribute('data-generated', '');
1439
1409
  var rel = relOf(p);
1440
1410
  a.setAttribute('data-rel', rel);
1441
- /* the planet: shape and style as data attributes, cosmos.css does the rest */
1411
+ /* the planet: shape, material and style as data attributes (shape[-material]-style;
1412
+ no material = chrome), cosmos.css does the rest */
1442
1413
  var tpl = templateOf(p), tplParts = tpl.split('-');
1443
1414
  a.setAttribute('data-template', tpl);
1444
1415
  a.setAttribute('data-shape', tplParts[0]);
1445
- a.setAttribute('data-style', tplParts[1]);
1416
+ a.setAttribute('data-material', tplParts.length === 3 ? tplParts[1] : 'chrome');
1417
+ a.setAttribute('data-style', tplParts[tplParts.length - 1]);
1446
1418
  var inst = p._inst;
1447
1419
  var href = safeHttpUrl(p.origin);
1448
1420
  if (inst) {
@@ -82,9 +82,6 @@
82
82
  { "name": "a solo Shift tap focuses the search on the map", "continue": true,
83
83
  "steps": [["clickAt", 700, 600], ["press", "Shift"], ["wait", 100]],
84
84
  "expect": { "activeId": "mapQ" } },
85
- { "name": "the mode toggle: the label says what the press does, and the press does it", "continue": true,
86
- "steps": [["clickAt", 700, 600], ["click", "#modeToggle"], ["wait", 100]],
87
- "expect": { "attr": [["#modeToggle", "aria-label", "Switch to light mode"], ["html", "data-mode", "dark"]] } },
88
85
  { "name": "the Table deep link (the landing's band item) lands on the table, Table current (task 1003332)", "auth": true, "width": 1440, "url": "/projects?view=map&sub=table",
89
86
  "steps": [["wait", 300]],
90
87
  "expect": { "visible": ["#sub-table-body", ".tblMast"], "hidden": ["#sub-sky-body"], "text": [["[aria-current=\"page\"]", "Table"], [".tblTitle", "every project"]] } },