@buoy-gg/shared-ui 7.0.26 → 7.0.28

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 (221) hide show
  1. package/lib/commonjs/JsModal.js +156 -20
  2. package/lib/commonjs/JsModal.web.js +85 -39
  3. package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +14 -4
  4. package/lib/commonjs/dataViewer/diffThemes.js +20 -18
  5. package/lib/commonjs/dataViewer/tree/TreeDiffViewer.js +8 -8
  6. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  7. package/lib/commonjs/icons/lucide-icons.js +74 -15
  8. package/lib/commonjs/index.js +86 -0
  9. package/lib/commonjs/ui/backgrounds/BackgroundSwitcher.js +248 -74
  10. package/lib/commonjs/ui/backgrounds/README.md +102 -20
  11. package/lib/commonjs/ui/backgrounds/backgroundStore.js +20 -6
  12. package/lib/commonjs/ui/backgrounds/index.js +91 -1
  13. package/lib/commonjs/ui/backgrounds/primitives.js +50 -14
  14. package/lib/commonjs/ui/backgrounds/registry.js +41 -64
  15. package/lib/commonjs/ui/backgrounds/rng.js +21 -0
  16. package/lib/commonjs/ui/backgrounds/skyClock.js +45 -0
  17. package/lib/commonjs/ui/backgrounds/variants/daylight.js +453 -0
  18. package/lib/commonjs/ui/backgrounds/variants/deepField.js +60 -30
  19. package/lib/commonjs/ui/backgrounds/variants/hyperspace.js +218 -0
  20. package/lib/commonjs/ui/backgrounds/variants/liveSky.js +898 -0
  21. package/lib/commonjs/ui/backgrounds/variants/meteors.js +257 -0
  22. package/lib/commonjs/ui/backgrounds/variants/moonlight.js +472 -0
  23. package/lib/commonjs/ui/backgrounds/variants/underwater.js +757 -0
  24. package/lib/commonjs/ui/components/Badge.js +11 -10
  25. package/lib/commonjs/ui/components/CompactRow.js +35 -18
  26. package/lib/commonjs/ui/components/CopyButton.js +6 -4
  27. package/lib/commonjs/ui/components/DevToolsCard.js +16 -9
  28. package/lib/commonjs/ui/components/DynamicFilterView.js +43 -41
  29. package/lib/commonjs/ui/components/EventStepperFooter.js +38 -10
  30. package/lib/commonjs/ui/components/FilterComponents.js +11 -11
  31. package/lib/commonjs/ui/components/ModalHeader.js +28 -15
  32. package/lib/commonjs/ui/components/SectionHeader.js +3 -1
  33. package/lib/commonjs/ui/components/StorageTypeBadge.js +3 -2
  34. package/lib/commonjs/ui/components/TabSelector.js +56 -38
  35. package/lib/commonjs/ui/components/ValueTypeBadge.js +7 -6
  36. package/lib/commonjs/ui/components/WindowControls.js +37 -10
  37. package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +8 -0
  38. package/lib/commonjs/ui/night/nightTheme.js +120 -0
  39. package/lib/commonjs/ui/night/primitives.js +498 -0
  40. package/lib/module/JsModal.js +156 -20
  41. package/lib/module/JsModal.web.js +83 -39
  42. package/lib/module/dataViewer/VirtualizedDataExplorer.js +14 -4
  43. package/lib/module/dataViewer/diffThemes.js +20 -18
  44. package/lib/module/dataViewer/tree/TreeDiffViewer.js +9 -9
  45. package/lib/module/hooks/safe-area-impl.js +1 -1
  46. package/lib/module/icons/lucide-icons.js +74 -15
  47. package/lib/module/index.js +4 -0
  48. package/lib/module/ui/backgrounds/BackgroundSwitcher.js +250 -76
  49. package/lib/module/ui/backgrounds/README.md +102 -20
  50. package/lib/module/ui/backgrounds/backgroundStore.js +19 -5
  51. package/lib/module/ui/backgrounds/index.js +11 -2
  52. package/lib/module/ui/backgrounds/primitives.js +53 -16
  53. package/lib/module/ui/backgrounds/registry.js +41 -64
  54. package/lib/module/ui/backgrounds/rng.js +20 -0
  55. package/lib/module/ui/backgrounds/skyClock.js +39 -0
  56. package/lib/module/ui/backgrounds/variants/daylight.js +449 -0
  57. package/lib/module/ui/backgrounds/variants/deepField.js +62 -31
  58. package/lib/module/ui/backgrounds/variants/hyperspace.js +214 -0
  59. package/lib/module/ui/backgrounds/variants/liveSky.js +893 -0
  60. package/lib/module/ui/backgrounds/variants/meteors.js +253 -0
  61. package/lib/module/ui/backgrounds/variants/moonlight.js +468 -0
  62. package/lib/module/ui/backgrounds/variants/underwater.js +753 -0
  63. package/lib/module/ui/components/Badge.js +11 -10
  64. package/lib/module/ui/components/CompactRow.js +35 -18
  65. package/lib/module/ui/components/CopyButton.js +6 -4
  66. package/lib/module/ui/components/DevToolsCard.js +16 -9
  67. package/lib/module/ui/components/DynamicFilterView.js +43 -41
  68. package/lib/module/ui/components/EventStepperFooter.js +38 -10
  69. package/lib/module/ui/components/FilterComponents.js +11 -11
  70. package/lib/module/ui/components/ModalHeader.js +28 -15
  71. package/lib/module/ui/components/SectionHeader.js +3 -1
  72. package/lib/module/ui/components/StorageTypeBadge.js +3 -2
  73. package/lib/module/ui/components/TabSelector.js +57 -39
  74. package/lib/module/ui/components/ValueTypeBadge.js +7 -6
  75. package/lib/module/ui/components/WindowControls.js +37 -10
  76. package/lib/module/ui/gameUI/constants/gameUIColors.js +8 -0
  77. package/lib/module/ui/night/nightTheme.js +116 -0
  78. package/lib/module/ui/night/primitives.js +485 -0
  79. package/lib/typescript/commonjs/JsModal.d.ts +18 -0
  80. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/JsModal.web.d.ts +13 -0
  82. package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/dataViewer/diffThemes.d.ts +3 -1
  85. package/lib/typescript/commonjs/dataViewer/diffThemes.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/dataViewer/tree/TreeDiffViewer.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  88. package/lib/typescript/commonjs/icons/lucide-icons.d.ts +12 -1
  89. package/lib/typescript/commonjs/icons/lucide-icons.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/index.d.ts +3 -0
  91. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts +26 -6
  93. package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts +3 -0
  95. package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts +7 -1
  97. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts +18 -3
  99. package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts +8 -0
  101. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts +6 -0
  103. package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/ui/backgrounds/skyClock.d.ts +17 -0
  105. package/lib/typescript/commonjs/ui/backgrounds/skyClock.d.ts.map +1 -0
  106. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts +1 -1
  107. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/backgrounds/variants/daylight.d.ts +30 -0
  109. package/lib/typescript/commonjs/ui/backgrounds/variants/daylight.d.ts.map +1 -0
  110. package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts +19 -4
  111. package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/ui/backgrounds/variants/hyperspace.d.ts +39 -0
  113. package/lib/typescript/commonjs/ui/backgrounds/variants/hyperspace.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/ui/backgrounds/variants/liveSky.d.ts +41 -0
  115. package/lib/typescript/commonjs/ui/backgrounds/variants/liveSky.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts +26 -0
  117. package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/ui/backgrounds/variants/moonlight.d.ts +35 -0
  119. package/lib/typescript/commonjs/ui/backgrounds/variants/moonlight.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/ui/backgrounds/variants/underwater.d.ts +51 -0
  121. package/lib/typescript/commonjs/ui/backgrounds/variants/underwater.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/ui/components/Badge.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +3 -1
  124. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/ui/components/CopyButton.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts +3 -1
  127. package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/ui/components/DynamicFilterView.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/ui/components/EventStepperFooter.d.ts +2 -0
  130. package/lib/typescript/commonjs/ui/components/EventStepperFooter.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/ui/components/StorageTypeBadge.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/ui/components/TabSelector.d.ts +9 -0
  134. package/lib/typescript/commonjs/ui/components/TabSelector.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/ui/components/ValueTypeBadge.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts +4 -2
  137. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +8 -0
  139. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/ui/night/nightTheme.d.ts +113 -0
  141. package/lib/typescript/commonjs/ui/night/nightTheme.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/ui/night/primitives.d.ts +111 -0
  143. package/lib/typescript/commonjs/ui/night/primitives.d.ts.map +1 -0
  144. package/lib/typescript/module/JsModal.d.ts +18 -0
  145. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  146. package/lib/typescript/module/JsModal.web.d.ts +13 -0
  147. package/lib/typescript/module/JsModal.web.d.ts.map +1 -1
  148. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  149. package/lib/typescript/module/dataViewer/diffThemes.d.ts +3 -1
  150. package/lib/typescript/module/dataViewer/diffThemes.d.ts.map +1 -1
  151. package/lib/typescript/module/dataViewer/tree/TreeDiffViewer.d.ts.map +1 -1
  152. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  153. package/lib/typescript/module/icons/lucide-icons.d.ts +12 -1
  154. package/lib/typescript/module/icons/lucide-icons.d.ts.map +1 -1
  155. package/lib/typescript/module/index.d.ts +3 -0
  156. package/lib/typescript/module/index.d.ts.map +1 -1
  157. package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts +26 -6
  158. package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -1
  159. package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts +3 -0
  160. package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts.map +1 -1
  161. package/lib/typescript/module/ui/backgrounds/index.d.ts +7 -1
  162. package/lib/typescript/module/ui/backgrounds/index.d.ts.map +1 -1
  163. package/lib/typescript/module/ui/backgrounds/primitives.d.ts +18 -3
  164. package/lib/typescript/module/ui/backgrounds/primitives.d.ts.map +1 -1
  165. package/lib/typescript/module/ui/backgrounds/registry.d.ts +8 -0
  166. package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -1
  167. package/lib/typescript/module/ui/backgrounds/rng.d.ts +6 -0
  168. package/lib/typescript/module/ui/backgrounds/rng.d.ts.map +1 -1
  169. package/lib/typescript/module/ui/backgrounds/skyClock.d.ts +17 -0
  170. package/lib/typescript/module/ui/backgrounds/skyClock.d.ts.map +1 -0
  171. package/lib/typescript/module/ui/backgrounds/types.d.ts +1 -1
  172. package/lib/typescript/module/ui/backgrounds/types.d.ts.map +1 -1
  173. package/lib/typescript/module/ui/backgrounds/variants/daylight.d.ts +30 -0
  174. package/lib/typescript/module/ui/backgrounds/variants/daylight.d.ts.map +1 -0
  175. package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts +19 -4
  176. package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts.map +1 -1
  177. package/lib/typescript/module/ui/backgrounds/variants/hyperspace.d.ts +39 -0
  178. package/lib/typescript/module/ui/backgrounds/variants/hyperspace.d.ts.map +1 -0
  179. package/lib/typescript/module/ui/backgrounds/variants/liveSky.d.ts +41 -0
  180. package/lib/typescript/module/ui/backgrounds/variants/liveSky.d.ts.map +1 -0
  181. package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts +26 -0
  182. package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts.map +1 -0
  183. package/lib/typescript/module/ui/backgrounds/variants/moonlight.d.ts +35 -0
  184. package/lib/typescript/module/ui/backgrounds/variants/moonlight.d.ts.map +1 -0
  185. package/lib/typescript/module/ui/backgrounds/variants/underwater.d.ts +51 -0
  186. package/lib/typescript/module/ui/backgrounds/variants/underwater.d.ts.map +1 -0
  187. package/lib/typescript/module/ui/components/Badge.d.ts.map +1 -1
  188. package/lib/typescript/module/ui/components/CompactRow.d.ts +3 -1
  189. package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
  190. package/lib/typescript/module/ui/components/CopyButton.d.ts.map +1 -1
  191. package/lib/typescript/module/ui/components/DevToolsCard.d.ts +3 -1
  192. package/lib/typescript/module/ui/components/DevToolsCard.d.ts.map +1 -1
  193. package/lib/typescript/module/ui/components/DynamicFilterView.d.ts.map +1 -1
  194. package/lib/typescript/module/ui/components/EventStepperFooter.d.ts +2 -0
  195. package/lib/typescript/module/ui/components/EventStepperFooter.d.ts.map +1 -1
  196. package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
  197. package/lib/typescript/module/ui/components/StorageTypeBadge.d.ts.map +1 -1
  198. package/lib/typescript/module/ui/components/TabSelector.d.ts +9 -0
  199. package/lib/typescript/module/ui/components/TabSelector.d.ts.map +1 -1
  200. package/lib/typescript/module/ui/components/ValueTypeBadge.d.ts.map +1 -1
  201. package/lib/typescript/module/ui/components/WindowControls.d.ts +4 -2
  202. package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
  203. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +8 -0
  204. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  205. package/lib/typescript/module/ui/night/nightTheme.d.ts +113 -0
  206. package/lib/typescript/module/ui/night/nightTheme.d.ts.map +1 -0
  207. package/lib/typescript/module/ui/night/primitives.d.ts +111 -0
  208. package/lib/typescript/module/ui/night/primitives.d.ts.map +1 -0
  209. package/package.json +4 -4
  210. package/lib/commonjs/ui/backgrounds/variants/lattice.js +0 -579
  211. package/lib/commonjs/ui/backgrounds/variants/starfield.js +0 -210
  212. package/lib/module/ui/backgrounds/variants/lattice.js +0 -575
  213. package/lib/module/ui/backgrounds/variants/starfield.js +0 -206
  214. package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts +0 -33
  215. package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts.map +0 -1
  216. package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts +0 -12
  217. package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts.map +0 -1
  218. package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts +0 -33
  219. package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts.map +0 -1
  220. package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts +0 -12
  221. package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts.map +0 -1
