@tastic/hud 0.8.5 → 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,25 +210,25 @@ 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
220
  const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
129
- 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: [
130
- /* @__PURE__ */ jsx2(Icon2, { source: icon, size: 64, color: accent }),
131
- /* @__PURE__ */ jsx2(Text2, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
132
- /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
133
- /* @__PURE__ */ jsxs2(View2, { style: styles2.actions, children: [
134
- showCancel && /* @__PURE__ */ jsx2(Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
135
- /* @__PURE__ */ jsx2(Button, { mode: "contained", onPress: onConfirm, style: showCancel ? styles2.actionButton : styles2.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
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 })
136
228
  ] })
137
229
  ] }) }) });
138
230
  }
139
- var styles2 = StyleSheet3.create({
231
+ var styles5 = StyleSheet6.create({
140
232
  // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
141
233
  // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
142
234
  // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
@@ -224,12 +316,12 @@ function useUpdateConfirmBridge() {
224
316
  }
225
317
 
226
318
  // src/BaseSettingsDialog.tsx
227
- 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";
228
320
  function SettingIcon({ source, color, containerColor }) {
229
- 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 }) });
230
322
  }
231
323
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
232
- const { colors } = useAutoPaperTheme3();
324
+ const { colors } = useAutoPaperTheme6();
233
325
  const ambientRotation = useRotation();
234
326
  const rotation = rotationOverride ?? ambientRotation;
235
327
  const isTouchPrimary = useIsTouchPrimaryDevice();
@@ -248,32 +340,32 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
248
340
  });
249
341
  const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
250
342
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
251
- const showEdgeGuard = Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
252
- 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;
253
345
  const showSound = !hideSound;
254
346
  const showAppearance = !hideAppearance;
