@tastic/hud 0.21.0 → 0.23.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.
@@ -40,21 +40,29 @@ __export(chrome_exports, {
40
40
  ContentGutter: () => ContentGutter,
41
41
  CornerActionButtons: () => CornerActionButtons,
42
42
  DialogHeader: () => DialogHeader,
43
+ InlineColorPicker: () => InlineColorPicker,
43
44
  LOCKED_BADGE_COLOR: () => LOCKED_BADGE_COLOR,
44
45
  MONO_FONT: () => MONO_FONT,
45
46
  OverlayBackdrop: () => OverlayBackdrop,
46
47
  OverlayCard: () => OverlayCard,
48
+ PopoverBody: () => PopoverBody,
49
+ PressAwayOverlay: () => PressAwayOverlay,
47
50
  RotatedFrame: () => RotatedFrame,
48
51
  StaggeredWord: () => StaggeredWord,
49
52
  StatRow: () => StatRow,
50
53
  StatSection: () => StatSection,
51
54
  UpdateDialog: () => UpdateDialog,
55
+ WELL_SHADOW: () => WELL_SHADOW,
56
+ getInlineColorPickerContentSize: () => getInlineColorPickerContentSize,
52
57
  getStaggeredWordDuration: () => getStaggeredWordDuration,
53
58
  isCompactDialogHeight: () => isCompactDialogHeight,
54
59
  overlayActionStyles: () => overlayActionStyles,
55
60
  overlayStyles: () => overlayStyles,
61
+ useAutoAlign: () => useAutoAlign,
56
62
  useCompactDialog: () => useCompactDialog,
57
- useOverlayCardColors: () => useOverlayCardColors
63
+ useOverlayCardColors: () => useOverlayCardColors,
64
+ usePopoverHost: () => usePopoverHost,
65
+ useWellColors: () => useWellColors
58
66
  });
59
67
  module.exports = __toCommonJS(chrome_exports);
60
68
 
@@ -65,16 +73,25 @@ var import_react_native3 = require("react-native");
65
73
  var import_react_native_paper2 = require("react-native-paper");
66
74
 
67
75
  // src/AchievementRow.tsx
68
- var import_auto_paper = require("@rific/auto-paper");
69
76
  var import_react_native = require("react-native");
70
77
  var import_react_native_paper = require("react-native-paper");
78
+
79
+ // src/wellSurface.ts
80
+ var import_auto_paper = require("@rific/auto-paper");
81
+ var WELL_SHADOW = "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)";
82
+ function useWellColors() {
83
+ const { colors } = (0, import_auto_paper.useAutoPaperTheme)();
84
+ return { bg: colors.surface, fg: colors.onSurface, fgMuted: colors.onSurfaceVariant };
85
+ }
86
+
87
+ // src/AchievementRow.tsx
71
88
  var import_jsx_runtime = require("react/jsx-runtime");
72
89
  var LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
73
90
  function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }) {
74
- const { dark } = (0, import_auto_paper.useAutoPaperTheme)();
75
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
76
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
77
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
91
+ const well = useWellColors();
92
+ const fg = fgOverride ?? well.fg;
93
+ const fgMuted = fgMutedOverride ?? well.fgMuted;
94
+ const sectionBg = sectionBgOverride ?? well.bg;
78
95
  const unlocked = unlockedLabel !== void 0;
79
96
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.row, { backgroundColor: sectionBg }], children: [
80
97
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.badge, { backgroundColor: badgeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: 20, color: unlocked ? "#000000" : fgMuted }) }),
@@ -117,6 +134,7 @@ var styles = import_react_native.StyleSheet.create({
117
134
  row: {
118
135
  alignItems: "center",
119
136
  borderRadius: 12,
137
+ boxShadow: WELL_SHADOW,
120
138
  flexDirection: "row",
121
139
  gap: 12,
122
140
  padding: 12
@@ -138,13 +156,13 @@ var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "m
138
156
 
139
157
  // src/AchievementCatalogSection.tsx
140
158
  var import_jsx_runtime2 = require("react/jsx-runtime");
141
- function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
159
+ function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride, sectionBg }) {
142
160
  const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
143
161
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
144
162
  const tierAccent = (tierColor) => dark ? tierColor : (0, import_auto_paper2.getBlendedColor)("#000000", tierColor, 0.5);
145
163
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react.Fragment, { children: [
146
164
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
147
- 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: tierAccent(row.tierColor), unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride }, row.id))
165
+ 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: tierAccent(row.tierColor), unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride, sectionBg }, row.id))
148
166
  ] });
149
167
  }
