@found-in-space/skykit 0.2.0-alpha.0 → 0.2.0-dev.20260527.0

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 (42) hide show
  1. package/README.md +223 -8
  2. package/examples/custom-object-layer/custom-object-layer.js +1 -24
  3. package/examples/xr-free-roam/index.html +62 -4
  4. package/examples/xr-free-roam/xr-free-roam.css +249 -18
  5. package/examples/xr-free-roam/xr-free-roam.js +644 -217
  6. package/package.json +46 -5
  7. package/src/__tests__/skykit-anchored-images.test.js +32 -4
  8. package/src/__tests__/skykit-browser.test.js +442 -0
  9. package/src/__tests__/skykit-data.test.js +131 -0
  10. package/src/__tests__/skykit-parallax.test.js +4 -4
  11. package/src/__tests__/skykit-touch-os.test.js +71 -0
  12. package/src/__tests__/skykit-xr.test.js +123 -2
  13. package/src/__tests__/skykit.test.js +138 -1
  14. package/src/anchored-images.js +14 -15
  15. package/src/browser-addons.d.ts +16 -0
  16. package/src/browser-addons.js +155 -0
  17. package/src/browser-constellations.d.ts +13 -0
  18. package/src/browser-constellations.js +387 -0
  19. package/src/browser-journey.d.ts +8 -0
  20. package/src/browser-journey.js +240 -0
  21. package/src/browser.d.ts +170 -0
  22. package/src/browser.js +369 -0
  23. package/src/data.d.ts +133 -0
  24. package/src/data.js +447 -0
  25. package/src/embed.d.ts +6 -0
  26. package/src/embed.js +119 -0
  27. package/src/hr-diagram.js +23 -5
  28. package/src/index.d.ts +32 -7
  29. package/src/plugins.js +87 -43
  30. package/src/story.d.ts +57 -0
  31. package/src/story.js +396 -0
  32. package/src/three-shim.d.ts +32 -0
  33. package/src/touch-os.d.ts +70 -0
  34. package/src/touch-os.js +275 -0
  35. package/src/utils.js +96 -6
  36. package/src/viewer-entry.d.ts +10 -0
  37. package/src/viewer-entry.js +4 -0
  38. package/src/viewer.js +110 -12
  39. package/src/xr/plugins.js +224 -13
  40. package/src/xr/session.js +60 -14
  41. package/src/xr.d.ts +22 -0
  42. package/src/xr.js +1 -0
package/README.md CHANGED
@@ -20,17 +20,204 @@ strategies. SkyKit passes strategies through to provider sessions; it does not
20
20
  redefine planning, inspect strategy kinds, or hide loader registries behind
21
21
  string names.
22
22
 