255
- const showUpdateCheck = Platform.OS !== "web" && !hideUpdateCheck;
256
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
257
- /* @__PURE__ */ jsxs3(Dialog, { visible, onDismiss, style: [styles3.dialog, toRotationStyle2(rotation)], children: [
258
- /* @__PURE__ */ jsx3(Dialog.Title, { children: "Settings" }),
259
- /* @__PURE__ */ jsx3(Dialog.ScrollArea, { style: styles3.scrollArea, children: /* @__PURE__ */ jsxs3(ScrollView, { contentContainerStyle: styles3.content, showsVerticalScrollIndicator: false, children: [
260
- /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleGroup, children: [
261
- showLockOrientation && /* @__PURE__ */ jsx3(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles3.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
262
- /* @__PURE__ */ jsx3(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
263
- /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
264
- /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
265
- /* @__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" })
266
358
  ] })
267
359
  ] }) }),
268
- showEdgeGuard && /* @__PURE__ */ jsx3(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles3.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
269
- /* @__PURE__ */ jsx3(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
270
- /* @__PURE__ */ jsxs3(View3, { style: styles3.flexShrink, children: [
271
- /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
272
- /* @__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" })
273
365
  ] })
274
366
  ] }) }),
275
- (showSound || showHaptics) && /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleRow, children: [
276
- showSound && /* @__PURE__ */ jsx3(
367
+ (showSound || showHaptics) && /* @__PURE__ */ jsxs7(View5, { style: styles6.toggleRow, children: [
368
+ showSound && /* @__PURE__ */ jsx7(
277
369
  TouchableRipple,
278
370
  {
279
371
  soundDisabled: true,
@@ -282,15 +374,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
282
374
  setSoundSettings({ enabled });
283
375
  if (enabled) sound.selection?.();
284
376
  },
285
- style: styles3.toggleButtonInRow,
377
+ style: styles6.toggleButtonInRow,
286
378
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
287
- children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
288
- /* @__PURE__ */ jsx3(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
289
- /* @__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" })
290
382
  ] })
291
383
  }
292
384
  ),
293
- showHaptics && /* @__PURE__ */ jsx3(
385
+ showHaptics && /* @__PURE__ */ jsx7(
294
386
  TouchableRipple,
295
387
  {
296
388
  hapticDisabled: true,
@@ -299,36 +391,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
299
391
  setHapticSettings({ vibrate });
300
392
  if (vibrate) forceHapticFeedback();
301
393
  },
302
- style: styles3.toggleButtonInRow,
394
+ style: styles6.toggleButtonInRow,
303
395
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
304
- children: /* @__PURE__ */ jsxs3(View3, { style: styles3.toggleContent, children: [
305
- /* @__PURE__ */ jsx3(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
306
- /* @__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" })
307
399
  ] })
308
400
  }
309
401
  )
310
402
  ] })
311
403
  ] }),
312
- showAppearance && /* @__PURE__ */ jsxs3(View3, { style: styles3.section, children: [
313
- /* @__PURE__ */ jsx3(Text3, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
314
- /* @__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 })
315
407
  ] }),
316
408
  children,
317
- showUpdateCheck && /* @__PURE__ */ jsxs3(View3, { style: styles3.section, children: [
318
- /* @__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: [
319
411
  "VERSION ",
320
412
  version,
321
413
  updateReady ? " \xB7 UPDATE READY" : ""
322
414
  ] }),
323
- /* @__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" })
324
416
  ] })
325
417
  ] }) })
326
418
  ] }),
327
- /* @__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) }),
328
- /* @__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) })
329
421
  ] });
330
422
  }
331
- var styles3 = StyleSheet4.create({
423
+ var styles6 = StyleSheet7.create({
332
424
  content: {
333
425
  gap: 24,
334
426
  paddingVertical: 20
@@ -396,31 +488,31 @@ var styles3 = StyleSheet4.create({
396
488
  });
397
489
 
398
490
  // src/BaseStatsScreen.tsx
399
- import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
491
+ import { useAutoPaperTheme as useAutoPaperTheme7 } from "@rific/auto-paper";
400
492
  import { Button as Button3, IconButton } from "@rific/feedback-press";
401
493
  import { useRotation as useRotation2 } from "@tastic/core";
402
494
  import { useState as useState3 } from "react";
403
- import { ScrollView as ScrollView2, StyleSheet as StyleSheet5, View as View4 } from "react-native";
404
- import { Text as Text4 } from "react-native-paper";
405
- 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";
406
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 }) {
407
- const { dark, colors } = useAutoPaperTheme4();
499
+ const { dark, colors } = useAutoPaperTheme7();
408
500
  const ambientRotation = useRotation2();
409
501
  const rotation = rotationOverride ?? ambientRotation;
410
502
  const [confirmResetVisible, setConfirmResetVisible] = useState3(false);
411
503
  const showReset = !!onReset;
412
504
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
413
505
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
414
- return /* @__PURE__ */ jsxs4(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
415
- /* @__PURE__ */ jsxs4(View4, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
416
- /* @__PURE__ */ jsx4(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
417
- /* @__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 })
418
510
  ] }),
419
- /* @__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: [
420
512
  children,
421
- 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 })
422
514
  ] }),
423
- /* @__PURE__ */ jsx4(
515
+ /* @__PURE__ */ jsx8(
424
516
  ConfirmDialog,
425
517
  {
426
518
  visible: confirmResetVisible,
@@ -440,7 +532,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
440
532
  )
441
533
  ] });
442
534
  }
443
- var styles4 = StyleSheet5.create({
535
+ var styles7 = StyleSheet8.create({
444
536
  container: {
445
537
  flex: 1
446
538
  },
@@ -472,18 +564,18 @@ var styles4 = StyleSheet5.create({
472
564
 
473
565
  // src/ContentGutter.tsx
474
566
  import { computeContentBounds } from "@tastic/core";
475
- import { StyleSheet as StyleSheet6, useWindowDimensions, View as View5 } from "react-native";
476
- 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";
477
569
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
478
570
  const { width: windowWidth } = useWindowDimensions();
479
571
  const { contentWidth, gutterWidth } = computeContentBounds(windowWidth, maxContentWidth ?? Infinity);
480
- return /* @__PURE__ */ jsxs5(View5, { style: [styles5.row, style], children: [
481
- /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
482
- /* @__PURE__ */ jsx5(View5, { style: { width: contentWidth }, children }),
483
- /* @__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 })
484
576
  ] });
485
577
  }
486
- var styles5 = StyleSheet6.create({
578
+ var styles8 = StyleSheet9.create({
487
579
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
488
580
  // column instead of clipping cleanly at its own real width.
489
581
  gutter: {
@@ -502,38 +594,34 @@ import { Platform as Platform3 } from "react-native";
502
594
  import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
503
595
  import { IconButton as IconButton2, TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
504
596
  import { useRotation as useRotation3 } from "@tastic/core";
505
- import { ScrollView as ScrollView3, StyleSheet as StyleSheet8, View as View8 } from "react-native";
506
- import { Icon as Icon4, Text as Text5 } from "react-native-paper";
507
-
508
- // src/fonts.ts
509
- import { Platform as Platform2 } from "react-native";
510
- 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";
511
599
 
512
600
  // src/PopoverBody.tsx
513
- import { StyleSheet as StyleSheet7, View as View6 } from "react-native";
514
- 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";
515
603
  var CARET_SIZE = 7;
516
604
  var CARET_RING_OFFSET = 2;
517
605
  var CARET_DIP = 1;
518
606
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
519
607
  if (!visible) return null;
520
608
  const above = verticalAlign === "above";
521
- const alignStyle = align === "left" ? styles6.contentLeft : align === "right" ? styles6.contentRight : styles6.contentCenter;
522
- 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;
523
611
  const ringSize = CARET_SIZE + caretBorderWidth;
524
612
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
525
613
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
526
614
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
527
615
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
528
- return /* @__PURE__ */ jsxs6(View6, { style: [styles6.content, alignStyle, verticalStyle], children: [
616
+ return /* @__PURE__ */ jsxs10(View8, { style: [styles9.content, alignStyle, verticalStyle], children: [
529
617
  children,
530
- caretColor && /* @__PURE__ */ jsxs6(Fragment2, { children: [
531
- /* @__PURE__ */ jsx6(View6, { style: [styles6.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
532
- /* @__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 }] })
533
621
  ] })
534
622
  ] });
535
623
  }
536
- var styles6 = StyleSheet7.create({
624
+ var styles9 = StyleSheet10.create({
537
625
  caret: {
538
626
  borderLeftColor: "transparent",
539
627
  borderRightColor: "transparent",
@@ -576,10 +664,10 @@ var styles6 = StyleSheet7.create({
576
664
  });
577
665
 
578
666
  // src/TriggerGaugeHost.tsx
579
- import { jsx as jsx7 } from "react/jsx-runtime";
667
+ import { jsx as jsx11 } from "react/jsx-runtime";
580
668
  var getTriggerGauge = () => import("./TriggerGauge-2OJOOF6I.mjs").then((mod) => ({ default: mod.TriggerGauge }));
581
669
  function TriggerGaugeHost(props) {
582
- return /* @__PURE__ */ jsx7(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
670
+ return /* @__PURE__ */ jsx11(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
583
671
  }
584
672
 
585
673
  // src/useAutoAlign.ts
@@ -631,7 +719,7 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
631
719
  }
632
720
 
633
721
  // src/SectionedDropdown.tsx
634
- 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";
635
723
  var MENU_BORDER_WIDTH = 1;
636
724
  var TRIGGER_SIZE = 44;
637
725
  var MENU_MIN_WIDTH = 200;
@@ -642,7 +730,7 @@ var MENU_ITEM_HEIGHT = 40;
642
730
  var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
643
731
  var MENU_DIVIDER_HEIGHT = 9;
644
732
  var MENU_VERTICAL_PADDING = 12;
645
- 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" } }) {
646
734
  const ambientRotation = useRotation3();
647
735
  const rotation = rotationOverride ?? ambientRotation;
648
736
  const menuBg = dark ? "#000000" : "#FFFFFF";
@@ -656,8 +744,10 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
656
744
  const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
657
745
  return sum + rows + footer + divider;
658
746
  }, 0);
659
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
660
- 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;
661
751
  const onlySection = sections.length === 1 ? sections[0] : null;
662
752
  const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
663
753
  const triggerIcon = selectedOption?.icon ?? icon;
@@ -679,17 +769,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
679
769
  }
680
770
  gaugeOffset += section.options.length;
681
771
  }
682
- const anchorStyle = [styles7.anchor, open && styles7.anchorOpen];
772
+ const anchorStyle = [styles10.anchor, open && styles10.anchorOpen];
683
773
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
684
774
  const dividerStyle = { backgroundColor: menuBorderColor };
685
- return /* @__PURE__ */ jsxs7(View8, { style: anchorStyle, children: [
686
- /* @__PURE__ */ jsxs7(View8, { ref: triggerRef, collapsable: false, style: styles7.triggerBox, children: [
687
- /* @__PURE__ */ jsx8(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
688
- /* @__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) })
689
779
  ] }),
690
- /* @__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: [
691
- sectionIndex > 0 && /* @__PURE__ */ jsx8(View8, { style: [styles7.divider, dividerStyle] }),
692
- 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(
693
783
  SingleSection,
694
784
  {
695
785
  section,
@@ -702,7 +792,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
702
792
  if (autoDismiss) host.close();
703
793
  }
704
794
  }
705
- ) : /* @__PURE__ */ jsx8(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
795
+ ) : /* @__PURE__ */ jsx12(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
706
796
  ] }, section.id)) }) })
707
797
  ] });
708
798
  }
@@ -714,18 +804,18 @@ function SingleSection({
714
804
  labelFontFamily,
715
805
  onSelect
716
806
  }) {
717
- return /* @__PURE__ */ jsx8(Fragment3, { children: section.options.map((option) => {
807
+ return /* @__PURE__ */ jsx12(Fragment4, { children: section.options.map((option) => {
718
808
  const selected = option.value === section.value;
719
809
  const taken = !selected && option.value === section.takenValue;
720
810
  const rowColor = selected ? onAccentColor : mutedColor;
721
- const itemStyle = [styles7.item, selected && { backgroundColor: accentColor }, taken && styles7.itemTaken];
811
+ const itemStyle = [styles10.item, selected && { backgroundColor: accentColor }, taken && styles10.itemTaken];
722
812
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
723
813
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
724
- return /* @__PURE__ */ jsx8(TouchableRipple2, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs7(View8, { style: styles7.itemRow, children: [
725
- option.icon && /* @__PURE__ */ jsx8(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
726
- /* @__PURE__ */ jsxs7(View8, { style: styles7.itemLabelColumn, children: [
727
- /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: labelStyle, children: option.label }),
728
- 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 })
729
819
  ] })
730
820
  ] }) }, option.value);
731
821
  }) });
@@ -739,23 +829,23 @@ function MultiSection({
739
829
  allClearLabels
740
830
  }) {
741
831
  const allSelected = section.value.length === section.options.length;
742
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
832
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
743
833
  section.options.map((option, i) => {
744
834
  const selected = section.value.includes(option.value);
745
835
  const rowColor = selected ? onAccentColor : mutedColor;
746
836
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
747
837
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
748
- 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 }];
749
839
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
750
- 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: [
751
- option.icon && /* @__PURE__ */ jsx8(Icon4, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
752
- /* @__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 })
753
843
  ] }) }, option.value);
754
844
  }),
755
- 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 }) })
756
846
  ] });
757
847
  }
758
- var styles7 = StyleSheet8.create({
848
+ var styles10 = StyleSheet11.create({
759
849
  allClearLabel: {
760
850
  fontWeight: "bold",
761
851
  textAlign: "center"
@@ -772,7 +862,7 @@ var styles7 = StyleSheet8.create({
772
862
  zIndex: 100
773
863
  },
774
864
  divider: {
775
- height: StyleSheet8.hairlineWidth,
865
+ height: StyleSheet11.hairlineWidth,
776
866
  marginVertical: 4
777
867
  },
778
868
  item: {
@@ -825,24 +915,24 @@ var styles7 = StyleSheet8.create({
825
915
  });
826
916
 
827
917
  // src/ControlSchemePicker.tsx
828
- import { jsx as jsx9 } from "react/jsx-runtime";
829
- 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 }) {
830
920
  const isTouchPrimary = useIsTouchPrimaryDevice2();
831
921
  if (Platform3.OS !== "web" || isTouchPrimary) return null;
832
- 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 });
833
923
  }
834
924
 
835
925
  // src/CornerActionButtons.tsx
836
926
  import { IconButton as IconButton3 } from "@rific/feedback-press";
837
- import { StyleSheet as StyleSheet9 } from "react-native";
838
- 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";
839
929
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
840
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
841
- /* @__PURE__ */ jsx10(IconButton3, { icon: backIcon, iconColor: fg, size: 24, style: [styles8.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
842
- /* @__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" })
843
933
  ] });
844
934
  }
845
- var styles8 = StyleSheet9.create({
935
+ var styles11 = StyleSheet12.create({
846
936
  back: {
847
937
  position: "absolute"
848
938
  },
@@ -852,22 +942,22 @@ var styles8 = StyleSheet9.create({
852
942
  });
853
943
 
854
944
  // src/CornerStatusBadges.tsx
855
- import { StyleSheet as StyleSheet10, View as View9 } from "react-native";
945
+ import { StyleSheet as StyleSheet13, View as View11 } from "react-native";
856
946
  import { Icon as Icon5 } from "react-native-paper";
857
- 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";
858
948
  var BADGE_SIZE = 30;
859
949
  function StatusBadge({ icon }) {
860
- 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)" }) });
861
951
  }
862
952
  function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
863
953
  const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
864
954
  const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
865
- return /* @__PURE__ */ jsxs9(Fragment5, { children: [
866
- p1 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p1OnRightSide ? styles9.bottomRight : styles9.bottomLeft, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p1.icon }) }),
867
- 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 }) })
868
958
  ] });
869
959
  }
870
- var styles9 = StyleSheet10.create({
960
+ var styles12 = StyleSheet13.create({
871
961
  badge: {
872
962
  alignItems: "center",
873
963
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -889,9 +979,9 @@ var styles9 = StyleSheet10.create({
889
979
  import { defaultColors, getContrastColor } from "@rific/auto-paper";
890
980
  import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
891
981
  import { clamp, useRotation as useRotation4 } from "@tastic/core";
892
- import { ScrollView as ScrollView4, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
893
- import { Icon as Icon6, Text as Text6 } from "react-native-paper";
894
- 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";
895
985
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
896
986
  var DEFAULT_SIZE = 48;
897
987
  var SWATCH_SIZE = 28;
@@ -904,31 +994,37 @@ var SCREEN_MARGIN = 40;
904
994
  function widthForColumns(columns) {
905
995
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
906
996
  }
907
- 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 }) {
908
1006
  const ambientRotation = useRotation4();
909
1007
  const rotation = rotationOverride ?? ambientRotation;
910
1008
  const menuBg = dark ? "#000000" : "#FFFFFF";
911
1009
  const { width: windowWidth } = useWindowDimensions3();
912
- const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
913
- const resolvedColumns = columns ?? autoColumns;
914
- const swatchesWidth = widthForColumns(resolvedColumns);
915
- const swatchRows = Math.ceil(swatches.length / resolvedColumns);
916
- 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);
917
1011
  const open = host.openId === id;
918
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
919
- 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;
920
1016
  const displayFor = previewValue ?? ((hex) => hex);
921
1017
  const displayValue = displayFor(value);
922
1018
  const contrastColor = getContrastColor(displayValue);
923
1019
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
924
- return /* @__PURE__ */ jsxs10(View10, { style: [styles10.anchor, open && styles10.anchorOpen], children: [
925
- /* @__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 }) }) }),
926
- /* @__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) => {
927
1023
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
928
1024
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
929
1025
  const swappable = taken && allowSwapTaken;
930
1026
  const swatchDisplay = displayFor(swatch.value);
931
- return /* @__PURE__ */ jsx12(
1027
+ return /* @__PURE__ */ jsx16(
932
1028
  TouchableRipple3,
933
1029
  {
934
1030
  disabled: taken && !swappable,
@@ -937,15 +1033,15 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
937
1033
  if (autoDismiss) host.close();
938
1034
  },
939
1035
  borderless: true,
940
- style: [styles10.swatch, { backgroundColor: swatchDisplay }, selected && styles10.swatchSelected, taken && !swappable && styles10.swatchTaken],
941
- 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, {})
942
1038
  },
943
1039
  swatch.value
944
1040
  );
945
1041
  }) }) })
946
1042
  ] });
947
1043
  }
948
- var styles10 = StyleSheet11.create({
1044
+ var styles13 = StyleSheet14.create({
949
1045
  anchor: {
950
1046
  position: "relative"
951
1047
  },
@@ -1019,9 +1115,9 @@ var styles10 = StyleSheet11.create({
1019
1115
  // src/LabeledDropdown.tsx
1020
1116
  import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
1021
1117
  import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
1022
- import { ScrollView as ScrollView5, StyleSheet as StyleSheet12, View as View11 } from "react-native";
1023
- import { Icon as Icon7, Text as Text7 } from "react-native-paper";
1024
- 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";
1025
1121
  var TRIGGER_HEIGHT = 28;
1026
1122
  var POPOVER_WIDTH = 180;
1027
1123
  var ROW_HEIGHT = 36;
@@ -1044,22 +1140,22 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1044
1140
  onChange(option.value);
1045
1141
  host.close();
1046
1142
  };
1047
- return /* @__PURE__ */ jsxs11(View11, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1048
- /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => host.toggle(id), style: styles11.trigger, children: /* @__PURE__ */ jsxs11(View11, { ref: triggerRef, collapsable: false, style: styles11.triggerInner, children: [
1049
- /* @__PURE__ */ jsx13(Text7, { style: [styles11.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1050
- /* @__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 })
1051
1147
  ] }) }),
1052
- /* @__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) => {
1053
1149
  const isSelected = option.value === value;
1054
1150
  const onColor = getContrastColor2(color);
1055
- return /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => handleSelect(option), style: [styles11.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs11(View11, { style: styles11.rowInner, children: [
1056
- option.icon && /* @__PURE__ */ jsx13(Icon7, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1057
- /* @__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 })
1058
1154
  ] }) }, option.value);
1059
1155
  }) }) })
1060
1156
  ] });
1061
1157
  }
1062
- var styles11 = StyleSheet12.create({
1158
+ var styles14 = StyleSheet15.create({
1063
1159
  anchor: {
1064
1160
  position: "relative"
1065
1161
  },
@@ -1116,26 +1212,23 @@ function loadSkiaWeb() {
1116
1212
  return Promise.resolve();
1117
1213
  }
1118
1214
 
1119
- // src/PressAwayOverlay.tsx
1120
- import { Pressable, StyleSheet as StyleSheet13 } from "react-native";
1121
- import { jsx as jsx14 } from "react/jsx-runtime";
1122
- function PressAwayOverlay({ active, onPress, style }) {
1123
- if (!active) return null;
1124
- return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet13.absoluteFill, style] });
1125
- }
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";
1126
1219
 
1127
1220
  // src/ReadyButton.tsx
1128
1221
  import { TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
1129
- import { StyleSheet as StyleSheet14 } from "react-native";
1130
- import { Text as Text8 } from "react-native-paper";
1131
- 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";
1132
1225
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
1133
- const readyButtonStyle = [styles12.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1226
+ const readyButtonStyle = [styles15.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1134
1227
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
1135
1228
  const labelColorStyle = { color: ready ? "#000000" : color };
1136
- 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?" }) });
1137
1230
  }
1138
- var styles12 = StyleSheet14.create({
1231
+ var styles15 = StyleSheet16.create({
1139
1232
  readyButton: {
1140
1233
  alignItems: "center",
1141
1234
  borderRadius: 12,
@@ -1147,22 +1240,97 @@ var styles12 = StyleSheet14.create({
1147
1240
  }
1148
1241
  });
1149
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
+
1150
1318
  // src/SharedActionBand.tsx
1151
1319
  import { IconButton as IconButton4 } from "@rific/feedback-press";
1152
- import { StyleSheet as StyleSheet15, View as View12 } from "react-native";
1153
- 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";
1154
1322
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1155
- return /* @__PURE__ */ jsxs12(View12, { style: [styles13.column, popoverOpen && styles13.columnOpen], children: [
1156
- /* @__PURE__ */ jsxs12(View12, { style: styles13.actionsRow, children: [
1157
- /* @__PURE__ */ jsx16(IconButton4, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1158
- onRandomize && /* @__PURE__ */ jsx16(IconButton4, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1159
- onReset && /* @__PURE__ */ jsx16(IconButton4, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1160
- /* @__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" })
1161
1329
  ] }),
1162
1330
  children
1163
1331
  ] });
1164
1332
  }
1165
- var styles13 = StyleSheet15.create({
1333
+ var styles17 = StyleSheet19.create({
1166
1334
  actionsRow: {
1167
1335
  alignItems: "center",
1168
1336
  flexDirection: "row",
@@ -1177,83 +1345,109 @@ var styles13 = StyleSheet15.create({
1177
1345
  }
1178
1346
  });
1179
1347
 
1180
- // src/StatRow.tsx
1181
- import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1182
- import { StyleSheet as StyleSheet16, View as View13 } from "react-native";
1183
- import { Text as Text9 } from "react-native-paper";
1184
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1185
- function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1186
- const { dark } = useAutoPaperTheme5();
1187
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1188
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
1189
- return /* @__PURE__ */ jsxs13(View13, { style: styles14.row, children: [
1190
- /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1191
- /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: [styles14.value, { color: fg }], children: value })
1192
- ] });
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;
1193
1357
  }
1194
- var styles14 = StyleSheet16.create({
1195
- row: {
1196
- alignItems: "center",
1197
- flexDirection: "row",
1198
- justifyContent: "space-between"
1199
- },
1200
- value: {
1201
- fontWeight: "bold"
1202
- }
1203
- });
1204
-
1205
- // src/StatSection.tsx
1206
- import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1207
- import { StyleSheet as StyleSheet17, View as View14 } from "react-native";
1208
- import { Text as Text10 } from "react-native-paper";
1209
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1210
- function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1211
- const { dark } = useAutoPaperTheme6();
1212
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1213
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1214
- return /* @__PURE__ */ jsxs14(View14, { style: [styles15.section, { backgroundColor: sectionBg }], children: [
1215
- /* @__PURE__ */ jsx18(Text10, { variant: "labelMedium", style: [styles15.label, { color: fg }], children: label }),
1216
- children
1217
- ] });
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 });
1218
1369
  }
1219
- var styles15 = StyleSheet17.create({
1220
- // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1221
- // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1222
- // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
1223
- // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
1224
- label: {
1225
- fontWeight: "bold",
1226
- letterSpacing: 2
1227
- },
1228
- section: {
1229
- borderRadius: 12,
1230
- gap: 8,
1231
- 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"
1232
1376
  }
1233
1377
  });
1234
1378
 
1235
1379
  // src/UpdateDialog.tsx
1236
1380
  import { useUpdater as useUpdater2 } from "@rific/updater";
1237
- import { jsx as jsx19 } from "react/jsx-runtime";
1381
+ import { jsx as jsx23 } from "react/jsx-runtime";
1238
1382
  function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1239
1383
  const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1240
1384
  useUpdater2({ autoCheck, autoPrompt, onConfirm, onError });
1241
1385
  const date = manifest ? new Date(manifest.createdAt) : null;
1242
- 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) });
1243
1387
  }
1244
1388
 
1245
- // src/usePopoverHost.ts
1246
- import { useCallback as useCallback3, useState as useState5 } from "react";
1247
- function usePopoverHost() {
1248
- const [openId, setOpenId] = useState5(null);
1249
- const toggle = useCallback3((id) => {
1250
- 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();
1251
1404
  }, []);
1252
- const close = useCallback3(() => setOpenId(null), []);
1253
- 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 };
1254
1446
  }
1255
1447
  export {
1448
+ AchievementCatalogSection,
1256
1449
  AchievementRow,
1450
+ ActivityStatSection,
1257
1451
  BaseSettingsDialog,
1258
1452
  BaseStatsScreen,
1259
1453
  ConfirmDialog,
@@ -1266,20 +1460,28 @@ export {
1266
1460
  LOCKED_BADGE_COLOR,
1267
1461
  LabeledDropdown,
1268
1462
  MONO_FONT,
1463
+ PlayerSetupPanel,
1269
1464
  PopoverBody,
1270
1465
  PressAwayOverlay,
1271
1466
  ReadyButton,
1272
1467
  SectionedDropdown,
1273
1468
  SharedActionBand,
1274
1469
  SkiaGate,
1470
+ StaggeredWord,
1275
1471
  StatRow,
1276
1472
  StatSection,
1277
1473
  TriggerGaugeHost,
1278
1474
  UpdateDialog,
1475
+ getColorPopoverId,
1476
+ getCpuDifficultyPopoverId,
1477
+ getInlineColorPickerContentSize,
1279
1478
  getLabeledDropdownContentHeight,
1280
1479
  getOverlayCardColors,
1480
+ getStaggeredWordDuration,
1281
1481
  loadSkiaWeb,
1282
1482
  overlayStyles,
1283
1483
  useAutoAlign,
1284
- usePopoverHost
1484
+ usePopoverHost,
1485
+ useQuitConfirmation,
1486
+ useZoneClampedAlign
1285
1487
  };