@bongos/core 1.21.3 → 1.21.5

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 (73) hide show
  1. package/.bongos-core.json +128 -68
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +2 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/adr/0362-the-governor-docket-is-collected-from-contributed-sources.md +34 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +35 -1
  10. package/docs/api-reference.md +3 -2
  11. package/docs/copy-inventory.md +389 -518
  12. package/docs/copy-registry.json +880 -2143
  13. package/docs/module-api-changelog.md +4 -0
  14. package/docs/page-inventory.json +17 -25
  15. package/docs/page-readings.json +1008 -1246
  16. package/migrations/core_272_rename_cn_criterion_slugs.sql +40 -0
  17. package/modules/autonomy/governor-docket.js +101 -0
  18. package/modules/autonomy/routes/autonomy.js +5 -0
  19. package/modules/discord/board-broadcast.js +3 -1
  20. package/modules/discord/craft-broadcast.js +107 -0
  21. package/modules/discord/routes/discord.js +10 -0
  22. package/modules/discord/ship-broadcast.js +30 -2
  23. package/modules/economy/reward.js +3 -0
  24. package/modules/government/board.js +20 -5
  25. package/modules/government/docket-vocab.js +82 -0
  26. package/modules/government/docket.js +458 -0
  27. package/modules/government/routes/government.js +34 -0
  28. package/modules/hall-ui/public/genesis-home.js +9 -10
  29. package/modules/ideas/governor-docket.js +93 -0
  30. package/modules/ideas/routes/blockers.js +5 -0
  31. package/modules/lifecycle/criterion-ref.js +11 -0
  32. package/modules/lifecycle/db-tasks.js +7 -0
  33. package/modules/lifecycle/governor-docket.js +192 -0
  34. package/modules/lifecycle/routes/done-when.js +6 -0
  35. package/modules/lifecycle/routes/gate-approvals.js +78 -33
  36. package/modules/lifecycle/routes/lifecycle.js +3 -0
  37. package/modules/onboarding/routes/access-requests.js +68 -27
  38. package/modules/public-landing/public/assets/cosmos.css +6 -50
  39. package/modules/public-landing/public/projects.html +1170 -1659
  40. package/modules/public-landing/public/projects.probes.json +18 -18
  41. package/modules/public-landing/public/projects.states.json +23 -31
  42. package/modules/security/routes/reports.js +39 -0
  43. package/package-lock.json +2 -2
  44. package/package.json +1 -1
  45. package/release-notes.json +20 -0
  46. package/src/bongos/module-rechecks.js +77 -0
  47. package/src/bongos/route-rank-check.js +3 -0
  48. package/src/bongos/routes/core-update.js +41 -0
  49. package/src/bongos/routes/modules.js +17 -0
  50. package/src/module-api.js +1 -1
  51. package/tests/criterion_goal_attach.mjs +12 -4
  52. package/tests/criterion_ref_parse.mjs +41 -0
  53. package/tests/discord_craft_broadcast.mjs +162 -0
  54. package/tests/genesis_home.mjs +1 -1
  55. package/tests/government_routes.mjs +3 -1
  56. package/tests/governor_docket.mjs +552 -0
  57. package/tests/onboard.mjs +10 -4
  58. package/tests/platform_boot.mjs +1 -1
  59. package/tests/projects_hub.mjs +196 -89
  60. package/tests/projects_hub_app_status.mjs +3 -1
  61. package/tests/projects_hub_app_step.mjs +72 -48
  62. package/tests/projects_hub_dns_ready.mjs +10 -2
  63. package/tests/projects_hub_look.mjs +32 -22
  64. package/tests/projects_hub_module_picker.mjs +112 -62
  65. package/tests/projects_hub_pre_uat.mjs +48 -41
  66. package/tests/public_landing_projects.mjs +12 -6
  67. package/tests/wizard_demo.mjs +37 -11
  68. package/tests/wizard_draft_resume.mjs +34 -22
  69. package/tests/wizard_front_door.mjs +173 -103
  70. package/tests/wizard_intent_resume.mjs +28 -25
  71. package/tests/wizard_physics.mjs +94 -76
  72. package/tests/wizard_physics_more.mjs +409 -277
  73. package/tests/wizard_six_screens.mjs +126 -0
@@ -71,31 +71,19 @@
71
71
  #view-manage .pubVerdict{font-size:15px;font-weight:500;line-height:1.55;color:var(--ink-soft);margin-bottom:14px;}
72
72
  #view-manage .pubVerdict b{font-weight:700;color:var(--ink);}
73
73
 
74
- /* The bongo pair on the New-project view. Component-scoped custom properties,
75
- never :root (C1). The drum head is SKIN — using --bg-card would turn it black
76
- in dark mode — and --drum-shadow is ink in light but PLAIN BLACK in dark,
77
- because --ink is near-white there and an ink-filled ellipse paints a glowing
78
- puddle under the drums instead of a shadow. Same move cosmos.css already
79
- makes for --shadow-card. Written twice so an explicit choice and the OS
80
- preference both land. */
81
- .bongos{display:block;width:100%;height:auto;overflow:visible;
82
- --skin-hi:#fffdf7;--skin-mid:#f4e6cd;--skin-lo:#cdb896;--drum-shadow:var(--ink);}
83
- :root[data-mode="dark"] .bongos{--skin-hi:#fdf7ea;--skin-mid:#efe1c8;--skin-lo:#c3ae90;--drum-shadow:#000;}
84
- @media (prefers-color-scheme:dark){:root:not([data-mode="light"]) .bongos{--skin-hi:#fdf7ea;--skin-mid:#efe1c8;--skin-lo:#c3ae90;--drum-shadow:#000;}}
85
-
86
74
  /* ── the create wizard (task 1002763, absorbed from /create), in the world
87
75
  (task 1003346; direction record §15, the pick: v1 the rail above) ──────
88
- Tokens, the reset, the sky and the shared vocabulary (.card, .btn, .steps)
76
+ Tokens, the reset, the sky and the shared vocabulary (.card, .btn)
89
77
  come from cosmos.css, which draws the wizard's FRAME in the world's grammar
90
- by scope (#view-new: no card ground, the six-step strip as the lifecycle
91
- rail, the doors as pills); what lands here is the wizard's own structure —
78
+ by scope (#view-new: no card ground, no progress rail since task 1004504,
79
+ the doors as pills); what lands here is the wizard’s own structure —
92
80
  fields on one bottom hairline, the choices as cells of one band, the review
93
81
  as a ledger, the human steps as a numbered ledger, the standup strip as the
94
- same rail. No literal color anywhere: every surface is a cosmos token or a
82
+ same strip. No literal color anywhere: every surface is a cosmos token or a
95
83
  color-mix over one (C1/C2/C4); no plate, no box shadow, no glow — halos are
96
84
  outlines. Naming: the wizard's field wrapper is .wizField — cosmos owns
97
- .field (the orb map) — and its done checklist is .humanSteps — cosmos owns
98
- .steps (the five-beat rail). Its inputs are scoped to the view so the glass
85
+ .field (the orb map) — and its done checklist is .humanSteps. Its inputs are
86
+ scoped to the view so the glass
99
87
  search pills (#mapQ, #mineQ) keep their own look. */
100
88
  .panel{display:none;}
101
89
  .panel.on{display:block;animation:fadeStep .3s ease both;}
@@ -103,80 +91,33 @@
103
91
  /* show() moves focus to each panel's h1 for keyboard/SR context — keep the
104
92
  focus, lose the ring (a black rectangle around a heading; owner walk) */
105
93
  h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
106
- /* the done step needs the full width — the split collapses and the bongo
107
- pair steps aside for the checklist */
108
- .createSplit.wizDone{display:block;}
109
- .createSplit.wizDone .bongoCell{display:none;}
110
- /* the front door (task 1004417, BV2.PS05): two screens before the numbered
111
- steps. The rail is the steps', so it steps aside for both; the fork takes the
112
- whole card like the done panel, the bongo pair giving way to the seed. */
113
- #view-new .card.wizFront .steps{display:none;}
114
- .createSplit.wizFork{display:block;}
115
- .createSplit.wizFork .bongoCell{display:none;}
116
94
  /* the one tick: a hairline row, the box in the accent, the whole row the target */
117
95
  .termsAck{display:flex;gap:14px;align-items:flex-start;cursor:pointer;max-width:62ch;margin:6px 0 0;padding:16px 0 10px;border-top:1px solid var(--rule);}
118
96
  .termsAck input{width:20px;height:20px;margin:2px 0 0;flex:none;accent-color:var(--accent);}
119
97
  .termsAck span{font-size:16px;line-height:1.5;color:var(--ink);}
120
- .termsRead{display:inline-block;margin-left:34px;padding:3px 0;min-height:24px;line-height:18px;font-size:14px;color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
98
+ .termsRead{display:inline-block;min-height:24px;line-height:24px;color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
121
99
  /* the door pill (DESIGN.md): two equal doors joined into one object — one grid
122
100
  of two equal tracks inside one hairline pill; the demo door is transparent at
123
101
  rest, the accent only on the one that goes on */
124
102
  .doorPill{display:inline-grid;grid-template-columns:1fr 1fr;gap:6px;padding:7px;margin:6px 0 16px;border-radius:999px;border:1px solid var(--rule);background:color-mix(in srgb,var(--bg) 55%,transparent);}
125
103
  #view-new .doorPill .btn.ghost{border-color:transparent;}
126
104
  #view-new .doorPill .btn.ghost:hover{border-color:var(--rule);}
127
- /* the demo route (task 1004420): two screens inside the fork. Steppers on the wizard's
128
- field grammar, the fit check as a hairline band, the picked defaults as a ledger, and a
129
- dashed ring on the planet for the time box (the canvas's "when it closes, the demo ends") */
105
+ /* the demo route (task 1004420): one screen inside the fork — steppers on the wizard's
106
+ field grammar, the fit check as one quiet line, two teammates */
130
107
  .demoSteppers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;}
131
108
  .demoStep{display:flex;align-items:center;gap:12px;margin-top:6px;}
132
109
  .demoStep output{min-width:2ch;text-align:center;font-family:var(--font-display);font-weight:300;font-size:28px;color:var(--ink);}
133
110
  .demoStep .btn{min-width:40px;min-height:40px;justify-content:center;}
134
- .demoFit{border-top:1px solid var(--rule);padding:14px 0 6px;margin:0 0 10px;max-width:62ch;}
135
- .demoFitLine{font-size:15.5px;line-height:1.5;color:var(--ink);margin:8px 0 12px;}
136
- .demoFitTry{font-size:14px;color:var(--ink-soft);margin:0 0 4px;}
137
- .demoTry{margin:0 0 6px;padding-left:18px;font-size:14.5px;line-height:1.6;color:var(--ink);}
138
- .demoLedger{display:grid;grid-template-columns:150px minmax(0,1fr);gap:0;margin:6px 0 18px;border-top:1px solid var(--rule);}
139
- .demoLedger dt,.demoLedger dd{margin:0;padding:11px 0;border-bottom:1px solid var(--rule-soft);}
140
- .demoLedger dt{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);padding-top:14px;}
141
- .demoLedger dd{font-size:15.5px;color:var(--ink);}
142
- .demoLedger dd small{display:block;font-size:13.5px;color:var(--ink-soft);margin-top:2px;}
111
+ .demoFitLine{font-size:14.5px;line-height:1.5;color:var(--ink-soft);margin:0 0 14px;max-width:62ch;}
143
112
  .demoInvites{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;}
144
- .demoRing{position:absolute;inset:4%;border:1px dashed color-mix(in srgb,var(--accent) 55%,transparent);border-radius:50%;}
145
113
  .demoKeep{border-top:1px solid var(--rule);padding-top:14px;margin:6px 0 10px;}
146
114
  .demoKeepH{font-family:var(--font-display);font-weight:300;font-size:24px;letter-spacing:-.02em;text-transform:lowercase;color:var(--ink);margin:0 0 10px;}
147
115
  .demoKeepCells{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;}
148
116
  .demoClock{font-size:15.5px;color:var(--ink);margin:0 0 10px;}
149
117
  .demoSoonWarn{color:var(--warn);font-weight:600;}
150
- .demoStageCap{position:absolute;left:16px;right:16px;bottom:14px;margin:0;font-size:13px;line-height:1.4;color:var(--ink);}
151
- @media (max-width:640px){ .demoSteppers,.demoInvites,.demoKeepCells{grid-template-columns:1fr;} .demoLedger{grid-template-columns:1fr;} .demoLedger dt{border-bottom:0;padding-bottom:0;} }
152
- /* the seed on the field plate: the world's own render, a hairline orbit and a
153
- dashed one, the chrome blob cut out on top — nothing drawn but hairlines */
154
- .forkBody{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,380px);gap:48px;align-items:center;}
155
- .forkStage{position:relative;aspect-ratio:1/1;border-radius:16px;overflow:hidden;background:var(--sky-bg) var(--field-plate) center/cover no-repeat;}
156
- .forkOrbit{position:absolute;inset:20%;border:1px solid var(--rule);border-radius:50%;}
157
- .forkOrbit.forkOrbitDash{inset:9%;border-style:dashed;border-color:var(--rule-soft);}
158
- .forkSeed{position:absolute;left:50%;top:50%;width:44%;height:auto;transform:translate(-50%,-50%);}
159
- /* the two routes on one hairline band */
160
- .forkRoutes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 48px;margin-top:40px;}
161
- .forkRoutes>div{border-top:1px solid var(--rule);padding-top:18px;}
162
- .routeLbl{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);margin:0;}
163
- .routeT{font-family:var(--font-display);font-weight:300;font-size:26px;letter-spacing:-.02em;line-height:1.1;text-transform:lowercase;color:var(--ink);margin:8px 0 0;}
164
- .routeD{font-size:14.5px;line-height:1.5;color:var(--ink-soft);margin:8px 0 0;max-width:52ch;}
165
- @media (max-width:840px){
166
- .forkBody,.forkRoutes{grid-template-columns:1fr;gap:28px;}
167
- .forkStage{max-width:300px;}
168
- .forkRoutes{margin-top:32px;}
169
- }
170
- /* planet physics (task 1004418, BV2.PS07): the fork's layout, its stage forming a
171
- planet as the answers come in. The canvas's seven-question rail is a quiet
172
- label-face row inside the panel; the wizard's own rail stays above it. Choices
173
- are the wizard's hairline pills; the planet grows by a step size per answer. */
174
- .physCrumb{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);margin:0 0 10px;}
175
- .physRail{list-style:none;display:flex;flex-wrap:wrap;gap:4px 16px;margin:0 0 22px;padding:0;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
176
- .physRail li.now{color:var(--ink);}
177
- .physRail li.done{color:var(--ink-soft);}
178
- .physRail li.now::before{content:"";display:inline-block;width:6px;height:6px;margin-right:6px;border-radius:50%;background:var(--accent);vertical-align:2px;}
179
- .physQ h1 em{font-style:italic;}
118
+ @media (max-width:640px){ .demoSteppers,.demoInvites,.demoKeepCells{grid-template-columns:1fr;} }
119
+ /* planet physics (task 1004418, BV2.PS07; the quick checks and the review rows since
120
+ task 1004504): the wizard's hairline pills, the pickers, the age tiers and the suite. */
180
121
  .physCells .tpill small{display:block;font-size:13.5px;font-weight:400;color:var(--ink-soft);line-height:1.4;}
181
122
  .physCells .tpill[disabled]{opacity:.45;cursor:default;}
182
123
  .physWhy{font-size:14px;line-height:1.5;color:var(--ink-soft);max-width:60ch;margin:0 0 14px;}
@@ -199,26 +140,9 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
199
140
  .physTiers dd{margin:0;color:var(--ink);}
200
141
  .physGrp{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);margin:12px 0 4px;}
201
142
  .physLink{background:none;border:0;padding:3px 0;min-height:24px;font:inherit;font-size:14px;color:var(--ink);text-decoration:underline;text-underline-offset:3px;cursor:pointer;}
202
- .physStage .physPlanet{transition:width .6s ease;}
203
- .physStage .physPlanet[data-size="1"]{width:22%;}
204
- .physStage .physPlanet[data-size="2"]{width:28%;}
205
- .physStage .physPlanet[data-size="3"]{width:34%;}
206
- .physStage .physPlanet[data-size="4"]{width:40%;}
207
- .physStage .physPlanet[data-size="5"]{width:46%;}
208
- .physStage .physPlanet[data-size="6"]{width:50%;}
209
- .physGuard{position:absolute;inset:14%;border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);border-radius:50%;outline:1px dashed color-mix(in srgb,var(--accent) 30%,transparent);outline-offset:6px;}
210
- .physStage.physStopped{opacity:.6;}
211
- .physCap,.physFoot{position:absolute;left:16px;right:16px;margin:0;}
212
- .physFoot{bottom:14px;display:grid;gap:6px;}
213
- .physCap,.physCallout,.physCoords{margin:0;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-soft);}
214
- .physCap{top:14px;}
215
- .physCallout{color:var(--ink);text-transform:none;letter-spacing:0;font-family:var(--font-body);font-size:13px;}
216
- .physCoords{color:var(--ink-faint);}
217
143
  @media (max-width:840px){ .physRow2{grid-template-columns:1fr;} }
218
- /* questions 5 to 7 and the summary (task 1004419, BV2.PS11). The rail strikes a question
219
- the answers skip; the options are the wizard's hairline pills in one column, the suite a
220
- two-column grid; the growth guess is a ledger of three months over a range. */
221
- .physRail li.skipped,#view-new .steps li.skipped .slbl{text-decoration:line-through;text-decoration-color:var(--ink-faint);}
144
+ /* questions 5 to 7 (task 1004419, BV2.PS11): the suite a two-column grid, the growth
145
+ guess one line over a range. */
222
146
  .typeCards.physOpts{grid-template-columns:1fr;}
223
147
  .physOpts .tpill,.physSuiteGrid .tpill{align-items:flex-start;border-left:0;padding-left:0;border-top:1px solid var(--rule-soft);}
224
148
  .physOpts .tpill:first-child{border-top:0;}
@@ -228,46 +152,144 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
228
152
  .typeCards.physSuiteGrid{grid-template-columns:repeat(2,minmax(0,1fr));gap:0 16px;}
229
153
  .physSuiteGrid .tpill:nth-child(-n+2){border-top:0;}
230
154
  .physWhyList{margin:0 0 14px;padding:0 0 0 18px;font-size:14px;line-height:1.5;color:var(--ink-soft);max-width:62ch;}
