@tastic/hud 0.7.0 → 0.8.2

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
@@ -28,22 +28,22 @@ __export(TriggerGauge_exports, {
28
28
  function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
29
29
  const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
30
30
  const mountProgress = (0, import_react_native_reanimated.useSharedValue)(0);
31
- (0, import_react3.useLayoutEffect)(() => {
31
+ (0, import_react4.useLayoutEffect)(() => {
32
32
  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
33
  }, []);
34
34
  const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
35
35
  const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
36
36
  const step = segments > 0 ? totalSweepDeg / segments : 0;
37
- const centerDeg = (0, import_react3.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
37
+ const centerDeg = (0, import_react4.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
38
38
  const singleSelect = litIndices.length === 1;
39
39
  const angleDeg = (0, import_react_native_reanimated.useSharedValue)(centerDeg(litIndices[0] ?? 0));
40
40
  const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
41
- const [transitionFrom, setTransitionFrom] = (0, import_react3.useState)(litIndices);
41
+ const [transitionFrom, setTransitionFrom] = (0, import_react4.useState)(litIndices);
42
42
  const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
43
- const lastKeyRef = (0, import_react3.useRef)(litIndicesKey);
44
- const lastIndicesRef = (0, import_react3.useRef)(litIndices);
43
+ const lastKeyRef = (0, import_react4.useRef)(litIndicesKey);
44
+ const lastIndicesRef = (0, import_react4.useRef)(litIndices);
45
45
  const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
46
- (0, import_react3.useLayoutEffect)(() => {
46
+ (0, import_react4.useLayoutEffect)(() => {
47
47
  if (litIndicesKey !== lastKeyRef.current) {
48
48
  if (singleSelect && lastIndicesRef.current.length === 1) {
49
49
  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) });
@@ -72,7 +72,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
72
72
  if (mountProgress.value < 1) {
73
73
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
74
74
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
75
- local = (0, import_core3.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
75
+ local = (0, import_core4.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
76
76
  }
77
77
  const sweep = dashAngleDeg * local;
78
78
  if (sweep <= 0) continue;
@@ -96,7 +96,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
96
96
  if (!isLitNow) continue;
97
97
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
98
98
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
99
- local = (0, import_core3.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
99
+ local = (0, import_core4.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
100
100
  } else if (isLitNow === wasLit) {
101
101
  local = 1;
102
102
  } else if (isLitNow) {
@@ -120,21 +120,21 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
120
120
  height: canvasSize,
121
121
  width: canvasSize
122
122
  };
123
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [styles7.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime7.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_runtime7.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
123
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.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 })
126
126
  ] }) });
127
127
  }
128
- var import_react_native_skia, import_core3, import_react3, import_react_native8, import_react_native_reanimated, import_jsx_runtime7, 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;
128
+ var import_react_native_skia, import_core4, import_react4, import_react_native8, 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;
129
129
  var init_TriggerGauge = __esm({
130
130
  "src/TriggerGauge.tsx"() {
131
131
  "use strict";
132
132
  import_react_native_skia = require("@shopify/react-native-skia");
133
- import_core3 = require("@tastic/core");
134
- import_react3 = require("react");
133
+ import_core4 = require("@tastic/core");
134
+ import_react4 = require("react");
135
135
  import_react_native8 = require("react-native");
136
136
  import_react_native_reanimated = require("react-native-reanimated");
137
- import_jsx_runtime7 = require("react/jsx-runtime");
137
+ import_jsx_runtime8 = require("react/jsx-runtime");
138
138
  UNLIT_THICKNESS = 3;
139
139
  LIT_THICKNESS = 7;
140
140
  DASH_GAP_PX = 9;
@@ -175,6 +175,7 @@ __export(index_exports, {
175
175
  ReadyButton: () => ReadyButton,
176
176
  SectionedDropdown: () => SectionedDropdown,
177
177
  SharedActionBand: () => SharedActionBand,
178
+ SkiaGate: () => SkiaGate,
178
179
  StatRow: () => StatRow,
179
180
  StatSection: () => StatSection,
180
181
  TriggerGaugeHost: () => TriggerGaugeHost,
@@ -265,8 +266,10 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
265
266
  function SettingIcon({ source, color, containerColor }) {
266
267
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source, size: 18, color }) });
267
268
  }
268
- function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
269
+ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
269
270
  const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
271
+ const ambientRotation = (0, import_core.useRotation)();
272
+ const rotation = rotationOverride ?? ambientRotation;
270
273
  const isTouchPrimary = (0, import_core.useIsTouchPrimaryDevice)();
271
274
  const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press.useHapticSettings)();
272
275
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press.useSoundSettings)();
@@ -456,136 +459,56 @@ var styles2 = import_react_native2.StyleSheet.create({
456
459
  });
457
460
 
458
461
  // src/BaseStatsScreen.tsx
462
+ var import_auto_paper4 = require("@rific/auto-paper");
463
+ var import_feedback_press3 = require("@rific/feedback-press");
464
+ var import_core2 = require("@tastic/core");
465
+ var import_react2 = require("react");
466
+ var import_react_native4 = require("react-native");
467
+ var import_react_native_paper4 = require("react-native-paper");
468
+
469
+ // src/ConfirmDialog.tsx
459
470
  var import_auto_paper3 = require("@rific/auto-paper");
460
471
  var import_feedback_press2 = require("@rific/feedback-press");
461
- var import_react2 = require("react");
462
472
  var import_react_native3 = require("react-native");
463
473
  var import_react_native_paper3 = require("react-native-paper");
464
474
  var import_jsx_runtime3 = require("react/jsx-runtime");
465
- function BaseStatsScreen({ rotation = 0, 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, cardBg: cardBgOverride, accentColor: accentColorOverride, children }) {
466
- const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
467
- const [confirmResetVisible, setConfirmResetVisible] = (0, import_react2.useState)(false);
468
- const showReset = !!onReset;
469
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
470
- const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
471
- const cardBg = cardBgOverride ?? (dark ? "#111111" : "#F2F2F2");
472
- const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
473
- const accentColor = accentColorOverride ?? colors.secondary;
474
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.container, { backgroundColor: bg }], children: [
475
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
476
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
477
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: title })
478
- ] }),
479
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
480
- children,
481
- showReset && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.secondary, textColor: colors.onSecondary, style: styles3.resetButton, children: resetLabel })
482
- ] }),
483
- confirmResetVisible && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.overlay, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }], children: [
484
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "alert-outline", size: 64, color: accentColor }),
485
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "headlineLarge", style: [styles3.overlayTitle, { color: accentColor }], children: resetConfirmTitle }),
486
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.overlayBody, { color: fg }], children: resetConfirmBody }),
487
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "contained", onPress: () => setConfirmResetVisible(false), style: styles3.overlayButton, children: "Cancel" }),
488
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
489
- import_feedback_press2.Button,
490
- {
491
- mode: "contained",
492
- onPress: () => {
493
- onReset?.();
494
- setConfirmResetVisible(false);
495
- },
496
- style: styles3.overlayButton,
497
- buttonColor: colors.secondary,
498
- textColor: colors.onSecondary,
499
- children: "Reset"
500
- }
501
- )
502
- ] }) }) })
503
- ] });
504
- }
505
- var styles3 = import_react_native3.StyleSheet.create({
506
- container: {
507
- flex: 1
508
- },
509
- content: {
510
- gap: 12,
511
- paddingHorizontal: 20
512
- },
513
- header: {
514
- alignItems: "center",
515
- flexDirection: "row",
516
- gap: 4,
517
- paddingBottom: 8
518
- },
519
- // Same overlay shape BaseSettingsDialog's own info overlay uses (backdrop + centered card),
520
- // rotated in place the same way rather than via Portal-root transform for the same reason — see
521
- // that component's own identical styles for the full rationale.
522
- overlay: {
523
- alignItems: "center",
524
- backgroundColor: "rgba(0,0,0,0.72)",
525
- bottom: 0,
526
- justifyContent: "center",
527
- left: 0,
528
- position: "absolute",
529
- right: 0,
530
- top: 0
531
- },
532
- overlayBody: { textAlign: "center" },
533
- overlayButton: { width: 160 },
534
- overlayCard: {
535
- alignItems: "center",
536
- borderRadius: 20,
537
- borderWidth: 1,
538
- gap: 16,
539
- maxWidth: 360,
540
- padding: 32
541
- },
542
- overlayTitle: { fontWeight: "bold", marginBottom: -8 },
543
- resetButton: {
544
- marginTop: 16
545
- },
546
- // Bounds the ScrollView to the space `container`'s flex:1 actually gives it — without this, a
547
- // ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
548
- // can render at its full unclipped content height instead of the screen's), which leaves the
549
- // header's back button touch target unreliable underneath it.
550
- scrollView: {
551
- flex: 1
552
- },
553
- title: {
554
- flexShrink: 1,
555
- fontWeight: "bold"
556
- }
557
- });
558
-
559
- // src/ConfirmDialog.tsx
560
- var import_auto_paper4 = require("@rific/auto-paper");
561
- var import_feedback_press3 = require("@rific/feedback-press");
562
- var import_react_native4 = require("react-native");
563
- var import_react_native_paper4 = require("react-native-paper");
564
- var import_jsx_runtime4 = require("react/jsx-runtime");
565
475
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
566
- const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
476
+ const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
567
477
  if (!visible) return null;
