@bongos/core 1.19.1052 → 1.19.1054

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.
@@ -2591,5 +2591,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2591
2591
  landed since 1.19.1050 with no explicit bump. run 36369343116. (task 1002620)
2592
2592
  1.19.1052 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2593
2593
  landed since 1.19.1051 with no explicit bump. run 36372902384. (task 1002620)
2594
+ 1.19.1053 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2595
+ landed since 1.19.1052 with no explicit bump. run 36375859084. (task 1002620)
2596
+ 1.19.1054 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2597
+ landed since 1.19.1053 with no explicit bump. run 36378326588. (task 1002620)
2594
2598
  ---------------------------------------------------------------------------
2595
2599
  ```
@@ -11,7 +11,7 @@
11
11
  <!-- The faces (Manrope, JetBrains Mono) come from the branding pack's font
12
12
  links, injected into this head by the server like every hall page's
13
13
  (tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet). -->
14
- <link rel="stylesheet" href="/builders/thinking.css?v=2026-09-25-sky4">
14
+ <link rel="stylesheet" href="/builders/thinking.css?v=2026-09-27-split">
15
15
  </head>
16
16
  <!-- THE PROJECT SKY (task 1004232 / sky part 4 of 5, goal 1000095; ADR 0331,
17
17
  ADR 0227 §11). The SAME page as /thinking — docs/design/mocks/sky/
@@ -25,13 +25,15 @@
25
25
  URL and took the nav slot the map was denied, under Goals (owner,
26
26
  2026-09-24; ADR 0227 §11).
27
27
 
28
- The markup is thinking.html's, verbatim but for three things: the body
29
- says which lens is home (`data-sky-lens="all"` — sky-tour.js boots into it
30
- and the tour hands back to it), the brand line and the lens switcher start
31
- in that lens, and the canvas's own words say what this sky draws. The
32
- sheet and the seven sky-*.js files are SHARED with /thinking, never
33
- copied: the switcher's "simple" and "your thinking" leave for /thinking,
34
- and /thinking's "the whole project" leaves for here. The same rule as
28
+ THE WHOLE PROJECT ONLY (task 1004328, owner decision 2026-09-27). The
29
+ sheet, the seven sky-*.js files and the drawing are SHARED with /thinking,
30
+ never copied; the lenses and the tools are not. This page has one lens,
31
+ "the whole project" (`data-sky-lens="all"`), so it carries no switcher,
32
+ and it no longer offers "simple" or "your thinking". Its tools are the
33
+ project map's: explore and its start dialog, "my ideas" (your thoughts
34
+ laid over the map), routes, the ledger drawer. The ideator's pills
35
+ (recent, claude layer, my influence) are /thinking's alone, and nothing
36
+ here leaves for that page as a lens. The same rule as
35
37
  /thinking on chrome: none — the sky is a full-screen surface with its own
36
38
  header, and the hall's doors (the goal, the task, the record) open from
37
39
  the panel.
@@ -58,16 +60,8 @@
58
60
  <h1>the sky <em id="lensWord">— the whole project</em></h1>
59
61
  <div class="stats" id="stats"></div>
60
62
  </header>
61
- <div class="lens" role="group" aria-label="Filter" id="lensGroup">
62
- <button type="button" id="lensSimple" aria-pressed="false">simple</button>
63
- <button type="button" id="lensMine" aria-pressed="false">your thinking</button>
64
- <button type="button" id="lensAll" aria-pressed="true">the whole project</button>
65
- </div>
66
63
  <div class="tools" id="tools">
67
64
  <button type="button" class="pill" id="roomBtn" aria-pressed="false" title="The same sky, as a list (L)">list</button>
68
- <button type="button" class="pill" id="recentBtn" aria-pressed="false" hidden title="Only the thoughts of the last thirty days.">recent</button>
69
- <button type="button" class="pill" id="claudeBtn" aria-pressed="false" hidden title="Show the thoughts a session filed under your name. Off by default: only what a person filed.">claude layer</button>
70
- <button type="button" class="pill" id="influenceBtn" aria-pressed="false" hidden title="Only the goals your thoughts touched — everything else fades.">my influence</button>
71
65
  <button type="button" class="pill" id="exploreBtn" aria-pressed="true" title="Explore: the sky reveals itself as you follow routes from where you begin.">explore</button>
72
66
  <button type="button" class="pill" id="ideasBtn" aria-pressed="false" title="Lay your thoughts over the project map.">my ideas</button>
73
67
  <button type="button" class="pill" id="rehearse" aria-pressed="false" hidden title="What if: invents removals, dimming and nods — none has happened in the ledger yet — so you can see how the dead would read. Sizes are never invented.">what if</button>
@@ -131,13 +125,13 @@
131
125
  </div>
132
126
  </footer>
133
127
 
134
- <script src="/builders/idea-objects.js?v=2026-09-25-sky4"></script>
135
- <script src="/builders/sky-data.js?v=2026-09-25-sky4"></script>
136
- <script src="/builders/sky-layout.js?v=2026-09-25-sky4"></script>
137
- <script src="/builders/sky-draw.js?v=2026-09-25-sky4"></script>
138
- <script src="/builders/sky-interaction.js?v=2026-09-25-sky4"></script>
139
- <script src="/builders/sky-panel.js?v=2026-09-25-sky4"></script>
140
- <script src="/builders/sky-composer.js?v=2026-09-25-sky4"></script>
141
- <script src="/builders/sky-tour.js?v=2026-09-25-sky4"></script>
128
+ <script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
129
+ <script src="/builders/sky-data.js?v=2026-09-27-split"></script>
130
+ <script src="/builders/sky-layout.js?v=2026-09-27-split"></script>
131
+ <script src="/builders/sky-draw.js?v=2026-09-27-split"></script>
132
+ <script src="/builders/sky-interaction.js?v=2026-09-27-split"></script>
133
+ <script src="/builders/sky-panel.js?v=2026-09-27-split"></script>
134
+ <script src="/builders/sky-composer.js?v=2026-09-27-split"></script>
135
+ <script src="/builders/sky-tour.js?v=2026-09-27-split"></script>
142
136
  </body>
143
137
  </html>
@@ -1,5 +1,5 @@
1
1
  {
2
- "_": "THE PROJECT SKY (task 1004232 / sky part 4 of 5, goal 1000095; ADR 0331, ADR 0227 §11) through modules/ui-design/kit/render.js — the same page as /thinking (thinking.states.json) opened in its 'the whole project' lens. The page draws GET /sky + GET /sky/mine; against the kit's own stub neither exists, so every state below renders the EMPTY sky — a real state the floors must hold in (the lenses, the tools, the ledger drawer and the tour all exist with no goal read). To look at the populated sky — the wells sized by credits, the routes, the shuttle menu, explore's 'where do you begin' — run it against the hall-preview harness, whose sky.json + sky__mine.json fixtures carry the wells and the routes:\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/goal-map.states.json --base-in http://localhost:4620 --base-out http://localhost:4620\n\nThe tour opens by itself on a first visit (sky-tour-seen), so states that want the bare sky skip it first; explore is on by default and asks where you begin the first time (sky-seen), so the 'sky' state renders that prompt on purpose.",
2
+ "_": "THE PROJECT SKY (task 1004232 / sky part 4 of 5, goal 1000095; ADR 0331, ADR 0227 §11) through modules/ui-design/kit/render.js — the same renderer as /thinking (thinking.states.json), but its own sky: its only lens is 'the whole project', and it carries no switcher (task 1004328). The page draws GET /sky + GET /sky/mine; against the kit's own stub neither exists, so every state below renders the EMPTY sky — a real state the floors must hold in (the tools, the ledger drawer and the tour all exist with no goal read). To look at the populated sky — the wells sized by credits, the routes, the shuttle menu, explore's 'where do you begin' — run it against the hall-preview harness, whose sky.json + sky__mine.json fixtures carry the wells and the routes:\n\n node scripts/hall-preview/server.js --port 4620 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/goal-map.states.json --base-in http://localhost:4620 --base-out http://localhost:4620\n\nThe tour opens by itself on a first visit (sky-map-tour-seen — its own key, since the two tours differ), so states that want the bare sky skip it first; explore is on by default and asks where you begin the first time (sky-seen), so the 'sky' state renders that prompt on purpose.",
3
3
  "page": "/builders/goal-map",
4
4
  "surface": "hall-ui",
5
5
  "stub": { "prefix": "/builders/" },
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "sky": {
18
18
  "auth": true,
19
- "_": "The 'the whole project' lens, bare: the brand line, the lenses, explore + my ideas + the clock in the tools, the legend — and explore's 'where do you begin' prompt on a first visit.",
19
+ "_": "The 'the whole project' lens, bare: the brand line, explore + my ideas + the clock in the tools, the legend — and explore's 'where do you begin' prompt on a first visit.",
20
20
  "actions": [["wait", 1200], ["clickIfVisible", "#tourSkip"], ["wait", 300]],
21
21
  "expect": { "visible": ["#sky", "#exploreBtn", "#ideasBtn"] }
22
22
  },
@@ -29,23 +29,21 @@
29
29
  // GET /sky (which thoughts became goals or rings, from the ratify tables).
30
30
  // Nothing here guesses either from text.
31
31
  //
32
- // ONE PAGE, TWO DOORS (part 4, task 1004232). /thinking and /goal-map are the
33
- // same seven files over the same markup; the body's `data-sky-lens` says which
34
- // lens is HOME — 'mine' on /thinking (the ideator's sky), 'all' on /goal-map
35
- // (the project sky). `S.home` is read once here; sky-tour.js boots into it and
36
- // hands back to it after the tour, and sky-interaction.js's switcher leaves
37
- // for the other page rather than switching when the lens asked for lives
38
- // there. Every lens still exists inside either page, because the tour walks
39
- // all three.
32
+ // TWO SKIES, ONE RENDERER (task 1004328, owner decision 2026-09-27; it
33
+ // replaced part 4's "one page, two doors"). /thinking and /goal-map share
34
+ // these seven files, the sheet and the drawing, and nothing else: each page
35
+ // has its own lenses and its own tools. The body's `data-sky-lens` says which
36
+ // sky this is — 'mine' on /thinking, YOUR thinking only, with the lenses
37
+ // "simple" and "your thinking"; 'all' on /goal-map, the whole project, with
38
+ // the one lens "the whole project" and its features (explore, "my ideas"
39
+ // laid over the map, routes, the ledger of goals). `S.lenses` is the list a
40
+ // page may be in, and setLens never leaves it — not from a button, the tour,
41
+ // a jump or the panel. Neither page links to the other as a lens.
40
42
  //
41
- // ONE PAGE, TWO DOORS (part 4, task 1004232). /thinking and /goal-map are the
42
- // same seven files over the same markup; the body's `data-sky-lens` says which
43
- // lens is HOME — 'mine' on /thinking (the ideator's sky), 'all' on /goal-map
44
- // (the project sky). `S.home` is read once here; sky-tour.js boots into it and
45
- // hands back to it after the tour, and sky-interaction.js's switcher leaves
46
- // for the other page rather than switching when the lens asked for lives
47
- // there. Every lens still exists inside either page, because the tour walks
48
- // all three.
43
+ // A control belongs to one page's markup only (the switcher's buttons, the
44
+ // ideator's pills, the project map's pills, explore's start dialog), so the
45
+ // files touch those through hide / press / on below, which do nothing when
46
+ // the page does not carry the element.
49
47
 
50
48
  (() => {
51
49
  'use strict';
@@ -54,6 +52,10 @@
54
52
 
55
53
  // ---------------------------------------------------------------- helpers
56
54
  const $ = (id) => document.getElementById(id);
55
+ // A control one page carries and the other does not (task 1004328).
56
+ const hide = (id, v) => { const el = $(id); if (el) el.hidden = v; };
57
+ const press = (id, v) => { const el = $(id); if (el) el.setAttribute('aria-pressed', String(v)); };
58
+ const on = (id, ev, fn) => { const el = $(id); if (el) el.addEventListener(ev, fn); };
57
59
  const TAU = Math.PI * 2;
58
60
  const GOLDEN = 137.50776405003785 * Math.PI / 180;
59
61
  const reduce = !!(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches);
@@ -114,12 +116,14 @@
114
116
  // ---------------------------------------------------------------- shared state
115
117
  // Every `let` the prototype's closure held, on one object so seven files can
116
118
  // read and write the same page. Data-derived fields are filled by buildWorld.
119
+ // Which sky this page is — read once, before the state that depends on it.
120
+ const HOME = document.body.dataset.skyLens === 'all' ? 'all' : 'mine';
117
121
  const S = {
118
122
  goals: [], goalById: new Map(), taskById: new Map(), edges: [], BANDS: [], ASPECT: 1, BORN: { goals: {}, criteria: {} },
119
123
  baseIdeas: [], ideas: [], becameGoal: new Map(), becameCrit: new Map(), T0: NOW - 86400000,
120
124
  hasFiledVia: false, canNod: false, template: null, buildingVersionIds: [], loaded: false,
121
125
  rehearsal: false, claudeLayer: false, influence: false, ideasOnMap: false, recentOnly: false,
122
- lens: 'mine', home: document.body.dataset.skyLens === 'all' ? 'all' : 'mine', picked: null, hover: null, filter: 'all', gfilter: 'all', dirty: true, gravityGoal: null, flying: null, tourSpot: null,
126
+ lens: HOME, home: HOME, lenses: HOME === 'all' ? ['all'] : ['simple', 'mine'], tourKey: HOME === 'all' ? 'sky-map-tour-seen' : 'sky-tour-seen', picked: null, hover: null, filter: 'all', gfilter: 'all', dirty: true, gravityGoal: null, flying: null, tourSpot: null,
123
127
  clock: NOW, playing: false, clockHidden: store.get('sky-clock') === 'off',
124
128
  charted: new Map(), focusSet: null, explore: { on: store.get('sky-explore') !== 'off', seen: new Set(JSON.parse(store.get('sky-seen') || '[]')) },
125
129
  cam: { x: 0, y: 0, k: 0.12 }, flight: null, W: 0, H: 0, DPR: 1, t0: 0, shuttles: [],
@@ -323,7 +327,7 @@
323
327
  }
324
328
 
325
329
  Object.assign(SKY, {
326
- S, $, TAU, GOLDEN, reduce, NOW, hash, rng, clamp, lerp, smooth, ease, viewport, esc, fmtDate, fmtDay, ageDays, plain, short, store, tokens, toMs, hallBase, API, api,
330
+ S, $, hide, press, on, TAU, GOLDEN, reduce, NOW, hash, rng, clamp, lerp, smooth, ease, viewport, esc, fmtDate, fmtDay, ageDays, plain, short, store, tokens, toMs, hallBase, API, api,
327
331
  OBJ, objects, SESSION_VIAS, viaWord, bornFromSky, goalFromSky, ideaFromMine, bandsFor, wellRadius, ringR, taskAt, buildWorld, edgesOf,
328
332
  applyRehearsal, inLayer, shown, diameter, positionWord, brightnessWord, reach, became, temp, placeIdeas, touched, load,
329
333
  });
@@ -5,12 +5,11 @@
5
5
  //
6
6
  // The prototype's "hover card + shuttle menu", "interaction" and "lenses and
7
7
  // toggles" sections, verbatim but for the shared state on `SKY.S`. The one
8
- // behaviour this part CHANGES on purpose: the lens switcher LEAVES for the
9
- // other page when the lens asked for is that page's home (part 4, task
10
- // 1004232) — on /thinking, "the whole project" goes to /goal-map; on
11
- // /goal-map, "simple" and "your thinking" go to /thinking — instead of
12
- // switching in place. Every lens survives inside both pages because the tour
13
- // walks all three. And the "claude layer" pill only appears once part 5's
8
+ // behaviour this part CHANGES on purpose: the two skies are separate (task
9
+ // 1004328, owner decision 2026-09-27). /thinking's switcher is "simple" and
10
+ // "your thinking" and nothing else; /goal-map has no switcher, only "the
11
+ // whole project". setLens stays inside the page's own `S.lenses`, and no
12
+ // button leaves for the other page. And the "claude layer" pill only appears once part 5's
14
13
  // `filed_via` exists on at least one thought; until then it is hidden and
15
14
  // every thought shows (the README's "must not ship" row).
16
15
  //
@@ -21,7 +20,7 @@
21
20
  'use strict';
22
21
 
23
22
  const SKY = window.OTBSky; const S = SKY.S;
24
- const { $, TAU, hash, clamp, esc, fmtDay, short, hallBase, OBJ, diameter, reach, became, inLayer, shown, seen, present, toWorld, bandAt, cv, hit, edgesOf } = SKY;
23
+ const { $, hide, press, on, TAU, hash, clamp, esc, fmtDay, short, OBJ, diameter, reach, became, inLayer, shown, seen, present, toWorld, bandAt, cv, hit, edgesOf } = SKY;
25
24
 
26
25
  // ---------------------------------------------------------------- hover card + shuttle menu
27
26
  const card = $('card'); const menu = $('menu');
@@ -87,32 +86,33 @@
87
86
 
88
87
  // ---------------------------------------------------------------- lenses and toggles
89
88
  function setLens(l, noFly) {
89
+ // A lens this page does not have is never entered (task 1004328): the page's own home stands in.
90
+ if (!S.lenses.includes(l)) l = S.home;
90
91
  S.lens = l;
91
- for (const [id, v] of [['lensSimple', 'simple'], ['lensMine', 'mine'], ['lensAll', 'all']]) $(id).setAttribute('aria-pressed', String(l === v));
92
+ for (const [id, v] of [['lensSimple', 'simple'], ['lensMine', 'mine']]) press(id, l === v);
92
93
  $('lensWord').textContent = l === 'simple' ? '— simply' : l === 'mine' ? '— your thinking' : '— the whole project'; document.querySelector('.brand h1').firstChild.textContent = l === 'all' ? 'the sky ' : 'your sky ';
93
- $('claudeBtn').hidden = l !== 'mine' || !S.hasFiledVia; $('influenceBtn').hidden = l !== 'mine'; $('ideasBtn').hidden = l !== 'all'; $('exploreBtn').hidden = l !== 'all'; $('rehearse').hidden = !(l === 'mine' || (l === 'all' && S.ideasOnMap));
94
- $('recentBtn').hidden = l !== 'simple'; $('roomBtn').hidden = l === 'simple'; $('clockBtn').hidden = l === 'simple'; $('time').hidden = S.clockHidden || l === 'simple'; document.querySelector('.jump').hidden = l === 'simple'; $('legend').hidden = l === 'simple';
94
+ hide('claudeBtn', l !== 'mine' || !S.hasFiledVia); hide('influenceBtn', l !== 'mine'); hide('ideasBtn', l !== 'all'); hide('exploreBtn', l !== 'all'); $('rehearse').hidden = !(l === 'mine' || (l === 'all' && S.ideasOnMap));
95
+ hide('recentBtn', l !== 'simple'); $('roomBtn').hidden = l === 'simple'; $('clockBtn').hidden = l === 'simple'; $('time').hidden = S.clockHidden || l === 'simple'; document.querySelector('.jump').hidden = l === 'simple'; $('legend').hidden = l === 'simple';
95
96
  if (l !== 'all') SKY.clearCharts();
96
97
  if (l === 'simple') { SKY.toggleRoom(false); pick(null); }
97
98
  if (!noFly) { if (l === 'all') SKY.fitAll(); else SKY.fitPresent(); }
98
99
  SKY.maybeStart(); SKY.renderRoom(); updateStats(); S.dirty = true;
99
100
  }
100
- // The switcher is two doors (part 4, task 1004232): a lens that lives on the
101
- // OTHER page is reached by going there. /thinking is home to 'simple' and
102
- // 'mine'; /goal-map is home to 'all'. Inside a page every lens still works
103
- // (the tour walks all three), so only the button leaves.
104
- const leave = (p) => location.assign(`${hallBase()}${p}`);
105
- $('lensSimple').addEventListener('click', () => (S.home === 'all' ? leave('/thinking') : setLens('simple')));
106
- $('lensMine').addEventListener('click', () => (S.home === 'all' ? leave('/thinking') : setLens('mine')));
107
- $('lensAll').addEventListener('click', () => (S.home === 'all' ? setLens('all') : leave('/goal-map')));
101
+ // The switcher is /thinking's alone (task 1004328): "simple" and "your
102
+ // thinking", both switching in place. /goal-map carries no switcher, and no
103
+ // button on either page leaves for the other.
104
+ on('lensSimple', 'click', () => setLens('simple'));
105
+ on('lensMine', 'click', () => setLens('mine'));
108
106
  $('fitBtn').addEventListener('click', () => (S.lens === 'all' ? SKY.fitAll() : SKY.fitPresent()));
109
- function togglePill(id, get, set) { $(id).addEventListener('click', () => { set(!get()); $(id).setAttribute('aria-pressed', String(get())); SKY.renderRoom(); updateStats(); S.dirty = true; }); }
107
+ // Each pill is one page's: recent, claude layer and my influence are
108
+ // /thinking's; my ideas and explore are /goal-map's (task 1004328).
109
+ function togglePill(id, get, set) { on(id, 'click', () => { set(!get()); press(id, get()); SKY.renderRoom(); updateStats(); S.dirty = true; }); }
110
110
  togglePill('recentBtn', () => S.recentOnly, (v) => { S.recentOnly = v; });
111
111
  togglePill('claudeBtn', () => S.claudeLayer, (v) => { S.claudeLayer = v; });
112
112
  togglePill('influenceBtn', () => S.influence, (v) => { S.influence = v; });
113
113
  togglePill('ideasBtn', () => S.ideasOnMap, (v) => { S.ideasOnMap = v; $('rehearse').hidden = !(S.lens === 'mine' || S.ideasOnMap); });
114
114
  togglePill('exploreBtn', () => S.explore.on, (v) => { S.explore.on = v; SKY.saveExplore(); SKY.maybeStart(); });
115
- $('exploreBtn').setAttribute('aria-pressed', String(S.explore.on));
115
+ press('exploreBtn', S.explore.on);
116
116
  $('rehearse').addEventListener('click', () => { S.rehearsal = !S.rehearsal; $('rehearse').setAttribute('aria-pressed', String(S.rehearsal)); SKY.applyRehearsal(); SKY.placeIdeas(); if (S.picked && S.picked.kind === 'idea') { S.picked = { kind: 'idea', ref: S.ideas.find((i) => i.id === S.picked.ref.id) }; SKY.renderPanel(S.picked); } SKY.renderRoom(); updateStats(); S.dirty = true; });
117
117
 
118
118
  function updateStats() {
@@ -136,14 +136,15 @@
136
136
  const is = S.ideas.filter((i) => inLayer(i) && i.title.toLowerCase().includes(q)).slice(0, 8).map((i) => ({ kind: 'idea', ref: i, w: (OBJ[i.st] || { word: i.st }).word }));
137
137
  const all = [...gs, ...is]; ul.hidden = false;
138
138
  ul.innerHTML = all.length ? all.map((h, n) => `<li><button type="button" data-n="${n}"><span class="lbl">${esc(h.w)}</span><span>${esc(h.ref.title)}</span></button></li>`).join('') : '<li><button type="button" disabled>nothing by that name — or not explored yet</button></li>';
139
- ul.querySelectorAll('button[data-n]').forEach((b) => b.addEventListener('click', () => { const h = all[+b.dataset.n]; if (h.kind === 'goal' && S.lens !== 'all' && h.ref.status !== 'open') setLens('all', true); if (h.kind === 'idea' && S.lens === 'all') { S.ideasOnMap = true; $('ideasBtn').setAttribute('aria-pressed', 'true'); } pick({ kind: h.kind, ref: h.ref }, true); ul.hidden = true; $('jump').value = ''; }));
139
+ ul.querySelectorAll('button[data-n]').forEach((b) => b.addEventListener('click', () => { const h = all[+b.dataset.n]; if (h.kind === 'idea' && S.lens === 'all') { S.ideasOnMap = true; press('ideasBtn', true); } pick({ kind: h.kind, ref: h.ref }, true); ul.hidden = true; $('jump').value = ''; }));
140
140
  });
141
141
  document.addEventListener('click', (e) => { if (!(e.target && e.target.closest && e.target.closest('.jump'))) $('jumpList').hidden = true; });
142
142
 
143
143
  $('legend').innerHTML = [
144
144
  ['star', 'star', 'burning'], ['protostar', 'protostar', 'waiting'], ['satellite', 'satellite', 'folded in'], ['remnant', 'remnant', 'burned out'], ['cold', 'cold body', 'declined'],
145
145
  ['well', 'well', 'an open goal'], ['alive', 'alive', 'a well with work in motion'], ['settled', 'settled', 'achieved'], ['collapsed', 'collapsed', 'archived'], ['unseen', 'unexplored', 'not yet reached'], ['task live', 'body', 'a task'], ['born', 'born of a thought', 'a goal or ring an idea became'], ['route', 'route', 'a dependency, charted'],
146
- ].map(([cls, name, what]) => `<button type="button" class="obj" title="${esc(what)}"><span class="g"><span class="mk ${cls}"></span></span>${esc(name)}</button>`).join('');
146
+ // "unexplored" is explore's word, and explore is the project map's (task 1004328).
147
+ ].filter(([cls]) => cls !== 'unseen' || S.lenses.includes('all')).map(([cls, name, what]) => `<button type="button" class="obj" title="${esc(what)}"><span class="g"><span class="mk ${cls}"></span></span>${esc(name)}</button>`).join('');
147
148
 
148
149
  Object.assign(SKY, { card, menu, glyphHtml, ideaTitle, sizeWords, showCard, showMenu, hideMenu, pick, setLens, togglePill, updateStats, altText, TAU, hash });
149
150
  })();
@@ -6,15 +6,15 @@
6
6
  // sky-data.js for the split). The clock draws the sky as it stood on a day —
7
7
  // nothing moves position, only what exists changes. Routes are the goal
8
8
  // graph's own edges, charted by a shuttle when asked. Explore is the project
9
- // sky's rule (home at /goal-map since part 4; the tour walks it on /thinking
10
- // too). The camera is a
9
+ // sky's rule, and only /goal-map carries it (task 1004328: its pill and its
10
+ // start dialog are that page's markup alone). The camera is a
11
11
  // pan/zoom over world coordinates laid out in sky-data.js.
12
12
 
13
13
  (() => {
14
14
  'use strict';
15
15
 
16
16
  const SKY = window.OTBSky; const S = SKY.S;
17
- const { $, TAU, reduce, NOW, clamp, smooth, ease, esc, fmtDay, short, store } = SKY;
17
+ const { $, hide, press, TAU, reduce, NOW, clamp, smooth, ease, esc, fmtDay, short, store } = SKY;
18
18
 
19
19
  // ---------------------------------------------------------------- the clock
20
20
  const slider = $('clock');
@@ -50,13 +50,13 @@
50
50
  el.innerHTML = `<button type="button" class="door primary" data-start="mine">where my thoughts are</button>` + wells.map((g) => `<button type="button" class="door" data-start="${esc(g.id)}">${esc(short(g.title))}</button>`).join('') + `<button type="button" class="door" data-start="all">show me everything</button>`;
51
51
  el.querySelectorAll('button').forEach((b) => b.addEventListener('click', () => {
52
52
  const v = b.dataset.start; $('start').hidden = true;
53
- if (v === 'all') { explore.on = false; $('exploreBtn').setAttribute('aria-pressed', 'false'); saveExplore(); fitAll(); }
53
+ if (v === 'all') { explore.on = false; press('exploreBtn', false); saveExplore(); fitAll(); }
54
54
  else if (v === 'mine') { const t = S.goals.filter(SKY.touched); (t.length ? t : wells.slice(0, 1)).forEach(reveal); fitPresent(); }
55
55
  else { const g = S.goalById.get(v); reveal(g); SKY.pick({ kind: 'goal', ref: g }, true); }
56
56
  SKY.renderRoom(); SKY.updateStats(); S.dirty = true;
57
57
  }));
58
58
  }
59
- function maybeStart() { const need = S.lens === 'all' && explore.on && explore.seen.size === 0 && $('tour').hidden; $('start').hidden = !need; if (need) startOptions(); }
59
+ function maybeStart() { const need = S.lens === 'all' && explore.on && explore.seen.size === 0 && $('tour').hidden && !!$('start'); hide('start', !need); if (need) startOptions(); }
60
60
 
61
61
  // ---------------------------------------------------------------- the camera
62
62
  const cv = $('sky'); const cam = S.cam;
@@ -85,10 +85,11 @@
85
85
  <a class="door" href="${HALL}/goals#/goal/${esc(g.id)}" target="_blank" rel="noopener">the goal</a>
86
86
  </div>
87
87
  <p class="note">${g.status === 'open' ? 'A well holds work. Its rings are the done-when criteria, thick with the credits they carry, solid once met. It glows while it is alive: a builder holding a task, or work landing this week. Held and claimable tasks drift along their rings; landed work holds still.' : g.status === 'achieved' ? 'Every ring closed. It keeps the full mass of the work; nothing in it is live any more, so it holds no accent — a steady light, not a burning one.' : 'Archived. The mass of what was done stays on the record, drawn at half the radius; nothing here can ignite.'}</p>`;
