@tastic/hud 0.8.5 → 0.13.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_runtime13.jsx)(import_react_native13.View, { style: [styles11.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
134
+ /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.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_native13, import_react_native_reanimated, import_jsx_runtime13, 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, styles11;
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_native13 = require("react-native");
136
146
  import_react_native_reanimated = require("react-native-reanimated");
137
- import_jsx_runtime8 = require("react/jsx-runtime");
147
+ import_jsx_runtime13 = 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
+ styles11 = import_react_native13.StyleSheet.create({
148
158
  container: {
149
159
  alignItems: "center",
150
160
  justifyContent: "center",
@@ -157,7 +167,10 @@ 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
+ AchievementUnlockList: () => AchievementUnlockList,
173
+ ActivityStatSection: () => ActivityStatSection,
161
174
  BaseSettingsDialog: () => BaseSettingsDialog,
162
175
  BaseStatsScreen: () => BaseStatsScreen,
163
176
  ConfirmDialog: () => ConfirmDialog,
@@ -170,25 +183,40 @@ __export(index_exports, {
170
183
  LOCKED_BADGE_COLOR: () => LOCKED_BADGE_COLOR,
171
184
  LabeledDropdown: () => LabeledDropdown,
172
185
  MONO_FONT: () => MONO_FONT,
186
+ PlayerSetupPanel: () => PlayerSetupPanel,
173
187
  PopoverBody: () => PopoverBody,
174
188
  PressAwayOverlay: () => PressAwayOverlay,
175
189
  ReadyButton: () => ReadyButton,
176
190
  SectionedDropdown: () => SectionedDropdown,
177
191
  SharedActionBand: () => SharedActionBand,
178
192
  SkiaGate: () => SkiaGate,
193
+ StaggeredWord: () => StaggeredWord,
179
194
  StatRow: () => StatRow,
180
195
  StatSection: () => StatSection,
181
196
  TriggerGaugeHost: () => TriggerGaugeHost,
182
197
  UpdateDialog: () => UpdateDialog,
198
+ getColorPopoverId: () => getColorPopoverId,
199
+ getCpuDifficultyPopoverId: () => getCpuDifficultyPopoverId,
200
+ getInlineColorPickerContentSize: () => getInlineColorPickerContentSize,
183
201
  getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
184
202
  getOverlayCardColors: () => getOverlayCardColors,
203
+ getStaggeredWordDuration: () => getStaggeredWordDuration,
185
204
  loadSkiaWeb: () => loadSkiaWeb,
205
+ overlayActionStyles: () => overlayActionStyles,
186
206
  overlayStyles: () => overlayStyles,
187
207
  useAutoAlign: () => useAutoAlign,
188
- usePopoverHost: () => usePopoverHost
208
+ usePopoverHost: () => usePopoverHost,
209
+ useQuitConfirmation: () => useQuitConfirmation,
210
+ useZoneClampedAlign: () => useZoneClampedAlign
189
211
  });
190
212
  module.exports = __toCommonJS(index_exports);
191
213
 
214
+ // src/AchievementCatalogSection.tsx
215
+ var import_auto_paper2 = require("@rific/auto-paper");
216
+ var import_react = require("react");
217
+ var import_react_native3 = require("react-native");
218
+ var import_react_native_paper2 = require("react-native-paper");
219
+
192
220
  // src/AchievementRow.tsx
193
221
  var import_auto_paper = require("@rific/auto-paper");
194
222
  var import_react_native = require("react-native");
@@ -257,24 +285,154 @@ var styles = import_react_native.StyleSheet.create({
257
285
  }
258
286
  });
259
287
 
260
- // src/BaseSettingsDialog.tsx
288
+ // src/fonts.ts
289
+ var import_react_native2 = require("react-native");
290
+ var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
291
+
292
+ // src/AchievementCatalogSection.tsx
293
+ var import_jsx_runtime2 = require("react/jsx-runtime");
294
+ function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
295
+ const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
296
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
297
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react.Fragment, { children: [
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
299
+ 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))
300
+ ] });
301
+ }
302
+ var styles2 = import_react_native3.StyleSheet.create({
303
+ label: {
304
+ letterSpacing: 2,
305
+ marginTop: 8
306
+ }
307
+ });
308
+
309
+ // src/AchievementUnlockList.tsx
261
310
  var import_auto_paper3 = require("@rific/auto-paper");
311
+ var import_profile = require("@tastic/profile");
312
+ var import_react_native4 = require("react-native");
313
+ var import_react_native_paper3 = require("react-native-paper");
314
+ var import_jsx_runtime3 = require("react/jsx-runtime");
315
+ var AVATAR_SIZE = 18;
316
+ function AchievementUnlockList({ unlocks, seats, owners, tierColors, fg: fgOverride, style }) {
317
+ const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
318
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
319
+ const rows = seats.flatMap((seat) => (unlocks[seat] ?? []).map((achievement) => ({ seat, achievement })));
320
+ if (rows.length === 0) return null;
321
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.list, style], children: rows.map(({ seat, achievement }) => {
322
+ const owner = owners[seat];
323
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.row, children: [
324
+ owner.profile ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_profile.ProfileChip, { profile: owner.profile, size: AVATAR_SIZE }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.circle, { backgroundColor: owner.color }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "account", size: 10, color: (0, import_auto_paper3.getContrastColor)(owner.color) }) }),
325
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.circle, { backgroundColor: tierColors[achievement.tier] }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: achievement.icon, size: 12, color: "#000000" }) }),
326
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { style: [styles3.label, { color: fg }], numberOfLines: 1, children: achievement.title })
327
+ ] }, `${seat}-${achievement.id}`);
328
+ }) });
329
+ }
330
+ var styles3 = import_react_native4.StyleSheet.create({
331
+ circle: {
332
+ alignItems: "center",
333
+ borderRadius: AVATAR_SIZE / 2,
334
+ height: AVATAR_SIZE,
335
+ justifyContent: "center",
336
+ width: AVATAR_SIZE
337
+ },
338
+ label: {
339
+ flexShrink: 1,
340
+ fontSize: 12,
341
+ fontWeight: "700"
342
+ },
343
+ list: {
344
+ gap: 6
345
+ },
346
+ row: {
347
+ alignItems: "center",
348
+ flexDirection: "row",
349
+ gap: 6
350
+ }
351
+ });
352
+
353
+ // src/StatRow.tsx
354
+ var import_auto_paper4 = require("@rific/auto-paper");
355
+ var import_react_native5 = require("react-native");
356
+ var import_react_native_paper4 = require("react-native-paper");
357
+ var import_jsx_runtime4 = require("react/jsx-runtime");
358
+ function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
359
+ const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
360
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
361
+ const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
362
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: styles4.row, children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
364
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "bodyMedium", style: [styles4.value, { color: fg }], children: value })
365
+ ] });
366
+ }
367
+ var styles4 = import_react_native5.StyleSheet.create({
368
+ row: {
369
+ alignItems: "center",
370
+ flexDirection: "row",
371
+ justifyContent: "space-between"
372
+ },
373
+ value: {
374
+ fontWeight: "bold"
375
+ }
376
+ });
377
+
378
+ // src/StatSection.tsx
379
+ var import_auto_paper5 = require("@rific/auto-paper");
380
+ var import_react_native6 = require("react-native");
381
+ var import_react_native_paper5 = require("react-native-paper");
382
+ var import_jsx_runtime5 = require("react/jsx-runtime");
383
+ function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
384
+ const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
385
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
386
+ const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
387
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: [styles5.section, { backgroundColor: sectionBg }], children: [
388
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper5.Text, { variant: "labelMedium", style: [styles5.label, { color: fg }], children: label }),
389
+ children
390
+ ] });
391
+ }
392
+ var styles5 = import_react_native6.StyleSheet.create({
393
+ // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
394
+ // same visual weight as StatRow's own row labels below it (both used the identical muted color;
395
+ // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
396
+ // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
397
+ label: {
398
+ fontWeight: "bold",
399
+ letterSpacing: 2
400
+ },
401
+ section: {
402
+ borderRadius: 12,
403
+ gap: 8,
404
+ padding: 16
405
+ }
406
+ });
407
+
408
+ // src/ActivityStatSection.tsx
409
+ var import_jsx_runtime6 = require("react/jsx-runtime");
410
+ function ActivityStatSection({ stats }) {
411
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(StatSection, { label: "ACTIVITY", children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
415
+ ] });
416
+ }
417
+
418
+ // src/BaseSettingsDialog.tsx
419
+ var import_auto_paper7 = require("@rific/auto-paper");
262
420
  var import_feedback_press2 = require("@rific/feedback-press");
