@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.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __esm = (fn, res) => function __init() {
7
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -18,6 +20,14 @@ var __copyProps = (to, from, except, desc) => {
18
20
  }
19
21
  return to;
20
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
21
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
32
 
23
33
  // src/TriggerGauge.tsx
@@ -28,22 +38,22 @@ __export(TriggerGauge_exports, {
28
38
  function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
29
39
  const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
30
40
  const mountProgress = (0, import_react_native_reanimated.useSharedValue)(0);
31
- (0, import_react5.useLayoutEffect)(() => {
41
+ (0, import_react6.useLayoutEffect)(() => {
32
42
  mountProgress.value = (0, import_react_native_reanimated.withTiming)(1, { duration: totalDurationMs, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
33
43
  }, []);
34
44
  const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
35
45
  const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
36
46
  const step = segments > 0 ? totalSweepDeg / segments : 0;
37
- const centerDeg = (0, import_react5.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
47
+ const centerDeg = (0, import_react6.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
38
48
  const singleSelect = litIndices.length === 1;
39
49
  const angleDeg = (0, import_react_native_reanimated.useSharedValue)(centerDeg(litIndices[0] ?? 0));
40
50
  const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
41
- const [transitionFrom, setTransitionFrom] = (0, import_react5.useState)(litIndices);
51
+ const [transitionFrom, setTransitionFrom] = (0, import_react6.useState)(litIndices);
42
52
  const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
43
- const lastKeyRef = (0, import_react5.useRef)(litIndicesKey);
44
- const lastIndicesRef = (0, import_react5.useRef)(litIndices);
53
+ const lastKeyRef = (0, import_react6.useRef)(litIndicesKey);
54
+ const lastIndicesRef = (0, import_react6.useRef)(litIndices);
45
55
  const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
46
- (0, import_react5.useLayoutEffect)(() => {
56
+ (0, import_react6.useLayoutEffect)(() => {
47
57
  if (litIndicesKey !== lastKeyRef.current) {
48
58
  if (singleSelect && lastIndicesRef.current.length === 1) {
49
59
  angleDeg.value = (0, import_react_native_reanimated.withTiming)(centerDeg(litIndices[0]), { duration: SELECTION_TRANSITION_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
@@ -120,21 +130,21 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
120
130
  height: canvasSize,
121
131
  width: canvasSize
122
132
  };
123
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { style: [styles7.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
125
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
133
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { style: [styles10.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
134
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
135
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
126
136
  ] }) });
127
137
  }
128
- var import_react_native_skia, import_core5, import_react5, import_react_native9, import_react_native_reanimated, import_jsx_runtime8, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles7;
138
+ var import_react_native_skia, import_core5, import_react6, import_react_native12, import_react_native_reanimated, import_jsx_runtime12, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles10;
129
139
  var init_TriggerGauge = __esm({
130
140
  "src/TriggerGauge.tsx"() {
131
141
  "use strict";
132
142
  import_react_native_skia = require("@shopify/react-native-skia");
133
143
  import_core5 = require("@tastic/core");
134
- import_react5 = require("react");
135
- import_react_native9 = require("react-native");
144
+ import_react6 = require("react");
145
+ import_react_native12 = require("react-native");
136
146
  import_react_native_reanimated = require("react-native-reanimated");
137
- import_jsx_runtime8 = require("react/jsx-runtime");
147
+ import_jsx_runtime12 = require("react/jsx-runtime");
138
148
  UNLIT_THICKNESS = 3;
139
149
  LIT_THICKNESS = 7;
140
150
  DASH_GAP_PX = 9;
@@ -144,7 +154,7 @@ var init_TriggerGauge = __esm({
144
154
  MOUNT_DURATION_MS = 450;
145
155
  MOUNT_STAGGER_MS = 40;
146
156
  SELECTION_TRANSITION_DURATION_MS = 260;
147
- styles7 = import_react_native9.StyleSheet.create({
157
+ styles10 = import_react_native12.StyleSheet.create({
148
158
  container: {
149
159
  alignItems: "center",
150
160
  justifyContent: "center",
@@ -157,7 +167,9 @@ var init_TriggerGauge = __esm({
157
167
  // src/index.ts
158
168
  var index_exports = {};
159
169
  __export(index_exports, {
170
+ AchievementCatalogSection: () => AchievementCatalogSection,
160
171
  AchievementRow: () => AchievementRow,
172
+ ActivityStatSection: () => ActivityStatSection,
161
173
  BaseSettingsDialog: () => BaseSettingsDialog,
162
174
  BaseStatsScreen: () => BaseStatsScreen,
163
175
  ConfirmDialog: () => ConfirmDialog,
@@ -170,25 +182,39 @@ __export(index_exports, {
170
182
  LOCKED_BADGE_COLOR: () => LOCKED_BADGE_COLOR,
171
183
  LabeledDropdown: () => LabeledDropdown,
172
184
  MONO_FONT: () => MONO_FONT,
185
+ PlayerSetupPanel: () => PlayerSetupPanel,
173
186
  PopoverBody: () => PopoverBody,
174
187
  PressAwayOverlay: () => PressAwayOverlay,
175
188
  ReadyButton: () => ReadyButton,
176
189
  SectionedDropdown: () => SectionedDropdown,
177
190
  SharedActionBand: () => SharedActionBand,
178
191
  SkiaGate: () => SkiaGate,
192
+ StaggeredWord: () => StaggeredWord,
179
193
  StatRow: () => StatRow,
180
194
  StatSection: () => StatSection,
181
195
  TriggerGaugeHost: () => TriggerGaugeHost,
182
196
  UpdateDialog: () => UpdateDialog,
197
+ getColorPopoverId: () => getColorPopoverId,
198
+ getCpuDifficultyPopoverId: () => getCpuDifficultyPopoverId,
199
+ getInlineColorPickerContentSize: () => getInlineColorPickerContentSize,
183
200
  getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
184
201
  getOverlayCardColors: () => getOverlayCardColors,
202
+ getStaggeredWordDuration: () => getStaggeredWordDuration,
185
203
  loadSkiaWeb: () => loadSkiaWeb,
186
204
  overlayStyles: () => overlayStyles,
187
205
  useAutoAlign: () => useAutoAlign,
188
- usePopoverHost: () => usePopoverHost
206
+ usePopoverHost: () => usePopoverHost,
207
+ useQuitConfirmation: () => useQuitConfirmation,
208
+ useZoneClampedAlign: () => useZoneClampedAlign
189
209
  });
190
210
  module.exports = __toCommonJS(index_exports);
191
211
 
212
+ // src/AchievementCatalogSection.tsx
213
+ var import_auto_paper2 = require("@rific/auto-paper");
214
+ var import_react = require("react");
215
+ var import_react_native3 = require("react-native");
216
+ var import_react_native_paper2 = require("react-native-paper");
217
+
192
218
  // src/AchievementRow.tsx
193
219
  var import_auto_paper = require("@rific/auto-paper");
194
220
  var import_react_native = require("react-native");
@@ -257,24 +283,110 @@ var styles = import_react_native.StyleSheet.create({
257
283
  }
258
284
  });
259
285
 
260
- // src/BaseSettingsDialog.tsx
286
+ // src/fonts.ts
287
+ var import_react_native2 = require("react-native");
288
+ var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
289
+
290
+ // src/AchievementCatalogSection.tsx
291
+ var import_jsx_runtime2 = require("react/jsx-runtime");
292
+ function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
293
+ const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
294
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
295
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react.Fragment, { children: [
296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
297
+ rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(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))
298
+ ] });
299
+ }
300
+ var styles2 = import_react_native3.StyleSheet.create({
301
+ label: {
302
+ letterSpacing: 2,
303
+ marginTop: 8
304
+ }
305
+ });
306
+
307
+ // src/StatRow.tsx
261
308
  var import_auto_paper3 = require("@rific/auto-paper");
309
+ var import_react_native4 = require("react-native");
310
+ var import_react_native_paper3 = require("react-native-paper");
311
+ var import_jsx_runtime3 = require("react/jsx-runtime");
312
+ function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
313
+ const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
314
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
315
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
316
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.row, children: [
317
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: [styles3.value, { color: fg }], children: value })
319
+ ] });
320
+ }
321
+ var styles3 = import_react_native4.StyleSheet.create({
322
+ row: {
323
+ alignItems: "center",
324
+ flexDirection: "row",
325
+ justifyContent: "space-between"
326
+ },
327
+ value: {
328
+ fontWeight: "bold"
329
+ }
330
+ });
331
+
332
+ // src/StatSection.tsx
333
+ var import_auto_paper4 = require("@rific/auto-paper");
334
+ var import_react_native5 = require("react-native");
335
+ var import_react_native_paper4 = require("react-native-paper");
336
+ var import_jsx_runtime4 = require("react/jsx-runtime");
337
+ function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
338
+ const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
339
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
340
+ const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
341
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.section, { backgroundColor: sectionBg }], children: [
342
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "labelMedium", style: [styles4.label, { color: fg }], children: label }),
343
+ children
344
+ ] });
345
+ }
346
+ var styles4 = import_react_native5.StyleSheet.create({
347
+ // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
348
+ // same visual weight as StatRow's own row labels below it (both used the identical muted color;
349
+ // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
350
+ // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
351
+ label: {
352
+ fontWeight: "bold",
353
+ letterSpacing: 2
354
+ },
355
+ section: {
356
+ borderRadius: 12,
357
+ gap: 8,
358
+ padding: 16
359
+ }
360
+ });
361
+
362
+ // src/ActivityStatSection.tsx
363
+ var import_jsx_runtime5 = require("react/jsx-runtime");
364
+ function ActivityStatSection({ stats }) {
365
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StatSection, { label: "ACTIVITY", children: [
366
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
367
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
368
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
369
+ ] });
370
+ }
371
+
372
+ // src/BaseSettingsDialog.tsx
373
+ var import_auto_paper6 = require("@rific/auto-paper");
262
374
  var import_feedback_press2 = require("@rific/feedback-press");
263
375
  var import_updater = require("@rific/updater");
264
376
  var import_core2 = require("@tastic/core");
265
- var import_react2 = require("react");
266
- var import_react_native4 = require("react-native");
267
- var import_react_native_paper3 = require("react-native-paper");
377
+ var import_react3 = require("react");
378
+ var import_react_native8 = require("react-native");
379
+ var import_react_native_paper6 = require("react-native-paper");
268
380
 
269
381
  // src/ConfirmDialog.tsx
270
- var import_auto_paper2 = require("@rific/auto-paper");
382
+ var import_auto_paper5 = require("@rific/auto-paper");
271
383
  var import_feedback_press = require("@rific/feedback-press");
272
384
  var import_core = require("@tastic/core");
273
- var import_react_native3 = require("react-native");
274
- var import_react_native_paper2 = require("react-native-paper");
385
+ var import_react_native7 = require("react-native");
386
+ var import_react_native_paper5 = require("react-native-paper");
275
387
 
276
388
  // src/overlayCard.ts
277
- var import_react_native2 = require("react-native");
389
+ var import_react_native6 = require("react-native");
278
390
  function getOverlayCardColors(dark) {
279
391
  return {
280
392
  fg: dark ? "#FFFFFF" : "#000000",
@@ -283,7 +395,7 @@ function getOverlayCardColors(dark) {
283
395
  cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
284
396
  };
285
397
  }
286
- var overlayStyles = import_react_native2.StyleSheet.create({
398
+ var overlayStyles = import_react_native6.StyleSheet.create({
287
399
  card: {
288
400
  alignItems: "center",
289
401
  borderRadius: 20,
@@ -305,43 +417,62 @@ var overlayStyles = import_react_native2.StyleSheet.create({
305
417
  });
306
418
 
307
419
  // src/ConfirmDialog.tsx
308
- var import_jsx_runtime2 = require("react/jsx-runtime");
420
+ var import_jsx_runtime6 = require("react/jsx-runtime");
309
421
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
310
- const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
422
+ const { dark, colors } = (0, import_auto_paper5.useAutoPaperTheme)();
311
423
  if (!visible) return null;
312
424
  const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
313
425
  const accent = destructive ? colors.danger : colors.primary;
314
426
  const onAccent = destructive ? colors.onDanger : colors.onPrimary;
315
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [overlayStyles.overlay, styles2.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [overlayStyles.card, styles2.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
316
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 64, color: accent }),
317
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
318
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
319
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.actions, children: [
320
- cancelLabel !== void 0 && onCancel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
321
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: styles2.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
427
+ const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
428
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native7.View, { style: [overlayStyles.overlay, styles5.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native7.View, { style: [overlayStyles.card, styles5.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Icon, { source: icon, size: 64, color: accent }),
430
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Text, { variant: "headlineLarge", style: [styles5.title, { color: accent }], children: title }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Text, { variant: "bodyLarge", style: [styles5.body, { color: fg }], children: message }),
432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native7.View, { style: styles5.actions, children: [
433
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: styles5.actionButton, children: cancelLabel }),
434
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: showCancel ? styles5.actionButton : styles5.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
322
435
  ] })
323
436
  ] }) }) });
324
437
  }
325
- var styles2 = import_react_native3.StyleSheet.create({
326
- // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
327
- // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
328
- // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
329
- // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
438
+ var styles5 = import_react_native7.StyleSheet.create({
439
+ // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
440
+ // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
441
+ // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
442
+ // row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
443
+ // correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
444
+ // "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
445
+ // tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
446
+ // nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
447
+ // allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
448
+ // flexBasis) reaches the outer view, which is the real flex item inside this row; every other
449
+ // key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
450
+ // defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
451
+ // correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
452
+ // sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
453
+ // correctly regardless of which of the two views it ends up on. react-native-paper's Button also
330
454
  // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
331
455
  // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
332
456
  // fixable by just giving the row more width, until confirmed live that these two-word labels
333
457
  // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
334
- // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
335
- // width there's no side-by-side arrangement that fits both buttons at their natural label size,
336
- // so the layout has to give (stack) rather than the label (truncate).
458
+ // width fix below provides, on a narrow enough screen.
337
459
  actionButton: {
338
- flexBasis: 128,
339
- flexGrow: 1
460
+ flexGrow: 1,
461
+ minWidth: 128
462
+ },
463
+ // A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
464
+ // update-check "no update found" message) has nothing left to share actions' width with, so
465
+ // actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
466
+ // normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
467
+ // the card - centered by actions' own justifyContent below.
468
+ actionButtonSingle: {
469
+ minWidth: 128
340
470
  },
341
471
  actions: {
342
472
  flexDirection: "row",
343
473
  flexWrap: "wrap",
344
474
  gap: 12,
475
+ justifyContent: "center",
345
476
  width: "100%"
346
477
  },
347
478
  body: {
@@ -372,18 +503,18 @@ var styles2 = import_react_native3.StyleSheet.create({
372
503
  });
373
504
 
374
505
  // src/useUpdateConfirmBridge.ts
375
- var import_react = require("react");
506
+ var import_react2 = require("react");
376
507
  function useUpdateConfirmBridge() {
377
- const [manifest, setManifest] = (0, import_react.useState)(null);
378
- const resolveRef = (0, import_react.useRef)(null);
379
- const onConfirm = (0, import_react.useCallback)(
508
+ const [manifest, setManifest] = (0, import_react2.useState)(null);
509
+ const resolveRef = (0, import_react2.useRef)(null);
510
+ const onConfirm = (0, import_react2.useCallback)(
380
511
  (next) => new Promise((resolve) => {
381
512
  resolveRef.current = resolve;
382
513
  setManifest(next);
383
514
  }),
384
515
  []
385
516
  );
386
- const respond = (0, import_react.useCallback)((confirmed) => {
517
+ const respond = (0, import_react2.useCallback)((confirmed) => {
387
518
  setManifest(null);
388
519
  resolveRef.current?.(confirmed);
389
520
  resolveRef.current = null;
@@ -392,20 +523,20 @@ function useUpdateConfirmBridge() {
392
523
  }
393
524
 
394
525
  // src/BaseSettingsDialog.tsx
395
- var import_jsx_runtime3 = require("react/jsx-runtime");
526
+ var import_jsx_runtime7 = require("react/jsx-runtime");
396
527
  function SettingIcon({ source, color, containerColor }) {
397
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source, size: 18, color }) });
528
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [styles6.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Icon, { source, size: 18, color }) });
398
529
  }
399
530
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
400
- const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
531
+ const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
401
532
  const ambientRotation = (0, import_core2.useRotation)();
402
533
  const rotation = rotationOverride ?? ambientRotation;
403
534
  const isTouchPrimary = (0, import_core2.useIsTouchPrimaryDevice)();
404
535
  const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
405
536
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
406
- const sound = (0, import_react2.useContext)(import_feedback_press2.SoundContext);
537
+ const sound = (0, import_react3.useContext)(import_feedback_press2.SoundContext);
407
538
  const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
408
- const [infoMessage, setInfoMessage] = (0, import_react2.useState)(null);
539
+ const [infoMessage, setInfoMessage] = (0, import_react3.useState)(null);
409
540
  const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
410
541
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
411
542
  autoCheck: false,
@@ -416,32 +547,32 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
416
547
  });
417
548
  const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
418
549
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
419
- const showEdgeGuard = import_react_native4.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
420
- const showHaptics = import_react_native4.Platform.OS !== "web" && !hideHaptics;
550
+ const showEdgeGuard = import_react_native8.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
551
+ const showHaptics = import_react_native8.Platform.OS !== "web" && !hideHaptics;
421
552
  const showSound = !hideSound;
422
553
  const showAppearance = !hideAppearance;
423
- const showUpdateCheck = import_react_native4.Platform.OS !== "web" && !hideUpdateCheck;
424
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
425
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_auto_paper3.Dialog, { visible, onDismiss, style: [styles3.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
426
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.Title, { children: "Settings" }),
427
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.ScrollArea, { style: styles3.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles3.content, showsVerticalScrollIndicator: false, children: [
428
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleGroup, children: [
429
- showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles3.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
430
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
431
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
432
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
433
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
554
+ const showUpdateCheck = import_react_native8.Platform.OS !== "web" && !hideUpdateCheck;
555
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
556
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_auto_paper6.Dialog, { visible, onDismiss, style: [styles6.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper6.Dialog.Title, { children: "Settings" }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper6.Dialog.ScrollArea, { style: styles6.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.ScrollView, { contentContainerStyle: styles6.content, showsVerticalScrollIndicator: false, children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleGroup, children: [
560
+ showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles6.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleContent, children: [
561
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
562
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.flexShrink, children: [
563
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
434
565
  ] })
435
566
  ] }) }),
436
- showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles3.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
437
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
438
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
439
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
440
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
567
+ showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles6.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleContent, children: [
568
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
569
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.flexShrink, children: [
570
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
441
572
  ] })
442
573
  ] }) }),
443
- (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleRow, children: [
444
- showSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
574
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleRow, children: [
575
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
445
576
  import_feedback_press2.TouchableRipple,
446
577
  {
447
578
  soundDisabled: true,
@@ -450,15 +581,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
450
581
  setSoundSettings({ enabled });
451
582
  if (enabled) sound.selection?.();
452
583
  },
453
- style: styles3.toggleButtonInRow,
584
+ style: styles6.toggleButtonInRow,
454
585
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
455
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
456
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
457
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
586
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleContent, children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
458
589
  ] })
459
590
  }
460
591
  ),
461
- showHaptics && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
592
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
462
593
  import_feedback_press2.TouchableRipple,
463
594
  {
464
595
  hapticDisabled: true,
@@ -467,36 +598,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
467
598
  setHapticSettings({ vibrate });
468
599
  if (vibrate) forceHapticFeedback();
469
600
  },
470
- style: styles3.toggleButtonInRow,
601
+ style: styles6.toggleButtonInRow,
471
602
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
472
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
473
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
474
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
603
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.toggleContent, children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
475
606
  ] })
476
607
  }
477
608
  )
478
609
  ] })
