@bongos/core 1.19.928 → 1.19.930

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 (50) hide show
  1. package/.bongos-core.json +80 -50
  2. package/docs/copy-inventory.md +36 -15
  3. package/docs/copy-registry.json +239 -28
  4. package/docs/file-map.md +1 -1
  5. package/docs/module-api-changelog.md +4 -0
  6. package/modules/hall-ui/public/atlas.html +1 -1
  7. package/modules/hall-ui/public/blockers.html +1 -1
  8. package/modules/hall-ui/public/board-room.html +1 -1
  9. package/modules/hall-ui/public/collab.html +1 -1
  10. package/modules/hall-ui/public/copy-desk.html +1 -1
  11. package/modules/hall-ui/public/deploy.html +1 -1
  12. package/modules/hall-ui/public/diagrams.html +1 -1
  13. package/modules/hall-ui/public/drachmae.html +1 -1
  14. package/modules/hall-ui/public/gate.html +1 -1
  15. package/modules/hall-ui/public/goals.html +1 -1
  16. package/modules/hall-ui/public/government.html +1 -1
  17. package/modules/hall-ui/public/idea-objects.js +109 -0
  18. package/modules/hall-ui/public/idea.html +1 -1
  19. package/modules/hall-ui/public/ideas.html +1 -1
  20. package/modules/hall-ui/public/index.html +1 -1
  21. package/modules/hall-ui/public/modules.html +1 -1
  22. package/modules/hall-ui/public/people.html +1 -1
  23. package/modules/hall-ui/public/primer.html +1 -1
  24. package/modules/hall-ui/public/profile.html +1 -1
  25. package/modules/hall-ui/public/project-settings.html +1 -1
  26. package/modules/hall-ui/public/ranks.html +1 -1
  27. package/modules/hall-ui/public/roadmap.html +1 -1
  28. package/modules/hall-ui/public/roster.html +1 -1
  29. package/modules/hall-ui/public/sessions.html +1 -1
  30. package/modules/hall-ui/public/settings.html +1 -1
  31. package/modules/hall-ui/public/shell.js +11 -0
  32. package/modules/hall-ui/public/studio.html +1 -1
  33. package/modules/hall-ui/public/style.css +10 -0
  34. package/modules/hall-ui/public/task.html +1 -1
  35. package/modules/hall-ui/public/thinking.css +277 -0
  36. package/modules/hall-ui/public/thinking.html +74 -0
  37. package/modules/hall-ui/public/thinking.js +418 -0
  38. package/modules/hall-ui/public/thinking.states.json +20 -0
  39. package/modules/hall-ui/public/watch.html +1 -1
  40. package/modules/hall-ui/public/work.html +1 -1
  41. package/package-lock.json +2 -2
  42. package/package.json +1 -1
  43. package/src/bongos/serve-internal.js +7 -1
  44. package/src/module-api.js +1 -1
  45. package/tests/hall_board_world.mjs +32 -0
  46. package/tests/hall_nav.mjs +1 -1
  47. package/tests/hall_page_gate_map.mjs +6 -0
  48. package/tests/hall_thinking_room.mjs +366 -0
  49. package/tests/helpers.mjs +124 -0
  50. package/tests/nav_permission_atoms.mjs +1 -1