231
- .physGuess{margin:12px 0 10px;padding:14px 0 0;border-top:1px solid var(--rule);max-width:62ch;}
232
- .physGuessLine{font-size:15px;line-height:1.5;color:var(--ink);margin:0 0 12px;}
233
- .physMonths{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-rows:auto auto;grid-auto-flow:column;gap:2px 16px;margin:0 0 6px;padding-top:10px;border-top:1px solid var(--rule-soft);}
234
- .physMonths dt{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
235
- .physMonths dd{margin:0;font-family:var(--font-display);font-weight:300;font-size:30px;line-height:1.1;color:var(--ink);font-variant-numeric:tabular-nums;}
236
155
  .physAdjust input[type=range]{display:block;width:100%;min-height:24px;margin:6px 0 0;accent-color:var(--accent);background:none;}
237
- .physLedger dd{display:flex;align-items:flex-start;gap:12px;}
238
- .physLedger dd>span{flex:1;min-width:0;}
239
- .physLedger .physLink{flex:none;}
240
156
  @media (max-width:520px){ .typeCards.physSuiteGrid{grid-template-columns:1fr;} .physSuiteGrid .tpill:nth-child(2){border-top:1px solid var(--rule-soft);} }
241
- /* the stage's new bodies: hairline rings, beads for moons, the platform's mark as the
242
- sun the planet orbits — nearer the more the platform runs, absent for its own system */
243
- .physBelt{position:absolute;left:50%;top:50%;border:1px solid var(--rule);border-radius:50%;transform:translate(-50%,-50%) rotate(-16deg);}
244
- .physBelt[data-ring="1"]{width:76%;height:22%;}
245
- .physBelt[data-ring="2"]{width:84%;height:25%;}
246
- .physBelt[data-ring="3"]{width:92%;height:28%;border-color:var(--rule-soft);}
247
- .physBelt[data-ring="4"]{width:98%;height:31%;border-color:var(--rule-soft);}
248
- .physMoons,.physGhosts{position:absolute;border-radius:50%;}
249
- .physMoons{inset:20%;}
250
- .physGhosts{inset:9%;}
251
- .physMoons img,.physGhosts img{position:absolute;width:12%;height:auto;transform:translate(-50%,-50%);}
252
- .physGhosts img{width:8%;opacity:.45;}
253
- /* the first places taken are the ones clear of the caption under the planet */
254
- .physMoons img:nth-child(1),.physGhosts img:nth-child(1){left:50%;top:0;}
255
- .physMoons img:nth-child(2),.physGhosts img:nth-child(2){left:0;top:50%;}
256
- .physMoons img:nth-child(3),.physGhosts img:nth-child(3){left:100%;top:50%;}
257
- .physMoons img:nth-child(4),.physGhosts img:nth-child(4){left:85.4%;top:14.6%;}
258
- .physMoons img:nth-child(5){left:14.6%;top:14.6%;}
259
- .physMoons img:nth-child(6){left:50%;top:100%;}
260
- .physStage .physCap,.physStage .physFoot{z-index:2;}
261
- .physStage .physCap,.physStage .physCallout,.physStage .physCoords{text-shadow:var(--type-halo);}
262
- .physSun{position:absolute;width:15%;height:auto;left:var(--sun-x);top:var(--sun-y);transform:translate(-50%,-50%);}
263
- .physTether{position:absolute;left:50%;top:50%;width:var(--sun-d);height:0;border-top:1px dashed var(--rule);transform-origin:0 0;transform:rotate(-45deg);}
264
- #physStage[data-orbit="near"]{--sun-x:73%;--sun-y:27%;--sun-d:33%;}
265
- #physStage[data-orbit="mid"]{--sun-x:80%;--sun-y:20%;--sun-d:42%;}
266
- #physStage[data-orbit="far"]{--sun-x:87%;--sun-y:13%;--sun-d:52%;}
157
+ /* ── the night room (task 1004504; the design of record docs/design/mocks/combined-onboarding/)
158
+ The studio's lofi room (modules/hall-ui/public/studio.css) behind the whole wizard: a
159
+ window with its moon, stars and city lights, the floor, the lamp and its glow, a mug and
160
+ a plant, and a soft grain. Its colours are the room's own, but each is a mix of the
161
+ fifteen (sea for the night, accent for the lights) with black or white, so a re-branded
162
+ pack re-lights the room and no literal colour is spent (C1/C2). Component-scoped tokens on
163
+ the room itself — never :root. Fixed under the top bar, clipped, and decoration only;
164
+ the words column sits above it and the window never slides under it. The chrome seed in
165
+ the window grows a little on each screen (show() writes data-screen). The only motion is
166
+ a slow twinkle, the lamp's breathe and the seed's float, and the kill switch stops all
167
+ three: the rest frame is the whole composition. ── */
168
+ #view-new .wizRoom{
169
+ --rm-room:color-mix(in srgb,var(--sea) 4%,#000);
170
+ --rm-frame:color-mix(in srgb,var(--sea) 9%,#000);
171
+ --rm-night:color-mix(in srgb,var(--sea) 15%,#000);
172
+ --rm-moon:color-mix(in srgb,var(--accent) 12%,#fff);
173
+ --rm-halo:color-mix(in srgb,var(--accent) 12%,rgba(255,255,255,.16));
174
+ --rm-star:color-mix(in srgb,var(--sea) 14%,#fff);
175
+ --rm-tower-1:color-mix(in srgb,var(--sea) 8%,#000);
176
+ --rm-tower-2:color-mix(in srgb,var(--sea) 10%,#000);
177
+ --rm-tower-3:color-mix(in srgb,var(--sea) 7%,#000);
178
+ --rm-floor:color-mix(in srgb,var(--sea) 5%,#000);
179
+ --rm-edge:rgba(255,255,255,.06);
180
+ --rm-glow:color-mix(in srgb,var(--accent) 22%,transparent);
181
+ --rm-stem:color-mix(in srgb,var(--sea) 12%,#000);
182
+ --rm-shade:color-mix(in srgb,var(--sea) 16%,#000);
183
+ --rm-mug:color-mix(in srgb,var(--sea) 11%,#000);
184
+ --rm-leaf-1:color-mix(in srgb,var(--sea) 20%,#000);
185
+ --rm-leaf-2:color-mix(in srgb,var(--sea) 26%,#000);
186
+ --seed:.45;
187
+ position:fixed;left:0;right:0;top:var(--top-h,64px);bottom:0;z-index:0;overflow:hidden;pointer-events:none;
188
+ background:var(--rm-room);
189
+ }
190
+ #view-new .wizRoom>*,#view-new .rmWindow>*{position:absolute;}
191
+ #view-new .rmWindow{left:max(calc(50% + 80px),700px);top:56px;width:520px;height:520px;box-sizing:border-box;border:14px solid var(--rm-frame);border-radius:6px;background:var(--rm-night);overflow:hidden;}
192
+ #view-new .rmMoon{left:350px;top:60px;width:56px;height:56px;border-radius:50%;background:var(--rm-moon);box-shadow:0 0 60px 12px var(--rm-halo);}
193
+ #view-new .rmStar{border-radius:50%;background:var(--rm-star);}
194
+ #view-new .rmS1{left:70px;top:70px;width:3px;height:3px;}
195
+ #view-new .rmS2{left:180px;top:38px;width:2px;height:2px;}
196
+ #view-new .rmS3{left:250px;top:118px;width:3px;height:3px;opacity:.7;}
197
+ #view-new .rmS4{left:440px;top:168px;width:2px;height:2px;}
198
+ #view-new .rmS5{left:120px;top:186px;width:2px;height:2px;opacity:.6;}
199
+ #view-new .rmTower{bottom:0;}
200
+ #view-new .rmT1{left:0;width:110px;height:210px;background:var(--rm-tower-1);}
201
+ #view-new .rmT2{left:100px;width:86px;height:290px;background:var(--rm-tower-2);}
202
+ #view-new .rmT3{left:186px;width:130px;height:180px;background:var(--rm-tower-3);}
203
+ #view-new .rmT4{left:306px;width:76px;height:250px;background:var(--rm-tower-2);}
204
+ #view-new .rmT5{left:372px;width:140px;height:160px;background:var(--rm-tower-1);}
205
+ #view-new .rmLight{width:6px;height:8px;background:var(--accent);}
206
+ #view-new .rmL1{left:124px;bottom:232px;opacity:.7;}
207
+ #view-new .rmL2{left:148px;bottom:196px;opacity:.5;}
208
+ #view-new .rmL3{left:28px;bottom:140px;opacity:.55;}
209
+ #view-new .rmL4{left:326px;bottom:204px;opacity:.65;}
210
+ #view-new .rmL5{left:430px;bottom:100px;opacity:.55;}
211
+ #view-new .rmL6{left:232px;bottom:110px;opacity:.45;}
212
+ #view-new .rmMullion{left:246px;top:0;height:100%;border-left:12px solid var(--rm-frame);}
213
+ #view-new .rmFloor{left:0;right:0;top:660px;bottom:0;background:var(--rm-floor);border-top:1px solid var(--rm-edge);}
214
+ #view-new .rmGlow{left:calc(50% + 420px);top:430px;width:300px;height:300px;border-radius:50%;background:radial-gradient(circle,var(--rm-glow) 0%,transparent 65%);}
215
+ #view-new .rmStem{left:calc(50% + 550px);top:520px;width:6px;height:140px;background:var(--rm-stem);}
216
+ #view-new .rmShade{left:calc(50% + 506px);top:500px;width:94px;height:46px;border-radius:48px 48px 6px 6px;background:var(--rm-shade);}
217
+ #view-new .rmMug{left:calc(50% + 330px);top:610px;width:46px;height:52px;border-radius:0 0 10px 10px;background:var(--rm-mug);}
218
+ #view-new .rmLeaf{border-radius:50% 50% 0 0;}
219
+ #view-new .rmF1{left:calc(50% + 170px);top:570px;width:64px;height:90px;background:var(--rm-leaf-1);}
220
+ #view-new .rmF2{left:calc(50% + 210px);top:550px;width:50px;height:110px;background:var(--rm-leaf-2);}
221
+ /* the seed: the world's chrome blob, hanging in the window's sky */
222
+ #view-new .rmSeed{left:120px;top:120px;width:120px;height:120px;transform:scale(var(--seed));transform-origin:50% 50%;}
223
+ #view-new .rmSeed img{display:block;width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 0 22px var(--rm-glow));}
224
+ #view-new .wizRoom[data-screen="signin"]{--seed:.3;}
225
+ #view-new .wizRoom[data-screen="fork"]{--seed:.45;}
226
+ #view-new .wizRoom[data-screen="what"]{--seed:.55;}
227
+ #view-new .wizRoom[data-screen="checks"]{--seed:.65;}
228
+ #view-new .wizRoom[data-screen="review"]{--seed:.75;}
229
+ #view-new .wizRoom[data-screen="look"]{--seed:.85;}
230
+ #view-new .wizRoom[data-screen="done"]{--seed:1.1;}
231
+ /* soft film grain over the room — the lofi texture, no colour of its own */
232
+ #view-new .rmGrain{inset:0;opacity:.07;mix-blend-mode:overlay;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");}
267
233
  @media (prefers-reduced-motion:no-preference){
268
- .physMoons{animation:physOrbit 160s linear infinite;}
269
- .physGhosts{animation:physOrbit 260s linear infinite reverse;}
270
- @keyframes physOrbit{to{transform:rotate(360deg);}}
234
+ #view-new .rmSeed{transition:transform 1.2s ease;}
235
+ #view-new .rmStar{animation:rmTwinkle 7s ease-in-out infinite;}
236
+ #view-new .rmS2{animation-delay:-2s;}
237
+ #view-new .rmS3{animation-delay:-4s;}
238
+ #view-new .rmS4{animation-delay:-1s;}
239
+ #view-new .rmS5{animation-delay:-5s;}
240
+ #view-new .rmGlow{animation:rmBreathe 9s ease-in-out infinite;}
241
+ #view-new .rmSeed img{animation:rmFloat 8s ease-in-out infinite;}
242
+ @keyframes rmTwinkle{0%,100%{opacity:.9}50%{opacity:.35}}
243
+ @keyframes rmBreathe{0%,100%{opacity:1}50%{opacity:.7}}
244
+ @keyframes rmFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
245
+ }
246
+ /* under 900 the window steps back and dims behind the words; the lamp, mug and plant go */
247
+ @media (max-width:900px){
248
+ #view-new .rmWindow{left:auto;right:-140px;top:24px;opacity:.35;}
249
+ #view-new .rmGlow,#view-new .rmStem,#view-new .rmShade,#view-new .rmMug,#view-new .rmLeaf{display:none;}
250
+ #view-new .rmFloor{top:auto;height:80px;}
251
+ }
252
+ /* the words column above the room, held to the mockup's measure so the window never
253
+ slides under it; headings in the mockup's sentence case */
254
+ #view-new .wrap{position:relative;z-index:1;min-height:calc(100vh - var(--top-h,64px) - 120px);}
255
+ #view-new .panel{max-width:600px;}
256
+ .wizTop{display:flex;align-items:center;gap:16px;min-height:44px;margin:0 0 28px;max-width:600px;}
257
+ .wizEyebrow{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
258
+ .wizBack{margin-left:auto;min-height:40px;padding:0 16px;border-radius:999px;border:1px solid var(--glass-edge);background:var(--glass);color:var(--ink-soft);font:inherit;font-size:13px;cursor:pointer;}
259
+ .wizBack:hover{color:var(--ink);}
260
+ .wizBack[hidden]{display:none;}
261
+ .demoTag{display:inline-block;margin-left:12px;padding:3px 10px;border-radius:999px;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--accent);border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);vertical-align:middle;}
262
+ .demoTag[hidden]{display:none;}
263
+ .fillOffer{margin:0 0 12px;}
264
+ .wizRecap{margin:22px 0 0;font-size:15px;line-height:1.5;color:var(--ink-soft);}
265
+ #view-new .doorPill .btn[disabled]{opacity:.35;cursor:default;}
266
+ /* the review: one hairline ledger of rows. Each row is its label, its value with one
267
+ plain line under it, and two actions — Looks right (the accent, softly) and Change.
268
+ A confirmed row settles to a quiet tick; an open row shows the old step's own controls
269
+ under its value, and Continue below stays locked until every row is confirmed. */
270
+ .setRows{margin:24px 0 0;border-top:1px solid var(--rule);}
271
+ .setRow{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:0 16px;align-items:start;padding:16px 0;border-bottom:1px solid var(--rule);}
272
+ .setRow[hidden]{display:none;}
273
+ .setRow .k{font-size:13px;line-height:22px;color:var(--ink-faint);padding-top:2px;}
274
+ .setRow .v{min-width:0;}
275
+ .setRow .rowV{display:block;font-size:15.5px;line-height:1.45;color:var(--ink);overflow-wrap:anywhere;}
276
+ .setRow .rowS{display:block;margin-top:3px;font-size:13px;line-height:1.45;color:var(--ink-faint);overflow-wrap:anywhere;}
277
+ .setRow .rowS:empty{display:none;}
278
+ .setRow .acts{display:flex;gap:6px;flex:none;}
279
+ .setRow .acts button{min-height:34px;padding:0 14px;border-radius:999px;font:inherit;font-size:13px;white-space:nowrap;cursor:pointer;}
280
+ .setRow [data-row-ok]{border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink);}
281
+ .setRow [data-row-chg]{border:1px solid var(--glass-edge);background:var(--glass);color:var(--ink-soft);}
282
+ .setRow [data-row-chg]:hover{color:var(--ink);}
283
+ .setRow [data-row-chg][hidden]{display:none;}
284
+ .setRow.done .rowV{color:var(--ink-soft);}
285
+ .setRow.done [data-row-ok]{border-color:transparent;background:none;color:var(--accent);cursor:default;}
286
+ .setRow.done [data-row-ok]::before{content:"✓ ";}
287
+ .setRow.open [data-row-ok]{display:none;}
288
+ .setRow .opts{margin-top:14px;}
289
+ .setRow .opts .rowUse{margin-top:6px;}
290
+ @media (max-width:560px){
291
+ .setRow{grid-template-columns:minmax(0,1fr);gap:6px 0;}
292
+ .setRow .k{padding-top:0;}
271
293
  }
272
294
  /* under 520 the two doors cannot sit side by side at 48px, so they stack —
273
295
  still two equal doors in one object */
@@ -343,13 +365,6 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
343
365
 
344
366
  /* the review: a ledger of what is about to be filed — label · value rows on
345
367
  soft hairlines, the last row closed */
346
- .review{list-style:none;display:grid;grid-template-columns:172px 1fr;gap:0 22px;border:0;border-radius:0;overflow:visible;margin:0 0 6px;}
347
- .review li{display:contents;}
348
- .review .k,.review .v{border-top:1px solid var(--rule-soft);padding:13px 0;min-width:0;}
349
- .review li:last-child .k,.review li:last-child .v{border-bottom:1px solid var(--rule-soft);}
350
- .review .k{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;font-weight:400;color:var(--ink-faint);padding-top:16px;}
351
- .review .v{font-size:15px;font-weight:400;line-height:1.5;color:var(--ink);text-align:left;overflow-wrap:anywhere;}
352
- .review .v code{font-family:var(--font-mono);font-size:13px;background:none;border-radius:0;padding:0;color:var(--ink-soft);}
353
368
 
354
369
  /* ── done / human steps: the tick leaves (the rail says filed), the title
355
370
  stands in the display voice, the steps only you can play are a numbered
@@ -470,11 +485,9 @@ summary{min-height:24px;padding:3px 0;}
470
485
  .addrConfirm details .inner{margin-top:6px;}
471
486
  .addrOpt{display:flex;align-items:flex-start;gap:12px;margin-top:14px;font-size:14px;font-weight:400;color:var(--warn);line-height:1.5;cursor:pointer;}
472
487
  .addrOpt input{margin-top:4px;accent-color:var(--accent);}
473
- /* the app step (task 1004351, ADR 0345): the hall line, then one row per place the app
488
+ /* the app row (task 1004351, ADR 0345): one row per place the app
474
489
  can run. A radio per row so the choice is native; "soon" is a row that is shown but
475
490
  cannot be picked yet, and says so in words rather than by colour alone. */
476
- .appHall{font-size:14.5px;font-weight:400;color:var(--ink-soft);line-height:1.5;max-width:62ch;margin:0 0 18px;}
477
- .appHall b{color:var(--ink);font-weight:500;}
478
491
  .appOpts{border:0;padding:0;margin:0 0 14px;border-top:1px solid var(--rule);}
479
492
  .appOpts legend{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;font-weight:400;color:var(--ink-faint);padding:0;margin:0 0 10px;}
480
493
  .appOpt{display:flex;align-items:flex-start;gap:12px;min-height:24px;padding:14px 0;border-bottom:1px solid var(--rule);cursor:pointer;}
@@ -623,10 +636,6 @@ summary{min-height:24px;padding:3px 0;}
623
636
  .typeCards{grid-template-columns:1fr;}
624
637
  .tpill{border-left:0;padding-left:0;border-top:1px solid var(--rule-soft);}
625
638
  .tpill:first-child{border-top:0;}
626
- .review{grid-template-columns:1fr;gap:0;}
627
- .review .k{padding-bottom:0;}
628
- .review .v{border-top:0;padding-top:6px;}
629
- .review li:last-child .k{border-bottom:0;}
630
639
  }
631
640
 
632
641
  </style>
@@ -1342,50 +1351,53 @@ summary{min-height:24px;padding:3px 0;}
1342
1351
  The terms panel carries a static `on` so a no-script visitor still reads
1343
1352
  step 1 rather than an empty card. -->
1344
1353
  <section id="view-new" hidden>
1354
+ <!-- the night room (task 1004504, the design of record docs/design/mocks/combined-onboarding/):
1355
+ the studio's lofi room behind the whole wizard, in place of the nebula plate, the
1356
+ forming planet and the bongo pair. Every colour is a mix of the fifteen (no page
1357
+ :root, no literal); the chrome seed hangs in the window and grows a little on each
1358
+ screen (show() writes data-screen); the only motion is a slow twinkle, the lamp's
1359
+ breathe and the seed's float, all inside the kill switch. Decoration only. -->
1360
+ <div class="wizRoom" id="wizRoom" data-screen="fork" aria-hidden="true">
1361
+ <div class="rmWindow">
1362
+ <span class="rmMoon"></span>
1363
+ <span class="rmStar rmS1"></span><span class="rmStar rmS2"></span><span class="rmStar rmS3"></span><span class="rmStar rmS4"></span><span class="rmStar rmS5"></span>
1364
+ <span class="rmSeed"><img src="/assets/world/r2-chrome-blob-one-cut.webp" alt=""></span>
1365
+ <span class="rmTower rmT1"></span><span class="rmTower rmT2"></span><span class="rmTower rmT3"></span><span class="rmTower rmT4"></span><span class="rmTower rmT5"></span>
1366
+ <span class="rmLight rmL1"></span><span class="rmLight rmL2"></span><span class="rmLight rmL3"></span><span class="rmLight rmL4"></span><span class="rmLight rmL5"></span><span class="rmLight rmL6"></span>
1367
+ <span class="rmMullion"></span>
1368
+ </div>
1369
+ <span class="rmFloor"></span>
1370
+ <span class="rmGlow"></span><span class="rmStem"></span><span class="rmShade"></span>
1371
+ <span class="rmMug"></span><span class="rmLeaf rmF1"></span><span class="rmLeaf rmF2"></span>
1372
+ <span class="rmGrain"></span>
1373
+ </div>
1345
1374
  <div class="wrap viewWrap">
1346
- <div class="card wizFront">
1347
- <ol class="steps" aria-label="Creation steps">
1348
- <li class="now" data-step="3" aria-current="step"><span class="snum">1</span><span class="slbl">Physics</span></li>
1349
- <li data-step="4"><span class="snum">2</span><span class="slbl">Name</span></li>
1350
- <li data-step="5"><span class="snum">3</span><span class="slbl">Type</span></li>
1351
- <li data-step="6"><span class="snum">4</span><span class="slbl">About</span></li>
1352
- <li data-step="7"><span class="snum">5</span><span class="slbl">Modules</span></li>
1353
- <li data-step="8"><span class="snum">6</span><span class="slbl">Home</span></li>
1354
- <li data-step="9"><span class="snum">7</span><span class="slbl">Address</span></li>
1355
- <li data-step="10"><span class="snum">8</span><span class="slbl">App</span></li>
1356
- <li data-step="11"><span class="snum">9</span><span class="slbl">Look</span></li>
1357
- <li data-step="12"><span class="snum">10</span><span class="slbl">Create</span></li>
1358
- </ol>
1359
-
1375
+ <div class="card">
1376
+ <!-- no progress rail (owner, 2026-10-01): the eyebrow names where you are and one
1377
+ Back walks the screens backwards -->
1378
+ <div class="wizTop">
1379
+ <span class="wizEyebrow" id="wizEyebrow">New project</span>
1380
+ <button type="button" class="wizBack" id="wizBack" hidden>← Back</button>
1381
+ </div>
1360
1382
  <div class="createSplit">
1361
1383
  <div>
1362
- <!-- step 0 · sign-in gate -->
1384
+ <!-- 0 · sign in -->
1363
1385
  <section class="panel" id="panel-gate" aria-labelledby="gate-h">
1364
- <h1 id="gate-h">Create a project</h1>
1365
- <p class="lede">Your own <strong data-brand-product-inline>Cloud Bongos</strong> project: its own repository, its own home on the web, and a hall of AI builders who do the building. You set the direction; agents lay down the code. Sign in with GitHub so the project belongs to <em>you</em> from the very first beat.</p>
1386
+ <h1 id="gate-h">Make something.</h1>
1387
+ <p class="lede">Sign in with GitHub to start your project.</p>
1366
1388
  <!-- The wizard's own sign-in opts into repo scope (read:user + repo, ADR 0155) up
1367
1389
  front (&repos=1) — anyone here intends to create a project, which needs repo
1368
- access, so we ask once at sign-in instead of a second "Connect" step at Home.
1390
+ access, so we ask once at sign-in instead of a second "Connect" step later.
1369
1391
  Hall + CLI sign-in stay identity-only (ADR 0143 least-privilege default). -->
1370
1392
  <a class="btn big" id="wizSignin" href="/api/bongos/auth/web/start?return=/projects%3Fview%3Dnew&amp;repos=1">
1371
1393
  <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
1372
- Sign in with GitHub
1394
+ Continue with GitHub
1373
1395
  </a>
1374
1396
  <!-- Informed consent BEFORE GitHub's own wording (ADR 0155): the scope the
1375
1397
  button asks for reads as "all your repositories" on GitHub's screen, so
1376
- the reason is stated here, on the path every founder actually takes —
1377
- not only on the Home step's re-connect branch (task 1003232). -->
1378
- <p class="finePrint" id="wizScopeNote">Signing in asks GitHub for repository access. GitHub words this as access to <em>all</em> your repositories — that is the only permission it offers that can write to a private repo. We use it to create (or read) the one repo your project lives in and push its starting files. Nothing else is touched.</p>
1379
- <!-- Trust links (task 1003502, superseding task 1002705's license-only fallback).
1380
- TWO links, because the sentence makes two claims: "open source" → the CODE,
1381
- "(AGPL-3.0)" → the licence it is under. The code half was impossible until
1382
- 2026-09-13 — the old private-repo URL 404ed, both npm scopes were private,
1383
- and the ADR 0099 mirror is dormant until ~2026-11, which left the licence
1384
- text as the only live artifact. Task 1003872 / ADR 0283 published
1385
- @bongos/core publicly, so the claim now resolves to the platform itself
1386
- (npm renders a README + a browsable Code tab; verified by a tokenless
1387
- `npm pack` from a clean HOME, the ADR 0283 standard). Not @cloudbongos/cli:
1388
- that is the 32-file generated client, a slice of the thing being claimed. -->
1398
+ the reason is stated here, on the path every founder actually takes. -->
1399
+ <p class="finePrint" id="wizScopeNote">Signing in asks GitHub for repository access. GitHub calls this access to <em>all</em> your repositories, because it is the only option that can write to a private repo. We use it only for your project’s one repo. Nothing else is touched.</p>
1400
+ <!-- Trust links (task 1003502): "open source" → the code, "(AGPL-3.0)" → its licence. -->
1389
1401
  <p class="finePrint">Free while we find our feet. Your project’s repo and server are yours —
1390
1402
  <span data-brand-product-inline>Cloud Bongos</span> is <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">open source</a>
1391
1403
  (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>)
@@ -1393,735 +1405,513 @@ summary{min-height:24px;padding:3px 0;}
1393
1405
  <div class="alert" id="wizGateAlert" role="alert"></div>
1394
1406
  </section>
1395
1407
 
1396
- <!-- step 1 · the front door, part one: the terms (task 1004417, BV2.PS05; the
1397
- canvas's Main.dc.html, docs/design/project-startup-direction.md). Before
1398
- anything is asked, the founder acknowledges the terms. Sign-in stays where
1399
- it was: the gate above comes first. The tick rides the create request as
1400
- `terms_acknowledged` and the SERVER records which terms version it was
1401
- (modules/provisioning/terms-ack.js), so this page never names one. The rail
1402
- is for the numbered steps, so it steps aside on both front-door screens. -->
1403
- <section class="panel on" id="panel-terms" aria-labelledby="terms-h">
1408
+ <!-- 1 · demo or real project, with the terms as its one tick (tasks 1004417 and
1409
+ 1004504). The tick rides the create as `terms_acknowledged` and the SERVER
1410
+ records which terms version it was (modules/provisioning/terms-ack.js), so
1411
+ this page never names one. Both doors wait for it. The demo opens one screen
1412
+ in place (task 1004420), then joins the ordinary flow marked "demo". The panel
1413
+ carries a static `on` so a no-script visitor reads it rather than an empty card. -->
1414
+ <section class="panel on" id="panel-fork" aria-labelledby="fork-h">
1404
1415
  <!-- The standup already being watched, kept one click away (task 1003503).
1405
- A deliberate "New project" click opens THIS fresh step 1 (the front door since task 1004417); the previous
1406
- project's done panel is reachable here instead of taking the panel over.
1407
1416
  Baked markup, hidden until enter() has a record to name. -->
1408
1417
  <p class="resumeOffer" id="wizResume" hidden>still watching <strong id="wizResumeName">your project</strong> — <a href="#" id="wizResumeGo">resume it</a></p>
1409
- <h1 id="terms-h">Anything you want to make</h1>
1410
- <p class="lede"><span data-brand-product-inline>Cloud Bongos</span> can be used for any project, by any kind of project entity: from a solo developer to a large corporation.</p>
1411
- <p class="lede">Because of the sensitive nature of this platform and its projects, we highly recommend you review the legal terms as diligently as is appropriate for your project entity type.</p>
1412
- <form data-next="2">
1413
- <label class="termsAck">
1414
- <input type="checkbox" id="termsAck" aria-describedby="h-terms">
1415
- <span>I can sign legally, or can get a signature, for the use of this platform, and I acknowledge the terms and conditions.</span>
1418
+ <div id="forkIntro">
1419
+ <h1 id="fork-h">You’re about to start a digital planet.</h1>
1420
+ <p class="lede">Try a small demo first, or start your real project.</p>
1421
+ <form data-next="2">
1422
+ <label class="termsAck">
1423
+ <input type="checkbox" id="termsAck" aria-describedby="h-terms">
1424
+ <span>I can sign for this project, and I accept the <a class="termsRead" href="/terms" target="_blank" rel="noopener">terms</a>.</span>
1425
+ </label>
1426
+ <div class="hint" id="h-terms">&nbsp;</div>
1427
+ <div class="doorPill" role="group" aria-label="How to begin">
1428
+ <button type="button" class="btn big ghost" id="wizDemo" aria-controls="demoSetup" aria-expanded="false" disabled>Try the demo</button>
1429
+ <button type="submit" class="btn big" id="wizStart" disabled>Start my project</button>
1430
+ </div>
1431
+ </form>
1432
+ </div>
1433
+ <!-- the demo route (task 1004420, BV2.PS09): one screen inside the fork, then
1434
+ the ordinary flow from "What are you making?": a demo is a normal project
1435
+ marked "demo" (D9). The fit check is a copy of modules/provisioning/demo.js
1436
+ fitCheck, held equal by tests/wizard_demo.mjs. -->
1437
+ <div id="demoSetup" class="demoView" hidden>
1438
+ <h1 id="demo-h1">Try a demo.</h1>
1439
+ <p class="lede">A small, time-boxed project to learn by doing. You can keep it at the end.</p>
1440
+ <div class="demoSteppers">
1441
+ <div class="wizField"><span id="demoPeopleLbl">People, you included</span>
1442
+ <div class="demoStep" role="group" aria-labelledby="demoPeopleLbl">
1443
+ <button type="button" class="btn small ghost" data-demo-step="people" data-by="-1" aria-label="One fewer person">−</button>
1444
+ <output id="demoPeople" aria-live="polite">3</output>
1445
+ <button type="button" class="btn small ghost" data-demo-step="people" data-by="1" aria-label="One more person">+</button>
1446
+ </div>
1447
+ </div>
1448
+ <div class="wizField"><span id="demoHoursLbl">Hours each</span>
1449
+ <div class="demoStep" role="group" aria-labelledby="demoHoursLbl">
1450
+ <button type="button" class="btn small ghost" data-demo-step="hours" data-by="-1" aria-label="One hour less">−</button>
1451
+ <output id="demoHours" aria-live="polite">4</output>
1452
+ <button type="button" class="btn small ghost" data-demo-step="hours" data-by="1" aria-label="One hour more">+</button>
1453
+ </div>
1454
+ </div>
1455
+ </div>
1456
+ <label class="wizField"><span>Your idea</span>
1457
+ <input type="text" id="demoIdea" maxlength="600" placeholder="A voting app for movie night" autocomplete="off">
1416
1458
  </label>
1417
- <a class="termsRead" href="/terms" target="_blank" rel="noopener">Read the terms</a>
1418
- <div class="hint" id="h-terms">&nbsp;</div>
1459
+ <p class="hint demoFitLine" aria-live="polite"><span id="demoFitSum">3 people × 4 hours = 12 builder-hours.</span> <span id="demoFitVerdict">Enough for a small version. Trim it to one shared list, voting and a winner reveal; accounts and notifications will not fit.</span></p>
1460
+ <div class="demoInvites" id="demoInvites">
1461
+ <label class="wizField"><span>Invite by GitHub handle</span>
1462
+ <input type="text" id="demoInvite1" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1463
+ </label>
1464
+ <label class="wizField"><span>And one more</span>
1465
+ <input type="text" id="demoInvite2" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1466
+ </label>
1467
+ </div>
1468
+ <p class="finePrint">An unkept demo goes offline after 30 days. We warn you first, and it can come back.</p>
1419
1469
  <div class="nav">
1420
1470
  <span></span>
1421
- <button type="submit" class="btn">Proceed to project creation</button>
1471
+ <button type="button" class="btn" id="demoStart">Continue</button>
1472
+ </div>
1473
+ </div>
1474
+ </section>
1475
+
1476
+ <!-- 2 · what are you making: the name, the kind (task 1002772 — a declaration,
1477
+ closed vocabulary, Not sure the escape hatch) and the optional sentence that
1478
+ puts the project on the public map (ADR 0182 D4). -->
1479
+ <section class="panel" id="panel-what" aria-labelledby="what-h">
1480
+ <h1 id="what-h">What are you making?<span class="demoTag" id="demoTag" hidden>demo</span></h1>
1481
+ <form id="newForm" data-next="3">
1482
+ <label class="wizField"><span>Name</span>
1483
+ <input type="text" id="newName" placeholder="Moonbase" autocomplete="off" maxlength="80" aria-label="Project name">
1484
+ <div class="hint" id="h-name">&nbsp;</div>
1485
+ </label>
1486
+ <div class="wizField"><span id="typeLbl">Kind</span>
1487
+ <div class="typeCards" id="typeCards" role="group" aria-labelledby="typeLbl">
1488
+ <button type="button" class="tpill" data-type="game" aria-pressed="false">Game</button>
1489
+ <button type="button" class="tpill" data-type="research" aria-pressed="false">Research</button>
1490
+ <button type="button" class="tpill" data-type="business" aria-pressed="false">Business</button>
1491
+ <button type="button" class="tpill" data-type="non-profit" aria-pressed="false">Non-profit</button>
1492
+ <button type="button" class="tpill" data-type="not-sure" aria-pressed="false">Not sure</button>
1493
+ </div>
1494
+ <div class="hint" id="h-projtype">&nbsp;</div>
1495
+ </div>
1496
+ <label class="wizField"><span>In a sentence <small>(optional — puts it on the public map)</small></span>
1497
+ <input type="text" id="f-desc" maxlength="600" placeholder="A co-op space game built by its players" autocomplete="off">
1498
+ </label>
1499
+ <div class="nav">
1500
+ <span></span>
1501
+ <button type="submit" class="btn">Continue</button>
1422
1502
  </div>
1423
1503
  </form>
1424
1504
  </section>
1425
1505
 
1426
- <!-- step 2 · the front door, part two: demo or create (task 1004417; the canvas's
1427
- Fork.dc.html). Two equal ways in, so it is the world's door pill (DESIGN.md):
1428
- two doors joined into one object, equal in size, the accent only on the one
1429
- that goes on. The seed stands on the field plate beside it. The demo door opens
1430
- the demo route's two screens in place (task 1004420, BV2.PS09). -->
1431
- <section class="panel" id="panel-fork" aria-labelledby="fork-h">
1432
- <div class="forkBody">
1433
- <div>
1434
- <div id="forkIntro">
1435
- <h1 id="fork-h">You are now creating a digital planet</h1>
1436
- <p class="lede">If that sounds like too much for now, try our demo system. Otherwise, proceed with planet initiation :)</p>
1437
- <form data-next="3">
1438
- <div class="doorPill" role="group" aria-label="How to begin">
1439
- <button type="button" class="btn big ghost" id="wizDemo" aria-controls="demoSetup" aria-expanded="false">Try the demo</button>
1440
- <button type="submit" class="btn big">Begin planet creation</button>
1441
- </div>
1442
- <div class="nav">
1443
- <button type="button" class="btn ghost" data-back="1">← Back</button>
1444
- <span></span>
1445
- </div>
1446
- </form>
1506
+ <!-- 3 · a few quick checks: the planet-physics answers that change something
1507
+ (tasks 1004418 and 1004419), under planet-physics.js's own keys. Continue asks
1508
+ POST /provisioning/physics-check — the same screen and soft-limit table the
1509
+ create runs — and a stop ends here, before anything exists. The age tiers are
1510
+ described, never claimed as enforced (spec D6). A demo skips this screen. -->
1511
+ <section class="panel" id="panel-checks" aria-labelledby="checks-h">
1512
+ <div id="checksMain">
1513
+ <h1 id="checks-h">A few quick checks.</h1>
1514
+ <p class="lede">These decide which legal and payout options we can offer.</p>
1515
+ <!-- auto-fill: the answers this account gave on its most recent project -->
1516
+ <p class="fillOffer" id="physFillOffer" hidden><button type="button" class="linklike" id="physFromAccount">Fill these in from my last project</button></p>
1517
+ <form data-next="4" id="checksForm">
1518
+ <div class="physRow2">
1519
+ <label class="wizField"><span>Where are you?</span><select id="physCountry"><option value="">Choose…</option></select></label>
1520
+ <label class="wizField" id="physRegionField" hidden><span>State or province</span><select id="physRegion"><option value="">Choose…</option></select></label>
1447
1521
  </div>
1448
- <!-- the demo route (task 1004420, BV2.PS09; the canvas's DemoSetup and DemoReady).
1449
- Two screens inside the fork, then the ordinary wizard from the name step on:
1450
- a demo is a normal project marked "demo" (D9). The fit check is a copy of
1451
- modules/provisioning/demo.js fitCheck, held equal by tests/wizard_demo.mjs. -->
1452
- <div id="demoSetup" class="demoView" hidden>
1453
- <h1 id="demo-h1">Training wheels</h1>
1454
- <p class="lede">A small, time-boxed project to learn the platform by doing. Everyone uses their own AI plan.</p>
1455
- <div class="demoSteppers">
1456
- <div class="wizField"><span id="demoPeopleLbl">People, you included</span>
1457
- <div class="demoStep" role="group" aria-labelledby="demoPeopleLbl">
1458
- <button type="button" class="btn small ghost" data-demo-step="people" data-by="-1" aria-label="One fewer person">−</button>
1459
- <output id="demoPeople" aria-live="polite">3</output>
1460
- <button type="button" class="btn small ghost" data-demo-step="people" data-by="1" aria-label="One more person">+</button>
1461
- </div>
1462
- </div>
1463
- <div class="wizField"><span id="demoHoursLbl">Hours each</span>
1464
- <div class="demoStep" role="group" aria-labelledby="demoHoursLbl">
1465
- <button type="button" class="btn small ghost" data-demo-step="hours" data-by="-1" aria-label="One hour less">−</button>
1466
- <output id="demoHours" aria-live="polite">4</output>
1467
- <button type="button" class="btn small ghost" data-demo-step="hours" data-by="1" aria-label="One hour more">+</button>
1468
- </div>
1469
- </div>
1522
+ <div class="wizField"><span id="physGeoLbl">Where will builders be?</span>
1523
+ <div class="typeCards physCells" role="group" aria-labelledby="physGeoLbl">
1524
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="us-only" aria-pressed="false">US only</button>
1525
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="us-eu" aria-pressed="false">US and EU</button>
1526
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="global" aria-pressed="false">Anywhere</button>
1527
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="not-sure" aria-pressed="false">Not sure</button>
1470
1528
  </div>
1471
- <label class="wizField"><span>Your demo project idea</span>
1472
- <textarea id="demoIdea" rows="3" maxlength="600" placeholder="A shared reading list for our team: everyone adds books, we vote, and the winner is next month’s read."></textarea>
1473
- </label>
1474
- <div class="demoFit" aria-live="polite">
1475
- <p class="routeLbl">First read</p>
1476
- <p class="demoFitLine"><span id="demoFitSum">3 people × 4 hours = 12 builder-hours.</span> <span id="demoFitVerdict">Enough for a small version. Trim it to one shared list, voting and a winner reveal; accounts and notifications will not fit.</span></p>
1477
- <p class="demoFitTry">What you’ll get to try</p>
1478
- <ul class="demoTry">
1479
- <li>Claim a task and ship it</li>
1480
- <li>See your work graded</li>
1481
- <li>Put an idea to a vote in the Board Room</li>
1482
- <li>Watch your rank grow with shipped work</li>
1483
- </ul>
1484
- </div>
1485
- <div class="nav">
1486
- <button type="button" class="btn ghost" id="demoBack1">← Back</button>
1487
- <button type="button" class="btn" id="demoNext1">See the settings</button>
1529
+ </div>
1530
+ <div class="wizField"><span id="physAgeLbl">Will anyone under 18 build?</span>
1531
+ <div class="typeCards physCells" role="group" aria-labelledby="physAgeLbl">
1532
+ <button type="button" class="tpill" data-phys="minors_on_board" data-val="false" aria-pressed="false">No</button>
1533
+ <button type="button" class="tpill" data-phys="minors_on_board" data-val="true" aria-pressed="false">Yes</button>
1488
1534
  </div>
1489
1535
  </div>
1490
- <div id="demoReady" class="demoView" hidden>
1491
- <h1 id="demo-h2">Your demo planet’s settings</h1>
1492
- <p class="lede">We picked sensible settings so you can start straight away, and you can change any of them later. Next you name it and choose where its code lives, and then it is created.</p>
1493
- <dl class="demoLedger">
1494
- <dt>People</dt><dd><span id="demoLPeople">3</span><small id="demoLPeopleSub">You, plus the two people you invite below</small></dd>
1495
- <dt>Time box</dt><dd><span id="demoLHours">4 hours each</span><small id="demoLTotal">12 builder-hours in total. When it runs out you are asked whether to keep your planet; your work is never stopped.</small></dd>
1496
- <dt>Government</dt><dd>Monarchy / BDFL<small>One leader decides: you make the calls.</small></dd>
1497
- <dt>Rewards</dt><dd>None<small>It’s a demo, for learning.</small></dd>
1498
- <dt>Framing</dt><dd>All managed for you</dd>
1499
- <dt>Who can see it</dt><dd>Only your team</dd>
1500
- <dt>AI usage</dt><dd>Everyone uses their own AI plan</dd>
1536
+ <div class="physNote" id="physAges" hidden>
1537
+ <p class="physNoteH">The age tiers young builders work under</p>
1538
+ <dl class="physTiers">
1539
+ <dt>13 to 15</dt><dd>Non-monetary tracks only</dd>
1540
+ <dt>16 to 17</dt><dd>Paid work, with a guardian’s co-signature and a work-permit attestation</dd>
1541
+ <dt>18 and over</dt><dd>Full access</dd>
1501
1542
  </dl>
1502
- <div class="demoInvites" id="demoInvites">
1503
- <label class="wizField"><span>Invite by GitHub handle</span>
1504
- <input type="text" id="demoInvite1" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1505
- </label>
1506
- <label class="wizField"><span>And one more</span>
1507
- <input type="text" id="demoInvite2" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1508
- </label>
1543
+ <p class="finePrint">A guardian signs for each young builder and controls their payouts. The platform does not check ages for you yet.</p>
1544
+ </div>
1545
+ <div class="wizField" id="physIntent"><span id="physWhoLbl">Who’s behind it?</span>
1546
+ <div class="typeCards physCells" role="group" aria-labelledby="physWhoLbl">
1547
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-solo" aria-pressed="false">Just me</button>
1548
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-company" aria-pressed="false">A company</button>
1549
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-nonprofit" aria-pressed="false">A nonprofit</button>
1550
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-unsure" aria-pressed="false">Not sure yet</button>
1509
1551
  </div>
1510
- <p class="finePrint">A demo nobody keeps goes offline 30 days after it starts. You are told before, and it can be brought back.</p>
1511
- <div class="nav">
1512
- <button type="button" class="btn ghost" id="demoBack2">← Back</button>
1513
- <button type="button" class="btn" id="demoStart">Name my demo planet</button>
1552
+ <button type="button" class="physLink" id="physHaveEntity">We already have a legal entity</button>
1553
+ </div>
1554
+ <div class="wizField" id="physForms" hidden><span>Your legal entity</span>
1555
+ <p class="physGrp">Organization</p>
1556
+ <div class="typeCards physCells" role="group" aria-label="Organization">
1557
+ <button type="button" class="tpill" data-phys="entity" data-val="nonprofit" aria-pressed="false">Nonprofit</button>
1558
+ <button type="button" class="tpill" data-phys="entity" data-val="for-profit" aria-pressed="false">For-profit company</button>
1559
+ <button type="button" class="tpill" data-phys="entity" data-val="corporate-division" aria-pressed="false">Division of a corporation</button>
1560
+ <button type="button" class="tpill" data-phys="entity" data-val="research" aria-pressed="false">Research entity</button>
1561
+ </div>
1562
+ <p class="physGrp">Group</p>
1563
+ <div class="typeCards physCells" role="group" aria-label="Group">
1564
+ <button type="button" class="tpill" data-phys="entity" data-val="university-project" aria-pressed="false">University project</button>
1565
+ <button type="button" class="tpill" data-phys="entity" data-val="social-group" aria-pressed="false">Social group</button>
1566
+ </div>
1567
+ <p class="physGrp">Individual</p>
1568
+ <div class="typeCards physCells" role="group" aria-label="Individual">
1569
+ <button type="button" class="tpill" data-phys="entity" data-val="single-owner-llc" aria-pressed="false">Single-owner LLC</button>
1570
+ <button type="button" class="tpill" data-phys="entity" data-val="private-individual" aria-pressed="false">Private individual</button>
1514
1571
  </div>
1572
+ <button type="button" class="physLink" id="physNoEntity">We don’t have one yet</button>
1515
1573
  </div>
1516
- </div>
1517
- <div class="forkStage" id="forkStage" aria-hidden="true">
1518
- <span class="forkOrbit"></span>
1519
- <span class="forkOrbit forkOrbitDash"></span>
1520
- <span class="demoRing" id="demoRing" hidden></span>
1521
- <img class="forkSeed" src="/assets/world/r2-chrome-blob-one-cut.webp" alt="">
1522
- <p class="demoStageCap" id="demoStageCap" hidden>time box · <span id="demoStageHours">12</span> builder-hours · when the dashed ring closes, the demo asks whether to keep your planet</p>
1523
- </div>
1574
+ <div class="hint" id="h-phys">&nbsp;</div>
1575
+ <div class="nav">
1576
+ <span></span>
1577
+ <button type="submit" class="btn" id="physNext">Continue</button>
1578
+ </div>
1579
+ </form>
1524
1580
  </div>
1525
- <div class="forkRoutes">
1526
- <div>
1527
- <p class="routeLbl">Route 1 · demo</p>
1528
- <p class="routeT">Training wheels</p>
1529
- <p class="routeD">Pick how many people, how many hours each, and an idea. The platform checks that it fits, picks the settings, and you learn by building something small. Keep your planet at the end if you like it.</p>
1530
- </div>
1531
- <div>
1532
- <p class="routeLbl">Route 2 · planet creation</p>
1533
- <p class="routeT">Create your planet</p>
1534
- <p class="routeD">Answer a few questions about your project, choose where its code and its app live, and it stands up as yours.</p>
1581
+ <!-- the screening stop: nothing is created -->
1582
+ <div id="checksStop" hidden>
1583
+ <h1 id="phys-hstop">This can’t be built <em>on <span data-brand-product-inline>Cloud Bongos</span></em></h1>
1584
+ <p class="lede" id="physStopMsg"></p>
1585
+ <div class="nav">
1586
+ <button type="button" class="btn ghost" id="physStartOver">Start over</button>
1587
+ <a class="btn" href="/contact">Contact us</a>
1535
1588
  </div>
1536
1589
  </div>
1537
1590
  </section>
1538
1591
 
1539
- <!-- step 3 · planet physics, questions 1 to 4 (task 1004418, BV2.PS07; the canvas's
1540
- Autofill, State, StateExisting, NotPossible, Geography, Ages, Entity and
1541
- EntityExisting frames, docs/design/project-startup-direction.md). ONE rail step
1542
- with its own inner sequence, so the wizard's numbering moved by one and the
1543
- canvas's seven-question rail lives inside the panel (questions 5 to 7 and the
1544
- summary joined it in BV2.PS11, task 1004419). Left: the question, its choices and the doors. Right: the field plate
1545
- with the planet forming from the world's own plates as the answers come in.
1546
- Every answer rides the create request's `detail` (planet-physics.js); the stop
1547
- screen and the soft-limit table come from POST /provisioning/physics-check, the
1548
- same screen and table the create runs. The age tiers are described, never
1549
- claimed as enforced (spec D6). -->
1550
- <section class="panel" id="panel-physics" aria-labelledby="phys-h0">
1551
- <div class="forkBody physBody">
1552
- <div>
1553
- <!-- the voice (spec D5, task 1004419): a formed organization reads the plain
1554
- professional copy, everyone else the planet's. Both variants are baked;
1555
- physPaintVoice() shows one. -->
1556
- <p class="physCrumb"><span data-voice="planet">planet creation · part 1 of 2 · planet physics</span><span data-voice="pro" hidden>project setup · part 1 of 2 · project profile</span></p>
1557
- <ol class="physRail" id="physRail" aria-label="Planet physics questions">
1558
- <li data-q="1">state</li><li data-q="2">geography</li><li data-q="3">ages</li><li data-q="4">entity</li><li data-q="5"><span data-voice="planet">builders</span><span data-voice="pro" hidden>team</span></li><li data-q="6">rewards</li><li data-q="7">framing</li>
1559
- </ol>
1560
- <form data-next="4" id="physForm">
1561
- <div class="physQ" data-q="0">
1562
- <h1 id="phys-h0">Start from your account, <em>or from scratch?</em></h1>
1563
- <p class="lede">Seven questions set your planet’s physics: the rules it will live by. We can fill in what your account already knows from your last project, and you check every answer before it counts.</p>
1564
- <div class="typeCards physCells" role="group" aria-label="How to start">
1565
- <button type="button" class="tpill" data-phys-start="account" aria-pressed="false" id="physFromAccount">Auto-fill from my account</button>
1566
- <button type="button" class="tpill" data-phys-start="scratch" aria-pressed="false">Fill it in myself</button>
1567
- </div>
1568
- <p class="physWhy" id="physAccountWhy" hidden></p>
1569
- </div>
1570
-
1571
- <div class="physQ" data-q="1" hidden>
1572
- <h1 id="phys-h1">Is your planet new, <em>or already started?</em></h1>
1573
- <div class="typeCards physCells" role="group" aria-label="Your project’s state">
1574
- <button type="button" class="tpill" data-phys="project_state" data-val="new" aria-pressed="false">New <small>Starting from zero.</small></button>
1575
- <button type="button" class="tpill" data-phys="project_state" data-val="existing" aria-pressed="false">Existing <small>My project has already been started.</small></button>
1592
+ <!-- 4 · check what we set up: every prefilled answer is a row the founder
1593
+ confirms ("Looks right") or changes in place. Change opens the step's own
1594
+ controls, moved here whole, so every handler and id is the one it always was;
1595
+ "Use this" checks the row the way its old step did and confirms it. Continue
1596
+ stays locked until every row that applies is confirmed. -->
1597
+ <section class="panel" id="panel-review" aria-labelledby="rev-h">
1598
+ <h1 id="rev-h">Check what we set up.</h1>
1599
+ <p class="lede">We filled these in from your answers. Confirm each one, or change it.</p>
1600
+ <form data-next="5" id="reviewForm">
1601
+ <div class="setRows" id="reviewRows">
1602
+
1603
+ <div class="setRow" data-row="code">
1604
+ <span class="k">Code</span>
1605
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1606
+ <div class="opts" hidden>
1607
+ <div class="typeCards physCells" role="group" aria-label="Your project’s state">
1608
+ <button type="button" class="tpill" data-phys="project_state" data-val="new" aria-pressed="false">Starting from zero</button>
1609
+ <button type="button" class="tpill" data-phys="project_state" data-val="existing" aria-pressed="false">Already started</button>
1610
+ </div>
1611
+ <fieldset class="physExisting" id="physExisting" data-limit="existing_work" hidden>
1612
+ <legend>What already exists? Pick all that apply, then your main one.</legend>
1613
+ <label class="physCheck"><input type="checkbox" data-work="code"> <span>Code files</span> <span class="physMain"><input type="radio" name="physMain" value="code" aria-label="Code files is the main one"> main</span></label>
1614
+ <label class="physCheck"><input type="checkbox" data-work="digital-files"> <span>Digital files <small>Word documents, spreadsheets, other software.</small></span> <span class="physMain"><input type="radio" name="physMain" value="digital-files" aria-label="Digital files is the main one"> main</span></label>
1615
+ <label class="physCheck"><input type="checkbox" data-work="physical-work"> <span>Physical files or work <small>You’ll describe it later, in words or a document.</small></span> <span class="physMain"><input type="radio" name="physMain" value="physical-work" aria-label="Physical files or work is the main one"> main</span></label>
1616
+ </fieldset>
1617
+ <p class="physWhy" data-why="existing_work" hidden></p>
1618
+ <div class="choices" role="group" aria-label="Starting point">
1619
+ <button type="button" class="choice" id="c-green" aria-pressed="true">
1620
+ <b>A new GitHub repo</b>
1621
+ <p>An empty <strong>public</strong> repo, set up for you.</p>
1622
+ </button>
1623
+ <button type="button" class="choice" id="c-adopt" aria-pressed="false">
1624
+ <b>A repo I already have</b>
1625
+ <p>Public or private, we never change it.</p>
1626
+ </button>
1627
+ </div>
1628
+ <div class="reassure" id="adoptNote">Nothing is overwritten. You see any conflicts first, and your open issues become tasks.</div>
1629
+ <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
1630
+ Without repo access we fall back to the manual owner/repo inputs
1631
+ (#manualHome), which stay authoritative. -->
1632
+ <div id="ghConnect" class="reassure">
1633
+ <p id="ghConnectMsg">&nbsp;</p>
1634
+ <button type="button" class="btn" id="ghConnectBtn">Allow repo access</button>
1635
+ <button type="button" class="linklike" id="ghManualToggle">or enter it manually</button>
1636
+ </div>
1637
+ <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
1638
+ <div id="ghAdopt" class="wizField" hidden>
1639
+ <span>Pick one of your repositories</span>
1640
+ <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off" aria-label="Search your repositories">
1641
+ <div id="ghList" class="ghlist" role="group" aria-label="Your repositories"></div>
1642
+ <div class="hint" id="ghAdoptHint">&nbsp;</div>
1643
+ <!-- visibility stated on EVERY repo path (task 1002721) -->
1644
+ <div class="hint">We never change a repo’s visibility — public stays public, private stays private.</div>
1645
+ </div>
1646
+ <div id="ghFresh" class="wizField" hidden>
1647
+ <span>New repository name</span>
1648
+ <input type="text" id="ghNewName" placeholder="moonbase" autocomplete="off" maxlength="100" aria-label="New repository name">
1649
+ <!-- visibility is stated on EVERY repo path (task 1002721); updateFreshPreview()
1650
+ replaces this floor with the same claim plus the resolved repo URL -->
1651
+ <div class="hint" id="ghFreshHint">We’ll create an empty <strong>public</strong> repo — projects here are open by design. Want private code? Create the repo yourself on GitHub and “adopt” it instead — a private repository gets its own read-only deploy key automatically.</div>
1652
+ </div>
1653
+ <div class="row2" id="manualHome">
1654
+ <label class="wizField"><span>GitHub owner <small>(user or org)</small></span>
1655
+ <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
1656
+ </label>
1657
+ <label class="wizField"><span>Repository name</span>
1658
+ <input type="text" id="f-repo" placeholder="moonbase" autocomplete="off" maxlength="120">
1659
+ </label>
1660
+ <!-- visibility stated BEFORE GitHub's own radio is the first place it appears (task 1002721) -->
1661
+ <div class="hint" style="grid-column:1 / -1;">Creating the repo yourself on GitHub? Public or private is your choice there — both work here, and we never change it.</div>
1662
+ <!-- the way back from the manual fallback (task 1003683) -->
1663
+ <button type="button" class="linklike" id="ghManualBack" style="grid-column:1 / -1;" hidden>Use my GitHub repositories instead</button>
1664
+ </div>
1665
+ <div class="hint" id="h-home">&nbsp;</div>
1666
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1576
1667
  </div>
1577
- <fieldset class="physExisting" id="physExisting" data-limit="existing_work" hidden>
1578
- <legend>What already exists? Pick all that apply, then your main one.</legend>
1579
- <label class="physCheck"><input type="checkbox" data-work="code"> <span>Code files</span> <span class="physMain"><input type="radio" name="physMain" value="code" aria-label="Code files is the main one"> main</span></label>
1580
- <label class="physCheck"><input type="checkbox" data-work="digital-files"> <span>Digital files <small>Word documents, spreadsheets, other software.</small></span> <span class="physMain"><input type="radio" name="physMain" value="digital-files" aria-label="Digital files is the main one"> main</span></label>
1581
- <label class="physCheck"><input type="checkbox" data-work="physical-work"> <span>Physical files or work <small>You’ll describe it in words, or upload a document, during genesis.</small></span> <span class="physMain"><input type="radio" name="physMain" value="physical-work" aria-label="Physical files or work is the main one"> main</span></label>
1582
- </fieldset>
1583
- <p class="physWhy" data-why="existing_work" hidden></p>
1584
- <label class="wizField"><span>What is your project, and what do you want <span data-brand-product-inline>Cloud Bongos</span> to do for you?</span>
1585
- <textarea id="physDesc" rows="3" maxlength="600" placeholder="A co-op farming game where the seasons never repeat."></textarea>
1586
- </label>
1587
1668
  </div>
1669
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1670
+ </div>
1588
1671
 
1589
- <div class="physQ" data-q="2" hidden>
1590
- <h1 id="phys-h2">Where is <em>everyone?</em></h1>
1591
- <p class="lede">Where you are, and where your builders will be, decides which payout and legal options we can offer. You can change it later.</p>
1592
- <div class="physRow2">
1593
- <label class="wizField"><span>Your country</span><select id="physCountry"><option value="">Choose…</option></select></label>
1594
- <label class="wizField" id="physRegionField" hidden><span>State or province</span><select id="physRegion"><option value="">Choose…</option></select></label>
1595
- </div>
1596
- <div class="wizField"><span id="physGeoLbl">Where do you expect builders to be?</span>
1597
- <div class="typeCards physCells" role="group" aria-labelledby="physGeoLbl">
1598
- <button type="button" class="tpill" data-phys="builder_geography" data-val="us-only" aria-pressed="false">US only</button>
1599
- <button type="button" class="tpill" data-phys="builder_geography" data-val="us-eu" aria-pressed="false">US and EU</button>
1600
- <button type="button" class="tpill" data-phys="builder_geography" data-val="global" aria-pressed="false">Global</button>
1601
- <button type="button" class="tpill" data-phys="builder_geography" data-val="not-sure" aria-pressed="false">Not sure yet</button>
1672
+ <!-- the address (tasks 1002704, 1002898 N8, PS19): the assigned address is
1673
+ stated, and both alternatives sit under it. It is shown and confirmed on
1674
+ every path, the demo's too — nothing is created before it is chosen. -->
1675
+ <div class="setRow" data-row="address">
1676
+ <span class="k">Address</span>
1677
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1678
+ <div class="opts" hidden>
1679
+ <label class="wizField"><span>Project handle</span>
1680
+ <input type="text" id="f-slug" placeholder="moonbase" autocomplete="off" maxlength="40" spellcheck="false">
1681
+ <div class="hint" id="h-slug">&nbsp;</div>
1682
+ </label>
1683
+ <div class="addrConfirm" id="addrConfirm">
1684
+ <div class="addrBig" id="addrAssigned"></div>
1685
+ <p id="addrAssignedNote">Yours once it’s set up. You can add your own domain any time.</p>
1686
+ <details id="addrCustom">
1687
+ <summary>A domain I own</summary>
1688
+ <div class="inner">
1689
+ <label class="wizField"><span>Domain you own</span>
1690
+ <input type="text" id="f-domain" placeholder="moonbase.example.com" autocomplete="off" maxlength="253" spellcheck="false">
1691
+ <div class="hint" id="h-domain">&nbsp;</div>
1692
+ </label>
1693
+ </div>
1694
+ </details>
1695
+ <label class="addrOpt"><input type="checkbox" id="f-noaddr">
1696
+ <span>No address yet. Sign-in can’t be switched on until it has one.</span>
1697
+ </label>
1602
1698
  </div>
1699
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1603
1700
  </div>
1604
1701
  </div>
1702
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1703
+ </div>
1605
1704
 
1606
- <div class="physQ" data-q="3" hidden>
1607
- <h1 id="phys-h3">Will anyone under 18 <em>be building?</em></h1>
1608
- <div class="typeCards physCells" role="group" aria-label="Builders under 18">
1609
- <button type="button" class="tpill" data-phys="minors_on_board" data-val="false" aria-pressed="false">No <small>Everyone building is an adult.</small></button>
1610
- <button type="button" class="tpill" data-phys="minors_on_board" data-val="true" aria-pressed="false">Yes <small>Some builders are under 18.</small></button>
1611
- </div>
1612
- <div class="physNote" id="physAges" hidden>
1613
- <p class="physNoteH">The age tiers young builders work under</p>
1614
- <dl class="physTiers">
1615
- <dt>13 to 15</dt><dd>Non-monetary tracks only</dd>
1616
- <dt>16 to 17</dt><dd>Paid work, with a guardian’s co-signature and a work-permit attestation</dd>
1617
- <dt>18 and over</dt><dd>Full access</dd>
1618
- </dl>
1619
- <p class="finePrint">Each young builder’s guardian signs for them, and their payouts go to an account the guardian controls. These are the rules your project is set up to follow; the platform does not check ages for you yet.</p>
1705
+ <!-- where the app runs (tasks 1004351, 1004352; ADR 0345): the hall is ours to
1706
+ host, so this row is only the owner's APP. "Decide later" is the default.
1707
+ The Render key lives in a variable only: never the draft, a URL or a log. -->
1708
+ <div class="setRow" data-row="app">
1709
+ <span class="k">App hosting</span>
1710
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1711
+ <div class="opts" hidden>
1712
+ <fieldset class="appOpts" id="appOpts">
1713
+ <legend>Where your app runs</legend>
1714
+ <label class="appOpt">
1715
+ <input type="radio" name="appPlatform" value="later" checked>
1716
+ <span><b>Decide later</b>
1717
+ <span>There’s no app to host until your builders write one.</span></span>
1718
+ </label>
1719
+ <label class="appOpt" id="appOptRender">
1720
+ <input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">
1721
+ <span><b>Render</b>
1722
+ <span id="appRenderWhat">We set it up on your own Render account. Render bills you directly.</span></span>
1723
+ </label>
1724
+ </fieldset>
1725
+ <p class="renderWhy" id="renderLater" role="status" hidden>Your new repo has no app yet, so there is nothing for Render to run. Once your builders write it, set it up from your project’s page. Nothing is created on Render now.</p>
1726
+ <div class="renderSetup" id="renderSetup" hidden>
1727
+ <p class="renderWhy">Your Render key is used once, to create your app on your own Render account, and then thrown away — it is not kept, not even in this browser. Render bills you directly. A key can act on your whole Render account, so only paste it here. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
1728
+ <div class="renderKeyRow">
1729
+ <label class="wizField"><span>Render API key</span>
1730
+ <input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
1731
+ </label>
1732
+ <button type="button" class="btn ghost small" id="renderCheck">Check key</button>
1733
+ </div>
1734
+ <div class="hint" id="h-renderKey" role="status">&nbsp;</div>
1735
+ <fieldset class="appOpts" id="renderWs" hidden>
1736
+ <legend>Which Render workspace</legend>
1737
+ <div id="renderWsList"></div>
1738
+ </fieldset>
1739
+ <fieldset class="appOpts" id="renderSvc" hidden>
1740
+ <legend>A new app, or one you already run</legend>
1741
+ <div id="renderSvcList"></div>
1742
+ </fieldset>
1743
+ <div class="renderNew" id="renderNew" hidden>
1744
+ <label class="wizField"><span>Your app is built with</span>
1745
+ <select id="f-renderRuntime">
1746
+ <option value="node">Node.js</option><option value="python">Python</option><option value="ruby">Ruby</option>
1747
+ <option value="go">Go</option><option value="rust">Rust</option><option value="elixir">Elixir</option>
1748
+ <option value="docker">Docker — use my repository’s Dockerfile</option>
1749
+ </select>
1750
+ </label>
1751
+ <label class="wizField" id="renderBuildWrap"><span>Build command <small>— Render needs one; the usual one is filled in</small></span>
1752
+ <input type="text" id="f-renderBuild" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm ci">
1753
+ </label>
1754
+ <label class="wizField" id="renderStartWrap"><span>Start command</span>
1755
+ <input type="text" id="f-renderStart" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm start">
1756
+ </label>
1757
+ <label class="wizField"><span>Plan</span>
1758
+ <select id="f-renderPlan"></select>
1759
+ </label>
1760
+ <label class="addrOpt"><input type="checkbox" id="f-renderDb">
1761
+ <span>My app needs a database (Render Postgres)</span>
1762
+ </label>
1763
+ <label class="wizField" id="renderDbPlanWrap" hidden><span>Database plan</span>
1764
+ <select id="f-renderDbPlan"></select>
1765
+ </label>
1766
+ </div>
1767
+ <div class="hint" id="h-render" role="status">&nbsp;</div>
1768
+ </div>
1769
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1620
1770
  </div>
1621
1771
  </div>
1772
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1773
+ </div>
1622
1774
 
1623
- <div class="physQ" data-q="4" hidden>
1624
- <h1 id="phys-h4">What <em>are you?</em></h1>
1625
- <div id="physIntent">
1626
- <p class="lede">Your planet is new, so there’s nothing to sign yet. Tell us roughly where you’re heading. You’ll make the binding choice later, once you know what you’re building.</p>
1627
- <div class="typeCards physCells" role="group" aria-label="Where you are heading">
1628
- <button type="button" class="tpill" data-phys="entity" data-val="intent-company" aria-pressed="false">Probably a company</button>
1629
- <button type="button" class="tpill" data-phys="entity" data-val="intent-nonprofit" aria-pressed="false">Probably a nonprofit</button>
1630
- <button type="button" class="tpill" data-phys="entity" data-val="intent-solo" aria-pressed="false">Just me for now</button>
1631
- <button type="button" class="tpill" data-phys="entity" data-val="intent-unsure" aria-pressed="false">Don’t know yet</button>
1632
- </div>
1633
- <button type="button" class="physLink" id="physHaveEntity">I already have a legal entity</button>
1634
- </div>
1635
- <div id="physForms" hidden>
1636
- <p class="lede">Pick the legal entity behind your project. The laws that apply, and the ways you can reward builders, depend on it.</p>
1637
- <p class="physGrp">Organization</p>
1638
- <div class="typeCards physCells" role="group" aria-label="Organization">
1639
- <button type="button" class="tpill" data-phys="entity" data-val="nonprofit" aria-pressed="false">Nonprofit</button>
1640
- <button type="button" class="tpill" data-phys="entity" data-val="for-profit" aria-pressed="false">For-profit company</button>
1641
- <button type="button" class="tpill" data-phys="entity" data-val="corporate-division" aria-pressed="false">Division of a corporation</button>
1642
- <button type="button" class="tpill" data-phys="entity" data-val="research" aria-pressed="false">Research entity</button>
1643
- </div>
1644
- <p class="physGrp">Group</p>
1645
- <div class="typeCards physCells" role="group" aria-label="Group">
1646
- <button type="button" class="tpill" data-phys="entity" data-val="university-project" aria-pressed="false">University project</button>
1647
- <button type="button" class="tpill" data-phys="entity" data-val="social-group" aria-pressed="false">Social group</button>
1775
+ <!-- the extras (tasks 1002339, 1002340): the type's suggested set until the
1776
+ owner says otherwise; the server resolves the same bundle from the same
1777
+ declaration (ADR 0237), so accepting and changing nothing are one answer.
1778
+ The always-on core is stated as a clause, never as dead toggles. -->
1779
+ <div class="setRow" data-row="extras">
1780
+ <span class="k">Extras</span>
1781
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1782
+ <div class="opts" hidden>
1783
+ <div class="wizField"><span id="modLbl">Extras for this project</span>
1784
+ <input type="text" id="modQ" placeholder="Search extras…" autocomplete="off" aria-label="Search the extras by name" aria-describedby="modFilterNote">
1785
+ <div class="hint" id="modFilterNote" aria-live="polite"></div>
1786
+ <div class="typeCards" id="modCards" role="group" aria-labelledby="modLbl"></div>
1648
1787
  </div>
1649
- <p class="physGrp">Individual</p>
1650
- <div class="typeCards physCells" role="group" aria-label="Individual">
1651
- <button type="button" class="tpill" data-phys="entity" data-val="single-owner-llc" aria-pressed="false">Single-owner LLC</button>
1652
- <button type="button" class="tpill" data-phys="entity" data-val="private-individual" aria-pressed="false">Private individual</button>
1653
- </div>
1654
- <button type="button" class="physLink" id="physNoEntity">We don’t have one yet</button>
1788
+ <div class="modWhy" id="modWhy" aria-live="polite"></div>
1789
+ <div class="reassure" id="modCoreNote"></div>
1790
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1655
1791
  </div>
1656
1792
  </div>
1793
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1794
+ </div>
1657
1795
 
1658
- <!-- questions 5 to 7 and the summary (task 1004419, BV2.PS11; the canvas's
1659
- Builders, BuildersPro, Rewards, Framing and Physics frames). Further
1660
- screens of this same step, so the wizard is not renumbered. -->
1661
- <div class="physQ" data-q="5" hidden>
1662
- <h1 id="phys-h5"><span data-voice="planet">Who will live <em>on your planet?</em></span><span data-voice="pro" hidden>How many people <em>will work on this?</em></span></h1>
1663
- <p class="lede"><span data-voice="planet">One builder, or many? This can always change.</span><span data-voice="pro" hidden>One person or a team. You can change this at any time.</span></p>
1664
- <div class="typeCards physCells" role="group" aria-label="One or many">
1665
- <button type="button" class="tpill" data-phys-solo="one" aria-pressed="false"><span><span data-voice="planet">One</span><span data-voice="pro" hidden>One person</span> <small><span data-voice="planet">Just me, building solo.</span><span data-voice="pro" hidden>A single contributor.</span></small></span></button>
1666
- <button type="button" class="tpill" data-phys-solo="many" aria-pressed="false"><span><span data-voice="planet">Many</span><span data-voice="pro" hidden>A team</span> <small><span data-voice="planet">A team, now or soon.</span><span data-voice="pro" hidden>Several contributors, now or soon.</span></small></span></button>
1667
- </div>
1668
- <div id="physTeam" hidden>
1669
- <div class="wizField"><span id="physStartLbl"><span data-voice="planet">Builders at the start, you included</span><span data-voice="pro" hidden>Contributors at the start</span></span>
1670
- <div class="demoStep" role="group" aria-labelledby="physStartLbl">
1671
- <button type="button" class="btn small ghost" data-phys-count="-1" aria-label="One fewer">−</button>
1672
- <output id="physStartN" aria-live="polite">2</output>
1673
- <button type="button" class="btn small ghost" data-phys-count="1" aria-label="One more">+</button>
1674
- </div>
1796
+ <!-- team size (question 5, task 1004419): one person, or a team with a count
1797
+ and the month-three guess the founder corrects; who can join a team is the
1798
+ team shape the extras' preselect reads (task 1002341) -->
1799
+ <div class="setRow" data-row="team">
1800
+ <span class="k">Team size</span>
1801
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1802
+ <div class="opts" hidden>
1803
+ <div class="typeCards physCells" role="group" aria-label="One or many">
1804
+ <button type="button" class="tpill" data-phys-solo="one" aria-pressed="false">One person</button>
1805
+ <button type="button" class="tpill" data-phys-solo="many" aria-pressed="false">A team</button>
1675
1806
  </div>
1676
- <div class="physGuess">
1677
- <p class="physNoteH"><span data-voice="planet">A growth guess</span><span data-voice="pro" hidden>Our estimate</span></p>
1807
+ <div id="physTeam" hidden>
1808
+ <div class="wizField"><span id="physStartLbl">People at the start, you included</span>
1809
+ <div class="demoStep" role="group" aria-labelledby="physStartLbl">
1810
+ <button type="button" class="btn small ghost" data-phys-count="-1" aria-label="One fewer">−</button>
1811
+ <output id="physStartN" aria-live="polite">2</output>
1812
+ <button type="button" class="btn small ghost" data-phys-count="1" aria-label="One more">+</button>
1813
+ </div>
1814
+ </div>
1678
1815
  <p class="physGuessLine" id="physGuessLine" aria-live="polite"></p>
1679
- <dl class="physMonths">
1680
- <dt>Month 1</dt><dd id="physM1">2</dd>
1681
- <dt>Month 2</dt><dd id="physM2">4</dd>
1682
- <dt>Month 3</dt><dd id="physM3">5</dd>
1683
- </dl>
1684
1816
  <label class="wizField physAdjust"><span>Adjust month three</span>
1685
1817
  <input type="range" id="physExpected" min="2" max="30" step="1" value="5">
1686
1818
  </label>
1819
+ <div class="wizField"><span id="teamLbl">Who can join?</span>
1820
+ <div class="typeCards" id="teamCards" role="group" aria-labelledby="teamLbl">
1821
+ <button type="button" class="tpill" data-team="small-team" aria-pressed="false">People I invite</button>
1822
+ <button type="button" class="tpill" data-team="community" aria-pressed="false">Anyone can apply</button>
1823
+ </div>
1824
+ </div>
1687
1825
  </div>
1826
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1688
1827
  </div>
1689
1828
  </div>
1829
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1830
+ </div>
1690
1831
 
1691
- <div class="physQ" data-q="6" hidden>
1692
- <h1 id="phys-h6"><span data-voice="planet">How will builders <em>be rewarded?</em></span><span data-voice="pro" hidden>How will contributors <em>be rewarded?</em></span></h1>
1693
- <p class="lede"><span data-voice="planet">Pick everything you’re considering. This is intent only: you’ll design the binding rewards later, once your entity is settled.</span><span data-voice="pro" hidden>Select every option you are considering. Nothing here is binding: the reward terms are set later, once the entity is settled.</span></p>
1694
- <div class="typeCards physCells physOpts" role="group" aria-label="Rewards being considered">
1695
- <button type="button" class="tpill" data-phys="reward_intents" data-val="non-monetary" aria-pressed="false"><span>Non-monetary <small>Entertainment, learning, experience, volunteering.</small></span></button>
1696
- <button type="button" class="tpill" data-phys="reward_intents" data-val="cash" aria-pressed="false"><span>Cash for work <small>Paid in money for the work done.</small></span></button>
1697
- <button type="button" class="tpill" data-phys="reward_intents" data-val="equity" aria-pressed="false"><span>Equity or shares <small>A stake in the entity.</small></span></button>
1698
- <button type="button" class="tpill" data-phys="reward_intents" data-val="future-payouts" aria-pressed="false"><span>Future profits or payouts <small>Paid later, tied to conditions you set.</small></span></button>
1699
- </div>
1700
- <ul class="physWhyList" data-why-list="reward_intents" hidden></ul>
1701
- </div>
1702
-
1703
- <div class="physQ" data-q="7" hidden>
1704
- <h1 id="phys-h7">How much should <em><span data-brand-product-inline>Cloud Bongos</span> run for you?</em></h1>
1705
- <p class="lede">Not binding. It just speeds up your setup.</p>
1706
- <div class="typeCards physCells physOpts" role="group" aria-label="How much is run for you">
1707
- <button type="button" class="tpill" data-phys="framing" data-val="managed" aria-pressed="false"><span>A · All managed <small>The full suite, run for you.</small></span></button>
1708
- <button type="button" class="tpill" data-phys="framing" data-val="hybrid" aria-pressed="false"><span>B · Hybrid <small>Choose what you want from the full suite.</small></span></button>
1709
- <button type="button" class="tpill" data-phys="framing" data-val="local" aria-pressed="false"><span>C · Local or internal <small>Choose from the suite, and keep your data on your own machines.</small></span></button>
1710
- <button type="button" class="tpill" data-phys="framing" data-val="open-source" aria-pressed="false"><span>D · Open source, local <small>Just the free core. Build on your own machines and pay only for what you add.</small></span></button>
1711
- </div>
1712
- <p class="physWhy" data-why="services" hidden></p>
1713
- <fieldset class="physSuite" id="physSuite" hidden>
1714
- <legend id="physSuiteLbl">Your hybrid suite</legend>
1715
- <div class="typeCards physCells physSuiteGrid">
1716
- <button type="button" class="tpill" data-phys="services" data-val="hosting" aria-pressed="false">Hosting</button>
1717
- <button type="button" class="tpill" data-phys="services" data-val="labor-payment" aria-pressed="false">Labor payment and HR</button>
1718
- <button type="button" class="tpill" data-phys="services" data-val="starter-kit" aria-pressed="false">Starting module kit</button>
1719
- <button type="button" class="tpill" data-phys="services" data-val="marketplace-buy" aria-pressed="false">Module marketplace: buy</button>
1720
- <button type="button" class="tpill" data-phys="services" data-val="marketplace-sell" aria-pressed="false">Module marketplace: sell</button>
1721
- <button type="button" class="tpill" data-phys="services" data-val="guild-access" aria-pressed="false">Builder guild access</button>
1722
- <button type="button" class="tpill" data-phys="services" data-val="guild-create" aria-pressed="false">Create a builder guild</button>
1723
- <button type="button" class="tpill" data-phys="services" data-val="company-creation" aria-pressed="false">Company creation</button>
1724
- <button type="button" class="tpill" data-phys="services" data-val="cbaas-consulting" aria-pressed="false">Expert consulting</button>
1725
- <button type="button" class="tpill" data-phys="services" data-val="public-marketing" aria-pressed="false">Public marketed project</button>
1832
+ <!-- rewards (question 6): intent only, never asked of a founder building
1833
+ alone (the table hides it, and this row goes with it) -->
1834
+ <div class="setRow" data-row="rewards">
1835
+ <span class="k">Rewards</span>
1836
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1837
+ <div class="opts" hidden>
1838
+ <div class="typeCards physCells" role="group" aria-label="Rewards being considered">
1839
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="non-monetary" aria-pressed="false">Learning and fun</button>
1840
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="cash" aria-pressed="false">Cash for work</button>
1841
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="equity" aria-pressed="false">Equity</button>
1842
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="future-payouts" aria-pressed="false">Future payouts</button>
1726
1843
  </div>
1727
- <ul class="physWhyList" data-why-list="services" hidden></ul>
1728
- </fieldset>
1729
- </div>
1730
-
1731
- <div class="physQ" data-q="sum" hidden>
1732
- <h1 id="phys-hsum"><span data-voice="planet">Your planet’s <em>physics</em></span><span data-voice="pro" hidden>Your project’s <em>profile</em></span></h1>
1733
- <p class="lede"><span data-voice="planet">This is what shapes your planet. Change any answer before it’s born.</span><span data-voice="pro" hidden>This is what shapes your project. Change any answer before it is created.</span></p>
1734
- <dl class="demoLedger physLedger" id="physSummary"></dl>
1735
- </div>
1736
-
1737
- <div class="physQ" data-q="stop" hidden>
1738
- <h1 id="phys-hstop">This can’t be built <em>on <span data-brand-product-inline>Cloud Bongos</span></em></h1>
1739
- <p class="lede" id="physStopMsg"></p>
1740
- <div class="nav">
1741
- <button type="button" class="btn ghost" id="physStartOver">Start over</button>
1742
- <a class="btn" href="/contact">Contact us</a>
1844
+ <ul class="physWhyList" data-why-list="reward_intents" hidden></ul>
1845
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1743
1846
  </div>
1744
1847
  </div>
1745
-
1746
- <div class="hint" id="h-phys">&nbsp;</div>
1747
- <div class="nav" id="physNav">
1748
- <button type="button" class="btn ghost" id="physBack">← Back</button>
1749
- <button type="submit" class="btn" id="physNext">Continue →</button>
1750
- </div>
1751
- </form>
1752
- </div>
1753
- <div class="forkStage physStage" id="physStage" aria-hidden="true">
1754
- <span class="forkOrbit"></span>
1755
- <span class="forkOrbit forkOrbitDash"></span>
1756
- <!-- task 1004419: rings for the rewards being considered, moons for the
1757
- builders (the faint ones on the dashed orbit are the expected growth), and
1758
- the planet's orbit around the platform's mark, set by the framing -->
1759
- <span class="physBelt" data-ring="1" hidden></span><span class="physBelt" data-ring="2" hidden></span><span class="physBelt" data-ring="3" hidden></span><span class="physBelt" data-ring="4" hidden></span>
1760
- <span class="physGhosts" id="physGhosts" hidden><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""></span>
1761
- <span class="physMoons" id="physMoons" hidden><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""><img src="/assets/world/r3-frost-bead-cut.webp" alt=""></span>
1762
- <span class="physTether" id="physTether" hidden></span>
1763
- <span class="physGuard" id="physGuard" hidden></span>
1764
- <img class="forkSeed physPlanet" id="physPlanet" src="/assets/world/r2-chrome-blob-one-cut.webp" alt="">
1765
- <img class="physSun" id="physSun" src="/assets/world/r2-bongos-chrome-a-cut.webp" alt="" hidden>
1766
- <p class="physCap"><span id="physCapLbl">planet physics</span> · <span id="physCapN">0 of 7</span></p>
1767
- <div class="physFoot"><p class="physCallout" id="physCallout">physics · the rules it lives by · each answer shapes your planet here</p>
1768
- <p class="physCoords" id="physCoords"></p></div>
1769
- </div>
1770
- </div>
1771
- </section>
1772
-
1773
- <!-- step 4 · name (the hub's original step-1 form, now the wizard's) -->
1774
- <section class="panel" id="panel-name" aria-labelledby="name-h">
1775
- <h1 id="name-h">Start a project</h1>
1776
- <p class="lede">Your own project: its own repository, its own home on the web, and a hall of AI builders
1777
- who do the building. You set the direction; agents lay down the code.</p>
1778
- <form id="newForm" data-next="5">
1779
- <label class="wizField"><span>Project name</span>
1780
- <input type="text" id="newName" placeholder="What should it be called?" autocomplete="off" maxlength="80" aria-label="Project name">
1781
- <div class="hint" id="h-name">&nbsp;</div>
1782
- </label>
1783
- <div class="nav">
1784
- <span></span>
1785
- <button type="submit" class="btn">Continue →</button>
1786
- </div>
1787
- </form>
1788
- <p class="finePrint">Free while we find our feet. Your project’s repo and server are yours —
1789
- <span data-brand-product-inline>Cloud Bongos</span> is <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">open source</a>
1790
- (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>)
1791
- and capture-resistant by design.</p>
1792
- </section>
1793
-
1794
- <!-- step 2 · type (task 1002772) — a DECLARATION, closed vocabulary
1795
- (owner decision 2026-08-13): Game / Research / Business /
1796
- Non-Profit / Not Sure, selectable buttons with a themed
1797
- description card. Not Sure is the escape hatch — nobody is
1798
- forced into a wrong answer, and it can change later. -->
1799
- <section class="panel" id="panel-type" aria-labelledby="type-h">
1800
- <h1 id="type-h">What kind of project is it?</h1>
1801
- <p class="lede">Pick the shape that fits best — it’s how the project is listed on the map, and you can change it later from the project’s own page. Not sure is a fine answer.</p>
1802
- <form data-next="6">
1803
- <div class="typeCards" id="typeCards" role="group" aria-label="Project type">
1804
- <button type="button" class="tpill" data-type="game" aria-pressed="false">Game</button>
1805
- <button type="button" class="tpill" data-type="research" aria-pressed="false">Research</button>
1806
- <button type="button" class="tpill" data-type="business" aria-pressed="false">Business</button>
1807
- <button type="button" class="tpill" data-type="non-profit" aria-pressed="false">Non-Profit</button>
1808
- <button type="button" class="tpill" data-type="not-sure" aria-pressed="false">Not Sure</button>
1809
- </div>
1810
- <div class="typeCard" id="typeCard" hidden aria-live="polite">
1811
- <b id="typeCardName"></b>
1812
- <p id="typeCardDesc"></p>
1813
- </div>
1814
- <!-- pre-filled from the planet-physics answers (task 1004419), and says so -->
1815
- <p class="physWhy" id="typePrefill" hidden></p>
1816
- <div class="hint" id="h-projtype">&nbsp;</div>
1817
- <div class="nav">
1818
- <button type="button" class="btn ghost" data-back="4">← Back</button>
1819
- <button type="submit" class="btn">Next →</button>
1820
- </div>
1821
- </form>
1822
- </section>
1823
-
1824
- <!-- step 3 · about (task 1002334) — the OPTIONAL project-detail step.
1825
- The one panel in the wizard that can be walked past without an
1826
- answer, so the skip is a real button of its own, sitting beside
1827
- Next rather than hidden as "leave it blank and press Next" — an
1828
- optional step whose escape hatch has to be inferred is not
1829
- optional in practice. What skipping costs is stated ON the panel
1830
- (ADR 0182 D4: no description ⇒ the project is off the public map
1831
- until it has one), because a consequence discovered afterwards is
1832
- not a choice the owner made. -->
1833
- <section class="panel" id="panel-detail" aria-labelledby="detail-h">
1834
- <h1 id="detail-h">Tell us about it</h1>
1835
- <p class="lede">Optional — skip it and nothing about your project changes. Answering shapes what we set up for you, and a description is what puts the project on the public map.</p>
1836
- <form data-next="7">
1837
- <label class="wizField"><span>What are you building? <small>(a sentence or two)</small></span>
1838
- <textarea id="f-desc" rows="3" maxlength="600" placeholder="A co-op farming game where the seasons never repeat."></textarea>
1839
- </label>
1840
- <div class="wizField"><span id="teamLbl">Who’s building it?</span>
1841
- <div class="typeCards" id="teamCards" role="group" aria-labelledby="teamLbl">
1842
- <button type="button" class="tpill" data-team="solo" aria-pressed="false">Just me</button>
1843
- <button type="button" class="tpill" data-team="small-team" aria-pressed="false">A small team</button>
1844
- <button type="button" class="tpill" data-team="community" aria-pressed="false">An open community</button>
1848
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1845
1849
  </div>
1846
- </div>
1847
- <div class="reassure" id="detailSkipNote"></div>
1848
- <div class="nav">
1849
- <button type="button" class="btn ghost" data-back="5">← Back</button>
1850
- <span class="navPair">
1851
- <button type="button" class="btn ghost" id="skipDetail">Skip for now</button>
1852
- <button type="submit" class="btn">Next →</button>
1853
- </span>
1854
- </div>
1855
- </form>
1856
- </section>
1857
-
1858
- <!-- step 4 · modules (task 1002339, BV1.R20) — the picker. Skippable
1859
- like About, and for the same reason: an owner who has met none of
1860
- these modules should not have to audit a list to get a project.
1861
- What Skip means here is spelled out on the panel and is TRUE by
1862
- construction — the request omits the field, and the server resolves
1863
- the same starter bundle this panel preselected (ADR 0237), so the
1864
- set is the one they were shown either way.
1865
-
1866
- The always-on core is stated as a clause, never as a row of dead
1867
- toggles: sixteen disabled controls read as choices the owner failed
1868
- to make, when in fact they were never choices. -->
1869
- <section class="panel" id="panel-modules" aria-labelledby="mod-h">
1870
- <h1 id="mod-h">What it comes with</h1>
1871
- <p class="lede">Optional — skip it and you get the suggested set below, shaped by what you just told us. These are extras on top of everything every project already has, and you can switch any of them on or off later.</p>
1872
- <form data-next="8">
1873
- <div class="wizField"><span id="modLbl">Extras for this project</span>
1874
- <!-- the catalog search (task 1002340, BV1.R21). The wizard's own
1875
- search idiom (#ghSearch): a plain field on one hairline, no
1876
- new class and no icon. It filters the band below and NOTHING
1877
- else — what is switched on is unaffected by what is on
1878
- screen, and the line under it counts the ones it is hiding
1879
- (ADR 0245). -->
1880
- <input type="text" id="modQ" placeholder="Search extras…" autocomplete="off" aria-label="Search the extras by name" aria-describedby="modFilterNote">
1881
- <div class="hint" id="modFilterNote" aria-live="polite"></div>
1882
- <div class="typeCards" id="modCards" role="group" aria-labelledby="modLbl"></div>
1883
- </div>
1884
- <div class="modWhy" id="modWhy" aria-live="polite"></div>
1885
- <div class="reassure" id="modCoreNote"></div>
1886
- <div class="hint" id="h-modules">&nbsp;</div>
1887
- <div class="nav">
1888
- <button type="button" class="btn ghost" data-back="6">← Back</button>
1889
- <span class="navPair">
1890
- <button type="button" class="btn ghost" id="skipModules">Skip for now</button>
1891
- <button type="submit" class="btn">Next →</button>
1892
- </span>
1893
- </div>
1894
- </form>
1895
- </section>
1896
1850
 
1897
- <!-- step 5 · home (repo + mode) -->
1898
- <section class="panel" id="panel-home" aria-labelledby="home-h">
1899
- <h1 id="home-h">Where the code lives</h1>
1900
- <p class="lede">Every project is a real GitHub repository that you own. Start from a clean slate, or bring a repo you already have.</p>
1901
- <form data-next="9">
1902
- <div class="choices" role="group" aria-label="Starting point">
1903
- <button type="button" class="choice" id="c-green" aria-pressed="true">
1904
- <!-- token-built, not platform orange — public-landing/ ships wholesale to
1905
- every instance (C5), so a re-branded accent must carry these too -->
1906
- <svg width="30" height="30" viewBox="0 0 30 30" fill="none" aria-hidden="true"><path d="M15 26V14" stroke="var(--accent)" stroke-width="2.6" stroke-linecap="round"/><path d="M15 15c0-5 3.5-8.5 9-8.5 0 5.5-3.5 8.5-9 8.5Z" fill="color-mix(in srgb,var(--accent) 45%,var(--bg-card))" stroke="var(--accent)" stroke-width="2"/><path d="M15 18c0-3.8-2.6-6.5-7-6.5 0 4.3 2.7 6.5 7 6.5Z" fill="color-mix(in srgb,var(--accent) 22%,var(--bg-card))" stroke="var(--accent)" stroke-width="2"/></svg>
1907
- <b>Start fresh</b>
1908
- <p>A new, empty <strong>public</strong> repo. We scaffold everything: config, first version, kickoff tasks.</p>
1909
- </button>
1910
- <button type="button" class="choice" id="c-adopt" aria-pressed="false">
1911
- <svg width="30" height="30" viewBox="0 0 30 30" fill="none" aria-hidden="true"><rect x="4" y="9" width="22" height="16" rx="3" fill="color-mix(in srgb,var(--accent) 22%,var(--bg-card))" stroke="var(--accent)" stroke-width="2"/><path d="M4 13h22" stroke="var(--accent)" stroke-width="2"/><path d="M10 9V6.5A1.5 1.5 0 0 1 11.5 5h7A1.5 1.5 0 0 1 20 6.5V9" stroke="var(--accent)" stroke-width="2"/><path d="M12 19h6" stroke="var(--accent-deep)" stroke-width="2.4" stroke-linecap="round"/></svg>
1912
- <b>Adopt a repo I have</b>
1913
- <p>Layer <span data-brand-product-inline>Cloud Bongos</span> onto an existing repository — history, code, and backlog intact. Public or private, we never change it.</p>
1914
- </button>
1915
- </div>
1916
- <!-- pre-filled from the planet-physics answers (task 1004419), and says so -->
1917
- <p class="physWhy" id="homePrefill" hidden></p>
1918
- <div class="reassure" id="adoptNote">Adopting never overwrites your files: you get a conflict pre-flight first, nothing is written until you accept, and your existing backlog (issues, TODOs) is imported as real tasks.</div>
1919
-
1920
- <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
1921
- Progressive enhancement: when the signed-in user grants repo access we let
1922
- them PICK a repo (adopt) or NAME one we auto-create (fresh). Without that
1923
- access — not granted yet, or the server can't store the token — we fall back
1924
- to the manual owner/repo inputs below (#manualHome), which stay authoritative. -->
1925
- <div id="ghConnect" class="reassure">
1926
- <p id="ghConnectMsg">&nbsp;</p>
1927
- <button type="button" class="btn" id="ghConnectBtn">Allow repo access</button>
1928
- <button type="button" class="linklike" id="ghManualToggle">or enter it manually</button>
1929
- </div>
1930
- <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
1931
- <div id="ghAdopt" class="wizField" hidden>
1932
- <span>Pick one of your repositories</span>
1933
- <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off" aria-label="Search your repositories">
1934
- <div id="ghList" class="ghlist" role="group" aria-label="Your repositories"></div>
1935
- <div class="hint" id="ghAdoptHint">&nbsp;</div>
1936
- <!-- visibility stated on EVERY repo path (task 1002721) — the picker
1937
- marks private repos, and this says what adoption does about it -->
1938
- <div class="hint">We never change a repo’s visibility — public stays public, private stays private.</div>
1939
- </div>
1940
- <div id="ghFresh" class="wizField" hidden>
1941
- <span>New repository name</span>
1942
- <input type="text" id="ghNewName" placeholder="mercury" autocomplete="off" maxlength="100" aria-label="New repository name">
1943
- <!-- visibility is stated on EVERY repo path (task 1002721) — this baked
1944
- line is the floor; updateFreshPreview() replaces it with the same
1945
- claim plus the resolved repo URL once a name is typed -->
1946
- <div class="hint" id="ghFreshHint">We’ll create an empty <strong>public</strong> repo — projects here are open by design. Want private code? Create the repo yourself on GitHub and “adopt” it instead — a private repository gets its own read-only deploy key automatically.</div>
1947
- </div>
1948
-
1949
- <div class="row2" id="manualHome">
1950
- <label class="wizField"><span>GitHub owner <small>(user or org)</small></span>
1951
- <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
1952
- </label>
1953
- <label class="wizField"><span>Repository name</span>
1954
- <input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
1955
- </label>
1956
- <!-- visibility stated BEFORE GitHub's own radio is the first place it
1957
- appears (task 1002721, owner firsthand in the QA walk) -->
1958
- <div class="hint" style="grid-column:1 / -1;">Creating the repo yourself on GitHub? Public or private is your choice there — both work here, and we never change it.</div>
1959
- <!-- the way back from the manual fallback (task 1003683). Choosing "or enter
1960
- it manually" used to hide the connect offer for good: #ghConnect renders
1961
- only while repoAccess is 'need-connect', and nothing ever set it back, so
1962
- a full reload was the only route to the button. Stays hidden when the
1963
- fallback was FORCED (repoAccess 'unavailable'), where connecting cannot
1964
- help and the offer would be a lie. -->
1965
- <button type="button" class="linklike" id="ghManualBack" style="grid-column:1 / -1;" hidden>Use my GitHub repositories instead</button>
1966
- </div>
1967
- <div class="hint" id="h-home">&nbsp;</div>
1968
- <div class="nav">
1969
- <button type="button" class="btn ghost" data-back="7">← Back</button>
1970
- <button type="submit" class="btn">Next →</button>
1971
- </div>
1972
- </form>
1973
- </section>
1974
-
1975
- <!-- step 6 · address — a CONFIRMATION, not a form (task 1002704: every project
1976
- gets its slug subdomain assigned up front). The domainless default was a
1977
- proven dead-end (QA walk, task 1002702).
1978
- Three choices, all legible without opening anything (task 1002898 N8):
1979
- the assigned address is stated, and BOTH alternatives sit under it — the
1980
- bring-your-own domain behind its disclosure, the no-address opt-out in the
1981
- open. The opt-out used to live INSIDE the "domain I own" fold, i.e. the
1982
- choice for NO address was hidden under a fold labelled the opposite
1983
- choice; the owner read the step as "weird" and never found it. -->
1984
- <section class="panel" id="panel-address" aria-labelledby="addr-h">
1985
- <h1 id="addr-h">Its address in the sky</h1>
1986
- <p class="lede">The handle names your project on the platform — and its address comes with it. Once it stands up, a few GitHub steps (the next screen walks you through them) switch sign-in on.</p>
1987
- <form data-next="10">
1988
- <label class="wizField"><span>Project handle</span>
1989
- <input type="text" id="f-slug" placeholder="mercury" autocomplete="off" maxlength="40" spellcheck="false">
1990
- <div class="hint" id="h-slug">&nbsp;</div>
1991
- </label>
1992
- <div class="addrConfirm" id="addrConfirm">
1993
- <div class="addrBig" id="addrAssigned"></div>
1994
- <p id="addrAssignedNote">Yours from the moment it stands up. You can point a domain you own at it any time.</p>
1995
- <details id="addrCustom">
1996
- <summary>Use a domain I own instead</summary>
1997
- <div class="inner">
1998
- <label class="wizField"><span>Domain you own</span>
1999
- <input type="text" id="f-domain" placeholder="mercury.example.com" autocomplete="off" maxlength="253" spellcheck="false">
2000
- <div class="hint" id="h-domain">&nbsp;</div>
2001
- </label>
1851
+ <!-- how much we run (question 7): a hybrid or local framing opens its suite -->
1852
+ <div class="setRow" data-row="run">
1853
+ <span class="k">We run</span>
1854
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1855
+ <div class="opts" hidden>
1856
+ <div class="typeCards physCells" role="group" aria-label="How much is run for you">
1857
+ <button type="button" class="tpill" data-phys="framing" data-val="managed" aria-pressed="false">Everything, for you</button>
1858
+ <button type="button" class="tpill" data-phys="framing" data-val="hybrid" aria-pressed="false">Some of it</button>
1859
+ <button type="button" class="tpill" data-phys="framing" data-val="local" aria-pressed="false">On my machines</button>
1860
+ <button type="button" class="tpill" data-phys="framing" data-val="open-source" aria-pressed="false">Free core only</button>
1861
+ </div>
1862
+ <p class="physWhy" data-why="services" hidden></p>
1863
+ <fieldset class="physSuite" id="physSuite" hidden>
1864
+ <legend id="physSuiteLbl">Your hybrid suite</legend>
1865
+ <div class="typeCards physCells physSuiteGrid">
1866
+ <button type="button" class="tpill" data-phys="services" data-val="hosting" aria-pressed="false">Hosting</button>
1867
+ <button type="button" class="tpill" data-phys="services" data-val="labor-payment" aria-pressed="false">Labor payment and HR</button>
1868
+ <button type="button" class="tpill" data-phys="services" data-val="starter-kit" aria-pressed="false">Starting module kit</button>
1869
+ <button type="button" class="tpill" data-phys="services" data-val="marketplace-buy" aria-pressed="false">Module marketplace: buy</button>
1870
+ <button type="button" class="tpill" data-phys="services" data-val="marketplace-sell" aria-pressed="false">Module marketplace: sell</button>
1871
+ <button type="button" class="tpill" data-phys="services" data-val="guild-access" aria-pressed="false">Builder guild access</button>
1872
+ <button type="button" class="tpill" data-phys="services" data-val="guild-create" aria-pressed="false">Create a builder guild</button>
1873
+ <button type="button" class="tpill" data-phys="services" data-val="company-creation" aria-pressed="false">Company creation</button>
1874
+ <button type="button" class="tpill" data-phys="services" data-val="cbaas-consulting" aria-pressed="false">Expert consulting</button>
1875
+ <button type="button" class="tpill" data-phys="services" data-val="public-marketing" aria-pressed="false">Public marketed project</button>
1876
+ </div>
1877
+ <ul class="physWhyList" data-why-list="services" hidden></ul>
1878
+ </fieldset>
1879
+ <button type="button" class="btn small rowUse" data-row-use>Use this</button>
1880
+ </div>
2002
1881
  </div>
2003
- </details>
2004
- <label class="addrOpt"><input type="checkbox" id="f-noaddr">
2005
- <span>Start without an address — possible, but sign-in on your project can’t be set up until it has one.</span>
2006
- </label>
2007
- </div>
2008
- <div class="nav">
2009
- <button type="button" class="btn ghost" data-back="8">← Back</button>
2010
- <button type="submit" class="btn">Next →</button>
2011
- </div>
2012
- </form>
2013
- </section>
2014
-
2015
- <!-- step 7 · where the app runs (task 1004351, ADR 0345). The hall is ours to
2016
- host, so this step is only about the owner's APP. "Decide later" is the
2017
- owner's ruling and is always the default. Render (task 1004352) asks for
2018
- the owner's key, checks it, and makes them CHOOSE the workspace — a
2019
- Render key reaches every workspace its user is in, so nothing is
2020
- pre-picked. The key lives in a variable only: never in the draft, a
2021
- URL, a log or any browser store. -->
2022
- <section class="panel" id="panel-app" aria-labelledby="app-h">
2023
- <h1 id="app-h">Where your app will run</h1>
2024
- <p class="appHall"><b>We host your Builders' hall</b> — the place where the project gets built — at the address you just chose. There is nothing for you to set up.</p>
2025
- <p class="lede">Your app is the thing the project builds. Choose where it should run, or decide later.</p>
2026
- <form data-next="11">
2027
- <fieldset class="appOpts" id="appOpts">
2028
- <legend>Where your app runs</legend>
2029
- <label class="appOpt" id="appOptRender">
2030
- <input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">
2031
- <span><b>Render</b>
2032
- <span id="appRenderWhat">We set your app up on your own Render account. You lend us your Render key once, and Render bills you directly.</span></span>
2033
- </label>
2034
- <label class="appOpt">
2035
- <input type="radio" name="appPlatform" value="later" checked>
2036
- <span><b>Decide later</b>
2037
- <span>Create the project now. You can choose where your app runs afterwards.</span></span>
2038
- </label>
2039
- </fieldset>
2040
- <p class="renderWhy" id="renderLater" role="status" hidden>Your new repository has no app in it yet, so there is nothing for Render to run today. Once your builders have written the app, set it up on Render from your project’s page — it takes a minute, and asks for your Render key once. Nothing is created on Render now.</p>
2041
- <div class="renderSetup" id="renderSetup" hidden>
2042
- <p class="renderWhy">Your Render key is used once, to create your app on your own Render account, and then thrown away — it is not kept, not even in this browser. Render bills you directly. A key can act on your whole Render account, so only paste it here. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
2043
- <div class="renderKeyRow">
2044
- <label class="wizField"><span>Render API key</span>
2045
- <input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
2046
- </label>
2047
- <button type="button" class="btn ghost small" id="renderCheck">Check key</button>
1882
+ <span class="acts"><button type="button" class="rowOk" data-row-ok>Looks right</button><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
2048
1883
  </div>
2049
- <div class="hint" id="h-renderKey" role="status">&nbsp;</div>
2050
- <fieldset class="appOpts" id="renderWs" hidden>
2051
- <legend>Which Render workspace</legend>
2052
- <div id="renderWsList"></div>
2053
- </fieldset>
2054
- <fieldset class="appOpts" id="renderSvc" hidden>
2055
- <legend>A new app, or one you already run</legend>
2056
- <div id="renderSvcList"></div>
2057
- </fieldset>
2058
- <div class="renderNew" id="renderNew" hidden>
2059
- <label class="wizField"><span>Your app is built with</span>
2060
- <select id="f-renderRuntime">
2061
- <option value="node">Node.js</option><option value="python">Python</option><option value="ruby">Ruby</option>
2062
- <option value="go">Go</option><option value="rust">Rust</option><option value="elixir">Elixir</option>
2063
- <option value="docker">Docker — use my repository’s Dockerfile</option>
2064
- </select>
2065
- </label>
2066
- <label class="wizField" id="renderBuildWrap"><span>Build command <small>— Render needs one; the usual one is filled in</small></span>
2067
- <input type="text" id="f-renderBuild" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm ci">
2068
- </label>
2069
- <label class="wizField" id="renderStartWrap"><span>Start command</span>
2070
- <input type="text" id="f-renderStart" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm start">
2071
- </label>
2072
- <label class="wizField"><span>Plan</span>
2073
- <select id="f-renderPlan"></select>
2074
- </label>
2075
- <label class="addrOpt"><input type="checkbox" id="f-renderDb">
2076
- <span>My app needs a database (Render Postgres)</span>
2077
- </label>
2078
- <label class="wizField" id="renderDbPlanWrap" hidden><span>Database plan</span>
2079
- <select id="f-renderDbPlan"></select>
2080
- </label>
2081
- </div>
2082
- <div class="hint" id="h-render" role="status">&nbsp;</div>
2083
1884
  </div>
1885
+ <div class="hint" id="h-review">&nbsp;</div>
2084
1886
  <div class="nav">
2085
- <button type="button" class="btn ghost" data-back="9">← Back</button>
2086
- <button type="submit" class="btn">Next →</button>
1887
+ <span></span>
1888
+ <button type="submit" class="btn" id="reviewGo" disabled>Confirm 7 more</button>
2087
1889
  </div>
2088
1890
  </form>
2089
1891
  </section>
2090
1892
 
2091
- <!-- step 8 · a look (task 1004421, ADR 0358): colours for light and dark, the
2092
- night tint and a logo, so builders in several projects always know which hall
2093
- they are in. lookKit.buildLookPicker fills it — the same picker the manage
2094
- page's "Its look" uses — and the choice is sent right after Create (sendLook). -->
1893
+ <!-- 5 · a look, then Create (task 1004421, ADR 0358): colours and a logo so builders
1894
+ in several projects always know which hall they are in. lookKit.buildLookPicker
1895
+ fills it — the same picker the manage page's "Its look" uses — and the choice is
1896
+ sent right after Create (sendLook). -->
2095
1897
  <section class="panel" id="panel-look" aria-labelledby="look-h">
2096
- <h1 id="look-h">Give it a look</h1>
2097
- <p class="lede">Colours for light and dark mode, and a logo, so builders who work in several projects always know which hall they’re in.</p>
2098
- <form data-next="12">
2099
- <div class="lookPick" id="wizLook"></div>
2100
- <div class="nav">
2101
- <button type="button" class="btn ghost" data-back="10">← Back</button>
2102
- <button type="submit" class="btn">Next →</button>
2103
- </div>
2104
- </form>
2105
- </section>
2106
-
2107
- <!-- step 9 · review + create -->
2108
- <section class="panel" id="panel-review" aria-labelledby="rev-h">
2109
- <h1 id="rev-h">Ready to roll</h1>
2110
- <p class="lede">One tap files the request and the platform starts standing your project up — server, database, address. Here's the beat you're about to drop:</p>
2111
- <ul class="review" id="reviewList"></ul>
1898
+ <h1 id="look-h">Give it a look.</h1>
1899
+ <p class="lede">So builders always know which project they’re in.</p>
1900
+ <div class="lookPick" id="wizLook"></div>
1901
+ <p class="wizRecap" id="wizRecap"></p>
2112
1902
  <div class="alert" id="createAlert" role="alert"></div>
2113
1903
  <div class="nav">
2114
- <button type="button" class="btn ghost" data-back="11">← Back</button>
1904
+ <span></span>
2115
1905
  <button type="button" class="btn big" id="doCreate">Create my project</button>
2116
1906
  </div>
2117
- <p class="finePrint">This files the request; the control plane does the real standup and nothing touches servers from this page. You'll get the few steps only you can do right after.</p>
1907
+ <p class="finePrint">Right after, you’ll get the few steps only you can do.</p>
2118
1908
  </section>
2119
1909
 
2120
- <!-- done · the human steps -->
1910
+ <!-- 6 · done · the human steps -->
2121
1911
  <section class="panel" id="panel-done" aria-labelledby="done-h">
2122
1912
  <div class="doneHead">
2123
1913
  <span class="tick" aria-hidden="true"><svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M4 11.5 9 16.5 18 6" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
2124
- <h1 id="done-h">The drums are rolling</h1>
1914
+ <h1 id="done-h">Setting up your project</h1>
2125
1915
  </div>
2126
1916
  <p class="lede" id="doneLede"></p>
2127
1917
  <p class="hint" id="doneRender" role="status" hidden></p>
@@ -2130,16 +1920,13 @@ summary{min-height:24px;padding:3px 0;}
2130
1920
  <!-- the STABLE live region: the strip inside is rebuilt every 5s tick,
2131
1921
  and a live region that is itself replaced never announces -->
2132
1922
  <div id="liveProgress" role="status" aria-live="polite"></div>
2133
- <!-- the invite step (task 1002346, BV1.R27) — OPTIONAL, and it stands HERE
2134
- rather than in the numbered rail because an invite needs a project that
2135
- already exists: the catalog row a client_id resolves is projected at
2136
- create (ADR 0246), so before the Create step there is nothing to invite
2137
- anyone TO. The whole reasoning is ADR 0249. Hidden until the created
2138
- record carries an id and a slug; the script is the only thing that
1923
+ <!-- the invite step (task 1002346, BV1.R27; ADR 0249) — OPTIONAL, and it stands
1924
+ HERE because an invite needs a project that already exists. Hidden until the
1925
+ created record carries an id and a slug; the script is the only thing that
2139
1926
  shows it. -->
2140
1927
  <div class="inviteBox" id="wizInvite" hidden>
2141
1928
  <h2 class="stepsHead">Invite builders</h2>
2142
- <p class="finePrint">Optional — invite people by GitHub username. Each invite is sent from your project’s own hall, which is what lets them in, and they see it on their projects page here too. Skip this and invite anyone later; nothing about your project waits on it.</p>
1929
+ <p class="finePrint">Optional. Invite people by GitHub username. The invite comes from your project’s hall, and you can invite anyone later.</p>
2143
1930
  <div class="attach">
2144
1931
  <label class="vh" for="wizInviteWho">GitHub username</label>
2145
1932
  <input type="text" id="wizInviteWho" placeholder="GitHub username" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
@@ -2157,76 +1944,12 @@ summary{min-height:24px;padding:3px 0;}
2157
1944
  <p class="samePath" id="wizInviteLater"></p>
2158
1945
  <h2 class="stepsHead" id="doneStepsHead">Your steps — the parts only you can play</h2>
2159
1946
  <ol class="humanSteps" id="humanSteps"></ol>
2160
- <!-- the modules step was optional (task 1002342, BV1.R23) — one line
2161
- saying where it lives afterwards, so a skip is never a dead end -->
1947
+ <!-- the extras row was a default (task 1002342, BV1.R23) — one line saying
1948
+ where they live afterwards, so accepting them is never a dead end -->
2162
1949
  <p class="samePath" id="doneModules"></p>
2163
- <p class="samePath">These are the same steps however a project is created — nothing here is special to this page.</p>
1950
+ <p class="samePath">These steps are the same however a project is created.</p>
2164
1951
  </section>
2165
1952
  </div>
2166
-
2167
- <!-- the bongo pair — one mark on every surface (owner, 2026-08-13).
2168
- The drum head is SKIN, never --bg-card, or it turns black in dark
2169
- mode; --drum-shadow is ink in light and plain black in dark, the
2170
- same move cosmos.css already makes for --shadow-card. -->
2171
- <div class="bongoCell">
2172
- <svg class="bongos" viewBox="0 0 440 320" role="img" aria-label="A pair of bongo drums">
2173
- <defs>
2174
- <linearGradient id="shellBig" x1="0" y1="0" x2="1" y2="0.35">
2175
- <stop offset="0%" stop-color="var(--accent-deep)"/><stop offset="16%" stop-color="var(--accent)"/>
2176
- <stop offset="48%" stop-color="var(--accent)"/><stop offset="86%" stop-color="var(--accent-deep)"/>
2177
- <stop offset="100%" stop-color="var(--accent-deep)"/>
2178
- </linearGradient>
2179
- <linearGradient id="shellSml" x1="0" y1="0" x2="1" y2="0.35">
2180
- <stop offset="0%" stop-color="var(--accent-deep)"/><stop offset="18%" stop-color="var(--terracotta)"/>
2181
- <stop offset="55%" stop-color="var(--terracotta)"/><stop offset="100%" stop-color="var(--accent-deep)"/>
2182
- </linearGradient>
2183
- <radialGradient id="skin" cx="0.36" cy="0.26" r="0.9">
2184
- <stop offset="0%" stop-color="var(--skin-hi)"/><stop offset="58%" stop-color="var(--skin-mid)"/>
2185
- <stop offset="100%" stop-color="var(--skin-lo)"/>
2186
- </radialGradient>
2187
- <radialGradient id="halo" cx="0.5" cy="0.5" r="0.5">
2188
- <stop offset="0%" stop-color="var(--accent)" stop-opacity="0.30"/>
2189
- <stop offset="48%" stop-color="var(--accent)" stop-opacity="0.13"/>
2190
- <stop offset="100%" stop-color="var(--accent)" stop-opacity="0"/>
2191
- </radialGradient>
2192
- <filter id="soft" x="-60%" y="-160%" width="220%" height="420%"><feGaussianBlur stdDeviation="9"/></filter>
2193
- </defs>
2194
- <ellipse class="glow" cx="222" cy="170" rx="215" ry="140" fill="url(#halo)"/>
2195
- <ellipse class="bshadow" cx="220" cy="286" rx="142" ry="14" fill="var(--drum-shadow)" opacity=".22" filter="url(#soft)"/>
2196
- <path d="M 214,196 C 232,188 250,188 264,196 L 258,232 C 244,225 230,225 219,232 Z" fill="var(--accent-deep)"/>
2197
- <path d="M 214,196 C 232,188 250,188 264,196 L 262,205 C 247,197 231,197 216,205 Z" fill="var(--ink)" opacity=".22"/>
2198
- <g class="drumBig">
2199
- <path d="M 74,118 C 76,166 90,228 98,266 A 52,17 0 0 0 202,266 C 210,228 224,166 226,118 Z" fill="url(#shellBig)"/>
2200
- <path d="M 172,124 C 174,176 186,232 192,268 A 52,17 0 0 0 202,266 C 210,228 224,166 226,118 Z" fill="var(--ink)" opacity=".17"/>
2201
- <path d="M 92,126 C 94,174 104,230 110,266 A 52,17 0 0 1 100,267 C 92,230 78,172 76,124 Z" fill="#fff" opacity=".20"/>
2202
- <ellipse cx="150" cy="121" rx="80" ry="29" fill="var(--accent-deep)"/>
2203
- <ellipse cx="150" cy="117" rx="80" ry="29" fill="var(--accent)"/>
2204
- <ellipse cx="150" cy="117" rx="80" ry="29" fill="#fff" opacity=".13"/>
2205
- <ellipse cx="150" cy="118" rx="70" ry="24" fill="url(#skin)"/>
2206
- <ellipse cx="150" cy="118" rx="70" ry="24" fill="none" stroke="var(--ink)" stroke-opacity=".09" stroke-width="1.4"/>
2207
- <ellipse cx="150" cy="118" rx="55" ry="18" fill="none" stroke="var(--ink)" stroke-opacity=".045" stroke-width="1"/>
2208
- <g fill="var(--accent-deep)">
2209
- <path d="M 78,138 l 9,0 -1.5,20 -6,0 z"/><path d="M 120,148 l 9,0 -1,21 -7,0 z"/>
2210
- <path d="M 172,148 l 9,0 -1,21 -7,0 z"/><path d="M 214,138 l 9,0 -1.5,20 -6,0 z"/>
2211
- </g>
2212
- </g>
2213
- <g class="drumSml">
2214
- <path d="M 240,134 C 242,178 252,230 258,266 A 40,13 0 0 0 338,266 C 344,230 354,178 356,134 Z" fill="url(#shellSml)"/>
2215
- <path d="M 314,140 C 316,186 324,234 328,268 A 40,13 0 0 0 338,266 C 344,230 354,178 356,134 Z" fill="var(--ink)" opacity=".19"/>
2216
- <path d="M 254,140 C 256,182 264,232 268,266 A 40,13 0 0 1 260,267 C 254,232 244,182 242,138 Z" fill="#fff" opacity=".17"/>
2217
- <ellipse cx="296" cy="137" rx="60" ry="22" fill="var(--accent-deep)"/>
2218
- <ellipse cx="296" cy="133" rx="60" ry="22" fill="var(--terracotta)"/>
2219
- <ellipse cx="296" cy="133" rx="60" ry="22" fill="#fff" opacity=".13"/>
2220
- <ellipse cx="296" cy="134" rx="51" ry="18" fill="url(#skin)"/>
2221
- <ellipse cx="296" cy="134" rx="51" ry="18" fill="none" stroke="var(--ink)" stroke-opacity=".09" stroke-width="1.4"/>
2222
- <ellipse cx="296" cy="134" rx="39" ry="13" fill="none" stroke="var(--ink)" stroke-opacity=".045" stroke-width="1"/>
2223
- <g fill="var(--accent-deep)">
2224
- <path d="M 243,152 l 8,0 -1,17 -6,0 z"/><path d="M 276,160 l 8,0 -1,18 -6,0 z"/>
2225
- <path d="M 312,160 l 8,0 -1,18 -6,0 z"/><path d="M 345,152 l 8,0 -1,17 -6,0 z"/>
2226
- </g>
2227
- </g>
2228
- </svg>
2229
- </div>
2230
1953
  </div>
2231
1954
  </div>
2232
1955
 
@@ -4772,11 +4495,14 @@ summary{min-height:24px;padding:3px 0;}
4772
4495
  and replaying it would land on the wrong panel with the rail already
4773
4496
  marking the skipped ones done. Bump this key on EVERY renumbering
4774
4497
  (-v6: the front door's two screens took steps 1 and 2, task 1004417;
4775
- -v7: the Look step took 10, so review is 11 and done 12, task 1004421). */
4498
+ -v7: the Look step took 10, so review is 11 and done 12, task 1004421;
4499
+ -v9: the six blended screens, task 1004504). */
4776
4500
  /* `termsAck` is the front door's one tick (task 1004417). It is the founder's
4777
4501
  own answer, so it lives in the draft like every other one and survives the
4778
4502
  GitHub round-trip; the server, not this page, names the terms version. */
4779
- var DRAFT_KEY = 'cb-create-draft-v8';
4503
+ /* `confirmed` is the review screen's own answer (task 1004504): which prefilled rows
4504
+ the founder has looked at and accepted or changed. Create waits for all of them. */
4505
+ var DRAFT_KEY = 'cb-create-draft-v9';
4780
4506
  var CREATED_KEY = 'cb-created-instance';
4781
4507
  /* `description` + `teamShape` are the optional About step (task 1002334) —
4782
4508
  both legitimately stay empty, and empty is what the create request omits. */
@@ -4785,7 +4511,7 @@ summary{min-height:24px;padding:3px 0;}
4785
4511
  request omits the field), [] = the owner switched them all off, [...] =
4786
4512
  what they picked. Never seed it with the preselect — that would store an
4787
4513
  answer nobody gave. */
4788
- var state = { step: 0, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', physics: null, demo: null, look: null, termsAck: false };
4514
+ var state = { step: 0, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', physics: null, demo: null, look: null, termsAck: false, confirmed: {} };
4789
4515
  try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
4790
4516
  var authKnown = false; /* flips true when the boot's /me settles, either way */
4791
4517
 
@@ -4799,7 +4525,7 @@ summary{min-height:24px;padding:3px 0;}
4799
4525
  state.name = handedName.trim().slice(0, 80);
4800
4526
  state.slug = deriveSlug(state.name); state.slugTouched = false;
4801
4527
  state.repo = deriveRepo(state.name); state.repoTouched = false;
4802
- state.step = state.step >= 1 && state.step <= 12 ? state.step : 1;
4528
+ state.step = state.step >= 1 && state.step <= 5 ? state.step : 1;
4803
4529
  try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
4804
4530
  }
4805
4531
  } catch (e) {}
@@ -4923,13 +4649,13 @@ summary{min-height:24px;padding:3px 0;}
4923
4649
  if (avail === 'taken' || avail === 'yours' || avail === 'reserved') { var hs = $('h-slug'); hs.innerHTML = '&nbsp;'; hs.className = 'hint'; }
4924
4650
  if (custom) {
4925
4651
  big.innerHTML = '<code>' + esc(custom) + '</code> — your own domain, wired up for you.';
4926
- note.textContent = 'DNS pointing happens after the standup; the card on your projects page walks you through it.';
4652
+ note.textContent = 'You point it here after setup; your projects page walks you through it.';
4927
4653
  } else if (state.noAddress) {
4928
4654
  big.textContent = 'Starting without an address.';
4929
4655
  note.textContent = 'Sign-in on your project can’t be set up until it has one — add it any time from your projects page.';
4930
4656
  } else if (assigned && avail === 'free') {
4931
4657
  big.innerHTML = '<code>' + esc(assigned) + '</code> — yours from the moment it stands up.';
4932
- note.textContent = 'Sharing works from the moment it stands up; sign-in switches on after the GitHub steps. You can point a domain you own at it any time.';
4658
+ note.textContent = 'Sign-in switches on after the GitHub steps. You can add your own domain any time.';
4933
4659
  } else if (assigned && avail === 'taken') {
4934
4660
  big.innerHTML = '<code>' + esc(assigned) + '</code> — already taken.';
4935
4661
  note.textContent = 'Another project on the platform holds that handle. Pick a different one.';
@@ -4991,115 +4717,89 @@ summary{min-height:24px;padding:3px 0;}
4991
4717
  }
4992
4718
 
4993
4719
  /* ── step navigation ──
4994
- Since task 1004417 the front door comes first: gate 0 · terms 1 · fork 2 ·
4995
- name 3 · type 4 · about 5 · modules 6 · home 7 · address 8 · app 9 · look 10 ·
4996
- review 11 · done 12 (the Look step joined at 10, task 1004421). The rail numbers
4997
- 3-11 as its 1-9 and steps aside on 1 and 2; done marks it all. About, Modules and
4998
- Look are the steps Next can pass with nothing changed — Look has a default. */
4999
- var panels = { 0: 'panel-gate', 1: 'panel-terms', 2: 'panel-fork', 3: 'panel-physics', 4: 'panel-name', 5: 'panel-type', 6: 'panel-detail', 7: 'panel-modules', 8: 'panel-home', 9: 'panel-address', 10: 'panel-app', 11: 'panel-look', 12: 'panel-review', 13: 'panel-done' };
5000
- /* the rail at the done panel (task 1003244): "done" is the Create step
5001
- still running — the marker stays on 5 until the project actually
5002
- answers, and only then does the whole rail read finished. aria-current
5003
- tells a screen reader the same thing. */
5004
- function paintRail(step) {
5005
- var landed = step === 13 && !!(live && live.instStatus === 'active');
5006
- var cur = step === 13 ? 12 : step;
5007
- root.querySelectorAll('.steps li').forEach(function (li) {
5008
- var n = Number(li.getAttribute('data-step'));
5009
- var now = !landed && n === cur;
5010
- li.classList.toggle('now', now);
5011
- li.classList.toggle('done', landed || n < cur);
5012
- /* a step the planet-physics answers skip (task 1004419); still reachable */
5013
- li.classList.toggle('skipped', step < 13 && physStepSkipped(n));
5014
- if (now) li.setAttribute('aria-current', 'step'); else li.removeAttribute('aria-current');
5015
- /* a step already answered is a way BACK (task 1003246): only those take
5016
- the button role, the tab stop and the data-goto the delegated handler
5017
- below reads. The done panel offers none — the project is filed, and
5018
- there is nothing behind it to change. */
5019
- if (step < 13 && n < cur) {
5020
- li.setAttribute('role', 'button');
5021
- li.setAttribute('tabindex', '0');
5022
- li.setAttribute('data-goto', String(n));
5023
- } else {
5024
- li.removeAttribute('role');
5025
- li.removeAttribute('tabindex');
5026
- li.removeAttribute('data-goto');
5027
- }
5028
- });
4720
+ The six blended screens (task 1004504, the design of record
4721
+ docs/design/mocks/combined-onboarding/): gate 0 · fork 1 (demo or real, the terms
4722
+ as its one tick) · what 2 (name, kind, a sentence) · checks 3 (the planet-physics
4723
+ answers that change something) · review 4 (every prefilled answer, confirmed row by
4724
+ row) · look 5 (then Create) · done 6. No rail: the eyebrow names the screen and one
4725
+ Back walks backwards. A demo skips the checks — its answers are the demo's own. */
4726
+ var panels = { 0: 'panel-gate', 1: 'panel-fork', 2: 'panel-what', 3: 'panel-checks', 4: 'panel-review', 5: 'panel-look', 6: 'panel-done' };
4727
+ var SCREEN = { 0: 'signin', 1: 'fork', 2: 'what', 3: 'checks', 4: 'review', 5: 'look', 6: 'done' };
4728
+ function eyebrowFor(step) {
4729
+ if (step === 0) return 'Sign in';
4730
+ if (step === 6) return 'Founding';
4731
+ if (step === 1 && !$('demoSetup').hidden) return 'Demo';
4732
+ return state.demo && step > 1 ? 'New demo' : 'New project';
4733
+ }
4734
+ /* where Back goes from each screen: the demo's own screen first, then the one before */
4735
+ function prevStep(step) {
4736
+ if (step === 4 && state.demo) return 2;
4737
+ return step - 1;
5029
4738
  }
5030
4739
  function show(step, moveFocus) {
5031
4740
  state.step = step;
5032
4741
  Object.keys(panels).forEach(function (k) {
5033
4742
  $(panels[k]).classList.toggle('on', Number(k) === step);
5034
4743
  });
5035
- paintRail(step);
5036
4744
  saveDraft();
5037
- /* the done step takes the whole card — the bongo pair steps aside */
5038
- root.querySelector('.createSplit').classList.toggle('wizDone', step === 13);
5039
- /* the front door (task 1004417): no rail on its two screens, and the fork
5040
- takes the whole card for the seed on its plate */
5041
- root.querySelector('.card').classList.toggle('wizFront', step === 1 || step === 2);
5042
- root.querySelector('.createSplit').classList.toggle('wizFork', step === 2 || step === 3);
4745
+ /* the room's seed grows a little on each screen (CSS reads data-screen) */
4746
+ $('wizRoom').setAttribute('data-screen', SCREEN[step]);
4747
+ $('wizEyebrow').textContent = eyebrowFor(step);
4748
+ $('wizBack').hidden = step === 0 || step === 6 || (step === 1 && $('demoSetup').hidden);
4749
+ $('demoTag').hidden = !state.demo;
5043
4750
  /* focus moves ONLY on a deliberate step (the hub rule setView already
5044
4751
  applies to this view: never on boot, never on a background settle) */
5045
4752
  if (moveFocus) {
5046
4753
  var h = $(panels[step]).querySelector('h1');
5047
4754
  if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
5048
4755
  }
5049
- /* the repo probe is authenticated — never fire it before /me settles
5050
- (a pre-auth probe 401s for strangers and, worse, could seed the
5051
- draft's owner with myLogin()'s placeholder) */
5052
- /* the picker repaints on entry, not once at boot: the owner can walk back
5053
- to the Type step and change the answer the preselect is keyed to */
5054
- if (step === 3) paintPhysics();
5055
- if (step === 5 || step === 8) physPaintPrefillNotes();
5056
- if (step === 7) renderModules();
5057
- if (step === 8) { syncHome(); if (authKnown) ensureRepoAccess(); }
5058
- /* a resumed draft can land PAST the Home step with the probe never run —
5059
- Create would then file a repo it promised to make but never did */
5060
- else if (step > 8 && step < 13 && authKnown && repoAccess === null) ensureRepoAccess();
5061
- /* re-check on every entry, not just on typing: a resumed draft arrives here with
5062
- a handle nobody has asked the server about, and a handle free ten minutes ago
5063
- may not be now (task 1002898 N10) */
5064
- if (step === 9) { checkSlugAvailable(); renderAddrPanel(); }
5065
- if (step === 10) { syncAppStep(); syncRenderSetup(); }
5066
- if (step === 11) renderLook();
5067
- if (step === 12) renderReview();
5068
- }
5069
-
5070
- /* One delegated pair on the rail, so paintRail can add and drop data-goto
5071
- freely (task 1003246). Enter and Space are the contract role="button"
5072
- makes; Space must not scroll the card while it keeps it. */
5073
- var railEl = root.querySelector('.steps');
5074
- if (railEl) {
5075
- railEl.addEventListener('click', function (ev) {
5076
- var li = ev.target.closest && ev.target.closest('li[data-goto]');
5077
- if (li) show(Number(li.getAttribute('data-goto')), true);
5078
- });
5079
- railEl.addEventListener('keydown', function (ev) {
5080
- if (ev.key !== 'Enter' && ev.key !== ' ' && ev.key !== 'Spacebar') return;
5081
- var li = ev.target.closest && ev.target.closest('li[data-goto]');
5082
- if (!li) return;
5083
- ev.preventDefault();
5084
- show(Number(li.getAttribute('data-goto')), true);
5085
- });
5086
- }
4756
+ if (step === 1) paintDoors();
4757
+ if (step === 3) { physLoadAccountOnce(); paintChecks(); }
4758
+ /* the review hosts the old steps' own controls, so it repaints them on entry.
4759
+ The repo probe is authenticated — never fire it before /me settles (a pre-auth
4760
+ probe 401s for strangers and could seed the draft's owner with a placeholder) */
4761
+ if (step === 4) {
4762
+ physDefaults();
4763
+ renderModules();
4764
+ syncHome();
4765
+ if (authKnown) ensureRepoAccess();
4766
+ /* re-check on every entry: a handle free ten minutes ago may not be now (N10) */
4767
+ checkSlugAvailable(); renderAddrPanel();
4768
+ syncAppStep(); syncRenderSetup();
4769
+ paintSetup();
4770
+ }
4771
+ /* a resumed draft can land PAST the review with the repo probe never run — Create
4772
+ would then file a repo it promised to make but never did */
4773
+ else if (step > 4 && step < 6 && authKnown && repoAccess === null) ensureRepoAccess();
4774
+ if (step === 5) { renderLook(); renderRecap(); }
4775
+ }
4776
+ $('wizBack').addEventListener('click', function () {
4777
+ if (state.step === 1 && !$('demoSetup').hidden) { demoShow('intro'); $('wizDemo').focus(); return; }
4778
+ if (state.step === 3 && !$('checksStop').hidden) { checksShow('main'); return; }
4779
+ if (state.step > 1 && state.step < 6) show(prevStep(state.step), true);
4780
+ });
5087
4781
 
5088
- /* ── the front door (task 1004417) ── the tick mirrors into the draft; the
5089
- demo door opens the demo route (task 1004420, below) */
4782
+ /* ── the front door (tasks 1004417 and 1004504) ── the tick mirrors into the draft,
4783
+ and both doors wait for it: nothing past the fork until the terms are accepted */
4784
+ function paintDoors() {
4785
+ $('wizDemo').disabled = !state.termsAck;
4786
+ $('wizStart').disabled = !state.termsAck;
4787
+ }
5090
4788
  $('termsAck').checked = !!state.termsAck;
5091
4789
  $('termsAck').addEventListener('change', function () {
5092
4790
  state.termsAck = !!this.checked;
5093
- if (state.termsAck) { $('h-terms').textContent = '\u00a0'; $('h-terms').className = 'hint'; }
4791
+ if (state.termsAck) { $('h-terms').textContent = ' '; $('h-terms').className = 'hint'; }
4792
+ paintDoors();
5094
4793
  saveDraft();
5095
4794
  });
4795
+ paintDoors();
5096
4796
  /* ── the demo route (task 1004420, BV2.PS09) ──
5097
- "Try the demo" opens two screens inside the fork: set up (people, hours each, the
5098
- idea, and a first read of what fits) and the picked defaults with two invites.
5099
- "Start the demo" goes on through the ordinary wizard from the name step: a demo is
5100
- a normal project marked "demo" (D9), so the create carries `demo` and the server
5101
- applies the demo's picked answers and makes it private to the team. The time box
5102
- is soft (D3). The limits and the fit sentences are copies of
4797
+ "Try the demo" opens one screen inside the fork: the people, the hours each, the
4798
+ idea, a first read of what fits, and up to two teammates. Continue goes on through
4799
+ the ordinary flow from "What are you making?": a demo is a normal project marked
4800
+ "demo" (D9), so the create carries `demo` and the server applies the demo's picked
4801
+ answers and makes it private to the team — which is why it skips the checks. The
4802
+ time box is soft (D3). The limits and the fit sentences are copies of
5103
4803
  modules/provisioning/demo.js, held equal by tests/wizard_demo.mjs. */
5104
4804
  var DEMO_LIMITS = { people: [1, 12], hours: [1, 40] };
5105
4805
  var demoDraft = state.demo
@@ -5125,32 +4825,26 @@ summary{min-height:24px;padding:3px 0;}
5125
4825
  $('demoHours').textContent = String(demoDraft.hours);
5126
4826
  $('demoFitSum').textContent = demoDraft.people + (demoDraft.people === 1 ? ' person' : ' people') + ' × ' + demoDraft.hours + (demoDraft.hours === 1 ? ' hour' : ' hours') + ' = ' + fit.total + ' builder-hours.';
5127
4827
  $('demoFitVerdict').textContent = fit.sentence;
5128
- $('demoLPeople').textContent = String(demoDraft.people);
5129
4828
  var others = demoDraft.people - 1;
5130
- $('demoLPeopleSub').textContent = others === 0 ? 'Just you' : others === 1 ? 'You, plus the person you invite below' : others === 2 ? 'You, plus the two people you invite below' : 'You, plus ' + others + ' people: invite two below, and the rest from your hall';
5131
- $('demoLHours').textContent = demoDraft.hours + (demoDraft.hours === 1 ? ' hour each' : ' hours each');
5132
- $('demoLTotal').textContent = fit.total + ' builder-hours in total. When it runs out you are asked whether to keep your planet; your work is never stopped.';
5133
- $('demoStageHours').textContent = String(fit.total);
5134
4829
  $('demoInvites').hidden = others === 0;
5135
4830
  $('demoInvite2').closest('label').hidden = others < 2;
5136
4831
  }
5137
4832
  function demoShow(which) {
5138
4833
  $('forkIntro').hidden = which !== 'intro';
5139
4834
  $('demoSetup').hidden = which !== 'setup';
5140
- $('demoReady').hidden = which !== 'ready';
5141
- $('demoRing').hidden = which === 'intro';
5142
- $('demoStageCap').hidden = which === 'intro';
5143
4835
  $('wizDemo').setAttribute('aria-expanded', which === 'intro' ? 'false' : 'true');
4836
+ $('wizEyebrow').textContent = eyebrowFor(state.step);
4837
+ $('wizBack').hidden = which === 'intro';
5144
4838
  if (which !== 'intro') {
5145
4839
  paintDemo();
5146
- var h = $(which === 'setup' ? 'demo-h1' : 'demo-h2');
4840
+ var h = $('demo-h1');
5147
4841
  h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false });
5148
4842
  }
5149
4843
  }
5150
4844
  $('demoIdea').value = demoDraft.idea;
5151
4845
  $('demoInvite1').value = demoDraft.invites[0] || '';
5152
4846
  $('demoInvite2').value = demoDraft.invites[1] || '';
5153
- $('wizDemo').addEventListener('click', function () { demoShow('setup'); });
4847
+ $('wizDemo').addEventListener('click', function () { if (state.termsAck) demoShow('setup'); });
5154
4848
  root.querySelectorAll('[data-demo-step]').forEach(function (b) {
5155
4849
  b.addEventListener('click', function () {
5156
4850
  var key = b.getAttribute('data-demo-step');
@@ -5161,9 +4855,6 @@ summary{min-height:24px;padding:3px 0;}
5161
4855
  });
5162
4856
  });
5163
4857
  $('demoIdea').addEventListener('input', function () { demoDraft.idea = this.value; });
5164
- $('demoNext1').addEventListener('click', function () { demoShow('ready'); });
5165
- $('demoBack1').addEventListener('click', function () { demoShow('intro'); $('wizDemo').focus(); });
5166
- $('demoBack2').addEventListener('click', function () { demoShow('setup'); });
5167
4858
  $('demoStart').addEventListener('click', function () {
5168
4859
  var invites = [demoHandle($('demoInvite1').value), demoHandle($('demoInvite2').value)]
5169
4860
  .filter(function (h, i, all) { return h && all.indexOf(h) === i; })
@@ -5176,9 +4867,10 @@ summary{min-height:24px;padding:3px 0;}
5176
4867
  $('f-desc').value = state.description;
5177
4868
  }
5178
4869
  state.teamShape = demoDraft.people > 1 ? 'small-team' : 'solo';
4870
+ /* the demo's own rows are the demo's answers, so the review asks again */
4871
+ state.confirmed = {};
5179
4872
  saveDraft();
5180
- /* the name step: a demo skips planet physics (task 1004418) */
5181
- show(4, true);
4873
+ show(2, true);
5182
4874
  });
5183
4875
  /* the done panel's invite step names the demo's teammates (task 1004420): the hub
5184
4876
  holds no invite of its own, so the founder sends each from the project's own box */
@@ -5220,6 +4912,8 @@ summary{min-height:24px;padding:3px 0;}
5220
4912
  var h = $('h-name');
5221
4913
  if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
5222
4914
  else { h.innerHTML = '&nbsp;'; }
4915
+ /* the repo and the address follow the name, so the review asks about them again */
4916
+ unconfirm(['code', 'address']);
5223
4917
  saveDraft();
5224
4918
  });
5225
4919
  if (state.name && state.slug) $('h-name').innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>';
@@ -5231,53 +4925,35 @@ summary{min-height:24px;padding:3px 0;}
5231
4925
  $('adoptNote').classList.toggle('on', mode === 'adopt');
5232
4926
  saveDraft();
5233
4927
  syncHome();
5234
- physPaintPrefillNotes();
4928
+ if (typeof paintSetup === 'function') paintSetup();
5235
4929
  }
5236
4930
  $('c-green').addEventListener('click', function () { setMode('greenfield'); });
5237
4931
  $('c-adopt').addEventListener('click', function () { setMode('adopt'); });
5238
4932
  setMode(state.mode);
5239
4933
 
5240
- /* ── the type declaration (task 1002772) ── selecting a pill fills the
5241
- description card, themed per type via --type-acc (component-scoped, C1) */
4934
+ /* ── the kind (task 1002772) ── a declaration: the pill is the answer */
5242
4935
  function paintType(t) {
5243
4936
  root.querySelectorAll('#typeCards .tpill').forEach(function (b) {
5244
4937
  b.setAttribute('aria-pressed', String(b.getAttribute('data-type') === t));
5245
4938
  });
5246
- var card = $('typeCard');
5247
- var m = t && TYPE_META[t];
5248
- if (m) {
5249
- card.hidden = false;
5250
- card.style.setProperty('--type-acc', m.acc);
5251
- $('typeCardName').textContent = m.label;
5252
- $('typeCardDesc').textContent = m.desc;
5253
- } else {
5254
- card.hidden = true;
5255
- }
5256
4939
  }
5257
4940
  root.querySelectorAll('#typeCards .tpill').forEach(function (b) {
5258
4941
  b.addEventListener('click', function () {
5259
4942
  state.projType = b.getAttribute('data-type');
5260
4943
  $('h-projtype').innerHTML = '&nbsp;'; $('h-projtype').className = 'hint';
5261
4944
  paintType(state.projType);
5262
- physPaintPrefillNotes();
4945
+ /* the suggested extras follow the kind, so the review asks about them again */
4946
+ unconfirm(['extras']);
5263
4947
  saveDraft();
5264
4948
  });
5265
4949
  });
5266
4950
  paintType(state.projType || null);
5267
4951
 
5268
- /* ── the OPTIONAL About step (task 1002334) ──
5269
- Two answers, both skippable. The description is half the publish gate
5270
- (ADR 0182 D4) and the team shape is what the setup recommendations read;
5271
- neither gates the standup, and neither is ever guessed on the owner's
5272
- behalf. paintDetail keeps the consequence visible while they are still on
5273
- the panel — it flips between "here's what you'll get" and "here's what
5274
- skipping costs", so pressing Skip is an informed choice rather than a
5275
- discovery made three screens later. */
5276
- var TEAM_META = {
5277
- 'solo': 'Just you. We keep the setup light and skip the joining machinery.',
5278
- 'small-team': 'A handful of people you know. We suggest a setup built around invites.',
5279
- 'community': 'Open to whoever turns up. We suggest a setup built for people applying to join.'
5280
- };
4952
+ /* ── the sentence and the team shape (task 1002334) ──
4953
+ The sentence is optional and half the publish gate (ADR 0182 D4): it is what puts
4954
+ the project on the public map, which the field's own label says. The team shape is
4955
+ what the extras' preselect reads: "solo" follows a team of one, and a team says who
4956
+ can join (people invited, or anyone applying) on the review's Team size row. */
5281
4957
  function teamLabel(t) {
5282
4958
  var b = t && root.querySelector('#teamCards .tpill[data-team="' + t + '"]');
5283
4959
  return b ? b.textContent : '';
@@ -5287,42 +4963,21 @@ summary{min-height:24px;padding:3px 0;}
5287
4963
  b.setAttribute('aria-pressed', String(b.getAttribute('data-team') === t));
5288
4964
  });
5289
4965
  }
5290
- function paintDetail() {
5291
- var note = $('detailSkipNote');
5292
- var told = String(state.description || '').trim();
5293
- var parts = [];
5294
- if (!told) {
5295
- parts.push('No description yet — your project still stands up, it just stays off the public map until it has one. You can add it any time from its page.');
5296
- } else {
5297
- parts.push('That description is what puts your project on the public map.');
5298
- }
5299
- if (state.teamShape && TEAM_META[state.teamShape]) parts.push(TEAM_META[state.teamShape]);
5300
- note.textContent = parts.join(' ');
5301
- note.classList.add('on');
5302
- }
5303
4966
  $('f-desc').value = state.description || '';
5304
4967
  $('f-desc').addEventListener('input', function () {
5305
4968
  state.description = this.value.slice(0, 600);
5306
- paintDetail();
5307
4969
  saveDraft();
5308
4970
  });
5309
4971
  root.querySelectorAll('#teamCards .tpill').forEach(function (b) {
5310
4972
  b.addEventListener('click', function () {
5311
- /* clicking the chosen pill again UNSAYS it — the only way back to "I did
5312
- not answer", which an optional question has to keep offering */
5313
- var pick = b.getAttribute('data-team');
5314
- state.teamShape = state.teamShape === pick ? null : pick;
4973
+ state.teamShape = b.getAttribute('data-team');
5315
4974
  paintTeam(state.teamShape);
5316
- paintDetail();
4975
+ unconfirm(['extras']);
5317
4976
  saveDraft();
4977
+ paintSetup();
5318
4978
  });
5319
4979
  });
5320
4980
  paintTeam(state.teamShape || null);
5321
- paintDetail();
5322
- /* Skip = go on with whatever is (or isn't) filled in. It deliberately does
5323
- NOT erase an answer already given: a visitor who typed a description and
5324
- then pressed Skip meant "I'm done here", not "throw that away". */
5325
- $('skipDetail').addEventListener('click', function () { show(7, true); });
5326
4981
 
5327
4982
  /* ── the module picker (task 1002339, BV1.R20) ──
5328
4983
  ONE public read gives the panel both halves it needs: `optional` is the
@@ -5365,8 +5020,7 @@ summary{min-height:24px;padding:3px 0;}
5365
5020
  exact dishonesty the About row's "off the public map" clause exists to
5366
5021
  prevent, one screen before Create. */
5367
5022
  function repaintForBundles() {
5368
- if (state.step === 7) renderModules();
5369
- else if (state.step === 12) renderReview();
5023
+ if (state.step === 4) { renderModules(); paintSetup(); }
5370
5024
  }
5371
5025
  /* The row for the declared type, or null before the read lands / for a type
5372
5026
  the table has no row for (including no type at all). */
@@ -5527,7 +5181,7 @@ summary{min-height:24px;padding:3px 0;}
5527
5181
  why.classList.toggle('on', reasons.length > 0);
5528
5182
  var core = (starterBundles.core || []).length;
5529
5183
  note.textContent = core
5530
- ? 'Every project also starts with the same ' + core + ' core parts — the platform itself, the way it tracks work, and the builders’ hall. They are always on, so they are not part of this choice.'
5184
+ ? 'Every project also has the same ' + core + ' core parts, always on: the platform, its work tracking and the builders’ hall.'
5531
5185
  : '';
5532
5186
  note.classList.toggle('on', core > 0);
5533
5187
  }
@@ -5553,12 +5207,6 @@ summary{min-height:24px;padding:3px 0;}
5553
5207
  moduleQuery = (ev && ev.target && ev.target.value) || '';
5554
5208
  renderModules();
5555
5209
  });
5556
- /* Next off this panel CONFIRMS what is on screen — the owner looked at the
5557
- set and accepted it, which is a different fact from never being asked. */
5558
- root.querySelector('#panel-modules form').addEventListener('submit', function () {
5559
- state.modules = pickedModules();
5560
- saveDraft();
5561
- });
5562
5210
  /* What the review ledger names. Three cases, and the third is the one worth
5563
5211
  writing down: with nothing said and the catalog not (yet) loaded we do not
5564
5212
  know the module NAMES, but we do know the owner will get the type's
@@ -5575,14 +5223,9 @@ summary{min-height:24px;padding:3px 0;}
5575
5223
  : 'The extras you picked';
5576
5224
  }
5577
5225
  var picked = pickedModules();
5578
- return picked.length ? esc(picked.map(moduleLabel).join(', ')) : 'None — just the core';
5226
+ return picked.length ? picked.map(moduleLabel).join(', ') : 'None — just the core';
5579
5227
  }
5580
5228
 
5581
- /* Skip = walk past without answering. It does NOT erase a toggle already
5582
- made (the About step's rule), and with nothing said it leaves state.modules
5583
- null so the server applies the bundle this panel was showing. */
5584
- $('skipModules').addEventListener('click', function () { show(8, true); });
5585
-
5586
5229
  /* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
5587
5230
  Progressive enhancement: probe GET /github/repos once we reach the Home step.
5588
5231
  200 → we hold repo access → show the picker (adopt) / name field (fresh);
@@ -5759,7 +5402,7 @@ summary{min-height:24px;padding:3px 0;}
5759
5402
  var h = $('h-domain');
5760
5403
  if (!state.domain) { h.innerHTML = '&nbsp;'; h.className = 'hint'; this.classList.remove('bad'); }
5761
5404
  else if (looksLikeDomain(state.domain)) { h.textContent = 'Looks like a domain.'; h.className = 'hint ok'; this.classList.remove('bad'); }
5762
- else { h.textContent = 'That doesn’t look like a domain yet (e.g. mercury.example.com).'; h.className = 'hint err'; this.classList.add('bad'); }
5405
+ else { h.textContent = 'That doesn’t look like a domain yet (e.g. moonbase.example.com).'; h.className = 'hint err'; this.classList.add('bad'); }
5763
5406
  renderAddrPanel();
5764
5407
  saveDraft();
5765
5408
  });
@@ -5769,10 +5412,10 @@ summary{min-height:24px;padding:3px 0;}
5769
5412
  saveDraft();
5770
5413
  });
5771
5414
 
5772
- /* ── planet physics, questions 1 to 7 (tasks 1004418 and 1004419, BV2.PS07 + PS11) ──
5773
- One wizard step with its own inner sequence: 0 account-or-scratch, 1 state,
5774
- 2 geography, 3 ages, 4 entity, 5 builders, 6 rewards, 7 framing, the summary
5775
- ('sum') and the stop screen. The answers live in state.physics.answers under
5415
+ /* ── planet physics (tasks 1004418 and 1004419, BV2.PS07 + PS11; regrouped by task 1004504) ──
5416
+ Geography, ages and entity are the quick checks (screen 3); the project's state,
5417
+ the builders, the rewards and the framing are rows of the review (screen 4), each
5418
+ prefilled and confirmed there. The answers live in state.physics.answers under
5776
5419
  planet-physics.js's own keys, so the create sends them as `detail` untouched.
5777
5420
  The vocabularies below are COPIES of that file's (a page cannot require a
5778
5421
  module), held equal by tests/wizard_physics.mjs and wizard_physics_more.mjs. */
@@ -5803,32 +5446,14 @@ summary{min-height:24px;padding:3px 0;}
5803
5446
  'CA-ON': 'Ontario', 'CA-PE': 'Prince Edward Island', 'CA-QC': 'Quebec', 'CA-SK': 'Saskatchewan', 'CA-YT': 'Yukon'
5804
5447
  };