23
- ## Create A Viewer
23
+ ## Website Use-Cases
24
+
25
+ The beginner website entries are use-case bounded:
26
+
27
+ | Use-case | Website owner says | Public entry |
28
+ | --- | --- | --- |
29
+ | Viewer | "Put stars on my page and let me customize the scene." | `embed.js`, `viewer.js` |
30
+ | Data | "Give me star data so I can render, list, map, or game it myself." | `data.js` |
31
+ | Story | "Let me tell a curated story through space." | `story.js` |
32
+
33
+ `embed.js` is the no-code viewer entry. It is not a separate use-case.
34
+ `viewer.js` is the JavaScript-customizable viewer entry. `data.js` is renderer
35
+ independent. `story.js` is authored chapters plus a viewer.
36
+
37
+ ## Paste into a static page or CMS
38
+
39
+ For the beginner path, use the auto-booting embed. Paste this into a static HTML
40
+ page or a CMS custom HTML block:
41
+
42
+ ```html
43
+ <div
44
+ data-skykit-browser
45
+ data-skykit-status="#skykit-status"
46
+ style="width: 100%; height: 70vh; min-height: 420px; background: #02040b"
47
+ ></div>
48
+
49
+ <pre id="skykit-status">Loading stars...</pre>
50
+
51
+ <script
52
+ type="module"
53
+ src="https://esm.sh/@found-in-space/skykit@0.2.0-alpha.2/embed?bundle&deps=three@0.170.0"
54
+ ></script>
55
+ ```
56
+
57
+ The embed script finds every `[data-skykit-browser]` element and creates the
58
+ standard star browser there. It owns the normal beginner plumbing: Three.js
59
+ renderer and camera, the public star provider, the star-field renderer, streaming
60
+ stars, keyboard navigation, drag-to-look controls, resize handling, the animation
61
+ loop, and page-lifecycle cleanup.
62
+
63
+ Optional attributes keep small tweaks HTML-only:
64
+
65
+ ```html
66
+ <div
67
+ data-skykit-browser
68
+ data-skykit-status="#skykit-status"
69
+ data-skykit-magnitude="7"
70
+ data-skykit-speed="4"
71
+ data-skykit-exposure="2600"
72
+ data-skykit-look-at="ra=4.496h, dec=16.948"
73
+ data-skykit-mouse-mode="strafe"
74
+ style="width: 100%; height: 520px; background: #02040b"
75
+ ></div>
76
+ ```
77
+
78
+ `data-skykit-look-at` accepts RA/Dec text such as
79
+ `ra=4.496h, dec=16.948`, decimal degrees such as `67.447,16.948`, or a
80
+ parsec-space `x,y,z` target for exact generated coordinates. `data-skykit-mouse-mode`
81
+ defaults to `grab`; use `look` or `strafe` for the first-person mouse-look
82
+ direction, or `none` to disable mouse drag controls.
83
+
84
+ The host dispatches `skykit-browser-ready` with `{ browser, viewer }` in
85
+ `event.detail` after startup, and `skykit-browser-error` if startup fails. The
86
+ embed also installs a small `Skykit` global for noob-path scripts:
87
+
88
+ ```js
89
+ const browser = await Skykit.whenReady();
90
+ ```
91
+
92
+ Pages can host multiple viewers. Pass a selector or element to choose one:
93
+
94
+ ```js
95
+ const browser = await Skykit.whenReady('#orion-viewer');
96
+ ```
97
+
98
+ Pin the package CDN URL to a released SkyKit version when publishing long-lived
99
+ pages, for example
100
+ `https://esm.sh/@found-in-space/skykit@x.y.z/embed?bundle&deps=three@0.170.0`.
101
+
102
+ Optional first-party capabilities stay out of the initial browser until they are
103
+ requested. This keeps the one-script noob path while avoiding bundle bloat.
104
+
105
+ ```html
106
+ <div
107
+ data-skykit-browser
108
+ data-skykit-constellations="western"
109
+ data-skykit-constellation-art="off"
110
+ style="width:100%;height:520px;background:#02040b"
111
+ ></div>
112
+ ```
113
+
114
+ For small scripted interactions, use the browser handle:
115
+
116
+ ```html
117
+ <script type="module">
118
+ const browser = await Skykit.whenReady();
119
+
120
+ document.querySelector('#orion').addEventListener('click', () => {
121
+ browser.journey.transitionTo({
122
+ lookAt: 'ra=5.919h, dec=7.407',
123
+ durationSecs: 3,
124
+ });
125
+ });
126
+ </script>
127
+ ```
128
+
129
+ ## Create a Viewer from JavaScript
130
+
131
+ If your site has a module script, npm, or a bundler, call the helper directly:
132
+
133
+ ```html
134
+ <div id="viewer" style="width: 100vw; height: 100vh"></div>
135
+ <pre id="status">Loading stars...</pre>
136
+
137
+ <script type="module">
138
+ import { createSkykitBrowser } from 'https://esm.sh/@found-in-space/skykit@0.2.0-alpha.2/viewer?bundle&deps=three@0.170.0';
139
+
140
+ await createSkykitBrowser({
141
+ host: '#viewer',
142
+ status: '#status',
143
+ });
144
+ </script>
145
+ ```
146
+
147
+ The helper still returns the pieces when a lesson wants to grow:
148
+
149
+ ```js
150
+ const sky = await createSkykitBrowser('#viewer');
151
+
152
+ sky.viewer.requestViewState({ observerPc: { x: 4, y: 0, z: -8 } });
153
+ sky.addObject(marker, {
154
+ positionPc: { x: 17.574, y: 42.316, z: 13.963 },
155
+ });
156
+ sky.loop.stop();
157
+ await sky.dispose();
158
+ ```
159
+
160
+ For npm or bundlers, use the same beginner entry:
161
+
162
+ ```js
163
+ import { THREE, createSkykitBrowser } from '@found-in-space/skykit/viewer';
164
+ ```
165
+
166
+ ## Use Star Data Without a Viewer
167
+
168
+ Use `data.js` when SkyKit should supply rows and your app should own rendering:
169
+
170
+ ```js
171
+ import { loadStarRows } from 'https://esm.sh/@found-in-space/skykit@0.2.0-alpha.2/data?bundle';
172
+
173
+ const stars = await loadStarRows({
174
+ limitingMagnitude: 6.5,
175
+ maxStars: 100,
176
+ sortBy: 'apparentMagnitude',
177
+ });
178
+
179
+ console.table(stars);
180
+ ```
181
+
182
+ For games and maps, load a local volume and hand rows to Canvas, PixiJS,
183
+ Phaser, SVG, or your own renderer:
184
+
185
+ ```js
186
+ const stars = await loadStarRows({
187
+ centerPc: { x: 0, y: 0, z: 0 },
188
+ radiusPc: 50,
189
+ maxStars: 2000,
190
+ });
191
+ ```
192
+
193
+ ## Create a Guided Story
194
+
195
+ Use `story.js` when the page is an authored article or tour:
196
+
197
+ ```html
198
+ <div data-skykit-story style="height:600px;background:#02040b">
199
+ <section data-skykit-chapter data-title="The Sun" data-target-pc="0,0,0">
200
+ We start at the Sun.
201
+ </section>
202
+ <section data-skykit-chapter data-title="The Hyades" data-target-pc="17.574,42.316,13.963">
203
+ Now jump to the Hyades cluster.
204
+ </section>
205
+ </div>
206
+
207
+ <script
208
+ type="module"
209
+ src="https://esm.sh/@found-in-space/skykit@0.2.0-alpha.2/story?bundle&deps=three@0.170.0"
210
+ ></script>
211
+ ```
212
+
213
+ Use the lower-level factories when a lesson is teaching composition or replacing
214
+ a part of the stack:
24
215
 