263
421
  var import_updater = require("@rific/updater");
264
422
  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");
423
+ var import_react3 = require("react");
424
+ var import_react_native9 = require("react-native");
425
+ var import_react_native_paper7 = require("react-native-paper");
268
426
 
269
427
  // src/ConfirmDialog.tsx
270
- var import_auto_paper2 = require("@rific/auto-paper");
428
+ var import_auto_paper6 = require("@rific/auto-paper");
271
429
  var import_feedback_press = require("@rific/feedback-press");
272
430
  var import_core = require("@tastic/core");
273
- var import_react_native3 = require("react-native");
274
- var import_react_native_paper2 = require("react-native-paper");
431
+ var import_react_native8 = require("react-native");
432
+ var import_react_native_paper6 = require("react-native-paper");
275
433
 
276
434
  // src/overlayCard.ts
277
- var import_react_native2 = require("react-native");
435
+ var import_react_native7 = require("react-native");
278
436
  function getOverlayCardColors(dark) {
279
437
  return {
280
438
  fg: dark ? "#FFFFFF" : "#000000",
@@ -283,7 +441,7 @@ function getOverlayCardColors(dark) {
283
441
  cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
284
442
  };
285
443
  }
286
- var overlayStyles = import_react_native2.StyleSheet.create({
444
+ var overlayStyles = import_react_native7.StyleSheet.create({
287
445
  card: {
288
446
  alignItems: "center",
289
447
  borderRadius: 20,
@@ -303,27 +461,7 @@ var overlayStyles = import_react_native2.StyleSheet.create({
303
461
  top: 0
304
462
  }
305
463
  });
306
-
307
- // src/ConfirmDialog.tsx
308
- var import_jsx_runtime2 = require("react/jsx-runtime");
309
- 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)();
311
- if (!visible) return null;
312
- const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
313
- const accent = destructive ? colors.danger : colors.primary;
314
- const onAccent = destructive ? colors.onDanger : colors.onPrimary;
315
- const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
316
- 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: [
317
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 64, color: accent }),
318
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
319
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
320
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.actions, children: [
321
- showCancel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
322
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: showCancel ? styles2.actionButton : styles2.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
323
- ] })
324
- ] }) }) });
325
- }
326
- var styles2 = import_react_native3.StyleSheet.create({
464
+ var overlayActionStyles = import_react_native7.StyleSheet.create({
327
465
  // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
328
466
  // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
329
467
  // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
@@ -342,8 +480,8 @@ var styles2 = import_react_native3.StyleSheet.create({
342
480
  // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
343
481
  // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
344
482
  // fixable by just giving the row more width, until confirmed live that these two-word labels
345
- // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
346
- // width fix below provides, on a narrow enough screen.
483
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room ConfirmDialog's own
484
+ // card width fix provides, on a narrow enough screen.
347
485
  actionButton: {
348
486
  flexGrow: 1,
349
487
  minWidth: 128
@@ -362,7 +500,29 @@ var styles2 = import_react_native3.StyleSheet.create({
362
500
  gap: 12,
363
501
  justifyContent: "center",
364
502
  width: "100%"
365
- },
503
+ }
504
+ });
505
+
506
+ // src/ConfirmDialog.tsx
507
+ var import_jsx_runtime7 = require("react/jsx-runtime");
508
+ function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
509
+ const { dark, colors } = (0, import_auto_paper6.useAutoPaperTheme)();
510
+ if (!visible) return null;
511
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
512
+ const accent = destructive ? colors.danger : colors.primary;
513
+ const onAccent = destructive ? colors.onDanger : colors.onPrimary;
514
+ const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
515
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [overlayStyles.overlay, styles6.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: [overlayStyles.card, styles6.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Icon, { source: icon, size: 64, color: accent }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "headlineLarge", style: [styles6.title, { color: accent }], children: title }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Text, { variant: "bodyLarge", style: [styles6.body, { color: fg }], children: message }),
519
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: overlayActionStyles.actions, children: [
520
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: overlayActionStyles.actionButton, children: cancelLabel }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: showCancel ? overlayActionStyles.actionButton : overlayActionStyles.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
522
+ ] })
523
+ ] }) }) });
524
+ }
525
+ var styles6 = import_react_native8.StyleSheet.create({
366
526
  body: {
367
527
  textAlign: "center"
368
528
  },
@@ -391,18 +551,18 @@ var styles2 = import_react_native3.StyleSheet.create({
391
551
  });
392
552
 
393
553
  // src/useUpdateConfirmBridge.ts
394
- var import_react = require("react");
554
+ var import_react2 = require("react");
395
555
  function useUpdateConfirmBridge() {
396
- const [manifest, setManifest] = (0, import_react.useState)(null);
397
- const resolveRef = (0, import_react.useRef)(null);
398
- const onConfirm = (0, import_react.useCallback)(
556
+ const [manifest, setManifest] = (0, import_react2.useState)(null);
557
+ const resolveRef = (0, import_react2.useRef)(null);
558
+ const onConfirm = (0, import_react2.useCallback)(
399
559
  (next) => new Promise((resolve) => {
400
560
  resolveRef.current = resolve;
401
561
  setManifest(next);
402
562
  }),
403
563
  []
404
564
  );
405
- const respond = (0, import_react.useCallback)((confirmed) => {
565
+ const respond = (0, import_react2.useCallback)((confirmed) => {
406
566
  setManifest(null);
407
567
  resolveRef.current?.(confirmed);
408
568
  resolveRef.current = null;
@@ -411,20 +571,20 @@ function useUpdateConfirmBridge() {
411
571
  }
412
572
 
413
573
  // src/BaseSettingsDialog.tsx
414
- var import_jsx_runtime3 = require("react/jsx-runtime");
574
+ var import_jsx_runtime8 = require("react/jsx-runtime");
415
575
  function SettingIcon({ source, color, containerColor }) {
416
- 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 }) });
576
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { style: [styles7.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Icon, { source, size: 18, color }) });
417
577
  }
418
- function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
419
- const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
578
+ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
579
+ const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
420
580
  const ambientRotation = (0, import_core2.useRotation)();
421
581
  const rotation = rotationOverride ?? ambientRotation;
422
582
  const isTouchPrimary = (0, import_core2.useIsTouchPrimaryDevice)();
423
583
  const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
424
584
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
425
- const sound = (0, import_react2.useContext)(import_feedback_press2.SoundContext);
585
+ const sound = (0, import_react3.useContext)(import_feedback_press2.SoundContext);
426
586
  const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
427
- const [infoMessage, setInfoMessage] = (0, import_react2.useState)(null);
587
+ const [infoMessage, setInfoMessage] = (0, import_react3.useState)(null);
428
588
  const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
429
589
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
430
590
  autoCheck: false,
@@ -435,32 +595,32 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
435
595
  });
436
596
  const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
437
597
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
438
- const showEdgeGuard = import_react_native4.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
439
- const showHaptics = import_react_native4.Platform.OS !== "web" && !hideHaptics;
598
+ const showEdgeGuard = import_react_native9.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
599
+ const showHaptics = import_react_native9.Platform.OS !== "web" && !hideHaptics;
440
600
  const showSound = !hideSound;
441
601
  const showAppearance = !hideAppearance;
442
- const showUpdateCheck = import_react_native4.Platform.OS !== "web" && !hideUpdateCheck;
443
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
444
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_auto_paper3.Dialog, { visible, onDismiss, style: [styles3.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
445
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.Title, { children: "Settings" }),
446
- /* @__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: [
447
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleGroup, children: [
448
- 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: [
449
- /* @__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 }),
450
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
451
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
452
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
602
+ const showUpdateCheck = import_react_native9.Platform.OS !== "web" && !hideUpdateCheck;
603
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_auto_paper7.Dialog, { visible, onDismiss, style: [styles7.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
605
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper7.Dialog.Title, { children: "Settings" }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper7.Dialog.ScrollArea, { style: styles7.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.ScrollView, { contentContainerStyle: styles7.content, showsVerticalScrollIndicator: false, children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleGroup, children: [
608
+ showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles7.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
453
613
  ] })
454
614
  ] }) }),
455
- 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: [
456
- /* @__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 }),
457
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
458
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
459
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
615
+ showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles7.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
460
620
  ] })
461
621
  ] }) }),
462
- (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleRow, children: [
463
- showSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
622
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleRow, children: [
623
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
464
624
  import_feedback_press2.TouchableRipple,
465
625
  {
466
626
  soundDisabled: true,
@@ -469,15 +629,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
469
629
  setSoundSettings({ enabled });
470
630
  if (enabled) sound.selection?.();
471
631
  },
472
- style: styles3.toggleButtonInRow,
632
+ style: styles7.toggleButtonInRow,
473
633
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
474
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
475
- /* @__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 }),
476
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
634
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
635
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
477
637
  ] })
478
638
  }
479
639
  ),
480
- showHaptics && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
640
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
481
641
  import_feedback_press2.TouchableRipple,
482
642
  {
483
643
  hapticDisabled: true,
@@ -486,36 +646,54 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
486
646
  setHapticSettings({ vibrate });
487
647
  if (vibrate) forceHapticFeedback();
488
648
  },
489
- style: styles3.toggleButtonInRow,
649
+ style: styles7.toggleButtonInRow,
490
650
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
491
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
492
- /* @__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 }),
493
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
651
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
652
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
653
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
494
654
  ] })
495
655
  }
496
656
  )
497
- ] })
657
+ ] }),
658
+ onShowHowToPlay && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
659
+ import_feedback_press2.TouchableRipple,
660
+ {
661
+ onPress: () => {
662
+ onDismiss();
663
+ onShowHowToPlay();
664
+ },
665
+ style: styles7.toggleButton,
666
+ accessibilityLabel: "How to Play",
667
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.toggleContent, children: [
668
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SettingIcon, { source: "help-circle-outline", color: colors.secondary, containerColor: colors.secondaryContainer }),
669
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.flexShrink, children: [
670
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "How to Play" }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Replay the quick intro" })
672
+ ] })
673
+ ] })
674
+ }
675
+ )
498
676
  ] }),
499
- showAppearance && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
500
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
501
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.AutoAppearancePicker, { showLabels: false })
677
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.section, children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper7.Text, { variant: "labelMedium", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper7.AutoAppearancePicker, { showLabels: false })
502
680
  ] }),
503
681
  children,
504
- showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
505
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper3.Text, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
682
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles7.section, children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_paper7.Text, { variant: "labelSmall", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: [
506
684
  "VERSION ",
507
685
  version,
508
686
  updateReady ? " \xB7 UPDATE READY" : ""
509
687
  ] }),
510
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
688
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
511
689
  ] })
512
690
  ] }) })
513
691
  ] }),
514
- /* @__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) }),
515
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
692
+ /* @__PURE__ */ (0, import_jsx_runtime8.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) }),
693
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
516
694
  ] });
