@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.
@@ -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
 
@@ -100,31 +100,65 @@ var styles = import_react_native.StyleSheet.create({
100
100
  var import_react2 = require("react");
101
101
 
102
102
  // src/guide/HowToPlayDialog.tsx
103
- var import_auto_paper2 = require("@rific/auto-paper");
103
+ var import_auto_paper4 = require("@rific/auto-paper");
104
104
  var import_feedback_press = require("@rific/feedback-press");
105
105
  var import_react = require("react");
106
- var import_react_native3 = require("react-native");
107
- var import_react_native_paper2 = require("react-native-paper");
106
+ var import_react_native5 = require("react-native");
107
+ var import_react_native_paper3 = require("react-native-paper");
108
108
  var import_react_native_reanimated = require("react-native-reanimated");
109
109
 
110
- // src/overlayCard.ts
110
+ // src/DialogHeader.tsx
111
+ var import_auto_paper2 = require("@rific/auto-paper");
111
112
  var import_react_native2 = require("react-native");
112
- function getOverlayCardColors(dark) {
113
- return {
114
- fg: dark ? "#FFFFFF" : "#000000",
115
- fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
116
- cardBg: dark ? "#111111" : "#F2F2F2",
117
- cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
118
- };
113
+ var import_react_native_paper2 = require("react-native-paper");
114
+ var import_jsx_runtime2 = require("react/jsx-runtime");
115
+ var COMPACT_DIALOG_HEIGHT = 528;
116
+ function isCompactDialogHeight(height) {
117
+ return height < COMPACT_DIALOG_HEIGHT;
119
118
  }
120
- var overlayStyles = import_react_native2.StyleSheet.create({
119
+ function DialogHeader({ icon, title, color, compact = false, style }) {
120
+ const { colors } = (0, import_auto_paper2.useAutoPaperTheme)();
121
+ const accent = color ?? colors.primary;
122
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [compact ? styles2.compact : styles2.full, style], children: [
123
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: compact ? COMPACT_ICON_SIZE : ICON_SIZE, color: accent }),
124
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { accessibilityRole: "header", variant: compact ? "headlineSmall" : "headlineLarge", style: [styles2.title, compact && styles2.titleCompact, { color: accent }], children: title })
125
+ ] });
126
+ }
127
+ var ICON_SIZE = 64;
128
+ var COMPACT_ICON_SIZE = 32;
129
+ var styles2 = import_react_native2.StyleSheet.create({
130
+ compact: {
131
+ alignItems: "center",
132
+ alignSelf: "stretch",
133
+ flexDirection: "row",
134
+ gap: 12,
135
+ justifyContent: "center"
136
+ },
137
+ full: {
138
+ alignItems: "center",
139
+ alignSelf: "stretch",
140
+ gap: 16
141
+ },
142
+ // textAlign matters once a title wraps: the card centers the Text's block, but each wrapped line
143
+ // still defaults to left-aligned without it.
144
+ title: {
145
+ fontWeight: "bold",
146
+ textAlign: "center"
147
+ },
148
+ titleCompact: {
149
+ flexShrink: 1
150
+ }
151
+ });
152
+
153
+ // src/overlayCard.ts
154
+ var import_react_native3 = require("react-native");
155
+ var overlayStyles = import_react_native3.StyleSheet.create({
121
156
  card: {
122
157
  alignItems: "center",
123
- borderRadius: 20,
124
- borderWidth: 1,
158
+ borderRadius: 28,
125
159
  gap: 16,
126
160
  maxWidth: 360,
127
- padding: 32
161
+ padding: 24
128
162
  },
129
163
  overlay: {
130
164
  alignItems: "center",
@@ -137,7 +171,7 @@ var overlayStyles = import_react_native2.StyleSheet.create({
137
171
  top: 0
138
172
  }
139
173
  });
140
- var overlayActionStyles = import_react_native2.StyleSheet.create({
174
+ var overlayActionStyles = import_react_native3.StyleSheet.create({
141
175
  // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
142
176
  // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
143
177
  // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
@@ -179,42 +213,64 @@ var overlayActionStyles = import_react_native2.StyleSheet.create({
179
213
  }
180
214
  });
181
215
 
216
+ // src/OverlaySurface.tsx
217
+ var import_auto_paper3 = require("@rific/auto-paper");
218
+ var import_react_native4 = require("react-native");
219
+ var import_jsx_runtime3 = require("react/jsx-runtime");
220
+ function useOverlayCardColors() {
221
+ const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
222
+ return { fg: colors.onSurface, fgMuted: colors.onSurfaceVariant, cardBorder: colors.outlineVariant, backdrop: colors.backdrop };
223
+ }
224
+ function OverlayBackdrop({ children, style, onLayout }) {
225
+ const { backdrop } = useOverlayCardColors();
226
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { onLayout, style: [overlayStyles.overlay, { backgroundColor: backdrop }, style], children });
227
+ }
228
+ function OverlayCard({ children, style, ...rest }) {
229
+ const { isV3, roundness } = (0, import_auto_paper3.useAutoPaperTheme)();
230
+ const blur = (0, import_auto_paper3.useBlur)();
231
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.BlurView, { blur, style: [overlayStyles.card, styles3.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
232
+ }
233
+ var styles3 = import_react_native4.StyleSheet.create({
234
+ card: { overflow: "hidden" }
235
+ });
236
+
182
237
  // src/guide/HowToPlayDialog.tsx
183
- var import_jsx_runtime2 = require("react/jsx-runtime");
238
+ var import_jsx_runtime4 = require("react/jsx-runtime");
184
239
  var OVERLAY_GUTTER = 24;
185
240
  var CARD_PADDING = 24;
186
241
  var MAX_CARD_WIDTH = 360;
187
242
  var MAX_CARD_WIDTH_TURNED = 520;
188
243
  var ART_HEIGHT = 132;
189
- var COMPACT_BELOW = 480;
190
244
  var ART_HEIGHT_COMPACT = 88;
191
245
  function getGuideCardSize(windowWidth, windowHeight, rotation) {
192
246
  const turned = rotation === 90 || rotation === -90;
193
247
  const availableWidth = (turned ? windowHeight : windowWidth) - OVERLAY_GUTTER * 2;
194
248
  const availableHeight = (turned ? windowWidth : windowHeight) - OVERLAY_GUTTER * 2;
195
249
  const width = Math.min(turned ? MAX_CARD_WIDTH_TURNED : MAX_CARD_WIDTH, availableWidth);
250
+ const compact = isCompactDialogHeight(turned ? windowWidth : windowHeight);
196
251
  return {
197
252
  width,
198
253
  pageWidth: width - CARD_PADDING * 2,
199
254
  maxHeight: availableHeight,
200
- artHeight: availableHeight < COMPACT_BELOW ? ART_HEIGHT_COMPACT : ART_HEIGHT
255
+ artHeight: compact ? ART_HEIGHT_COMPACT : ART_HEIGHT,
256
+ compact
201
257
  };
202
258
  }
203
- function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0 }) {
204
- const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
205
- const { width: windowWidth, height: windowHeight } = (0, import_react_native3.useWindowDimensions)();
259
+ function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0, title = "How to Play", icon = "help-circle-outline" }) {
260
+ const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
261
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native5.useWindowDimensions)();
206
262
  const reducedMotion = (0, import_react_native_reanimated.useReducedMotion)();
207
263
  const [index, setIndex] = (0, import_react.useState)(0);
208
264
  const scrollRef = (0, import_react.useRef)(null);
209
265
  (0, import_react.useEffect)(() => {
210
- const subscription = import_react_native3.BackHandler.addEventListener("hardwareBackPress", () => {
266
+ const subscription = import_react_native5.BackHandler.addEventListener("hardwareBackPress", () => {
211
267
  onSkip();
212
268
  return true;
213
269
  });
214
270
  return () => subscription.remove();
215
271
  }, [onSkip]);
216
- const { fg, cardBg, cardBorder, fgMuted } = getOverlayCardColors(dark);
217
- const { width: cardWidth, pageWidth, maxHeight, artHeight } = getGuideCardSize(windowWidth, windowHeight, rotation);
272
+ const { fg, fgMuted } = useOverlayCardColors();
273
+ const { width: cardWidth, pageWidth, maxHeight, artHeight, compact } = getGuideCardSize(windowWidth, windowHeight, rotation);
218
274
  const rotationStyle = rotation === 0 ? void 0 : { transform: [{ rotate: `${rotation}deg` }] };
219
275
  const isFirst = index === 0;
220
276
  const isLast = index === steps.length - 1;
@@ -242,24 +298,25 @@ function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0 }) {
242
298
  targetRef.current = null;
243
299
  syncToOffset(e.nativeEvent.contentOffset.x);
244
300
  }
245
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [overlayStyles.overlay, styles2.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { accessibilityViewIsModal: true, style: [overlayStyles.card, styles2.card, { width: cardWidth, maxHeight, backgroundColor: cardBg, borderColor: cardBorder }, rotationStyle], children: [
246
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.ScrollView, { ref: scrollRef, horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll: handleScroll, scrollEventThrottle: 16, onMomentumScrollEnd: handleMomentumEnd, style: [styles2.pager, { width: pageWidth }], children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.ScrollView, { nestedScrollEnabled: true, showsVerticalScrollIndicator: false, style: { width: pageWidth }, contentContainerStyle: styles2.page, children: [
247
- step.art !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [styles2.art, { height: artHeight, backgroundColor: artBackground }], children: step.art }),
248
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineSmall", style: [styles2.title, { color: fg }], children: step.title }),
249
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyMedium", style: [styles2.body, { color: fg }], children: step.body })
301
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverlayBackdrop, { style: styles4.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(OverlayCard, { accessibilityViewIsModal: true, style: [styles4.card, { width: cardWidth, maxHeight }, rotationStyle], children: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DialogHeader, { icon, title, compact }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.ScrollView, { ref: scrollRef, horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll: handleScroll, scrollEventThrottle: 16, onMomentumScrollEnd: handleMomentumEnd, style: [styles4.pager, { width: pageWidth }], children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.ScrollView, { nestedScrollEnabled: true, showsVerticalScrollIndicator: false, style: { width: pageWidth }, contentContainerStyle: [styles4.page, compact && styles4.pageCompact], children: [
304
+ step.art !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.View, { style: [styles4.art, { height: artHeight, backgroundColor: artBackground }], children: step.art }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "headlineSmall", style: [styles4.title, { color: fg }], children: step.title }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "bodyMedium", style: [styles4.body, { color: fg }], children: step.body })
250
307
  ] }, i)) }),
251
- steps.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { accessible: true, accessibilityLabel: `Step ${index + 1} of ${steps.length}`, style: styles2.dots, children: steps.map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [styles2.dot, { backgroundColor: i === index ? colors.primary : fgMuted }] }, i)) }),
252
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: overlayActionStyles.actions, children: [
253
- !isOnly && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: isFirst ? onSkip : () => goTo(index - 1), style: overlayActionStyles.actionButton, children: isFirst ? "Skip" : "Back" }),
254
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: isLast ? onFinish : () => goTo(index + 1), style: isOnly ? overlayActionStyles.actionButtonSingle : overlayActionStyles.actionButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: isLast ? "Ready" : "Next" })
308
+ steps.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.View, { accessible: true, accessibilityLabel: `Step ${index + 1} of ${steps.length}`, style: styles4.dots, children: steps.map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.View, { style: i === index ? [styles4.dot, { backgroundColor: colors.primary }] : [styles4.dot, styles4.dotInactive, { backgroundColor: fgMuted }] }, i)) }),
309
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: overlayActionStyles.actions, children: [
310
+ !isOnly && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: isFirst ? onSkip : () => goTo(index - 1), style: overlayActionStyles.actionButton, children: isFirst ? "Skip" : "Back" }),
311
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.Button, { mode: "contained", onPress: isLast ? onFinish : () => goTo(index + 1), style: isOnly ? overlayActionStyles.actionButtonSingle : overlayActionStyles.actionButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: isLast ? "Ready" : "Next" })
255
312
  ] })
