@bongos/core 1.24.93 → 1.24.95

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 (44) hide show
  1. package/.bongos-core.json +123 -33
  2. package/docs/adr/0404-a-party-is-a-room-and-rooms-register-as-kinds.md +118 -0
  3. package/docs/adr/0405-each-character-has-one-colour-and-the-visor-wears-it.md +31 -0
  4. package/docs/adr/README.md +2 -0
  5. package/docs/copy-inventory.md +7 -7
  6. package/docs/copy-registry.json +7 -7
  7. package/docs/design/characters/v2/artisan-visor.png +0 -0
  8. package/docs/design/characters/v2/artisan.png +0 -0
  9. package/docs/design/characters/v2/artist-visor.png +0 -0
  10. package/docs/design/characters/v2/artist.png +0 -0
  11. package/docs/design/characters/v2/draw-characters.js +465 -0
  12. package/docs/design/characters/v2/patron-visor.png +0 -0
  13. package/docs/design/characters/v2/patron.png +0 -0
  14. package/docs/design/characters/v2/sheet.html +114 -0
  15. package/docs/module-api-changelog.md +4 -0
  16. package/docs/modules-contract.md +2 -0
  17. package/docs/page-readings.json +35 -35
  18. package/modules/hall-ui/public/characters/artisan-face.png +0 -0
  19. package/modules/hall-ui/public/characters/artisan-visor.png +0 -0
  20. package/modules/hall-ui/public/characters/artisan.png +0 -0
  21. package/modules/hall-ui/public/characters/artist-face.png +0 -0
  22. package/modules/hall-ui/public/characters/artist-visor.png +0 -0
  23. package/modules/hall-ui/public/characters/artist.png +0 -0
  24. package/modules/hall-ui/public/characters/patron-face.png +0 -0
  25. package/modules/hall-ui/public/characters/patron-visor.png +0 -0
  26. package/modules/hall-ui/public/characters/patron.png +0 -0
  27. package/modules/hall-ui/public/craft-mode.js +15 -4
  28. package/modules/hall-ui/public/home-city.js +14 -3
  29. package/modules/hall-ui/public/home-gallery.css +6 -0
  30. package/modules/hall-ui/public/home-gallery.js +30 -0
  31. package/modules/hall-ui/public/style.css +18 -5
  32. package/modules/hall-ui/public/visor.css +20 -5
  33. package/modules/hall-ui/public/visor.js +6 -3
  34. package/modules/hall-ui/records/characters.md +19 -0
  35. package/modules/hall-ui/records/visor.md +10 -1
  36. package/package-lock.json +2 -2
  37. package/package.json +1 -1
  38. package/release-notes.json +20 -0
  39. package/src/module-api.js +1 -1
  40. package/src/module-seams.js +3 -0
  41. package/tests/character_creation.mjs +44 -7
  42. package/tests/hall_visor.mjs +28 -2
  43. package/tests/home_gallery_widget.mjs +43 -0
  44. package/tests/room_kinds_seam.mjs +68 -0
@@ -12,7 +12,7 @@
12
12
  "surface": "builders",
13
13
  "title": "Studio",
14
14
  "states_read": ["pick","describe","make","waiting","result","result-own","failed","balance","balance-all","out"],
15
- "files_hash": "4d3ddcf70c72e2d8",
15
+ "files_hash": "bfd352bc483d41c2",
16
16
  "reading_hash": "d9e0e85bcee4aff4",
17
17
  "counts": {"lines":106,"placed":41,"shared":7,"unplaced":58},
18
18
  "omitted": {"data":17},
@@ -131,7 +131,7 @@
131
131
  "surface": "builders",
132
132
  "title": "Repo Atlas",
133
133
  "states_read": ["map","truth","detail","index","out"],
134
- "files_hash": "5ccfecad1f11842c",
134
+ "files_hash": "133220425bccce34",
135
135
  "reading_hash": "6934dead335b2507",
136
136
  "counts": {"lines":25,"placed":14,"shared":7,"unplaced":4},
137
137
  "omitted": {"data":5},
@@ -169,7 +169,7 @@
169
169
  "surface": "builders",
170
170
  "title": "Blockers",
171
171
  "states_read": ["default"],
172
- "files_hash": "3edd4c9a8287965e",
172
+ "files_hash": "a819c6030cf15a6e",
173
173
  "reading_hash": "60d73d854ced958a",
174
174
  "counts": {"lines":36,"placed":17,"shared":7,"unplaced":12},
175
175
  "omitted": {"data":51},
@@ -218,7 +218,7 @@
218
218
  "surface": "builders",
219
219
  "title": "Board Room",
220
220
  "states_read": ["room","out","sitting-meta","objection-form"],
221
- "files_hash": "797b50d46b74f122",
221
+ "files_hash": "649b8241ac4ad473",
222
222
  "reading_hash": "0260b797b8dc1526",
223
223
  "counts": {"lines":52,"placed":21,"shared":7,"unplaced":24},
224
224
  "omitted": {"data":17},
@@ -283,7 +283,7 @@
283
283
  "surface": "builders",
284
284
  "title": "The city",
285
285
  "states_read": ["city","fullscreen","focus","pins","living","card","card-ask"],
286
- "files_hash": "d9dd949934a5b719",
286
+ "files_hash": "804ce56d156d7046",
287
287
  "reading_hash": "277096d903879a57",
288
288
  "counts": {"lines":26,"placed":9,"shared":6,"unplaced":11},
289
289
  "omitted": {"data":7},
