@bongos/core 1.21.91 → 1.21.93

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 (55) hide show
  1. package/.bongos-core.json +72 -62
  2. package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +1 -0
  3. package/docs/copy-inventory.md +357 -367
  4. package/docs/copy-registry.json +559 -641
  5. package/docs/module-api-changelog.md +9 -0
  6. package/docs/page-inventory.json +15 -18
  7. package/docs/page-readings.json +1235 -918
  8. package/modules/autonomy/routes/autonomy.js +14 -5
  9. package/modules/government/decision-board.js +13 -3
  10. package/modules/government/decision-rules.js +61 -13
  11. package/modules/lifecycle/routes/done-when.js +2 -2
  12. package/modules/lifecycle/routes/gate-approvals.js +1 -1
  13. package/modules/lifecycle/routes/goals.js +4 -4
  14. package/modules/lifecycle/routes/project-settings.js +2 -2
  15. package/modules/lifecycle/routes/tasks.js +1 -1
  16. package/modules/lifecycle/routes/versions.js +1 -1
  17. package/modules/npm-release/routes/release.js +1 -0
  18. package/modules/onboarding/routes/access-requests.js +3 -3
  19. package/modules/provisioning/routes/core-upgrade.js +11 -0
  20. package/modules/public-landing/public/projects-hall-live.states.json +6 -35
  21. package/modules/public-landing/public/projects.html +1019 -983
  22. package/modules/public-landing/public/projects.probes.json +15 -18
  23. package/modules/public-landing/public/projects.states.json +22 -25
  24. package/modules/render-deploy/routes/act.js +1 -1
  25. package/modules/security/routes/reports.js +3 -0
  26. package/package-lock.json +2 -2
  27. package/package.json +1 -1
  28. package/release-notes.json +12 -0
  29. package/scripts/gds/fitness.js +7 -0
  30. package/src/bongos/act-alone.js +84 -0
  31. package/src/bongos/routes/builders.js +6 -1
  32. package/src/module-api.js +7 -1
  33. package/tests/autonomy_fence_priority.mjs +3 -1
  34. package/tests/government_act_alone_buildings.mjs +430 -0
  35. package/tests/module_api.mjs +1 -0
  36. package/tests/npm_release_release.mjs +2 -1
  37. package/tests/onboard.mjs +8 -8
  38. package/tests/project_invite_ui.mjs +6 -5
  39. package/tests/projects_hub.mjs +77 -98
  40. package/tests/projects_hub_app_status.mjs +3 -3
  41. package/tests/projects_hub_app_step.mjs +8 -8
  42. package/tests/projects_hub_cloud_hop.mjs +29 -53
  43. package/tests/projects_hub_look.mjs +29 -39
  44. package/tests/projects_hub_module_picker.mjs +32 -39
  45. package/tests/projects_hub_pre_uat.mjs +11 -14
  46. package/tests/ship_mode.mjs +2 -1
  47. package/tests/wizard_demo.mjs +30 -25
  48. package/tests/wizard_draft_resume.mjs +24 -32
  49. package/tests/wizard_front_door.mjs +91 -243
  50. package/tests/wizard_intent_resume.mjs +28 -48
  51. package/tests/wizard_one_page.mjs +201 -0
  52. package/tests/wizard_physics.mjs +46 -41
  53. package/tests/wizard_physics_more.mjs +71 -106
  54. package/tests/wizard_ship_mode.mjs +1 -1
  55. package/tests/wizard_six_screens.mjs +0 -129
@@ -91,19 +91,13 @@
91
91
  /* show() moves focus to each panel's h1 for keyboard/SR context — keep the
92
92
  focus, lose the ring (a black rectangle around a heading; owner walk) */
93
93
  h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
94
- /* the one tick: a hairline row, the box in the accent, the whole row the target */
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);}
96
- .termsAck input{width:20px;height:20px;margin:2px 0 0;flex:none;accent-color:var(--accent);}
97
- .termsAck span{font-size:16px;line-height:1.5;color:var(--ink);}
98
- .termsRead{display:inline-block;min-height:24px;line-height:24px;color:var(--ink);text-decoration:underline;text-underline-offset:3px;}
99
- /* the door pill (DESIGN.md): two equal doors joined into one object — one grid
100
- of two equal tracks inside one hairline pill; the demo door is transparent at
101
- rest, the accent only on the one that goes on */
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);}
103
- #view-new .doorPill .btn.ghost{border-color:transparent;}
104
- #view-new .doorPill .btn.ghost:hover{border-color:var(--rule);}
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 */
94
+ /* the one tick above Create: the box in the accent, the whole row the target */
95
+ .termsAck{display:flex;gap:12px;align-items:flex-start;cursor:pointer;max-width:62ch;margin:28px 0 0;padding:0;}
96
+ .termsAck input{width:18px;height:18px;margin:3px 0 0;flex:none;accent-color:var(--accent);}
97
+ .termsAck span{font-size:14px;line-height:1.5;color:var(--ink-soft);}
98
+ .termsRead{display:inline-block;min-height:24px;line-height:21px;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
99
+ /* the demo route (task 1004420): opened in place at the top of part 01 — steppers on
100
+ the wizard's field grammar, the fit check as one quiet line, two teammates */
107
101
  .demoSteppers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 24px;}
108
102
  .demoStep{display:flex;align-items:center;gap:12px;margin-top:6px;}
109
103
  .demoStep output{min-width:2ch;text-align:center;font-family:var(--font-display);font-weight:300;font-size:28px;color:var(--ink);}