@@ -10,87 +10,205 @@ var _lucideIcons = require("../../icons/lucide-icons.js");
10
10
  var _macOSDesignSystemColors = require("../gameUI/constants/macOSDesignSystemColors.js");
11
11
  var _backgroundStore = require("./backgroundStore.js");
12
12
  var _registry = require("./registry.js");
13
+ var _skyClock = require("./skyClock.js");
13
14
  var _jsxRuntime = require("react/jsx-runtime");
14
15
  /**
15
- * The ‹ prev / next › strip for auditioning backgrounds.
16
+ * The ‹ prev / next › strip for choosing a background.
16
17
  *
17
- * This is a REVIEW control, not shipped chrome: it exists so the whole
18
- * catalogue can be walked without a rebuild, with the cost of each preset shown
19
- * next to its name. Mount it at the top of a tool's body while evaluating, and
20
- * pull it once a winner is picked (or keep it behind a setting).
18
+ * Shipping mount: the dial menu's settings modal (pass `embedded` there so the
19
+ * host card supplies the chrome). It can also be dropped at the top of a
20
+ * tool's body while auditioning new presets — the cost of each preset shows
21
+ * next to its name, so it doubles as the review tool it started as.
21
22
  *
22
23
  * It writes to the shared `backgroundStore`, so stepping here changes the
23
24
  * background in every tool at once.
25
+ *
26
+ * While Live Sky is showing, a second row appears: a WALL-clock slider that
27
+ * scrubs the scene through the whole cycle (Live Sky applies its workday
28
+ * remap to whatever wall time the scrub holds, same as it does to the live
29
+ * clock). LIVE snaps back to the real clock. The scrub is quantized to
30
+ * 5-minute steps so a drag re-renders the scene at most once per step, and it
31
+ * is not persisted — it's a preview gesture.
32
+ *
33
+ * ▶ runs the cycle like a movie: one full 24h lap in 15s, starting from the
34
+ * displayed time and finishing exactly where it began (back on LIVE if that's
35
+ * where it was). It drives the SAME scrub value on a JS interval — ~19 5-min
36
+ * steps a second, each one small re-render of the scene — which is fine for a
37
+ * 15s preview gesture and is exactly what a fast drag already does.
24
38
  */
