@bongos/core 1.19.484 → 1.19.486

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.bongos-core.json +102 -52
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +6 -0
  4. package/clients/bongos-client/index.cjs +6 -0
  5. package/clients/bongos-client/index.d.ts +7 -0
  6. package/clients/bongos-client/index.mjs +6 -0
  7. package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +155 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +147 -3
  10. package/docs/api-reference.md +7 -4
  11. package/docs/copy-inventory.md +169 -178
  12. package/docs/copy-registry.json +295 -405
  13. package/docs/file-map.md +1 -1
  14. package/docs/module-api-changelog.md +4 -0
  15. package/migrations/core_231_project_settings_and_attention_water.sql +163 -0
  16. package/modules/dev-box/app/src/vendor/bongos-client.cjs +6 -0
  17. package/modules/hall-ui/public/goal-inbox.js +119 -1
  18. package/modules/hall-ui/public/not-ready.html +14 -10
  19. package/modules/hall-ui/public/not-ready.states.json +14 -0
  20. package/modules/hall-ui/public/pair.css +17 -67
  21. package/modules/hall-ui/public/pair.html +8 -1
  22. package/modules/hall-ui/public/pair.states.json +14 -0
  23. package/modules/hall-ui/public/panel.css +333 -0
  24. package/modules/hall-ui/public/profile.css +85 -0
  25. package/modules/hall-ui/public/profile.html +12 -5
  26. package/modules/hall-ui/public/profile.states.json +15 -0
  27. package/modules/hall-ui/public/settings.css +119 -336
  28. package/modules/hall-ui/public/settings.html +28 -19
  29. package/modules/hall-ui/public/settings.js +44 -35
  30. package/modules/hall-ui/public/settings.states.json +16 -0
  31. package/modules/hall-ui/public/style.css +5 -2
  32. package/modules/lifecycle/module.json +2 -1
  33. package/modules/lifecycle/rot.js +210 -0
  34. package/modules/lifecycle/routes/rot.js +160 -0
  35. package/package-lock.json +2 -2
  36. package/package.json +1 -1
  37. package/scripts/gds/fitness-checks-vocabulary.js +0 -1
  38. package/scripts/gds/fitness.js +9 -0
  39. package/src/bongos/project-settings.js +193 -0
  40. package/src/module-api.js +29 -1
  41. package/tests/hall_settings_world.mjs +274 -0
  42. package/tests/join_application_note.mjs +7 -15
  43. package/tests/module-scope-map.mjs +1 -1
  44. package/tests/module_api.mjs +1 -0
  45. package/tests/rot_inbox.mjs +532 -0
  46. package/modules/hall-ui/public/my-projects.css +0 -62
  47. package/modules/hall-ui/public/my-projects.html +0 -73
  48. package/modules/hall-ui/public/my-projects.js +0 -153