@@ -322,7 +322,7 @@
322
322
  "surface": "builders",
323
323
  "title": "Collab",
324
324
  "states_read": ["board","out"],
325
- "files_hash": "6bd34870e07488b0",
325
+ "files_hash": "6e58d1336b59ed6d",
326
326
  "reading_hash": "e6452bf92231656f",
327
327
  "counts": {"lines":82,"placed":36,"shared":6,"unplaced":40},
328
328
  "omitted": {"data":23},
@@ -417,7 +417,7 @@
417
417
  "surface": "builders",
418
418
  "title": "Commands",
419
419
  "states_read": ["in","out"],
420
- "files_hash": "19b25bb84e65c93a",
420
+ "files_hash": "6c7abde80e1c7a74",
421
421
  "reading_hash": "ecc452689a8ee9d7",
422
422
  "counts": {"lines":71,"placed":49,"shared":7,"unplaced":15},
423
423
  "omitted": {"data":177},
@@ -501,7 +501,7 @@
501
501
  "surface": "builders",
502
502
  "title": "Copy desk",
503
503
  "states_read": ["queue","out","search","composer","closer","proposer"],
504
- "files_hash": "3598688fde8da13f",
504
+ "files_hash": "1f6a0864849d0263",
505
505
  "reading_hash": "3819ef6fa8f6fb7c",
506
506
  "counts": {"lines":72,"placed":36,"shared":7,"unplaced":29},
507
507
  "omitted": {"data":94},
@@ -586,7 +586,7 @@
586
586
  "surface": "builders",
587
587
  "title": "Deploy",
588
588
  "states_read": ["work","out"],
589
- "files_hash": "f66bb9f77832d5fe",
589
+ "files_hash": "b534665c0eb1a176",
590
590
  "reading_hash": "3770e8b2fa56bee1",
591
591
  "counts": {"lines":14,"placed":5,"shared":7,"unplaced":2},
592
592
  "omitted": {"data":5},
@@ -613,7 +613,7 @@
613
613
  "surface": "builders",
614
614
  "title": "The sky",
615
615
  "states_read": ["tour","sky","wells","ledger","full","full-back","out"],
616
- "files_hash": "f54ebe767edaf0c8",
616
+ "files_hash": "a5f2d9cada6d1773",
617
617
  "reading_hash": "4a879397a84631e3",
618
618
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
619
619
  "omitted": {"data":10},
@@ -676,7 +676,7 @@
676
676
  "surface": "builders",
677
677
  "title": "Goals",
678
678
  "states_read": ["list","filters","cards","detail","dependencies","out","uat","uat-panel","author"],
679
- "files_hash": "e0237363305a997e",
679
+ "files_hash": "83d51fe0a0cbdc81",
680
680
  "reading_hash": "13a5d07b3804b627",
681
681
  "counts": {"lines":187,"placed":61,"shared":7,"unplaced":119},
682
682
  "omitted": {"data":315},
@@ -876,7 +876,7 @@
876
876
  "surface": "builders",
877
877
  "title": "Government",
878
878
  "states_read": ["constitution","out","permissions","board","constitution-tab"],
879
- "files_hash": "cee3223b05313772",
879
+ "files_hash": "67e03165f6801e25",
880
880
  "reading_hash": "37c6878d60447fd6",
881
881
  "counts": {"lines":65,"placed":27,"shared":7,"unplaced":31},
882
882
  "omitted": {"data":276},
@@ -954,8 +954,8 @@
954
954
  "surface": "builders",
955
955
  "title": "Builders' Hall",
956
956
  "states_read": ["home","out","founding","genesis","decide","demo"],
957
- "files_hash": "6fce98a5eabe1c82",
958
- "reading_hash": "42901560779fc4d1",
957
+ "files_hash": "057978d931dfd529",
958
+ "reading_hash": "c46b0ff89002a2d9",
959
959
  "counts": {"lines":32,"placed":12,"shared":8,"unplaced":12},
960
960
  "omitted": {"data":5},
961
961
  "notes": ["founding: an action failed (page.focus: Timeout 6000ms exceeded.); read as it stood"],
@@ -977,7 +977,7 @@
977
977
  {"key":"L0015","section":"Needs your attention13","text":"Ideas awaiting triage","placement":"placed","file":"modules/hall-ui/public/builders.js","line":320,"string_id":"e181637c5a5e"},
978
978
  {"key":"L0016","section":"Needs your attention13","text":"Requires project {…} fix","placement":"unplaced"},
979
979
  {"key":"L0017","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":514,"string_id":"1883ad7f07fc"},
980
- {"key":"L0018","section":"Needs your attention13","text":"core 1.24.90","placement":"unplaced"},
980
+ {"key":"L0018","section":"Needs your attention13","text":"core 1.24.93","placement":"unplaced"},
981
981
  {"key":"L0019","section":"Needs your attention13","text":"Skip","placement":"unplaced"},
982
982
  {"key":"L0020","section":"Needs your attention13","text":"Built on","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":183,"string_id":"fef59346ffee"},
983
983
  {"key":"L0021","section":"Needs your attention13","text":"cloudbongos.com","placement":"unplaced"},
@@ -999,7 +999,7 @@
999
999
  "surface": "builders",
1000
1000
  "title": "Idea",
1001
1001
  "states_read": ["record","missing"],
1002
- "files_hash": "d657a33a67bc8f3b",
1002
+ "files_hash": "b19a972d3190b27c",
1003
1003
  "reading_hash": "f7d644748c87e426",
