@bongos/core 1.19.1011 → 1.19.1013

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 (36) hide show
  1. package/.bongos-core.json +53 -43
  2. package/docs/adr/0331-the-sky-is-the-ideators-home.md +6 -1
  3. package/docs/copy-inventory.md +99 -56
  4. package/docs/copy-registry.json +830 -465
  5. package/docs/file-map.md +1 -1
  6. package/docs/module-api-changelog.md +4 -0
  7. package/modules/hall-ui/public/idea-objects.js +4 -1
  8. package/modules/hall-ui/public/sky-composer.js +155 -0
  9. package/modules/hall-ui/public/sky-data.js +287 -0
  10. package/modules/hall-ui/public/sky-draw.js +244 -0
  11. package/modules/hall-ui/public/sky-interaction.js +137 -0
  12. package/modules/hall-ui/public/sky-layout.js +81 -0
  13. package/modules/hall-ui/public/sky-panel.js +141 -0
  14. package/modules/hall-ui/public/sky-tour.js +80 -0
  15. package/modules/hall-ui/public/thinking.css +203 -267
  16. package/modules/hall-ui/public/thinking.html +119 -58
  17. package/modules/hall-ui/public/thinking.states.json +33 -9
  18. package/package-lock.json +2 -2
  19. package/package.json +1 -1
  20. package/scripts/gds/gen-file-map.js +29 -12
  21. package/src/bongos/routes/me.js +4 -1
  22. package/src/bongos/serve-internal.js +20 -6
  23. package/src/module-api.js +1 -1
  24. package/tests/file_map.mjs +35 -3
  25. package/tests/hall_audit.mjs +27 -0
  26. package/tests/hall_page_gate_map.mjs +11 -5
  27. package/tests/hall_sky.mjs +393 -405
  28. package/tests/hall_thinking_room.mjs +274 -296
  29. package/tests/helpers.mjs +185 -217
  30. package/tests/ideator_large_dead_thought_proof.mjs +33 -25
  31. package/tests/ideator_traceable_proof.mjs +61 -39
  32. package/modules/hall-ui/public/sky.css +0 -365
  33. package/modules/hall-ui/public/sky.html +0 -79
  34. package/modules/hall-ui/public/sky.js +0 -580
  35. package/modules/hall-ui/public/sky.states.json +0 -20
  36. package/modules/hall-ui/public/thinking.js +0 -418