479
610
  ] }),
480
- showAppearance && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
481
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
482
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.AutoAppearancePicker, { showLabels: false })
611
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.section, children: [
612
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "labelMedium", style: [styles6.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper6.AutoAppearancePicker, { showLabels: false })
483
614
  ] }),
484
615
  children,
485
- showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
486
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper3.Text, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
616
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles6.section, children: [
617
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native_paper6.Text, { variant: "labelSmall", style: [styles6.sectionLabel, { color: colors.onSurfaceVariant }], children: [
487
618
  "VERSION ",
488
619
  version,
489
620
  updateReady ? " \xB7 UPDATE READY" : ""
490
621
  ] }),
491
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
622
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
492
623
  ] })
493
624
  ] }) })
494
625
  ] }),
495
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(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) }),
496
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
626
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(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) }),
627
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
497
628
  ] });
498
629
  }
499
- var styles3 = import_react_native4.StyleSheet.create({
630
+ var styles6 = import_react_native8.StyleSheet.create({
500
631
  content: {
501
632
  gap: 24,
502
633
  paddingVertical: 20
@@ -564,31 +695,31 @@ var styles3 = import_react_native4.StyleSheet.create({
564
695
  });
565
696
 
566
697
  // src/BaseStatsScreen.tsx
567
- var import_auto_paper4 = require("@rific/auto-paper");
698
+ var import_auto_paper7 = require("@rific/auto-paper");
568
699
  var import_feedback_press3 = require("@rific/feedback-press");
569
700
  var import_core3 = require("@tastic/core");
570
- var import_react3 = require("react");
571
- var import_react_native5 = require("react-native");
572
- var import_react_native_paper4 = require("react-native-paper");
573
- var import_jsx_runtime4 = require("react/jsx-runtime");
701
+ var import_react4 = require("react");
702
+ var import_react_native9 = require("react-native");
703
+ var import_react_native_paper7 = require("react-native-paper");
704
+ var import_jsx_runtime8 = require("react/jsx-runtime");
574
705
  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 }) {
575
- const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
706
+ const { dark, colors } = (0, import_auto_paper7.useAutoPaperTheme)();
576
707
  const ambientRotation = (0, import_core3.useRotation)();
577
708
  const rotation = rotationOverride ?? ambientRotation;
578
- const [confirmResetVisible, setConfirmResetVisible] = (0, import_react3.useState)(false);
709
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react4.useState)(false);
579
710
  const showReset = !!onReset;
580
711
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
581
712
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
582
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.container, { backgroundColor: bg }], children: [
583
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
584
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
585
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles4.title, { color: fg }], children: title })
713
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: [styles7.container, { backgroundColor: bg }], children: [
714
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: [styles7.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
715
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: titleVariant, style: [styles7.title, { color: fg }], children: title })
586
717
  ] }),