@@ -183,6 +177,7 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
183
177
  --rm-mug:color-mix(in srgb,var(--sea) 11%,#000);
184
178
  --rm-leaf-1:color-mix(in srgb,var(--sea) 20%,#000);
185
179
  --rm-leaf-2:color-mix(in srgb,var(--sea) 26%,#000);
180
+ --rm-pane:color-mix(in srgb,var(--accent) 92%,#fff);
186
181
  --seed:.45;
187
182
  position:fixed;left:0;right:0;top:var(--top-h,64px);bottom:0;z-index:0;overflow:hidden;pointer-events:none;
188
183
  background:var(--rm-room);
@@ -221,13 +216,79 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
221
216
  /* the seed: the world's chrome blob, hanging in the window's sky */
222
217
  #view-new .rmSeed{left:120px;top:120px;width:120px;height:120px;transform:scale(var(--seed));transform-origin:50% 50%;}
223
218
  #view-new .rmSeed img{display:block;width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 0 22px var(--rm-glow));}
219
+ /* the seed grows as the page fills in (paintFill: a name, a kind, the checks, the tick) */
224
220
  #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;}
221
+ #view-new .wizRoom[data-screen="page"][data-fill="1"]{--seed:.55;}
222
+ #view-new .wizRoom[data-screen="page"][data-fill="2"]{--seed:.65;}
223
+ #view-new .wizRoom[data-screen="page"][data-fill="3"]{--seed:.75;}
224
+ #view-new .wizRoom[data-screen="page"][data-fill="4"]{--seed:.85;}
230
225
  #view-new .wizRoom[data-screen="done"]{--seed:1.1;}
226
+ /* the clouds and the patch of sun belong to the day room; at night they are not there */
227
+ #view-new .rmCloud{background:var(--rm-star);border-radius:40px;opacity:0;}
228
+ #view-new .rmCloud::before,#view-new .rmCloud::after{content:"";position:absolute;background:inherit;border-radius:50%;}
229
+ #view-new .rmC1{left:40px;top:70px;width:110px;height:26px;}
230
+ #view-new .rmC1::before{left:22px;top:-16px;width:44px;height:38px;}
231
+ #view-new .rmC1::after{left:54px;top:-10px;width:34px;height:30px;}
232
+ #view-new .rmC2{left:230px;top:140px;width:80px;height:20px;}
233
+ #view-new .rmC2::before{left:16px;top:-12px;width:32px;height:28px;}
234
+ #view-new .rmC2::after{left:40px;top:-8px;width:26px;height:22px;}
235
+ #view-new .rmSun{left:calc(50% + 120px);top:672px;width:520px;height:120px;opacity:0;background:var(--rm-glow);transform:skewX(-28deg);filter:blur(10px);}
236
+ #view-new .rmLight{background:var(--rm-pane);}
237
+ /* ── the day room (task 1004648; the mockup's [data-sky="day"]): the same drawing in
238
+ daylight — a sunny sky with clouds, a sun for the moon, lighter towers with unlit
239
+ windows, a warm wall and floor, the lamp off and a patch of sun on the floor. Every
240
+ colour is still a mix of the fifteen with black or white. The words' own tokens are
241
+ re-pointed for a light ground on the view's children (never :root): the status inks
242
+ are read off the page's own through --wz-*, declared one level up so no token ever
243
+ names itself. ── */
244
+ #view-new{--wz-ok:var(--ok);--wz-err:var(--err);--wz-warn:var(--warn);}
245
+ #view-new[data-sky="day"]>*{
246
+ --ink:color-mix(in srgb,#fff 10%,#000);
247
+ --ink-soft:color-mix(in srgb,#fff 24%,#000);
248
+ --ink-faint:color-mix(in srgb,#fff 36%,#000);
249
+ --rule:rgba(0,0,0,.14);
250
+ --rule-soft:rgba(0,0,0,.08);
251
+ --line:rgba(0,0,0,.1);
252
+ --ctl-line:rgba(0,0,0,.45);
253
+ --bg:color-mix(in srgb,var(--accent) 6%,#fff);
254
+ --bg-card:#fff;
255
+ --card:color-mix(in srgb,var(--accent) 4%,#fff);
256
+ --glass:rgba(255,255,255,.52);
257
+ --glass-strong:rgba(255,255,255,.75);
258
+ --glass-edge:rgba(0,0,0,.16);
259
+ --accent-deep:color-mix(in srgb,var(--terracotta) 78%,#000);
260
+ --link-ink:color-mix(in srgb,var(--terracotta) 78%,#000);
261
+ --ok:color-mix(in srgb,var(--wz-ok) 40%,#000);
262
+ --err:color-mix(in srgb,var(--wz-err) 55%,#000);
263
+ --warn:color-mix(in srgb,var(--wz-warn) 45%,#000);
264
+ --focus:color-mix(in srgb,var(--terracotta) 78%,#000);
265
+ color-scheme:light;
266
+ }
267
+ #view-new[data-sky="day"] .wizRoom{
268
+ --rm-room:color-mix(in srgb,var(--accent) 9%,color-mix(in srgb,#fff 92%,#000));
269
+ --rm-frame:color-mix(in srgb,var(--terracotta) 14%,color-mix(in srgb,#fff 84%,#000));
270
+ --rm-night:color-mix(in srgb,var(--sea) 30%,#fff);
271
+ --rm-moon:var(--gold);
272
+ --rm-halo:color-mix(in srgb,var(--gold) 50%,transparent);
273
+ --rm-star:#fff;
274
+ --rm-tower-1:color-mix(in srgb,var(--sea) 28%,color-mix(in srgb,#fff 70%,#000));
275
+ --rm-tower-2:color-mix(in srgb,var(--sea) 30%,color-mix(in srgb,#fff 64%,#000));
276
+ --rm-tower-3:color-mix(in srgb,var(--sea) 26%,color-mix(in srgb,#fff 76%,#000));
277
+ --rm-pane:color-mix(in srgb,var(--sea) 8%,#fff);
278
+ --rm-floor:color-mix(in srgb,var(--terracotta) 10%,color-mix(in srgb,#fff 88%,#000));
279
+ --rm-edge:rgba(0,0,0,.08);
280
+ --rm-glow:color-mix(in srgb,var(--gold) 55%,transparent);
281
+ --rm-stem:color-mix(in srgb,var(--terracotta) 12%,color-mix(in srgb,#fff 76%,#000));
282
+ --rm-shade:color-mix(in srgb,var(--terracotta) 12%,color-mix(in srgb,#fff 82%,#000));
283
+ --rm-mug:color-mix(in srgb,var(--terracotta) 12%,color-mix(in srgb,#fff 78%,#000));
284
+ --rm-leaf-1:color-mix(in srgb,var(--wz-ok) 32%,color-mix(in srgb,#fff 46%,#000));
285
+ --rm-leaf-2:color-mix(in srgb,var(--wz-ok) 36%,color-mix(in srgb,#fff 40%,#000));
286
+ }
287
+ #view-new[data-sky="day"] .rmMoon{left:380px;top:40px;width:70px;height:70px;box-shadow:0 0 80px 26px var(--rm-halo);}
288
+ #view-new[data-sky="day"] .rmStar,#view-new[data-sky="day"] .rmGlow{opacity:0;}
289
+ #view-new[data-sky="day"] .rmCloud{opacity:.9;}
290
+ #view-new[data-sky="day"] .rmSun{opacity:.7;}
291
+ #view-new[data-sky="day"] .rmGrain{opacity:.04;}
231
292
  /* soft film grain over the room — the lofi texture, no colour of its own */
232
293
  #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>");}
233
294
  @media (prefers-reduced-motion:no-preference){
@@ -239,6 +300,11 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
239
300
  #view-new .rmS5{animation-delay:-5s;}
240
301
  #view-new .rmGlow{animation:rmBreathe 9s ease-in-out infinite;}
241
302
  #view-new .rmSeed img{animation:rmFloat 8s ease-in-out infinite;}
303
+ #view-new .rmCloud{animation:rmDrift 60s linear infinite;}
304
+ #view-new .rmC2{animation-delay:-25s;}
305
+ #view-new .wizRoom,#view-new .wizRoom>*,#view-new .rmWindow>*{transition:background .8s ease,opacity .8s ease,box-shadow .8s ease;}
306
+ #view-new[data-sky="day"] .rmStar,#view-new[data-sky="day"] .rmGlow{animation:none;}
307
+ @keyframes rmDrift{from{transform:translateX(-30px)}to{transform:translateX(60px)}}
242
308
  @keyframes rmTwinkle{0%,100%{opacity:.9}50%{opacity:.35}}
243
309
  @keyframes rmBreathe{0%,100%{opacity:1}50%{opacity:.7}}
244
310
  @keyframes rmFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
@@ -246,7 +312,7 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
246
312
  /* under 900 the window steps back and dims behind the words; the lamp, mug and plant go */
247
313
  @media (max-width:900px){
248
314
  #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;}
315
+ #view-new .rmGlow,#view-new .rmSun,#view-new .rmStem,#view-new .rmShade,#view-new .rmMug,#view-new .rmLeaf{display:none;}
250
316
  #view-new .rmFloor{top:auto;height:80px;}
251
317
  }
252
318
  /* under 600 the words fill the screen, so the window fades further and its lit windows
@@ -258,65 +324,114 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
258
324
  /* the words column above the room, held to the mockup's measure so the window never
259
325
  slides under it; headings in the mockup's sentence case */
260
326
  #view-new .wrap{position:relative;z-index:1;min-height:calc(100vh - var(--top-h,64px) - 120px);}
327
+ /* the footer stands on its own ground above the fixed room, so its links keep their ink
328
+ whichever room is behind the page (task 1004648) */
329
+ body:has(#view-new:not([hidden])) footer.foot{position:relative;z-index:1;background:var(--bg);}
261
330
  #view-new .panel{max-width:600px;}
331
+ #view-new #panel-page{max-width:560px;}
262
332
  .wizTop{display:flex;align-items:center;gap:16px;min-height:44px;margin:0 0 28px;max-width:600px;}
263
333
  .wizEyebrow{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
264
- .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;}
265
- .wizBack:hover{color:var(--ink);}
266
- .wizBack[hidden]{display:none;}
267
- .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;}
334
+ .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-deep);border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);vertical-align:middle;}
268
335
  .demoTag[hidden]{display:none;}
269
- .fillOffer{margin:0 0 12px;}
270
- .wizRecap{margin:22px 0 0;font-size:15px;line-height:1.5;color:var(--ink-soft);}
271
- #view-new .doorPill .btn[disabled]{opacity:.35;cursor:default;}
272
- /* the review: one hairline ledger of rows (task 1004550). Each row is its label, its
273
- value with one plain line under it, and one action — Edit, which reads Done while the
274
- row is open over the old step's own controls. One "Looks right, continue" under the
275
- ledger confirms them all. Inside an open row the choices are compact toggles, not the
276
- full-width hairline cards the question screens use. */
277
- .setRows{margin:24px 0 0;border-top:1px solid var(--rule);}
278
- .setRow{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:0 16px;align-items:start;padding:14px 0;border-bottom:1px solid var(--rule);}
336
+ .fillOffer{margin:14px 0 0;}
337
+ /* ── the one page (task 1004648; the design of record docs/design/mocks/day-night-rooms/):
338
+ the heading, the demo as a small link under it, then three parts as glass cards in
339
+ one column — 01 your project, 02 how it's run, 03 Connect GitHub — with the terms
340
+ tick and Create between the second and the third. ── */
341
+ .pageSub{margin:14px 0 0;font-size:18px;line-height:1.5;font-weight:300;color:var(--ink-soft);max-width:62ch;}
342
+ .pageSub[hidden]{display:none;}
343
+ #view-new .pageSub .linklike{display:inline;min-height:24px;padding:0;font:inherit;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
344
+ #view-new .pageSub .linklike:hover{color:var(--ink);}
345
+ .wizParts{margin-top:20px;}
346
+ .wizParts[hidden]{display:none;}
347
+ .part{margin-top:28px;padding:24px 26px;border-radius:22px;background:var(--glass);border:1px solid var(--glass-edge);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);}
348
+ .part h2{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 12px;margin:0;font-family:var(--font-display);font-size:22px;font-weight:300;letter-spacing:-.01em;line-height:1.3;text-transform:none;color:var(--ink);}
349
+ .part h2 .n{font-family:var(--font-mono);font-size:11px;letter-spacing:.1em;color:var(--ink-faint);}
350
+ .part h2 .state{margin-left:auto;font-family:var(--font-body);font-size:13px;font-weight:400;letter-spacing:0;color:var(--ink-faint);}
351
+ .part h2[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
352
+ .part .wizField:first-of-type{margin-top:20px;}
353
+ .part.waiting h2{color:var(--ink-soft);}
354
+ .part.live{border-color:color-mix(in srgb,var(--accent) 55%,transparent);}
355
+ .partSub{margin:12px 0 0;font-size:15px;line-height:1.5;font-weight:300;color:var(--ink-soft);max-width:62ch;}
356
+ .partH3{margin:0 0 6px;font-family:var(--font-display);font-size:20px;font-weight:300;letter-spacing:-.01em;color:var(--ink);}
357
+ .partH3:focus:not(:focus-visible){outline:none;}
358
+ .partH3 em{font-style:normal;}
359
+ .demoView{margin:18px 0 6px;padding:0 0 14px;border-bottom:1px solid var(--rule);}
360
+ .demoLede{margin:0 0 14px;}
361
+ .demoActs{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px;}
362
+ .checksHead{margin:26px 0 16px;padding-top:18px;border-top:1px solid var(--rule);font-size:13px;line-height:1.5;color:var(--ink-soft);}
363
+ .checksStop{margin-top:22px;padding:16px;border-radius:14px;background:color-mix(in srgb,var(--err) 10%,transparent);border:1px solid var(--err);}
364
+ .checksStop p{margin:0;font-size:14.5px;line-height:1.5;color:var(--ink);}
365
+ /* the page's own fields: the mockup's glass boxes, a 48px row, the boundary held to
366
+ 3:1 on its ground in both rooms */
367
+ #view-new .part input[type=text],#view-new .part input[type=password],#view-new .part select{
368
+ width:100%;min-height:48px;padding:0 16px;border-radius:14px;border:1px solid color-mix(in srgb,var(--ink) 40%,transparent);
369
+ background:var(--glass-strong);color:var(--ink);font-family:var(--font-body);font-size:16px;outline:none;}
370
+ #view-new .part input[type=text]:focus,#view-new .part input[type=password]:focus,#view-new .part select:focus{border-color:color-mix(in srgb,var(--accent) 70%,var(--ink));}
371
+ #view-new .part select{appearance:none;cursor:pointer;}
372
+ #view-new .part select option{background:var(--bg-card);color:var(--ink);}
373
+ #view-new .part .wizField>span{margin-bottom:8px;font-family:var(--font-body);font-size:13px;letter-spacing:0;text-transform:none;color:var(--ink-soft);}
374
+ #view-new .part .wizField small{font-size:12.5px;color:var(--ink-faint);}
375
+ #view-new .part .wizField{margin:20px 0 0;}
376
+ #view-new .part .physRow2 .wizField{margin-top:0;}
377
+ /* the chips: every short choice on the page is a row of glass pills, the chosen one in
378
+ the accent's soft fill (the mockup's .chip) */
379
+ #view-new .part .typeCards:not(#modCards):not(.physSuiteGrid),
380
+ #view-new .part .choices{display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;border:0;}
381
+ #view-new .part .typeCards:not(#modCards):not(.physSuiteGrid) .tpill,
382
+ #view-new .part .choices .choice{display:inline-flex;align-items:center;min-height:38px;padding:0 15px;border:0;border-radius:999px;cursor:pointer;
383
+ font-family:var(--font-body);font-size:14px;font-weight:400;color:var(--ink-soft);background:var(--glass-strong);border:1px solid var(--glass-edge);}
384
+ #view-new .part .typeCards:not(#modCards):not(.physSuiteGrid) .tpill::before,
385
+ #view-new .part .choices .choice b::before{display:none;}
386
+ #view-new .part .choices .choice b{margin:0;font-size:14px;font-weight:400;color:inherit;}
387
+ #view-new .part .choices .choice p{display:none;}
388
+ #view-new .part .typeCards:not(#modCards):not(.physSuiteGrid) .tpill[aria-pressed=true],
389
+ #view-new .part .choices .choice[aria-pressed=true]{background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--ink);border:1px solid color-mix(in srgb,var(--accent) 60%,transparent);}
390
+ #view-new .part .typeCards .tpill[disabled]{opacity:.45;cursor:default;}
391
+ #view-new .part .typeCards .tpill[hidden]{display:none;}
392
+ /* Create, with the recap beside it, and the reason it waits under it */
393
+ .createBar{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-top:24px;}
394
+ #view-new .createBar #doCreate{min-height:52px;padding:0 28px;}
395
+ #view-new .createBar #doCreate:disabled{background:color-mix(in srgb,var(--accent) 40%,transparent);border:0;color:var(--ink);cursor:default;}
396
+ .wizRecap{margin:0;font-size:14px;line-height:1.5;color:var(--ink-soft);overflow-wrap:anywhere;}
397
+ .createWhy{margin-top:8px;}
398
+ /* part 02: one hairline ledger of rows. Each row is its label, its value with one plain
399
+ line under it, and Change, which reads Done while the row is open over the control
400
+ it always was. Inside an open row the choices are the same chips. */
401
+ .setRows{margin:14px 0 0;border-top:1px solid var(--rule);}
402
+ .setRow{display:grid;grid-template-columns:108px minmax(0,1fr) auto;gap:0 14px;align-items:start;padding:13px 0;border-bottom:1px solid var(--rule);}
403
+ .setRow:last-child{border-bottom:0;}
279
404
  .setRow[hidden]{display:none;}
280
- .setRow .k{font-size:13px;line-height:22px;color:var(--ink-faint);padding-top:2px;}
405
+ .setRow .k{font-size:13px;line-height:22px;color:var(--ink-faint);}
281
406
  .setRow .v{min-width:0;}
282
- .setRow .rowV{display:block;font-size:15.5px;line-height:1.45;color:var(--ink);overflow-wrap:anywhere;}
283
- .setRow .rowS{display:block;margin-top:3px;font-size:13px;line-height:1.45;color:var(--ink-faint);overflow-wrap:anywhere;}
407
+ .setRow .rowV{display:block;font-size:15px;line-height:1.45;color:var(--ink);overflow-wrap:anywhere;}
408
+ .setRow .rowS{display:block;margin-top:2px;font-size:12.5px;line-height:1.45;color:var(--ink-faint);overflow-wrap:anywhere;}
284
409
  .setRow .rowS:empty{display:none;}
285
410
  .setRow .acts{display:flex;gap:6px;flex:none;}
286
- .setRow .acts button{min-height:34px;padding:0 14px;border-radius:999px;font:inherit;font-size:13px;white-space:nowrap;cursor:pointer;}
287
- .setRow [data-row-chg]{border:1px solid var(--glass-edge);background:var(--glass);color:var(--ink-soft);}
411
+ .setRow .acts button{min-height:32px;padding:0 4px;border:0;background:none;font:inherit;font-size:13px;white-space:nowrap;cursor:pointer;color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
288
412
  .setRow [data-row-chg]:hover{color:var(--ink);}
289
413
  .setRow [data-row-chg][hidden]{display:none;}
290
- .setRow.open [data-row-chg]{border-color:color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink);}
414
+ .swDot{display:inline-block;box-sizing:border-box;width:14px;height:14px;margin-right:8px;border-radius:50%;vertical-align:-2px;border:1px solid var(--glass-edge);}
291
415
  .setRow .opts{margin-top:12px;}
292
416
  .setRow .opts .wizField{margin-bottom:12px;}
293
417
  .setRow .opts .hint:empty{display:none;}
294
- /* compact toggles: the repo choice and every short pill group in an open row */
295
- .setRow .opts .choices,
296
- .setRow .opts .typeCards.physCells:not(.physSuiteGrid),
297
- .setRow .opts #teamCards{display:inline-flex;flex-wrap:wrap;gap:0;border:1px solid var(--rule);border-radius:999px;padding:3px;margin:0 8px 12px 0;vertical-align:top;}
298
- .setRow .opts .choices .choice,
299
- .setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill,
300
- .setRow .opts #teamCards .tpill{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 14px;border:0;border-radius:999px;font-size:14px;font-weight:500;color:var(--ink-soft);background:none;}
301
- .setRow .opts .choices .choice b{margin:0;font-size:14px;color:inherit;}
302
- .setRow .opts .choices .choice b::before,
303
- .setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill::before,
304
- .setRow .opts #teamCards .tpill::before{display:none;}
305
- .setRow .opts .choices .choice p{display:none;}
418
+ /* inside an open row the chip groups sit one under another */
419
+ #view-new .setRow .opts .choices,
420
+ #view-new .setRow .opts .typeCards:not(#modCards):not(.physSuiteGrid){margin:0 0 12px;}
306
421
  .setRow .opts .reassure{font-size:13px;margin:0 0 12px;}
307
- .setRow .opts .choices .choice[aria-pressed=true],
308
- .setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill[aria-pressed=true],
309
- .setRow .opts #teamCards .tpill[aria-pressed=true]{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--ink);}
310
422
  /* a reason for a question the table hides explains nothing on screen */
311
423
  .physExisting[hidden] + .physWhy{display:none;}
424
+ /* the phone: the label and Change share a line, the value and its control take the width */
312
425
  @media (max-width:560px){
313
- .setRow{grid-template-columns:minmax(0,1fr);gap:6px 0;}
314
- .setRow .k{padding-top:0;}
426
+ .setRow{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"k acts" "v v";gap:2px 0;}
427
+ .setRow .k{grid-area:k;}
428
+ .setRow .v{grid-area:v;}
429
+ .setRow .acts{grid-area:acts;}
315
430
  }
316
- /* under 520 the two doors cannot sit side by side at 48px, so they stack —
317
- still two equal doors in one object */
318
- @media (max-width:520px){
319
- .doorPill{display:grid;grid-template-columns:1fr;width:100%;border-radius:32px;}
431
+ /* the phone: the parts take the width, the pairs stack, the rows' labels sit above */
432
+ @media (max-width:900px){
433
+ .part{padding:20px 18px;}
434
+ .pageSub{font-size:16px;}
320
435
  }
321
436
 
322
437
  /* a field: the label in the label face above, the input on one bottom
@@ -458,10 +573,8 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
458
573
  #panel-done[data-moment="wait"] #doneModules,
459
574
  #panel-done[data-moment="wait"] .samePath,
460
575
  #panel-done[data-moment="wait"] .again{display:none;}
461
- #panel-done[data-moment="wait"]:not(.hopSignin) #doneStepsHead,
462
- #panel-done[data-moment="wait"]:not(.hopSignin) #humanSteps{display:none;}
463
- #panel-done[data-moment="wait"].hopSignin .humanSteps>li:not([data-onboard-step="oauth-app"]){display:none;}
464
- #panel-done[data-moment="wait"].hopSignin .humanSteps>li::before{content:none;}
576
+ /* part 03 before Create: the heading, its state and the sentence; the rest waits */
577
+ #panel-done:not(.on) .doneBody,#panel-done:not(.on) .again{display:none;}
465
578
  .hopStatus{font-size:14px;font-weight:400;line-height:1.45;color:var(--ink-soft);margin:0;min-height:20px;}
466
579
  .hopBar{height:3px;background:var(--rule-soft);margin:10px 0 12px;overflow:hidden;}
467
580
  .hopBar span{display:block;height:100%;width:0;background:var(--accent);transition:width .8s ease;}
@@ -475,24 +588,24 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
475
588
  .hopHint{font-size:13px;color:var(--ink-faint);margin-right:auto;}
476
589
  @media (hover:none){.hopHint{display:none;}}
477
590
  .hopScore{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-soft);}
478
- .hopWhile{font-size:14px;color:var(--ink-soft);margin-top:16px;}
479
- .hopWhile button{min-height:24px;}
480
591
  /* reduced motion: the sky stands still and there is nothing to steer */
481
592
  .hop.isStill .hopRow{display:none;}
482
- /* ARRIVE — the strip's three dots stand down (its note stays: it carries the
483
- address), and the checklist shows one step at a time under a pager */
593
+ /* ARRIVE — the strip's three dots stand down (its note stays: it carries the address) */
484
594
  #panel-done[data-moment="arrive"] #liveProgress .stages{display:none;}
485
- #panel-done[data-moment="arrive"] .humanSteps>li:not(.cur){display:none;}
486
- #panel-done[data-moment="arrive"] .humanSteps>li.cur{border-bottom:1px solid var(--rule-soft);}
487
- #panel-done[data-moment="arrive"] .humanSteps>li.cur:not(.sdone)::before{content:attr(data-n);}
488
- .stepPager{display:none;align-items:center;gap:12px;flex-wrap:wrap;margin-top:16px;max-width:680px;}
489
- #panel-done[data-moment="arrive"] .stepPager{display:flex;}
490
- .stepCount{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);margin-right:auto;}
491
- @media (prefers-reduced-motion:no-preference){
492
- #panel-done[data-moment="arrive"] .humanSteps>li.cur,
493
- #panel-done[data-moment="arrive"] .doneHead{animation:arriveIn .7s ease both;}
494
- }
495
- @keyframes arriveIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
595
+ /* the checklist (task 1004648, the mockup's steps): one dot per step — an empty ring,
596
+ a tick in the ok ink once the server has verified it — and every step on show */
597
+ #panel-done .humanSteps{margin-top:18px;}
598
+ #panel-done .humanSteps>li{grid-template-columns:24px 1fr;gap:0 12px;padding:12px 0 14px;border-top:0;}
599
+ #panel-done .humanSteps>li:last-child{border-bottom:0;}
600
+ #panel-done .humanSteps>li::before{content:"";box-sizing:border-box;width:24px;height:24px;padding:0;border-radius:50%;border:1px solid var(--glass-edge);
601
+ display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-body);font-size:13px;letter-spacing:0;}
602
+ #panel-done .humanSteps>li.sdone::before{content:"✓";text-transform:none;color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);border:1px solid var(--ok);}
603
+ #panel-done .humanSteps>li.slocked::before{border:1px solid var(--rule-soft);}
604
+ #panel-done .humanSteps b{font-size:15px;line-height:24px;}
605
+ .alive{margin-top:18px;padding-top:18px;border-top:1px solid var(--rule);}
606
+ #view-new .aliveH{margin:0;font-family:var(--font-display);font-size:28px;font-weight:200;letter-spacing:-.02em;line-height:1.2;text-transform:none;color:var(--ink);}
607
+ #view-new #panel-done .lede{margin:8px 0 0;font-size:14.5px;line-height:1.5;color:var(--ink-soft);}
608
+ #view-new #panel-done .lede a{color:var(--link-ink);text-decoration:underline;text-underline-offset:3px;}
496
609
 
497
610
  /* ── the standup strip (task 2083), drawn as the lifecycle rail (DESIGN.md):
498
611
  three hairlines, a 7px dot, one lit step — no plate, no connector, no
@@ -579,6 +692,8 @@ summary{min-height:24px;padding:3px 0;}
579
692
  fallback before a choice is painted. */
580
693
  .lookGrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,300px);gap:28px;align-items:start;}
581
694
  @media (max-width:760px){.lookGrid{grid-template-columns:minmax(0,1fr);}}
695
+ /* in part 02's Look row the picker has one column's width, so its preview sits under it */
696
+ #reviewRows .lookGrid{grid-template-columns:minmax(0,1fr);}
582
697
  .lookSw{flex:none;display:inline-flex;gap:3px;margin-top:3px;}
583
698
  .lookSw i{display:block;width:14px;height:14px;border-radius:50%;border:1px solid var(--rule);}
584
699
  .lookSw.custom i{background:conic-gradient(var(--sea),var(--accent),var(--terracotta),var(--sea));}
@@ -1419,45 +1534,45 @@ summary{min-height:24px;padding:3px 0;}
1419
1534
  </section>
1420
1535
 
1421
1536
  <!-- ══════════════════ VIEW · NEW PROJECT ══════════════════════════════════
1422
- The whole create wizard, absorbed from /create (task 1002763; direction
1423
- §4 — that route now 302s here). The front door's two screens (terms, then
1424
- demo or create — task 1004417), then eight panels behind the rail; the
1425
- wizard block in the script below drives them. Same trust boundary as
1426
- ever: every button here only ENQUEUES a provisioning request (ADR 0111).
1427
- The terms panel carries a static `on` so a no-script visitor still reads
1428
- step 1 rather than an empty card. -->
1537
+ The whole create flow, absorbed from /create (task 1002763; direction
1538
+ §4 — that route now 302s here). Since task 1004648 it is ONE page (the design
1539
+ of record docs/design/mocks/day-night-rooms/): sign-in stays the gate in front,
1540
+ then three parts — 01 your project, 02 how it's run (a summary to skim, each row
1541
+ opening today's real control in place), 03 Connect GitHub once the project
1542
+ exists. Same trust boundary as ever: every button here only ENQUEUES a
1543
+ provisioning request (ADR 0111). The page carries a static `on` so a no-script
1544
+ visitor still reads it rather than an empty card. -->
1429
1545
  <section id="view-new" hidden>
1430
- <!-- the night room (task 1004504, the design of record docs/design/mocks/combined-onboarding/):
1431
- the studio's lofi room behind the whole wizard, in place of the nebula plate, the
1432
- forming planet and the bongo pair. Every colour is a mix of the fifteen (no page
1433
- :root, no literal); the chrome seed hangs in the window and grows a little on each
1434
- screen (show() writes data-screen); the only motion is a slow twinkle, the lamp's
1435
- breathe and the seed's float, all inside the kill switch. Decoration only. -->
1436
- <div class="wizRoom" id="wizRoom" data-screen="fork" aria-hidden="true">
1546
+ <!-- the room (tasks 1004504 and 1004648): the studio's lofi room behind the page. Night
1547
+ after dark, a day room in daylight — day from 07:00 to 18:59 on the viewer's own
1548
+ clock (data-sky on #view-new, re-checked each minute). Every colour is a mix of the
1549
+ fifteen (no page :root, no literal); the chrome seed hangs in the window and grows
1550
+ as the page fills in (data-screen + data-fill). The only motion is a slow twinkle,
1551
+ the clouds' drift, the lamp's breathe and the seed's float, all inside the kill
1552
+ switch. Decoration only. -->
1553
+ <div class="wizRoom" id="wizRoom" data-screen="page" data-fill="0" aria-hidden="true">
1437
1554
  <div class="rmWindow">
1438
1555
  <span class="rmMoon"></span>
1439
1556
  <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>
1557
+ <span class="rmCloud rmC1"></span><span class="rmCloud rmC2"></span>
1440
1558
  <span class="rmSeed"><img src="/assets/world/r2-chrome-blob-one-cut.webp" alt=""></span>
1441
1559
  <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>
1442
1560
  <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>
1443
1561
  <span class="rmMullion"></span>
1444
1562
  </div>
1445
- <span class="rmFloor"></span>
1563
+ <span class="rmFloor"></span><span class="rmSun"></span>
1446
1564
  <span class="rmGlow"></span><span class="rmStem"></span><span class="rmShade"></span>
1447
1565
  <span class="rmMug"></span><span class="rmLeaf rmF1"></span><span class="rmLeaf rmF2"></span>
1448
1566
  <span class="rmGrain"></span>
1449
1567
  </div>
1450
1568
  <div class="wrap viewWrap">
1451
1569
  <div class="card">
1452
- <!-- no progress rail (owner, 2026-10-01): the eyebrow names where you are and one
1453
- Back walks the screens backwards -->
1454
1570
  <div class="wizTop">
1455
1571
  <span class="wizEyebrow" id="wizEyebrow">New project</span>
1456
- <button type="button" class="wizBack" id="wizBack" hidden>← Back</button>
1457
1572
  </div>
1458
1573
  <div class="createSplit">
1459
1574
  <div>
1460
- <!-- 0 · sign in -->
1575
+ <!-- sign in: the gate in front, unchanged -->
1461
1576
  <section class="panel" id="panel-gate" aria-labelledby="gate-h">
1462
1577
  <h1 id="gate-h">Make something.</h1>
1463
1578
  <p class="lede">Sign in with GitHub to start your project.</p>
@@ -1482,591 +1597,564 @@ summary{min-height:24px;padding:3px 0;}
1482
1597
  <div class="alert" id="wizGateAlert" role="alert"></div>
1483
1598
  </section>
1484
1599
 
1485
- <!-- 1 · demo or real project, with the terms as its one tick (tasks 1004417 and
1486
- 1004504). The tick rides the create as `terms_acknowledged` and the SERVER
1487
- records which terms version it was (modules/provisioning/terms-ack.js), so
1488
- this page never names one. Both doors wait for it. The demo opens one screen
1489
- in place (task 1004420), then joins the ordinary flow marked "demo". The panel
1490
- carries a static `on` so a no-script visitor reads it rather than an empty card. -->
1491
- <section class="panel on" id="panel-fork" aria-labelledby="fork-h">
1600
+ <!-- the one page (task 1004648): three parts, nothing to confirm. Create waits only
1601
+ for a name and the terms tick; the screening stop still runs before anything
1602
+ is created. -->
1603
+ <section class="panel on" id="panel-page" aria-labelledby="page-h">
1492
1604
  <!-- The standup already being watched, kept one click away (task 1003503).
1493
1605
  Baked markup, hidden until enter() has a record to name. -->
1494
1606
  <p class="resumeOffer" id="wizResume" hidden>still watching <strong id="wizResumeName">your project</strong> — <a href="#" id="wizResumeGo">resume it</a></p>
1495
- <div id="forkIntro">
1496
- <h1 id="fork-h">You’re about to start a digital planet.</h1>
1497
- <p class="lede">Try a small demo first, or start your real project.</p>
1498
- <form data-next="2">
1499
- <label class="termsAck">
1500
- <input type="checkbox" id="termsAck" aria-describedby="h-terms">
1501
- <span>I can sign for this project, and I accept the <a class="termsRead" href="/terms" target="_blank" rel="noopener">terms</a>.</span>
1502
- </label>
1503
- <div class="hint" id="h-terms">&nbsp;</div>
1504
- <div class="doorPill" role="group" aria-label="How to begin">
1505
- <button type="button" class="btn big ghost" id="wizDemo" aria-controls="demoSetup" aria-expanded="false" disabled>Try the demo</button>
1506
- <button type="submit" class="btn big" id="wizStart" disabled>Start my project</button>
1507
- </div>
1508
- </form>
1509
- </div>
1510
- <!-- the demo route (task 1004420, BV2.PS09): one screen inside the fork, then
1511
- the ordinary flow from "What are you making?": a demo is a normal project
1512
- marked "demo" (D9). The fit check is a copy of modules/provisioning/demo.js
1513
- fitCheck, held equal by tests/wizard_demo.mjs. -->
1514
- <div id="demoSetup" class="demoView" hidden>
1515
- <h1 id="demo-h1">Try a demo.</h1>
1516
- <p class="lede">A small, time-boxed project to learn by doing. You can keep it at the end.</p>
1517
- <div class="demoSteppers">
1518
- <div class="wizField"><span id="demoPeopleLbl">People, you included</span>
1519
- <div class="demoStep" role="group" aria-labelledby="demoPeopleLbl">
1520
- <button type="button" class="btn small ghost" data-demo-step="people" data-by="-1" aria-label="One fewer person">−</button>
1521
- <output id="demoPeople" aria-live="polite">3</output>
1522
- <button type="button" class="btn small ghost" data-demo-step="people" data-by="1" aria-label="One more person">+</button>
1607
+ <h1 id="page-h">Make something.<span class="demoTag" id="demoTag" hidden>demo</span></h1>
1608
+ <p class="pageSub" id="pageSub">Three parts, one page. Just looking? <button type="button" class="linklike" id="wizDemo" aria-controls="demoSetup" aria-expanded="false">Try a small demo instead</button>.</p>
1609
+
1610
+ <div class="wizParts" id="wizParts">
1611
+ <!-- 01 · your project -->
1612
+ <div class="part" id="part-project" aria-labelledby="p1-h">
1613
+ <h2 id="p1-h"><span class="n">01</span>Your project</h2>
1614
+ <!-- auto-fill: the answers this account gave on its most recent project -->
1615
+ <p class="fillOffer" id="physFillOffer" hidden><button type="button" class="linklike" id="physFromAccount">Fill these in from my last project</button></p>
1616
+ <!-- the demo route (task 1004420, BV2.PS09): opened in place at the top of part
1617
+ 01 by the small link under the heading; the rest of the page is unchanged
1618
+ and marked "demo" (D9). The fit check is a copy of modules/provisioning/demo.js
1619
+ fitCheck, held equal by tests/wizard_demo.mjs. -->
1620
+ <div id="demoSetup" class="demoView" hidden>
1621
+ <h3 class="partH3" id="demo-h1" tabindex="-1">Try a demo.</h3>
1622
+ <p class="hint demoLede">A small, time-boxed project to learn by doing. You can keep it at the end.</p>
1623
+ <div class="demoSteppers">
1624
+ <div class="wizField"><span id="demoPeopleLbl">People, you included</span>
1625
+ <div class="demoStep" role="group" aria-labelledby="demoPeopleLbl">
1626
+ <button type="button" class="btn small ghost" data-demo-step="people" data-by="-1" aria-label="One fewer person">−</button>
1627
+ <output id="demoPeople" aria-live="polite">3</output>
1628
+ <button type="button" class="btn small ghost" data-demo-step="people" data-by="1" aria-label="One more person">+</button>
1629
+ </div>
1630
+ </div>
1631
+ <div class="wizField"><span id="demoHoursLbl">Hours each</span>
1632
+ <div class="demoStep" role="group" aria-labelledby="demoHoursLbl">
1633
+ <button type="button" class="btn small ghost" data-demo-step="hours" data-by="-1" aria-label="One hour less">−</button>
1634
+ <output id="demoHours" aria-live="polite">4</output>
1635
+ <button type="button" class="btn small ghost" data-demo-step="hours" data-by="1" aria-label="One hour more">+</button>
1636
+ </div>
1637
+ </div>
1523
1638
  </div>
1524
- </div>
1525
- <div class="wizField"><span id="demoHoursLbl">Hours each</span>
1526
- <div class="demoStep" role="group" aria-labelledby="demoHoursLbl">
1527
- <button type="button" class="btn small ghost" data-demo-step="hours" data-by="-1" aria-label="One hour less">−</button>
1528
- <output id="demoHours" aria-live="polite">4</output>
1529
- <button type="button" class="btn small ghost" data-demo-step="hours" data-by="1" aria-label="One hour more">+</button>
1639
+ <label class="wizField"><span>Your idea</span>
1640
+ <input type="text" id="demoIdea" maxlength="600" placeholder="A voting app for movie night" autocomplete="off">
1641
+ </label>
1642
+ <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>
1643
+ <div class="demoInvites" id="demoInvites">
1644
+ <label class="wizField"><span>Invite by GitHub handle</span>
1645
+ <input type="text" id="demoInvite1" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1646
+ </label>
1647
+ <label class="wizField"><span>And one more</span>
1648
+ <input type="text" id="demoInvite2" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1649
+ </label>
1650
+ </div>
1651
+ <p class="finePrint">An unkept demo goes offline after 30 days. We warn you first, and it can come back.</p>
1652
+ <div class="demoActs">
1653
+ <button type="button" class="btn small" id="demoStart">Use these</button>
1654
+ <button type="button" class="linklike" id="demoCancel">Make a real project instead</button>
1530
1655
  </div>
1531
1656
  </div>
1532
- </div>
1533
- <label class="wizField"><span>Your idea</span>
1534
- <input type="text" id="demoIdea" maxlength="600" placeholder="A voting app for movie night" autocomplete="off">
1535
- </label>
1536
- <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>
1537
- <div class="demoInvites" id="demoInvites">
1538
- <label class="wizField"><span>Invite by GitHub handle</span>
1539
- <input type="text" id="demoInvite1" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1540
- </label>
1541
- <label class="wizField"><span>And one more</span>
1542
- <input type="text" id="demoInvite2" placeholder="teammate’s handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="39">
1543
- </label>
1544
- </div>
1545
- <p class="finePrint">An unkept demo goes offline after 30 days. We warn you first, and it can come back.</p>
1546
- <div class="nav">
1547
- <span></span>
1548
- <button type="button" class="btn" id="demoStart">Continue</button>
1549
- </div>
1550
- </div>
1551
- </section>
1552
1657
 
1553
- <!-- 2 · what are you making: the name, the kind (task 1002772 — a declaration,
1554
- closed vocabulary, Not sure the escape hatch) and the optional sentence that
1555
- puts the project on the public map (ADR 0182 D4). -->
1556
- <section class="panel" id="panel-what" aria-labelledby="what-h">
1557
- <h1 id="what-h">What are you making?<span class="demoTag" id="demoTag" hidden>demo</span></h1>
1558
- <form id="newForm" data-next="3">
1559
- <label class="wizField"><span>Name</span>
1560
- <input type="text" id="newName" placeholder="Moonbase" autocomplete="off" maxlength="80" aria-label="Project name">
1561
- <div class="hint" id="h-name">&nbsp;</div>
1562
- </label>
1563
- <div class="wizField"><span id="typeLbl">Kind</span>
1564
- <div class="typeCards" id="typeCards" role="group" aria-labelledby="typeLbl">
1565
- <button type="button" class="tpill" data-type="game" aria-pressed="false">Game</button>
1566
- <button type="button" class="tpill" data-type="research" aria-pressed="false">Research</button>
1567
- <button type="button" class="tpill" data-type="business" aria-pressed="false">Business</button>
1568
- <button type="button" class="tpill" data-type="non-profit" aria-pressed="false">Non-profit</button>
1569
- <button type="button" class="tpill" data-type="not-sure" aria-pressed="false">Not sure</button>
1658
+ <!-- the name, the kind (task 1002772 — a declaration, closed vocabulary, Not
1659
+ sure the escape hatch) and the optional sentence that puts the project on
1660
+ the public map (ADR 0182 D4) -->
1661
+ <label class="wizField"><span>Name</span>
1662
+ <input type="text" id="newName" placeholder="Moonbase" autocomplete="off" maxlength="80" aria-label="Project name">
1663
+ <div class="hint" id="h-name">&nbsp;</div>
1664
+ </label>
1665
+ <div class="wizField"><span id="typeLbl">Kind</span>
1666
+ <div class="typeCards" id="typeCards" role="group" aria-labelledby="typeLbl">
1667
+ <button type="button" class="tpill" data-type="game" aria-pressed="false">Game</button>
1668
+ <button type="button" class="tpill" data-type="research" aria-pressed="false">Research</button>
1669
+ <button type="button" class="tpill" data-type="business" aria-pressed="false">Business</button>
1670
+ <button type="button" class="tpill" data-type="non-profit" aria-pressed="false">Non-profit</button>
1671
+ <button type="button" class="tpill" data-type="not-sure" aria-pressed="false">Not sure</button>
1672
+ </div>
1570
1673
  </div>
1571
- <div class="hint" id="h-projtype">&nbsp;</div>
1572
- </div>
1573
- <label class="wizField"><span>In a sentence <small>(optional — puts it on the public map)</small></span>
1574
- <input type="text" id="f-desc" maxlength="600" placeholder="A co-op space game built by its players" autocomplete="off">
1575
- </label>
1576
- <div class="nav">
1577
- <span></span>
1578
- <button type="submit" class="btn">Continue</button>
1579
- </div>
1580
- </form>
1581
- </section>
1674
+ <label class="wizField"><span>In a sentence <small>(optional, it goes on the public map)</small></span>
1675
+ <input type="text" id="f-desc" maxlength="600" placeholder="A co-op space game built by its players" autocomplete="off">
1676
+ </label>
1582
1677
 
1583
- <!-- 3 · a few quick checks: the planet-physics answers that change something
1584
- (tasks 1004418 and 1004419), under planet-physics.js's own keys. Continue asks
1585
- POST /provisioning/physics-check — the same screen and soft-limit table the
1586
- create runs — and a stop ends here, before anything exists. The age tiers are
1587
- described, never claimed as enforced (spec D6). A demo skips this screen. -->
1588
- <section class="panel" id="panel-checks" aria-labelledby="checks-h">
1589
- <div id="checksMain">
1590
- <h1 id="checks-h">A few quick checks.</h1>
1591
- <p class="lede">These decide which legal and payout options we can offer.</p>
1592
- <!-- auto-fill: the answers this account gave on its most recent project -->
1593
- <p class="fillOffer" id="physFillOffer" hidden><button type="button" class="linklike" id="physFromAccount">Fill these in from my last project</button></p>
1594
- <form data-next="4" id="checksForm">
1595
- <div class="physRow2">
1596
- <label class="wizField"><span>Where are you?</span><select id="physCountry"><option value="">Choose…</option></select></label>
1597
- <label class="wizField" id="physRegionField" hidden><span>State or province</span><select id="physRegion"><option value="">Choose…</option></select></label>
1598
- </div>
1599
- <div class="wizField"><span id="physGeoLbl">Where will builders be?</span>
1600
- <div class="typeCards physCells" role="group" aria-labelledby="physGeoLbl">
1601
- <button type="button" class="tpill" data-phys="builder_geography" data-val="us-only" aria-pressed="false">US only</button>
1602
- <button type="button" class="tpill" data-phys="builder_geography" data-val="us-eu" aria-pressed="false">US and EU</button>
1603
- <button type="button" class="tpill" data-phys="builder_geography" data-val="global" aria-pressed="false">Anywhere</button>
1604
- <button type="button" class="tpill" data-phys="builder_geography" data-val="not-sure" aria-pressed="false">Not sure</button>
1678
+ <!-- the quick checks: the planet-physics answers that change something (tasks
1679
+ 1004418 and 1004419), under planet-physics.js's own keys. Create asks
1680
+ POST /provisioning/physics-check first — the same screen and soft-limit
1681
+ table the create runs — and a stop ends the page there, before anything
1682
+ exists. The age tiers are described, never claimed as enforced (spec D6).
1683
+ A demo's answers are the demo's own, so the checks stand down for it. -->
1684
+ <div class="checks" id="checksMain">
1685
+ <p class="checksHead">A few quick checks. They decide which legal and payout options we can offer.</p>
1686
+ <div class="physRow2">
1687
+ <label class="wizField"><span>Where are you?</span><select id="physCountry"><option value="">Choose…</option></select></label>
1688
+ <label class="wizField" id="physRegionField" hidden><span>State or province</span><select id="physRegion"><option value="">Choose…</option></select></label>
1605
1689
  </div>
1606
- </div>
1607
- <div class="wizField"><span id="physAgeLbl">Will anyone under 18 build?</span>
1608
- <div class="typeCards physCells" role="group" aria-labelledby="physAgeLbl">
1609
- <button type="button" class="tpill" data-phys="minors_on_board" data-val="false" aria-pressed="false">No</button>
1610
- <button type="button" class="tpill" data-phys="minors_on_board" data-val="true" aria-pressed="false">Yes</button>
1690
+ <div class="wizField"><span id="physAgeLbl">Will anyone under 18 build?</span>
1691
+ <div class="typeCards physCells" role="group" aria-labelledby="physAgeLbl">
1692
+ <button type="button" class="tpill" data-phys="minors_on_board" data-val="false" aria-pressed="false">No</button>
1693
+ <button type="button" class="tpill" data-phys="minors_on_board" data-val="true" aria-pressed="false">Yes</button>
1694
+ </div>
1611
1695
  </div>
1612
- </div>
1613
- <div class="physNote" id="physAges" hidden>
1614
- <p class="physNoteH">The age tiers young builders work under</p>
1615
- <dl class="physTiers">
1616
- <dt>13 to 15</dt><dd>Non-monetary tracks only</dd>
1617
- <dt>16 to 17</dt><dd>Paid work, with a guardian’s co-signature and a work-permit attestation</dd>
1618
- <dt>18 and over</dt><dd>Full access</dd>
1619
- </dl>
1620
- <p class="finePrint">A guardian signs for each young builder and controls their payouts. The platform does not check ages for you yet.</p>
1621
- </div>
1622
- <div class="wizField" id="physIntent"><span id="physWhoLbl">Who’s behind it?</span>
1623
- <div class="typeCards physCells" role="group" aria-labelledby="physWhoLbl">
1624
- <button type="button" class="tpill" data-phys="entity" data-val="intent-solo" aria-pressed="false">Just me</button>
1625
- <button type="button" class="tpill" data-phys="entity" data-val="intent-company" aria-pressed="false">A company</button>
1626
- <button type="button" class="tpill" data-phys="entity" data-val="intent-nonprofit" aria-pressed="false">A nonprofit</button>
1627
- <button type="button" class="tpill" data-phys="entity" data-val="intent-unsure" aria-pressed="false">Not sure yet</button>
1696
+ <div class="physNote" id="physAges" hidden>
1697
+ <p class="physNoteH">The age tiers young builders work under</p>
1698
+ <dl class="physTiers">
1699
+ <dt>13 to 15</dt><dd>Non-monetary tracks only</dd>
1700
+ <dt>16 to 17</dt><dd>Paid work, with a guardian’s co-signature and a work-permit attestation</dd>
1701
+ <dt>18 and over</dt><dd>Full access</dd>
1702
+ </dl>
1703
+ <p class="finePrint">A guardian signs for each young builder and controls their payouts. The platform does not check ages for you yet.</p>
1628
1704
  </div>
1629
- <button type="button" class="physLink" id="physHaveEntity">We already have a legal entity</button>
1630
- </div>
1631
- <div class="wizField" id="physForms" hidden><span>Your legal entity</span>
1632
- <p class="physGrp">Organization</p>
1633
- <div class="typeCards physCells" role="group" aria-label="Organization">
1634
- <button type="button" class="tpill" data-phys="entity" data-val="nonprofit" aria-pressed="false">Nonprofit</button>
1635
- <button type="button" class="tpill" data-phys="entity" data-val="for-profit" aria-pressed="false">For-profit company</button>
1636
- <button type="button" class="tpill" data-phys="entity" data-val="corporate-division" aria-pressed="false">Division of a corporation</button>
1637
- <button type="button" class="tpill" data-phys="entity" data-val="research" aria-pressed="false">Research entity</button>
1705
+ <div class="wizField"><span id="physGeoLbl">Where will builders be?</span>
1706
+ <div class="typeCards physCells" role="group" aria-labelledby="physGeoLbl">
1707
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="us-only" aria-pressed="false">US only</button>
1708
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="us-eu" aria-pressed="false">US and EU</button>
1709
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="global" aria-pressed="false">Anywhere</button>
1710
+ <button type="button" class="tpill" data-phys="builder_geography" data-val="not-sure" aria-pressed="false">Not sure</button>
1711
+ </div>
1638
1712
  </div>
1639
- <p class="physGrp">Group</p>
1640
- <div class="typeCards physCells" role="group" aria-label="Group">
1641
- <button type="button" class="tpill" data-phys="entity" data-val="university-project" aria-pressed="false">University project</button>
1642
- <button type="button" class="tpill" data-phys="entity" data-val="social-group" aria-pressed="false">Social group</button>
1713
+ <div class="wizField" id="physIntent"><span id="physWhoLbl">Who’s behind it?</span>
1714
+ <div class="typeCards physCells" role="group" aria-labelledby="physWhoLbl">
1715
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-solo" aria-pressed="false">Just me</button>
1716
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-company" aria-pressed="false">A company</button>
1717
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-nonprofit" aria-pressed="false">A nonprofit</button>
1718
+ <button type="button" class="tpill" data-phys="entity" data-val="intent-unsure" aria-pressed="false">Not sure yet</button>
1719
+ </div>
1720
+ <button type="button" class="physLink" id="physHaveEntity">We already have a legal entity</button>
1643
1721
  </div>
1644
- <p class="physGrp">Individual</p>
1645
- <div class="typeCards physCells" role="group" aria-label="Individual">
1646
- <button type="button" class="tpill" data-phys="entity" data-val="single-owner-llc" aria-pressed="false">Single-owner LLC</button>
1647
- <button type="button" class="tpill" data-phys="entity" data-val="private-individual" aria-pressed="false">Private individual</button>
1722
+ <div class="wizField" id="physForms" hidden><span>Your legal entity</span>
1723
+ <p class="physGrp">Organization</p>
1724
+ <div class="typeCards physCells" role="group" aria-label="Organization">
1725
+ <button type="button" class="tpill" data-phys="entity" data-val="nonprofit" aria-pressed="false">Nonprofit</button>
1726
+ <button type="button" class="tpill" data-phys="entity" data-val="for-profit" aria-pressed="false">For-profit company</button>
1727
+ <button type="button" class="tpill" data-phys="entity" data-val="corporate-division" aria-pressed="false">Division of a corporation</button>
1728
+ <button type="button" class="tpill" data-phys="entity" data-val="research" aria-pressed="false">Research entity</button>
1729
+ </div>
1730
+ <p class="physGrp">Group</p>
1731
+ <div class="typeCards physCells" role="group" aria-label="Group">
1732
+ <button type="button" class="tpill" data-phys="entity" data-val="university-project" aria-pressed="false">University project</button>
1733
+ <button type="button" class="tpill" data-phys="entity" data-val="social-group" aria-pressed="false">Social group</button>
1734
+ </div>
1735
+ <p class="physGrp">Individual</p>
1736
+ <div class="typeCards physCells" role="group" aria-label="Individual">
1737
+ <button type="button" class="tpill" data-phys="entity" data-val="single-owner-llc" aria-pressed="false">Single-owner LLC</button>
1738
+ <button type="button" class="tpill" data-phys="entity" data-val="private-individual" aria-pressed="false">Private individual</button>
1739
+ </div>
1740
+ <button type="button" class="physLink" id="physNoEntity">We don’t have one yet</button>
1648
1741
  </div>
1649
- <button type="button" class="physLink" id="physNoEntity">We don’t have one yet</button>
1742
+ <div class="hint" id="h-phys">&nbsp;</div>
1650
1743
  </div>
1651
- <div class="hint" id="h-phys">&nbsp;</div>
1652
- <div class="nav">
1653
- <span></span>
1654
- <button type="submit" class="btn" id="physNext">Continue</button>
1744
+ <!-- the screening stop, in place: nothing is created -->
1745
+ <div class="checksStop" id="checksStop" role="alert" hidden>
1746
+ <h3 class="partH3" id="phys-hstop" tabindex="-1">This can’t be built <em>on <span data-brand-product-inline>Cloud Bongos</span></em></h3>
1747
+ <p id="physStopMsg"></p>
1748
+ <div class="demoActs">
1749
+ <button type="button" class="btn small ghost" id="physStartOver">Change my answers</button>
1750
+ <a class="btn small" href="/contact">Contact us</a>
1751
+ </div>
1655
1752
  </div>
1656
- </form>
1657
- </div>
1658
- <!-- the screening stop: nothing is created -->
1659
- <div id="checksStop" hidden>
1660
- <h1 id="phys-hstop">This can’t be built <em>on <span data-brand-product-inline>Cloud Bongos</span></em></h1>
1661
- <p class="lede" id="physStopMsg"></p>
1662
- <div class="nav">
1663
- <button type="button" class="btn ghost" id="physStartOver">Start over</button>
1664
- <a class="btn" href="/contact">Contact us</a>
1665
1753
  </div>
1666
- </div>
1667
- </section>
1668
1754
 
1669
- <!-- 4 · check what we set up: every prefilled answer is a one-line row the
1670
- founder reads, and changes in place if it is wrong (task 1004550). Edit opens
1671
- the step's own controls, moved here whole, so every handler and id is the one
1672
- it always was; Done checks the row the way its old step did. One "Looks right,
1673
- continue" confirms every row at once, and stops on the first row that fails
1674
- its check, opened with its reason. -->
1675
- <section class="panel" id="panel-review" aria-labelledby="rev-h">
1676
- <h1 id="rev-h">Check what we set up.</h1>
1677
- <p class="lede">We filled these in from your answers. Change anything that’s wrong.</p>
1678
- <form data-next="5" id="reviewForm">
1679
- <div class="setRows" id="reviewRows">
1680
-
1681
- <div class="setRow" data-row="code">
1682
- <span class="k">Code</span>
1683
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1684
- <div class="opts" hidden>
1685
- <div class="typeCards physCells" role="group" aria-label="Your project’s state">
1686
- <button type="button" class="tpill" data-phys="project_state" data-val="new" aria-pressed="false">Starting from zero</button>
1687
- <button type="button" class="tpill" data-phys="project_state" data-val="existing" aria-pressed="false">Already started</button>
1688
- </div>
1689
- <fieldset class="physExisting" id="physExisting" data-limit="existing_work" hidden>
1690
- <legend>What already exists? Pick all that apply, then your main one.</legend>
1691
- <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>
1692
- <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>
1693
- <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>
1694
- </fieldset>
1695
- <p class="physWhy" data-why="existing_work" hidden></p>
1696
- <div class="choices" role="group" aria-label="Starting point">
1697
- <button type="button" class="choice" id="c-green" aria-pressed="true">
1698
- <b>A new repo</b>
1699
- <p>Private, set up for you</p>
1700
- </button>
1701
- <button type="button" class="choice" id="c-adopt" aria-pressed="false">
1702
- <b>A repo I have</b>
1703
- <p>Public or private</p>
1704
- </button>
1705
- </div>
1706
- <div class="reassure" id="adoptNote">Nothing is overwritten. You see conflicts first, and open issues become tasks.</div>
1707
- <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
1708
- Without repo access we fall back to the manual owner/repo inputs
1709
- (#manualHome), which stay authoritative. -->
1710
- <!-- no repo-access ask in the wizard (task 1004555): every hub sign-in asks for repo
1711
- access (ADR 0364), and a missing token is fetched by one automatic GitHub
1712
- round-trip. Only when that hop could not help does this line show, above
1713
- the manual fields. -->
1714
- <div id="ghNoAccess" class="hint" hidden>We couldn’t read your GitHub repositories just now, so enter the repo by hand below.</div>
1715
- <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
1716
- <div id="ghAdopt" class="wizField" hidden>
1717
- <span>Pick one of your repositories</span>
1718
- <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off" aria-label="Search your repositories">
1719
- <div id="ghList" class="ghlist" role="group" aria-label="Your repositories"></div>
1720
- <div class="hint" id="ghAdoptHint">&nbsp;</div>
1721
- <!-- visibility stated on EVERY repo path (task 1002721) -->
1722
- <div class="hint">We never change it: public stays public, private stays private.</div>
1723
- </div>
1724
- <div id="ghFresh" class="wizField" hidden>
1725
- <span>New repository name</span>
1726
- <input type="text" id="ghNewName" placeholder="moonbase" autocomplete="off" maxlength="100" aria-label="New repository name">
1727
- <!-- visibility is stated on EVERY repo path (task 1002721); updateFreshPreview()
1728
- replaces this floor with the same claim plus the resolved repo URL -->
1729
- <div class="hint" id="ghFreshHint">A new <strong>private</strong> repo. You can make it public later.</div>
1730
- </div>
1731
- <div class="row2" id="manualHome">
1732
- <label class="wizField"><span>GitHub owner <small>(user or org)</small></span>
1733
- <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
1734
- </label>
1735
- <label class="wizField"><span>Repository name</span>
1736
- <input type="text" id="f-repo" placeholder="moonbase" autocomplete="off" maxlength="120">
1737
- </label>
1738
- <!-- visibility stated BEFORE GitHub's own radio is the first place it appears (task 1002721) -->
1739
- <div class="hint" style="grid-column:1 / -1;">Public or private both work, and we never change it.</div>
1755
+ <!-- 02 · how it's run: a summary to skim, not a gate (task 1004648). Every row is
1756
+ prefilled from the answers above; Change opens today's own control in place —
1757
+ moved here whole, so every handler and id is the one it always was — and Done
1758
+ closes it again. No row needs confirming. -->
1759
+ <div class="part" id="part-run" aria-labelledby="p2-h">
1760
+ <h2 id="p2-h"><span class="n">02</span>How it’s run <span class="state">filled in from your answers</span></h2>
1761
+ <div class="setRows" id="reviewRows">
1762
+
1763
+ <div class="setRow" data-row="code">
1764
+ <span class="k">Code</span>
1765
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1766
+ <div class="opts" hidden>
1767
+ <div class="typeCards physCells" role="group" aria-label="Your project’s state">
1768
+ <button type="button" class="tpill" data-phys="project_state" data-val="new" aria-pressed="false">Starting from zero</button>
1769
+ <button type="button" class="tpill" data-phys="project_state" data-val="existing" aria-pressed="false">Already started</button>
1770
+ </div>
1771
+ <fieldset class="physExisting" id="physExisting" data-limit="existing_work" hidden>
1772
+ <legend>What already exists? Pick all that apply, then your main one.</legend>
1773
+ <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>
1774
+ <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>
1775
+ <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>
1776
+ </fieldset>
1777
+ <p class="physWhy" data-why="existing_work" hidden></p>
1778
+ <div class="choices" role="group" aria-label="Starting point">
1779
+ <button type="button" class="choice" id="c-green" aria-pressed="true">
1780
+ <b>A new GitHub repo</b>
1781
+ <p>Private, set up for you</p>
1782
+ </button>
1783
+ <button type="button" class="choice" id="c-adopt" aria-pressed="false">
1784
+ <b>A repo I already have</b>
1785
+ <p>Public or private</p>
1786
+ </button>
1787
+ </div>
1788
+ <div class="reassure" id="adoptNote">Nothing is overwritten. You see conflicts first, and open issues become tasks.</div>
1789
+ <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
1790
+ Without repo access we fall back to the manual owner/repo inputs
1791
+ (#manualHome), which stay authoritative. No repo-access ask here (task
1792
+ 1004555): every hub sign-in asks for repo access (ADR 0364), and a
1793
+ missing token is fetched by one automatic GitHub round-trip. Only when
1794
+ that hop could not help does this line show, above the manual fields. -->
1795
+ <div id="ghNoAccess" class="hint" hidden>We couldn’t read your GitHub repositories just now, so enter the repo by hand below.</div>
1796
+ <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
1797
+ <div id="ghAdopt" class="wizField" hidden>
1798
+ <span>Pick one of your repositories</span>
1799
+ <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off" aria-label="Search your repositories">
1800
+ <div id="ghList" class="ghlist" role="group" aria-label="Your repositories"></div>
1801
+ <div class="hint" id="ghAdoptHint">&nbsp;</div>
1802
+ <!-- visibility stated on EVERY repo path (task 1002721) -->
1803
+ <div class="hint">We never change it: public stays public, private stays private.</div>
1804
+ </div>
1805
+ <div id="ghFresh" class="wizField" hidden>
1806
+ <span>New repository name</span>
1807
+ <input type="text" id="ghNewName" placeholder="moonbase" autocomplete="off" maxlength="100" aria-label="New repository name">
1808
+ <!-- visibility is stated on EVERY repo path (task 1002721); updateFreshPreview()
1809
+ replaces this floor with the same claim plus the resolved repo URL -->
1810
+ <div class="hint" id="ghFreshHint">A new <strong>private</strong> repo. You can make it public later.</div>
1811
+ </div>
1812
+ <div class="row2" id="manualHome">
1813
+ <label class="wizField"><span>GitHub owner <small>(user or org)</small></span>
1814
+ <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
1815
+ </label>
1816
+ <label class="wizField"><span>Repository name</span>
1817
+ <input type="text" id="f-repo" placeholder="moonbase" autocomplete="off" maxlength="120">
1818
+ </label>
1819
+ <!-- visibility stated BEFORE GitHub's own radio is the first place it appears (task 1002721) -->
1820
+ <div class="hint" style="grid-column:1 / -1;">Public or private both work, and we never change it.</div>
1821
+ </div>
1822
+ <div class="hint" id="h-home">&nbsp;</div>
1740
1823
  </div>
1741
- <div class="hint" id="h-home">&nbsp;</div>
1742
1824
  </div>
1825
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1743
1826
  </div>
1744
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1745
- </div>
1746
1827
 
1747
- <!-- the address (tasks 1002704, 1002898 N8, PS19): the assigned address is
1748
- stated, and both alternatives sit under it. It is shown and confirmed on
1749
- every path, the demo's too — nothing is created before it is chosen. -->
1750
- <div class="setRow" data-row="address">
1751
- <span class="k">Address</span>
1752
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1753
- <div class="opts" hidden>
1754
- <label class="wizField"><span>Project handle</span>
1755
- <input type="text" id="f-slug" placeholder="moonbase" autocomplete="off" maxlength="40" spellcheck="false">
1756
- <div class="hint" id="h-slug">&nbsp;</div>
1757
- </label>
1758
- <div class="addrConfirm" id="addrConfirm">
1759
- <div class="addrBig" id="addrAssigned"></div>
1760
- <p id="addrAssignedNote">Yours once it’s set up. You can add your own domain any time.</p>
1761
- <details id="addrCustom">
1762
- <summary>A domain I own</summary>
1763
- <div class="inner">
1764
- <label class="wizField"><span>Domain you own</span>
1765
- <input type="text" id="f-domain" placeholder="moonbase.example.com" autocomplete="off" maxlength="253" spellcheck="false">
1766
- <div class="hint" id="h-domain">&nbsp;</div>
1767
- </label>
1768
- </div>
1769
- </details>
1770
- <label class="addrOpt"><input type="checkbox" id="f-noaddr">
1771
- <span>No address yet. Sign-in can’t be switched on until it has one.</span>
1828
+ <!-- the address (tasks 1002704, 1002898 N8, PS19): the assigned address is
1829
+ stated, and both alternatives sit under it, on every path, the demo's too -->
1830
+ <div class="setRow" data-row="address">
1831
+ <span class="k">Address</span>
1832
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1833
+ <div class="opts" hidden>
1834
+ <label class="wizField"><span>Project handle</span>
1835
+ <input type="text" id="f-slug" placeholder="moonbase" autocomplete="off" maxlength="40" spellcheck="false">
1836
+ <div class="hint" id="h-slug">&nbsp;</div>
1772
1837
  </label>
1838
+ <div class="addrConfirm" id="addrConfirm">
1839
+ <div class="addrBig" id="addrAssigned"></div>
1840
+ <p id="addrAssignedNote">Yours once it’s set up. You can add your own domain any time.</p>
1841
+ <details id="addrCustom">
1842
+ <summary>A domain I own</summary>
1843
+ <div class="inner">
1844
+ <label class="wizField"><span>Domain you own</span>
1845
+ <input type="text" id="f-domain" placeholder="moonbase.example.com" autocomplete="off" maxlength="253" spellcheck="false">
1846
+ <div class="hint" id="h-domain">&nbsp;</div>
1847
+ </label>
1848
+ </div>
1849
+ </details>
1850
+ <label class="addrOpt"><input type="checkbox" id="f-noaddr">
1851
+ <span>No address yet. Sign-in can’t be switched on until it has one.</span>
1852
+ </label>
1853
+ </div>
1773
1854
  </div>
1774
1855
  </div>
1856
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1775
1857
  </div>
1776
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1777
- </div>
1778
1858
 
1779
- <!-- where the app runs (tasks 1004351, 1004352; ADR 0345): the hall is ours to
1780
- host, so this row is only the owner's APP. "Decide later" is the default.
1781
- The Render key lives in a variable only: never the draft, a URL or a log. -->
1782
- <div class="setRow" data-row="app">
1783
- <span class="k">App hosting</span>
1784
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1785
- <div class="opts" hidden>
1786
- <fieldset class="appOpts" id="appOpts">
1787
- <legend>Where your app runs</legend>
1788
- <label class="appOpt">
1789
- <input type="radio" name="appPlatform" value="later" checked>
1790
- <span><b>Decide later</b>
1791
- <span>There’s no app to host until your builders write one.</span></span>
1792
- </label>
1793
- <label class="appOpt" id="appOptRender">
1794
- <input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">
1795
- <span><b>Render</b>
1796
- <span id="appRenderWhat">We set it up on your own Render account. Render bills you directly.</span></span>
1797
- </label>
1798
- </fieldset>
1799
- <p class="renderWhy" id="renderLater" role="status" hidden>Your new repo has no app yet, so nothing is created on Render now. Set it up from your project’s page once there is one.</p>
1800
- <div class="renderSetup" id="renderSetup" hidden>
1801
- <p class="renderWhy">Used once to create your app on your Render account, then thrown away — not kept, not even in this browser. A key can act on your whole Render account. Render bills you directly. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
1802
- <div class="renderKeyRow">
1803
- <label class="wizField"><span>Render API key</span>
1804
- <input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
1805
- </label>
1806
- <button type="button" class="btn ghost small" id="renderCheck">Check key</button>
1859
+ <!-- the team (question 5, task 1004419): one person, or a team with a count and
1860
+ the month-three guess the founder corrects; who can join a team is the team
1861
+ shape the extras' preselect reads (task 1002341) -->
1862
+ <div class="setRow" data-row="team">
1863
+ <span class="k">Team</span>
1864
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1865
+ <div class="opts" hidden>
1866
+ <div class="typeCards physCells" role="group" aria-label="One or many">
1867
+ <button type="button" class="tpill" data-phys-solo="one" aria-pressed="false">Just you to start</button>
1868
+ <button type="button" class="tpill" data-phys-solo="many" aria-pressed="false">A team</button>
1807
1869
  </div>
1808
- <div class="hint" id="h-renderKey" role="status">&nbsp;</div>
1809
- <fieldset class="appOpts" id="renderWs" hidden>
1810
- <legend>Which Render workspace</legend>
1811
- <div id="renderWsList"></div>
1812
- </fieldset>
1813
- <fieldset class="appOpts" id="renderSvc" hidden>
1814
- <legend>A new app, or one you already run</legend>
1815
- <div id="renderSvcList"></div>
1816
- </fieldset>
1817
- <div class="renderNew" id="renderNew" hidden>
1818
- <label class="wizField"><span>Your app is built with</span>
1819
- <select id="f-renderRuntime">
1820
- <option value="node">Node.js</option><option value="python">Python</option><option value="ruby">Ruby</option>
1821
- <option value="go">Go</option><option value="rust">Rust</option><option value="elixir">Elixir</option>
1822
- <option value="docker">Docker — use my repository’s Dockerfile</option>
1823
- </select>
1824
- </label>
1825
- <label class="wizField" id="renderBuildWrap"><span>Build command <small>— Render needs one; the usual one is filled in</small></span>
1826
- <input type="text" id="f-renderBuild" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm ci">
1827
- </label>
1828
- <label class="wizField" id="renderStartWrap"><span>Start command</span>
1829
- <input type="text" id="f-renderStart" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm start">
1830
- </label>
1831
- <label class="wizField"><span>Plan</span>
1832
- <select id="f-renderPlan"></select>
1833
- </label>
1834
- <label class="addrOpt"><input type="checkbox" id="f-renderDb">
1835
- <span>My app needs a database (Render Postgres)</span>
1836
- </label>
1837
- <label class="wizField" id="renderDbPlanWrap" hidden><span>Database plan</span>
1838
- <select id="f-renderDbPlan"></select>
1870
+ <div id="physTeam" hidden>
1871
+ <div class="wizField"><span id="physStartLbl">People at the start, you included</span>
1872
+ <div class="demoStep" role="group" aria-labelledby="physStartLbl">
1873
+ <button type="button" class="btn small ghost" data-phys-count="-1" aria-label="One fewer">−</button>
1874
+ <output id="physStartN" aria-live="polite">2</output>
1875
+ <button type="button" class="btn small ghost" data-phys-count="1" aria-label="One more">+</button>
1876
+ </div>
1877
+ </div>
1878
+ <p class="physGuessLine" id="physGuessLine" aria-live="polite"></p>
1879
+ <label class="wizField physAdjust"><span>Adjust month three</span>
1880
+ <input type="range" id="physExpected" min="2" max="30" step="1" value="5">
1839
1881
  </label>
1882
+ <div class="wizField"><span id="teamLbl">Who can join?</span>
1883
+ <div class="typeCards" id="teamCards" role="group" aria-labelledby="teamLbl">
1884
+ <button type="button" class="tpill" data-team="small-team" aria-pressed="false">People I invite</button>
1885
+ <button type="button" class="tpill" data-team="community" aria-pressed="false">Anyone can apply</button>
1886
+ </div>
1887
+ </div>
1840
1888
  </div>
1841
- <div class="hint" id="h-render" role="status">&nbsp;</div>
1842
1889
  </div>
1843
1890
  </div>
1891
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1844
1892
  </div>
1845
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1846
- </div>
1847
1893
 
1848
- <!-- the extras (tasks 1002339, 1002340): the type's suggested set until the
1849
- owner says otherwise; the server resolves the same bundle from the same
1850
- declaration (ADR 0237), so accepting and changing nothing are one answer.
1851
- The always-on core is stated as a clause, never as dead toggles. -->
1852
- <div class="setRow" data-row="extras">
1853
- <span class="k">Extras</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="wizField"><span id="modLbl">Extras for this project</span>
1857
- <input type="text" id="modQ" placeholder="Search extras…" autocomplete="off" aria-label="Search the extras by name" aria-describedby="modFilterNote">
1858
- <div class="hint" id="modFilterNote" aria-live="polite"></div>
1859
- <div class="typeCards" id="modCards" role="group" aria-labelledby="modLbl"></div>
1894
+ <!-- the extras (tasks 1002339, 1002340): the type's suggested set until the
1895
+ owner says otherwise; the server resolves the same bundle from the same
1896
+ declaration (ADR 0237), so accepting and changing nothing are one answer.
1897
+ The always-on core is stated as a clause, never as dead toggles. -->
1898
+ <div class="setRow" data-row="extras">
1899
+ <span class="k">Extras</span>
1900
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1901
+ <div class="opts" hidden>
1902
+ <div class="wizField"><span id="modLbl">Extras for this project</span>
1903
+ <input type="text" id="modQ" placeholder="Search extras…" autocomplete="off" aria-label="Search the extras by name" aria-describedby="modFilterNote">
1904
+ <div class="hint" id="modFilterNote" aria-live="polite"></div>
1905
+ <div class="typeCards" id="modCards" role="group" aria-labelledby="modLbl"></div>
1906
+ </div>
1907
+ <div class="modWhy" id="modWhy" aria-live="polite"></div>
1908
+ <div class="reassure" id="modCoreNote"></div>
1860
1909
  </div>
1861
- <div class="modWhy" id="modWhy" aria-live="polite"></div>
1862
- <div class="reassure" id="modCoreNote"></div>
1863
1910
  </div>
1911
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1864
1912
  </div>
1865
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1866
- </div>
1867
1913
 
1868
- <!-- team size (question 5, task 1004419): one person, or a team with a count
1869
- and the month-three guess the founder corrects; who can join a team is the
1870
- team shape the extras' preselect reads (task 1002341) -->
1871
- <div class="setRow" data-row="team">
1872
- <span class="k">Team size</span>
1873
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1874
- <div class="opts" hidden>
1875
- <div class="typeCards physCells" role="group" aria-label="One or many">
1876
- <button type="button" class="tpill" data-phys-solo="one" aria-pressed="false">One person</button>
1877
- <button type="button" class="tpill" data-phys-solo="many" aria-pressed="false">A team</button>
1914
+ <!-- rewards (question 6): intent only, pick several; never asked of a founder
1915
+ building alone (the table hides it, and this row goes with it) -->
1916
+ <div class="setRow" data-row="rewards">
1917
+ <span class="k">Rewards</span>
1918
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1919
+ <div class="opts" hidden>
1920
+ <div class="typeCards physCells" role="group" aria-label="Rewards being considered">
1921
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="non-monetary" aria-pressed="false">Learning and fun</button>
1922
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="cash" aria-pressed="false">Cash for work</button>
1923
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="equity" aria-pressed="false">Equity</button>
1924
+ <button type="button" class="tpill" data-phys="reward_intents" data-val="future-payouts" aria-pressed="false">Future payouts</button>
1925
+ </div>
1926
+ <ul class="physWhyList" data-why-list="reward_intents" hidden></ul>
1878
1927
  </div>
1879
- <div id="physTeam" hidden>
1880
- <div class="wizField"><span id="physStartLbl">People at the start, you included</span>
1881
- <div class="demoStep" role="group" aria-labelledby="physStartLbl">
1882
- <button type="button" class="btn small ghost" data-phys-count="-1" aria-label="One fewer">−</button>
1883
- <output id="physStartN" aria-live="polite">2</output>
1884
- <button type="button" class="btn small ghost" data-phys-count="1" aria-label="One more">+</button>
1928
+ </div>
1929
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1930
+ </div>
1931
+
1932
+ <!-- where the app runs (tasks 1004351, 1004352; ADR 0345): the hall is ours to
1933
+ host, so this row is only the owner's APP. "Decide later" is the default.
1934
+ The Render key lives in a variable only: never the draft, a URL or a log. -->
1935
+ <div class="setRow" data-row="app">
1936
+ <span class="k">Hosting</span>
1937
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1938
+ <div class="opts" hidden>
1939
+ <fieldset class="appOpts" id="appOpts">
1940
+ <legend>Where your app runs</legend>
1941
+ <label class="appOpt">
1942
+ <input type="radio" name="appPlatform" value="later" checked>
1943
+ <span><b>Decide later</b>
1944
+ <span>There’s no app to host until your builders write one.</span></span>
1945
+ </label>
1946
+ <label class="appOpt" id="appOptRender">
1947
+ <input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">
1948
+ <span><b>Render</b>
1949
+ <span id="appRenderWhat">We set it up on your own Render account. Render bills you directly.</span></span>
1950
+ </label>
1951
+ </fieldset>
1952
+ <p class="renderWhy" id="renderLater" role="status" hidden>Your new repo has no app yet, so nothing is created on Render now. Set it up from your project’s page once there is one.</p>
1953
+ <div class="renderSetup" id="renderSetup" hidden>
1954
+ <p class="renderWhy">Used once to create your app on your Render account, then thrown away — not kept, not even in this browser. A key can act on your whole Render account. Render bills you directly. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
1955
+ <div class="renderKeyRow">
1956
+ <label class="wizField"><span>Render API key</span>
1957
+ <input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
1958
+ </label>
1959
+ <button type="button" class="btn ghost small" id="renderCheck">Check key</button>
1885
1960
  </div>
1886
- </div>
1887
- <p class="physGuessLine" id="physGuessLine" aria-live="polite"></p>
1888
- <label class="wizField physAdjust"><span>Adjust month three</span>
1889
- <input type="range" id="physExpected" min="2" max="30" step="1" value="5">
1890
- </label>
1891
- <div class="wizField"><span id="teamLbl">Who can join?</span>
1892
- <div class="typeCards" id="teamCards" role="group" aria-labelledby="teamLbl">
1893
- <button type="button" class="tpill" data-team="small-team" aria-pressed="false">People I invite</button>
1894
- <button type="button" class="tpill" data-team="community" aria-pressed="false">Anyone can apply</button>
1961
+ <div class="hint" id="h-renderKey" role="status">&nbsp;</div>
1962
+ <fieldset class="appOpts" id="renderWs" hidden>
1963
+ <legend>Which Render workspace</legend>
1964
+ <div id="renderWsList"></div>
1965
+ </fieldset>
1966
+ <fieldset class="appOpts" id="renderSvc" hidden>
1967
+ <legend>A new app, or one you already run</legend>
1968
+ <div id="renderSvcList"></div>
1969
+ </fieldset>
1970
+ <div class="renderNew" id="renderNew" hidden>
1971
+ <label class="wizField"><span>Your app is built with</span>
1972
+ <select id="f-renderRuntime">
1973
+ <option value="node">Node.js</option><option value="python">Python</option><option value="ruby">Ruby</option>
1974
+ <option value="go">Go</option><option value="rust">Rust</option><option value="elixir">Elixir</option>
1975
+ <option value="docker">Docker — use my repository’s Dockerfile</option>
1976
+ </select>
1977
+ </label>
1978
+ <label class="wizField" id="renderBuildWrap"><span>Build command <small>— Render needs one; the usual one is filled in</small></span>
1979
+ <input type="text" id="f-renderBuild" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm ci">
1980
+ </label>
1981
+ <label class="wizField" id="renderStartWrap"><span>Start command</span>
1982
+ <input type="text" id="f-renderStart" autocomplete="off" spellcheck="false" maxlength="500" placeholder="for example: npm start">
1983
+ </label>
1984
+ <label class="wizField"><span>Plan</span>
1985
+ <select id="f-renderPlan"></select>
1986
+ </label>
1987
+ <label class="addrOpt"><input type="checkbox" id="f-renderDb">
1988
+ <span>My app needs a database (Render Postgres)</span>
1989
+ </label>
1990
+ <label class="wizField" id="renderDbPlanWrap" hidden><span>Database plan</span>
1991
+ <select id="f-renderDbPlan"></select>
1992
+ </label>
1895
1993
  </div>
1994
+ <div class="hint" id="h-render" role="status">&nbsp;</div>
1896
1995
  </div>
1897
1996
  </div>
1898
1997
  </div>
1998
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1899
1999
  </div>
1900
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1901
- </div>
1902
2000
 
1903
- <!-- rewards (question 6): intent only, never asked of a founder building
1904
- alone (the table hides it, and this row goes with it) -->
1905
- <div class="setRow" data-row="rewards">
1906
- <span class="k">Rewards</span>
1907
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1908
- <div class="opts" hidden>
1909
- <div class="typeCards physCells" role="group" aria-label="Rewards being considered">
1910
- <button type="button" class="tpill" data-phys="reward_intents" data-val="non-monetary" aria-pressed="false">Learning and fun</button>
1911
- <button type="button" class="tpill" data-phys="reward_intents" data-val="cash" aria-pressed="false">Cash for work</button>
1912
- <button type="button" class="tpill" data-phys="reward_intents" data-val="equity" aria-pressed="false">Equity</button>
1913
- <button type="button" class="tpill" data-phys="reward_intents" data-val="future-payouts" aria-pressed="false">Future payouts</button>
2001
+ <!-- how much we run (question 7): "Some of it" (hybrid) or "On my machines"
2002
+ (local) reveals the service checklist -->
2003
+ <div class="setRow" data-row="run">
2004
+ <span class="k">We run</span>
2005
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
2006
+ <div class="opts" hidden>
2007
+ <div class="typeCards physCells" role="group" aria-label="How much is run for you">
2008
+ <button type="button" class="tpill" data-phys="framing" data-val="managed" aria-pressed="false">Everything, for you</button>
2009
+ <button type="button" class="tpill" data-phys="framing" data-val="hybrid" aria-pressed="false">Some of it</button>
2010
+ <button type="button" class="tpill" data-phys="framing" data-val="local" aria-pressed="false">On my machines</button>
2011
+ <button type="button" class="tpill" data-phys="framing" data-val="open-source" aria-pressed="false">Free core only</button>
2012
+ </div>
2013
+ <p class="physWhy" data-why="services" hidden></p>
2014
+ <fieldset class="physSuite" id="physSuite" hidden>
2015
+ <legend id="physSuiteLbl">Your hybrid suite</legend>
2016
+ <div class="typeCards physCells physSuiteGrid">
2017
+ <button type="button" class="tpill" data-phys="services" data-val="hosting" aria-pressed="false">Hosting</button>
2018
+ <button type="button" class="tpill" data-phys="services" data-val="labor-payment" aria-pressed="false">Labor payment and HR</button>
2019
+ <button type="button" class="tpill" data-phys="services" data-val="starter-kit" aria-pressed="false">Starting module kit</button>
2020
+ <button type="button" class="tpill" data-phys="services" data-val="marketplace-buy" aria-pressed="false">Module marketplace: buy</button>
2021
+ <button type="button" class="tpill" data-phys="services" data-val="marketplace-sell" aria-pressed="false">Module marketplace: sell</button>
2022
+ <button type="button" class="tpill" data-phys="services" data-val="guild-access" aria-pressed="false">Builder guild access</button>
2023
+ <button type="button" class="tpill" data-phys="services" data-val="guild-create" aria-pressed="false">Create a builder guild</button>
2024
+ <button type="button" class="tpill" data-phys="services" data-val="company-creation" aria-pressed="false">Company creation</button>
2025
+ <button type="button" class="tpill" data-phys="services" data-val="cbaas-consulting" aria-pressed="false">Expert consulting</button>
2026
+ <button type="button" class="tpill" data-phys="services" data-val="public-marketing" aria-pressed="false">Public marketed project</button>
2027
+ </div>
2028
+ <ul class="physWhyList" data-why-list="services" hidden></ul>
2029
+ </fieldset>
1914
2030
  </div>
1915
- <ul class="physWhyList" data-why-list="reward_intents" hidden></ul>
1916
2031
  </div>
2032
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1917
2033
  </div>
1918
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1919
- </div>
1920
2034
 
1921
- <!-- how much we run (question 7): a hybrid or local framing opens its suite -->
1922
- <div class="setRow" data-row="run">
1923
- <span class="k">We run</span>
1924
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1925
- <div class="opts" hidden>
1926
- <div class="typeCards physCells" role="group" aria-label="How much is run for you">
1927
- <button type="button" class="tpill" data-phys="framing" data-val="managed" aria-pressed="false">Everything, for you</button>
1928
- <button type="button" class="tpill" data-phys="framing" data-val="hybrid" aria-pressed="false">Some of it</button>
1929
- <button type="button" class="tpill" data-phys="framing" data-val="local" aria-pressed="false">On my machines</button>
1930
- <button type="button" class="tpill" data-phys="framing" data-val="open-source" aria-pressed="false">Free core only</button>
2035
+ <!-- how finished work ships (task 1004629, ADR 0369): Auto unless the owner
2036
+ says otherwise. It rides the create as ship_mode and becomes the project's
2037
+ own ship mode, which its settings page can change any time. -->
2038
+ <div class="setRow" data-row="ship">
2039
+ <span class="k">Shipping</span>
2040
+ <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
2041
+ <div class="opts" hidden>
2042
+ <fieldset class="appOpts" id="shipOpts">
2043
+ <legend>When the AI finishes a task</legend>
2044
+ <label class="appOpt">
2045
+ <input type="radio" name="shipMode" value="auto" checked>
2046
+ <span><b>Auto (recommended)</b>
2047
+ <span>The AI ships finished work on its own. You don’t run any commands.</span></span>
2048
+ </label>
2049
+ <label class="appOpt">
2050
+ <input type="radio" name="shipMode" value="manual">
2051
+ <span><b>Manual</b>
2052
+ <span>The AI stops before each ship and waits for you to approve it.</span></span>
2053
+ </label>
2054
+ </fieldset>
1931
2055
  </div>
1932
- <p class="physWhy" data-why="services" hidden></p>
1933
- <fieldset class="physSuite" id="physSuite" hidden>
1934
- <legend id="physSuiteLbl">Your hybrid suite</legend>
1935
- <div class="typeCards physCells physSuiteGrid">
1936
- <button type="button" class="tpill" data-phys="services" data-val="hosting" aria-pressed="false">Hosting</button>
1937
- <button type="button" class="tpill" data-phys="services" data-val="labor-payment" aria-pressed="false">Labor payment and HR</button>
1938
- <button type="button" class="tpill" data-phys="services" data-val="starter-kit" aria-pressed="false">Starting module kit</button>
1939
- <button type="button" class="tpill" data-phys="services" data-val="marketplace-buy" aria-pressed="false">Module marketplace: buy</button>
1940
- <button type="button" class="tpill" data-phys="services" data-val="marketplace-sell" aria-pressed="false">Module marketplace: sell</button>
1941
- <button type="button" class="tpill" data-phys="services" data-val="guild-access" aria-pressed="false">Builder guild access</button>
1942
- <button type="button" class="tpill" data-phys="services" data-val="guild-create" aria-pressed="false">Create a builder guild</button>
1943
- <button type="button" class="tpill" data-phys="services" data-val="company-creation" aria-pressed="false">Company creation</button>
1944
- <button type="button" class="tpill" data-phys="services" data-val="cbaas-consulting" aria-pressed="false">Expert consulting</button>
1945
- <button type="button" class="tpill" data-phys="services" data-val="public-marketing" aria-pressed="false">Public marketed project</button>
1946
- </div>
1947
- <ul class="physWhyList" data-why-list="services" hidden></ul>
1948
- </fieldset>
1949
2056
  </div>
2057
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1950
2058
  </div>
1951
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1952
- </div>
1953
2059
 
1954
- <!-- how finished work ships (task 1004629, ADR 0369): Auto unless the owner
1955
- says otherwise. It rides the create as ship_mode and becomes the project's
1956
- own ship mode, which its settings page can change any time. -->
1957
- <div class="setRow" data-row="ship">
1958
- <span class="k">Shipping</span>
1959
- <div class="v"><span class="rowV" data-row-v></span><small class="rowS" data-row-s></small>
1960
- <div class="opts" hidden>
1961
- <fieldset class="appOpts" id="shipOpts">
1962
- <legend>When the AI finishes a task</legend>
1963
- <label class="appOpt">
1964
- <input type="radio" name="shipMode" value="auto" checked>
1965
- <span><b>Auto (recommended)</b>
1966
- <span>The AI ships finished work on its own. You don’t run any commands.</span></span>
1967
- </label>
1968
- <label class="appOpt">
1969
- <input type="radio" name="shipMode" value="manual">
1970
- <span><b>Manual</b>
1971
- <span>The AI stops before each ship and waits for you to approve it.</span></span>
1972
- </label>
1973
- </fieldset>
2060
+ <!-- a look (task 1004421, ADR 0358): colours, a logo and the night tint, so
2061
+ builders in several projects always know which hall they are in.
2062
+ lookKit.buildLookPicker fills it — the same picker the manage page's "Its
2063
+ look" uses — and the choice is sent right after Create (sendLook). -->
2064
+ <div class="setRow" data-row="look">
2065
+ <span class="k">Look</span>
2066
+ <div class="v"><span class="rowV"><span class="swDot" id="lookDot" aria-hidden="true"></span><span data-row-v></span></span><small class="rowS" data-row-s></small>
2067
+ <div class="opts" hidden>
2068
+ <div class="lookPick" id="wizLook"></div>
2069
+ </div>
1974
2070
  </div>
2071
+ <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change</button></span>
1975
2072
  </div>
1976
- <span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
1977
2073
  </div>
1978
2074
  </div>
1979
- <div class="hint" id="h-review">&nbsp;</div>
1980
- <div class="nav">
1981
- <span></span>
1982
- <button type="submit" class="btn" id="reviewGo">Looks right, continue</button>
1983
- </div>
1984
- </form>
1985
- </section>
1986
2075
 
1987
- <!-- 5 · a look, then Create (task 1004421, ADR 0358): colours and a logo so builders
1988
- in several projects always know which hall they are in. lookKit.buildLookPicker
1989
- fills it — the same picker the manage page's "Its look" uses — and the choice is
1990
- sent right after Create (sendLook). -->
1991
- <section class="panel" id="panel-look" aria-labelledby="look-h">
1992
- <h1 id="look-h">Give it a look.</h1>
1993
- <p class="lede">So builders always know which project they’re in.</p>
1994
- <div class="lookPick" id="wizLook"></div>
1995
- <p class="wizRecap" id="wizRecap"></p>
1996
- <div class="alert" id="createAlert" role="alert"></div>
1997
- <div class="nav">
1998
- <span></span>
1999
- <button type="button" class="btn big" id="doCreate">Create my project</button>
2076
+ <!-- the terms (tasks 1004417 and 1004648): the one required tick, above Create. It
2077
+ rides the create as `terms_acknowledged` and the SERVER records which terms
2078
+ version it was (modules/provisioning/terms-ack.js), so this page never names one. -->
2079
+ <label class="termsAck">
2080
+ <input type="checkbox" id="termsAck" aria-describedby="h-terms">
2081
+ <span>I can sign for this project, and I accept the <a class="termsRead" href="/terms" target="_blank" rel="noopener">terms</a>.</span>
2082
+ </label>
2083
+ <div class="hint" id="h-terms">&nbsp;</div>
2000
2084
  </div>
2001
- <p class="finePrint">Right after, you’ll get the few steps only you can do.</p>
2002
- </section>
2003
2085
 
2004
- <!-- 6 · done · the human steps -->
2005
- <section class="panel" id="panel-done" aria-labelledby="done-h">
2006
- <div class="doneHead">
2007
- <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>
2008
- <h1 id="done-h">Setting up your project</h1>
2009
- </div>
2010
- <p class="lede" id="doneLede"></p>
2011
- <p class="hint" id="doneRender" role="status" hidden></p>
2012
- <p class="hint" id="doneLook" role="status" hidden></p>
2013
- <div id="doneApp" hidden></div>
2014
- <!-- the STABLE live region: the strip inside is rebuilt every 5s tick,
2015
- and a live region that is itself replaced never announces -->
2016
- <div id="liveProgress" role="status" aria-live="polite"></div>
2017
- <!-- the wait (task 1004578): one status line, a thin bar, and the cloud
2018
- hop — one cloud per public project on the platform, in its planet's
2019
- colour. Shown only while data-moment="wait"; under reduced motion
2020
- the sky is drawn once and stands still. -->
2021
- <div class="hop" id="hop">
2022
- <p class="hopStatus" id="hopStatus" role="status" aria-live="polite"></p>
2023
- <div class="hopBar" aria-hidden="true"><span id="hopBarFill"></span></div>
2024
- <div class="hopStage">
2025
- <canvas id="hopCanvas" tabindex="0" role="img" aria-label="A cloud-hopping game to pass the wait: each cloud is a public project on the platform. Arrow keys steer, space jumps."></canvas>
2026
- </div>
2027
- <div class="hopRow">
2028
- <button type="button" class="btn small ghost" id="hopLeft" aria-label="Move left">←</button>
2029
- <button type="button" class="btn small ghost" id="hopJump">Jump</button>
2030
- <button type="button" class="btn small ghost" id="hopRight" aria-label="Move right">→</button>
2031
- <span class="hopHint">Arrows or A and D steer, space jumps.</span>
2032
- <span class="hopScore" id="hopScore" aria-live="polite"></span>
2033
- </div>
2034
- <p class="hopWhile" id="hopWhile" hidden>While you wait, <button type="button" class="linklike" id="hopSignin" aria-expanded="false">set up GitHub sign-in</button>.</p>
2086
+ <!-- Create, with the one-line recap beside it; the reason it waits says itself
2087
+ under it while it does -->
2088
+ <div class="createBar">
2089
+ <button type="button" class="btn big" id="doCreate">Create my project</button>
2090
+ <span class="wizRecap" id="wizRecap"></span>
2035
2091
  </div>
2036
- <!-- the invite step (task 1002346, BV1.R27; ADR 0249) — OPTIONAL, and it stands
2037
- HERE because an invite needs a project that already exists. Hidden until the
2038
- created record carries an id and a slug; the script is the only thing that
2039
- shows it. -->
2040
- <div class="inviteBox" id="wizInvite" hidden>
2041
- <h2 class="stepsHead">Invite builders</h2>
2042
- <p class="finePrint">Optional. Invite people by GitHub username. The invite comes from your project’s hall, and you can invite anyone later.</p>
2043
- <div class="attach">
2044
- <label class="vh" for="wizInviteWho">GitHub username</label>
2045
- <input type="text" id="wizInviteWho" placeholder="GitHub username" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
2046
- <button type="button" class="btn small" data-invite-send="wizInvite">Invite</button>
2047
- <button type="button" class="btn small ghost" data-invite-skip="wizInvite">Skip</button>
2092
+ <p class="hint createWhy" id="createWhy" aria-live="polite">&nbsp;</p>
2093
+ <div class="alert" id="createAlert" role="alert"></div>
2094
+
2095
+ <!-- 03 · Connect GitHub: waits until the project exists, then drives the steps only
2096
+ the founder can do (today's onboard-plan steps, task 1002694), drawn as one
2097
+ checklist that ticks itself from what the server has verified. -->
2098
+ <div class="part waiting" id="panel-done" aria-labelledby="done-part-h">
2099
+ <h2 id="done-part-h"><span class="n">03</span>Connect GitHub <span class="state" id="ghState">opens once your project exists</span></h2>
2100
+ <p class="partSub">One trip to GitHub. Approve the app, pick your repo, and you land back here. We tick each step as it happens, from any tab or device.</p>
2101
+ <div class="doneBody" id="doneBody">
2102
+ <p class="hint" id="doneRender" role="status" hidden></p>
2103
+ <p class="hint" id="doneLook" role="status" hidden></p>
2104
+ <div id="doneApp" hidden></div>
2105
+ <ol class="humanSteps" id="humanSteps"></ol>
2106
+ <!-- the STABLE live region: the strip inside is rebuilt every 5s tick,
2107
+ and a live region that is itself replaced never announces -->
2108
+ <div id="liveProgress" role="status" aria-live="polite"></div>
2109
+ <!-- the wait (task 1004578): one status line, a thin bar, and the cloud
2110
+ hop — one cloud per public project on the platform, in its planet's
2111
+ colour. Shown only while data-moment="wait"; under reduced motion
2112
+ the sky is drawn once and stands still. -->
2113
+ <div class="hop" id="hop">
2114
+ <p class="hopStatus" id="hopStatus" role="status" aria-live="polite"></p>
2115
+ <div class="hopBar" aria-hidden="true"><span id="hopBarFill"></span></div>
2116
+ <div class="hopStage">
2117
+ <canvas id="hopCanvas" tabindex="0" role="img" aria-label="A cloud-hopping game to pass the wait: each cloud is a public project on the platform. Arrow keys steer, space jumps."></canvas>
2118
+ </div>
2119
+ <div class="hopRow">
2120
+ <button type="button" class="btn small ghost" id="hopLeft" aria-label="Move left">←</button>
2121
+ <button type="button" class="btn small ghost" id="hopJump">Jump</button>
2122
+ <button type="button" class="btn small ghost" id="hopRight" aria-label="Move right">→</button>
2123
+ <span class="hopHint">Arrows or A and D steer, space jumps.</span>
2124
+ <span class="hopScore" id="hopScore" aria-live="polite"></span>
2125
+ </div>
2126
+ </div>
2127
+ <div class="alive">
2128
+ <h2 class="aliveH" id="done-h">Setting up your project</h2>
2129
+ <p class="lede" id="doneLede"></p>
2130
+ <!-- the invite step (task 1002346, BV1.R27; ADR 0249) — OPTIONAL, and it stands
2131
+ HERE because an invite needs a project that already exists. Hidden until the
2132
+ created record carries an id and a slug; the script is the only thing that
2133
+ shows it. -->
2134
+ <div class="inviteBox" id="wizInvite" hidden>
2135
+ <p class="finePrint">Invite your first builders. They become founding builders. Optional: the invite comes from your project’s hall, and you can invite anyone later.</p>
2136
+ <div class="attach">
2137
+ <label class="vh" for="wizInviteWho">GitHub handle</label>
2138
+ <input type="text" id="wizInviteWho" placeholder="GitHub handle" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
2139
+ <button type="button" class="btn small" data-invite-send="wizInvite">Invite</button>
2140
+ <button type="button" class="btn small ghost" data-invite-skip="wizInvite">Skip</button>
2141
+ </div>
2142
+ <div class="inviteWhy" id="wizInviteWhy"></div>
2143
+ <p class="inviteWhy" id="wizDemoTeam" hidden></p>
2144
+ <ul class="suggList" id="wizInviteSugg"></ul>
2145
+ <ul class="inviteList" id="wizInviteList"></ul>
2146
+ <div class="projMsg" id="wizInviteMsg" role="status" aria-live="polite"></div>
2147
+ </div>
2148
+ <!-- the way back, the doneModules idiom: an optional step is a default
2149
+ rather than a dead end only if the panel names where it lives after -->
2150
+ <p class="samePath" id="wizInviteLater"></p>
2151
+ <!-- the extras row was a default (task 1002342, BV1.R23) — one line saying
2152
+ where they live afterwards, so accepting them is never a dead end -->
2153
+ <p class="samePath" id="doneModules"></p>
2154
+ <p class="samePath">These steps are the same however a project is created.</p>
2155
+ </div>
2048
2156
  </div>
2049
- <div class="inviteWhy" id="wizInviteWhy"></div>
2050
- <p class="inviteWhy" id="wizDemoTeam" hidden></p>
2051
- <ul class="suggList" id="wizInviteSugg"></ul>
2052
- <ul class="inviteList" id="wizInviteList"></ul>
2053
- <div class="projMsg" id="wizInviteMsg" role="status" aria-live="polite"></div>
2054
- </div>
2055
- <!-- the way back, the doneModules idiom: an optional step is a default
2056
- rather than a dead end only if the panel names where it lives after -->
2057
- <p class="samePath" id="wizInviteLater"></p>
2058
- <h2 class="stepsHead" id="doneStepsHead">Your steps — the parts only you can play</h2>
2059
- <ol class="humanSteps" id="humanSteps"></ol>
2060
- <!-- the arrival shows the steps one at a time (task 1004578) -->
2061
- <div class="stepPager" id="stepPager">
2062
- <span class="stepCount" id="stepCount" aria-live="polite"></span>
2063
- <button type="button" class="btn small ghost" id="stepBack">← Back</button>
2064
- <button type="button" class="btn small" id="stepNext">Continue</button>
2065
2157
  </div>
2066
- <!-- the extras row was a default (task 1002342, BV1.R23) — one line saying
2067
- where they live afterwards, so accepting them is never a dead end -->
2068
- <p class="samePath" id="doneModules"></p>
2069
- <p class="samePath">These steps are the same however a project is created.</p>
2070
2158
  </section>
2071
2159
  </div>
2072
2160
  </div>
@@ -4486,13 +4574,12 @@ summary{min-height:24px;padding:3px 0;}
4486
4574
  marking the skipped ones done. Bump this key on EVERY renumbering
4487
4575
  (-v6: the front door's two screens took steps 1 and 2, task 1004417;
4488
4576
  -v7: the Look step took 10, so review is 11 and done 12, task 1004421;
4489
- -v9: the six blended screens, task 1004504). */
4490
- /* `termsAck` is the front door's one tick (task 1004417). It is the founder's
4491
- own answer, so it lives in the draft like every other one and survives the
4492
- GitHub round-trip; the server, not this page, names the terms version. */
4493
- /* `confirmed` is the review screen's own answer (task 1004504): which prefilled rows
4494
- the founder has looked at and accepted or changed. Create waits for all of them. */
4495
- var DRAFT_KEY = 'cb-create-draft-v9';
4577
+ -v9: the six blended screens, task 1004504;
4578
+ -v10: one page, task 1004648 — gate 0, the page 1, created 2). */
4579
+ /* `termsAck` is the required tick above Create (tasks 1004417 and 1004648). It is
4580
+ the founder's own answer, so it lives in the draft like every other one and
4581
+ survives the GitHub round-trip; the server, not this page, names the terms version. */
4582
+ var DRAFT_KEY = 'cb-create-draft-v10';
4496
4583
  var CREATED_KEY = 'cb-created-instance';
4497
4584
  /* `description` + `teamShape` are the optional About step (task 1002334) —
4498
4585
  both legitimately stay empty, and empty is what the create request omits. */
@@ -4501,7 +4588,7 @@ summary{min-height:24px;padding:3px 0;}
4501
4588
  request omits the field), [] = the owner switched them all off, [...] =
4502
4589
  what they picked. Never seed it with the preselect — that would store an
4503
4590
  answer nobody gave. */
4504
- 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', shipMode: 'auto', physics: null, demo: null, look: null, termsAck: false, confirmed: {} };
4591
+ var state = { step: 0, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', modeChosen: false, owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', shipMode: 'auto', physics: null, demo: null, look: null, termsAck: false };
4505
4592
  try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
4506
4593
  var authKnown = false; /* flips true when the boot's /me settles, either way */
4507
4594
 
@@ -4515,7 +4602,7 @@ summary{min-height:24px;padding:3px 0;}
4515
4602
  state.name = handedName.trim().slice(0, 80);
4516
4603
  state.slug = deriveSlug(state.name); state.slugTouched = false;
4517
4604
  state.repo = deriveRepo(state.name); state.repoTouched = false;
4518
- state.step = state.step >= 1 && state.step <= 5 ? state.step : 1;
4605
+ state.step = 1;
4519
4606
  try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
4520
4607
  }
4521
4608
  } catch (e) {}
@@ -4706,93 +4793,117 @@ summary{min-height:24px;padding:3px 0;}
4706
4793
  try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
4707
4794
  }
4708
4795
 
4709
- /* ── step navigation ──
4710
- The six blended screens (task 1004504, the design of record
4711
- docs/design/mocks/combined-onboarding/): gate 0 · fork 1 (demo or real, the terms
4712
- as its one tick) · what 2 (name, kind, a sentence) · checks 3 (the planet-physics
4713
- answers that change something) · review 4 (every prefilled answer, confirmed row by
4714
- row) · look 5 (then Create) · done 6. No rail: the eyebrow names the screen and one
4715
- Back walks backwards. A demo skips the checks — its answers are the demo's own. */
4716
- var panels = { 0: 'panel-gate', 1: 'panel-fork', 2: 'panel-what', 3: 'panel-checks', 4: 'panel-review', 5: 'panel-look', 6: 'panel-done' };
4717
- var SCREEN = { 0: 'signin', 1: 'fork', 2: 'what', 3: 'checks', 4: 'review', 5: 'look', 6: 'done' };
4796
+ /* ── the page (task 1004648, the design of record docs/design/mocks/day-night-rooms/) ──
4797
+ gate 0 (sign in) · the page 1 (01 your project, 02 how it's run, the terms tick and
4798
+ Create) · created 2 (the same page: parts 01 and 02 stand down behind the recap and
4799
+ 03 Connect GitHub goes live). There are no screens to walk, so there is no Back. */
4800
+ var panels = { 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' };
4801
+ var SCREEN = { 0: 'signin', 1: 'page', 2: 'done' };
4718
4802
  function eyebrowFor(step) {
4719
4803
  if (step === 0) return 'Sign in';
4720
- if (step === 6) return 'Founding';
4721
- if (step === 1 && !$('demoSetup').hidden) return 'Demo';
4722
- return state.demo && step > 1 ? 'New demo' : 'New project';
4723
- }
4724
- /* where Back goes from each screen: the demo's own screen first, then the one before */
4725
- function prevStep(step) {
4726
- if (step === 4 && state.demo) return 2;
4727
- return step - 1;
4804
+ if (step === 2) return 'Founding';
4805
+ return state.demo ? 'New demo' : 'New project';
4728
4806
  }
4729
4807
  function show(step, moveFocus) {
4730
4808
  state.step = step;
4731
- Object.keys(panels).forEach(function (k) {
4732
- $(panels[k]).classList.toggle('on', Number(k) === step);
4733
- });
4809
+ var created = step === 2;
4810
+ $('panel-gate').classList.toggle('on', step === 0);
4811
+ $('panel-page').classList.toggle('on', step !== 0);
4812
+ $('wizParts').hidden = created;
4813
+ $('pageSub').hidden = created;
4814
+ var done = $('panel-done');
4815
+ done.classList.toggle('on', created);
4816
+ done.classList.toggle('waiting', !created);
4817
+ done.classList.toggle('live', created);
4734
4818
  saveDraft();
4735
- /* the room's seed grows a little on each screen (CSS reads data-screen) */
4819
+ /* the room's seed grows as the page fills in (CSS reads data-screen + data-fill) */
4736
4820
  $('wizRoom').setAttribute('data-screen', SCREEN[step]);
4737
4821
  $('wizEyebrow').textContent = eyebrowFor(step);
4738
- $('wizBack').hidden = step === 0 || step === 6 || (step === 1 && $('demoSetup').hidden);
4739
4822
  $('demoTag').hidden = !state.demo;
4740
4823
  /* focus moves ONLY on a deliberate step (the hub rule setView already
4741
4824
  applies to this view: never on boot, never on a background settle) */
4742
4825
  if (moveFocus) {
4743
- var h = $(panels[step]).querySelector('h1');
4826
+ var h = created ? $('done-part-h') : $(panels[step]).querySelector('h1');
4744
4827
  if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
4745
4828
  }
4746
- if (step === 1) paintDoors();
4747
- /* the cloud hop's motion runs only while its panel is the one on show
4748
- (task 1004578) — a start/stop, not a paint: renderDone paints the panel */
4749
- if (typeof hopStart === 'function') { if ($('panel-done').classList.contains('on')) hopStart(); else hopStop(); }
4750
- if (step === 3) { physLoadAccountOnce(); paintChecks(); }
4751
- /* the review hosts the old steps' own controls, so it repaints them on entry.
4752
- The repo probe is authenticated — never fire it before /me settles (a pre-auth
4753
- probe 401s for strangers and could seed the draft's owner with a placeholder) */
4754
- if (step === 4) {
4755
- physDefaults();
4756
- renderModules();
4757
- syncHome();
4758
- if (authKnown) ensureRepoAccess();
4759
- /* re-check on every entry: a handle free ten minutes ago may not be now (N10) */
4760
- checkSlugAvailable(); renderAddrPanel();
4761
- syncAppStep(); syncRenderSetup();
4762
- paintSetup();
4829
+ /* the cloud hop's motion runs only while part 03 is live (task 1004578) — a
4830
+ start/stop, not a paint: renderDone paints the part */
4831
+ if (typeof hopStart === 'function') { if (created) hopStart(); else hopStop(); }
4832
+ if (step === 1) paintPage();
4833
+ else paintCreate();
4834
+ if (typeof stepsPaint === 'function') stepsPaint();
4835
+ }
4836
+ /* The page hosts every control at once, so it paints them all on entry. The repo
4837
+ probe is authenticated — never fire it before /me settles (a pre-auth probe 401s
4838
+ for strangers and could seed the draft's owner with a placeholder). */
4839
+ function paintPage() {
4840
+ physLoadAccountOnce();
4841
+ physDefaults();
4842
+ paintChecks();
4843
+ renderModules();
4844
+ syncHome();
4845
+ if (authKnown) ensureRepoAccess();
4846
+ /* re-check on every entry: a handle free ten minutes ago may not be now (N10) */
4847
+ checkSlugAvailable(); renderAddrPanel();
4848
+ syncAppStep(); syncRenderSetup(); syncShipStep();
4849
+ renderLook();
4850
+ paintSetup();
4851
+ paintCreate();
4852
+ }
4853
+
4854
+ /* ── the room's sky (task 1004648): day from 07:00 to 18:59 on the viewer's own
4855
+ clock, night otherwise, re-checked each minute. Only an attribute changes here;
4856
+ CSS draws both rooms, and the kill switch stops their motion. ── */
4857
+ function skyAt(d) { var h = d.getHours(); return h >= 7 && h < 19 ? 'day' : 'night'; }
4858
+ function paintSky() { root.setAttribute('data-sky', skyAt(new Date())); }
4859
+ paintSky();
4860
+ setInterval(paintSky, 60000);
4861
+
4862
+ /* ── Create waits for two things only (task 1004648): a name, and the terms tick.
4863
+ The reason it waits is said under it, in place; the summary rows never gate it. ── */
4864
+ var createBusy = false;
4865
+ function createWhy() {
4866
+ if (!String(state.name || '').trim()) return 'Give it a name first.';
4867
+ if (!state.termsAck) return 'Tick the terms to create your project.';
4868
+ return '';
4869
+ }
4870
+ function paintCreate() {
4871
+ var cb = $('doCreate');
4872
+ if (state.step === 2) {
4873
+ cb.disabled = true; cb.textContent = 'Created';
4874
+ $('createWhy').textContent = ' ';
4875
+ } else if (!createBusy) {
4876
+ var why = createWhy();
4877
+ cb.disabled = !!why; cb.textContent = 'Create my project';
4878
+ $('createWhy').textContent = why || ' ';
4763
4879
  }
4764
- /* a resumed draft can land PAST the review with the repo probe never run — Create
4765
- would then file a repo it promised to make but never did */
4766
- else if (step > 4 && step < 6 && authKnown && repoAccess === null) ensureRepoAccess();
4767
- if (step === 5) { renderLook(); renderRecap(); }
4768
- }
4769
- $('wizBack').addEventListener('click', function () {
4770
- if (state.step === 1 && !$('demoSetup').hidden) { demoShow('intro'); $('wizDemo').focus(); return; }
4771
- if (state.step === 3 && !$('checksStop').hidden) { checksShow('main'); return; }
4772
- if (state.step > 1 && state.step < 6) show(prevStep(state.step), true);
4773
- });
4774
-
4775
- /* ── the front door (tasks 1004417 and 1004504) ── the tick mirrors into the draft,
4776
- and both doors wait for it: nothing past the fork until the terms are accepted */
4777
- function paintDoors() {
4778
- $('wizDemo').disabled = !state.termsAck;
4779
- $('wizStart').disabled = !state.termsAck;
4880
+ renderRecap();
4881
+ paintFill();
4882
+ }
4883
+ /* how far the page has filled in: the seed in the window grows with it */
4884
+ function paintFill() {
4885
+ var a = phys().answers, n = 0;
4886
+ if (String(state.name || '').trim()) n++;
4887
+ if (state.projType) n++;
4888
+ if (a.home_country || a.builder_geography || a.entity || a.minors_on_board === true || a.minors_on_board === false) n++;
4889
+ if (state.termsAck) n++;
4890
+ $('wizRoom').setAttribute('data-fill', String(n));
4780
4891
  }
4781
4892
  $('termsAck').checked = !!state.termsAck;
4782
4893
  $('termsAck').addEventListener('change', function () {
4783
4894
  state.termsAck = !!this.checked;
4784
4895
  if (state.termsAck) { $('h-terms').textContent = ' '; $('h-terms').className = 'hint'; }
4785
- paintDoors();
4786
4896
  saveDraft();
4897
+ paintCreate();
4787
4898
  });
4788
- paintDoors();
4789
4899
  /* ── the demo route (task 1004420, BV2.PS09) ──
4790
- "Try the demo" opens one screen inside the fork: the people, the hours each, the
4791
- idea, a first read of what fits, and up to two teammates. Continue goes on through
4792
- the ordinary flow from "What are you making?": a demo is a normal project marked
4793
- "demo" (D9), so the create carries `demo` and the server applies the demo's picked
4794
- answers and makes it private to the team — which is why it skips the checks. The
4795
- time box is soft (D3). The limits and the fit sentences are copies of
4900
+ "Try a small demo instead", the small link under the heading, opens the demo in
4901
+ place at the top of part 01: the people, the hours each, the idea, a first read of
4902
+ what fits, and up to two teammates. "Use these" marks the page "demo" and the rest
4903
+ of the page is unchanged: a demo is a normal project marked "demo" (D9), so the
4904
+ create carries `demo` and the server applies the demo's picked answers and makes it
4905
+ private to the team — which is why its quick checks stand down. The time box is
4906
+ soft (D3). The limits and the fit sentences are copies of
4796
4907
  modules/provisioning/demo.js, held equal by tests/wizard_demo.mjs. */
4797
4908
  var DEMO_LIMITS = { people: [1, 12], hours: [1, 40] };
4798
4909
  var demoDraft = state.demo
@@ -4822,13 +4933,10 @@ summary{min-height:24px;padding:3px 0;}
4822
4933
  $('demoInvites').hidden = others === 0;
4823
4934
  $('demoInvite2').closest('label').hidden = others < 2;
4824
4935
  }
4825
- function demoShow(which) {
4826
- $('forkIntro').hidden = which !== 'intro';
4827
- $('demoSetup').hidden = which !== 'setup';
4828
- $('wizDemo').setAttribute('aria-expanded', which === 'intro' ? 'false' : 'true');
4829
- $('wizEyebrow').textContent = eyebrowFor(state.step);
4830
- $('wizBack').hidden = which === 'intro';
4831
- if (which !== 'intro') {
4936
+ function demoShow(open) {
4937
+ $('demoSetup').hidden = !open;
4938
+ $('wizDemo').setAttribute('aria-expanded', open ? 'true' : 'false');
4939
+ if (open) {
4832
4940
  paintDemo();
4833
4941
  var h = $('demo-h1');
4834
4942
  h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false });
@@ -4837,7 +4945,15 @@ summary{min-height:24px;padding:3px 0;}
4837
4945
  $('demoIdea').value = demoDraft.idea;
4838
4946
  $('demoInvite1').value = demoDraft.invites[0] || '';
4839
4947
  $('demoInvite2').value = demoDraft.invites[1] || '';
4840
- $('wizDemo').addEventListener('click', function () { if (state.termsAck) demoShow('setup'); });
4948
+ $('wizDemo').addEventListener('click', function () { demoShow($('demoSetup').hidden); });
4949
+ /* back to a real project: the demo's answers go, and the quick checks come back */
4950
+ $('demoCancel').addEventListener('click', function () {
4951
+ state.demo = null;
4952
+ saveDraft();
4953
+ demoShow(false);
4954
+ show(1);
4955
+ $('wizDemo').focus();
4956
+ });
4841
4957
  root.querySelectorAll('[data-demo-step]').forEach(function (b) {
4842
4958
  b.addEventListener('click', function () {
4843
4959
  var key = b.getAttribute('data-demo-step');
@@ -4860,10 +4976,10 @@ summary{min-height:24px;padding:3px 0;}
4860
4976
  $('f-desc').value = state.description;
4861
4977
  }
4862
4978
  state.teamShape = demoDraft.people > 1 ? 'small-team' : 'solo';
4863
- /* the demo's own rows are the demo's answers, so the review asks again */
4864
- state.confirmed = {};
4865
4979
  saveDraft();
4866
- show(2, true);
4980
+ demoShow(false);
4981
+ show(1);
4982
+ $('newName').focus();
4867
4983
  });
4868
4984
  /* the done panel's invite step names the demo's teammates (task 1004420): the hub
4869
4985
  holds no invite of its own, so the founder sends each from the project's own box.
@@ -4937,17 +5053,31 @@ summary{min-height:24px;padding:3px 0;}
4937
5053
  state.repo = deriveRepo(state.name);
4938
5054
  $('f-repo').value = state.repo;
4939
5055
  }
4940
- var h = $('h-name');
4941
- if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
4942
- else { h.innerHTML = '&nbsp;'; }
4943
- /* the repo and the address follow the name, so the review asks about them again */
4944
- unconfirm(['code', 'address']);
5056
+ paintNameHint();
5057
+ /* the repo and the address follow the name, so their rows repaint with it */
5058
+ if (!state.slugTouched) checkSlugAvailable();
5059
+ if (!state.repoTouched && repoAccess === true && state.mode !== 'adopt') { $('ghNewName').value = state.repo; updateFreshPreview(); }
4945
5060
  saveDraft();
5061
+ renderAddrPanel();
5062
+ paintSetup();
5063
+ paintCreate();
4946
5064
  });
4947
- if (state.name && state.slug) $('h-name').innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>';
5065
+ /* the handle under the name, built from nodes: the name is typed, so never markup */
5066
+ function paintNameHint() {
5067
+ var h = $('h-name');
5068
+ h.className = 'hint';
5069
+ h.textContent = '';
5070
+ if (!state.slug) { h.textContent = ' '; return; }
5071
+ h.appendChild(document.createTextNode('its handle on the platform: '));
5072
+ var code = document.createElement('code');
5073
+ code.textContent = state.slug;
5074
+ h.appendChild(code);
5075
+ }
5076
+ if (state.name && state.slug) paintNameHint();
4948
5077
 
4949
- function setMode(mode) {
5078
+ function setMode(mode, chosen) {
4950
5079
  state.mode = mode;
5080
+ if (chosen) state.modeChosen = true;
4951
5081
  $('c-green').setAttribute('aria-pressed', String(mode === 'greenfield'));
4952
5082
  $('c-adopt').setAttribute('aria-pressed', String(mode === 'adopt'));
4953
5083
  $('adoptNote').classList.toggle('on', mode === 'adopt');
@@ -4955,8 +5085,8 @@ summary{min-height:24px;padding:3px 0;}
4955
5085
  syncHome();
4956
5086
  if (typeof paintSetup === 'function') paintSetup();
4957
5087
  }
4958
- $('c-green').addEventListener('click', function () { setMode('greenfield'); });
4959
- $('c-adopt').addEventListener('click', function () { setMode('adopt'); });
5088
+ $('c-green').addEventListener('click', function () { setMode('greenfield', true); });
5089
+ $('c-adopt').addEventListener('click', function () { setMode('adopt', true); });
4960
5090
  setMode(state.mode);
4961
5091
 
4962
5092
  /* ── the kind (task 1002772) ── a declaration: the pill is the answer */
@@ -4968,11 +5098,12 @@ summary{min-height:24px;padding:3px 0;}
4968
5098
  root.querySelectorAll('#typeCards .tpill').forEach(function (b) {
4969
5099
  b.addEventListener('click', function () {
4970
5100
  state.projType = b.getAttribute('data-type');
4971
- $('h-projtype').innerHTML = '&nbsp;'; $('h-projtype').className = 'hint';
4972
5101
  paintType(state.projType);
4973
- /* the suggested extras follow the kind, so the review asks about them again */
4974
- unconfirm(['extras']);
4975
5102
  saveDraft();
5103
+ /* the suggested extras follow the kind, so their row repaints with it */
5104
+ renderModules();
5105
+ paintSetup();
5106
+ paintCreate();
4976
5107
  });
4977
5108
  });
4978
5109
  paintType(state.projType || null);
@@ -4981,7 +5112,7 @@ summary{min-height:24px;padding:3px 0;}
4981
5112
  The sentence is optional and half the publish gate (ADR 0182 D4): it is what puts
4982
5113
  the project on the public map, which the field's own label says. The team shape is
4983
5114
  what the extras' preselect reads: "solo" follows a team of one, and a team says who
4984
- can join (people invited, or anyone applying) on the review's Team size row. */
5115
+ can join (people invited, or anyone applying) on part 02's Team row. */
4985
5116
  function teamLabel(t) {
4986
5117
  var b = t && root.querySelector('#teamCards .tpill[data-team="' + t + '"]');
4987
5118
  return b ? b.textContent : '';
@@ -5000,7 +5131,6 @@ summary{min-height:24px;padding:3px 0;}
5000
5131
  b.addEventListener('click', function () {
5001
5132
  state.teamShape = b.getAttribute('data-team');
5002
5133
  paintTeam(state.teamShape);
5003
- unconfirm(['extras']);
5004
5134
  saveDraft();
5005
5135
  paintSetup();
5006
5136
  });
@@ -5048,7 +5178,7 @@ summary{min-height:24px;padding:3px 0;}
5048
5178
  exact dishonesty the About row's "off the public map" clause exists to
5049
5179
  prevent, one screen before Create. */
5050
5180
  function repaintForBundles() {
5051
- if (state.step === 4) { renderModules(); paintSetup(); }
5181
+ if (state.step === 1) { renderModules(); paintSetup(); }
5052
5182
  }
5053
5183
  /* The row for the declared type, or null before the read lands / for a type
5054
5184
  the table has no row for (including no type at all). */
@@ -5439,9 +5569,9 @@ summary{min-height:24px;padding:3px 0;}
5439
5569
  });
5440
5570
 
5441
5571
  /* ── planet physics (tasks 1004418 and 1004419, BV2.PS07 + PS11; regrouped by task 1004504) ──
5442
- Geography, ages and entity are the quick checks (screen 3); the project's state,
5443
- the builders, the rewards and the framing are rows of the review (screen 4), each
5444
- prefilled and confirmed there. The answers live in state.physics.answers under
5572
+ Geography, ages and entity are part 01's quick checks; the project's state, the
5573
+ builders, the rewards and the framing are part 02's rows, each prefilled there and
5574
+ never confirmed (task 1004648). The answers live in state.physics.answers under
5445
5575
  planet-physics.js's own keys, so the create sends them as `detail` untouched.
5446
5576
  The vocabularies below are COPIES of that file's (a page cannot require a
5447
5577
  module), held equal by tests/wizard_physics.mjs and wizard_physics_more.mjs. */
@@ -5477,6 +5607,8 @@ summary{min-height:24px;padding:3px 0;}
5477
5607
  var PHYS_FRAMINGS = ['managed', 'hybrid', 'local', 'open-source'];
5478
5608
  var PHYS_SERVICES = ['hosting', 'labor-payment', 'starter-kit', 'marketplace-buy', 'marketplace-sell', 'guild-access', 'guild-create', 'company-creation', 'cbaas-consulting', 'public-marketing'];
5479
5609
  var PHYS_MANY = ['reward_intents', 'services'];
5610
+ /* part 01's quick checks: an answer to any of these asks the table again */
5611
+ var PHYS_CHECK_KEYS = ['builder_geography', 'minors_on_board', 'entity'];
5480
5612
  /* the suite a hybrid or local framing starts from (the canvas's Framing frame) */
5481
5613
  var PHYS_SUITE_START = { hybrid: ['hosting', 'starter-kit', 'marketplace-buy', 'guild-access'], local: ['starter-kit', 'marketplace-buy', 'guild-access'] };
5482
5614
  /* what the review and the reasons say for each answer */
@@ -5561,9 +5693,9 @@ summary{min-height:24px;padding:3px 0;}
5561
5693
  if (state.teamShape) d.team_shape = state.teamShape;
5562
5694
  return Object.keys(d).length ? d : undefined;
5563
5695
  }
5564
- /* The review's prefilled answers, written once on the way in and never over a
5565
- founder's own: a new project, all managed, one person. Each still has to be
5566
- confirmed on its row before Create — confirming is the answer. */
5696
+ /* Part 02's prefilled answers, written once on the way in and never over a
5697
+ founder's own: a new project, all managed, one person. Nothing needs confirming
5698
+ (task 1004648): what the summary says is what Create files. */
5567
5699
  function physDefaults() {
5568
5700
  var a = phys().answers;
5569
5701
  if (!a.project_state) a.project_state = 'new';
@@ -5571,7 +5703,7 @@ summary{min-height:24px;padding:3px 0;}
5571
5703
  if (!Number.isInteger(a.builders_at_start)) { a.builders_at_start = 1; a.builders_expected = 1; }
5572
5704
  if (physSolo()) state.teamShape = 'solo';
5573
5705
  else if (!state.teamShape || state.teamShape === 'solo') state.teamShape = 'small-team';
5574
- if (state.mode === 'greenfield' && a.project_state === 'existing' && Array.isArray(a.existing_work) && a.existing_work.indexOf('code') !== -1 && !(state.confirmed || {}).code) setMode('adopt');
5706
+ if (state.mode === 'greenfield' && a.project_state === 'existing' && Array.isArray(a.existing_work) && a.existing_work.indexOf('code') !== -1 && !state.modeChosen) setMode('adopt');
5575
5707
  saveDraft();
5576
5708
  }
5577
5709
 
@@ -5649,11 +5781,13 @@ summary{min-height:24px;padding:3px 0;}
5649
5781
  b.setAttribute('aria-pressed', String(on));
5650
5782
  });
5651
5783
  }
5652
- /* screen 3, the quick checks: country and region, where builders will be, anyone
5653
- under 18 (the tiers shown for a yes), and who is behind it (the full list of legal
5654
- entities for someone who already has one) */
5784
+ /* part 01's quick checks: country and region, where builders will be, anyone under
5785
+ 18 (the tiers shown for a yes), and who is behind it (the full list of legal
5786
+ entities for someone who already has one). A demo's answers are its own, so its
5787
+ checks stand down. */
5655
5788
  function paintChecks() {
5656
5789
  var p = phys(), a = p.answers;
5790
+ $('checksMain').hidden = physStopped || !!state.demo;
5657
5791
  physPaintPills();
5658
5792
  $('physAges').hidden = a.minors_on_board !== true;
5659
5793
  var forms = PHYS_ENTITY_FORMS.indexOf(a.entity) !== -1 || p.entityForms === true;
@@ -5662,12 +5796,16 @@ summary{min-height:24px;padding:3px 0;}
5662
5796
  physFillSelects();
5663
5797
  physApplyLimits();
5664
5798
  }
5799
+ /* the screening stop, in place of the checks: nothing is created */
5800
+ var physStopped = false;
5665
5801
  function checksShow(which) {
5666
- $('checksMain').hidden = which !== 'main';
5667
- $('checksStop').hidden = which !== 'stop';
5668
- $('wizBack').hidden = false;
5669
- var h = which === 'stop' ? $('phys-hstop') : $('checks-h');
5670
- h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false });
5802
+ physStopped = which === 'stop';
5803
+ $('checksStop').hidden = !physStopped;
5804
+ $('checksMain').hidden = physStopped || !!state.demo;
5805
+ if (physStopped) {
5806
+ var h = $('phys-hstop');
5807
+ h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false });
5808
+ }
5671
5809
  }
5672
5810
 
5673
5811
  /* the Team size row's controls: one or many, the start count, and the month-three guess */
@@ -5704,9 +5842,11 @@ summary{min-height:24px;padding:3px 0;}
5704
5842
  }).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });
5705
5843
  }
5706
5844
  /* What the table hides follows the team and the framing, so a change to either on
5707
- the review asks it again and repaints the rows it touches. */
5845
+ part 02 asks it again and repaints the rows it touches. */
5708
5846
  function physRecheck() {
5847
+ if (state.demo) return;
5709
5848
  physCheck().then(function (res) {
5849
+ if (res && res.stop) { $('physStopMsg').textContent = res.message || ''; checksShow('stop'); return; }
5710
5850
  if (!res || !res.soft_limits) return;
5711
5851
  physLimits = res.soft_limits.limits || [];
5712
5852
  physApplyLimits();
@@ -5714,37 +5854,26 @@ summary{min-height:24px;padding:3px 0;}
5714
5854
  });
5715
5855
  }
5716
5856
 
5717
- /* Continue on the quick checks: every answer given, then the server's screen — the
5718
- one the create runs — with the description from screen 2. A stop ends here,
5719
- before anything exists; otherwise its soft limits shape the review. */
5720
- var physBusy = false;
5721
- function checksAdvance() {
5722
- var a = phys().answers;
5723
- physHint('');
5724
- if (!a.home_country) { physHint('Pick your country.'); return false; }
5725
- if (!a.builder_geography) { physHint('Say where builders will be. Not sure is a fine answer.'); return false; }
5726
- if (a.minors_on_board !== true && a.minors_on_board !== false) { physHint('Answer yes or no for builders under 18.'); return false; }
5727
- if (!a.entity) { physHint('Say who’s behind it. Not sure yet is a fine answer.'); return false; }
5728
- if (physBusy) return false;
5729
- physBusy = true;
5730
- physDefaults();
5731
- physCheck().then(function (res) {
5732
- physBusy = false;
5733
- if (res && res.stop) { $('physStopMsg').textContent = res.message || ''; checksShow('stop'); return; }
5734
- physLimits = (res && res.soft_limits && res.soft_limits.limits) || [];
5735
- show(4, true);
5857
+ /* The screening stop (task 1004418), asked by Create itself (task 1004648): the
5858
+ server's own screen — the one the create runs — over the answers given so far,
5859
+ with the sentence from part 01. A stop ends the page there, in place of the
5860
+ checks, before anything exists; otherwise its soft limits shape part 02. An
5861
+ unanswered check is not a stop: the create runs the same screen and is the
5862
+ authority. Resolves true when Create may go on. */
5863
+ function physScreen() {
5864
+ if (state.demo) return Promise.resolve(true);
5865
+ return physCheck().then(function (res) {
5866
+ if (res && res.stop) { $('physStopMsg').textContent = res.message || ''; checksShow('stop'); return false; }
5867
+ if (res && res.soft_limits) { physLimits = res.soft_limits.limits || []; physApplyLimits(); paintSetup(); }
5868
+ return true;
5736
5869
  });
5737
- return false;
5738
5870
  }
5739
5871
 
5872
+ /* the stop's way back: the answers stay, so the founder changes the one that stopped it */
5740
5873
  $('physStartOver').addEventListener('click', function () {
5741
- state.description = '';
5742
- if ($('f-desc')) $('f-desc').value = '';
5743
- phys().answers = {};
5744
- state.confirmed = {};
5745
- saveDraft();
5746
- show(2, true);
5747
5874
  checksShow('main');
5875
+ paintChecks();
5876
+ $('physCountry').focus();
5748
5877
  });
5749
5878
  root.querySelectorAll('#view-new [data-phys]').forEach(function (b) {
5750
5879
  b.addEventListener('click', function () {
@@ -5763,9 +5892,12 @@ summary{min-height:24px;padding:3px 0;}
5763
5892
  }
5764
5893
  if (k === 'project_state' && a[k] !== 'existing') { delete a.existing_work; delete a.main_work; }
5765
5894
  if (k === 'framing') physFramingChanged();
5895
+ /* what the table hides follows the checks too, and a stop says itself at once */
5896
+ else if (PHYS_CHECK_KEYS.indexOf(k) !== -1) physRecheck();
5766
5897
  physHint(''); saveDraft();
5767
5898
  paintChecks();
5768
5899
  paintSetup();
5900
+ paintCreate();
5769
5901
  });
5770
5902
  });
5771
5903
  /* A hybrid or local framing starts from the canvas's suite until the founder picks
@@ -5773,7 +5905,6 @@ summary{min-height:24px;padding:3px 0;}
5773
5905
  function physFramingChanged() {
5774
5906
  var p = phys(), a = p.answers;
5775
5907
  if ((a.framing === 'hybrid' || a.framing === 'local') && !p.suiteTouched) a.services = PHYS_SUITE_START[a.framing].slice();
5776
- unconfirm(['app']);
5777
5908
  physRecheck();
5778
5909
  }
5779
5910
  /* the Team size row's controls: one or many, the start count, the guess */
@@ -5787,7 +5918,6 @@ summary{min-height:24px;padding:3px 0;}
5787
5918
  a.builders_at_start = 2; a.builders_expected = physGuess(2); p.guessTouched = false;
5788
5919
  state.teamShape = 'small-team';
5789
5920
  }
5790
- unconfirm(['rewards', 'extras']);
5791
5921
  saveDraft(); paintSetup(); physRecheck();
5792
5922
  });
5793
5923
  });
@@ -5829,7 +5959,7 @@ summary{min-height:24px;padding:3px 0;}
5829
5959
  var a = phys().answers;
5830
5960
  a.home_country = $('physCountry').value || null;
5831
5961
  if (a.home_region && a.home_region.indexOf((a.home_country || '') + '-') !== 0) a.home_region = null;
5832
- physHint(''); saveDraft(); paintChecks();
5962
+ physHint(''); saveDraft(); paintChecks(); paintCreate(); physRecheck();
5833
5963
  });
5834
5964
  $('physRegion').addEventListener('change', function () { phys().answers.home_region = $('physRegion').value || null; saveDraft(); });
5835
5965
  $('physHaveEntity').addEventListener('click', function () { phys().entityForms = true; saveDraft(); paintChecks(); });
@@ -5937,46 +6067,6 @@ summary{min-height:24px;padding:3px 0;}
5937
6067
  return true;
5938
6068
  }
5939
6069
 
5940
- /* per-screen validation on Continue */
5941
- function validateStep(step) {
5942
- /* the front door's one tick (task 1004417): nothing past it until it is given.
5943
- The fork's own submit door is "Start my project": a real project, not the demo. */
5944
- if (step === 1) {
5945
- if (!state.termsAck) {
5946
- $('h-terms').textContent = 'Tick the box to go on. It’s how your project records that you accepted the terms.';
5947
- $('h-terms').className = 'hint err';
5948
- return false;
5949
- }
5950
- if (state.demo) { state.demo = null; state.confirmed = {}; }
5951
- return true;
5952
- }
5953
- if (step === 2) {
5954
- if (!state.name.trim()) { $('h-name').textContent = 'Give it a name to start.'; $('h-name').className = 'hint err'; return false; }
5955
- if (!state.projType) {
5956
- $('h-projtype').textContent = 'Pick one — Not sure is a fine answer.';
5957
- $('h-projtype').className = 'hint err';
5958
- return false;
5959
- }
5960
- return true;
5961
- }
5962
- /* the quick checks walk to the review themselves, after the server's screen */
5963
- if (step === 3) return checksAdvance();
5964
- /* the review: every row that applies confirmed at once, each still true */
5965
- if (step === 4) return confirmAllRows();
5966
- return true;
5967
- }
5968
-
5969
- root.querySelectorAll('.panel form').forEach(function (form) {
5970
- form.addEventListener('submit', function (ev) {
5971
- ev.preventDefault();
5972
- var next = Number(form.getAttribute('data-next'));
5973
- if (!validateStep(next - 1)) return;
5974
- /* a demo's answers are the demo's own, so it skips the quick checks */
5975
- if (next === 3 && state.demo) { physDefaults(); show(4, true); return; }
5976
- show(next, true);
5977
- });
5978
- });
5979
-
5980
6070
  var appOptsEl = root.querySelector('#appOpts');
5981
6071
  if (appOptsEl) appOptsEl.addEventListener('change', function (ev) {
5982
6072
  if (ev.target && ev.target.name === 'appPlatform' && !ev.target.disabled) { state.appPlatform = ev.target.value; saveDraft(); syncRenderSetup(); paintSetup(); }
@@ -6033,7 +6123,7 @@ summary{min-height:24px;padding:3px 0;}
6033
6123
  : 'Once the project is up, we create your app on your Render account. Your key is used once, then thrown away.';
6034
6124
  /* then the app's own progress, from the same read the project's page uses (task 1004353) */
6035
6125
  var box = $('doneApp');
6036
- if (box) watchApp(instanceId, box, function () { return state.step === 6; }, 5000);
6126
+ if (box) watchApp(instanceId, box, function () { return state.step === 2; }, 5000);
6037
6127
  });
6038
6128
  }).catch(function (err) {
6039
6129
  note.className = 'hint err';
@@ -6053,7 +6143,7 @@ summary{min-height:24px;padding:3px 0;}
6053
6143
  state wholesale), so the picker never edits an orphaned copy */
6054
6144
  if (!lookPicker || lookPickerModel !== state.look) {
6055
6145
  lookPickerModel = state.look;
6056
- lookPicker = lookKit.buildLookPicker($('wizLook'), state.look, function () { saveDraft(); }, 'wiz',
6146
+ lookPicker = lookKit.buildLookPicker($('wizLook'), state.look, function () { saveDraft(); paintSetup(); }, 'wiz',
6057
6147
  function () { return state.name; });
6058
6148
  } else lookPicker.repaint();
6059
6149
  }
@@ -6070,7 +6160,7 @@ summary{min-height:24px;padding:3px 0;}
6070
6160
  });
6071
6161
  }
6072
6162
 
6073
- /* ── the review: check what we set up (task 1004504) ── */
6163
+ /* ── part 02's controls: the app platform and the ship mode (tasks 1004351, 1004629) ── */
6074
6164
  /* the app row (task 1004351): a native radio group, so the draft only mirrors it.
6075
6165
  An older draft has no appPlatform, and a value this page does not offer (a
6076
6166
  platform still marked soon, or one that was removed) reads as 'later' — the
@@ -6104,31 +6194,25 @@ summary{min-height:24px;padding:3px 0;}
6104
6194
  root.querySelectorAll('#shipOpts input[name=shipMode]').forEach(function (i) { i.checked = i.value === pick; });
6105
6195
  }
6106
6196
 
6107
- var ROWS = ['code', 'address', 'app', 'extras', 'team', 'rewards', 'run', 'ship'];
6197
+ /* ── part 02, how it's run (task 1004648): a summary to skim, not a gate ──
6198
+ Every row is prefilled from the answers and says its value with one plain line
6199
+ under it. Change opens the row's own control in place (the old steps' controls,
6200
+ moved whole, so every handler and id is the one it always was) and reads Done
6201
+ while it is open; one row is open at a time. No row needs confirming. */
6202
+ var ROWS = ['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look'];
6108
6203
  function rowEl(key) { return root.querySelector('#reviewRows .setRow[data-row="' + key + '"]'); }
6109
- function confirmedRows() {
6110
- if (!state.confirmed || typeof state.confirmed !== 'object') state.confirmed = {};
6111
- return state.confirmed;
6112
- }
6113
6204
  /* a row the answers make not apply: rewards for someone building alone (the table
6114
6205
  hides the question) */
6115
6206
  function rowApplies(key) { return !(key === 'rewards' && physSkips(6)); }
6116
- /* a row whose value is fixed, so it is confirmed but never changed here: the demo's
6117
- own answers, and the app platform a local or open-source framing rules out */
6207
+ /* a row whose value is fixed, so it is said but never changed here: the demo's own
6208
+ answers, and the app platform a local or open-source framing rules out */
6118
6209
  function rowFixed(key) {
6119
6210
  if (state.demo && (key === 'team' || key === 'rewards' || key === 'run')) return true;
6120
6211
  return key === 'app' && appSkipped();
6121
6212
  }
6122
- function unconfirmedRows() {
6123
- var c = confirmedRows();
6124
- return ROWS.filter(function (k) { return rowApplies(k) && !c[k]; });
6125
- }
6126
- function unconfirm(keys) {
6127
- var c = confirmedRows();
6128
- keys.forEach(function (k) { delete c[k]; });
6129
- if (state.step === 4) paintSetup();
6130
- }
6131
- /* the checks a row's own old step made on Next — a row cannot be confirmed past them */
6213
+ /* the checks a row's own old step made on Next. Nothing is confirmed, but a row
6214
+ whose answer cannot be filed as it stands (no repo picked yet, a handle that is
6215
+ taken, a Render key not checked) keeps Create from filing it. */
6132
6216
  function rowValid(key) {
6133
6217
  if (key === 'code') return validateCode();
6134
6218
  if (key === 'address') return validateAddress();
@@ -6137,6 +6221,10 @@ summary{min-height:24px;padding:3px 0;}
6137
6221
  if (key === 'run') return !!phys().answers.framing;
6138
6222
  return true;
6139
6223
  }
6224
+ function firstInvalidRow() {
6225
+ for (var i = 0; i < ROWS.length; i++) if (rowApplies(ROWS[i]) && !rowValid(ROWS[i])) return ROWS[i];
6226
+ return null;
6227
+ }
6140
6228
  function openRow(key) {
6141
6229
  var row = rowEl(key);
6142
6230
  if (!row || rowFixed(key)) return;
@@ -6150,6 +6238,7 @@ summary{min-height:24px;padding:3px 0;}
6150
6238
  if (key === 'code') syncHome();
6151
6239
  if (key === 'app') { syncAppStep(); syncRenderSetup(); }
6152
6240
  if (key === 'ship') syncShipStep();
6241
+ if (key === 'look') renderLook();
6153
6242
  }
6154
6243
  function closeRow(key) {
6155
6244
  var row = rowEl(key);
@@ -6157,58 +6246,20 @@ summary{min-height:24px;padding:3px 0;}
6157
6246
  row.querySelector('.opts').hidden = true;
6158
6247
  var chg = row.querySelector('[data-row-chg]');
6159
6248
  chg.setAttribute('aria-expanded', 'false');
6160
- chg.textContent = 'Edit';
6161
- }
6162
- /* Done on an open row, or the page's one "Looks right, continue": the row's own
6163
- checks first — a row that fails stays open with its reason showing — then it is
6164
- the founder's answer. The extras row turns "the suggested set" into an answer
6165
- given, a different fact from never asked. */
6166
- function confirmRow(key) {
6167
- if (!rowValid(key)) { openRow(key); return false; }
6168
- /* only once the catalog is here: without it the suggested set is unknown, and an
6169
- empty answer would switch off the very bundle the server applies to no answer */
6170
- if (key === 'extras' && starterBundles) state.modules = pickedModules();
6171
- confirmedRows()[key] = true;
6172
- closeRow(key);
6173
- saveDraft();
6174
- paintSetup();
6175
- return true;
6176
- }
6177
- /* the page's one confirmation (task 1004550): every row that applies, in order,
6178
- through its own check; the first that fails opens with its reason and stops it */
6179
- function confirmAllRows() {
6180
- var rows = unconfirmedRows();
6181
- for (var i = 0; i < rows.length; i++) {
6182
- if (!confirmRow(rows[i])) {
6183
- var row = rowEl(rows[i]);
6184
- if (row && row.scrollIntoView) row.scrollIntoView({ block: 'center' });
6185
- $('h-review').textContent = 'Fix the row that’s open, then continue.';
6186
- $('h-review').className = 'hint err';
6187
- return false;
6188
- }
6189
- }
6190
- $('h-review').textContent = '\u00a0'; $('h-review').className = 'hint';
6191
- return true;
6192
- }
6193
- /* back to the review from a later screen with rows left to confirm: show the first
6194
- one that fails its check opened, and confirm nothing on the founder's behalf */
6195
- function reopenReview() {
6196
- show(4, true);
6197
- var bad = unconfirmedRows().filter(function (k) { return !rowValid(k); })[0];
6198
- if (bad) openRow(bad);
6249
+ chg.textContent = 'Change';
6199
6250
  }
6200
6251
  $('reviewRows').addEventListener('click', function (ev) {
6201
6252
  var t = ev.target.closest && ev.target.closest('[data-row-chg]');
6202
6253
  if (!t) return;
6203
6254
  var key = t.closest('.setRow').getAttribute('data-row');
6204
- /* Done: the row's check, then closed — or left open with its reason */
6255
+ /* Done: the row's own check, then closed — or left open with its reason showing */
6205
6256
  if (rowEl(key).classList.contains('open')) {
6206
- if (confirmRow(key)) t.focus({ preventScroll: true });
6257
+ if (rowValid(key)) { closeRow(key); paintSetup(); t.focus({ preventScroll: true }); }
6207
6258
  return;
6208
6259
  }
6209
- /* Edit: one row open at a time — another open row closes if its answer holds */
6210
- ROWS.forEach(function (k) { if (k !== key && rowEl(k).classList.contains('open') && rowValid(k)) confirmRow(k); });
6211
- delete confirmedRows()[key]; openRow(key); paintSetup();
6260
+ /* Change: one row open at a time — another open row closes if its answer holds */
6261
+ ROWS.forEach(function (k) { if (k !== key && rowEl(k).classList.contains('open') && rowValid(k)) closeRow(k); });
6262
+ openRow(key); paintSetup();
6212
6263
  });
6213
6264
  /* an open row's value line follows what is typed or picked under it, live — the
6214
6265
  controls' own handlers run first (they are on the targets), then the row repaints */
@@ -6220,9 +6271,16 @@ summary{min-height:24px;padding:3px 0;}
6220
6271
  var row = ev.target.closest('.setRow'), t = rowText(row.getAttribute('data-row'));
6221
6272
  row.querySelector('[data-row-v]').textContent = t.v;
6222
6273
  row.querySelector('[data-row-s]').textContent = t.s;
6274
+ if (row.getAttribute('data-row') !== 'look') paintCreate();
6223
6275
  }
6224
6276
  });
6225
6277
  });