517
695
  }
518
- var styles3 = import_react_native4.StyleSheet.create({
696
+ var styles7 = import_react_native9.StyleSheet.create({
519
697
  content: {
520
698
  gap: 24,
521
699
  paddingVertical: 20
@@ -583,31 +761,31 @@ var styles3 = import_react_native4.StyleSheet.create({
583
761
  });
584
762
 
585
763
  // src/BaseStatsScreen.tsx
586
- var import_auto_paper4 = require("@rific/auto-paper");
764
+ var import_auto_paper8 = require("@rific/auto-paper");
587
765
  var import_feedback_press3 = require("@rific/feedback-press");
588
766
  var import_core3 = require("@tastic/core");
589
- var import_react3 = require("react");
590
- var import_react_native5 = require("react-native");
591
- var import_react_native_paper4 = require("react-native-paper");
592
- var import_jsx_runtime4 = require("react/jsx-runtime");
767
+ var import_react4 = require("react");
768
+ var import_react_native10 = require("react-native");
769
+ var import_react_native_paper8 = require("react-native-paper");
770
+ var import_jsx_runtime9 = require("react/jsx-runtime");
593
771
  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 }) {
594
- const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
772
+ const { dark, colors } = (0, import_auto_paper8.useAutoPaperTheme)();
595
773
  const ambientRotation = (0, import_core3.useRotation)();
596
774
  const rotation = rotationOverride ?? ambientRotation;
597
- const [confirmResetVisible, setConfirmResetVisible] = (0, import_react3.useState)(false);
775
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react4.useState)(false);
598
776
  const showReset = !!onReset;
599
777
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
600
778
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
601
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.container, { backgroundColor: bg }], children: [
602
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
603
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
604
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles4.title, { color: fg }], children: title })
779
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles8.container, { backgroundColor: bg }], children: [
780
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles8.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
782
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper8.Text, { variant: titleVariant, style: [styles8.title, { color: fg }], children: title })
605
783
  ] }),
606
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.ScrollView, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
784
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.ScrollView, { style: styles8.scrollView, contentContainerStyle: [styles8.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
607
785
  children,
608
- 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 })
786
+ showReset && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles8.resetButton, children: resetLabel })
609
787
  ] }),
610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
788
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
611
789
  ConfirmDialog,
612
790
  {
613
791
  visible: confirmResetVisible,
@@ -627,7 +805,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
627
805
  )
628
806
  ] });
629
807
  }
630
- var styles4 = import_react_native5.StyleSheet.create({
808
+ var styles8 = import_react_native10.StyleSheet.create({
631
809
  container: {
632
810
  flex: 1
633
811
  },
@@ -659,18 +837,18 @@ var styles4 = import_react_native5.StyleSheet.create({
659
837
 
660
838
  // src/ContentGutter.tsx
661
839
  var import_core4 = require("@tastic/core");
662
- var import_react_native6 = require("react-native");
663
- var import_jsx_runtime5 = require("react/jsx-runtime");
840
+ var import_react_native11 = require("react-native");
841
+ var import_jsx_runtime10 = require("react/jsx-runtime");
664
842
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
665
- const { width: windowWidth } = (0, import_react_native6.useWindowDimensions)();
843
+ const { width: windowWidth } = (0, import_core4.useRotatedWindowDimensions)();
666
844
  const { contentWidth, gutterWidth } = (0, import_core4.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
667
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: [styles5.row, style], children: [
668
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
669
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: { width: contentWidth }, children }),
670
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
845
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: [styles9.row, style], children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.gutter, { width: gutterWidth }], children: leftGutter }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: { width: contentWidth }, children }),
848
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles9.gutter, { width: gutterWidth }], children: rightGutter })
671
849
  ] });
672
850
  }
