@tastic/shell 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DEFAULT_APP_SETTINGS: () => DEFAULT_APP_SETTINGS,
34
34
  DEFAULT_OPPONENT: () => DEFAULT_OPPONENT,
35
+ GUEST_PLAYER_ID: () => GUEST_PLAYER_ID,
35
36
  HomeScreen: () => HomeScreen,
36
37
  LoadoutRoute: () => LoadoutRoute,
37
38
  PREVIEW_ASPECT: () => PREVIEW_ASPECT,
@@ -99,7 +100,8 @@ var import_auto_paper2 = require("@rific/auto-paper");
99
100
  var import_feedback_press2 = require("@rific/feedback-press");
100
101
  var import_toaster2 = require("@rific/toaster");
101
102
  var import_core6 = require("@tastic/core");
102
- var import_hud2 = require("@tastic/hud");
103
+ var import_chrome2 = require("@tastic/hud/chrome");
104
+ var import_profile3 = require("@tastic/profile");
103
105
  var Haptics = __toESM(require("expo-haptics"));
104
106
  var import_expo_router = require("expo-router");
105
107
  var import_expo_sensors = require("expo-sensors");
@@ -148,24 +150,35 @@ function seatTwoFor(saved, choices) {
148
150
  function humanSeatsFor(mode) {
149
151
  return mode === "twoPlayer" ? [1, 2] : [1];
150
152
  }
153
+ var GUEST_PLAYER_ID = "guest";
151
154
 
152
155
  // src/components/GuideHost.tsx
153
156
  var import_jsx_runtime3 = require("react/jsx-runtime");
157
+ var PACK_GUIDE = "__pack__";
158
+ var NO_INPUT = {};
154
159
  var GameGuideContext = (0, import_react2.createContext)(null);
155
160
  function GuideHost({ children }) {
156
161
  const pack = usePack();
157
- const [gameId, setGameId] = (0, import_react2.useState)(pack.games[0].id);
158
- const game = pack.games.find((g) => g.id === gameId) ?? pack.games[0];
162
+ const [subject, setSubject] = (0, import_react2.useState)(() => pack.guide ? PACK_GUIDE : pack.games.find((g) => g.guide)?.id ?? null);
163
+ const packGuide = subject === PACK_GUIDE ? pack.guide : void 0;
164
+ const game = subject === PACK_GUIDE ? void 0 : pack.games.find((g) => g.id === subject);
165
+ const gameGuide = game?.guide;
159
166
  const rotation = (0, import_core.useRotation)();
160
167
  const touch = (0, import_core.useIsTouchPrimaryDevice)();
161
168
  const seatTwo = (0, import_react_redux.useSelector)((state) => state.opponent.seatTwo);
162
- const input = (0, import_react_redux.useSelector)((state) => game.guide.selectInput(state), import_react_redux.shallowEqual);
163
- const seenVersion = (0, import_react_redux.useSelector)((state) => game.guide.selectVersionSeen(state));
169
+ const input = (0, import_react_redux.useSelector)((state) => gameGuide ? gameGuide.selectInput(state) : NO_INPUT, import_react_redux.shallowEqual);
170
+ const seenVersion = (0, import_react_redux.useSelector)((state) => (packGuide ?? gameGuide)?.selectVersionSeen(state) ?? 0);
164
171
  const dispatch = (0, import_react_redux.useDispatch)();
165
- const mode = roundModeFor(seatTwoFor(seatTwo, game.seatTwo));
166
- const steps = (0, import_react2.useMemo)(() => game.guide.getSteps(input, { touch, mode }), [game, input, touch, mode]);
167
- const onSeen = (0, import_react2.useCallback)((version) => dispatch(game.guide.markSeen(version)), [dispatch, game]);
168
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_guide.GuideProvider, { steps, currentVersion: game.guide.version, seenVersion, onSeen, rotation, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GameGuideBridge, { onPick: setGameId, children }) });
172
+ const mode = roundModeFor(seatTwoFor(seatTwo, game?.seatTwo));
173
+ const steps = (0, import_react2.useMemo)(() => packGuide ? packGuide.getSteps({ touch }) : gameGuide ? gameGuide.getSteps(input, { touch, mode }) : [], [packGuide, gameGuide, input, touch, mode]);
174
+ const onSeen = (0, import_react2.useCallback)(
175
+ (version) => {
176
+ const guide = packGuide ?? gameGuide;
177
+ if (guide) dispatch(guide.markSeen(version));
178
+ },
179
+ [dispatch, packGuide, gameGuide]
180
+ );
181
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_guide.GuideProvider, { steps, currentVersion: (packGuide ?? gameGuide)?.version ?? 0, seenVersion, onSeen, rotation, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GameGuideBridge, { onPick: setSubject, children }) });
169
182
  }
170
183
  function GameGuideBridge({ onPick, children }) {
171
184
  const pack = usePack();
@@ -173,6 +186,7 @@ function GameGuideBridge({ onPick, children }) {
173
186
  const { isOpen, open } = (0, import_guide.useGuide)();
174
187
  const pendingNext = (0, import_react2.useRef)(null);
175
188
  const wasOpen = (0, import_react2.useRef)(false);
189
+ const packAutoShown = (0, import_react2.useRef)(false);
176
190
  (0, import_react2.useEffect)(() => {
177
191
  if (wasOpen.current && !isOpen && pendingNext.current) {
178
192
  const next = pendingNext.current;
@@ -183,15 +197,16 @@ function GameGuideBridge({ onPick, children }) {
183
197
  }, [isOpen]);
184
198
  const openFor = (0, import_react2.useCallback)(
185
199
  (gameId) => {
200
+ if (!pack.games.find((g) => g.id === gameId)?.guide) return;
186
201
  onPick(gameId);
187
202
  open();
188
203
  },
189
- [onPick, open]
204
+ [pack, onPick, open]
190
205
  );
191
206
  const openFirstTime = (0, import_react2.useCallback)(
192
207
  (gameId, next) => {
193
- const game = pack.games.find((g) => g.id === gameId);
194
- if (!game || game.guide.selectVersionSeen(store.getState()) >= game.guide.version) {
208
+ const guide = pack.games.find((g) => g.id === gameId)?.guide;
209
+ if (!guide || guide.selectVersionSeen(store.getState()) >= guide.version) {
195
210
  next();
196
211
  return;
197
212
  }
@@ -200,7 +215,18 @@ function GameGuideBridge({ onPick, children }) {
200
215
  },
201
216
  [pack, store, openFor]
202
217
  );
203
- const value = (0, import_react2.useMemo)(() => ({ openFirstTime, openFor }), [openFirstTime, openFor]);
218
+ const openPack = (0, import_react2.useCallback)(() => {
219
+ if (!pack.guide) return;
220
+ onPick(PACK_GUIDE);
221
+ open();
222
+ }, [pack, onPick, open]);
223
+ const autoShowPack = (0, import_react2.useCallback)(() => {
224
+ const guide = pack.guide;
225
+ if (!guide || packAutoShown.current || guide.selectVersionSeen(store.getState()) >= guide.version) return;
226
+ packAutoShown.current = true;
227
+ openPack();
228
+ }, [pack, store, openPack]);
229
+ const value = (0, import_react2.useMemo)(() => ({ openFirstTime, openFor, openPack, autoShowPack }), [openFirstTime, openFor, openPack, autoShowPack]);
204
230
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GameGuideContext.Provider, { value, children });
205
231
  }
206
232
  function useGameGuide() {
@@ -220,15 +246,20 @@ var RNPaper = __toESM(require("react-native-paper"));
220
246
  var import_react_redux2 = require("react-redux");
221
247
  var import_react4 = require("redux-persist/integration/react");
222
248
  var import_jsx_runtime4 = require("react/jsx-runtime");
249
+ function useShellFeedbackSounds() {
250
+ const selection = (0, import_audio.useAudioPool)(require("./select-MLBNNA4N.wav"));
251
+ const notification = (0, import_audio.useAudioPool)(require("./notification-CGGFPYK7.wav"));
252
+ return { selection, notification };
253
+ }
223
254
  var FeedbackBridge = ({ children }) => {
224
255
  const haptic = (0, import_react_redux2.useSelector)((state) => state.haptic);
225
256
  const sound = (0, import_react_redux2.useSelector)((state) => state.sound);
226
257
  const dispatch = (0, import_react_redux2.useDispatch)();
227
258
  const onChange = (0, import_react3.useCallback)((next) => dispatch(import_feedback_press.hapticActions.initialize(next)), [dispatch]);
228
259
  const onSoundChange = (0, import_react3.useCallback)((next) => dispatch(import_feedback_press.soundActions.initialize(next)), [dispatch]);
229
- const playSelection = (0, import_audio.useAudioPool)(require("./select-MLBNNA4N.wav"));
230
- const playNotification = (0, import_audio.useAudioPool)(require("./notification-CGGFPYK7.wav"));
231
- const bridgeProps = (0, import_feedback_press.useFeedbackBridgeProps)({ initialValue: haptic, onChange, soundInitialValue: sound, onSoundChange, sound: { selection: playSelection, notification: playNotification } });
260
+ const { useFeedbackSounds = useShellFeedbackSounds } = usePack();
261
+ const sounds = useFeedbackSounds();
262
+ const bridgeProps = (0, import_feedback_press.useFeedbackBridgeProps)({ initialValue: haptic, onChange, soundInitialValue: sound, onSoundChange, sound: sounds });
232
263
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.FeedbackPressProvider, { ...bridgeProps, paper: RNPaper, children });
233
264
  };
234
265
  var EdgeGuardBridge = ({ children }) => {
@@ -271,12 +302,12 @@ function Theme({ children }) {
271
302
  // src/components/UpdateDialog.tsx
272
303
  var import_toaster = require("@rific/toaster");
273
304
  var import_core3 = require("@tastic/core");
274
- var import_hud = require("@tastic/hud");
305
+ var import_chrome = require("@tastic/hud/chrome");
275
306
  var import_jsx_runtime6 = require("react/jsx-runtime");
276
307
  function UpdateDialog() {
277
308
  const onError = (0, import_toaster.useUpdateErrorToast)();
278
309
  const rotation = (0, import_core3.useRotation)();
279
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_hud.UpdateDialog, { onError, rotation });
310
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_chrome.UpdateDialog, { onError, rotation });
280
311
  }
281
312
 
282
313
  // src/hooks/useAppSettings.ts
@@ -446,18 +477,39 @@ function useProfiles() {
446
477
  // src/RootLayout.tsx
447
478
  var import_jsx_runtime9 = require("react/jsx-runtime");
448
479
  var PLAY_OPTIONS = { statusBarHidden: true };
480
+ var KEEP_STATUS_BAR_OPTIONS = {};
481
+ var TRANSPARENT_CONTENT = { backgroundColor: "transparent" };
482
+ var noScreenOptions = () => ({});
449
483
  function AppStack() {
484
+ const pack = usePack();
485
+ const { Backdrop } = pack;
450
486
  const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
451
- const rootBackground = (0, import_core6.useThemedRootBackground)(dark, false);
487
+ const rootBackground = (0, import_core6.useThemedRootBackground)(Backdrop ? true : dark, false);
452
488
  const statusBar = (0, import_core6.useRotationAwareStatusBar)();
453
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_expo_router.Stack, { screenOptions: { ...rootBackground, ...statusBar }, children: [
489
+ const packOptions = (pack.useScreenOptions ?? noScreenOptions)();
490
+ const backdropOptions = Backdrop ? { contentStyle: TRANSPARENT_CONTENT, animation: import_react_native2.Platform.select({ android: "fade" }) } : void 0;
491
+ const playOptions = (0, import_react9.useCallback)(
492
+ ({ route }) => {
493
+ const id = route.params?.game;
494
+ return pack.games.some((g) => g.id === id && g.keepStatusBar) ? KEEP_STATUS_BAR_OPTIONS : PLAY_OPTIONS;
495
+ },
496
+ [pack.games]
497
+ );
498
+ const navigationTheme = (0, import_react9.useMemo)(() => Backdrop ? { ...import_expo_router.DarkTheme, colors: { ...import_expo_router.DarkTheme.colors, background: "transparent" } } : null, [Backdrop]);
499
+ const stack = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_expo_router.Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
454
500
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.Stack.Screen, { name: "index" }),
455
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.Stack.Screen, { name: "play/[game]", options: PLAY_OPTIONS })
501
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.Stack.Screen, { name: "play/[game]", options: playOptions })
502
+ ] });
503
+ if (!Backdrop || !navigationTheme) return stack;
504
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native2.View, { style: styles.root, children: [
505
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Backdrop, {}),
506
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_expo_router.ThemeProvider, { value: navigationTheme, children: stack })
456
507
  ] });
