@tastic/hud 0.17.0 → 0.19.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.
@@ -14,25 +14,30 @@ import {
14
14
  StatRow,
15
15
  StatSection,
16
16
  UpdateDialog,
17
- getStaggeredWordDuration
18
- } from "../chunk-27FIA26F.mjs";
17
+ getStaggeredWordDuration,
18
+ useCompactDialog
19
+ } from "../chunk-HGJKNLMD.mjs";
19
20
  import {
21
+ COMPACT_DIALOG_HEIGHT,
22
+ DialogHeader,
20
23
  OverlayBackdrop,
21
24
  OverlayCard,
22
- getOverlayCardColors,
25
+ isCompactDialogHeight,
23
26
  overlayActionStyles,
24
27
  overlayStyles,
25
28
  useOverlayCardColors
26
- } from "../chunk-AX4GC5KU.mjs";
29
+ } from "../chunk-PUF6TABL.mjs";
27
30
  export {
28
31
  AchievementCatalogSection,
29
32
  AchievementRow,
30
33
  ActivityStatSection,
31
34
  BaseSettingsDialog,
32
35
  BaseStatsScreen,
36
+ COMPACT_DIALOG_HEIGHT,
33
37
  ConfirmDialog,
34
38
  ContentGutter,
35
39
  CornerActionButtons,
40
+ DialogHeader,
36
41
  LOCKED_BADGE_COLOR,
37
42
  MONO_FONT,
38
43
  OverlayBackdrop,
@@ -42,9 +47,10 @@ export {
42
47
  StatRow,
43
48
  StatSection,
44
49
  UpdateDialog,
45
- getOverlayCardColors,
46
50
  getStaggeredWordDuration,
51
+ isCompactDialogHeight,
47
52
  overlayActionStyles,
48
53
  overlayStyles,
54
+ useCompactDialog,
49
55
  useOverlayCardColors
50
56
  };
@@ -1,9 +1,12 @@
1
1
  import {
2
+ DialogHeader,
2
3
  OverlayBackdrop,
3
4
  OverlayCard,
5
+ isCompactDialogHeight,
4
6
  overlayActionStyles,
7
+ overlayStyles,
5
8
  useOverlayCardColors
6
- } from "./chunk-AX4GC5KU.mjs";
9
+ } from "./chunk-PUF6TABL.mjs";
7
10
 
8
11
  // src/AchievementRow.tsx
9
12
  import { useAutoPaperTheme } from "@rific/auto-paper";
@@ -163,23 +166,40 @@ function ActivityStatSection({ stats }) {
163
166
  ] });
164
167
  }
165
168
 
169
+ // src/useCompactDialog.ts
170
+ import { useRotatedWindowDimensions } from "@tastic/core";
171
+ import { useCallback, useState } from "react";
172
+ import { useWindowDimensions } from "react-native";
173
+ function useCompactDialog(rotation = 0) {
174
+ const window = useWindowDimensions();
175
+ const framed = useRotatedWindowDimensions();
176
+ const [measured, setMeasured] = useState(null);
177
+ const onLayout = useCallback((e) => {
178
+ const { width, height } = e.nativeEvent.layout;
179
+ setMeasured((prev) => prev && prev.width === width && prev.height === height ? prev : { width, height });
180
+ }, []);
181
+ const box = measured ?? (rotation === 0 ? framed : window);
182
+ const turned = rotation === 90 || rotation === -90;
183
+ return { compact: isCompactDialogHeight(turned ? box.width : box.height), onLayout };
184
+ }
185
+
166
186
  // src/ConfirmDialog.tsx
167
187
  import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
168
188
  import { Button } from "@rific/feedback-press";
169
189
  import { toRotationStyle } from "@tastic/core";
170
190
  import { StyleSheet as StyleSheet5, View as View4 } from "react-native";
171
- import { Icon as Icon2, Portal, Text as Text5 } from "react-native-paper";
191
+ import { Portal, Text as Text5 } from "react-native-paper";
172
192
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
173
193
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
174
194
  const { colors } = useAutoPaperTheme5();
175
195
  const { fg } = useOverlayCardColors();
196
+ const { compact, onLayout } = useCompactDialog(rotation);
176
197
  if (!visible) return null;
177
198
  const accent = destructive ? colors.danger : colors.primary;