25
39
 
26
- const BackgroundSwitcher = exports.BackgroundSwitcher = /*#__PURE__*/(0, _react.memo)(function BackgroundSwitcher() {
40
+ const THUMB = 14;
41
+ function formatMinutes(m) {
42
+ const h = Math.floor(m / 60) % 24;
43
+ const min = m % 60;
44
+ return `${String(h).padStart(2, "0")}:${String(min).padStart(2, "0")}`;
45
+ }
46
+ const PLAY_DURATION_MS = 15000;
47
+ const PLAY_FRAME_MS = 50;
48
+ const TimeSlider = /*#__PURE__*/(0, _react.memo)(function TimeSlider() {
49
+ const scrub = (0, _skyClock.useSkyScrub)();
50
+ const [trackW, setTrackW] = (0, _react.useState)(0);
51
+ const widthRef = (0, _react.useRef)(0);
52
+ const [playing, setPlaying] = (0, _react.useState)(false);
53
+ const playTimer = (0, _react.useRef)(null);
54
+ const stopPlay = () => {
55
+ if (playTimer.current) clearInterval(playTimer.current);
56
+ playTimer.current = null;
57
+ setPlaying(false);
58
+ };
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ (0, _react.useEffect)(() => stopPlay, []);
61
+ const startPlay = () => {
62
+ stopPlay();
63
+ const now = new Date();
64
+ const startMinutes = (0, _skyClock.getSkyScrub)() ?? now.getHours() * 60 + now.getMinutes();
65
+ const wasLive = (0, _skyClock.getSkyScrub)() === null;
66
+ const begin = Date.now();
67
+ setPlaying(true);
68
+ playTimer.current = setInterval(() => {
69
+ const t = (Date.now() - begin) / PLAY_DURATION_MS;
70
+ if (t >= 1) {
71
+ stopPlay();
72
+ // Finish exactly where the day began.
73
+ (0, _skyClock.setSkyScrub)(wasLive ? null : startMinutes);
74
+ return;
75
+ }
76
+ (0, _skyClock.setSkyScrub)(Math.floor((startMinutes + t * 1440) % 1440 / 5) * 5);
77
+ }, PLAY_FRAME_MS);
78
+ };
79
+ const applyX = x => {
80
+ stopPlay();
81
+ const w = widthRef.current;
82
+ if (w <= 0) return;
83
+ const frac = Math.min(1, Math.max(0, x / w));
84
+ (0, _skyClock.setSkyScrub)(Math.round(frac * 1439 / 5) * 5);
85
+ };
86
+ const responder = (0, _react.useRef)(_reactNative.PanResponder.create({
87
+ onStartShouldSetPanResponder: () => true,
88
+ onMoveShouldSetPanResponder: () => true,
89
+ onPanResponderGrant: e => applyX(e.nativeEvent.locationX),
90
+ onPanResponderMove: e => applyX(e.nativeEvent.locationX)
91
+ })).current;
92
+ const now = new Date();
93
+ // Wall time throughout — the scene applies its own remap to this.
94
+ const minutes = scrub ?? now.getHours() * 60 + now.getMinutes();
95
+ const frac = minutes / 1440;
96
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
97
+ style: styles.sliderRow,
98
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
99
+ onPress: () => {
100
+ stopPlay();
101
+ (0, _skyClock.setSkyScrub)(null);
102
+ },
103
+ style: [styles.live, scrub === null ? styles.liveOn : null],
104
+ hitSlop: HIT_SLOP,
105
+ accessibilityLabel: "Follow the real clock",
106
+ "sentry-label": "ignore background sky live",
107
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
108
+ style: [styles.liveText, scrub === null ? styles.liveTextOn : null],
109
+ children: "LIVE"
110
+ })
111
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
112
+ onPress: playing ? stopPlay : startPlay,
113
+ style: [styles.live, playing ? styles.playOn : null],
114
+ hitSlop: HIT_SLOP,
115
+ accessibilityLabel: playing ? "Stop the day cycle" : "Play the full day in 15 seconds",
116
+ "sentry-label": "ignore background sky play",
117
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
118
+ style: [styles.liveText, playing ? styles.playTextOn : null],
119
+ children: playing ? "■" : "▶"
120
+ })
121
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
122
+ style: styles.track,
123
+ onLayout: e => {
124
+ widthRef.current = e.nativeEvent.layout.width;
125
+ setTrackW(e.nativeEvent.layout.width);
126
+ },
127
+ ...responder.panHandlers,
128
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
129
+ style: styles.trackBar,
130
+ pointerEvents: "none"
131
+ }), trackW > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
132
+ pointerEvents: "none",
133
+ style: [styles.thumb, scrub === null ? null : styles.thumbScrub, {
134
+ left: frac * trackW - THUMB / 2
135
+ }]
136
+ }) : null]
137
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
138
+ style: styles.time,
139
+ children: formatMinutes(minutes)
140
+ })]
141
+ });
142
+ });
143
+ const BackgroundSwitcher = exports.BackgroundSwitcher = /*#__PURE__*/(0, _react.memo)(function BackgroundSwitcher({
144
+ embedded = false
145
+ }) {
27
146
  const {
28
147
  id,
29
148
  index,
30
149
  count,
31
- motion,
32
150
  next,
33
151
  previous,
34
- setMotion
152
+ setId
35
153
  } = (0, _backgroundStore.useToolBackground)();
36
154
  const preset = (0, _registry.getPreset)(id);
155
+ const counterpart = preset.counterpart;
37
156
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
38
- style: styles.root,
39
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
40
- onPress: previous,
41
- style: styles.step,
42
- hitSlop: HIT_SLOP,
43
- accessibilityLabel: "Previous background",
44
- "sentry-label": "ignore background previous",
45
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronLeft, {
46
- size: 16,
47
- color: _macOSDesignSystemColors.macOSColors.text.secondary
48
- })
49
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
50
- style: styles.center,
51
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
52
- style: styles.titleRow,
53
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.Palette, {
54
- size: 11,
55
- color: _macOSDesignSystemColors.macOSColors.semantic.info
157
+ style: embedded ? styles.rootEmbedded : styles.root,
158
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
159
+ style: styles.row,
160
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
161
+ onPress: previous,
162
+ style: styles.step,
163
+ hitSlop: HIT_SLOP,
164
+ accessibilityLabel: "Previous background",
165
+ "sentry-label": "ignore background previous",
166
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronLeft, {
167
+ size: 16,
168
+ color: _macOSDesignSystemColors.macOSColors.text.secondary
169
+ })
170
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
171
+ style: styles.center,
172
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
173
+ style: styles.titleRow,
174
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.Palette, {
175
+ size: 11,
176
+ color: _macOSDesignSystemColors.macOSColors.semantic.info
177
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
178
+ style: styles.title,
179
+ numberOfLines: 1,
180
+ children: preset.label
181
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
182
+ style: styles.counter,
183
+ children: [index + 1, "/", count]
184
+ })]
56
185
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
57
- style: styles.title,
186
+ style: styles.blurb,
58
187
  numberOfLines: 1,