256
313
  ] }) }) });
257
314
  }
258
315
  function HowToPlayDialog({ visible, steps, ...rest }) {
259
316
  if (!visible || steps.length === 0) return null;
260
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HowToPlayCard, { steps, ...rest });
317
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HowToPlayCard, { steps, ...rest });
261
318
  }
262
- var styles2 = import_react_native3.StyleSheet.create({
319
+ var styles4 = import_react_native5.StyleSheet.create({
263
320
  art: {
264
321
  alignItems: "center",
265
322
  borderRadius: 12,
@@ -270,8 +327,7 @@ var styles2 = import_react_native3.StyleSheet.create({
270
327
  body: {
271
328
  textAlign: "center"
272
329
  },
273
- // Additive on top of overlayCard's own shared `card` shape - its default 32px padding is tighter
274
- // here (a paged card wants more width for its art) and the shared 16px gap is right.
330
+ // overlayCard's shared `card` padding, restated: the page width above is computed from it.
275
331
  card: {
276
332
  padding: CARD_PADDING
277
333
  },
@@ -280,6 +336,9 @@ var styles2 = import_react_native3.StyleSheet.create({
280
336
  height: 8,
281
337
  width: 8
282
338
  },
339
+ dotInactive: {
340
+ opacity: 0.5
341
+ },
283
342
  dots: {
284
343
  flexDirection: "row",
285
344
  gap: 8
@@ -291,6 +350,11 @@ var styles2 = import_react_native3.StyleSheet.create({
291
350
  alignItems: "center",
292
351
  gap: 12
293
352
  },
353
+ // The 8pt this saves is what the compact dialog header cost a page on a 402pt-tall window (a
354
+ // Pro-size iPhone held sideways, measured on web): without it the body's second line scrolled.
355
+ pageCompact: {
356
+ gap: 8
357
+ },
294
358
  pager: {
295
359
  flexShrink: 1
296
360
  },
@@ -301,7 +365,7 @@ var styles2 = import_react_native3.StyleSheet.create({
301
365
  });
302
366
 
303
367
  // src/guide/GuideProvider.tsx
304
- var import_jsx_runtime3 = require("react/jsx-runtime");
368
+ var import_jsx_runtime5 = require("react/jsx-runtime");
305
369
  var noop = () => {
306
370
  };
307
371
  var GuideContext = (0, import_react2.createContext)({ isOpen: false, open: noop, close: noop, requestAutoShow: noop });
@@ -320,9 +384,9 @@ function GuideProvider({ steps, currentVersion, seenVersion, onSeen, rotation, c
320
384
  setIsOpen(true);
321
385
  }, [unseen]);
322
386
  const value = (0, import_react2.useMemo)(() => ({ isOpen, open, close, requestAutoShow }), [isOpen, open, close, requestAutoShow]);
323
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(GuideContext.Provider, { value, children: [
387
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(GuideContext.Provider, { value, children: [
324
388
  children,
325
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HowToPlayDialog, { visible: isOpen, steps, onFinish: close, onSkip: close, rotation })
389
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HowToPlayDialog, { visible: isOpen, steps, onFinish: close, onSkip: close, rotation })
326
390
  ] });
327
391
  }
328
392
  function useGuide() {
@@ -375,27 +439,27 @@ function createReturningPlayerMigrate(storage, legacyKeys) {
375
439
  }
376
440
 
377
441
  // src/guide/SeatDevicesHint.tsx
378
- var import_auto_paper3 = require("@rific/auto-paper");
379
- var import_react_native4 = require("react-native");
380
- var import_react_native_paper3 = require("react-native-paper");
381
- var import_jsx_runtime4 = require("react/jsx-runtime");
442
+ var import_auto_paper5 = require("@rific/auto-paper");
443
+ var import_react_native6 = require("react-native");
444
+ var import_react_native_paper4 = require("react-native-paper");
445
+ var import_jsx_runtime6 = require("react/jsx-runtime");
382
446
  var ICON = {
383
447
  keyboard: { source: "keyboard-outline", size: 56 },
384
448
  mouse: { source: "mouse-outline", size: 44 }
385
449
  };
386
450
  var ORDER = ["keyboard", "mouse"];
387
451
  function SeatDevicesHint({ devices, seatColors }) {
388
- const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
452
+ const { colors } = (0, import_auto_paper5.useAutoPaperTheme)();
389
453
  const tints = seatColors ?? [colors.primary, colors.secondary];
390
454
  const accessibilityLabel = devices.map((device, i) => `Player ${i + 1} uses the ${device}`).join(", ");
391
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { accessible: true, accessibilityLabel, style: styles3.row, children: ORDER.map((device) => {
455
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { accessible: true, accessibilityLabel, style: styles5.row, children: ORDER.map((device) => {
392
456
  const seats = devices.flatMap((d, i) => d === device ? [i] : []);
393
457
  if (seats.length === 0) return null;
394
458
  const color = seats.length === 1 ? tints[seats[0]] ?? colors.onSurface : colors.onSurface;
395
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Icon, { source: ICON[device].source, size: ICON[device].size, color }, device);
459
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Icon, { source: ICON[device].source, size: ICON[device].size, color }, device);
396
460
  }) });
397
461
  }
398
- var styles3 = import_react_native4.StyleSheet.create({
462
+ var styles5 = import_react_native6.StyleSheet.create({
399
463
  row: {
400
464
  alignItems: "center",
401
465
  flexDirection: "row",
@@ -404,28 +468,28 @@ var styles3 = import_react_native4.StyleSheet.create({
404
468
  });
405
469
 
406
470
  // src/guide/SeatDiagram.tsx
407
- var import_auto_paper4 = require("@rific/auto-paper");
408
- var import_react_native5 = require("react-native");
409
- var import_react_native_paper4 = require("react-native-paper");
410
- var import_jsx_runtime5 = require("react/jsx-runtime");
471
+ var import_auto_paper6 = require("@rific/auto-paper");
472
+ var import_react_native7 = require("react-native");
473
+ var import_react_native_paper5 = require("react-native-paper");
474
+ var import_jsx_runtime7 = require("react/jsx-runtime");
411
475
  var DEVICE_WIDTH = 88;
412
476
  var DEVICE_HEIGHT = 120;
413
477
  function SeatDiagram({ topColor, bottomColor, topLabel = "P2", bottomLabel = "P1" }) {
414
- const { colors } = (0, import_auto_paper4.useAutoPaperTheme)();
478
+ const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
415
479
  const top = topColor ?? colors.secondary;
416
480
  const bottom = bottomColor ?? colors.primary;
417
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { accessible: true, accessibilityLabel: `${bottomLabel} plays the bottom half, ${topLabel} plays the top half`, style: [styles4.device, { borderColor: colors.onSurface }], children: [
418
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.half, { borderColor: top }], children: [
419
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [import_react_native5.StyleSheet.absoluteFill, styles4.tint, { backgroundColor: top }] }),
420
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper4.Text, { variant: "titleMedium", style: [styles4.label, styles4.flipped, { color: top }], children: topLabel })
481
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { accessible: true, accessibilityLabel: `${bottomLabel} plays the bottom half, ${topLabel} plays the top half`, style: [styles6.device, { borderColor: colors.onSurface }], children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: [styles6.half, { borderColor: top }], children: [
483
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: [import_react_native7.StyleSheet.absoluteFill, styles6.tint, { backgroundColor: top }] }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Text, { variant: "titleMedium", style: [styles6.label, styles6.flipped, { color: top }], children: topLabel })
421
485
  ] }),
422
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.half, styles4.bottomHalf, { borderColor: bottom }], children: [
423
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [import_react_native5.StyleSheet.absoluteFill, styles4.tint, { backgroundColor: bottom }] }),
424
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper4.Text, { variant: "titleMedium", style: [styles4.label, { color: bottom }], children: bottomLabel })
486
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: [styles6.half, styles6.bottomHalf, { borderColor: bottom }], children: [
487
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: [import_react_native7.StyleSheet.absoluteFill, styles6.tint, { backgroundColor: bottom }] }),
488
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Text, { variant: "titleMedium", style: [styles6.label, { color: bottom }], children: bottomLabel })
425
489
  ] })
426
490
  ] });
427
491
  }
428
- var styles4 = import_react_native5.StyleSheet.create({
492
+ var styles6 = import_react_native7.StyleSheet.create({
429
493
  bottomHalf: {
430
494
  borderTopWidth: 0
431
495
  },
@@ -456,12 +520,12 @@ var styles4 = import_react_native5.StyleSheet.create({
456
520
  });
457
521
 
458
522
  // src/guide/SwipeHint.tsx
459
- var import_auto_paper5 = require("@rific/auto-paper");
523
+ var import_auto_paper7 = require("@rific/auto-paper");
460
524
  var import_react3 = require("react");
461
- var import_react_native6 = require("react-native");
462
- var import_react_native_paper5 = require("react-native-paper");
525
+ var import_react_native8 = require("react-native");
526
+ var import_react_native_paper6 = require("react-native-paper");
463
527
  var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
464
- var import_jsx_runtime6 = require("react/jsx-runtime");
528
+ var import_jsx_runtime8 = require("react/jsx-runtime");
465
529
  var DOT_COUNT = 7;
466
530
  var FINGER_SIZE = 28;
467
531
  var MOUSE_SIZE = 30;
@@ -469,7 +533,7 @@ var SWEEP_MS = 900;
469
533
  var PAUSE_MS = 500;
470
534
  var ARROW_ICON = { up: "arrow-up", down: "arrow-down", left: "arrow-left", right: "arrow-right" };
471
535
  function SwipeHint({ direction = "right", pointer = "finger", color, length = 96 }) {
472
- const { colors } = (0, import_auto_paper5.useAutoPaperTheme)();
536
+ const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
473
537
  const tint = color ?? colors.primary;
474
538
  const reducedMotion = (0, import_react_native_reanimated2.useReducedMotion)();
475
539
  const progress = (0, import_react_native_reanimated2.useSharedValue)(0.5);
@@ -491,13 +555,13 @@ function SwipeHint({ direction = "right", pointer = "finger", color, length = 96
491
555
  };
492
556
  });
493
557
  const axis = horizontal ? sign > 0 ? "row" : "row-reverse" : sign > 0 ? "column" : "column-reverse";
494
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { accessibilityLabel: `${pointer === "mouse" ? "Drag" : "Swipe"} ${direction}`, accessible: true, style: [styles5.path, { flexDirection: axis }], children: [
495
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: [styles5.dots, { flexDirection: axis, [horizontal ? "width" : "height"]: length }], children: Array.from({ length: DOT_COUNT }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: [styles5.dot, { backgroundColor: tint, opacity: 0.35 + 0.65 * i / (DOT_COUNT - 1) }] }, i)) }),
496
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Icon, { source: ARROW_ICON[direction], size: 28, color: tint }),
497
- pointer === "mouse" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_reanimated2.default.View, { style: [styles5.mouse, fingerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper5.Icon, { source: "mouse", size: MOUSE_SIZE, color: tint }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_reanimated2.default.View, { style: [styles5.finger, { borderColor: tint }, fingerStyle] })
558
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { accessibilityLabel: `${pointer === "mouse" ? "Drag" : "Swipe"} ${direction}`, accessible: true, style: [styles7.path, { flexDirection: axis }], children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: [styles7.dots, { flexDirection: axis, [horizontal ? "width" : "height"]: length }], children: Array.from({ length: DOT_COUNT }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: [styles7.dot, { backgroundColor: tint, opacity: 0.35 + 0.65 * i / (DOT_COUNT - 1) }] }, i)) }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper6.Icon, { source: ARROW_ICON[direction], size: 28, color: tint }),
561
+ pointer === "mouse" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_reanimated2.default.View, { style: [styles7.mouse, fingerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper6.Icon, { source: "mouse", size: MOUSE_SIZE, color: tint }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_reanimated2.default.View, { style: [styles7.finger, { borderColor: tint }, fingerStyle] })
498
562
  ] });