1004
1004
  "counts": {"lines":52,"placed":25,"shared":8,"unplaced":19},
1005
1005
  "omitted": {"data":25},
@@ -1064,7 +1064,7 @@
1064
1064
  "surface": "builders",
1065
1065
  "title": "Ideas",
1066
1066
  "states_read": ["compose","full","develop"],
1067
- "files_hash": "64316b241e5de273",
1067
+ "files_hash": "65f50755fa5d2c77",
1068
1068
  "reading_hash": "a62c05dcef3bf87f",
1069
1069
  "counts": {"lines":68,"placed":34,"shared":7,"unplaced":27},
1070
1070
  "omitted": {"data":118},
@@ -1145,7 +1145,7 @@
1145
1145
  "surface": "builders",
1146
1146
  "title": "Market Watch",
1147
1147
  "states_read": ["desk","settings","out"],
1148
- "files_hash": "c19fa516071448c1",
1148
+ "files_hash": "2685309696818881",
1149
1149
  "reading_hash": "30512dda207b5ea6",
1150
1150
  "counts": {"lines":23,"placed":11,"shared":7,"unplaced":5},
1151
1151
  "omitted": {"data":5},
@@ -1181,7 +1181,7 @@
1181
1181
  "surface": "builders",
1182
1182
  "title": "Market Watch",
1183
1183
  "states_read": ["dossier","out","propose","correct","filtered"],
1184
- "files_hash": "9f574f36d050ecb4",
1184
+ "files_hash": "4dd2ab01e84c217f",
1185
1185
  "reading_hash": "129bff28c4e4ed6d",
1186
1186
  "counts": {"lines":24,"placed":12,"shared":7,"unplaced":5},
1187
1187
  "omitted": {"data":5},
@@ -1218,7 +1218,7 @@
1218
1218
  "surface": "builders",
1219
1219
  "title": "Market Watch rules",
1220
1220
  "states_read": ["in","scoring","names","propose","out"],
1221
- "files_hash": "04757fe324f6331f",
1221
+ "files_hash": "526c2149de84f633",
1222
1222
  "reading_hash": "ca0c62e8dce51c8b",
1223
1223
  "counts": {"lines":36,"placed":12,"shared":7,"unplaced":17},
1224
1224
  "omitted": {"data":20},
@@ -1267,7 +1267,7 @@
1267
1267
  "surface": "builders",
1268
1268
  "title": "Modules",
1269
1269
  "states_read": ["browse","search-empty","lens","details","out"],
1270
- "files_hash": "bb880eb91b5bffa2",
1270
+ "files_hash": "574e1738477d342c",
1271
1271
  "reading_hash": "6a12e9306bba5622",
1272
1272
  "counts": {"lines":59,"placed":29,"shared":7,"unplaced":23},
1273
1273
  "omitted": {"data":107},
@@ -1339,7 +1339,7 @@
1339
1339
  "surface": "builders",
1340
1340
  "title": "Money",
1341
1341
  "states_read": ["money","hosting","out"],
1342
- "files_hash": "eab833a98ae63570",
1342
+ "files_hash": "816343bf158f836b",
1343
1343
  "reading_hash": "e355d5466b123e07",
1344
1344
  "counts": {"lines":83,"placed":28,"shared":7,"unplaced":48},
1345
1345
  "omitted": {"data":27},
@@ -1435,7 +1435,7 @@
1435
1435
  "surface": "builders",
1436
1436
  "title": "My agents",
1437
1437
  "states_read": ["desk","factory","saved","what-it-knows","speciality-walk","speciality-forms","nobody"],
1438
- "files_hash": "f9fd9939a04db900",
1438
+ "files_hash": "3e69046bdd44b756",
1439
1439
  "reading_hash": "5347e0496b38727d",
1440
1440
  "counts": {"lines":100,"placed":33,"shared":7,"unplaced":60},
1441
1441
  "omitted": {"data":38},
@@ -1548,7 +1548,7 @@
1548
1548
  "surface": "builders",
1549
1549
  "title": "Primer",
1550
1550
  "states_read": ["in","toc-open","jump","out"],
1551
- "files_hash": "bab434369863a9c0",
1551
+ "files_hash": "da0299600036bb6f",
1552
1552
  "reading_hash": "85790bd421e5c387",
1553
1553
  "counts": {"lines":487,"placed":57,"shared":7,"unplaced":423},
1554
1554
  "omitted": {"data":18},
@@ -2048,7 +2048,7 @@
2048
2048
  "surface": "builders",
2049
2049
  "title": "Profile",
2050
2050
  "states_read": ["own","works","activity","yours","activity-artist","activity-artist-ideas","other","artist","artist-ideas","out"],
2051
- "files_hash": "a1e1acb3badc9e27",
2051
+ "files_hash": "88fc36c8238827bf",
2052
2052
  "reading_hash": "3ff8d703d17e8c2a",
2053
2053
  "counts": {"lines":232,"placed":84,"shared":7,"unplaced":141},
2054
2054
  "omitted": {"data":263},
@@ -2293,7 +2293,7 @@
2293
2293
  "surface": "builders",
2294
2294
  "title": "Project settings",
2295
2295
  "states_read": ["default"],
2296
- "files_hash": "bf4ce03972123088",
2296
+ "files_hash": "dec5d7d8359c913d",
2297
2297
  "reading_hash": "283df27d0d5bfc1b",
2298
2298
  "counts": {"lines":43,"placed":25,"shared":7,"unplaced":11},
2299
2299
  "omitted": {"data":33},