587
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.ScrollView, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
718
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.ScrollView, { style: styles7.scrollView, contentContainerStyle: [styles7.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
588
719
  children,
589
- showReset && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles4.resetButton, children: resetLabel })
720
+ showReset && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles7.resetButton, children: resetLabel })
590
721
  ] }),
591
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
722
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
592
723
  ConfirmDialog,
593
724
  {
594
725
  visible: confirmResetVisible,
@@ -608,7 +739,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
608
739
  )
609
740
  ] });
610
741
  }
611
- var styles4 = import_react_native5.StyleSheet.create({
742
+ var styles7 = import_react_native9.StyleSheet.create({
612
743
  container: {
613
744
  flex: 1
614
745
  },
@@ -640,18 +771,18 @@ var styles4 = import_react_native5.StyleSheet.create({
640
771
 
641
772
  // src/ContentGutter.tsx
642
773
  var import_core4 = require("@tastic/core");
643
- var import_react_native6 = require("react-native");
644
- var import_jsx_runtime5 = require("react/jsx-runtime");
774
+ var import_react_native10 = require("react-native");
775
+ var import_jsx_runtime9 = require("react/jsx-runtime");
645
776
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
646
- const { width: windowWidth } = (0, import_react_native6.useWindowDimensions)();
777
+ const { width: windowWidth } = (0, import_react_native10.useWindowDimensions)();
647
778
  const { contentWidth, gutterWidth } = (0, import_core4.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
648
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: [styles5.row, style], children: [
649
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
650
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: { width: contentWidth }, children }),
651
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
779
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles8.row, style], children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [styles8.gutter, { width: gutterWidth }], children: leftGutter }),
781
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: { width: contentWidth }, children }),
782
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [styles8.gutter, { width: gutterWidth }], children: rightGutter })
652
783
  ] });