25
216
  ```js
26
217
  import {
27
- SKYKIT_ACTIONS,
28
- SKYKIT_DEFAULT_KEYBOARD_NAVIGATION_BINDINGS,
29
218
  createKeyboardNavigationPlugin,
30
- createSkykitDefaultKeyboardNavigationBindings,
31
219
  createSkyGrabPlugin,
32
220
  createSkykitAnimationLoop,
33
- createSkykitStatusPlugin,
34
221
  createSkykitViewer,
35
222
  createStreamingStarsPlugin,
36
223
  } from '@found-in-space/skykit';
@@ -41,11 +228,12 @@ import {
41
228
  import { createObserverShellStrategy } from '@found-in-space/star-trees';
42
229
  import { createThreeStarField } from '@found-in-space/three-star-field';
43
230
 
231
+ const host = document.querySelector('#viewer');
44
232
  const provider = createStarOctreeProviderService({ url: OCTREE_DEFAULT });
45
233
  const starField = createThreeStarField();
46
234
 
47
235
  const viewer = await createSkykitViewer({
48
- host: document.querySelector('#skykit'),
236
+ host,
49
237
  view: { coordinateUnitsPerParsec: 0.001 },
50
238
  plugins: [
51
239
  createStreamingStarsPlugin({
@@ -54,8 +242,7 @@ const viewer = await createSkykitViewer({
54
242
  session: { strategy: createObserverShellStrategy() },
55
243
  }),
56
244
  createKeyboardNavigationPlugin({ speedPcPerSec: 2 }),
57
- createSkyGrabPlugin({ target: document.querySelector('#skykit') }),
58
- createSkykitStatusPlugin({ target: document.querySelector('#status') }),
245
+ createSkyGrabPlugin({ target: host }),
59
246
  ],
60
247
  });
61
248
 
@@ -69,6 +256,12 @@ supplied, it is the complete key map. Multiple keys can still point to the same
69
256
  action:
70
257
 
71
258
  ```js