5805
5448
  var PHYS_ENTITY_FORMS = ['nonprofit', 'for-profit', 'corporate-division', 'research', 'university-project', 'social-group', 'single-owner-llc', 'private-individual'];
5806
- /* The core material the entity gives the planet (the direction doc's placeholder
5807
- art direction): frost for one person, chrome for a group, gold for an
5808
- organization, obsidian for a nonprofit. */
5809
- var PHYS_MATERIAL = {
5810
- 'intent-solo': 'frost', 'single-owner-llc': 'frost', 'private-individual': 'frost',
5811
- 'university-project': 'chrome', 'social-group': 'chrome', 'intent-unsure': 'chrome',
5812
- 'intent-company': 'gold', 'for-profit': 'gold', 'corporate-division': 'gold', 'research': 'gold',
5813
- 'intent-nonprofit': 'obsidian', 'nonprofit': 'obsidian'
5814
- };
5815
- var PHYS_CORE_WORD = { frost: 'individual', chrome: 'group', gold: 'organization', obsidian: 'nonprofit' };
5816
- var PHYS_GEO_WORD = { 'us-only': 'us only', 'us-eu': 'us + eu', 'global': 'global', 'not-sure': 'not sure yet' };
5817
- /* Questions 5 to 7 (task 1004419). The voice (spec D5): a formed organization
5818
- reads the plain professional copy; individuals, groups and every "not yet" read
5819
- the planet's. The lists are planet-physics.js's, in its order. */
5820
- var PHYS_ORGS = ['nonprofit', 'for-profit', 'corporate-division', 'research'];
5449
+ /* Questions 5 to 7 (task 1004419). The lists are planet-physics.js's, in its order. */
5821
5450
  var PHYS_REWARDS = ['non-monetary', 'cash', 'equity', 'future-payouts'];