@@ -0,0 +1,333 @@
1
+ /* panel.css — the PANEL archetype: a titled panel that holds a form, read top
2
+ to bottom (task 1003308, goal 1000081). Shared by settings, profile, pair,
3
+ and not-ready — the pages where the builder reads a label, a
4
+ sentence of consequence, and then acts on ONE control.
5
+
6
+ THE ARCHETYPE is v3 "the glass hall", the owner's pick from the composition
7
+ round — docs/design/hall-direction-v2.md §6, mock
8
+ docs/design/mocks/hall/settings/v3.html. Reading order inside a panel: the
9
+ panel's name, one lede of purpose, then ROWS — each a label, a description in
10
+ the soft ink, and the control on the right — or a FIELD, its label above it
11
+ in the label voice, the input and its button on one line, the status line
12
+ under it, and a note in the faint ink at the foot saying when the change
13
+ applies. The panel is the shell's `.scroll`; v3 has exactly three card levels
14
+ and a fourth would be a design decision, not a local override.
15
+
16
+ WHAT IS SHARED and what is not: the row, the switch, the field, the chip
17
+ control, the status/hint/note voices and the sign-in gate are the family's.
18
+ The section rail and the token ladder are settings' (settings.css); the
19
+ identity band and the shelf are the profile's (profile.css); the device card
20
+ is pair's (pair.css).
21
+
22
+ THE NAMES the page scripts already write (`.sound-row`, `.sessions-row`,
23
+ `.settings-btn`, `.craft-chip` …) are aliased onto the family's rules here
24
+ rather than renamed in three JS files: a class name is not what was wrong
25
+ with these pages — the six divergent hand-rolled rows were.
26
+
27
+ Everything below spends the world's real token names. No compat alias
28
+ (--surface / --surface-2 / --border / --border-strong / --accent-weak /
29
+ --accent-text) appears here — they exist for pages nobody has re-skinned, so
30
+ spending one is legal CSS that quietly opts this sheet back out of the world.
31
+ `--line` is a WIDTH; the hairline's colour is --rule / --rule-soft. */
32
+
33
+ /* ---- the page head ------------------------------------------------------ */
34
+ /* The family takes the reading room's VOICE — the page's own name, lowercase in
35
+ the display face — because on a settings page the document IS the page: the
36
+ builder came to read their own record and change one line of it. The room's
37
+ top air is a reading room's; a form page starts at the content edge. */
38
+ .page-head--room.page-head--panel { padding-top: 0; }
39
+ .page-head--room.page-head--panel .page-head__sub { margin-top: var(--sp-sm); }
40
+
41
+ /* ---- the panel's voices ------------------------------------------------- */
42
+ .panel-lede,
43
+ .scroll > .panel-lede { margin: 0 0 var(--sp-sm); max-width: var(--measure); font-size: 14.5px; color: var(--ink-soft); }
44
+ .panel-note,
45
+ .section-note { margin: var(--sp-sm) 0 0; max-width: var(--measure); font-size: 13px; color: var(--ink-faint); }
46
+ .panel-note:empty, .section-note:empty { display: none; }
47
+ .panel-hint,
48
+ .settings-card__hint { font-size: 13px; color: var(--ink-faint); }
49
+ .panel-status,
50
+ .settings-status,
51
+ .pair-status { margin: var(--sp-xs) 0 0; min-height: 1.2em; font-size: 13.5px; color: var(--ink-soft); }
52
+ .panel-status--ok, .settings-card__ok, .pair-status--ok { color: var(--ok); }
53
+ .panel-status--danger, .pair-status--err { color: var(--danger); }
54
+ .panel-status:empty, .settings-status:empty { margin-top: 0; min-height: 0; }
55
+ /* a prose link in any of the four voices is a control and owes the 24px floor */
56
+ .panel-lede a, .panel-note a, .panel-hint a, .panel-status a, .settings-card__hint a, .section-note a {
57
+ display: inline-block;
58
+ min-width: 24px;
59
+ min-height: 24px;
60
+ padding: 3px 4px;
61
+ margin: 0 -4px;
62
+ color: var(--accent-ink);
63
+ }
64
+
65
+ /* ---- the row: label, description, control ------------------------------ */
66
+ .prows,
67
+ .sound-rows, .event-sound-rows, .sessions-list, #cross-project-rows, #privacy-rows { display: flex; flex-direction: column; margin-top: var(--sp-sm); }
68
+ .prow,
69
+ .sound-row, .event-sound-row, .sessions-row {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: var(--sp-sm);
73
+ padding: var(--sp-sm) 0;
74
+ border-top: var(--line) solid var(--rule-soft);
75
+ }
76
+ .prow__main,
77
+ .sound-row__text, .event-sound-row__text, .sessions-row__main { flex: 1 1 auto; min-width: 0; }
78
+ .prow__label,
79
+ .sound-row__label, .event-sound-row__label, .sessions-row__main > strong { display: block; margin: 0; font-size: 14.5px; font-weight: 500; color: var(--ink); }
80
+ .prow__desc,
81
+ .sound-row__desc, .event-sound-row__desc, .sessions-row__agent, .sessions-row__when { display: block; margin: 2px 0 0; font-size: 13.5px; color: var(--ink-soft); }
82
+ .prow__ctl,
83
+ .event-sound-row__controls { display: flex; align-items: center; justify-content: flex-end; flex: none; flex-wrap: wrap; gap: var(--sp-hair); }
84
+ /* the default note on a row label — "· default for your rank" — is a fact, so it
85
+ takes the label voice beside the label rather than the label's own weight */
86
+ .prow__tag,
87
+ .voices__default { margin-left: 8px; font-family: var(--label-face); font-size: 10.5px; font-weight: 400; letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-faint); }
88
+ /* a pick row: the whole row is the radio's label */
89
+ .prow--pick { cursor: pointer; }
90
+ .prow--pick .prow__main { display: block; }
91
+ .prow--pick .prow__label, .prow--pick .prow__desc { display: block; }
92
+ .prow--pick[aria-disabled="true"] { cursor: default; }
93
+ .prow--pick[aria-disabled="true"] .prow__label { color: var(--ink-soft); }
94
+
95
+ /* the action row at a panel's foot: buttons and their status on one line */
96
+ .row-end,
97
+ .settings-card, .craft-actions, .artkey-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-xs); margin-top: var(--sp-sm); }
98
+ .row-end .panel-status, .row-end .settings-status,
99
+ .craft-actions .settings-status { margin: 0; }
100
+
101
+ /* ---- the controls: every one clears 24px on both axes ------------------- */
102
+ /* the family button: a glass pill, 38px, quiet by default. `--save` is the
103
+ accent door — the panel's one primary act — and takes --accent-on, the dark
104
+ ink for the salmon; white on it fails AA and DESIGN.md bans it. */
105
+ .pbtn,
106
+ .settings-btn, .sessions-row__end, .event-sound-preview, .pair-approve {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ gap: 8px;
111
+ min-height: 38px;
112
+ min-width: 38px;
113
+ padding: 0 15px;
114
+ border: var(--line) solid var(--glass-edge);
115
+ border-radius: var(--radius-pill);
116
+ background: var(--glass);
117
+ color: var(--ink);
118
+ font: inherit;
119
+ font-size: 14px;
120
+ line-height: 1.2;
121
+ white-space: nowrap;
122
+ text-decoration: none;
123
+ cursor: pointer;
124
+ transition: background 200ms cubic-bezier(.2,.7,.3,1), border-color 200ms cubic-bezier(.2,.7,.3,1), color 200ms cubic-bezier(.2,.7,.3,1);
125
+ }
126
+ .pbtn:hover:not(:disabled), .settings-btn:hover:not(:disabled), .sessions-row__end:hover:not(:disabled),
127
+ .event-sound-preview:hover:not(:disabled), .pair-approve:hover:not(:disabled) { background: var(--glass-strong); color: var(--ink); text-decoration: none; }
128
+ .pbtn:disabled, .settings-btn:disabled, .sessions-row__end:disabled, .pair-approve:disabled,
129
+ .pbtn[aria-disabled="true"], .settings-btn[aria-disabled="true"] { color: var(--ink-faint); background: var(--glass); cursor: default; }
130
+ .pbtn--quiet, .settings-btn--quiet, .sessions-row__end, .event-sound-preview { background: transparent; }
131
+ .pbtn--save,
132
+ .craft-save, .pair-approve { background: var(--accent); border-color: var(--accent); color: var(--accent-on); font-weight: 500; }
133
+ .pbtn--save:hover:not(:disabled), .craft-save:hover:not(:disabled), .pair-approve:hover:not(:disabled) { background: var(--accent-lift); border-color: var(--accent-lift); color: var(--accent-on); }
134
+ .craft-save:disabled, .pbtn--save:disabled, .pair-approve:disabled { background: var(--glass); border-color: var(--glass-edge); color: var(--ink-faint); }
135
+ /* the shell's `.craft-save` was a bordered box; it is the family's save button
136
+ now, so it carries the pill's own box model too */
137
+ button.craft-save { display: inline-flex; align-items: center; justify-content: center; min-height: 38px; min-width: 38px; padding: 0 15px; border-radius: var(--radius-pill); font: inherit; font-size: 14px; cursor: pointer; }
138
+
139
+ /* the field: label above, input and its button on one line */
140
+ .field-label,
141
+ .join-note-label {
142
+ display: block;
143
+ margin: var(--sp-sm) 0 var(--sp-hair);
144
+ font-family: var(--label-face);
145
+ font-size: var(--label-size);
146
+ letter-spacing: var(--label-space);
147
+ text-transform: var(--label-case);
148
+ color: var(--ink-faint);
149
+ }
150
+ .field,
151
+ .display-name-row, .artkey-entry-row, .join { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-xs); margin-top: 0; }
152
+ .field .field-label { flex: 1 1 100%; margin-top: 0; }
153
+ .pinput,
154
+ .display-name-input, .artkey-input, .join input, .join-note {
155
+ flex: 1 1 260px;
156
+ max-width: 420px;
157
+ min-height: 38px;
158
+ padding: 0 16px;
159
+ font: inherit;
160
+ font-size: 15px;
161
+ color: var(--ink);
162
+ background: var(--glass);
163
+ border: var(--line) solid var(--glass-edge);
164
+ border-radius: var(--radius-pill);
165
+ transition: border-color 200ms cubic-bezier(.2,.7,.3,1);
166
+ }
167
+ .pinput::placeholder, .display-name-input::placeholder, .artkey-input::placeholder, .join input::placeholder, .join-note::placeholder { color: var(--ink-faint); }
168
+ .pinput:hover, .display-name-input:hover, .artkey-input:hover, .join input:hover, .join-note:hover { border-color: var(--rule); }
169
+ .pinput[aria-invalid="true"], .display-name-input[aria-invalid="true"] { border-color: var(--danger); }
170
+ /* a secret is typed in the mono face so a pasted key reads as what it is */
171
+ .artkey-input { font-family: var(--font-mono); font-size: 13.5px; }
172
+ .psel,
173
+ .event-sound-select, .voices__select { min-height: 38px; padding: 0 var(--sp-xs); font: inherit; font-size: 14px; color: var(--ink); background: var(--glass); border: var(--line) solid var(--glass-edge); border-radius: var(--radius-pill); cursor: pointer; }
174
+ .psel:hover, .event-sound-select:hover, .voices__select:hover { border-color: var(--rule); }
175
+ .prange,
176
+ .sound-volume input[type="range"] { flex: 1 1 200px; max-width: 260px; height: 38px; margin: 0; accent-color: var(--accent); cursor: pointer; }
177
+ .pradio { width: 24px; height: 24px; flex: none; margin: 0; accent-color: var(--accent); cursor: pointer; }
178
+ .pradio:disabled { cursor: default; opacity: .55; }
179
+
180
+ /* the switch: a 46x38 button whose drawn track is 44x24 — the target clears
181
+ the floor, the drawing stays the world's small shape. State is aria-checked;
182
+ the page scripts still read data-on, which they wrote. */
183
+ .sw,
184
+ .sound-toggle {
185
+ position: relative;
186
+ display: inline-flex;
187
+ align-items: center;
188
+ width: 46px;
189
+ min-width: 46px;
190
+ height: 38px;
191
+ flex: none;
192
+ padding: 0;
193
+ margin: 0;
194
+ border: 0;
195
+ background: none;
196
+ font-size: 0;
197
+ color: transparent;
198
+ cursor: pointer;
199
+ }
200
+ .sw__track {
201
+ width: 44px;
202
+ height: 24px;
203
+ border-radius: var(--radius-pill);
204
+ background: var(--glass-strong);
205
+ border: 1px solid var(--rule);
206
+ transition: background 240ms cubic-bezier(.2,.7,.3,1), border-color 240ms cubic-bezier(.2,.7,.3,1);
207
+ }
208
+ .sw__knob {
209
+ position: absolute;
210
+ left: 5px;
211
+ width: 16px;
212
+ height: 16px;
213
+ border-radius: var(--radius-pill);
214
+ background: var(--ink-faint);
215
+ transition: transform 260ms cubic-bezier(.2,.7,.3,1), background 240ms cubic-bezier(.2,.7,.3,1);
216
+ }
217
+ .sw[aria-checked="true"] .sw__track, .sound-toggle[aria-checked="true"] .sw__track { background: var(--accent-tint); border-color: var(--accent); }
218
+ .sw[aria-checked="true"] .sw__knob, .sound-toggle[aria-checked="true"] .sw__knob { transform: translateX(20px); background: var(--accent); }
219
+ .sw:disabled, .sound-toggle:disabled { cursor: default; opacity: .55; }
220
+ @media (prefers-reduced-motion: reduce) {
221
+ .sw__track, .sw__knob, .pbtn, .settings-btn, .pinput { transition: none; }
222
+ }
223
+
224
+ /* the chip control: a checkbox in a pill. The box is a 24px target drawing a
225
+ 14px square — the 13x13 native box was the smallest control in the hall. */
226
+ .chips,
227
+ .craft-chips { display: flex; flex-wrap: wrap; gap: var(--sp-hair); margin-top: var(--sp-sm); }
228
+ .chip-ctl,
229
+ .craft-chip {
230
+ display: inline-flex;
231
+ align-items: center;
232
+ gap: 6px;
233
+ min-height: 38px;
234
+ padding: 0 14px 0 8px;
235
+ border: var(--line) solid var(--glass-edge);
236
+ border-radius: var(--radius-pill);
237
+ background: var(--glass);
238
+ color: var(--ink-soft);
239
+ font-size: 14px;
240
+ text-transform: lowercase;
241
+ cursor: pointer;
242
+ user-select: none;
243
+ }
244
+ .chip-ctl:has(input:checked), .craft-chip[data-on="1"] { color: var(--ink); border-color: var(--rule); }
245
+ .chip-ctl input, .craft-pref {
246
+ appearance: none;
247
+ -webkit-appearance: none;
248
+ position: relative;
249
+ width: 24px;
250
+ height: 24px;
251
+ margin: 0;
252
+ flex: none;
253
+ cursor: pointer;
254
+ }
255
+ .chip-ctl input::before, .craft-pref::before {
256
+ content: "";
257
+ position: absolute;
258
+ inset: 5px;
259
+ width: 14px;
260
+ height: 14px;
261
+ border: var(--line) solid var(--ink-faint);
262
+ border-radius: 4px;
263
+ background: transparent;
264
+ }
265
+ .chip-ctl input:checked::before, .craft-pref:checked::before { background: var(--accent); border-color: var(--accent); }
266
+ .chip-ctl input:checked::after, .craft-pref:checked::after {
267
+ content: "";
268
+ position: absolute;
269
+ left: 9px;
270
+ top: 6px;
271
+ width: 4px;
272
+ height: 8px;
273
+ border: solid var(--accent-on);
274
+ border-width: 0 2px 2px 0;
275
+ transform: rotate(45deg);
276
+ }
277
+
278
+ /* ---- the status pill on a row --------------------------------------------- */
279
+ /* the fixed status vocabulary in the label voice — the shell's .fact-pill; the
280
+ sessions badge is the same pill under the name settings-sessions.js writes */
281
+ .sessions-row__badge {
282
+ display: inline-flex;
283
+ align-items: center;
284
+ min-height: 24px;
285
+ padding: 0 11px;
286
+ margin-left: 8px;
287
+ border: var(--line) solid currentColor;
288
+ border-radius: var(--radius-pill);
289
+ font-family: var(--label-face);
290
+ font-size: 10.5px;
291
+ font-weight: 400;
292
+ letter-spacing: var(--label-space);
293
+ text-transform: var(--label-case);
294
+ color: var(--info);
295
+ white-space: nowrap;
296
+ vertical-align: middle;
297
+ }
298
+
299
+ /* ---- the banner: one line of consequence, warn or ok ---------------------- */
300
+ .pbanner {
301
+ margin: 0 0 var(--sp-sm);
302
+ padding: var(--sp-xs) var(--sp-sm);
303
+ font-size: 14px;
304
+ line-height: 1.45;
305
+ color: var(--ink);
306
+ background: var(--warn-weak);
307
+ border-left: 2px solid var(--warn);
308
+ border-radius: var(--radius-sm);
309
+ }
310
+ .pbanner--ok { background: var(--ok-weak); border-left-color: var(--ok); }
311
+ .pbanner strong { font-weight: 500; }
312
+
313
+ /* ---- the sign-in gate ----------------------------------------------------- */
314
+ /* Shown INSTEAD of the page when there is nobody to show it to. It is a panel
315
+ with the display voice, the honest sentence, and the one door. */
316
+ .gate { border-top: var(--line) solid var(--glass-edge); }
317
+ .gate__h { margin: 0; font-family: var(--font-display); font-weight: 300; font-size: clamp(22px, 2.1vw, 29px); line-height: 1.15; letter-spacing: -.03em; color: var(--ink); }
318
+ .gate__lede { margin: var(--sp-xs) 0 var(--sp-sm); max-width: var(--measure); font-size: 15px; color: var(--ink-soft); }
319
+ .gate .row-end { margin-top: 0; }
320
+
321
+ /* ---- the skeleton --------------------------------------------------------- */
322
+ /* --skeleton, never --bg-deep: that one collapses to pure black in dark */
323
+ .pskel { display: block; background: var(--skeleton); border-radius: var(--radius-sm); }
324
+ .pskel--row { height: 52px; margin-top: var(--sp-hair); }
325
+ .pskel--h { height: 26px; width: min(240px, 60%); }
326
+
327
+ /* ---- responsive ----------------------------------------------------------- */
328
+ @media (max-width: 520px) {
329
+ .prow, .sound-row, .event-sound-row, .sessions-row { flex-direction: column; align-items: flex-start; gap: var(--sp-xs); }
330
+ .prow__ctl, .event-sound-row__controls { justify-content: flex-start; }
331
+ .prow--pick { flex-direction: row; align-items: flex-start; }
332
+ .pinput, .display-name-input, .artkey-input, .join input, .join-note { max-width: none; }
333
+ }
@@ -0,0 +1,85 @@
1
+ /* profile.css — what the builder's profile page has and the rest of the panel
2
+ family does not (task 1003308, goal 1000081; the archetype is panel.css). The
3
+ page everyone else reads: the identity band, the figures, the shelf, the
4
+ ledgers. The figures are the shell's glass band (.stat-row) and the ledgers
5
+ the shell's .ledger; this sheet owns the identity band and the shelf, both
6
+ drawn from docs/design/mocks/hall/settings/v3.html (the profile half).
7
+
8
+ The shell's .profile grid is shared with Home's Standing card, so the band
9
+ here is a MODIFIER (.profile--ident) rather than a second rule for the base
10
+ name; the shelf's .ach is scoped by this sheet loading on this page only. */
11
+
12
+ /* ---- the identity band ---------------------------------------------------- */
13
+ .profile--ident {
14
+ grid-template-columns: 56px minmax(0, 1fr) auto;
15
+ gap: var(--sp-sm);
16
+ align-items: center;
17
+ }
18
+ .profile--ident .profile__avatar {
19
+ width: 56px;
20
+ height: 56px;
21
+ border-radius: var(--radius-pill);
22
+ background: var(--skeleton);
23
+ border: 1px solid var(--rule);
24
+ }
25
+ .profile--ident .profile__name {
26
+ font-family: var(--font-display);
27
+ font-weight: 300;
28
+ font-size: clamp(22px, 2vw, 28px);
29
+ letter-spacing: -.03em;
30
+ line-height: 1.1;
31
+ }
32
+ .profile--ident .profile__handle { margin: 4px 0 0; font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-faint); }
33
+ .profile--ident .profile__stats { margin-top: 4px; font-size: 13px; }
34
+ .profile--ident .profile__disciplines { margin-top: 4px; font-size: 13px; }
35
+ .profile--ident .profile__credits-num { font-family: var(--font-display); font-weight: 300; font-size: clamp(26px, 2.4vw, 32px); letter-spacing: -.035em; line-height: 1; color: var(--ink); }
36
+ .profile--ident .profile__credits-label,
37
+ .profile--ident .profile__karma-label { font-family: var(--label-face); font-size: var(--label-size); font-weight: 400; letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-faint); }
38
+ .profile--ident .profile__karma-num { font-weight: 500; }
39
+ .profile--ident .profile__placeholder { grid-column: 1 / -1; }
40
+
41
+ /* ---- the shelf -------------------------------------------------------------
42
+ An achievement is a --bg-card tile on the ground (the section is bare, so a
43
+ tile is the panel and not a card in a card), a hairline on top, the mark as
44
+ its figure, the name at the row's weight and the day it was won in the soft
45
+ ink at the foot. */
46
+ .ach-grid { grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: var(--sp-sm); }
47
+ .ach {
48
+ flex-direction: column;
49
+ gap: 5px;
50
+ padding: 18px;
51
+ background: var(--bg-card);
52
+ border: var(--line) solid var(--glass-edge);
53
+ border-top-color: var(--rule);
54
+ border-radius: var(--radius);
55
+ }
56
+ .ach__icon { font-size: 22px; line-height: 1; margin-bottom: 4px; }
57
+ .ach__name { font-size: 14.5px; font-weight: 500; margin: 0; }
58
+ .ach__desc { font-size: 13px; line-height: 1.4; color: var(--ink-soft); }
59
+ .ach__bonus { margin: auto 0 0; padding-top: var(--sp-hair); font-size: 12.5px; font-weight: 400; color: var(--ink-soft); }
60
+ .ach--locked { border-top-color: var(--rule-soft); }
61
+ .ach--locked .ach__name, .ach--locked .ach__desc { color: var(--ink-faint); }
62
+
63
+ /* ---- the ledgers ---------------------------------------------------------- */
64
+ /* A title in a ledger cell is a door and owes the 24px floor on both axes; it
65
+ measured 18px tall on every populated render (thirteen links a page). The
66
+ prose-link recipe: inline-block buys the height, the padding buys the width,
67
+ the negative margin hands both back so the cell does not re-space. */
68
+ #works-scroll .ledger td a,
69
+ #activity-scroll .ledger td a,
70
+ #xp-scroll a {
71
+ display: inline-block;
72
+ min-width: 24px;
73
+ min-height: 24px;
74
+ padding: 3px 4px;
75
+ margin: -3px -4px;
76
+ color: var(--accent-ink);
77
+ }
78
+ .profile-act__h { margin: var(--sp-md) 0 var(--sp-xs); font-size: 15px; font-weight: 500; color: var(--ink); }
79
+ #activity-body > [id$="-panel"] + [id$="-panel"] { margin-top: var(--sp-md); }
80
+
81
+ @media (max-width: 640px) {
82
+ .profile--ident { grid-template-columns: 48px minmax(0, 1fr); }
83
+ .profile--ident .profile__avatar { width: 48px; height: 48px; }
84
+ .profile--ident .profile__credits { grid-column: 2; align-items: flex-start; text-align: left; }
85
+ }
@@ -14,6 +14,9 @@
14
14
  <link rel="preconnect" href="https://fonts.googleapis.com">