@@ -1,277 +1,213 @@
1
- /* thinking.css — what THE ROOM has that no other hall page has
2
- (task 1004098 / BV2.R03, goal 1000095).
1
+ /* modules/hall-ui/public/thinking.css — THE SKY's one sheet (task 1004231 / sky
2
+ part 3 of 5, goal 1000095; ADR 0331). The <style> of
3
+ docs/design/mocks/sky/sky.template.html, verbatim: the owner adopted the
4
+ prototype as the design of record and this sheet is its pixel contract. It
5
+ deliberately does NOT load the hall's style.css or its tokens — the sky is
6
+ its own surface (black void, Manrope, JetBrains Mono for figures), and the
7
+ template's values are the contract. Keep it one file (the task says so). */
8
+ /* The template declared these on :root. They sit on the page's body here,
9
+ with the template's values untouched, because the server splices the
10
+ branding pack's own :root (--ink, --accent, --rule …) into <head> AFTER this
11
+ sheet — equal specificity, later wins — and would repaint the sky in the
12
+ hall's palette. A body-scoped declaration is inherited by everything drawn
13
+ and is not what the pack overrides (and a page sheet may declare no :root of
14
+ its own — tests/hall_reading_world.mjs; color-scheme inherits from body
15
+ too). Same reason `body` below is qualified: a pack backdrop is written as
16
+ `body{background:…}` and the void must win. */
17
+ body[data-page="thinking"] {
18
+ color-scheme: dark;
19
+ --void: #000000; --ink: #ffffff; --ink-soft: #c0c0c0; --ink-faint: #a8a8a8; --ink-dim: #65656a; --ink-ghost: #3a3a3f;
20
+ --rule: rgba(255,255,255,.16); --rule-soft: rgba(255,255,255,.08); --glass: rgba(255,255,255,.075); --glass-strong: rgba(255,255,255,.14);
21
+ --accent: #fb9569; --accent-on: #2a1305; --chart: #f2d27a;
22
+ --label: 400 11px/1.2 "JetBrains Mono", ui-monospace, Consolas, monospace; --mono: 400 13px/1.4 "JetBrains Mono", ui-monospace, Consolas, monospace;
23
+ --face: Manrope, "Segoe UI", system-ui, -apple-system, sans-serif;
24
+ }
25
+ html, body { height: 100%; }
26
+ body[data-page="thinking"] { margin: 0; background: var(--void); color: var(--ink); font-family: var(--face); font-size: 15px; line-height: 1.5; overflow: hidden; -webkit-font-smoothing: antialiased; }
27
+ button { font-family: inherit; }
28
+ a { color: var(--accent); }
29
+ .lbl { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-faint); }
30
+ .mono { font: var(--mono); font-variant-numeric: tabular-nums; }
31
+ [hidden] { display: none !important; }
32
+ #sky { position: fixed; inset: 0; display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
33
+ #sky.dragging { cursor: grabbing; }
34
+ #sky:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
3
35
 
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.
36
+ .top { position: fixed; left: 0; right: 0; top: 0; padding: calc(16px + env(safe-area-inset-top, 0px)) 20px 0; display: flex; align-items: flex-start; gap: 16px; pointer-events: none; z-index: 5; }
37
+ .top > * { pointer-events: auto; }
38
+ .brand { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
39
+ .brand h1 { margin: 0; font-size: 26px; font-weight: 300; letter-spacing: -.035em; line-height: 1.05; white-space: nowrap; }
40
+ .brand h1 em { font-style: normal; color: var(--ink-dim); }
41
+ .stats { color: var(--ink-dim); font: var(--mono); font-variant-numeric: tabular-nums; }
42
+ .stats b { color: var(--ink-soft); font-weight: 400; }
43
+ .lens { margin-left: auto; display: inline-flex; padding: 4px; border-radius: 999px; background: var(--glass); gap: 2px; }
44
+ .lens button, .pill { height: 36px; padding: 0 16px; border: 0; border-radius: 999px; background: transparent; color: var(--ink-soft); font-size: 13px; cursor: pointer; white-space: nowrap; }
45
+ .lens button[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
46
+ .lens button:focus-visible, .pill:focus-visible, .row:focus-visible, .obj:focus-visible, input:focus-visible, .door:focus-visible, .menu button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
47
+ .tools { display: inline-flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; max-width: 680px; }
48
+ .pill { background: var(--glass); }
49
+ .pill[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
50
+ .pill[aria-pressed="true"]::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); margin-right: 8px; vertical-align: 1px; }
51
+ .pill.icon { width: 36px; padding: 0; display: inline-grid; place-items: center; }
52
+ .pill.icon::before { display: none; }
53
+ .spot { outline: 2px solid var(--accent) !important; outline-offset: 4px; box-shadow: 0 0 0 8px rgba(251,149,105,.12), 0 0 40px rgba(251,149,105,.35); }
9
54
 
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`.
55
+ .composer { position: fixed; left: 50%; top: calc(76px + env(safe-area-inset-top, 0px)); transform: translateX(-50%); width: min(620px, calc(100vw - 32px)); z-index: 6; display: flex; flex-direction: column; gap: 8px; align-items: center; }
56
+ .composer form { display: flex; width: 100%; height: 48px; border-radius: 999px; background: rgba(0,0,0,.5); box-shadow: inset 0 0 0 1px var(--rule); backdrop-filter: blur(6px); align-items: center; padding: 0 6px 0 22px; gap: 8px; }
57
+ .composer form:focus-within { box-shadow: inset 0 0 0 1px var(--ink-faint); }
58
+ .composer input { flex: 1; min-width: 0; height: 100%; background: transparent; border: 0; color: var(--ink); font: 300 17px/1 var(--face); letter-spacing: -.01em; outline: none; }
59
+ .composer input::placeholder { color: var(--ink-dim); }
60
+ .composer .go { height: 36px; padding: 0 18px; border: 0; border-radius: 999px; background: var(--glass-strong); color: var(--ink); font-size: 13px; cursor: pointer; }
61
+ .composer form.live .go { background: var(--accent); color: var(--accent-on); }
62
+ .gravity { min-height: 18px; font: var(--mono); font-variant-numeric: tabular-nums; color: var(--ink-dim); text-align: center; }
63
+ .gravity b { font-weight: 400; color: var(--ink-soft); }
64
+ .gravity .hot { color: var(--accent); }
65
+ .receipt { display: flex; align-items: center; gap: 12px; height: 34px; padding: 0 8px 0 16px; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(0,0,0,.45); color: var(--ink-soft); font-size: 13px; }
66
+ .receipt .dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.5); box-shadow: 0 0 0 4px rgba(255,255,255,.05); }
67
+ .receipt button { border: 0; background: transparent; color: var(--ink-dim); cursor: pointer; height: 26px; padding: 0 8px; border-radius: 999px; font-size: 12px; white-space: nowrap; flex-shrink: 0; }
68
+ .receipt button:hover { color: var(--ink); background: var(--glass); }
15
69
 
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. */
70
+ .glass { background: rgba(255,255,255,.07); box-shadow: inset 0 0 0 1px rgba(255,255,255,.14), inset 0 1px 0 rgba(255,255,255,.12), 0 18px 40px rgba(0,0,0,.6); backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%); }
71
+ .card { position: fixed; z-index: 7; max-width: 300px; padding: 12px 14px; border-radius: 14px; pointer-events: none; color: var(--ink); font-size: 13.5px; line-height: 1.4; display: flex; flex-direction: column; gap: 5px; opacity: 0; transform: translateY(4px); transition: opacity .12s ease, transform .12s ease; }
72
+ .card.on { opacity: 1; transform: none; }
73
+ .card .k { display: flex; align-items: center; gap: 8px; }
74
+ .card .k .lbl.hot { color: var(--accent); }
75
+ .card .k .lbl.chart { color: var(--chart); }
76
+ .card .t { font-size: 14px; letter-spacing: -.01em; }
77
+ .card .m { color: var(--ink-faint); font-size: 12.5px; }
78
+ .card .m b { color: var(--ink-soft); font-weight: 400; }
79
+ .menu { position: fixed; z-index: 8; padding: 8px; border-radius: 14px; display: flex; flex-direction: column; gap: 2px; min-width: 230px; }
80
+ .menu .h { padding: 6px 10px 8px; font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--chart); }
81
+ .menu button { border: 0; background: transparent; color: var(--ink-soft); text-align: left; padding: 8px 10px; border-radius: 8px; font-size: 13.5px; cursor: pointer; }
82
+ .menu button:hover { background: var(--glass-strong); color: var(--ink); }
21
83
 
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
- }
84
+ .panel { position: fixed; right: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 340px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 24px; border-radius: 16px; background: rgba(0,0,0,.62); box-shadow: inset 0 0 0 1px var(--rule-soft); backdrop-filter: blur(8px); overflow: auto; }
85
+ .panel .kind { display: flex; align-items: center; gap: 12px; min-height: 28px; }
86
+ .panel .kind .glyph { width: 32px; display: grid; place-items: center; }
87
+ .panel h2 { margin: 0; font-size: 21px; font-weight: 300; line-height: 1.25; letter-spacing: -.02em; text-wrap: balance; }
88
+ .facts { display: flex; flex-direction: column; }
89
+ .fact { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; padding: 10px 0; border-top: 1px solid var(--rule-soft); }
90
+ .fact:last-child { border-bottom: 1px solid var(--rule-soft); }
91
+ .fact .k { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-dim); flex-shrink: 0; }
92
+ .fact .v { text-align: right; font-size: 14px; color: var(--ink-soft); }
93
+ .fact .v.num { font: var(--mono); font-variant-numeric: tabular-nums; font-size: 15px; color: var(--ink); }
94
+ .fact .v .bar { display: inline-block; vertical-align: middle; height: 3px; border-radius: 999px; background: var(--accent); margin-right: 8px; }
95
+ .doors { display: flex; flex-wrap: wrap; gap: 8px; }
96
+ .door { display: inline-flex; align-items: center; height: 40px; padding: 0 18px; border-radius: 999px; border: 0; font-size: 13px; text-decoration: none; background: var(--glass); color: var(--ink); cursor: pointer; }
97
+ .door.primary { background: var(--accent); color: var(--accent-on); }
98
+ .door[disabled] { opacity: .45; cursor: default; }
99
+ .note { margin: auto 0 0; font-size: 13px; line-height: 1.5; color: var(--ink-dim); }
100
+ .crit { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
101
+ .crit li { display: flex; gap: 10px; font-size: 13px; color: var(--ink-soft); line-height: 1.4; }
102
+ .crit .ring { flex-shrink: 0; width: 12px; height: 12px; border-radius: 50%; border: 1px dashed var(--ink-dim); margin-top: 3px; }
103
+ .crit .ring.on { border-style: solid; border-color: var(--ink-faint); }
104
+ .crit .born { color: var(--accent); }
105
+ .orbit-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
106
+ .orbit-list li { display: flex; align-items: baseline; gap: 10px; padding: 8px 0; border-top: 1px solid var(--rule-soft); font-size: 13px; color: var(--ink-soft); }
107
+ .orbit-list .st { font: var(--label); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-dim); flex-shrink: 0; width: 72px; }
108
+ .orbit-list .st.live { color: var(--accent); }
58
109
 
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); }
110
+ .room { position: fixed; left: 20px; top: calc(140px + env(safe-area-inset-top, 0px)); bottom: calc(96px + env(safe-area-inset-bottom, 0px)); width: 440px; z-index: 5; display: flex; flex-direction: column; gap: 14px; padding: 22px 0 0; border-radius: 16px; background: rgba(0,0,0,.62); box-shadow: inset 0 0 0 1px var(--rule-soft); backdrop-filter: blur(8px); }
111
+ .room .head { display: flex; align-items: baseline; gap: 14px; padding: 0 24px; }
112
+ .room .head h2 { margin: 0; font-size: 21px; font-weight: 300; letter-spacing: -.02em; }
113
+ .room .head .lbl { margin-left: auto; }
114
+ .filters { display: flex; flex-wrap: wrap; gap: 4px; padding: 0 20px; }
115
+ .filters .pill { height: 32px; padding: 0 14px; font-size: 12.5px; background: transparent; color: var(--ink-faint); }
116
+ .filters .pill[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
117
+ .filters .pill[aria-pressed="true"]::before { display: none; }
118
+ .rows { flex: 1; overflow: auto; padding: 0 12px 12px; }
119
+ .row { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; width: 100%; text-align: left; padding: 11px 12px; border: 0; border-top: 1px solid var(--rule-soft); background: transparent; color: var(--ink); cursor: pointer; border-radius: 8px; }
120
+ .row:hover { background: var(--glass); }
121
+ .row[aria-current="true"] { background: var(--glass-strong); }
122
+ .row .t { font-size: 14px; line-height: 1.35; grid-column: 1; }
123
+ .row .m { grid-column: 1; font-size: 12.5px; color: var(--ink-dim); }
124
+ .row .g { grid-column: 2; grid-row: 1 / span 2; display: grid; place-items: center; width: 28px; }
125
+ .empty { padding: 20px 24px; color: var(--ink-dim); font-size: 14px; }
64
126
 
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); }
127
+ .jump { position: fixed; left: 20px; top: calc(76px + env(safe-area-inset-top, 0px)); z-index: 6; }
128
+ .jump input { width: 260px; height: 40px; border-radius: 999px; border: 0; box-shadow: inset 0 0 0 1px var(--rule-soft); background: rgba(0,0,0,.5); color: var(--ink); padding: 0 16px; font: 400 14px/1 var(--face); }
129
+ .jump input::placeholder { color: var(--ink-dim); }
130
+ .jump ul { list-style: none; margin: 6px 0 0; padding: 6px; border-radius: 12px; background: rgba(0,0,0,.8); box-shadow: inset 0 0 0 1px var(--rule-soft); max-height: 300px; overflow: auto; width: 360px; }
131
+ .jump li button { width: 100%; text-align: left; border: 0; background: transparent; color: var(--ink-soft); padding: 8px 10px; border-radius: 8px; font-size: 13.5px; cursor: pointer; display: flex; gap: 10px; align-items: baseline; }
132
+ .jump li button:hover, .jump li button:focus-visible { background: var(--glass); color: var(--ink); }
133
+ .jump li .lbl { flex-shrink: 0; width: 60px; }
103
134
 
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
- }
135
+ .tour, .start { position: fixed; left: 50%; bottom: calc(110px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); width: min(560px, calc(100vw - 32px)); z-index: 8; padding: 18px 22px 16px; border-radius: 16px; display: flex; flex-direction: column; gap: 10px; }
136
+ .tour .step { display: flex; align-items: center; gap: 10px; }
137
+ .tour .dots { display: flex; gap: 5px; margin-left: auto; }
138
+ .tour .dots i { width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,.2); }
139
+ .tour .dots i.on { background: var(--accent); }
140
+ .tour h3, .start h3 { margin: 0; font-size: 19px; font-weight: 300; letter-spacing: -.02em; line-height: 1.25; }
141
+ .tour p, .start p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
142
+ .tour .acts { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
143
+ .tour .acts .skip { margin-left: auto; border: 0; background: transparent; color: var(--ink-dim); cursor: pointer; font-size: 13px; height: 36px; padding: 0 10px; border-radius: 999px; }
144
+ .tour .acts .skip:hover { color: var(--ink); background: var(--glass); }
145
+ .tour .door { height: 38px; }
146
+ .start .opts { display: flex; flex-wrap: wrap; gap: 6px; }
147
+ .start .opts .door { height: 34px; padding: 0 14px; font-size: 12.5px; }
145
148
 
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.
149
+ .foot { position: fixed; left: 0; right: 0; bottom: 0; padding: 0 20px calc(14px + env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: 6px; z-index: 5; pointer-events: none; }
150
+ .foot > * { pointer-events: auto; }
151
+ .footrow { display: flex; align-items: flex-end; gap: 22px; }
152
+ .legend { display: flex; flex-wrap: wrap; gap: 6px 22px; max-width: 62%; }
153
+ .obj { display: inline-flex; align-items: center; gap: 9px; border: 0; background: transparent; color: var(--ink-faint); font: var(--label); letter-spacing: .14em; text-transform: uppercase; cursor: pointer; padding: 6px 0; }
154
+ .obj:hover { color: var(--ink); }
155
+ .obj .g { width: 20px; height: 18px; display: grid; place-items: center; }
156
+ .alt { margin-left: auto; text-align: right; color: var(--ink-dim); font: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
157
+ .alt b { color: var(--ink-soft); font-weight: 400; }
158
+ .hint { color: var(--ink-ghost); }
159
+ .time { display: flex; align-items: center; gap: 8px; width: min(360px, 100%); align-self: center; padding: 3px 4px 3px 4px; border-radius: 999px; background: rgba(0,0,0,.45); box-shadow: inset 0 0 0 1px var(--rule-soft); opacity: .8; }
160
+ .time:hover { opacity: 1; }
161
+ .time .play { width: 24px; height: 24px; border-radius: 50%; border: 0; background: var(--glass-strong); color: var(--ink); cursor: pointer; display: grid; place-items: center; font-size: 9px; flex-shrink: 0; }
162
+ .time .play[aria-pressed="true"] { background: var(--accent); color: var(--accent-on); }
163
+ .time input[type=range] { flex: 1; accent-color: var(--accent); height: 24px; margin: 0; }
164
+ .time .when { font: var(--mono); font-size: 11px; font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; min-width: 74px; text-align: right; }
165
+ .time .hide { width: 24px; height: 24px; border: 0; border-radius: 50%; background: transparent; color: var(--ink-dim); cursor: pointer; font-size: 13px; line-height: 1; flex-shrink: 0; }
166
+ .time .hide:hover { color: var(--ink); background: var(--glass); }
167
+ .composer .mode { height: 32px; padding: 0 12px; border: 0; border-radius: 999px; background: transparent; color: var(--ink-dim); font-size: 12.5px; cursor: pointer; white-space: nowrap; }
168
+ .composer .mode:hover { color: var(--ink); }
169
+ .composer .mode[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
170
+ .composer .mode[aria-pressed="true"]::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); margin-right: 7px; vertical-align: 1px; }
171
+ .steps { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; max-width: 100%; font-size: 13px; color: var(--ink-soft); padding: 5px 8px 5px 14px; border-radius: 999px; background: rgba(0,0,0,.5); box-shadow: inset 0 0 0 1px var(--rule-soft); }
172
+ .steps b { font: var(--label); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); font-weight: 400; }
173
+ .steps .skip { border: 0; background: transparent; color: var(--ink-dim); cursor: pointer; font-size: 12px; height: 24px; padding: 0 8px; border-radius: 999px; }
174
+ .steps .skip:hover { color: var(--ink); background: var(--glass); }
175
+ .time .when.past { color: var(--chart); }
152
176
 
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); }
177
+ .mk { display: block; width: 9px; height: 9px; border-radius: 50%; box-sizing: border-box; }
178
+ .mk.star { background: var(--accent); box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 75%, transparent); }
179
+ .mk.star.dim { opacity: .55; box-shadow: 0 0 4px color-mix(in srgb, var(--accent) 40%, transparent); }
180
+ .mk.protostar { width: 7px; height: 7px; background: rgba(255,255,255,.5); box-shadow: 0 0 0 4px rgba(255,255,255,.05); }
181
+ .mk.satellite { width: 6px; height: 6px; background: rgba(255,255,255,.4); outline: 1px solid rgba(255,255,255,.16); outline-offset: 3px; }
182
+ .mk.remnant { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(255,255,255,.4); }
183
+ .mk.cold { width: 6px; height: 6px; background: #141418; border: 1px solid rgba(255,255,255,.18); }
184
+ .mk.well { width: 14px; height: 14px; background: transparent; border: 1px dashed rgba(255,255,255,.35); }
185
+ .mk.alive { width: 14px; height: 14px; background: transparent; border: 1px solid rgba(255,255,255,.35); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent), 0 0 10px var(--accent); }
186
+ .mk.settled { width: 12px; height: 12px; background: transparent; border: 1px solid rgba(255,255,255,.5); box-shadow: inset 0 0 0 3px #000, inset 0 0 0 4px rgba(255,255,255,.35), 0 0 8px rgba(255,255,255,.25); }
187
+ .mk.collapsed { width: 9px; height: 9px; background: #000; border: 1px solid rgba(255,255,255,.3); box-shadow: 0 0 0 2px rgba(0,0,0,1), 0 0 0 3px rgba(255,255,255,.08); }
188
+ .mk.unseen { width: 12px; height: 12px; background: transparent; border: 1px dashed rgba(255,255,255,.2); }
189
+ .mk.task { width: 4px; height: 4px; background: rgba(255,255,255,.6); }
190
+ .mk.task.live { background: var(--accent); box-shadow: 0 0 6px var(--accent); }
191
+ .mk.born { width: 10px; height: 10px; background: transparent; border: 1px solid var(--accent); box-shadow: inset 0 0 0 2px #000, inset 0 0 0 5px var(--accent); }
192
+ .mk.route { width: 14px; height: 2px; border-radius: 1px; background: var(--chart); box-shadow: 0 0 6px var(--chart); }
174
193
 
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
- }
194
+ @media (max-width: 900px) {
195
+ .top { flex-wrap: wrap; gap: 8px 10px; padding-left: 16px; padding-right: 16px; }
196
+ .brand { flex: 1 1 100%; }
197
+ .brand h1 { font-size: 20px; }
198
+ .stats { display: none; }
199
+ .lens { margin-left: 0; }
200
+ .lens button, .pill { height: 32px; padding: 0 12px; font-size: 12px; }
201
+ .pill.icon { width: 32px; }
202
+ .tools { margin-left: auto; }
203
+ .composer { top: calc(186px + env(safe-area-inset-top, 0px)); width: calc(100vw - 32px); }
204
+ .composer form { height: 44px; padding-left: 16px; }
205
+ .composer input { font-size: 16px; }
206
+ .gravity { font-size: 11.5px; }
207
+ .jump, .legend, .time { display: none; }
208
+ .room, .panel { left: 10px; right: 10px; width: auto; top: auto; bottom: calc(70px + env(safe-area-inset-bottom, 0px)); max-height: 46vh; }
209
+ .alt { margin-left: 0; text-align: left; }
210
+ .tour, .start { bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
211
+ .card { max-width: 240px; }
212
+ }
213
+ @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }