@bongos/core 1.19.429 → 1.19.430

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.
@@ -0,0 +1,421 @@
1
+ <!doctype html>
2
+ <html lang="en" data-mode="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>The planet picker on the manage page, in the world</title>
7
+ <link rel="icon" href="data:,">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
11
+ <link rel="stylesheet" href="../../../../public-landing/assets/cosmos.css">
12
+ <style>
13
+ body{
14
+ /* The composition round for task 1003347 (docs/design/projects-hub-direction-v2.md §16): the
15
+ planet picker in the manage page's "its planet" section, the slot task 1003345 left. cosmos.css
16
+ carries the hub's contract and the planets (their crops, materials and styles); the manage
17
+ frame is mine.html's (§14.1, built); this sheet adds only the picker's composition. Dark only
18
+ (ADR 0204). Every colour is a token. */
19
+ --orb-shadow:opacity(1);
20
+ }
21
+ /* ── the planet, docked: the sky's object, relative instead of absolute ── */
22
+ .mock .worldOrb{position:relative;left:auto;top:auto;transform:none;display:block;cursor:default;}
23
+ .mock .worldOrb .disc{display:block;margin:0;}
24
+
25
+ /* ── the manage frame (mine.html §14.1, as built) ── */
26
+ .view{max-width:1180px;margin:0 auto;padding:34px var(--gutter) 60px;}
27
+ .vTitle{font-family:var(--font-display);font-weight:300;font-size:clamp(28px,3.4vw,44px);letter-spacing:-.035em;line-height:1.04;text-transform:lowercase;color:var(--ink);margin:0;}
28
+ .vLede{font-size:15.5px;line-height:1.5;color:var(--ink-soft);max-width:62ch;margin:0 0 22px;}
29
+ .backRow{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;}
30
+ .backRow a{color:var(--ink-faint);text-decoration:none;display:inline-block;padding:3px 0;min-height:24px;line-height:18px;}
31
+ .backRow a:hover{color:var(--ink);}
32
+ .mHead{display:grid;grid-template-columns:auto 1fr;gap:0 26px;align-items:center;padding:22px 0 8px;}
33
+ .mHead .titleRow{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;}
34
+ .ledger{display:grid;grid-template-columns:172px 1fr;gap:0 22px;margin:14px 0 0;}
35
+ .ledger > *{border-top:1px solid var(--rule-soft);padding:13px 0;}
36
+ .ledger .k{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);padding-top:16px;}
37
+ .ledger .v{font-size:15px;line-height:1.5;color:var(--ink);}
38
+ .ledger .v a{font-family:var(--font-mono);font-size:13.5px;color:var(--ink-soft);text-decoration:none;display:inline-block;padding:3px 0;min-height:24px;line-height:18px;}
39
+ .ledger .v a:hover{color:var(--ink);text-decoration:underline;}
40
+ .ledger .v small{display:block;font-size:13px;color:var(--ink-soft);}
41
+ .swDot{display:inline-block;width:11px;height:11px;border-radius:50%;background:var(--sw);border:1px solid var(--rule);vertical-align:-1px;margin-right:8px;}
42
+ .rail{display:grid;grid-template-columns:repeat(3,1fr);gap:0 10px;list-style:none;margin:0;padding:0;}
43
+ .rail li{position:relative;border-top:1px solid var(--rule);padding:12px 0 0;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);line-height:1.3;}
44
+ .rail li::before{content:"";position:absolute;top:-4px;left:0;width:7px;height:7px;border-radius:50%;border:1px solid var(--rule);background:var(--bg);}
45
+ .rail.isLive li.done{border-top-color:var(--ok);color:var(--ok);}
46
+ .rail.isLive li.done::before{background:var(--ok);border-color:var(--ok);}
47
+ .railNote{grid-column:1/-1;margin:8px 0 0;font-family:var(--font-body);font-size:13px;line-height:1.45;color:var(--ink-soft);}
48
+ .railWrap{display:grid;gap:0;}
49
+ .doors{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-start;align-items:center;}
50
+ .door{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:0 18px;border:1px solid var(--rule);border-radius:var(--radius-pill);background:none;font-family:var(--font-body);font-size:14.5px;color:var(--ink);text-transform:lowercase;text-decoration:none;cursor:pointer;white-space:nowrap;}
51
+ .door:hover{border-color:var(--ink-faint);}
52
+ .door.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-on);font-weight:500;}
53
+ .door.primary:hover{background:color-mix(in srgb,var(--accent) 88%,var(--ink));}
54
+ .door.danger{border-color:color-mix(in srgb,var(--err) 45%,transparent);color:var(--err);}
55
+ .door.text{border-color:transparent;color:var(--ink-soft);text-decoration:underline;text-underline-offset:4px;padding:0 6px;}
56
+ .door.text:hover{color:var(--ink);border-color:transparent;}
57
+ .mSec{border-top:1px solid var(--rule);padding:30px 0 34px;display:grid;grid-template-columns:minmax(200px,300px) 1fr;gap:0 40px;}
58
+ .mSec h2{font-family:var(--font-display);font-weight:300;font-size:clamp(22px,2.4vw,28px);letter-spacing:-.03em;line-height:1.08;text-transform:lowercase;color:var(--ink);margin:0 0 8px;}
59
+ .mSec .why{font-size:13.5px;line-height:1.5;color:var(--ink-faint);max-width:34ch;margin:0;}
60
+ .mSec .body{display:grid;gap:16px;align-content:start;}
61
+ .choice{display:grid;grid-template-columns:repeat(2,1fr);border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);}
62
+ .choice label{display:grid;gap:6px;padding:16px 18px 18px;border-left:1px solid var(--rule-soft);cursor:pointer;color:var(--ink-soft);}
63
+ .choice label:first-child{border-left:0;padding-left:0;}
64
+ .choice .ch{display:flex;align-items:center;gap:10px;font-family:var(--font-body);font-weight:500;font-size:15px;color:var(--ink);}
65
+ .choice .ch::before{content:"";width:7px;height:7px;border-radius:50%;border:1px solid var(--rule);background:var(--bg);flex:none;}
66
+ .choice label.on .ch::before{background:var(--accent);border-color:var(--accent);outline:4px solid color-mix(in srgb,var(--accent) 22%,transparent);}
67
+ .choice .cb{font-size:13px;line-height:1.45;color:var(--ink-faint);}
68
+ .runs{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
69
+ .runs b{color:var(--ok);font-weight:400;}
70
+ .typed{display:grid;gap:10px;max-width:440px;}
71
+ .hairField{display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--rule);padding:10px 0;}
72
+ .hairField input{flex:1;min-width:0;background:none;border:0;font-family:var(--font-body);font-size:15.5px;color:var(--ink);padding:4px 0;}
73
+ .hairField input::placeholder{color:var(--ink-faint);}
74
+ .hairField input:focus{outline:none;}
75
+ .hairField:focus-within{border-bottom-color:var(--ink);}
76
+ input.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);}
77
+
78
+ /* ── THE PICKER: the preview planet on the left, the four bands on the right ── */
79
+ .pick{display:grid;grid-template-columns:168px 1fr;gap:0 34px;align-items:start;}
80
+ .pick .preview{display:grid;gap:12px;justify-items:center;padding-top:6px;}
81
+ .pick .preview .worldOrb{--d:132px;}
82
+ .pick .words{font-size:14px;line-height:1.45;color:var(--ink);text-align:center;max-width:168px;}
83
+ .pick .words small{display:block;font-size:12.5px;color:var(--ink-soft);}
84
+ .bands{display:grid;border-bottom:1px solid var(--rule);}
85
+ .band{display:grid;grid-template-columns:88px 1fr;gap:0 18px;align-items:center;border-top:1px solid var(--rule);}
86
+ .band:first-child{border-top-color:var(--rule);}
87
+ .band .bl{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);padding:14px 0;}
88
+ /* v1 · the bands: hairline cells of one band, a word each, the chosen one carries the dot */
89
+ .cells{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;}
90
+ .cells label{display:flex;align-items:center;gap:10px;min-height:48px;padding:10px 16px;border-left:1px solid var(--rule-soft);cursor:pointer;font-family:var(--font-body);font-size:14.5px;color:var(--ink-soft);}
91
+ .cells label::before{content:"";width:7px;height:7px;border-radius:50%;border:1px solid var(--rule);background:var(--bg);flex:none;}
92
+ .cells label.on{color:var(--ink);}
93
+ .cells label.on::before{background:var(--accent);border-color:var(--accent);outline:4px solid color-mix(in srgb,var(--accent) 22%,transparent);}
94
+ .cells label:hover{color:var(--ink);}
95
+ /* the colour: five swatches, the chosen one ringed by a hairline of the ink (a swatch is its own colour) */
96
+ .swatches{display:flex;gap:14px;align-items:center;padding:12px 0 12px 16px;border-left:1px solid var(--rule-soft);}
97
+ .sw{position:relative;width:28px;height:28px;border-radius:50%;border:1px solid var(--rule);background:var(--sw);cursor:pointer;}
98
+ .sw.on{outline:1px solid var(--ink);outline-offset:3px;}
99
+ /* v2 · the objects: the cell IS the planet, in the current material and colour; the chosen one
100
+ sits in the relationship ring (a hairline circle behind the object, DESIGN.md), the word under it */
101
+ .objs label{display:grid;justify-items:center;gap:6px;padding:12px 8px 12px;border-left:1px solid var(--rule-soft);cursor:pointer;}
102
+ .objs label::before{display:none;}
103
+ .objs .obj{position:relative;display:grid;place-items:center;width:78px;height:78px;}
104
+ .objs label .worldOrb{--d:60px;}
105
+ .objs label .nm{font-family:var(--label-face);font-size:11px;letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
106
+ .objs label.on .nm{color:var(--ink);}
107
+ .objs label .ring{position:absolute;inset:0;border-radius:50%;border:1px solid var(--accent);opacity:0;}
108
+ .objs label.on .ring{opacity:1;}
109
+ .objs label .worldOrb{position:relative;z-index:1;}
110
+ .pickDoors{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:4px;}
111
+ .receipt{margin:0;font-size:14px;line-height:1.5;color:var(--ok);}
112
+ .receipt.note{color:var(--ink-soft);}
113
+
114
+ /* ── the sheet furniture ── */
115
+ section[data-state]{display:none;}
116
+ body[data-state="bands"] section[data-state="bands"],
117
+ body[data-state="objects"] section[data-state="objects"],
118
+ body[data-state="saved"] section[data-state="saved"]{display:block;}
119
+ .vh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
120
+ .skip{position:absolute;left:12px;top:-60px;background:var(--bg-card);color:var(--ink);padding:11px 18px;border-radius:999px;}
121
+ .skip:focus{top:12px;}
122
+ .bar{height:var(--top-h);display:flex;align-items:center;padding:0 var(--gutter);border-bottom:1px solid var(--rule);font-family:var(--font-body);font-weight:500;font-size:17px;letter-spacing:-.015em;color:var(--ink);}
123
+ .mock .foot{padding:18px var(--gutter);font-size:14px;color:var(--ink-soft);text-align:center;}
124
+ a:focus-visible,button:focus-visible,label:focus-within{outline:2px solid var(--accent);outline-offset:3px;}
125
+
126
+ @media (max-width:900px){
127
+ .mHead{grid-template-columns:1fr;gap:14px;}
128
+ .ledger{grid-template-columns:1fr;gap:0;}
129
+ .ledger .k{border-top:1px solid var(--rule-soft);padding-bottom:0;}
130
+ .ledger .v{border-top:0;padding-top:6px;}
131
+ .mSec{grid-template-columns:1fr;gap:14px;}
132
+ .choice{grid-template-columns:1fr;}
133
+ .choice label{border-left:0;padding-left:0;border-top:1px solid var(--rule-soft);}
134
+ .choice label:first-child{border-top:0;}
135
+ /* the picker stacks: the preview above; each band's label above its choices; the cells become
136
+ the filter pills (hairline, the chosen one inverted to ink), so four bands stay one screen */
137
+ .pick{grid-template-columns:1fr;gap:22px 0;}
138
+ .pick .preview{justify-items:start;}
139
+ .pick .words{text-align:left;max-width:none;}
140
+ .band{grid-template-columns:1fr;gap:0;}
141
+ .band .bl{padding:14px 0 6px;}
142
+ .cells{display:flex;flex-wrap:wrap;gap:8px;padding:0 0 14px;}
143
+ .cells label{min-height:36px;padding:0 14px;border:1px solid var(--rule);border-radius:var(--radius-pill);gap:0;font-size:14px;}
144
+ .cells label::before{display:none;}
145
+ .cells label.on{background:var(--ink);color:var(--bg);border-color:var(--ink);}
146
+ .objs label{border:0;padding:6px 4px 8px;}
147
+ .objs label.on{background:none;}
148
+ .objs label .worldOrb{--d:52px;}
149
+ .objs .obj{width:66px;height:66px;}
150
+ .swatches{padding:6px 0 14px;border-left:0;}
151
+ }
152
+ </style>
153
+ </head>
154
+ <body class="mock" data-state="bands">
155
+ <!--
156
+ The planet picker on the manage page, the composition round (task 1003347, goal 1000080).
157
+ ?state=bands v1 the bands: the preview planet, then shape, material, style and colour as hairline
158
+ cells of one band each, a word per cell, the chosen one carrying the dot; the platform's
159
+ own pick shown first, "chosen for it by the platform"
160
+ ?state=objects v2 the objects: the same four bands, but the cell IS the planet, drawn in the current
161
+ material and colour; the chosen one sits in the relationship ring; the word under it
162
+ ?state=saved after the save: the receipt under the doors, the head and the ledger row repainted,
163
+ "your choice", and the door back to the platform's pick
164
+ The card's backdrop is not on this sheet: the poster's plate is the project's own art today, and a
165
+ platform-drawn plate set is an art round of its own; deferred, see §16.
166
+ -->
167
+ <a class="skip" href="#main">Skip to content</a>
168
+ <header class="bar">Cloud Bongos</header>
169
+ <main id="main">
170
+ <h1 class="vh">The planet picker on the manage page</h1>
171
+
172
+ <section data-state="bands">
173
+ <div class="view">
174
+ <p class="backRow"><a href="#">your projects</a></p>
175
+ <div class="mHead">
176
+ <span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta);--d:96px"><span class="disc"></span></span>
177
+ <div>
178
+ <div class="titleRow"><h2 class="vTitle">demo</h2><span class="chip live"><span class="cdot"></span>live <span class="rec">checked 3m ago</span></span></div>
179
+ <p class="vLede" style="margin:8px 0 0">Its address, its repository, what the platform last verified about it, and the controls you have today.</p>
180
+ </div>
181
+ </div>
182
+ <div class="ledger">
183
+ <span class="k">repository</span><span class="v"><a href="#">builder/demo</a></span>
184
+ <span class="k">address</span><span class="v">Answering at <a href="#">demo.cloudbongos.com</a><small>last answered 3 minutes ago</small></span>
185
+ <span class="k">planet</span><span class="v"><span class="swDot" style="--sw:var(--terracotta)"></span>the bongo pair in gold, washed<small>chosen for it by the platform. Pick your own below.</small></span>
186
+ </div>
187
+ <div class="railWrap" style="margin:22px 0 0;max-width:520px"><ul class="rail isLive"><li class="done">Request filed</li><li class="done">Standing it up</li><li class="done">Live</li></ul><p class="railNote">Up and answering since 1 Aug.</p></div>
188
+ <div class="doors" style="margin:22px 0 34px"><a class="door" href="#">open site</a><button type="button" class="door">restart it</button></div>
189
+
190
+ <div class="mSec">
191
+ <div><h2>who can see it</h2><p class="why">The platform's request. What the project runs is read back from its own manifest.</p></div>
192
+ <div class="body">
193
+ <div class="choice">
194
+ <label class="on"><input class="vh" type="radio" name="vis" checked><span class="ch">Everyone</span><span class="cb">Anyone can open this project's pages. Signing in is only needed to take part.</span></label>
195
+ <label><input class="vh" type="radio" name="vis"><span class="ch">Only signed-in members</span><span class="cb">Visitors meet a sign-in door first.</span></label>
196
+ </div>
197
+ <p class="runs">runs <b>everyone</b>, read from the project 2m ago</p>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="mSec">
202
+ <div><h2>its planet</h2><p class="why">How this project shows on the sky, in your list and on its card. Saved here, the platform draws it that way everywhere; nothing on your project restarts for it.</p></div>
203
+ <div class="body">
204
+ <div class="pick">
205
+ <div class="preview">
206
+ <span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span>
207
+ <p class="words">the bongo pair in gold, washed<small>the platform's pick for it</small></p>
208
+ </div>
209
+ <div class="bands">
210
+ <div class="band"><span class="bl">shape</span>
211
+ <div class="cells" role="radiogroup" aria-label="Shape">
212
+ <label><input class="vh" type="radio" name="shape">the blob</label>
213
+ <label class="on"><input class="vh" type="radio" name="shape" checked>the bongo pair</label>
214
+ <label><input class="vh" type="radio" name="shape">the bead</label>
215
+ </div>
216
+ </div>
217
+ <div class="band"><span class="bl">material</span>
218
+ <div class="cells" role="radiogroup" aria-label="Material">
219
+ <label><input class="vh" type="radio" name="material">chrome</label>
220
+ <label><input class="vh" type="radio" name="material">frost</label>
221
+ <label class="on"><input class="vh" type="radio" name="material" checked>gold</label>
222
+ <label><input class="vh" type="radio" name="material">obsidian</label>
223
+ </div>
224
+ </div>
225
+ <div class="band"><span class="bl">style</span>
226
+ <div class="cells" role="radiogroup" aria-label="Style">
227
+ <label><input class="vh" type="radio" name="style">tinted</label>
228
+ <label class="on"><input class="vh" type="radio" name="style" checked>washed</label>
229
+ <label><input class="vh" type="radio" name="style">plain</label>
230
+ </div>
231
+ </div>
232
+ <div class="band"><span class="bl">colour</span>
233
+ <div class="swatches" role="radiogroup" aria-label="Colour">
234
+ <label class="sw" style="--sw:var(--sea)"><input class="vh" type="radio" name="colour"><span class="vh">sea</span></label>
235
+ <label class="sw on" style="--sw:var(--terracotta)"><input class="vh" type="radio" name="colour" checked><span class="vh">terracotta</span></label>
236
+ <label class="sw" style="--sw:var(--gold-soft)"><input class="vh" type="radio" name="colour"><span class="vh">soft gold</span></label>
237
+ <label class="sw" style="--sw:var(--sun-lit)"><input class="vh" type="radio" name="colour"><span class="vh">sunlit</span></label>
238
+ <label class="sw" style="--sw:var(--accent)"><input class="vh" type="radio" name="colour"><span class="vh">the accent</span></label>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ <div class="pickDoors"><button type="button" class="door primary">save its planet</button></div>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="mSec">
248
+ <div><h2>take it offline</h2><p class="why">Stops the server and its nightly backups. It stays in your list, marked torn down, and the repository is untouched.</p></div>
249
+ <div class="body">
250
+ <div class="typed">
251
+ <label class="hairField"><input type="text" placeholder="Type the project's name to confirm" aria-label="Type the project's name to confirm"></label>
252
+ <div class="doors"><button type="button" class="door danger">tear it down</button></div>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </section>
258
+
259
+ <section data-state="objects">
260
+ <div class="view">
261
+ <p class="backRow"><a href="#">your projects</a></p>
262
+ <div class="mHead">
263
+ <span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta);--d:96px"><span class="disc"></span></span>
264
+ <div>
265
+ <div class="titleRow"><h2 class="vTitle">demo</h2><span class="chip live"><span class="cdot"></span>live <span class="rec">checked 3m ago</span></span></div>
266
+ <p class="vLede" style="margin:8px 0 0">Its address, its repository, what the platform last verified about it, and the controls you have today.</p>
267
+ </div>
268
+ </div>
269
+ <div class="ledger">
270
+ <span class="k">repository</span><span class="v"><a href="#">builder/demo</a></span>
271
+ <span class="k">address</span><span class="v">Answering at <a href="#">demo.cloudbongos.com</a><small>last answered 3 minutes ago</small></span>
272
+ <span class="k">planet</span><span class="v"><span class="swDot" style="--sw:var(--terracotta)"></span>the bongo pair in gold, washed<small>chosen for it by the platform. Pick your own below.</small></span>
273
+ </div>
274
+ <div class="doors" style="margin:22px 0 34px"><a class="door" href="#">open site</a><button type="button" class="door">restart it</button></div>
275
+
276
+ <div class="mSec">
277
+ <div><h2>its planet</h2><p class="why">How this project shows on the sky, in your list and on its card. Saved here, the platform draws it that way everywhere; nothing on your project restarts for it.</p></div>
278
+ <div class="body">
279
+ <div class="pick">
280
+ <div class="preview">
281
+ <span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span>
282
+ <p class="words">the bongo pair in gold, washed<small>the platform's pick for it</small></p>
283
+ </div>
284
+ <div class="bands">
285
+ <div class="band"><span class="bl">shape</span>
286
+ <div class="cells objs" role="radiogroup" aria-label="Shape">
287
+ <label><input class="vh" type="radio" name="shape2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="blob" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">the blob</span></label>
288
+ <label class="on"><input class="vh" type="radio" name="shape2" checked><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">the pair</span></label>
289
+ <label><input class="vh" type="radio" name="shape2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="bead" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">the bead</span></label>
290
+ </div>
291
+ </div>
292
+ <div class="band"><span class="bl">material</span>
293
+ <div class="cells objs" role="radiogroup" aria-label="Material">
294
+ <label><input class="vh" type="radio" name="material2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">chrome</span></label>
295
+ <label><input class="vh" type="radio" name="material2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="frost" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">frost</span></label>
296
+ <label class="on"><input class="vh" type="radio" name="material2" checked><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">gold</span></label>
297
+ <label><input class="vh" type="radio" name="material2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="obsidian" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">obsidian</span></label>
298
+ </div>
299
+ </div>
300
+ <div class="band"><span class="bl">style</span>
301
+ <div class="cells objs" role="radiogroup" aria-label="Style">
302
+ <label><input class="vh" type="radio" name="style2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="gold" data-style="tint" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">tinted</span></label>
303
+ <label class="on"><input class="vh" type="radio" name="style2" checked><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="gold" data-style="wash" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">washed</span></label>
304
+ <label><input class="vh" type="radio" name="style2"><span class="obj"><span class="ring"></span><span class="worldOrb" data-shape="pair" data-material="gold" data-style="plain" style="--acc:var(--terracotta)"><span class="disc"></span></span></span><span class="nm">plain</span></label>
305
+ </div>
306
+ </div>
307
+ <div class="band"><span class="bl">colour</span>
308
+ <div class="swatches" role="radiogroup" aria-label="Colour">
309
+ <label class="sw" style="--sw:var(--sea)"><input class="vh" type="radio" name="colour2"><span class="vh">sea</span></label>
310
+ <label class="sw on" style="--sw:var(--terracotta)"><input class="vh" type="radio" name="colour2" checked><span class="vh">terracotta</span></label>
311
+ <label class="sw" style="--sw:var(--gold-soft)"><input class="vh" type="radio" name="colour2"><span class="vh">soft gold</span></label>
312
+ <label class="sw" style="--sw:var(--sun-lit)"><input class="vh" type="radio" name="colour2"><span class="vh">sunlit</span></label>
313
+ <label class="sw" style="--sw:var(--accent)"><input class="vh" type="radio" name="colour2"><span class="vh">the accent</span></label>
314
+ </div>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ <div class="pickDoors"><button type="button" class="door primary">save its planet</button></div>
319
+ </div>
320
+ </div>
321
+
322
+ <div class="mSec">
323
+ <div><h2>take it offline</h2><p class="why">Stops the server and its nightly backups. It stays in your list, marked torn down, and the repository is untouched.</p></div>
324
+ <div class="body">
325
+ <div class="typed">
326
+ <label class="hairField"><input type="text" placeholder="Type the project's name to confirm" aria-label="Type the project's name to confirm"></label>
327
+ <div class="doors"><button type="button" class="door danger">tear it down</button></div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </section>
333
+
334
+ <section data-state="saved">
335
+ <div class="view">
336
+ <p class="backRow"><a href="#">your projects</a></p>
337
+ <div class="mHead">
338
+ <span class="worldOrb" data-shape="bead" data-material="obsidian" data-style="tint" style="--acc:var(--sea);--d:96px"><span class="disc"></span></span>
339
+ <div>
340
+ <div class="titleRow"><h2 class="vTitle">demo</h2><span class="chip live"><span class="cdot"></span>live <span class="rec">checked 3m ago</span></span></div>
341
+ <p class="vLede" style="margin:8px 0 0">Its address, its repository, what the platform last verified about it, and the controls you have today.</p>
342
+ </div>
343
+ </div>
344
+ <div class="ledger">
345
+ <span class="k">repository</span><span class="v"><a href="#">builder/demo</a></span>
346
+ <span class="k">address</span><span class="v">Answering at <a href="#">demo.cloudbongos.com</a><small>last answered 3 minutes ago</small></span>
347
+ <span class="k">planet</span><span class="v"><span class="swDot" style="--sw:var(--sea)"></span>the bead in obsidian, tinted<small>your choice.</small></span>
348
+ </div>
349
+ <div class="doors" style="margin:22px 0 34px"><a class="door" href="#">open site</a><button type="button" class="door">restart it</button></div>
350
+
351
+ <div class="mSec">
352
+ <div><h2>its planet</h2><p class="why">How this project shows on the sky, in your list and on its card. Saved here, the platform draws it that way everywhere; nothing on your project restarts for it.</p></div>
353
+ <div class="body">
354
+ <div class="pick">
355
+ <div class="preview">
356
+ <span class="worldOrb" data-shape="bead" data-material="obsidian" data-style="tint" style="--acc:var(--sea)"><span class="disc"></span></span>
357
+ <p class="words">the bead in obsidian, tinted<small>your choice</small></p>
358
+ </div>
359
+ <div class="bands">
360
+ <div class="band"><span class="bl">shape</span>
361
+ <div class="cells" role="radiogroup" aria-label="Shape">
362
+ <label><input class="vh" type="radio" name="shape3">the blob</label>
363
+ <label><input class="vh" type="radio" name="shape3">the bongo pair</label>
364
+ <label class="on"><input class="vh" type="radio" name="shape3" checked>the bead</label>
365
+ </div>
366
+ </div>
367
+ <div class="band"><span class="bl">material</span>
368
+ <div class="cells" role="radiogroup" aria-label="Material">
369
+ <label><input class="vh" type="radio" name="material3">chrome</label>
370
+ <label><input class="vh" type="radio" name="material3">frost</label>
371
+ <label><input class="vh" type="radio" name="material3">gold</label>
372
+ <label class="on"><input class="vh" type="radio" name="material3" checked>obsidian</label>
373
+ </div>
374
+ </div>
375
+ <div class="band"><span class="bl">style</span>
376
+ <div class="cells" role="radiogroup" aria-label="Style">
377
+ <label class="on"><input class="vh" type="radio" name="style3" checked>tinted</label>
378
+ <label><input class="vh" type="radio" name="style3">washed</label>
379
+ <label><input class="vh" type="radio" name="style3">plain</label>
380
+ </div>
381
+ </div>
382
+ <div class="band"><span class="bl">colour</span>
383
+ <div class="swatches" role="radiogroup" aria-label="Colour">
384
+ <label class="sw on" style="--sw:var(--sea)"><input class="vh" type="radio" name="colour3" checked><span class="vh">sea</span></label>
385
+ <label class="sw" style="--sw:var(--terracotta)"><input class="vh" type="radio" name="colour3"><span class="vh">terracotta</span></label>
386
+ <label class="sw" style="--sw:var(--gold-soft)"><input class="vh" type="radio" name="colour3"><span class="vh">soft gold</span></label>
387
+ <label class="sw" style="--sw:var(--sun-lit)"><input class="vh" type="radio" name="colour3"><span class="vh">sunlit</span></label>
388
+ <label class="sw" style="--sw:var(--accent)"><input class="vh" type="radio" name="colour3"><span class="vh">the accent</span></label>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ <div class="pickDoors"><button type="button" class="door primary">save its planet</button><button type="button" class="door text">let the platform choose again</button></div>
394
+ <p class="receipt" role="status">Saved. The sky, your list and the card draw it this way from here.</p>
395
+ </div>
396
+ </div>
397
+
398
+ <div class="mSec">
399
+ <div><h2>take it offline</h2><p class="why">Stops the server and its nightly backups. It stays in your list, marked torn down, and the repository is untouched.</p></div>
400
+ <div class="body">
401
+ <div class="typed">
402
+ <label class="hairField"><input type="text" placeholder="Type the project's name to confirm" aria-label="Type the project's name to confirm"></label>
403
+ <div class="doors"><button type="button" class="door danger">tear it down</button></div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+ </section>
409
+ </main>
410
+ <footer class="foot">A composition sheet for task 1003347. Nothing here is served.</footer>
411
+ <script>
412
+ /* The mock's state contract: read ?state= once, apply one state (bands | objects | saved).
413
+ No motion in script; nothing is built here. */
414
+ (function () {
415
+ var state = new URLSearchParams(location.search || location.hash.slice(1) || (window.__Q || '')).get('state') || 'bands';
416
+ if (['bands', 'objects', 'saved'].indexOf(state) === -1) state = 'bands';
417
+ document.body.setAttribute('data-state', state);
418
+ })();
419
+ </script>
420
+ </body>
421
+ </html>
@@ -234,3 +234,25 @@ Three mastheads, rendered dark and light at 1440 / 390 / 320 (ALL CLEAN) and mea
234
234
  **The kit.** Six per-step states in `projects.states.json` (`in-new-type` … `in-new-done`), each replaying the walk from the name step in a fresh context, and a **wizard driver** in `projects.probes.json` that files a project end to end at 1440 (and reaches the review at 390): the name, the type cell, the about textarea and the team cell, home, the handle, a domain, the review, `#doCreate`; then the done panel with the standup rail lit and the human steps rendered. The stub gained the handle check (`/provisioning/slug-available` → free) and its filing receipt now names an instance — Harbor, the fixture's standing-up row — so the done panel polls a real row and the rail reads "standing it up". Three things the driver taught: the stub's `/github/repo*` answer reads as repo access granted, so the home step takes the **fresh** path and names the repository from the project (no manual fields); a **dotless host assigns no address**, so the address step opens its domain fold itself and the walk brings `mercury.example.com` (a click on the summary would close it); the done clause's inline link sat under the 24px floor, the same problem the sheet had. AFTER: `render.js --page /projects` ALL CLEAN (66 states × 3 sizes), the empty and floor siblings ALL CLEAN, probes 41/41.