568
478
  const fg = dark ? "#FFFFFF" : "#000000";
569
479
  const cardBg = dark ? "#111111" : "#F2F2F2";
570
480
  const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
571
481
  const accent = destructive ? colors.danger : colors.primary;
572
482
  const onAccent = destructive ? colors.onDanger : colors.onPrimary;
573
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
574
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Icon, { source: icon, size: 64, color: accent }),
575
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "headlineLarge", style: [styles4.title, { color: accent }], children: title }),
576
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: [styles4.body, { color: fg }], children: message }),
577
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.actions, children: [
578
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.Button, { mode: "outlined", onPress: onCancel, style: styles4.actionButton, children: cancelLabel }),
579
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: onConfirm, style: styles4.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
483
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
484
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: icon, size: 64, color: accent }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "headlineLarge", style: [styles3.title, { color: accent }], children: title }),
486
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.body, { color: fg }], children: message }),
487
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.actions, children: [
488
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: onCancel, style: styles3.actionButton, children: cancelLabel }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "contained", onPress: onConfirm, style: styles3.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
580
490
  ] })
581
491
  ] }) }) });
582
492
  }
583
- var styles4 = import_react_native4.StyleSheet.create({
493
+ var styles3 = import_react_native3.StyleSheet.create({
494
+ // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
495
+ // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
496
+ // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
497
+ // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
498
+ // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
499
+ // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
500
+ // fixable by just giving the row more width, until confirmed live that these two-word labels
501
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
502
+ // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
503
+ // width there's no side-by-side arrangement that fits both buttons at their natural label size,
504
+ // so the layout has to give (stack) rather than the label (truncate).
584
505
  actionButton: {
585
- flex: 1
506
+ flexBasis: 128,
507
+ flexGrow: 1
586
508
  },
587
509
  actions: {
588
510
  flexDirection: "row",
511
+ flexWrap: "wrap",
589
512
  gap: 12,
590
513
  width: "100%"
591
514
  },
@@ -598,7 +521,12 @@ var styles4 = import_react_native4.StyleSheet.create({
598
521
  borderWidth: 1,
599
522
  gap: 16,
600
523
  maxWidth: 360,
601
- padding: 32
524
+ padding: 32,
525
+ // Claims its full allowance instead of just maxWidth alone, which only caps how wide the card
526
+ // is allowed to get - without an explicit width, a flex-centered child sizes to its own content
527
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
528
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
529
+ width: "100%"
602
530
  },
603
531
  overlay: {
604
532
  alignItems: "center",
@@ -606,22 +534,99 @@ var styles4 = import_react_native4.StyleSheet.create({
606
534
  bottom: 0,
607
535
  justifyContent: "center",
608
536
  left: 0,
537
+ // Gives the card room to breathe from the screen edges now that it reliably stretches to fill
538
+ // its full width allowance (see card's own width comment) - without this, width: '100%' would
539
+ // run the card edge-to-edge on a screen narrower than maxWidth's own 360.
540
+ paddingHorizontal: 24,
609
541
  position: "absolute",
610
542
  right: 0,
611
543
  top: 0
612
544
  },
545
+ // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
546
+ // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
547
+ // line inside that block still defaults to left-aligned without this.
548
+ title: {
549
+ fontWeight: "bold",
550
+ textAlign: "center"
551
+ }
552
+ });
553
+
554
+ // src/BaseStatsScreen.tsx
555
+ var import_jsx_runtime4 = require("react/jsx-runtime");
556
+ 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 }) {
557
+ const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
558
+ const ambientRotation = (0, import_core2.useRotation)();
559
+ const rotation = rotationOverride ?? ambientRotation;
560
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react2.useState)(false);
561
+ const showReset = !!onReset;
562
+ const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
563
+ const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
564
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.container, { backgroundColor: bg }], children: [
565
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
567
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles4.title, { color: fg }], children: title })
568
+ ] }),
569
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
570
+ children,
571
+ 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 })
572
+ ] }),
573
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
574
+ ConfirmDialog,
575
+ {
576
+ visible: confirmResetVisible,
577
+ title: resetConfirmTitle,
578
+ message: resetConfirmBody,
579
+ confirmLabel: "Reset",
580
+ cancelLabel: "Cancel",
581
+ icon: "alert-outline",
582
+ destructive: true,
583
+ rotation,
584
+ onCancel: () => setConfirmResetVisible(false),
585
+ onConfirm: () => {
586
+ onReset?.();
587
+ setConfirmResetVisible(false);
588
+ }
589
+ }
590
+ )
591
+ ] });
592
+ }
593
+ var styles4 = import_react_native4.StyleSheet.create({
594
+ container: {
595
+ flex: 1
596
+ },
597
+ content: {
598
+ gap: 12,
599
+ paddingHorizontal: 20
600
+ },
601
+ header: {
602
+ alignItems: "center",
603
+ flexDirection: "row",
604
+ gap: 4,
605
+ paddingBottom: 8
606
+ },
607
+ resetButton: {
608
+ marginTop: 16
609
+ },
610
+ // Bounds the ScrollView to the space `container`'s flex:1 actually gives it — without this, a
611
+ // ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
612
+ // can render at its full unclipped content height instead of the screen's), which leaves the
613
+ // header's back button touch target unreliable underneath it.
614
+ scrollView: {
615
+ flex: 1
616
+ },
613
617
  title: {
618
+ flexShrink: 1,
614
619
  fontWeight: "bold"
615
620
  }
616
621
  });