178
199
  const onAccent = destructive ? colors.onDanger : colors.onPrimary;
179
200
  const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
180
- return /* @__PURE__ */ jsx6(Portal, { children: /* @__PURE__ */ jsx6(OverlayBackdrop, { style: styles5.overlay, children: /* @__PURE__ */ jsxs6(OverlayCard, { style: [styles5.card, toRotationStyle(rotation)], children: [
181
- /* @__PURE__ */ jsx6(Icon2, { source: icon, size: 64, color: accent }),
182
- /* @__PURE__ */ jsx6(Text5, { variant: "headlineLarge", style: [styles5.title, { color: accent }], children: title }),
201
+ return /* @__PURE__ */ jsx6(Portal, { children: /* @__PURE__ */ jsx6(OverlayBackdrop, { style: styles5.overlay, onLayout, children: /* @__PURE__ */ jsxs6(OverlayCard, { style: [styles5.card, toRotationStyle(rotation)], children: [
202
+ /* @__PURE__ */ jsx6(DialogHeader, { icon, title, color: accent, compact }),
183
203
  /* @__PURE__ */ jsx6(Text5, { variant: "bodyLarge", style: [styles5.body, { color: fg }], children: message }),
184
204
  /* @__PURE__ */ jsxs6(View4, { style: overlayActionStyles.actions, children: [
185
205
  showCancel && /* @__PURE__ */ jsx6(Button, { mode: "outlined", onPress: onCancel, style: overlayActionStyles.actionButton, children: cancelLabel }),
@@ -205,27 +225,20 @@ var styles5 = StyleSheet5.create({
205
225
  // screen narrower than maxWidth's own 360.
206
226
  overlay: {
207
227
  paddingHorizontal: 24
208
- },
209
- // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
210
- // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
211
- // line inside that block still defaults to left-aligned without this.
212
- title: {
213
- fontWeight: "bold",
214
- textAlign: "center"
215
228
  }
216
229
  });
217
230
 
218
231
  // src/RotatedFrame.tsx
219
232
  import { FakeLandscapeView, rotateDimensions, rotateInsets, useRotation } from "@tastic/core";
220
233
  import { useMemo } from "react";
221
- import { StyleSheet as StyleSheet6, useWindowDimensions, View as View5 } from "react-native";
234
+ import { StyleSheet as StyleSheet6, useWindowDimensions as useWindowDimensions2, View as View5 } from "react-native";
222
235
  import { Portal as Portal2 } from "react-native-paper";
223
236
  import { SafeAreaInsetsContext, useSafeAreaInsets } from "react-native-safe-area-context";
224
237
  import { jsx as jsx7 } from "react/jsx-runtime";
225
238
  function RotatedFrame({ children, rotation: rotationOverride, rotatedSafeArea = false, portalHost = false, passThrough = false, maxContentWidth }) {
226
239
  const ambientRotation = useRotation();
227
240
  const rotation = rotationOverride ?? ambientRotation;
228
- const window = useWindowDimensions();
241
+ const window = useWindowDimensions2();
229
242
  const { width, height } = rotateDimensions(window.width, window.height, rotation);
230
243
  const rawInsets = useSafeAreaInsets();
231
244
  const insets = useMemo(() => rotatedSafeArea ? rotateInsets(rawInsets, rotation) : rawInsets, [rotatedSafeArea, rawInsets, rotation]);
@@ -246,27 +259,28 @@ var styles6 = StyleSheet6.create({
246
259
  });
247
260
 
248
261
  // src/BaseSettingsDialog.tsx
249
- import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme6, useBlurModule, useThemeSettings } from "@rific/auto-paper";
262
+ import { AutoAppearancePicker, Dialog, FadingScrollView, useAutoPaperTheme as useAutoPaperTheme6, useBlurModule, useThemeSettings } from "@rific/auto-paper";
250
263
  import { Button as Button2, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
251
264
  import { useUpdater } from "@rific/updater";
252
- import { useIsTouchPrimaryDevice, useRotation as useRotation2 } from "@tastic/core";
253
- import { useContext, useState as useState2 } from "react";
254
- import { Platform as Platform2, ScrollView, StyleSheet as StyleSheet7, View as View6 } from "react-native";
255
- import { Icon as Icon3, Portal as Portal3, Text as Text6 } from "react-native-paper";
265
+ import { rotateInsets as rotateInsets2, useIsTouchPrimaryDevice, useRotation as useRotation2 } from "@tastic/core";
266
+ import { useContext, useState as useState3 } from "react";
267
+ import { Platform as Platform2, StyleSheet as StyleSheet7, useWindowDimensions as useWindowDimensions3, View as View6 } from "react-native";
268
+ import { Icon as Icon2, Portal as Portal3, Text as Text6 } from "react-native-paper";
269
+ import { useSafeAreaInsets as useSafeAreaInsets2 } from "react-native-safe-area-context";
256
270
 
257
271
  // src/useUpdateConfirmBridge.ts
258
- import { useCallback, useRef, useState } from "react";
272
+ import { useCallback as useCallback2, useRef, useState as useState2 } from "react";
259
273
  function useUpdateConfirmBridge() {
260
- const [manifest, setManifest] = useState(null);
274
+ const [manifest, setManifest] = useState2(null);
261
275
  const resolveRef = useRef(null);
262
- const onConfirm = useCallback(
276
+ const onConfirm = useCallback2(
263
277
  (next) => new Promise((resolve) => {
264
278
  resolveRef.current = resolve;
265
279
  setManifest(next);
266
280
  }),
267
281
  []
268
282
  );
269
- const respond = useCallback((confirmed) => {
283
+ const respond = useCallback2((confirmed) => {
270
284
  setManifest(null);
271
285
  resolveRef.current?.(confirmed);
272
286
  resolveRef.current = null;
@@ -277,12 +291,17 @@ function useUpdateConfirmBridge() {
277
291
  // src/BaseSettingsDialog.tsx
278
292
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
279
293
  function SettingIcon({ source, color, containerColor }) {
280
- return /* @__PURE__ */ jsx8(View6, { style: [styles7.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx8(Icon3, { source, size: 18, color }) });
294
+ return /* @__PURE__ */ jsx8(View6, { style: [styles7.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx8(Icon2, { source, size: 18, color }) });
281
295
  }
282
- function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
296
+ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, keepAwake, onKeepAwakeChange, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
283
297
  const { colors } = useAutoPaperTheme6();
284
298
  const ambientRotation = useRotation2();
285
299
  const rotation = rotationOverride ?? ambientRotation;
300
+ const window = useWindowDimensions3();
301
+ const turned = rotation === 90 || rotation === -90;
302
+ const compactHeader = isCompactDialogHeight(turned ? window.width : window.height);
303
+ const insets = rotateInsets2(useSafeAreaInsets2(), rotation);
304
+ const sheetWidth = Math.min(overlayStyles.card.maxWidth, (turned ? window.height : window.width) - 2 * Math.max(insets.left, insets.right, SHEET_GUTTER));
286
305
  const isTouchPrimary = useIsTouchPrimaryDevice();
287
306
  const { settings: hapticSettings, set: setHapticSettings } = useHapticSettings();
288
307
  const { settings: soundSettings, set: setSoundSettings } = useSoundSettings();
@@ -290,7 +309,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
290
309
  const { forceShort: forceHapticFeedback } = useVibration();
291
310
  const { settings: themeSettings, set: setThemeSettings } = useThemeSettings();
292
311
  const expoBlur = useBlurModule();
293
- const [infoMessage, setInfoMessage] = useState2(null);
312
+ const [infoMessage, setInfoMessage] = useState3(null);
294
313
  const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
295
314
  const { check, checking, updateReady } = useUpdater({
296
315
  autoCheck: false,
@@ -300,10 +319,11 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
300
319
  onInfo: (title, message) => setInfoMessage({ title, message })
301
320
  });
302
321
  const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
303
- const [opened, setOpened] = useState2(visible);
322
+ const [opened, setOpened] = useState3(visible);
304
323
  if (visible && !opened) setOpened(true);
305
324
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
306
325
  const showEdgeGuard = Platform2.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
326
+ const showKeepAwake = isTouchPrimary && keepAwake !== void 0 && onKeepAwakeChange !== void 0;
307
327
  const showHaptics = Platform2.OS !== "web" && !hideHaptics;
308
328
  const showSound = !hideSound;
309
329
  const showAppearance = !hideAppearance;
@@ -311,106 +331,120 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
311
331
  const showUpdateCheck = Platform2.OS !== "web" && !hideUpdateCheck;
312
332
  if (!opened) return null;
313
333
  return /* @__PURE__ */ jsx8(Portal3, { children: /* @__PURE__ */ jsxs7(RotatedFrame, { rotation, rotatedSafeArea: true, portalHost: true, passThrough: true, children: [
314
- /* @__PURE__ */ jsxs7(Dialog, { visible, onDismiss, style: styles7.dialog, children: [
315
- /* @__PURE__ */ jsx8(Dialog.Title, { children: "Settings" }),
316
- /* @__PURE__ */ jsx8(Dialog.ScrollArea, { style: styles7.scrollArea, children: /* @__PURE__ */ jsxs7(ScrollView, { contentContainerStyle: styles7.content, showsVerticalScrollIndicator: false, children: [
317
- /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleGroup, children: [
318
- showLockOrientation && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles7.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
319
- /* @__PURE__ */ jsx8(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.tertiary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.tertiaryContainer : colors.surfaceVariant }),
320
- /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
321
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
322
- /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
323
- ] })
324
- ] }) }),
325
- showEdgeGuard && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles7.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
326
- /* @__PURE__ */ jsx8(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.tertiary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.tertiaryContainer : colors.surfaceVariant }),
327
- /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
328
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
329
- /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
330
- ] })
331
- ] }) }),
332
- (showSound || showHaptics) && /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleRow, children: [
333
- showSound && /* @__PURE__ */ jsx8(
334
- TouchableRipple,
335
- {
336
- soundDisabled: true,
337
- onPress: () => {
338
- const enabled = !soundSettings.enabled;
339
- setSoundSettings({ enabled });
340
- if (enabled) sound.selection?.();
341
- },
342
- style: styles7.toggleButtonInRow,
343
- accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
344
- children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
345
- /* @__PURE__ */ jsx8(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
346
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
347
- ] })
348
- }
349
- ),
350
- showHaptics && /* @__PURE__ */ jsx8(
334
+ /* @__PURE__ */ jsxs7(Dialog, { visible, onDismiss, style: [styles7.dialog, { width: sheetWidth }], children: [
335
+ /* @__PURE__ */ jsx8(Dialog.ScrollArea, { style: styles7.scrollArea, children: /* @__PURE__ */ jsxs7(FadingScrollView, { contentContainerStyle: styles7.scrollContent, showsVerticalScrollIndicator: false, children: [
336
+ /* @__PURE__ */ jsx8(DialogHeader, { icon: "cog", title: "Settings", compact: compactHeader }),
337
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.content, children: [
338
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleGroup, children: [
339
+ showLockOrientation && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles7.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
340
+ /* @__PURE__ */ jsx8(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.tertiary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.tertiaryContainer : colors.surfaceVariant }),
341
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
342
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
343
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pin current layout" })
344
+ ] })
345
+ ] }) }),
346
+ showEdgeGuard && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles7.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
347
+ /* @__PURE__ */ jsx8(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.tertiary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.tertiaryContainer : colors.surfaceVariant }),
348
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
349
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
350
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Require second swipe" })
351
+ ] })
352
+ ] }) }),
353
+ showKeepAwake && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => onKeepAwakeChange(!keepAwake), style: styles7.toggleButton, accessibilityLabel: `Keep awake ${keepAwake ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
354
+ /* @__PURE__ */ jsx8(SettingIcon, { source: keepAwake ? "eye" : "eye-closed", color: keepAwake ? colors.tertiary : colors.onSurfaceVariant, containerColor: keepAwake ? colors.tertiaryContainer : colors.surfaceVariant }),
355
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
356
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Keep Awake" }),
357
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Stop auto-lock mid-match" })
358
+ ] })
359
+ ] }) }),
360
+ (showSound || showHaptics) && /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleRow, children: [
361
+ showSound && /* @__PURE__ */ jsx8(
362
+ TouchableRipple,
363
+ {
364
+ soundDisabled: true,
365
+ onPress: () => {
366
+ const enabled = !soundSettings.enabled;
367
+ setSoundSettings({ enabled });
368
+ if (enabled) sound.selection?.();
369
+ },
370
+ style: styles7.toggleButtonInRow,
371
+ accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
372
+ children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
373
+ /* @__PURE__ */ jsx8(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
374
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
375
+ ] })
376
+ }
377
+ ),
378
+ showHaptics && /* @__PURE__ */ jsx8(
379
+ TouchableRipple,
380
+ {
381
+ hapticDisabled: true,
382
+ onPress: () => {
383
+ const vibrate = !hapticSettings.vibrate;
384
+ setHapticSettings({ vibrate });
385
+ if (vibrate) forceHapticFeedback();
386
+ },
387
+ style: styles7.toggleButtonInRow,
388
+ accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
389
+ children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
390
+ /* @__PURE__ */ jsx8(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
391
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
392
+ ] })
393
+ }
394
+ )
395
+ ] }),
396
+ onShowHowToPlay && /* @__PURE__ */ jsx8(
351
397
  TouchableRipple,
352
398
  {
353
- hapticDisabled: true,
354
399
  onPress: () => {
355
- const vibrate = !hapticSettings.vibrate;
356
- setHapticSettings({ vibrate });
357
- if (vibrate) forceHapticFeedback();
400
+ onDismiss();
401
+ onShowHowToPlay();
358
402
  },
359
- style: styles7.toggleButtonInRow,
360
- accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
403
+ style: styles7.toggleButton,
404
+ accessibilityLabel: "How to Play",
361
405
  children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
362
- /* @__PURE__ */ jsx8(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
363
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
406
+ /* @__PURE__ */ jsx8(SettingIcon, { source: "help-circle-outline", color: colors.secondary, containerColor: colors.secondaryContainer }),
407
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
408
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "How to Play" }),
409
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Replay quick intro" })
410
+ ] })
364
411
  ] })
365
412
  }
366
413
  )
367
414
  ] }),
368
- onShowHowToPlay && /* @__PURE__ */ jsx8(
369
- TouchableRipple,
370
- {
371
- onPress: () => {
372
- onDismiss();
373
- onShowHowToPlay();
374
- },
375
- style: styles7.toggleButton,
376
- accessibilityLabel: "How to Play",
377
- children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
378
- /* @__PURE__ */ jsx8(SettingIcon, { source: "help-circle-outline", color: colors.secondary, containerColor: colors.secondaryContainer }),
379
- /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
380
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "How to Play" }),
381
- /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Replay the quick intro" })
382
- ] })
415
+ showAppearance && /* @__PURE__ */ jsxs7(View6, { style: styles7.section, children: [
416
+ /* @__PURE__ */ jsx8(Text6, { variant: "labelMedium", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
417
+ /* @__PURE__ */ jsx8(AutoAppearancePicker, { showLabels: false }),
418
+ showBlur && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => setThemeSettings({ blur: !themeSettings.blur }), style: styles7.toggleButton, accessibilityLabel: `Blur ${themeSettings.blur ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
419
+ /* @__PURE__ */ jsx8(SettingIcon, { source: themeSettings.blur ? "blur" : "blur-off", color: themeSettings.blur ? colors.tertiary : colors.onSurfaceVariant, containerColor: themeSettings.blur ? colors.tertiaryContainer : colors.surfaceVariant }),
420
+ /* @__PURE__ */ jsxs7(View6, { style: styles7.flexShrink, children: [
421
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Blur" }),
422
+ /* @__PURE__ */ jsx8(Text6, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Frost behind dialogs" })
383
423
  ] })
384
- }
385
- )
386
- ] }),
387
- showAppearance && /* @__PURE__ */ jsxs7(View6, { style: styles7.section, children: [
388
- /* @__PURE__ */ jsx8(Text6, { variant: "labelMedium", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
389
- /* @__PURE__ */ jsx8(AutoAppearancePicker, { showLabels: false }),
390
- showBlur && /* @__PURE__ */ jsx8(TouchableRipple, { onPress: () => setThemeSettings({ blur: !themeSettings.blur }), style: styles7.toggleButton, accessibilityLabel: `Blur ${themeSettings.blur ? "on" : "off"}`, children: /* @__PURE__ */ jsxs7(View6, { style: styles7.toggleContent, children: [
391
- /* @__PURE__ */ jsx8(SettingIcon, { source: themeSettings.blur ? "blur" : "blur-off", color: themeSettings.blur ? colors.tertiary : colors.onSurfaceVariant, containerColor: themeSettings.blur ? colors.tertiaryContainer : colors.surfaceVariant }),
392
- /* @__PURE__ */ jsx8(Text6, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Blur" })
393
- ] }) })
394
- ] }),
395
- children,
396
- showUpdateCheck && /* @__PURE__ */ jsxs7(View6, { style: styles7.section, children: [
397
- /* @__PURE__ */ jsxs7(Text6, { variant: "labelSmall", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: [
398
- "VERSION ",
399
- version,
400
- updateReady ? " \xB7 UPDATE READY" : ""
424
+ ] }) })
401
425
  ] }),
402
- /* @__PURE__ */ jsx8(Button2, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
426
+ children,
427
+ showUpdateCheck && /* @__PURE__ */ jsxs7(View6, { style: styles7.section, children: [
428
+ /* @__PURE__ */ jsxs7(Text6, { variant: "labelMedium", style: [styles7.sectionLabel, { color: colors.onSurfaceVariant }], children: [
429
+ "VERSION ",
430
+ version,
431
+ updateReady ? " \xB7 UPDATE READY" : ""
432
+ ] }),
433
+ /* @__PURE__ */ jsx8(Button2, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
434
+ ] })
403
435
  ] })
404
- ] }) })
436
+ ] }) }),
437
+ /* @__PURE__ */ jsx8(View6, { style: [overlayActionStyles.actions, styles7.footer], children: /* @__PURE__ */ jsx8(Button2, { mode: "contained", onPress: onDismiss, style: overlayActionStyles.actionButtonSingle, buttonColor: colors.primary, textColor: colors.onPrimary, children: "Done" }) })
405
438
  ] }),