5822
5451
  var PHYS_FRAMINGS = ['managed', 'hybrid', 'local', 'open-source'];
5823
5452
  var PHYS_SERVICES = ['hosting', 'labor-payment', 'starter-kit', 'marketplace-buy', 'marketplace-sell', 'guild-access', 'guild-create', 'company-creation', 'cbaas-consulting', 'public-marketing'];
5824
5453
  var PHYS_MANY = ['reward_intents', 'services'];
5825
5454
  /* the suite a hybrid or local framing starts from (the canvas's Framing frame) */
5826
5455
  var PHYS_SUITE_START = { hybrid: ['hosting', 'starter-kit', 'marketplace-buy', 'guild-access'], local: ['starter-kit', 'marketplace-buy', 'guild-access'] };
5827
- /* the framing is the planet's orbit around the platform: all managed is close,
5828
- hybrid the middle, local far, and open source a system of its own */
5829
- var PHYS_ORBIT = { managed: 'near', hybrid: 'mid', local: 'far', 'open-source': 'own' };
5830
- var PHYS_ORBIT_WORD = { managed: 'a close orbit around the platform', hybrid: 'a middle orbit around the platform', local: 'a far orbit around the platform', 'open-source': 'a system of its own' };
5831
- /* what the summary says for each answer */
5456
+ /* what the review and the reasons say for each answer */
5832
5457
  var PHYS_SAY = {
5833
5458
  project_state: { 'new': 'New', existing: 'Existing' },
5834
5459
  builder_geography: { 'us-only': 'Builders in the US only', 'us-eu': 'Builders in the US and EU', global: 'Builders anywhere', 'not-sure': 'Not sure yet where builders will be' },
@@ -5837,7 +5462,7 @@ summary{min-height:24px;padding:3px 0;}
5837
5462
  nonprofit: 'Nonprofit', 'for-profit': 'For-profit company', 'corporate-division': 'Division of a corporation', research: 'Research entity',
5838
5463
  'university-project': 'University project', 'social-group': 'Social group', 'single-owner-llc': 'Single-owner LLC', 'private-individual': 'Private individual'
5839
5464
  },