@@ -2349,7 +2349,7 @@
2349
2349
  "surface": "builders",
2350
2350
  "title": "Builders",
2351
2351
  "states_read": ["roster","artists","search","out"],
2352
- "files_hash": "c4f41160afeaf87f",
2352
+ "files_hash": "eada2a8ff8a184ab",
2353
2353
  "reading_hash": "665ac2e36339f5d3",
2354
2354
  "counts": {"lines":56,"placed":17,"shared":7,"unplaced":32},
2355
2355
  "omitted": {"data":137},
@@ -2418,7 +2418,7 @@
2418
2418
  "surface": "builders",
2419
2419
  "title": "Options",
2420
2420
  "states_read": ["account","access","preferences","install-reports","sound","update"],
2421
- "files_hash": "0f1849a1dab98d9a",
2421
+ "files_hash": "9ee8d40cdee36f53",
2422
2422
  "reading_hash": "898a13fda24c3c90",
2423
2423
  "counts": {"lines":162,"placed":104,"shared":7,"unplaced":51},
2424
2424
  "omitted": {"data":23},
@@ -2593,7 +2593,7 @@
2593
2593
  "surface": "builders",
2594
2594
  "title": "The street",
2595
2595
  "states_read": ["street","fullscreen","card","focus","here","inside-rules","inside-disputes","inside-people","inside-planning","inside-automation","inside-review","inside-release","inside-treasury","floor","amend","district","desk","desk-facts","desk-quiet","desk-people-builders","desk-people-tier","desk-disputes","desk-automation-goals","desk-release-core","desk-rules","desk-rules-category","desk-planning-priorities","desk-planning-ideas","desk-planning-blockers","pins","pins-pager","living","deck","deck-treasury","sitting","object","voted","admission","settled","blocker","blocker-more","report","report-more","core","core-more","recheck","recheck-more","idea","idea-more","queue","queue-more","automation","automation-more"],
2596
- "files_hash": "6768ffb4c12c21ac",
2596
+ "files_hash": "b034bee92146665b",
2597
2597
  "reading_hash": "e788e6bf4be8d68c",
2598
2598
  "counts": {"lines":325,"placed":54,"shared":6,"unplaced":265},
2599
2599
  "omitted": {"data":240},
@@ -2931,7 +2931,7 @@
2931
2931
  "surface": "builders",
2932
2932
  "title": "Studio",
2933
2933
  "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
2934
- "files_hash": "8afa7e332b24559f",
2934
+ "files_hash": "c01cf7220bebfb79",
2935
2935
  "reading_hash": "c4e3b4188c1db755",
2936
2936
  "counts": {"lines":51,"placed":29,"shared":7,"unplaced":15},
2937
2937
  "omitted": {"data":23},
@@ -2995,7 +2995,7 @@
2995
2995
  "surface": "builders",
2996
2996
  "title": "Task",
2997
2997
  "states_read": ["record","from-goal","missing"],
2998
- "files_hash": "b839c7b452afbfbe",
2998
+ "files_hash": "91a9c833cdebb28b",
2999
2999
  "reading_hash": "8dd803b6934e6388",
3000
3000
  "counts": {"lines":151,"placed":46,"shared":9,"unplaced":96},
3001
3001
  "omitted": {"data":111},
@@ -3159,7 +3159,7 @@
3159
3159
  "surface": "builders",
3160
3160
  "title": "Your thinking",
3161
3161
  "states_read": ["tour","sky","list","simple","full-idea","full","full-list","full-back","out"],
3162
- "files_hash": "3cce7079079c7782",
3162
+ "files_hash": "fa151338327f0a86",
3163
3163
  "reading_hash": "b73f848b46b0f9b8",
3164
3164
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
3165
3165
  "omitted": {"data":10},
@@ -3222,7 +3222,7 @@
3222
3222
  "surface": "builders",
3223
3223
  "title": "Tweak a page",
3224
3224
  "states_read": ["doc","typed","chrome","submitted","picking","out"],
3225
- "files_hash": "6fc3e45b7cc82f9f",
3225
+ "files_hash": "f52e3d547a72e46c",
3226
3226
  "reading_hash": "f521f8d9a7147557",
3227
3227
  "counts": {"lines":26,"placed":18,"shared":2,"unplaced":6},
3228
3228
  "omitted": {"data":19},
@@ -3261,7 +3261,7 @@
3261
3261
  "surface": "builders",
3262
3262
  "title": "Watch",
3263
3263
  "states_read": ["board","out","operations","security","sessions","autobongos"],
3264
- "files_hash": "4e769b3c76d5f3b8",
3264
+ "files_hash": "8c900aaef19935e9",
3265
3265
  "reading_hash": "1961a0c06b9d8477",
3266
3266
  "counts": {"lines":72,"placed":33,"shared":7,"unplaced":32},
3267
3267
  "omitted": {"data":52},
@@ -3346,7 +3346,7 @@
3346
3346
  "surface": "builders",
3347
3347
  "title": "Tasks",
3348
3348
  "states_read": ["ready","in-progress","backlog","completed","filters-open","out","founding"],
3349
- "files_hash": "cb9ac65fd6baed84",
3349
+ "files_hash": "932f03e5ce99e576",
3350
3350
  "reading_hash": "e9b412d1ec59ceee",
3351
3351
  "counts": {"lines":122,"placed":49,"shared":7,"unplaced":66},
3352
3352
  "omitted": {"data":323},
@@ -830,8 +830,12 @@
830
830
  // clears the pick and signs out, and the next sign-in finds none and asks again.