673
- var styles5 = import_react_native6.StyleSheet.create({
851
+ var styles9 = import_react_native11.StyleSheet.create({
674
852
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
675
853
  // column instead of clipping cleanly at its own real width.
676
854
  gutter: {
@@ -683,44 +861,40 @@ var styles5 = import_react_native6.StyleSheet.create({
683
861
 
684
862
  // src/ControlSchemePicker.tsx
685
863
  var import_core7 = require("@tastic/core");
686
- var import_react_native12 = require("react-native");
864
+ var import_react_native16 = require("react-native");
687
865
 
688
866
  // src/SectionedDropdown.tsx
689
- var import_auto_paper5 = require("@rific/auto-paper");
867
+ var import_auto_paper9 = require("@rific/auto-paper");
690
868
  var import_feedback_press4 = require("@rific/feedback-press");
691
869
  var import_core6 = require("@tastic/core");
692
- var import_react_native11 = require("react-native");
693
- var import_react_native_paper5 = require("react-native-paper");
694
-
695
- // src/fonts.ts
696
- var import_react_native7 = require("react-native");
697
- var MONO_FONT = import_react_native7.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
870
+ var import_react_native15 = require("react-native");
871
+ var import_react_native_paper9 = require("react-native-paper");
698
872
 
699
873
  // src/PopoverBody.tsx
700
- var import_react_native8 = require("react-native");
701
- var import_jsx_runtime6 = require("react/jsx-runtime");
874
+ var import_react_native12 = require("react-native");
875
+ var import_jsx_runtime11 = require("react/jsx-runtime");
702
876
  var CARET_SIZE = 7;
703
877
  var CARET_RING_OFFSET = 2;
704
878
  var CARET_DIP = 1;
705
879
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
706
880
  if (!visible) return null;
707
881
  const above = verticalAlign === "above";
708
- const alignStyle = align === "left" ? styles6.contentLeft : align === "right" ? styles6.contentRight : styles6.contentCenter;
709
- const verticalStyle = above ? styles6.contentAbove : styles6.contentBelow;
882
+ const alignStyle = align === "left" ? styles10.contentLeft : align === "right" ? styles10.contentRight : styles10.contentCenter;
883
+ const verticalStyle = above ? styles10.contentAbove : styles10.contentBelow;
710
884
  const ringSize = CARET_SIZE + caretBorderWidth;
711
885
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
712
886
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
713
887
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
714
888
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
715
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native8.View, { style: [styles6.content, alignStyle, verticalStyle], children: [
889
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native12.View, { style: [styles10.content, alignStyle, verticalStyle], children: [
716
890
  children,
717
- caretColor && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
718
- /* @__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 }] }),
719
- /* @__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 }] })
891
+ caretColor && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native12.View, { style: [styles10.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native12.View, { style: [styles10.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
720
894
  ] })
721
895
  ] });
722
896
  }
723
- var styles6 = import_react_native8.StyleSheet.create({
897
+ var styles10 = import_react_native12.StyleSheet.create({
724
898
  caret: {
725
899
  borderLeftColor: "transparent",
726
900
  borderRightColor: "transparent",
@@ -763,11 +937,11 @@ var styles6 = import_react_native8.StyleSheet.create({
763
937
  });
764
938
 
765
939
  // src/SkiaGate.tsx
766
- var import_react4 = require("react");
767
- var import_jsx_runtime7 = require("react/jsx-runtime");
940
+ var import_react5 = require("react");
941
+ var import_jsx_runtime12 = require("react/jsx-runtime");
768
942
  function SkiaGate({ getComponent, componentProps, fallback = null }) {
769
- const [Component, setComponent] = (0, import_react4.useState)(null);
770
- (0, import_react4.useEffect)(() => {
943
+ const [Component, setComponent] = (0, import_react5.useState)(null);
944
+ (0, import_react5.useEffect)(() => {
771
945
  let cancelled = false;
772
946
  getComponent().then((mod) => {
773
947
  if (!cancelled) setComponent(() => mod.default);
@@ -777,19 +951,19 @@ function SkiaGate({ getComponent, componentProps, fallback = null }) {
777
951
  };
778
952
  }, [getComponent]);
779
953
  if (!Component) return fallback;
780
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Component, { ...componentProps });
954
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Component, { ...componentProps });
781
955
  }
782
956
 
783
957
  // src/TriggerGaugeHost.tsx
784
- var import_jsx_runtime9 = require("react/jsx-runtime");
958
+ var import_jsx_runtime14 = require("react/jsx-runtime");
785
959
  var getTriggerGauge = () => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((mod) => ({ default: mod.TriggerGauge }));
786
960
  function TriggerGaugeHost(props) {
787
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
961
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
788
962
  }
789
963
 
790
964
  // src/useAutoAlign.ts
791
- var import_react6 = require("react");
792
- var import_react_native10 = require("react-native");
965
+ var import_react7 = require("react");
966
+ var import_react_native14 = require("react-native");
793
967
  var EDGE_MARGIN = 12;
794
968
  function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
795
969
  if (rotation === 0) return { x, y, width, height };
@@ -801,18 +975,18 @@ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
801
975
  return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
802
976
  }
803
977
  function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
804
- const triggerRef = (0, import_react6.useRef)(null);
805
- const { width: windowWidth, height: windowHeight } = (0, import_react_native10.useWindowDimensions)();
806
- const [align, setAlign] = (0, import_react6.useState)("center");
807
- const [verticalAlign, setVerticalAlign] = (0, import_react6.useState)("below");
808
- const [maxHeight, setMaxHeight] = (0, import_react6.useState)(contentHeight);
809
- const [measured, setMeasured] = (0, import_react6.useState)(false);
810
- const [prevOpen, setPrevOpen] = (0, import_react6.useState)(open);
978
+ const triggerRef = (0, import_react7.useRef)(null);
979
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native14.useWindowDimensions)();
980
+ const [align, setAlign] = (0, import_react7.useState)("center");
981
+ const [verticalAlign, setVerticalAlign] = (0, import_react7.useState)("below");
982
+ const [maxHeight, setMaxHeight] = (0, import_react7.useState)(contentHeight);
983
+ const [measured, setMeasured] = (0, import_react7.useState)(false);
984
+ const [prevOpen, setPrevOpen] = (0, import_react7.useState)(open);
811
985
  if (open !== prevOpen) {
812
986
  setPrevOpen(open);
813
987
  if (open) setMeasured(false);
814
988
  }
815
- const measure = (0, import_react6.useCallback)(() => {
989
+ const measure = (0, import_react7.useCallback)(() => {
816
990
  triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
817
991
  const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
818
992
  const centerX = x + triggerWidth / 2;
@@ -829,14 +1003,14 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
829
1003
  setMeasured(true);
830
1004
  });
831
1005
  }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
832
- (0, import_react6.useEffect)(() => {
1006
+ (0, import_react7.useEffect)(() => {
833
1007
  if (open) measure();
834
1008
  }, [open, measure]);
835
1009
  return { align, maxHeight, measured, triggerRef, verticalAlign };
836
1010
  }
837
1011
 
838
1012
  // src/SectionedDropdown.tsx
839
- var import_jsx_runtime10 = require("react/jsx-runtime");
1013
+ var import_jsx_runtime15 = require("react/jsx-runtime");
840
1014
  var MENU_BORDER_WIDTH = 1;
841
1015
  var TRIGGER_SIZE = 44;
842
1016
  var MENU_MIN_WIDTH = 200;
@@ -847,12 +1021,12 @@ var MENU_ITEM_HEIGHT = 40;
847
1021
  var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
848
1022
  var MENU_DIVIDER_HEIGHT = 9;
849
1023
  var MENU_VERTICAL_PADDING = 12;
850
- 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" } }) {
1024
+ 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" } }) {
851
1025
  const ambientRotation = (0, import_core6.useRotation)();
852
1026
  const rotation = rotationOverride ?? ambientRotation;
853
1027
  const menuBg = dark ? "#000000" : "#FFFFFF";
854
- const menuBorderColor = (0, import_auto_paper5.getBlendedColor)(mutedColor, menuBg, 0.5);
855
- const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper5.getColorRoles)(accentColor, menuBg).onColor;
1028
+ const menuBorderColor = (0, import_auto_paper9.getBlendedColor)(mutedColor, menuBg, 0.5);
1029
+ const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper9.getColorRoles)(accentColor, menuBg).onColor;
856
1030
  const fg = dark ? "#FFFFFF" : "#000000";
857
1031
  const open = host.openId === id;
858
1032
  const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
@@ -861,8 +1035,10 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
861
1035
  const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
862
1036
  return sum + rows + footer + divider;
863
1037
  }, 0);
864
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
865
- const align = alignOverride ?? autoAlign;
1038
+ const auto = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
1039
+ const placement = alignOverride ?? auto;
1040
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
1041
+ const align = forcedAlign ?? placement.align;
866
1042
  const onlySection = sections.length === 1 ? sections[0] : null;
867
1043
  const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
868
1044
  const triggerIcon = selectedOption?.icon ?? icon;
@@ -884,17 +1060,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
884
1060
  }
885
1061
  gaugeOffset += section.options.length;
886
1062
  }
887
- const anchorStyle = [styles8.anchor, open && styles8.anchorOpen];
1063
+ const anchorStyle = [styles12.anchor, open && styles12.anchorOpen];
888
1064
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
889
1065
  const dividerStyle = { backgroundColor: menuBorderColor };
890
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: anchorStyle, children: [
891
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { ref: triggerRef, collapsable: false, style: styles8.triggerBox, children: [
892
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
893
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
1066
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { style: anchorStyle, children: [
1067
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { ref: triggerRef, collapsable: false, style: styles12.triggerBox, children: [
1068
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
1069
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
894
1070
  ] }),
895
- /* @__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: [
896
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles8.divider, dividerStyle] }),
897
- section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1071
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native15.ScrollView, { style: [styles12.menu, menuStyle], contentContainerStyle: styles12.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { children: [
1072
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native15.View, { style: [styles12.divider, dividerStyle] }),
1073
+ section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
898
1074
  SingleSection,
899
1075
  {
900
1076
  section,
@@ -907,7 +1083,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
907
1083
  if (autoDismiss) host.close();
908
1084
  }
909
1085
  }
910
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
1086
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
911
1087
  ] }, section.id)) }) })
912
1088
  ] });
913
1089
  }
@@ -919,18 +1095,18 @@ function SingleSection({
919
1095
  labelFontFamily,
920
1096
  onSelect
921
1097
  }) {
922
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: section.options.map((option) => {
1098
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: section.options.map((option) => {
923
1099
  const selected = option.value === section.value;
924
1100
  const taken = !selected && option.value === section.takenValue;
925
1101
  const rowColor = selected ? onAccentColor : mutedColor;
926
- const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, taken && styles8.itemTaken];
1102
+ const itemStyle = [styles12.item, selected && { backgroundColor: accentColor }, taken && styles12.itemTaken];
927
1103
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
928
1104
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
929
- 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: [
930
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
931
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemLabelColumn, children: [
932
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
933
- option.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
1105
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press4.TouchableRipple, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { style: styles12.itemRow, children: [
1106
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1107
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { style: styles12.itemLabelColumn, children: [
1108
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
1109
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
934
1110
  ] })
935
1111
  ] }) }, option.value);
936
1112
  }) });
@@ -944,23 +1120,23 @@ function MultiSection({
944
1120
  allClearLabels
945
1121
  }) {
946
1122
  const allSelected = section.value.length === section.options.length;
947
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1123
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
948
1124
  section.options.map((option, i) => {
949
1125
  const selected = section.value.includes(option.value);
950
1126
  const rowColor = selected ? onAccentColor : mutedColor;
951
1127
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
952
1128
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
953
- const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
1129
+ const itemStyle = [styles12.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
954
1130
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
955
- 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: [
956
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
957
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.itemLabelFlex, labelStyle], children: option.label })
1131
+ return /* @__PURE__ */ (0, import_jsx_runtime15.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_runtime15.jsxs)(import_react_native15.View, { style: styles12.itemRow, children: [
1132
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Text, { variant: "labelLarge", style: [styles12.itemLabelFlex, labelStyle], children: option.label })
958
1134
  ] }) }, option.value);
959
1135
  }),
960
- 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 }) })
1136
+ section.allClear && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles12.item, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Text, { variant: "labelLarge", style: [styles12.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
961
1137
  ] });
962
1138
  }