5840
- reward_intents: { 'non-monetary': 'Non-monetary', cash: 'Cash for work', equity: 'Equity or shares', 'future-payouts': 'Future profits or payouts' },
5465
+ reward_intents: { 'non-monetary': 'Learning and fun', cash: 'Cash for work', equity: 'Equity', 'future-payouts': 'Future payouts' },
5841
5466
  framing: { managed: 'All managed', hybrid: 'Hybrid', local: 'Local or internal', 'open-source': 'Open source, local' },
5842
5467
  services: {
5843
5468
  hosting: 'Hosting', 'labor-payment': 'Labor payment and HR', 'starter-kit': 'Starting module kit', 'marketplace-buy': 'Module marketplace: buy',
@@ -5845,16 +5470,10 @@ summary{min-height:24px;padding:3px 0;}
5845
5470
  'company-creation': 'Company creation', 'cbaas-consulting': 'Expert consulting', 'public-marketing': 'Public marketed project'
5846
5471
  }
5847
5472
  };
5848
- /* the project type the entity suggests for the Type step (a pre-fill the founder sees and can change) */
5849
- var PHYS_TYPE = {
5850
- 'intent-company': 'business', 'for-profit': 'business', 'corporate-division': 'business', 'single-owner-llc': 'business',
5851
- 'intent-nonprofit': 'non-profit', nonprofit: 'non-profit', research: 'research', 'university-project': 'research'
5852
- };
5853
- var PHYS_LAST = 7;
5854
5473
  var physLimits = [];