617
622
 
618
623
  // src/ContentGutter.tsx
619
- var import_core2 = require("@tastic/core");
624
+ var import_core3 = require("@tastic/core");
620
625
  var import_react_native5 = require("react-native");
621
626
  var import_jsx_runtime5 = require("react/jsx-runtime");
622
627
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
623
628
  const { width: windowWidth } = (0, import_react_native5.useWindowDimensions)();
624
- const { contentWidth, gutterWidth } = (0, import_core2.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
629
+ const { contentWidth, gutterWidth } = (0, import_core3.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
625
630
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.row, style], children: [
626
631
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
627
632
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: { width: contentWidth }, children }),
@@ -640,12 +645,13 @@ var styles5 = import_react_native5.StyleSheet.create({
640
645
  });
641
646
 
642
647
  // src/ControlSchemePicker.tsx
643
- var import_core4 = require("@tastic/core");
648
+ var import_core6 = require("@tastic/core");
644
649
  var import_react_native11 = require("react-native");
645
650
 
646
651
  // src/SectionedDropdown.tsx
647
652
  var import_auto_paper5 = require("@rific/auto-paper");
648
653
  var import_feedback_press4 = require("@rific/feedback-press");
654
+ var import_core5 = require("@tastic/core");
649
655
  var import_react_native10 = require("react-native");