6278
+ /* the look row's swatch: the chosen look's own accent, or the founder's */
6279
+ function lookAccent(lk) {
6280
+ if (lk.look === 'custom') return lk.accent || '';
6281
+ var L = lookKit.LOOKS[lk.look];
6282
+ return L ? L.colors.accent : '';
6283
+ }
6226
6284
 
6227
6285
  /* what each row says: its value and one plain line under it */
6228
6286
  function rowText(key) {
@@ -6237,14 +6295,14 @@ summary{min-height:24px;padding:3px 0;}
6237
6295
  var av = !custom && !state.noAddress ? slugAvailState() : 'unknown';
6238
6296
  if (state.noAddress && !custom) return { v: 'No address yet', s: 'Sign-in can’t be switched on until it has one.' };
6239
6297
  /* a dotless host (local dev) assigns nothing, so the row asks for a domain or the opt-out */
6240
- if (!resolvedDomain()) return { v: 'No address assigned here', s: 'Add a domain you own, or go without one for now.' };
6298
+ if (!resolvedDomain()) return { v: validSlug(state.slug) ? 'No address assigned here' : 'Name it first', s: validSlug(state.slug) ? 'Add a domain you own, or go without one for now.' : 'The address comes with the name.' };
6241
6299
  return {
6242
- v: resolvedDomain() || 'Pick a handle',
6300
+ v: resolvedDomain(),
6243
6301
  s: custom ? 'Your own domain. Your projects page walks you through pointing it here.'
6244
6302
  : av === 'taken' ? 'Already taken — change the handle.'
6245
6303
  : av === 'yours' ? 'You already have that one — change the handle.'
6246
6304
  : av === 'reserved' ? 'Reserved by the platform — change the handle.'
6247
- : 'Yours once it’s set up. You can add your own domain any time.'
6305
+ : 'You can add your own domain later.'
6248
6306
  };
6249
6307
  }