831
831
  // Identity and view only — rank still gates every verb on the server (ADR 0016).
832
832
  //
833
- // The sprites are the owner's art (Builder → Artisan, Artist → Artist, Ruler →
834
- // Patron; the Governor is the spare), one file per character under characters/.
833
+ // The sprites are the second look (task 1005218): 64x64 pixel art, two animals in one
834
+ // body (Artisan a ram on a lizard, Artist an ibis on an octopus, Patron a falcon on a
835
+ // lion), each with one colour of its own (style.css --char-*). Three files per character
836
+ // under characters/: <id>.png the whole figure, <id>-face.png the head (the chip),
837
+ // <id>-visor.png the head wearing the visor (the visor's corner). All are DRAWN by
838
+ // docs/design/characters/v2/draw-characters.js --hall, never edited by hand.
835
839
  var account; // undefined: not told; null: none picked; else the character
836
840
  var creating = false; // the preselected door while character creation is open
837
841
  var CHAR_KEY = 'hall-character'; // localStorage: the last held character, so the first paint is right
@@ -841,9 +845,14 @@
841
845
  patron: 'Watch the project, propose, and with a seat decide.',
842
846
  };
843
847
  // Only ever one of the three names reaches the URL (valid() is the allowlist).
844
- function spriteSrc(id) { return '/builders/characters/' + (valid(id) || 'artisan') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
848
+ function pictureSrc(id, part) { return '/builders/characters/' + (valid(id) || 'artisan') + (part ? '-' + part : '') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
849
+ function spriteSrc(id) { return pictureSrc(id, ''); }
850
+ // The head alone, and the head wearing the visor (what the visor shows of you).
851
+ function faceSrc(id) { return pictureSrc(id, 'face'); }
852
+ function portraitSrc(id) { return pictureSrc(id, 'visor'); }
845
853
  function spriteHtml(id, cls) {
846
- return '<img class="craft-sprite ' + cls + '" src="' + spriteSrc(id) + '" alt="" width="250" height="290" decoding="async">';
854
+ var face = cls === 'craft-sprite--chip';
855
+ return '<img class="craft-sprite ' + cls + '" src="' + (face ? faceSrc(id) : spriteSrc(id)) + '" alt="" width="' + (face ? 32 : 64) + '" height="' + (face ? 32 : 64) + '" decoding="async">';
847
856
  }
848
857
  function heldCharacter() {
849
858
  if (typeof account === 'string') return account;
@@ -1061,6 +1070,8 @@
1061
1070
  character: function () { return account; },
1062
1071
  changeCharacter: changeCharacter,
1063
1072
  spriteSrc: spriteSrc,
1073
+ faceSrc: faceSrc,
1074
+ portraitSrc: portraitSrc,
1064
1075
  // task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
1065
1076
  me: function () { return lastMe; },
1066
1077
  // A tool opens only for the character whose tool it is (task 1005207): the
@@ -347,17 +347,28 @@
347
347
  wireTip();
348
348
  wireDoors();
349
349
  if (window.OTBHomeGallery) gallery = window.OTBHomeGallery.attach(host);
350
+ if (gallery && gallery.onChange) gallery.onChange(registerScene);
350
351
  if (window.OTBHomeIdeaBox) ideaBox = window.OTBHomeIdeaBox.attach(host);
351
352
  document.addEventListener('otb:craft', craftHome);
352
353
  craftHome();
353
354
  new MutationObserver(() => { if (model) drawScene(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
354
355
  document.addEventListener('otb:craft', () => { if (model) { drawScene(); drawLadder(); } });
355
356
  window.addEventListener('resize', () => { if (model) drawLadder(); });
356
- // task 1005148: Home's city as the visor's scene; R13 adds its camera's actions here.
357
- window.__visorScene = { name: 'home', place: 'Home' };
358
- if (window.OTBVisor) window.OTBVisor.scene(window.__visorScene);
357
+ registerScene();
359
358
  load();
360
359
  }
360
+ // task 1005148: Home's city as the visor's scene; R13 adds its camera's actions here.
361
+ // task 1005211 (ADR 0403): in Artistry Home is the gallery, and its controls are the visor's:
362
+ // the gallery's own rows (home-gallery.js visorActions), registered again when they change.
363
+ let sceneSig = null;
364
+ function registerScene() {
365
+ const actions = gallery && gallery.visorActions ? gallery.visorActions() : [];
366
+ const sig = actions.map((a) => a.label + '|' + (a.key || '')).join('\n');
367
+ if (sig === sceneSig && window.__visorScene) return;
368
+ sceneSig = sig;
369
+ window.__visorScene = { name: 'home', place: 'Home', actions };
370
+ if (window.OTBVisor) window.OTBVisor.scene(window.__visorScene);
371
+ }
361
372
 
362
373
  window.OTB.contributeHallWidget({
363
374
  id: 'home-city', label: 'The project', order: 15, core: true, mount,
@@ -127,3 +127,9 @@
127
127
  .hg-count { left: 50%; }
128
128
  .hg-salon { left: 20px; right: 20px; top: 196px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 22px 16px; }
129
129
  }
130
+
131
+ /* task 1005211 (ADR 0403): in game mode the visor is the control and the gallery is the view.
132
+ Its order tabs, its ways (the full gallery, your studio), the arrows and the full-screen
133
+ button are rows on the visor (home-gallery.js visorActions); looking along the wall, a work's
134
+ label, its heart and a piece in the full gallery stay in the scene. Classic mode is as it was. */
135
+ html.visor-on .hg-sort, html.visor-on .hg-ways, html.visor-on .hg-edge, html.visor-on .hg-fsbtn { display: none; }
@@ -122,6 +122,29 @@
122
122
  var room = root.querySelector('.hg-room'), track = root.querySelector('.hg-track'), salon = root.querySelector('.hg-salon');
123
123
  var prev = root.querySelector('.hg-prev'), next = root.querySelector('.hg-next'), count = root.querySelector('.hg-count');
124
124
  var wayAll = root.querySelector('.hg-way-all'), empty = root.querySelector('.hg-empty');
125
+ var fsbtn = root.querySelector('.hg-fsbtn'), studio = root.querySelector('a.hg-way');
126
+ // task 1005211 (ADR 0403): in game mode the gallery's tabs, ways, arrows and full-screen
127
+ // button are the visor's (home-gallery.css hides them under html.visor-on). These are
128
+ // their rows, each pressing the gallery's own control, in the control's own words; Home
129
+ // (home-city.js) registers them as the scene's actions and asks again when they change.
130
+ var changed = function () {};
131
+ function visorActions() {
132
+ if (root.hidden) return [];
133
+ var all = room.classList.contains('is-all');
134
+ var out = [{ label: wayAll.firstChild.textContent.trim(), key: 'g', bind: true, lead: true, run: function () { wayAll.click(); room.focus(); } }];
135
+ root.querySelectorAll('.hg-sort button').forEach(function (b) {
136
+ out.push({ label: b.textContent + (b.getAttribute('aria-pressed') === 'true' ? ' · on' : ' · off'), run: function () { b.click(); } });
137
+ });
138
+ // the wall's own keys answer once the room has the keyboard, so a row hands it over
139
+ if (!all && list.length) {
140
+ out.push({ label: prev.getAttribute('aria-label'), key: '←', run: function () { prev.click(); room.focus(); } });
141
+ out.push({ label: next.getAttribute('aria-label'), key: '→', run: function () { next.click(); room.focus(); } });
142
+ out.push({ label: 'Full screen', key: 'F', run: function () { fsbtn.click(); room.focus(); } });
143
+ }
144
+ var href = studio.getAttribute('href');
145
+ out.push({ label: studio.firstChild.textContent.trim(), run: function () { window.location.assign(href); } });
146
+ return out;
147
+ }
125
148
  room.setAttribute('aria-label', ROOM_LABEL);
126
149
 
127
150
  function hang(w, i) {
@@ -239,6 +262,7 @@
239
262
  wayAll.firstChild.textContent = on ? 'Back to the bench ' : 'The full gallery ';
240
263
  room.setAttribute('aria-label', on ? ALL_LABEL : ROOM_LABEL);
241
264
  if (on) renderSalon();
265
+ changed();
242
266
  }
243
267
 
244
268
  // ---- full screen: the art alone, with its signature and heart ----
@@ -264,6 +288,7 @@
264
288
  if (order === b.dataset.order) return;
265
289
  order = b.dataset.order;
266
290
  root.querySelectorAll('.hg-sort button').forEach(function (x) { x.setAttribute('aria-pressed', String(x === b)); });
291
+ changed();
267
292
  load();
268
293
  });
269
294
  });
@@ -327,6 +352,7 @@
327
352
  if (mine !== asking) return;
328
353
  list = res && res.ok && res.data && Array.isArray(res.data.works) ? res.data.works.filter(function (w) { return w && w.id && w.artist; }) : [];
329
354
  render();
355
+ changed();
330
356
  if (first) { first = false; arrive(); }
331
357
  }, function () { if (mine === asking) { list = []; render(); } });