15
15
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
16
  <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
17
+ <!-- The PANEL archetype (task 1003308) before the page's own sheet. -->
18
+ <link rel="stylesheet" href="/builders/panel.css">
19
+ <link rel="stylesheet" href="/builders/profile.css">
17
20
  </head>
18
21
  <body data-page="profile" data-page-title="Profile">
19
22
 
@@ -23,7 +26,11 @@
23
26
  <div class="app-body">
24
27
  <header class="app-topbar" id="app-topbar"></header>
25
28
  <div class="app-content">
26
- <div class="page-head">
29
+ <!-- The page's ONE h1 (one-header-main-h1), in the family's voice; the
30
+ sub-line is profile.js's — "<name> — <rank> · N works shipped". -->
31
+ <div class="page-head page-head--room page-head--panel">
32
+ <p class="page-head__eyebrow">the page everyone else reads</p>
33
+ <h1 id="profile-h1" class="page-head__h">profile</h1>
27
34
  <p class="page-head__sub" id="profile-sub">Loading profile…</p>
28
35
  </div>
29
36
 
@@ -31,7 +38,7 @@
31
38
  <!-- Standing — avatar, name, rank badge, member-since. Filled by profile.js. -->
32
39
  <section class="scroll" id="standing-scroll" aria-labelledby="standing-h">