6250
6308
  if (key === 'app') {
@@ -6252,6 +6310,8 @@ summary{min-height:24px;padding:3px 0;}
6252
6310
  return { v: appPlatformChoice() === 'render' ? 'Render' : 'Decide later', s: appRowDetail() };
6253
6311
  }
6254
6312
  if (key === 'extras') {
6313
+ /* no kind yet: the suggestion follows the kind, so say that rather than "0" */
6314
+ if (state.modules === null && !state.projType) return { v: 'Just the core for now', s: 'Pick a kind and we suggest extras for it.' };
6255
6315
  var picked = pickedModules();
6256
6316
  var lead = !starterBundles
6257
6317
  ? (state.modules === null ? 'The suggested set' : 'The extras you picked')
@@ -6262,7 +6322,7 @@ summary{min-height:24px;padding:3px 0;}
6262
6322
  }
6263
6323
  if (key === 'team') {
6264
6324
  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.' };
6265
- if (physSolo()) return { v: 'One person', s: 'Just you.' };
6325
+ if (physSolo()) return { v: 'Just you to start', s: 'Open to invites.' };
6266
6326
  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') + '.' };
6267
6327
  }
6268
6328
  if (key === 'rewards') {
@@ -6286,12 +6346,20 @@ summary{min-height:24px;padding:3px 0;}
6286
6346
  ? { v: 'Manual', s: 'The AI waits for you to approve each ship. You can change this later in your project’s settings.' }
6287
6347
  : { v: 'Auto (recommended)', s: 'The AI ships finished work on its own. You can change this later in your project’s settings.' };
6288
6348
  }