332
358
  }
@@ -348,7 +374,11 @@
348
374
  root.hidden = !on;
349
375
  if (on && !loaded) load();
350
376
  if (!on && isFs()) setFs(false);
377
+ changed();
351
378
  },
379
+ // task 1005211: the rows the visor shows for the gallery, and a hook for when they change
380
+ visorActions: visorActions,
381
+ onChange: function (fn) { changed = typeof fn === 'function' ? fn : function () {}; },
352
382
  };
353
383
  }
354
384
 
@@ -1587,15 +1587,28 @@ html.opt-window-open { overflow: hidden; }
1587
1587
 
1588
1588
  /* THE CHARACTER (task 1005147). A builder who holds a character wears its sprite in
1589
1589
  the top bar in place of the switch: one button, which offers "Change character"
1590
- (signing out). The sprites are pixel art, so they scale by whole pixels. */
1590
+ (signing out). The sprites are pixel art, so they scale by whole pixels.
1591
+ EACH CHARACTER HAS ONE COLOUR (task 1005218; --char-*): the Artisan's is the hall's own
1592
+ accent, the Artist's violet and the Patron's gold are the two colours the second look
1593
+ added, the same three the pictures are drawn in (docs/design/characters/v2). In game mode
1594
+ the visor wears the held character's (visor.css). */
1595
+ html {
1596
+ --char-artisan: var(--accent);
1597
+ --char-artist: color-mix(in oklch, color-mix(in oklch, var(--sea) 82%, var(--accent)) 72%, var(--ink));
1598
+ --char-patron: color-mix(in srgb, var(--warn) 72%, var(--ink));
1599
+ }
1591
1600
  .craft-sprite { display: block; image-rendering: pixelated; object-fit: contain; }
1592
1601
  .craft-switch__me { padding: 0 10px 0 4px; color: var(--ink); }