653
784
  }
654
- var styles5 = import_react_native6.StyleSheet.create({
785
+ var styles8 = import_react_native10.StyleSheet.create({
655
786
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
656
787
  // column instead of clipping cleanly at its own real width.
657
788
  gutter: {
@@ -664,44 +795,40 @@ var styles5 = import_react_native6.StyleSheet.create({
664
795
 
665
796
  // src/ControlSchemePicker.tsx
666
797
  var import_core7 = require("@tastic/core");
667
- var import_react_native12 = require("react-native");
798
+ var import_react_native15 = require("react-native");
668
799
 
669
800
  // src/SectionedDropdown.tsx
670
- var import_auto_paper5 = require("@rific/auto-paper");
801
+ var import_auto_paper8 = require("@rific/auto-paper");
671
802
  var import_feedback_press4 = require("@rific/feedback-press");
672
803
  var import_core6 = require("@tastic/core");
673
- var import_react_native11 = require("react-native");
674
- var import_react_native_paper5 = require("react-native-paper");
675
-
676
- // src/fonts.ts
677
- var import_react_native7 = require("react-native");
678
- var MONO_FONT = import_react_native7.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
804
+ var import_react_native14 = require("react-native");
805
+ var import_react_native_paper8 = require("react-native-paper");
679
806
 
680
807
  // src/PopoverBody.tsx
681
- var import_react_native8 = require("react-native");
682
- var import_jsx_runtime6 = require("react/jsx-runtime");
808
+ var import_react_native11 = require("react-native");
809
+ var import_jsx_runtime10 = require("react/jsx-runtime");
683
810
  var CARET_SIZE = 7;
684
811
  var CARET_RING_OFFSET = 2;
685
812
  var CARET_DIP = 1;
686
813
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
687
814
  if (!visible) return null;
688
815
  const above = verticalAlign === "above";
689
- const alignStyle = align === "left" ? styles6.contentLeft : align === "right" ? styles6.contentRight : styles6.contentCenter;
690
- const verticalStyle = above ? styles6.contentAbove : styles6.contentBelow;
816
+ const alignStyle = align === "left" ? styles9.contentLeft : align === "right" ? styles9.contentRight : styles9.contentCenter;
817
+ const verticalStyle = above ? styles9.contentAbove : styles9.contentBelow;
691
818
  const ringSize = CARET_SIZE + caretBorderWidth;
692
819
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
693
820
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
694
821
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
695
822
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
696
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native8.View, { style: [styles6.content, alignStyle, verticalStyle], children: [
823
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: [styles9.content, alignStyle, verticalStyle], children: [
697
824
  children,
698
- caretColor && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
699
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.View, { style: [styles6.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
700
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.View, { style: [styles6.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
825
+ caretColor && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
827
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
701
828
  ] })
702
829
  ] });
703
830
  }
704
- var styles6 = import_react_native8.StyleSheet.create({
831
+ var styles9 = import_react_native11.StyleSheet.create({
705
832
  caret: {
706
833
  borderLeftColor: "transparent",
707
834
  borderRightColor: "transparent",
@@ -744,11 +871,11 @@ var styles6 = import_react_native8.StyleSheet.create({
744
871
  });
745
872
 
746
873
  // src/SkiaGate.tsx
747
- var import_react4 = require("react");
748
- var import_jsx_runtime7 = require("react/jsx-runtime");
874
+ var import_react5 = require("react");
875
+ var import_jsx_runtime11 = require("react/jsx-runtime");
749
876
  function SkiaGate({ getComponent, componentProps, fallback = null }) {
750
- const [Component, setComponent] = (0, import_react4.useState)(null);
751
- (0, import_react4.useEffect)(() => {
877
+ const [Component, setComponent] = (0, import_react5.useState)(null);
878
+ (0, import_react5.useEffect)(() => {
752
879
  let cancelled = false;
753
880
  getComponent().then((mod) => {
754
881
  if (!cancelled) setComponent(() => mod.default);
@@ -758,19 +885,19 @@ function SkiaGate({ getComponent, componentProps, fallback = null }) {
758
885
  };
759
886
  }, [getComponent]);
760
887
  if (!Component) return fallback;
761
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Component, { ...componentProps });
888
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Component, { ...componentProps });
762
889
  }
763
890
 
764
891
  // src/TriggerGaugeHost.tsx
765
- var import_jsx_runtime9 = require("react/jsx-runtime");
892
+ var import_jsx_runtime13 = require("react/jsx-runtime");
766
893
  var getTriggerGauge = () => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((mod) => ({ default: mod.TriggerGauge }));
767
894
  function TriggerGaugeHost(props) {
768
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
895
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
769
896
  }
770
897
 
771
898
  // src/useAutoAlign.ts
772
- var import_react6 = require("react");
773
- var import_react_native10 = require("react-native");
899
+ var import_react7 = require("react");
900
+ var import_react_native13 = require("react-native");
774
901
  var EDGE_MARGIN = 12;
775
902
  function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
776
903
  if (rotation === 0) return { x, y, width, height };
@@ -782,18 +909,18 @@ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
782
909
  return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
783
910
  }
784
911
  function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
785
- const triggerRef = (0, import_react6.useRef)(null);
786
- const { width: windowWidth, height: windowHeight } = (0, import_react_native10.useWindowDimensions)();
787
- const [align, setAlign] = (0, import_react6.useState)("center");
788
- const [verticalAlign, setVerticalAlign] = (0, import_react6.useState)("below");
789
- const [maxHeight, setMaxHeight] = (0, import_react6.useState)(contentHeight);
790
- const [measured, setMeasured] = (0, import_react6.useState)(false);
791
- const [prevOpen, setPrevOpen] = (0, import_react6.useState)(open);
912
+ const triggerRef = (0, import_react7.useRef)(null);
913
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native13.useWindowDimensions)();
914
+ const [align, setAlign] = (0, import_react7.useState)("center");
915
+ const [verticalAlign, setVerticalAlign] = (0, import_react7.useState)("below");
916
+ const [maxHeight, setMaxHeight] = (0, import_react7.useState)(contentHeight);
917
+ const [measured, setMeasured] = (0, import_react7.useState)(false);
918
+ const [prevOpen, setPrevOpen] = (0, import_react7.useState)(open);
792
919
  if (open !== prevOpen) {
793
920
  setPrevOpen(open);
794
921
  if (open) setMeasured(false);
795
922
  }
796
- const measure = (0, import_react6.useCallback)(() => {
923
+ const measure = (0, import_react7.useCallback)(() => {
797
924
  triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
798
925
  const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
799
926
  const centerX = x + triggerWidth / 2;
@@ -810,14 +937,14 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
810
937
  setMeasured(true);
811
938
  });
812
939
  }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
813
- (0, import_react6.useEffect)(() => {
940
+ (0, import_react7.useEffect)(() => {
814
941
  if (open) measure();
815
942
  }, [open, measure]);
816
943
  return { align, maxHeight, measured, triggerRef, verticalAlign };
817
944
  }
818
945
 
819
946
  // src/SectionedDropdown.tsx
820
- var import_jsx_runtime10 = require("react/jsx-runtime");
947
+ var import_jsx_runtime14 = require("react/jsx-runtime");
821
948
  var MENU_BORDER_WIDTH = 1;
822
949
  var TRIGGER_SIZE = 44;
823
950
  var MENU_MIN_WIDTH = 200;
@@ -828,12 +955,12 @@ var MENU_ITEM_HEIGHT = 40;
828
955
  var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
829
956
  var MENU_DIVIDER_HEIGHT = 9;
830
957
  var MENU_VERTICAL_PADDING = 12;
831
- 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" } }) {
958
+ 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" } }) {
832
959
  const ambientRotation = (0, import_core6.useRotation)();
833
960
  const rotation = rotationOverride ?? ambientRotation;
834
961
  const menuBg = dark ? "#000000" : "#FFFFFF";
835
- const menuBorderColor = (0, import_auto_paper5.getBlendedColor)(mutedColor, menuBg, 0.5);
836
- const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper5.getColorRoles)(accentColor, menuBg).onColor;
962
+ const menuBorderColor = (0, import_auto_paper8.getBlendedColor)(mutedColor, menuBg, 0.5);
963
+ const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper8.getColorRoles)(accentColor, menuBg).onColor;
837
964
  const fg = dark ? "#FFFFFF" : "#000000";
838
965
  const open = host.openId === id;
839
966
  const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
@@ -842,8 +969,10 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
842
969
  const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
843
970
  return sum + rows + footer + divider;
844
971
  }, 0);
845
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
846
- const align = alignOverride ?? autoAlign;
972
+ const auto = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
973
+ const placement = alignOverride ?? auto;
974
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
975
+ const align = forcedAlign ?? placement.align;
847
976
  const onlySection = sections.length === 1 ? sections[0] : null;
848
977
  const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
849
978
  const triggerIcon = selectedOption?.icon ?? icon;
@@ -865,17 +994,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
865
994
  }
866
995
  gaugeOffset += section.options.length;
867
996
  }
868
- const anchorStyle = [styles8.anchor, open && styles8.anchorOpen];
997
+ const anchorStyle = [styles11.anchor, open && styles11.anchorOpen];
869
998
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
870
999
  const dividerStyle = { backgroundColor: menuBorderColor };
871
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: anchorStyle, children: [
872
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { ref: triggerRef, collapsable: false, style: styles8.triggerBox, children: [
873
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
874
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
1000
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { style: anchorStyle, children: [
1001
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { ref: triggerRef, collapsable: false, style: styles11.triggerBox, children: [
1002
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
1003
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
875
1004
  ] }),
876
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.ScrollView, { style: [styles8.menu, menuStyle], contentContainerStyle: styles8.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { children: [
877
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles8.divider, dividerStyle] }),
878
- section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1005
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native14.ScrollView, { style: [styles11.menu, menuStyle], contentContainerStyle: styles11.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { children: [
1006
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native14.View, { style: [styles11.divider, dividerStyle] }),
1007
+ section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
879
1008
  SingleSection,
880
1009
  {
881
1010
  section,
@@ -888,7 +1017,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
888
1017
  if (autoDismiss) host.close();
889
1018
  }
890
1019
  }
891
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
1020
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
892
1021
  ] }, section.id)) }) })
893
1022
  ] });
894
1023
  }
@@ -900,18 +1029,18 @@ function SingleSection({
900
1029
  labelFontFamily,
901
1030
  onSelect
902
1031
  }) {
903
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: section.options.map((option) => {
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: section.options.map((option) => {
904
1033
  const selected = option.value === section.value;
905
1034
  const taken = !selected && option.value === section.takenValue;
906
1035
  const rowColor = selected ? onAccentColor : mutedColor;
907
- const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, taken && styles8.itemTaken];
1036
+ const itemStyle = [styles11.item, selected && { backgroundColor: accentColor }, taken && styles11.itemTaken];
908
1037
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
909
1038
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
910
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.TouchableRipple, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemRow, children: [
911
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
912
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemLabelColumn, children: [
913
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
914
- option.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
1039
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press4.TouchableRipple, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { style: styles11.itemRow, children: [
1040
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1041
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { style: styles11.itemLabelColumn, children: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
1043
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
915
1044
  ] })
916
1045
  ] }) }, option.value);
917
1046
  }) });