150
168
  var styles2 = import_react_native3.StyleSheet.create({
@@ -155,14 +173,13 @@ var styles2 = import_react_native3.StyleSheet.create({
155
173
  });
156
174
 
157
175
  // src/StatRow.tsx
158
- var import_auto_paper3 = require("@rific/auto-paper");
159
176
  var import_react_native4 = require("react-native");
160
177
  var import_react_native_paper3 = require("react-native-paper");
161
178
  var import_jsx_runtime3 = require("react/jsx-runtime");
162
179
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
163
- const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
164
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
165
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
180
+ const well = useWellColors();
181
+ const fg = fgOverride ?? well.fg;
182
+ const fgMuted = fgMutedOverride ?? well.fgMuted;
166
183
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.row, children: [
167
184
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
168
185
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: [styles3.value, { color: fg }], children: value })
@@ -180,14 +197,13 @@ var styles3 = import_react_native4.StyleSheet.create({
180
197
  });
181
198
 
182
199
  // src/StatSection.tsx
183
- var import_auto_paper4 = require("@rific/auto-paper");
184
200
  var import_react_native5 = require("react-native");
185
201
  var import_react_native_paper4 = require("react-native-paper");
186
202
  var import_jsx_runtime4 = require("react/jsx-runtime");
187
203
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
188
- const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
189
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
190
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
204
+ const well = useWellColors();
205
+ const fg = fgOverride ?? well.fg;
206
+ const sectionBg = sectionBgOverride ?? well.bg;
191
207
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: [styles4.section, { backgroundColor: sectionBg }], children: [
192
208
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "labelMedium", style: [styles4.label, { color: fg }], children: label }),
193
209
  children
@@ -204,6 +220,7 @@ var styles4 = import_react_native5.StyleSheet.create({
204
220
  },
205
221
  section: {
206
222
  borderRadius: 12,
223
+ boxShadow: WELL_SHADOW,
207
224
  gap: 8,
208
225
  padding: 16
209
226
  }
@@ -211,26 +228,27 @@ var styles4 = import_react_native5.StyleSheet.create({
211
228
 
212
229
  // src/ActivityStatSection.tsx
213
230
  var import_jsx_runtime5 = require("react/jsx-runtime");
214
- function ActivityStatSection({ stats }) {
215
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StatSection, { label: "ACTIVITY", children: [
216
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
217
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
218
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
231
+ function ActivityStatSection({ stats, fg, fgMuted, sectionBg }) {
232
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StatSection, { label: "ACTIVITY", fg, sectionBg, children: [
233
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed), fg, fgMuted }),
234
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak), fg, fgMuted }),
235
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak), fg, fgMuted })
219
236
  ] });
220
237
  }
221
238
 
222
239
  // src/BaseSettingsDialog.tsx
223
- var import_auto_paper8 = require("@rific/auto-paper");
240
+ var import_auto_paper6 = require("@rific/auto-paper");
241
+ var import_core4 = require("@rific/core");
224
242
  var import_feedback_press2 = require("@rific/feedback-press");
225
243
  var import_updater = require("@rific/updater");
226
- var import_core4 = require("@tastic/core");
244
+ var import_core5 = require("@tastic/core");
227
245
  var import_react6 = require("react");
228
246
  var import_react_native12 = require("react-native");
229
247
  var import_react_native_paper8 = require("react-native-paper");
230
248
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
231
249
 
232
250
  // src/ConfirmDialog.tsx
233
- var import_auto_paper7 = require("@rific/auto-paper");
251
+ var import_auto_paper5 = require("@rific/auto-paper");
234
252
  var import_feedback_press = require("@rific/feedback-press");
235
253
  var import_core2 = require("@tastic/core");
236
254
  var import_react3 = require("react");
@@ -238,7 +256,7 @@ var import_react_native10 = require("react-native");
238
256
  var import_react_native_paper6 = require("react-native-paper");
239
257
 
240
258
  // src/DialogHeader.tsx
241
- var import_auto_paper5 = require("@rific/auto-paper");
259
+ var import_auto_paper3 = require("@rific/auto-paper");
242
260
  var import_react_native6 = require("react-native");
243
261
  var import_react_native_paper5 = require("react-native-paper");
244
262
  var import_jsx_runtime6 = require("react/jsx-runtime");
@@ -247,7 +265,7 @@ function isCompactDialogHeight(height) {
247
265
  return height < COMPACT_DIALOG_HEIGHT;
248
266
  }