259
+ import {
260
+ SKYKIT_ACTIONS,
261
+ createKeyboardNavigationPlugin,
262
+ createSkykitDefaultKeyboardNavigationBindings,
263
+ } from '@found-in-space/skykit';
264
+
72
265
  createKeyboardNavigationPlugin({
73
266
  rotationSpeedDegPerSec: 45,
74
267
  bindings: createSkykitDefaultKeyboardNavigationBindings({
@@ -185,6 +378,28 @@ For a slightly more playful example, see `examples/plugin-lab.js`. It builds
185
378
  app-owned Three objects and action-driven annotations from the same public hooks
186
379
  a learner would use.
187
380
 
381
+ The pasteable browser embed has a smaller add-on convention for noob pages:
382
+
383
+ ```js
384
+ Skykit.registerBrowserAddon({
385
+ id: 'lesson:marker',
386
+ install({ browser, THREE }) {
387
+ const marker = new THREE.Mesh(
388
+ new THREE.SphereGeometry(0.02),
389
+ new THREE.MeshBasicMaterial({ color: 0xffcc00 }),
390
+ );
391
+ const handle = browser.addObject(marker, {
392
+ positionPc: { x: 17.574, y: 42.316, z: 13.963 },
393
+ });
394
+ return () => handle.dispose();
395
+ },
396
+ });
397
+ ```
398
+
399
+ See `docs/skykit-browser-plugins.md` for the browser add-on spec,
400
+ `Skykit.whenReady()`, first-party constellation support, and the
401
+ `browser.journey` API.
402
+
188
403
  Browser lessons:
189
404
 
190
405
  - `examples/free-roam-lesson/` composes streamed stars, keyboard navigation,
@@ -145,7 +145,7 @@ function createInitialViewState() {
145
145
  observerPc: { x: 0, y: 0, z: 0 },
146
146
  coordinateUnitsPerParsec: UNITS_PER_PARSEC,
147
147
  limitingMagnitude: 7.5,
148
- orientationIcrs: lookAtFromOriginWithNorthUp(HYADES_CENTER_PC),
148
+ lookAt: { targetPc: HYADES_CENTER_PC },
149
149
  };
150
150
  }
151
151
 
@@ -332,29 +332,6 @@ function toRenderPosition(pointPc) {
332
332
  );
333
333
  }
334
334
 
335
- function lookAtFromOriginWithNorthUp(targetPc) {
336
- const forward = new THREE.Vector3(targetPc.x, targetPc.y, targetPc.z).normalize();
337
- const north = new THREE.Vector3(0, 0, 1);
338
- let up = north.clone().sub(forward.clone().multiplyScalar(north.dot(forward)));
339
- if (up.lengthSq() < 1e-8) {
340
- up = new THREE.Vector3(0, 1, 0);
341
- }
342
- up.normalize();
343
-
344
- const backward = forward.clone().negate();
345
- const right = up.clone().cross(backward).normalize();
346
- up = backward.clone().cross(right).normalize();
347
-
348
- const matrix = new THREE.Matrix4().makeBasis(right, up, backward);
349
- const quaternion = new THREE.Quaternion().setFromRotationMatrix(matrix);
350
- return {
351
- x: quaternion.x,
352
- y: quaternion.y,
353
- z: quaternion.z,
354
- w: quaternion.w,
355
- };
356
- }
357
-
358
335
  function directionFromRaDec(raDeg, decDeg) {
359
336
  const ra = THREE.MathUtils.degToRad(raDeg);
360
337
  const dec = THREE.MathUtils.degToRad(decDeg);
@@ -10,10 +10,68 @@
10
10
  <body>
11
11
  <main class="xr-free-roam-shell">
12
12
  <div class="viewer-shell" data-viewer></div>
13
- <div class="xr-actions" aria-label="XR actions">
14
- <button type="button" data-action="enter-xr">Enter VR</button>
15
- <button type="button" data-action="exit-xr">Exit VR</button>
16
- </div>
13
+ <section class="xr-preflight" data-preflight aria-labelledby="xr-preflight-title">
14
+ <div class="xr-preflight-copy">
15
+ <p class="xr-kicker">SkyKit XR</p>
16
+ <h1 id="xr-preflight-title">Free Roam Preflight</h1>
17
+ <p>
18
+ Choose the rendering defaults for this headset run, then enter the immersive session.
19
+ </p>
20
+ <dl class="xr-status-list">
21
+ <div>
22
+ <dt>WebXR</dt>
23
+ <dd data-xr-supported>Checking</dd>
24
+ </div>
25
+ <div>
26
+ <dt>Scale</dt>
27
+ <dd data-setting-value="worldScaleLog10">1 m/pc</dd>
28
+ </div>
29
+ <div>
30
+ <dt>Stars</dt>
31
+ <dd data-setting-value="limitingMagnitude">Mag 7.5</dd>
32
+ </div>
33
+ </dl>
34
+ </div>
35
+
36
+ <form class="xr-settings" data-xr-settings>
37
+ <fieldset>
38
+ <legend>Rendering</legend>
39
+ <label>
40
+ <span>Magnitude limit</span>
41
+ <output data-setting-value="limitingMagnitude">7.5</output>
42
+ <input data-setting="limitingMagnitude" type="range" min="4" max="10" step="0.1" value="7.5" />
43
+ </label>
44
+ <label>
45
+ <span>Exposure</span>
46
+ <output data-setting-value="exposureLog10">100,000</output>
47
+ <input data-setting="exposureLog10" type="range" min="3.5" max="5.5" step="0.05" value="5" />
48
+ </label>
49
+ <label>
50
+ <span>World scale</span>
51
+ <output data-setting-value="worldScaleLog10">1 m/pc</output>
52
+ <input data-setting="worldScaleLog10" type="range" min="-3" max="0" step="0.05" value="0" />
53
+ </label>
54
+ </fieldset>
55
+
56
+ <fieldset>
57
+ <legend>Session Flags</legend>
58
+ <label class="xr-toggle">
59
+ <input data-setting="nearFloor" type="checkbox" checked />
60
+ <span>Near-star readability floor</span>
61
+ </label>
62
+ <label class="xr-toggle">
63
+ <input data-setting="constellationArt" type="checkbox" checked />
64
+ <span>Constellation art</span>
65
+ </label>
66
+ </fieldset>
67
+
68
+ <div class="xr-actions" aria-label="XR actions">
69
+ <button class="xr-primary-action" type="button" data-action="enter-xr">Enter VR</button>
70
+ <button type="button" data-action="exit-xr" hidden disabled>Exit VR</button>
71
+ </div>
72
+ <p class="xr-session-status" data-session-status>Idle</p>
73
+ </form>
74
+ </section>
17
75
  </main>
18
76
  <script type="module" src="./xr-free-roam.js"></script>
19
77
  </body>
@@ -2,9 +2,20 @@ html,
2
2
  body {
3
3
  margin: 0;
4
4
  width: 100%;
5
+ min-width: 320px;
5
6
  height: 100%;
6
7
  overflow: hidden;
7
- background: #020712;
8
+ background: #05070b;
9
+ color: #f4efe2;
10
+ font: 16px/1.45 "Avenir Next", Avenir, "Segoe UI", sans-serif;
11
+ }
12
+
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ [hidden] {
18
+ display: none !important;
8
19
  }
9
20
 
10
21
  .xr-free-roam-shell,
@@ -13,39 +24,259 @@ body {
13
24
  inset: 0;
14
25
  }
15
26
 
27
+ .xr-free-roam-shell {
28
+ background:
29
+ linear-gradient(90deg, rgba(5, 7, 11, 0.96) 0%, rgba(5, 7, 11, 0.84) 46%, rgba(5, 7, 11, 0.58) 100%),
30
+ #05070b;
31
+ }
32
+
33
+ .viewer-shell {
34
+ z-index: 0;
35
+ opacity: 0.34;
36
+ filter: saturate(0.78) contrast(1.08);
37
+ pointer-events: none;
38
+ transition: opacity 0.18s ease, filter 0.18s ease;
39
+ }
40
+
41
+ .xr-free-roam-shell.is-presenting .viewer-shell {
42
+ opacity: 1;
43
+ filter: none;
44
+ pointer-events: auto;
45
+ }
46
+
16
47
  .viewer-shell canvas {
17
48
  display: block;
18
49
  width: 100%;
19
50
  height: 100%;
20
- cursor: grab;
51
+ cursor: default;
21
52
  touch-action: none;
22
53
  user-select: none;
23
54
  }
24
55
 
25
- .viewer-shell canvas:active {
26
- cursor: grabbing;
56
+ .xr-preflight {
57
+ position: fixed;
58
+ inset: 0;
59
+ z-index: 10;
60
+ display: grid;
61
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 430px);
62
+ gap: clamp(24px, 5vw, 72px);
63
+ align-items: center;
64
+ width: 100%;
65
+ height: 100%;
66
+ padding: clamp(24px, 6vh, 72px) clamp(18px, 7vw, 92px);
67
+ overflow: auto;
68
+ }
69
+
70
+ .xr-free-roam-shell.is-presenting .xr-preflight {
71
+ display: none;
72
+ }
73
+
74
+ .xr-preflight-copy {
75
+ max-width: 720px;
76
+ }
77
+
78
+ .xr-kicker {
79
+ margin: 0 0 10px;
80
+ color: #6fd7c3;
81
+ font-size: 0.82rem;
82
+ font-weight: 700;
83
+ text-transform: uppercase;
84
+ }
85
+
86
+ .xr-preflight h1 {
87
+ margin: 0;
88
+ max-width: 11ch;
89
+ color: #fff9e8;
90
+ font-size: clamp(2.6rem, 6vh, 5.6rem);
91
+ line-height: 0.96;
92
+ font-weight: 760;
93
+ }
94
+
95
+ .xr-preflight-copy > p:not(.xr-kicker) {
96
+ max-width: 46rem;
97
+ margin: 22px 0 0;
98
+ color: #cfc7b8;
99
+ font-size: 1.08rem;
100
+ }
101
+
102
+ .xr-status-list {
103
+ display: grid;
104
+ grid-template-columns: repeat(3, minmax(0, 1fr));
105
+ gap: 1px;
106
+ max-width: 620px;
107
+ margin: 34px 0 0;
108
+ padding: 1px;
109
+ background: rgba(244, 239, 226, 0.14);
110
+ border-radius: 8px;
111
+ overflow: hidden;
112
+ }
113
+
114
+ .xr-status-list div {
115
+ min-width: 0;
116
+ padding: 13px 14px;
117
+ background: rgba(15, 18, 18, 0.88);
118
+ }
119
+
120
+ .xr-status-list dt {
121
+ color: #a7b2ac;
122
+ font-size: 0.76rem;
123
+ font-weight: 700;
124
+ text-transform: uppercase;
125
+ }
126
+
127
+ .xr-status-list dd {
128
+ margin: 4px 0 0;
129
+ color: #fff6dd;
130
+ font-weight: 700;
131
+ overflow-wrap: anywhere;
132
+ }
133
+
134
+ .xr-settings {
135
+ display: grid;
136
+ gap: 14px;
137
+ width: min(100%, 430px);
138
+ padding: 18px;
139
+ border: 1px solid rgba(244, 239, 226, 0.18);
140
+ border-radius: 8px;
141
+ background: rgba(14, 17, 16, 0.88);
142
+ box-shadow: 0 24px 80px rgba(0, 0, 0, 0.28);
143
+ }
144
+
145
+ .xr-settings fieldset {
146
+ display: grid;
147
+ gap: 14px;
148
+ min-width: 0;
149
+ margin: 0;
150
+ padding: 0 0 16px;
151
+ border: 0;
152
+ border-bottom: 1px solid rgba(244, 239, 226, 0.13);
153
+ }
154
+
155
+ .xr-settings fieldset:last-of-type {
156
+ padding-bottom: 2px;
157
+ border-bottom: 0;
158
+ }
159
+
160
+ .xr-settings legend {
161
+ padding: 0;
162
+ color: #d9b779;
163
+ font-size: 0.82rem;
164
+ font-weight: 760;
165
+ text-transform: uppercase;
166
+ }
167
+
168
+ .xr-settings label:not(.xr-toggle) {
169
+ display: grid;
170
+ grid-template-columns: 1fr auto;
171
+ gap: 8px 14px;
172
+ align-items: center;
173
+ color: #f4efe2;
174
+ font-weight: 650;
175
+ }
176
+
177
+ .xr-settings output {
178
+ color: #9ddbd1;
179
+ font-variant-numeric: tabular-nums;
180
+ font-weight: 760;
181
+ text-align: right;
182
+ }
183
+
184
+ .xr-settings input[type="range"] {
185
+ grid-column: 1 / -1;
186
+ width: 100%;
187
+ accent-color: #d9b779;
188
+ }
189
+
190
+ .xr-toggle {
191
+ display: grid;
192
+ grid-template-columns: 22px 1fr;
193
+ gap: 10px;
194
+ align-items: center;
195
+ min-height: 32px;
196
+ color: #e8dfcf;
197
+ font-weight: 650;
198
+ }
199
+
200
+ .xr-toggle input {
201
+ width: 18px;
202
+ height: 18px;
203
+ accent-color: #6fd7c3;
27
204
  }
28
205
 
29
206
  .xr-actions {
30
- position: fixed;
31
- top: 16px;
32
- right: 16px;
33
- z-index: 10;
34
207
  display: flex;
35
- gap: 8px;
208
+ flex-wrap: wrap;
209
+ gap: 10px;
36
210
  }
37
211
 
38
212
  .xr-actions button {
39
- min-height: 38px;
40
- padding: 0 14px;
41
- border: 1px solid rgba(145, 220, 255, 0.35);
213
+ min-height: 42px;
214
+ padding: 0 16px;
215
+ border: 1px solid rgba(244, 239, 226, 0.24);
42
216
  border-radius: 8px;
43
- background: rgba(5, 13, 26, 0.76);
44
- color: #eef8ff;
45
- font: 600 0.9rem/1 "Avenir Next", Avenir, "Segoe UI", sans-serif;
217
+ background: rgba(244, 239, 226, 0.08);
218
+ color: #fff9e8;
219
+ font: inherit;
220
+ font-weight: 760;
221
+ }
222
+
223
+ .xr-actions button:hover:not(:disabled) {
224
+ border-color: rgba(244, 239, 226, 0.72);
225
+ background: rgba(244, 239, 226, 0.13);
226
+ }
227
+
228
+ .xr-actions button:disabled {
229
+ cursor: not-allowed;
230
+ opacity: 0.55;
231
+ }
232
+
233
+ .xr-actions .xr-primary-action {
234
+ min-width: 144px;
235
+ border-color: rgba(217, 183, 121, 0.72);
236
+ background: #d9b779;
237
+ color: #101313;
238
+ }
239
+
240
+ .xr-actions .xr-primary-action:hover:not(:disabled) {
241
+ border-color: #ffe0a6;
242
+ background: #e8c987;
46
243
  }
47
244
 
48
- .xr-actions button:hover {
49
- border-color: rgba(145, 220, 255, 0.8);
50
- background: rgba(10, 25, 44, 0.9);
245
+ .xr-session-status {
246
+ min-height: 1.4em;
247
+ margin: 0;
248
+ color: #a7b2ac;
249
+ font-size: 0.88rem;
250
+ }
251
+
252
+ @media (max-width: 820px) {
253
+ .xr-preflight {
254
+ grid-template-columns: 1fr;
255
+ align-content: start;
256
+ }
257
+
258
+ .xr-preflight h1 {
259
+ max-width: 13ch;
260
+ font-size: clamp(2.3rem, 12vw, 4.4rem);
261
+ }
262
+
263
+ .xr-status-list {
264
+ grid-template-columns: 1fr;
265
+ }
266
+
267
+ .xr-settings {
268
+ width: 100%;
269
+ }
270
+ }
271
+
272
+ @media (max-height: 640px) and (min-width: 821px) {
273
+ .xr-preflight {
274
+ align-items: start;
275
+ padding-top: 28px;
276
+ padding-bottom: 28px;
277
+ }
278
+
279
+ .xr-preflight h1 {
280
+ font-size: 3.4rem;
281
+ }
51
282
  }