5855
5474
 
5856
5475
  function phys() {
5857
- if (!state.physics || typeof state.physics !== 'object') state.physics = { q: 0, start: null, answers: {} };
5476
+ if (!state.physics || typeof state.physics !== 'object') state.physics = { answers: {} };
5858
5477
  if (!state.physics.answers || typeof state.physics.answers !== 'object') state.physics.answers = {};
5859
5478
  return state.physics;
5860
5479
  }
@@ -5879,11 +5498,11 @@ summary{min-height:24px;padding:3px 0;}
5879
5498
  if ((a.framing === 'hybrid' || a.framing === 'local') && Array.isArray(a.services)) {
5880
5499
  out.services = PHYS_SERVICES.filter(function (s) { return a.services.indexOf(s) !== -1; });
5881
5500
  }
5882
- /* the voice follows the entity (spec D5), stored so settings can switch it later */
5883
- if (a.entity) out.voice = physVoice();
5501
+ /* the voice (spec D5) is the plain one for every founder since task 1004504;
5502
+ still stored, so settings can switch it later */
5503
+ if (a.entity) out.voice = 'professional';
5884
5504
  return out;
5885
5505
  }
5886
- function physVoice() { return PHYS_ORGS.indexOf(phys().answers.entity) !== -1 ? 'professional' : 'planet'; }
5887
5506
  function physSolo() { var a = phys().answers; return a.builders_at_start === 1 && a.builders_expected === 1; }
5888
5507
  function physHidden(option) { return physLimits.some(function (l) { return l && l.option === option && l.effect === 'hide'; }); }
5889
5508
  /* a question the answers skip: rewards, when the table hides them (building alone) */
@@ -5901,88 +5520,34 @@ summary{min-height:24px;padding:3px 0;}
5901
5520
  if (q === 7) return !!a.framing;
5902
5521
  return false;
5903
5522
  }