457
508
  }
458
509
  function OrientationBridge({ children }) {
459
510
  const { settings, setSettings } = useAppSettings();
460
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_core6.OrientationProvider, { deviceMotion: import_expo_sensors.DeviceMotion, freezeWhileLocked: true, lockInitialValue: { locked: settings.lockOrientation, rotation: settings.lockedRotation }, onLockChange: (next) => setSettings({ lockOrientation: next.locked, lockedRotation: next.rotation }), children });
511
+ const { portraitWhileKeyboard } = usePack();
512
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_core6.OrientationProvider, { deviceMotion: import_expo_sensors.DeviceMotion, freezeWhileLocked: true, lockInitialValue: { locked: settings.lockOrientation, rotation: settings.lockedRotation }, onLockChange: (next) => setSettings({ lockOrientation: next.locked, lockedRotation: next.rotation }), portraitWhileKeyboard, children });
461
513
  }
462
514
  function GatedApp({ children }) {
463
515
  const { loaded } = useProfiles();
@@ -465,24 +517,38 @@ function GatedApp({ children }) {
465
517
  }
466
518
  function HapticGatedToastProvider({ children }) {
467
519
  const { settings } = (0, import_feedback_press2.useHapticSettings)();
468
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_toaster2.ToastProvider, { haptics: settings.vibrate ? Haptics : void 0, paper: RNPaper2, children });
520
+ const { Toaster: PackToasterComponent } = usePack();
521
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_toaster2.ToastProvider, { haptics: settings.vibrate ? Haptics : void 0, paper: PackToasterComponent ? void 0 : RNPaper2, children });
469
522
  }
470
523
  function RotatingToaster() {
471
524
  const renderFrame = (0, import_react9.useCallback)(
472
- (toasts) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_hud2.RotatedFrame, { rotatedSafeArea: true, passThrough: true, children: toasts }),
525
+ (toasts) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_chrome2.RotatedFrame, { rotatedSafeArea: true, passThrough: true, children: toasts }),
473
526
  []
474
527
  );
475
528
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_toaster2.Toaster, { historyButton: null, clearButton: null, limit: 3, renderFrame });
476
529
  }
530
+ function PackToaster({ Toaster: PackToasterComponent }) {
531
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native2.View, { style: import_react_native2.StyleSheet.absoluteFill, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_chrome2.RotatedFrame, { rotatedSafeArea: true, passThrough: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PackToasterComponent, {}) }) });
532
+ }
477
533
  function GameProviders({ games, children }) {
478
- return games.reduceRight((inner, game) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(game.Provider, { children: inner }, game.id), children);
534
+ return games.reduceRight((inner, game) => game.Provider ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(game.Provider, { children: inner }, game.id) : inner, children);
535
+ }
536
+ function PreviewColors({ previewColor, children }) {
537
+ const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
538
+ const preview = (0, import_react9.useCallback)((hex) => previewColor(hex, dark), [previewColor, dark]);
539
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_profile3.ProfileColorProvider, { preview, children });
540
+ }
541
+ function PackRoot({ pack, children }) {
542
+ const { Provider, previewColor } = pack;
543
+ const inner = Provider ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Provider, { children }) : children;
544
+ return previewColor ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PreviewColors, { previewColor, children: inner }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: inner });
479
545
  }
480
546
  function RootLayout({ pack, store, persistor }) {
481
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_gesture_handler.GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_safe_area_context.SafeAreaProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PackProvider, { pack, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Fonts, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Providers, { store, persistor, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Theme, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ProfilesProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LastActiveGameProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GameProviders, { games: pack.games, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GatedApp, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OrientationBridge, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(HapticGatedToastProvider, { children: [
547
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_gesture_handler.GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_safe_area_context.SafeAreaProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PackProvider, { pack, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Fonts, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Providers, { store, persistor, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Theme, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ProfilesProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LastActiveGameProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GameProviders, { games: pack.games, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PackRoot, { pack, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GatedApp, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(OrientationBridge, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(HapticGatedToastProvider, { children: [
482
548
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GuideHost, { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AppStack, {}) }),
483
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RotatingToaster, {}),
549
+ pack.Toaster ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PackToaster, { Toaster: pack.Toaster }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RotatingToaster, {}),
484
550
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UpdateDialog, {})
485
- ] }) }) }) }) }) }) }) }) }) }) }) });
551
+ ] }) }) }) }) }) }) }) }) }) }) }) }) });
486
552
  }
487
553
  var styles = import_react_native2.StyleSheet.create({
488
554
  root: { flex: 1 }
@@ -492,10 +558,17 @@ var styles = import_react_native2.StyleSheet.create({
492
558
  var import_expo_router5 = require("expo-router");
493
559
  var import_react14 = require("react");
494
560
 
561
+ // src/routes.ts
562
+ var playHref = (game) => ({ pathname: "/play/[game]", params: { game } });
563
+ var loadoutHref = (game) => ({ pathname: "/loadout/[game]", params: { game } });
564
+ var STATS_HREF = "/stats";
565
+ var PROFILES_HREF = "/profiles";
566
+
495
567
  // src/screens/LoadoutScreen.tsx
496
568
  var import_auto_paper4 = require("@rific/auto-paper");
497
569
  var import_core10 = require("@tastic/core");
498
- var import_hud5 = require("@tastic/hud");
570
+ var import_chrome4 = require("@tastic/hud/chrome");
571
+ var import_lobby2 = require("@tastic/hud/lobby");
499
572
  var import_split_screen = require("@tastic/split-screen");
500
573
  var import_expo_router4 = require("expo-router");
501
574
  var import_react13 = require("react");
@@ -505,8 +578,8 @@ var import_react_native_safe_area_context2 = require("react-native-safe-area-con
505
578
  var import_react_redux9 = require("react-redux");
506
579
 
507
580
  // src/components/SeatTwoPanel.tsx
508
- var import_hud3 = require("@tastic/hud");
509
- var import_profile3 = require("@tastic/profile");
581
+ var import_lobby = require("@tastic/hud/lobby");
582
+ var import_profile4 = require("@tastic/profile");
510
583
  var import_react10 = require("react");
511
584
  var import_react_native3 = require("react-native");
512
585
  var import_jsx_runtime10 = require("react/jsx-runtime");
@@ -549,14 +622,14 @@ function SeatTwoPanel({ idPrefix, host, choices, seatTwo, onSeatTwoChange, cpuDi
549
622
  };
550
623
  const colorSection = seatTwo === "nobody" ? void 0 : { value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, caption: selectedProfile ? colorCaption : void 0 };
551
624
  const pickerId = getSeatTwoPopoverId(idPrefix);
552
- const size = (0, import_profile3.getOptionPickerContentSize)(options.length, colorSection);
553
- const align = (0, import_hud3.useZoneClampedAlign)(host.openId === pickerId, size.width, size.height);
625
+ const size = (0, import_profile4.getOptionPickerContentSize)(options.length, colorSection);
626
+ const align = (0, import_lobby.useZoneClampedAlign)(host.openId === pickerId, size.width, size.height);
554
627
  const ownPopoverOpen = host.openId?.startsWith(`${idPrefix}-`) ?? false;
555
628
  const pickerRowOpen = secondPickerId !== void 0 && host.openId === secondPickerId;
556
629
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native3.View, { style: [styles2.panel, ownPopoverOpen && styles2.panelOpen], children: [
557
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_profile3.OptionPicker, { id: pickerId, host, options, value, onChange: handleChange, color, dark, icon, colorSection, alignOverride: align }),
630
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_profile4.OptionPicker, { id: pickerId, host, options, value, onChange: handleChange, color, dark, icon, colorSection, alignOverride: align }),
558
631
  isPerson && secondPicker && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native3.View, { style: [styles2.pickerRow, pickerRowOpen && styles2.pickerRowOpen], children: secondPicker }),
559
- isPerson && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_hud3.ReadyButton, { color, ready: ready ?? false, onToggleReady })
632
+ isPerson && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lobby.ReadyButton, { color, ready: ready ?? false, onToggleReady })
560
633
  ] });