59
- children: preset.label
60
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
61
- style: styles.counter,
62
- children: [index + 1, "/", count]
188
+ children: preset.blurb
63
189
  })]
64
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
65
- style: styles.blurb,
66
- numberOfLines: 1,
67
- children: preset.blurb
68
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
69
- style: styles.cost,
70
- numberOfLines: 1,
71
- children: [preset.nodes === 0 ? "0 animated nodes" : `${preset.nodes} native node${preset.nodes === 1 ? "" : "s"}`, " · ", "~", preset.views, " static views"]
190
+ }), counterpart ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
191
+ onPress: () => setId(counterpart.id),
192
+ style: styles.motion,
193
+ hitSlop: HIT_SLOP,
194
+ accessibilityLabel: `Switch to ${counterpart.id} background`,
195
+ "sentry-label": "ignore background counterpart",
196
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
197
+ style: styles.motionText,
198
+ children: counterpart.label
199
+ })
200
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
201
+ onPress: next,
202
+ style: styles.step,
203
+ hitSlop: HIT_SLOP,
204
+ accessibilityLabel: "Next background",
205
+ "sentry-label": "ignore background next",
206
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronRight, {
207
+ size: 16,
208
+ color: _macOSDesignSystemColors.macOSColors.text.secondary
209
+ })
72
210
  })]
