@bongos/core 1.19.83 → 1.19.85

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.
@@ -11,24 +11,31 @@
11
11
  <link rel="preconnect" href="https://fonts.googleapis.com">
12
12
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
13
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&display=swap">
14
+ <!-- The R07 redesign (task 1002696): shared token + component layer — the space
15
+ sky both modes ride on, light/dark governing the cards. The server re-injects
16
+ the branding :root AFTER stylesheets, so the pack stays authoritative.
17
+ Design contract: docs/design/projects-surfaces-redesign-direction.md -->
18
+ <link rel="stylesheet" href="/assets/cosmos.css">
19
+ <script>
20
+ /* apply the saved color mode BEFORE first paint so there's no flash;
21
+ no saved choice = follow the OS (prefers-color-scheme) */
22
+ try{var _m=localStorage.getItem('cb-color-mode');if(_m==='light'||_m==='dark')document.documentElement.setAttribute('data-mode',_m);}catch(e){}
23
+ </script>
14
24
  <style>
15
- /* ── Cloud Bongos vanilla look (ADR 0064) — same baked defaults as index.html;
16
- for cloudbongos.com these equal config/branding.neutral.json and the server
17
- re-injects the same vars after this stylesheet so the pack stays authoritative.
25
+ /* ── /create page-specific layout. Tokens, the reset, the sky, the header voice
26
+ and the shared card/chip/button vocabulary all come from cosmos.css above;
27
+ what stays here is this page's own structure (the wizard) plus token-built
28
+ variants of it.
29
+ DUAL MODE (task 1002696, owner decision 2026-08-12): light = the sunrise
30
+ white/orange page, dark = the gate's deep-space sky with dark-navy cards. The
31
+ card INTERIOR flips too — so no literal color may live in this block; every
32
+ surface here is a cosmos token or a color-mix over one. (A local :root token
33
+ block would land after cosmos's dark tiers and silently defeat them — that is
34
+ why the old one is gone.)
18
35
  The /create page is the branded project-creation experience (goal 35, task 2081):
19
36
  one page, no framework, the same trust boundary as the hall wizard — it only
20
37
  ENQUEUES a provisioning request and then hands the human the steps only they
21
38
  can do. ── */
22
- :root{
23
- --bg:#fff7ed; --bg-deep:#ffedd5;
24
- --ink:#2a1f17; --ink-soft:#6b5d52; --ink-faint:#9c8d80;
25
- --accent:#f97316; --accent-deep:#ea580c; --sea:#38bdf8; --terracotta:#fb7185;
26
- --card:#fffdfa; --line:#f3e1c9; --ok:#16a34a; --err:#dc2626;
27
- --font-display:"Baloo 2","Quicksand",system-ui,"Arial Rounded MT Bold",sans-serif;
28
- --font-body:"Quicksand",system-ui,-apple-system,"Segoe UI",sans-serif;
29
- --font-mono:"Fira Code",ui-monospace,SFMono-Regular,Menlo,monospace;
30
- }
31
- *{box-sizing:border-box;margin:0;padding:0;}
32
39
  /* The `hidden` attribute must always win. Several blocks below set an explicit
33
40
  `display` via a class (.field→block, .row2→grid); an author rule like that
34
41
  silently defeats the user-agent [hidden] rule, so `el.hidden = true` fails to
@@ -36,52 +43,18 @@
36
43
  adopt-picker / fresh-name / manual sub-panels — without this they ALL render at
37
44
  once. Make hidden authoritative for the whole page. */
38
45
  [hidden]{display:none !important;}
39
- html{scroll-behavior:smooth;}
40
- body{
41
- font-family:var(--font-body); color:var(--ink);
42
- background:var(--bg);
43
- min-height:100vh; -webkit-font-smoothing:antialiased; overflow-x:hidden;
44
- }
45
- /* ── the sky — a fixed layer carrying the dawn gradient + clouds. The gradient
46
- lives HERE (not background-attachment:fixed on body): fixed-attachment
47
- backgrounds black out on scrolled composited pages in Chromium, and /create
48
- is the first landing page tall enough to scroll. ── */
49
- .sky{
50
- position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
51
- /* dawn at altitude: a whisper of sky up top settling into the sunrise warmth */
52
- background:linear-gradient(180deg,#e9f5fd 0%,#fde8c8 34%,#fff4e6 62%,#fffdfa 100%);
53
- }
54
- .cloud{position:absolute;border-radius:50%;background:rgba(255,255,255,0.55);filter:blur(38px);}
55
- .cloud.a{width:380px;height:380px;top:-120px;left:-80px;animation:drift 26s ease-in-out infinite alternate;}
56
- .cloud.b{width:300px;height:300px;top:4%;right:-60px;background:rgba(255,224,178,0.5);animation:drift 32s ease-in-out infinite alternate-reverse;}
57
- .cloud.c{width:460px;height:460px;bottom:-200px;left:24%;background:rgba(255,255,255,0.4);animation:drift 40s ease-in-out infinite alternate;}
58
- .cloud.d{width:230px;height:230px;top:38%;left:2%;background:rgba(56,189,248,0.14);animation:drift 30s ease-in-out infinite alternate-reverse;}
59
- .cloud.e{width:200px;height:200px;top:16%;left:56%;background:rgba(56,189,248,0.10);animation:drift 36s ease-in-out infinite alternate;}
60
- @keyframes drift{from{transform:translateX(-18px)}to{transform:translateX(26px)}}
61
-
62
- /* ── header ── */
63
- .top{
64
- position:relative;z-index:2;display:flex;align-items:center;justify-content:space-between;
65
- padding:20px clamp(18px,4vw,40px);
66
- }
67
- .top .mark{
68
- font-family:var(--font-display);font-weight:700;font-size:22px;letter-spacing:-0.5px;
69
- text-transform:lowercase;color:var(--ink);text-decoration:none;
70
- }
71
- .top .mark span{color:var(--accent);}
72
- .top-right{display:flex;align-items:center;gap:clamp(10px,2vw,18px);}
46
+
73
47
  /* exit the wizard — Esc, or this button (task 2098). Nothing is created until the
74
48
  final step, so leaving simply drops the in-progress project. */
75
49
  .exit-wizard{
76
50
  display:inline-flex;align-items:center;gap:7px;cursor:pointer;
77
- background:transparent;border:1px solid rgba(42,31,23,0.16);border-radius:999px;
78
- padding:7px 14px;font-family:var(--font-body);font-weight:600;font-size:13px;color:var(--ink-soft);
51
+ background:var(--glass-bg);border:1.5px solid var(--glass-border);border-radius:999px;
52
+ padding:7px 14px;font-family:var(--font-body);font-weight:600;font-size:13px;color:var(--glass-ink);
53
+ text-shadow:var(--chrome-glow);backdrop-filter:blur(4px);
79
54
  transition:background .14s ease,border-color .14s ease,color .14s ease;
80
55
  }
81
- .exit-wizard:hover{background:rgba(42,31,23,0.05);color:var(--ink);border-color:rgba(42,31,23,0.28);}
82
- .exit-wizard kbd{font-family:var(--font-mono);font-size:10px;line-height:1;background:rgba(42,31,23,0.08);border-radius:4px;padding:2px 5px;color:var(--ink-faint);}
83
- .who{display:flex;align-items:center;gap:10px;font-size:14px;font-weight:600;color:var(--ink-soft);min-height:34px;}
84
- .who img{width:30px;height:30px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 8px rgba(42,31,23,0.12);}
56
+ .exit-wizard:hover{border-color:var(--chrome-accent);color:var(--chrome-accent);}
57
+ .exit-wizard kbd{font-family:var(--font-mono);font-size:10px;line-height:1;background:color-mix(in srgb,var(--chrome-ink) 12%,transparent);border-radius:4px;padding:2px 5px;color:var(--chrome-ink-dim);}
85
58
 
86
59
  /* ── layout ── */
87
60
  .wrap{
@@ -101,20 +74,20 @@ body{
101
74
  }
102
75
  .guide .mascot img{display:block;width:100%;height:auto;}
103
76
  .bubble{
104
- position:relative;background:#fff;border-radius:18px;padding:14px 18px;max-width:280px;
77
+ position:relative;background:var(--bg-card);border-radius:18px;padding:14px 18px;max-width:280px;
105
78
  font-size:15px;font-weight:600;color:var(--ink-soft);text-align:center;line-height:1.45;
106
- box-shadow:0 10px 30px -12px rgba(42,31,23,0.18);
79
+ box-shadow:0 10px 30px -12px rgba(5,6,15,0.35);
107
80
  }
108
81
  .bubble::after{
109
82
  content:"";position:absolute;top:-8px;left:50%;transform:translateX(-50%) rotate(45deg);
110
- width:16px;height:16px;background:#fff;border-radius:3px;
83
+ width:16px;height:16px;background:var(--bg-card);border-radius:3px;
111
84
  }
112
85
  .guide .bubble{order:-1;margin-bottom:16px;}
113
86
 
114
87
  /* the wizard card — a cloud you build on */
115
88
  .card{
116
89
  flex:1;min-width:0;background:var(--card);border-radius:26px;
117
- box-shadow:0 24px 60px -24px rgba(234,88,12,0.22), 0 6px 22px -8px rgba(42,31,23,0.10);
90
+ box-shadow:var(--shadow-card);
118
91
  padding:clamp(22px,3.6vw,40px);
119
92
  animation:rise .7s ease-out .08s both;
120
93
  }
@@ -130,11 +103,11 @@ body{
130
103
  .beats li.done:not(:first-child)::before{background:var(--accent);}
131
104
  .beats .dot{
132
105
  position:relative;z-index:1;width:28px;height:28px;border-radius:50%;
133
- border:3px solid var(--line);background:#fff;display:flex;align-items:center;justify-content:center;
106
+ border:3px solid var(--line);background:var(--bg-card);display:flex;align-items:center;justify-content:center;
134
107
  font-family:var(--font-display);font-weight:700;font-size:13px;color:var(--ink-faint);
135
108
  transition:all .25s ease;
136
109
  }
137
- .beats li.now .dot{border-color:var(--accent);color:var(--accent-deep);box-shadow:0 0 0 6px rgba(249,115,22,0.14);}
110
+ .beats li.now .dot{border-color:var(--accent);color:var(--accent-deep);box-shadow:0 0 0 6px color-mix(in srgb,var(--accent) 14%,transparent);}
138
111
  .beats li.done .dot{border-color:var(--accent);background:var(--accent);color:#fff;}
139
112
  .beats .lbl{font-size:12px;font-weight:700;color:var(--ink-faint);letter-spacing:0.2px;}
140
113
  .beats li.now .lbl{color:var(--accent-deep);}
@@ -145,20 +118,21 @@ body{
145
118
  .panel.on{display:block;animation:fadeStep .3s ease both;}
146
119
  @keyframes fadeStep{from{opacity:0;transform:translateX(10px)}to{opacity:1;transform:none}}
147
120
  h1{font-family:var(--font-display);font-weight:700;font-size:clamp(24px,3.4vw,32px);letter-spacing:-0.5px;line-height:1.15;margin-bottom:8px;}
121
+ /* show() moves focus to each panel's h1 for keyboard/SR context — keep the
122
+ focus, lose the ring (a black rectangle around a heading; owner walk) */
123
+ h1[tabindex="-1"]:focus{outline:none;}
148
124
  .lede{font-size:15.5px;color:var(--ink-soft);font-weight:500;line-height:1.55;margin-bottom:24px;max-width:56ch;}
149
- .lede a{color:var(--accent-deep);font-weight:700;text-decoration:none;}
150
- .lede a:hover{text-decoration:underline;}
151
125
 
152
126
  .field{display:block;margin-bottom:18px;}
153
127
  .field>span{display:block;font-size:13.5px;font-weight:700;color:var(--ink-soft);margin-bottom:7px;letter-spacing:0.2px;}
154
128
  .field small{font-weight:500;color:var(--ink-faint);}
155
129
  input[type=text]{
156
130
  width:100%;font-family:var(--font-body);font-size:17px;font-weight:600;color:var(--ink);
157
- background:#fff;border:2px solid var(--line);border-radius:14px;padding:13px 16px;outline:none;
131
+ background:var(--bg-card);border:2px solid var(--line);border-radius:14px;padding:13px 16px;outline:none;
158
132
  transition:border-color .15s ease, box-shadow .15s ease;
159
133
  }
160
- input[type=text]::placeholder{color:#c9b8a6;font-weight:500;}
161
- input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(249,115,22,0.13);}
134
+ input[type=text]::placeholder{color:color-mix(in srgb,var(--ink-faint) 70%,var(--bg-card));font-weight:500;}
135
+ input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 13%,transparent);}
162
136
  input[type=text].bad{border-color:var(--err);}
163
137
  .hint{font-size:13px;font-weight:600;color:var(--ink-faint);margin-top:7px;min-height:18px;}
164
138
  .hint.ok{color:var(--ok);}
@@ -170,17 +144,18 @@ input[type=text].bad{border-color:var(--err);}
170
144
  /* choice cards (greenfield / adopt) */
171
145
  .choices{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:18px;}
172
146
  .choice{
173
- text-align:left;cursor:pointer;background:#fff;border:2px solid var(--line);border-radius:18px;
147
+ text-align:left;cursor:pointer;background:var(--bg-card);border:2px solid var(--line);border-radius:18px;
174
148
  padding:16px;font-family:var(--font-body);transition:all .15s ease;
175
149
  }
176
- .choice:hover{border-color:#eacba4;transform:translateY(-1px);}
177
- .choice[aria-pressed=true]{border-color:var(--accent);background:#fff9f2;box-shadow:0 8px 24px -12px rgba(249,115,22,0.35);}
150
+ .choice:hover{border-color:var(--rule);transform:translateY(-1px);}
151
+ .choice[aria-pressed=true]{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 5%,var(--bg-card));box-shadow:0 8px 24px -12px color-mix(in srgb,var(--accent) 35%,transparent);}
178
152
  .choice svg{display:block;margin-bottom:9px;}
179
153
  .choice b{display:block;font-size:15.5px;font-weight:700;margin-bottom:3px;}
180
154
  .choice p{font-size:13px;font-weight:500;color:var(--ink-soft);line-height:1.45;}
181
155
  .reassure{
182
- display:none;background:#f0f9ff;border:1.5px solid #d3ecfb;border-radius:14px;
183
- padding:12px 15px;font-size:13.5px;font-weight:500;color:#22617e;line-height:1.5;margin-bottom:18px;
156
+ display:none;background:color-mix(in srgb,var(--sea) 8%,var(--bg-card));
157
+ border:1.5px solid color-mix(in srgb,var(--sea) 25%,var(--bg-card));border-radius:14px;
158
+ padding:12px 15px;font-size:13.5px;font-weight:500;color:color-mix(in srgb,var(--sea) 40%,var(--ink));line-height:1.5;margin-bottom:18px;
184
159
  }
185
160
  .reassure.on{display:block;}
186
161
 
@@ -210,14 +185,14 @@ input[type=text].bad{border-color:var(--err);}
210
185
  }
211
186
  .btn:hover{transform:translateY(-1px);box-shadow:0 16px 34px -10px var(--accent);}
212
187
  .btn:active{transform:translateY(0) scale(.98);}
213
- .btn:disabled{background:#ecd9c3;color:#b09a85;box-shadow:none;cursor:not-allowed;transform:none;}
188
+ .btn:disabled{background:color-mix(in srgb,var(--ink) 12%,var(--bg-deep));color:color-mix(in srgb,var(--ink) 40%,var(--bg-deep));box-shadow:none;cursor:not-allowed;transform:none;}
214
189
  .btn.ghost{background:transparent;color:var(--ink-soft);box-shadow:none;border:2px solid var(--line);padding:12px 22px;}
215
- .btn.ghost:hover{border-color:#d9c1a4;color:var(--ink);}
190
+ .btn.ghost:hover{border-color:var(--rule-soft);color:var(--ink);}
216
191
  .btn.big{width:100%;justify-content:center;font-size:18px;padding:17px 30px;}
217
192
  .gh{width:20px;height:20px;fill:currentColor;}
218
193
 
219
194
  .alert{
220
- display:none;background:#fef2f2;border:1.5px solid #fbd5d5;border-radius:14px;
195
+ display:none;background:var(--err-soft);border:1.5px solid color-mix(in srgb,var(--err) 25%,transparent);border-radius:14px;
221
196
  padding:12px 15px;font-size:14px;font-weight:600;color:var(--err);line-height:1.5;margin-top:16px;
222
197
  }
223
198
  .alert.on{display:block;}
@@ -241,7 +216,7 @@ input[type=text].bad{border-color:var(--err);}
241
216
  @keyframes pop{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}}
242
217
  .steps{list-style:none;counter-reset:hs;margin-top:20px;}
243
218
  .steps>li{
244
- position:relative;background:#fff;border:2px solid var(--line);border-radius:18px;
219
+ position:relative;background:var(--bg-card);border:2px solid var(--line);border-radius:18px;
245
220
  padding:16px 18px 16px 58px;margin-bottom:12px;counter-increment:hs;
246
221
  }
247
222
  .steps>li::before{
@@ -253,7 +228,7 @@ input[type=text].bad{border-color:var(--err);}
253
228
  .steps p{font-size:13.5px;font-weight:500;color:var(--ink-soft);line-height:1.55;margin-top:4px;}
254
229
  /* live checklist states (task 1002694): a step the platform has verified done, and a
255
230
  step that can't be performed yet (its prerequisite — usually the domain — is missing) */
256
- .steps>li.sdone{border-color:#bbe7c8;background:linear-gradient(180deg,#f0fdf4 0%,#fcfffc 100%);}
231
+ .steps>li.sdone{border-color:color-mix(in srgb,var(--ok) 30%,var(--bg-card));background:linear-gradient(180deg,color-mix(in srgb,var(--ok) 8%,var(--bg-card)) 0%,var(--bg-card) 100%);}
257
232
  .steps>li.sdone::before{content:"✓";background:var(--ok);color:#fff;}
258
233
  .steps>li.slocked{opacity:.6;}
259
234
  .steps details{margin-top:10px;}
@@ -266,7 +241,7 @@ input[type=text].bad{border-color:var(--err);}
266
241
  .kv .kk{font-weight:700;color:var(--ink-faint);flex:0 0 auto;}
267
242
  .kv code{font-family:var(--font-mono);font-size:12px;color:var(--accent-deep);word-break:break-all;flex:1;}
268
243
  .copy{
269
- flex:0 0 auto;cursor:pointer;border:none;border-radius:8px;background:#fff;border:1.5px solid var(--line);
244
+ flex:0 0 auto;cursor:pointer;border:none;border-radius:8px;background:var(--bg-card);border:1.5px solid var(--line);
270
245
  font-family:var(--font-body);font-size:11.5px;font-weight:700;color:var(--ink-soft);padding:5px 10px;
271
246
  transition:all .12s ease;
272
247
  }
@@ -277,19 +252,19 @@ input[type=text].bad{border-color:var(--err);}
277
252
 
278
253
  /* ── live build progress (task 2083) ── */
279
254
  .prog{
280
- background:linear-gradient(180deg,#f2faff 0%,#fbfeff 100%);
281
- border:2px solid #dceefb;border-radius:18px;padding:18px 20px 16px;margin-top:14px;
255
+ background:linear-gradient(180deg,color-mix(in srgb,var(--sea) 8%,var(--bg-card)) 0%,var(--bg-card) 100%);
256
+ border:2px solid var(--sea-soft);border-radius:18px;padding:18px 20px 16px;margin-top:14px;
282
257
  }
283
258
  .prog .stages{display:flex;list-style:none;margin-bottom:4px;}
284
259
  .prog .stages li{flex:1;display:flex;flex-direction:column;align-items:center;gap:7px;position:relative;}
285
260
  .prog .stages li:not(:first-child)::before{
286
261
  content:"";position:absolute;top:11px;right:50%;width:100%;height:3px;border-radius:2px;
287
- background:#dceefb;z-index:0;margin-right:12px;
262
+ background:var(--sea-soft);z-index:0;margin-right:12px;
288
263
  }
289
264
  .prog .stages li.done:not(:first-child)::before{background:var(--sea);}
290
265
  .prog .sdot{
291
266
  position:relative;z-index:1;width:24px;height:24px;border-radius:50%;
292
- border:3px solid #dceefb;background:#fff;display:flex;align-items:center;justify-content:center;
267
+ border:3px solid var(--sea-soft);background:var(--bg-card);display:flex;align-items:center;justify-content:center;
293
268
  }
294
269
  .prog .stages li.done .sdot{border-color:var(--sea);background:var(--sea);}
295
270
  .prog .stages li.done .sdot::after{content:"";width:8px;height:5px;border-left:2.5px solid #fff;border-bottom:2.5px solid #fff;transform:rotate(-45deg) translate(1px,-1px);}
@@ -299,18 +274,18 @@ input[type=text].bad{border-color:var(--err);}
299
274
  animation:beatPulse 1.2s ease-in-out infinite;
300
275
  }
301
276
  @keyframes beatPulse{0%,100%{transform:scale(.7);opacity:.6}50%{transform:scale(1.15);opacity:1}}
302
- .prog .slbl{font-size:12px;font-weight:700;color:#8fb6cc;}
303
- .prog .stages li.now .slbl,.prog .stages li.done .slbl{color:#22617e;}
304
- .prog .pnote{text-align:center;font-size:13px;font-weight:600;color:#4a809d;min-height:18px;}
277
+ .prog .slbl{font-size:12px;font-weight:700;color:color-mix(in srgb,var(--sea) 55%,var(--ink-faint));}
278
+ .prog .stages li.now .slbl,.prog .stages li.done .slbl{color:color-mix(in srgb,var(--sea) 40%,var(--ink));}
279
+ .prog .pnote{text-align:center;font-size:13px;font-weight:600;color:color-mix(in srgb,var(--sea) 45%,var(--ink-soft));min-height:18px;}
305
280
  .prog .pnote a{color:var(--accent-deep);font-weight:700;text-decoration:none;}
306
281
  .prog .pnote a:hover{text-decoration:underline;}
307
- .prog.isLive{background:linear-gradient(180deg,#f0fdf4 0%,#fcfffc 100%);border-color:#bbe7c8;}
282
+ .prog.isLive{background:linear-gradient(180deg,color-mix(in srgb,var(--ok) 8%,var(--bg-card)) 0%,var(--bg-card) 100%);border-color:color-mix(in srgb,var(--ok) 30%,var(--bg-card));}
308
283
  .prog.isLive .stages li.done:not(:first-child)::before{background:var(--ok);}
309
284
  .prog.isLive .stages li.done .sdot{border-color:var(--ok);background:var(--ok);}
310
- .prog.isLive .slbl{color:#3e9e5f;}
311
- .prog.isLive .stages li.done .slbl{color:#166534;}
312
- .prog.isLive .pnote{color:#166534;font-size:14px;}
313
- .prog.isErr{background:#fef6f5;border-color:#f6d5d2;}
285
+ .prog.isLive .slbl{color:var(--ok);}
286
+ .prog.isLive .stages li.done .slbl{color:color-mix(in srgb,var(--ok) 75%,var(--ink));}
287
+ .prog.isLive .pnote{color:color-mix(in srgb,var(--ok) 75%,var(--ink));font-size:14px;}
288
+ .prog.isErr{background:var(--err-soft);border-color:color-mix(in srgb,var(--err) 25%,var(--bg-card));}
314
289
  .prog.isErr .pnote{color:var(--err);}
315
290
  .again{
316
291
  display:inline-block;margin-top:14px;font-size:13px;font-weight:700;color:var(--ink-faint);
@@ -319,7 +294,17 @@ input[type=text].bad{border-color:var(--err);}
319
294
  }
320
295
  .again:hover{color:var(--accent-deep);}
321
296
 
322
- .foot{position:relative;z-index:1;text-align:center;color:var(--ink-soft);font-size:13px;opacity:0.8;padding:0 24px 26px;}
297
+ /* the collapsed address step — a confirmation, not a form (task 1002704) */
298
+ .addrConfirm{border:2px solid color-mix(in srgb,var(--ok) 30%,var(--bg-card));border-radius:18px;padding:18px 20px;background:linear-gradient(180deg,color-mix(in srgb,var(--ok) 8%,var(--bg-card)) 0%,var(--bg-card) 100%);margin-bottom:16px;}
299
+ .addrConfirm.optedOut{border-color:var(--line);background:var(--bg);opacity:.75;}
300
+ .addrBig{font-family:var(--font-display);font-weight:700;font-size:19px;letter-spacing:-0.3px;line-height:1.4;}
301
+ .addrBig code{font-family:var(--font-mono);font-size:15px;background:var(--bg-deep);border-radius:8px;padding:2px 9px;color:var(--accent-deep);word-break:break-all;}
302
+ .addrConfirm>p{font-size:13.5px;font-weight:500;color:var(--ink-soft);line-height:1.5;margin-top:6px;}
303
+ .addrConfirm details{margin-top:12px;}
304
+ .addrConfirm summary{cursor:pointer;font-size:13px;font-weight:700;color:var(--ink-faint);}
305
+ .addrConfirm details .inner{margin-top:10px;}
306
+ .addrOpt{display:flex;align-items:flex-start;gap:8px;margin-top:12px;font-size:12.5px;font-weight:600;color:var(--warn);line-height:1.5;}
307
+ .addrOpt input{margin-top:2px;}
323
308
 
324
309
  /* ── responsive ── */
325
310
  @media (max-width:860px){
@@ -336,25 +321,37 @@ input[type=text].bad{border-color:var(--err);}
336
321
  /* GitHub repo picker (task 1002099) */
337
322
  .linklike{background:none;border:0;padding:0;margin-left:12px;color:var(--accent-deep);font:inherit;font-size:13px;cursor:pointer;text-decoration:underline;}
338
323
  #ghConnect .btn{margin-top:4px;}
339
- .ghlist{margin-top:8px;max-height:220px;overflow-y:auto;border:1px solid var(--line,#e7d8c4);border-radius:10px;background:var(--bg-deep,#fff);}
324
+ .ghlist{margin-top:8px;max-height:220px;overflow-y:auto;border:1px solid var(--line);border-radius:10px;background:var(--bg-deep);}
340
325
  .ghlist:empty{display:none;}
341
- .ghrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:0;border-bottom:1px solid rgba(42,31,23,0.06);padding:9px 12px;font:inherit;font-size:13.5px;color:var(--ink,#2a1f17);cursor:pointer;}
326
+ /* rows sit on --bg-deep, so their tints mix over THAT, not the card */
327
+ .ghrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:0;border-bottom:1px solid color-mix(in srgb,var(--ink) 8%,transparent);padding:9px 12px;font:inherit;font-size:13.5px;color:var(--ink);cursor:pointer;}
342
328
  .ghrow:last-child{border-bottom:0;}
343
- .ghrow:hover{background:#fff9f2;}
344
- .ghrow[aria-selected=true]{background:#fff3e6;box-shadow:inset 3px 0 0 var(--accent,#f97316);font-weight:600;}
345
- .ghrow .priv{margin-left:auto;font-size:11px;font-weight:600;color:var(--ink-faint,#9b8a78);background:rgba(42,31,23,0.06);border-radius:5px;padding:1px 6px;}
329
+ .ghrow:hover{background:color-mix(in srgb,var(--accent) 6%,var(--bg-deep));}
330
+ .ghrow[aria-selected=true]{background:color-mix(in srgb,var(--accent) 12%,var(--bg-deep));box-shadow:inset 3px 0 0 var(--accent);font-weight:600;}
331
+ .ghrow .priv{margin-left:auto;font-size:11px;font-weight:600;color:var(--ink-faint);background:color-mix(in srgb,var(--ink) 8%,transparent);border-radius:5px;padding:1px 6px;}
346
332
  </style>
347
333
  </head>
348
334
  <body>
349
335
  <div class="sky" aria-hidden="true">
350
- <div class="cloud a"></div><div class="cloud b"></div><div class="cloud c"></div>
351
- <div class="cloud d"></div><div class="cloud e"></div>
336
+ <span class="star" style="top:3%;left:14%;width:2px;height:2px;"></span>
337
+ <span class="star" style="top:6%;left:32%;width:1.5px;height:1.5px;animation-delay:.7s;"></span>
338
+ <span class="star" style="top:4%;left:52%;width:2px;height:2px;animation-delay:1.4s;"></span>
339
+ <span class="star" style="top:9%;left:70%;width:1.5px;height:1.5px;animation-delay:.3s;"></span>
340
+ <span class="star" style="top:5%;left:86%;width:2.5px;height:2.5px;animation-delay:2s;"></span>
341
+ <span class="star" style="top:16%;left:8%;width:1.5px;height:1.5px;animation-delay:1s;"></span>
342
+ <span class="star" style="top:24%;left:44%;width:2px;height:2px;animation-delay:1.8s;"></span>
343
+ <span class="star" style="top:32%;left:80%;width:1.5px;height:1.5px;animation-delay:.5s;"></span>
344
+ <span class="star" style="top:46%;left:18%;width:1.5px;height:1.5px;animation-delay:2.4s;"></span>
345
+ <span class="star" style="top:58%;left:62%;width:2px;height:2px;animation-delay:1.2s;"></span>
346
+ <span class="star" style="top:72%;left:36%;width:1.5px;height:1.5px;animation-delay:.9s;"></span>
347
+ <span class="star" style="top:84%;left:74%;width:1.5px;height:1.5px;animation-delay:1.6s;"></span>
352
348
  </div>
353
349
 
354
350
  <header class="top">
355
351
  <a class="mark" href="/"><span data-brand-product>cloud bongos</span></a>
356
352
  <div class="top-right">
357
353
  <button type="button" class="exit-wizard" id="exitWizard" title="Leave the wizard — nothing is created">Exit <kbd>esc</kbd></button>
354
+ <button type="button" class="mode-toggle" id="modeToggle" title="Switch light / dark" aria-label="Switch light or dark mode">☾</button>
358
355
  <div class="who" id="who" aria-live="polite"></div>
359
356
  </div>
360
357
  </header>
@@ -420,12 +417,12 @@ input[type=text].bad{border-color:var(--err);}
420
417
  <button type="button" class="choice" id="c-green" aria-pressed="true">
421
418
  <svg width="30" height="30" viewBox="0 0 30 30" fill="none" aria-hidden="true"><path d="M15 26V14" stroke="#f97316" stroke-width="2.6" stroke-linecap="round"/><path d="M15 15c0-5 3.5-8.5 9-8.5 0 5.5-3.5 8.5-9 8.5Z" fill="#fdba74" stroke="#f97316" stroke-width="2"/><path d="M15 18c0-3.8-2.6-6.5-7-6.5 0 4.3 2.7 6.5 7 6.5Z" fill="#fed7aa" stroke="#f97316" stroke-width="2"/></svg>
422
419
  <b>Start fresh</b>
423
- <p>A new, empty repo. We scaffold everything: config, first version, kickoff tasks.</p>
420
+ <p>A new, empty <strong>public</strong> repo. We scaffold everything: config, first version, kickoff tasks.</p>
424
421
  </button>
425
422
  <button type="button" class="choice" id="c-adopt" aria-pressed="false">
426
423
  <svg width="30" height="30" viewBox="0 0 30 30" fill="none" aria-hidden="true"><rect x="4" y="9" width="22" height="16" rx="3" fill="#fed7aa" stroke="#f97316" stroke-width="2"/><path d="M4 13h22" stroke="#f97316" stroke-width="2"/><path d="M10 9V6.5A1.5 1.5 0 0 1 11.5 5h7A1.5 1.5 0 0 1 20 6.5V9" stroke="#f97316" stroke-width="2"/><path d="M12 19h6" stroke="#ea580c" stroke-width="2.4" stroke-linecap="round"/></svg>
427
424
  <b>Adopt a repo I have</b>
428
- <p>Layer Cloud Bongos onto an existing repository — history, code, and backlog intact.</p>
425
+ <p>Layer Cloud Bongos onto an existing repository — history, code, and backlog intact. Public or private, we never change it.</p>
429
426
  </button>
430
427
  </div>
431
428
  <div class="reassure" id="adoptNote">Adopting never overwrites your files: you get a conflict pre-flight first, nothing is written until you accept, and your existing backlog (issues, TODOs) is imported as real tasks.</div>
@@ -446,11 +443,17 @@ input[type=text].bad{border-color:var(--err);}
446
443
  <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off">
447
444
  <div id="ghList" class="ghlist" role="listbox" aria-label="Your repositories"></div>
448
445
  <div class="hint" id="ghAdoptHint">&nbsp;</div>
446
+ <!-- visibility stated on EVERY repo path (task 1002721) — the picker
447
+ marks private repos, and this says what adoption does about it -->
448
+ <div class="hint">We never change a repo’s visibility — public stays public, private stays private.</div>
449
449
  </div>
450
450
  <div id="ghFresh" class="field" hidden>
451
451
  <span>New repository name</span>
452
452
  <input type="text" id="ghNewName" placeholder="mercury" autocomplete="off" maxlength="100">
453
- <div class="hint" id="ghFreshHint">We’ll create an empty repo at <code id="ghFreshPreview" style="font-family:var(--font-mono)">github.com/you/mercury</code>.</div>
453
+ <!-- visibility is stated on EVERY repo path (task 1002721) — this baked
454
+ line is the floor; updateFreshPreview() replaces it with the same
455
+ claim plus the resolved repo URL once a name is typed -->
456
+ <div class="hint" id="ghFreshHint">We’ll create an empty <strong>public</strong> repo — projects here are open by design. Want private code? Create the repo yourself on GitHub and “adopt” it instead.</div>
454
457
  </div>
455
458
 
456
459
  <div class="row2" id="manualHome">
@@ -460,6 +463,9 @@ input[type=text].bad{border-color:var(--err);}
460
463
  <label class="field"><span>Repository name</span>
461
464
  <input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
462
465
  </label>
466
+ <!-- visibility stated BEFORE GitHub's own radio is the first place it
467
+ appears (task 1002721, owner firsthand in the QA walk) -->
468
+ <div class="hint" style="grid-column:1 / -1;">Creating the repo yourself on GitHub? Public or private is your choice there — both work here, and we never change it.</div>
463
469
  </div>
464
470
  <div class="hint" id="h-home">&nbsp;</div>
465
471
  <div class="nav">
@@ -469,19 +475,33 @@ input[type=text].bad{border-color:var(--err);}
469
475
  </form>
470
476
  </section>
471
477
 
472
- <!-- step 3 · address -->
478
+ <!-- step 3 · address — a CONFIRMATION, not a form (task 1002704: every project
479
+ gets its slug subdomain assigned up front; opt-out lives in the disclosure).
480
+ The domainless default was a proven dead-end (QA walk, task 1002702). -->
473
481
  <section class="panel" id="panel-address" aria-labelledby="addr-h">
474
482
  <h1 id="addr-h">Its address in the sky</h1>
475
- <p class="lede">The handle names your project on the platform. A domain of your own is where the world will find it — you can add one later, but sign-in on your instance only works once it has a domain.</p>
483
+ <p class="lede">The handle names your project on the platform — and its address comes with it. Sign-in, sharing, everything just works from the moment it stands up.</p>
476
484
  <form data-next="4">
477
485
  <label class="field"><span>Project handle</span>
478
486
  <input type="text" id="f-slug" placeholder="mercury" autocomplete="off" maxlength="40" spellcheck="false">
479
487
  <div class="hint" id="h-slug">&nbsp;</div>
480
488
  </label>
481
- <label class="field"><span>Domain you own <small>(optional for now)</small></span>
482
- <input type="text" id="f-domain" placeholder="mercury.example.com" autocomplete="off" maxlength="253" spellcheck="false">
483
- <div class="hint" id="h-domain">&nbsp;</div>
484
- </label>
489
+ <div class="addrConfirm" id="addrConfirm">
490
+ <div class="addrBig" id="addrAssigned"></div>
491
+ <p id="addrAssignedNote">Yours from the moment it stands up. You can point a domain you own at it any time.</p>
492
+ <details id="addrCustom">
493
+ <summary>Use a domain I own instead</summary>
494
+ <div class="inner">
495
+ <label class="field"><span>Domain you own</span>
496
+ <input type="text" id="f-domain" placeholder="mercury.example.com" autocomplete="off" maxlength="253" spellcheck="false">
497
+ <div class="hint" id="h-domain">&nbsp;</div>
498
+ </label>
499
+ <label class="addrOpt"><input type="checkbox" id="f-noaddr">
500
+ <span>Start without an address — possible, but sign-in on your project can’t be set up until it has one.</span>
501
+ </label>
502
+ </div>
503
+ </details>
504
+ </div>
485
505
  <div class="nav">
486
506
  <button type="button" class="btn ghost" data-back="2">← Back</button>
487
507
  <button type="submit" class="btn">Next →</button>
@@ -534,10 +554,26 @@ input[type=text].bad{border-color:var(--err);}
534
554
  document.querySelectorAll('[data-brand-product]').forEach(function (n) { n.textContent = productName.toLowerCase(); });
535
555
  document.querySelectorAll('[data-brand-product-inline]').forEach(function (n) { n.textContent = productName; });
536
556
 
557
+ /* ── light/dark: the sky is constant, the mode governs the cards. No saved
558
+ choice = follow the OS; the toggle stores an explicit one. ── */
559
+ function resolvedMode() {
560
+ var m = document.documentElement.getAttribute('data-mode');
561
+ if (m === 'light' || m === 'dark') return m;
562
+ return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
563
+ }
564
+ function paintModeToggle() { $('modeToggle').textContent = resolvedMode() === 'dark' ? '☀' : '☾'; }
565
+ $('modeToggle').addEventListener('click', function () {
566
+ var next = resolvedMode() === 'dark' ? 'light' : 'dark';
567
+ document.documentElement.setAttribute('data-mode', next);
568
+ try { localStorage.setItem('cb-color-mode', next); } catch (e) {}
569
+ paintModeToggle();
570
+ });
571
+ paintModeToggle();
572
+
537
573
  /* ── state ── */
538
574
  var DRAFT_KEY = 'cb-create-draft';
539
575
  var CREATED_KEY = 'cb-created-instance';
540
- var state = { step: 0, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
576
+ var state = { step: 0, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
541
577
  try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
542
578
  var me = null;
543
579
 
@@ -591,6 +627,58 @@ input[type=text].bad{border-color:var(--err);}
591
627
  function looksLikeDomain(d) {
592
628
  return /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/i.test(String(d || ''));
593
629
  }
630
+ /* ── the assigned address (task 1002704): every project gets <slug>.<apex> up
631
+ front, derived from the host this wizard runs on — cloudbongos.com assigns
632
+ slug.cloudbongos.com; a self-hosted apex assigns under its own name. On a
633
+ dotless/local host (localhost, an IP) there is nothing sane to assign, so
634
+ the step degrades to the old optional-domain form. ── */
635
+ function apexBase() {
636
+ var h = String(location.hostname || '').toLowerCase().replace(/^www\./, '');
637
+ if (!h || h.indexOf('.') < 0) return null;
638
+ if (/^(localhost|127\.|\d+\.\d+\.\d+\.\d+$|\[)/.test(h)) return null;
639
+ return h;
640
+ }
641
+ function assignedDomain() {
642
+ var base = apexBase();
643
+ return base && validSlug(state.slug) ? state.slug + '.' + base : null;
644
+ }
645
+ /* What the create actually files: a custom domain wins; opting out files none;
646
+ otherwise the assigned subdomain. Everything downstream (review, submit,
647
+ the done-panel steps) reads THIS, never state.domain directly. */
648
+ function resolvedDomain() {
649
+ if (state.domain) return state.domain;
650
+ if (state.noAddress) return '';
651
+ return assignedDomain() || '';
652
+ }
653
+ function renderAddrPanel() {
654
+ var box = $('addrConfirm');
655
+ if (!box) return;
656
+ var assigned = assignedDomain();
657
+ var custom = state.domain && looksLikeDomain(state.domain) ? state.domain : '';
658
+ box.classList.toggle('optedOut', state.noAddress && !custom);
659
+ var big = $('addrAssigned');
660
+ var note = $('addrAssignedNote');
661
+ if (custom) {
662
+ big.innerHTML = '<code>' + esc(custom) + '</code> — your own domain, wired up for you.';
663
+ note.textContent = 'DNS pointing happens after the standup; the card on your projects page walks you through it.';
664
+ } else if (state.noAddress) {
665
+ big.textContent = 'Starting without an address.';
666
+ note.textContent = 'Sign-in on your project can’t be set up until it has one — add it any time from your projects page.';
667
+ } else if (assigned) {
668
+ big.innerHTML = '<code>' + esc(assigned) + '</code> — yours from the moment it stands up.';
669
+ note.textContent = 'Sign-in, sharing, everything just works. You can point a domain you own at it any time.';
670
+ } else if (!validSlug(state.slug)) {
671
+ big.textContent = 'Pick a handle above and the address comes with it.';
672
+ note.textContent = '';
673
+ } else {
674
+ /* dotless host (local dev): nothing sane to auto-assign — the domain
675
+ field IS the step here, so unfold it (a folded disclosure made the
676
+ opt-out checkbox unreachable-by-default; owner walk, 2026-08-12) */
677
+ big.textContent = 'No address is assigned on this host.';
678
+ note.textContent = 'Add a domain you own below, or start without one.';
679
+ $('addrCustom').open = true;
680
+ }
681
+ }
594
682
  /* Pinned GitHub OAuth-app values for a domain (auth.js WEB_REDIRECT_ORIGIN —
595
683
  callback is the apex /api/gds/auth/web/callback). Mirrors onboard.oauthAppInstructions. */
596
684
  function oauthAppValues(domain) {
@@ -633,6 +721,7 @@ input[type=text].bad{border-color:var(--err);}
633
721
  var h = $(panels[step]).querySelector('h1');
634
722
  if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
635
723
  if (step === 2) { syncHome(); ensureRepoAccess(); }
724
+ if (step === 3) renderAddrPanel();
636
725
  if (step === 4) renderReview();
637
726
  }
638
727
 
@@ -642,6 +731,7 @@ input[type=text].bad{border-color:var(--err);}
642
731
  $('f-repo').value = state.repo;
643
732
  $('f-slug').value = state.slug;
644
733
  $('f-domain').value = state.domain;
734
+ $('f-noaddr').checked = !!state.noAddress;
645
735
 
646
736
  /* Auto-fill the repo name from the project name until the visitor edits it
647
737
  directly (mirrors the handle's auto-derive). The owner is filled once we know
@@ -826,6 +916,7 @@ input[type=text].bad{border-color:var(--err);}
826
916
  if (!state.slug) { h.innerHTML = '&nbsp;'; h.className = 'hint'; this.classList.remove('bad'); }
827
917
  else if (validSlug(state.slug)) { h.textContent = 'Nice — that works.'; h.className = 'hint ok'; this.classList.remove('bad'); }
828
918
  else { h.textContent = '2–40 lowercase letters, numbers, or dashes; no leading/trailing/double dash.'; h.className = 'hint err'; this.classList.add('bad'); }
919
+ renderAddrPanel();
829
920
  saveDraft();
830
921
  });
831
922
  $('f-domain').addEventListener('input', function () {
@@ -834,6 +925,12 @@ input[type=text].bad{border-color:var(--err);}
834
925
  if (!state.domain) { h.innerHTML = '&nbsp;'; h.className = 'hint'; this.classList.remove('bad'); }
835
926
  else if (looksLikeDomain(state.domain)) { h.textContent = 'Looks like a domain.'; h.className = 'hint ok'; this.classList.remove('bad'); }
836
927
  else { h.textContent = 'That doesn’t look like a domain yet (e.g. mercury.example.com).'; h.className = 'hint err'; this.classList.add('bad'); }
928
+ renderAddrPanel();
929
+ saveDraft();
930
+ });
931
+ $('f-noaddr').addEventListener('change', function () {
932
+ state.noAddress = !!this.checked;
933
+ renderAddrPanel();
837
934
  saveDraft();
838
935
  });
839
936
 
@@ -865,6 +962,15 @@ input[type=text].bad{border-color:var(--err);}
865
962
  return false;
866
963
  }
867
964
  if (state.domain && !looksLikeDomain(state.domain)) return false;
965
+ /* no auto-assigned address (dotless host): going domainless must be an
966
+ EXPLICIT choice — a domain, or the opt-out box. Never silent. */
967
+ if (!resolvedDomain() && !state.noAddress) {
968
+ $('addrCustom').open = true;
969
+ var hd = $('h-domain');
970
+ hd.textContent = 'Add a domain you own — or tick “start without an address” to go without one.';
971
+ hd.className = 'hint err';
972
+ return false;
973
+ }
868
974
  return true;
869
975
  }
870
976
  return true;
@@ -888,7 +994,9 @@ input[type=text].bad{border-color:var(--err);}
888
994
  ['Starting from', state.mode === 'adopt' ? 'A repo you already have' : 'A fresh, empty repo'],
889
995
  ['Repository', '<code>' + esc(state.owner + '/' + state.repo) + '</code>'],
890
996
  ['Handle', '<code>' + esc(state.slug) + '</code>'],
891
- ['Domain', state.domain ? '<code>' + esc(state.domain) + '</code>' : '<em style="font-weight:600;color:var(--ink-faint)">add later</em>'],
997
+ ['Address', resolvedDomain()
998
+ ? '<code>' + esc(resolvedDomain()) + '</code>'
999
+ : '<em style="font-weight:600;color:var(--warn)">none — sign-in stays locked until it has one</em>'],
892
1000
  ['Shape', 'Standalone — your own repo, pinned platform core']
893
1001
  ];
894
1002
  $('reviewList').innerHTML = rows.map(function (r) {
@@ -952,7 +1060,7 @@ input[type=text].bad{border-color:var(--err);}
952
1060
  body: JSON.stringify({
953
1061
  slug: state.slug,
954
1062
  hosting_shape: 'standalone',
955
- domain: state.domain || undefined,
1063
+ domain: resolvedDomain() || undefined,
956
1064
  target_ref: state.owner + '/' + state.repo,
957
1065
  /* LOAD-BEARING for the control-plane runner, not just for the checklist we
958
1066
  render (task 1002562): 'adopt' makes it clone + layer onto the repo's
@@ -974,7 +1082,7 @@ input[type=text].bad{border-color:var(--err);}
974
1082
  }).then(function (data) {
975
1083
  try {
976
1084
  sessionStorage.setItem(CREATED_KEY, JSON.stringify({
977
- id: data.instance && data.instance.id, slug: state.slug, domain: state.domain,
1085
+ id: data.instance && data.instance.id, slug: state.slug, domain: resolvedDomain(),
978
1086
  mode: state.mode, name: state.name, at: Date.now()
979
1087
  }));
980
1088
  sessionStorage.removeItem(DRAFT_KEY);
@@ -1016,10 +1124,12 @@ input[type=text].bad{border-color:var(--err);}
1016
1124
  var FALLBACK_HUMAN_KEYS = ['github-repo', 'oauth-app', 'first-sign-in', 'enter-hall'];
1017
1125
 
1018
1126
  function fetchPlan() {
1019
- var domain = state.domain || '';
1127
+ var domain = resolvedDomain() || '';
1020
1128
  var key = state.mode + '|' + domain;
1021
1129
  if (!planPromises[key]) {
1022
- planPromises[key] = fetch(API + '/provisioning/onboard-plan?mode=' + encodeURIComponent(state.mode) + (domain ? '&domain=' + encodeURIComponent(domain) : ''))
1130
+ // shape=standalone: this wizard only creates control-plane standalone
1131
+ // instances, and the plan is shape-filtered server-side (direction §7)
1132
+ planPromises[key] = fetch(API + '/provisioning/onboard-plan?mode=' + encodeURIComponent(state.mode) + '&shape=standalone' + (domain ? '&domain=' + encodeURIComponent(domain) : ''))
1023
1133
  .then(function (res) { if (!res.ok) throw new Error('plan'); return res.json(); })
1024
1134
  .then(function (d) { return (d && d.steps) || []; })
1025
1135
  .catch(function () {
@@ -1048,7 +1158,7 @@ input[type=text].bad{border-color:var(--err);}
1048
1158
  : '<li data-onboard-step="github-repo"><b>Your empty repo is ready</b><p>We created ' + repoLabel + ' on GitHub for you — public (projects here are open by design), empty, and waiting. The scaffold lands there when the build runs; nothing for you to do here.</p></li>';
1049
1159
  }
1050
1160
  var ghNewUrl = githubNewRepoUrl(state.owner, state.repo);
1051
- return '<li' + (landed ? ' class="sdone"' : '') + ' data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
1161
+ return '<li' + (landed ? ' class="sdone"' : '') + ' data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. GitHub will ask public or private — your choice; both work here, and we never change it. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
1052
1162
  '<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + GH_ICON + 'Create ' + repoLabel + ' on GitHub</a></li>';
1053
1163
  }
1054
1164
 
@@ -1056,7 +1166,7 @@ input[type=text].bad{border-color:var(--err);}
1056
1166
  control-plane placement is named as the operator's, never handed to the owner
1057
1167
  as a command to run (gap 4). */
1058
1168
  function manualOauthDetails() {
1059
- var oa = oauthAppValues(state.domain);
1169
+ var oa = oauthAppValues(resolvedDomain());
1060
1170
  return '<details><summary>Prefer to create an OAuth app by hand?</summary>' +
1061
1171
  '<p>GitHub → <em>your account’s</em> Settings → Developer settings → OAuth Apps → New OAuth App. (Developer settings live under your account, not the repo.)</p>' +
1062
1172
  '<div class="kv"><span class="kk">Homepage URL</span><code>' + esc(oa.homepageUrl) + '</code>' + copyBtn(oa.homepageUrl) + '</div>' +
@@ -1068,11 +1178,18 @@ input[type=text].bad{border-color:var(--err);}
1068
1178
  (gap 3 / idea 1000676); locked until a domain exists (the App needs the real
1069
1179
  callback host — the server refuses domainless with domain_required). */
1070
1180
  function renderStepOauthApp() {
1071
- if (!state.domain) {
1181
+ var domain = resolvedDomain();
1182
+ if (!domain) {
1072
1183
  return '<li class="slocked" data-onboard-step="oauth-app"><b>Wire GitHub sign-in</b><p>Unlocks once your project has an address — sign-in needs a real domain. Attach one any time from <a href="/projects">your projects page</a>.</p></li>';
1073
1184
  }
1185
+ /* honesty about the venue (owner walk, 2026-08-12): GitHub rejects app
1186
+ manifests whose hook URL isn't publicly reachable, so a locally-hosted
1187
+ platform can't complete the one-click flow — say it BEFORE the bounce. */
1188
+ if (!apexBase()) {
1189
+ return '<li class="slocked" data-onboard-step="oauth-app"><b>Wire GitHub sign-in</b><p>This platform is running on a local address, and GitHub refuses app setups it can’t reach over the public internet — so the one-click flow can’t complete here. On a publicly-addressed platform this is one click.</p>' + manualOauthDetails() + '</li>';
1190
+ }
1074
1191
  if (live.manifest === 'done') {
1075
- return '<li class="sdone" data-onboard-step="oauth-app"><b>GitHub sign-in is wired</b><p>Credentials placed and verified — sign-in on <code style="font-family:var(--font-mono)">' + esc(state.domain) + '</code> is live.</p></li>';
1192
+ return '<li class="sdone" data-onboard-step="oauth-app"><b>GitHub sign-in is wired</b><p>Credentials placed and verified — sign-in on <code style="font-family:var(--font-mono)">' + esc(domain) + '</code> is live.</p></li>';
1076
1193
  }
1077
1194
  if (live.manifest === 'error' || live.manifest === 'expired') {
1078
1195
  return '<li data-onboard-step="oauth-app"><b>Wire GitHub sign-in</b><p>' +
@@ -1087,15 +1204,17 @@ input[type=text].bad{border-color:var(--err);}
1087
1204
  }
1088
1205
 
1089
1206
  function renderStepFirstSignIn() {
1090
- if (!state.domain) {
1207
+ var domain = resolvedDomain();
1208
+ if (!domain) {
1091
1209
  return '<li class="slocked" data-onboard-step="first-sign-in"><b>First sign-in</b><p>Unlocks with sign-in above — you’ll open your project’s address, sign in with GitHub once, and be seated as the owner (the founding Archon).</p></li>';
1092
1210
  }
1093
- return '<li data-onboard-step="first-sign-in"><b>First sign-in</b><p>Open <a href="https://' + esc(state.domain) + '" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(state.domain) + '</code></a> and sign in with GitHub once. That seats you as the owner — the founding Archon.</p></li>';
1211
+ return '<li data-onboard-step="first-sign-in"><b>First sign-in</b><p>Open <a href="https://' + esc(domain) + '" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(domain) + '</code></a> and sign in with GitHub once. That seats you as the owner — the founding Archon.</p></li>';
1094
1212
  }
1095
1213
 
1096
1214
  function renderStepEnterHall() {
1097
- return state.domain
1098
- ? '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>Your project’s home — open <a href="https://' + esc(state.domain) + '/builders" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(state.domain) + '/builders</code></a> and start building.</p></li>'
1215
+ var domain = resolvedDomain();
1216
+ return domain
1217
+ ? '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>Your project’s home — open <a href="https://' + esc(domain) + '/builders" target="_blank" rel="noopener"><code style="font-family:var(--font-mono)">' + esc(domain) + '/builders</code></a> and start building.</p></li>'
1099
1218
  : '<li data-onboard-step="enter-hall"><b>Enter your builders hall</b><p>No address yet — run <code style="font-family:var(--font-mono)">bongos dev</code> in your repo checkout to start the local hall on your machine.</p></li>';
1100
1219
  }
1101
1220
 
@@ -1106,6 +1225,12 @@ input[type=text].bad{border-color:var(--err);}
1106
1225
  'enter-hall': renderStepEnterHall
1107
1226
  };
1108
1227
 
1228
+ /* The newborn-instance welcome + account-nag suppression (direction §5.6,
1229
+ idea 1000701) is NOT implemented here by design: it lives on the CHILD
1230
+ instance's own surfaces (its hall greeting + needs roster), which this
1231
+ control-plane wizard cannot reach. Spun out per the direction's §9
1232
+ delivery-split as task 1002739. */
1233
+
1109
1234
  function renderSteps() {
1110
1235
  fetchPlan().then(function (steps) {
1111
1236
  var html = steps.filter(function (s) { return s && s.kind === 'human'; }).map(function (s) {
@@ -1122,7 +1247,7 @@ input[type=text].bad{border-color:var(--err);}
1122
1247
  var adopt = state.mode === 'adopt';
1123
1248
  $('doneLede').innerHTML = 'Provisioning is requested for <strong>' + esc(state.name || state.slug) + '</strong>' +
1124
1249
  (adopt ? ' — adopting your existing repo' : '') +
1125
- '. The platform stands up the server, database, and address on its own — you can watch its live status right here.';
1250
+ '. The platform stands up the server, database, and address on its own — watch it right here, or any time on <a href="/projects">your projects page</a>.';
1126
1251
  renderSteps();
1127
1252
  }
1128
1253
 
@@ -1215,9 +1340,9 @@ input[type=text].bad{border-color:var(--err);}
1215
1340
  if (!el) return true;
1216
1341
  var s = inst && inst.status;
1217
1342
  if (s === 'requested') {
1218
- el.innerHTML = progressMarkup(1, 1, 'Waiting for a builder runner to pick it up — usually under a minute.');
1343
+ el.innerHTML = progressMarkup(1, 1, 'Waiting for a runner to pick it up. This updates the moment it starts.');
1219
1344
  } else if (s === 'provisioning') {
1220
- el.innerHTML = progressMarkup(1, 1, 'Raising the server, database, and address. A few minutes, no rush needed on your side.');
1345
+ el.innerHTML = progressMarkup(1, 1, 'Raising the server, database, and address — nothing needed from you.');
1221
1346
  } else if (s === 'active') {
1222
1347
  var addr = inst.domain ? 'https://' + inst.domain : null;
1223
1348
  el.innerHTML = progressMarkup(-1, 3,
@@ -1225,11 +1350,13 @@ input[type=text].bad{border-color:var(--err);}
1225
1350
  '. Finish the steps below and the hall is yours.', 'isLive');
1226
1351
  return true;
1227
1352
  } else if (s === 'error') {
1353
+ /* the honest snag (direction §5 / F19): no raw stderr as the headline, no
1354
+ false "keeps retrying" — retries stop, and the retry door is /projects */
1228
1355
  el.innerHTML = progressMarkup(1, 1,
1229
- 'The standup hit a snag' + (inst.error_note ? ': ' + esc(inst.error_note) : '') +
1230
- '. The platform keeps retrying — this page updates as it recovers.', 'isErr');
1356
+ 'The standup stopped partway — it won’t continue on its own. Retry it (and see the technical details) from <a href="/projects">your projects page</a>.', 'isErr');
1231
1357
  } else if (s === 'tearing_down' || s === 'torn_down') {
1232
- el.innerHTML = progressMarkup(-1, 0, 'This instance was torn down. You can start another project below.', 'isErr');
1358
+ /* a deliberate end is calm, never styled as a failure (direction §5) */
1359
+ el.innerHTML = progressMarkup(-1, 0, 'This instance was torn down. Bring it back any time from <a href="/projects">your projects page</a>, or start another below.');
1233
1360
  return true;
1234
1361
  } else {
1235
1362
  el.innerHTML = progressMarkup(0, 1, 'Filed. Watching for the standup to start…');
@@ -1297,8 +1424,9 @@ input[type=text].bad{border-color:var(--err);}
1297
1424
  live = { instStatus: null, manifest: null, manifestMsg: '' };
1298
1425
  lastStepsHtml = '';
1299
1426
  try { sessionStorage.removeItem(CREATED_KEY); sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
1300
- state = { step: 1, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
1427
+ state = { step: 1, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false };
1301
1428
  $('f-name').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
1429
+ $('f-noaddr').checked = false;
1302
1430
  // Re-seed the owner from the signed-in account for the next project.
1303
1431
  if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
1304
1432
  $('h-name').innerHTML = '&nbsp;';