@@ -925,23 +1054,23 @@ function MultiSection({
925
1054
  allClearLabels
926
1055
  }) {
927
1056
  const allSelected = section.value.length === section.options.length;
928
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1057
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
929
1058
  section.options.map((option, i) => {
930
1059
  const selected = section.value.includes(option.value);
931
1060
  const rowColor = selected ? onAccentColor : mutedColor;
932
1061
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
933
1062
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
934
- const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
1063
+ const itemStyle = [styles11.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
935
1064
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
936
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemRow, children: [
937
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
938
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.itemLabelFlex, labelStyle], children: option.label })
1065
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { style: styles11.itemRow, children: [
1066
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1067
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "labelLarge", style: [styles11.itemLabelFlex, labelStyle], children: option.label })
939
1068
  ] }) }, option.value);
940
1069
  }),
941
- section.allClear && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles8.item, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
1070
+ section.allClear && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles11.item, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "labelLarge", style: [styles11.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
942
1071
  ] });
943
1072
  }
944
- var styles8 = import_react_native11.StyleSheet.create({
1073
+ var styles11 = import_react_native14.StyleSheet.create({
945
1074
  allClearLabel: {
946
1075
  fontWeight: "bold",
947
1076
  textAlign: "center"
@@ -958,7 +1087,7 @@ var styles8 = import_react_native11.StyleSheet.create({
958
1087
  zIndex: 100
959
1088
  },
960
1089
  divider: {
961
- height: import_react_native11.StyleSheet.hairlineWidth,
1090
+ height: import_react_native14.StyleSheet.hairlineWidth,
962
1091
  marginVertical: 4
963
1092
  },
964
1093
  item: {
@@ -1011,24 +1140,24 @@ var styles8 = import_react_native11.StyleSheet.create({
1011
1140
  });
1012
1141
 
1013
1142
  // src/ControlSchemePicker.tsx
1014
- var import_jsx_runtime11 = require("react/jsx-runtime");
1015
- function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily }) {
1143
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1144
+ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily }) {
1016
1145
  const isTouchPrimary = (0, import_core7.useIsTouchPrimaryDevice)();
1017
- if (import_react_native12.Platform.OS !== "web" || isTouchPrimary) return null;
1018
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SectionedDropdown, { id, host, icon: "keyboard-outline", accessibilityLabel: "Control scheme", sections: [{ kind: "single", id: "controls", options, value, onChange, takenValue }], accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily });
1146
+ if (import_react_native15.Platform.OS !== "web" || isTouchPrimary) return null;
1147
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(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 });
1019
1148
  }
1020
1149
 
1021
1150
  // src/CornerActionButtons.tsx
1022
1151
  var import_feedback_press5 = require("@rific/feedback-press");
1023
- var import_react_native13 = require("react-native");
1024
- var import_jsx_runtime12 = require("react/jsx-runtime");
1152
+ var import_react_native16 = require("react-native");
1153
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1025
1154
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
1026
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1027
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press5.IconButton, { icon: backIcon, iconColor: fg, size: 24, style: [styles9.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
1028
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press5.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles9.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
1155
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_feedback_press5.IconButton, { icon: backIcon, iconColor: fg, size: 24, style: [styles12.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_feedback_press5.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles12.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
1029
1158
  ] });
1030
1159
  }
1031
- var styles9 = import_react_native13.StyleSheet.create({
1160
+ var styles12 = import_react_native16.StyleSheet.create({
1032
1161
  back: {
1033
1162
  position: "absolute"
1034
1163
  },
@@ -1038,22 +1167,22 @@ var styles9 = import_react_native13.StyleSheet.create({
1038
1167
  });
1039
1168
 
1040
1169
  // src/CornerStatusBadges.tsx
1041
- var import_react_native14 = require("react-native");
1042
- var import_react_native_paper6 = require("react-native-paper");
1043
- var import_jsx_runtime13 = require("react/jsx-runtime");
1170
+ var import_react_native17 = require("react-native");
1171
+ var import_react_native_paper9 = require("react-native-paper");
1172
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1044
1173
  var BADGE_SIZE = 30;
1045
1174
  function StatusBadge({ icon }) {
1046
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native14.View, { style: styles10.badge, children: icon && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_paper6.Icon, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
1175
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native17.View, { style: styles13.badge, children: icon && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper9.Icon, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
1047
1176
  }
1048
1177
  function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
1049
1178
  const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
1050
1179
  const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
1051
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1052
- p1 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native14.View, { pointerEvents: "none", style: p1OnRightSide ? styles10.bottomRight : styles10.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StatusBadge, { icon: p1.icon }) }),
1053
- p2 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native14.View, { pointerEvents: "none", style: p2OnLeftSide ? styles10.topLeft : styles10.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StatusBadge, { icon: p2.icon }) })
1180
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1181
+ p1 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native17.View, { pointerEvents: "none", style: p1OnRightSide ? styles13.bottomRight : styles13.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusBadge, { icon: p1.icon }) }),
1182
+ p2 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native17.View, { pointerEvents: "none", style: p2OnLeftSide ? styles13.topLeft : styles13.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusBadge, { icon: p2.icon }) })
1054
1183
  ] });