963
- var styles8 = import_react_native11.StyleSheet.create({
1139
+ var styles12 = import_react_native15.StyleSheet.create({
964
1140
  allClearLabel: {
965
1141
  fontWeight: "bold",
966
1142
  textAlign: "center"
@@ -977,7 +1153,7 @@ var styles8 = import_react_native11.StyleSheet.create({
977
1153
  zIndex: 100
978
1154
  },
979
1155
  divider: {
980
- height: import_react_native11.StyleSheet.hairlineWidth,
1156
+ height: import_react_native15.StyleSheet.hairlineWidth,
981
1157
  marginVertical: 4
982
1158
  },
983
1159
  item: {
@@ -1030,24 +1206,24 @@ var styles8 = import_react_native11.StyleSheet.create({
1030
1206
  });
1031
1207
 
1032
1208
  // src/ControlSchemePicker.tsx
1033
- var import_jsx_runtime11 = require("react/jsx-runtime");
1034
- function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily }) {
1209
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1210
+ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, alignOverride, rotation, labelFontFamily }) {
1035
1211
  const isTouchPrimary = (0, import_core7.useIsTouchPrimaryDevice)();
1036
- if (import_react_native12.Platform.OS !== "web" || isTouchPrimary) return null;
1037
- 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 });
1212
+ if (import_react_native16.Platform.OS !== "web" || isTouchPrimary) return null;
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime16.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 });
1038
1214
  }
1039
1215
 
1040
1216
  // src/CornerActionButtons.tsx
1041
1217
  var import_feedback_press5 = require("@rific/feedback-press");
