@bongos/core 1.21.113 → 1.21.114
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 +603 -183
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/bongos-feedback/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -0
- package/GOVERNMENT.md +128 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +28 -0
- package/clients/bongos-client/index.cjs +28 -0
- package/clients/bongos-client/index.d.ts +45 -2
- package/clients/bongos-client/index.mjs +28 -0
- package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
- package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
- package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
- package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
- package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
- package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
- package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
- package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
- package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +989 -87
- package/docs/api-reference.md +18 -4
- package/docs/copy-inventory.md +343 -262
- package/docs/copy-registry.json +1223 -426
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/packs/engineer.md +2 -1
- package/docs/page-inventory.json +91 -6
- package/docs/page-readings.json +5017 -4458
- package/modules/builder-settings/law-locks.js +202 -0
- package/modules/builder-settings/routes/settings.js +66 -6
- package/modules/builder-settings/settings.js +7 -0
- package/modules/discord/ship-broadcast.js +22 -13
- package/modules/government/asks-db.js +54 -0
- package/modules/government/asks.js +269 -0
- package/modules/government/board-idea-reads.js +68 -0
- package/modules/government/board.js +21 -57
- package/modules/government/build-rights.js +148 -0
- package/modules/government/buildouts.js +160 -0
- package/modules/government/counts.js +81 -0
- package/modules/government/db.js +242 -0
- package/modules/government/decision-board.js +234 -8
- package/modules/government/decision-rules.js +2 -0
- package/modules/government/docket.js +114 -6
- package/modules/government/law-board.js +131 -0
- package/modules/government/laws.js +241 -0
- package/modules/government/leads.js +170 -0
- package/modules/government/migrations/government_027_laws.sql +143 -0
- package/modules/government/migrations/government_028_asks.sql +57 -0
- package/modules/government/migrations/government_029_leads.sql +135 -0
- package/modules/government/migrations/government_030_buildouts.sql +111 -0
- package/modules/government/migrations/government_031_treasury.sql +107 -0
- package/modules/government/module.json +2 -1
- package/modules/government/pollers/treasury-borrow.js +62 -0
- package/modules/government/positions.js +107 -0
- package/modules/government/presets.js +149 -0
- package/modules/government/routes/government.js +425 -2
- package/modules/government/routing.js +154 -0
- package/modules/government/treasury-db.js +166 -0
- package/modules/government/treasury.js +241 -0
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/board-room.js +2 -0
- package/modules/hall-ui/public/city-draw.js +604 -11
- package/modules/hall-ui/public/city.css +157 -1
- package/modules/hall-ui/public/city.html +37 -7
- package/modules/hall-ui/public/city.js +441 -21
- package/modules/hall-ui/public/city.states.json +110 -5
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +112 -29
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagram-viewer.css +12 -0
- package/modules/hall-ui/public/diagram-viewer.js +4 -0
- package/modules/hall-ui/public/diagrams.html +2 -2
- package/modules/hall-ui/public/drachmae.html +2 -2
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/gov-feeds.js +141 -0
- package/modules/hall-ui/public/gov-living.js +1002 -0
- package/modules/hall-ui/public/gov-positions.js +97 -0
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +3 -0
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/my-agents.css +334 -0
- package/modules/hall-ui/public/my-agents.html +72 -0
- package/modules/hall-ui/public/my-agents.js +603 -0
- package/modules/hall-ui/public/my-agents.states.json +37 -0
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile-connections.js +40 -28
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/ranks.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/room.css +8 -0
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +116 -16
- package/modules/hall-ui/public/street-card.js +18 -1
- package/modules/hall-ui/public/street-deck.css +299 -0
- package/modules/hall-ui/public/street-deck.js +1145 -0
- package/modules/hall-ui/public/street-depth.js +712 -0
- package/modules/hall-ui/public/street-desk.js +5 -2
- package/modules/hall-ui/public/street-draw.js +109 -16
- package/modules/hall-ui/public/street-inside.js +137 -22
- package/modules/hall-ui/public/street-office.js +705 -0
- package/modules/hall-ui/public/street.css +161 -11
- package/modules/hall-ui/public/street.html +41 -11
- package/modules/hall-ui/public/street.js +78 -66
- package/modules/hall-ui/public/street.states.json +129 -21
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +5 -1
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/watch.js +14 -1
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/living-city.md +57 -0
- package/modules/hall-ui/records/my-agents.md +21 -0
- package/modules/hall-ui/records/oversight-family.md +1 -0
- package/modules/hall-ui/records/profile-connections.md +10 -0
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/ideas/routes/blockers.js +92 -3
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/moving.js +17 -1
- package/modules/lifecycle/routes/moving.js +6 -0
- package/modules/lifecycle/routes/project-settings.js +23 -0
- package/modules/onboarding/approval-broadcast.js +8 -0
- package/modules/onboarding/routes/access-requests.js +16 -6
- package/modules/platform-identity/connections.js +13 -2
- package/modules/platform-identity/routes/connections.js +7 -1
- package/modules/public-landing/public/account-private.states.json +2 -2
- package/modules/public-landing/public/account.html +65 -5
- package/modules/public-landing/public/account.states.json +3 -3
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
- package/modules/public-landing/public/assets/world.css +4 -4
- package/modules/public-landing/public/connections-empty.states.json +14 -0
- package/modules/public-landing/public/connections-inactive.states.json +14 -0
- package/modules/public-landing/public/connections.html +450 -0
- package/modules/public-landing/public/connections.probes.json +414 -0
- package/modules/public-landing/public/connections.states.json +17 -0
- package/modules/public-landing/public/contact.html +2 -2
- package/modules/public-landing/public/guild.html +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/privacy.html +2 -2
- package/modules/public-landing/public/projects.html +2 -2
- package/modules/public-landing/public/projects.probes.json +6 -2
- package/modules/public-landing/public/terms.html +2 -2
- package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
- package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
- package/modules/ui-design/kit/fixtures/connections.json +22 -0
- package/modules/ui-design/kit/serve.js +95 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +122 -0
- package/scripts/discord/README.md +8 -5
- package/scripts/discord/post.js +12 -1
- package/scripts/gds/artifact-audit.js +20 -3
- package/scripts/gds/ask.js +164 -0
- package/scripts/gds/autobongos-verify.js +21 -2
- package/scripts/gds/government-walk.js +638 -4
- package/scripts/gds/pulse.js +9 -6
- package/scripts/gds/ship-escalation.js +22 -3
- package/scripts/gds/ship-finish.js +5 -4
- package/scripts/gds/ship-merge-gate.js +18 -6
- package/scripts/hall-preview/README.md +41 -1
- package/scripts/hall-preview/pref-writes.js +140 -0
- package/scripts/hall-preview/refresh-fixtures.js +101 -7
- package/scripts/hall-preview/scrub-identity.js +366 -7
- package/scripts/hall-preview/server.js +27 -1
- package/scripts/render-diagrams.sh +4 -2
- package/src/bongos/auth-idp.js +6 -1
- package/src/bongos/project-settings.js +41 -0
- package/src/bongos/route-rank-check.js +27 -0
- package/src/bongos/routes/me.js +68 -7
- package/src/bongos/serve-internal.js +5 -1
- package/src/module-api.js +7 -1
- package/src/platform-server.js +10 -0
- package/tests/application_lifecycle.mjs +47 -5
- package/tests/conductor_main_e2e.mjs +21 -0
- package/tests/connections_api.mjs +18 -0
- package/tests/craft_mode_nav.mjs +5 -3
- package/tests/craft_mode_tallies.mjs +52 -16
- package/tests/discord_approvals.mjs +11 -0
- package/tests/discord_channels.mjs +20 -0
- package/tests/fixtures/gov-seated.mjs +64 -0
- package/tests/fixtures/government-ask-cards.json +1284 -0
- package/tests/government_act_alone_buildings.mjs +1 -0
- package/tests/government_agent_asks.mjs +322 -0
- package/tests/government_asks.mjs +408 -0
- package/tests/government_build_rights.mjs +253 -0
- package/tests/government_buildouts.mjs +382 -0
- package/tests/government_counts.mjs +143 -0
- package/tests/government_decision_rules.mjs +5 -1
- package/tests/government_laws.mjs +488 -0
- package/tests/government_laws_session.mjs +321 -0
- package/tests/government_leads.mjs +375 -0
- package/tests/government_positions.mjs +127 -0
- package/tests/government_presets.mjs +215 -0
- package/tests/government_routes.mjs +8 -1
- package/tests/government_routing.mjs +118 -0
- package/tests/government_seed.mjs +5 -0
- package/tests/government_treasury.mjs +394 -0
- package/tests/governor_docket.mjs +26 -2
- package/tests/hall_buildouts.mjs +208 -0
- package/tests/hall_city.mjs +14 -6
- package/tests/hall_city_living.mjs +617 -0
- package/tests/hall_gov_counts.mjs +336 -0
- package/tests/hall_gov_living.mjs +636 -0
- package/tests/hall_gov_real_site.mjs +655 -0
- package/tests/hall_matter_card.mjs +6 -1
- package/tests/hall_my_agents.mjs +481 -0
- package/tests/hall_nav.mjs +107 -4
- package/tests/hall_page_gate_map.mjs +4 -0
- package/tests/hall_preview_governor_fixtures.mjs +30 -3
- package/tests/hall_preview_scrub.mjs +59 -1
- package/tests/hall_preview_security.mjs +169 -0
- package/tests/hall_reading_world.mjs +44 -0
- package/tests/hall_roster_world.mjs +6 -2
- package/tests/hall_street.mjs +18 -2
- package/tests/hall_street_amend.mjs +1 -1
- package/tests/hall_street_deck.mjs +609 -0
- package/tests/hall_street_depth.mjs +559 -0
- package/tests/hall_street_desk.mjs +1 -1
- package/tests/hall_street_inside.mjs +29 -5
- package/tests/hall_tweak_editor.mjs +4 -1
- package/tests/hub_account_page.mjs +94 -3
- package/tests/hub_connections_page.mjs +507 -0
- package/tests/hub_invite_badge.mjs +1 -1
- package/tests/idp_device_exchange.mjs +18 -0
- package/tests/landing_legal_pages.mjs +8 -5
- package/tests/landing_page.mjs +23 -4
- package/tests/nav_permission_atoms.mjs +4 -1
- package/tests/profile_connections_ui.mjs +88 -25
- package/tests/projects_hub.mjs +15 -0
- package/tests/render_check.mjs +2 -2
- package/tests/ship_broadcast_routing.mjs +57 -16
- package/tests/ship_broadcast_server.mjs +40 -7
- package/tests/ship_merge_gate.mjs +19 -0
- package/tests/ui_design_kit.mjs +2 -2
- package/tests/watch_applications_queue.mjs +23 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
// modules/hall-ui/public/street-depth.js — THE LIVING STREET's exterior: its
|
|
2
|
+
// depth, as data, and the painters that put it on the page (task 1004685, the
|
|
3
|
+
// living-city prototype; carved out of street-draw.js and street.js).
|
|
4
|
+
//
|
|
5
|
+
// WHY ITS OWN FILE. The living street made street.js cross the 1,500-line
|
|
6
|
+
// ratchet and gave street-draw.js a require cycle with street-inside.js (the
|
|
7
|
+
// floor plan the needs-you windows light). So the living street's exterior is
|
|
8
|
+
// here, in the shape street-deck.js has: two halves.
|
|
9
|
+
//
|
|
10
|
+
// MODEL (pure: no DOM, no fetch, no clock, node-require-safe). The three
|
|
11
|
+
// planes over a street street-draw.js has already laid out: structureOf (a
|
|
12
|
+
// civic building's capsule floors and its construction), skylineOf (every
|
|
13
|
+
// open goal as a cluster on a hazed skyline), readingTiles (the six readings
|
|
14
|
+
// as one number and two words) and depthOf (the skylanes, the road, the
|
|
15
|
+
// sittings, the windows that need you, the plates, the key). street-draw.js
|
|
16
|
+
// finds this file on its global when a street is drawn deep (it never
|
|
17
|
+
// requires it, so there is no cycle); this file requires street-draw.js for
|
|
18
|
+
// the front-on kit and street-inside.js for the floor plan.
|
|
19
|
+
//
|
|
20
|
+
// PAGE (`page`, browser only). street.js hands it the street's data, its card
|
|
21
|
+
// opener and a redraw: pin(p) and sign(sg, tile) are the exterior's two
|
|
22
|
+
// controls, depth(dm, W) paints the planes, and loadLiving / livingModel read
|
|
23
|
+
// the living model's other feeds once through gov-feeds.js (the same reads the
|
|
24
|
+
// city, the deck and the shell's tally stand on). No innerHTML anywhere.
|
|
25
|
+
//
|
|
26
|
+
// Motion lives in street.css, and only says "this is live": no number here is
|
|
27
|
+
// a speed or a blink.
|
|
28
|
+
|
|
29
|
+
(function (root) {
|
|
30
|
+
'use strict';
|
|
31
|
+
const S = root.GovStreet || (typeof require === 'function' ? require('./street-draw.js') : null);
|
|
32
|
+
const City = root.GovCity || (typeof require === 'function' ? require('./city-draw.js') : null);
|
|
33
|
+
const push = S.push, rect = S.rect, ellipse = S.ellipse, box = S.box, money = S.money, count = S.count;
|
|
34
|
+
const G = S.G, CIVIC_KEYS = S.CIVIC_KEYS, MORE_KEY = S.MORE_KEY;
|
|
35
|
+
|
|
36
|
+
// ---------- THE DEPTH (task 1004685, the living-city prototype) ----------
|
|
37
|
+
// The street was one flat row; it now stands in three planes, front-on, in
|
|
38
|
+
// the street's own drawing style:
|
|
39
|
+
// BACK a skyline of EVERY open goal, one cluster each, its height from
|
|
40
|
+
// the goal's real rollup (its size, sqrt-scaled to the largest goal,
|
|
41
|
+
// times how much of it is built), in two rows the page hazes toward
|
|
42
|
+
// the void (the faces mixed 25% and 50% into the ground; the civic
|
|
43
|
+
// row is 0%). No detail: no windows, no names.
|
|
44
|
+
// MIDDLE the nine civic buildings with a deeper side face (DEPTH.side) and
|
|
45
|
+
// a height that is structure: one capsule floor plugs into the core
|
|
46
|
+
// for a seated Lead and each committee room (gov-living.js, over GET
|
|
47
|
+
// /government/leads), so a Lead's added floor makes the
|
|
48
|
+
// building taller. Under it, at street level, the Lead's name plate,
|
|
49
|
+
// or "owner" where the building is unseated.
|
|
50
|
+
// FRONT the road: an agent whose claim answers to a building but to no
|
|
51
|
+
// goal on the street works on the road in front of it, and the
|
|
52
|
+
// parcels (the crates at Review) stay where they were.
|
|
53
|
+
// SKY the skylanes: an agent whose claim belongs to a goal travels a
|
|
54
|
+
// lane between that goal's plot (or the plot that stands for the
|
|
55
|
+
// other goals) and the building it answers to. One pod per active
|
|
56
|
+
// claim, wherever it rides, at a fixed spacing, so they count.
|
|
57
|
+
// Construction is the city's: an authorised build-out is a live site (its
|
|
58
|
+
// finished floors, the scaffold at its % complete, a crane whose jib swings);
|
|
59
|
+
// a proposal waiting on a vote is a parked crane, a dashed-glass ghost annex
|
|
60
|
+
// and a tally arc of one dot per seat; an open sitting is a light shaft from
|
|
61
|
+
// the roof; and a matter that needs you lights SALMON windows on the floor
|
|
62
|
+
// that holds it (street-inside.js PLAN), with a ring that fills as it ages.
|
|
63
|
+
// Motion lives in street.css, and only says "this is live": no number here is
|
|
64
|
+
// a speed or a blink.
|
|
65
|
+
const DEPTH = Object.freeze({
|
|
66
|
+
side: 12, // the civic row's side face (the prototype's is 7)
|
|
67
|
+
cap: 13, // one capsule floor: an 11 px pod and its 2 px joint
|
|
68
|
+
siteFloors: 3, // an authorised build-out, finished
|
|
69
|
+
ghostFloors: 3, // a proposal's ghost annex, as tall as the build-out it would be
|
|
70
|
+
siteW: 40, // a build-out's footprint: wider than the core, so it reads as a site
|
|
71
|
+
craneRise: 30, // the crane's mast stands this far over the finished height
|
|
72
|
+
jib: 58, // the jib's reach from the mast
|
|
73
|
+
tallyR: 18, // the council's tally arc
|
|
74
|
+
skyTop: 236, // the first skylane's crest (just under the band's top, street.js BAND_TOP)
|
|
75
|
+
laneStep: 16, // each next lane crests this much lower
|
|
76
|
+
laneDrop: 30, // a lane's way back runs this much under its crest
|
|
77
|
+
podGap: 7, // pods on a skylane, in % of its loop
|
|
78
|
+
trainGap: 13, // pods on the road, in px
|
|
79
|
+
shaftTop: 200, // a sitting's light shaft reaches up to here
|
|
80
|
+
ageFull: 30, // days: an age ring is full at a month
|
|
81
|
+
period: 28, // s: one lap of a skylane (street.css street-depth-travel)
|
|
82
|
+
});
|
|
83
|
+
// Where a building's capsule floors plug in: the core's centre from the
|
|
84
|
+
// building's left edge, and the height its first floor sits at, on the
|
|
85
|
+
// recipe's own roof (a gable's apex, the sawtooth's teeth, Release's bridge).
|
|
86
|
+
const PLUG = Object.freeze({
|
|
87
|
+
charter: [42, 212], rules: [48, 58], disputes: [40, 100], people: [60, 54], planning: [40, 90],
|
|
88
|
+
automation: [62, 72], review: [43, 86], release: [46, 122], treasury: [42, 114],
|
|
89
|
+
});
|
|
90
|
+
// The face whose storeys stand for a building's floors (street-inside.js
|
|
91
|
+
// PLAN, bottom first): [x0, x1] from its left edge, [y0, y1] above the street.
|
|
92
|
+
const FACE = Object.freeze({
|
|
93
|
+
charter: [14, 70, 10, 136], rules: [8, 88, 8, 50], disputes: [25, 55, 4, 82], people: [41, 79, 6, 34],
|
|
94
|
+
planning: [3, 77, 6, 70], automation: [3, 77, 10, 52], review: [7, 79, 4, 80], release: [2, 24, 6, 146], treasury: [10, 74, 12, 90],
|
|
95
|
+
});
|
|
96
|
+
const CAP_W = Object.freeze({ lead: 30, committee: 24 });
|
|
97
|
+
const DECK_HREF = '/builders/street#deck';
|
|
98
|
+
|
|
99
|
+
// The street-inside.js floor plan, read when a street is drawn (it requires
|
|
100
|
+
// this file, so it is found on its global, or required late in node).
|
|
101
|
+
function floorPlan() {
|
|
102
|
+
const I = root.GovInside || (typeof require === 'function' ? require('./street-inside.js') : null);
|
|
103
|
+
return I && I.PLAN ? I.PLAN : {};
|
|
104
|
+
}
|
|
105
|
+
function floorOfSource(kind, source) {
|
|
106
|
+
const plan = floorPlan()[kind];
|
|
107
|
+
const floors = plan && Array.isArray(plan.floors) ? plan.floors : [];
|
|
108
|
+
for (let i = 0; i < floors.length; i++) if (floors[i][2] && floors[i][2].source === source) return { index: i, of: floors.length, name: floors[i][0] };
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// A building's structure, standing on its recipe's roof: the core, one
|
|
113
|
+
// capsule floor per seat, then what is being built on top. Returns the
|
|
114
|
+
// polys, the site and proposal parts the page draws in front (the crane,
|
|
115
|
+
// the tally), and the heights above the street.
|
|
116
|
+
function structureOf(T, kind, x, b, ctx) {
|
|
117
|
+
const polys = [];
|
|
118
|
+
const plug = PLUG[kind];
|
|
119
|
+
const cx = x + plug[0];
|
|
120
|
+
const floors = [];
|
|
121
|
+
if (b) {
|
|
122
|
+
if (b.lead) floors.push({ role: 'lead' });
|
|
123
|
+
((b.structure && b.structure.committees) || []).forEach(() => floors.push({ role: 'committee' }));
|
|
124
|
+
}
|
|
125
|
+
let h = plug[1];
|
|
126
|
+
if (floors.length) {
|
|
127
|
+
const coreTop = h + floors.length * DEPTH.cap + 4;
|
|
128
|
+
push(polys, rect(cx - 3, G - coreTop, 6, coreTop - h + 6), T.civic.r, T.civic.s);
|
|
129
|
+
floors.forEach((f, i) => {
|
|
130
|
+
const cw = CAP_W[f.role];
|
|
131
|
+
const off = i % 2 ? 3 : -3;
|
|
132
|
+
const z = h + i * DEPTH.cap + 1;
|
|
133
|
+
const x0 = cx - cw / 2 + off;
|
|
134
|
+
box(polys, T, x0, G, cw, 11, T.civic, { z: z, d: 5 });
|
|
135
|
+
// its window: a Lead's office is a strip, a committee two portholes
|
|
136
|
+
const wy = G - z - 5.5;
|
|
137
|
+
if (f.role === 'lead') push(polys, rect(x0 + 5, wy - 1.5, cw - 10, 3), T.win, 'none');
|
|
138
|
+
else push(polys, ellipse(cx + off - 5, wy, 2.2, 2.2) + ellipse(cx + off + 5, wy, 2.2, 2.2), T.win, 'none');
|
|
139
|
+
});
|
|
140
|
+
h += floors.length * DEPTH.cap;
|
|
141
|
+
}
|
|
142
|
+
const stack = h;
|
|
143
|
+
// the top capsule's own top face (d 5) is part of the building's height
|
|
144
|
+
let top = floors.length ? h + 5 : h;
|
|
145
|
+
const cons = (b && Array.isArray(b.construction)) ? b.construction : [];
|
|
146
|
+
const siteOf = cons.find((c) => c && c.kind === 'buildout' && c.authorised);
|
|
147
|
+
const propOf = cons.find((c) => c && !c.authorised);
|
|
148
|
+
let site = null;
|
|
149
|
+
let proposal = null;
|
|
150
|
+
// Construction reads at a glance and is the city's family: the same parts at the
|
|
151
|
+
// same proportions (a footprint wider than the core, a lattice mast standing
|
|
152
|
+
// clear of it, a jib with its ties and counterweight, a hook and its load).
|
|
153
|
+
const fw = DEPTH.siteW;
|
|
154
|
+
if (siteOf) {
|
|
155
|
+
// a live site: the finished floors, the scaffold up to its share built, a crane past the finished height
|
|
156
|
+
const p = siteOf.progress == null ? 0 : Math.max(0, Math.min(1, Number(siteOf.progress) || 0));
|
|
157
|
+
const target = DEPTH.siteFloors * DEPTH.cap;
|
|
158
|
+
const built = Math.floor(p * DEPTH.siteFloors + 1e-9);
|
|
159
|
+
for (let i = 0; i < built; i++) box(polys, T, cx - fw / 2, G, fw, 11, T.civic, { z: h + i * DEPTH.cap + 1, d: 5, fins: 3 });
|
|
160
|
+
const sh = Math.max(4, Math.round(p * target));
|
|
161
|
+
box(polys, T, cx - fw / 2 - 3, G, fw + 6, sh, T.scaffold, { z: h, d: 5, brace: true });
|
|
162
|
+
const mx = cx + fw / 2 + 10, base = G - h, mastTop = base - target - DEPTH.craneRise, hookY = base - sh - 12;
|
|
163
|
+
let mast = 'M' + (mx - 2.5) + ' ' + base + 'V' + mastTop + 'M' + (mx + 2.5) + ' ' + base + 'V' + mastTop;
|
|
164
|
+
for (let y = base - 8, k = 0; y > mastTop + 6; y -= 8, k++) mast += 'M' + (mx - 2.5) + ' ' + y + 'L' + (mx + 2.5) + ' ' + (k % 2 ? y + 8 : y - 8);
|
|
165
|
+
mast += rect(mx - 4, mastTop + 1, 8, 6);
|
|
166
|
+
const tip = mx - DEPTH.jib, tail = mx + 18;
|
|
167
|
+
site = {
|
|
168
|
+
key: 'site:' + kind, building: kind, label: String(siteOf.label || ''), progress: p,
|
|
169
|
+
mast: mast,
|
|
170
|
+
jib: 'M' + tip + ' ' + mastTop + 'H' + tail + 'M' + tip + ' ' + mastTop + 'L' + mx + ' ' + (mastTop - 12) + 'L' + tail + ' ' + mastTop
|
|
171
|
+
+ 'M' + (tip + 14) + ' ' + mastTop + 'L' + (tip + 14) + ' ' + (mastTop + 3) + 'M' + (tip + 28) + ' ' + mastTop + 'L' + (tip + 28) + ' ' + (mastTop + 3),
|
|
172
|
+
weight: rect(tail - 9, mastTop, 9, 7),
|
|
173
|
+
line: 'M' + (tip + 8) + ' ' + mastTop + 'V' + hookY,
|
|
174
|
+
slab: rect(tip + 1, hookY, 16, 4),
|
|
175
|
+
ox: mx, oy: mastTop, hx: tip + 8, lift: Math.round((hookY - mastTop) / 2),
|
|
176
|
+
platform: { x: cx - fw / 2 - 2, y: base - sh, w: fw + 4, rise: Math.max(0, sh - 3) },
|
|
177
|
+
};
|
|
178
|
+
h += target;
|
|
179
|
+
top = Math.max(top, h + DEPTH.craneRise + 14);
|
|
180
|
+
}
|
|
181
|
+
if (propOf) {
|
|
182
|
+
// a proposal waiting on a vote: a dashed-glass ghost annex, a parked crane, a tally arc
|
|
183
|
+
const gh = DEPTH.ghostFloors * DEPTH.cap;
|
|
184
|
+
const ghost = { f: T.glass.f, r: T.glass.r, t: T.glass.t, s: T.glass.s, dash: '3 3' };
|
|
185
|
+
box(polys, T, cx - fw / 2, G, fw, gh, ghost, { z: h, d: 5, grid: 3 });
|
|
186
|
+
const base = G - h, mx = cx + fw / 2 + 10, mastTop = base - gh - 20;
|
|
187
|
+
const t = ctx.tallyOf(propOf);
|
|
188
|
+
const r = DEPTH.tallyR, acx = cx, acy = base - gh - 14;
|
|
189
|
+
const dots = [];
|
|
190
|
+
for (let i = 0; i < t.seats; i++) {
|
|
191
|
+
const a = Math.PI * (1 - (t.seats === 1 ? 0.5 : i / (t.seats - 1)));
|
|
192
|
+
dots.push({ cx: Math.round((acx + r * Math.cos(a)) * 10) / 10, cy: Math.round((acy - r * Math.sin(a)) * 10) / 10, vote: i < t.for ? 'for' : i < t.for + t.against ? 'against' : 'open' });
|
|
193
|
+
}
|
|
194
|
+
let mast = 'M' + (mx - 2.5) + ' ' + base + 'V' + mastTop + 'M' + (mx + 2.5) + ' ' + base + 'V' + mastTop + 'M' + (mx - 2.5) + ' ' + mastTop + 'H' + (mx + 2.5);
|
|
195
|
+
for (let y = base - 8, k = 0; y > mastTop + 6; y -= 8, k++) mast += 'M' + (mx - 2.5) + ' ' + y + 'L' + (mx + 2.5) + ' ' + (k % 2 ? y + 8 : y - 8);
|
|
196
|
+
proposal = {
|
|
197
|
+
key: 'proposal:' + kind, building: kind, label: String(propOf.label || ''),
|
|
198
|
+
// parked: the jib folded down along the mast, no hook, no load
|
|
199
|
+
crane: mast + 'M' + mx + ' ' + mastTop + 'L' + (mx - 9) + ' ' + (mastTop + 30) + 'M' + mx + ' ' + mastTop + 'L' + (mx + 7) + ' ' + (mastTop - 7),
|
|
200
|
+
arc: 'M' + (acx - r) + ' ' + acy + 'A' + r + ' ' + r + ' 0 0 1 ' + (acx + r) + ' ' + acy,
|
|
201
|
+
dots: dots, tally: t,
|
|
202
|
+
};
|
|
203
|
+
h += gh;
|
|
204
|
+
top = Math.max(top, h + 14 + r + 4);
|
|
205
|
+
}
|
|
206
|
+
return { polys: polys, cx: cx, floors: floors, stack: stack, top: top, site: site, proposal: proposal };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Every open goal as a cluster on the skyline, in two rows (near, far): the
|
|
210
|
+
// tallest tower of the largest, most-built goal stands SKY_MAX high.
|
|
211
|
+
const SKY_MIN = 24, SKY_MAX = 196;
|
|
212
|
+
function skylineOf(goals, byGoal, W) {
|
|
213
|
+
const total = (g) => {
|
|
214
|
+
const r = byGoal[String(g.id)];
|
|
215
|
+
return count(r, 'total') || (count(r, 'done') + count(r, 'active') + count(r, 'ready') + count(r, 'blocked') + count(r, 'backlog'));
|
|
216
|
+
};
|
|
217
|
+
const max = Math.max(1, ...goals.map(total), 1);
|
|
218
|
+
const rows = { near: { face: '', side: '' }, far: { face: '', side: '' } };
|
|
219
|
+
const clusters = [];
|
|
220
|
+
const lists = [['near', goals.filter((_, i) => i % 2 === 0), 0.5], ['far', goals.filter((_, i) => i % 2 === 1), 1]];
|
|
221
|
+
lists.forEach((l) => {
|
|
222
|
+
const row = l[0], list = l[1], shift = l[2];
|
|
223
|
+
const slot = W / Math.max(1, list.length);
|
|
224
|
+
list.forEach((g, j) => {
|
|
225
|
+
const tot = total(g), done = count(byGoal[String(g.id)], 'done');
|
|
226
|
+
const seed = String(g.id).split('').reduce((a, c) => (a * 31 + c.charCodeAt(0)) % 9973, 7);
|
|
227
|
+
// never above the Charter's own height, so the civic row stays the subject
|
|
228
|
+
const H = Math.round(SKY_MIN + (SKY_MAX - SKY_MIN) * Math.sqrt(tot / max) * (0.45 + 0.55 * (tot ? Math.min(1, done / tot) : 0)));
|
|
229
|
+
const n = 2 + Math.min(2, Math.floor(tot / 60));
|
|
230
|
+
const tw = [];
|
|
231
|
+
for (let t = 0; t < n; t++) tw.push(16 + Math.round(City.rnd(seed + t * 7) * 14));
|
|
232
|
+
const width = tw.reduce((a, b) => a + b, 0) + (n - 1) * 3;
|
|
233
|
+
const cx = (((j + shift) * slot) % W + W) % W;
|
|
234
|
+
let x = Math.round(cx - width / 2);
|
|
235
|
+
const x0 = x;
|
|
236
|
+
const tallest = Math.floor(City.rnd(seed + 3) * n) % n;
|
|
237
|
+
tw.forEach((w, t) => {
|
|
238
|
+
const h = t === tallest ? H : Math.max(14, Math.round(H * (0.45 + 0.4 * City.rnd(seed + t * 11 + 5))));
|
|
239
|
+
const y = G - h, d = 6;
|
|
240
|
+
rows[row].face += rect(x, y, w, h) + 'M' + x + ' ' + y + 'L' + (x + d) + ' ' + (y - d) + 'L' + (x + w + d) + ' ' + (y - d) + 'L' + (x + w) + ' ' + y + 'Z';
|
|
241
|
+
rows[row].side += 'M' + (x + w) + ' ' + y + 'L' + (x + w + d) + ' ' + (y - d) + 'L' + (x + w + d) + ' ' + (G - d) + 'L' + (x + w) + ' ' + G + 'Z';
|
|
242
|
+
x += w + 3;
|
|
243
|
+
});
|
|
244
|
+
clusters.push({ goal: String(g.id), row: row, x0: x0, x1: x, h: H, total: tot, done: done });
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
const paths = (r) => (rows[r].face ? [{ d: rows[r].side, c: 'street-depth-sil__side' }, { d: rows[r].face, c: 'street-depth-sil__face' }] : []);
|
|
248
|
+
return { near: paths('near'), far: paths('far'), clusters: clusters };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// The six readings as the desk's tiles: one number and two words, or null
|
|
252
|
+
// (hidden) at zero or when nothing real holds the number. The living model
|
|
253
|
+
// drives the four it can read (flow: agents working; money: spend this
|
|
254
|
+
// month; people: the roster; rules: the laws in force); promises and
|
|
255
|
+
// fairness are the docket's own readings.
|
|
256
|
+
function readingTiles(docket, living, roster) {
|
|
257
|
+
const reading = {};
|
|
258
|
+
(docket && Array.isArray(docket.readings) ? docket.readings : []).forEach((r) => { if (r && r.key) reading[r.key] = r; });
|
|
259
|
+
const live = (k) => (reading[k] && reading[k].status === 'live' ? reading[k] : null);
|
|
260
|
+
const num = (v) => (typeof v === 'number' && isFinite(v) ? v : null);
|
|
261
|
+
const tile = (n, one, many, title) => (n == null || n === 0 || n === '$0' ? null : { n: typeof n === 'number' ? n.toLocaleString('en-US') : String(n), words: n === 1 ? one : many, title: String(title || '') });
|
|
262
|
+
// a live reading's own figure, split into its number and the rest
|
|
263
|
+
const fromFigure = (k) => {
|
|
264
|
+
const r = live(k);
|
|
265
|
+
const m = r && /^\s*(\$?[\d,.]+)\s+(.+)$/.exec(String(r.figure || ''));
|
|
266
|
+
// a figure that is not one number and at most two words is no tile
|
|
267
|
+
return m && !/^\$?0$/.test(m[1]) && m[2].trim().split(/\s+/).length <= 2 && !/\d/.test(m[2]) ? { n: m[1], words: m[2].trim(), title: String(r.line || '') } : null;
|
|
268
|
+
};
|
|
269
|
+
const city = living && living.city ? living.city : null;
|
|
270
|
+
const out = {};
|
|
271
|
+
// flow: the agents working right now (the count every surface prints, gov-living.js)
|
|
272
|
+
const atWork = city && city.counts ? num(city.counts.agents_at_work) : city && city.traffic ? num(city.traffic.agents) : null;
|
|
273
|
+
out.flow = city && (city.counts || city.traffic) ? tile(atWork, 'agent working', 'agents working', 'active claims right now') : fromFigure('flow');
|
|
274
|
+
// promises: the criteria the goals say they deliver, kept
|
|
275
|
+
const pv = live('promises') && live('promises').value;
|
|
276
|
+
out.promises = pv && typeof pv === 'object' ? tile(num(pv.kept), 'promise kept', 'promises kept', live('promises').figure) : fromFigure('promises');
|
|
277
|
+
// money: the AI spend this month
|
|
278
|
+
const spend = city && city.spend ? num(city.spend.month_usd) : null;
|
|
279
|
+
out.money = spend != null ? tile(spend > 0 ? money(spend) : 0, 'this month', 'this month', 'spent on AI this month') : fromFigure('money');
|
|
280
|
+
// people: who builds here
|
|
281
|
+
const list = roster && Array.isArray(roster.builders) ? roster.builders : null;
|
|
282
|
+
const active = list ? list.filter((b) => b && (b.status == null || b.status === 'active')).length : null;
|
|
283
|
+
out.people = active != null ? tile(active, 'builder here', 'builders here', 'active builders on the roster') : fromFigure('people');
|
|
284
|
+
// fairness: the disputes still open
|
|
285
|
+
const fv = live('fairness') && num(live('fairness').value);
|
|
286
|
+
const fd = docket && Array.isArray(docket.matters) ? docket.matters.filter((m) => m && m.building === 'disputes' && m.state !== 'settled').length : null;
|
|
287
|
+
out.fairness = fv != null ? tile(fv, 'dispute open', 'disputes open', live('fairness').line) : fd != null ? tile(fd, 'dispute open', 'disputes open', 'open at Disputes') : null;
|
|
288
|
+
// rules: the laws in force (GET /government/laws, and the rule the project runs)
|
|
289
|
+
const laws = city && Array.isArray(city.laws) ? city.laws : null;
|
|
290
|
+
out.rules = laws ? tile(laws.length, 'standing law', 'standing laws', laws.map((l) => l.text).join(' · ')) : fromFigure('rules');
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// The depth layers over a street already laid out (see the header above).
|
|
295
|
+
function depthOf(c) {
|
|
296
|
+
const segs = c.segs, at = c.at, roofs = c.roofs, struct = c.struct, living = c.living, docket = c.docket;
|
|
297
|
+
const openGoal = {};
|
|
298
|
+
c.goals.forEach((g) => { openGoal[String(g.id)] = true; });
|
|
299
|
+
const W = c.W;
|
|
300
|
+
const sky = skylineOf(c.goals, c.byGoal, W);
|
|
301
|
+
|
|
302
|
+
// the plates: the Lead's name, or the owner where nobody holds it
|
|
303
|
+
const plates = [];
|
|
304
|
+
const viewerId = living && living.viewer ? String(living.viewer.id) : null;
|
|
305
|
+
if (living) {
|
|
306
|
+
segs.forEach((s) => {
|
|
307
|
+
if (!s.k) return;
|
|
308
|
+
const b = c.lb[s.k];
|
|
309
|
+
if (!b) return;
|
|
310
|
+
const lead = b.lead;
|
|
311
|
+
const holder = lead || b.holder;
|
|
312
|
+
if (!holder) return;
|
|
313
|
+
const you = viewerId != null && String(holder.id) === viewerId;
|
|
314
|
+
const text = lead ? (you ? 'you' : String(lead.name || lead.login || 'a Lead')) : (you ? 'you · owner' : 'owner');
|
|
315
|
+
plates.push({ key: 'plate:' + s.k, building: s.k, x: s.x - 3, y: G + 3, w: s.w + 6, text: text, seat: lead ? 'lead' : 'owner', you: you });
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// the agents: one pod per active claim
|
|
320
|
+
const claims = living && living.city && living.city.traffic && Array.isArray(living.city.traffic.claims) ? living.city.traffic.claims : [];
|
|
321
|
+
const lanes = [];
|
|
322
|
+
const laneBy = {};
|
|
323
|
+
const trains = {};
|
|
324
|
+
claims.forEach((cl) => {
|
|
325
|
+
const b = cl && cl.building && at[cl.building] ? cl.building : null;
|
|
326
|
+
// its goal's own plot, or the plot that stands for the open goals past the eighth
|
|
327
|
+
const plot = cl && cl.goal_id != null && openGoal[String(cl.goal_id)] ? (at['d:' + cl.goal_id] || at['d:' + MORE_KEY] || null) : null;
|
|
328
|
+
if (b && plot) {
|
|
329
|
+
const k = plot.d + '>' + b;
|
|
330
|
+
if (!laneBy[k]) { laneBy[k] = { key: 'lane:' + k, from: plot.d, building: b, n: 0 }; lanes.push(laneBy[k]); }
|
|
331
|
+
laneBy[k].n += 1;
|
|
332
|
+
} else {
|
|
333
|
+
const k = b || '*';
|
|
334
|
+
trains[k] = (trains[k] || 0) + 1;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
lanes.sort((a, b) => at[a.building].x - at[b.building].x || a.key.localeCompare(b.key));
|
|
338
|
+
const pods = [];
|
|
339
|
+
lanes.forEach((ln, i) => {
|
|
340
|
+
const from = at['d:' + ln.from], to = at[ln.building];
|
|
341
|
+
const sx = Math.round(from.x + from.w / 2), sy = G - (roofs['d:' + ln.from] || 0) - 4;
|
|
342
|
+
const ex = struct[ln.building] ? Math.round(struct[ln.building].cx) : Math.round(to.x + to.w / 2), ey = G - (struct[ln.building] ? struct[ln.building].stack : roofs[ln.building]) - 3;
|
|
343
|
+
const yA = DEPTH.skyTop + i * DEPTH.laneStep, yB = yA + DEPTH.laneDrop;
|
|
344
|
+
ln.d = 'M' + sx + ' ' + sy + 'C' + sx + ' ' + yA + ' ' + ex + ' ' + yA + ' ' + ex + ' ' + ey + 'C' + ex + ' ' + yB + ' ' + sx + ' ' + yB + ' ' + sx + ' ' + sy;
|
|
345
|
+
const gap = Math.min(DEPTH.podGap, 100 / ln.n);
|
|
346
|
+
for (let j = 0; j < ln.n; j++) {
|
|
347
|
+
const pct = Math.round((25 + (j - (ln.n - 1) / 2) * gap) * 100) / 100;
|
|
348
|
+
const atPct = ((pct % 100) + 100) % 100;
|
|
349
|
+
pods.push({ key: ln.key + ':' + j, lane: ln.key, path: ln.d, at: atPct, delay: -Math.round(atPct / 100 * DEPTH.period * 100) / 100 });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
const road = Object.keys(trains).map((k) => {
|
|
353
|
+
const s = k === '*' ? null : at[k];
|
|
354
|
+
const n = trains[k];
|
|
355
|
+
const len = (n - 1) * DEPTH.trainGap + 9;
|
|
356
|
+
const x0 = s ? s.x + 4 : 43, x1 = s ? s.x + s.w - 4 : W - 43;
|
|
357
|
+
const half = len / 2;
|
|
358
|
+
const a = Math.min(x0 + half, (x0 + x1) / 2), b = Math.max(x1 - half, (x0 + x1) / 2);
|
|
359
|
+
return { key: 'road:' + k, building: s ? k : null, n: n, path: 'M' + a + ' ' + (G + 22) + 'H' + b, w: len, gap: DEPTH.trainGap };
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// an open sitting: a light shaft from the roof (a board sitting still open,
|
|
363
|
+
// or a building whose way of deciding is being voted)
|
|
364
|
+
const sitting = {};
|
|
365
|
+
(docket && Array.isArray(docket.matters) ? docket.matters : []).forEach((m) => {
|
|
366
|
+
if (m && m.source === 'board' && m.state !== 'settled' && at[m.building]) sitting[m.building] = true;
|
|
367
|
+
});
|
|
368
|
+
(docket && Array.isArray(docket.buildings) ? docket.buildings : []).forEach((b) => {
|
|
369
|
+
if (b && b.decision && b.decision.sitting && at[b.key]) sitting[b.key] = true;
|
|
370
|
+
});
|
|
371
|
+
const shafts = CIVIC_KEYS.filter((k) => sitting[k]).map((k) => {
|
|
372
|
+
const cx = struct[k] ? struct[k].cx : at[k].x + at[k].w / 2;
|
|
373
|
+
const y1 = G - (struct[k] ? struct[k].stack : roofs[k]) + 4;
|
|
374
|
+
return { key: 'shaft:' + k, building: k, x: Math.round(cx - 10), y: DEPTH.shaftTop, w: 20, h: Math.max(40, y1 - DEPTH.shaftTop) };
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// what needs you lights the windows of the floor that holds it (c.matters: the
|
|
378
|
+
// matters as the living street sees them, a seat's asks for someone else in ink)
|
|
379
|
+
const windows = [];
|
|
380
|
+
const byFloor = {};
|
|
381
|
+
(Array.isArray(c.matters) ? c.matters : docket && Array.isArray(docket.matters) ? docket.matters : []).forEach((m) => {
|
|
382
|
+
if (!m || m.state !== 'needs' || !at[m.building] || !FACE[m.building]) return;
|
|
383
|
+
const f = floorOfSource(m.building, m.source);
|
|
384
|
+
if (!f) return;
|
|
385
|
+
const k = m.building + ':' + f.index;
|
|
386
|
+
(byFloor[k] = byFloor[k] || { building: m.building, floor: f, matters: [] }).matters.push(m);
|
|
387
|
+
});
|
|
388
|
+
Object.keys(byFloor).sort().forEach((k) => {
|
|
389
|
+
const g = byFloor[k];
|
|
390
|
+
const s = at[g.building], face = FACE[g.building];
|
|
391
|
+
const y = Math.round(G - (face[2] + (g.floor.index + 0.5) * (face[3] - face[2]) / g.floor.of));
|
|
392
|
+
const ringX = s.x + face[0] + 4;
|
|
393
|
+
const room = Math.max(1, Math.floor((face[1] - face[0] - 10) / 7));
|
|
394
|
+
const n = Math.min(g.matters.length, room);
|
|
395
|
+
let d = '';
|
|
396
|
+
for (let i = 0; i < n; i++) d += rect(s.x + face[0] + 10 + i * 7, y - 1.5, 4, 3);
|
|
397
|
+
const opened = g.matters.map((m) => (m.opened_at ? Date.parse(m.opened_at) : NaN)).filter((t) => isFinite(t));
|
|
398
|
+
const age = opened.length && c.now ? Math.max(0, Math.floor((c.now - Math.min(...opened)) / 86400000)) : null;
|
|
399
|
+
const frac = age == null ? 0 : Math.min(1, age / DEPTH.ageFull);
|
|
400
|
+
const r = 3.6;
|
|
401
|
+
let arc = '';
|
|
402
|
+
if (frac >= 1) arc = ellipse(ringX, y, r, r);
|
|
403
|
+
else if (frac > 0) {
|
|
404
|
+
const a = -Math.PI / 2 + frac * 2 * Math.PI;
|
|
405
|
+
arc = 'M' + ringX + ' ' + Math.round((y - r) * 100) / 100 + 'A' + r + ' ' + r + ' 0 ' + (frac > 0.5 ? 1 : 0) + ' 1 ' + Math.round((ringX + r * Math.cos(a)) * 100) / 100 + ' ' + Math.round((y + r * Math.sin(a)) * 100) / 100;
|
|
406
|
+
}
|
|
407
|
+
windows.push({ key: 'needs:' + k, building: g.building, floor: g.floor.name, count: g.matters.length, lit: n, d: d, ring: ellipse(ringX, y, r, r), arc: arc, age: age, frac: Math.round(frac * 1000) / 1000 });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// where agents are at work the road is lit: a pool under the building or the
|
|
411
|
+
// district, as strong as it is busy (an ink wash on paper: paper cannot glow)
|
|
412
|
+
const pools = [];
|
|
413
|
+
if (living) {
|
|
414
|
+
const byDistrict = {};
|
|
415
|
+
(Array.isArray(living.districts) ? living.districts : []).forEach((d) => { if (d && d.goal_id != null) byDistrict[String(d.goal_id)] = d; });
|
|
416
|
+
segs.forEach((s) => {
|
|
417
|
+
const b = s.k ? c.lb[s.k] : byDistrict[String(s.d)];
|
|
418
|
+
const a = b ? Math.max(0, Math.min(1, Number(b.activity) || 0)) : 0;
|
|
419
|
+
if (a > 0) pools.push({ key: 'pool:' + (s.k || 'd:' + s.d), building: s.k || null, x: Math.round(s.x - 14), y: G - 10, w: Math.round(s.w + 28), a: Math.round(a * 100) / 100 });
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
const sites = CIVIC_KEYS.map((k) => struct[k] && struct[k].site).filter(Boolean);
|
|
423
|
+
const proposals = CIVIC_KEYS.map((k) => struct[k] && struct[k].proposal).filter(Boolean);
|
|
424
|
+
const capsules = CIVIC_KEYS.filter((k) => struct[k] && struct[k].floors.length).map((k) => ({ building: k, floors: struct[k].floors.length, roles: struct[k].floors.map((f) => f.role) }));
|
|
425
|
+
const tiles = readingTiles(docket, living, c.roster);
|
|
426
|
+
const roadN = road.reduce((a, t) => a + t.n, 0);
|
|
427
|
+
const label = (sky.clusters.length ? 'Behind the street, ' + (sky.clusters.length === 1 ? 'one goal stands' : sky.clusters.length + ' goals stand') + ' as a skyline. ' : '')
|
|
428
|
+
+ (claims.length ? (claims.length === 1 ? 'One agent is' : claims.length + ' agents are') + ' at work: ' + pods.length + ' on the skylanes, ' + roadN + ' on the road.' : 'No agent is at work.');
|
|
429
|
+
return {
|
|
430
|
+
far: sky.far, near: sky.near, clusters: sky.clusters,
|
|
431
|
+
lanes: lanes.map((l) => ({ key: l.key, from: l.from, building: l.building, n: l.n, d: l.d })), pods: pods, road: road,
|
|
432
|
+
shafts: shafts, windows: windows, sites: sites, proposals: proposals, capsules: capsules, plates: plates, pools: pools,
|
|
433
|
+
tiles: tiles, agents: claims.length,
|
|
434
|
+
label: label, period: DEPTH.period,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// =====================================================================
|
|
439
|
+
// THE PAGE HALF. street.js calls these with its own data and callbacks;
|
|
440
|
+
// nothing here runs until it does, so node can require the model above.
|
|
441
|
+
// =====================================================================
|
|
442
|
+
const page = (function () {
|
|
443
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
444
|
+
// the page's document (a global in the browser and in the tests' vm alike)
|
|
445
|
+
const doc = () => (typeof document !== 'undefined' ? document : root.document);
|
|
446
|
+
const $ = (id) => doc().getElementById(id);
|
|
447
|
+
function clear(el) { while (el && el.firstChild) el.removeChild(el.firstChild); }
|
|
448
|
+
function node(tag, cls, text) {
|
|
449
|
+
const el = doc().createElement(tag);
|
|
450
|
+
if (cls) el.className = cls;
|
|
451
|
+
if (text != null) el.textContent = String(text);
|
|
452
|
+
return el;
|
|
453
|
+
}
|
|
454
|
+
function svgEl(tag) { return doc().createElementNS(SVG_NS, tag); }
|
|
455
|
+
function at(el, x, y, w, h) {
|
|
456
|
+
el.style.left = x + 'px';
|
|
457
|
+
el.style.top = y + 'px';
|
|
458
|
+
if (w != null) el.style.width = w + 'px';
|
|
459
|
+
if (h != null) el.style.height = h + 'px';
|
|
460
|
+
return el;
|
|
461
|
+
}
|
|
462
|
+
// a CSS offset path from path data this file computed; anything but path data is refused
|
|
463
|
+
function offsetPathOf(d) { return /^[MLHVCQAZmlhvcqaz0-9 .,-]+$/.test(String(d)) ? "path('" + d + "')" : "none"; }
|
|
464
|
+
function pathEl(p) {
|
|
465
|
+
const el = svgEl('path');
|
|
466
|
+
el.setAttribute('d', p.d);
|
|
467
|
+
el.setAttribute('fill', p.f);
|
|
468
|
+
el.setAttribute('stroke', p.s);
|
|
469
|
+
if (p.dash && p.dash !== 'none') el.setAttribute('stroke-dasharray', p.dash);
|
|
470
|
+
el.setAttribute('stroke-width', String(p.w || 1));
|
|
471
|
+
return el;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// One pin per building (task 1004569): its tier is its class, a label shows
|
|
475
|
+
// only when the collision pass placed it (street-pin--shown; the rest show
|
|
476
|
+
// on hover or focus), and a count badge says how many matters are open there.
|
|
477
|
+
// env { open: the matter whose card is open, openCard(key, el, keys) }
|
|
478
|
+
function pin(p, env) {
|
|
479
|
+
const keys = p.matters && p.matters.length ? p.matters : [p.key];
|
|
480
|
+
const el = node('button', 'street-pin street-pin--' + p.state + (p.shown ? ' street-pin--shown' : ''));
|
|
481
|
+
el.setAttribute('type', 'button');
|
|
482
|
+
el.setAttribute('aria-label', p.aria);
|
|
483
|
+
el.setAttribute('aria-expanded', keys.indexOf(env.open) >= 0 ? 'true' : 'false');
|
|
484
|
+
el.setAttribute('aria-controls', 'streetCard');
|
|
485
|
+
el.setAttribute('data-matter', p.key);
|
|
486
|
+
el.setAttribute('data-matters', keys.join(' '));
|
|
487
|
+
el.setAttribute('data-building', p.building);
|
|
488
|
+
at(el, p.x, p.y, S.PIN_W, p.h);
|
|
489
|
+
const label = node('span', 'street-pin__label', p.label);
|
|
490
|
+
if (p.label) label.setAttribute('title', p.label);
|
|
491
|
+
el.appendChild(label);
|
|
492
|
+
const head = node('span', 'street-pin__head');
|
|
493
|
+
if (p.state === 'urgent') head.appendChild(node('span', 'street-pin__ring'));
|
|
494
|
+
if (p.isCount) head.appendChild(node('span', 'street-pin__count', p.count));
|
|
495
|
+
else if (p.state !== 'settled') {
|
|
496
|
+
const svg = svgEl('svg');
|
|
497
|
+
svg.setAttribute('width', '18');
|
|
498
|
+
svg.setAttribute('height', '18');
|
|
499
|
+
svg.setAttribute('viewBox', '0 0 18 18');
|
|
500
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
501
|
+
const fill = svgEl('path');
|
|
502
|
+
fill.setAttribute('d', p.gf || '');
|
|
503
|
+
const line = svgEl('path');
|
|
504
|
+
line.setAttribute('d', p.gl || '');
|
|
505
|
+
svg.appendChild(fill);
|
|
506
|
+
svg.appendChild(line);
|
|
507
|
+
head.appendChild(svg);
|
|
508
|
+
}
|
|
509
|
+
if (!p.isCount && Number(p.count) > 1) head.appendChild(node('span', 'street-pin__badge', p.count));
|
|
510
|
+
el.appendChild(head);
|
|
511
|
+
el.appendChild(node('span', 'street-pin__stem'));
|
|
512
|
+
el.addEventListener('click', function () { env.openCard(keys[0], el, keys); });
|
|
513
|
+
return el;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// A reading is a tile (task 1004685): one number and two words, hidden at
|
|
517
|
+
// zero or when nothing real holds it (the living model's tiles); without a
|
|
518
|
+
// tile it is the docket's own figure and line, as before.
|
|
519
|
+
function sign(sg, tile) {
|
|
520
|
+
const a = node('a', 'street-sign' + (sg.hot ? ' street-sign--hot' : '') + (sg.here ? ' street-sign--here' : '') + (tile !== undefined ? ' street-depth-tile' : ''));
|
|
521
|
+
a.setAttribute('href', sg.href);
|
|
522
|
+
a.setAttribute('aria-label', tile ? sg.label + ': ' + tile.n + ' ' + tile.words + '. Go to ' + sg.building : sg.aria);
|
|
523
|
+
a.setAttribute('data-reading', sg.key);
|
|
524
|
+
if (sg.here) a.setAttribute('aria-current', 'location');
|
|
525
|
+
const top = node('span', 'street-sign__top');
|
|
526
|
+
top.appendChild(node('span', 'street-sign__dot'));
|
|
527
|
+
top.appendChild(node('span', 'street-label', sg.label));
|
|
528
|
+
a.appendChild(top);
|
|
529
|
+
if (tile === undefined) {
|
|
530
|
+
a.appendChild(node('span', 'street-sign__figure', sg.figure));
|
|
531
|
+
a.appendChild(node('span', 'street-sign__line', sg.line));
|
|
532
|
+
return a;
|
|
533
|
+
}
|
|
534
|
+
if (!tile) { a.hidden = true; return a; }
|
|
535
|
+
const fig = node('span', 'street-sign__figure street-depth-tile__fig');
|
|
536
|
+
const n = node('span', 'street-depth-tile__n', tile.n);
|
|
537
|
+
fig.appendChild(n);
|
|
538
|
+
fig.appendChild(node('span', 'street-depth-tile__words', tile.words));
|
|
539
|
+
a.appendChild(fig);
|
|
540
|
+
if (tile.title) a.setAttribute('title', tile.title);
|
|
541
|
+
return a;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ---------- the living model's reads ----------
|
|
545
|
+
// The street's own three feeds are street.js's load(); these are the living
|
|
546
|
+
// model's others, read once through gov-feeds.js and again only after an act
|
|
547
|
+
// refreshed one (the memo hands back the same bodies until then, so a redraw
|
|
548
|
+
// never re-reads and never loops). GovLiving or GovFeeds missing (an old cached
|
|
549
|
+
// page) draws the deep street from real data alone. The model is data.living,
|
|
550
|
+
// which the deck and the offices draw from; its feeds are data.livingFeeds.
|
|
551
|
+
// laws: the project's law book (task 1004706)
|
|
552
|
+
// counts: the server's one read of the counts every surface prints (task 1004716)
|
|
553
|
+
const LIVING_KEYS = Object.freeze(['counts', 'claims', 'tasks', 'cost', 'roster', 'me', 'blockers', 'moving', 'fence', 'constitution', 'laws', 'leads', 'treasury', 'presets']);
|
|
554
|
+
const living = { asking: false, feeds: null };
|
|
555
|
+
function loadLiving(data, redraw) {
|
|
556
|
+
const F = root.GovFeeds;
|
|
557
|
+
if (living.asking || !root.GovLiving || !F || !data.loaded) return null;
|
|
558
|
+
living.asking = true;
|
|
559
|
+
return F.read(LIVING_KEYS.slice()).then(function (f) {
|
|
560
|
+
living.asking = false;
|
|
561
|
+
const same = living.feeds && LIVING_KEYS.every(function (k) { return living.feeds[k] === f[k]; });
|
|
562
|
+
if (same) return;
|
|
563
|
+
living.feeds = f;
|
|
564
|
+
redraw();
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function livingModel(data) {
|
|
568
|
+
if (!living.feeds || !root.GovFeeds) return null;
|
|
569
|
+
data.livingFeeds = living.feeds;
|
|
570
|
+
const raw = Object.assign({ docket: data.docket, goals: data.goals, rollups: data.rollups }, living.feeds);
|
|
571
|
+
return root.GovFeeds.model(raw, Date.now());
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// ---------- the planes ----------
|
|
575
|
+
// A skyline row's svg is a 1 px box that paints its whole row as overflow:
|
|
576
|
+
// a deep, scaled-up layer's own box would otherwise widen the street's
|
|
577
|
+
// sideways scroll (a 3D transform counts toward it; an svg's painted
|
|
578
|
+
// overflow does not), and clipping it would flatten the parallax.
|
|
579
|
+
function depthSvg(host, W, point) {
|
|
580
|
+
const svg = svgEl('svg');
|
|
581
|
+
svg.setAttribute('class', 'street-depth-svg');
|
|
582
|
+
svg.setAttribute('width', point ? '1' : String(W));
|
|
583
|
+
svg.setAttribute('height', point ? '1' : '900');
|
|
584
|
+
if (!point) svg.setAttribute('viewBox', '0 0 ' + W + ' 900');
|
|
585
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
586
|
+
svg.setAttribute('focusable', 'false');
|
|
587
|
+
host.appendChild(svg);
|
|
588
|
+
return svg;
|
|
589
|
+
}
|
|
590
|
+
// a path the page colours by class (the hall's tokens), or one in the drawing's own palette
|
|
591
|
+
function depthPath(p, cls) {
|
|
592
|
+
const el = p.c || cls ? svgEl('path') : pathEl(p);
|
|
593
|
+
if (p.c || cls) { el.setAttribute('d', p.d); el.setAttribute('class', p.c || cls); }
|
|
594
|
+
return el;
|
|
595
|
+
}
|
|
596
|
+
function depth(dm, W) {
|
|
597
|
+
const far = $('streetDepthFar'), near = $('streetDepthNear'), sky = $('streetDepthSky'), front = $('streetDepthFront');
|
|
598
|
+
[far, near, sky, front].forEach(clear);
|
|
599
|
+
if (!dm) return;
|
|
600
|
+
// BACK: the goals' skyline, two rows hazed toward the void
|
|
601
|
+
const board = $('streetBoard');
|
|
602
|
+
if (board) board.style.setProperty('--street-depth-ox', (W / 2) + 'px');
|
|
603
|
+
if (far) { const fs = depthSvg(far, W, true); dm.far.forEach(function (p) { fs.appendChild(depthPath(p)); }); }
|
|
604
|
+
if (near) { const ns = depthSvg(near, W, true); dm.near.forEach(function (p) { ns.appendChild(depthPath(p)); }); }
|
|
605
|
+
// SKY: the sittings' light shafts and the skylanes, behind the civic row
|
|
606
|
+
if (sky) {
|
|
607
|
+
dm.shafts.forEach(function (sh) {
|
|
608
|
+
const el = at(node('span', 'street-depth-shaft'), sh.x, sh.y, sh.w, sh.h);
|
|
609
|
+
el.setAttribute('data-building', sh.building);
|
|
610
|
+
el.style.setProperty('--street-depth-climb', sh.h + 'px');
|
|
611
|
+
// the ring rises inside a track that fades at both ends, so it never pops
|
|
612
|
+
const track = node('span', 'street-depth-shaft__track');
|
|
613
|
+
track.appendChild(node('span', 'street-depth-shaft__ring'));
|
|
614
|
+
el.appendChild(track);
|
|
615
|
+
sky.appendChild(el);
|
|
616
|
+
});
|
|
617
|
+
const ls = depthSvg(sky, W);
|
|
618
|
+
dm.lanes.forEach(function (ln) { const p = depthPath({ d: ln.d }, 'street-depth-lane'); p.setAttribute('data-lane', ln.key); ls.appendChild(p); });
|
|
619
|
+
dm.pods.forEach(function (pd) {
|
|
620
|
+
const el = node('span', 'street-depth-pod');
|
|
621
|
+
el.style.offsetPath = offsetPathOf(pd.path);
|
|
622
|
+
el.style.offsetDistance = pd.at + '%';
|
|
623
|
+
el.style.animationDelay = pd.delay + 's';
|
|
624
|
+
el.setAttribute('data-lane', pd.lane);
|
|
625
|
+
sky.appendChild(el);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (!front) return;
|
|
629
|
+
// FRONT: what needs you, construction, the plates and the road
|
|
630
|
+
const fsv = depthSvg(front, W);
|
|
631
|
+
dm.windows.forEach(function (w) {
|
|
632
|
+
const g = svgEl('g');
|
|
633
|
+
g.setAttribute('data-building', w.building);
|
|
634
|
+
g.appendChild(depthPath({ d: w.d }, 'street-depth-needs'));
|
|
635
|
+
g.appendChild(depthPath({ d: w.ring }, 'street-depth-age'));
|
|
636
|
+
if (w.arc) g.appendChild(depthPath({ d: w.arc }, 'street-depth-age__arc'));
|
|
637
|
+
fsv.appendChild(g);
|
|
638
|
+
});
|
|
639
|
+
dm.sites.forEach(function (st) {
|
|
640
|
+
const g = svgEl('g');
|
|
641
|
+
g.setAttribute('class', 'street-depth-site');
|
|
642
|
+
g.setAttribute('data-building', st.building);
|
|
643
|
+
const pf = svgEl('rect');
|
|
644
|
+
pf.setAttribute('class', 'street-depth-platform');
|
|
645
|
+
pf.setAttribute('x', String(st.platform.x)); pf.setAttribute('y', String(st.platform.y - 1));
|
|
646
|
+
pf.setAttribute('width', String(st.platform.w)); pf.setAttribute('height', '2');
|
|
647
|
+
pf.style.setProperty('--street-depth-rise', st.platform.rise + 'px');
|
|
648
|
+
g.appendChild(pf);
|
|
649
|
+
g.appendChild(depthPath({ d: st.mast }, 'street-depth-crane'));
|
|
650
|
+
const jib = svgEl('g');
|
|
651
|
+
jib.setAttribute('class', 'street-depth-jib');
|
|
652
|
+
jib.style.transformOrigin = st.ox + 'px ' + st.oy + 'px';
|
|
653
|
+
jib.appendChild(depthPath({ d: st.jib }, 'street-depth-crane'));
|
|
654
|
+
jib.appendChild(depthPath({ d: st.weight }, 'street-depth-weight'));
|
|
655
|
+
// the hook: its line reels in from the jib while the slab rises with it
|
|
656
|
+
const line = depthPath({ d: st.line }, 'street-depth-crane street-depth-line');
|
|
657
|
+
line.style.transformOrigin = st.hx + 'px ' + st.oy + 'px';
|
|
658
|
+
const slab = depthPath({ d: st.slab }, 'street-depth-slab');
|
|
659
|
+
slab.style.setProperty('--street-depth-lift', (-st.lift) + 'px');
|
|
660
|
+
jib.appendChild(line);
|
|
661
|
+
jib.appendChild(slab);
|
|
662
|
+
g.appendChild(jib);
|
|
663
|
+
fsv.appendChild(g);
|
|
664
|
+
});
|
|
665
|
+
dm.proposals.forEach(function (pr) {
|
|
666
|
+
const g = svgEl('g');
|
|
667
|
+
g.setAttribute('class', 'street-depth-proposal');
|
|
668
|
+
g.setAttribute('data-building', pr.building);
|
|
669
|
+
g.appendChild(depthPath({ d: pr.crane }, 'street-depth-crane'));
|
|
670
|
+
g.appendChild(depthPath({ d: pr.arc }, 'street-depth-tally'));
|
|
671
|
+
pr.dots.forEach(function (dt) {
|
|
672
|
+
const c = svgEl('circle');
|
|
673
|
+
c.setAttribute('class', 'street-depth-seat street-depth-seat--' + dt.vote);
|
|
674
|
+
c.setAttribute('cx', String(dt.cx)); c.setAttribute('cy', String(dt.cy)); c.setAttribute('r', '2.4');
|
|
675
|
+
g.appendChild(c);
|
|
676
|
+
});
|
|
677
|
+
fsv.appendChild(g);
|
|
678
|
+
});
|
|
679
|
+
// the light pools sit on the road, under the plates
|
|
680
|
+
(dm.pools || []).forEach(function (pl) {
|
|
681
|
+
const el = at(node('span', 'street-depth-pool'), pl.x, pl.y, pl.w);
|
|
682
|
+
el.style.setProperty('--street-depth-pool-a', String(pl.a));
|
|
683
|
+
if (pl.building) el.setAttribute('data-building', pl.building);
|
|
684
|
+
front.appendChild(el);
|
|
685
|
+
});
|
|
686
|
+
dm.plates.forEach(function (pl) {
|
|
687
|
+
const el = at(node('span', 'street-depth-plate street-depth-plate--' + pl.seat + (pl.you ? ' street-depth-plate--you' : '')), pl.x, pl.y, pl.w);
|
|
688
|
+
el.setAttribute('data-building', pl.building);
|
|
689
|
+
el.appendChild(node('span', 'street-depth-plate__t', pl.text));
|
|
690
|
+
front.appendChild(el);
|
|
691
|
+
});
|
|
692
|
+
dm.road.forEach(function (rd) {
|
|
693
|
+
const tr = node('span', 'street-depth-train');
|
|
694
|
+
tr.style.offsetPath = offsetPathOf(rd.path);
|
|
695
|
+
tr.style.width = rd.w + 'px';
|
|
696
|
+
if (rd.building) tr.setAttribute('data-building', rd.building);
|
|
697
|
+
for (let i = 0; i < rd.n; i++) tr.appendChild(at(node('span', 'street-depth-pod street-depth-pod--road'), i * rd.gap, 0));
|
|
698
|
+
front.appendChild(tr);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return { pin: pin, sign: sign, depth: depth, loadLiving: loadLiving, livingModel: livingModel, living: living, LIVING_KEYS: LIVING_KEYS };
|
|
703
|
+
}());
|
|
704
|
+
|
|
705
|
+
const api = {
|
|
706
|
+
structureOf: structureOf, skylineOf: skylineOf, readingTiles: readingTiles, floorOfSource: floorOfSource, depthOf: depthOf,
|
|
707
|
+
DEPTH: DEPTH, PLUG: PLUG, FACE: FACE, CAP_W: CAP_W, DECK_HREF: DECK_HREF, SKY_MIN: SKY_MIN, SKY_MAX: SKY_MAX,
|
|
708
|
+
page: page,
|
|
709
|
+
};
|
|
710
|
+
root.GovStreetDepth = api;
|
|
711
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
712
|
+
}(typeof window !== 'undefined' ? window : globalThis));
|