406
439
  /* @__PURE__ */ jsx8(ConfirmDialog, { visible: !!updateManifest, title: "Update Available", message: updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation: 0, onConfirm: () => respondToUpdateConfirm(true), onCancel: () => respondToUpdateConfirm(false) }),
407
440
  /* @__PURE__ */ jsx8(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation: 0, onConfirm: () => setInfoMessage(null) })
408
441
  ] }) });
409
442
  }
443
+ var SHEET_GUTTER = 24;
410
444
  var styles7 = StyleSheet7.create({
445
+ // The rows' sections, 24pt apart.
411
446
  content: {
412
- gap: 24,
413
- paddingVertical: 20
447
+ gap: 24
414
448
  },
415
449
  // Caps the card so it never grows past the screen - without this the dialog just keeps
416
450
  // growing to fit its content and the overflow gets clipped by the screen edge, which is
@@ -422,6 +456,11 @@ var styles7 = StyleSheet7.create({
422
456
  flexShrink: {
423
457
  flexShrink: 1
424
458
  },
459
+ // The overlay cards' 24pt padding, as the sheet's own insets (its scroll area pads 24 sideways).
460
+ footer: {
461
+ paddingBottom: 24,
462
+ paddingHorizontal: 24
463
+ },
425
464
  iconBadge: {
426
465
  alignItems: "center",
427
466
  borderRadius: 10,
@@ -429,9 +468,20 @@ var styles7 = StyleSheet7.create({
429
468
  justifyContent: "center",
430
469
  width: 36
431
470
  },
471
+ // No hairline dividers above and below the scroll area (Paper's MD2-era borders): none of the
472
+ // fleet's other dialogs draws one, so the sheet matches them (Jay, 2026-10-15).
432
473
  scrollArea: {
474
+ borderBottomWidth: 0,
475
+ borderTopWidth: 0,
433
476
  marginBottom: 0
434
477
  },
478
+ // The header and the rows scroll together: the card's 24pt inset above the header, the overlay
479
+ // cards' 16pt gap between it and the rows, and 16pt below the last row before the Done row.
480
+ scrollContent: {
481
+ gap: 16,
482
+ paddingBottom: 16,
483
+ paddingTop: 24
484
+ },
435
485
  section: {
436
486
  gap: 12
437
487
  },
@@ -478,15 +528,15 @@ var styles7 = StyleSheet7.create({
478
528
  import { useAutoPaperTheme as useAutoPaperTheme7 } from "@rific/auto-paper";
479
529
  import { Button as Button3, IconButton } from "@rific/feedback-press";
480
530
  import { useRotation as useRotation3 } from "@tastic/core";
481
- import { useState as useState3 } from "react";
482
- import { ScrollView as ScrollView2, StyleSheet as StyleSheet8, View as View7 } from "react-native";
531
+ import { useState as useState4 } from "react";
532
+ import { ScrollView, StyleSheet as StyleSheet8, View as View7 } from "react-native";
483
533
  import { Text as Text7 } from "react-native-paper";
484
534
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
485
535
  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 }) {
486
536
  const { dark, colors } = useAutoPaperTheme7();
487
537
  const ambientRotation = useRotation3();
488
538
  const rotation = rotationOverride ?? ambientRotation;
489
- const [confirmResetVisible, setConfirmResetVisible] = useState3(false);
539
+ const [confirmResetVisible, setConfirmResetVisible] = useState4(false);
490
540
  const showReset = !!onReset;
491
541
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
492
542
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
@@ -495,7 +545,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
495
545
  /* @__PURE__ */ jsx9(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
496
546
  /* @__PURE__ */ jsx9(Text7, { variant: titleVariant, style: [styles8.title, { color: fg }], children: title })
497
547
  ] }),
498
- /* @__PURE__ */ jsxs8(ScrollView2, { style: styles8.scrollView, contentContainerStyle: [styles8.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
548
+ /* @__PURE__ */ jsxs8(ScrollView, { style: styles8.scrollView, contentContainerStyle: [styles8.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
499
549
  children,
500
550
  showReset && /* @__PURE__ */ jsx9(Button3, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles8.resetButton, children: resetLabel })
501
551
  ] }),
@@ -550,11 +600,11 @@ var styles8 = StyleSheet8.create({
550
600
  });
551
601
 
552
602
  // src/ContentGutter.tsx
553
- import { computeContentBounds, useRotatedWindowDimensions } from "@tastic/core";
603
+ import { computeContentBounds, useRotatedWindowDimensions as useRotatedWindowDimensions2 } from "@tastic/core";
554
604
  import { StyleSheet as StyleSheet9, View as View8 } from "react-native";
555
605
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
556
606
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
557
- const { width: windowWidth } = useRotatedWindowDimensions();
607
+ const { width: windowWidth } = useRotatedWindowDimensions2();
558
608
  const { contentWidth, gutterWidth } = computeContentBounds(windowWidth, maxContentWidth ?? Infinity);
559
609
  return /* @__PURE__ */ jsxs9(View8, { style: [styles9.row, style], children: [
560
610
  /* @__PURE__ */ jsx10(View8, { style: [styles9.gutter, { width: gutterWidth }], children: leftGutter }),
@@ -641,6 +691,7 @@ export {
641
691
  StatRow,
642
692
  StatSection,
643
693
  ActivityStatSection,
694
+ useCompactDialog,
644
695
  ConfirmDialog,
645
696
  RotatedFrame,
646
697
  BaseSettingsDialog,
@@ -1,21 +1,55 @@
1
- // src/overlayCard.ts
2
- import { StyleSheet } from "react-native";
3
- function getOverlayCardColors(dark) {
4
- return {
5
- fg: dark ? "#FFFFFF" : "#000000",
6
- fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
7
- cardBg: dark ? "#111111" : "#F2F2F2",
8
- cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
9
- };
1
+ // src/DialogHeader.tsx
2
+ import { useAutoPaperTheme } from "@rific/auto-paper";
3
+ import { StyleSheet, View } from "react-native";
4
+ import { Icon, Text } from "react-native-paper";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ var COMPACT_DIALOG_HEIGHT = 528;
7
+ function isCompactDialogHeight(height) {
8
+ return height < COMPACT_DIALOG_HEIGHT;
9
+ }
10
+ function DialogHeader({ icon, title, color, compact = false, style }) {
11
+ const { colors } = useAutoPaperTheme();
12
+ const accent = color ?? colors.primary;
13
+ return /* @__PURE__ */ jsxs(View, { style: [compact ? styles.compact : styles.full, style], children: [
14
+ /* @__PURE__ */ jsx(Icon, { source: icon, size: compact ? COMPACT_ICON_SIZE : ICON_SIZE, color: accent }),
15
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", variant: compact ? "headlineSmall" : "headlineLarge", style: [styles.title, compact && styles.titleCompact, { color: accent }], children: title })
16
+ ] });
10
17
  }
11
- var overlayStyles = StyleSheet.create({
18
+ var ICON_SIZE = 64;
19
+ var COMPACT_ICON_SIZE = 32;
20
+ var styles = StyleSheet.create({
21
+ compact: {
22
+ alignItems: "center",
23
+ alignSelf: "stretch",
24
+ flexDirection: "row",
25
+ gap: 12,
26
+ justifyContent: "center"
27
+ },
28
+ full: {
29
+ alignItems: "center",
30
+ alignSelf: "stretch",
31
+ gap: 16
32
+ },
33
+ // textAlign matters once a title wraps: the card centers the Text's block, but each wrapped line
34
+ // still defaults to left-aligned without it.
35
+ title: {
36
+ fontWeight: "bold",
37
+ textAlign: "center"
38
+ },
39
+ titleCompact: {
40
+ flexShrink: 1
41
+ }
42
+ });
43
+
44
+ // src/overlayCard.ts
45
+ import { StyleSheet as StyleSheet2 } from "react-native";
46
+ var overlayStyles = StyleSheet2.create({
12
47
  card: {
13
48
  alignItems: "center",
14
- borderRadius: 20,
15
- borderWidth: 1,
49
+ borderRadius: 28,
16
50
  gap: 16,
17
51
  maxWidth: 360,
18
- padding: 32
52
+ padding: 24
19
53
  },
20
54
  overlay: {
21
55
  alignItems: "center",
@@ -28,7 +62,7 @@ var overlayStyles = StyleSheet.create({
28
62
  top: 0
29
63
  }
30
64
  });
31
- var overlayActionStyles = StyleSheet.create({
65
+ var overlayActionStyles = StyleSheet2.create({
32
66
  // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
33
67
  // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
34
68
  // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
@@ -71,28 +105,30 @@ var overlayActionStyles = StyleSheet.create({
71
105
  });
72
106
 
73
107
  // src/OverlaySurface.tsx
74
- import { BlurView, useAutoPaperTheme, useBlur } from "@rific/auto-paper";
75
- import { StyleSheet as StyleSheet2, View } from "react-native";
76
- import { jsx } from "react/jsx-runtime";
108
+ import { BlurView, useAutoPaperTheme as useAutoPaperTheme2, useBlur } from "@rific/auto-paper";
109
+ import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
110
+ import { jsx as jsx2 } from "react/jsx-runtime";
77
111
  function useOverlayCardColors() {
78
- const { colors } = useAutoPaperTheme();
112
+ const { colors } = useAutoPaperTheme2();
79
113
  return { fg: colors.onSurface, fgMuted: colors.onSurfaceVariant, cardBorder: colors.outlineVariant, backdrop: colors.backdrop };
80
114
  }
81
- function OverlayBackdrop({ children, style }) {
115
+ function OverlayBackdrop({ children, style, onLayout }) {
82
116
  const { backdrop } = useOverlayCardColors();
83
- return /* @__PURE__ */ jsx(View, { style: [overlayStyles.overlay, { backgroundColor: backdrop }, style], children });
117
+ return /* @__PURE__ */ jsx2(View2, { onLayout, style: [overlayStyles.overlay, { backgroundColor: backdrop }, style], children });
84
118
  }
85
119
  function OverlayCard({ children, style, ...rest }) {
86
- const { cardBorder } = useOverlayCardColors();
120
+ const { isV3, roundness } = useAutoPaperTheme2();
87
121
  const blur = useBlur();
88
- return /* @__PURE__ */ jsx(BlurView, { blur, elevation: 3, style: [overlayStyles.card, styles.card, { borderColor: cardBorder }, style], ...rest, children });
122
+ return /* @__PURE__ */ jsx2(BlurView, { blur, style: [overlayStyles.card, styles2.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
89
123
  }
90
- var styles = StyleSheet2.create({
124
+ var styles2 = StyleSheet3.create({
91
125
  card: { overflow: "hidden" }
92
126
  });
93
127
 
94
128
  export {
95
- getOverlayCardColors,
129
+ COMPACT_DIALOG_HEIGHT,
130
+ isCompactDialogHeight,
131
+ DialogHeader,
96
132
  overlayStyles,
97
133
  overlayActionStyles,
98
134
  useOverlayCardColors,