@buoy-gg/shared-ui 7.0.36 → 7.0.38

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 (284) hide show
  1. package/README.md +33 -26
  2. package/floating.d.ts +1 -0
  3. package/floating.js +2 -0
  4. package/lib/commonjs/JsModal.js +16 -1
  5. package/lib/commonjs/JsModal.web.js +16 -1
  6. package/lib/commonjs/dataViewer/CyberpunkInput.js +6 -1
  7. package/lib/commonjs/dataViewer/LiveExplorer.js +28 -16
  8. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  9. package/lib/commonjs/icons/ReactQueryIcon.js +9 -161
  10. package/lib/commonjs/icons/SentryBugIcon.js +9 -171
  11. package/lib/commonjs/index.js +57 -6
  12. package/lib/commonjs/license/AccountSetup.js +56 -0
  13. package/lib/commonjs/license/AccountSetup.web.js +81 -0
  14. package/lib/commonjs/license/FeatureGate.js +10 -43
  15. package/lib/commonjs/license/FreeAccountPrompt.js +35 -12
  16. package/lib/commonjs/license/index.js +0 -6
  17. package/lib/commonjs/license/licenseModule.js +56 -36
  18. package/lib/commonjs/license/tierLimits.js +12 -63
  19. package/lib/commonjs/storage/devToolsStorageKeys.js +8 -1
  20. package/lib/commonjs/stores/BaseEventStore.js +21 -5
  21. package/lib/commonjs/stores/createToolStore.js +21 -3
  22. package/lib/commonjs/stores/floatingToolStore.js +71 -0
  23. package/lib/commonjs/stores/floatingToolStore.test.js +112 -0
  24. package/lib/commonjs/sync/toolActionRegistry.js +85 -2
  25. package/lib/commonjs/ui/backgrounds/README.md +52 -0
  26. package/lib/commonjs/ui/backgrounds/ToolBackground.js +4 -2
  27. package/lib/commonjs/ui/backgrounds/index.js +8 -1
  28. package/lib/commonjs/ui/backgrounds/registry.js +8 -0
  29. package/lib/commonjs/ui/backgrounds/variants/wormhole/motion.js +48 -0
  30. package/lib/commonjs/ui/backgrounds/variants/wormhole/textures.js +3 -0
  31. package/lib/commonjs/ui/backgrounds/variants/wormhole.js +133 -0
  32. package/lib/commonjs/ui/components/ClipboardHintBanner.js +5 -6
  33. package/lib/commonjs/ui/components/CompactRow.js +94 -74
  34. package/lib/commonjs/ui/components/DraggableHeader.js +16 -1
  35. package/lib/commonjs/ui/components/HeaderSearchButton.js +17 -4
  36. package/lib/commonjs/ui/floating/FloatingTool.js +1010 -0
  37. package/lib/commonjs/ui/floating/FloatingTool.web.js +591 -0
  38. package/lib/commonjs/ui/floating/FloatingToolHost.js +32 -0
  39. package/lib/commonjs/ui/floating/FloatingToolHost.web.js +28 -0
  40. package/lib/commonjs/ui/floating/index.js +105 -0
  41. package/lib/commonjs/ui/floating/index.web.js +105 -0
  42. package/lib/commonjs/ui/floating/onboardingConstants.js +70 -0
  43. package/lib/commonjs/ui/floating/onboardingConstants.web.js +34 -0
  44. package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +3 -1
  45. package/lib/commonjs/ui/index.js +118 -2
  46. package/lib/commonjs/ui/night/nightTheme.js +10 -0
  47. package/lib/commonjs/ui/night/primitives.js +22 -5
  48. package/lib/commonjs/ui/otto/OttoSceneView.js +69 -0
  49. package/lib/commonjs/ui/otto/index.js +22 -0
  50. package/lib/commonjs/ui/otto/ottoEngine.js +10 -7
  51. package/lib/commonjs/ui/otto/ottoScenes.js +949 -0
  52. package/lib/commonjs/utils/minimalSuperJSON.js +13 -3
  53. package/lib/module/JsModal.js +16 -1
  54. package/lib/module/JsModal.web.js +16 -1
  55. package/lib/module/dataViewer/CyberpunkInput.js +6 -1
  56. package/lib/module/dataViewer/LiveExplorer.js +28 -16
  57. package/lib/module/hooks/safe-area-impl.js +1 -1
  58. package/lib/module/icons/ReactQueryIcon.js +9 -161
  59. package/lib/module/icons/SentryBugIcon.js +9 -171
  60. package/lib/module/index.js +4 -1
  61. package/lib/module/license/AccountSetup.js +52 -0
  62. package/lib/module/license/AccountSetup.web.js +77 -0
  63. package/lib/module/license/FeatureGate.js +10 -43
  64. package/lib/module/license/FreeAccountPrompt.js +35 -12
  65. package/lib/module/license/index.js +1 -1
  66. package/lib/module/license/licenseModule.js +50 -35
  67. package/lib/module/license/tierLimits.js +13 -63
  68. package/lib/module/storage/devToolsStorageKeys.js +8 -1
  69. package/lib/module/stores/BaseEventStore.js +21 -5
  70. package/lib/module/stores/createToolStore.js +21 -3
  71. package/lib/module/stores/floatingToolStore.js +61 -0
  72. package/lib/module/stores/floatingToolStore.test.js +111 -0
  73. package/lib/module/sync/toolActionRegistry.js +84 -2
  74. package/lib/module/ui/backgrounds/README.md +52 -0
  75. package/lib/module/ui/backgrounds/ToolBackground.js +4 -2
  76. package/lib/module/ui/backgrounds/index.js +2 -1
  77. package/lib/module/ui/backgrounds/registry.js +8 -0
  78. package/lib/module/ui/backgrounds/variants/wormhole/motion.js +44 -0
  79. package/lib/module/ui/backgrounds/variants/wormhole/textures.js +3 -0
  80. package/lib/module/ui/backgrounds/variants/wormhole.js +129 -0
  81. package/lib/module/ui/components/ClipboardHintBanner.js +5 -6
  82. package/lib/module/ui/components/CompactRow.js +94 -74
  83. package/lib/module/ui/components/DraggableHeader.js +16 -1
  84. package/lib/module/ui/components/HeaderSearchButton.js +17 -4
  85. package/lib/module/ui/floating/FloatingTool.js +1003 -0
  86. package/lib/module/ui/floating/FloatingTool.web.js +590 -0
  87. package/lib/module/ui/floating/FloatingToolHost.js +29 -0
  88. package/lib/module/ui/floating/FloatingToolHost.web.js +25 -0
  89. package/lib/module/ui/floating/index.js +6 -0
  90. package/lib/module/ui/floating/index.web.js +6 -0
  91. package/lib/module/ui/floating/onboardingConstants.js +64 -0
  92. package/lib/module/ui/floating/onboardingConstants.web.js +27 -0
  93. package/lib/module/ui/gameUI/constants/gameUIColors.js +3 -1
  94. package/lib/module/ui/index.js +5 -1
  95. package/lib/module/ui/night/nightTheme.js +10 -0
  96. package/lib/module/ui/night/primitives.js +22 -5
  97. package/lib/module/ui/otto/OttoSceneView.js +64 -0
  98. package/lib/module/ui/otto/index.js +3 -1
  99. package/lib/module/ui/otto/ottoEngine.js +10 -7
  100. package/lib/module/ui/otto/ottoScenes.js +942 -0
  101. package/lib/module/utils/minimalSuperJSON.js +13 -3
  102. package/lib/typescript/commonjs/JsModal.d.ts +43 -2
  103. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  104. package/lib/typescript/commonjs/JsModal.web.d.ts +5 -2
  105. package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/dataViewer/CyberpunkInput.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/dataViewer/LiveExplorer.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  109. package/lib/typescript/commonjs/icons/ReactQueryIcon.d.ts +1 -2
  110. package/lib/typescript/commonjs/icons/ReactQueryIcon.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/icons/SentryBugIcon.d.ts +1 -2
  112. package/lib/typescript/commonjs/icons/SentryBugIcon.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/index.d.ts +4 -1
  114. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/license/AccountSetup.d.ts +4 -0
  116. package/lib/typescript/commonjs/license/AccountSetup.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/license/AccountSetup.web.d.ts +2 -0
  118. package/lib/typescript/commonjs/license/AccountSetup.web.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/license/FeatureGate.d.ts +3 -19
  120. package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts +8 -11
  122. package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/license/index.d.ts +1 -1
  124. package/lib/typescript/commonjs/license/index.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/license/licenseModule.d.ts +18 -31
  126. package/lib/typescript/commonjs/license/licenseModule.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/license/tierLimits.d.ts +9 -52
  128. package/lib/typescript/commonjs/license/tierLimits.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +6 -0
  130. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/stores/BaseEventStore.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/stores/createToolStore.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/stores/floatingToolStore.d.ts +27 -0
  134. package/lib/typescript/commonjs/stores/floatingToolStore.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/stores/floatingToolStore.test.d.ts +2 -0
  136. package/lib/typescript/commonjs/stores/floatingToolStore.test.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts +4 -32
  138. package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts +7 -1
  140. package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts +2 -1
  142. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -1
  144. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts +11 -1
  145. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts.map +1 -1
  146. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/motion.d.ts +24 -0
  147. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/motion.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/textures.d.ts +13 -0
  149. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/textures.d.ts.map +1 -0
  150. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole.d.ts +3 -0
  151. package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole.d.ts.map +1 -0
  152. package/lib/typescript/commonjs/ui/components/ClipboardHintBanner.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +8 -1
  154. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts +4 -1
  156. package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts.map +1 -1
  157. package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts +7 -1
  158. package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts.map +1 -1
  159. package/lib/typescript/commonjs/ui/floating/FloatingTool.d.ts +79 -0
  160. package/lib/typescript/commonjs/ui/floating/FloatingTool.d.ts.map +1 -0
  161. package/lib/typescript/commonjs/ui/floating/FloatingTool.web.d.ts +64 -0
  162. package/lib/typescript/commonjs/ui/floating/FloatingTool.web.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/ui/floating/FloatingToolHost.d.ts +3 -0
  164. package/lib/typescript/commonjs/ui/floating/FloatingToolHost.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/ui/floating/FloatingToolHost.web.d.ts +3 -0
  166. package/lib/typescript/commonjs/ui/floating/FloatingToolHost.web.d.ts.map +1 -0
  167. package/lib/typescript/commonjs/ui/floating/index.d.ts +7 -0
  168. package/lib/typescript/commonjs/ui/floating/index.d.ts.map +1 -0
  169. package/lib/typescript/commonjs/ui/floating/index.web.d.ts +7 -0
  170. package/lib/typescript/commonjs/ui/floating/index.web.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/ui/floating/onboardingConstants.d.ts +30 -0
  172. package/lib/typescript/commonjs/ui/floating/onboardingConstants.d.ts.map +1 -0
  173. package/lib/typescript/commonjs/ui/floating/onboardingConstants.web.d.ts +13 -0
  174. package/lib/typescript/commonjs/ui/floating/onboardingConstants.web.d.ts.map +1 -0
  175. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +2 -0
  176. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/ui/index.d.ts +6 -0
  178. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/ui/night/nightTheme.d.ts +10 -0
  180. package/lib/typescript/commonjs/ui/night/nightTheme.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/ui/night/primitives.d.ts +10 -3
  182. package/lib/typescript/commonjs/ui/night/primitives.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/ui/otto/OttoSceneView.d.ts +30 -0
  184. package/lib/typescript/commonjs/ui/otto/OttoSceneView.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/ui/otto/index.d.ts +2 -0
  186. package/lib/typescript/commonjs/ui/otto/index.d.ts.map +1 -1
  187. package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts +1 -1
  188. package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts.map +1 -1
  189. package/lib/typescript/commonjs/ui/otto/ottoScenes.d.ts +75 -0
  190. package/lib/typescript/commonjs/ui/otto/ottoScenes.d.ts.map +1 -0
  191. package/lib/typescript/commonjs/utils/minimalSuperJSON.d.ts.map +1 -1
  192. package/lib/typescript/module/JsModal.d.ts +43 -2
  193. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  194. package/lib/typescript/module/JsModal.web.d.ts +5 -2
  195. package/lib/typescript/module/JsModal.web.d.ts.map +1 -1
  196. package/lib/typescript/module/dataViewer/CyberpunkInput.d.ts.map +1 -1
  197. package/lib/typescript/module/dataViewer/LiveExplorer.d.ts.map +1 -1
  198. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  199. package/lib/typescript/module/icons/ReactQueryIcon.d.ts +1 -2
  200. package/lib/typescript/module/icons/ReactQueryIcon.d.ts.map +1 -1
  201. package/lib/typescript/module/icons/SentryBugIcon.d.ts +1 -2
  202. package/lib/typescript/module/icons/SentryBugIcon.d.ts.map +1 -1
  203. package/lib/typescript/module/index.d.ts +4 -1
  204. package/lib/typescript/module/index.d.ts.map +1 -1
  205. package/lib/typescript/module/license/AccountSetup.d.ts +4 -0
  206. package/lib/typescript/module/license/AccountSetup.d.ts.map +1 -0
  207. package/lib/typescript/module/license/AccountSetup.web.d.ts +2 -0
  208. package/lib/typescript/module/license/AccountSetup.web.d.ts.map +1 -0
  209. package/lib/typescript/module/license/FeatureGate.d.ts +3 -19
  210. package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
  211. package/lib/typescript/module/license/FreeAccountPrompt.d.ts +8 -11
  212. package/lib/typescript/module/license/FreeAccountPrompt.d.ts.map +1 -1
  213. package/lib/typescript/module/license/index.d.ts +1 -1
  214. package/lib/typescript/module/license/index.d.ts.map +1 -1
  215. package/lib/typescript/module/license/licenseModule.d.ts +18 -31
  216. package/lib/typescript/module/license/licenseModule.d.ts.map +1 -1
  217. package/lib/typescript/module/license/tierLimits.d.ts +9 -52
  218. package/lib/typescript/module/license/tierLimits.d.ts.map +1 -1
  219. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +6 -0
  220. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
  221. package/lib/typescript/module/stores/BaseEventStore.d.ts.map +1 -1
  222. package/lib/typescript/module/stores/createToolStore.d.ts.map +1 -1
  223. package/lib/typescript/module/stores/floatingToolStore.d.ts +27 -0
  224. package/lib/typescript/module/stores/floatingToolStore.d.ts.map +1 -0
  225. package/lib/typescript/module/stores/floatingToolStore.test.d.ts +2 -0
  226. package/lib/typescript/module/stores/floatingToolStore.test.d.ts.map +1 -0
  227. package/lib/typescript/module/sync/toolActionRegistry.d.ts +4 -32
  228. package/lib/typescript/module/sync/toolActionRegistry.d.ts.map +1 -1
  229. package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts +7 -1
  230. package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts.map +1 -1
  231. package/lib/typescript/module/ui/backgrounds/index.d.ts +2 -1
  232. package/lib/typescript/module/ui/backgrounds/index.d.ts.map +1 -1
  233. package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -1
  234. package/lib/typescript/module/ui/backgrounds/types.d.ts +11 -1
  235. package/lib/typescript/module/ui/backgrounds/types.d.ts.map +1 -1
  236. package/lib/typescript/module/ui/backgrounds/variants/wormhole/motion.d.ts +24 -0
  237. package/lib/typescript/module/ui/backgrounds/variants/wormhole/motion.d.ts.map +1 -0
  238. package/lib/typescript/module/ui/backgrounds/variants/wormhole/textures.d.ts +13 -0
  239. package/lib/typescript/module/ui/backgrounds/variants/wormhole/textures.d.ts.map +1 -0
  240. package/lib/typescript/module/ui/backgrounds/variants/wormhole.d.ts +3 -0
  241. package/lib/typescript/module/ui/backgrounds/variants/wormhole.d.ts.map +1 -0
  242. package/lib/typescript/module/ui/components/ClipboardHintBanner.d.ts.map +1 -1
  243. package/lib/typescript/module/ui/components/CompactRow.d.ts +8 -1
  244. package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
  245. package/lib/typescript/module/ui/components/DraggableHeader.d.ts +4 -1
  246. package/lib/typescript/module/ui/components/DraggableHeader.d.ts.map +1 -1
  247. package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts +7 -1
  248. package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts.map +1 -1
  249. package/lib/typescript/module/ui/floating/FloatingTool.d.ts +79 -0
  250. package/lib/typescript/module/ui/floating/FloatingTool.d.ts.map +1 -0
  251. package/lib/typescript/module/ui/floating/FloatingTool.web.d.ts +64 -0
  252. package/lib/typescript/module/ui/floating/FloatingTool.web.d.ts.map +1 -0
  253. package/lib/typescript/module/ui/floating/FloatingToolHost.d.ts +3 -0
  254. package/lib/typescript/module/ui/floating/FloatingToolHost.d.ts.map +1 -0
  255. package/lib/typescript/module/ui/floating/FloatingToolHost.web.d.ts +3 -0
  256. package/lib/typescript/module/ui/floating/FloatingToolHost.web.d.ts.map +1 -0
  257. package/lib/typescript/module/ui/floating/index.d.ts +7 -0
  258. package/lib/typescript/module/ui/floating/index.d.ts.map +1 -0
  259. package/lib/typescript/module/ui/floating/index.web.d.ts +7 -0
  260. package/lib/typescript/module/ui/floating/index.web.d.ts.map +1 -0
  261. package/lib/typescript/module/ui/floating/onboardingConstants.d.ts +30 -0
  262. package/lib/typescript/module/ui/floating/onboardingConstants.d.ts.map +1 -0
  263. package/lib/typescript/module/ui/floating/onboardingConstants.web.d.ts +13 -0
  264. package/lib/typescript/module/ui/floating/onboardingConstants.web.d.ts.map +1 -0
  265. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +2 -0
  266. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  267. package/lib/typescript/module/ui/index.d.ts +6 -0
  268. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  269. package/lib/typescript/module/ui/night/nightTheme.d.ts +10 -0
  270. package/lib/typescript/module/ui/night/nightTheme.d.ts.map +1 -1
  271. package/lib/typescript/module/ui/night/primitives.d.ts +10 -3
  272. package/lib/typescript/module/ui/night/primitives.d.ts.map +1 -1
  273. package/lib/typescript/module/ui/otto/OttoSceneView.d.ts +30 -0
  274. package/lib/typescript/module/ui/otto/OttoSceneView.d.ts.map +1 -0
  275. package/lib/typescript/module/ui/otto/index.d.ts +2 -0
  276. package/lib/typescript/module/ui/otto/index.d.ts.map +1 -1
  277. package/lib/typescript/module/ui/otto/ottoEngine.d.ts +1 -1
  278. package/lib/typescript/module/ui/otto/ottoEngine.d.ts.map +1 -1
  279. package/lib/typescript/module/ui/otto/ottoScenes.d.ts +75 -0
  280. package/lib/typescript/module/ui/otto/ottoScenes.d.ts.map +1 -0
  281. package/lib/typescript/module/utils/minimalSuperJSON.d.ts.map +1 -1
  282. package/package.json +25 -5
  283. package/scripts/generate-wormhole-textures.cjs +261 -0
  284. package/scripts/test-wormhole-motion.cjs +59 -0
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { getAccountAccess, hasAccountAccess, subscribeAccountAccess } from "../license/licenseModule.js";
3
4
  /**
4
5
  * BaseEventStore - Abstract base class for event aggregation stores
5
6
  *
@@ -65,6 +66,20 @@ export class BaseEventStore extends Subscribable {
65
66
  super();
66
67
  this.maxEvents = options.maxEvents ?? 500;
67
68
  this.storeName = options.storeName;
69
+ // Defer until subclass fields are initialized before invoking lifecycle hooks.
70
+ Promise.resolve().then(() => {
71
+ const reconcile = () => {
72
+ if (getAccountAccess().status === "locked") {
73
+ this.stopCapturing();
74
+ this.events = [];
75
+ this.notifyArrayListeners();
76
+ } else if (this.getTotalSubscriberCount() > 0 && !this.captureSuppressed && !this.isCapturing()) {
77
+ this.startCapturing();
78
+ }
79
+ };
80
+ subscribeAccountAccess(reconcile);
81
+ reconcile();
82
+ });
68
83
  }
69
84
 
70
85
  // ===========================================================================
@@ -94,7 +109,7 @@ export class BaseEventStore extends Subscribable {
94
109
  * Starts capturing if no one was subscribed.
95
110
  */