561
634
  }
562
635
  var styles2 = import_react_native3.StyleSheet.create({
@@ -582,13 +655,14 @@ var styles2 = import_react_native3.StyleSheet.create({
582
655
 
583
656
  // src/components/SettingsDialog.tsx
584
657
  var import_toaster3 = require("@rific/toaster");
585
- var import_hud4 = require("@tastic/hud");
658
+ var import_chrome3 = require("@tastic/hud/chrome");
586
659
  var import_jsx_runtime11 = require("react/jsx-runtime");
587
660
  function SettingsDialog({ visible, onDismiss, rotation = 0, onShowHowToPlay, children }) {
588
661
  const onUpdateError = (0, import_toaster3.useUpdateErrorToast)();
589
662
  const { settings, setSettings } = useAppSettings();
590
- const { otaVersion, SettingsSections } = usePack();
591
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_hud4.BaseSettingsDialog, { visible, onDismiss, rotation, version: otaVersion, lockOrientation: settings.lockOrientation, onLockOrientationChange: (value) => setSettings({ lockOrientation: value }), deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }), onUpdateError, onShowHowToPlay, children: [
663
+ const { otaVersion, SettingsSections, edgeGuard = true } = usePack();
664
+ const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }) } : {};
665
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_chrome3.BaseSettingsDialog, { visible, onDismiss, rotation, version: otaVersion, lockOrientation: settings.lockOrientation, onLockOrientationChange: (value) => setSettings({ lockOrientation: value }), ...edgeGuardProps, onUpdateError, onShowHowToPlay, children: [
592
666
  children,
593
667
  SettingsSections && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SettingsSections, {})
594
668
  ] });
@@ -617,29 +691,33 @@ function useOpponent() {
617
691
 
618
692
  // src/hooks/useSeatColors.ts
619
693
  var import_auto_paper3 = require("@rific/auto-paper");
620
- var import_profile5 = require("@tastic/profile");
694
+ var import_profile6 = require("@tastic/profile");
621
695
  var import_expo_router2 = require("expo-router");
622
696
  var import_react12 = require("react");
623
697
  var import_react_redux8 = require("react-redux");
624
698
 
625
699
  // src/redux/seatColorsSlice.ts
626
- var import_profile4 = require("@tastic/profile");
700
+ var import_profile5 = require("@tastic/profile");
627
701
  function defaultSeatColorsState(seatColors) {
628
702
  return { lastGuestColor: { 1: seatColors[1], 2: seatColors[2] }, lastCpuColor: seatColors[2], profileColors: {} };
629
703
  }
630
704
  function createSeatColorsReducer(seatColors) {
631
- return (0, import_profile4.createSeatColorsSlice)("pack", defaultSeatColorsState(seatColors)).reducer;
705
+ return (0, import_profile5.createSeatColorsSlice)("pack", defaultSeatColorsState(seatColors)).reducer;
632
706
  }
633
- var seatColorsActions = (0, import_profile4.createSeatColorsSlice)("pack", defaultSeatColorsState({ 1: "", 2: "" })).actions;
707
+ var seatColorsActions = (0, import_profile5.createSeatColorsSlice)("pack", defaultSeatColorsState({ 1: "", 2: "" })).actions;
634
708
 
635
709
  // src/hooks/useSeatColors.ts
710
+ function sameTheme(current, next) {
711
+ const a = current.color;
712
+ const b = next.color;
713
+ const sameColor = typeof a === "string" || typeof b === "string" ? typeof a === typeof b && String(a).toLowerCase() === String(b).toLowerCase() : !!a && !!b && a.primary.toLowerCase() === b.primary.toLowerCase() && a.secondary.toLowerCase() === b.secondary.toLowerCase();
714
+ return sameColor && (next.harmony === void 0 || next.harmony === current.harmony);
715
+ }
636
716
  function useSeatColors(p2IsHuman) {
637
- const { colors: themeColors } = (0, import_auto_paper3.useAutoPaperTheme)();
638
- const { set: setThemeColor } = (0, import_auto_paper3.useThemeSettings)();
639
- const p1Color = themeColors.primary;
640
- const p2Color = themeColors.secondary;
717
+ const { dark, colors: themeColors } = (0, import_auto_paper3.useAutoPaperTheme)();
718
+ const { settings: themeSettings, set: setThemeColor } = (0, import_auto_paper3.useThemeSettings)();
641
719
  const { profiles, lastSelected } = useProfiles();
642
- const { seatColors } = usePack();
720
+ const { seatColors, seatTheme } = usePack();
643
721
  const seatDefaultColors = [seatColors[1], seatColors[2]];
644
722
  const dispatch = (0, import_react_redux8.useDispatch)();
645
723
  const guestColors = (0, import_react_redux8.useSelector)((state) => state.seatColors.lastGuestColor);
@@ -647,11 +725,21 @@ function useSeatColors(p2IsHuman) {
647
725
  const profileColors = (0, import_react_redux8.useSelector)((state) => state.seatColors.profileColors);
648
726
  const p1Profile = profiles.find((p) => p.id === lastSelected[1]) ?? null;
649
727
  const p2Profile = p2IsHuman ? profiles.find((p) => p.id === lastSelected[2]) ?? null : null;
728
+ const rememberedColors = (p1Prof, p2Prof, picks, guest, cpu) => {
729
+ const p1 = p1Prof ? (0, import_profile6.profileSeatColor)(p1Prof, picks) : guest[1];
730
+ const p2 = (0, import_profile6.avoidSeatColorClash)(p1, p2Prof ? (0, import_profile6.profileSeatColor)(p2Prof, picks) : p2IsHuman ? guest[2] : cpu, seatDefaultColors);
731
+ return { p1, p2 };
732
+ };
733
+ const remembered = rememberedColors(p1Profile, p2Profile, profileColors, guestColors, cpuColor);
734
+ const p1Color = seatTheme ? remembered.p1 : themeColors.primary;
735
+ const p2Color = seatTheme ? remembered.p2 : themeColors.secondary;
736
+ const themeFor = (0, import_react12.useCallback)((p1, p2) => seatTheme ? seatTheme({ 1: p1, 2: p2 }, dark) : { color: { primary: p1, secondary: p2, tertiary: (0, import_auto_paper3.getThirdColor)(p1, p2) } }, [seatTheme, dark]);
650
737
  const p1ProfileRef = (0, import_react12.useRef)(p1Profile);
651
738
  const p2ProfileRef = (0, import_react12.useRef)(p2Profile);
652
739
  const guestColorsRef = (0, import_react12.useRef)(guestColors);
653
740
  const cpuColorRef = (0, import_react12.useRef)(cpuColor);
654
741
  const profileColorsRef = (0, import_react12.useRef)(profileColors);
742
+ const themeForRef = (0, import_react12.useRef)(themeFor);
655
743
  (0, import_react12.useEffect)(() => {
656
744
  p1ProfileRef.current = p1Profile;
657
745
  }, [p1Profile]);
@@ -668,21 +756,17 @@ function useSeatColors(p2IsHuman) {
668
756
  profileColorsRef.current = profileColors;
669
757
  }, [profileColors]);
670
758
  (0, import_react12.useEffect)(() => {
671
- const nextP1 = p1Profile ? (0, import_profile5.profileSeatColor)(p1Profile, profileColors) : guestColors[1];
672
- const nextP2 = (0, import_profile5.avoidSeatColorClash)(nextP1, p2Profile ? (0, import_profile5.profileSeatColor)(p2Profile, profileColors) : p2IsHuman ? guestColors[2] : cpuColor, seatDefaultColors);
673
- if (nextP1.toLowerCase() === p1Color.toLowerCase() && nextP2.toLowerCase() === p2Color.toLowerCase()) return;
674
- setThemeColor({ color: { primary: nextP1, secondary: nextP2, tertiary: (0, import_auto_paper3.getThirdColor)(nextP1, nextP2) } });
675
- }, [lastSelected[1], lastSelected[2], p2IsHuman, guestColors, cpuColor, profileColors, profiles.length]);
759
+ themeForRef.current = themeFor;
760
+ }, [themeFor]);
761
+ (0, import_react12.useEffect)(() => {
762
+ const next = themeFor(remembered.p1, remembered.p2);
763
+ if (sameTheme(themeSettings, next)) return;
764
+ setThemeColor(next);
765
+ }, [lastSelected[1], lastSelected[2], p2IsHuman, guestColors, cpuColor, profileColors, profiles.length, dark]);
676
766
  (0, import_expo_router2.useFocusEffect)(
677
767
  (0, import_react12.useCallback)(() => {
678
- const guest = guestColorsRef.current;
679
- const cpu = cpuColorRef.current;
680
- const p1Prof = p1ProfileRef.current;
681
- const p2Prof = p2ProfileRef.current;
682
- const picks = profileColorsRef.current;
683
- const nextP1 = p1Prof ? (0, import_profile5.profileSeatColor)(p1Prof, picks) : guest[1];
684
- const nextP2 = (0, import_profile5.avoidSeatColorClash)(nextP1, p2Prof ? (0, import_profile5.profileSeatColor)(p2Prof, picks) : p2IsHuman ? guest[2] : cpu, seatDefaultColors);
685
- setThemeColor({ color: { primary: nextP1, secondary: nextP2, tertiary: (0, import_auto_paper3.getThirdColor)(nextP1, nextP2) } });
768
+ const next = rememberedColors(p1ProfileRef.current, p2ProfileRef.current, profileColorsRef.current, guestColorsRef.current, cpuColorRef.current);
769
+ setThemeColor(themeForRef.current(next.p1, next.p2));
686
770
  }, [lastSelected[1], lastSelected[2], p2IsHuman, setThemeColor])
687
771
  );
