@tastic/hud 0.8.2 → 0.8.4

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/dist/index.mjs CHANGED
@@ -71,19 +71,146 @@ var styles = StyleSheet.create({
71
71
  });
72
72
 
73
73
  // src/BaseSettingsDialog.tsx
74
- import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
75
- import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
74
+ import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
75
+ import { Button as Button2, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
76
76
  import { useUpdater } from "@rific/updater";
77
- import { useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
78
- import { useContext, useState } from "react";
79
- import { Platform, ScrollView, StyleSheet as StyleSheet2, View as View2 } from "react-native";
77
+ import { toRotationStyle as toRotationStyle2, useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
78
+ import { useContext, useState as useState2 } from "react";
79
+ import { Platform, ScrollView, StyleSheet as StyleSheet4, View as View3 } from "react-native";
80
+ import { Icon as Icon3, Text as Text3 } from "react-native-paper";
81
+
82
+ // src/ConfirmDialog.tsx
83
+ import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
84
+ import { Button } from "@rific/feedback-press";
85
+ import { toRotationStyle } from "@tastic/core";
86
+ import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
80
87
  import { Icon as Icon2, Portal, Text as Text2 } from "react-native-paper";
81
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
88
+
89
+ // src/overlayCard.ts
90
+ import { StyleSheet as StyleSheet2 } from "react-native";
91
+ function getOverlayCardColors(dark) {
92
+ return {
93
+ fg: dark ? "#FFFFFF" : "#000000",
94
+ fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
95
+ cardBg: dark ? "#111111" : "#F2F2F2",
96
+ cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
97
+ };
98
+ }
99
+ var overlayStyles = StyleSheet2.create({
100
+ card: {
101
+ alignItems: "center",
102
+ borderRadius: 20,
103
+ borderWidth: 1,
104
+ gap: 16,
105
+ maxWidth: 360,
106
+ padding: 32
107
+ },
108
+ overlay: {
109
+ alignItems: "center",
110
+ backgroundColor: "rgba(0,0,0,0.72)",
111
+ bottom: 0,
112
+ justifyContent: "center",
113
+ left: 0,
114
+ position: "absolute",
115
+ right: 0,
116
+ top: 0
117
+ }
118
+ });
119
+
120
+ // src/ConfirmDialog.tsx
121
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
122
+ function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
123
+ const { dark, colors } = useAutoPaperTheme2();
124
+ if (!visible) return null;
125
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
126
+ const accent = destructive ? colors.danger : colors.primary;
127
+ const onAccent = destructive ? colors.onDanger : colors.onPrimary;
128
+ return /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(View2, { style: [overlayStyles.overlay, styles2.overlay], children: /* @__PURE__ */ jsxs2(View2, { style: [overlayStyles.card, styles2.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)], children: [
129
+ /* @__PURE__ */ jsx2(Icon2, { source: icon, size: 64, color: accent }),
130
+ /* @__PURE__ */ jsx2(Text2, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
131
+ /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
132
+ /* @__PURE__ */ jsxs2(View2, { style: styles2.actions, children: [
133
+ cancelLabel !== void 0 && onCancel !== void 0 && /* @__PURE__ */ jsx2(Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
134
+ /* @__PURE__ */ jsx2(Button, { mode: "contained", onPress: onConfirm, style: styles2.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
135
+ ] })
136
+ ] }) }) });
137
+ }
138
+ var styles2 = StyleSheet3.create({
139
+ // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
140
+ // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
141
+ // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
142
+ // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
143
+ // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
144
+ // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
145
+ // fixable by just giving the row more width, until confirmed live that these two-word labels
146
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
147
+ // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
148
+ // width there's no side-by-side arrangement that fits both buttons at their natural label size,
149
+ // so the layout has to give (stack) rather than the label (truncate).
150
+ actionButton: {
151
+ flexBasis: 128,
152
+ flexGrow: 1
153
+ },
154
+ actions: {
155
+ flexDirection: "row",
156
+ flexWrap: "wrap",
157
+ gap: 12,
158
+ width: "100%"
159
+ },
160
+ body: {
161
+ textAlign: "center"
162
+ },
163
+ // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
164
+ // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
165
+ // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
166
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
167
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
168
+ card: {
169
+ width: "100%"
170
+ },
171
+ // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
172
+ // from the screen edges now that it reliably stretches to fill its full width allowance (see
173
+ // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
174
+ // screen narrower than maxWidth's own 360.
175
+ overlay: {
176
+ paddingHorizontal: 24
177
+ },
178
+ // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
179
+ // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
180
+ // line inside that block still defaults to left-aligned without this.
181
+ title: {
182
+ fontWeight: "bold",
183
+ textAlign: "center"
184
+ }
185
+ });
186
+
187
+ // src/useUpdateConfirmBridge.ts
188
+ import { useCallback, useRef, useState } from "react";
189
+ function useUpdateConfirmBridge() {
190
+ const [manifest, setManifest] = useState(null);
191
+ const resolveRef = useRef(null);
192
+ const onConfirm = useCallback(
193
+ (next) => new Promise((resolve) => {
194
+ resolveRef.current = resolve;
195
+ setManifest(next);
196
+ }),
197
+ []
198
+ );
199
+ const respond = useCallback((confirmed) => {
200
+ setManifest(null);
201
+ resolveRef.current?.(confirmed);
202
+ resolveRef.current = null;
203
+ }, []);
204
+ return { manifest, onConfirm, respond };
205
+ }
206
+
207
+ // src/BaseSettingsDialog.tsx
208
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
82
209
  function SettingIcon({ source, color, containerColor }) {
83
- return /* @__PURE__ */ jsx2(View2, { style: [styles2.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx2(Icon2, { source, size: 18, color }) });
210
+ return /* @__PURE__ */ jsx3(View3, { style: [styles3.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx3(Icon3, { source, size: 18, color }) });
84
211
  }
85
212
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
86
- const { dark, colors } = useAutoPaperTheme2();
213
+ const { colors } = useAutoPaperTheme3();
87
214
  const ambientRotation = useRotation();
88
215
  const rotation = rotationOverride ?? ambientRotation;
89
216
  const isTouchPrimary = useIsTouchPrimaryDevice();
@@ -91,43 +218,43 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
91
218
  const { settings: soundSettings, set: setSoundSettings } = useSoundSettings();
92
219
  const sound = useContext(SoundContext);
93
220
  const { forceShort: forceHapticFeedback } = useVibration();
94
- const [infoMessage, setInfoMessage] = useState(null);
221
+ const [infoMessage, setInfoMessage] = useState2(null);
222
+ const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
95
223
  const { check, checking, updateReady } = useUpdater({
96
224
  autoCheck: false,
97
225
  autoPrompt: false,
226
+ onConfirm: onUpdateConfirm,
98
227
  onError: onUpdateError,
99
228
  onInfo: (title, message) => setInfoMessage({ title, message })
100
229
  });
230
+ const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
101
231
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
102
232
  const showEdgeGuard = Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
103
233
  const showHaptics = Platform.OS !== "web" && !hideHaptics;
104
234
  const showSound = !hideSound;
105
235
  const showAppearance = !hideAppearance;
106
236
  const showUpdateCheck = Platform.OS !== "web" && !hideUpdateCheck;
107
- const fg = dark ? "#FFFFFF" : "#000000";
108
- const cardBg = dark ? "#111111" : "#F2F2F2";
109
- const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
110
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
111
- /* @__PURE__ */ jsxs2(Dialog, { visible, onDismiss, style: [styles2.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
112
- /* @__PURE__ */ jsx2(Dialog.Title, { children: "Settings" }),
113
- /* @__PURE__ */ jsx2(Dialog.ScrollArea, { style: styles2.scrollArea, children: /* @__PURE__ */ jsxs2(ScrollView, { contentContainerStyle: styles2.content, showsVerticalScrollIndicator: false, children: [
114
- /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleGroup, children: [
115
- showLockOrientation && /* @__PURE__ */ jsx2(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles2.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleContent, children: [
116
- /* @__PURE__ */ jsx2(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
117
- /* @__PURE__ */ jsxs2(View2, { style: styles2.flexShrink, children: [
118
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
119
- /* @__PURE__ */ jsx2(Text2, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
237
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
238
+ /* @__PURE__ */ jsxs3(Dialog, { visible, onDismiss, style: [styles3.dialog, toRotationStyle2(rotation)], children: [
239
+ /* @__PURE__ */ jsx3(Dialog.Title, { children: "Settings" }),
240
+ /* @__PURE__ */ jsx3(Dialog.ScrollArea, { style: styles3.scrollArea, children: /* @__PURE__ */ jsxs3(ScrollView, { contentContainerStyle: styles3.content, showsVerticalScrollIndicator: false, children: [
241
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleGroup, children: [
242
+ showLockOrientation && /* @__PURE__ */ jsx3(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles3.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
243
+ /* @__PURE__ */ jsx3(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
244
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
245
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
246
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
120
247
  ] })
121
248
  ] }) }),
122
- showEdgeGuard && /* @__PURE__ */ jsx2(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles2.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleContent, children: [
123
- /* @__PURE__ */ jsx2(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
124
- /* @__PURE__ */ jsxs2(View2, { style: styles2.flexShrink, children: [
125
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
126
- /* @__PURE__ */ jsx2(Text2, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
249
+ showEdgeGuard && /* @__PURE__ */ jsx3(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles3.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
250
+ /* @__PURE__ */ jsx3(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
251
+ /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
252
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
253
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
127
254
  ] })
128
255
  ] }) }),
129
- (showSound || showHaptics) && /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleRow, children: [
130
- showSound && /* @__PURE__ */ jsx2(
256
+ (showSound || showHaptics) && /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleRow, children: [
257
+ showSound && /* @__PURE__ */ jsx3(
131
258
  TouchableRipple,
132
259
  {
133
260
  soundDisabled: true,
@@ -136,15 +263,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
136
263
  setSoundSettings({ enabled });
137
264
  if (enabled) sound.selection?.();
138
265
  },
139
- style: styles2.toggleButtonInRow,
266
+ style: styles3.toggleButtonInRow,
140
267
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
141
- children: /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleContent, children: [
142
- /* @__PURE__ */ jsx2(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
143
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
268
+ children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
269
+ /* @__PURE__ */ jsx3(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
270
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
144
271
  ] })
145
272
  }
146
273
  ),
147
- showHaptics && /* @__PURE__ */ jsx2(
274
+ showHaptics && /* @__PURE__ */ jsx3(
148
275
  TouchableRipple,
149
276
  {
150
277
  hapticDisabled: true,
@@ -153,40 +280,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
153
280
  setHapticSettings({ vibrate });
154
281
  if (vibrate) forceHapticFeedback();
155
282
  },
156
- style: styles2.toggleButtonInRow,
283
+ style: styles3.toggleButtonInRow,
157
284
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
158
- children: /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleContent, children: [
159
- /* @__PURE__ */ jsx2(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
160
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
285
+ children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
286
+ /* @__PURE__ */ jsx3(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
287
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
161
288
  ] })
162
289
  }
163
290
  )
164
291
  ] })
165
292
  ] }),
166
- showAppearance && /* @__PURE__ */ jsxs2(View2, { style: styles2.section, children: [
167
- /* @__PURE__ */ jsx2(Text2, { variant: "labelMedium", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
168
- /* @__PURE__ */ jsx2(AutoAppearancePicker, { showLabels: false })
293
+ showAppearance && /* @__PURE__ */ jsxs3(View3, { style: styles3.section, children: [
294
+ /* @__PURE__ */ jsx3(Text3, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
295
+ /* @__PURE__ */ jsx3(AutoAppearancePicker, { showLabels: false })
169
296
  ] }),
170
297
  children,
171
- showUpdateCheck && /* @__PURE__ */ jsxs2(View2, { style: styles2.section, children: [
172
- /* @__PURE__ */ jsxs2(Text2, { variant: "labelSmall", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: [
298
+ showUpdateCheck && /* @__PURE__ */ jsxs3(View3, { style: styles3.section, children: [
299
+ /* @__PURE__ */ jsxs3(Text3, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
173
300
  "VERSION ",
174
301
  version,
175
302
  updateReady ? " \xB7 UPDATE READY" : ""
176
303
  ] }),
177
- /* @__PURE__ */ jsx2(Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
304
+ /* @__PURE__ */ jsx3(Button2, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
178
305
  ] })
179
306
  ] }) })
180
307
  ] }),
181
- infoMessage && /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsxs2(View2, { style: [styles2.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
182
- /* @__PURE__ */ jsx2(Icon2, { source: "information-outline", size: 64, color: colors.secondary }),
183
- /* @__PURE__ */ jsx2(Text2, { variant: "headlineLarge", style: [styles2.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
184
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: [styles2.overlayBody, { color: fg }], children: infoMessage.message }),
185
- /* @__PURE__ */ jsx2(Button, { mode: "contained", onPress: () => setInfoMessage(null), style: styles2.overlayButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: "OK" })
186
- ] }) }) })
308
+ /* @__PURE__ */ jsx3(ConfirmDialog, { visible: !!updateManifest, title: "Update Available", message: updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respondToUpdateConfirm(true), onCancel: () => respondToUpdateConfirm(false) }),
309
+ /* @__PURE__ */ jsx3(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
187
310
  ] });
188
311
  }
189
- var styles2 = StyleSheet2.create({
312
+ var styles3 = StyleSheet4.create({
190
313
  content: {
191
314
  gap: 24,
192
315
  paddingVertical: 20
@@ -208,27 +331,6 @@ var styles2 = StyleSheet2.create({
208
331
  justifyContent: "center",
209
332
  width: 36
210
333
  },
211
- overlay: {
212
- alignItems: "center",
213
- backgroundColor: "rgba(0,0,0,0.72)",
214
- bottom: 0,
215
- justifyContent: "center",
216
- left: 0,
217
- position: "absolute",
218
- right: 0,
219
- top: 0
220
- },
221
- overlayBody: { textAlign: "center" },
222
- overlayButton: { width: 160 },
223
- overlayCard: {
224
- alignItems: "center",
225
- borderRadius: 20,
226
- borderWidth: 1,
227
- gap: 16,
228
- maxWidth: 360,
229
- padding: 32
230
- },
231
- overlayTitle: { fontWeight: "bold" },
232
334
  scrollArea: {
233
335
  marginBottom: 0
234
336
  },
@@ -278,102 +380,15 @@ var styles2 = StyleSheet2.create({
278
380
  import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
279
381
  import { Button as Button3, IconButton } from "@rific/feedback-press";
280
382
  import { useRotation as useRotation2 } from "@tastic/core";
281
- import { useState as useState2 } from "react";
282
- import { ScrollView as ScrollView2, StyleSheet as StyleSheet4, View as View4 } from "react-native";
383
+ import { useState as useState3 } from "react";
384
+ import { ScrollView as ScrollView2, StyleSheet as StyleSheet5, View as View4 } from "react-native";
283
385
  import { Text as Text4 } from "react-native-paper";
284
-
285
- // src/ConfirmDialog.tsx
286
- import { useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
287
- import { Button as Button2 } from "@rific/feedback-press";
288
- import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
289
- import { Icon as Icon3, Portal as Portal2, Text as Text3 } from "react-native-paper";
290
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
291
- function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
292
- const { dark, colors } = useAutoPaperTheme3();
293
- if (!visible) return null;
294
- const fg = dark ? "#FFFFFF" : "#000000";
295
- const cardBg = dark ? "#111111" : "#F2F2F2";
296
- const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
297
- const accent = destructive ? colors.danger : colors.primary;
298
- const onAccent = destructive ? colors.onDanger : colors.onPrimary;
299
- return /* @__PURE__ */ jsx3(Portal2, { children: /* @__PURE__ */ jsx3(View3, { style: styles3.overlay, children: /* @__PURE__ */ jsxs3(View3, { style: [styles3.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
300
- /* @__PURE__ */ jsx3(Icon3, { source: icon, size: 64, color: accent }),
301
- /* @__PURE__ */ jsx3(Text3, { variant: "headlineLarge", style: [styles3.title, { color: accent }], children: title }),
302
- /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: [styles3.body, { color: fg }], children: message }),
303
- /* @__PURE__ */ jsxs3(View3, { style: styles3.actions, children: [
304
- /* @__PURE__ */ jsx3(Button2, { mode: "outlined", onPress: onCancel, style: styles3.actionButton, children: cancelLabel }),
305
- /* @__PURE__ */ jsx3(Button2, { mode: "contained", onPress: onConfirm, style: styles3.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
306
- ] })
307
- ] }) }) });
308
- }
309
- var styles3 = StyleSheet3.create({
310
- // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
311
- // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
312
- // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
313
- // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
314
- // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
315
- // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
316
- // fixable by just giving the row more width, until confirmed live that these two-word labels
317
- // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
318
- // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
319
- // width there's no side-by-side arrangement that fits both buttons at their natural label size,
320
- // so the layout has to give (stack) rather than the label (truncate).
321
- actionButton: {
322
- flexBasis: 128,
323
- flexGrow: 1
324
- },
325
- actions: {
326
- flexDirection: "row",
327
- flexWrap: "wrap",
328
- gap: 12,
329
- width: "100%"
330
- },
331
- body: {
332
- textAlign: "center"
333
- },
334
- card: {
335
- alignItems: "center",
336
- borderRadius: 20,
337
- borderWidth: 1,
338
- gap: 16,
339
- maxWidth: 360,
340
- padding: 32,
341
- // Claims its full allowance instead of just maxWidth alone, which only caps how wide the card
342
- // is allowed to get - without an explicit width, a flex-centered child sizes to its own content
343
- // instead of stretching, so the card (and therefore actions below) can end up narrower than the
344
- // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
345
- width: "100%"
346
- },
347
- overlay: {
348
- alignItems: "center",
349
- backgroundColor: "rgba(0,0,0,0.72)",
350
- bottom: 0,
351
- justifyContent: "center",
352
- left: 0,
353
- // Gives the card room to breathe from the screen edges now that it reliably stretches to fill
354
- // its full width allowance (see card's own width comment) - without this, width: '100%' would
355
- // run the card edge-to-edge on a screen narrower than maxWidth's own 360.
356
- paddingHorizontal: 24,
357
- position: "absolute",
358
- right: 0,
359
- top: 0
360
- },
361
- // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
362
- // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
363
- // line inside that block still defaults to left-aligned without this.
364
- title: {
365
- fontWeight: "bold",
366
- textAlign: "center"
367
- }
368
- });
369
-
370
- // src/BaseStatsScreen.tsx
371
386
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
372
387
  function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", fg: fgOverride, bg: bgOverride, children }) {
373
388
  const { dark, colors } = useAutoPaperTheme4();
374
389
  const ambientRotation = useRotation2();
375
390
  const rotation = rotationOverride ?? ambientRotation;
376
- const [confirmResetVisible, setConfirmResetVisible] = useState2(false);
391
+ const [confirmResetVisible, setConfirmResetVisible] = useState3(false);
377
392
  const showReset = !!onReset;
378
393
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
379
394
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
@@ -406,7 +421,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
406
421
  )
407
422
  ] });
408
423
  }
409
- var styles4 = StyleSheet4.create({
424
+ var styles4 = StyleSheet5.create({
410
425
  container: {
411
426
  flex: 1
412
427
  },
@@ -438,7 +453,7 @@ var styles4 = StyleSheet4.create({
438
453
 
439
454
  // src/ContentGutter.tsx
440
455
  import { computeContentBounds } from "@tastic/core";
441
- import { StyleSheet as StyleSheet5, useWindowDimensions, View as View5 } from "react-native";
456
+ import { StyleSheet as StyleSheet6, useWindowDimensions, View as View5 } from "react-native";
442
457
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
443
458
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
444
459
  const { width: windowWidth } = useWindowDimensions();
@@ -449,7 +464,7 @@ function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, childr
449
464
  /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
450
465
  ] });
451
466
  }
452
- var styles5 = StyleSheet5.create({
467
+ var styles5 = StyleSheet6.create({
453
468
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
454
469
  // column instead of clipping cleanly at its own real width.
455
470
  gutter: {
@@ -468,7 +483,7 @@ import { Platform as Platform3 } from "react-native";
468
483
  import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
469
484
  import { IconButton as IconButton2, TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
470
485
  import { useRotation as useRotation3 } from "@tastic/core";
471
- import { ScrollView as ScrollView3, StyleSheet as StyleSheet7, View as View8 } from "react-native";
486
+ import { ScrollView as ScrollView3, StyleSheet as StyleSheet8, View as View8 } from "react-native";
472
487
  import { Icon as Icon4, Text as Text5 } from "react-native-paper";
473
488
 
474
489
  // src/fonts.ts
@@ -476,7 +491,7 @@ import { Platform as Platform2 } from "react-native";
476
491
  var MONO_FONT = Platform2.select({ ios: "Menlo", android: "monospace", default: "monospace" });
477
492
 
478
493
  // src/PopoverBody.tsx
479
- import { StyleSheet as StyleSheet6, View as View6 } from "react-native";
494
+ import { StyleSheet as StyleSheet7, View as View6 } from "react-native";
480
495
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
481
496
  var CARET_SIZE = 7;
482
497
  var CARET_RING_OFFSET = 2;
@@ -499,7 +514,7 @@ function PopoverBody({ visible, children, align = "center", verticalAlign = "bel
499
514
  ] })
500
515
  ] });
501
516
  }
502
- var styles6 = StyleSheet6.create({
517
+ var styles6 = StyleSheet7.create({
503
518
  caret: {
504
519
  borderLeftColor: "transparent",
505
520
  borderRightColor: "transparent",
@@ -549,7 +564,7 @@ function TriggerGaugeHost(props) {
549
564
  }
550
565
 
551
566
  // src/useAutoAlign.ts
552
- import { useCallback, useEffect, useRef, useState as useState3 } from "react";
567
+ import { useCallback as useCallback2, useEffect, useRef as useRef2, useState as useState4 } from "react";
553
568
  import { useWindowDimensions as useWindowDimensions2 } from "react-native";
554
569
  var EDGE_MARGIN = 12;
555
570
  function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
@@ -562,18 +577,18 @@ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
562
577
  return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
563
578
  }
564
579
  function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
565
- const triggerRef = useRef(null);
580
+ const triggerRef = useRef2(null);
566
581
  const { width: windowWidth, height: windowHeight } = useWindowDimensions2();
567
- const [align, setAlign] = useState3("center");
568
- const [verticalAlign, setVerticalAlign] = useState3("below");
569
- const [maxHeight, setMaxHeight] = useState3(contentHeight);
570
- const [measured, setMeasured] = useState3(false);
571
- const [prevOpen, setPrevOpen] = useState3(open);
582
+ const [align, setAlign] = useState4("center");
583
+ const [verticalAlign, setVerticalAlign] = useState4("below");
584
+ const [maxHeight, setMaxHeight] = useState4(contentHeight);
585
+ const [measured, setMeasured] = useState4(false);
586
+ const [prevOpen, setPrevOpen] = useState4(open);
572
587
  if (open !== prevOpen) {
573
588
  setPrevOpen(open);
574
589
  if (open) setMeasured(false);
575
590
  }
576
- const measure = useCallback(() => {
591
+ const measure = useCallback2(() => {
577
592
  triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
578
593
  const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
579
594
  const centerX = x + triggerWidth / 2;
@@ -721,7 +736,7 @@ function MultiSection({
721
736
  section.allClear && /* @__PURE__ */ jsx8(TouchableRipple2, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles7.item, children: /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: [styles7.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
722
737
  ] });
723
738
  }
724
- var styles7 = StyleSheet7.create({
739
+ var styles7 = StyleSheet8.create({
725
740
  allClearLabel: {
726
741
  fontWeight: "bold",
727
742
  textAlign: "center"
@@ -738,7 +753,7 @@ var styles7 = StyleSheet7.create({
738
753
  zIndex: 100
739
754
  },
740
755
  divider: {
741
- height: StyleSheet7.hairlineWidth,
756
+ height: StyleSheet8.hairlineWidth,
742
757
  marginVertical: 4
743
758
  },
744
759
  item: {
@@ -800,7 +815,7 @@ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, a
800
815
 
801
816
  // src/CornerActionButtons.tsx
802
817
  import { IconButton as IconButton3 } from "@rific/feedback-press";
803
- import { StyleSheet as StyleSheet8 } from "react-native";
818
+ import { StyleSheet as StyleSheet9 } from "react-native";
804
819
  import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
805
820
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
806
821
  return /* @__PURE__ */ jsxs8(Fragment4, { children: [
@@ -808,7 +823,7 @@ function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow
808
823
  /* @__PURE__ */ jsx10(IconButton3, { icon: "cog", iconColor: fg, size: 24, style: [styles8.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
809
824
  ] });
810
825
  }
811
- var styles8 = StyleSheet8.create({
826
+ var styles8 = StyleSheet9.create({
812
827
  back: {
813
828
  position: "absolute"
814
829
  },
@@ -818,7 +833,7 @@ var styles8 = StyleSheet8.create({
818
833
  });
819
834
 
820
835
  // src/CornerStatusBadges.tsx
821
- import { StyleSheet as StyleSheet9, View as View9 } from "react-native";
836
+ import { StyleSheet as StyleSheet10, View as View9 } from "react-native";
822
837
  import { Icon as Icon5 } from "react-native-paper";
823
838
  import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
824
839
  var BADGE_SIZE = 30;
@@ -833,7 +848,7 @@ function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
833
848
  p2 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p2OnLeftSide ? styles9.topLeft : styles9.topRight, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p2.icon }) })
834
849
  ] });
835
850
  }
836
- var styles9 = StyleSheet9.create({
851
+ var styles9 = StyleSheet10.create({
837
852
  badge: {
838
853
  alignItems: "center",
839
854
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -855,7 +870,7 @@ var styles9 = StyleSheet9.create({
855
870
  import { defaultColors, getContrastColor } from "@rific/auto-paper";
856
871
  import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
857
872
  import { clamp, useRotation as useRotation4 } from "@tastic/core";
858
- import { ScrollView as ScrollView4, StyleSheet as StyleSheet10, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
873
+ import { ScrollView as ScrollView4, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
859
874
  import { Icon as Icon6, Text as Text6 } from "react-native-paper";
860
875
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
861
876
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
@@ -911,7 +926,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
911
926
  }) }) })
912
927
  ] });
913
928
  }
914
- var styles10 = StyleSheet10.create({
929
+ var styles10 = StyleSheet11.create({
915
930
  anchor: {
916
931
  position: "relative"
917
932
  },
@@ -985,7 +1000,7 @@ var styles10 = StyleSheet10.create({
985
1000
  // src/LabeledDropdown.tsx
986
1001
  import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
987
1002
  import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
988
- import { ScrollView as ScrollView5, StyleSheet as StyleSheet11, View as View11 } from "react-native";
1003
+ import { ScrollView as ScrollView5, StyleSheet as StyleSheet12, View as View11 } from "react-native";
989
1004
  import { Icon as Icon7, Text as Text7 } from "react-native-paper";
990
1005
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
991
1006
  var TRIGGER_HEIGHT = 28;
@@ -1025,7 +1040,7 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1025
1040
  }) }) })
1026
1041
  ] });
1027
1042
  }
1028
- var styles11 = StyleSheet11.create({
1043
+ var styles11 = StyleSheet12.create({
1029
1044
  anchor: {
1030
1045
  position: "relative"
1031
1046
  },
@@ -1083,16 +1098,16 @@ function loadSkiaWeb() {
1083
1098
  }
1084
1099
 
1085
1100
  // src/PressAwayOverlay.tsx
1086
- import { Pressable, StyleSheet as StyleSheet12 } from "react-native";
1101
+ import { Pressable, StyleSheet as StyleSheet13 } from "react-native";
1087
1102
  import { jsx as jsx14 } from "react/jsx-runtime";
1088
1103
  function PressAwayOverlay({ active, onPress, style }) {
1089
1104
  if (!active) return null;
1090
- return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet12.absoluteFill, style] });
1105
+ return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet13.absoluteFill, style] });
1091
1106
  }
1092
1107
 
1093
1108
  // src/ReadyButton.tsx
1094
1109
  import { TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
1095
- import { StyleSheet as StyleSheet13 } from "react-native";
1110
+ import { StyleSheet as StyleSheet14 } from "react-native";
1096
1111
  import { Text as Text8 } from "react-native-paper";
1097
1112
  import { jsx as jsx15 } from "react/jsx-runtime";
1098
1113
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
@@ -1101,7 +1116,7 @@ function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MON
1101
1116
  const labelColorStyle = { color: ready ? "#000000" : color };
1102
1117
  return /* @__PURE__ */ jsx15(TouchableRipple5, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx15(Text8, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1103
1118
  }
1104
- var styles12 = StyleSheet13.create({
1119
+ var styles12 = StyleSheet14.create({
1105
1120
  readyButton: {
1106
1121
  alignItems: "center",
1107
1122
  borderRadius: 12,
@@ -1115,7 +1130,7 @@ var styles12 = StyleSheet13.create({
1115
1130
 
1116
1131
  // src/SharedActionBand.tsx
1117
1132
  import { IconButton as IconButton4 } from "@rific/feedback-press";
1118
- import { StyleSheet as StyleSheet14, View as View12 } from "react-native";
1133
+ import { StyleSheet as StyleSheet15, View as View12 } from "react-native";
1119
1134
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1120
1135
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1121
1136
  return /* @__PURE__ */ jsxs12(View12, { style: [styles13.column, popoverOpen && styles13.columnOpen], children: [
@@ -1128,7 +1143,7 @@ function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popove
1128
1143
  children
1129
1144
  ] });
1130
1145
  }
1131
- var styles13 = StyleSheet14.create({
1146
+ var styles13 = StyleSheet15.create({
1132
1147
  actionsRow: {
1133
1148
  alignItems: "center",
1134
1149
  flexDirection: "row",
@@ -1145,7 +1160,7 @@ var styles13 = StyleSheet14.create({
1145
1160
 
1146
1161
  // src/StatRow.tsx
1147
1162
  import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1148
- import { StyleSheet as StyleSheet15, View as View13 } from "react-native";
1163
+ import { StyleSheet as StyleSheet16, View as View13 } from "react-native";
1149
1164
  import { Text as Text9 } from "react-native-paper";
1150
1165
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1151
1166
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
@@ -1157,7 +1172,7 @@ function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1157
1172
  /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: [styles14.value, { color: fg }], children: value })
1158
1173
  ] });
1159
1174
  }
1160
- var styles14 = StyleSheet15.create({
1175
+ var styles14 = StyleSheet16.create({
1161
1176
  row: {
1162
1177
  alignItems: "center",
1163
1178
  flexDirection: "row",
@@ -1170,7 +1185,7 @@ var styles14 = StyleSheet15.create({
1170
1185
 
1171
1186
  // src/StatSection.tsx
1172
1187
  import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1173
- import { StyleSheet as StyleSheet16, View as View14 } from "react-native";
1188
+ import { StyleSheet as StyleSheet17, View as View14 } from "react-native";
1174
1189
  import { Text as Text10 } from "react-native-paper";
1175
1190
  import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1176
1191
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
@@ -1182,7 +1197,7 @@ function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, chil
1182
1197
  children
1183
1198
  ] });
1184
1199
  }
1185
- var styles15 = StyleSheet16.create({
1200
+ var styles15 = StyleSheet17.create({
1186
1201
  // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1187
1202
  // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1188
1203
  // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
@@ -1198,14 +1213,24 @@ var styles15 = StyleSheet16.create({
1198
1213
  }
1199
1214
  });
1200
1215
 
1216
+ // src/UpdateDialog.tsx
1217
+ import { useUpdater as useUpdater2 } from "@rific/updater";
1218
+ import { jsx as jsx19 } from "react/jsx-runtime";
1219
+ function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1220
+ const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1221
+ useUpdater2({ autoCheck, autoPrompt, onConfirm, onError });
1222
+ const date = manifest ? new Date(manifest.createdAt) : null;
1223
+ return /* @__PURE__ */ jsx19(ConfirmDialog, { visible: !!manifest, title: "Update Available", message: date ? `Released ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respond(true), onCancel: () => respond(false) });
1224
+ }
1225
+
1201
1226
  // src/usePopoverHost.ts
1202
- import { useCallback as useCallback2, useState as useState4 } from "react";
1227
+ import { useCallback as useCallback3, useState as useState5 } from "react";
1203
1228
  function usePopoverHost() {
1204
- const [openId, setOpenId] = useState4(null);
1205
- const toggle = useCallback2((id) => {
1229
+ const [openId, setOpenId] = useState5(null);
1230
+ const toggle = useCallback3((id) => {
1206
1231
  setOpenId((prev) => prev === id ? null : id);
1207
1232
  }, []);
1208
- const close = useCallback2(() => setOpenId(null), []);
1233
+ const close = useCallback3(() => setOpenId(null), []);
1209
1234
  return { openId, toggle, close };
1210
1235
  }
1211
1236
  export {
@@ -1231,8 +1256,11 @@ export {
1231
1256
  StatRow,
1232
1257
  StatSection,
1233
1258
  TriggerGaugeHost,
1259
+ UpdateDialog,
1234
1260
  getLabeledDropdownContentHeight,
1261
+ getOverlayCardColors,
1235
1262
  loadSkiaWeb,
1263
+ overlayStyles,
1236
1264
  useAutoAlign,
1237
1265
  usePopoverHost
1238
1266
  };