73
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
74
- onPress: () => setMotion(!motion),
75
- style: [styles.motion, motion ? styles.motionOn : null],
76
- hitSlop: HIT_SLOP,
77
- accessibilityLabel: motion ? "Turn motion off" : "Turn motion on",
78
- "sentry-label": "ignore background motion",
79
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
80
- style: [styles.motionText, motion ? styles.motionTextOn : null],
81
- children: motion ? "MOTION" : "STATIC"
82
- })
83
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
84
- onPress: next,
85
- style: styles.step,
86
- hitSlop: HIT_SLOP,
87
- accessibilityLabel: "Next background",
88
- "sentry-label": "ignore background next",
89
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronRight, {
90
- size: 16,
91
- color: _macOSDesignSystemColors.macOSColors.text.secondary
92
- })
93
- })]
211
+ }), id === "livesky" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(TimeSlider, {}) : null]
94
212
  });
95
213
  });
96
214
  const HIT_SLOP = {
@@ -101,9 +219,6 @@ const HIT_SLOP = {
101
219
  };
102
220
  const styles = _reactNative.StyleSheet.create({
103
221
  root: {
104
- flexDirection: "row",
105
- alignItems: "center",
106
- gap: 8,
107
222
  paddingHorizontal: 8,
108
223
  paddingVertical: 6,
109
224
  marginHorizontal: 8,
@@ -116,6 +231,14 @@ const styles = _reactNative.StyleSheet.create({
116
231
  // previewing, so you can see the field through it.
117
232
  backgroundColor: "rgba(26,26,28,0.72)"
118
233
  },
234
+ rootEmbedded: {
235
+ // The host card supplies border/fill/margins.
236
+ },
237
+ row: {
238
+ flexDirection: "row",
239
+ alignItems: "center",
240
+ gap: 8
241
+ },
119
242
  step: {
120
243
  width: 28,
121
244
  height: 28,
@@ -150,12 +273,6 @@ const styles = _reactNative.StyleSheet.create({
150
273
  fontSize: 10,
151
274
  marginTop: 1
152
275
  },
153
- cost: {
154
- color: _macOSDesignSystemColors.macOSColors.text.icon,
155
- fontSize: 9,
156
- marginTop: 1,
157
- fontVariant: ["tabular-nums"]
158
- },
159
276
  motion: {
160
277
  paddingHorizontal: 7,
161
278
  paddingVertical: 4,
@@ -164,17 +281,74 @@ const styles = _reactNative.StyleSheet.create({
164
281
  borderColor: _macOSDesignSystemColors.macOSColors.border.default,
165
282
  backgroundColor: "rgba(38,38,42,0.8)"
166
283
  },
167
- motionOn: {
168
- borderColor: _macOSDesignSystemColors.macOSColors.semantic.info + "55",
169
- backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.infoBackground
170
- },
171
284
  motionText: {
172
285
  fontSize: 9,
173
286
  fontWeight: "700",
174
287
  letterSpacing: 0.6,
175
288
  color: _macOSDesignSystemColors.macOSColors.text.muted
176
289
  },
177
- motionTextOn: {
290
+ sliderRow: {
291
+ flexDirection: "row",
292
+ alignItems: "center",
293
+ gap: 8,
294
+ marginTop: 7
295
+ },
296
+ live: {
297
+ paddingHorizontal: 7,
298
+ paddingVertical: 3,
299
+ borderRadius: 6,
300
+ borderWidth: 1,
301
+ borderColor: _macOSDesignSystemColors.macOSColors.border.default,
302
+ backgroundColor: "rgba(38,38,42,0.8)"
303
+ },
304
+ liveOn: {
305
+ borderColor: _macOSDesignSystemColors.macOSColors.semantic.success + "55",
306
+ backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.successBackground
307
+ },
308
+ liveText: {
309
+ fontSize: 9,
310
+ fontWeight: "700",
311
+ letterSpacing: 0.6,
312
+ color: _macOSDesignSystemColors.macOSColors.text.muted
313
+ },
314
+ liveTextOn: {
315
+ color: _macOSDesignSystemColors.macOSColors.semantic.success
316
+ },
317
+ playOn: {
318
+ borderColor: _macOSDesignSystemColors.macOSColors.semantic.info + "55",
319
+ backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.infoBackground
320
+ },
321
+ playTextOn: {
178
322
  color: _macOSDesignSystemColors.macOSColors.semantic.info
323
+ },
324
+ track: {
325
+ flex: 1,
326
+ height: 24,
327
+ justifyContent: "center"
328
+ },
329
+ trackBar: {
330
+ height: 3,
331
+ borderRadius: 1.5,
332
+ backgroundColor: "rgba(120,130,150,0.35)"
333
+ },
334
+ thumb: {
335
+ position: "absolute",
336
+ top: (24 - THUMB) / 2,
337
+ width: THUMB,
338
+ height: THUMB,
339
+ borderRadius: THUMB / 2,
340
+ backgroundColor: _macOSDesignSystemColors.macOSColors.text.secondary,
341
+ borderWidth: 1,
342
+ borderColor: "rgba(255,255,255,0.5)"
343
+ },
344
+ thumbScrub: {
345
+ backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.info
346
+ },
347
+ time: {
348
+ color: _macOSDesignSystemColors.macOSColors.text.secondary,
349
+ fontSize: 10,
350
+ fontVariant: ["tabular-nums"],
351
+ width: 32,
352
+ textAlign: "right"
179
353
  }
180
354
  });
@@ -11,27 +11,65 @@ top.
11
11
  </View>
12
12
  ```
13
13
 
14
- The preset is a single shared, persisted choice (`backgroundStore`), so
15
- switching it in one tool switches it everywhere. `<BackgroundSwitcher />` is the
16
- ‹ prev / next › review strip used to audition the catalogue.
14
+ The preset is a single shared, persisted choice (`backgroundStore`, default:
15
+ **Hyperspace**), so switching it in one tool switches it everywhere. Shipping
16
+ surfaces get it automatically: `JsModal` and `BottomSheet` mount
17
+ `<ToolBackground />` inside their content box by default (opt out per tool
18
+ with `background={false}`; the network tool does, because it mounts its own so
19
+ the desktop inline panel matches).
17
20
 
18
- ## The two families
21
+ `<BackgroundSwitcher />` is the ‹ prev / next › review strip used to audition
22
+ the catalogue (plus Live Sky's time slider and ▶ movie). It is currently
23
+ mounted NOWHERE — the catalogue picked its winner — but stays in the package:
24
+ drop it back at the top of a tool body to audition changes.
25
+
26
+ ## The catalogue
27
+
28
+ Round 3 cut the set to the keepers plus three underwater variations (round 4
29
+ folded the Meteors preset into Deep Field):
19
30
 
20
- - **Starfield** (`variants/starfield.tsx`) — the cheap scenic preset: 99 stars
21
- quantised into five depth bands.
22
31
  - **Deep Field** (`variants/deepField.tsx`) — the Everlights
23
32
  `ParallaxDriftBackground`, ported 1:1: same seed and generator call order,
24
33
  same 160 stars, same far-plane bias, sizes, palette and drift. Speed is
25
34
  quantised into 18 bands (a distinct speed costs a distinct animated node);
26
- everything else stays per-star. Roughly twice Starfield's cost — the file
27
- documents exactly what could and couldn't come across from Skia.
28
- - **The lattice family** (`variants/lattice.tsx`) — Hex Mesh, Iso Grid,
29
- Crosshatch, Perf Board, Halftone, Waffle, Instrument, Radar. Quiet technical
30
- geometry: near-black base, one soft off-centre glow, a rasterized hairline
31
- pattern at low alpha, a slow breath, a vignette. They read as instrument
32
- panels and drafting paper rather than scenery, which is what keeps them
33
- legible under dense tool UI. `LatticeShell` owns everything but the geometry,
34
- so the family stays a family.
35
+ everything else stays per-star. The priciest preset — the file documents
36
+ exactly what could and couldn't come across from Skia. The shipping preset
37
+ also composes the **meteor layer** (`variants/meteors.tsx`, once its own
38
+ "Meteors" preset): shooting stars that streak in, hit a WALL of the surface
39
+ and burst — flash, expanding shockwave ring (half-clipped by the edge, which
40
+ sells the impact), sparks ricocheting back into the room; each meteor's
41
+ whole life cycle is keyframed into interpolate stops on one plain loop.
42
+ `DeepFieldTunable` stays meteor-free for the bench.
43
+ - **Hyperspace** (`variants/hyperspace.tsx`) — the Everlights Skia
44
+ `HyperspaceBackground` (lightspeed starfield) re-derived for this contract:
45
+ the per-frame `screen = plane/z` projection becomes scale keyframes on
46
+ phase-offset shells (a layer scaled by 1/z moves every star on exactly the
47
+ original's hyperbolic curve), with the wrap snap hidden inside the
48
+ original's own 6% fade edges. One animated node total; raster OFF on the
49
+ shells because a cached bitmap scaled 8× goes to mush.
50
+ - **Live Sky** (`variants/liveSky.tsx`) — the cove on the real local clock:
51
+ sun up 06:30–19:00 on a sine arc, moon on the night shift, dawn/golden/
52
+ sunset/dusk as lerped palette keyframes (night = Moonlight's palette, noon =
53
+ Daylight's). ZERO extra animated nodes — the sun moves ~0.4px/minute, so the
54
+ scene is laid out statically per minute and a once-a-minute AppState-gated
55
+ tick re-renders it; this also sidesteps the native driver's
56
+ transform/opacity-only limit for the colour transitions. Discs draw UNDER
57
+ the opaque sea so the horizon clips a rising/setting body for free. While
58
+ it's showing, the switcher grows a time slider (writes `skyClock.ts`,
59
+ 5-minute quantized, not persisted) to scrub the whole cycle by hand; LIVE
60
+ snaps back to the clock. See `LIVE_SKY_PLAN.md`.
61
+ - **The underwater family** (`variants/underwater.tsx`) — Abyss, Bubbles,
62
+ Jellyfish. One register — lit from the surface, near-black at the bottom,
63
+ motion in tens of seconds — with different life in the water: marine snow,
64
+ rising bubbles (a ring plus an off-centre highlight is what makes a bubble),
65
+ and pulsing jellies that traverse bottom-to-top entirely off-screen at the
66
+ loop seam.
67
+
68
+ Catalogue trims (2026-08-20): Off, Night Drift, Moonlight, Daylight, Kelp and
69
+ Kelp Forest were cut from the registry. `variants/moonlight.tsx` and
70
+ `variants/daylight.tsx` stay in the tree — Live Sky's palettes are documented
71
+ against them and the bg-bench lanes (`MoonlightTunable`/`DaylightTunable`)
72
+ still drive them — they just aren't offered in the switcher.
35
73
 
36
74
  ## The constraint
37
75
 
@@ -58,6 +96,15 @@ translucent layers. For the same reason nothing here uses `transformOrigin`
58
96
 
59
97
  Loops stop when the app backgrounds, and unmount with the tool.
60
98
 
99
+ 4. **Geometry never scales with the modal.** Resizing the surface must crop
100
+ the scene, not stretch it — rays, plants, blooms and particles size
101
+ themselves from `Dimensions.get("window")` and only their POSITIONS
102
+ distribute across the measured width/height (the preset root clips with
103
+ `overflow: "hidden"`). Smooth gradients (base washes, vignettes) may track
104
+ the live size — their scaling is imperceptible. The one sanctioned
105
+ exception is geometry that is ABOUT the surface itself, like the meteors'
106
+ wall-impact points.
107
+
61
108
  **Known trap:** `Animated.sequence([timing, Animated.loop(...)])` silently fails
62
109
  to drive the value on the native driver — no warning, the field renders and
63
110
  never moves. It's the obvious way to phase-offset siblings; don't. Stagger via
@@ -66,8 +113,9 @@ different periods, or bake the offset into the `interpolate`.
66
113
  ### Measured
67
114
 
68
115
  Network tool open on an iPhone 16 simulator, via perf-monitor and
69
- `measure_renders`. Starfield is the heaviest preset (6 animated nodes, ~222
70
- static views); the lattice presets are 1–2 nodes and 40–176 views.
116
+ `measure_renders`. (Starfield and the lattice presets were measured before the
117
+ round-3 cull; the rows stay as calibration points — Deep Field is still the
118
+ ceiling, and the newer presets all run 3–7 nodes.)
71
119
 
72
120
  **Steady state** — 12–13s idle recordings:
73
121
 
@@ -87,6 +135,40 @@ The commits in every column are the network tool's own once-a-minute
87
135
  reported one component render in every configuration — the animation contributes
88
136
  zero React work while it runs, which is rule 1 holding regardless of node count.
89
137
 
138
+ **Moonlight v3** (buoy-optimize loop, iPhone 17 Pro sim, bg-bench, 3-run
139
+ medians, reproduced across two batches):
140
+
141
+ | | UI FPS | UI jank | JS FPS |
142
+ |---|---:|---:|---:|
143
+ | control (no background) | 60.0 | 0 | 58.3 |
144
+ | Moonlight, shipping (~330 views, 5 nodes) | 59.9 | 0 | 57.5–58.1 |
145
+ | glade ×12 (~1.2k blocks) | 59.5 | 0 | 57.0 |
146
+ | glade ×30 (~3.1k blocks) | 56.4–56.8 | 2 | 53.8 |
147
+
148
+ Shipping Moonlight is indistinguishable from no background; the UI cliff sits
149
+ between 12× and 30× the shipping glade. Structural variants (fused glade,
150
+ raster off, chrome off) all tied with shipping at 1× AND failed to separate at
151
+ ×30 (±1 FPS, and raster-off ballooned memory 287→351MB), so the shipping
152
+ structure stays. Bench lane: `bg-bench?v=ml-*` drives `MoonlightTunable`.
153
+
154
+ **Confirmed on a real iPhone SE (375×667, 60Hz)** — both in-batch controls
155
+ held 60.0/0 (zero drift): ship 59.6 UI / 0 UI jank; ×12 58.8 / 1; ×30 breaks
156
+ (54.4 UI, CPU 57). Same ranking as the sim; the sim → device carry-over held.
157
+
158
+ **Animated vs static (real iPhone, same batch):** identical — 59.6 UI / 0
159
+ jank / 0 JS jank both; static saved ~2.7 CPU points, less than the batch's
160
+ own control-to-control CPU spread (4.4), though the direction repeated on the
161
+ sim (~2.4). The motion toggle is a preference, not a perf lever, at shipping
162
+ scale — which is the native-driver contract holding. (A JS-jank scare in an
163
+ earlier batch attributed ~3 events to the ml cases; a later batch put 4 on a
164
+ CONTROL, so those are between-case reload noise.)
165
+
166
+ **Benchmark-protocol note:** the batch's FINAL case is unreliable — in two
167
+ consecutive batches the trailing control recorded byte-identical bad numbers
168
+ (53.7 UI FPS, 4 JS jank, only 2/3 runs) while an identical control at slot 1
169
+ was clean, so the batch teardown bleeds into the last recording. End every
170
+ batch with a sacrificial case, the same way it starts with one.
171
+
90
172
  **Mount** — the cost that is real for all of them:
91
173
 
92
174
  ```
@@ -110,6 +192,6 @@ Add the component to `variants/`, then one entry in `registry.tsx` with an
110
192
  honest `nodes` (animated nodes) and `views` (static views at ~380×620). The
111
193
  switcher shows both, which is the point — it's a review tool, not decoration.
112
194
 
113
- Alpha calibration: Hex Mesh is the reference. Anything fainter than its lattice
114
- disappears under the vignette — hairline strokes in particular need ~0.2 alpha
115
- or a second pixel of height to register at all.
195
+ Alpha calibration: every preset runs a 0.4–0.55 vignette, and anything fainter
196
+ than ~0.2 alpha disappears under it — hairline strokes in particular need ~0.2
197
+ alpha or a second pixel of height to register at all.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.backgroundStore = void 0;
6
+ exports.backgroundStore = exports.BACKGROUND_STORAGE_KEY = void 0;
7
7
  exports.useToolBackground = useToolBackground;
8
8
  var _react = require("react");
9
9
  var _devToolsStorageKeys = require("../../storage/devToolsStorageKeys.js");
@@ -19,7 +19,20 @@ var _registry = require("./registry.js");
19
19
  * choice the user already made.
20
20
  */
21
21
 
22
- const KEY = `${_devToolsStorageKeys.devToolsStorageKeys.base}_tool_background`;
22
+ // v3: bumped when the default became "off" (backgrounds are opt-in now), so
23
+ // devices that stepped the switcher during the (unreleased) audition rounds
24
+ // re-default to none instead of keeping a pre-release pick forever. Earlier
25
+ // keys are simply abandoned, same as v1 was.
26
+ const KEY = `${_devToolsStorageKeys.devToolsStorageKeys.base}_tool_background_v3`;
27
+
28
+ /** The persisted-selection key, exported so a host can ask "has the user ever
29
+ * picked a background?" (the desktop's first-run hint modal reads it). */
30
+ const BACKGROUND_STORAGE_KEY = exports.BACKGROUND_STORAGE_KEY = KEY;
31
+
32
+ // Motion is deliberately NOT persisted: the switcher no longer offers a
33
+ // motion toggle, so every session starts animated. `setMotion` survives as a
34
+ // session-only seam (bench harnesses, a tool forcing stillness).
35
+
23
36
  function isValidId(value) {
24
37
  return typeof value === "string" && _registry.BACKGROUND_PRESETS.some(p => p.id === value);
25
38
  }
@@ -52,8 +65,10 @@ class BackgroundStore {
52
65
  const raw = await _persistentStorage.persistentStorage.getItem(KEY);
53
66
  if (!raw || this.dirty) return;
54
67
  const parsed = JSON.parse(raw);
55
- if (isValidId(parsed.id)) this.id = parsed.id;
56
- if (typeof parsed.motion === "boolean") this.motion = parsed.motion;
68
+ // The standalone Meteors preset was folded into Deep Field — re-home
69
+ // anyone still persisted on it rather than dropping them to the default.
70
+ const id = parsed.id === "meteors" ? "deepfield" : parsed.id;
71
+ if (isValidId(id)) this.id = id;
57
72
  this.notify();
58
73
  } catch {
59
74
  // A missing or corrupt entry just means "use the default".
@@ -64,8 +79,7 @@ class BackgroundStore {
64
79
  persist() {
65
80
  this.dirty = true;
66
81
  const payload = {
67
- id: this.id,
68
- motion: this.motion
82
+ id: this.id
69
83
  };
70
84
  void _persistentStorage.persistentStorage.setItem(KEY, JSON.stringify(payload)).catch(() => {});
71
85
  }