6349
+ if (key === 'look') {
6350
+ var lk = state.look || defaultLook();
6351
+ var L = lookKit.LOOKS[lk.look];
6352
+ var name = L ? L.title.replace(/^The /, '') : 'Your own colours';
6353
+ return {
6354
+ v: name.charAt(0).toUpperCase() + name.slice(1),
6355
+ s: (lk.logo ? 'With your logo.' : 'Add a logo later from the project page.') + (lk.nightTint === false ? ' Night tint off.' : '')
6356
+ };
6357
+ }
6289
6358
  return { v: '', s: '' };
6290
6359
  }
6291
- /* paint every row from the draft, and the locked Continue under them */
6360
+ /* paint every row from the draft */
6292
6361
  function paintSetup() {
6293
6362
  if (!ROWS) return; /* boot: setMode() runs before this block has run */
6294
- var c = confirmedRows();
6295
6363
  var suite = phys().answers.framing === 'hybrid' || phys().answers.framing === 'local';
6296
6364
  $('physSuite').hidden = !suite;
6297
6365
  $('physSuiteLbl').textContent = phys().answers.framing === 'local' ? 'Your local suite' : 'Your hybrid suite';
@@ -6308,19 +6376,15 @@ summary{min-height:24px;padding:3px 0;}
6308
6376
  ROWS.forEach(function (key) {
6309
6377
  var row = rowEl(key), t = rowText(key);
6310
6378
  row.hidden = !rowApplies(key);
6311
- row.classList.toggle('done', !!c[key]);
6312
6379
  row.querySelector('[data-row-v]').textContent = t.v;
6313
6380
  row.querySelector('[data-row-s]').textContent = t.s;
6314
6381
  row.querySelector('[data-row-chg]').hidden = rowFixed(key);
6315
6382
  });
6383
+ $('lookDot').style.background = lookAccent(state.look || defaultLook());
6316
6384
  }