96
111
  onSubscribe() {
97
- if (this.getTotalSubscriberCount() === 1 && !this.captureSuppressed) {
112
+ if (this.getTotalSubscriberCount() === 1 && !this.captureSuppressed && getAccountAccess().status !== "locked") {
98
113
  this.startCapturing();
99
114
  }
100
115
  notifySubscriberCountChange(this.storeName);
@@ -134,7 +149,7 @@ export class BaseEventStore extends Subscribable {
134
149
  this.arrayListeners.add(listener);
135
150
 
136
151
  // Start capturing if this is the first subscriber
137
- if (wasEmpty && !this.captureSuppressed) {
152
+ if (wasEmpty && !this.captureSuppressed && getAccountAccess().status !== "locked") {
138
153
  this.startCapturing();
139
154
  }
140
155
 
@@ -181,11 +196,12 @@ export class BaseEventStore extends Subscribable {
181
196
  * @param event - The event to add
182
197
  */
183
198
  addEvent(event) {
199
+ if (getAccountAccess().status === "locked") return;
184
200
  // Add to beginning (newest first) and limit to maxEvents
185
201
  this.events = [event, ...this.events].slice(0, this.maxEvents);
186
202
 
187
203
  // Notify individual event callbacks (via Subscribable)
188
- this.notify(event);
204
+ if (hasAccountAccess()) this.notify(event);
189
205
 
190
206
  // Notify array listeners
191
207
  this.notifyArrayListeners();
@@ -209,14 +225,14 @@ export class BaseEventStore extends Subscribable {
209
225
  * Get all events (newest first)
210
226
  */
211
227
  getEvents() {
212
- return this.events;
228
+ return hasAccountAccess() ? this.events : [];
213
229
  }
214
230
 
215
231
  /**
216
232
  * Get event count
217
233
  */
218
234
  getEventCount() {
219
- return this.events.length;
235
+ return hasAccountAccess() ? this.events.length : 0;
220
236
  }
221
237
 
222
238
  /**
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { getAccountAccess, hasAccountAccess, subscribeAccountAccess } from "../license/licenseModule.js";
3
4
  /**
4
5
  * createToolStore — the one bounded, subscribable buffer every tool used to
5
6
  * hand-roll.
@@ -49,6 +50,16 @@ export function createToolStore(options) {
49
50
  let nextId = options.firstId ?? 1;
50
51
  let lastFlushAt = 0;
51
52
  let emitScheduled = false;
53
+ const empty = [];
54
+ let observing = false;
55
+ function observe() {
56
+ if (observing) return;
57
+ observing = true;
58
+ subscribeAccountAccess(() => {
59
+ if (getAccountAccess().status === "locked") items = [];
60
+ emit();
61
+ });
62
+ }
52
63
  const listeners = new Set();
53
64
  const clearListeners = new Set();
54
65
  function fire() {
@@ -79,19 +90,22 @@ export function createToolStore(options) {
79
90
  const store = {
80
91
  id,
81
92
  subscribe(listener) {
93
+ observe();
82
94
  listeners.add(listener);
83
95
  return () => {
84
96
  listeners.delete(listener);
85
97
  };
86
98
  },
87
99
  getSnapshot() {
88
- return items;
100
+ observe();
101
+ return hasAccountAccess() ? items : empty;
89
102
  },
90
103
  size() {
91
- return items.length;
104
+ return hasAccountAccess() ? items.length : 0;
92
105
  },
93
106
  add(item) {
94
- if (paused) return;
107
+ observe();
108
+ if (paused || getAccountAccess().status === "locked") return;
95
109
  if (order === "append") {
96
110
  items = items.length >= max ? [...items.slice(items.length - max + 1), item] : [...items, item];
97
111
  } else {
@@ -100,10 +114,14 @@ export function createToolStore(options) {
100
114
  emit();
101
115
  },
102
116
  set(next) {
117
+ observe();
118
+ if (!hasAccountAccess()) return;
103
119
  items = trim(next);
104
120
  emit();
105
121
  },
106
122
  update(fn) {
123
+ observe();
124
+ if (getAccountAccess().status === "locked") return;
107
125
  items = trim(fn(items));
108
126
  emit();
109
127
  },
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ let entries = [];
4
+ let revision = 0;
5
+ const listeners = new Set();
6
+ const notify = () => listeners.forEach(listener => listener());
7
+ export const getFloatingTools = () => entries;
8
+ export const subscribeFloatingTools = listener => {
9
+ listeners.add(listener);
10
+ return () => {
11
+ listeners.delete(listener);
12
+ };
13
+ };
14
+
15
+ /** Open or restore one tool. Mount FloatingToolHost once at the application root. */
16
+ export function openFloatingTool(tool) {
17
+ if (!tool.id.trim()) throw new Error("Floating tools need a non-empty id");
18
+ const entry = {
19
+ ...tool,
20
+ revealToken: ++revision
21
+ };
22
+ entries = entries.some(value => value.id === tool.id) ? entries.map(value => value.id === tool.id ? entry : value) : [...entries, entry];
23
+ notify();
24
+ }
25
+
26
+ /** Removes the UI only. Tool actions such as Stop belong to the tool's own controller. */
27
+ export function dismissFloatingTool(id) {
28
+ if (!entries.some(entry => entry.id === id)) return;
29
+ entries = entries.filter(entry => entry.id !== id);
30
+ notify();
31
+ }
32
+
33
+ /** Register at app startup to restore an open tool using its existing position storage. */
34
+ export function registerPersistentFloatingTool(tool, storage) {
35
+ if (!tool.id.trim()) throw new Error("Floating tools need a non-empty id");
36
+ const key = `@buoy/floating-tool/${tool.id}/open`;
37
+ let disposed = false;
38
+ let changed = false;
39
+ let wasOpen = entries.some(entry => entry.id === tool.id);
40
+ let writes = Promise.resolve();
41
+ const unsubscribe = subscribeFloatingTools(() => {
42
+ const isOpen = entries.some(entry => entry.id === tool.id);
43
+ if (isOpen === wasOpen) return;
44
+ wasOpen = isOpen;
45
+ changed = true;
46
+ writes = writes.then(() => storage.setItem(key, isOpen ? "1" : "0")).then(() => {}, () => {});
47
+ });
48
+ void storage.getItem(key).then(saved => {
49
+ if (disposed || changed || wasOpen || saved !== "1") return;
50
+ // Zero restores the saved hidden state; explicit opens still reveal the tool.
51
+ entries = [...entries, {
52
+ ...tool,
53
+ revealToken: 0
54
+ }];
55
+ notify();
56
+ }).catch(() => {});
57
+ return () => {
58
+ disposed = true;
59
+ unsubscribe();
60
+ };
61
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { dismissFloatingTool, getFloatingTools, openFloatingTool, subscribeFloatingTools } from "./floatingToolStore.js";
5
+ afterEach(() => getFloatingTools().forEach(tool => dismissFloatingTool(tool.id)));
6
+ describe("floating tool registry", () => {
7
+ it("restores an existing id and keeps other tools intact", () => {
8
+ openFloatingTool({
9
+ id: "network",
10
+ render: () => null
11
+ });
12
+ openFloatingTool({
13
+ id: "recorder",
14
+ render: () => null
15
+ });
16
+ const first = getFloatingTools()[0].revealToken;
17
+ const other = getFloatingTools()[1];
18
+ openFloatingTool({
19
+ id: "network",
20
+ render: () => "updated"
21
+ });
22
+ expect(getFloatingTools()).toHaveLength(2);
23
+ expect(getFloatingTools()[0].revealToken).toBeGreaterThan(first);
24
+ expect(getFloatingTools()[1]).toBe(other);
25
+ });
26
+ it("publishes stable snapshots and isolates dismissal", () => {
27
+ const listener = vi.fn();
28
+ const unsubscribe = subscribeFloatingTools(listener);
29
+ openFloatingTool({
30
+ id: "one",
31
+ render: () => null
32
+ });
33
+ const snapshot = getFloatingTools();
34
+ expect(getFloatingTools()).toBe(snapshot);
35
+ dismissFloatingTool("missing");
36
+ expect(listener).toHaveBeenCalledTimes(1);
37
+ openFloatingTool({
38
+ id: "two",
39
+ render: () => null
40
+ });
41
+ dismissFloatingTool("one");
42
+ expect(getFloatingTools().map(tool => tool.id)).toEqual(["two"]);
43
+ unsubscribe();
44
+ dismissFloatingTool("two");
45
+ expect(listener).toHaveBeenCalledTimes(3);
46
+ });
47
+ it("rejects missing tool identities", () => {
48
+ expect(() => openFloatingTool({
49
+ id: " ",
50
+ render: () => null
51
+ })).toThrow("non-empty id");
52
+ expect(getFloatingTools()).toHaveLength(0);
53
+ });
54
+ });
55
+ describe("persistent floating tools", () => {
56
+ it("restores after a fresh registry loads and remembers dismissal", async () => {
57
+ const values = new Map();
58
+ const storage = {
59
+ getItem: vi.fn(async key => values.get(key) ?? null),
60
+ setItem: vi.fn(async (key, value) => {
61
+ values.set(key, value);
62
+ })
63
+ };
64
+ const tool = {
65
+ id: "persisted",
66
+ render: () => null
67
+ };
68
+ vi.resetModules();
69
+ const first = await import("./floatingToolStore");
70
+ const disposeFirst = first.registerPersistentFloatingTool(tool, storage);
71
+ first.openFloatingTool(tool);
72
+ await vi.waitFor(() => expect(values.get("@buoy/floating-tool/persisted/open")).toBe("1"));
73
+ disposeFirst();
74
+ vi.resetModules();
75
+ const second = await import("./floatingToolStore");
76
+ const disposeSecond = second.registerPersistentFloatingTool(tool, storage);
77
+ await vi.waitFor(() => expect(second.getFloatingTools()).toHaveLength(1));
78
+ expect(second.getFloatingTools()[0].revealToken).toBe(0);
79
+ second.dismissFloatingTool(tool.id);
80
+ await vi.waitFor(() => expect(values.get("@buoy/floating-tool/persisted/open")).toBe("0"));
81
+ disposeSecond();
82
+ vi.resetModules();
83
+ const third = await import("./floatingToolStore");
84
+ const disposeThird = third.registerPersistentFloatingTool(tool, storage);
85
+ await Promise.resolve();
86
+ expect(third.getFloatingTools()).toHaveLength(0);
87
+ disposeThird();
88
+ });
89
+ it("does not reopen a tool closed while its saved state was loading", async () => {
90
+ vi.resetModules();
91
+ const registry = await import("./floatingToolStore");
92
+ let resolve;
93
+ const storage = {
94
+ getItem: () => new Promise(done => {
95
+ resolve = done;
96
+ }),
97
+ setItem: vi.fn(async () => {})
98
+ };
99
+ const tool = {
100
+ id: "race",
101
+ render: () => null
102
+ };
103
+ const dispose = registry.registerPersistentFloatingTool(tool, storage);
104
+ registry.openFloatingTool(tool);
105
+ registry.dismissFloatingTool(tool.id);
106
+ resolve("1");
107
+ await vi.waitFor(() => expect(storage.setItem).toHaveBeenLastCalledWith("@buoy/floating-tool/race/open", "0"));
108
+ expect(registry.getFloatingTools()).toHaveLength(0);
109
+ dispose();
110
+ });
111
+ });
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { assertAccountAccess, hasAccountAccess, subscribeAccountAccess } from "../license/licenseModule.js";
3
4
  /**
4
5
  * toolActionRegistry — the on-device twin of the broker's action dispatch.
5
6
  *
@@ -56,13 +57,45 @@ function sources() {
56
57
  export function registerToolAdapters(getTools) {
57
58
  const set = sources();
58
59
  set.add(getTools);
60
+ let disposed = false;
61
+ const dispose = () => {
62
+ if (disposed) return;
63
+ disposed = true;
64
+ const raw = getTools();
65
+ // Private inverse operations only. No credential bypass reaches public dispatch.
66
+ const cleanup = async (toolId, action, params) => {
67
+ if (!(toolId === "network" && action === "deleteOverrideRule" || toolId === "impersonate" && action === "stopImpersonation")) {
68
+ throw new Error("Unsupported account-lock cleanup operation");
69
+ }
70
+ if (toolId === "impersonate") return raw[toolId]?.dispose?.(cleanup);
71
+ const fn = raw[toolId]?.actions?.[action];
72
+ if (!fn) throw new Error(`Cleanup unavailable for ${toolId}`);
73
+ return fn(params);
74
+ };
75
+ for (const adapter of Object.values(raw)) {
76
+ try {
77
+ Promise.resolve(adapter.dispose?.(cleanup)).catch(error => console.warn("[Buoy] Tool cleanup failed", error));
78
+ } catch (error) {
79
+ console.warn("[Buoy] Tool cleanup failed", error);
80
+ }
81
+ }
82
+ };
83
+ const off = subscribeAccountAccess(() => {
84
+ if (!hasAccountAccess()) dispose();else disposed = false;
85
+ });
59
86
  return () => {
87
+ off();
60
88
  set.delete(getTools);
89
+ // A second provider still owns these adapters during Strict Mode/remount.
90
+ Promise.resolve().then(() => {
91
+ if (set.size === 0) dispose();
92
+ });
61
93
  };
62
94
  }
63
95
 
64
96
  /** The merged adapter map across every live source. */
65
97
  export function getToolAdapters() {
98
+ if (!hasAccountAccess()) return {};
66
99
  const merged = {};
67
100
  for (const get of sources()) {
68
101
  try {
@@ -71,7 +104,7 @@ export function getToolAdapters() {
71
104
  // A source that throws must not take the whole registry down.
72
105
  }
73
106
  }
74
- return merged;
107
+ return Object.fromEntries(Object.entries(merged).map(([id, adapter]) => [id, guardToolAdapter(adapter)]));
75
108
  }
76
109
 
77
110
  /** Tool ids currently dispatchable. */
@@ -111,6 +144,7 @@ export function listSnapshotToolIds() {
111
144
  * an adapter is free to make this a promise later without breaking them.
112
145
  */
113
146
  export async function readToolSnapshot(toolId) {
147
+ assertAccountAccess();
114
148
  const tools = getToolAdapters();
115
149
  const adapter = tools[toolId];
116
150
  if (!adapter) throw new Error(`Unknown tool "${toolId}"`);
@@ -120,7 +154,7 @@ export async function readToolSnapshot(toolId) {
120
154
  return await adapter.getSnapshot();
121
155
  }
122
156
  export function isToolRegistryReady() {
123
- return sources().size > 0;
157
+ return hasAccountAccess() && sources().size > 0;
124
158
  }
125
159
 
126
160
  /**
@@ -129,6 +163,7 @@ export function isToolRegistryReady() {
129
163
  * surface as plain messages rather than objects nobody can read on a phone.
130
164
  */
131
165
  export async function dispatchToolAction(toolId, action, params) {
166
+ assertAccountAccess();
132
167
  const tools = getToolAdapters();
133
168
  const tool = tools[toolId];
134
169
  const fn = tool?.actions?.[action];
@@ -151,4 +186,51 @@ export async function waitForToolRegistry(timeoutMs = 2000) {
151
186
  if (isToolRegistryReady()) return true;
152
187
  }
153
188
  return false;
189
+ }
190
+
191
+ /** Guard retained references too, and discard results completed after revocation. */
192
+ export function guardToolAdapter(adapter) {
193
+ const guard = fn => (...args) => {
194
+ assertAccountAccess();
195
+ let revoked = false;
196
+ const off = subscribeAccountAccess(() => {
197
+ if (!hasAccountAccess()) revoked = true;
198
+ });
199
+ const check = () => {
200
+ if (revoked) throw new Error("Buoy account access ended while the operation was pending.");
201
+ assertAccountAccess();
202
+ };
203
+ try {
204
+ const result = fn.apply(adapter, args);
205
+ if (result && typeof result.then === "function") {
206
+ return Promise.resolve(result).then(value => {
207
+ check();
208
+ return value;
209
+ }).finally(off);
210
+ }
211
+ check();
212
+ off();
213
+ return result;
214
+ } catch (error) {
215
+ off();
216
+ throw error;
217
+ }
218
+ };
219
+ return {
220
+ ...adapter,
221
+ ...(adapter.getSnapshot ? {
222
+ getSnapshot: guard(adapter.getSnapshot)
223
+ } : {}),
224
+ ...(adapter.actions ? {
225
+ actions: Object.fromEntries(Object.entries(adapter.actions).map(([name, action]) => [name, guard(action)]))
226
+ } : {}),
227
+ ...(adapter.subscribe ? {
228
+ subscribe: listener => {
229
+ assertAccountAccess();
230
+ return adapter.subscribe(() => {
231
+ if (hasAccountAccess()) listener();
232
+ });
233
+ }
234
+ } : {})
235
+ };
154
236
  }
@@ -195,3 +195,55 @@ switcher shows both, which is the point — it's a review tool, not decoration.
195
195
  Alpha calibration: every preset runs a 0.4–0.55 vignette, and anything fainter
196
196
  than ~0.2 alpha disappears under it — hairline strokes in particular need ~0.2
197
197
  alpha or a second pixel of height to register at all.
198
+
199
+
200
+ ## Wormhole mobile preview
201
+
202
+ Wormhole is the eighth registry entry (including Off). Select it under the
203
+ Buoy dial's Settings → Background; the existing store persists that choice.
204
+ The example app also exposes `/bg-bench?v=wormhole` and
205
+ `/bg-bench?v=wormhole-still` for comparison under identical tool content.
206
+
207
+ The moving scene is a zoom, and a zoom only loops invisibly when the picture
208
+ is self-similar under the handoff. Two copies of one opaque cloud sheet scale
209
+ toward the viewer one period apart (×√6 in scale, +21° in rotation) and
210
+ crossfade with balanced source-over opacities; each forward pass takes six
211
+ seconds (two periods) at constant proportional expansion, and four passes fit
212
+ inside one 24-second camera-bank cycle. The sheet is built by
213
+ `scripts/generate-wormhole-textures.cjs` by tiling one annulus of the real
214
+ shader frame in log-polar space with exactly that (×√6, +21°) symmetry, so the
215
+ two copies are the same picture wherever they overlap and neither the
216
+ crossfade nor a layer's reset can be seen. The destination is cut from the
217
+ same frame with a feathered alpha and drawn as a static layer above the
218
+ sheets — the far end of a tunnel does not zoom. One native Animated loop
219
+ drives the scene. It approximates the flight: the tunnel geometry is baked into
220
+ the textures, not ray-marched on the device. There is no Skia, Reanimated,
221
+ WebView, new native dependency, or per-frame JS callback.
222
+
223
+ TRAP (the "badly looping GIF" we shipped first): a feathered ring texture is
224
+ NOT self-similar, so a copy born at 0.42× painted a shrunken tunnel — its own
225
+ copper rim included — over the destination every 3 s, then pushed it outward.
226
+ Fade tuning cannot hide that; only a sheet that matches itself under the
227
+ handoff transform can. `WORMHOLE_PERIOD` / `WORMHOLE_TWIST` in `motion.ts` are
228
+ that transform: change either and regenerate the sheet (the generator checks
229
+ the pixels against the symmetry and refuses a sheet that does not match).
230
+
231
+ While moving, the 1440×1440 sheet (JPEG) and the 384×384 eye (PNG) contribute
232
+ about 337 KiB of source data and roughly 8.5 MiB of decoded pixels; the still
233
+ (640×640 JPEG, 57 KiB, 1.6 MiB decoded) is mounted only when the scene does
234
+ not move. Instances can reuse the same image source objects. Reduced motion
235
+ shows the still texture; the existing animation primitive pauses on AppState
236
+ changes and stops on unmount. Geometry is window-sized and cropped by each tool
237
+ surface. Actual device performance remains to be measured in the benchmark
238
+ screen.
239
+
240
+ The desktop menu keeps one Wormhole entry and its existing live WebGL renderer.
241
+ The mobile texture preview does not replace the desktop shader.
242
+
243
+ Texture source: `node scripts/generate-wormhole-textures.cjs` (from
244
+ `packages/shared`) ports the website's dark shader to the CPU, renders it at
245
+ u_t=4 with the destination centred and no copy scrim or vignette, tiles the
246
+ sheet, cuts the eye, and rewrites `textures.ts`. It keeps the embedded still
247
+ unless `--still scene.jpg` supplies a fresh centered 640px WebGL capture; JPEG
248
+ encoding shells out to macOS `sips`. Both packages and Metro can consume the
249
+ module without an asset-loader plugin.
@@ -33,7 +33,8 @@ const bucket = n => Math.max(0, Math.ceil(n / 8) * 8);
33
33
  export const ToolBackground = /*#__PURE__*/memo(function ToolBackground({
34
34
  id,
35
35
  motion,
36
- opacity = 1
36
+ opacity = 1,
37
+ focus
37
38
  }) {
38
39
  const shared = useToolBackground();
39
40
  const [size, setSize] = useState({
@@ -64,7 +65,8 @@ export const ToolBackground = /*#__PURE__*/memo(function ToolBackground({
64
65
  children: Variant && ready ? /*#__PURE__*/_jsx(Variant, {
65
66
  width: size.width,
66
67
  height: size.height,
67
- animated: animated
68
+ animated: animated,
69
+ focus: focus
68
70
  }) : null
69
71
  });
70
72
  });
@@ -15,4 +15,5 @@ export { DeepFieldTunable } from "./variants/deepField.js";
15
15
  export { MoonlightTunable } from "./variants/moonlight.js";
16
16
  export { DaylightTunable } from "./variants/daylight.js";
17
17
  export { LiveSkyTunable } from "./variants/liveSky.js";
18
- export { getSkyScrub, setSkyScrub, useSkyScrub } from "./skyClock.js";
18
+ export { getSkyScrub, setSkyScrub, useSkyScrub } from "./skyClock.js";
19
+ export { Wormhole } from "./variants/wormhole.js";
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { DeepField } from "./variants/deepField.js";
14
+ import { Wormhole } from "./variants/wormhole.js";
14
15
  import { Hyperspace } from "./variants/hyperspace.js";
15
16
  import { LiveSky } from "./variants/liveSky.js";
16
17
  import { Abyss, Bubbles, Jellyfish } from "./variants/underwater.js";
@@ -65,6 +66,13 @@ export const BACKGROUND_PRESETS = [{
65
66
  nodes: 15,
66
67
  views: 250,
67
68
  Component: Jellyfish
69
+ }, {
70
+ id: "wormhole",
71
+ label: "Wormhole",
72
+ blurb: "Cobalt clouds and copper light — a native flight through the tunnel",
73
+ nodes: 1,
74
+ views: 7,
75
+ Component: Wormhole
68
76
  }];
69
77
 
70
78
  // Ships OFF: a backdrop is an opt-in, so first load is plain black and the
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ /** One slow camera cycle containing four faster forward passes.
4
+ * Keyframes run on Animated's native driver; this math runs only at import.
5
+ *
6
+ * The sheet texture is self-similar: scaled by WORMHOLE_PERIOD and rotated by
7
+ * WORMHOLE_TWIST it is the same picture (scripts/generate-wormhole-textures.cjs
8
+ * builds it that way and checks it). The two shells sit exactly one period
9
+ * apart, so at every instant they show identical pixels and the crossfade
10
+ * between them — and each shell's reset — cannot be seen. Change either
11
+ * constant and regenerate the textures; the sheet must be rebuilt to match.
12
+ */
13
+ export const WORMHOLE_CYCLE_MS = 24000;
14
+ export const WORMHOLE_PASSES = 4;
15
+ /** Scale ratio between the two shells; one pass covers two periods (×6). */
16
+ export const WORMHOLE_PERIOD = Math.sqrt(6);
17
+ /** Degrees the picture turns per period — its spiral symmetry. */
18
+ export const WORMHOLE_TWIST = 21;
19
+ const STEPS_PER_PASS = 64;
20
+ export const WORMHOLE_INPUT = Array.from({
21
+ length: STEPS_PER_PASS * WORMHOLE_PASSES + 1
22
+ }, (_, i) => i / (STEPS_PER_PASS * WORMHOLE_PASSES));
23
+ const smooth = (from, to, value) => {
24
+ const t = Math.max(0, Math.min(1, (value - from) / (to - from)));
25
+ return t * t * (3 - 2 * t);
26
+ };
27
+ const PHASES = [0, 0.5].map(offset => WORMHOLE_INPUT.map(t => (t * WORMHOLE_PASSES + offset) % 1));
28
+ // Broad overlapping windows, with a completely invisible interval around wraps.
29
+ const WEIGHTS = PHASES.map(phase => phase.map(t => Math.sin(Math.PI * t) ** 2 * smooth(0.02, 0.12, t) * (1 - smooth(0.88, 0.98, t))));
30
+ export const WORMHOLE_SHELLS = PHASES.map((phase, layer) => ({
31
+ // Constant proportional expansion keeps the forward speed steady. The
32
+ // sheet covers the whole plane from scale 1, so a shell never shows an edge.
33
+ scale: phase.map(t => Math.pow(WORMHOLE_PERIOD, 2 * t)),
34
+ // Normalize layer transmission across the overlapping fades.
35
+ opacity: phase.map((_, i) => {
36
+ const total = WEIGHTS.reduce((sum, weights) => sum + weights[i], 0);
37
+ return 1 - Math.pow(0.08, WEIGHTS[layer][i] / total);
38
+ }),
39
+ // One twist per period, centred so the shells sit ±θ/2 around the camera.
40
+ rotate: phase.map(t => `${(2 * t - 1) * WORMHOLE_TWIST}deg`)
41
+ }));
42
+ // Zero velocity at the turnarounds and the loop boundary, rather than the
43
+ // sharp reversal from a three-keyframe triangle-wave camera bank.
44
+ export const WORMHOLE_CAMERA_ROTATION = WORMHOLE_INPUT.map(t => `${-5 * Math.cos(t * Math.PI * 2)}deg`);