1042
- var import_react_native13 = require("react-native");
1043
- var import_jsx_runtime12 = require("react/jsx-runtime");
1218
+ var import_react_native17 = require("react-native");
1219
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1044
1220
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
1045
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1046
- /* @__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 }),
1047
- /* @__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" })
1221
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1222
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press5.IconButton, { icon: backIcon, iconColor: fg, size: 24, style: [styles13.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
1223
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press5.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles13.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
1048
1224
  ] });
1049
1225
  }
1050
- var styles9 = import_react_native13.StyleSheet.create({
1226
+ var styles13 = import_react_native17.StyleSheet.create({
1051
1227
  back: {
1052
1228
  position: "absolute"
1053
1229
  },
@@ -1057,22 +1233,22 @@ var styles9 = import_react_native13.StyleSheet.create({
1057
1233
  });
1058
1234
 
1059
1235
  // src/CornerStatusBadges.tsx
1060
- var import_react_native14 = require("react-native");
1061
- var import_react_native_paper6 = require("react-native-paper");
1062
- var import_jsx_runtime13 = require("react/jsx-runtime");
1236
+ var import_react_native18 = require("react-native");
1237
+ var import_react_native_paper10 = require("react-native-paper");
1238
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1063
1239
  var BADGE_SIZE = 30;
1064
1240
  function StatusBadge({ icon }) {
1065
- 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)" }) });
1241
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.View, { style: styles14.badge, children: icon && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Icon, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
1066
1242
  }
1067
1243
  function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
1068
1244
  const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
1069
1245
  const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
1070
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1071
- 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 }) }),
1072
- 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 }) })
1246
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
1247
+ p1 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.View, { pointerEvents: "none", style: p1OnRightSide ? styles14.bottomRight : styles14.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StatusBadge, { icon: p1.icon }) }),
1248
+ p2 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native18.View, { pointerEvents: "none", style: p2OnLeftSide ? styles14.topLeft : styles14.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StatusBadge, { icon: p2.icon }) })
1073
1249
  ] });
1074
1250
  }
1075
- var styles10 = import_react_native14.StyleSheet.create({
1251
+ var styles14 = import_react_native18.StyleSheet.create({
1076
1252
  badge: {
1077
1253
  alignItems: "center",
1078
1254
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -1091,12 +1267,12 @@ var styles10 = import_react_native14.StyleSheet.create({
1091
1267
  });
1092
1268
 
1093
1269
  // src/InlineColorPicker.tsx
1094
- var import_auto_paper6 = require("@rific/auto-paper");
1270
+ var import_auto_paper10 = require("@rific/auto-paper");
1095
1271
  var import_feedback_press6 = require("@rific/feedback-press");
1096
1272
  var import_core8 = require("@tastic/core");
1097
- var import_react_native15 = require("react-native");
1098
- var import_react_native_paper7 = require("react-native-paper");
1099
- var import_jsx_runtime14 = require("react/jsx-runtime");
1273
+ var import_react_native19 = require("react-native");
1274
+ var import_react_native_paper11 = require("react-native-paper");
1275
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1100
1276
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
1101
1277
  var DEFAULT_SIZE = 48;
1102
1278
  var SWATCH_SIZE = 28;
@@ -1109,31 +1285,37 @@ var SCREEN_MARGIN = 40;
1109
1285
  function widthForColumns(columns) {
1110
1286
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
1111
1287
  }
1112
- 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 }) {
1288
+ function getInlineColorPickerContentSize(swatchCount, windowWidth, columns) {
1289
+ const autoColumns = (0, import_core8.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1290
+ const resolvedColumns = columns ?? autoColumns;
1291
+ const width = widthForColumns(resolvedColumns);
1292
+ const rows = Math.ceil(swatchCount / resolvedColumns);
1293
+ const height = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * rows + SWATCHES_GAP * (rows - 1);
1294
+ return { width, height };
1295
+ }
1296
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper10.defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1113
1297
  const ambientRotation = (0, import_core8.useRotation)();
1114
1298
  const rotation = rotationOverride ?? ambientRotation;
1115
1299
  const menuBg = dark ? "#000000" : "#FFFFFF";
1116
- const { width: windowWidth } = (0, import_react_native15.useWindowDimensions)();
1117
- const autoColumns = (0, import_core8.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1118
- const resolvedColumns = columns ?? autoColumns;
1119
- const swatchesWidth = widthForColumns(resolvedColumns);
1120
- const swatchRows = Math.ceil(swatches.length / resolvedColumns);
1121
- const swatchesHeight = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * swatchRows + SWATCHES_GAP * (swatchRows - 1);
1300
+ const { width: windowWidth } = (0, import_react_native19.useWindowDimensions)();
1301
+ const { width: swatchesWidth, height: swatchesHeight } = getInlineColorPickerContentSize(swatches.length, windowWidth, columns);
1122
1302
  const open = host.openId === id;
1123
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1124
- const align = alignOverride ?? autoAlign;
1303
+ const auto = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1304
+ const placement = alignOverride ?? auto;
1305
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
1306
+ const align = forcedAlign ?? placement.align;
1125
1307
  const displayFor = previewValue ?? ((hex) => hex);
1126
1308
  const displayValue = displayFor(value);
1127
- const contrastColor = (0, import_auto_paper6.getContrastColor)(displayValue);
1309
+ const contrastColor = (0, import_auto_paper10.getContrastColor)(displayValue);
1128
1310
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
1129
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1130
- /* @__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 }) }) }),
1131
- /* @__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) => {
1311
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native19.View, { style: [styles15.anchor, open && styles15.anchorOpen], children: [
1312
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles15.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native19.View, { ref: triggerRef, collapsable: false, style: styles15.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Text, { style: [styles15.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1313
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native19.ScrollView, { style: [styles15.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles15.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1132
1314
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
1133
1315
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
1134
1316
  const swappable = taken && allowSwapTaken;
1135
1317
  const swatchDisplay = displayFor(swatch.value);
1136
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1318
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1137
1319
  import_feedback_press6.TouchableRipple,
1138
1320
  {
1139
1321
  disabled: taken && !swappable,
@@ -1142,15 +1324,15 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
1142
1324
  if (autoDismiss) host.close();
1143
1325
  },
1144
1326
  borderless: true,
1145
- style: [styles11.swatch, { backgroundColor: swatchDisplay }, selected && styles11.swatchSelected, taken && !swappable && styles11.swatchTaken],
1146
- 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, {})
1327
+ style: [styles15.swatch, { backgroundColor: swatchDisplay }, selected && styles15.swatchSelected, taken && !swappable && styles15.swatchTaken],
1328
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: "check", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Icon, { source: "close", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native19.View, {})
1147
1329
  },
1148
1330
  swatch.value
1149
1331
  );
1150
1332
  }) }) })
1151
1333
  ] });
1152
1334
  }
1153
- var styles11 = import_react_native15.StyleSheet.create({
1335
+ var styles15 = import_react_native19.StyleSheet.create({
1154
1336
  anchor: {
1155
1337
  position: "relative"
1156
1338
  },
@@ -1222,11 +1404,11 @@ var styles11 = import_react_native15.StyleSheet.create({
1222
1404
  });
1223
1405
 
1224
1406
  // src/LabeledDropdown.tsx
1225
- var import_auto_paper7 = require("@rific/auto-paper");
1407
+ var import_auto_paper11 = require("@rific/auto-paper");
1226
1408
  var import_feedback_press7 = require("@rific/feedback-press");
1227
- var import_react_native16 = require("react-native");
1228
- var import_react_native_paper8 = require("react-native-paper");
1229
- var import_jsx_runtime15 = require("react/jsx-runtime");
1409
+ var import_react_native20 = require("react-native");
1410
+ var import_react_native_paper12 = require("react-native-paper");
1411
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1230
1412
  var TRIGGER_HEIGHT = 28;
1231
1413
  var POPOVER_WIDTH = 180;
1232
1414
  var ROW_HEIGHT = 36;
@@ -1249,22 +1431,22 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1249
1431
  onChange(option.value);
1250
1432
  host.close();
1251
1433
  };
1252
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native16.View, { style: [styles12.anchor, open && styles12.anchorOpen], children: [
1253
- /* @__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: [
1254
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1255
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: "menu-down", size: 16, color })
1434
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native20.View, { style: [styles16.anchor, open && styles16.anchorOpen], children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => host.toggle(id), style: styles16.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native20.View, { ref: triggerRef, collapsable: false, style: styles16.triggerInner, children: [
1436
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Text, { style: [styles16.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1437
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Icon, { source: "menu-down", size: 16, color })
1256
1438
  ] }) }),
1257
- /* @__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) => {
1439
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native20.ScrollView, { style: [styles16.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles16.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1258
1440
  const isSelected = option.value === value;
1259
- const onColor = (0, import_auto_paper7.getContrastColor)(color);
1260
- 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: [
1261
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1262
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1441
+ const onColor = (0, import_auto_paper11.getContrastColor)(color);
1442
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles16.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native20.View, { style: styles16.rowInner, children: [
1443
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper12.Text, { style: [styles16.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1263
1445
  ] }) }, option.value);
1264
1446
  }) }) })
1265
1447
  ] });
1266
1448
  }
1267
- var styles12 = import_react_native16.StyleSheet.create({
1449
+ var styles16 = import_react_native20.StyleSheet.create({
1268
1450
  anchor: {
1269
1451
  position: "relative"
1270
1452
  },
@@ -1321,26 +1503,23 @@ function loadSkiaWeb() {
1321
1503
  return Promise.resolve();
1322
1504
  }
1323
1505
 
1324
- // src/PressAwayOverlay.tsx
1325
- var import_react_native17 = require("react-native");
1326
- var import_jsx_runtime16 = require("react/jsx-runtime");
1327
- function PressAwayOverlay({ active, onPress, style }) {
1328
- if (!active) return null;
1329
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native17.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native17.StyleSheet.absoluteFill, style] });
1330
- }
1506
+ // src/PlayerSetupPanel.tsx
1507
+ var import_profile2 = require("@tastic/profile");
1508
+ var import_react_native22 = require("react-native");
1509
+ var import_react_native_paper14 = require("react-native-paper");
1331
1510
 
1332
1511
  // src/ReadyButton.tsx
1333
1512
  var import_feedback_press8 = require("@rific/feedback-press");
1334
- var import_react_native18 = require("react-native");
1335
- var import_react_native_paper9 = require("react-native-paper");
1336
- var import_jsx_runtime17 = require("react/jsx-runtime");
1513
+ var import_react_native21 = require("react-native");
1514
+ var import_react_native_paper13 = require("react-native-paper");
1515
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1337
1516
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
1338
- const readyButtonStyle = [styles13.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1517
+ const readyButtonStyle = [styles17.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1339
1518
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
1340
1519
  const labelColorStyle = { color: ready ? "#000000" : color };
1341
- 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?" }) });
1520
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_feedback_press8.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_react_native_paper13.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1342
1521
  }
1343
- var styles13 = import_react_native18.StyleSheet.create({
1522
+ var styles17 = import_react_native21.StyleSheet.create({
1344
1523
  readyButton: {
1345
1524
  alignItems: "center",
1346
1525
  borderRadius: 12,
@@ -1352,22 +1531,97 @@ var styles13 = import_react_native18.StyleSheet.create({
1352
1531
  }
1353
1532
  });
1354
1533
 
1534
+ // src/usePopoverHost.ts
1535
+ var import_react8 = require("react");
1536
+ function usePopoverHost() {
1537
+ const [openId, setOpenId] = (0, import_react8.useState)(null);
1538
+ const toggle = (0, import_react8.useCallback)((id) => {
1539
+ setOpenId((prev) => prev === id ? null : id);
1540
+ }, []);
1541
+ const close = (0, import_react8.useCallback)(() => setOpenId(null), []);
1542
+ return { openId, toggle, close };
1543
+ }
1544
+
1545
+ // src/PlayerSetupPanel.tsx
1546
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1547
+ function getColorPopoverId(idPrefix) {
1548
+ return `${idPrefix}-color`;
1549
+ }
1550
+ function getCpuDifficultyPopoverId(idPrefix) {
1551
+ return `${idPrefix}-difficulty`;
1552
+ }
1553
+ 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" }) {
1554
+ const ownHost = usePopoverHost();
1555
+ const popover = host ?? ownHost;
1556
+ const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false;
1557
+ const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || secondPickerId !== void 0 && popover.openId === secondPickerId;
1558
+ const showProfilePicker = isHuman && profiles !== void 0 && !!onProfileSelect;
1559
+ const showCpuDifficulty = !isHuman && cpuDifficulty !== void 0 && cpuDifficultyOptions !== void 0 && !!onCpuDifficultyChange;
1560
+ const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null;
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native22.View, { style: [styles18.panel, ownPopoverOpen && styles18.panelOpen], children: [
1562
+ showProfilePicker ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_profile2.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_runtime22.jsx)(LabeledDropdown, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, alignOverride: cpuDifficultyAlignOverride }) : label && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper14.Text, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native22.View, { style: [styles18.pickerRow, pickerRowPopoverOpen && styles18.pickerRowOpen], children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime22.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 }),
1565
+ secondPicker
1566
+ ] }),
1567
+ isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReadyButton, { color, ready: ready ?? false, onToggleReady })
1568
+ ] });
1569
+ }
1570
+ var styles18 = import_react_native22.StyleSheet.create({
1571
+ panel: {
1572
+ alignItems: "center",
1573
+ gap: 12
1574
+ },
1575
+ // See SectionedDropdown's anchorOpen comment — this panel is typically a sibling of another
1576
+ // panel (and/or a shared-controls row), so a popover escaping this panel's bounds needs the panel
1577
+ // itself elevated, not just the popover content, to paint above a later sibling.
1578
+ panelOpen: {
1579
+ zIndex: 100
1580
+ },
1581
+ // Same gap as `panel`'s own vertical rhythm, reused horizontally — the color picker and (when
1582
+ // present) secondPicker sit side by side within this row instead of stacked in the panel's own
1583
+ // column.
1584
+ pickerRow: {
1585
+ alignItems: "center",
1586
+ flexDirection: "row",
1587
+ gap: 12
1588
+ },
1589
+ // A popover is an absolutely-positioned sibling of its own trigger — so a tall one (the color
1590
+ // swatch grid) can extend down far enough to overlap the ReadyButton below, which paints on top of
1591
+ // it by default DOM order since it's a later sibling within `panel`. `panel`'s own panelOpen
1592
+ // elevation only helps this panel paint above OTHER panels (see its own comment) — it does nothing
1593
+ // for stacking *within* this panel, since React Native Web gives each View its own stacking
1594
+ // context. This is what actually fixes that: elevate pickerRow itself above its own later sibling
1595
+ // whenever one of its own popovers is open.
1596
+ pickerRowOpen: {
1597
+ zIndex: 100
1598
+ }
1599
+ });
1600
+
1601
+ // src/PressAwayOverlay.tsx
1602
+ var import_react_native23 = require("react-native");
1603
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1604
+ function PressAwayOverlay({ active, onPress, style }) {
1605
+ if (!active) return null;
1606
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native23.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native23.StyleSheet.absoluteFill, style] });
1607
+ }
1608
+
1355
1609
  // src/SharedActionBand.tsx
1356
1610
  var import_feedback_press9 = require("@rific/feedback-press");
1357
- var import_react_native19 = require("react-native");
1358
- var import_jsx_runtime18 = require("react/jsx-runtime");
1611
+ var import_react_native24 = require("react-native");
1612
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1359
1613
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1360
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native19.View, { style: [styles14.column, popoverOpen && styles14.columnOpen], children: [
1361
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native19.View, { style: styles14.actionsRow, children: [
1362
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1363
- onRandomize && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1364
- 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" }),
1365
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1614
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native24.View, { style: [styles19.column, popoverOpen && styles19.columnOpen], children: [
1615
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_native24.View, { style: styles19.actionsRow, children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1617
+ onRandomize && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1618
+ onReset && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1619
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1366
1620
  ] }),
1367
1621
  children
1368
1622
  ] });
1369
1623
  }
1370
- var styles14 = import_react_native19.StyleSheet.create({
1624
+ var styles19 = import_react_native24.StyleSheet.create({
1371
1625
  actionsRow: {
1372
1626
  alignItems: "center",
1373
1627
  flexDirection: "row",
@@ -1382,84 +1636,111 @@ var styles14 = import_react_native19.StyleSheet.create({
1382
1636
  }
1383
1637
  });
1384
1638
 
1385
- // src/StatRow.tsx
1386
- var import_auto_paper8 = require("@rific/auto-paper");
1387
- var import_react_native20 = require("react-native");
1388
- var import_react_native_paper10 = require("react-native-paper");
1389
- var import_jsx_runtime19 = require("react/jsx-runtime");
1390
- function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1391
- const { dark } = (0, import_auto_paper8.useAutoPaperTheme)();
1392
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1393
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
1394
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native20.View, { style: styles15.row, children: [
1395
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1396
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: [styles15.value, { color: fg }], children: value })
1397
- ] });
1639
+ // src/StaggeredWord.tsx
1640
+ var import_react9 = require("react");
1641
+ var import_react_native25 = require("react-native");
1642
+ var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
1643
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1644
+ var STAGGER_MS = 45;
1645
+ var LETTER_DURATION_MS = 320;
1646
+ function getStaggeredWordDuration(letterCount) {
1647
+ return (letterCount - 1) * STAGGER_MS + LETTER_DURATION_MS;
1398
1648
  }
1399
- var styles15 = import_react_native20.StyleSheet.create({
1400
- row: {
1401
- alignItems: "center",
1402
- flexDirection: "row",
1403
- justifyContent: "space-between"
1404
- },
1405
- value: {
1406
- fontWeight: "bold"
1407
- }
1408
- });
1409
-
1410
- // src/StatSection.tsx
1411
- var import_auto_paper9 = require("@rific/auto-paper");
1412
- var import_react_native21 = require("react-native");
1413
- var import_react_native_paper11 = require("react-native-paper");
1414
- var import_jsx_runtime20 = require("react/jsx-runtime");
1415
- function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1416
- const { dark } = (0, import_auto_paper9.useAutoPaperTheme)();
1417
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1418
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1419
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native21.View, { style: [styles16.section, { backgroundColor: sectionBg }], children: [
1420
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper11.Text, { variant: "labelMedium", style: [styles16.label, { color: fg }], children: label }),
1421
- children
1422
- ] });
1649
+ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
1650
+ const progress = (0, import_react_native_reanimated2.useSharedValue)(reducedMotion ? 1 : 0);
1651
+ (0, import_react9.useEffect)(() => {
1652
+ if (reducedMotion) return;
1653
+ 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)) }));
1654
+ }, [reducedMotion]);
1655
+ const style = (0, import_react_native_reanimated2.useAnimatedStyle)(() => ({
1656
+ opacity: progress.value,
1657
+ transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
1658
+ }));
1659
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native_reanimated2.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
1423
1660
  }
1424
- var styles16 = import_react_native21.StyleSheet.create({
1425
- // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1426
- // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1427
- // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
1428
- // at a glance). Matches AchievementRow's own title styling (bold + fg) below it in the same list.
1429
- label: {
1430
- fontWeight: "bold",
1431
- letterSpacing: 2
1432
- },
1433
- section: {
1434
- borderRadius: 12,
1435
- gap: 8,
1436
- padding: 16
1661
+ function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
1662
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native25.View, { style: [styles20.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
1663
+ }
1664
+ var styles20 = import_react_native25.StyleSheet.create({
1665
+ row: {
1666
+ flexDirection: "row"
1437
1667
  }
1438
1668
  });
1439
1669
 
1440
1670
  // src/UpdateDialog.tsx
1441
1671
  var import_updater2 = require("@rific/updater");
1442
- var import_jsx_runtime21 = require("react/jsx-runtime");
1672
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1443
1673
  function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1444
1674
  const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1445
1675
  (0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
1446
1676
  const date = manifest ? new Date(manifest.createdAt) : null;
1447
- 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) });
1677
+ return /* @__PURE__ */ (0, import_jsx_runtime26.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) });
1448
1678
  }