499
563
  }
500
- var styles5 = import_react_native6.StyleSheet.create({
564
+ var styles7 = import_react_native8.StyleSheet.create({
501
565
  dot: {
502
566
  borderRadius: 3,
503
567
  height: 6,
@@ -1,8 +1,11 @@
1
1
  import {
2
- getOverlayCardColors,
2
+ DialogHeader,
3
+ OverlayBackdrop,
4
+ OverlayCard,
5
+ isCompactDialogHeight,
3
6
  overlayActionStyles,
4
- overlayStyles
5
- } from "../chunk-32U2OIRW.mjs";
7
+ useOverlayCardColors
8
+ } from "../chunk-PUF6TABL.mjs";
6
9
 
7
10
  // src/guide/DirectionKeysHint.tsx
8
11
  import { useAutoPaperTheme } from "@rific/auto-paper";
@@ -72,21 +75,22 @@ var CARD_PADDING = 24;
72
75
  var MAX_CARD_WIDTH = 360;
73
76
  var MAX_CARD_WIDTH_TURNED = 520;
74
77
  var ART_HEIGHT = 132;
75
- var COMPACT_BELOW = 480;
76
78
  var ART_HEIGHT_COMPACT = 88;
77
79
  function getGuideCardSize(windowWidth, windowHeight, rotation) {
78
80
  const turned = rotation === 90 || rotation === -90;
79
81
  const availableWidth = (turned ? windowHeight : windowWidth) - OVERLAY_GUTTER * 2;
80
82
  const availableHeight = (turned ? windowWidth : windowHeight) - OVERLAY_GUTTER * 2;
81
83
  const width = Math.min(turned ? MAX_CARD_WIDTH_TURNED : MAX_CARD_WIDTH, availableWidth);
84
+ const compact = isCompactDialogHeight(turned ? windowWidth : windowHeight);
82
85
  return {
83
86
  width,
84
87
  pageWidth: width - CARD_PADDING * 2,
85
88
  maxHeight: availableHeight,
86
- artHeight: availableHeight < COMPACT_BELOW ? ART_HEIGHT_COMPACT : ART_HEIGHT
89
+ artHeight: compact ? ART_HEIGHT_COMPACT : ART_HEIGHT,
90
+ compact
87
91
  };
88
92
  }
89
- function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0 }) {
93
+ function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0, title = "How to Play", icon = "help-circle-outline" }) {
90
94
  const { dark, colors } = useAutoPaperTheme2();
91
95
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
92
96
  const reducedMotion = useReducedMotion();
@@ -99,8 +103,8 @@ function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0 }) {
99
103
  });