@@ -0,0 +1,109 @@
1
+ // modules/hall-ui/public/idea-objects.js — THE FIVE OBJECTS, as a reader's
2
+ // words (task 1004098 / BV2.R03 "the room", goal 1000095).
3
+ //
4
+ // The server derives which object a thought IS — once, in
5
+ // modules/ideas/idea-state.js, from the ledger (ADR 0322). This file is the
6
+ // other half of that decision and deliberately does NOT re-derive anything: it
7
+ // maps a state the server already answered onto the word, the sentence and the
8
+ // mark a surface draws for it. `resolveIdeaState` is the authority on WHICH of
9
+ // the five; this is the authority on how one LOOKS.
10
+ //
11
+ // IT IS SHARED BECAUSE TWO SURFACES DRAW THESE AND A THIRD WILL. The room
12
+ // (this task) is the first, the sky (BV2.R26) is the next, and the idea record
13
+ // is the obvious third. A vocabulary copied into each of them is how the sky
14
+ // ends up calling a thought "burned out" while the room beside it says
15
+ // "declined" — the same failure the resolver exists to prevent, one layer up.
16
+ // The descent on /idea/:id is deliberately NOT a consumer: a descent row is a
17
+ // TASK, and marking one with an idea-level object would be exactly the
18
+ // re-derivation this pair of files refuses (see idea.js, "what a row is marked
19
+ // by").
20
+ //
21
+ // THE WORDS ARE THE DESIGN'S, taken from docs/design/ideator-surface-direction.md
22
+ // §"The five objects" rather than invented here, so the surface says what the
23
+ // owner said. Where that table is a fragment ("it burned, then the project
24
+ // removed it"), it is spelled into a sentence and nothing more.
25
+ //
26
+ // NO DOM, no fetch, no state. One frozen table and three lookups, so a page
27
+ // under test can read it without booting a page.
28
+ (function () {
29
+ 'use strict';
30
+
31
+ // Canonical order. It is the order the design lists them in and the order a
32
+ // filter row draws them in — brightest and most actionable first, folded and
33
+ // declined last — so two surfaces cannot disagree about where a pill sits.
34
+ var ORDER = ['star', 'protostar', 'satellite', 'remnant', 'cold-body'];
35
+
36
+ var OBJECTS = {
37
+ star: {
38
+ label: 'burning',
39
+ noun: 'star',
40
+ blurb: 'caused work that is still alive. it dims as pieces of that work are removed',
41
+ },
42
+ protostar: {
43
+ label: 'waiting',
44
+ noun: 'protostar',
45
+ blurb: 'filed, not yet ignited',
46
+ },
47
+ satellite: {
48
+ label: 'folded in',
49
+ noun: 'satellite',
50
+ blurb: 'folded into another thought. it orbits that one now, and its value counts there',
51
+ },
52
+ remnant: {
53
+ label: 'burned out',
54
+ noun: 'remnant',
55
+ blurb: 'it burned, then the project removed it. it keeps every bit of its size',
56
+ },
57
+ 'cold-body': {
58
+ label: 'declined',
59
+ noun: 'cold body',
60
+ blurb: 'declined at triage. it never ignited, so it has no size at all',
61
+ },
62
+ };
63
+
64
+ // An unknown state is passed through rather than swallowed, the way idea.js
65
+ // passes through an unknown descent relation: a sixth object would be a real
66
+ // answer the server gave, and drawing it unstyled beats drawing nothing or,
67
+ // worse, silently drawing it as a protostar.
68
+ function objectFor(state) {
69
+ var key = String(state || '');
70
+ return OBJECTS[key] || { label: key || 'unknown', noun: key || 'unknown', blurb: '' };
71
+ }
72
+
73
+ // POSITION is the second axis and it is only load-bearing for a protostar —
74
+ // the design is explicit that a star has a position too, and that the
75
+ // distinction only MEANS something for a thought nothing has claimed yet.
76
+ // So this returns a sentence for exactly that case and null otherwise,
77
+ // rather than letting every row carry a word that tells a reader nothing.
78
+ function positionWord(state) {
79
+ if (!state || state.state !== 'protostar') return null;
80
+ if (state.position !== 'well') return 'nothing has claimed it';
81
+ return state.awaitingNod ? 'waiting for a nod' : 'has a home';
82
+ }
83
+
84
+ // Brightness as a reader's phrase. THE THREE ANSWERS ARE THREE, never two:
85
+ // a number → this share of the work it caused is still alive
86
+ // null → the removal facts could not be read at all
87
+ // no work → nothing to be alive or dead yet
88
+ // Collapsing "we could not look" into "none of it survived" is the exact
89
+ // mistake modules/ideas/idea-state.js step 3 was written to prevent, and it
90
+ // would render a healthy instance as a field of hollow rings the moment the
91
+ // lifecycle module failed to mount.
92
+ function brightnessWord(state) {
93
+ if (!state || state.hasWork !== true) return null;
94
+ if (state.attributable !== true || state.brightness === null || state.brightness === undefined) {
95
+ return 'still alive — unknown';
96
+ }
97
+ var pct = Math.round(Number(state.brightness) * 100);
98
+ if (pct >= 100) return 'all of it still alive';
99
+ if (pct <= 0) return 'none of it still alive';
100
+ return pct + '% still alive';
101
+ }
102
+
103
+ window.OTBIdeaObjects = {
104
+ ORDER: ORDER,
105
+ objectFor: objectFor,
106
+ positionWord: positionWord,
107
+ brightnessWord: brightnessWord,
108
+ };
109
+ })();
@@ -52,7 +52,7 @@
52
52
 
