@tastic/hud 0.8.4 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,6 +2,12 @@ import {
2
2
  SkiaGate
3
3
  } from "./chunk-XEOFR236.mjs";
4
4
 
5
+ // src/AchievementCatalogSection.tsx
6
+ import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
7
+ import { Fragment } from "react";
8
+ import { StyleSheet as StyleSheet2 } from "react-native";
9
+ import { Text as Text2 } from "react-native-paper";
10
+
5
11
  // src/AchievementRow.tsx
6
12
  import { useAutoPaperTheme } from "@rific/auto-paper";
7
13
  import { StyleSheet, View } from "react-native";
@@ -70,24 +76,110 @@ var styles = StyleSheet.create({
70
76
  }
71
77
  });
72
78
 
79
+ // src/fonts.ts
80
+ import { Platform } from "react-native";
81
+ var MONO_FONT = Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
82
+
83
+ // src/AchievementCatalogSection.tsx
84
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
85
+ function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
86
+ const { dark } = useAutoPaperTheme2();
87
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
88
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
89
+ /* @__PURE__ */ jsx2(Text2, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
90
+ rows.map((row) => /* @__PURE__ */ jsx2(AchievementRow, { icon: row.icon, title: row.title, description: row.description, badgeColor: row.unlockedAt !== void 0 ? row.tierColor : LOCKED_BADGE_COLOR, checkColor: row.tierColor, unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride }, row.id))
91
+ ] });
92
+ }
93
+ var styles2 = StyleSheet2.create({
94
+ label: {
95
+ letterSpacing: 2,
96
+ marginTop: 8
97
+ }
98
+ });
99
+
100
+ // src/StatRow.tsx
101
+ import { useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
102
+ import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
103
+ import { Text as Text3 } from "react-native-paper";
104
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
105
+ function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
106
+ const { dark } = useAutoPaperTheme3();
107
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
108
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
109
+ return /* @__PURE__ */ jsxs3(View2, { style: styles3.row, children: [
110
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
111
+ /* @__PURE__ */ jsx3(Text3, { variant: "bodyMedium", style: [styles3.value, { color: fg }], children: value })
112
+ ] });
113
+ }
114
+ var styles3 = StyleSheet3.create({
115
+ row: {
116
+ alignItems: "center",
117
+ flexDirection: "row",
118
+ justifyContent: "space-between"
119
+ },
120
+ value: {
121
+ fontWeight: "bold"
122
+ }
123
+ });
124
+
125
+ // src/StatSection.tsx
126
+ import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
127
+ import { StyleSheet as StyleSheet4, View as View3 } from "react-native";
128
+ import { Text as Text4 } from "react-native-paper";
129
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
130
+ function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
131
+ const { dark } = useAutoPaperTheme4();
132
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
133
+ const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
134
+ return /* @__PURE__ */ jsxs4(View3, { style: [styles4.section, { backgroundColor: sectionBg }], children: [
135
+ /* @__PURE__ */ jsx4(Text4, { variant: "labelMedium", style: [styles4.label, { color: fg }], children: label }),
136
+ children
137
+ ] });
138
+ }
139
+ var styles4 = StyleSheet4.create({
140
+ // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
141
+ // same visual weight as StatRow's own row labels below it (both used the identical muted color;
142
+ // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
143
+ // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
144
+ label: {
145
+ fontWeight: "bold",
146
+ letterSpacing: 2
147
+ },
148
+ section: {
149
+ borderRadius: 12,
150
+ gap: 8,
151
+ padding: 16
152
+ }
153
+ });
154
+
155
+ // src/ActivityStatSection.tsx
156
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
157
+ function ActivityStatSection({ stats }) {
158
+ return /* @__PURE__ */ jsxs5(StatSection, { label: "ACTIVITY", children: [
159
+ /* @__PURE__ */ jsx5(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
160
+ /* @__PURE__ */ jsx5(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
161
+ /* @__PURE__ */ jsx5(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
162
+ ] });
163
+ }
164
+
73
165
  // src/BaseSettingsDialog.tsx
74
- import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
166
+ import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
75
167
  import { Button as Button2, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
76
168
  import { useUpdater } from "@rific/updater";
77
169
  import { toRotationStyle as toRotationStyle2, useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
78
170
  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";
171
+ import { Platform as Platform2, ScrollView, StyleSheet as StyleSheet7, View as View5 } from "react-native";
172
+ import { Icon as Icon3, Text as Text6 } from "react-native-paper";
81
173
 
82
174
  // src/ConfirmDialog.tsx
83
- import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
175
+ import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
84
176
  import { Button } from "@rific/feedback-press";
85
177
  import { toRotationStyle } from "@tastic/core";
86
- import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
87
- import { Icon as Icon2, Portal, Text as Text2 } from "react-native-paper";
178
+ import { StyleSheet as StyleSheet6, View as View4 } from "react-native";
179
+ import { Icon as Icon2, Portal, Text as Text5 } from "react-native-paper";
88
180
 
89
181
  // src/overlayCard.ts
90
- import { StyleSheet as StyleSheet2 } from "react-native";
182
+ import { StyleSheet as StyleSheet5 } from "react-native";
91
183
  function getOverlayCardColors(dark) {
92
184
  return {
93
185
  fg: dark ? "#FFFFFF" : "#000000",
@@ -96,7 +188,7 @@ function getOverlayCardColors(dark) {
96
188
  cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
97
189
  };
98
190
  }
99
- var overlayStyles = StyleSheet2.create({
191
+ var overlayStyles = StyleSheet5.create({
100
192
  card: {
101
193
  alignItems: "center",
102
194
  borderRadius: 20,
@@ -118,43 +210,62 @@ var overlayStyles = StyleSheet2.create({
118
210
  });
119
211
 
120
212
  // src/ConfirmDialog.tsx
121
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
213
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
122
214
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
123
- const { dark, colors } = useAutoPaperTheme2();
215
+ const { dark, colors } = useAutoPaperTheme5();
124
216
  if (!visible) return null;
125
217
  const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
126
218
  const accent = destructive ? colors.danger : colors.primary;
127
219
  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 })
220
+ const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
221
+ return /* @__PURE__ */ jsx6(Portal, { children: /* @__PURE__ */ jsx6(View4, { style: [overlayStyles.overlay, styles5.overlay], children: /* @__PURE__ */ jsxs6(View4, { style: [overlayStyles.card, styles5.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)], children: [
222
+ /* @__PURE__ */ jsx6(Icon2, { source: icon, size: 64, color: accent }),
223
+ /* @__PURE__ */ jsx6(Text5, { variant: "headlineLarge", style: [styles5.title, { color: accent }], children: title }),
224
+ /* @__PURE__ */ jsx6(Text5, { variant: "bodyLarge", style: [styles5.body, { color: fg }], children: message }),
225
+ /* @__PURE__ */ jsxs6(View4, { style: styles5.actions, children: [
226
+ showCancel && /* @__PURE__ */ jsx6(Button, { mode: "outlined", onPress: onCancel, style: styles5.actionButton, children: cancelLabel }),
227
+ /* @__PURE__ */ jsx6(Button, { mode: "contained", onPress: onConfirm, style: showCancel ? styles5.actionButton : styles5.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
135
228
  ] })
136
229
  ] }) }) });
137
230
  }
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
231
+ var styles5 = StyleSheet6.create({
232
+ // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
233
+ // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
234
+ // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
235
+ // row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
236
+ // correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
237
+ // "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
238
+ // tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
239
+ // nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
240
+ // allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
241
+ // flexBasis) reaches the outer view, which is the real flex item inside this row; every other
242
+ // key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
243
+ // defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
244
+ // correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
245
+ // sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
246
+ // correctly regardless of which of the two views it ends up on. react-native-paper's Button also
143
247
  // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
144
248
  // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
145
249
  // fixable by just giving the row more width, until confirmed live that these two-word labels
146
250
  // (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).
251
+ // width fix below provides, on a narrow enough screen.
150
252
  actionButton: {
151
- flexBasis: 128,
152
- flexGrow: 1
253
+ flexGrow: 1,
254
+ minWidth: 128
255
+ },
256
+ // A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
257
+ // update-check "no update found" message) has nothing left to share actions' width with, so
258
+ // actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
259
+ // normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
260
+ // the card - centered by actions' own justifyContent below.
261
+ actionButtonSingle: {
262
+ minWidth: 128
153
263
  },
154
264
  actions: {
155
265
  flexDirection: "row",
156
266
  flexWrap: "wrap",
157
267
  gap: 12,
268
+ justifyContent: "center",
158
269
  width: "100%"
159
270
  },
160
271
  body: {
@@ -205,12 +316,12 @@ function useUpdateConfirmBridge() {
205
316
  }
206
317
 
207
318
  // src/BaseSettingsDialog.tsx
208
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
319
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
209
320
  function SettingIcon({ source, color, containerColor }) {
210
- return /* @__PURE__ */ jsx3(View3, { style: [styles3.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx3(Icon3, { source, size: 18, color }) });
321
+ return /* @__PURE__ */ jsx7(View5, { style: [styles6.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx7(Icon3, { source, size: 18, color }) });
211
322
  }
212
323
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
213
- const { colors } = useAutoPaperTheme3();
324
+ const { colors } = useAutoPaperTheme6();
214
325
  const ambientRotation = useRotation();
215
326
  const rotation = rotationOverride ?? ambientRotation;
216
327
  const isTouchPrimary = useIsTouchPrimaryDevice();
@@ -229,32 +340,32 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
229
340
  });
230
341
  const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
231
342
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
232
- const showEdgeGuard = Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
233
- const showHaptics = Platform.OS !== "web" && !hideHaptics;
343
+ const showEdgeGuard = Platform2.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
344
+ const showHaptics = Platform2.OS !== "web" && !hideHaptics;
234
345
  const showSound = !hideSound;
235
346
  const showAppearance = !hideAppearance;
236
- const showUpdateCheck = Platform.OS !== "web" && !hideUpdateCheck;
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" })
347
+ const showUpdateCheck = Platform2.OS !== "web" && !hideUpdateCheck;
348
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
349
+ /* @__PURE__ */ jsxs7(Dialog, { visible, onDismiss, style: [styles6.dialog, toRotationStyle2(rotation)], children: [
350
+ /* @__PURE__ */ jsx7(Dialog.Title, { children: "Settings" }),
351
+ /* @__PURE__ */ jsx7(Dialog.ScrollArea, { style: styles6.scrollArea, children: /* @__PURE__ */ jsxs7(ScrollView, { contentContainerStyle: styles6.content, showsVerticalScrollIndicator: false, children: [
352
+ /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleGroup, children: [
353
+ showLockOrientation && /* @__PURE__ */ jsx7(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles6.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleContent, children: [
354
+ /* @__PURE__ */ jsx7(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
355
+ /* @__PURE__ */ jsxs7(View5, { style: styles6.flexShrink, children: [
356
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
357
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
247
358
  ] })
248
359
  ] }) }),
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" })
360
+ showEdgeGuard && /* @__PURE__ */ jsx7(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles6.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleContent, children: [
361
+ /* @__PURE__ */ jsx7(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
362
+ /* @__PURE__ */ jsxs7(View5, { style: styles6.flexShrink, children: [
363
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
364
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
254
365
  ] })
255
366
  ] }) }),
256
- (showSound || showHaptics) && /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleRow, children: [
257
- showSound && /* @__PURE__ */ jsx3(
367
+ (showSound || showHaptics) && /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleRow, children: [
368
+ showSound && /* @__PURE__ */ jsx7(
258
369
  TouchableRipple,
259
370
  {
260
371
  soundDisabled: true,
@@ -263,15 +374,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
263
374
  setSoundSettings({ enabled });
264
375
  if (enabled) sound.selection?.();
265
376
  },
266
- style: styles3.toggleButtonInRow,
377
+ style: styles6.toggleButtonInRow,
267
378
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
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" })
379
+ children: /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleContent, children: [
380
+ /* @__PURE__ */ jsx7(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
381
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
271
382
  ] })
272
383
  }
273
384
  ),
274
- showHaptics && /* @__PURE__ */ jsx3(
385
+ showHaptics && /* @__PURE__ */ jsx7(
275
386
  TouchableRipple,
276
387
  {
277
388
  hapticDisabled: true,
@@ -280,36 +391,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
280
391
  setHapticSettings({ vibrate });
281
392
  if (vibrate) forceHapticFeedback();
282
393
  },
283
- style: styles3.toggleButtonInRow,
394
+ style: styles6.toggleButtonInRow,
284
395
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
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" })
396
+ children: /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleContent, children: [
397
+ /* @__PURE__ */ jsx7(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
398
+ /* @__PURE__ */ jsx7(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
288
399
  ] })
289
400
  }
290
401
  )
291
402
  ] })
292
403
  ] }),
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 })
404
+ showAppearance && /* @__PURE__ */ jsxs7(View5, { style: styles6.section, children: [
405
+ /* @__PURE__ */ jsx7(Text6, { variant: "labelMedium", style: [styles6.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
406
+ /* @__PURE__ */ jsx7(AutoAppearancePicker, { showLabels: false })
296
407
  ] }),
297
408
  children,
298
- showUpdateCheck && /* @__PURE__ */ jsxs3(View3, { style: styles3.section, children: [
299
- /* @__PURE__ */ jsxs3(Text3, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
409
+ showUpdateCheck && /* @__PURE__ */ jsxs7(View5, { style: styles6.section, children: [
410
+ /* @__PURE__ */ jsxs7(Text6, { variant: "labelSmall", style: [styles6.sectionLabel, { color: colors.onSurfaceVariant }], children: [
300
411
  "VERSION ",
301
412
  version,
302
413
  updateReady ? " \xB7 UPDATE READY" : ""
303
414
  ] }),
304
- /* @__PURE__ */ jsx3(Button2, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
415
+ /* @__PURE__ */ jsx7(Button2, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
305
416
  ] })
306
417
  ] }) })
307
418
  ] }),
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) })
419
+ /* @__PURE__ */ jsx7(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) }),
420
+ /* @__PURE__ */ jsx7(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
310
421
  ] });
311
422
  }
312
- var styles3 = StyleSheet4.create({
423
+ var styles6 = StyleSheet7.create({
313
424
  content: {
314
425
  gap: 24,
315
426
  paddingVertical: 20
@@ -377,31 +488,31 @@ var styles3 = StyleSheet4.create({
377
488
  });
378
489
 
379
490
  // src/BaseStatsScreen.tsx
380
- import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
491
+ import { useAutoPaperTheme as useAutoPaperTheme7 } from "@rific/auto-paper";
381
492
  import { Button as Button3, IconButton } from "@rific/feedback-press";
382
493
  import { useRotation as useRotation2 } from "@tastic/core";
383
494
  import { useState as useState3 } from "react";
384
- import { ScrollView as ScrollView2, StyleSheet as StyleSheet5, View as View4 } from "react-native";
385
- import { Text as Text4 } from "react-native-paper";
386
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
495
+ import { ScrollView as ScrollView2, StyleSheet as StyleSheet8, View as View6 } from "react-native";
496
+ import { Text as Text7 } from "react-native-paper";
497
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
387
498
  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 }) {
388
- const { dark, colors } = useAutoPaperTheme4();
499
+ const { dark, colors } = useAutoPaperTheme7();
389
500
  const ambientRotation = useRotation2();
390
501
  const rotation = rotationOverride ?? ambientRotation;
391
502
  const [confirmResetVisible, setConfirmResetVisible] = useState3(false);
392
503
  const showReset = !!onReset;
393
504
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
394
505
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
395
- return /* @__PURE__ */ jsxs4(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
396
- /* @__PURE__ */ jsxs4(View4, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
397
- /* @__PURE__ */ jsx4(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
398
- /* @__PURE__ */ jsx4(Text4, { variant: titleVariant, style: [styles4.title, { color: fg }], children: title })
506
+ return /* @__PURE__ */ jsxs8(View6, { style: [styles7.container, { backgroundColor: bg }], children: [
507
+ /* @__PURE__ */ jsxs8(View6, { style: [styles7.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
508
+ /* @__PURE__ */ jsx8(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
509
+ /* @__PURE__ */ jsx8(Text7, { variant: titleVariant, style: [styles7.title, { color: fg }], children: title })
399
510
  ] }),
400
- /* @__PURE__ */ jsxs4(ScrollView2, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
511
+ /* @__PURE__ */ jsxs8(ScrollView2, { style: styles7.scrollView, contentContainerStyle: [styles7.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
401
512
  children,
402
- showReset && /* @__PURE__ */ jsx4(Button3, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles4.resetButton, children: resetLabel })
513
+ showReset && /* @__PURE__ */ jsx8(Button3, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles7.resetButton, children: resetLabel })
403
514
  ] }),
404
- /* @__PURE__ */ jsx4(
515
+ /* @__PURE__ */ jsx8(
405
516
  ConfirmDialog,
406
517
  {
407
518
  visible: confirmResetVisible,
@@ -421,7 +532,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
421
532
  )
422
533
  ] });
423
534
  }
424
- var styles4 = StyleSheet5.create({
535
+ var styles7 = StyleSheet8.create({
425
536
  container: {
426
537
  flex: 1
427
538
  },
@@ -453,18 +564,18 @@ var styles4 = StyleSheet5.create({
453
564
 
454
565
  // src/ContentGutter.tsx
455
566
  import { computeContentBounds } from "@tastic/core";
456
- import { StyleSheet as StyleSheet6, useWindowDimensions, View as View5 } from "react-native";
457
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
567
+ import { StyleSheet as StyleSheet9, useWindowDimensions, View as View7 } from "react-native";
568
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
458
569
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
459
570
  const { width: windowWidth } = useWindowDimensions();
460
571
  const { contentWidth, gutterWidth } = computeContentBounds(windowWidth, maxContentWidth ?? Infinity);
461
- return /* @__PURE__ */ jsxs5(View5, { style: [styles5.row, style], children: [
462
- /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
463
- /* @__PURE__ */ jsx5(View5, { style: { width: contentWidth }, children }),
464
- /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
572
+ return /* @__PURE__ */ jsxs9(View7, { style: [styles8.row, style], children: [
573
+ /* @__PURE__ */ jsx9(View7, { style: [styles8.gutter, { width: gutterWidth }], children: leftGutter }),
574
+ /* @__PURE__ */ jsx9(View7, { style: { width: contentWidth }, children }),
575
+ /* @__PURE__ */ jsx9(View7, { style: [styles8.gutter, { width: gutterWidth }], children: rightGutter })
465
576
  ] });
466
577
  }
467
- var styles5 = StyleSheet6.create({
578
+ var styles8 = StyleSheet9.create({
468
579
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
469
580
  // column instead of clipping cleanly at its own real width.
470
581
  gutter: {
@@ -483,38 +594,34 @@ import { Platform as Platform3 } from "react-native";
483
594
  import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
484
595
  import { IconButton as IconButton2, TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
485
596
  import { useRotation as useRotation3 } from "@tastic/core";
486
- import { ScrollView as ScrollView3, StyleSheet as StyleSheet8, View as View8 } from "react-native";
487
- import { Icon as Icon4, Text as Text5 } from "react-native-paper";
488
-
489
- // src/fonts.ts
490
- import { Platform as Platform2 } from "react-native";
491
- var MONO_FONT = Platform2.select({ ios: "Menlo", android: "monospace", default: "monospace" });
597
+ import { ScrollView as ScrollView3, StyleSheet as StyleSheet11, View as View10 } from "react-native";
598
+ import { Icon as Icon4, Text as Text8 } from "react-native-paper";
492
599
 
493
600
  // src/PopoverBody.tsx
494
- import { StyleSheet as StyleSheet7, View as View6 } from "react-native";
495
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
601
+ import { StyleSheet as StyleSheet10, View as View8 } from "react-native";
602
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
496
603
  var CARET_SIZE = 7;
497
604
  var CARET_RING_OFFSET = 2;
498
605
  var CARET_DIP = 1;
499
606
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
500
607
  if (!visible) return null;
501
608
  const above = verticalAlign === "above";
502
- const alignStyle = align === "left" ? styles6.contentLeft : align === "right" ? styles6.contentRight : styles6.contentCenter;
503
- const verticalStyle = above ? styles6.contentAbove : styles6.contentBelow;
609
+ const alignStyle = align === "left" ? styles9.contentLeft : align === "right" ? styles9.contentRight : styles9.contentCenter;
610
+ const verticalStyle = above ? styles9.contentAbove : styles9.contentBelow;
504
611
  const ringSize = CARET_SIZE + caretBorderWidth;
505
612
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
506
613
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
507
614
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
508
615
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
509
- return /* @__PURE__ */ jsxs6(View6, { style: [styles6.content, alignStyle, verticalStyle], children: [
616
+ return /* @__PURE__ */ jsxs10(View8, { style: [styles9.content, alignStyle, verticalStyle], children: [
510
617
  children,
511
- caretColor && /* @__PURE__ */ jsxs6(Fragment2, { children: [
512
- /* @__PURE__ */ jsx6(View6, { style: [styles6.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
513
- /* @__PURE__ */ jsx6(View6, { style: [styles6.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
618
+ caretColor && /* @__PURE__ */ jsxs10(Fragment3, { children: [
619
+ /* @__PURE__ */ jsx10(View8, { style: [styles9.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
620
+ /* @__PURE__ */ jsx10(View8, { style: [styles9.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
514
621
  ] })
515
622
  ] });
516
623
  }
517
- var styles6 = StyleSheet7.create({
624
+ var styles9 = StyleSheet10.create({
518
625
  caret: {
519
626
  borderLeftColor: "transparent",
520
627
  borderRightColor: "transparent",
@@ -557,10 +664,10 @@ var styles6 = StyleSheet7.create({
557
664
  });
558
665
 
559
666
  // src/TriggerGaugeHost.tsx
560
- import { jsx as jsx7 } from "react/jsx-runtime";
667
+ import { jsx as jsx11 } from "react/jsx-runtime";
561
668
  var getTriggerGauge = () => import("./TriggerGauge-2OJOOF6I.mjs").then((mod) => ({ default: mod.TriggerGauge }));
562
669
  function TriggerGaugeHost(props) {
563
- return /* @__PURE__ */ jsx7(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
670
+ return /* @__PURE__ */ jsx11(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
564
671
  }
565
672
 
566
673
  // src/useAutoAlign.ts
@@ -612,7 +719,7 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
612
719
  }
613
720
 
614
721
  // src/SectionedDropdown.tsx
615
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
722
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
616
723
  var MENU_BORDER_WIDTH = 1;
617
724
  var TRIGGER_SIZE = 44;
618
725
  var MENU_MIN_WIDTH = 200;
@@ -623,7 +730,7 @@ var MENU_ITEM_HEIGHT = 40;
623
730
  var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
624
731
  var MENU_DIVIDER_HEIGHT = 9;
625
732
  var MENU_VERTICAL_PADDING = 12;
626
- function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, rotation: rotationOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
733
+ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
627
734
  const ambientRotation = useRotation3();
628
735
  const rotation = rotationOverride ?? ambientRotation;
629
736
  const menuBg = dark ? "#000000" : "#FFFFFF";
@@ -637,8 +744,10 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
637
744
  const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
638
745
  return sum + rows + footer + divider;
639
746
  }, 0);
640
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
641
- const align = alignOverride ?? autoAlign;
747
+ const auto = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
748
+ const placement = alignOverride ?? auto;
749
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
750
+ const align = forcedAlign ?? placement.align;
642
751
  const onlySection = sections.length === 1 ? sections[0] : null;
643
752
  const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
644
753
  const triggerIcon = selectedOption?.icon ?? icon;
@@ -660,17 +769,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
660
769
  }
661
770
  gaugeOffset += section.options.length;
662
771
  }
663
- const anchorStyle = [styles7.anchor, open && styles7.anchorOpen];
772
+ const anchorStyle = [styles10.anchor, open && styles10.anchorOpen];
664
773
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
665
774
  const dividerStyle = { backgroundColor: menuBorderColor };
666
- return /* @__PURE__ */ jsxs7(View8, { style: anchorStyle, children: [
667
- /* @__PURE__ */ jsxs7(View8, { ref: triggerRef, collapsable: false, style: styles7.triggerBox, children: [
668
- /* @__PURE__ */ jsx8(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
669
- /* @__PURE__ */ jsx8(IconButton2, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
775
+ return /* @__PURE__ */ jsxs11(View10, { style: anchorStyle, children: [
776
+ /* @__PURE__ */ jsxs11(View10, { ref: triggerRef, collapsable: false, style: styles10.triggerBox, children: [
777
+ /* @__PURE__ */ jsx12(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
778
+ /* @__PURE__ */ jsx12(IconButton2, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
670
779
  ] }),
671
- /* @__PURE__ */ jsx8(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ jsx8(ScrollView3, { style: [styles7.menu, menuStyle], contentContainerStyle: styles7.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs7(View8, { children: [
672
- sectionIndex > 0 && /* @__PURE__ */ jsx8(View8, { style: [styles7.divider, dividerStyle] }),
673
- section.kind === "single" ? /* @__PURE__ */ jsx8(
780
+ /* @__PURE__ */ jsx12(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ jsx12(ScrollView3, { style: [styles10.menu, menuStyle], contentContainerStyle: styles10.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs11(View10, { children: [
781
+ sectionIndex > 0 && /* @__PURE__ */ jsx12(View10, { style: [styles10.divider, dividerStyle] }),
782
+ section.kind === "single" ? /* @__PURE__ */ jsx12(
674
783
  SingleSection,
675
784
  {
676
785
  section,
@@ -683,7 +792,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
683
792
  if (autoDismiss) host.close();
684
793
  }
685
794
  }
686
- ) : /* @__PURE__ */ jsx8(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
795
+ ) : /* @__PURE__ */ jsx12(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
687
796
  ] }, section.id)) }) })
688
797
  ] });
689
798
  }
@@ -695,18 +804,18 @@ function SingleSection({
695
804
  labelFontFamily,
696
805
  onSelect
697
806
  }) {
698
- return /* @__PURE__ */ jsx8(Fragment3, { children: section.options.map((option) => {
807
+ return /* @__PURE__ */ jsx12(Fragment4, { children: section.options.map((option) => {
699
808
  const selected = option.value === section.value;
700
809
  const taken = !selected && option.value === section.takenValue;
701
810
  const rowColor = selected ? onAccentColor : mutedColor;
702
- const itemStyle = [styles7.item, selected && { backgroundColor: accentColor }, taken && styles7.itemTaken];
811
+ const itemStyle = [styles10.item, selected && { backgroundColor: accentColor }, taken && styles10.itemTaken];
703
812
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
704
813
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
705
- return /* @__PURE__ */ jsx8(TouchableRipple2, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs7(View8, { style: styles7.itemRow, children: [
706
- option.icon && /* @__PURE__ */ jsx8(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
707
- /* @__PURE__ */ jsxs7(View8, { style: styles7.itemLabelColumn, children: [
708
- /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: labelStyle, children: option.label }),
709
- option.description && /* @__PURE__ */ jsx8(Text5, { variant: "labelSmall", style: descriptionStyle, children: option.description })
814
+ return /* @__PURE__ */ jsx12(TouchableRipple2, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs11(View10, { style: styles10.itemRow, children: [
815
+ option.icon && /* @__PURE__ */ jsx12(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
816
+ /* @__PURE__ */ jsxs11(View10, { style: styles10.itemLabelColumn, children: [
817
+ /* @__PURE__ */ jsx12(Text8, { variant: "labelLarge", style: labelStyle, children: option.label }),
818
+ option.description && /* @__PURE__ */ jsx12(Text8, { variant: "labelSmall", style: descriptionStyle, children: option.description })
710
819
  ] })
711
820
  ] }) }, option.value);
712
821
  }) });
@@ -720,23 +829,23 @@ function MultiSection({
720
829
  allClearLabels
721
830
  }) {
722
831
  const allSelected = section.value.length === section.options.length;
723
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
832
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
724
833
  section.options.map((option, i) => {
725
834
  const selected = section.value.includes(option.value);
726
835
  const rowColor = selected ? onAccentColor : mutedColor;
727
836
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
728
837
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
729
- const itemStyle = [styles7.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
838
+ const itemStyle = [styles10.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
730
839
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
731
- return /* @__PURE__ */ jsx8(TouchableRipple2, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ jsxs7(View8, { style: styles7.itemRow, children: [
732
- option.icon && /* @__PURE__ */ jsx8(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
733
- /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: [styles7.itemLabelFlex, labelStyle], children: option.label })
840
+ return /* @__PURE__ */ jsx12(TouchableRipple2, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ jsxs11(View10, { style: styles10.itemRow, children: [
841
+ option.icon && /* @__PURE__ */ jsx12(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
842
+ /* @__PURE__ */ jsx12(Text8, { variant: "labelLarge", style: [styles10.itemLabelFlex, labelStyle], children: option.label })
734
843
  ] }) }, option.value);
735
844
  }),
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 }) })
845
+ section.allClear && /* @__PURE__ */ jsx12(TouchableRipple2, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles10.item, children: /* @__PURE__ */ jsx12(Text8, { variant: "labelLarge", style: [styles10.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
737
846
  ] });
738
847
  }
739
- var styles7 = StyleSheet8.create({
848
+ var styles10 = StyleSheet11.create({
740
849
  allClearLabel: {
741
850
  fontWeight: "bold",
742
851
  textAlign: "center"
@@ -753,7 +862,7 @@ var styles7 = StyleSheet8.create({
753
862
  zIndex: 100
754
863
  },
755
864
  divider: {
756
- height: StyleSheet8.hairlineWidth,
865
+ height: StyleSheet11.hairlineWidth,
757
866
  marginVertical: 4
758
867
  },
759
868
  item: {
@@ -806,24 +915,24 @@ var styles7 = StyleSheet8.create({
806
915
  });
807
916
 
808
917
  // src/ControlSchemePicker.tsx
809
- import { jsx as jsx9 } from "react/jsx-runtime";
810
- function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily }) {
918
+ import { jsx as jsx13 } from "react/jsx-runtime";
919
+ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily }) {
811
920
  const isTouchPrimary = useIsTouchPrimaryDevice2();
812
921
  if (Platform3.OS !== "web" || isTouchPrimary) return null;
813
- return /* @__PURE__ */ jsx9(SectionedDropdown, { id, host, icon: "keyboard-outline", accessibilityLabel: "Control scheme", sections: [{ kind: "single", id: "controls", options, value, onChange, takenValue }], accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily });
922
+ return /* @__PURE__ */ jsx13(SectionedDropdown, { id, host, icon: "keyboard-outline", accessibilityLabel: "Control scheme", sections: [{ kind: "single", id: "controls", options, value, onChange, takenValue }], accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily });
814
923
  }
815
924
 
816
925
  // src/CornerActionButtons.tsx
817
926
  import { IconButton as IconButton3 } from "@rific/feedback-press";
818
- import { StyleSheet as StyleSheet9 } from "react-native";
819
- import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
927
+ import { StyleSheet as StyleSheet12 } from "react-native";
928
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
820
929
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
821
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
822
- /* @__PURE__ */ jsx10(IconButton3, { icon: backIcon, iconColor: fg, size: 24, style: [styles8.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
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" })
930
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
931
+ /* @__PURE__ */ jsx14(IconButton3, { icon: backIcon, iconColor: fg, size: 24, style: [styles11.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
932
+ /* @__PURE__ */ jsx14(IconButton3, { icon: "cog", iconColor: fg, size: 24, style: [styles11.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
824
933
  ] });
825
934
  }
826
- var styles8 = StyleSheet9.create({
935
+ var styles11 = StyleSheet12.create({
827
936
  back: {
828
937
  position: "absolute"
829
938
  },
@@ -833,22 +942,22 @@ var styles8 = StyleSheet9.create({
833
942
  });
834
943
 
835
944
  // src/CornerStatusBadges.tsx
836
- import { StyleSheet as StyleSheet10, View as View9 } from "react-native";
945
+ import { StyleSheet as StyleSheet13, View as View11 } from "react-native";
837
946
  import { Icon as Icon5 } from "react-native-paper";
838
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
947
+ import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
839
948
  var BADGE_SIZE = 30;
840
949
  function StatusBadge({ icon }) {
841
- return /* @__PURE__ */ jsx11(View9, { style: styles9.badge, children: icon && /* @__PURE__ */ jsx11(Icon5, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
950
+ return /* @__PURE__ */ jsx15(View11, { style: styles12.badge, children: icon && /* @__PURE__ */ jsx15(Icon5, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
842
951
  }
843
952
  function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
844
953
  const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
845
954
  const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
846
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
847
- p1 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p1OnRightSide ? styles9.bottomRight : styles9.bottomLeft, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p1.icon }) }),
848
- p2 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p2OnLeftSide ? styles9.topLeft : styles9.topRight, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p2.icon }) })
955
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
956
+ p1 && /* @__PURE__ */ jsx15(View11, { pointerEvents: "none", style: p1OnRightSide ? styles12.bottomRight : styles12.bottomLeft, children: /* @__PURE__ */ jsx15(StatusBadge, { icon: p1.icon }) }),
957
+ p2 && /* @__PURE__ */ jsx15(View11, { pointerEvents: "none", style: p2OnLeftSide ? styles12.topLeft : styles12.topRight, children: /* @__PURE__ */ jsx15(StatusBadge, { icon: p2.icon }) })
849
958
  ] });
850
959
  }
851
- var styles9 = StyleSheet10.create({
960
+ var styles12 = StyleSheet13.create({
852
961
  badge: {
853
962
  alignItems: "center",
854
963
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -870,9 +979,9 @@ var styles9 = StyleSheet10.create({
870
979
  import { defaultColors, getContrastColor } from "@rific/auto-paper";
871
980
  import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
872
981
  import { clamp, useRotation as useRotation4 } from "@tastic/core";
873
- import { ScrollView as ScrollView4, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
874
- import { Icon as Icon6, Text as Text6 } from "react-native-paper";
875
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
982
+ import { ScrollView as ScrollView4, StyleSheet as StyleSheet14, useWindowDimensions as useWindowDimensions3, View as View12 } from "react-native";
983
+ import { Icon as Icon6, Text as Text9 } from "react-native-paper";
984
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
876
985
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
877
986
  var DEFAULT_SIZE = 48;
878
987
  var SWATCH_SIZE = 28;
@@ -885,31 +994,37 @@ var SCREEN_MARGIN = 40;
885
994
  function widthForColumns(columns) {
886
995
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
887
996
  }
888
- function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
997
+ function getInlineColorPickerContentSize(swatchCount, windowWidth, columns) {
998
+ const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
999
+ const resolvedColumns = columns ?? autoColumns;
1000
+ const width = widthForColumns(resolvedColumns);
1001
+ const rows = Math.ceil(swatchCount / resolvedColumns);
1002
+ const height = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * rows + SWATCHES_GAP * (rows - 1);
1003
+ return { width, height };
1004
+ }
1005
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
889
1006
  const ambientRotation = useRotation4();
890
1007
  const rotation = rotationOverride ?? ambientRotation;
891
1008
  const menuBg = dark ? "#000000" : "#FFFFFF";
892
1009
  const { width: windowWidth } = useWindowDimensions3();
893
- const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
894
- const resolvedColumns = columns ?? autoColumns;
895
- const swatchesWidth = widthForColumns(resolvedColumns);
896
- const swatchRows = Math.ceil(swatches.length / resolvedColumns);
897
- const swatchesHeight = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * swatchRows + SWATCHES_GAP * (swatchRows - 1);
1010
+ const { width: swatchesWidth, height: swatchesHeight } = getInlineColorPickerContentSize(swatches.length, windowWidth, columns);
898
1011
  const open = host.openId === id;
899
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
900
- const align = alignOverride ?? autoAlign;
1012
+ const auto = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1013
+ const placement = alignOverride ?? auto;
1014
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
1015
+ const align = forcedAlign ?? placement.align;
901
1016
  const displayFor = previewValue ?? ((hex) => hex);
902
1017
  const displayValue = displayFor(value);
903
1018
  const contrastColor = getContrastColor(displayValue);
904
1019
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
905
- return /* @__PURE__ */ jsxs10(View10, { style: [styles10.anchor, open && styles10.anchorOpen], children: [
906
- /* @__PURE__ */ jsx12(TouchableRipple3, { onPress: () => host.toggle(id), borderless: true, style: [styles10.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ jsx12(View10, { ref: triggerRef, collapsable: false, style: styles10.triggerMeasure, children: tag ? /* @__PURE__ */ jsx12(Text6, { style: [styles10.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx12(Icon6, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
907
- /* @__PURE__ */ jsx12(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ jsx12(ScrollView4, { style: [styles10.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles10.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1020
+ return /* @__PURE__ */ jsxs14(View12, { style: [styles13.anchor, open && styles13.anchorOpen], children: [
1021
+ /* @__PURE__ */ jsx16(TouchableRipple3, { onPress: () => host.toggle(id), borderless: true, style: [styles13.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ jsx16(View12, { ref: triggerRef, collapsable: false, style: styles13.triggerMeasure, children: tag ? /* @__PURE__ */ jsx16(Text9, { style: [styles13.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx16(Icon6, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1022
+ /* @__PURE__ */ jsx16(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ jsx16(ScrollView4, { style: [styles13.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles13.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
908
1023
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
909
1024
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
910
1025
  const swappable = taken && allowSwapTaken;
911
1026
  const swatchDisplay = displayFor(swatch.value);
912
- return /* @__PURE__ */ jsx12(
1027
+ return /* @__PURE__ */ jsx16(
913
1028
  TouchableRipple3,
914
1029
  {
915
1030
  disabled: taken && !swappable,
@@ -918,15 +1033,15 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
918
1033
  if (autoDismiss) host.close();
919
1034
  },
920
1035
  borderless: true,
921
- style: [styles10.swatch, { backgroundColor: swatchDisplay }, selected && styles10.swatchSelected, taken && !swappable && styles10.swatchTaken],
922
- children: selected ? /* @__PURE__ */ jsx12(Icon6, { source: "check", size: 16, color: getContrastColor(swatchDisplay) }) : swappable ? /* @__PURE__ */ jsx12(Icon6, { source: "swap-horizontal", size: 16, color: getContrastColor(swatchDisplay) }) : taken ? /* @__PURE__ */ jsx12(Icon6, { source: "close", size: 16, color: getContrastColor(swatchDisplay) }) : /* @__PURE__ */ jsx12(View10, {})
1036
+ style: [styles13.swatch, { backgroundColor: swatchDisplay }, selected && styles13.swatchSelected, taken && !swappable && styles13.swatchTaken],
1037
+ children: selected ? /* @__PURE__ */ jsx16(Icon6, { source: "check", size: 16, color: getContrastColor(swatchDisplay) }) : swappable ? /* @__PURE__ */ jsx16(Icon6, { source: "swap-horizontal", size: 16, color: getContrastColor(swatchDisplay) }) : taken ? /* @__PURE__ */ jsx16(Icon6, { source: "close", size: 16, color: getContrastColor(swatchDisplay) }) : /* @__PURE__ */ jsx16(View12, {})
923
1038
  },
924
1039
  swatch.value
925
1040
  );
926
1041
  }) }) })
927
1042
  ] });
928
1043
  }
929
- var styles10 = StyleSheet11.create({
1044
+ var styles13 = StyleSheet14.create({
930
1045
  anchor: {
931
1046
  position: "relative"
932
1047
  },
@@ -1000,9 +1115,9 @@ var styles10 = StyleSheet11.create({
1000
1115
  // src/LabeledDropdown.tsx
1001
1116
  import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
1002
1117
  import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
1003
- import { ScrollView as ScrollView5, StyleSheet as StyleSheet12, View as View11 } from "react-native";
1004
- import { Icon as Icon7, Text as Text7 } from "react-native-paper";
1005
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1118
+ import { ScrollView as ScrollView5, StyleSheet as StyleSheet15, View as View13 } from "react-native";
1119
+ import { Icon as Icon7, Text as Text10 } from "react-native-paper";
1120
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1006
1121
  var TRIGGER_HEIGHT = 28;
1007
1122
  var POPOVER_WIDTH = 180;
1008
1123
  var ROW_HEIGHT = 36;
@@ -1025,22 +1140,22 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1025
1140
  onChange(option.value);
1026
1141
  host.close();
1027
1142
  };
1028
- return /* @__PURE__ */ jsxs11(View11, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1029
- /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => host.toggle(id), style: styles11.trigger, children: /* @__PURE__ */ jsxs11(View11, { ref: triggerRef, collapsable: false, style: styles11.triggerInner, children: [
1030
- /* @__PURE__ */ jsx13(Text7, { style: [styles11.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1031
- /* @__PURE__ */ jsx13(Icon7, { source: "menu-down", size: 16, color })
1143
+ return /* @__PURE__ */ jsxs15(View13, { style: [styles14.anchor, open && styles14.anchorOpen], children: [
1144
+ /* @__PURE__ */ jsx17(TouchableRipple4, { onPress: () => host.toggle(id), style: styles14.trigger, children: /* @__PURE__ */ jsxs15(View13, { ref: triggerRef, collapsable: false, style: styles14.triggerInner, children: [
1145
+ /* @__PURE__ */ jsx17(Text10, { style: [styles14.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1146
+ /* @__PURE__ */ jsx17(Icon7, { source: "menu-down", size: 16, color })
1032
1147
  ] }) }),
1033
- /* @__PURE__ */ jsx13(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ jsx13(ScrollView5, { style: [styles11.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles11.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1148
+ /* @__PURE__ */ jsx17(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ jsx17(ScrollView5, { style: [styles14.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles14.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1034
1149
  const isSelected = option.value === value;
1035
1150
  const onColor = getContrastColor2(color);
1036
- return /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => handleSelect(option), style: [styles11.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs11(View11, { style: styles11.rowInner, children: [
1037
- option.icon && /* @__PURE__ */ jsx13(Icon7, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1038
- /* @__PURE__ */ jsx13(Text7, { style: [styles11.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1151
+ return /* @__PURE__ */ jsx17(TouchableRipple4, { onPress: () => handleSelect(option), style: [styles14.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs15(View13, { style: styles14.rowInner, children: [
1152
+ option.icon && /* @__PURE__ */ jsx17(Icon7, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1153
+ /* @__PURE__ */ jsx17(Text10, { style: [styles14.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1039
1154
  ] }) }, option.value);
1040
1155
  }) }) })
1041
1156
  ] });
1042
1157
  }
1043
- var styles11 = StyleSheet12.create({
1158
+ var styles14 = StyleSheet15.create({
1044
1159
  anchor: {
1045
1160
  position: "relative"
1046
1161
  },
@@ -1097,26 +1212,23 @@ function loadSkiaWeb() {
1097
1212
  return Promise.resolve();
1098
1213
  }
1099
1214
 
1100
- // src/PressAwayOverlay.tsx
1101
- import { Pressable, StyleSheet as StyleSheet13 } from "react-native";
1102
- import { jsx as jsx14 } from "react/jsx-runtime";
1103
- function PressAwayOverlay({ active, onPress, style }) {
1104
- if (!active) return null;
1105
- return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet13.absoluteFill, style] });
1106
- }
1215
+ // src/PlayerSetupPanel.tsx
1216
+ import { ProfilePicker } from "@tastic/profile";
1217
+ import { StyleSheet as StyleSheet17, View as View14 } from "react-native";
1218
+ import { Text as Text12 } from "react-native-paper";
1107
1219
 
1108
1220
  // src/ReadyButton.tsx
1109
1221
  import { TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
1110
- import { StyleSheet as StyleSheet14 } from "react-native";
1111
- import { Text as Text8 } from "react-native-paper";
1112
- import { jsx as jsx15 } from "react/jsx-runtime";
1222
+ import { StyleSheet as StyleSheet16 } from "react-native";
1223
+ import { Text as Text11 } from "react-native-paper";
1224
+ import { jsx as jsx18 } from "react/jsx-runtime";
1113
1225
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
1114
- const readyButtonStyle = [styles12.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1226
+ const readyButtonStyle = [styles15.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1115
1227
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
1116
1228
  const labelColorStyle = { color: ready ? "#000000" : color };
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?" }) });
1229
+ return /* @__PURE__ */ jsx18(TouchableRipple5, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx18(Text11, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1118
1230
  }
1119
- var styles12 = StyleSheet14.create({
1231
+ var styles15 = StyleSheet16.create({
1120
1232
  readyButton: {
1121
1233
  alignItems: "center",
1122
1234
  borderRadius: 12,
@@ -1128,22 +1240,97 @@ var styles12 = StyleSheet14.create({
1128
1240
  }
1129
1241
  });
1130
1242
 
1243
+ // src/usePopoverHost.ts
1244
+ import { useCallback as useCallback3, useState as useState5 } from "react";
1245
+ function usePopoverHost() {
1246
+ const [openId, setOpenId] = useState5(null);
1247
+ const toggle = useCallback3((id) => {
1248
+ setOpenId((prev) => prev === id ? null : id);
1249
+ }, []);
1250
+ const close = useCallback3(() => setOpenId(null), []);
1251
+ return { openId, toggle, close };
1252
+ }
1253
+
1254
+ // src/PlayerSetupPanel.tsx
1255
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1256
+ function getColorPopoverId(idPrefix) {
1257
+ return `${idPrefix}-color`;
1258
+ }
1259
+ function getCpuDifficultyPopoverId(idPrefix) {
1260
+ return `${idPrefix}-difficulty`;
1261
+ }
1262
+ function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, cpuDifficultyAlignOverride, humanIcon = "face-man", cpuIcon = "robot" }) {
1263
+ const ownHost = usePopoverHost();
1264
+ const popover = host ?? ownHost;
1265
+ const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false;
1266
+ const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || secondPickerId !== void 0 && popover.openId === secondPickerId;
1267
+ const showProfilePicker = isHuman && profiles !== void 0 && !!onProfileSelect;
1268
+ const showCpuDifficulty = !isHuman && cpuDifficulty !== void 0 && cpuDifficultyOptions !== void 0 && !!onCpuDifficultyChange;
1269
+ const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null;
1270
+ return /* @__PURE__ */ jsxs16(View14, { style: [styles16.panel, ownPopoverOpen && styles16.panelOpen], children: [
1271
+ showProfilePicker ? /* @__PURE__ */ jsx19(ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles }) : showCpuDifficulty ? /* @__PURE__ */ jsx19(LabeledDropdown, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, alignOverride: cpuDifficultyAlignOverride }) : label && /* @__PURE__ */ jsx19(Text12, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
1272
+ /* @__PURE__ */ jsxs16(View14, { style: [styles16.pickerRow, pickerRowPopoverOpen && styles16.pickerRowOpen], children: [
1273
+ /* @__PURE__ */ jsx19(InlineColorPicker, { id: getColorPopoverId(idPrefix), host: popover, value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, alignOverride: colorAlignOverride, rotation: colorRotation, dark, tag: isHuman ? selectedProfile?.tag : void 0, icon: isHuman ? humanIcon : cpuIcon }),
1274
+ secondPicker
1275
+ ] }),
1276
+ isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ jsx19(ReadyButton, { color, ready: ready ?? false, onToggleReady })
1277
+ ] });
1278
+ }
1279
+ var styles16 = StyleSheet17.create({
1280
+ panel: {
1281
+ alignItems: "center",
1282
+ gap: 12
1283
+ },
1284
+ // See SectionedDropdown's anchorOpen comment — this panel is typically a sibling of another
1285
+ // panel (and/or a shared-controls row), so a popover escaping this panel's bounds needs the panel
1286
+ // itself elevated, not just the popover content, to paint above a later sibling.
1287
+ panelOpen: {
1288
+ zIndex: 100
1289
+ },
1290
+ // Same gap as `panel`'s own vertical rhythm, reused horizontally — the color picker and (when
1291
+ // present) secondPicker sit side by side within this row instead of stacked in the panel's own
1292
+ // column.
1293
+ pickerRow: {
1294
+ alignItems: "center",
1295
+ flexDirection: "row",
1296
+ gap: 12
1297
+ },
1298
+ // A popover is an absolutely-positioned sibling of its own trigger — so a tall one (the color
1299
+ // swatch grid) can extend down far enough to overlap the ReadyButton below, which paints on top of
1300
+ // it by default DOM order since it's a later sibling within `panel`. `panel`'s own panelOpen
1301
+ // elevation only helps this panel paint above OTHER panels (see its own comment) — it does nothing
1302
+ // for stacking *within* this panel, since React Native Web gives each View its own stacking
1303
+ // context. This is what actually fixes that: elevate pickerRow itself above its own later sibling
1304
+ // whenever one of its own popovers is open.
1305
+ pickerRowOpen: {
1306
+ zIndex: 100
1307
+ }
1308
+ });
1309
+
1310
+ // src/PressAwayOverlay.tsx
1311
+ import { Pressable, StyleSheet as StyleSheet18 } from "react-native";
1312
+ import { jsx as jsx20 } from "react/jsx-runtime";
1313
+ function PressAwayOverlay({ active, onPress, style }) {
1314
+ if (!active) return null;
1315
+ return /* @__PURE__ */ jsx20(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet18.absoluteFill, style] });
1316
+ }
1317
+
1131
1318
  // src/SharedActionBand.tsx
1132
1319
  import { IconButton as IconButton4 } from "@rific/feedback-press";
1133
- import { StyleSheet as StyleSheet15, View as View12 } from "react-native";
1134
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1320
+ import { StyleSheet as StyleSheet19, View as View15 } from "react-native";
1321
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
1135
1322
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1136
- return /* @__PURE__ */ jsxs12(View12, { style: [styles13.column, popoverOpen && styles13.columnOpen], children: [
1137
- /* @__PURE__ */ jsxs12(View12, { style: styles13.actionsRow, children: [
1138
- /* @__PURE__ */ jsx16(IconButton4, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1139
- onRandomize && /* @__PURE__ */ jsx16(IconButton4, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1140
- onReset && /* @__PURE__ */ jsx16(IconButton4, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1141
- /* @__PURE__ */ jsx16(IconButton4, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1323
+ return /* @__PURE__ */ jsxs17(View15, { style: [styles17.column, popoverOpen && styles17.columnOpen], children: [
1324
+ /* @__PURE__ */ jsxs17(View15, { style: styles17.actionsRow, children: [
1325
+ /* @__PURE__ */ jsx21(IconButton4, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1326
+ onRandomize && /* @__PURE__ */ jsx21(IconButton4, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1327
+ onReset && /* @__PURE__ */ jsx21(IconButton4, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1328
+ /* @__PURE__ */ jsx21(IconButton4, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1142
1329
  ] }),
1143
1330
  children
1144
1331
  ] });
1145
1332
  }
1146
- var styles13 = StyleSheet15.create({
1333
+ var styles17 = StyleSheet19.create({
1147
1334
  actionsRow: {
1148
1335
  alignItems: "center",
1149
1336
  flexDirection: "row",
@@ -1158,83 +1345,109 @@ var styles13 = StyleSheet15.create({
1158
1345
  }
1159
1346
  });
1160
1347
 
1161
- // src/StatRow.tsx
1162
- import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1163
- import { StyleSheet as StyleSheet16, View as View13 } from "react-native";
1164
- import { Text as Text9 } from "react-native-paper";
1165
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1166
- function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1167
- const { dark } = useAutoPaperTheme5();
1168
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1169
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
1170
- return /* @__PURE__ */ jsxs13(View13, { style: styles14.row, children: [
1171
- /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1172
- /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: [styles14.value, { color: fg }], children: value })
1173
- ] });
1348
+ // src/StaggeredWord.tsx
1349
+ import { useEffect as useEffect2 } from "react";
1350
+ import { StyleSheet as StyleSheet20, View as View16 } from "react-native";
1351
+ import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming } from "react-native-reanimated";
1352
+ import { jsx as jsx22 } from "react/jsx-runtime";
1353
+ var STAGGER_MS = 45;
1354
+ var LETTER_DURATION_MS = 320;
1355
+ function getStaggeredWordDuration(letterCount) {
1356
+ return (letterCount - 1) * STAGGER_MS + LETTER_DURATION_MS;
1174
1357
  }
1175
- var styles14 = StyleSheet16.create({
1176
- row: {
1177
- alignItems: "center",
1178
- flexDirection: "row",
1179
- justifyContent: "space-between"
1180
- },
1181
- value: {
1182
- fontWeight: "bold"
1183
- }
1184
- });
1185
-
1186
- // src/StatSection.tsx
1187
- import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1188
- import { StyleSheet as StyleSheet17, View as View14 } from "react-native";
1189
- import { Text as Text10 } from "react-native-paper";
1190
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1191
- function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1192
- const { dark } = useAutoPaperTheme6();
1193
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1194
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1195
- return /* @__PURE__ */ jsxs14(View14, { style: [styles15.section, { backgroundColor: sectionBg }], children: [
1196
- /* @__PURE__ */ jsx18(Text10, { variant: "labelMedium", style: [styles15.label, { color: fg }], children: label }),
1197
- children
1198
- ] });
1358
+ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
1359
+ const progress = useSharedValue(reducedMotion ? 1 : 0);
1360
+ useEffect2(() => {
1361
+ if (reducedMotion) return;
1362
+ progress.value = withDelay(index * STAGGER_MS, withTiming(1, { duration: LETTER_DURATION_MS, easing: Easing.out(Easing.back(1.5)) }));
1363
+ }, [reducedMotion]);
1364
+ const style = useAnimatedStyle(() => ({
1365
+ opacity: progress.value,
1366
+ transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
1367
+ }));
1368
+ return /* @__PURE__ */ jsx22(Animated.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
1199
1369
  }
1200
- var styles15 = StyleSheet17.create({
1201
- // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1202
- // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1203
- // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
1204
- // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
1205
- label: {
1206
- fontWeight: "bold",
1207
- letterSpacing: 2
1208
- },
1209
- section: {
1210
- borderRadius: 12,
1211
- gap: 8,
1212
- padding: 16
1370
+ function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
1371
+ return /* @__PURE__ */ jsx22(View16, { style: [styles18.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ jsx22(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
1372
+ }
1373
+ var styles18 = StyleSheet20.create({
1374
+ row: {
1375
+ flexDirection: "row"
1213
1376
  }
1214
1377
  });
1215
1378
 
1216
1379
  // src/UpdateDialog.tsx
1217
1380
  import { useUpdater as useUpdater2 } from "@rific/updater";
1218
- import { jsx as jsx19 } from "react/jsx-runtime";
1381
+ import { jsx as jsx23 } from "react/jsx-runtime";
1219
1382
  function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1220
1383
  const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1221
1384
  useUpdater2({ autoCheck, autoPrompt, onConfirm, onError });
1222
1385
  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) });
1386
+ return /* @__PURE__ */ jsx23(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
1387
  }
1225
1388
 
1226
- // src/usePopoverHost.ts
1227
- import { useCallback as useCallback3, useState as useState5 } from "react";
1228
- function usePopoverHost() {
1229
- const [openId, setOpenId] = useState5(null);
1230
- const toggle = useCallback3((id) => {
1231
- setOpenId((prev) => prev === id ? null : id);
1389
+ // src/useQuitConfirmation.ts
1390
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
1391
+ function useQuitConfirmation(hasProgress, onConfirmedBack) {
1392
+ const [confirmVisible, setConfirmVisible] = useState6(false);
1393
+ const hasProgressRef = useRef3(hasProgress);
1394
+ useEffect3(() => {
1395
+ hasProgressRef.current = hasProgress;
1396
+ });
1397
+ const onConfirmedBackRef = useRef3(onConfirmedBack);
1398
+ useEffect3(() => {
1399
+ onConfirmedBackRef.current = onConfirmedBack;
1400
+ });
1401
+ const requestBack = useCallback4(() => {
1402
+ if (hasProgressRef.current()) setConfirmVisible(true);
1403
+ else onConfirmedBackRef.current();
1232
1404
  }, []);
1233
- const close = useCallback3(() => setOpenId(null), []);
1234
- return { openId, toggle, close };
1405
+ const cancelBack = useCallback4(() => setConfirmVisible(false), []);
1406
+ return { confirmVisible, requestBack, cancelBack };
1407
+ }
1408
+
1409
+ // src/useZoneClampedAlign.ts
1410
+ import { useZoneBounds } from "@tastic/split-screen";
1411
+ import { useEffect as useEffect4, useState as useState7 } from "react";
1412
+ import { useWindowDimensions as useWindowDimensions4 } from "react-native";
1413
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
1414
+ var EDGE_MARGIN2 = 12;
1415
+ function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
1416
+ const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
1417
+ const zone = useZoneBounds();
1418
+ const { height: windowHeight } = useWindowDimensions4();
1419
+ const insets = useSafeAreaInsets();
1420
+ const shouldMeasure = open && auto.measured && zone !== null;
1421
+ const [ownBounds, setOwnBounds] = useState7(null);
1422
+ const [prevShouldMeasure, setPrevShouldMeasure] = useState7(shouldMeasure);
1423
+ if (shouldMeasure !== prevShouldMeasure) {
1424
+ setPrevShouldMeasure(shouldMeasure);
1425
+ setOwnBounds(null);
1426
+ }
1427
+ useEffect4(() => {
1428
+ if (!shouldMeasure) return;
1429
+ auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
1430
+ const belowShared = zone.zoneSide === "belowShared";
1431
+ const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
1432
+ const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
1433
+ const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
1434
+ let awaySide = belowShared ? "below" : "above";
1435
+ let towardSide = belowShared ? "above" : "below";
1436
+ if (zone.rotated) {
1437
+ awaySide = awaySide === "above" ? "below" : "above";
1438
+ towardSide = towardSide === "above" ? "below" : "above";
1439
+ }
1440
+ setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
1441
+ });
1442
+ }, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
1443
+ if (!zone) return auto;
1444
+ if (!ownBounds) return { ...auto, measured: false };
1445
+ return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
1235
1446
  }
1236
1447
  export {
1448
+ AchievementCatalogSection,
1237
1449
  AchievementRow,
1450
+ ActivityStatSection,
1238
1451
  BaseSettingsDialog,
1239
1452
  BaseStatsScreen,
1240
1453
  ConfirmDialog,
@@ -1247,20 +1460,28 @@ export {
1247
1460
  LOCKED_BADGE_COLOR,
1248
1461
  LabeledDropdown,
1249
1462
  MONO_FONT,
1463
+ PlayerSetupPanel,
1250
1464
  PopoverBody,
1251
1465
  PressAwayOverlay,
1252
1466
  ReadyButton,
1253
1467
  SectionedDropdown,
1254
1468
  SharedActionBand,
1255
1469
  SkiaGate,
1470
+ StaggeredWord,
1256
1471
  StatRow,
1257
1472
  StatSection,
1258
1473
  TriggerGaugeHost,
1259
1474
  UpdateDialog,
1475
+ getColorPopoverId,
1476
+ getCpuDifficultyPopoverId,
1477
+ getInlineColorPickerContentSize,
1260
1478
  getLabeledDropdownContentHeight,
1261
1479
  getOverlayCardColors,
1480
+ getStaggeredWordDuration,
1262
1481
  loadSkiaWeb,
1263
1482
  overlayStyles,
1264
1483
  useAutoAlign,
1265
- usePopoverHost
1484
+ usePopoverHost,
1485
+ useQuitConfirmation,
1486
+ useZoneClampedAlign
1266
1487
  };