100
104
  return () => subscription.remove();
101
105
  }, [onSkip]);
102
- const { fg, cardBg, cardBorder, fgMuted } = getOverlayCardColors(dark);
103
- const { width: cardWidth, pageWidth, maxHeight, artHeight } = getGuideCardSize(windowWidth, windowHeight, rotation);
106
+ const { fg, fgMuted } = useOverlayCardColors();
107
+ const { width: cardWidth, pageWidth, maxHeight, artHeight, compact } = getGuideCardSize(windowWidth, windowHeight, rotation);
104
108
  const rotationStyle = rotation === 0 ? void 0 : { transform: [{ rotate: `${rotation}deg` }] };
105
109
  const isFirst = index === 0;
106
110
  const isLast = index === steps.length - 1;
@@ -128,13 +132,14 @@ function HowToPlayCard({ steps, onFinish, onSkip, rotation = 0 }) {
128
132
  targetRef.current = null;
129
133
  syncToOffset(e.nativeEvent.contentOffset.x);
130
134
  }
131
- return /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(View2, { style: [overlayStyles.overlay, styles2.overlay], children: /* @__PURE__ */ jsxs2(View2, { accessibilityViewIsModal: true, style: [overlayStyles.card, styles2.card, { width: cardWidth, maxHeight, backgroundColor: cardBg, borderColor: cardBorder }, rotationStyle], children: [
132
- /* @__PURE__ */ jsx2(ScrollView, { ref: scrollRef, horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll: handleScroll, scrollEventThrottle: 16, onMomentumScrollEnd: handleMomentumEnd, style: [styles2.pager, { width: pageWidth }], children: steps.map((step, i) => /* @__PURE__ */ jsxs2(ScrollView, { nestedScrollEnabled: true, showsVerticalScrollIndicator: false, style: { width: pageWidth }, contentContainerStyle: styles2.page, children: [
135
+ return /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(OverlayBackdrop, { style: styles2.overlay, children: /* @__PURE__ */ jsxs2(OverlayCard, { accessibilityViewIsModal: true, style: [styles2.card, { width: cardWidth, maxHeight }, rotationStyle], children: [
136
+ /* @__PURE__ */ jsx2(DialogHeader, { icon, title, compact }),
137
+ /* @__PURE__ */ jsx2(ScrollView, { ref: scrollRef, horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll: handleScroll, scrollEventThrottle: 16, onMomentumScrollEnd: handleMomentumEnd, style: [styles2.pager, { width: pageWidth }], children: steps.map((step, i) => /* @__PURE__ */ jsxs2(ScrollView, { nestedScrollEnabled: true, showsVerticalScrollIndicator: false, style: { width: pageWidth }, contentContainerStyle: [styles2.page, compact && styles2.pageCompact], children: [
133
138
  step.art !== void 0 && /* @__PURE__ */ jsx2(View2, { style: [styles2.art, { height: artHeight, backgroundColor: artBackground }], children: step.art }),
134
139
  /* @__PURE__ */ jsx2(Text2, { variant: "headlineSmall", style: [styles2.title, { color: fg }], children: step.title }),
135
140
  /* @__PURE__ */ jsx2(Text2, { variant: "bodyMedium", style: [styles2.body, { color: fg }], children: step.body })
136
141
  ] }, i)) }),
137
- steps.length > 1 && /* @__PURE__ */ jsx2(View2, { accessible: true, accessibilityLabel: `Step ${index + 1} of ${steps.length}`, style: styles2.dots, children: steps.map((_, i) => /* @__PURE__ */ jsx2(View2, { style: [styles2.dot, { backgroundColor: i === index ? colors.primary : fgMuted }] }, i)) }),
142
+ steps.length > 1 && /* @__PURE__ */ jsx2(View2, { accessible: true, accessibilityLabel: `Step ${index + 1} of ${steps.length}`, style: styles2.dots, children: steps.map((_, i) => /* @__PURE__ */ jsx2(View2, { style: i === index ? [styles2.dot, { backgroundColor: colors.primary }] : [styles2.dot, styles2.dotInactive, { backgroundColor: fgMuted }] }, i)) }),
138
143
  /* @__PURE__ */ jsxs2(View2, { style: overlayActionStyles.actions, children: [
139
144
  !isOnly && /* @__PURE__ */ jsx2(Button, { mode: "outlined", onPress: isFirst ? onSkip : () => goTo(index - 1), style: overlayActionStyles.actionButton, children: isFirst ? "Skip" : "Back" }),
140
145
  /* @__PURE__ */ jsx2(Button, { mode: "contained", onPress: isLast ? onFinish : () => goTo(index + 1), style: isOnly ? overlayActionStyles.actionButtonSingle : overlayActionStyles.actionButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: isLast ? "Ready" : "Next" })
@@ -156,8 +161,7 @@ var styles2 = StyleSheet2.create({
156
161
  body: {
157
162
  textAlign: "center"
158
163
  },
159
- // Additive on top of overlayCard's own shared `card` shape - its default 32px padding is tighter
160
- // here (a paged card wants more width for its art) and the shared 16px gap is right.
164
+ // overlayCard's shared `card` padding, restated: the page width above is computed from it.
161
165
  card: {
162
166
  padding: CARD_PADDING
163
167
  },
@@ -166,6 +170,9 @@ var styles2 = StyleSheet2.create({
166
170
  height: 8,
167
171
  width: 8
168
172
  },
173
+ dotInactive: {
174
+ opacity: 0.5
175
+ },
169
176
  dots: {
170
177
  flexDirection: "row",
171
178
  gap: 8
@@ -177,6 +184,11 @@ var styles2 = StyleSheet2.create({
177
184
  alignItems: "center",
178
185
  gap: 12
179
186
  },
187
+ // The 8pt this saves is what the compact dialog header cost a page on a 402pt-tall window (a
188
+ // Pro-size iPhone held sideways, measured on web): without it the body's second line scrolled.
189
+ pageCompact: {
190
+ gap: 8
191
+ },
180
192
  pager: {
181
193
  flexShrink: 1
182
194
  },
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, ConfirmDialogProps, ContentGutter, CornerActionButtons, LOCKED_BADGE_COLOR, MONO_FONT, OverlayCardColors, RotatedFrame, RotatedFrameProps, StaggeredWord, StaggeredWordProps, StatRow, StatSection, UpdateDialog, UpdateDialogProps, getOverlayCardColors, getStaggeredWordDuration, overlayActionStyles, overlayStyles } from './chrome/index.mjs';
1
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, OverlayBackdropProps, OverlayCard, OverlayCardProps, OverlayCardThemeColors, RotatedFrame, RotatedFrameProps, StaggeredWord, StaggeredWordProps, StatRow, StatSection, UpdateDialog, UpdateDialogProps, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors } from './chrome/index.mjs';
2
2
  import * as react from 'react';
3
3
  import { RefObject, ReactNode } from 'react';
4
4
  import { AchievementTier } from '@tastic/achievements';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, ConfirmDialogProps, ContentGutter, CornerActionButtons, LOCKED_BADGE_COLOR, MONO_FONT, OverlayCardColors, RotatedFrame, RotatedFrameProps, StaggeredWord, StaggeredWordProps, StatRow, StatSection, UpdateDialog, UpdateDialogProps, getOverlayCardColors, getStaggeredWordDuration, overlayActionStyles, overlayStyles } from './chrome/index.js';
1
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, OverlayBackdropProps, OverlayCard, OverlayCardProps, OverlayCardThemeColors, RotatedFrame, RotatedFrameProps, StaggeredWord, StaggeredWordProps, StatRow, StatSection, UpdateDialog, UpdateDialogProps, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors } from './chrome/index.js';
2
2
  import * as react from 'react';
3
3
  import { RefObject, ReactNode } from 'react';
4
4
  import { AchievementTier } from '@tastic/achievements';