650
656
  var import_react_native_paper5 = require("react-native-paper");
651
657
 
@@ -719,26 +725,45 @@ var styles6 = import_react_native7.StyleSheet.create({
719
725
  }
720
726
  });
721
727
 
722
- // src/TriggerGaugeHost.tsx
723
- var import_react4 = require("react");
724
-
725
- // src/loadSkiaWeb.ts
726
- function loadSkiaWeb() {
727
- return Promise.resolve();
728
+ // src/SkiaGate.tsx
729
+ var import_react3 = require("react");
730
+ var import_jsx_runtime7 = require("react/jsx-runtime");
731
+ function SkiaGate({ getComponent, componentProps, fallback = null }) {
732
+ const [Component, setComponent] = (0, import_react3.useState)(null);
733
+ (0, import_react3.useEffect)(() => {
734
+ let cancelled = false;
735
+ getComponent().then((mod) => {
736
+ if (!cancelled) setComponent(() => mod.default);
737
+ });
738
+ return () => {
739
+ cancelled = true;
740
+ };
741
+ }, [getComponent]);
742
+ if (!Component) return fallback;
743
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Component, { ...componentProps });
728
744
  }
729
745
 
730
746
  // src/TriggerGaugeHost.tsx
731
- var import_jsx_runtime8 = require("react/jsx-runtime");
732
- var LazyTriggerGauge = (0, import_react4.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((m) => ({ default: m.TriggerGauge }))));
747
+ var import_jsx_runtime9 = require("react/jsx-runtime");
748
+ var getTriggerGauge = () => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((mod) => ({ default: mod.TriggerGauge }));
733
749
  function TriggerGaugeHost(props) {
734
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react4.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LazyTriggerGauge, { ...props }) });
750
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(SkiaGate, { getComponent: getTriggerGauge, componentProps: props });
735
751
  }
736
752
 
737
753
  // src/useAutoAlign.ts
738
754
  var import_react5 = require("react");
739
755
  var import_react_native9 = require("react-native");
740
756
  var EDGE_MARGIN = 12;
741
- function useAutoAlign(open, contentWidth, contentHeight) {
757
+ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
758
+ if (rotation === 0) return { x, y, width, height };
759
+ const cx = windowWidth / 2;
760
+ const cy = windowHeight / 2;
761
+ const dx = x + width / 2 - cx;
762
+ const dy = y + height / 2 - cy;
763
+ const [newDx, newDy, newWidth, newHeight] = rotation === 180 ? [-dx, -dy, width, height] : rotation === 90 ? [-dy, dx, height, width] : [dy, -dx, height, width];
764
+ return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
765
+ }
766
+ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
742
767
  const triggerRef = (0, import_react5.useRef)(null);
743
768
  const { width: windowWidth, height: windowHeight } = (0, import_react_native9.useWindowDimensions)();
744
769
  const [align, setAlign] = (0, import_react5.useState)("center");
@@ -751,7 +776,8 @@ function useAutoAlign(open, contentWidth, contentHeight) {
751
776
  if (open) setMeasured(false);
752
777
  }