5904
- /* Where Continue goes from question q: the next one that applies — or, once the
5905
- founder came here from the summary's "change", straight back to the summary
5906
- unless a later question still needs an answer. */
5907
- function physNextQ(q) {
5908
- var p = phys();
5909
- for (var n = q + 1; n <= PHYS_LAST; n++) {
5910
- if (physSkips(n)) continue;
5911
- if (!p.fromSum || !physAnswered(n)) return n;
5912
- }
5913
- return 'sum';
5914
- }
5915
- function physPrevQ(q) {
5916
- var from = q === 'sum' ? PHYS_LAST + 1 : q;
5917
- for (var n = from - 1; n >= 1; n--) if (!physSkips(n)) return n;
5918
- return 0;
5919
- }
5920
-
5921
- /* ── the technical steps, pre-filled from the answers (task 1004419) ──
5922
- What the answers suggest, applied only where the founder has not chosen for
5923
- themselves (the field still holds its default, or what we filled last time), so
5924
- walking back to change an answer moves the suggestion and never their choice. */
5925
- var PHYS_PREFILL_DEFAULT = { projType: null, teamShape: null, mode: 'greenfield' };
5926
- function physPrefillFor() {
5927
- var a = phys().answers, out = {};
5928
- if (PHYS_TYPE[a.entity]) out.projType = PHYS_TYPE[a.entity];
5929
- if (physSolo()) out.teamShape = 'solo';
5930
- if (a.project_state) out.mode = a.project_state === 'existing' && Array.isArray(a.existing_work) && a.existing_work.indexOf('code') !== -1 ? 'adopt' : 'greenfield';
5931
- return out;
5932
- }
5933
- function physPrefill() {
5934
- var want = physPrefillFor(), was = state.prefilled || {}, now = {};
5935
- Object.keys(PHYS_PREFILL_DEFAULT).forEach(function (k) {
5936
- var cur = state[k] === undefined ? PHYS_PREFILL_DEFAULT[k] : state[k];
5937
- var untouched = cur === PHYS_PREFILL_DEFAULT[k] || (was[k] !== undefined && cur === was[k]);
5938
- if (!untouched) return;
5939
- if (want[k] !== undefined) { state[k] = want[k]; now[k] = want[k]; } else state[k] = PHYS_PREFILL_DEFAULT[k];
5940
- });
5941
- state.prefilled = now;
5942
- /* a step the answers skip keeps its default answer */
5943
- if (physStepSkipped(10)) state.appPlatform = 'later';
5944
- if (typeof setMode === 'function') setMode(state.mode);
5945
- if (typeof paintType === 'function') paintType(state.projType || null);
5946
- if (typeof paintTeam === 'function') paintTeam(state.teamShape || null);
5947
- saveDraft();
5948
- return now;
5949
- }
5950
- /* The technical steps that do not apply to these answers: a planet built on its
5951
- own machines (framing local, or open source) has no app platform to pick. A demo
5952
- is all managed, whatever an abandoned walk through the questions left behind. */
5953
- function physStepSkipped(n) {
5523
+ /* A planet built on its own machines (framing local, or open source) has no app
5524
+ platform to pick, so the review's App hosting row only says so. A demo is all
5525
+ managed, whatever an abandoned walk through the questions left behind. */
5526
+ function appSkipped() {
5954
5527
  if (state.demo) return false;
5955
5528
  var a = (state.physics && state.physics.answers) || {};
5956
- return n === 10 && (a.framing === 'local' || a.framing === 'open-source');
5957
- }
5958
- /* the wizard's Next and Back step over a skipped step; the rail still reaches it */
5959
- function physPastSkips(n, dir) {
5960
- while (n > 0 && n < 13 && physStepSkipped(n)) n += dir;
5961
- return n;
5962
- }
5963
- /* the line on the Type and Home steps saying the answer came from the questions */
5964
- function physPaintPrefillNotes() {
5965
- if (!PHYS_SAY) return; /* setMode runs once at boot, above where these are set */
5966
- var pf = state.prefilled || {}, a = (state.physics && state.physics.answers) || {};
5967
- var whose = PHYS_ORGS.indexOf(a.entity) !== -1 ? 'your project' : 'your planet';
5968
- var t = $('typePrefill');
5969
- var tOn = !!pf.projType && state.projType === pf.projType && !!PHYS_SAY.entity[a.entity];
5970
- t.hidden = !tOn;
5971
- t.textContent = tOn ? 'Picked from your answer “' + PHYS_SAY.entity[a.entity] + '”. Change it if another fits better.' : '';
5972
- var h = $('homePrefill');
5973
- var hOn = !!pf.mode && state.mode === pf.mode;
5974
- h.hidden = !hOn;
5975
- h.textContent = !hOn ? '' : pf.mode === 'adopt'
5976
- ? 'Adopt is picked because ' + whose + ' already has code. Choose Start fresh if it should begin again.'
5977
- : a.project_state === 'existing'
5978
- ? 'Start fresh is picked because what ' + whose + ' has so far is not code.'
5979
- : 'Start fresh is picked because ' + whose + ' is new.';
5529
+ return a.framing === 'local' || a.framing === 'open-source';
5980
5530
  }
5531
+ /* What the create sends as `detail`. A demo sends only its team shape: the server
5532
+ applies the demo's own picked answers (task 1004420), as it always has. */
5981
5533
  function createDetail() {
5982
- var d = physDetail();
5534
+ var d = state.demo ? {} : physDetail();
5983
5535
  if (state.teamShape) d.team_shape = state.teamShape;
5984
5536
  return Object.keys(d).length ? d : undefined;
5985
5537
  }
5538
+ /* The review's prefilled answers, written once on the way in and never over a
5539
+ founder's own: a new project, all managed, one person. Each still has to be
5540
+ confirmed on its row before Create — confirming is the answer. */
5541
+ function physDefaults() {
5542
+ var a = phys().answers;
5543
+ if (!a.project_state) a.project_state = 'new';
5544
+ if (!a.framing) a.framing = 'managed';
5545
+ if (!Number.isInteger(a.builders_at_start)) { a.builders_at_start = 1; a.builders_expected = 1; }
5546
+ if (physSolo()) state.teamShape = 'solo';
5547
+ else if (!state.teamShape || state.teamShape === 'solo') state.teamShape = 'small-team';
5548
+ if (state.mode === 'greenfield' && a.project_state === 'existing' && Array.isArray(a.existing_work) && a.existing_work.indexOf('code') !== -1 && !(state.confirmed || {}).code) setMode('adopt');
5549
+ saveDraft();
5550
+ }
5986
5551
 
5987
5552
  function physFillSelects() {
5988
5553
  var c = $('physCountry'), r = $('physRegion');
@@ -6003,100 +5568,30 @@ summary{min-height:24px;padding:3px 0;}
6003
5568
  r.value = a.home_region || '';
6004
5569
  }
6005
5570
 
6006
- /* The stage: the planet forming from the world's own plates. A seed for a new
6007
- planet, a formed one for an existing; the core material once the entity is
6008
- known; a protective ring when young builders are on board. */
6009
- function physPaintStage() {
6010
- var p = phys(), a = p.answers;
6011
- var mat = PHYS_MATERIAL[a.entity] || null;
6012
- var src = mat && mat !== 'chrome' ? '/assets/world/r3-' + mat + '-blob-cut.webp'
6013
- : a.project_state === 'existing' ? '/assets/world/r2-chrome-blobs-set-cut.webp'
6014
- : '/assets/world/r2-chrome-blob-one-cut.webp';
6015
- var img = $('physPlanet');
6016
- if (img.getAttribute('src') !== src) img.setAttribute('src', src);
6017
- var size = p.q === 'stop' ? 0 : p.q === 'sum' ? 6
6018
- : Math.min(6, Math.max(1, Math.min(typeof p.q === 'number' ? p.q : 0, PHYS_LAST) + (a.project_state === 'existing' ? 2 : 1)));
6019
- img.setAttribute('data-size', String(size));
6020
- img.hidden = p.q === 'stop';
6021
- $('physGuard').hidden = a.minors_on_board !== true || p.q === 'stop';
6022
- $('physStage').classList.toggle('physStopped', p.q === 'stop');
6023
- var pro = physVoice() === 'professional';
6024
- $('physCapLbl').textContent = pro ? 'project profile' : 'planet physics';
6025
- $('physCapN').textContent = p.q === 'stop' ? 'stopped' : p.q === 'sum' ? 'complete' : (typeof p.q === 'number' ? p.q : 0) + ' of 7';
6026
- /* task 1004419: moons for the builders now (beads of the core's material), faint
6027
- ones on the dashed orbit for the expected growth, a ring per reward being
6028
- considered, and the orbit around the platform's mark the framing sets */
6029
- var live = p.q !== 'stop';
6030
- var at = live && Number.isInteger(a.builders_at_start) ? a.builders_at_start : 0;
6031
- var exp = live && Number.isInteger(a.builders_expected) ? a.builders_expected : 0;
6032
- var bead = '/assets/world/r3-' + (mat && mat !== 'chrome' ? mat : 'frost') + '-bead-cut.webp';
6033
- var nMoons = Math.min(at, 6), nGhosts = Math.min(Math.max(exp - at, 0), 4);
6034
- root.querySelectorAll('#physMoons img, #physGhosts img').forEach(function (m) { if (m.getAttribute('src') !== bead) m.setAttribute('src', bead); });
6035
- root.querySelectorAll('#physMoons img').forEach(function (m, i) { m.hidden = i >= nMoons; });
6036
- root.querySelectorAll('#physGhosts img').forEach(function (m, i) { m.hidden = i >= nGhosts; });
6037
- $('physMoons').hidden = nMoons === 0;
6038
- $('physGhosts').hidden = nGhosts === 0;
6039
- var rewards = live && Array.isArray(a.reward_intents) && !physSolo() && !physHidden('reward_intents') ? a.reward_intents : [];
6040
- root.querySelectorAll('#physStage .physBelt').forEach(function (b, i) { b.hidden = i >= rewards.length; });
6041
- var orbit = live && a.framing ? PHYS_ORBIT[a.framing] : 'none';
6042
- $('physStage').setAttribute('data-orbit', orbit);
6043
- $('physSun').hidden = orbit === 'none' || orbit === 'own';
6044
- $('physTether').hidden = orbit === 'none' || orbit === 'own';
6045
- var rewardWords = rewards.map(function (r) { return PHYS_SAY.reward_intents[r].toLowerCase(); }).join(', ');
6046
- var call = pro ? 'profile · each answer shapes your project here' : 'physics · the rules it lives by · each answer shapes your planet here';
6047
- if (p.q === 'stop') call = 'no planet forms · stopped at state · nothing was created';
6048
- else if (p.q === 'sum') call = pro ? 'profile · seven answers · ready for setup' : 'physics · seven answers, one planet · ready for its coordinates';
6049
- else if (p.q === 7 && a.framing) call = pro
6050
- ? 'framing · ' + PHYS_SAY.framing[a.framing].toLowerCase()
6051
- : 'orbit · ' + PHYS_SAY.framing[a.framing].toLowerCase() + ' · ' + PHYS_ORBIT_WORD[a.framing];
6052
- else if (p.q === 6 && rewards.length) call = pro
6053
- ? 'rewards · ' + rewards.length + ' being considered · ' + rewardWords
6054
- : 'rings · ' + rewards.length + (rewards.length === 1 ? ' current of value · ' : ' currents of value · ') + rewardWords;
6055
- else if (p.q === 5 && at) call = physSolo()
6056
- ? (pro ? 'team · one person' : 'moons · just you · one moon')
6057
- : (pro ? 'team · ' + at + (at === 1 ? ' contributor' : ' contributors') + ' · estimated ' + exp + ' by month three'
6058
- : 'moons · ' + at + (at === 1 ? ' builder now' : ' builders now') + ' · the dashed orbit holds about ' + exp + ' by month three');
6059
- else if (p.q === 4 && mat) call = 'core · ' + PHYS_CORE_WORD[mat] + ' · a ' + mat + ' core';
6060
- else if (p.q === 3 && a.minors_on_board === true) call = 'protective ring · under 18s on board · the age tiers apply';
6061
- else if (p.q === 2 && a.home_country) call = 'coordinates · ' + (a.home_region || a.home_country).toLowerCase() + (a.builder_geography ? ' · builders ' + PHYS_GEO_WORD[a.builder_geography] : '');
6062
- else if (p.q === 1 && a.project_state) call = a.project_state === 'existing' ? 'state · existing · terrain already formed' : 'state · new · a seed: nothing built yet';
6063
- $('physCallout').textContent = call;
6064
- var coords = [];
6065
- if (a.project_state) coords.push('state ' + a.project_state);
6066
- if (a.home_country) coords.push('home ' + (a.home_region || a.home_country).toLowerCase());
6067
- if (a.builder_geography) coords.push('builders ' + PHYS_GEO_WORD[a.builder_geography]);
6068
- if (a.minors_on_board !== undefined && a.minors_on_board !== null) coords.push(a.minors_on_board ? 'ages 13+' : 'ages 18+');
6069
- if (mat) coords.push('core ' + PHYS_CORE_WORD[mat]);
6070
- if (at) coords.push((pro ? 'team ' : 'builders ') + (physSolo() ? '1' : at + ' → ' + exp));
6071
- if (rewards.length) coords.push('rewards ' + rewards.map(function (r) { return r === 'future-payouts' ? 'future' : r; }).join(', '));
6072
- if (live && a.framing) coords.push((pro ? 'framing ' : 'orbit ') + a.framing);
6073
- $('physCoords').textContent = coords.join(' · ');
6074
- }
6075
-
6076
5571
  /* The soft-limit table's verdicts (spec D6): an option it hides is hidden and
6077
5572
  one it greys is disabled, each with its reason beside it. */
6078
5573
  /* Task 1004419 widened it to the list questions: a member the table hides is
6079
5574
  hidden and one it greys disabled, and either leaves the founder's list (it can
6080
5575
  no longer be chosen); a member's reasons gather once each in its question's
6081
5576
  why list, naming the options they are about. A whole question it hides, the
6082
- flow skips (physSkips) or hides (data-limit). */
5577
+ review hides with its row (physSkips) or hides in place (data-limit). */
6083
5578
  function physApplyLimits() {
6084
5579
  var a = phys().answers;
6085
- root.querySelectorAll('#panel-physics [data-why]').forEach(function (el) { el.hidden = true; el.textContent = ''; });
6086
- root.querySelectorAll('#panel-physics [data-limit-grey]').forEach(function (el) { el.removeAttribute('data-limit-grey'); el.disabled = false; el.title = ''; });
6087
- root.querySelectorAll('#panel-physics [data-limit-hide]').forEach(function (el) { el.removeAttribute('data-limit-hide'); el.hidden = false; });
5580
+ root.querySelectorAll('#view-new [data-why]').forEach(function (el) { el.hidden = true; el.textContent = ''; });
5581
+ root.querySelectorAll('#view-new [data-limit-grey]').forEach(function (el) { el.removeAttribute('data-limit-grey'); el.disabled = false; el.title = ''; });
5582
+ root.querySelectorAll('#view-new [data-limit-hide]').forEach(function (el) { el.removeAttribute('data-limit-hide'); el.hidden = false; });
6088
5583
  var lists = {};
6089
5584
  physLimits.forEach(function (l) {
6090
5585
  var key = String((l && l.option) || '');
6091
5586
  var parts = key.split(':');
6092
- var why = root.querySelector('#panel-physics [data-why="' + key + '"]');
5587
+ var why = root.querySelector('#view-new [data-why="' + key + '"]');
6093
5588
  if (why && l.reason) { why.textContent = why.textContent ? why.textContent + ' ' + l.reason : l.reason; why.hidden = false; }
6094
5589
  if (!parts[1]) {
6095
- if (l.effect === 'hide') root.querySelectorAll('#panel-physics [data-limit="' + key + '"]').forEach(function (el) { el.hidden = true; });
5590
+ if (l.effect === 'hide') root.querySelectorAll('#view-new [data-limit="' + key + '"]').forEach(function (el) { el.hidden = true; });
6096
5591
  return;
6097
5592
  }
6098
5593
  /* a note is offered as it is, with its reason in the list below */
6099
- root.querySelectorAll('#panel-physics [data-phys="' + parts[0] + '"][data-val="' + parts[1] + '"]').forEach(function (b) {
5594
+ root.querySelectorAll('#view-new [data-phys="' + parts[0] + '"][data-val="' + parts[1] + '"]').forEach(function (b) {
6100
5595
  if (l.effect === 'grey') { b.disabled = true; b.setAttribute('data-limit-grey', '1'); b.title = l.reason || ''; }
6101
5596
  if (l.effect === 'hide') { b.hidden = true; b.setAttribute('data-limit-hide', '1'); }
6102
5597
  });
@@ -6107,7 +5602,7 @@ summary{min-height:24px;padding:3px 0;}
6107
5602
  var byReason = lists[parts[0]] = lists[parts[0]] || {};
6108
5603
  (byReason[l.reason] = byReason[l.reason] || []).push(PHYS_SAY[parts[0]][parts[1]] || parts[1]);
6109
5604
  });
6110
- root.querySelectorAll('#panel-physics [data-why-list]').forEach(function (ul) {
5605
+ root.querySelectorAll('#view-new [data-why-list]').forEach(function (ul) {
6111
5606
  while (ul.firstChild) ul.removeChild(ul.firstChild);
6112
5607
  var byReason = lists[ul.getAttribute('data-why-list')] || {};
6113
5608
  Object.keys(byReason).forEach(function (reason) {
@@ -6119,59 +5614,37 @@ summary{min-height:24px;padding:3px 0;}
6119
5614
  });
6120
5615
  }
6121
5616
 
6122
- function paintPhysics() {
6123
- var p = phys(), a = p.answers;
6124
- root.querySelectorAll('#panel-physics .physQ').forEach(function (el) {
6125
- el.hidden = String(el.getAttribute('data-q')) !== String(p.q);
6126
- });
6127
- root.querySelectorAll('#physRail li').forEach(function (li) {
6128
- var n = Number(li.getAttribute('data-q'));
6129
- li.classList.toggle('now', n === p.q);
6130
- li.classList.toggle('done', !!n && (p.q === 'sum' || (typeof p.q === 'number' && n < p.q)) && !physSkips(n));
6131
- li.classList.toggle('skipped', physSkips(n));
6132
- });
6133
- $('physRail').hidden = p.q === 'stop' || p.q === 0;
6134
- $('physNav').hidden = p.q === 'stop';
6135
- physPaintVoice();
6136
- root.querySelectorAll('[data-phys-start]').forEach(function (b) { b.setAttribute('aria-pressed', String(b.getAttribute('data-phys-start') === p.start)); });
6137
- root.querySelectorAll('#panel-physics [data-phys]').forEach(function (b) {
5617
+ /* every [data-phys] pill, pressed or not, from the answers — on both screens */
5618
+ function physPaintPills() {
5619
+ var a = phys().answers;
5620
+ root.querySelectorAll('#view-new [data-phys]').forEach(function (b) {
6138
5621
  var v = a[b.getAttribute('data-phys')], val = b.getAttribute('data-val');
6139
5622
  var on = Array.isArray(v) ? v.indexOf(val) !== -1 : v !== undefined && v !== null && String(v) === val;
6140
5623
  b.setAttribute('aria-pressed', String(on));
6141
5624
  });
6142
- physPaintTeam();
6143
- var suite = a.framing === 'hybrid' || a.framing === 'local';
6144
- $('physSuite').hidden = !suite;
6145
- $('physSuiteLbl').textContent = a.framing === 'local' ? 'Your local suite' : 'Your hybrid suite';
6146
- if (p.q === 'sum') physPaintSummary();
6147
- $('physNext').textContent = p.q !== 'sum' ? 'Continue →'
6148
- : physVoice() === 'professional' ? 'Continue to setup →' : 'Create my planet →';
6149
- var existing = a.project_state === 'existing';
6150
- $('physExisting').hidden = !existing;
6151
- var works = Array.isArray(a.existing_work) ? a.existing_work : [];
6152
- root.querySelectorAll('#physExisting [data-work]').forEach(function (cb) { cb.checked = works.indexOf(cb.getAttribute('data-work')) !== -1; });
6153
- root.querySelectorAll('#physExisting input[name="physMain"]').forEach(function (r) {
6154
- r.checked = a.main_work === r.value;
6155
- r.disabled = works.indexOf(r.value) === -1;
6156
- });
6157
- $('physDesc').value = state.description || '';
5625
+ }
5626
+ /* screen 3, the quick checks: country and region, where builders will be, anyone
5627
+ under 18 (the tiers shown for a yes), and who is behind it (the full list of legal
5628
+ entities for someone who already has one) */
5629
+ function paintChecks() {
5630
+ var p = phys(), a = p.answers;
5631
+ physPaintPills();
6158
5632
  $('physAges').hidden = a.minors_on_board !== true;
6159
- var forms = existing || PHYS_ENTITY_FORMS.indexOf(a.entity) !== -1 || p.entityForms === true;
5633
+ var forms = PHYS_ENTITY_FORMS.indexOf(a.entity) !== -1 || p.entityForms === true;
6160
5634
  $('physForms').hidden = !forms;
6161
5635
  $('physIntent').hidden = forms;
6162
- if (p.q === 0 && !physAccountLoaded) { physAccountLoaded = true; physLoadAccount(); }
6163
5636
  physFillSelects();
6164
5637
  physApplyLimits();
6165
- physPaintStage();
6166
5638
  }
6167
-
6168
- /* the voice (spec D5): show the baked variant the entity answer picks */
6169
- function physPaintVoice() {
6170
- var v = physVoice() === 'professional' ? 'pro' : 'planet';
6171
- root.querySelectorAll('#panel-physics [data-voice]').forEach(function (el) { el.hidden = el.getAttribute('data-voice') !== v; });
5639
+ function checksShow(which) {
5640
+ $('checksMain').hidden = which !== 'main';
5641
+ $('checksStop').hidden = which !== 'stop';
5642
+ $('wizBack').hidden = false;
5643
+ var h = which === 'stop' ? $('phys-hstop') : $('checks-h');
5644
+ h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false });
6172
5645
  }
6173
5646
 
6174
- /* question 5: one or many, the start count, and the month-three guess */
5647
+ /* the Team size row's controls: one or many, the start count, and the month-three guess */
6175
5648
  function physPaintTeam() {
6176
5649
  var a = phys().answers, many = Number.isInteger(a.builders_at_start) && !physSolo();
6177
5650
  root.querySelectorAll('[data-phys-solo]').forEach(function (b) {
@@ -6179,85 +5652,23 @@ summary{min-height:24px;padding:3px 0;}
6179
5652
  b.setAttribute('aria-pressed', String(on));
6180
5653
  });
6181
5654
  $('physTeam').hidden = !many;
5655
+ paintTeam(state.teamShape || null);
6182
5656
  if (!many) return;
6183
5657
  var start = a.builders_at_start, exp = a.builders_expected;
6184
- var pro = physVoice() === 'professional';
6185
5658
  $('physStartN').textContent = String(start);
6186
- $('physM1').textContent = String(start);
6187
- $('physM2').textContent = String(Math.round(Math.sqrt(start * exp)));
6188
- $('physM3').textContent = String(exp);
6189
5659
  var range = $('physExpected');
6190
5660
  range.setAttribute('min', String(Math.max(2, start)));
6191
5661
  range.setAttribute('max', String(Math.min(100000, Math.max(30, physGuess(start) * 3, exp))));
6192
5662
  range.value = String(exp);
6193
- range.setAttribute('aria-valuetext', exp + (pro ? ' contributors' : ' builders') + ' by month three');
6194
- $('physGuessLine').textContent = pro
6195
- ? 'Based on your project, we estimate ' + exp + ' contributors by month three. Adjust it if you know better.'
6196
- : 'From what you’re building, we’d expect about ' + exp + ' builders by month three. Nobody can estimate builder growth cold, so we guess first and you correct.';
5663
+ range.setAttribute('aria-valuetext', exp + ' people by month three');
5664
+ $('physGuessLine').textContent = 'We guess about ' + exp + ' people by month three. Adjust it if you know better.';
6197
5665
  }
6198
5666
 
6199
- /* the summary: every answer, each with a way back to its question */
6200
5667
  function physCountryName(code) {
6201
5668
  try { return new Intl.DisplayNames(['en'], { type: 'region' }).of(code) || code; } catch (e) { return code; }
6202
5669
  }
6203
- function physSummaryRows() {
6204
- var a = phys().answers, pro = physVoice() === 'professional';
6205
- var rows = [];
6206
- var told = String(state.description || '').trim();
6207
- rows.push({ q: 1, k: 'State', v: PHYS_SAY.project_state[a.project_state] || 'Not answered', s: told });
6208
- rows.push({ q: 2, k: 'Geography', v: a.home_country ? physCountryName(a.home_country) + (a.home_region && PHYS_REGIONS[a.home_region] ? ' (' + PHYS_REGIONS[a.home_region] + ')' : '') : 'Not answered',
6209
- s: PHYS_SAY.builder_geography[a.builder_geography] || '' });
6210
- rows.push({ q: 3, k: 'Under 18', v: a.minors_on_board === true ? 'Yes' : a.minors_on_board === false ? 'No' : 'Not answered',
6211
- s: a.minors_on_board === true ? 'The age tiers apply, and a guardian signs for each young builder' : a.minors_on_board === false ? 'Everyone building is an adult' : '' });
6212
- rows.push({ q: 4, k: pro ? 'Entity' : 'You are', v: PHYS_SAY.entity[a.entity] || 'Not answered',
6213
- s: PHYS_ENTITY_FORMS.indexOf(a.entity) !== -1 ? 'An entity already formed' : a.entity ? 'The binding choice comes later' : '' });
6214
- var solo = physSolo();
6215
- rows.push({ q: 5, k: pro ? 'Team' : 'Builders', v: solo ? 'Just you' : Number.isInteger(a.builders_at_start) ? a.builders_at_start + ' now' : 'Not answered',
6216
- s: solo ? (pro ? 'A single contributor' : 'Building solo') : Number.isInteger(a.builders_expected) ? 'About ' + a.builders_expected + ' by month three' : '' });
6217
- var hiddenWhy = physLimits.filter(function (l) { return l && l.option === 'reward_intents' && l.effect === 'hide'; })[0];
6218
- var rewards = Array.isArray(a.reward_intents) ? a.reward_intents : [];
6219
- if (physSkips(6)) rows.push({ q: 5, k: 'Rewards', v: 'Not asked', s: hiddenWhy ? hiddenWhy.reason : '' });
6220
- else rows.push({ q: 6, k: 'Rewards', v: rewards.length ? PHYS_REWARDS.filter(function (r) { return rewards.indexOf(r) !== -1; }).map(function (r) { return PHYS_SAY.reward_intents[r]; }).join(', ') : 'Not answered',
6221
- s: rewards.length ? 'Intent only' : '' });
6222
- var services = Array.isArray(a.services) ? a.services : [];
6223
- rows.push({ q: 7, k: 'Framing', v: PHYS_SAY.framing[a.framing] || 'Not answered',
6224
- s: a.framing === 'managed' ? 'The whole suite, run for you'
6225
- : a.framing === 'open-source' ? 'The free core, on your own machines'
6226
- : (a.framing === 'hybrid' || a.framing === 'local') ? (services.length ? PHYS_SERVICES.filter(function (x) { return services.indexOf(x) !== -1; }).map(function (x) { return PHYS_SAY.services[x]; }).join(', ') : 'No services picked yet') : '' });
6227
- return rows;
6228
- }
6229
- function physPaintSummary() {
6230
- var dl = $('physSummary');
6231
- while (dl.firstChild) dl.removeChild(dl.firstChild);
6232
- physSummaryRows().forEach(function (r) {
6233
- var dt = document.createElement('dt');
6234
- dt.textContent = r.k;
6235
- var dd = document.createElement('dd');
6236
- var body = document.createElement('span');
6237
- body.textContent = r.v;
6238
- if (r.s) { var sm = document.createElement('small'); sm.textContent = r.s; body.appendChild(sm); }
6239
- var change = document.createElement('button');
6240
- change.type = 'button';
6241
- change.className = 'physLink';
6242
- change.textContent = 'change';
6243
- change.setAttribute('data-phys-goto', String(r.q));
6244
- change.setAttribute('aria-label', 'Change ' + r.k.toLowerCase());
6245
- change.addEventListener('click', function () { physChange(r.q); });
6246
- dd.appendChild(body);
6247
- dd.appendChild(change);
6248
- dl.appendChild(dt);
6249
- dl.appendChild(dd);
6250
- });
6251
- }
6252
- /* "change": back to that question; Continue from it returns to the summary */
6253
- function physChange(q) {
6254
- phys().fromSum = true;
6255
- physHint('');
6256
- physGo(q);
6257
- }
6258
-
6259
5670
  function physHint(msg) {
6260
- $('h-phys').textContent = msg || ' ';
5671
+ $('h-phys').textContent = msg || ' ';
6261
5672
  $('h-phys').className = msg ? 'hint err' : 'hint';
6262
5673
  }
6263
5674
  function physCheck() {
@@ -6266,101 +5677,50 @@ summary{min-height:24px;padding:3px 0;}
6266
5677
  body: JSON.stringify({ description: String(state.description || '').trim() || undefined, detail: physDetail() })
6267
5678
  }).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });
6268
5679
  }
6269
- function physGo(q) {
6270
- phys().q = q;
6271
- if (q === 'sum') phys().fromSum = false;
6272
- saveDraft();
6273
- paintPhysics();
6274
- var h = root.querySelector('#panel-physics .physQ[data-q="' + q + '"] h1');
6275
- if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
5680
+ /* What the table hides follows the team and the framing, so a change to either on
5681
+ the review asks it again and repaints the rows it touches. */
5682
+ function physRecheck() {
5683
+ physCheck().then(function (res) {
5684
+ if (!res || !res.soft_limits) return;
5685
+ physLimits = res.soft_limits.limits || [];
5686
+ physApplyLimits();
5687
+ paintSetup();
5688
+ });
6276
5689
  }
6277
5690
 
6278
- /* Continue: check this question, then move on. Question 1 asks the server's
6279
- screen first — a description it stops ends here, before anything exists. */
5691
+ /* Continue on the quick checks: every answer given, then the server's screen — the
5692
+ one the create runs — with the description from screen 2. A stop ends here,
5693
+ before anything exists; otherwise its soft limits shape the review. */
6280
5694
  var physBusy = false;
6281
- function physAdvance() {
6282
- var p = phys(), a = p.answers;
5695
+ function checksAdvance() {
5696
+ var a = phys().answers;
6283
5697
  physHint('');
6284
- if (p.q === 0) { if (!p.start) p.start = 'scratch'; physGo(1); return false; }
6285
- if (p.q === 1) {
6286
- if (!a.project_state) { physHint('Pick new or existing to go on.'); return false; }
6287
- if (a.project_state === 'existing') {
6288
- var works = Array.isArray(a.existing_work) ? a.existing_work : [];
6289
- if (!works.length) { physHint('Pick what already exists, then your main one.'); return false; }
6290
- if (works.indexOf(a.main_work) === -1) { physHint('Mark which of them is your main one.'); return false; }
6291
- }
6292
- if (physBusy) return false;
6293
- physBusy = true;
6294
- physCheck().then(function (res) {
6295
- physBusy = false;
6296
- if (res && res.stop) { $('physStopMsg').textContent = res.message || ''; physGo('stop'); return; }
6297
- physLimits = (res && res.soft_limits && res.soft_limits.limits) || [];
6298
- physGo(physNextQ(1));
6299
- });
6300
- return false;
6301
- }
6302
- if (p.q === 2) {
6303
- if (!a.home_country) { physHint('Pick your country.'); return false; }
6304
- if (!a.builder_geography) { physHint('Say where you expect builders to be. Not sure yet is a fine answer.'); return false; }
6305
- physGo(physNextQ(2)); return false;
6306
- }
6307
- if (p.q === 3) {
6308
- if (a.minors_on_board !== true && a.minors_on_board !== false) { physHint('Answer yes or no to go on.'); return false; }
6309
- physGo(physNextQ(3)); return false;
6310
- }
6311
- /* from question 4 on, the table is asked again on every Continue: what it hides
6312
- or greys on the next screens depends on the answers just given */
6313
- if (p.q >= 4 && p.q <= PHYS_LAST) {
6314
- if (!physAnswered(p.q)) {
6315
- physHint(p.q === 4 ? 'Pick the one that fits best. Don’t know yet is a fine answer.'
6316
- : p.q === 5 ? 'Say one or many to go on.'
6317
- : p.q === 6 ? 'Pick at least one. Non-monetary is a fine answer.'
6318
- : 'Pick how much should be run for you to go on.');
6319
- return false;
6320
- }
6321
- if (physBusy) return false;
6322
- physBusy = true;
6323
- var from = p.q;
6324
- physCheck().then(function (res) {
6325
- physBusy = false;
6326
- if (res && res.soft_limits) physLimits = res.soft_limits.limits || [];
6327
- physGo(physNextQ(from));
6328
- });
6329
- return false;
6330
- }
6331
- /* the summary's Create my planet: the answers pre-fill the technical steps,
6332
- and the wizard moves on to the first of them, the name */
6333
- if (p.q === 'sum') {
6334
- physPrefill();
5698
+ if (!a.home_country) { physHint('Pick your country.'); return false; }
5699
+ if (!a.builder_geography) { physHint('Say where builders will be. Not sure is a fine answer.'); return false; }
5700
+ if (a.minors_on_board !== true && a.minors_on_board !== false) { physHint('Answer yes or no for builders under 18.'); return false; }
5701
+ if (!a.entity) { physHint('Say who’s behind it. Not sure yet is a fine answer.'); return false; }
5702
+ if (physBusy) return false;
5703
+ physBusy = true;
5704
+ physDefaults();
5705
+ physCheck().then(function (res) {
5706
+ physBusy = false;
5707
+ if (res && res.stop) { $('physStopMsg').textContent = res.message || ''; checksShow('stop'); return; }
5708
+ physLimits = (res && res.soft_limits && res.soft_limits.limits) || [];
6335
5709
  show(4, true);
6336
- return false;
6337
- }
5710
+ });
6338
5711
  return false;
6339
5712
  }
6340
5713
 
6341
- $('physBack').addEventListener('click', function () {
6342
- var p = phys();
6343
- physHint('');
6344
- p.fromSum = false;
6345
- if (p.q === 'sum' || (typeof p.q === 'number' && p.q > 0)) physGo(physPrevQ(p.q)); else show(2, true);
6346
- });
6347
5714
  $('physStartOver').addEventListener('click', function () {
6348
5715
  state.description = '';
6349
5716
  if ($('f-desc')) $('f-desc').value = '';
6350
5717
  phys().answers = {};
6351
- physGo(1);
6352
- });
6353
- root.querySelectorAll('[data-phys-start]').forEach(function (b) {
6354
- b.addEventListener('click', function () {
6355
- var p = phys();
6356
- p.start = b.getAttribute('data-phys-start');
6357
- if (p.start === 'account' && physAccountAnswers) {
6358
- Object.keys(physAccountAnswers).forEach(function (k) { p.answers[k] = physAccountAnswers[k]; });
6359
- }
6360
- saveDraft(); paintPhysics();
6361
- });
5718
+ state.confirmed = {};
5719
+ saveDraft();
5720
+ show(2, true);
5721
+ checksShow('main');
6362
5722
  });
6363
- root.querySelectorAll('#panel-physics [data-phys]').forEach(function (b) {
5723
+ root.querySelectorAll('#view-new [data-phys]').forEach(function (b) {
6364
5724
  b.addEventListener('click', function () {
6365
5725
  var k = b.getAttribute('data-phys'), raw = b.getAttribute('data-val');
6366
5726
  var v = raw === 'true' ? true : raw === 'false' ? false : raw;
@@ -6377,7 +5737,9 @@ summary{min-height:24px;padding:3px 0;}
6377
5737
  }
6378
5738
  if (k === 'project_state' && a[k] !== 'existing') { delete a.existing_work; delete a.main_work; }
6379
5739
  if (k === 'framing') physFramingChanged();
6380
- physHint(''); saveDraft(); paintPhysics();
5740
+ physHint(''); saveDraft();
5741
+ paintChecks();
5742
+ paintSetup();
6381
5743
  });
6382
5744
  });
6383
5745
  /* A hybrid or local framing starts from the canvas's suite until the founder picks
@@ -6385,22 +5747,22 @@ summary{min-height:24px;padding:3px 0;}
6385
5747
  function physFramingChanged() {
6386
5748
  var p = phys(), a = p.answers;
6387
5749
  if ((a.framing === 'hybrid' || a.framing === 'local') && !p.suiteTouched) a.services = PHYS_SUITE_START[a.framing].slice();
6388
- physCheck().then(function (res) {
6389
- if (!res || !res.soft_limits) return;
6390
- physLimits = res.soft_limits.limits || [];
6391
- paintPhysics();
6392
- });
5750
+ unconfirm(['app']);
5751
+ physRecheck();
6393
5752
  }
6394
- /* question 5's controls: one or many, the start count, the guess */
5753
+ /* the Team size row's controls: one or many, the start count, the guess */
6395
5754
  root.querySelectorAll('[data-phys-solo]').forEach(function (b) {
6396
5755
  b.addEventListener('click', function () {
6397
5756
  var p = phys(), a = p.answers;
6398
5757
  if (b.getAttribute('data-phys-solo') === 'one') {
6399
5758
  a.builders_at_start = 1; a.builders_expected = 1; p.guessTouched = false;
5759
+ state.teamShape = 'solo';
6400
5760
  } else if (physSolo() || !Number.isInteger(a.builders_at_start)) {
6401
5761
  a.builders_at_start = 2; a.builders_expected = physGuess(2); p.guessTouched = false;
5762
+ state.teamShape = 'small-team';
6402
5763
  }
6403
- physHint(''); saveDraft(); paintPhysics();
5764
+ unconfirm(['rewards', 'extras']);
5765
+ saveDraft(); paintSetup(); physRecheck();
6404
5766
  });
6405
5767
  });
6406
5768
  root.querySelectorAll('[data-phys-count]').forEach(function (b) {
@@ -6412,7 +5774,7 @@ summary{min-height:24px;padding:3px 0;}
6412
5774
  expects company, or it would read as building alone */
6413
5775
  if (!p.guessTouched) a.builders_expected = physGuess(start);
6414
5776
  a.builders_expected = Math.max(a.builders_expected || 0, start, 2);
6415
- saveDraft(); paintPhysics();
5777
+ saveDraft(); paintSetup();
6416
5778
  });
6417
5779
  });
6418
5780
  $('physExpected').addEventListener('input', function () {
@@ -6420,7 +5782,7 @@ summary{min-height:24px;padding:3px 0;}
6420
5782
  if (!Number.isFinite(n)) return;
6421
5783
  a.builders_expected = Math.max(n, a.builders_at_start || 1, 2);
6422
5784
  p.guessTouched = true;
6423
- saveDraft(); physPaintTeam(); physPaintStage();
5785
+ saveDraft(); paintSetup();
6424
5786
  });
6425
5787
  root.querySelectorAll('#physExisting [data-work]').forEach(function (cb) {
6426
5788
  cb.addEventListener('change', function () {
@@ -6431,35 +5793,30 @@ summary{min-height:24px;padding:3px 0;}
6431
5793
  a.existing_work = list;
6432
5794
  if (list.length === 1) a.main_work = list[0];
6433
5795
  if (list.indexOf(a.main_work) === -1) a.main_work = list.length ? list[0] : null;
6434
- saveDraft(); paintPhysics();
5796
+ saveDraft(); paintSetup();
6435
5797
  });
6436
5798
  });
6437
5799
  root.querySelectorAll('#physExisting input[name="physMain"]').forEach(function (r) {
6438
- r.addEventListener('change', function () { if (r.checked) { phys().answers.main_work = r.value; saveDraft(); paintPhysics(); } });
6439
- });
6440
- $('physDesc').addEventListener('input', function () {
6441
- state.description = $('physDesc').value;
6442
- if ($('f-desc')) $('f-desc').value = state.description;
6443
- saveDraft();
5800
+ r.addEventListener('change', function () { if (r.checked) { phys().answers.main_work = r.value; saveDraft(); paintSetup(); } });
6444
5801
  });
6445
5802
  $('physCountry').addEventListener('change', function () {
6446
5803
  var a = phys().answers;
6447
5804
  a.home_country = $('physCountry').value || null;
6448
5805
  if (a.home_region && a.home_region.indexOf((a.home_country || '') + '-') !== 0) a.home_region = null;
6449
- saveDraft(); paintPhysics();
5806
+ physHint(''); saveDraft(); paintChecks();
6450
5807
  });
6451
- $('physRegion').addEventListener('change', function () { phys().answers.home_region = $('physRegion').value || null; saveDraft(); physPaintStage(); });
6452
- $('physHaveEntity').addEventListener('click', function () { phys().entityForms = true; saveDraft(); paintPhysics(); });
5808
+ $('physRegion').addEventListener('change', function () { phys().answers.home_region = $('physRegion').value || null; saveDraft(); });
5809
+ $('physHaveEntity').addEventListener('click', function () { phys().entityForms = true; saveDraft(); paintChecks(); });
6453
5810
  $('physNoEntity').addEventListener('click', function () {
6454
5811
  var p = phys();
6455
5812
  p.entityForms = false;
6456
5813
  if (PHYS_ENTITY_FORMS.indexOf(p.answers.entity) !== -1) p.answers.entity = null;
6457
- saveDraft(); paintPhysics();
5814
+ saveDraft(); paintChecks();
6458
5815
  });
6459
5816
 
6460
5817
  /* Auto-fill: the answers this account already gave on its most recent project —
6461
- the only details the platform holds about a founder. With none, the door says so
6462
- and stays shut. The state of the planet is never copied: it is this project's. */
5818
+ the only details the platform holds about a founder. With none, the offer stays
5819
+ hidden. The state of the project is never copied: it is this project's. */
6463
5820
  var physAccountAnswers = null;
6464
5821
  var physAccountLoaded = false;
6465
5822
  function physLoadAccount() {
@@ -6475,101 +5832,114 @@ summary{min-height:24px;padding:3px 0;}
6475
5832
  if (withDetail.detail[k] !== undefined && withDetail.detail[k] !== null) pick[k] = withDetail.detail[k];
6476
5833
  });
6477
5834
  physAccountAnswers = Object.keys(pick).length ? pick : null;
6478
- $('physFromAccount').disabled = !physAccountAnswers;
6479
- var why = $('physAccountWhy');
6480
- why.hidden = !!physAccountAnswers;
6481
- why.textContent = physAccountAnswers ? '' : 'Nothing to fill from yet: your account has no other project with these answers.';
5835
+ $('physFillOffer').hidden = !physAccountAnswers;
6482
5836
  })
6483
5837
  .catch(function () {});
6484
5838
  }
5839
+ function physLoadAccountOnce() {
5840
+ if (physAccountLoaded || !authKnown) return;
5841
+ physAccountLoaded = true;
5842
+ physLoadAccount();
5843
+ }
5844
+ $('physFromAccount').addEventListener('click', function () {
5845
+ if (!physAccountAnswers) return;
5846
+ var a = phys().answers;
5847
+ Object.keys(physAccountAnswers).forEach(function (k) { a[k] = physAccountAnswers[k]; });
5848
+ physHint(''); saveDraft(); paintChecks();
5849
+ });
6485
5850
 
6486
- /* per-panel validation on Next */
5851
+ /* ── checks the old steps made on Next, now made by their rows ── */
5852
+ function validateCode() {
5853
+ var a = phys().answers;
5854
+ var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
5855
+ if (a.project_state === 'existing') {
5856
+ var works = Array.isArray(a.existing_work) ? a.existing_work : [];
5857
+ if (!works.length) return homeErr('Pick what already exists, then your main one.');
5858
+ if (works.indexOf(a.main_work) === -1) return homeErr('Mark which of them is your main one.');
5859
+ }
5860
+ if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
5861
+ if (repoAccess === true && state.mode !== 'adopt') {
5862
+ /* Fresh with access: just the new repo NAME (owner is the signed-in account). */
5863
+ if (!validRepoName(state.repo)) return homeErr('Name the new repository (letters, numbers, and . _ -).');
5864
+ } else {
5865
+ /* Adopt (picker or manual) and the manual fallback: need a concrete owner + repo. */
5866
+ if (!state.owner || !state.repo) {
5867
+ return homeErr(state.mode === 'adopt' && repoAccess === true ? 'Pick one of your repositories.' : 'Both owner and repository name are needed.');
5868
+ }
5869
+ }
5870
+ $('h-home').innerHTML = '&nbsp;'; $('h-home').className = 'hint';
5871
+ return true;
5872
+ }
5873
+ function validateAddress() {
5874
+ if (!validSlug(state.slug)) {
5875
+ $('h-slug').textContent = '2–40 lowercase letters, numbers, or dashes; no leading/trailing/double dash.';
5876
+ $('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
5877
+ return false;
5878
+ }
5879
+ if (state.domain && !looksLikeDomain(state.domain)) {
5880
+ $('addrCustom').open = true;
5881
+ var hd0 = $('h-domain');
5882
+ hd0.textContent = 'That doesn’t look like a domain yet (e.g. moonbase.example.com) — fix it, or clear the field to use the address above.';
5883
+ hd0.className = 'hint err';
5884
+ return false;
5885
+ }
5886
+ /* A handle the server has already said is not free would only 409 at Create —
5887
+ stop here instead (task 1002898 N10). ONLY a definite negative blocks:
5888
+ 'unknown' and 'checking' must never gate it, because the check is advisory
5889
+ and the 409 remains the authority. A chosen domain or the opt-out settles the
5890
+ address another way, so the block does not apply there. */
5891
+ if (!state.domain && !state.noAddress) {
5892
+ var av = slugAvailState();
5893
+ if (av === 'taken' || av === 'yours' || av === 'reserved') {
5894
+ $('h-slug').textContent = av === 'taken'
5895
+ ? 'That handle is already taken — pick a different one.'
5896
+ : av === 'yours'
5897
+ ? 'You already have a project with that handle — pick a different one.'
5898
+ : 'That handle is reserved by the platform — pick a different one.';
5899
+ $('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
5900
+ return false;
5901
+ }
5902
+ }
5903
+ /* no auto-assigned address (dotless host): going domainless must be an
5904
+ EXPLICIT choice — a domain, or the opt-out box. Never silent. */
5905
+ if (!resolvedDomain() && !state.noAddress) {
5906
+ $('addrCustom').open = true;
5907
+ var hd = $('h-domain');
5908
+ hd.textContent = 'Add a domain you own — or tick “no address yet” to go without one.';
5909
+ hd.className = 'hint err';
5910
+ return false;
5911
+ }
5912
+ return true;
5913
+ }
5914
+
5915
+ /* per-screen validation on Continue */
6487
5916
  function validateStep(step) {
6488
- if (step === 10) return validateRender();
6489
- /* planet physics (task 1004418): Continue walks the questions inside the step,
6490
- and only the last one moves the wizard on, so this never says yes itself */
6491
- if (step === 3) return physAdvance();
6492
- /* the fork's own door is "Begin planet creation": a real project, not the demo */
6493
- if (step === 2) { state.demo = null; return true; }
6494
- /* the front door's one tick (task 1004417): nothing past it until it is given */
5917
+ /* the front door's one tick (task 1004417): nothing past it until it is given.
5918
+ The fork's own submit door is "Start my project": a real project, not the demo. */
6495
5919
  if (step === 1) {
6496
5920
  if (!state.termsAck) {
6497
- $('h-terms').textContent = 'Tick the box to go on. It’s how your project records that you acknowledged the terms.';
5921
+ $('h-terms').textContent = 'Tick the box to go on. It’s how your project records that you accepted the terms.';
6498
5922
  $('h-terms').className = 'hint err';
6499
5923
  return false;
6500
5924
  }
5925
+ if (state.demo) { state.demo = null; state.confirmed = {}; }
6501
5926
  return true;
6502
5927
  }
6503
- if (step === 4) {
5928
+ if (step === 2) {
6504
5929
  if (!state.name.trim()) { $('h-name').textContent = 'Give it a name to start.'; $('h-name').className = 'hint err'; return false; }
6505
- return true;
6506
- }
6507
- if (step === 5) {
6508
5930
  if (!state.projType) {
6509
- $('h-projtype').textContent = 'Pick one — Not Sure is a fine answer.';
5931
+ $('h-projtype').textContent = 'Pick one — Not sure is a fine answer.';
6510
5932
  $('h-projtype').className = 'hint err';
6511
5933
  return false;
6512
5934
  }
6513
5935
  return true;
6514
5936
  }
6515
- /* steps 6 (About) and 7 (Modules) have no clause: they are the optional
6516
- ones, and Next past an empty answer is the whole point (tasks 1002334,
6517
- 1002339). Step 2 (the fork) has none either: its doors are the answer. */
6518
- if (step === 8) {
6519
- var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
6520
- if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
6521
- if (repoAccess === true && state.mode !== 'adopt') {
6522
- /* Fresh with access: just the new repo NAME (owner is the signed-in account). */
6523
- if (!validRepoName(state.repo)) return homeErr('Name the new repository (letters, numbers, and . _ -).');
6524
- } else {
6525
- /* Adopt (picker or manual) and the manual fallback: need a concrete owner + repo. */
6526
- if (!state.owner || !state.repo) {
6527
- return homeErr(state.mode === 'adopt' && repoAccess === true ? 'Pick one of your repositories.' : 'Both owner and repository name are needed.');
6528
- }
6529
- }
6530
- $('h-home').innerHTML = '&nbsp;'; $('h-home').className = 'hint';
6531
- return true;
6532
- }
6533
- if (step === 9) {
6534
- if (!validSlug(state.slug)) {
6535
- $('h-slug').textContent = '2–40 lowercase letters, numbers, or dashes; no leading/trailing/double dash.';
6536
- $('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
6537
- return false;
6538
- }
6539
- if (state.domain && !looksLikeDomain(state.domain)) {
6540
- $('addrCustom').open = true;
6541
- var hd0 = $('h-domain');
6542
- hd0.textContent = 'That doesn’t look like a domain yet (e.g. mercury.example.com) — fix it, or clear the field to use the address above.';
6543
- hd0.className = 'hint err';
6544
- $('f-domain').focus();
6545
- return false;
6546
- }
6547
- /* A handle the server has already said is not free would only 409 at Create —
6548
- stop here instead (task 1002898 N10). ONLY a definite negative blocks:
6549
- 'unknown' and 'checking' must never gate the step, because the check is
6550
- advisory and the 409 remains the authority. A chosen domain or the opt-out
6551
- settles the address another way, so the block does not apply there. */
6552
- if (!state.domain && !state.noAddress) {
6553
- var av = slugAvailState();
6554
- if (av === 'taken' || av === 'yours' || av === 'reserved') {
6555
- $('h-slug').textContent = av === 'taken'
6556
- ? 'That handle is already taken — pick a different one.'
6557
- : av === 'yours'
6558
- ? 'You already have a project with that handle — pick a different one.'
6559
- : 'That handle is reserved by the platform — pick a different one.';
6560
- $('h-slug').className = 'hint err'; $('f-slug').classList.add('bad');
6561
- return false;
6562
- }
6563
- }
6564
- /* no auto-assigned address (dotless host): going domainless must be an
6565
- EXPLICIT choice — a domain, or the opt-out box. Never silent. */
6566
- if (!resolvedDomain() && !state.noAddress) {
6567
- $('addrCustom').open = true;
6568
- var hd = $('h-domain');
6569
- hd.textContent = 'Add a domain you own — or tick “start without an address” to go without one.';
6570
- hd.className = 'hint err';
6571
- return false;
6572
- }
5937
+ /* the quick checks walk to the review themselves, after the server's screen */
5938
+ if (step === 3) return checksAdvance();
5939
+ /* the review: every row that applies confirmed, each still true */
5940
+ if (step === 4) {
5941
+ var open = unconfirmedRows();
5942
+ if (open.length) { openRow(open[0]); return false; }
6573
5943
  return true;
6574
5944
  }
6575
5945
  return true;
@@ -6579,44 +5949,16 @@ summary{min-height:24px;padding:3px 0;}
6579
5949
  form.addEventListener('submit', function (ev) {
6580
5950
  ev.preventDefault();
6581
5951
  var next = Number(form.getAttribute('data-next'));
6582
- /* a step the planet-physics answers skip is stepped over (task 1004419) */
6583
- if (validateStep(next - 1)) show(physPastSkips(next, 1), true);
5952
+ if (!validateStep(next - 1)) return;
5953
+ /* a demo's answers are the demo's own, so it skips the quick checks */
5954
+ if (next === 3 && state.demo) { physDefaults(); show(4, true); return; }
5955
+ show(next, true);
6584
5956
  });
6585
5957
  });
6586
- root.querySelectorAll('[data-back]').forEach(function (btn) {
6587
- btn.addEventListener('click', function () { show(physPastSkips(Number(btn.getAttribute('data-back')), -1), true); });
6588
- });
6589
5958
 
6590
- /* ── review ── */
6591
- /* the app step (task 1004351): a native radio group, so the draft only mirrors it.
6592
- An older draft has no appPlatform, and a value this page does not offer (a
6593
- platform still marked soon, or one that was removed) reads as 'later' — the
6594
- review must never name a platform the owner could not actually pick. */
6595
- function appPlatformChoice() {
6596
- var want = state.appPlatform;
6597
- var pick = Array.prototype.filter.call(root.querySelectorAll('#appOpts input[name=appPlatform]'), function (i) {
6598
- return i.value === want && !i.disabled;
6599
- })[0];
6600
- return pick ? pick.value : 'later';
6601
- }
6602
- function syncAppStep() {
6603
- var pick = appPlatformChoice();
6604
- root.querySelectorAll('#appOpts input[name=appPlatform]').forEach(function (i) { i.checked = i.value === pick; });
6605
- }
6606
- function appRow() {
6607
- /* the step the framing skipped (task 1004419) */
6608
- if (physStepSkipped(10)) return 'Runs on your own machines, as your framing says — you can choose a platform later from the project’s page';
6609
- if (appPlatformChoice() !== 'render') return 'Not chosen yet — you can decide later';
6610
- if (renderLater()) return 'Render — set up from your project’s page once your app has code. Nothing is created on Render now.';
6611
- var ws =esc(state.renderOwnerName || state.renderOwnerId || '');
6612
- if (state.renderMode === 'adopt') return 'Render, workspace ' + ws + ' — link <code>' + esc(state.renderServiceName || state.renderServiceId || '') + '</code>, changing nothing on it';
6613
- var plan = planLabel(RENDER_WEB_PLANS, state.renderPlan || 'starter');
6614
- var db = state.renderDb ? ', with a database (' + planLabel(RENDER_DB_PLANS, state.renderDbPlan || 'basic_256mb') + ')' : ', no database';
6615
- return 'Render, workspace ' + ws + ' — a new app (' + esc(runtimeLabel(state.renderRuntime || 'node')) + ', ' + plan + ')' + db + '. Render bills you directly.';
6616
- }
6617
5959
  var appOptsEl = root.querySelector('#appOpts');
6618
5960
  if (appOptsEl) appOptsEl.addEventListener('change', function (ev) {
6619
- if (ev.target && ev.target.name === 'appPlatform' && !ev.target.disabled) { state.appPlatform = ev.target.value; saveDraft(); syncRenderSetup(); }
5961
+ if (ev.target && ev.target.name === 'appPlatform' && !ev.target.disabled) { state.appPlatform = ev.target.value; saveDraft(); syncRenderSetup(); paintSetup(); }
6620
5962
  });
6621
5963
 
6622
5964
  /* ── Render (task 1004352, ADR 0348) ──
@@ -6629,8 +5971,9 @@ summary{min-height:24px;padding:3px 0;}
6629
5971
  fresh one, Render ran the repo's `npm start`, which is the hall, and timed out.
6630
5972
  Choosing Render for a fresh repo is still an answer: the step says when and where
6631
5973
  it happens, and nothing is sent to Render at Create. */
6632
- function renderNow() { return appPlatformChoice() === 'render' && state.mode === 'adopt'; }
6633
- function renderLater() { return appPlatformChoice() === 'render' && state.mode !== 'adopt'; }
5974
+ /* a framing that runs on the founder's own machines rules Render out, whatever was picked before */
5975
+ function renderNow() { return !appSkipped() && appPlatformChoice() === 'render' && state.mode === 'adopt'; }
5976
+ function renderLater() { return !appSkipped() && appPlatformChoice() === 'render' && state.mode !== 'adopt'; }
6634
5977
  var rc = renderConnect($, state, {
6635
5978
  save: saveDraft,
6636
5979
  repo: function () { return String(state.owner || '') + '/' + String(state.repo || ''); },
@@ -6670,7 +6013,7 @@ summary{min-height:24px;padding:3px 0;}
6670
6013
  : 'Once the project is up, we create your app on your Render account. Your key is used once, then thrown away.';
6671
6014
  /* then the app's own progress, from the same read the project's page uses (task 1004353) */
6672
6015
  var box = $('doneApp');
6673
- if (box) watchApp(instanceId, box, function () { return state.step === 13; }, 5000);
6016
+ if (box) watchApp(instanceId, box, function () { return state.step === 6; }, 5000);
6674
6017
  });
6675
6018
  }).catch(function (err) {
6676
6019
  note.className = 'hint err';
@@ -6694,14 +6037,6 @@ summary{min-height:24px;padding:3px 0;}
6694
6037
  function () { return state.name; });
6695
6038
  } else lookPicker.repaint();
6696
6039
  }
6697
- function lookRow() {
6698
- var l = state.look || defaultLook();
6699
- var name = l.look === 'custom'
6700
- ? 'Your own — ' + ((lookKit.lookAdjustAccent(l.accent) || {}).hex || '')
6701
- : (lookKit.LOOKS[l.look] || lookKit.LOOKS['chrome-world']).title;
6702
- return esc(name) + ', ' + (l.nightTint === false ? 'pure black at night' : 'a tinted night') +
6703
- (l.logo ? ', with your logo' : ', no logo');
6704
- }
6705
6040
  /* Send the look a moment after Create, like the Render key: the project exists, so a
6706
6041
  refusal here is the LOOK's, and the done panel says so in plain words. */
6707
6042
  function sendLookSetup(instanceId) {
@@ -6714,46 +6049,214 @@ summary{min-height:24px;padding:3px 0;}
6714
6049
  note.textContent = 'Your project is being created, but its look was not saved: ' + err.message + ' You can choose it again on the project’s page.';
6715
6050
  });
6716
6051
  }
6717
- function renderReview() {
6718
- /* every road back to this step gets a live button — the create path
6719
- disables it and only the failure branch used to restore it, which
6720
- left "Start another project" walking into a dead Create */
6052
+
6053
+ /* ── the review: check what we set up (task 1004504) ── */
6054
+ /* the app row (task 1004351): a native radio group, so the draft only mirrors it.
6055
+ An older draft has no appPlatform, and a value this page does not offer (a
6056
+ platform still marked soon, or one that was removed) reads as 'later' — the
6057
+ review must never name a platform the owner could not actually pick. */
6058
+ function appPlatformChoice() {
6059
+ var want = state.appPlatform;
6060
+ var pick = Array.prototype.filter.call(root.querySelectorAll('#appOpts input[name=appPlatform]'), function (i) {
6061
+ return i.value === want && !i.disabled;
6062
+ })[0];
6063
+ return pick ? pick.value : 'later';
6064
+ }
6065
+ function syncAppStep() {
6066
+ var pick = appPlatformChoice();
6067
+ root.querySelectorAll('#appOpts input[name=appPlatform]').forEach(function (i) { i.checked = i.value === pick; });
6068
+ }
6069
+ /* what the App hosting row says under its value, in plain text */
6070
+ function appRowDetail() {
6071
+ if (appPlatformChoice() !== 'render') return 'We host your builders’ hall. There’s no app to host until your builders write one.';
6072
+ if (renderLater()) return 'Set up from your project’s page once your app has code. Nothing is created on Render now.';
6073
+ var ws = state.renderOwnerName || state.renderOwnerId || '';
6074
+ if (state.renderMode === 'adopt') return 'Workspace ' + ws + ' — link ' + (state.renderServiceName || state.renderServiceId || '') + ', changing nothing on it.';
6075
+ var plan = planLabel(RENDER_WEB_PLANS, state.renderPlan || 'starter');
6076
+ var db = state.renderDb ? ', with a database (' + planLabel(RENDER_DB_PLANS, state.renderDbPlan || 'basic_256mb') + ')' : ', no database';
6077
+ return 'Workspace ' + ws + ' — a new app (' + runtimeLabel(state.renderRuntime || 'node') + ', ' + plan + ')' + db + '. Render bills you directly.';
6078
+ }
6079
+
6080
+ var ROWS = ['code', 'address', 'app', 'extras', 'team', 'rewards', 'run'];
6081
+ function rowEl(key) { return root.querySelector('#reviewRows .setRow[data-row="' + key + '"]'); }
6082
+ function confirmedRows() {
6083
+ if (!state.confirmed || typeof state.confirmed !== 'object') state.confirmed = {};
6084
+ return state.confirmed;
6085
+ }
6086
+ /* a row the answers make not apply: rewards for someone building alone (the table
6087
+ hides the question) */
6088
+ function rowApplies(key) { return !(key === 'rewards' && physSkips(6)); }
6089
+ /* a row whose value is fixed, so it is confirmed but never changed here: the demo's
6090
+ own answers, and the app platform a local or open-source framing rules out */
6091
+ function rowFixed(key) {
6092
+ if (state.demo && (key === 'team' || key === 'rewards' || key === 'run')) return true;
6093
+ return key === 'app' && appSkipped();
6094
+ }
6095
+ function unconfirmedRows() {
6096
+ var c = confirmedRows();
6097
+ return ROWS.filter(function (k) { return rowApplies(k) && !c[k]; });
6098
+ }
6099
+ function unconfirm(keys) {
6100
+ var c = confirmedRows();
6101
+ keys.forEach(function (k) { delete c[k]; });
6102
+ if (state.step === 4) paintSetup();
6103
+ }
6104
+ /* the checks a row's own old step made on Next — a row cannot be confirmed past them */
6105
+ function rowValid(key) {
6106
+ if (key === 'code') return validateCode();
6107
+ if (key === 'address') return validateAddress();
6108
+ if (key === 'app') return rowFixed('app') || validateRender();
6109
+ if (key === 'team') return physAnswered(5);
6110
+ if (key === 'run') return !!phys().answers.framing;
6111
+ return true;
6112
+ }
6113
+ function openRow(key) {
6114
+ var row = rowEl(key);
6115
+ if (!row || rowFixed(key)) return;
6116
+ row.classList.add('open');
6117
+ row.querySelector('.opts').hidden = false;
6118
+ row.querySelector('[data-row-chg]').setAttribute('aria-expanded', 'true');
6119
+ if (key === 'address') { checkSlugAvailable(); renderAddrPanel(); }
6120
+ if (key === 'extras') renderModules();
6121
+ if (key === 'code') syncHome();
6122
+ if (key === 'app') { syncAppStep(); syncRenderSetup(); }
6123
+ }
6124
+ function closeRow(key) {
6125
+ var row = rowEl(key);
6126
+ row.classList.remove('open');
6127
+ row.querySelector('.opts').hidden = true;
6128
+ row.querySelector('[data-row-chg]').setAttribute('aria-expanded', 'false');
6129
+ }
6130
+ /* Looks right, or Use this: the row's own checks first — a row that fails opens
6131
+ with its reason showing — then it is the founder's answer. The extras row turns
6132
+ "the suggested set" into an answer given, a different fact from never asked. */
6133
+ function confirmRow(key) {
6134
+ if (!rowValid(key)) { openRow(key); return false; }
6135
+ /* only once the catalog is here: without it the suggested set is unknown, and an
6136
+ empty answer would switch off the very bundle the server applies to no answer */
6137
+ if (key === 'extras' && starterBundles) state.modules = pickedModules();
6138
+ confirmedRows()[key] = true;
6139
+ closeRow(key);
6140
+ saveDraft();
6141
+ paintSetup();
6142
+ var next = unconfirmedRows()[0];
6143
+ var go = next ? rowEl(next).querySelector('[data-row-ok]') : $('reviewGo');
6144
+ if (go && !go.disabled) go.focus({ preventScroll: true });
6145
+ return true;
6146
+ }
6147
+ $('reviewRows').addEventListener('click', function (ev) {
6148
+ var t = ev.target.closest && ev.target.closest('[data-row-ok], [data-row-chg], [data-row-use]');
6149
+ if (!t) return;
6150
+ var key = t.closest('.setRow').getAttribute('data-row');
6151
+ if (t.hasAttribute('data-row-chg')) {
6152
+ if (rowEl(key).classList.contains('open')) closeRow(key);
6153
+ else { delete confirmedRows()[key]; openRow(key); paintSetup(); }
6154
+ return;
6155
+ }
6156
+ confirmRow(key);
6157
+ });
6158
+
6159
+ /* what each row says: its value and one plain line under it */
6160
+ function rowText(key) {
6161
+ var a = phys().answers;
6162
+ if (key === 'code') {
6163
+ var adopt = state.mode === 'adopt';
6164
+ var where = state.owner && state.repo ? 'github.com/' + state.owner + '/' + state.repo : adopt ? 'Pick the repo to adopt.' : '';
6165
+ return { v: adopt ? 'A repo I already have' : 'A new GitHub repo', s: where + (a.project_state === 'existing' ? (where ? ' · ' : '') + 'already started' : '') };
6166
+ }
6167
+ if (key === 'address') {
6168
+ var custom = state.domain && looksLikeDomain(state.domain);
6169
+ var av = !custom && !state.noAddress ? slugAvailState() : 'unknown';
6170
+ if (state.noAddress && !custom) return { v: 'No address yet', s: 'Sign-in can’t be switched on until it has one.' };
6171
+ /* a dotless host (local dev) assigns nothing, so the row asks for a domain or the opt-out */
6172
+ if (!resolvedDomain()) return { v: 'No address assigned here', s: 'Add a domain you own, or go without one for now.' };
6173
+ return {
6174
+ v: resolvedDomain() || 'Pick a handle',
6175
+ s: custom ? 'Your own domain. Your projects page walks you through pointing it here.'
6176
+ : av === 'taken' ? 'Already taken — change the handle.'
6177
+ : av === 'yours' ? 'You already have that one — change the handle.'
6178
+ : av === 'reserved' ? 'Reserved by the platform — change the handle.'
6179
+ : 'Yours once it’s set up. You can add your own domain any time.'
6180
+ };
6181
+ }
6182
+ if (key === 'app') {
6183
+ if (appSkipped()) return { v: 'Runs on your own machines', s: 'As your answer says. Choose a platform later from the project’s page.' };
6184
+ return { v: appPlatformChoice() === 'render' ? 'Render' : 'Decide later', s: appRowDetail() };
6185
+ }
6186
+ if (key === 'extras') {
6187
+ var picked = pickedModules();
6188
+ var lead = !starterBundles
6189
+ ? (state.modules === null ? 'The suggested set' : 'The extras you picked')
6190
+ : state.modules === null
6191
+ ? picked.length + ' suggested' + (typeLabel(state.projType) ? ' for a ' + typeLabel(state.projType).toLowerCase() : '')
6192
+ : picked.length ? picked.length + ' picked' : 'None — just the core';
6193
+ return { v: lead, s: starterBundles ? extrasRow() : '' };
6194
+ }
6195
+ if (key === 'team') {
6196
+ if (state.demo) return { v: state.demo.people + (state.demo.people === 1 ? ' person' : ' people'), s: state.demo.hours_each + (state.demo.hours_each === 1 ? ' hour' : ' hours') + ' each. When the time box runs out, we ask if you want to keep it.' };
6197
+ if (physSolo()) return { v: 'One person', s: 'Just you.' };
6198
+ return { v: a.builders_at_start + ' people now', s: 'About ' + a.builders_expected + ' by month three · ' + (state.teamShape === 'community' ? 'anyone can apply' : 'people you invite') + '.' };
6199
+ }
6200
+ if (key === 'rewards') {
6201
+ if (state.demo) return { v: 'None', s: 'It’s a demo, for learning.' };
6202
+ var r = Array.isArray(a.reward_intents) ? a.reward_intents : [];
6203
+ return r.length
6204
+ ? { v: PHYS_REWARDS.filter(function (x) { return r.indexOf(x) !== -1; }).map(function (x) { return PHYS_SAY.reward_intents[x]; }).join(', '), s: 'Intent only. You set the real terms later.' }
6205
+ : { v: 'Not decided yet', s: 'Intent only. You set the real terms later.' };
6206
+ }
6207
+ if (key === 'run') {
6208
+ if (state.demo) return { v: 'Everything, for you', s: 'A demo is all managed and private to your team.' };
6209
+ var services = Array.isArray(a.services) ? a.services : [];
6210
+ var named = PHYS_SERVICES.filter(function (x) { return services.indexOf(x) !== -1; }).map(function (x) { return PHYS_SAY.services[x]; }).join(', ');
6211
+ if (a.framing === 'hybrid') return { v: 'Some of it', s: named || 'Pick what you want from the suite.' };
6212
+ if (a.framing === 'local') return { v: 'On my machines', s: named || 'Pick what you want from the suite.' };
6213
+ if (a.framing === 'open-source') return { v: 'Free core only', s: 'The free core, on your own machines.' };
6214
+ return { v: 'Everything, for you', s: 'Hosting, payouts and the starter kit, managed.' };
6215
+ }
6216
+ return { v: '', s: '' };
6217
+ }
6218
+ /* paint every row from the draft, and the locked Continue under them */
6219
+ function paintSetup() {
6220
+ if (!ROWS) return; /* boot: setMode() runs before this block has run */
6221
+ var c = confirmedRows();
6222
+ var suite = phys().answers.framing === 'hybrid' || phys().answers.framing === 'local';
6223
+ $('physSuite').hidden = !suite;
6224
+ $('physSuiteLbl').textContent = phys().answers.framing === 'local' ? 'Your local suite' : 'Your hybrid suite';
6225
+ var a = phys().answers, works = Array.isArray(a.existing_work) ? a.existing_work : [];
6226
+ $('physExisting').hidden = a.project_state !== 'existing';
6227
+ root.querySelectorAll('#physExisting [data-work]').forEach(function (cb) { cb.checked = works.indexOf(cb.getAttribute('data-work')) !== -1; });
6228
+ root.querySelectorAll('#physExisting input[name="physMain"]').forEach(function (r) {
6229
+ r.checked = a.main_work === r.value;
6230
+ r.disabled = works.indexOf(r.value) === -1;
6231
+ });
6232
+ physPaintPills();
6233
+ physPaintTeam();
6234
+ physApplyLimits();
6235
+ ROWS.forEach(function (key) {
6236
+ var row = rowEl(key), t = rowText(key);
6237
+ row.hidden = !rowApplies(key);
6238
+ row.classList.toggle('done', !!c[key]);
6239
+ row.querySelector('[data-row-v]').textContent = t.v;
6240
+ row.querySelector('[data-row-s]').textContent = t.s;
6241
+ var ok = row.querySelector('[data-row-ok]');
6242
+ ok.textContent = c[key] ? 'Confirmed' : 'Looks right';
6243
+ ok.disabled = !!c[key];
6244
+ row.querySelector('[data-row-chg]').hidden = rowFixed(key);
6245
+ });
6246
+ var left = unconfirmedRows().length;
6247
+ var go = $('reviewGo');
6248
+ go.disabled = left > 0;
6249
+ go.textContent = left ? 'Confirm ' + left + ' more' : 'Continue';
6250
+ }
6251
+ /* screen 5's one line: what Create is about to make */
6252
+ function renderRecap() {
6721
6253
  var cb = $('doCreate');
6254
+ /* every road back to this screen gets a live button — the create path disables
6255
+ it and only the failure branch used to restore it */
6722
6256
  cb.disabled = false; cb.textContent = 'Create my project';
6723
- var rows = [
6724
- ['Project', esc(state.name)],
6725
- ['Type', typeLabel(state.projType) || 'Not declared'],
6726
- /* the About step is optional, so its row states the CONSEQUENCE of the
6727
- skip rather than a bare em-dash — the review is the last screen before
6728
- Create, and "off the map" should never be a surprise (task 1002334) */
6729
- ['About', String(state.description || '').trim()
6730
- ? esc(String(state.description).trim())
6731
- : '<em style="font-weight:600;color:var(--warn)">not said — off the public map until you add it</em>'],
6732
- ['Who’s building it', teamLabel(state.teamShape) || 'Not said'],
6733
- /* the picker is optional too, so this row shows what the project will
6734
- ACTUALLY come with — the owner's picks, or the preselect they walked
6735
- past — never a blank that a Create is about to turn into a surprise */
6736
- ['Extras', extrasRow()],
6737
- ['Starting from', state.mode === 'adopt' ? 'A repo you already have' : 'A fresh, empty repo'],
6738
- ['Repository', '<code>' + esc(state.owner + '/' + state.repo) + '</code>'],
6739
- ['Handle', '<code>' + esc(state.slug) + '</code>'],
6740
- ['Address', resolvedDomain()
6741
- ? '<code>' + esc(resolvedDomain()) + '</code>'
6742
- : '<em style="font-weight:600;color:var(--warn)">none — sign-in stays locked until it has one</em>'],
6743
- /* The wizard creates hosting_shape 'cloud-host', which since ADR 0345 means the
6744
- HALL is hosted by us; where the owner's APP runs is its own row (task 1004351). */
6745
- ['Builders\' hall', 'Hosted by us, on the platform’s current release'],
6746
- ['App', appRow()],
6747
- ['Look', lookRow()]
6748
- ];
6749
- /* the demo route (task 1004420): the review says it is a demo, and what that means */
6750
- if (state.demo) {
6751
- rows.unshift(['Demo', esc(state.demo.people + (state.demo.people === 1 ? ' person' : ' people') + ', ' + state.demo.hours_each + ' hours each.') +
6752
- ' Private to your team, no rewards, all managed. When the time box runs out you are asked whether to keep your planet.']);
6753
- }
6754
- $('reviewList').innerHTML = rows.map(function (r) {
6755
- return '<li><span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span></li>';
6756
- }).join('');
6257
+ var bits = [state.name || state.slug, typeLabel(state.projType) || '', state.mode === 'adopt' ? 'your repo' : 'new repo', resolvedDomain() || 'no address yet'];
6258
+ if (state.demo) bits.push('demo');
6259
+ $('wizRecap').textContent = bits.filter(Boolean).join(' · ');
6757
6260
  }
6758
6261
 
6759
6262
  /* ── create ── */
@@ -6833,6 +6336,8 @@ summary{min-height:24px;padding:3px 0;}
6833
6336
  box) — send the founder back to it rather than file an unacknowledged
6834
6337
  project (task 1004417) */
6835
6338
  if (!state.termsAck) { show(1, true); validateStep(1); return; }
6339
+ /* nothing is created before every row of the review is confirmed */
6340
+ if (unconfirmedRows().length) { show(4, true); validateStep(4); return; }
6836
6341
  btn.disabled = true; btn.textContent = 'Raising it into the sky…';
6837
6342
  ensureFreshRepo().then(function () {
6838
6343
  return fetch(API + '/provisioning/instances', {
@@ -6899,7 +6404,7 @@ summary{min-height:24px;padding:3px 0;}
6899
6404
  sessionStorage.removeItem(DRAFT_KEY);
6900
6405
  } catch (e) {}
6901
6406
  renderDone();
6902
- show(13, true);
6407
+ show(6, true);
6903
6408
  sendRenderSetup(data.instance && data.instance.id); // the key rides the new project's own setup run (task 1004352)
6904
6409
  sendLookSetup(data.instance && data.instance.id); // the look + logo, like the Render key: right after Create (task 1004421)
6905
6410
  startProgress(data.instance && data.instance.id);
@@ -6909,8 +6414,11 @@ summary{min-height:24px;padding:3px 0;}
6909
6414
  if (err && err.gotoHome) {
6910
6415
  /* A repo-creation problem (name taken / reconnect) — bounce back to the Home
6911
6416
  step with the reason, so the user fixes it where the repo is chosen. */
6912
- show(8, true);
6913
- $('h-home').textContent = err.homeMsg || 'Please revisit the repository step.';
6417
+ show(4, true);
6418
+ delete confirmedRows().code;
6419
+ openRow('code');
6420
+ paintSetup();
6421
+ $('h-home').textContent = err.homeMsg || 'Please check the repository again.';
6914
6422
  $('h-home').className = 'hint err';
6915
6423
  return;
6916
6424
  }
@@ -7134,9 +6642,10 @@ summary{min-height:24px;padding:3px 0;}
7134
6642
  var adopt = state.mode === 'adopt';
7135
6643
  $('doneRender').hidden = true; // sendRenderSetup shows it again when Render is the choice
7136
6644
  $('doneApp').hidden = true; $('doneApp').textContent = '';
7137
- $('doneLede').innerHTML = 'Provisioning is requested for <strong>' + esc(state.name || state.slug) + '</strong>' +
7138
- (adopt ? ' — adopting your existing repo' : '') +
7139
- '. The platform stands up the server, database, and address on its own — watch it right here, or any time on <a href="?view=mine">your projects page</a>.';
6645
+ $('done-h').textContent = 'Setting up ' + (state.name || state.slug || 'your project');
6646
+ $('doneLede').innerHTML = 'We’re setting up <strong>' + esc(state.name || state.slug) + '</strong>' +
6647
+ (adopt ? ' on your existing repo' : '') +
6648
+ ': its server, database and address. Watch it here, or any time on <a href="?view=mine">your projects page</a>.';
7140
6649
  /* the picker's later half (task 1002342, BV1.R23). The modules step is
7141
6650
  skippable, so this panel names where it lives afterwards rather than
7142
6651
  leaving an owner to go looking for it. */
@@ -7401,6 +6910,8 @@ summary{min-height:24px;padding:3px 0;}
7401
6910
  return false;
7402
6911
  }
7403
6912
  if (inst.last_verified_up) {
6913
+ /* the alive moment (task 1004504): said only once the project has answered */
6914
+ $('done-h').textContent = (state.name || state.slug || 'Your project') + ' is alive.';
7404
6915
  setProgress(progressMarkup(-1, 3,
7405
6916
  'Your project is answering at ' + link + '. Finish the steps below and the hall is yours.', 'isLive', flow));
7406
6917
  return true;
@@ -7485,7 +6996,6 @@ summary{min-height:24px;padding:3px 0;}
7485
6996
  5s tick never clobbers an open dropdown or mid-flight button) */
7486
6997
  if (inst && (inst.status !== live.instStatus || addressReady(inst) !== live.addrReady)) {
7487
6998
  live.instStatus = inst.status; live.addrReady = addressReady(inst); renderSteps();
7488
- if (state.step === 13) paintRail(13); /* the rail finishes only when the project answers */
7489
6999
  }
7490
7000
  var terminal = renderProgress(inst);
7491
7001
  if (!terminal) pollTimer = setTimeout(tick, 5000);
@@ -7525,9 +7035,10 @@ summary{min-height:24px;padding:3px 0;}
7525
7035
  is three-valued and null is its "nothing said" sentinel; leaving the key out
7526
7036
  left it UNDEFINED, which is not null — so pickedModules() reached .slice()
7527
7037
  on it and the second project's Modules step threw. */
7528
- state = { step: 1, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', physics: null, demo: null, look: null, termsAck: false };
7038
+ state = { step: 1, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', physics: null, demo: null, look: null, termsAck: false, confirmed: {} };
7529
7039
  $('newName').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
7530
7040
  $('f-noaddr').checked = false; $('f-desc').value = '';
7041
+ $('termsAck').checked = false; paintDoors(); demoShow('intro'); checksShow('main');
7531
7042
  /* Re-seed the owner from the signed-in account for the next project. */
7532
7043
  if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
7533
7044
  $('h-name').innerHTML = '&nbsp;';
@@ -7594,17 +7105,17 @@ summary{min-height:24px;padding:3px 0;}
7594
7105
  another project" the only way out.
7595
7106
 
7596
7107
  The reset fires only when there is a SECOND DRAFT to lose, and the draft's own
7597
- step is what says so. A landed create clears DRAFT_KEY — but show(9) then
7108
+ step is what says so. A landed create clears DRAFT_KEY — but show(6) then
7598
7109
  persists the done panel's own position straight back into it, so "is there a
7599
7110
  draft?" is always yes and would have skipped the reset forever, leaving the
7600
- finished project's name sitting in the fresh form. Only steps 1–10 are wizard
7111
+ finished project’s name sitting in the fresh form. Only steps 1–5 are wizard
7601
7112
  steps: anything else is the done panel's bookkeeping, not a project someone
7602
7113
  is part-way through typing. */
7603
7114
  function enterCreated(created, focus) {
7604
7115
  if (focus && !resumeIntent) {
7605
7116
  var draft = null;
7606
7117
  try { draft = JSON.parse(sessionStorage.getItem(DRAFT_KEY) || 'null'); } catch (e) {}
7607
- var at = draft && draft.step >= 1 && draft.step <= 12 ? draft.step : 0;
7118
+ var at = draft && draft.step >= 1 && draft.step <= 5 ? draft.step : 0;
7608
7119
  if (!at) { resetForNewProject(); at = 1; }
7609
7120
  paintResumeOffer(created);
7610
7121
  /* step 1's own focus target is its one control, the terms tick, not the
@@ -7628,7 +7139,7 @@ summary{min-height:24px;padding:3px 0;}
7628
7139
  manifestState = created.manifest_state; manifestPending = true;
7629
7140
  }
7630
7141
  renderDone();
7631
- show(13, !!focus);
7142
+ show(6, !!focus);
7632
7143
  startProgress(created.id);
7633
7144
  resumePolls();
7634
7145
  }
@@ -7642,10 +7153,10 @@ summary{min-height:24px;padding:3px 0;}
7642
7153
  var created = authKnown ? createdRecord() : null;
7643
7154
  if (created && created.id) { enterCreated(created, !!focus); return; }
7644
7155
  paintResumeOffer(null);
7645
- /* before identity settles, never render past step 1 (the terms, since task
7646
- 1004417): deeper steps carry authenticated side effects (the Home-step repo
7156
+ /* before identity settles, never render past step 1 (the fork and its terms
7157
+ tick, since task 1004504): deeper steps carry authenticated side effects (the Home-step repo
7647
7158
  probe), and onAuth() re-enters to the real draft step the moment /me answers */
7648
- var step = authKnown && state.step >= 1 && state.step <= 12 ? state.step : 1;
7159
+ var step = authKnown && state.step >= 1 && state.step <= 5 ? state.step : 1;
7649
7160
  if (step === 1) {
7650
7161
  /* Pre-auth, step 1 is a PLACEHOLDER, not where the visitor is — but show()
7651
7162
  persists whatever step it renders, so it used to overwrite the draft's real
@@ -7654,7 +7165,7 @@ summary{min-height:24px;padding:3px 0;}
7654
7165
  step back after rendering the placeholder, in memory AND in the draft. */
7655
7166
  var resumeStep = state.step;
7656
7167
  show(1, false);
7657
- if (!authKnown && resumeStep >= 1 && resumeStep <= 12) { state.step = resumeStep; saveDraft(); }
7168
+ if (!authKnown && resumeStep >= 1 && resumeStep <= 5) { state.step = resumeStep; saveDraft(); }
7658
7169
  if (focus) $('termsAck').focus();
7659
7170
  } else {
7660
7171
  show(step, !!focus);