33
40
  <h2 id="standing-h" class="sr-only">Standing</h2>
34
- <div class="profile" id="profile-head">
41
+ <div class="profile profile--ident" id="profile-head">
35
42
  <div class="profile__placeholder">…</div>
36
43
  </div>
37
44
  </section>
@@ -92,19 +99,19 @@
92
99
  <div class="profile__placeholder" id="activity-placeholder">…</div>
93
100
 
94
101
  <div id="activity-tasks-panel" hidden>
95
- <h3 class="scroll__h">Works completed</h3>
102
+ <h3 class="profile-act__h">Works completed</h3>
96
103
  <div id="activity-tasks"></div>
97
104
  </div>
98
105
 
99
106
  <div id="activity-goals-panel" hidden>
100
- <h3 class="scroll__h">Goals created</h3>
107
+ <h3 class="profile-act__h">Goals created</h3>
101
108
  <div id="activity-goals"></div>
102
109
  </div>
103
110
 
104
111
  <!-- The idea trail. Revealed only in the craft views where filing an
105
112
  idea is the work being shown (all + ideator) — see profile.js. -->
106
113
  <div id="activity-ideas-panel" hidden>
107
- <h3 class="scroll__h">Ideas filed, and what became of them</h3>
114
+ <h3 class="profile-act__h">Ideas filed, and what became of them</h3>
108
115
  <p class="scroll__lede">Every idea this builder captured — promoted, discarded, merged, or still open.</p>