688
772
  const persistSeatColor = (0, import_react12.useCallback)(
@@ -704,21 +788,21 @@ function useSeatColors(p2IsHuman) {
704
788
  (hex) => {
705
789
  const swapped = hex.toLowerCase() === p2Color.toLowerCase();
706
790
  const nextP2 = swapped ? p1Color : p2Color;
707
- setThemeColor({ color: { primary: hex, secondary: nextP2, tertiary: (0, import_auto_paper3.getThirdColor)(hex, nextP2) } });
791
+ setThemeColor(themeFor(hex, nextP2));
708
792
  persistSeatColor(1, hex);
709
793
  if (swapped) persistSeatColor(2, nextP2);
710
794
  },
711
- [setThemeColor, p1Color, p2Color, persistSeatColor]
795
+ [setThemeColor, themeFor, p1Color, p2Color, persistSeatColor]
712
796
  );
713
797
  const handleP2ColorChange = (0, import_react12.useCallback)(
714
798
  (hex) => {
715
799
  const swapped = hex.toLowerCase() === p1Color.toLowerCase();
716
800
  const nextP1 = swapped ? p2Color : p1Color;
717
- setThemeColor({ color: { primary: nextP1, secondary: hex, tertiary: (0, import_auto_paper3.getThirdColor)(nextP1, hex) } });
801
+ setThemeColor(themeFor(nextP1, hex));
718
802
  persistSeatColor(2, hex);
719
803
  if (swapped) persistSeatColor(1, nextP1);
720
804
  },
721
- [setThemeColor, p1Color, p2Color, persistSeatColor]
805
+ [setThemeColor, themeFor, p1Color, p2Color, persistSeatColor]
722
806
  );
723
807
  return { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange };
724
808
  }
@@ -754,6 +838,7 @@ function LoadoutScreen({ game }) {
754
838
  const fixedSeats = p2IsHuman && (!!game.twoPlayerSeats?.faceToFaceOnWeb || import_react_native4.Platform.OS !== "web");
755
839
  const fixedUpsideDown = !!game.twoPlayerSeats?.followUpsideDown && orientationMode === "faceToFace" && upsideDown;
756
840
  const { panelLayout, panelFadeStyle } = (0, import_split_screen.useDualZoneLayout)(fixedSeats ? "faceToFace" : orientationMode, fixedSeats ? true : p1OnRight, fixedSeats || p1OnRightResolved, fixedSeats ? fixedUpsideDown : upsideDown, PANEL_SWAP_FADE_MS);
841
+ const playersRowFade = panelFadeStyle;
757
842
  const [settingsOpen, setSettingsOpen] = (0, import_react13.useState)(false);
758
843
  const rotation = (0, import_core10.getViewRotation)(panelLayout.orientationMode, panelLayout.p1OnRight, panelLayout.upsideDown);
759
844
  const insets = (0, import_core10.rotateInsets)((0, import_react_native_safe_area_context2.useSafeAreaInsets)(), rotation);
@@ -782,7 +867,7 @@ function LoadoutScreen({ game }) {
782
867
  (0, import_react13.useEffect)(() => {
783
868
  if (!humanSeats.every((s) => ready[s])) return;
784
869
  dispatch(liveplayActions.setActiveRound({ game: game.id, mode, settings: roundSettingsRef.current }));
785
- import_expo_router4.router.push({ pathname: "/play/[game]", params: { game: game.id } });
870
+ import_expo_router4.router.push(playHref(game.id));
786
871
  }, [ready, humanSeats, game.id, mode, dispatch]);
787
872
  const bg = dark ? "#000000" : "#FFFFFF";
788
873
  const fg = dark ? "#FFFFFF" : "#000000";
@@ -791,8 +876,8 @@ function LoadoutScreen({ game }) {
791
876
  const showMetaInSharedBand = (0, import_split_screen.needsSharedNeutralZone)(panelLayout.orientationMode, humanSeats.length);
792
877
  const p2ZoneStyle = p2IsHuman ? isPortrait ? styles3.p2ZoneTop : panelLayout.p1OnRight ? styles3.p2ZoneLeft : styles3.p2ZoneRight : null;
793
878
  const showReadyButton = p2IsHuman;
794
- const controlsHost = (0, import_hud5.usePopoverHost)();
795
- const p2PanelHost = (0, import_hud5.usePopoverHost)();
879
+ const controlsHost = (0, import_chrome4.usePopoverHost)();
880
+ const p2PanelHost = (0, import_chrome4.usePopoverHost)();
796
881
  const p2Host = p2IsHuman ? p2PanelHost : controlsHost;
797
882
  const anyPlayerPickerOpen = !!(controlsHost.openId?.startsWith("p1-") || controlsHost.openId?.startsWith("p2-"));
798
883
  const optionsRow = loadout.options({
@@ -807,17 +892,17 @@ function LoadoutScreen({ game }) {
807
892
  onReset: showMetaInSharedBand ? void 0 : loadout.reset
808
893
  });
809
894
  const optionsPopoverOpen = controlsHost.openId !== null && loadout.optionIds.includes(controlsHost.openId);
810
- const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.SharedActionBand, { onBack: import_core8.safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
895
+ const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lobby2.SharedActionBand, { onBack: import_core8.safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
811
896
  const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark, rotation };
812
897
  const p1Control = loadout.seatControl(1, controlsHost, { ...controlLook, seatColor: p1Color });
813
- const p1Panel = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: import_auto_paper4.defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), dark, showReadyButton, secondPicker: p1Control, secondPickerId: p1Control ? "p1-control" : void 0, profiles, selectedProfileId: lastSelected[1], takenProfileId: p2IsHuman ? lastSelected[2] : null, guestLabel: "P1", onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => import_expo_router4.router.push("/profiles") });
898
+ const p1Panel = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lobby2.PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: import_auto_paper4.defaultColors, takenColor: p2Exists ? p2Color : void 0, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, isHuman: true, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), dark, showReadyButton, secondPicker: p1Control, secondPickerId: p1Control ? "p1-control" : void 0, profiles, selectedProfileId: lastSelected[1], takenProfileId: p2IsHuman ? lastSelected[2] : null, guestLabel: "P1", onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => import_expo_router4.router.push(PROFILES_HREF) });
814
899
  const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Color }) : void 0;
815
900
  const p2Panel = game.seatTwo && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SeatTwoPanel, { idPrefix: "p2", host: p2Host, choices: game.seatTwo, seatTwo, onSeatTwoChange: changeSeatTwo, cpuDifficulty: loadout.cpuDifficulty, profiles, selectedProfileId: lastSelected[2], onProfileSelect: handleP2ProfileSelect, color: p2Color, onColorChange: handleP2ColorChange, swatches: import_auto_paper4.defaultColors, takenColor: p1Color, allowSwapTaken: mode === "vsCpu", colorCaption: pack.colorCaption, dark, secondPicker: p2Control, secondPickerId: p2Control ? "p2-control" : void 0, ready: ready[2], onToggleReady: () => setReady((r) => ({ ...r, 2: !r[2] })), showReadyButton });
816
901
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native4.View, { style: [styles3.container, { backgroundColor: bg }], children: [
817
902
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_core10.FakeLandscapeView, { orientationMode: panelLayout.orientationMode, p1OnRight: panelLayout.p1OnRight, upsideDown: panelLayout.upsideDown, style: styles3.rotatable, children: [
818
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
819
- p2ZoneStyle && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
820
- !showMetaInSharedBand && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.CornerActionButtons, { onBack: import_core8.safeBack, onSettings: () => setSettingsOpen(true), fg, insets }),
903
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_chrome4.PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
904
+ p2ZoneStyle && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_chrome4.PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
905
+ !showMetaInSharedBand && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_chrome4.CornerActionButtons, { onBack: import_core8.safeBack, onSettings: () => setSettingsOpen(true), fg, insets }),
821
906
  p2IsHuman ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
822
907
  import_split_screen.DualZoneLayout,
823
908
  {
@@ -832,11 +917,11 @@ function LoadoutScreen({ game }) {
832
917
  }
833
918
  ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native4.View, { style: styles3.stackedZone, children: [
834
919
  sharedControls,
835
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native_reanimated2.default.View, { style: [styles3.playersRow, anyPlayerPickerOpen && styles3.playersRowOpen, panelFadeStyle], children: [
920
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native_reanimated2.default.View, { style: [styles3.playersRow, anyPlayerPickerOpen && styles3.playersRowOpen, playersRowFade], children: [
836
921
  !isPortrait && panelLayout.p1OnRight ? p2Panel : p1Panel,
837
922
  !isPortrait && panelLayout.p1OnRight ? p1Panel : p2Panel
838
923
  ] }),
839
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_hud5.ReadyButton, { color: p1Color, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), style: anyPlayerPickerOpen && styles3.readyButtonHidden })
924
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lobby2.ReadyButton, { color: p1Color, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), style: anyPlayerPickerOpen && styles3.readyButtonHidden })
840
925
  ] })