1055
1184
  }
1056
- var styles10 = import_react_native14.StyleSheet.create({
1185
+ var styles13 = import_react_native17.StyleSheet.create({
1057
1186
  badge: {
1058
1187
  alignItems: "center",
1059
1188
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -1072,12 +1201,12 @@ var styles10 = import_react_native14.StyleSheet.create({
1072
1201
  });
1073
1202
 
1074
1203
  // src/InlineColorPicker.tsx
1075
- var import_auto_paper6 = require("@rific/auto-paper");
1204
+ var import_auto_paper9 = require("@rific/auto-paper");
1076
1205
  var import_feedback_press6 = require("@rific/feedback-press");
1077
1206
  var import_core8 = require("@tastic/core");
1078
- var import_react_native15 = require("react-native");
1079
- var import_react_native_paper7 = require("react-native-paper");
1080
- var import_jsx_runtime14 = require("react/jsx-runtime");
1207
+ var import_react_native18 = require("react-native");
1208
+ var import_react_native_paper10 = require("react-native-paper");
1209
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1081
1210
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
1082
1211
  var DEFAULT_SIZE = 48;
1083
1212
  var SWATCH_SIZE = 28;
@@ -1090,31 +1219,37 @@ var SCREEN_MARGIN = 40;
1090
1219
  function widthForColumns(columns) {
1091
1220
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
1092
1221
  }
1093
- function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper6.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1222
+ function getInlineColorPickerContentSize(swatchCount, windowWidth, columns) {
1223
+ const autoColumns = (0, import_core8.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1224
+ const resolvedColumns = columns ?? autoColumns;
1225
+ const width = widthForColumns(resolvedColumns);
1226
+ const rows = Math.ceil(swatchCount / resolvedColumns);
1227
+ const height = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * rows + SWATCHES_GAP * (rows - 1);
1228
+ return { width, height };
1229
+ }
1230
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper9.defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1094
1231
  const ambientRotation = (0, import_core8.useRotation)();
1095
1232
  const rotation = rotationOverride ?? ambientRotation;
1096
1233
  const menuBg = dark ? "#000000" : "#FFFFFF";
1097
- const { width: windowWidth } = (0, import_react_native15.useWindowDimensions)();
1098
- const autoColumns = (0, import_core8.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1099
- const resolvedColumns = columns ?? autoColumns;
1100
- const swatchesWidth = widthForColumns(resolvedColumns);
1101
- const swatchRows = Math.ceil(swatches.length / resolvedColumns);
1102
- const swatchesHeight = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * swatchRows + SWATCHES_GAP * (swatchRows - 1);
1234
+ const { width: windowWidth } = (0, import_react_native18.useWindowDimensions)();
1235
+ const { width: swatchesWidth, height: swatchesHeight } = getInlineColorPickerContentSize(swatches.length, windowWidth, columns);
1103
1236
  const open = host.openId === id;
1104
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1105
- const align = alignOverride ?? autoAlign;
1237
+ const auto = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1238
+ const placement = alignOverride ?? auto;
1239
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
1240
+ const align = forcedAlign ?? placement.align;
1106
1241
  const displayFor = previewValue ?? ((hex) => hex);
1107
1242
  const displayValue = displayFor(value);
1108
- const contrastColor = (0, import_auto_paper6.getContrastColor)(displayValue);
1243
+ const contrastColor = (0, import_auto_paper9.getContrastColor)(displayValue);
1109
1244
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
1110
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1111
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles11.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native15.View, { ref: triggerRef, collapsable: false, style: styles11.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper7.Text, { style: [styles11.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper7.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1112
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native15.ScrollView, { style: [styles11.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles11.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1245
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native18.View, { style: [styles14.anchor, open && styles14.anchorOpen], children: [
1246
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles14.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.View, { ref: triggerRef, collapsable: false, style: styles14.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Text, { style: [styles14.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1247
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.ScrollView, { style: [styles14.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles14.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1113
1248
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
1114
1249
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
1115
1250
  const swappable = taken && allowSwapTaken;
1116
1251
  const swatchDisplay = displayFor(swatch.value);
1117
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1252
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1118
1253
  import_feedback_press6.TouchableRipple,
1119
1254
  {
1120
1255
  disabled: taken && !swappable,
@@ -1123,15 +1258,15 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
1123
1258
  if (autoDismiss) host.close();
1124
1259
  },
1125
1260
  borderless: true,
1126
- style: [styles11.swatch, { backgroundColor: swatchDisplay }, selected && styles11.swatchSelected, taken && !swappable && styles11.swatchTaken],
1127
- children: selected ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper7.Icon, { source: "check", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper7.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper7.Icon, { source: "close", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native15.View, {})
1261
+ style: [styles14.swatch, { backgroundColor: swatchDisplay }, selected && styles14.swatchSelected, taken && !swappable && styles14.swatchTaken],
1262
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Icon, { source: "check", size: 16, color: (0, import_auto_paper9.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper9.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Icon, { source: "close", size: 16, color: (0, import_auto_paper9.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.View, {})
1128
1263
  },
1129
1264
  swatch.value
1130
1265
  );
1131
1266
  }) }) })
1132
1267
  ] });
1133
1268
  }
1134
- var styles11 = import_react_native15.StyleSheet.create({
1269
+ var styles14 = import_react_native18.StyleSheet.create({
1135
1270
  anchor: {
1136
1271
  position: "relative"
1137
1272
  },
@@ -1203,11 +1338,11 @@ var styles11 = import_react_native15.StyleSheet.create({
1203
1338
  });
1204
1339
 
1205
1340
  // src/LabeledDropdown.tsx
1206
- var import_auto_paper7 = require("@rific/auto-paper");
1341
+ var import_auto_paper10 = require("@rific/auto-paper");
1207
1342
  var import_feedback_press7 = require("@rific/feedback-press");
1208
- var import_react_native16 = require("react-native");
1209
- var import_react_native_paper8 = require("react-native-paper");
1210
- var import_jsx_runtime15 = require("react/jsx-runtime");
1343
+ var import_react_native19 = require("react-native");
1344
+ var import_react_native_paper11 = require("react-native-paper");
1345
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1211
1346
  var TRIGGER_HEIGHT = 28;
1212
1347
  var POPOVER_WIDTH = 180;
1213
1348
  var ROW_HEIGHT = 36;
@@ -1230,22 +1365,22 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1230
1365
  onChange(option.value);
1231
1366
  host.close();
1232
1367
  };
1233
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native16.View, { style: [styles12.anchor, open && styles12.anchorOpen], children: [
1234
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => host.toggle(id), style: styles12.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native16.View, { ref: triggerRef, collapsable: false, style: styles12.triggerInner, children: [
1235
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1236
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: "menu-down", size: 16, color })
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native19.View, { style: [styles15.anchor, open && styles15.anchorOpen], children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => host.toggle(id), style: styles15.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native19.View, { ref: triggerRef, collapsable: false, style: styles15.triggerInner, children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Text, { style: [styles15.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1371
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: "menu-down", size: 16, color })
1237
1372
  ] }) }),
1238
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native16.ScrollView, { style: [styles12.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles12.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1373
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native19.ScrollView, { style: [styles15.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles15.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1239
1374
  const isSelected = option.value === value;
1240
- const onColor = (0, import_auto_paper7.getContrastColor)(color);
1241
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles12.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native16.View, { style: styles12.rowInner, children: [
1242
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1243
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1375
+ const onColor = (0, import_auto_paper10.getContrastColor)(color);
1376
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles15.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native19.View, { style: styles15.rowInner, children: [
1377
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1378
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Text, { style: [styles15.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1244
1379
  ] }) }, option.value);
1245
1380
  }) }) })
1246
1381
  ] });
1247
1382
  }
1248
- var styles12 = import_react_native16.StyleSheet.create({
1383
+ var styles15 = import_react_native19.StyleSheet.create({
1249
1384
  anchor: {
1250
1385
  position: "relative"
1251
1386
  },
@@ -1302,26 +1437,23 @@ function loadSkiaWeb() {
1302
1437
  return Promise.resolve();
1303
1438
  }
1304
1439
 
1305
- // src/PressAwayOverlay.tsx
1306
- var import_react_native17 = require("react-native");
1307
- var import_jsx_runtime16 = require("react/jsx-runtime");
1308
- function PressAwayOverlay({ active, onPress, style }) {
1309
- if (!active) return null;
1310
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native17.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native17.StyleSheet.absoluteFill, style] });
1311
- }
1440
+ // src/PlayerSetupPanel.tsx
1441
+ var import_profile = require("@tastic/profile");
1442
+ var import_react_native21 = require("react-native");
1443
+ var import_react_native_paper13 = require("react-native-paper");
1312
1444
 
1313
1445
  // src/ReadyButton.tsx
1314
1446
  var import_feedback_press8 = require("@rific/feedback-press");
1315
- var import_react_native18 = require("react-native");
1316
- var import_react_native_paper9 = require("react-native-paper");
1317
- var import_jsx_runtime17 = require("react/jsx-runtime");
1447
+ var import_react_native20 = require("react-native");
1448
+ var import_react_native_paper12 = require("react-native-paper");
1449
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1318
1450
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
1319
- const readyButtonStyle = [styles13.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1451
+ const readyButtonStyle = [styles16.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1320
1452
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
1321
1453
  const labelColorStyle = { color: ready ? "#000000" : color };
1322
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press8.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper9.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1454
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_feedback_press8.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1323
1455
  }
1324
- var styles13 = import_react_native18.StyleSheet.create({
1456
+ var styles16 = import_react_native20.StyleSheet.create({
1325
1457
  readyButton: {
1326
1458
  alignItems: "center",
1327
1459
  borderRadius: 12,
@@ -1333,22 +1465,97 @@ var styles13 = import_react_native18.StyleSheet.create({
1333
1465
  }
1334
1466
  });
1335
1467
 
1468
+ // src/usePopoverHost.ts
1469
+ var import_react8 = require("react");
1470
+ function usePopoverHost() {
1471
+ const [openId, setOpenId] = (0, import_react8.useState)(null);
1472
+ const toggle = (0, import_react8.useCallback)((id) => {
1473
+ setOpenId((prev) => prev === id ? null : id);
1474
+ }, []);
1475
+ const close = (0, import_react8.useCallback)(() => setOpenId(null), []);
1476
+ return { openId, toggle, close };
1477
+ }
1478
+
1479
+ // src/PlayerSetupPanel.tsx
1480
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1481
+ function getColorPopoverId(idPrefix) {
1482
+ return `${idPrefix}-color`;
1483
+ }
1484
+ function getCpuDifficultyPopoverId(idPrefix) {
1485
+ return `${idPrefix}-difficulty`;
1486
+ }
1487
+ 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" }) {
1488
+ const ownHost = usePopoverHost();
1489
+ const popover = host ?? ownHost;
1490
+ const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false;
1491
+ const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || secondPickerId !== void 0 && popover.openId === secondPickerId;
1492
+ const showProfilePicker = isHuman && profiles !== void 0 && !!onProfileSelect;
1493
+ const showCpuDifficulty = !isHuman && cpuDifficulty !== void 0 && cpuDifficultyOptions !== void 0 && !!onCpuDifficultyChange;
1494
+ const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null;
1495
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native21.View, { style: [styles17.panel, ownPopoverOpen && styles17.panelOpen], children: [
1496
+ showProfilePicker ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_profile.ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles }) : showCpuDifficulty ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LabeledDropdown, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, alignOverride: cpuDifficultyAlignOverride }) : label && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Text, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
1497
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_react_native21.View, { style: [styles17.pickerRow, pickerRowPopoverOpen && styles17.pickerRowOpen], children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(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 }),
1499
+ secondPicker
1500
+ ] }),
1501
+ isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReadyButton, { color, ready: ready ?? false, onToggleReady })
1502
+ ] });
1503
+ }
1504
+ var styles17 = import_react_native21.StyleSheet.create({
1505
+ panel: {
1506
+ alignItems: "center",
1507
+ gap: 12
1508
+ },
1509
+ // See SectionedDropdown's anchorOpen comment — this panel is typically a sibling of another
1510
+ // panel (and/or a shared-controls row), so a popover escaping this panel's bounds needs the panel
1511
+ // itself elevated, not just the popover content, to paint above a later sibling.
1512
+ panelOpen: {
1513
+ zIndex: 100
1514
+ },
1515
+ // Same gap as `panel`'s own vertical rhythm, reused horizontally — the color picker and (when
1516
+ // present) secondPicker sit side by side within this row instead of stacked in the panel's own
1517
+ // column.
1518
+ pickerRow: {
1519
+ alignItems: "center",
1520
+ flexDirection: "row",
1521
+ gap: 12
1522
+ },
1523
+ // A popover is an absolutely-positioned sibling of its own trigger — so a tall one (the color
1524
+ // swatch grid) can extend down far enough to overlap the ReadyButton below, which paints on top of
1525
+ // it by default DOM order since it's a later sibling within `panel`. `panel`'s own panelOpen
1526
+ // elevation only helps this panel paint above OTHER panels (see its own comment) — it does nothing
1527
+ // for stacking *within* this panel, since React Native Web gives each View its own stacking
1528
+ // context. This is what actually fixes that: elevate pickerRow itself above its own later sibling
1529
+ // whenever one of its own popovers is open.
1530
+ pickerRowOpen: {
1531
+ zIndex: 100
1532
+ }
1533
+ });
1534
+
1535
+ // src/PressAwayOverlay.tsx
1536
+ var import_react_native22 = require("react-native");
1537
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1538
+ function PressAwayOverlay({ active, onPress, style }) {
1539
+ if (!active) return null;
1540
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native22.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native22.StyleSheet.absoluteFill, style] });
1541
+ }
1542
+
1336
1543
  // src/SharedActionBand.tsx
1337
1544
  var import_feedback_press9 = require("@rific/feedback-press");
1338
- var import_react_native19 = require("react-native");
1339
- var import_jsx_runtime18 = require("react/jsx-runtime");
1545
+ var import_react_native23 = require("react-native");
1546
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1340
1547
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1341
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native19.View, { style: [styles14.column, popoverOpen && styles14.columnOpen], children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native19.View, { style: styles14.actionsRow, children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1344
- onRandomize && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1345
- onReset && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1346
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1548
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_native23.View, { style: [styles18.column, popoverOpen && styles18.columnOpen], children: [
1549
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_native23.View, { style: styles18.actionsRow, children: [
1550
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1551
+ onRandomize && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1552
+ onReset && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1347
1554
  ] }),
1348
1555
  children
1349
1556
  ] });
1350
1557
  }