753
778
  const measure = (0, import_react5.useCallback)(() => {
754
- triggerRef.current?.measureInWindow((x, y, triggerWidth, triggerHeight) => {
779
+ triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
780
+ const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
755
781
  const centerX = x + triggerWidth / 2;
756
782
  const overflowsRight = centerX + contentWidth / 2 > windowWidth - EDGE_MARGIN;
757
783
  const overflowsLeft = centerX - contentWidth / 2 < EDGE_MARGIN;
@@ -765,7 +791,7 @@ function useAutoAlign(open, contentWidth, contentHeight) {
765
791
  setMaxHeight(Math.max(above ? roomAbove : roomBelow, 0));
766
792
  setMeasured(true);
767
793
  });
768
- }, [contentWidth, contentHeight, windowWidth, windowHeight]);
794
+ }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
769
795
  (0, import_react5.useEffect)(() => {
770
796
  if (open) measure();
771
797
  }, [open, measure]);
@@ -773,7 +799,7 @@ function useAutoAlign(open, contentWidth, contentHeight) {
773
799
  }
774
800
 
775
801
  // src/SectionedDropdown.tsx
776
- var import_jsx_runtime9 = require("react/jsx-runtime");
802
+ var import_jsx_runtime10 = require("react/jsx-runtime");
777
803
  var MENU_BORDER_WIDTH = 1;
778
804
  var TRIGGER_SIZE = 44;
779
805
  var MENU_MIN_WIDTH = 200;
@@ -784,7 +810,9 @@ var MENU_ITEM_HEIGHT = 40;
784
810
  var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
785
811
  var MENU_DIVIDER_HEIGHT = 9;
786
812
  var MENU_VERTICAL_PADDING = 12;
787
- function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
813
+ 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" } }) {
814
+ const ambientRotation = (0, import_core5.useRotation)();
815
+ const rotation = rotationOverride ?? ambientRotation;
788
816
  const menuBg = dark ? "#000000" : "#FFFFFF";
789
817
  const menuBorderColor = (0, import_auto_paper5.getBlendedColor)(mutedColor, menuBg, 0.5);
790
818
  const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper5.getColorRoles)(accentColor, menuBg).onColor;
@@ -796,7 +824,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
796
824
  const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
797
825
  return sum + rows + footer + divider;
798
826
  }, 0);
799
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight);
827
+ const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight, rotation);
800
828
  const align = alignOverride ?? autoAlign;
801
829
  const onlySection = sections.length === 1 ? sections[0] : null;
802
830
  const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
@@ -822,14 +850,14 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
822
850
  const anchorStyle = [styles8.anchor, open && styles8.anchorOpen];
823
851
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
824
852
  const dividerStyle = { backgroundColor: menuBorderColor };
825
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: anchorStyle, children: [
826
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { ref: triggerRef, collapsable: false, style: styles8.triggerBox, children: [
827
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
828
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
853
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: anchorStyle, children: [
854
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { ref: triggerRef, collapsable: false, style: styles8.triggerBox, children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
856
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
829
857
  ] }),
830
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.ScrollView, { style: [styles8.menu, menuStyle], contentContainerStyle: styles8.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { children: [
831
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [styles8.divider, dividerStyle] }),
832
- section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
858
+ /* @__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_native10.ScrollView, { style: [styles8.menu, menuStyle], contentContainerStyle: styles8.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { children: [
859
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.View, { style: [styles8.divider, dividerStyle] }),
860
+ section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
833
861
  SingleSection,
834
862
  {
835
863
  section,
@@ -842,7 +870,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
842
870
  if (autoDismiss) host.close();
843
871
  }
844
872
  }
845
- ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
873
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
846
874
  ] }, section.id)) }) })
847
875
  ] });
848
876
  }
@@ -854,18 +882,18 @@ function SingleSection({
854
882
  labelFontFamily,
855
883
  onSelect
856
884
  }) {
857
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: section.options.map((option) => {
885
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: section.options.map((option) => {
858
886
  const selected = option.value === section.value;
859
887
  const taken = !selected && option.value === section.takenValue;
860
888
  const rowColor = selected ? onAccentColor : mutedColor;
861
889
  const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, taken && styles8.itemTaken];
862
890
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
863
891
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
864
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press4.TouchableRipple, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: styles8.itemRow, children: [
865
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
866
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: styles8.itemLabelColumn, children: [
867
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
868
- option.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
892
+ 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_native10.View, { style: styles8.itemRow, children: [
893
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
894
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: styles8.itemLabelColumn, children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
896
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
869
897
  ] })
870
898
  ] }) }, option.value);
871
899
  }) });
@@ -879,7 +907,7 @@ function MultiSection({
879
907
  allClearLabels
880
908
  }) {
881
909
  const allSelected = section.value.length === section.options.length;
882
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
910
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
883
911
  section.options.map((option, i) => {
884
912
  const selected = section.value.includes(option.value);
885
913
  const rowColor = selected ? onAccentColor : mutedColor;
@@ -887,12 +915,12 @@ function MultiSection({
887
915
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
888
916
  const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
889
917
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
890
- return /* @__PURE__ */ (0, import_jsx_runtime9.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_runtime9.jsxs)(import_react_native10.View, { style: styles8.itemRow, children: [
891
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
892
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.itemLabelFlex, labelStyle], children: option.label })
918
+ 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_native10.View, { style: styles8.itemRow, children: [
919
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
920
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.itemLabelFlex, labelStyle], children: option.label })
893
921
  ] }) }, option.value);