841
926
  ] }),
842
927
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation, onShowHowToPlay: () => openFor(game.id) })
@@ -902,7 +987,7 @@ function LoadoutRoute() {
902
987
  const { game: id } = (0, import_expo_router5.useLocalSearchParams)();
903
988
  const game = usePackGame(id);
904
989
  if (!game) return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_expo_router5.Redirect, { href: "/" });
905
- if (!hasLoadout(game)) return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_expo_router5.Redirect, { href: { pathname: "/play/[game]", params: { game: game.id } } });
990
+ if (!hasLoadout(game)) return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_expo_router5.Redirect, { href: playHref(game.id) });
906
991
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(LoadoutScreen, { game }, game.id);
907
992
  }
908
993
  function hasLoadout(game) {
@@ -929,8 +1014,8 @@ function useResumePoint(gameId) {
929
1014
  var import_auto_paper5 = require("@rific/auto-paper");
930
1015
  var import_feedback_press3 = require("@rific/feedback-press");
931
1016
  var import_core12 = require("@tastic/core");
932
- var import_hud6 = require("@tastic/hud");
933
- var import_profile6 = require("@tastic/profile");
1017
+ var import_chrome5 = require("@tastic/hud/chrome");
1018
+ var import_profile7 = require("@tastic/profile");
934
1019
  var import_expo_router6 = require("expo-router");
935
1020
  var import_react16 = require("react");
936
1021
  var import_react_native5 = require("react-native");
@@ -1018,11 +1103,15 @@ var import_jsx_runtime14 = require("react/jsx-runtime");
1018
1103
  function HomeScreen() {
1019
1104
  const pack = usePack();
1020
1105
  const { consumeResumeGame } = useLastActiveGame();
1106
+ const { autoShowPack } = useGameGuide();
1021
1107
  const [resumeGame] = (0, import_react16.useState)(() => {
1022
1108
  const id = consumeResumeGame();
1023
1109
  return pack.games.find((g) => g.id === id && g.resumable) ?? null;
1024
1110
  });
1025
- if (resumeGame) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_expo_router6.Redirect, { href: { pathname: "/play/[game]", params: { game: resumeGame.id } } });
1111
+ (0, import_react16.useEffect)(() => {
1112
+ if (!resumeGame) autoShowPack();
1113
+ }, [resumeGame, autoShowPack]);
1114
+ if (resumeGame) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_expo_router6.Redirect, { href: playHref(resumeGame.id) });
1026
1115
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HomeMenu, {});
1027
1116
  }
1028
1117
  function HomeMenu() {
@@ -1030,9 +1119,10 @@ function HomeMenu() {
1030
1119
  const rotation = (0, import_core12.useRotation)();
1031
1120
  const layout = useHomeLayout();
1032
1121
  const { insets } = layout;
1033
- const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
1034
- const { openFirstTime, openFor } = useGameGuide();
1122
+ const { dark, colors } = (0, import_auto_paper5.useAutoPaperTheme)();
1123
+ const { openFirstTime, openFor, openPack } = useGameGuide();
1035
1124
  const [settingsOpen, setSettingsOpen] = (0, import_react16.useState)(false);
1125
+ const [rulesGame, setRulesGame] = (0, import_react16.useState)(null);
1036
1126
  const isFocused = (0, import_expo_router6.useIsFocused)();
1037
1127
  const [appState, setAppState] = (0, import_react16.useState)(import_react_native5.AppState.currentState);
1038
1128
  (0, import_react16.useEffect)(() => {
@@ -1043,7 +1133,7 @@ function HomeMenu() {
1043
1133
  const { profiles, lastSelected, selectProfile } = useProfiles();
1044
1134
  const { seatTwo } = useOpponent();
1045
1135
  const { p1Color, p2Color, handleP1ColorChange } = useSeatColors(seatTwo === "person");
1046
- const pickerHost = (0, import_hud6.usePopoverHost)();
1136
+ const pickerHost = (0, import_chrome5.usePopoverHost)();
1047
1137
  const selectedProfile = profiles.find((p) => p.id === lastSelected[1]) ?? null;
1048
1138
  const tiled = pack.games.some((game) => game.Preview);
1049
1139
  const isWide = useFrozenWhileHidden(tiled && layout.isWide, !isFocused);
@@ -1056,65 +1146,85 @@ function HomeMenu() {
1056
1146
  const closePicker = pickerHost.close;
1057
1147
  (0, import_react16.useEffect)(() => {
1058
1148
  closePicker();
1059
- }, [isWide, closePicker]);
1060
- const bg = dark ? "#000000" : "#FFFFFF";
1061
- const fg = dark ? "#FFFFFF" : "#000000";
1062
- const openGame = (0, import_react16.useCallback)((game) => openFirstTime(game.id, () => import_expo_router6.router.push(game.useLoadout ? { pathname: "/loadout/[game]", params: { game: game.id } } : { pathname: "/play/[game]", params: { game: game.id } })), [openFirstTime]);
1063
- const picker = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_profile6.ProfilePicker, { idPrefix: "home-p1", host: pickerHost, profiles, selectedId: lastSelected[1], color: p1Color, dark, guestLabel: "P1", onSelect: (profile) => selectProfile(1, profile?.id ?? null), onManage: () => import_expo_router6.router.push("/profiles"), colorSection: { value: p1Color, onChange: handleP1ColorChange, swatches: import_auto_paper5.defaultColors, caption: selectedProfile ? pack.colorCaption : void 0 } });
1149
+ }, [isWide, rotation, layout.windowWidth, layout.windowHeight, closePicker]);
1150
+ const bg = pack.Backdrop ? "transparent" : dark ? "#000000" : "#FFFFFF";
1151
+ const fg = pack.foreground ?? (dark ? "#FFFFFF" : "#000000");
1152
+ const { previewColor } = pack;
1153
+ const p1Drawn = previewColor ? previewColor(p1Color, dark) : p1Color;
1154
+ const triggerStyle = (0, import_react16.useMemo)(() => pack.Backdrop ? [styles4.pill, { backgroundColor: colors.surface }] : void 0, [pack.Backdrop, colors.surface]);
1155
+ const openGame = (0, import_react16.useCallback)((game) => openFirstTime(game.id, () => import_expo_router6.router.push(game.useLoadout ? loadoutHref(game.id) : playHref(game.id))), [openFirstTime]);
1156
+ const openRules = (0, import_react16.useCallback)((game) => game.Rules ? setRulesGame(game) : openFor(game.id), [openFor]);
1157
+ const colorSection = { value: p1Color, onChange: handleP1ColorChange, swatches: import_auto_paper5.defaultColors, caption: selectedProfile ? pack.colorCaption : void 0, previewValue: previewColor ? (hex) => previewColor(hex, dark) : void 0 };
1158
+ const usePickerAlign = pack.usePickerAlign ?? noPickerAlign;
1159
+ const pickerSize = (0, import_profile7.getProfilePickerContentSize)(profiles.length, true, colorSection);
1160
+ const pickerAlign = usePickerAlign(pickerHost.openId === `${SEAT_ONE_PICKER}-profile`, pickerSize.width, pickerSize.height);
1161
+ const picker = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_profile7.ProfilePicker, { idPrefix: SEAT_ONE_PICKER, host: pickerHost, profiles, selectedId: lastSelected[1], color: p1Drawn, dark, guestLabel: pack.guestLabel ?? "P1", nullLabel: pack.nullLabel, onSelect: (profile) => selectProfile(1, profile?.id ?? null), onManage: () => import_expo_router6.router.push(PROFILES_HREF), colorSection, alignOverride: pickerAlign, triggerStyle });
1162
+ const pressAway = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_chrome5.PressAwayOverlay, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
1163
+ const RulesSheet = rulesGame?.Rules;
1064
1164
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.View, { style: [styles4.container, { backgroundColor: bg }], children: [
1065
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_core12.FakeLandscapeView, { style: styles4.rotatable, children: [
1066
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_hud6.PressAwayOverlay, { active: pickerHost.openId !== null, onPress: pickerHost.close }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_core12.FakeLandscapeView, { style: styles4.rotatable, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
1067
1166
  isWide ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1068
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.ScrollView, { style: [styles4.wideRightPane, { left: layout.paddingLeft + layout.leftPaneWidth + HOME_WIDE_PANE_GAP }], contentContainerStyle: { paddingTop: 16 + insets.top, paddingBottom: 16 + insets.bottom, paddingRight: layout.paddingRight }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameTile, { game, width: layout.tileWidth, previewWidth: layout.previewWidth, previewRevealed: previewsRevealed, dark, p1Color, p2Color, onPress: () => openGame(game), onRules: () => openFor(game.id) }, game.id)) }) }),
1167
+ pressAway,
1168
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_auto_paper5.FadingScrollView, { style: [styles4.wideRightPane, { left: layout.paddingLeft + layout.leftPaneWidth + HOME_WIDE_PANE_GAP }], contentContainerStyle: { paddingTop: 16 + insets.top, paddingBottom: 16 + insets.bottom, paddingRight: layout.paddingRight }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameTile, { game, width: layout.tileWidth, previewWidth: layout.previewWidth, previewRevealed: previewsRevealed, dark, p1Color, p2Color, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) }) }),
1069
1169
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.View, { style: [styles4.wideLeftPane, { left: layout.paddingLeft, width: layout.leftPaneWidth, paddingTop: HOME_TOP_BAND + insets.top, paddingBottom: 16 + insets.bottom }, pickerHost.openId !== null && styles4.pickerOpen], children: [
1070
1170
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive, fit: layout.heroFit }),
1071
1171
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: styles4.widePicker, children: picker })
1072
1172
  ] })