1593
- /* The chip is the figure's head: the portrait cropped to its top third. */
1594
- .craft-sprite--chip { width: 26px; height: 26px; border-radius: var(--radius-pill); object-fit: cover; object-position: 50% 22%; }
1602
+ /* The chip is the figure's head: its own 32px picture. */
1603
+ .craft-sprite--chip { width: 26px; height: 26px; border-radius: var(--radius-pill); object-fit: cover; background: var(--bg-deep); }
1595
1604
  /* Character creation: each door is the owner's portrait — the figure on its plinth
1596
1605
  in the torch-lit hall — filling the top of the door, the words beneath it. */
1597
- .craft-plinth { display: block; overflow: hidden; }
1598
- .craft-sprite--door { width: 100%; height: auto; aspect-ratio: 250 / 290; transition: transform var(--ds-dur-base) var(--ds-ease); }
1606
+ .craft-plinth { display: block; overflow: hidden; background: var(--bg-deep); }
1607
+ .craft-sprite--door { width: 100%; height: auto; aspect-ratio: 1; transition: transform var(--ds-dur-base) var(--ds-ease); }
1608
+ .craft-door--artisan { --char: var(--char-artisan); }
1609
+ .craft-door--artist { --char: var(--char-artist); }
1610
+ .craft-door--patron { --char: var(--char-patron); }
1611
+ .craft-door--character .craft-door__name { color: var(--char); }
1599
1612
  .craft-door--character:hover .craft-sprite--door,
1600
1613
  .craft-door--character:focus-visible .craft-sprite--door { transform: scale(1.03); }
1601
1614
 
@@ -18,6 +18,21 @@ html[data-game="on"] .visor:not([hidden]) {
18
18
  color: var(--ink); font-family: var(--font-body);
19
19
  }
20
20
 
21
+ /* THE GLASS TAKES YOUR CHARACTER'S COLOUR (task 1005218). --char is the held character's one
22
+ colour (style.css --char-*): the frame's lines, the sight, your name and the rim of your
23
+ picture wear it, and the accent on the glass (the one button, the segment being built, a
24
+ hovered row) IS it, so nothing here names a colour of its own. The Artisan's is the hall's
25
+ accent, so an Artisan's glass is the accent it always was. */
26
+ .visor[data-character="artisan"] { --char: var(--char-artisan, var(--accent)); }
27
+ .visor[data-character="artist"] { --char: var(--char-artist, var(--accent)); }
28
+ .visor[data-character="patron"] { --char: var(--char-patron, var(--accent)); }
29
+ /* on the glass's parts, not the glass: --char reads the hall's accent above, so re-pointing
30
+ the accent on the same element would be a cycle */
31
+ .visor[data-character] > * {
32
+ --accent: var(--char); --accent-ink: var(--char);
33
+ --accent-lift: color-mix(in srgb, var(--char) 58%, var(--ink));
34
+ }
35
+
21
36
  /* the frame: each arc is stretched across the stage, every stroke one hairline at any size. The
22
37
  top arc, its notch and the place all sit inside the top inset, above where a scene starts its
23
38
  own top row; the foot arc runs under the foot row. */
