@tastic/hud 0.8.5 → 0.12.0

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