1073
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.ScrollView, { style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
1173
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_auto_paper5.FadingScrollView, { style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
1174
+ pressAway,
1074
1175
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive }),
1075
1176
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.picker, pickerHost.openId !== null && styles4.pickerOpen], children: picker }),
1076
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: styles4.list, children: pack.games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameRow, { game, dark, p1Color, p2Color, previewRevealed: previewsRevealed, onPress: () => openGame(game), onRules: () => openFor(game.id) }, game.id)) })
1177
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: styles4.list, children: pack.games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameRow, { game, dark, p1Color, p2Color, previewRevealed: previewsRevealed, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
1077
1178
  ] }),
1078
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.IconButton, { icon: "trophy", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, left: 8 + insets.left }], onPress: () => import_expo_router6.router.push("/stats"), accessibilityLabel: "Stats & Achievements" }),
1079
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, right: 8 + insets.right }], onPress: () => setSettingsOpen(true), accessibilityLabel: "Settings" })
1080
- ] }),
1081
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation })
1179
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.IconButton, { icon: "trophy", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, left: 8 + insets.left }], onPress: () => import_expo_router6.router.push(STATS_HREF), accessibilityLabel: "Stats & Achievements" }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, right: 8 + insets.right }], onPress: () => setSettingsOpen(true), accessibilityLabel: "Settings" }),
1181
+ RulesSheet && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RulesSheet, { visible: true, onDismiss: () => setRulesGame(null) })
1182
+ ] }) }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation, onShowHowToPlay: pack.guide ? openPack : void 0 })
1082
1184
  ] });
1083
1185
  }
1186
+ var SEAT_ONE_PICKER = "home-p1";
1187
+ var noPickerAlign = () => void 0;
1188
+ function hasRules(game) {
1189
+ return !!(game.Rules || game.guide);
1190
+ }
1191
+ function RulesHost({ enabled, children }) {
1192
+ return enabled ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Portal.Host, { children }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children });
1193
+ }
1084
1194
  function GameRow({ game, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1085
- const well = (0, import_hud6.useWellColors)();
1195
+ const well = (0, import_chrome5.useWellColors)();
1086
1196
  const GameIcon = game.icon;
1087
1197
  return (
1088
1198
  // The rules button sits beside the row's own button, laid over its end, not inside it: a button
1089
1199
  // inside a button is invalid on the web.
1090
1200
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.View, { children: [
1091
1201
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_feedback_press3.Pressable, { onPress, style: [styles4.row, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
1092
- game.Preview ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.rowIcon, { backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameIcon, { size: ROW_ICON_SIZE, p1Color, p2Color, dark }) }),
1202
+ game.Preview ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.rowIcon, { backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: GameIcon && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameIcon, { size: ROW_ICON_SIZE, p1Color, p2Color, dark }) }),
1093
1203
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.View, { style: styles4.rowText, children: [
1094
1204
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Text, { variant: "titleLarge", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
1095
1205
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Text, { variant: "bodyMedium", style: { color: well.fgMuted }, numberOfLines: 2, children: game.blurb })
1096
1206
  ] })
1097
1207
  ] }),
1098
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.Pressable, { onPress: onRules, style: styles4.rulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 22, color: well.fgMuted }) })
1208
+ onRules && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.Pressable, { onPress: onRules, style: styles4.rulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 22, color: well.fgMuted }) })
1099
1209
  ] })
1100
1210
  );
1101
1211
  }
1102
1212
  function GameTile({ game, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1103
- const well = (0, import_hud6.useWellColors)();
1213
+ const well = (0, import_chrome5.useWellColors)();
1104
1214
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native5.View, { style: { width }, children: [
1105
1215
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_feedback_press3.Pressable, { onPress, style: [styles4.tile, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
1106
1216
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PreviewBox, { game, width: previewWidth, revealed: previewRevealed, dark, p1Color, p2Color }),
1107
1217
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Text, { variant: "titleMedium", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
1108
1218
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Text, { variant: "bodySmall", style: { color: well.fgMuted }, numberOfLines: 3, children: game.blurb })
1109
1219
  ] }),
1110
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.Pressable, { onPress: onRules, style: styles4.tileRulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 20, color: "#FFFFFF" }) })
1220
+ onRules && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_feedback_press3.Pressable, { onPress: onRules, style: styles4.tileRulesButton, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: `${game.title} rules`, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 20, color: "#FFFFFF" }) })
1111
1221
  ] });
1112
1222
  }
1113
1223
  function PreviewBox({ game, width, revealed, dark, p1Color, p2Color }) {
1114
1224
  const height = Math.round(width / PREVIEW_ASPECT);
1115
1225
  const { Preview, icon: GameIcon } = game;
1116
1226
  const iconSize = Math.min(height, ROW_ICON_SIZE);
1117
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.preview, { width, height, backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: Preview ? revealed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Preview, { width, height, p1Color, p2Color, dark }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.previewIcon, { width: iconSize, height: iconSize, borderRadius: iconSize * 0.22 }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameIcon, { size: iconSize, p1Color, p2Color, dark }) }) });
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.preview, { width, height, backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: Preview ? revealed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Preview, { width, height, p1Color, p2Color, dark }) : GameIcon && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native5.View, { style: [styles4.previewIcon, { width: iconSize, height: iconSize, borderRadius: iconSize * 0.22 }], children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(GameIcon, { size: iconSize, p1Color, p2Color, dark }) }) });
1118
1228
  }
1119
1229
  var ROW_ICON_SIZE = 56;
1120
1230
  var ICON_LIGHT_BG = "#FAFAFA";