1351
- var styles14 = import_react_native19.StyleSheet.create({
1558
+ var styles18 = import_react_native23.StyleSheet.create({
1352
1559
  actionsRow: {
1353
1560
  alignItems: "center",
1354
1561
  flexDirection: "row",
@@ -1363,84 +1570,110 @@ var styles14 = import_react_native19.StyleSheet.create({
1363
1570
  }
1364
1571
  });
1365
1572
 
1366
- // src/StatRow.tsx
1367
- var import_auto_paper8 = require("@rific/auto-paper");
1368
- var import_react_native20 = require("react-native");
1369
- var import_react_native_paper10 = require("react-native-paper");
1370
- var import_jsx_runtime19 = require("react/jsx-runtime");
1371
- function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1372
- const { dark } = (0, import_auto_paper8.useAutoPaperTheme)();
1373
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1374
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
1375
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native20.View, { style: styles15.row, children: [
1376
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1377
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: [styles15.value, { color: fg }], children: value })
1378
- ] });
1573
+ // src/StaggeredWord.tsx
1574
+ var import_react9 = require("react");
1575
+ var import_react_native24 = require("react-native");
1576
+ var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
1577
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1578
+ var STAGGER_MS = 45;
1579
+ var LETTER_DURATION_MS = 320;
1580
+ function getStaggeredWordDuration(letterCount) {
1581
+ return (letterCount - 1) * STAGGER_MS + LETTER_DURATION_MS;
1379
1582
  }
1380
- var styles15 = import_react_native20.StyleSheet.create({
1381
- row: {
1382
- alignItems: "center",
1383
- flexDirection: "row",
1384
- justifyContent: "space-between"
1385
- },
1386
- value: {
1387
- fontWeight: "bold"
1388
- }
1389
- });
1390
-
1391
- // src/StatSection.tsx
1392
- var import_auto_paper9 = require("@rific/auto-paper");
1393
- var import_react_native21 = require("react-native");
1394
- var import_react_native_paper11 = require("react-native-paper");
1395
- var import_jsx_runtime20 = require("react/jsx-runtime");
1396
- function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1397
- const { dark } = (0, import_auto_paper9.useAutoPaperTheme)();
1398
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1399
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1400
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native21.View, { style: [styles16.section, { backgroundColor: sectionBg }], children: [
1401
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper11.Text, { variant: "labelMedium", style: [styles16.label, { color: fg }], children: label }),
1402
- children
1403
- ] });
1583
+ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
1584
+ const progress = (0, import_react_native_reanimated2.useSharedValue)(reducedMotion ? 1 : 0);
1585
+ (0, import_react9.useEffect)(() => {
1586
+ if (reducedMotion) return;
1587
+ progress.value = (0, import_react_native_reanimated2.withDelay)(index * STAGGER_MS, (0, import_react_native_reanimated2.withTiming)(1, { duration: LETTER_DURATION_MS, easing: import_react_native_reanimated2.Easing.out(import_react_native_reanimated2.Easing.back(1.5)) }));
1588
+ }, [reducedMotion]);
1589
+ const style = (0, import_react_native_reanimated2.useAnimatedStyle)(() => ({
1590
+ opacity: progress.value,
1591
+ transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
1592
+ }));
1593
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_native_reanimated2.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
1404
1594
  }
