@buoy-gg/shared-ui 7.0.27 → 7.0.29
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.
- package/lib/commonjs/JsModal.js +12 -2
- package/lib/commonjs/dataViewer/DataTreeActionDock.js +1 -0
- package/lib/commonjs/dataViewer/DataViewer.js +6 -3
- package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +13 -4
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/license/FeatureGate.js +9 -0
- package/lib/commonjs/ui/backgrounds/registry.js +3 -2
- package/lib/commonjs/ui/backgrounds/variants/meteors.js +222 -78
- package/lib/commonjs/ui/components/Badge.js +3 -1
- package/lib/commonjs/ui/components/CompactRow.js +14 -1
- package/lib/commonjs/ui/components/CopyButton.js +9 -5
- package/lib/commonjs/ui/components/DevToolsCard.js +4 -1
- package/lib/commonjs/ui/components/DraggableHeader.js +5 -1
- package/lib/commonjs/ui/components/DynamicFilterView.js +23 -4
- package/lib/commonjs/ui/components/EventStepperFooter.js +8 -0
- package/lib/commonjs/ui/components/FilterComponents.js +18 -2
- package/lib/commonjs/ui/components/ModalHeader.js +27 -5
- package/lib/commonjs/ui/components/PowerToggleButton.js +2 -0
- package/lib/commonjs/ui/components/SearchBar.js +6 -1
- package/lib/commonjs/ui/components/SectionHeader.js +3 -1
- package/lib/commonjs/ui/components/TabSelector.js +15 -4
- package/lib/commonjs/ui/components/WindowControls.js +68 -96
- package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +12 -8
- package/lib/module/JsModal.js +12 -2
- package/lib/module/dataViewer/DataTreeActionDock.js +1 -0
- package/lib/module/dataViewer/DataViewer.js +6 -3
- package/lib/module/dataViewer/VirtualizedDataExplorer.js +13 -4
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/license/FeatureGate.js +9 -0
- package/lib/module/ui/backgrounds/registry.js +3 -2
- package/lib/module/ui/backgrounds/variants/meteors.js +224 -80
- package/lib/module/ui/components/Badge.js +3 -1
- package/lib/module/ui/components/CompactRow.js +14 -1
- package/lib/module/ui/components/CopyButton.js +9 -5
- package/lib/module/ui/components/DevToolsCard.js +4 -1
- package/lib/module/ui/components/DraggableHeader.js +5 -1
- package/lib/module/ui/components/DynamicFilterView.js +23 -4
- package/lib/module/ui/components/EventStepperFooter.js +8 -0
- package/lib/module/ui/components/FilterComponents.js +18 -2
- package/lib/module/ui/components/ModalHeader.js +27 -5
- package/lib/module/ui/components/PowerToggleButton.js +2 -0
- package/lib/module/ui/components/SearchBar.js +6 -1
- package/lib/module/ui/components/SectionHeader.js +3 -1
- package/lib/module/ui/components/TabSelector.js +15 -4
- package/lib/module/ui/components/WindowControls.js +69 -97
- package/lib/module/ui/gameUI/constants/gameUIColors.js +12 -8
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/DataTreeActionDock.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/DataViewer.d.ts +2 -0
- package/lib/typescript/commonjs/dataViewer/DataViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts +9 -0
- package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts +22 -6
- package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Badge.d.ts +3 -1
- package/lib/typescript/commonjs/ui/components/Badge.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/CopyButton.d.ts +5 -0
- package/lib/typescript/commonjs/ui/components/CopyButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts +3 -1
- package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/DynamicFilterView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/EventStepperFooter.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FilterComponents.d.ts +10 -2
- package/lib/typescript/commonjs/ui/components/FilterComponents.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts +16 -2
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts +4 -1
- package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SearchBar.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/SearchBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SectionHeader.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/SectionHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/TabSelector.d.ts +20 -1
- package/lib/typescript/commonjs/ui/components/TabSelector.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +5 -0
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/DataTreeActionDock.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/DataViewer.d.ts +2 -0
- package/lib/typescript/module/dataViewer/DataViewer.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts +9 -0
- package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts +22 -6
- package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Badge.d.ts +3 -1
- package/lib/typescript/module/ui/components/Badge.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/CompactRow.d.ts +7 -1
- package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/CopyButton.d.ts +5 -0
- package/lib/typescript/module/ui/components/CopyButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/DevToolsCard.d.ts +3 -1
- package/lib/typescript/module/ui/components/DevToolsCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/DraggableHeader.d.ts +2 -0
- package/lib/typescript/module/ui/components/DraggableHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/DynamicFilterView.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/EventStepperFooter.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FilterComponents.d.ts +10 -2
- package/lib/typescript/module/ui/components/FilterComponents.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ModalHeader.d.ts +16 -2
- package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/PowerToggleButton.d.ts +4 -1
- package/lib/typescript/module/ui/components/PowerToggleButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SearchBar.d.ts +7 -1
- package/lib/typescript/module/ui/components/SearchBar.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SectionHeader.d.ts +7 -1
- package/lib/typescript/module/ui/components/SectionHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/TabSelector.d.ts +20 -1
- package/lib/typescript/module/ui/components/TabSelector.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +5 -0
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -206,13 +206,17 @@ const buoyColors = exports.buoyColors = {
|
|
|
206
206
|
// Red
|
|
207
207
|
info: "#00B8E6" // Cyan for info/devices
|
|
208
208
|
};
|
|
209
|
-
|
|
209
|
+
/**
|
|
210
|
+
* LEGACY copy of the dial palette — the source of truth is `dialColors` in
|
|
211
|
+
* @buoy-gg/floating-tools-core (`src/dial.ts`); keep the two in sync.
|
|
212
|
+
* Values mirror the night theme (see `ui/night/nightTheme.ts`).
|
|
213
|
+
*/
|
|
210
214
|
const dialColors = exports.dialColors = {
|
|
211
|
-
dialBackground:
|
|
212
|
-
dialGradient1:
|
|
213
|
-
dialGradient2:
|
|
214
|
-
dialGradient3:
|
|
215
|
-
dialBorder:
|
|
216
|
-
dialShadow:
|
|
217
|
-
dialGridLine:
|
|
215
|
+
dialBackground: "#050505",
|
|
216
|
+
dialGradient1: "rgba(255, 255, 255, 0.05)",
|
|
217
|
+
dialGradient2: "rgba(255, 255, 255, 0.03)",
|
|
218
|
+
dialGradient3: "rgba(255, 255, 255, 0.06)",
|
|
219
|
+
dialBorder: "rgba(255, 255, 255, 0.12)",
|
|
220
|
+
dialShadow: "#000000",
|
|
221
|
+
dialGridLine: "rgba(255, 255, 255, 0.08)"
|
|
218
222
|
};
|
package/lib/module/JsModal.js
CHANGED
|
@@ -494,6 +494,16 @@ const JsModalComponent = ({
|
|
|
494
494
|
}
|
|
495
495
|
}, []);
|
|
496
496
|
|
|
497
|
+
// Parents routinely pass an inline `onModeChange` (a new identity every
|
|
498
|
+
// render). The persisted-state load below is mount/key-scoped — if the
|
|
499
|
+
// callback identity re-triggered it, every parent re-render would reload
|
|
500
|
+
// the OLD persisted mode and stomp a live toggleMode() before the 500ms
|
|
501
|
+
// save debounce could write the new one (mode flipped to floating, then
|
|
502
|
+
// snapped back ~50ms later). So the callback rides in a ref instead of
|
|
503
|
+
// the dependency list.
|
|
504
|
+
const onModeChangeRef = useRef(onModeChange);
|
|
505
|
+
onModeChangeRef.current = onModeChange;
|
|
506
|
+
|
|
497
507
|
// Load persisted state on mount
|
|
498
508
|
useEffect(() => {
|
|
499
509
|
if (!enablePersistence || !persistenceKey) {
|
|
@@ -508,7 +518,7 @@ const JsModalComponent = ({
|
|
|
508
518
|
if (savedState.mode) {
|
|
509
519
|
setMode(savedState.mode);
|
|
510
520
|
// Notify parent of loaded mode
|
|
511
|
-
|
|
521
|
+
onModeChangeRef.current?.(savedState.mode);
|
|
512
522
|
}
|
|
513
523
|
|
|
514
524
|
// Restore bottom sheet height
|
|
@@ -535,7 +545,7 @@ const JsModalComponent = ({
|
|
|
535
545
|
return () => {
|
|
536
546
|
mounted = false;
|
|
537
547
|
};
|
|
538
|
-
}, [persistenceKey, enablePersistence,
|
|
548
|
+
}, [persistenceKey, enablePersistence, animatedBottomPosition, animatedFloatingHeight, animatedWidth, floatingPosition]);
|
|
539
549
|
|
|
540
550
|
// Cleanup on unmount
|
|
541
551
|
useEffect(() => {
|
|
@@ -82,6 +82,7 @@ function Tile({
|
|
|
82
82
|
const color = danger && enabled ? T.dangerText : T.tileIcon;
|
|
83
83
|
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
84
84
|
"sentry-label": "ignore devtools tree action",
|
|
85
|
+
testID: `buoy-tree-action-${id}`,
|
|
85
86
|
accessibilityLabel: label,
|
|
86
87
|
accessibilityState: {
|
|
87
88
|
disabled: !enabled
|
|
@@ -27,7 +27,8 @@ export const DataViewer = ({
|
|
|
27
27
|
onRequestInlineEdit,
|
|
28
28
|
inlineEditPath,
|
|
29
29
|
renderInlineEditor,
|
|
30
|
-
selectPath
|
|
30
|
+
selectPath,
|
|
31
|
+
testIDPrefix
|
|
31
32
|
}) => {
|
|
32
33
|
const [activeFilter, setActiveFilter] = useState(null);
|
|
33
34
|
|
|
@@ -109,7 +110,8 @@ export const DataViewer = ({
|
|
|
109
110
|
maxDepth: maxDepth,
|
|
110
111
|
rawMode: rawMode,
|
|
111
112
|
initialExpanded: initialExpanded,
|
|
112
|
-
disableCopy: disableCopy
|
|
113
|
+
disableCopy: disableCopy,
|
|
114
|
+
testIDPrefix: testIDPrefix
|
|
113
115
|
});
|
|
114
116
|
}
|
|
115
117
|
|
|
@@ -125,7 +127,8 @@ export const DataViewer = ({
|
|
|
125
127
|
onRequestInlineEdit: onRequestInlineEdit,
|
|
126
128
|
inlineEditPath: inlineEditPath,
|
|
127
129
|
renderInlineEditor: renderInlineEditor,
|
|
128
|
-
selectPath: selectPath
|
|
130
|
+
selectPath: selectPath,
|
|
131
|
+
testIDPrefix: testIDPrefix
|
|
129
132
|
});
|
|
130
133
|
};
|
|
131
134
|
return /*#__PURE__*/_jsxs(View, {
|
|
@@ -822,7 +822,8 @@ const VirtualizedItemComponent = ({
|
|
|
822
822
|
selectable,
|
|
823
823
|
onRequestInlineEdit,
|
|
824
824
|
isInlineEditing,
|
|
825
|
-
inlineEditor
|
|
825
|
+
inlineEditor,
|
|
826
|
+
testIDPrefix
|
|
826
827
|
}) => {
|
|
827
828
|
const [isPressed, setIsPressed] = useState(false);
|
|
828
829
|
const [showFullKey, setShowFullKey] = useState(false);
|
|
@@ -891,7 +892,13 @@ const VirtualizedItemComponent = ({
|
|
|
891
892
|
return /*#__PURE__*/_jsxs(View, {
|
|
892
893
|
style: [STABLE_STYLES.itemContainer, indentStyle],
|
|
893
894
|
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
894
|
-
"sentry-label": "ignore devtools data explorer item"
|
|
895
|
+
"sentry-label": "ignore devtools data explorer item"
|
|
896
|
+
// `item.id` is the dotted path from the root ("root.settings.theme"),
|
|
897
|
+
// so this handle addresses a NODE rather than a list position — stable
|
|
898
|
+
// across expand/collapse and across re-renders.
|
|
899
|
+
,
|
|
900
|
+
testID: `${testIDPrefix}-${item.id}`,
|
|
901
|
+
accessibilityLabel: `${item.key}: ${item.valueType}`,
|
|
895
902
|
style: [STABLE_STYLES.itemTouchable, isPressed && STABLE_STYLES.itemTouchablePressed,
|
|
896
903
|
// The selected wash exists for the action-bar/edit flow. In a plain
|
|
897
904
|
// read-only viewer every expand/collapse tap ALSO selects, which
|
|
@@ -966,7 +973,8 @@ export const VirtualizedDataExplorer = ({
|
|
|
966
973
|
onRequestInlineEdit,
|
|
967
974
|
inlineEditPath,
|
|
968
975
|
renderInlineEditor,
|
|
969
|
-
selectPath
|
|
976
|
+
selectPath,
|
|
977
|
+
testIDPrefix = "buoy-tree-row"
|
|
970
978
|
}) => {
|
|
971
979
|
const [isExpanded, setIsExpanded] = useState(rawMode); // Auto-expand in raw mode
|
|
972
980
|
const {
|
|
@@ -1082,7 +1090,8 @@ export const VirtualizedDataExplorer = ({
|
|
|
1082
1090
|
selectable: onSelectionChange != null,
|
|
1083
1091
|
onRequestInlineEdit: handleRequestInlineEdit,
|
|
1084
1092
|
isInlineEditing: inlineEditPath != null && inlineEditPath.join(".") === item.path.slice(1).join("."),
|
|
1085
|
-
inlineEditor: renderInlineEditor ? renderInlineEditor(selectionOf(item)) : null
|
|
1093
|
+
inlineEditor: renderInlineEditor ? renderInlineEditor(selectionOf(item)) : null,
|
|
1094
|
+
testIDPrefix: testIDPrefix
|
|
1086
1095
|
});
|
|
1087
1096
|
|
|
1088
1097
|
// Uniform row height for crisp guide geometry
|
|
@@ -379,11 +379,20 @@ export const UpgradeModal = ({
|
|
|
379
379
|
style: styles.modalContent,
|
|
380
380
|
activeOpacity: 1,
|
|
381
381
|
onPress: () => {} // Prevent overlay dismiss when tapping content
|
|
382
|
+
// The handle a test needs to say a Pro gate did NOT fire. Without
|
|
383
|
+
// it "the action worked" and "the upsell appeared over it" are
|
|
384
|
+
// told apart only by what happened next, which is exactly the
|
|
385
|
+
// assertion that was supposed to be the proof.
|
|
382
386
|
,
|
|
387
|
+
testID: "buoy-upgrade-modal",
|
|
388
|
+
accessibilityLabel: `Unlock ${featureName}`,
|
|
383
389
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
384
390
|
style: styles.modalCloseButton,
|
|
385
391
|
onPress: onClose,
|
|
386
392
|
activeOpacity: 0.7,
|
|
393
|
+
testID: "buoy-upgrade-modal-close",
|
|
394
|
+
accessibilityRole: "button",
|
|
395
|
+
accessibilityLabel: "Close upgrade prompt",
|
|
387
396
|
children: /*#__PURE__*/_jsx(X, {
|
|
388
397
|
size: 18,
|
|
389
398
|
color: buoyColors.textMuted
|
|
@@ -25,9 +25,10 @@ export const BACKGROUND_PRESETS = [{
|
|
|
25
25
|
id: "deepfield",
|
|
26
26
|
label: "Deep Field",
|
|
27
27
|
blurb: "The Everlights field — 160 stars, with meteors slamming the walls",
|
|
28
|
-
// The old standalone Meteors preset folded in: +2 meteor loops, ~
|
|
28
|
+
// The old standalone Meteors preset folded in: +2 meteor loops, ~55 views
|
|
29
|
+
// (streak + flash + the pop's 6 rays and ~3 embers per impact).
|
|
29
30
|
nodes: 24,
|
|
30
|
-
views:
|
|
31
|
+
views: 515,
|
|
31
32
|
Component: DeepField
|
|
32
33
|
}, {
|
|
33
34
|
id: "hyperspace",
|
|
@@ -4,12 +4,28 @@
|
|
|
4
4
|
* MeteorShower — shooting stars that slam into the walls.
|
|
5
5
|
*
|
|
6
6
|
* Each meteor streaks in from off-screen, hits an EDGE of the surface, and
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* blows out like a firework: a flash, then a fan of streak RAYS that shoot
|
|
8
|
+
* back into the room — each ray grows out of the impact point while its tail
|
|
9
|
+
* chases the head, so it reads as a burning trail rather than a ring — with
|
|
10
|
+
* ember dots at the tips that decelerate, droop under gravity and gutter out.
|
|
11
|
+
* The whole life cycle — parked invisible, streak, impact, decay — is
|
|
12
|
+
* keyframed into interpolate stops on ONE plain native loop per meteor;
|
|
13
|
+
* `Animated.sequence([timing, loop])` silently fails to drive natively, so
|
|
14
|
+
* nothing here sequences.
|
|
15
|
+
*
|
|
16
|
+
* Each meteor also rolls WHAT IT'S MADE OF once per mount — a weighted draw
|
|
17
|
+
* over the real emission-line palette (sodium orange-yellow, iron yellow,
|
|
18
|
+
* magnesium blue-green, calcium violet, shocked atmospheric N₂/O₂ red,
|
|
19
|
+
* ionized-air blue-white) — and that mineral tints streak, trail, flash,
|
|
20
|
+
* rays and embers as one. See `MINERALS`.
|
|
21
|
+
*
|
|
22
|
+
* The ray trick, since there is no `transformOrigin`: a bar of full length
|
|
23
|
+
* `dist` is CENTERED on the impact point, rotated to its heading, then driven
|
|
24
|
+
* with `[{rotate}, {translateX}, {scaleX}]`. Rotate is outermost, so the
|
|
25
|
+
* translate runs along the ray's own axis; scaleX squashes about the bar's
|
|
26
|
+
* center, and translating by the midpoint of (head, tail) while scaling by
|
|
27
|
+
* (head − tail) puts the visible segment exactly between the two — a line
|
|
28
|
+
* growing out of the burst with both endpoints scripted.
|
|
13
29
|
*
|
|
14
30
|
* This used to be a standalone "Meteors" preset with its own 40-star sky;
|
|
15
31
|
* that scene was folded into Deep Field (which supplies the far better sky),
|
|
@@ -20,16 +36,16 @@
|
|
|
20
36
|
* `transformOrigin`.
|
|
21
37
|
*/
|
|
22
38
|
|
|
23
|
-
import { memo } from "react";
|
|
39
|
+
import { memo, useState } from "react";
|
|
24
40
|
import { Animated, View } from "react-native";
|
|
25
41
|
import { absoluteFill } from "../../../utils/absoluteFill.js";
|
|
26
42
|
import { RadialGlow, useLoop } from "../primitives.js";
|
|
27
|
-
import { withAlpha } from "../rng.js";
|
|
43
|
+
import { mulberry32, withAlpha } from "../rng.js";
|
|
28
44
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
29
45
|
/**
|
|
30
46
|
* `to` is ON a wall — that's the impact point. `sparkArc` is the hemisphere
|
|
31
|
-
* (in degrees)
|
|
32
|
-
* meteor just hit. Different periods keep the two impacts from syncing up.
|
|
47
|
+
* (in degrees) the firework rays fan into, pointed back away from the wall
|
|
48
|
+
* the meteor just hit. Different periods keep the two impacts from syncing up.
|
|
33
49
|
*/
|
|
34
50
|
const METEORS = [{
|
|
35
51
|
from: [0.08, -0.06],
|
|
@@ -44,8 +60,112 @@ const METEORS = [{
|
|
|
44
60
|
window: [0.56, 0.66],
|
|
45
61
|
sparkArc: [-70, 70] // left wall → sparks fly right
|
|
46
62
|
}];
|
|
47
|
-
const
|
|
48
|
-
|
|
63
|
+
const RAYS_PER_IMPACT = 6;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Real meteor color comes from what's burning. The dominant visible emission
|
|
67
|
+
* lines are sodium (orange-yellow), iron (yellow) and magnesium (blue-green);
|
|
68
|
+
* calcium-rich rock flares violet, and the shocked ATMOSPHERE itself — the
|
|
69
|
+
* excited nitrogen/oxygen sheath around the meteoroid — glows red. Faint fast
|
|
70
|
+
* meteors mostly show the blue-white of ionized air. The weights lean the
|
|
71
|
+
* same way the sky does: yellows and oranges common, teal frequent, red
|
|
72
|
+
* occasional, violet the rare treat.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
const MINERALS = [{
|
|
76
|
+
name: "sodium",
|
|
77
|
+
weight: 26,
|
|
78
|
+
head: "#FFF3DC",
|
|
79
|
+
glow: "#FFDCA8",
|
|
80
|
+
trail: "#FFCB85",
|
|
81
|
+
rays: ["#FFE8C2", "#FFCB85", "#F0B45E"]
|
|
82
|
+
}, {
|
|
83
|
+
name: "iron",
|
|
84
|
+
weight: 24,
|
|
85
|
+
head: "#FFF9E3",
|
|
86
|
+
glow: "#FFEDAE",
|
|
87
|
+
trail: "#F2DC8A",
|
|
88
|
+
rays: ["#FBF0C0", "#F2DC8A", "#E3C868"]
|
|
89
|
+
}, {
|
|
90
|
+
name: "magnesium",
|
|
91
|
+
weight: 20,
|
|
92
|
+
head: "#EAFFF9",
|
|
93
|
+
glow: "#ACF0DE",
|
|
94
|
+
trail: "#82E3CA",
|
|
95
|
+
rays: ["#D3FAEF", "#82E3CA", "#5FD6BC"]
|
|
96
|
+
}, {
|
|
97
|
+
// Not a mineral at all — ionized air, the old blue-white default.
|
|
98
|
+
name: "ionized-air",
|
|
99
|
+
weight: 14,
|
|
100
|
+
head: "#F5F9FF",
|
|
101
|
+
glow: "#E5EEFC",
|
|
102
|
+
trail: "#C9D9F0",
|
|
103
|
+
rays: ["#F5F9FF", "#DCE9FF", "#BBE5FA"]
|
|
104
|
+
}, {
|
|
105
|
+
// Also atmospheric: nitrogen/oxygen shocked red-hot in the sheath.
|
|
106
|
+
name: "nitrogen-oxygen",
|
|
107
|
+
weight: 10,
|
|
108
|
+
head: "#FFEAE5",
|
|
109
|
+
glow: "#FFB4A6",
|
|
110
|
+
trail: "#FF917E",
|
|
111
|
+
rays: ["#FFCFC6", "#FF917E", "#F27461"]
|
|
112
|
+
}, {
|
|
113
|
+
name: "calcium",
|
|
114
|
+
weight: 6,
|
|
115
|
+
head: "#F6EEFF",
|
|
116
|
+
glow: "#DCC2FF",
|
|
117
|
+
trail: "#C29BF7",
|
|
118
|
+
rays: ["#EADCFF", "#C29BF7", "#AB7EEC"]
|
|
119
|
+
}];
|
|
120
|
+
const MINERAL_WEIGHT = MINERALS.reduce((sum, m) => sum + m.weight, 0);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Weighted draw — `Math.random` ON PURPOSE, unlike the seeded burst geometry:
|
|
124
|
+
* each meteor rolls its make-up once per mount, so every session's sky burns
|
|
125
|
+
* a different mix while the animation itself stays deterministic.
|
|
126
|
+
*/
|
|
127
|
+
function pickMineral() {
|
|
128
|
+
let r = Math.random() * MINERAL_WEIGHT;
|
|
129
|
+
for (const m of MINERALS) {
|
|
130
|
+
r -= m.weight;
|
|
131
|
+
if (r <= 0) return m;
|
|
132
|
+
}
|
|
133
|
+
return MINERALS[0];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The firework: a deterministic fan of rays per impact, seeded so both
|
|
137
|
+
* platforms (and every reload) burst identically. Angles are stratified
|
|
138
|
+
* across the arc with jitter, so the fan covers it without reading as a
|
|
139
|
+
* perfect star; reach, thickness and stagger vary per ray so it reads as
|
|
140
|
+
* debris blowing out, not a rigid spoke wheel.
|
|
141
|
+
*/
|
|
142
|
+
function buildBurst(index) {
|
|
143
|
+
const m = METEORS[index];
|
|
144
|
+
const rnd = mulberry32((0xb1a57 ^ index) >>> 0);
|
|
145
|
+
const [a0, a1] = m.sparkArc;
|
|
146
|
+
const rays = [];
|
|
147
|
+
for (let i = 0; i < RAYS_PER_IMPACT; i++) {
|
|
148
|
+
const frac = (i + 0.5) / RAYS_PER_IMPACT;
|
|
149
|
+
const jitter = (rnd() - 0.5) * 0.7 / RAYS_PER_IMPACT;
|
|
150
|
+
const deg = a0 + (a1 - a0) * (frac + jitter);
|
|
151
|
+
// Small on purpose — a game-sprite pop, not a fireworks display: rays
|
|
152
|
+
// reach 10–22px, a hair over 1px thick, and the whole thing is over in a
|
|
153
|
+
// few frames.
|
|
154
|
+
rays.push({
|
|
155
|
+
rad: deg * Math.PI / 180,
|
|
156
|
+
dist: 10 + rnd() * 12,
|
|
157
|
+
thick: 1 + rnd() * 0.5,
|
|
158
|
+
delay: rnd() * 0.008,
|
|
159
|
+
grow: 0.03 + rnd() * 0.015,
|
|
160
|
+
ember: rnd() > 0.5,
|
|
161
|
+
emberDrop: 2 + rnd() * 3,
|
|
162
|
+
emberDrift: 1 + rnd() * 3,
|
|
163
|
+
emberDia: 1.2 + rnd() * 0.6
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return rays;
|
|
167
|
+
}
|
|
168
|
+
const BURSTS = METEORS.map((_, i) => buildBurst(i));
|
|
49
169
|
const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
50
170
|
index,
|
|
51
171
|
width,
|
|
@@ -53,6 +173,8 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
53
173
|
animated
|
|
54
174
|
}) {
|
|
55
175
|
const m = METEORS[index];
|
|
176
|
+
// What this meteor is made of — rolled once per mount, held for its life.
|
|
177
|
+
const [mineral] = useState(pickMineral);
|
|
56
178
|
const t = useLoop(m.periodMs, animated);
|
|
57
179
|
const [a, b] = m.window;
|
|
58
180
|
const x0 = m.from[0] * width;
|
|
@@ -92,12 +214,12 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
92
214
|
width: d,
|
|
93
215
|
height: d,
|
|
94
216
|
borderRadius: d / 2,
|
|
95
|
-
backgroundColor: withAlpha(
|
|
217
|
+
backgroundColor: withAlpha(mineral.trail, 0.68 - i * 0.08)
|
|
96
218
|
}
|
|
97
219
|
}, i));
|
|
98
220
|
}
|
|
99
221
|
|
|
100
|
-
// --- The impact
|
|
222
|
+
// --- The impact: firework blow-out --------------------------------------
|
|
101
223
|
// Everything below reads the SAME loop value; the burst simply lives in the
|
|
102
224
|
// slice of the timeline right after the streak arrives at the wall.
|
|
103
225
|
const flashOpacity = t.interpolate({
|
|
@@ -105,56 +227,94 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
105
227
|
outputRange: [0, 0, 1, 0, 0]
|
|
106
228
|
});
|
|
107
229
|
const flashScale = t.interpolate({
|
|
108
|
-
inputRange: [0, b, Math.min(b + 0.
|
|
109
|
-
outputRange: [0.
|
|
110
|
-
});
|
|
111
|
-
const waveOpacity = t.interpolate({
|
|
112
|
-
inputRange: [0, b, Math.min(b + 0.02, 0.999), Math.min(b + 0.11, 0.9995), 1],
|
|
113
|
-
outputRange: [0, 0, 0.85, 0, 0]
|
|
114
|
-
});
|
|
115
|
-
const waveScale = t.interpolate({
|
|
116
|
-
inputRange: [0, b, Math.min(b + 0.11, 0.999), 1],
|
|
117
|
-
outputRange: [0.12, 0.12, 1, 1]
|
|
230
|
+
inputRange: [0, b, Math.min(b + 0.05, 0.999), 1],
|
|
231
|
+
outputRange: [0.5, 0.5, 1.05, 1.05]
|
|
118
232
|
});
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
233
|
+
const rays = [];
|
|
234
|
+
const embers = [];
|
|
235
|
+
const burst = BURSTS[index];
|
|
236
|
+
for (let i = 0; i < burst.length; i++) {
|
|
237
|
+
const r = burst[i];
|
|
238
|
+
// The ray's timeline: head races out over `grow`, the tail leaves the
|
|
239
|
+
// impact point once the head is under way and catches it shortly after —
|
|
240
|
+
// the segment between them is the burning trail.
|
|
241
|
+
const t0 = b + r.delay;
|
|
242
|
+
const k1 = t0 + r.grow * 0.5; // head at 75%, tail just leaving
|
|
243
|
+
const k2 = t0 + r.grow; // head at full reach, tail halfway
|
|
244
|
+
const k3 = k2 + 0.035; // tail catches the head — ray gone
|
|
245
|
+
// Segment endpoints head/tail (fractions of dist):
|
|
246
|
+
// t0 → 0/0, k1 → 0.75/0.1, k2 → 1/0.5, k3 → 1/1
|
|
247
|
+
// scaleX = head − tail, translateX = dist · (head + tail)/2.
|
|
248
|
+
const len = t.interpolate({
|
|
249
|
+
inputRange: [0, t0, k1, k2, k3, 1],
|
|
250
|
+
outputRange: [0, 0, 0.65, 0.5, 0, 0]
|
|
130
251
|
});
|
|
131
|
-
const
|
|
132
|
-
inputRange: [0,
|
|
133
|
-
outputRange: [0, 0,
|
|
252
|
+
const mid = t.interpolate({
|
|
253
|
+
inputRange: [0, t0, k1, k2, k3, 1],
|
|
254
|
+
outputRange: [0, 0, r.dist * 0.425, r.dist * 0.75, r.dist, r.dist]
|
|
134
255
|
});
|
|
135
|
-
const
|
|
136
|
-
inputRange: [0,
|
|
137
|
-
outputRange: [0, 0, 0.
|
|
256
|
+
const rayOpacity = t.interpolate({
|
|
257
|
+
inputRange: [0, t0, t0 + 0.006, k2, k3, 1],
|
|
258
|
+
outputRange: [0, 0, 0.85, 0.7, 0, 0]
|
|
138
259
|
});
|
|
139
|
-
|
|
140
|
-
sparks.push(/*#__PURE__*/_jsx(Animated.View, {
|
|
260
|
+
rays.push(/*#__PURE__*/_jsx(Animated.View, {
|
|
141
261
|
style: {
|
|
142
262
|
position: "absolute",
|
|
143
|
-
left: x1 -
|
|
144
|
-
top: y1 -
|
|
145
|
-
width:
|
|
146
|
-
height:
|
|
147
|
-
borderRadius:
|
|
148
|
-
backgroundColor: withAlpha(
|
|
149
|
-
opacity:
|
|
263
|
+
left: x1 - r.dist / 2,
|
|
264
|
+
top: y1 - r.thick / 2,
|
|
265
|
+
width: r.dist,
|
|
266
|
+
height: r.thick,
|
|
267
|
+
borderRadius: r.thick / 2,
|
|
268
|
+
backgroundColor: withAlpha(mineral.rays[i % mineral.rays.length], 0.8),
|
|
269
|
+
opacity: rayOpacity,
|
|
270
|
+
// Rotate is OUTERMOST, so the translate runs along the ray's own
|
|
271
|
+
// axis — see the header note on faking a transform origin.
|
|
150
272
|
transform: [{
|
|
151
|
-
|
|
273
|
+
rotate: `${r.rad * 180 / Math.PI}deg`
|
|
152
274
|
}, {
|
|
153
|
-
|
|
275
|
+
translateX: mid
|
|
276
|
+
}, {
|
|
277
|
+
scaleX: len
|
|
154
278
|
}]
|
|
155
279
|
},
|
|
156
280
|
pointerEvents: "none"
|
|
157
|
-
}, i));
|
|
281
|
+
}, `ray-${i}`));
|
|
282
|
+
if (!r.ember) continue;
|
|
283
|
+
// The ember rides the ray's head, then keeps coasting a touch further
|
|
284
|
+
// while gravity drags it DOWN in world space, guttering out last.
|
|
285
|
+
const e3 = k3 + 0.04;
|
|
286
|
+
const cos = Math.cos(r.rad);
|
|
287
|
+
const sin = Math.sin(r.rad);
|
|
288
|
+
const ex = t.interpolate({
|
|
289
|
+
inputRange: [0, t0, k1, k2, e3, 1],
|
|
290
|
+
outputRange: [0, 0, cos * r.dist * 0.75, cos * r.dist, cos * (r.dist + r.emberDrift), cos * (r.dist + r.emberDrift)]
|
|
291
|
+
});
|
|
292
|
+
const ey = t.interpolate({
|
|
293
|
+
inputRange: [0, t0, k1, k2, e3, 1],
|
|
294
|
+
outputRange: [0, 0, sin * r.dist * 0.75, sin * r.dist, sin * (r.dist + r.emberDrift) + r.emberDrop, sin * (r.dist + r.emberDrift) + r.emberDrop]
|
|
295
|
+
});
|
|
296
|
+
const eo = t.interpolate({
|
|
297
|
+
inputRange: [0, t0, k1, k2, e3, 1],
|
|
298
|
+
outputRange: [0, 0, 0.8, 0.65, 0, 0]
|
|
299
|
+
});
|
|
300
|
+
embers.push(/*#__PURE__*/_jsx(Animated.View, {
|
|
301
|
+
style: {
|
|
302
|
+
position: "absolute",
|
|
303
|
+
left: x1 - r.emberDia / 2,
|
|
304
|
+
top: y1 - r.emberDia / 2,
|
|
305
|
+
width: r.emberDia,
|
|
306
|
+
height: r.emberDia,
|
|
307
|
+
borderRadius: r.emberDia / 2,
|
|
308
|
+
backgroundColor: withAlpha(mineral.head, 1),
|
|
309
|
+
opacity: eo,
|
|
310
|
+
transform: [{
|
|
311
|
+
translateX: ex
|
|
312
|
+
}, {
|
|
313
|
+
translateY: ey
|
|
314
|
+
}]
|
|
315
|
+
},
|
|
316
|
+
pointerEvents: "none"
|
|
317
|
+
}, `ember-${i}`));
|
|
158
318
|
}
|
|
159
319
|
return /*#__PURE__*/_jsxs(View, {
|
|
160
320
|
style: absoluteFill,
|
|
@@ -184,7 +344,7 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
184
344
|
width: 20,
|
|
185
345
|
height: 20,
|
|
186
346
|
borderRadius: 10,
|
|
187
|
-
backgroundColor: withAlpha(
|
|
347
|
+
backgroundColor: withAlpha(mineral.glow, 0.16)
|
|
188
348
|
}
|
|
189
349
|
}), /*#__PURE__*/_jsx(View, {
|
|
190
350
|
style: {
|
|
@@ -194,16 +354,16 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
194
354
|
width: 3,
|
|
195
355
|
height: 3,
|
|
196
356
|
borderRadius: 1.5,
|
|
197
|
-
backgroundColor: withAlpha(
|
|
357
|
+
backgroundColor: withAlpha(mineral.head, 1)
|
|
198
358
|
}
|
|
199
359
|
}), trail]
|
|
200
360
|
}), /*#__PURE__*/_jsx(Animated.View, {
|
|
201
361
|
style: {
|
|
202
362
|
position: "absolute",
|
|
203
|
-
left: x1 -
|
|
204
|
-
top: y1 -
|
|
205
|
-
width:
|
|
206
|
-
height:
|
|
363
|
+
left: x1 - 18,
|
|
364
|
+
top: y1 - 18,
|
|
365
|
+
width: 36,
|
|
366
|
+
height: 36,
|
|
207
367
|
opacity: flashOpacity,
|
|
208
368
|
transform: [{
|
|
209
369
|
scale: flashScale
|
|
@@ -211,29 +371,13 @@ const Meteor = /*#__PURE__*/memo(function Meteor({
|
|
|
211
371
|
},
|
|
212
372
|
pointerEvents: "none",
|
|
213
373
|
children: /*#__PURE__*/_jsx(RadialGlow, {
|
|
214
|
-
size:
|
|
215
|
-
color:
|
|
216
|
-
alpha: 0.
|
|
217
|
-
rings:
|
|
374
|
+
size: 36,
|
|
375
|
+
color: mineral.glow,
|
|
376
|
+
alpha: 0.45,
|
|
377
|
+
rings: 5,
|
|
218
378
|
falloff: 1.6
|
|
219
379
|
})
|
|
220
|
-
}),
|
|
221
|
-
style: {
|
|
222
|
-
position: "absolute",
|
|
223
|
-
left: x1 - SHOCKWAVE_DIA / 2,
|
|
224
|
-
top: y1 - SHOCKWAVE_DIA / 2,
|
|
225
|
-
width: SHOCKWAVE_DIA,
|
|
226
|
-
height: SHOCKWAVE_DIA,
|
|
227
|
-
borderRadius: SHOCKWAVE_DIA / 2,
|
|
228
|
-
borderWidth: 1.5,
|
|
229
|
-
borderColor: withAlpha("#BBE5FA", 0.9),
|
|
230
|
-
opacity: waveOpacity,
|
|
231
|
-
transform: [{
|
|
232
|
-
scale: waveScale
|
|
233
|
-
}]
|
|
234
|
-
},
|
|
235
|
-
pointerEvents: "none"
|
|
236
|
-
}), sparks]
|
|
380
|
+
}), rays, embers]
|
|
237
381
|
});
|
|
238
382
|
});
|
|
239
383
|
|
|
@@ -98,7 +98,8 @@ export function TypeBadge({
|
|
|
98
98
|
type,
|
|
99
99
|
color,
|
|
100
100
|
size = "small",
|
|
101
|
-
style
|
|
101
|
+
style,
|
|
102
|
+
testID
|
|
102
103
|
}) {
|
|
103
104
|
const typeColor = color || getTypeColor(type);
|
|
104
105
|
const badgeStyles = getBadgeStyles("type", typeColor, size);
|
|
@@ -108,6 +109,7 @@ export function TypeBadge({
|
|
|
108
109
|
style: [badgeStyles.text, {
|
|
109
110
|
color: typeColor
|
|
110
111
|
}],
|
|
112
|
+
testID: testID,
|
|
111
113
|
children: type
|
|
112
114
|
})
|
|
113
115
|
});
|
|
@@ -21,7 +21,11 @@ export function CompactRow({
|
|
|
21
21
|
isSelected,
|
|
22
22
|
onPress,
|
|
23
23
|
disabled,
|
|
24
|
-
expandedGlowColor
|
|
24
|
+
expandedGlowColor,
|
|
25
|
+
testID,
|
|
26
|
+
statusTestID,
|
|
27
|
+
statusSublabelTestID,
|
|
28
|
+
accessibilityLabel
|
|
25
29
|
}) {
|
|
26
30
|
return /*#__PURE__*/_jsx(View, {
|
|
27
31
|
style: [styles.rowWrapper, isExpanded && styles.rowWrapperExpanded],
|
|
@@ -38,6 +42,13 @@ export function CompactRow({
|
|
|
38
42
|
onPress: onPress,
|
|
39
43
|
activeOpacity: 0.8,
|
|
40
44
|
disabled: disabled || !onPress,
|
|
45
|
+
testID: testID,
|
|
46
|
+
accessibilityLabel: accessibilityLabel
|
|
47
|
+
// Label alone is not enough on Fabric: a row that announces text but
|
|
48
|
+
// no role reads to VoiceOver as static content, so the fact that it
|
|
49
|
+
// opens something is only discoverable by tapping it.
|
|
50
|
+
,
|
|
51
|
+
accessibilityRole: onPress ? "button" : undefined,
|
|
41
52
|
children: /*#__PURE__*/_jsxs(View, {
|
|
42
53
|
style: styles.rowContent,
|
|
43
54
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
@@ -53,10 +64,12 @@ export function CompactRow({
|
|
|
53
64
|
color: statusDotColor
|
|
54
65
|
}],
|
|
55
66
|
numberOfLines: 1,
|
|
67
|
+
testID: statusTestID,
|
|
56
68
|
children: statusLabel
|
|
57
69
|
}), statusSublabel ? /*#__PURE__*/_jsx(Text, {
|
|
58
70
|
style: styles.observerText,
|
|
59
71
|
numberOfLines: 1,
|
|
72
|
+
testID: statusSublabelTestID,
|
|
60
73
|
children: statusSublabel
|
|
61
74
|
}) : null]
|
|
62
75
|
})]
|