894
922
  }),
895
- section.allClear && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles8.item, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
923
+ 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 }) })
896
924
  ] });
897
925
  }
898
926
  var styles8 = import_react_native10.StyleSheet.create({
@@ -965,21 +993,21 @@ var styles8 = import_react_native10.StyleSheet.create({
965
993
  });
966
994
 
967
995
  // src/ControlSchemePicker.tsx
968
- var import_jsx_runtime10 = require("react/jsx-runtime");
996
+ var import_jsx_runtime11 = require("react/jsx-runtime");
969
997
  function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily }) {
970
- const isTouchPrimary = (0, import_core4.useIsTouchPrimaryDevice)();
998
+ const isTouchPrimary = (0, import_core6.useIsTouchPrimaryDevice)();
971
999
  if (import_react_native11.Platform.OS !== "web" || isTouchPrimary) return null;
972
- return /* @__PURE__ */ (0, import_jsx_runtime10.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 });
1000
+ 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 });
973
1001
  }
974
1002
 
975
1003
  // src/CornerActionButtons.tsx
976
1004
  var import_feedback_press5 = require("@rific/feedback-press");
977
1005
  var import_react_native12 = require("react-native");
978
- var import_jsx_runtime11 = require("react/jsx-runtime");
1006
+ var import_jsx_runtime12 = require("react/jsx-runtime");
979
1007
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
980
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
981
- /* @__PURE__ */ (0, import_jsx_runtime11.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 }),
982
- /* @__PURE__ */ (0, import_jsx_runtime11.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" })
1008
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1009
+ /* @__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 }),
1010
+ /* @__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" })
983
1011
  ] });
984
1012
  }
985
1013
  var styles9 = import_react_native12.StyleSheet.create({
@@ -994,17 +1022,17 @@ var styles9 = import_react_native12.StyleSheet.create({
994
1022
  // src/CornerStatusBadges.tsx
995
1023
  var import_react_native13 = require("react-native");
996
1024
  var import_react_native_paper6 = require("react-native-paper");
997
- var import_jsx_runtime12 = require("react/jsx-runtime");
1025
+ var import_jsx_runtime13 = require("react/jsx-runtime");
998
1026
  var BADGE_SIZE = 30;
999
1027
  function StatusBadge({ icon }) {
1000
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.View, { style: styles10.badge, children: icon && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper6.Icon, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
1028
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.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)" }) });
1001
1029
  }
1002
1030
  function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
1003
1031
  const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
1004
1032
  const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
1005
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1006
- p1 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.View, { pointerEvents: "none", style: p1OnRightSide ? styles10.bottomRight : styles10.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusBadge, { icon: p1.icon }) }),
1007
- p2 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.View, { pointerEvents: "none", style: p2OnLeftSide ? styles10.topLeft : styles10.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(StatusBadge, { icon: p2.icon }) })
1033
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1034
+ p1 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { pointerEvents: "none", style: p1OnRightSide ? styles10.bottomRight : styles10.bottomLeft, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StatusBadge, { icon: p1.icon }) }),
1035
+ p2 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native13.View, { pointerEvents: "none", style: p2OnLeftSide ? styles10.topLeft : styles10.topRight, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(StatusBadge, { icon: p2.icon }) })
1008
1036
  ] });
1009
1037
  }
1010
1038
  var styles10 = import_react_native13.StyleSheet.create({
@@ -1028,10 +1056,10 @@ var styles10 = import_react_native13.StyleSheet.create({
1028
1056
  // src/InlineColorPicker.tsx
1029
1057
  var import_auto_paper6 = require("@rific/auto-paper");
1030
1058
  var import_feedback_press6 = require("@rific/feedback-press");
1031
- var import_core5 = require("@tastic/core");
1059
+ var import_core7 = require("@tastic/core");
1032
1060
  var import_react_native14 = require("react-native");
1033
1061
  var import_react_native_paper7 = require("react-native-paper");
1034
- var import_jsx_runtime13 = require("react/jsx-runtime");
1062
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1035
1063
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
1036
1064
  var DEFAULT_SIZE = 48;
1037
1065
  var SWATCH_SIZE = 28;
@@ -1044,29 +1072,31 @@ var SCREEN_MARGIN = 40;
1044
1072
  function widthForColumns(columns) {
1045
1073
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
1046
1074
  }
1047
- function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper6.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1075
+ 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 }) {
1076
+ const ambientRotation = (0, import_core7.useRotation)();
1077
+ const rotation = rotationOverride ?? ambientRotation;
1048
1078
  const menuBg = dark ? "#000000" : "#FFFFFF";
1049
1079
  const { width: windowWidth } = (0, import_react_native14.useWindowDimensions)();
1050
- const autoColumns = (0, import_core5.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1080
+ const autoColumns = (0, import_core7.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
1051
1081
  const resolvedColumns = columns ?? autoColumns;
1052
1082
  const swatchesWidth = widthForColumns(resolvedColumns);
1053
1083
  const swatchRows = Math.ceil(swatches.length / resolvedColumns);
1054
1084
  const swatchesHeight = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * swatchRows + SWATCHES_GAP * (swatchRows - 1);
1055
1085
  const open = host.openId === id;
1056
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight);
1086
+ const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1057
1087
  const align = alignOverride ?? autoAlign;
1058
1088
  const displayFor = previewValue ?? ((hex) => hex);
1059
1089
  const displayValue = displayFor(value);
1060
1090
  const contrastColor = (0, import_auto_paper6.getContrastColor)(displayValue);
1061
1091
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
1062
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native14.View, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1063
- /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)(import_react_native14.View, { ref: triggerRef, collapsable: false, style: styles11.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsx)(import_react_native_paper7.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1064
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native14.ScrollView, { style: [styles11.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles11.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1092
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native14.View, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
1093
+ /* @__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_native14.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 }) }) }),
1094
+ /* @__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_native14.ScrollView, { style: [styles11.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles11.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1065
1095
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
1066
1096
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
1067
1097
  const swappable = taken && allowSwapTaken;
1068
1098
  const swatchDisplay = displayFor(swatch.value);
1069
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1099
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1070
1100
  import_feedback_press6.TouchableRipple,
1071
1101
  {
1072
1102
  disabled: taken && !swappable,
@@ -1076,7 +1106,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
1076
1106
  },
1077
1107
  borderless: true,
1078
1108
  style: [styles11.swatch, { backgroundColor: swatchDisplay }, selected && styles11.swatchSelected, taken && !swappable && styles11.swatchTaken],
1079
- children: selected ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_paper7.Icon, { source: "check", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_paper7.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native_paper7.Icon, { source: "close", size: 16, color: (0, import_auto_paper6.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native14.View, {})
1109
+ 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_native14.View, {})
1080
1110
  },
1081
1111
  swatch.value
1082
1112
  );
@@ -1159,7 +1189,7 @@ var import_auto_paper7 = require("@rific/auto-paper");
1159
1189
  var import_feedback_press7 = require("@rific/feedback-press");
1160
1190
  var import_react_native15 = require("react-native");
1161
1191
  var import_react_native_paper8 = require("react-native-paper");
1162
- var import_jsx_runtime14 = require("react/jsx-runtime");
1192
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1163
1193
  var TRIGGER_HEIGHT = 28;
1164
1194
  var POPOVER_WIDTH = 180;
1165
1195
  var ROW_HEIGHT = 36;
@@ -1182,17 +1212,17 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1182
1212
  onChange(option.value);
1183
1213
  host.close();
1184
1214
  };
1185
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { style: [styles12.anchor, open && styles12.anchorOpen], children: [
1186
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => host.toggle(id), style: styles12.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { ref: triggerRef, collapsable: false, style: styles12.triggerInner, children: [
1187
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { style: [styles12.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1188
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Icon, { source: "menu-down", size: 16, color })
1215
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native15.View, { style: [styles12.anchor, open && styles12.anchorOpen], children: [
1216
+ /* @__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_native15.View, { ref: triggerRef, collapsable: false, style: styles12.triggerInner, children: [
1217
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: "menu-down", size: 16, color })
1189
1219
  ] }) }),
1190
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native15.ScrollView, { style: [styles12.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles12.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1220
+ /* @__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_native15.ScrollView, { style: [styles12.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles12.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1191
1221
  const isSelected = option.value === value;
1192
1222
  const onColor = (0, import_auto_paper7.getContrastColor)(color);
1193
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles12.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { style: styles12.rowInner, children: [
1194
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1195
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { style: [styles12.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1223
+ 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_native15.View, { style: styles12.rowInner, children: [
1224
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper8.Text, { style: [styles12.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1196
1226
  ] }) }, option.value);
1197
1227
  }) }) })
1198
1228
  ] });
@@ -1249,24 +1279,29 @@ var styles12 = import_react_native15.StyleSheet.create({
1249
1279
  }
1250
1280
  });
1251
1281
 
1282
+ // src/loadSkiaWeb.ts
1283
+ function loadSkiaWeb() {
1284
+ return Promise.resolve();
1285
+ }
1286
+
1252
1287
  // src/PressAwayOverlay.tsx
1253
1288
  var import_react_native16 = require("react-native");
1254
- var import_jsx_runtime15 = require("react/jsx-runtime");
1289
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1255
1290
  function PressAwayOverlay({ active, onPress, style }) {
1256
1291
  if (!active) return null;
1257
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native16.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native16.StyleSheet.absoluteFill, style] });
1292
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native16.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native16.StyleSheet.absoluteFill, style] });
1258
1293
  }
1259
1294
 
1260
1295
  // src/ReadyButton.tsx
1261
1296
  var import_feedback_press8 = require("@rific/feedback-press");
1262
1297
  var import_react_native17 = require("react-native");
1263
1298
  var import_react_native_paper9 = require("react-native-paper");
1264
- var import_jsx_runtime16 = require("react/jsx-runtime");
1299
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1265
1300
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
1266
1301
  const readyButtonStyle = [styles13.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1267
1302
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
1268
1303
  const labelColorStyle = { color: ready ? "#000000" : color };
1269
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_feedback_press8.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper9.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1304
+ 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?" }) });
1270
1305
  }
1271
1306
  var styles13 = import_react_native17.StyleSheet.create({
1272
1307
  readyButton: {
@@ -1283,14 +1318,14 @@ var styles13 = import_react_native17.StyleSheet.create({
1283
1318
  // src/SharedActionBand.tsx
1284
1319
  var import_feedback_press9 = require("@rific/feedback-press");
1285
1320
  var import_react_native18 = require("react-native");
1286
- var import_jsx_runtime17 = require("react/jsx-runtime");
1321
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1287
1322
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1288
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native18.View, { style: [styles14.column, popoverOpen && styles14.columnOpen], children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react_native18.View, { style: styles14.actionsRow, children: [
1290
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1291
- onRandomize && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1292
- onReset && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1293
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1323
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native18.View, { style: [styles14.column, popoverOpen && styles14.columnOpen], children: [
1324
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native18.View, { style: styles14.actionsRow, children: [
1325
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1326
+ onRandomize && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1327
+ 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" }),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_feedback_press9.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1294
1329
  ] }),
1295
1330
  children
1296
1331
  ] });
@@ -1314,14 +1349,14 @@ var styles14 = import_react_native18.StyleSheet.create({
1314
1349
  var import_auto_paper8 = require("@rific/auto-paper");
1315
1350
  var import_react_native19 = require("react-native");
1316
1351
  var import_react_native_paper10 = require("react-native-paper");
1317
- var import_jsx_runtime18 = require("react/jsx-runtime");
1352
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1318
1353
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1319
1354
  const { dark } = (0, import_auto_paper8.useAutoPaperTheme)();
1320
1355
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1321
1356
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
1322
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native19.View, { style: styles15.row, children: [
1323
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1324
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: [styles15.value, { color: fg }], children: value })
1357
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native19.View, { style: styles15.row, children: [
1358
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1359
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper10.Text, { variant: "bodyMedium", style: [styles15.value, { color: fg }], children: value })
1325
1360
  ] });
1326
1361
  }
1327
1362
  var styles15 = import_react_native19.StyleSheet.create({
@@ -1339,13 +1374,13 @@ var styles15 = import_react_native19.StyleSheet.create({
1339
1374
  var import_auto_paper9 = require("@rific/auto-paper");
1340
1375
  var import_react_native20 = require("react-native");
1341
1376
  var import_react_native_paper11 = require("react-native-paper");
1342
- var import_jsx_runtime19 = require("react/jsx-runtime");
1377
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1343
1378
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1344
1379
  const { dark } = (0, import_auto_paper9.useAutoPaperTheme)();
1345
1380
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1346
1381
  const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1347
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_native20.View, { style: [styles16.section, { backgroundColor: sectionBg }], children: [
1348
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native_paper11.Text, { variant: "labelMedium", style: [styles16.label, { color: fg }], children: label }),
1382
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native20.View, { style: [styles16.section, { backgroundColor: sectionBg }], children: [
1383
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native_paper11.Text, { variant: "labelMedium", style: [styles16.label, { color: fg }], children: label }),
1349
1384
  children
1350
1385
  ] });
1351
1386
  }
@@ -1395,6 +1430,7 @@ function usePopoverHost() {
1395
1430
  ReadyButton,
1396
1431
  SectionedDropdown,
1397
1432
  SharedActionBand,
1433
+ SkiaGate,
1398
1434
  StatRow,
1399
1435
  StatSection,
1400
1436
  TriggerGaugeHost,