53
53
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
54
54
 
55
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
55
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
56
56
  <script src="/builders/palette.js" defer></script>
57
57
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
58
58
  <!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
@@ -192,7 +192,7 @@
192
192
 
193
193
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
194
194
 
195
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
195
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
196
196
  <script src="/builders/palette.js" defer></script>
197
197
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
198
198
  <!-- task 1003069 (R08) — the quick-filing form's routed result links a task
@@ -102,7 +102,7 @@
102
102
  <script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
103
103
  <!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
104
104
  BEFORE builders.js so the chrome ids exist when events are wired. -->
105
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1" defer></script>
105
+ <script src="/builders/shell.js?v=2026-09-20-room1" defer></script>
106
106
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
107
107
  <script src="/builders/palette.js" defer></script>
108
108
  <!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
@@ -80,7 +80,7 @@
80
80
 
81
81
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
82
82
 
83
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
83
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
84
84
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
85
85
  <script src="/builders/palette.js" defer></script>
86
86
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
@@ -55,7 +55,7 @@
55
55
 
56
56
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
57
57
  chrome exists before the page script runs. -->
58
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
58
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
59
59
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
60
60
  <script src="/builders/palette.js" defer></script>
61
61
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -115,7 +115,7 @@
115
115
 
116
116
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
117
117
 
118
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
118
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
119
119
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
120
120
  <script src="/builders/palette.js" defer></script>
121
121
  <!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
@@ -187,7 +187,7 @@
187
187
 
188
188
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
189
189
  chrome exists before the page script runs. -->
190
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
190
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
191
191
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
192
192
  <script src="/builders/palette.js" defer></script>
193
193
  <!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
@@ -58,7 +58,7 @@
58
58
 
59
59
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
60
60
 
61
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
61
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
62
62
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
63
63
  <script src="/builders/palette.js" defer></script>
64
64
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
@@ -68,7 +68,7 @@
68
68
 
69
69
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
70
70
  chrome exists before the page script runs. -->
71
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
71
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
72
72
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
73
73
  <script src="/builders/palette.js" defer></script>
74
74
  <!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
@@ -41,7 +41,7 @@
41
41
 
42
42
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
43
43
 
44
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
44
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
45
45
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
46
46
  <script src="/builders/palette.js" defer></script>
47
47
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
@@ -50,7 +50,7 @@
50
50
 
51
51
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
52
52
  chrome exists before the page script runs. -->
53
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
53
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
54
54
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
55
55
  <script src="/builders/palette.js" defer></script>
56
56
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -99,7 +99,7 @@
99
99
 
100
100
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
101
101
  chrome exists before the page script runs. -->
102
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
102
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
103
103
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
104
104
  <script src="/builders/palette.js" defer></script>
105
105
  <!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
@@ -416,7 +416,7 @@
416
416
 
417
417
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
418
418
  chrome exists before the page scripts run. -->
419
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
419
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
420
420
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
421
421
  <script src="/builders/palette.js" defer></script>
422
422
  <!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
@@ -216,6 +216,17 @@
216
216
  // behind the ideator craft would hide the ideator's own surface from
217
217
  // everyone else who has a thought, which is most of the value.
218
218
  { id: 'ideas', label: 'Ideas', icon: 'goals', href: '/ideas', gate: 'signed' },
219
+ // task 1004098 (BV2.R03 of goal 1000095): THE ROOM — what you filed and
220
+ // what became of it. Directly under Ideas because the two are the daily
221
+ // loop: you file on one and you find out on the other, and the whole
222
+ // criterion (`ideator-nothing-is-lost`) is that the second half must be
223
+ // reachable without asking anyone. NOT in the 'You' group beside Standing
224
+ // and Profile, even though it is your own view: that group is your
225
+ // numbers, and this page's subject is the project's work, seen from the
226
+ // thought that caused it. gate 'signed' for the same reason Ideas is —
227
+ // there is no permission gate on having a thought, and the route reads the
228
+ // VIEWER's own trail, so there is nothing here a rank could protect.
229
+ { id: 'thinking', label: 'Your thinking', icon: 'goals', href: '/thinking', gate: 'signed' },
219
230
  { id: 'copy-desk', label: 'Copy desk', icon: 'docs', href: '/copy-desk', gate: 'signed', module: 'copy-desk' },