@@ -1149,6 +1259,15 @@ var styles4 = import_react_native5.StyleSheet.create({
1149
1259
  pickerOpen: {
1150
1260
  zIndex: 100
1151
1261
  },
1262
+ // Seat 1's button on the theme's surface (over a backdrop), the same pill as the tiles' wells.
1263
+ pill: {
1264
+ borderRadius: 999
1265
+ },
1266
+ // Over the rows (and, wide, the grid), whose own stacking is unset, so a press on them reaches it on
1267
+ // the web too, and under the raised button and left pane, so the menu's rows stay pressable.
1268
+ pressAway: {
1269
+ zIndex: 5
1270
+ },
1152
1271
  // The preview's own rounding, clipping whatever it draws to the box.
1153
1272
  preview: {
1154
1273
  alignItems: "center",
@@ -1167,7 +1286,7 @@ var styles4 = import_react_native5.StyleSheet.create({
1167
1286
  row: {
1168
1287
  alignItems: "center",
1169
1288
  borderRadius: 12,
1170
- boxShadow: import_hud6.WELL_SHADOW,
1289
+ boxShadow: import_chrome5.WELL_SHADOW,
1171
1290
  flexDirection: "row",
1172
1291
  gap: 14,
1173
1292
  padding: 14,
@@ -1199,7 +1318,7 @@ var styles4 = import_react_native5.StyleSheet.create({
1199
1318
  // The same well as a row, turned on its side.
1200
1319
  tile: {
1201
1320
  borderRadius: 12,
1202
- boxShadow: import_hud6.WELL_SHADOW,
1321
+ boxShadow: import_chrome5.WELL_SHADOW,
1203
1322
  gap: 6,
1204
1323
  padding: HOME_TILE_PADDING
1205
1324
  },
@@ -1239,66 +1358,109 @@ var styles4 = import_react_native5.StyleSheet.create({
1239
1358
 
1240
1359
  // src/screens/ProfilesRoute.tsx
1241
1360
  var import_auto_paper6 = require("@rific/auto-paper");
1242
- var import_profile7 = require("@tastic/profile");
1361
+ var import_chrome6 = require("@tastic/hud/chrome");
1362
+ var import_profile8 = require("@tastic/profile");
1243
1363
  var import_jsx_runtime15 = require("react/jsx-runtime");
1364
+ var ROSTER_MAX_WIDTH = 640;
1365
+ function useRosterAlign(open, contentWidth, contentHeight) {
1366
+ const { usePickerAlign } = usePack();
1367
+ return usePickerAlign(open, contentWidth, contentHeight, { maxContentWidth: ROSTER_MAX_WIDTH });
1368
+ }
1244
1369
  function ProfilesRoute() {
1245
1370
  const { profiles, createProfile, updateProfile, deleteProfile } = useProfiles();
1246
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_profile7.ProfilesScreen, { profiles, defaultColor: import_auto_paper6.defaultColors[0].value, onCreate: (patch) => createProfile(patch), onSave: (id, patch) => updateProfile(id, patch), onDelete: deleteProfile, onBack: import_core8.safeBack });
1371
+ const { Backdrop, foreground, newProfileColor, usePickerAlign } = usePack();
1372
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_chrome6.RotatedFrame, { rotatedSafeArea: true, portalHost: true, maxContentWidth: ROSTER_MAX_WIDTH, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_profile8.ProfilesScreen, { profiles, defaultColor: newProfileColor ?? import_auto_paper6.defaultColors[0].value, onCreate: (patch) => createProfile(patch), onSave: (id, patch) => updateProfile(id, patch), onDelete: deleteProfile, onBack: import_core8.safeBack, fg: foreground, bg: Backdrop ? "transparent" : void 0, useColorPickerAlign: usePickerAlign ? useRosterAlign : void 0 }) });
1247
1373
  }
1248
1374
 
1249
1375
  // src/screens/StatsScreen.tsx
1250
1376
  var import_auto_paper7 = require("@rific/auto-paper");
1251
- var import_core13 = require("@tastic/core");
1252
- var import_hud7 = require("@tastic/hud");
1253
- var import_profile8 = require("@tastic/profile");
1377
+ var import_chrome7 = require("@tastic/hud/chrome");
1378
+ var import_profile9 = require("@tastic/profile");
1254
1379
  var import_react17 = require("react");
1255
1380
  var import_react_native6 = require("react-native");
1256
1381
  var import_react_native_paper2 = require("react-native-paper");
1257
1382
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
1258
1383
  var import_react_redux10 = require("react-redux");
1259
1384
  var import_jsx_runtime16 = require("react/jsx-runtime");
1385
+ var STATS = "stats";
1386
+ var ACHIEVEMENTS = "achievements";
1260
1387
  var TABS = [
1261
- { value: "stats", label: "Stats", icon: "chart-box-outline" },
1262
- { value: "achievements", label: "Achievements", icon: "trophy-outline" }
1388
+ { value: STATS, label: "Stats", icon: "chart-box-outline" },
1389
+ { value: ACHIEVEMENTS, label: "Achievements", icon: "trophy-outline" }
1263
1390
  ];
1391
+ var LABEL_WIDE_TAB = { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
1392
+ var NO_PACK_TABS = [];
1264
1393
  var EVERYONE = "__everyone__";
1265
1394
  var ALL_GAMES = "__all__";
1266
1395
  var PLAYER_PICKER_ID = "stats-player";
1267
1396
  var GAME_PICKER_ID = "stats-game";
1397
+ var STATS_MAX_WIDTH = 640;
1268
1398
  function StatsScreen() {
1399
+ const pickerHost = (0, import_chrome7.usePopoverHost)();
1400
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatsPressAway, { host: pickerHost, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_chrome7.RotatedFrame, { rotatedSafeArea: true, portalHost: true, maxContentWidth: STATS_MAX_WIDTH, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatsContent, { pickerHost }) }) });
1401
+ }
1402
+ function StatsPressAway({ host, children }) {
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native6.View, { testID: "stats-press-away", style: styles5.screen, onStartShouldSetResponder: () => host.openId !== null, onResponderRelease: host.close, children });
1404
+ }
1405
+ var useNoPackReset = () => null;
1406
+ var noPickerAlign2 = () => void 0;
1407
+ function StatsContent({ pickerHost }) {
1269
1408
  const pack = usePack();
1270
1409
  const { dark, colors } = (0, import_auto_paper7.useAutoPaperTheme)();
1271
- const rotation = (0, import_core13.useRotation)();
1272
- const insets = (0, import_core13.rotateInsets)((0, import_react_native_safe_area_context4.useSafeAreaInsets)(), rotation);
1273
- const { profiles } = useProfiles();
1410
+ const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
1411
+ const { profiles, lastSelected } = useProfiles();
1274
1412
  const dispatch = (0, import_react_redux10.useDispatch)();
1275
- const pickerHost = (0, import_hud7.usePopoverHost)();
1276
- const [tab, setTab] = (0, import_react17.useState)("stats");
1413
+ const fg = pack.foreground ?? colors.onBackground;
1414
+ const [tab, setTab] = (0, import_react17.useState)(STATS);
1277
1415
  const [gameId, setGameId] = (0, import_react17.useState)(ALL_GAMES);
1278
- const [selectedProfileId, setSelectedProfileId] = (0, import_react17.useState)(null);
1279
- const profileId = selectedProfileId && profiles.some((p) => p.id === selectedProfileId) ? selectedProfileId : null;
1416
+ const [selectedProfileId, setSelectedProfileId] = (0, import_react17.useState)(() => pack.guestStats ? lastSelected[1] ?? GUEST_PLAYER_ID : null);
1417
+ const profileId = selectedProfileId && profiles.length > 0 && (pack.guestStats && selectedProfileId === GUEST_PLAYER_ID || profiles.some((p) => p.id === selectedProfileId)) ? selectedProfileId : null;
1280
1418
  const games = gameId === ALL_GAMES ? pack.games : pack.games.filter((g) => g.id === gameId);
1281
- const playerOptions = (0, import_react17.useMemo)(() => [{ value: EVERYONE, label: "Everyone", icon: "account-group" }, ...profiles.map((p) => ({ value: p.id, label: p.name, icon: "account", color: p.color }))], [profiles]);
1419
+ const packTabs = pack.statsTabs ?? NO_PACK_TABS;
1420
+ const packTab = packTabs.find((t) => t.value === tab);
1421
+ const showGamePicker = !(tab === ACHIEVEMENTS && pack.AchievementList);
1422
+ const playerOptions = (0, import_react17.useMemo)(() => [{ value: EVERYONE, label: "Everyone", icon: "account-group" }, ...pack.guestStats ? [{ value: GUEST_PLAYER_ID, label: "Guest", icon: "account-outline" }] : [], ...profiles.map((p) => ({ value: p.id, label: p.name, icon: "account", color: pack.previewColor ? pack.previewColor(p.color, dark) : p.color }))], [profiles, pack, dark]);
1282
1423
  const gameOptions = (0, import_react17.useMemo)(() => [{ value: ALL_GAMES, label: "All Games", icon: "view-grid-outline" }, ...pack.games.map((g) => ({ value: g.id, label: g.title }))], [pack.games]);
1283
- const resetScope = games.length === 1 ? games[0].title : "every game";
1284
- const onReset = () => games.forEach((g) => dispatch(g.resetStats()));
1285
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_core13.FakeLandscapeView, { style: styles5.rotatable, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_hud7.BaseStatsScreen, { title: tab === "stats" ? "Stats" : "Achievements", onBack: import_core8.safeBack, insets, onReset, resetLabel: games.length === 1 ? `Reset ${resetScope} Stats` : "Reset All Stats", resetConfirmTitle: "Reset Stats?", resetConfirmBody: `Erases every player's stats and achievements in ${resetScope}. This cannot be undone.`, rotation, children: [
1286
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_hud7.PressAwayOverlay, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
1287
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper2.SegmentedButtons, { value: tab, onValueChange: (value) => setTab(value), buttons: TABS, style: styles5.tabs }),
1288
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react_native6.View, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], children: [
1289
- profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_profile8.OptionPicker, { id: PLAYER_PICKER_ID, host: pickerHost, options: playerOptions, value: profileId ?? EVERYONE, onChange: (value) => setSelectedProfileId(value === EVERYONE ? null : value), color: colors.primary, dark, icon: profileId ? "account" : "account-group" }),
1290
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_profile8.OptionPicker, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline" })
1424
+ const tabButtons = (0, import_react17.useMemo)(() => packTabs.length > 0 ? [{ value: STATS, label: "Stats" }, ...packTabs.map(({ value, label }) => ({ value, label })), { value: ACHIEVEMENTS, label: "Achievements" }].map((button) => ({ ...button, style: LABEL_WIDE_TAB })) : TABS, [packTabs]);
1425
+ const title = tab === STATS ? "Stats" : tab === ACHIEVEMENTS ? "Achievements" : packTab?.label ?? "";
1426
+ const usePackReset = pack.resetStats?.useReset ?? useNoPackReset;
1427
+ const packReset = usePackReset();
1428
+ const reset = resetFor(pack, packReset, profileId, profiles, games, () => games.forEach((g) => g.resetStats && dispatch(g.resetStats())));
1429
+ const usePickerAlign = pack.usePickerAlign ?? noPickerAlign2;
1430
+ const playerSize = (0, import_profile9.getOptionPickerContentSize)(playerOptions.length);
1431
+ const gameSize = (0, import_profile9.getOptionPickerContentSize)(gameOptions.length);
1432
+ const playerAlign = usePickerAlign(pickerHost.openId === PLAYER_PICKER_ID, playerSize.width, playerSize.height);
1433
+ const gameAlign = usePickerAlign(pickerHost.openId === GAME_PICKER_ID, gameSize.width, gameSize.height);
1434
+ const surface = pack.Backdrop ? { backgroundColor: colors.surface } : void 0;
1435
+ const triggerStyle = pack.Backdrop ? [styles5.pill, surface] : void 0;
1436
+ const PackTabContent = packTab?.Content;
1437
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_chrome7.BaseStatsScreen, { title, onBack: import_core8.safeBack, insets, onReset: reset.onReset, resetLabel: reset.label, resetConfirmTitle: reset.title, resetConfirmBody: reset.body, fg: pack.foreground, bg: pack.Backdrop ? "transparent" : void 0, rotation: 0, children: [
1438
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_chrome7.PressAwayOverlay, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
1439
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper2.SegmentedButtons, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react_native6.View, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], onStartShouldSetResponder: () => pickerHost.openId !== null, children: [
1441
+ profiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_profile9.OptionPicker, { id: PLAYER_PICKER_ID, host: pickerHost, options: playerOptions, value: profileId ?? EVERYONE, onChange: (value) => setSelectedProfileId(value === EVERYONE ? null : value), color: colors.primary, dark, icon: profileId ? "account" : "account-group", alignOverride: playerAlign, triggerStyle }),
1442
+ showGamePicker && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_profile9.OptionPicker, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline", alignOverride: gameAlign, triggerStyle })
1291
1443
  ] }),
1292
- tab === "stats" && pack.StatsSections && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(pack.StatsSections, { profileId, gameId: gameId === ALL_GAMES ? null : gameId }),
1293
- games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GameStats, { game, tab, profileId, showTitle: games.length > 1 }, game.id))
1294
- ] }) });
1444
+ tab === STATS && pack.StatsSections && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(pack.StatsSections, { profileId, gameId: gameId === ALL_GAMES ? null : gameId }),
1445
+ PackTabContent && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PackTabContent, { profileId, gameId: gameId === ALL_GAMES ? null : gameId }),
1446
+ tab === ACHIEVEMENTS && pack.AchievementList ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(pack.AchievementList, { profileId }) : (tab === STATS || tab === ACHIEVEMENTS) && games.map((game) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GameStats, { game, tab, profileId, showTitle: games.length > 1 && pack.statsGameTitles !== false, titleColor: fg }, game.id))
1447
+ ] });
1448
+ }
1449
+ function resetFor(pack, packReset, profileId, profiles, games, resetGames) {
1450
+ if (pack.resetStats && packReset) {
1451
+ const name = profileId === GUEST_PLAYER_ID ? "Guest" : profileId ? profiles.find((p) => p.id === profileId)?.name ?? null : null;
1452
+ return { onReset: () => packReset(profileId), ...pack.resetStats.labels(name) };
1453
+ }
1454
+ const scope = games.length === 1 ? games[0].title : "every game";
1455
+ return { onReset: resetGames, label: games.length === 1 ? `Reset ${scope} Stats` : "Reset All Stats", title: "Reset Stats?", body: `Erases every player's stats and achievements in ${scope}. This cannot be undone.` };
1295
1456
  }