1405
- var styles16 = import_react_native21.StyleSheet.create({
1406
- // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1407
- // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1408
- // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
1409
- // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
1410
- label: {
1411
- fontWeight: "bold",
1412
- letterSpacing: 2
1413
- },
1414
- section: {
1415
- borderRadius: 12,
1416
- gap: 8,
1417
- padding: 16
1595
+ function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
1596
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_native24.View, { style: [styles19.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
1597
+ }
1598
+ var styles19 = import_react_native24.StyleSheet.create({
1599
+ row: {
1600
+ flexDirection: "row"
1418
1601
  }
1419
1602
  });
1420
1603
 
1421
1604
  // src/UpdateDialog.tsx
1422
1605
  var import_updater2 = require("@rific/updater");
1423
- var import_jsx_runtime21 = require("react/jsx-runtime");
1606
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1424
1607
  function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1425
1608
  const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1426
1609
  (0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
1427
1610
  const date = manifest ? new Date(manifest.createdAt) : null;
1428
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(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) });
1611
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(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) });
1429
1612
  }
1430
1613
 
1431
- // src/usePopoverHost.ts
1432
- var import_react7 = require("react");
1433
- function usePopoverHost() {
1434
- const [openId, setOpenId] = (0, import_react7.useState)(null);
1435
- const toggle = (0, import_react7.useCallback)((id) => {
1436
- setOpenId((prev) => prev === id ? null : id);
1614
+ // src/useQuitConfirmation.ts
1615
+ var import_react10 = require("react");
1616
+ function useQuitConfirmation(hasProgress, onConfirmedBack) {
1617
+ const [confirmVisible, setConfirmVisible] = (0, import_react10.useState)(false);
1618
+ const hasProgressRef = (0, import_react10.useRef)(hasProgress);
1619
+ (0, import_react10.useEffect)(() => {
1620
+ hasProgressRef.current = hasProgress;
1621
+ });
1622
+ const onConfirmedBackRef = (0, import_react10.useRef)(onConfirmedBack);
1623
+ (0, import_react10.useEffect)(() => {
1624
+ onConfirmedBackRef.current = onConfirmedBack;
1625
+ });
1626
+ const requestBack = (0, import_react10.useCallback)(() => {
1627
+ if (hasProgressRef.current()) setConfirmVisible(true);
1628
+ else onConfirmedBackRef.current();
1437
1629
  }, []);
1438
- const close = (0, import_react7.useCallback)(() => setOpenId(null), []);
1439
- return { openId, toggle, close };
1630
+ const cancelBack = (0, import_react10.useCallback)(() => setConfirmVisible(false), []);
1631
+ return { confirmVisible, requestBack, cancelBack };
1632
+ }
1633
+
1634
+ // src/useZoneClampedAlign.ts
1635
+ var import_split_screen = require("@tastic/split-screen");
1636
+ var import_react11 = require("react");
1637
+ var import_react_native25 = require("react-native");
1638
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
1639
+ var EDGE_MARGIN2 = 12;
1640
+ function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
1641
+ const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
1642
+ const zone = (0, import_split_screen.useZoneBounds)();
1643
+ const { height: windowHeight } = (0, import_react_native25.useWindowDimensions)();
1644
+ const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
1645
+ const shouldMeasure = open && auto.measured && zone !== null;
1646
+ const [ownBounds, setOwnBounds] = (0, import_react11.useState)(null);
1647
+ const [prevShouldMeasure, setPrevShouldMeasure] = (0, import_react11.useState)(shouldMeasure);
1648
+ if (shouldMeasure !== prevShouldMeasure) {
1649
+ setPrevShouldMeasure(shouldMeasure);
1650
+ setOwnBounds(null);
1651
+ }
1652
+ (0, import_react11.useEffect)(() => {
1653
+ if (!shouldMeasure) return;
1654
+ auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
1655
+ const belowShared = zone.zoneSide === "belowShared";
1656
+ const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
1657
+ const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
1658
+ const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
1659
+ let awaySide = belowShared ? "below" : "above";
1660
+ let towardSide = belowShared ? "above" : "below";
1661
+ if (zone.rotated) {
1662
+ awaySide = awaySide === "above" ? "below" : "above";
1663
+ towardSide = towardSide === "above" ? "below" : "above";
1664
+ }
1665
+ setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
1666
+ });
1667
+ }, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
1668
+ if (!zone) return auto;
1669
+ if (!ownBounds) return { ...auto, measured: false };
1670
+ return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
1440
1671
  }
1441
1672
  // Annotate the CommonJS export names for ESM import in node:
1442
1673
  0 && (module.exports = {
1674
+ AchievementCatalogSection,
1443
1675
  AchievementRow,
1676
+ ActivityStatSection,
1444
1677
  BaseSettingsDialog,
1445
1678
  BaseStatsScreen,
1446
1679
  ConfirmDialog,
@@ -1453,20 +1686,28 @@ function usePopoverHost() {
1453
1686
  LOCKED_BADGE_COLOR,
1454
1687
  LabeledDropdown,
1455
1688
  MONO_FONT,
1689
+ PlayerSetupPanel,
1456
1690
  PopoverBody,
1457
1691
  PressAwayOverlay,
1458
1692
  ReadyButton,
1459
1693
  SectionedDropdown,
1460
1694
  SharedActionBand,
1461
1695
  SkiaGate,
1696
+ StaggeredWord,
1462
1697
  StatRow,
1463
1698
  StatSection,
1464
1699
  TriggerGaugeHost,
1465
1700
  UpdateDialog,
1701
+ getColorPopoverId,
1702
+ getCpuDifficultyPopoverId,
1703
+ getInlineColorPickerContentSize,
1466
1704
  getLabeledDropdownContentHeight,
1467
1705
  getOverlayCardColors,
1706
+ getStaggeredWordDuration,
1468
1707
  loadSkiaWeb,
1469
1708
  overlayStyles,
1470
1709
  useAutoAlign,
1471
- usePopoverHost
1710
+ usePopoverHost,
1711
+ useQuitConfirmation,
1712
+ useZoneClampedAlign
1472
1713
  });