1449
1679
 
1450
- // src/usePopoverHost.ts
1451
- var import_react7 = require("react");
1452
- function usePopoverHost() {
1453
- const [openId, setOpenId] = (0, import_react7.useState)(null);
1454
- const toggle = (0, import_react7.useCallback)((id) => {
1455
- setOpenId((prev) => prev === id ? null : id);
1680
+ // src/useQuitConfirmation.ts
1681
+ var import_react10 = require("react");
1682
+ function useQuitConfirmation(hasProgress, onConfirmedBack) {
1683
+ const [confirmVisible, setConfirmVisible] = (0, import_react10.useState)(false);
1684
+ const hasProgressRef = (0, import_react10.useRef)(hasProgress);
1685
+ (0, import_react10.useEffect)(() => {
1686
+ hasProgressRef.current = hasProgress;
1687
+ });
1688
+ const onConfirmedBackRef = (0, import_react10.useRef)(onConfirmedBack);
1689
+ (0, import_react10.useEffect)(() => {
1690
+ onConfirmedBackRef.current = onConfirmedBack;
1691
+ });
1692
+ const requestBack = (0, import_react10.useCallback)(() => {
1693
+ if (hasProgressRef.current()) setConfirmVisible(true);
1694
+ else onConfirmedBackRef.current();
1456
1695
  }, []);
1457
- const close = (0, import_react7.useCallback)(() => setOpenId(null), []);
1458
- return { openId, toggle, close };
1696
+ const cancelBack = (0, import_react10.useCallback)(() => setConfirmVisible(false), []);
1697
+ return { confirmVisible, requestBack, cancelBack };
1698
+ }
1699
+
1700
+ // src/useZoneClampedAlign.ts
1701
+ var import_split_screen = require("@tastic/split-screen");
1702
+ var import_react11 = require("react");
1703
+ var import_react_native26 = require("react-native");
1704
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
1705
+ var EDGE_MARGIN2 = 12;
1706
+ function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
1707
+ const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
1708
+ const zone = (0, import_split_screen.useZoneBounds)();
1709
+ const { height: windowHeight } = (0, import_react_native26.useWindowDimensions)();
1710
+ const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
1711
+ const shouldMeasure = open && auto.measured && zone !== null;
1712
+ const [ownBounds, setOwnBounds] = (0, import_react11.useState)(null);
1713
+ const [prevShouldMeasure, setPrevShouldMeasure] = (0, import_react11.useState)(shouldMeasure);
1714
+ if (shouldMeasure !== prevShouldMeasure) {
1715
+ setPrevShouldMeasure(shouldMeasure);
1716
+ setOwnBounds(null);
1717
+ }
1718
+ (0, import_react11.useEffect)(() => {
1719
+ if (!shouldMeasure) return;
1720
+ auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
1721
+ const belowShared = zone.zoneSide === "belowShared";
1722
+ const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
1723
+ const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
1724
+ const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
1725
+ let awaySide = belowShared ? "below" : "above";
1726
+ let towardSide = belowShared ? "above" : "below";
1727
+ if (zone.rotated) {
1728
+ awaySide = awaySide === "above" ? "below" : "above";
1729
+ towardSide = towardSide === "above" ? "below" : "above";
1730
+ }
1731
+ setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
1732
+ });
1733
+ }, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
1734
+ if (!zone) return auto;
1735
+ if (!ownBounds) return { ...auto, measured: false };
1736
+ return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
1459
1737
  }
1460
1738
  // Annotate the CommonJS export names for ESM import in node:
1461
1739
  0 && (module.exports = {
1740
+ AchievementCatalogSection,
1462
1741
  AchievementRow,
1742
+ AchievementUnlockList,
1743
+ ActivityStatSection,
1463
1744
  BaseSettingsDialog,
1464
1745
  BaseStatsScreen,
1465
1746
  ConfirmDialog,
@@ -1472,20 +1753,29 @@ function usePopoverHost() {
1472
1753
  LOCKED_BADGE_COLOR,
1473
1754
  LabeledDropdown,
1474
1755
  MONO_FONT,
1756
+ PlayerSetupPanel,
1475
1757
  PopoverBody,
1476
1758
  PressAwayOverlay,
1477
1759
  ReadyButton,
1478
1760
  SectionedDropdown,
1479
1761
  SharedActionBand,
1480
1762
  SkiaGate,
1763
+ StaggeredWord,
1481
1764
  StatRow,
1482
1765
  StatSection,
1483
1766
  TriggerGaugeHost,
1484
1767
  UpdateDialog,
1768
+ getColorPopoverId,
1769
+ getCpuDifficultyPopoverId,
1770
+ getInlineColorPickerContentSize,
1485
1771
  getLabeledDropdownContentHeight,
1486
1772
  getOverlayCardColors,
1773
+ getStaggeredWordDuration,
1487
1774
  loadSkiaWeb,
1775
+ overlayActionStyles,
1488
1776
  overlayStyles,
1489
1777
  useAutoAlign,
1490
- usePopoverHost
1778
+ usePopoverHost,
1779
+ useQuitConfirmation,
1780
+ useZoneClampedAlign
1491
1781
  });