@@ -25,9 +40,9 @@ html[data-game="on"] .visor:not([hidden]) {
25
40
  .visor__frame--top { top: calc(var(--visor-arc) - 50px); height: 42px; }
26
41
  .visor__frame--foot { bottom: calc(var(--visor-inset-bottom) - 44px); height: 18px; }
27
42
  .visor__frame path { vector-effect: non-scaling-stroke; }
28
- .visor__line { fill: none; stroke: var(--ink-soft); stroke-opacity: .6; stroke-width: var(--line-strong); }
43
+ .visor__line { fill: none; stroke: var(--char, var(--ink-soft)); stroke-opacity: .7; stroke-width: var(--line-strong); }
29
44
  .visor__line--under { stroke: var(--bg); stroke-opacity: 1; stroke-width: 5px; }
30
- .visor__dot { fill: var(--ink-soft); stroke: var(--bg); stroke-width: var(--line-strong); }
45
+ .visor__dot { fill: var(--char, var(--ink-soft)); stroke: var(--bg); stroke-width: var(--line-strong); }
31
46
  .visor__notch, .visor__sight { position: absolute; left: 50%; transform: translate(-50%, -50%); overflow: visible; }
32
47
  .visor__notch { top: calc(var(--visor-arc) - 36px); width: 12px; height: 9px; }
33
48
  .visor__sight { top: 50%; width: 24px; height: 24px; }
@@ -76,12 +91,12 @@ html[data-game="on"] .visor:not([hidden]) {
76
91
  transform: perspective(900px) rotateY(16deg); transform-origin: left center;
77
92
  }
78
93
  .visor__sprite {
79
- flex: none; width: 56px; height: 56px; object-fit: cover; object-position: 50% 12%;
80
- background: var(--glass-strong); clip-path: var(--cut-path-sm);
94
+ flex: none; box-sizing: border-box; width: 56px; height: 56px; object-fit: cover; image-rendering: pixelated;
95
+ background: var(--bg-card); border: var(--line-strong) solid var(--char, var(--ink-soft)); clip-path: var(--cut-path-sm);
81
96
  }
82
97
  /* your name and credits stand in a box of the night, cut like the sprite beside it */
83
98
  .visor__who { display: flex; flex-direction: column; gap: 2px; padding: var(--ds-space-hair) var(--ds-space-xs); }
84
- .visor__name { font-size: var(--ds-text-xs); letter-spacing: var(--name-space); line-height: 1; color: var(--ink-soft); }
99
+ .visor__name { font-size: var(--ds-text-xs); letter-spacing: var(--name-space); line-height: 1; color: var(--char, var(--ink-soft)); }
85
100
  .visor__credits { display: flex; align-items: baseline; gap: var(--ds-space-xs); white-space: nowrap; }
86
101
  .visor__num { font-family: var(--name-face); font-size: var(--ds-text-headline); font-weight: 400; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
87
102
  .visor__unit { font-size: var(--ds-text-md); letter-spacing: var(--name-space); color: var(--ink-soft); }
@@ -7,7 +7,8 @@
7
7
  // a small notch under the top arc and a ring-and-dot sight at the centre. Drawn as SVG;
8
8
  // it is ornament, never a control.
9
9
  // - the place, in the name voice under the top arc ("THE SKY", "GOVERNOR CITY", …).
10
- // - the foot row. Left: your sprite (third person), your character's name and your credits
10
+ // - the foot row. Left: your sprite (your character's head, wearing the visor, its rim in
11
+ // the character's colour; task 1005218), your character's name and your credits
11
12
  // (builders.total_credits — credits only, never a payout; the spec's "does not exist").
12
13
  // Centre: your character's main action, the one accent button, with its key. Right: the
13
14
  // quiet key hints, no boxes: your other actions, "Actions" (key A), which opens the
@@ -189,14 +190,16 @@
189
190
  }
190
191
  function glassHtml(id) {
191
192
  var acts = actionsFor(id), main = acts[0], C = craft();
192
- var sprite = C && typeof C.spriteSrc === 'function' ? C.spriteSrc(id) : '';
193
+ // Your picture on the glass is you wearing the visor (task 1005218); an older craft-mode
194
+ // without that picture still hands over the whole figure.
195
+ var sprite = C && typeof C.portraitSrc === 'function' ? C.portraitSrc(id) : (C && typeof C.spriteSrc === 'function' ? C.spriteSrc(id) : '');
193
196
  var n = credits();
194
197
  return frameSvg() +
195
198
  (scene.place ? '<p class="visor__place">' + esc(scene.place) + '</p>' : '') +
196
199
  progressHtml() +
197
200
  '<div class="visor__foot">' +
198
201
  '<div class="visor__me">' +
199
- (sprite ? '<img class="visor__sprite" src="' + esc(sprite) + '" alt="" width="250" height="290" decoding="async">' : '') +
202
+ (sprite ? '<img class="visor__sprite" src="' + esc(sprite) + '" alt="" width="32" height="32" decoding="async">' : '') +
200
203
  '<span class="visor__who"><span class="visor__name">' + esc(NAMES[id]) + '</span>' +
201
204
  '<span class="visor__credits" data-visor-credits>' + creditsHtml(n) + '</span></span>' +
202
205
  '</div>' +
@@ -0,0 +1,19 @@
1
+ # The characters — the three pictures, their colours, and where each is worn (task 1005218; ADR 0405)
2
+
3
+ Covers: `public/characters/*.png`, the character parts of `craft-mode.js` (creation doors, the top-bar chip), the `--char-*` tokens in `style.css`, and the character's colour on the visor (`visor.css`). The visor itself is in `visor.md`; character creation's behaviour is task 1005147's and is unchanged.
4
+
5
+ ## What it settled
6
+
7
+ - **The second look.** 64 by 64 pixel art, two animals in one body, as aliens in the Helm night. One animal gives the whole head, the other the whole body: Artisan a ram on a lizard with the long spanner, Artist an ibis on an octopus with the light-brush, Patron a falcon on a lion with the staff of office. They stand three-quarters to the viewer on a lit disc. The six rules the owner's feedback set (recognisable animals, not childish, a role tool, a 2.5D stance, simple shapes, one light) are the header of the drawing script.
8
+ - **The pictures are drawn, never edited.** `node docs/design/characters/v2/draw-characters.js --hall` writes all nine files: `<id>.png` (the whole figure, 64 by 64: the creation doors), `<id>-face.png` (the head, 32 by 32: the top-bar chip) and `<id>-visor.png` (the head wearing the visor, 32 by 32: the visor's corner). `craft-mode.js` hands them out as `spriteSrc`, `faceSrc` and `portraitSrc`.
9
+ - **One colour each** (`style.css`, on `html`, beside the sprite rules): `--char-artisan` is the accent, `--char-artist` a violet and `--char-patron` a gold, both mixed from the hall's own tokens (ADR 0405 §2). No colour is typed in.
10
+ - **Where the colour is worn.** The creation door's name; and, in game mode, the visor: `.visor[data-character]` sets `--char` and re-points `--accent`, `--accent-ink` and `--accent-lift` at it, so the frame's lines, the sight, the name, the picture's rim, the one button and the segment being built all follow with no rule of their own. An Artisan's glass is exactly what it was.
11
+ - **Pixel art scales by nearest pixel** (`image-rendering: pixelated`) everywhere a picture is shown.
12
+
13
+ ## Not done
14
+
15
+ - A scene's own buttons keep the hall's accent under a violet or gold glass (the Governor City's "decide" button). ADR 0403's follow-up tasks move them to the visor.
16
+ - The visor shows the 32px head at 56px (36px on a phone), which is not a whole multiple, so a few pixels are uneven.
17
+ - The pictures keep their own fixed colours when a host re-themes its accent.
18
+
19
+ Tests: `tests/character_creation.mjs` (the nine files, their sizes, the chip's picture) and `tests/hall_visor.mjs` (the portrait, the colour on the glass per character).