1296
- function GameStats({ game, tab, profileId, showTitle }) {
1297
- const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
1457
+ function GameStats({ game, tab, profileId, showTitle, titleColor }) {
1298
1458
  const { StatsSections, AchievementList } = game;
1459
+ const Sections = tab === STATS ? StatsSections : AchievementList;
1460
+ if (!Sections) return null;
1299
1461
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react_native6.View, { style: styles5.game, children: [
1300
- showTitle && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper2.Text, { variant: "titleLarge", style: [styles5.gameTitle, { color: colors.onBackground }], children: game.title }),
1301
- tab === "stats" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StatsSections, { profileId }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AchievementList, { profileId })
1462
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper2.Text, { variant: "titleLarge", style: [styles5.gameTitle, { color: titleColor }], children: game.title }),
1463
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Sections, { profileId })
1302
1464
  ] });
1303
1465
  }
1304
1466
  var styles5 = import_react_native6.StyleSheet.create({
@@ -1318,14 +1480,17 @@ var styles5 = import_react_native6.StyleSheet.create({
1318
1480
  fontWeight: "bold",
1319
1481
  marginTop: 8
1320
1482
  },
1483
+ // A picker's button on the theme's surface (over a backdrop), the same pill as Home's.
1484
+ pill: {
1485
+ borderRadius: 999
1486
+ },
1321
1487
  pressAway: {
1322
1488
  zIndex: 50
1323
1489
  },
1324
- // A real full-bleed box for FakeLandscapeView to size its turned content against.
1325
- rotatable: {
1490
+ screen: {
1326
1491
  flex: 1
1327
1492
  },
1328
- // SegmentedButtons' own rounding.
1493
+ // SegmentedButtons' own rounding, so a surface behind it is the same pill.
1329
1494
  tabs: {
1330
1495
  borderRadius: 20
1331
1496
  }
@@ -1356,12 +1521,12 @@ var import_redux_persist = require("redux-persist");
1356
1521
 
1357
1522
  // src/redux/shellState.ts
1358
1523
  var import_auto_paper8 = require("@rific/auto-paper");
1359
- var import_core14 = require("@rific/core");
1524
+ var import_core13 = require("@rific/core");
1360
1525
  var import_feedback_press4 = require("@rific/feedback-press");
1361
- var import_profile9 = require("@tastic/profile");
1526
+ var import_profile10 = require("@tastic/profile");
1362
1527
  var initialSoundSettings = { ...import_feedback_press4.defaultSoundSettings, enabled: !__DEV__ };
1363
1528
  var appSoundReducer = (state = initialSoundSettings, action) => (0, import_feedback_press4.soundReducer)(state, action);
1364
- function createShellReducers({ seatColors, appearance = "system" }) {
1529
+ function createShellReducers({ seatColors, appearance = "system", seatTheme }) {
1365
1530
  return {
1366
1531
  // Explicit primary/secondary triad (rather than a single seed expanded via harmony) so the pack's
1367
1532
  // identity is pinned exactly instead of drifting with whatever hue math a harmony offset would
@@ -1370,17 +1535,19 @@ function createShellReducers({ seatColors, appearance = "system" }) {
1370
1535
  // derived, not picked, via getThirdColor: the hue maximally distant from both (see
1371
1536
  // @rific/auto-paper's README "Explicit triad" section). The appearance is the pack's ('system',
1372
1537
  // createThemeReducer's own default, when it sets none).
1373
- theme: (0, import_auto_paper8.createThemeReducer)({ appearance, color: { primary: seatColors[1], secondary: seatColors[2], tertiary: (0, import_auto_paper8.getThirdColor)(seatColors[1], seatColors[2]) } }),
1538
+ // A pack with its own seatTheme starts from what seat 1's default color makes of it instead (dark
1539
+ // only when the pack's appearance is; hooks/useSeatColors.ts re-applies it with the live mode).
1540
+ theme: (0, import_auto_paper8.createThemeReducer)(seatTheme ? { appearance, ...seatTheme(seatColors, appearance === "dark") } : { appearance, color: { primary: seatColors[1], secondary: seatColors[2], tertiary: (0, import_auto_paper8.getThirdColor)(seatColors[1], seatColors[2]) } }),
1374
1541
  haptic: import_feedback_press4.hapticReducer,
1375
1542
  sound: appSoundReducer,
1376
1543
  // @rific/core's Keep Awake setting (see Providers.tsx's KeepAwakeBridge), under the namespace its
1377
1544
  // REHYDRATE handling expects.
1378
- keepAwake: import_core14.keepAwakeReducer,
1545
+ keepAwake: import_core13.keepAwakeReducer,
1379
1546
  // The shared App Group roster (see @tastic/profile's resolveInitialProfiles/
1380
1547
  // useSharedProfilesSync, wired in hooks/useProfiles.tsx) is layered on top of this as a separate
1381
1548
  // sync target - this slice persisting normally via redux-persist is what gives it local-fallback
1382
1549
  // behavior where the shared store isn't available, with no separate AsyncStorage path.
1383
- profiles: import_profile9.profilesReducer,
1550
+ profiles: import_profile10.profilesReducer,
1384
1551
  profileSelection: profileSelectionSlice_default,
1385
1552
  // The loadout's "remember this color for next time" memory (guest, CPU, and each profile's pick
1386
1553
  // in this pack) - separate from `theme` above, which always reflects whatever's live right now.
@@ -1398,7 +1565,7 @@ var errorMiddleware = () => (next) => (action) => {
1398
1565
  if (!hasError(action)) return next(action);
1399
1566
  return action;
1400
1567
  };
1401
- function createPackStore(pack, { keyPrefix, migrate }) {
1568
+ function createPackStore(pack, { keyPrefix, migrate, blacklist = [], serializableIgnoredPaths = [], middleware = [] }) {
1402
1569
  const gameReducers = Object.assign({}, ...pack.games.map((game) => game.reducers));
1403
1570
  const resumeReducers = pack.games.some((game) => game.resumable) ? { lastActiveGame: lastActiveGameSlice_default } : {};
1404
1571
  const rootReducer = (0, import_toolkit.combineReducers)({ ...createShellReducers(pack), ...resumeReducers, ...pack.reducers, ...gameReducers });
@@ -1416,7 +1583,7 @@ function createPackStore(pack, { keyPrefix, migrate }) {
1416
1583
  // failing to ever resolve at all isn't a failure mode worth a 5s failsafe for.
1417
1584
  timeout: 0,
1418
1585
  // liveplay is per-round state that must never survive a relaunch (see liveplaySlice.ts).
1419
- blacklist: ["liveplay"],
1586
+ blacklist: ["liveplay", ...blacklist],
1420
1587
  migrate
1421
1588
  },
1422
1589
  rootReducer
@@ -1426,10 +1593,11 @@ function createPackStore(pack, { keyPrefix, migrate }) {
1426
1593
  const defaultMiddleware = getDefaultMiddleware({
1427
1594
  immutableCheck: false,
1428
1595
  serializableCheck: {
1429
- ignoredActions: [import_redux_persist.FLUSH, import_redux_persist.REHYDRATE, import_redux_persist.PAUSE, import_redux_persist.PERSIST, import_redux_persist.PURGE, import_redux_persist.REGISTER]
1596
+ ignoredActions: [import_redux_persist.FLUSH, import_redux_persist.REHYDRATE, import_redux_persist.PAUSE, import_redux_persist.PERSIST, import_redux_persist.PURGE, import_redux_persist.REGISTER],
1597
+ ignoredPaths: [...serializableIgnoredPaths]
1430
1598
  }
1431
1599
  });
1432
- return defaultMiddleware.concat(errorMiddleware);
1600
+ return defaultMiddleware.concat(errorMiddleware, ...middleware);
1433
1601
  },
1434
1602
  reducer: persistedReducer
1435
1603
  });
@@ -1463,11 +1631,11 @@ function createGameGuideSlice(mountKey) {
1463
1631
 
1464
1632
  // src/redux/onProfileRemoved.ts
1465
1633
  var import_achievements = require("@tastic/achievements");
1466
- var import_profile10 = require("@tastic/profile");
1634
+ var import_profile11 = require("@tastic/profile");
1467
1635
  function onProfileRemoved(reducer6, forget) {
1468
1636
  return (state, action) => {
1469
1637
  const next = reducer6(state, action);
1470
- return import_profile10.profilesActions.remove.match(action) ? forget(next, action.payload) : next;
1638
+ return import_profile11.profilesActions.remove.match(action) ? forget(next, action.payload) : next;
1471
1639
  };
1472
1640
  }
1473
1641
  function forgetProfileAchievements(dropStats) {
@@ -1590,6 +1758,7 @@ function createPlayerSettings({ id, defaults, distinctControls }) {
1590
1758
  0 && (module.exports = {
1591
1759
  DEFAULT_APP_SETTINGS,
1592
1760
  DEFAULT_OPPONENT,
1761
+ GUEST_PLAYER_ID,
1593
1762
  HomeScreen,
1594
1763
  LoadoutRoute,
1595
1764
  PREVIEW_ASPECT,