6317
- /* screen 5's one line: what Create is about to make */
6385
+ /* the one line beside Create: what it is about to make */
6318
6386
  function renderRecap() {
6319
- var cb = $('doCreate');
6320
- /* every road back to this screen gets a live button — the create path disables
6321
- it and only the failure branch used to restore it */
6322
- cb.disabled = false; cb.textContent = 'Create my project';
6323
- var bits = [state.name || state.slug, typeLabel(state.projType) || '', state.mode === 'adopt' ? 'your repo' : 'new repo', resolvedDomain() || 'no address yet'];
6387
+ var bits = [String(state.name || '').trim() || state.slug, typeLabel(state.projType) || '', state.mode === 'adopt' ? 'your repo' : 'new repo', resolvedDomain() || 'no address yet'];
6324
6388
  if (state.demo) bits.push('demo');
6325
6389
  $('wizRecap').textContent = bits.filter(Boolean).join(' · ');
6326
6390
  }
@@ -6400,15 +6464,29 @@ summary{min-height:24px;padding:3px 0;}
6400
6464
  var btn = this;
6401
6465
  var alertBox = $('createAlert');
6402
6466
  alertBox.classList.remove('on');
6403
- /* a draft can reach Review without the front door's tick (one written before
6404
- it existed is dropped by the key bump, but a cleared box is still a cleared
6405
- box) — send the founder back to it rather than file an unacknowledged
6406
- project (task 1004417) */
6407
- if (!state.termsAck) { show(1, true); validateStep(1); return; }
6408
- /* nothing is created before every row of the review is confirmed */
6409
- if (unconfirmedRows().length) { reopenReview(); return; }
6467
+ /* a name and the terms tick, and nothing else (task 1004648); the button already
6468
+ waits for both, so this only guards a click that raced a change */
6469
+ if (createWhy()) { paintCreate(); $(String(state.name || '').trim() ? 'termsAck' : 'newName').focus(); return; }
6470
+ /* no row is confirmed, but one that cannot be filed as it stands opens with its
6471
+ reason, and Create waits for it */
6472
+ var bad = firstInvalidRow();
6473
+ if (bad) {
6474
+ ROWS.forEach(function (k) { if (k !== bad && rowEl(k).classList.contains('open')) closeRow(k); });
6475
+ openRow(bad);
6476
+ paintSetup();
6477
+ var badRow = rowEl(bad);
6478
+ if (badRow && badRow.scrollIntoView) badRow.scrollIntoView({ block: 'center' });
6479
+ $('createWhy').textContent = 'Fix the row that’s open, then create.';
6480
+ return;
6481
+ }
6482
+ createBusy = true;
6410
6483
  btn.disabled = true; btn.textContent = 'Raising it into the sky…';