235
235
 
236
236
  **Retargeted.** `tests/projects_hub.mjs` gains two pins: the frame by scope (no card ground, six tracks, the dot, the lit step's halo, the lowercase title, the pills, the inverted sign-in, the halo gone, no box-shadow, no literal colour) and the structures (hairline fields, the bands, the ledger, the numbered steps with every child in the text column — the sheet's own bug — the rail without a pulse, the tick gone, the address in the display voice, no gradient) plus **the filing body pinned** (slug, `hosting_shape`, `target_ref`, `onboard_mode` — nothing enqueued changed, ADR 0111) and the kit's states and driver present. `tests/onboard.mjs` needed no retarget: the human steps' strings and tags are JS-rendered and untouched. The copy registry regenerated (line numbers moved with the block; no string changed).
237
+
238
+ ## 16. The planet picker: the composition round (task 1003347)
239
+
240
+ **Date:** 2026-08-29 · the owner's note on the round-3 review ("backdrop and materials are good, we should have a projects setting that allows you to select which material and color you want. This could also house and project card backdrop customization"), filed as task 1003347 behind the manage page (§14.1) and the materials (§13). The slot is the manage page's "its planet" section and the ledger's planet row ("chosen for it by the platform" until now).
241
+
242
+ **The round.** One sheet, `docs/design/mocks/hub/picker.html` (`?state=bands|objects|saved`; check-mock ALL RULES PASS; shot at 1440 and 390 through the kit's stub, ALL CLEAN): the manage frame as built (§14.1), and the "its planet" section with **the preview planet on the left** (the sky's object at 132px, its words under it: "the bongo pair in gold, washed", then "the platform's pick for it" or "your choice") and **four bands on the right** (shape · material · style · colour, each a label-face word in an 88px column with its choices beside it, on shared hairlines); the doors under them (save its planet as the primary pill; once a choice is stored, "let the platform choose again" as an underlined text door); the receipt as one line in the ok ink. **v1 the bands**: the choices as hairline cells of one band, a word each, the chosen one carrying the dot (the grammar of the two settings above it); the colour as five swatches on the world's five accents, the chosen one ringed by a hairline of the ink. **v2 the objects**: the same bands, but the cell IS the planet, drawn in that shape, material or style in the colour on, the chosen one sitting in the relationship ring (the hairline circle behind the object, DESIGN.md), the word under it in the label face. Under 900px both stack the preview above the bands; v1's cells become the list's filter pills (the chosen one inverted to ink) so four bands stay one screen; v2 keeps its objects in a row at 52px. The shots found one thing the sheet fixed: an object's ring must sit on an equal box per cell (the pair is wide and short, the bead tall), not on the object's own box. The pick page: https://claude.ai/code/artifact/<redacted> (the owner picks v1 or v2; if no pick lands, the build takes v1 the bands).
243
+
244
+ **What the sheet decides.** The choice is the thirty-six template names the sky already knows (shape × material × style, §13) plus one of the world's five accents, never a free colour (the Fifteen Rule); it shows the same on the sky, the list, the manage page and the card; unset, the platform's pick stands: a curated template if there is one, else the walk; the save is a settings save like visibility and joinability, with a receipt, and nothing on the project restarts for it (the planet is the platform's to draw, not the instance's to run). **What it does not.** The card's backdrop: the poster's plate is the project's own `art_url` today and the world has no plate set for a card; a platform-drawn set to choose from is an art round of its own (on the owner's key now that the free coverage has ended), filed as task 1003352 behind this one.
245
+
246
+ **Not built yet.** The page waits for the pick (the round's method). The build: the settings row grows `planet_template` and `planet_accent` as platform-side keys (validated, stored, audited; never transported to the instance's env, so `settingsEnvVars` and the runner's consumed-check stay on the instance's keys), the PATCH answering a no-restart receipt when only those change; `publicInstance` carries both so the list and the manage page read them from the row; a `provisioning.projectPlanets` seam lets `/projects/featured` carry them for the matching origin, so the sky, `planetOf` and the poster read the owner's choice before the curated template and the walk; the section in `cosmos.css` by scope; a kit state and a probe that saves through the stub; `tests/projects_hub.mjs` and the settings suites retargeted; the copy registry regenerated.
247
+
248
+ ### 16.1 The build: v1 the bands (task 1003347)
249
+
250
+ **Date:** 2026-08-29 · the pick page carried no comment in the session, and the round named its fallback ("if no pick lands, the build takes v1 the bands"), so the build took it — **v1 the bands**, the settings' own grammar. The pick page stays up; v2 the objects is a CSS-and-markup round away (the cell drawing the planet in place of the word).
251
+
252
+ **What was built.** The planet is now the owner's to choose, and one choice reads the same everywhere the platform draws it. **The row.** The provisioning row's jsonb `settings` column carries `planet_template` (one of the thirty-six names, mirrored as `PLANET_TEMPLATES` so provisioning never imports the hub's module — the `PROJECT_TYPES` precedent, pinned name for name) and `planet_accent` (one of the five accents BY NAME: sea, terracotta, gold-soft, sun-lit, accent — a token name, never a colour). They are read by `planetChoice(row)`, not `effectiveSettings`: the planet is deliberately not a setting in `SETTINGS_VOCAB`'s sense — a setting is a policy the instance obeys (it rides web.env and a restart applies it), the planet is the platform's to draw, so it has no default (null = the platform's own pick: a curated template, else the walk), never rides the env transport, and never restarts anything. `GET …/settings` answers `planet` beside `settings`; the PATCH validates both keys (nullable: null hands the pick back), writes them with the same audited merge, and answers a no-restart receipt when only the planet changed ("Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it."); a policy key re-sent over a retired push's error_note still re-kicks the push, a planet re-sent does not. `publicInstance` carries both keys, so the list and the manage page read the choice off the row with no second read. **The feed.** A new port, `provisioning.projectPlanets` (hosts → the owners' choices, newest row per host), registered beside `provisioning.instanceState`; `/projects/featured` resolves it optionally (an instance without provisioning has no choices to carry; never a required capability, so the hub boots without it) and spreads `planet_template` / `planet_accent` on every project — the wire always carries both keys. **The page.** `accentOf` / `templateOf` read the owner's choice first, then the curated template, then the walk; `planetOf(inst)` reads the row's own choice before the catalog or the slug walk and says whose pick it is (`chosen`), so the sky, the list row, the manage head and the poster's dot agree; the ledger's planet row reads "your choice." once one is stored. The "its planet" section (`#mPlanetPick`: the title, the volatile slot `#mPick`, the fold `#mPickWhat` as a sibling) sits between how people join and the ender: the preview planet on the left (the sky's object at 132px, its words under it, "the platform's pick for it" or "your choice"), four bands on the right (shape · material · style as hairline cells of one band, a word each, the chosen one carrying the dot; the colour as five swatches on their tokens, the chosen one ringed by a hairline of the ink), the doors under them (Save its planet; Let the platform choose again once a choice is stored), the receipt as the section's own status line. The pending pick follows the cells (the preview redraws on every change, the focus kept), survives the minute tick, and survives a catalog that lands late unless its resolution actually changed; the save is the settings PATCH the two sections above use, its reply a receipt — the head, the ledger row and the list repaint from the row and the sky re-reads the feed. Under 900px the preview stacks above the bands and the cells become the list's filter pills, the chosen one inverted to ink. `cosmos.css` carries it by scope (`#view-manage .pick…`): no card ground, no box shadow, no literal colour.
253
+
254
+ **The card's backdrop, deferred.** The poster's plate is the project's own `art_url` today; a platform-drawn plate set to choose from is an art round of its own — task 1003352, behind this one.
255
+
256
+ **The kit.** The stub remembers a saved planet for the run (a `PLANETS` map, the teardown-ask precedent) and answers the route's own receipt shapes, so the page's re-read finds its choice; both featured fixtures carry the keys on every row and Demo's owner has chosen `pair-obsidian-tint` on sea (the feed path — kept the pair's box so the sky's composition, and a signed-out Shift probe's blank-spot click, stand); Harbor's row carries `blob-frost-tint` on sun-lit (the row path — the list draws it with no catalog row). A state, `in-manage-planet`, renders the section on Demo; three probes save a pick and read the receipt, the head and the ledger row back, hand the pick back to the platform, and save through the pills at 390. `render.js --page /projects` ALL CLEAN (67 states), the empty and floor siblings ALL CLEAN, probes 44/44.
257
+
258
+ **Retargeted.** `tests/projects_hub.mjs`: the mine-row and walk pins name the owner's choice first, the field-path pin reads Demo's choice over the curated template, and five new pins hold the vocabulary lockstep (client ↔ provisioning ↔ platform-identity), the section's markup and fold, the script (the preview, the radiogroups, the tick and late-catalog rules, the PATCH body, no open-intent re-read), the sheet by scope with the 900px pills, the kit's state/probes/fixtures/stub, and the seam. `tests/provisioning_settings.mjs` (the planet: saved, echoed, audited, no push; null clears; no change; no re-kick; a mixed save; off-vocabulary → 400), `modules/provisioning/tests/provisioning.mjs` (the mirror, `planetChoice`, `publicInstance`), `tests/provisioning_settings_env.mjs` (never in the env). The API docs regenerated; the copy registry regenerated.
@@ -1295,5 +1295,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1295
1295
  landed since 1.19.427 with no explicit bump. run 33274315388. (task 1002620)