220
231
  // task 1003117 (R06 of goal 1000074): the Studio — artist mode, "a place an
221
232
  // artist wants to be, not a form". Beside the copy desk (the artist's other
@@ -66,7 +66,7 @@
66
66
 
67
67
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
68
68
 
69
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
69
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
70
70
  <script src="/builders/palette.js" defer></script>
71
71
  <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
72
72
  <!-- The kit, for its ONE renderer: the full-size ship-time visual figure
@@ -3163,10 +3163,20 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
3163
3163
  /* tab strip — the .setup__tab idiom in the .setup__toggle frame, promoted */
3164
3164
  /* A tab strip is hairlines, not a segmented box: one rule under the row and a
3165
3165
  2px accent underline on the open tab, the lit-step grammar again. */
3166
+ /* Both axes are named on purpose, and the pair is load-bearing — do not
3167
+ "simplify" this back to the single `overflow-x`. Per the overflow spec a
3168
+ non-`visible` value on one axis computes the OTHER axis to `auto` too, so
3169
+ `overflow-x: auto` alone made the strip a scroll container vertically as
3170
+ well; `.setup__tab`'s `margin-bottom: -1px` (the underline sitting on the
3171
+ strip's hairline, below) then overflowed it by exactly 1px and every hall
3172
+ page with tabs grew a vertical scrollbar it could never scroll. `hidden` is
3173
+ the fix and `visible` is not: `visible` beside an `auto` is the value the
3174
+ spec rewrites, which would put the bug straight back (task 1004130). */
3166
3175
  .kit-tabs {
3167
3176
  display: flex;
3168
3177
  max-width: 100%;
3169
3178
  overflow-x: auto;
3179
+ overflow-y: hidden;
3170
3180
  border: 0;
3171
3181
  border-bottom: 1px solid var(--rule);
3172
3182
  border-radius: 0;
@@ -57,7 +57,7 @@
57
57
 
58
58
  <!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
59
59
  time so the chrome exists before the deferred page scripts execute. -->
60
- <script src="/builders/shell.js?v=2026-09-20-coreupdate1"></script>
60
+ <script src="/builders/shell.js?v=2026-09-20-room1"></script>
61
61
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
62
62
  <script src="/builders/palette.js" defer></script>
63
63
  <!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
@@ -0,0 +1,277 @@
1
+ /* thinking.css — what THE ROOM has that no other hall page has
2
+ (task 1004098 / BV2.R03, goal 1000095).
3
+
4
+ The composition is the shell's: `.page-head--board`, `.scroll`, `.ledger` and
5
+ the kit's `.kit-tabs` strip all come from style.css and are not restated here.
6
+ What is left is the three shapes only this page has — the figure run over the
7
+ list, the size bar, and the mark for the five objects — plus the cells the
8
+ ledger has no opinion about.
9
+
10
+ NO COLOUR THAT IS NOT A TOKEN. Every value below is one of the world's
11
+ fifteen or a color-mix of them, so the page inherits dark and light from the
12
+ pack rather than declaring either: there is not one mode-specific rule in this
13
+ sheet, which is why it needs no dark twin. `--line` is a WIDTH; a hairline's
14
+ colour is `--rule` / `--rule-soft`.
15
+
16
+ NO MOTION. Nothing here transitions or animates, so the sheet sits entirely
17
+ inside the Kill Switch rather than needing to be excused by it. The one thing
18
+ that could have moved — a size bar growing as the background pages land —
19
+ deliberately does not: the bar is a measurement, and a measurement that
20
+ animates on every re-render turns a quiet page into a twitching one. */
21
+
22
+ /* ---- the figures over the list -------------------------------------------
23
+ The same object as the descent's `.ig-desc__figs` on /idea/:id, and that is
24
+ on purpose rather than by accident: the two pages state the same kind of fact
25
+ (a count over a body of work) and a reader moving between them should not
26
+ have to learn a second shape for it. It is not SHARED code, because the
27
+ descent's figures live in the idea record's own sheet and hoisting them into
28
+ style.css to serve two pages is an archetype decision, not a page's. If a
29
+ third surface wants them — the sky will — that is the moment. */
30
+ .tr-figs {
31
+ display: flex;
32
+ flex-wrap: wrap;
33
+ gap: var(--sp-md) var(--sp-lg);
34
+ margin: 0 0 var(--sp-md);
35
+ }
36
+ .tr-fig { display: grid; gap: 6px; }
37
+ .tr-fig__n {
38
+ font-size: 32px;
39
+ font-weight: 300;
40
+ line-height: 1;
41
+ letter-spacing: -0.03em;
42
+ font-variant-numeric: tabular-nums;
43
+ color: var(--ink);
44
+ }
45
+ /* Only ONE figure is warm, and it is the live one — "still burning" is the
46
+ number the page exists to make visible, and an accent spent on all four would
47
+ say nothing. "burned out" goes faint for the same reason it does in the
48
+ descent: the record of work that happened is not an error to shout. */
49
+ .tr-fig[data-tone="warm"] .tr-fig__n { color: var(--accent-deep); }
50
+ .tr-fig[data-tone="dim"] .tr-fig__n { color: var(--ink-faint); }
51
+ .tr-fig__label {
52
+ font-family: var(--label-face);
53
+ font-size: var(--label-size);
54
+ letter-spacing: var(--label-space);
55
+ text-transform: var(--label-case);
56
+ color: var(--ink-faint);
57
+ }
58
+
59
+ /* ---- the filter strip ---------------------------------------------------- */
60
+ /* The strip itself is `.kit-tabs`; all this adds is the air under it, because
61
+ the kit's own margin assumes a panel directly beneath and here a table
62
+ follows. */
63
+ .tr-filters { margin: 0 0 var(--sp-sm); }
64
+
65
+ /* ---- the rows ------------------------------------------------------------ */
66
+ /* The four short columns are pinned and the two prose ones share what is left,
67
+ so the row's SUBJECT — the thought — gets the width instead of losing it to
68
+ an outcome sentence that happens to be long. `width` on a th is a hint under
69
+ `table-layout: auto`, which is what is wanted here: a pinned column still
70
+ gives way rather than clipping when a cell genuinely needs more. */
71
+ .tr-table { table-layout: auto; }
72
+ .tr-table th:nth-child(1) { width: 34%; }
73
+ .tr-table th:nth-child(2) { width: 100px; }
74
+ .tr-table th:nth-child(3) { width: 30%; }
75
+ .tr-table th:nth-child(4) { width: 104px; }
76
+ .tr-table th:nth-child(5) { width: 142px; }
77
+ /* The thought is the row's subject, so it is the one cell that may take the
78
+ width and the one cell whose link reads at body size. */
79
+ .tr-thought a {
80
+ display: inline-block;
81
+ min-height: 24px;
82
+ padding: 3px 0;
83
+ line-height: 18px;
84
+ color: var(--ink);
85
+ border-bottom: var(--line) solid var(--rule);
86
+ }
87
+ .tr-thought a:hover { border-bottom-color: var(--ink); text-decoration: none; }
88
+ .tr-became { font-size: 13.5px; color: var(--ink-soft); }
89
+ .tr-became a {
90
+ display: inline-block;
91
+ min-height: 24px;
92
+ padding: 3px 0;
93
+ line-height: 18px;
94
+ color: var(--ink-soft);
95
+ border-bottom: var(--line) solid var(--rule-soft);
96
+ }
97
+ .tr-became a:hover { color: var(--ink); border-bottom-color: var(--ink); text-decoration: none; }
98
+ .tr-quiet { color: var(--ink-faint); }
99
+ /* The one actionable phrase on the page — a task sitting at backlog inside a
100
+ goal is one promote from claimable — so it is the only thing in a row that
101
+ takes the accent, and it says the word as well as wearing the colour. */
102
+ .tr-nod { color: var(--accent-ink); }
103
+
104
+ /* ---- size ---------------------------------------------------------------- */
105
+ /* Bar and number together, never the bar alone: size is long-tailed and its
106
+ compressed width is a comparison, not a quantity. The direction is explicit
107
+ that the true figure stays available wherever the drawing is. */
108
+ .tr-sizecell { white-space: nowrap; }
109
+ .tr-size { display: inline-flex; align-items: center; gap: 10px; }
110
+ .tr-size__bar {
111
+ display: block;
112
+ flex: none;
113
+ width: 56px;
114
+ height: 3px;
115
+ border-radius: 999px;
116
+ /* The track is the unfilled remainder; the fill is the accent at the server's
117
+ own compressed width. One element, so a row costs no extra box. */
118
+ background:
119
+ linear-gradient(to right, var(--fill, var(--accent)) 0 var(--w, 0%), transparent var(--w, 0%) 100%),
120
+ var(--rule-soft);
121
+ }
122
+ /* Every bit of its size, none of its colour — the remnant's whole picture. See
123
+ thinking.js sizeHtml() for why this is set from the state and not from the
124
+ number. `--ink-faint` and not `--rule`: at the hairline's weight a full-width
125
+ bar stops clearing the 3:1 non-text floor on the dark ground, and this bar is
126
+ the one that must still read as LARGE. */
127
+ .tr-size[data-lit="out"] { --fill: var(--ink-faint); }
128
+ .tr-size__n {
129
+ font-size: 13px;
130
+ font-variant-numeric: tabular-nums;
131
+ color: var(--ink-soft);
132
+ }
133
+ /* A size computed from a descent rather than from payouts that were made. It is
134
+ a footnote, not a warning — the number is real, it just means something
135
+ slightly different, and the title says which. */
136
+ .tr-hist {
137
+ display: block;
138
+ margin-top: 2px;
139
+ font-size: 11.5px;
140
+ color: var(--ink-faint);
141
+ text-decoration: none;
142
+ border-bottom: var(--line) dotted var(--rule);
143
+ cursor: help;
144
+ }
145
+
146
+ /* ---- the five objects ---------------------------------------------------- */
147
+ /* THE MARK RESTATES THE WORD IN SHAPE. Every object below differs from the
148
+ others by geometry — filled, haloed, ringed, outlined — and not only by tone,
149
+ because a mark distinguished by colour alone tells a colour-blind reader
150
+ nothing. That is the sheet's existing rule for the grade badge and the
151
+ descent's glyph; this is the same rule applied to a vocabulary of five.
152
+
153
+ `--lit` is the server's brightness, between 0 and 1, set per row by
154
+ thinking.js. It rides the star's opacity — and the star's ALONE, because the
155
+ other four objects have no live share to show and dimming them would invent
156
+ one. A star whose brightness could not be READ arrives with --lit: 1 and says
157
+ so in words (idea-objects.js brightnessWord); a full glyph over "still alive
158
+ — unknown" is the honest pair. The floor of 0.35 is a contrast floor: below
159
+ it the mark stops clearing 3:1 against the ground and a dim star becomes an
160
+ absent one, which is the wrong reading entirely — a star is never nothing. */
161
+ .tr-objcell { white-space: nowrap; }
162
+ .tr-obj { display: inline-flex; align-items: flex-start; gap: 10px; }
163
+ .tr-obj__glyph {
164
+ flex: none;
165
+ width: 9px;
166
+ height: 9px;
167
+ margin-top: 5px;
168
+ border-radius: 50%;
169
+ background: var(--ink-faint);
170
+ }
171
+ .tr-obj__body { display: grid; gap: 2px; min-width: 0; }
172
+ .tr-obj__label { font-size: 13.5px; color: var(--ink); }
173
+ .tr-obj__sub { font-size: 12px; color: var(--ink-faint); }
174
+
175
+ /* star — filled, accent, dimmed by its live share. */
176
+ .tr-obj[data-object="star"] .tr-obj__glyph {
177
+ background: var(--accent);
178
+ opacity: max(0.35, var(--lit, 1));
179
+ }
180
+ /* protostar — small and haloed: matter gathering, not yet burning. The halo is
181
+ a ring of the accent mixed back into the ground, so it reads as glow rather
182
+ than as a second border. */
183
+ .tr-obj[data-object="protostar"] .tr-obj__glyph {
184
+ width: 7px;
185
+ height: 7px;
186
+ margin-top: 6px;
187
+ background: var(--ink-soft);
188
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent);
189
+ }
190
+ /* satellite — a small body with an orbit around it, detached from the mark so
191
+ it reads as something going round rather than as a thick edge. */
192
+ .tr-obj[data-object="satellite"] .tr-obj__glyph {
193
+ width: 6px;
194
+ height: 6px;
195
+ margin-top: 6px;
196
+ background: var(--ink-soft);
197
+ outline: var(--line) solid var(--rule);
198
+ outline-offset: 4px;
199
+ }
200
+ /* remnant — the hollow ring, and the largest mark of the five. A thought that
201
+ caused a great deal of work the project then tore out renders LARGE and DEAD:
202
+ that is the whole reason size and brightness are two channels, so the object
203
+ that carries it must not be the quietest thing in the column. `--ink-faint`
204
+ and not `--rule`: at the hairline's weight this measured under the 3:1
205
+ non-text floor on the dark ground, the same finding the descent's "out" glyph
206
+ recorded. */
207
+ .tr-obj[data-object="remnant"] .tr-obj__glyph {
208
+ width: 12px;
209
+ height: 12px;
210
+ margin-top: 4px;
211
+ background: transparent;
212
+ box-shadow: inset 0 0 0 1.5px var(--ink-faint);
213
+ }
214
+ /* cold body — the smallest mark there is, a dark point with a hairline edge. It
215
+ never ignited, so it has no size at all, and the mark says that by being the
216
+ one object with nothing inside it and nothing around it. */
217
+ .tr-obj[data-object="cold-body"] .tr-obj__glyph {
218
+ width: 6px;
219
+ height: 6px;
220
+ margin-top: 6px;
221
+ background: var(--surface-2);
222
+ box-shadow: inset 0 0 0 var(--line) var(--ink-faint);
223
+ }
224
+
225
+ /* ---- notes + the empty state --------------------------------------------- */
226
+ .tr-note { font-size: 13.5px; color: var(--ink-faint); margin: 0; }
227
+ .tr-empty {
228
+ margin: 0;
229
+ padding: var(--sp-md) 0;
230
+ font-size: 14px;
231
+ color: var(--ink-soft);
232
+ max-width: var(--measure);
233
+ }
234
+ .tr-foot {
235
+ margin: var(--sp-md) 0 0;
236
+ font-size: 13px;
237
+ color: var(--ink-faint);
238
+ max-width: var(--measure);
239
+ }
240
+
241
+ /* ---- the phone ----------------------------------------------------------- */
242
+ /* Nothing below widens the document — the page never scrolls sideways, and the
243
+ restacking here is what makes that true without leaning on the shell's strip
244
+ at all. */
245
+ @media (max-width: 640px) {
246
+ .tr-fig__n { font-size: 26px; }
247
+ /* THE TABLE STOPS BEING A TABLE. The shell would give it a horizontal strip
248
+ (`:has(> .ledger)`) and the page would still measure clean — but five
249
+ columns inside a 390px strip is a table nobody can read: the thought column
250
+ collapses to one word per line while the object scrolls off the right, so
251
+ the row's two most important facts end up on opposite sides of a gesture.
252
+ A strip is the right answer for a ledger whose columns are a grid of
253
+ numbers; this one is a sentence plus three facts about it, and those stack.
254
+ The row becomes one flex line-box: the thought takes the width, and filed,
255
+ size and object run under it. */
256
+ .tr-table,
257
+ .tr-table tbody,
258
+ .tr-table td { display: block; width: auto; }
259
+ .tr-table thead { display: none; }
260
+ .tr-table tr {
261
+ display: flex;
262
+ flex-wrap: wrap;
263
+ align-items: baseline;
264
+ gap: 6px 16px;
265
+ padding: 14px 0;
266
+ border-bottom: 1px solid var(--rule-soft);
267
+ }
268
+ .tr-table tr:last-child { border-bottom: 0; }
269
+ .tr-table td { padding: 0; border: 0; }
270
+ .tr-table .tr-thought { flex-basis: 100%; font-size: 15px; }
271
+ /* Dropped on a phone: the outcome prose, the longest cell in the row and the
272
+ only one the idea's own record — one tap away on the title — states in
273
+ full. Size and object stay, because they are the two facts this page exists
274
+ for and the two the record does not put side by side. */
275
+ .tr-table .tr-became { display: none; }
276
+ .tr-obj__sub { white-space: normal; }
277
+ }