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