6411
- ensureFreshRepo().then(function () {
6484
+ /* the screening stop first: an answer the platform cannot serve stops the page
6485
+ here, before anything — the repo included — exists */
6486
+ physScreen().then(function (ok) {
6487
+ if (!ok) { var stop = new Error('stopped'); stop.stopped = true; throw stop; }
6488
+ return ensureFreshRepo();
6489
+ }).then(function () {
6412
6490
  return fetch(API + '/provisioning/instances', {
6413
6491
  method: 'POST', credentials: 'same-origin',
6414
6492
  headers: { 'Content-Type': 'application/json' },
@@ -6476,21 +6554,24 @@ summary{min-height:24px;padding:3px 0;}
6476
6554
  }));
6477
6555
  sessionStorage.removeItem(DRAFT_KEY);
6478
6556
  } catch (e) {}
6557
+ createBusy = false;
6479
6558
  renderDone();
6480
- show(6, true);
6559
+ show(2, true);
6481
6560
  sendRenderSetup(data.instance && data.instance.id); // the key rides the new project's own setup run (task 1004352)
6482
6561
  sendLookSetup(data.instance && data.instance.id); // the look + logo, like the Render key: right after Create (task 1004421)
6483
6562
  startProgress(data.instance && data.instance.id);
6484
6563
  }).catch(function (err) {
6564
+ createBusy = false;
6485
6565
  if (String(err && err.message) === 'signing in') return;
6486
- btn.disabled = false; btn.textContent = 'Create my project';
6566
+ paintCreate();
6567
+ if (err && err.stopped) return; /* the stop says itself, in place of the checks */
6487
6568
  if (err && err.gotoHome) {
6488
- /* A repo-creation problem (name taken / reconnect) — bounce back to the Home
6489
- step with the reason, so the user fixes it where the repo is chosen. */
6490
- show(4, true);
6491
- delete confirmedRows().code;
6569
+ /* A repo-creation problem (name taken / reconnect) — open the Code row with the
6570
+ reason, so the founder fixes it where the repo is chosen. */
6492
6571
  openRow('code');
6493
6572
  paintSetup();
6573
+ var codeRow = rowEl('code');
6574
+ if (codeRow && codeRow.scrollIntoView) codeRow.scrollIntoView({ block: 'center' });
6494
6575
  $('h-home').textContent = err.homeMsg || 'Please check the repository again.';
6495
6576
  $('h-home').className = 'hint err';
6496
6577
  return;
@@ -7216,13 +7297,14 @@ summary{min-height:24px;padding:3px 0;}
7216
7297
  is three-valued and null is its "nothing said" sentinel; leaving the key out
7217
7298
  left it UNDEFINED, which is not null — so pickedModules() reached .slice()
7218
7299
  on it and the second project's Modules step threw. */
7219
- 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', shipMode: 'auto', physics: null, demo: null, look: null, termsAck: false, confirmed: {} };
7300
+ state = { step: 1, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', modeChosen: false, owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', shipMode: 'auto', physics: null, demo: null, look: null, termsAck: false };
7220
7301
  $('newName').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
7221
7302
  $('f-noaddr').checked = false; $('f-desc').value = '';
7222
- $('termsAck').checked = false; paintDoors(); demoShow('intro'); checksShow('main');
7303
+ $('termsAck').checked = false; demoShow(false); checksShow('main');
7304
+ ROWS.forEach(function (k) { if (rowEl(k).classList.contains('open')) closeRow(k); });
7223
7305
  /* Re-seed the owner from the signed-in account for the next project. */
7224
7306
  if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
7225
- $('h-name').innerHTML = '&nbsp;';
7307
+ paintNameHint();
7226
7308
  paintType(null);
7227
7309
  paintTeam(null);
7228
7310
  setMode('greenfield');
@@ -7601,8 +7683,7 @@ summary{min-height:24px;padding:3px 0;}
7601
7683
  panel.setAttribute('data-moment', hop.target);
7602
7684
  $('hop').classList.remove('hopFading');
7603
7685
  if (hop.target === 'wait') hopStart(); else hopStop();
7604
- if (hop.target === 'arrive') stepsPaint(true);
7605
- if (hop.target !== 'wait') { panel.classList.remove('hopSignin'); $('hopSignin').setAttribute('aria-expanded', 'false'); }
7686
+ stepsPaint();
7606
7687
  }
7607
7688
  if (m === 'arrive' && was === 'wait' && !REDUCED && hop.raf && performance.now() - hop.since > 2500) {
7608
7689
  cancelAnimationFrame(hop.raf); hop.raf = 0;
@@ -7618,9 +7699,7 @@ summary{min-height:24px;padding:3px 0;}
7618
7699
  hop.target = ''; hop.since = 0; hop.tr = 0; hop.trDone = null; hop.p = null;
7619
7700
  hop.best = 0; hop.maxed = false;
7620
7701
  if (hop.w) hopBuild();
7621
- stepIdx = -1; stepAuto = true;
7622
- var panel = $('panel-done');
7623
- panel.removeAttribute('data-moment'); panel.classList.remove('hopSignin');
7702
+ $('panel-done').removeAttribute('data-moment');
7624
7703
  }
7625
7704
 
7626
7705
  /* the status line and the bar read what the strip already says — one
@@ -7636,54 +7715,19 @@ summary{min-height:24px;padding:3px 0;}
7636
7715
  $('hopBarFill').style.width = Math.max(4, lis ? Math.min(100, done / lis * 100) : 0) + '%';
7637
7716
  }
7638
7717
 
7639
- /* the arrival's steps, one at a time. The list is still rendered whole
7640
- (renderSteps, change-only); this marks the one on show, numbers it, and
7641
- paints the pager. Until the founder pages for themselves, the step on
7642
- show follows the first one not yet done — the checklist ticks itself, so
7643
- a step finishing moves them on. Once they page, their choice holds. */
7644
- var stepIdx = -1, stepAuto = true;
7645
- function stepsPaint(fresh) {
7646
- var panel = $('panel-done'), list = $('humanSteps');
7647
- if (!panel || !list) return;
7718
+ /* Part 03's checklist (task 1004648): every step on show at once, each ticking
7719
+ itself from what the server has verified (renderSteps, change-only), and the
7720
+ part's state line saying where the founder is. It replaces the paged steps. */
7721
+ function stepsPaint() {
7722
+ var list = $('humanSteps'), st = $('ghState');
7723
+ if (!list || !st) return;
7724
+ if (state.step !== 2) { st.textContent = 'opens once your project exists'; return; }
7648
7725
  var items = Array.prototype.slice.call(list.children);
7649
- var oauth = list.querySelector('[data-onboard-step="oauth-app"]');
7650
- var open = panel.classList.contains('hopSignin');
7651
- var can = !!oauth && !oauth.classList.contains('slocked') && !oauth.classList.contains('sdone');
7652
- $('hopWhile').hidden = !(can || open);
7653
- if (!items.length) { $('stepCount').textContent = ''; $('stepBack').hidden = true; $('stepNext').hidden = true; return; }
7654
- if (fresh) stepAuto = true;
7655
- if (stepAuto || !(stepIdx >= 0)) {
7656
- stepIdx = 0;
7657
- for (var i = 0; i < items.length; i++) { if (!items[i].classList.contains('sdone')) { stepIdx = i; break; } }
7658
- }
7659
- stepIdx = Math.max(0, Math.min(items.length - 1, stepIdx));
7660
- items.forEach(function (li, j) {
7661
- li.classList.toggle('cur', j === stepIdx);
7662
- /* a hidden step never counts, so the one on show carries its own number */
7663
- li.setAttribute('data-n', (j < 9 ? '0' : '') + (j + 1));
7664
- });
7665
- $('stepCount').textContent = 'Step ' + (stepIdx + 1) + ' of ' + items.length;
7666
- $('stepBack').hidden = stepIdx <= 0;
7667
- $('stepNext').hidden = stepIdx >= items.length - 1;
7668
- }
7669
- function stepGo(d) {
7670
- stepAuto = false;
7671
- stepIdx += d;
7672
- stepsPaint();
7673
- var cur = $('humanSteps').querySelector('li.cur');
7674
- if (cur) { cur.setAttribute('tabindex', '-1'); cur.focus(); }
7726
+ var done = items.filter(function (li) { return li.classList.contains('sdone'); }).length;
7727
+ st.textContent = items.length && done === items.length ? 'connected ✓'
7728
+ : manifestInFlight() ? 'waiting on GitHub…'
7729
+ : done ? done + ' of ' + items.length + ' done' : 'ready';
7675
7730
  }
7676
- $('stepNext').addEventListener('click', function () { stepGo(1); });
7677
- $('stepBack').addEventListener('click', function () { stepGo(-1); });
7678
-
7679
- $('hopSignin').addEventListener('click', function () {
7680
- var panel = $('panel-done');
7681
- var open = !panel.classList.contains('hopSignin');
7682
- panel.classList.toggle('hopSignin', open);
7683
- this.setAttribute('aria-expanded', open ? 'true' : 'false');
7684
- var li = $('humanSteps').querySelector('[data-onboard-step="oauth-app"]');
7685
- if (open && li) { li.setAttribute('tabindex', '-1'); li.focus(); }
7686
- });
7687
7731
 
7688
7732
  (function hopInput() {
7689
7733
  var cv = $('hopCanvas');
@@ -7757,23 +7801,22 @@ summary{min-height:24px;padding:3px 0;}
7757
7801
  another project" the only way out.
7758
7802
 
7759
7803
  The reset fires only when there is a SECOND DRAFT to lose, and the draft's own
7760
- step is what says so. A landed create clears DRAFT_KEY — but show(6) then
7761
- persists the done panel's own position straight back into it, so "is there a
7804
+ step is what says so. A landed create clears DRAFT_KEY — but show(2) then
7805
+ persists the created page's own position straight back into it, so "is there a
7762
7806
  draft?" is always yes and would have skipped the reset forever, leaving the
7763
- finished project’s name sitting in the fresh form. Only steps 1–5 are wizard
7764
- steps: anything else is the done panel's bookkeeping, not a project someone
7807
+ finished project’s name sitting in the fresh form. Only step 1, the page, is a
7808
+ draft: anything else is the created page's bookkeeping, not a project someone
7765
7809
  is part-way through typing. */
7766
7810
  function enterCreated(created, focus) {
7767
7811
  if (focus && !resumeIntent) {
7768
7812
  var draft = null;
7769
7813
  try { draft = JSON.parse(sessionStorage.getItem(DRAFT_KEY) || 'null'); } catch (e) {}
7770
- var at = draft && draft.step >= 1 && draft.step <= 5 ? draft.step : 0;
7771
- if (!at) { resetForNewProject(); at = 1; }
7814
+ if (!(draft && draft.step === 1)) resetForNewProject();
7772
7815
  paintResumeOffer(created);
7773
- /* step 1's own focus target is its one control, the terms tick, not the
7774
- heading — the same deliberate-nav rule enter() applies below */
7775
- show(at, at !== 1);
7776
- if (at === 1) $('termsAck').focus();
7816
+ /* the page's own focus target is its first control, the name, not the heading —
7817
+ the same deliberate-nav rule enter() applies below */
7818
+ show(1, false);
7819
+ $('newName').focus();
7777
7820
  return;
7778
7821
  }
7779
7822
  resumeIntent = false;
@@ -7798,7 +7841,7 @@ summary{min-height:24px;padding:3px 0;}
7798
7841
  manifestState = created.manifest_state; manifestPending = true;
7799
7842
  }
7800
7843
  renderDone();
7801
- show(6, !!focus);
7844
+ show(2, !!focus);
7802
7845
  startProgress(created.id);
7803
7846
  resumePolls();
7804
7847
  }
@@ -7810,32 +7853,25 @@ summary{min-height:24px;padding:3px 0;}
7810
7853
  function enter(focus) {
7811
7854
  if (authKnown && !me) { show(0, !!focus); return; }
7812
7855
  var created = authKnown ? createdRecord() : null;
7813
- /* A draft part-way through the wizard (steps 1–5) beside a stored create is a
7814
- SECOND project being typed. A reload or a GitHub round-trip (repo access) is
7815
- that same visit continuing, so the draft keeps its place and the earlier
7816
- project stays one click away on the resume line — it used to land on the
7817
- earlier project's done panel, dropping the founder out of the draft
7818
- (task 1004549). A deliberate nav still goes through enterCreated. */
7819
- var draftInFlight = state.step >= 1 && state.step <= 5;
7856
+ /* A draft on the page (step 1) beside a stored create is a SECOND project being
7857
+ typed. A reload or a GitHub round-trip (repo access) is that same visit
7858
+ continuing, so the draft keeps its place and the earlier project stays one
7859
+ click away on the resume line — it used to land on the earlier project's done
7860
+ panel, dropping the founder out of the draft (task 1004549). A deliberate nav
7861
+ still goes through enterCreated. */
7862
+ var draftInFlight = state.step === 1;
7820
7863
  if (created && created.id && (focus || !draftInFlight || resumeIntent)) { enterCreated(created, !!focus); return; }
7821
7864
  paintResumeOffer(created && created.id ? created : null);
7822
- /* before identity settles, never render past step 1 (the fork and its terms
7823
- tick, since task 1004504): deeper steps carry authenticated side effects (the Home-step repo
7824
- probe), and onAuth() re-enters to the real draft step the moment /me answers */
7825
- var step = authKnown && state.step >= 1 && state.step <= 5 ? state.step : 1;
7826
- if (step === 1) {
7827
- /* Pre-auth, step 1 is a PLACEHOLDER, not where the visitor is — but show()
7828
- persists whatever step it renders, so it used to overwrite the draft's real
7829
- position with 1 and leave onAuth() nothing to re-enter to (task 1003683). Every
7830
- return through GitHub landed the visitor back at the name step. Put the real
7831
- step back after rendering the placeholder, in memory AND in the draft. */
7832
- var resumeStep = state.step;
7833
- show(1, false);
7834
- if (!authKnown && resumeStep >= 1 && resumeStep <= 5) { state.step = resumeStep; saveDraft(); }
7835
- if (focus) $('termsAck').focus();
7836
- } else {
7837
- show(step, !!focus);
7838
- }
7865
+ /* Pre-auth the page is a PLACEHOLDER, not where the visitor is — but show()
7866
+ persists whatever step it renders, so it used to overwrite the draft's real
7867
+ position and leave onAuth() nothing to re-enter to (task 1003683). Put the real
7868
+ step back after rendering the placeholder, in memory AND in the draft. The page
7869
+ itself holds back the authenticated side effects (the repo probe) until /me
7870
+ answers. */
7871
+ var resumeStep = state.step;
7872
+ show(1, false);
7873
+ if (!authKnown) { state.step = resumeStep; saveDraft(); }
7874
+ if (focus) $('newName').focus();
7839
7875
  }
7840
7876
  /* leave() — park the polls (C8). The due tick would park itself anyway;
7841
7877
  this just stops the timers immediately so nothing fires off-stage. */
@@ -10803,7 +10839,7 @@ summary{min-height:24px;padding:3px 0;}
10803
10839
  Proof: ../../../tests/project_invite_ui.mjs ══ */
10804
10840
 
10805
10841
  var INVITE_MOUNTS = {
10806
- wizInvite: { who: 'wizInviteWho', list: 'wizInviteList', msg: 'wizInviteMsg', after: 'doneStepsHead', why: 'wizInviteWhy', sugg: 'wizInviteSugg' },
10842
+ wizInvite: { who: 'wizInviteWho', list: 'wizInviteList', msg: 'wizInviteMsg', after: 'wizInviteLater', why: 'wizInviteWhy', sugg: 'wizInviteSugg' },
10807
10843
  mInvite: { who: 'mInviteWho', list: 'mInviteList', msg: 'mInviteMsg', after: null, why: 'mInviteWhy', sugg: 'mInviteSugg', guild: 'mInviteGuild', guildMsg: 'mInviteGuildMsg' }
10808
10844
  };
10809
10845
  var inviteSlug = {}; /* mount id → the slug it invites to; '' means no step at all */