249
267
  function DialogHeader({ icon, title, color, compact = false, style }) {
250
- const { colors } = (0, import_auto_paper5.useAutoPaperTheme)();
268
+ const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
251
269
  const accent = color ?? colors.primary;
252
270
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [compact ? styles5.compact : styles5.full, style], children: [
253
271
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Icon, { source: icon, size: compact ? COMPACT_ICON_SIZE : ICON_SIZE, color: accent }),
@@ -344,11 +362,11 @@ var overlayActionStyles = import_react_native7.StyleSheet.create({
344
362
  });
345
363
 
346
364
  // src/OverlaySurface.tsx
347
- var import_auto_paper6 = require("@rific/auto-paper");
365
+ var import_auto_paper4 = require("@rific/auto-paper");
348
366
  var import_react_native8 = require("react-native");
349
367
  var import_jsx_runtime7 = require("react/jsx-runtime");
350
368
  function useOverlayCardColors() {
351
- const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
369
+ const { colors } = (0, import_auto_paper4.useAutoPaperTheme)();
352
370
  return { fg: colors.onSurface, fgMuted: colors.onSurfaceVariant, cardBorder: colors.outlineVariant, backdrop: colors.backdrop };
353
371
  }
354
372
  function OverlayBackdrop({ children, style, onLayout }) {
@@ -356,9 +374,9 @@ function OverlayBackdrop({ children, style, onLayout }) {
356
374
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { onLayout, style: [overlayStyles.overlay, { backgroundColor: backdrop }, style], children });
357
375
  }
358
376
  function OverlayCard({ children, style, ...rest }) {
359
- const { isV3, roundness } = (0, import_auto_paper6.useAutoPaperTheme)();
360
- const blur = (0, import_auto_paper6.useBlur)();
361
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper6.BlurView, { blur, style: [overlayStyles.card, styles6.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
377
+ const { isV3, roundness } = (0, import_auto_paper4.useAutoPaperTheme)();
378
+ const blur = (0, import_auto_paper4.useBlur)();
379
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper4.BlurView, { blur, style: [overlayStyles.card, styles6.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
362
380
  }
363
381
  var styles6 = import_react_native8.StyleSheet.create({
364
382
  card: { overflow: "hidden" }
@@ -384,7 +402,7 @@ function useCompactDialog(rotation = 0) {
384
402
  // src/ConfirmDialog.tsx
385
403
  var import_jsx_runtime8 = require("react/jsx-runtime");
386
404
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
387
- const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
405
+ const { colors } = (0, import_auto_paper5.useAutoPaperTheme)();
388
406
  const { fg } = useOverlayCardColors();
389
407
  const { compact, onLayout } = useCompactDialog(rotation);
390
408
  const onCancelRef = (0, import_react3.useRef)(onCancel);
@@ -488,22 +506,24 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
488
506
  function SettingIcon({ source, color, containerColor }) {
489
507
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: [styles9.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Icon, { source, size: 18, color }) });
490
508
  }
491
- function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, keepAwake, onKeepAwakeChange, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
492
- const { colors } = (0, import_auto_paper8.useAutoPaperTheme)();
493
- const ambientRotation = (0, import_core4.useRotation)();
509
+ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
510
+ const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
511
+ const ambientRotation = (0, import_core5.useRotation)();
494
512
  const rotation = rotationOverride ?? ambientRotation;
495
513
  const window = (0, import_react_native12.useWindowDimensions)();
496
514
  const turned = rotation === 90 || rotation === -90;
497
515
  const compactHeader = isCompactDialogHeight(turned ? window.width : window.height);
498
- const insets = (0, import_core4.rotateInsets)((0, import_react_native_safe_area_context2.useSafeAreaInsets)(), rotation);
516
+ const insets = (0, import_core5.rotateInsets)((0, import_react_native_safe_area_context2.useSafeAreaInsets)(), rotation);
499
517
  const sheetWidth = Math.min(overlayStyles.card.maxWidth, (turned ? window.height : window.width) - 2 * Math.max(insets.left, insets.right, SHEET_GUTTER));
500
- const isTouchPrimary = (0, import_core4.useIsTouchPrimaryDevice)();
518
+ const isTouchPrimary = (0, import_core5.useIsTouchPrimaryDevice)();
501
519
  const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
502
520
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
521
+ const { settings: keepAwakeSettings, set: setKeepAwakeSettings } = (0, import_core4.useKeepAwakeSettings)();
522
+ const expoKeepAwake = (0, import_core4.useExpoKeepAwake)();
503
523
  const sound = (0, import_react6.useContext)(import_feedback_press2.SoundContext);
504
524
  const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
505
- const { settings: themeSettings, set: setThemeSettings } = (0, import_auto_paper8.useThemeSettings)();
506
- const expoBlur = (0, import_auto_paper8.useBlurModule)();
525
+ const { settings: themeSettings, set: setThemeSettings } = (0, import_auto_paper6.useThemeSettings)();
526
+ const expoBlur = (0, import_auto_paper6.useBlurModule)();
507
527
  const [infoMessage, setInfoMessage] = (0, import_react6.useState)(null);
508
528
  const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
509
529
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
@@ -518,7 +538,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
518
538
  if (visible && !opened) setOpened(true);
519
539
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
520
540
  const showEdgeGuard = import_react_native12.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
521
- const showKeepAwake = isTouchPrimary && keepAwake !== void 0 && onKeepAwakeChange !== void 0;
541
+ const showKeepAwake = isTouchPrimary && expoKeepAwake !== void 0;
522
542
  const showHaptics = import_react_native12.Platform.OS !== "web" && !hideHaptics;
523
543
  const showSound = !hideSound;
524
544
  const showAppearance = !hideAppearance;
@@ -526,8 +546,8 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
526
546
  const showUpdateCheck = import_react_native12.Platform.OS !== "web" && !hideUpdateCheck;
527
547
  if (!opened) return null;
528
548
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(RotatedFrame, { rotation, rotatedSafeArea: true, portalHost: true, passThrough: true, children: [
529
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_auto_paper8.Dialog, { visible, onDismiss, style: [styles9.dialog, { width: sheetWidth }], children: [
530
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_auto_paper8.Dialog.ScrollArea, { style: styles9.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_auto_paper8.FadingScrollView, { contentContainerStyle: styles9.scrollContent, showsVerticalScrollIndicator: false, children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_auto_paper6.Dialog, { visible, onDismiss, style: [styles9.dialog, { width: sheetWidth }], children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_auto_paper6.Dialog.ScrollArea, { style: styles9.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_auto_paper6.FadingScrollView, { contentContainerStyle: styles9.scrollContent, showsVerticalScrollIndicator: false, children: [
531
551
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DialogHeader, { icon: "cog", title: "Settings", compact: compactHeader }),
532
552
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.content, children: [
533
553
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.toggleGroup, children: [
@@ -545,8 +565,8 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
545
565
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Require second swipe" })
546
566
  ] })
547
567
  ] }) }),
548
- showKeepAwake && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onKeepAwakeChange(!keepAwake), style: styles9.toggleButton, accessibilityLabel: `Keep awake ${keepAwake ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.toggleContent, children: [
549
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SettingIcon, { source: keepAwake ? "eye" : "eye-closed", color: keepAwake ? colors.tertiary : colors.onSurfaceVariant, containerColor: keepAwake ? colors.tertiaryContainer : colors.surfaceVariant }),
568
+ showKeepAwake && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => setKeepAwakeSettings({ enabled: !keepAwakeSettings.enabled }), style: styles9.toggleButton, accessibilityLabel: `Keep awake ${keepAwakeSettings.enabled ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.toggleContent, children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SettingIcon, { source: keepAwakeSettings.enabled ? "eye" : "eye-closed", color: keepAwakeSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: keepAwakeSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
550
570
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.flexShrink, children: [
551
571
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Keep Awake" }),
552
572
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Stop auto-lock mid-match" })
@@ -609,7 +629,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
609
629
  ] }),
610
630
  showAppearance && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.section, children: [
611
631
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper8.Text, { variant: "labelMedium", style: [styles9.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
612
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_auto_paper8.AutoAppearancePicker, { showLabels: false }),
632
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_auto_paper6.AutoAppearancePicker, { showLabels: false }),
613
633
  showBlur && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => setThemeSettings({ blur: !themeSettings.blur }), style: styles9.toggleButton, accessibilityLabel: `Blur ${themeSettings.blur ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.toggleContent, children: [
614
634
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SettingIcon, { source: themeSettings.blur ? "blur" : "blur-off", color: themeSettings.blur ? colors.tertiary : colors.onSurfaceVariant, containerColor: themeSettings.blur ? colors.tertiaryContainer : colors.surfaceVariant }),
615
635
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native12.View, { style: styles9.flexShrink, children: [
@@ -720,16 +740,16 @@ var styles9 = import_react_native12.StyleSheet.create({
720
740
  });
721
741
 
722
742
  // src/BaseStatsScreen.tsx
723
- var import_auto_paper9 = require("@rific/auto-paper");
743
+ var import_auto_paper7 = require("@rific/auto-paper");
724
744
  var import_feedback_press3 = require("@rific/feedback-press");
725
- var import_core5 = require("@tastic/core");
745
+ var import_core6 = require("@tastic/core");
726
746
  var import_react7 = require("react");
727
747
  var import_react_native13 = require("react-native");
728
748
  var import_react_native_paper9 = require("react-native-paper");
729
749
  var import_jsx_runtime11 = require("react/jsx-runtime");
730
750
  function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, backLabel = "Back", hitSlop, 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 }) {
731
- const { dark, colors } = (0, import_auto_paper9.useAutoPaperTheme)();
732
- const ambientRotation = (0, import_core5.useRotation)();
751
+ const { dark, colors } = (0, import_auto_paper7.useAutoPaperTheme)();
752
+ const ambientRotation = (0, import_core6.useRotation)();
733
753
  const rotation = rotationOverride ?? ambientRotation;
734
754
  const [confirmResetVisible, setConfirmResetVisible] = (0, import_react7.useState)(false);
735
755
  const showReset = !!onReset;
@@ -740,7 +760,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
740
760
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
741
761
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper9.Text, { variant: titleVariant, style: [styles10.title, { color: fg }], children: title })
742
762
  ] }),
743
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper9.FadingScrollView, { style: styles10.scrollView, contentContainerStyle: [styles10.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper7.FadingScrollView, { style: styles10.scrollView, contentContainerStyle: [styles10.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
744
764
  children,
745
765
  showReset && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles10.resetButton, children: resetLabel })
746
766
  ] }),
@@ -795,12 +815,12 @@ var styles10 = import_react_native13.StyleSheet.create({
795
815
  });
796
816
 
797
817
  // src/ContentGutter.tsx
798
- var import_core6 = require("@tastic/core");
818
+ var import_core7 = require("@tastic/core");
799
819
  var import_react_native14 = require("react-native");
800
820
  var import_jsx_runtime12 = require("react/jsx-runtime");
801
821
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
802
- const { width: windowWidth } = (0, import_core6.useRotatedWindowDimensions)();
803
- const { contentWidth, gutterWidth } = (0, import_core6.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
822
+ const { width: windowWidth } = (0, import_core7.useRotatedWindowDimensions)();
823
+ const { contentWidth, gutterWidth } = (0, import_core7.computeContentBounds)(windowWidth, maxContentWidth ?? Infinity);
804
824
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native14.View, { style: [styles11.row, style], children: [
805
825
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native14.View, { style: [styles11.gutter, { width: gutterWidth }], children: leftGutter }),
806
826
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native14.View, { style: { width: contentWidth }, children }),
@@ -837,11 +857,272 @@ var styles12 = import_react_native15.StyleSheet.create({
837
857
  }
838
858
  });
839
859
 
840
- // src/StaggeredWord.tsx
841
- var import_react8 = require("react");
860
+ // src/InlineColorPicker.tsx
861
+ var import_auto_paper8 = require("@rific/auto-paper");
862
+ var import_feedback_press5 = require("@rific/feedback-press");
863
+ var import_core8 = require("@tastic/core");
864
+ var import_react_native18 = require("react-native");
865
+ var import_react_native_paper10 = require("react-native-paper");
866
+
867
+ // src/PopoverBody.tsx
842
868
  var import_react_native16 = require("react-native");
843
- var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
844
869
  var import_jsx_runtime14 = require("react/jsx-runtime");
870
+ var CARET_SIZE = 7;
871
+ var CARET_RING_OFFSET = 2;
872
+ var CARET_DIP = 1;
873
+ function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
874
+ if (!visible) return null;
875
+ const above = verticalAlign === "above";
876
+ const alignStyle = align === "left" ? styles13.contentLeft : align === "right" ? styles13.contentRight : styles13.contentCenter;
877
+ const verticalStyle = above ? styles13.contentAbove : styles13.contentBelow;
878
+ const ringSize = CARET_SIZE + caretBorderWidth;
879
+ const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
880
+ const caretFill = above ? "borderTopColor" : "borderBottomColor";
881
+ const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
882
+ const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
883
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native16.View, { style: [styles13.content, alignStyle, verticalStyle], children: [
884
+ children,
885
+ caretColor && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
886
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native16.View, { style: [styles13.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
887
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native16.View, { style: [styles13.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
888
+ ] })
889
+ ] });
890
+ }
891
+ var styles13 = import_react_native16.StyleSheet.create({
892
+ caret: {
893
+ borderLeftColor: "transparent",
894
+ borderRightColor: "transparent",
895
+ height: 0,
896
+ position: "absolute",
897
+ width: 0,
898
+ // Explicit, not left to DOM order - the box it dips into is itself a position:'relative' view,
899
+ // which React Native Web always gives an implicit zIndex:0 (see PopoverBody's stacking-context
900
+ // notes elsewhere in this codebase), so painting on top needs a real zIndex to beat that
901
+ // reliably rather than relying on being the later sibling.
902
+ zIndex: 1
903
+ },
904
+ content: {
905
+ position: "absolute",
906
+ zIndex: 50
907
+ },
908
+ contentAbove: {
909
+ bottom: "100%",
910
+ marginBottom: 8
911
+ },
912
+ contentBelow: {
913
+ marginTop: 8,
914
+ top: "100%"
915
+ },
916
+ // Stretched to the anchor's own (trigger) width, with alignItems centering the actual (usually
917
+ // much wider) popover box inside via plain flexbox - not `left:'50%'` plus a percentage
918
+ // `transform`, which depends on native resolving the box's own auto width before applying the
919
+ // transform and has proven unreliable there, visibly shifting the box off-center.
920
+ contentCenter: {
921
+ alignItems: "center",
922
+ left: 0,
923
+ right: 0
924
+ },
925
+ contentLeft: {
926
+ left: 0
927
+ },
928
+ contentRight: {
929
+ right: 0
930
+ }
931
+ });
932
+
933
+ // src/useAutoAlign.ts
934
+ var import_react8 = require("react");
935
+ var import_react_native17 = require("react-native");
936
+ var EDGE_MARGIN = 12;
937
+ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
938
+ if (rotation === 0) return { x, y, width, height };
939
+ const cx = windowWidth / 2;
940
+ const cy = windowHeight / 2;
941
+ const dx = x + width / 2 - cx;
942
+ const dy = y + height / 2 - cy;
943
+ const [newDx, newDy, newWidth, newHeight] = rotation === 180 ? [-dx, -dy, width, height] : rotation === 90 ? [-dy, dx, height, width] : [dy, -dx, height, width];
944
+ return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
945
+ }
946
+ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
947
+ const triggerRef = (0, import_react8.useRef)(null);
948
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native17.useWindowDimensions)();
949
+ const [align, setAlign] = (0, import_react8.useState)("center");
950
+ const [verticalAlign, setVerticalAlign] = (0, import_react8.useState)("below");
951
+ const [maxHeight, setMaxHeight] = (0, import_react8.useState)(contentHeight);
952
+ const [measured, setMeasured] = (0, import_react8.useState)(false);
953
+ const [prevOpen, setPrevOpen] = (0, import_react8.useState)(open);
954
+ if (open !== prevOpen) {
955
+ setPrevOpen(open);
956
+ if (open) setMeasured(false);
957
+ }
958
+ const measure = (0, import_react8.useCallback)(() => {
959
+ triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
960
+ const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
961
+ const centerX = x + triggerWidth / 2;
962
+ const overflowsRight = centerX + contentWidth / 2 > windowWidth - EDGE_MARGIN;
963
+ const overflowsLeft = centerX - contentWidth / 2 < EDGE_MARGIN;
964
+ if (overflowsRight && !overflowsLeft) setAlign("right");
965
+ else if (overflowsLeft && !overflowsRight) setAlign("left");
966
+ else setAlign("center");
967
+ const roomBelow = windowHeight - (y + triggerHeight) - EDGE_MARGIN;
968
+ const roomAbove = y - EDGE_MARGIN;
969
+ const above = contentHeight > roomBelow && roomAbove > roomBelow;
970
+ setVerticalAlign(above ? "above" : "below");
971
+ setMaxHeight(Math.max(above ? roomAbove : roomBelow, 0));
972
+ setMeasured(true);
973
+ });
974
+ }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
975
+ (0, import_react8.useEffect)(() => {
976
+ if (open) measure();
977
+ }, [open, measure]);
978
+ return { align, maxHeight, measured, triggerRef, verticalAlign };
979
+ }
980
+
981
+ // src/InlineColorPicker.tsx
982
+ var import_jsx_runtime15 = require("react/jsx-runtime");
983
+ var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
984
+ var DEFAULT_SIZE = 48;
985
+ var SWATCH_SIZE = 28;
986
+ var SWATCHES_PADDING = 8;
987
+ var SWATCHES_GAP = 6;
988
+ var SWATCHES_BORDER_WIDTH = 2;
989
+ var MIN_COLUMNS = 4;
990
+ var MAX_COLUMNS = 5;
991
+ var SCREEN_MARGIN = 40;
992
+ function widthForColumns(columns) {
993
+ return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
994
+ }
995
+ function getInlineColorPickerContentSize(swatchCount, windowWidth, columns) {
996
+ const autoColumns = (0, import_core8.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
997
+ const resolvedColumns = columns ?? autoColumns;
998
+ const width = widthForColumns(resolvedColumns);
999
+ const rows = Math.ceil(swatchCount / resolvedColumns);
1000
+ const height = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * rows + SWATCHES_GAP * (rows - 1);
1001
+ return { width, height };
1002
+ }
1003
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper8.defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1004
+ const ambientRotation = (0, import_core8.useRotation)();
1005
+ const rotation = rotationOverride ?? ambientRotation;
1006
+ const menuBg = dark ? "#000000" : "#FFFFFF";
1007
+ const { width: windowWidth } = (0, import_react_native18.useWindowDimensions)();
1008
+ const { width: swatchesWidth, height: swatchesHeight } = getInlineColorPickerContentSize(swatches.length, windowWidth, columns);
1009
+ const open = host.openId === id;
1010
+ const auto = useAutoAlign(open, swatchesWidth, swatchesHeight, rotation);
1011
+ const placement = alignOverride ?? auto;
1012
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
1013
+ const align = forcedAlign ?? placement.align;
1014
+ const displayFor = previewValue ?? ((hex) => hex);
1015
+ const displayValue = displayFor(value);
1016
+ const contrastColor = (0, import_auto_paper8.getContrastColor)(displayValue);
1017
+ const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native18.View, { style: [styles14.anchor, open && styles14.anchorOpen], children: [
1019
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_feedback_press5.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles14.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native18.View, { ref: triggerRef, collapsable: false, style: styles14.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime15.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_runtime15.jsx)(import_react_native_paper10.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
1020
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native18.ScrollView, { style: [styles14.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles14.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
1021
+ const selected = swatch.value.toLowerCase() === value.toLowerCase();
1022
+ const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
1023
+ const swappable = taken && allowSwapTaken;
1024
+ const swatchDisplay = displayFor(swatch.value);
1025
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1026
+ import_feedback_press5.TouchableRipple,
1027
+ {
1028
+ disabled: taken && !swappable,
1029
+ onPress: () => {
1030
+ onChange(swatch.value);
1031
+ if (autoDismiss) host.close();
1032
+ },
1033
+ borderless: true,
1034
+ style: [styles14.swatch, { backgroundColor: swatchDisplay }, selected && styles14.swatchSelected, taken && !swappable && styles14.swatchTaken],
1035
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper10.Icon, { source: "check", size: 16, color: (0, import_auto_paper8.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper10.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper8.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper10.Icon, { source: "close", size: 16, color: (0, import_auto_paper8.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native18.View, {})
1036
+ },
1037
+ swatch.value
1038
+ );
1039
+ }) }) })
1040
+ ] });
1041
+ }
1042
+ var styles14 = import_react_native18.StyleSheet.create({
1043
+ anchor: {
1044
+ position: "relative"
1045
+ },
1046
+ // See SectionedDropdown's identical comment - React Native Web gives every position:'relative' view
1047
+ // its own stacking context, so the elevation has to live on the anchor itself, not just the
1048
+ // popover content nested inside it, to correctly paint above this anchor's own later siblings.
1049
+ anchorOpen: {
1050
+ zIndex: 100
1051
+ },
1052
+ swatch: {
1053
+ alignItems: "center",
1054
+ borderRadius: SWATCH_SIZE / 2,
1055
+ height: SWATCH_SIZE,
1056
+ justifyContent: "center",
1057
+ width: SWATCH_SIZE
1058
+ },
1059
+ swatchSelected: {
1060
+ borderColor: "#ffffff",
1061
+ borderWidth: 2
1062
+ },
1063
+ swatchTaken: {
1064
+ opacity: 0.35
1065
+ },
1066
+ swatches: {
1067
+ borderRadius: 12,
1068
+ borderWidth: SWATCHES_BORDER_WIDTH,
1069
+ elevation: 8,
1070
+ shadowColor: "#000000",
1071
+ shadowOffset: { height: 2, width: 0 },
1072
+ shadowOpacity: 0.3,
1073
+ shadowRadius: 8
1074
+ // width is set inline above - border-box sizing means it has to include the border too
1075
+ // (2*SWATCHES_BORDER_WIDTH), not just padding+content, or exactly enough room goes missing that
1076
+ // the last column silently wraps to a new row. Anything wider than the exact sum just shows as
1077
+ // dead space on the right edge of each row instead.
1078
+ },
1079
+ // Separate from `swatches` itself - see SectionedDropdown's identical menu/menuContent split.
1080
+ // The scroll frame's own bounds (including maxHeight) live on the ScrollView; the actual grid
1081
+ // wrapping belongs to its contentContainerStyle, which sizes to the unclamped content instead.
1082
+ swatchesContent: {
1083
+ flexDirection: "row",
1084
+ flexWrap: "wrap",
1085
+ gap: SWATCHES_GAP,
1086
+ padding: SWATCHES_PADDING
1087
+ },
1088
+ tagLabel: {
1089
+ fontWeight: "700",
1090
+ letterSpacing: 0.5
1091
+ },
1092
+ // borderRadius/height/width come from the `size` prop instead (see render-site override) - no
1093
+ // fixed default here, since this style object is shared by every caller regardless of size.
1094
+ trigger: {
1095
+ alignItems: "center",
1096
+ alignSelf: "flex-start",
1097
+ justifyContent: "center"
1098
+ },
1099
+ // Wraps just the icon/tag, inside the TouchableRipple, purely so useAutoAlign has a plain View
1100
+ // to attach its measurement ref to - TouchableRipple itself doesn't forward a ref to a
1101
+ // measurable native node. Padded horizontally so a multi-character tag doesn't butt right up
1102
+ // against the trigger's own circular edge (an icon alone doesn't need it, but this style is
1103
+ // shared by both).
1104
+ triggerMeasure: {
1105
+ alignItems: "center",
1106
+ height: "100%",
1107
+ justifyContent: "center",
1108
+ paddingHorizontal: 4,
1109
+ width: "100%"
1110
+ }
1111
+ });
1112
+
1113
+ // src/PressAwayOverlay.tsx
1114
+ var import_react_native19 = require("react-native");
1115
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1116
+ function PressAwayOverlay({ active, onPress, style }) {
1117
+ if (!active) return null;
1118
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native19.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native19.StyleSheet.absoluteFill, style] });
1119
+ }
1120
+
1121
+ // src/StaggeredWord.tsx
1122
+ var import_react9 = require("react");
1123
+ var import_react_native20 = require("react-native");
1124
+ var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
1125
+ var import_jsx_runtime17 = require("react/jsx-runtime");
845
1126
  var STAGGER_MS = 45;
846
1127
  var LETTER_DURATION_MS = 320;
847
1128
  function getStaggeredWordDuration(letterCount) {
@@ -849,7 +1130,7 @@ function getStaggeredWordDuration(letterCount) {
849
1130
  }
850
1131
  function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
851
1132
  const progress = (0, import_react_native_reanimated.useSharedValue)(reducedMotion ? 1 : 0);
852
- (0, import_react8.useEffect)(() => {
1133
+ (0, import_react9.useEffect)(() => {
853
1134
  if (reducedMotion) return;
854
1135
  progress.value = (0, import_react_native_reanimated.withDelay)(index * STAGGER_MS, (0, import_react_native_reanimated.withTiming)(1, { duration: LETTER_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.back(1.5)) }));
855
1136
  }, [reducedMotion]);
@@ -857,12 +1138,12 @@ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSiz
857
1138
  opacity: progress.value,
858
1139
  transform: [{ translateY: (1 - progress.value) * 12 }, { scale: 0.85 + progress.value * 0.15 }]
859
1140
  }));
860
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_reanimated.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
1141
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_reanimated.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
861
1142
  }
862
1143
  function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
863
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native16.View, { style: [styles13.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
1144
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native20.View, { style: [styles15.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
864
1145
  }
865
- var styles13 = import_react_native16.StyleSheet.create({
1146
+ var styles15 = import_react_native20.StyleSheet.create({
866
1147
  row: {
867
1148
  flexDirection: "row"
868
1149
  }
@@ -870,12 +1151,23 @@ var styles13 = import_react_native16.StyleSheet.create({
870
1151
 
871
1152
  // src/UpdateDialog.tsx
872
1153
  var import_updater2 = require("@rific/updater");
873
- var import_jsx_runtime15 = require("react/jsx-runtime");
1154
+ var import_jsx_runtime18 = require("react/jsx-runtime");
874
1155
  function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
875
1156
  const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
876
1157
  (0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
877
1158
  const date = manifest ? new Date(manifest.createdAt) : null;
878
- return /* @__PURE__ */ (0, import_jsx_runtime15.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) });
1159
+ return /* @__PURE__ */ (0, import_jsx_runtime18.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) });
1160
+ }
1161
+
1162
+ // src/usePopoverHost.ts
1163
+ var import_react10 = require("react");
1164
+ function usePopoverHost() {
1165
+ const [openId, setOpenId] = (0, import_react10.useState)(null);
1166
+ const toggle = (0, import_react10.useCallback)((id) => {
1167
+ setOpenId((prev) => prev === id ? null : id);
1168
+ }, []);
1169
+ const close = (0, import_react10.useCallback)(() => setOpenId(null), []);
1170
+ return { openId, toggle, close };
879
1171
  }
880
1172
  // Annotate the CommonJS export names for ESM import in node:
881
1173
  0 && (module.exports = {
@@ -889,19 +1181,27 @@ function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onErr
889
1181
  ContentGutter,
890
1182
  CornerActionButtons,
891
1183
  DialogHeader,
1184
+ InlineColorPicker,
892
1185
  LOCKED_BADGE_COLOR,
893
1186
  MONO_FONT,
894
1187
  OverlayBackdrop,
895
1188
  OverlayCard,
1189
+ PopoverBody,
1190
+ PressAwayOverlay,
896
1191
  RotatedFrame,
897
1192
  StaggeredWord,
898
1193
  StatRow,
899
1194
  StatSection,
900
1195
  UpdateDialog,
1196
+ WELL_SHADOW,
1197
+ getInlineColorPickerContentSize,
901
1198
  getStaggeredWordDuration,
902
1199
  isCompactDialogHeight,
903
1200
  overlayActionStyles,
904
1201
  overlayStyles,
1202
+ useAutoAlign,
905
1203
  useCompactDialog,
906
- useOverlayCardColors
1204
+ useOverlayCardColors,
1205
+ usePopoverHost,
1206
+ useWellColors
907
1207
  });