88
- $('descendBtn').addEventListener('click', () => { if (S.lens !== 'all') SKY.setLens('all', true); SKY.fitGoal(g); });
88
+ // Descending stays in this page's sky (task 1004328): "simple" does not zoom, so it steps up to "your thinking" — never to the project map.
89
+ $('descendBtn').addEventListener('click', () => { if (S.lens === 'simple') SKY.setLens('mine', true); SKY.fitGoal(g); });
89
90
  const cb = $('chartBtn'); if (cb) cb.addEventListener('click', () => SKY.chartAllOf(g));
90
91
  el.querySelectorAll('[data-goal]').forEach((a) => a.addEventListener('click', (e) => { e.preventDefault(); SKY.pick({ kind: 'goal', ref: goalById.get(a.dataset.goal) }, true); }));
91
- el.querySelectorAll('[data-idea]').forEach((a) => a.addEventListener('click', (e) => { e.preventDefault(); const i = S.ideas.find((x) => x.id === a.dataset.idea); if (i) { if (S.lens === 'all') { S.ideasOnMap = true; $('ideasBtn').setAttribute('aria-pressed', 'true'); } SKY.pick({ kind: 'idea', ref: i }, true); } }));
92
+ el.querySelectorAll('[data-idea]').forEach((a) => a.addEventListener('click', (e) => { e.preventDefault(); const i = S.ideas.find((x) => x.id === a.dataset.idea); if (i) { if (S.lens === 'all') { S.ideasOnMap = true; SKY.press('ideasBtn', true); } SKY.pick({ kind: 'idea', ref: i }, true); } }));
92
93
  } else if (p.kind === 'task' || p.kind === 'ltask') {
93
94
  const t = p.ref; const g = t.goal || (t.goalId && goalById.get(t.goalId)); const crit = g && t.ci >= 0 ? g.crit[t.ci] : null;
94
95
  el.innerHTML = `
@@ -5,20 +5,27 @@
5
5
  // shared state on `SKY.S`, plus the boot the prototype did not need (its data
6
6
  // was baked into the page): the five reads (sky-data.js `load`), then the
7
7
  // prototype's own start-up line. The thirteen steps and their words are the
8
- // design of record's; the tour opens once per browser (`sky-tour-seen`).
8
+ // design of record's; the tour opens once per browser, per page (`S.tourKey`:
9
+ // `sky-tour-seen` on /thinking, `sky-map-tour-seen` on /goal-map — the two
10
+ // tours differ since task 1004328, so seeing one does not mark the other seen).
9
11
  //
10
- // The tour walks every lens inside whichever page it runs on — steps 7, 8
11
- // and 10 the project sky's ('all'), steps 1, 11 and 12 the ideator's ('mine'),
12
- // step 13 'simple' — which is why all three survive in both pages although the
13
- // switcher leaves for the other page. When it ends it hands back to the page's
14
- // HOME lens (`S.home`: 'mine' on /thinking, 'all' on /goal-map — part 4, task
15
- // 1004232), and the boot opens in that lens too.
12
+ // TWO TOURS FROM ONE LIST (task 1004328, owner decision 2026-09-27). The two
13
+ // skies no longer share lenses, so each page walks only its own: every step
14
+ // says which sky it belongs to (`sky`: 'mine', 'all' or 'both'), and STEPS is
15
+ // this page's slice of the list. /thinking keeps the ideator's steps, the
16
+ // wells and rings they sit in, the clock, the composer and "simple" — nine of
17
+ // the thirteen, in the template's order and words. /goal-map keeps the wells,
18
+ // the rings, life, routes, settled systems, the clock and explore, opened by
19
+ // one step of its own ("This is the whole project.", the one new sentence the
20
+ // split needed — the template's first step is "This is your sky." and
21
+ // belongs to /thinking). When a tour ends it hands back to the page's home
22
+ // lens (`S.home`), and the boot opens in that lens too.
16
23
 
17
24
  (() => {
18
25
  'use strict';
19
26
 
20
27
  const SKY = window.OTBSky; const S = SKY.S;
21
- const { $, NOW, store, edgesOf, inLayer, clamp, lerp, ease } = SKY;
28
+ const { $, hide, press, NOW, store, edgesOf, inLayer, clamp, lerp, ease } = SKY;
22
29
 
23
30
  // ---------------------------------------------------------------- the tour
24
31
  const bigWell = () => S.goals.filter((g) => g.status === 'open').sort((a, b) => b.crit.length - a.crit.length || b.mass - a.mass)[0];
@@ -28,25 +35,29 @@
28
35
  const aShaped = () => S.ideas.find((i) => i.shaped && inLayer(i)) || S.ideas.find((i) => i.shaped);
29
36
  const aRouted = () => S.goals.filter((g) => g.status === 'open' && edgesOf(g).length).sort((a, b) => edgesOf(b).length - edgesOf(a).length)[0];
30
37
  const aLive = () => S.goals.filter((g) => g.live > 0).sort((a, b) => b.live - a.live)[0];
31
- const STEPS = [
32
- { h: 'This is your sky.', p: 'Everything in it is a fact from the ledger, drawn instead of listed. The present sits at the centre; shipped versions ring outward as darker ground — hover the space between goals to be told which version you are in. Only what a person filed is shown; a session’s filings wait behind "claude layer".', go: () => { SKY.setLens('mine', true); SKY.fitPresent(); spot($('claudeBtn')); } },
33
- { h: 'A goal is a gravity well.', p: 'It holds the work. The rings inside it are its done-when criteria: dashed while open, solid once met, thicker the more credits ride on them. Thoughts caught by the goal sit inside the well.', go: () => { const w = bigWell(); if (w) { S.tourSpot = { kind: 'goal', ref: w }; SKY.flyTo(w.x, w.y, Math.min(S.W, S.H) / (w.r * 2.9), 1100); } } },
34
- { h: 'A ring is a criterion.', p: 'Hover any ring to read it. Come closer and the tasks that gate it resolve out of the dust onto the ring itself. Tasks born of the same thought share an angle, so kin line up radially. Held and claimable tasks drift slowly along their ring; landed work holds still.', go: () => { const w = bigWell(); if (w && w.crit.length) S.tourSpot = { kind: 'ring', goal: w, index: 0 }; } },
35
- { h: 'A burning thought is a star.', p: 'It caused work that is still alive. Its size is real: the credits the ledger paid this thought’s descent, a slice of every task that fell out of it. It grows as the work grows and never shrinks. Brightness is how much of that work still stands, and that can go out.', go: () => { const s = aStar(); if (s) { S.tourSpot = { kind: 'idea', ref: s }; SKY.flyTo(s.x, s.y, 2.6, 1100); } } },
36
- { h: 'Some thoughts became the structure.', p: 'A thought ratified into a goal sits at that well’s core with a small accent ring. One ratified into a criterion sits on its ring, and the ring wears an accent notch. When a thought’s descent reaches into other goals, hover it and its reach is drawn: dashed accent lines to the work it caused elsewhere.', go: () => { const s = aShaped(); const w = bigWell(); if (s) { S.tourSpot = { kind: 'idea', ref: s }; SKY.flyTo(s.x, s.y, Math.max(1.4, S.cam.k), 1100); } else if (w) S.tourSpot = { kind: 'goal', ref: w }; } },
37
- { h: 'Unclaimed thoughts drift.', p: 'A thought nothing caught floats in open space. The longer it stays unclaimed, the further from the centre it drifts and the cooler its light — warm white when fresh, blue-white by half a year.', go: () => { const d = aDrifter(); if (d) { S.tourSpot = { kind: 'idea', ref: d }; SKY.flyTo(d.x, d.y, 1.2, 1100); } } },
38
- { h: 'An alive well glows.', p: 'On the project map the one accent a goal can wear is life: a builder holding a task right now, or work landing this week. Quiet wells stay white. The ledger’s "alive" filter lists them.', go: () => { S.exploreWas = S.explore.on; S.explore.on = false; SKY.setLens('all', true); $('start').hidden = true; const g = aLive() || aRouted(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; SKY.flyTo(g.x, g.y, Math.min(S.W, S.H) / (g.r * 4), 1100); } } },
39
- { h: 'Routes run between goals that wait on each other.', p: 'Almost invisible until you ask. Each well wears a small arrow at its rim per route. Click one and the camera pulls back while a shuttle charts the route; double-click charts every route from that goal. Hover the parked shuttle for more: every route from this goal, every route in the sky, only the latest, hide the unrelated.', go: () => { const g = aRouted(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; setTimeout(() => { const e = edgesOf(g)[0]; if (e) SKY.chartRoute(e, g); }, 300); } } },
40
- { h: 'A settled system is a goal that finished.', p: 'It keeps the full mass of its work, measured in credits, and every ring is closed. Nothing in it is live any more, so it carries no accent — a steady white. Archived goals collapse to half their radius and go dark.', go: () => { const g = aSettled(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; SKY.flyTo(g.x, g.y, Math.min(S.W, S.H) / (g.r * 3.2), 1100); } } },
41
- { h: 'The sky has a clock.', p: 'Drag the slider at the bottom and the sky is drawn as it stood that day: goals, tasks and thoughts appear when they were born. Press play to watch it grow from the first day. Nothing moves position — a thought stays where it was caught — only what exists changes.', go: () => { SKY.fitAll(); spot($('time')); } },
42
- { h: 'By default, you explore.', p: 'The project map does not hand you everything. You pick a well to begin in; every route you chart brings the goal at its far end into view, and the ledger, the filters and the search only know what you have reached. Turn "explore" off to see the whole sky at once.', go: () => { spot($('exploreBtn')); } },
43
- { h: 'Release a thought here.', p: 'One line, and the well that would catch it lights up while you type. Letting it float is a real answer. Switch "full idea" on before the line and five questions follow it, one at a time; skip any. "My influence" fades every goal your thoughts never touched.', go: () => { S.tourSpot = null; SKY.setLens('mine', true); SKY.fitPresent(); spot($('composerWrap')); spot($('influenceBtn'), true); } },
44
- { h: 'When it is too much, go simple.', p: 'The "simple" view is the first sky: just your thoughts in their wells, recent or all, nothing else to press. Click a band anywhere else to fill the screen with that version. Press L for the list, ? to walk this again.', go: () => { SKY.setLens('simple', true); SKY.fitPresent(); spot($('lensSimple')); spot($('recentBtn'), true); } },
38
+ // Explore off for the tour's length (startTour kept what it was; endTour gives it back).
39
+ const unexplore = () => { S.explore.on = false; hide('start', true); };
40
+ const ALL_STEPS = [
41
+ { sky: 'all', h: 'This is the whole project.', p: 'Everything in it is a fact from the ledger, drawn instead of listed. Every goal is a gravity well, sized by the credits of its work; the present sits at the centre and shipped versions ring outward as darker ground. Your own thoughts live on Your thinking, and "my ideas" lays them over this map.', go: () => { unexplore(); SKY.setLens('all', true); SKY.fitAll(); spot($('ideasBtn')); } },
42
+ { sky: 'mine', h: 'This is your sky.', p: 'Everything in it is a fact from the ledger, drawn instead of listed. The present sits at the centre; shipped versions ring outward as darker ground — hover the space between goals to be told which version you are in. Only what a person filed is shown; a session’s filings wait behind "claude layer".', go: () => { SKY.setLens('mine', true); SKY.fitPresent(); spot($('claudeBtn')); } },
43
+ { sky: 'both', h: 'A goal is a gravity well.', p: 'It holds the work. The rings inside it are its done-when criteria: dashed while open, solid once met, thicker the more credits ride on them. Thoughts caught by the goal sit inside the well.', go: () => { const w = bigWell(); if (w) { S.tourSpot = { kind: 'goal', ref: w }; SKY.flyTo(w.x, w.y, Math.min(S.W, S.H) / (w.r * 2.9), 1100); } } },
44
+ { sky: 'both', h: 'A ring is a criterion.', p: 'Hover any ring to read it. Come closer and the tasks that gate it resolve out of the dust onto the ring itself. Tasks born of the same thought share an angle, so kin line up radially. Held and claimable tasks drift slowly along their ring; landed work holds still.', go: () => { const w = bigWell(); if (w && w.crit.length) S.tourSpot = { kind: 'ring', goal: w, index: 0 }; } },
45
+ { sky: 'mine', h: 'A burning thought is a star.', p: 'It caused work that is still alive. Its size is real: the credits the ledger paid this thought’s descent, a slice of every task that fell out of it. It grows as the work grows and never shrinks. Brightness is how much of that work still stands, and that can go out.', go: () => { const s = aStar(); if (s) { S.tourSpot = { kind: 'idea', ref: s }; SKY.flyTo(s.x, s.y, 2.6, 1100); } } },
46
+ { sky: 'mine', h: 'Some thoughts became the structure.', p: 'A thought ratified into a goal sits at that well’s core with a small accent ring. One ratified into a criterion sits on its ring, and the ring wears an accent notch. When a thought’s descent reaches into other goals, hover it and its reach is drawn: dashed accent lines to the work it caused elsewhere.', go: () => { const s = aShaped(); const w = bigWell(); if (s) { S.tourSpot = { kind: 'idea', ref: s }; SKY.flyTo(s.x, s.y, Math.max(1.4, S.cam.k), 1100); } else if (w) S.tourSpot = { kind: 'goal', ref: w }; } },
47
+ { sky: 'mine', h: 'Unclaimed thoughts drift.', p: 'A thought nothing caught floats in open space. The longer it stays unclaimed, the further from the centre it drifts and the cooler its light — warm white when fresh, blue-white by half a year.', go: () => { const d = aDrifter(); if (d) { S.tourSpot = { kind: 'idea', ref: d }; SKY.flyTo(d.x, d.y, 1.2, 1100); } } },
48
+ { sky: 'all', h: 'An alive well glows.', p: 'On the project map the one accent a goal can wear is life: a builder holding a task right now, or work landing this week. Quiet wells stay white. The ledger’s "alive" filter lists them.', go: () => { unexplore(); SKY.setLens('all', true); const g = aLive() || aRouted(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; SKY.flyTo(g.x, g.y, Math.min(S.W, S.H) / (g.r * 4), 1100); } } },
49
+ { sky: 'all', h: 'Routes run between goals that wait on each other.', p: 'Almost invisible until you ask. Each well wears a small arrow at its rim per route. Click one and the camera pulls back while a shuttle charts the route; double-click charts every route from that goal. Hover the parked shuttle for more: every route from this goal, every route in the sky, only the latest, hide the unrelated.', go: () => { const g = aRouted(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; setTimeout(() => { const e = edgesOf(g)[0]; if (e) SKY.chartRoute(e, g); }, 300); } } },
50
+ { sky: 'all', h: 'A settled system is a goal that finished.', p: 'It keeps the full mass of its work, measured in credits, and every ring is closed. Nothing in it is live any more, so it carries no accent — a steady white. Archived goals collapse to half their radius and go dark.', go: () => { const g = aSettled(); if (g) { S.tourSpot = { kind: 'goal', ref: g }; SKY.flyTo(g.x, g.y, Math.min(S.W, S.H) / (g.r * 3.2), 1100); } } },
51
+ { sky: 'both', h: 'The sky has a clock.', p: 'Drag the slider at the bottom and the sky is drawn as it stood that day: goals, tasks and thoughts appear when they were born. Press play to watch it grow from the first day. Nothing moves position — a thought stays where it was caught — only what exists changes.', go: () => { SKY.fitAll(); spot($('time')); } },
52
+ { sky: 'all', h: 'By default, you explore.', p: 'The project map does not hand you everything. You pick a well to begin in; every route you chart brings the goal at its far end into view, and the ledger, the filters and the search only know what you have reached. Turn "explore" off to see the whole sky at once.', go: () => { spot($('exploreBtn')); } },
53
+ { sky: 'mine', h: 'Release a thought here.', p: 'One line, and the well that would catch it lights up while you type. Letting it float is a real answer. Switch "full idea" on before the line and five questions follow it, one at a time; skip any. "My influence" fades every goal your thoughts never touched.', go: () => { S.tourSpot = null; SKY.setLens('mine', true); SKY.fitPresent(); spot($('composerWrap')); spot($('influenceBtn'), true); } },
54
+ { sky: 'mine', h: 'When it is too much, go simple.', p: 'The "simple" view is the first sky: just your thoughts in their wells, recent or all, nothing else to press. Click a band anywhere else to fill the screen with that version. Press L for the list, ? to walk this again.', go: () => { SKY.setLens('simple', true); SKY.fitPresent(); spot($('lensSimple')); spot($('recentBtn'), true); } },
45
55
  ];
56
+ const STEPS = ALL_STEPS.filter((s) => s.sky === 'both' || s.sky === S.home);
46
57
  function spot(el, add) { if (!add) { S.spotted.forEach((e) => e.classList.remove('spot')); S.spotted = []; } if (el) { el.classList.add('spot'); S.spotted.push(el); } }
47
58
  function showStep() { const s = STEPS[S.tourI]; if (!s) return endTour(); spot(null); S.tourSpot = null; s.go(); $('tourN').textContent = `how to read the sky · ${S.tourI + 1} of ${STEPS.length}`; $('tourDots').innerHTML = STEPS.map((_, n) => `<i class="${n === S.tourI ? 'on' : ''}"></i>`).join(''); $('tourH').textContent = s.h; $('tourP').textContent = s.p; $('tourNext').textContent = S.tourI === STEPS.length - 1 ? 'start reading' : 'next'; $('tourBack').hidden = S.tourI === 0; $('tour').hidden = false; S.dirty = true; }
48
- function startTour() { SKY.pick(null); SKY.toggleRoom(false); SKY.clearCharts(); $('start').hidden = true; S.exploreWas = S.explore.on; S.tourI = 0; showStep(); }
49
- function endTour() { $('tour').hidden = true; spot(null); S.tourSpot = null; S.tourI = -1; if (S.exploreWas != null) { S.explore.on = S.exploreWas; $('exploreBtn').setAttribute('aria-pressed', String(S.explore.on)); S.exploreWas = null; } store.set('sky-tour-seen', '4'); SKY.setLens(S.home, true); SKY.clearCharts(); SKY.maybeStart(); S.dirty = true; }
59
+ function startTour() { SKY.pick(null); SKY.toggleRoom(false); SKY.clearCharts(); hide('start', true); S.exploreWas = S.explore.on; S.tourI = 0; showStep(); }
60
+ function endTour() { $('tour').hidden = true; spot(null); S.tourSpot = null; S.tourI = -1; if (S.exploreWas != null) { S.explore.on = S.exploreWas; press('exploreBtn', S.explore.on); S.exploreWas = null; } store.set(S.tourKey, '4'); SKY.setLens(S.home, true); SKY.clearCharts(); SKY.maybeStart(); S.dirty = true; }
50
61
  $('tourNext').addEventListener('click', () => { S.tourI++; showStep(); }); $('tourBack').addEventListener('click', () => { S.tourI = Math.max(0, S.tourI - 1); showStep(); }); $('tourSkip').addEventListener('click', endTour); $('tourBtn').addEventListener('click', startTour);
51
62
 
52
63
  // ---------------------------------------------------------------- the loop
@@ -76,9 +87,9 @@
76
87
  S.cam.k = S.home === 'all' ? SKY.fitRad(S.BANDS[S.BANDS.length - 1].r1 + 100) : SKY.fitRad(S.BANDS[0].r1 + 120);
77
88
  document.body.dataset.skyReady = '1';
78
89
  requestAnimationFrame(frame);
79
- if (store.get('sky-tour-seen') !== '4') setTimeout(startTour, 600);
90
+ if (store.get(S.tourKey) !== '4') setTimeout(startTour, 600);
80
91
  }
81
92
 
82
- Object.assign(SKY, { STEPS, spot, showStep, startTour, endTour, frame, boot });
93
+ Object.assign(SKY, { ALL_STEPS, STEPS, spot, showStep, startTour, endTour, frame, boot });
83
94
  boot();
84
95
  })();
@@ -86,6 +86,8 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 64px; }
86
86
  .lens button[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
87
87
  .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; }
88
88
  .tools { display: inline-flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; max-width: 680px; }
89
+ /* /goal-map carries no switcher (task 1004328), so its tools take the switcher's push to the right. */
90
+ .brand + .tools { margin-left: auto; }
89
91
  .pill { background: var(--glass); }
90
92
  .pill[aria-pressed="true"] { background: var(--glass-strong); color: var(--ink); }
91
93
  .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; }