1296
1296
  1.19.429 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1297
1297
  landed since 1.19.428 with no explicit bump. run 33274860035. (task 1002620)
1298
+ 1.19.430 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1299
+ landed since 1.19.429 with no explicit bump. run 33275468532. (task 1002620)
1298
1300
  ---------------------------------------------------------------------------
1299
1301
  ```
@@ -17,6 +17,12 @@ const pi = require('../platform-identity');
17
17
  const projectStats = require('../project-stats');
18
18
 
19
19
  const { pool, requireBuilder, requirePermission, validateOrRespond, parseId, corsPublicGet } = api;
20
+ const log = api.logger('platform-identity');
21
+
22
+ // a catalog origin's host, the way the hub's sky joins it to an owned instance
23
+ function hostOfOrigin(origin) {
24
+ try { return new URL(String(origin)).hostname.replace(/^www\./, '').toLowerCase(); } catch { return ''; }
25
+ }
20
26
 
21
27
  module.exports = function projectsRoutes() {
22
28
  const router = express.Router();
@@ -127,6 +133,23 @@ module.exports = function projectsRoutes() {
127
133
  } catch (err) {
128
134
  console.error('[platform-identity] project stats enrichment failed (non-fatal):', err.message);
129
135
  }
136
+ // The owners' planets (task 1003347): a project's owner may have chosen how
137
+ // the platform draws it. The choice lives on the provisioning row, so it
138
+ // reaches this public feed through the `provisioning.projectPlanets` port —
139
+ // optional, because an instance without that module has no choices to
140
+ // carry — host-matched, the join the sky makes. Best-effort like the stats:
141
+ // a failed read leaves every project on the platform's own pick.
142
+ let planets = {};
143
+ try {
144
+ planets = await api.resolveOptional('provisioning.projectPlanets', async () => ({}))(projects.map((p) => hostOfOrigin(p.origin)));
145
+ } catch (err) {
146
+ log.warn('project planets enrichment failed (non-fatal):', err.message);
147
+ }
148
+ // the wire always carries both keys: null = the platform's own pick
149
+ const planetOf = (p) => {
150
+ const c = (planets && planets[hostOfOrigin(p.origin)]) || {};
151
+ return { planet_template: c.planet_template || null, planet_accent: c.planet_accent || null };
152
+ };
130
153
  // The enrichment's keys (builders, tags, owner, owner_avatar, faces,
131
154
  // shipped_30d, shipped_30d_capped, hall_origin) never collide with the
132
155
  // catalog's; a project that did not answer carries the ONE shared null
@@ -137,6 +160,7 @@ module.exports = function projectsRoutes() {
137
160
  ...p,
138
161
  art_url: projectStats.httpUrl(p.art_url, 2048),
139
162
  ...(stats[p.origin] || projectStats.NULL_STATS),
163
+ ...planetOf(p),
140
164
  })),
141
165
  });
142
166
  } catch (err) {
@@ -14,6 +14,6 @@
14
14
  "migrations": true,
15
15
  "pollers": ["liveness-sweep"]
16
16
  },
17
- "provides": ["provisioning.instanceState"],
17
+ "provides": ["provisioning.instanceState", "provisioning.projectPlanets"],
18
18
  "consumes": []
19
19
  }