109
116
  <div id="activity-ideas"></div>
110
117
  </div>
@@ -0,0 +1,15 @@
1
+ {
2
+ "_": "The hall's PROFILE page through modules/ui-design/kit/render.js (task 1003308). The identity band, the shelf and the three activity ledgers only exist once a profile has LOADED, so the floors run against the hall-preview harness (see settings.states.json for the two commands; pass --base-in/--base-out at it). `out` is a real state here rather than a redirect: a signed-out visitor with no ?id= gets the empty state, not a bounce — under the harness's --fixture-me the /me read still answers, so `out` renders the own profile there and the empty state only under the kit's own stub.",
3
+ "page": "/profile",
4
+ "surface": "hall-ui",
5
+ "stub": { "prefix": "/builders/" },
6
+ "modeQuery": false,
7
+ "ignoreRequests": ["/api/(gds|bongos)/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "\b401\b", "\b404\b", "Unauthorized", "Not Found", "load failed"],
9
+ "states": {
10
+ "own": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#profile-h1", "#profile-head", "#numbers-body", "#works-table", "#activity-tabs"] } },
11
+ "other": { "auth": true, "url": "/profile?id=1000025", "actions": [["wait", 900]], "expect": { "visible": ["#profile-head", "#ach-grid"] } },
12
+ "out": { "auth": false, "actions": [["wait", 600]], "expect": { "visible": ["#profile-h1", "#profile-head"] } }
13
+ },
14
+ "reducedMotion": { "auth": true, "actions": [["wait", 900]] }
15
+ }