@tastic/shell 0.3.1 → 0.5.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.mjs CHANGED
@@ -25,13 +25,12 @@ function usePackGame(id) {
25
25
  import { useAutoPaperTheme } from "@rific/auto-paper";
26
26
  import { useHapticSettings } from "@rific/feedback-press";
27
27
  import { Toaster, ToastProvider } from "@rific/toaster";
28
- import { OrientationProvider, useRotationAwareStatusBar, useThemedRootBackground } from "@tastic/core";
28
+ import { useThemedRootBackground } from "@tastic/core";
29
29
  import { RotatedFrame } from "@tastic/hud/chrome";
30
30
  import { ProfileColorProvider } from "@tastic/profile";
31
31
  import * as Haptics from "expo-haptics";
32
32
  import { DarkTheme, DefaultTheme, Stack, ThemeProvider as NavigationThemeProvider } from "expo-router";
33
- import { DeviceMotion } from "expo-sensors";
34
- import { useCallback as useCallback6, useMemo as useMemo3 } from "react";
33
+ import { useCallback as useCallback5, useMemo as useMemo3 } from "react";
35
34
  import { Platform as Platform2, StyleSheet, View } from "react-native";
36
35
  import { GestureHandlerRootView } from "react-native-gesture-handler";
37
36
  import * as RNPaper2 from "react-native-paper";
@@ -60,7 +59,7 @@ function Fonts({ children }) {
60
59
  }
61
60
 
62
61
  // src/components/GuideHost.tsx
63
- import { useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
62
+ import { useIsTouchPrimaryDevice } from "@tastic/core";
64
63
  import { GuideProvider, useGuide } from "@tastic/hud/guide";
65
64
  import { createContext as createContext2, useCallback, useContext as useContext2, useEffect, useMemo, useRef, useState } from "react";
66
65
  import { shallowEqual, useDispatch, useSelector, useStore } from "react-redux";
@@ -89,7 +88,6 @@ function GuideHost({ children }) {
89
88
  const packGuide = subject === PACK_GUIDE ? pack.guide : void 0;
90
89
  const game = subject === PACK_GUIDE ? void 0 : pack.games.find((g) => g.id === subject);
91
90
  const gameGuide = game?.guide;
92
- const rotation = useRotation();
93
91
  const touch = useIsTouchPrimaryDevice();
94
92
  const seatTwo = useSelector((state) => state.opponent.seatTwo);
95
93
  const input = useSelector((state) => gameGuide ? gameGuide.selectInput(state) : NO_INPUT, shallowEqual);
@@ -104,7 +102,7 @@ function GuideHost({ children }) {
104
102
  },
105
103
  [dispatch, packGuide, gameGuide]
106
104
  );
107
- return /* @__PURE__ */ jsx3(GuideProvider, { steps, currentVersion: (packGuide ?? gameGuide)?.version ?? 0, seenVersion, onSeen, rotation, children: /* @__PURE__ */ jsx3(GameGuideBridge, { onPick: setSubject, children }) });
105
+ return /* @__PURE__ */ jsx3(GuideProvider, { steps, currentVersion: (packGuide ?? gameGuide)?.version ?? 0, seenVersion, onSeen, children: /* @__PURE__ */ jsx3(GameGuideBridge, { onPick: setSubject, children }) });
108
106
  }
109
107
  function GameGuideBridge({ onPick, children }) {
110
108
  const pack = usePack();
@@ -227,62 +225,29 @@ function Theme({ children }) {
227
225
 
228
226
  // src/components/UpdateDialog.tsx
229
227
  import { useUpdateErrorToast } from "@rific/toaster";
230
- import { useRotation as useRotation2 } from "@tastic/core";
231
228
  import { UpdateDialog as SharedUpdateDialog } from "@tastic/hud/chrome";
232
229
  import { jsx as jsx6 } from "react/jsx-runtime";
233
230
  function UpdateDialog() {
234
231
  const onError = useUpdateErrorToast();
235
- const rotation = useRotation2();
236
- return /* @__PURE__ */ jsx6(SharedUpdateDialog, { onError, rotation });
237
- }
238
-
239
- // src/hooks/useAppSettings.ts
240
- import { useCallback as useCallback4 } from "react";
241
- import { useDispatch as useDispatch4, useSelector as useSelector4 } from "react-redux";
242
-
243
- // src/redux/appSettingsSlice.ts
244
- import { createSettingsSlice } from "@rific/core";
245
- var DEFAULT_APP_SETTINGS = {
246
- lockOrientation: false,
247
- deferBottomEdgeGestures: false
248
- };
249
- var { actions, reducer } = createSettingsSlice("appSettings", {
250
- initialState: DEFAULT_APP_SETTINGS,
251
- initializeMode: "merge",
252
- fieldSetters: []
253
- });
254
- var appSettingsActions = actions;
255
- var appSettingsSlice_default = reducer;
256
-
257
- // src/hooks/useAppSettings.ts
258
- function useAppSettings() {
259
- const settings = useSelector4((state) => state.appSettings);
260
- const dispatch = useDispatch4();
261
- const setSettings = useCallback4(
262
- (patch) => {
263
- dispatch(appSettingsActions.initialize(patch));
264
- },
265
- [dispatch]
266
- );
267
- return { settings, setSettings };
232
+ return /* @__PURE__ */ jsx6(SharedUpdateDialog, { onError });
268
233
  }
269
234
 
270
235
  // src/hooks/useLastActiveGame.tsx
271
236
  import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
272
- import { useDispatch as useDispatch5, useStore as useStore2 } from "react-redux";
237
+ import { useDispatch as useDispatch4, useStore as useStore2 } from "react-redux";
273
238
 
274
239
  // src/redux/lastActiveGameSlice.ts
275
- import { createSettingsSlice as createSettingsSlice2 } from "@rific/core";
276
- var { actions: actions2, reducer: reducer2 } = createSettingsSlice2("lastActiveGame", { initialState: { gameId: null } });
277
- var lastActiveGameActions = actions2;
278
- var lastActiveGameSlice_default = reducer2;
240
+ import { createSettingsSlice } from "@rific/core";
241
+ var { actions, reducer } = createSettingsSlice("lastActiveGame", { initialState: { gameId: null } });
242
+ var lastActiveGameActions = actions;
243
+ var lastActiveGameSlice_default = reducer;
279
244
 
280
245
  // src/hooks/useLastActiveGame.tsx
281
246
  import { jsx as jsx7 } from "react/jsx-runtime";
282
247
  var LastActiveGameContext = createContext3(null);
283
248
  function LastActiveGameProvider({ children }) {
284
249
  const store = useStore2();
285
- const dispatch = useDispatch5();
250
+ const dispatch = useDispatch4();
286
251
  const [initial] = useState2(() => store.getState().lastActiveGame?.gameId ?? null);
287
252
  const pendingResume = useRef2(initial);
288
253
  const persisted = useRef2(initial);
@@ -321,24 +286,24 @@ function useLastActiveGame() {
321
286
 
322
287
  // src/hooks/useProfiles.tsx
323
288
  import { createProfileRecord, profilesActions, resolveInitialProfiles, useSharedProfilesSync } from "@tastic/profile";
324
- import { createContext as createContext4, useCallback as useCallback5, useContext as useContext4, useEffect as useEffect2, useState as useState3 } from "react";
325
- import { useDispatch as useDispatch6, useSelector as useSelector5 } from "react-redux";
289
+ import { createContext as createContext4, useCallback as useCallback4, useContext as useContext4, useEffect as useEffect2, useState as useState3 } from "react";
290
+ import { useDispatch as useDispatch5, useSelector as useSelector4 } from "react-redux";
326
291
 
327
292
  // src/redux/profileSelectionSlice.ts
328
293
  import { createProfileSelectionSlice } from "@tastic/profile";
329
294
  var defaultProfileSelectionState = { 1: null, 2: null };
330
- var { actions: actions3, reducer: reducer3 } = createProfileSelectionSlice("pack", defaultProfileSelectionState);
331
- var profileSelectionActions = actions3;
332
- var profileSelectionSlice_default = reducer3;
295
+ var { actions: actions2, reducer: reducer2 } = createProfileSelectionSlice("pack", defaultProfileSelectionState);
296
+ var profileSelectionActions = actions2;
297
+ var profileSelectionSlice_default = reducer2;
333
298
 
334
299
  // src/hooks/useProfiles.tsx
335
300
  import { jsx as jsx8 } from "react/jsx-runtime";
336
301
  var SHARED_GROUP_ID = "group.com.infinitetoken.tastic";
337
302
  var ProfilesContext = createContext4(null);
338
303
  function ProfilesProvider({ children }) {
339
- const profiles = useSelector5((state) => state.profiles);
340
- const lastSelected = useSelector5((state) => state.profileSelection);
341
- const dispatch = useDispatch6();
304
+ const profiles = useSelector4((state) => state.profiles);
305
+ const lastSelected = useSelector4((state) => state.profileSelection);
306
+ const dispatch = useDispatch5();
342
307
  const [loaded, setLoaded] = useState3(false);
343
308
  useEffect2(() => {
344
309
  let cancelled = false;
@@ -354,7 +319,7 @@ function ProfilesProvider({ children }) {
354
319
  }, []);
355
320
  const { syncToShared } = useSharedProfilesSync({
356
321
  groupId: SHARED_GROUP_ID,
357
- onRemoteChange: useCallback5(
322
+ onRemoteChange: useCallback4(
358
323
  (remote) => {
359
324
  dispatch(profilesActions.setAll(remote));
360
325
  dispatch(profileSelectionActions.pruneToRoster(remote.map((p) => p.id)));
@@ -362,7 +327,7 @@ function ProfilesProvider({ children }) {
362
327
  [dispatch]
363
328
  )
364
329
  });
365
- const createProfile = useCallback5(
330
+ const createProfile = useCallback4(
366
331
  (input) => {
367
332
  const record = createProfileRecord(input);
368
333
  dispatch(profilesActions.add(record));
@@ -371,14 +336,14 @@ function ProfilesProvider({ children }) {
371
336
  },
372
337
  [profiles, dispatch, syncToShared]
373
338
  );
374
- const updateProfile = useCallback5(
339
+ const updateProfile = useCallback4(
375
340
  (id, patch) => {
376
341
  dispatch(profilesActions.update({ id, patch }));
377
342
  syncToShared(profiles.map((p) => p.id === id ? { ...p, ...patch, updatedAt: Date.now() } : p));
378
343
  },
379
344
  [profiles, dispatch, syncToShared]
380
345
  );
381
- const deleteProfile = useCallback5(
346
+ const deleteProfile = useCallback4(
382
347
  (id) => {
383
348
  dispatch(profilesActions.remove(id));
384
349
  syncToShared(profiles.filter((p) => p.id !== id));
@@ -386,7 +351,7 @@ function ProfilesProvider({ children }) {
386
351
  },
387
352
  [profiles, dispatch, syncToShared]
388
353
  );
389
- const selectProfile = useCallback5(
354
+ const selectProfile = useCallback4(
390
355
  (seat, profileId) => {
391
356
  dispatch(profileSelectionActions.select({ profileId, seat }));
392
357
  },
@@ -412,10 +377,10 @@ function AppStack() {
412
377
  const { dark } = useAutoPaperTheme();
413
378
  const darkRoot = Backdrop ? true : dark;
414
379
  const rootBackground = useThemedRootBackground(darkRoot, false);
415
- const statusBar = useRotationAwareStatusBar({ style: darkRoot ? "light" : "dark" });
380
+ const statusBarStyle = darkRoot ? "light" : "dark";
416
381
  const packOptions = (pack.useScreenOptions ?? noScreenOptions)();
417
382
  const backdropOptions = Backdrop ? { contentStyle: TRANSPARENT_CONTENT, animation: Platform2.select({ android: "fade" }) } : void 0;
418
- const playOptions = useCallback6(
383
+ const playOptions = useCallback5(
419
384
  ({ route }) => {
420
385
  const id = route.params?.game;
421
386
  return pack.games.some((g) => g.id === id && g.keepStatusBar) ? KEEP_STATUS_BAR_OPTIONS : PLAY_OPTIONS;
@@ -427,7 +392,7 @@ function AppStack() {
427
392
  const base = darkRoot ? DarkTheme : DefaultTheme;
428
393
  return { ...base, colors: { ...base.colors, background: navigationBackground } };
429
394
  }, [darkRoot, navigationBackground]);
430
- const stack = /* @__PURE__ */ jsx9(NavigationThemeProvider, { value: navigationTheme, children: /* @__PURE__ */ jsxs2(Stack, { screenOptions: { ...rootBackground, ...statusBar, ...backdropOptions, ...packOptions }, children: [
395
+ const stack = /* @__PURE__ */ jsx9(NavigationThemeProvider, { value: navigationTheme, children: /* @__PURE__ */ jsxs2(Stack, { screenOptions: { ...rootBackground, statusBarStyle, ...backdropOptions, ...packOptions }, children: [
431
396
  /* @__PURE__ */ jsx9(Stack.Screen, { name: "index" }),
432
397
  /* @__PURE__ */ jsx9(Stack.Screen, { name: "play/[game]", options: playOptions })
433
398
  ] }) });
@@ -437,11 +402,6 @@ function AppStack() {
437
402
  stack
438
403
  ] });
439
404
  }
440
- function OrientationBridge({ children }) {
441
- const { settings, setSettings } = useAppSettings();
442
- const { portraitWhileKeyboard } = usePack();
443
- return /* @__PURE__ */ jsx9(OrientationProvider, { deviceMotion: DeviceMotion, freezeWhileLocked: true, lockInitialValue: { locked: settings.lockOrientation, rotation: settings.lockedRotation }, onLockChange: (next) => setSettings({ lockOrientation: next.locked, lockedRotation: next.rotation }), portraitWhileKeyboard, children });
444
- }
445
405
  function GatedApp({ children }) {
446
406
  const { loaded } = useProfiles();
447
407
  return /* @__PURE__ */ jsx9(SplashGate, { gate: "profiles", ready: loaded, children });
@@ -452,7 +412,7 @@ function HapticGatedToastProvider({ children }) {
452
412
  return /* @__PURE__ */ jsx9(ToastProvider, { haptics: settings.vibrate ? Haptics : void 0, paper: PackToasterComponent ? void 0 : RNPaper2, children });
453
413
  }
454
414
  function RotatingToaster() {
455
- const renderFrame = useCallback6(
415
+ const renderFrame = useCallback5(
456
416
  (toasts) => /* @__PURE__ */ jsx9(RotatedFrame, { rotatedSafeArea: true, passThrough: true, children: toasts }),
457
417
  []
458
418
  );
@@ -466,7 +426,7 @@ function GameProviders({ games, children }) {
466
426
  }
467
427
  function PreviewColors({ previewColor, children }) {
468
428
  const { dark } = useAutoPaperTheme();
469
- const preview = useCallback6((hex) => previewColor(hex, dark), [previewColor, dark]);
429
+ const preview = useCallback5((hex) => previewColor(hex, dark), [previewColor, dark]);
470
430
  return /* @__PURE__ */ jsx9(ProfileColorProvider, { preview, children });
471
431
  }
472
432
  function PackRoot({ pack, children }) {
@@ -475,11 +435,11 @@ function PackRoot({ pack, children }) {
475
435
  return previewColor ? /* @__PURE__ */ jsx9(PreviewColors, { previewColor, children: inner }) : /* @__PURE__ */ jsx9(Fragment3, { children: inner });
476
436
  }
477
437
  function RootLayout({ pack, store, persistor }) {
478
- return /* @__PURE__ */ jsx9(GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ jsx9(SafeAreaProvider, { children: /* @__PURE__ */ jsx9(PackProvider, { pack, children: /* @__PURE__ */ jsx9(Fonts, { children: /* @__PURE__ */ jsx9(Providers, { store, persistor, children: /* @__PURE__ */ jsx9(Theme, { children: /* @__PURE__ */ jsx9(ProfilesProvider, { children: /* @__PURE__ */ jsx9(LastActiveGameProvider, { children: /* @__PURE__ */ jsx9(GameProviders, { games: pack.games, children: /* @__PURE__ */ jsx9(PackRoot, { pack, children: /* @__PURE__ */ jsx9(GatedApp, { children: /* @__PURE__ */ jsx9(OrientationBridge, { children: /* @__PURE__ */ jsxs2(HapticGatedToastProvider, { children: [
438
+ return /* @__PURE__ */ jsx9(GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ jsx9(SafeAreaProvider, { children: /* @__PURE__ */ jsx9(PackProvider, { pack, children: /* @__PURE__ */ jsx9(Fonts, { children: /* @__PURE__ */ jsx9(Providers, { store, persistor, children: /* @__PURE__ */ jsx9(Theme, { children: /* @__PURE__ */ jsx9(ProfilesProvider, { children: /* @__PURE__ */ jsx9(LastActiveGameProvider, { children: /* @__PURE__ */ jsx9(GameProviders, { games: pack.games, children: /* @__PURE__ */ jsx9(PackRoot, { pack, children: /* @__PURE__ */ jsx9(GatedApp, { children: /* @__PURE__ */ jsxs2(HapticGatedToastProvider, { children: [
479
439
  /* @__PURE__ */ jsx9(GuideHost, { children: /* @__PURE__ */ jsx9(AppStack, {}) }),
480
440
  pack.Toaster ? /* @__PURE__ */ jsx9(PackToaster, { Toaster: pack.Toaster }) : /* @__PURE__ */ jsx9(RotatingToaster, {}),
481
441
  /* @__PURE__ */ jsx9(UpdateDialog, {})
482
- ] }) }) }) }) }) }) }) }) }) }) }) }) });
442
+ ] }) }) }) }) }) }) }) }) }) }) }) });
483
443
  }
484
444
  var styles = StyleSheet.create({
485
445
  root: { flex: 1 }
@@ -498,13 +458,13 @@ var PROFILES_HREF = "/profiles";
498
458
 
499
459
  // src/screens/LoadoutScreen.tsx
500
460
  import { defaultColors, getRgb, useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
501
- import { FakeLandscapeView, getViewRotation, rotateInsets, useOrientationState } from "@tastic/core";
461
+ import { useOrientationState } from "@tastic/core";
502
462
  import { CornerActionButtons, PressAwayOverlay, usePopoverHost } from "@tastic/hud/chrome";
503
463
  import { PlayerSetupPanel, ReadyButton as ReadyButton2, SharedActionBand } from "@tastic/hud/lobby";
504
464
  import { DualZoneLayout, needsSharedNeutralZone, useDualZoneLayout } from "@tastic/split-screen";
505
465
  import { router as router2, useFocusEffect as useFocusEffect2 } from "expo-router";
506
466
  import { useCallback as useCallback9, useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
507
- import { Platform as Platform3, StyleSheet as StyleSheet3, View as View3 } from "react-native";
467
+ import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
508
468
  import Animated from "react-native-reanimated";
509
469
  import { useSafeAreaInsets } from "react-native-safe-area-context";
510
470
  import { useDispatch as useDispatch9 } from "react-redux";
@@ -590,13 +550,45 @@ var styles2 = StyleSheet2.create({
590
550
  // src/components/SettingsDialog.tsx
591
551
  import { useUpdateErrorToast as useUpdateErrorToast2 } from "@rific/toaster";
592
552
  import { BaseSettingsDialog } from "@tastic/hud/chrome";
553
+
554
+ // src/hooks/useAppSettings.ts
555
+ import { useCallback as useCallback6 } from "react";
556
+ import { useDispatch as useDispatch6, useSelector as useSelector5 } from "react-redux";
557
+
558
+ // src/redux/appSettingsSlice.ts
559
+ import { createSettingsSlice as createSettingsSlice2 } from "@rific/core";
560
+ var DEFAULT_APP_SETTINGS = {
561
+ deferBottomEdgeGestures: false
562
+ };
563
+ var { actions: actions3, reducer: reducer3 } = createSettingsSlice2("appSettings", {
564
+ initialState: DEFAULT_APP_SETTINGS,
565
+ initializeMode: "merge",
566
+ fieldSetters: []
567
+ });
568
+ var appSettingsActions = actions3;
569
+ var appSettingsSlice_default = reducer3;
570
+
571
+ // src/hooks/useAppSettings.ts
572
+ function useAppSettings() {
573
+ const settings = useSelector5((state) => state.appSettings);
574
+ const dispatch = useDispatch6();
575
+ const setSettings = useCallback6(
576
+ (patch) => {
577
+ dispatch(appSettingsActions.initialize(patch));
578
+ },
579
+ [dispatch]
580
+ );
581
+ return { settings, setSettings };
582
+ }
583
+
584
+ // src/components/SettingsDialog.tsx
593
585
  import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
594
- function SettingsDialog({ visible, onDismiss, rotation = 0, onShowHowToPlay, children }) {
586
+ function SettingsDialog({ visible, onDismiss, onShowHowToPlay, children }) {
595
587
  const onUpdateError = useUpdateErrorToast2();
596
588
  const { settings, setSettings } = useAppSettings();
597
589
  const { otaVersion, SettingsSections, edgeGuard = true } = usePack();
598
590
  const edgeGuardProps = edgeGuard ? { deferBottomEdgeGestures: settings.deferBottomEdgeGestures, onDeferBottomEdgeGestures: (value) => setSettings({ deferBottomEdgeGestures: value }) } : {};
599
- return /* @__PURE__ */ jsxs4(BaseSettingsDialog, { visible, onDismiss, rotation, version: otaVersion, lockOrientation: settings.lockOrientation, onLockOrientationChange: (value) => setSettings({ lockOrientation: value }), ...edgeGuardProps, onUpdateError, onShowHowToPlay, children: [
591
+ return /* @__PURE__ */ jsxs4(BaseSettingsDialog, { visible, onDismiss, version: otaVersion, ...edgeGuardProps, onUpdateError, onShowHowToPlay, children: [
600
592
  children,
601
593
  SettingsSections && /* @__PURE__ */ jsx11(SettingsSections, {})
602
594
  ] });
@@ -768,14 +760,12 @@ function LoadoutScreen({ game }) {
768
760
  const loadout = game.useLoadout(mode);
769
761
  const { openFor } = useGameGuide();
770
762
  const { profiles, lastSelected, selectProfile } = useProfiles();
771
- const { orientationMode, p1OnRight, upsideDown, resolved: p1OnRightResolved } = useOrientationState();
772
- const fixedSeats = p2IsHuman && (!!game.twoPlayerSeats?.faceToFaceOnWeb || Platform3.OS !== "web");
773
- const fixedUpsideDown = !!game.twoPlayerSeats?.followUpsideDown && orientationMode === "faceToFace" && upsideDown;
774
- const { panelLayout, panelFadeStyle } = useDualZoneLayout(fixedSeats ? "faceToFace" : orientationMode, fixedSeats ? true : p1OnRight, fixedSeats || p1OnRightResolved, fixedSeats ? fixedUpsideDown : upsideDown, PANEL_SWAP_FADE_MS);
763
+ const { orientationMode } = useOrientationState();
764
+ const panelMode = p2IsHuman && !game.sideBySideWhenWide ? "faceToFace" : orientationMode;
765
+ const { panelLayout, panelFadeStyle } = useDualZoneLayout(panelMode, false, true, false, PANEL_SWAP_FADE_MS);
775
766
  const playersRowFade = panelFadeStyle;
776
767
  const [settingsOpen, setSettingsOpen] = useState4(false);
777
- const rotation = getViewRotation(panelLayout.orientationMode, panelLayout.p1OnRight, panelLayout.upsideDown);
778
- const insets = rotateInsets(useSafeAreaInsets(), rotation);
768
+ const insets = useSafeAreaInsets();
779
769
  const { colors: themeColors, dark } = useAutoPaperTheme3();
780
770
  const { p1Color, p2Color, handleP1ColorChange, handleP2ColorChange } = useSeatColors(p2IsHuman);
781
771
  const dispatch = useDispatch9();
@@ -813,7 +803,7 @@ function LoadoutScreen({ game }) {
813
803
  const triggerStyle = useMemo5(() => pack.Backdrop ? [styles3.pill, { backgroundColor: themeColors.surface }] : void 0, [pack.Backdrop, themeColors.surface]);
814
804
  const isPortrait = panelLayout.orientationMode === "faceToFace";
815
805
  const showMetaInSharedBand = needsSharedNeutralZone(panelLayout.orientationMode, humanSeats.length);
816
- const p2ZoneStyle = p2IsHuman ? isPortrait ? styles3.p2ZoneTop : panelLayout.p1OnRight ? styles3.p2ZoneLeft : styles3.p2ZoneRight : null;
806
+ const p2ZoneStyle = p2IsHuman ? isPortrait ? styles3.p2ZoneTop : styles3.p2ZoneRight : null;
817
807
  const showReadyButton = p2IsHuman;
818
808
  const seatPickerAlign = p2IsHuman ? void 0 : pack.usePickerAlign;
819
809
  const layoutKey = p2IsHuman ? "zones" : "row";
@@ -823,7 +813,6 @@ function LoadoutScreen({ game }) {
823
813
  const anyPlayerPickerOpen = !!(controlsHost.openId?.startsWith("p1-") || controlsHost.openId?.startsWith("p2-"));
824
814
  const optionsRow = loadout.options({
825
815
  host: controlsHost,
826
- rotation,
827
816
  accentColor: themeColors.tertiary,
828
817
  mutedColor: fgMuted,
829
818
  onAccentColor: themeColors.onTertiary,
@@ -834,13 +823,13 @@ function LoadoutScreen({ game }) {
834
823
  });
835
824
  const optionsPopoverOpen = controlsHost.openId !== null && loadout.optionIds.includes(controlsHost.openId);
836
825
  const sharedControls = showMetaInSharedBand ? /* @__PURE__ */ jsx12(SharedActionBand, { onBack: safeBack, onSettings: () => setSettingsOpen(true), onRandomize: loadout.randomize, onReset: loadout.reset, fg, popoverOpen: optionsPopoverOpen, children: optionsRow }) : optionsRow;
837
- const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark, rotation };
826
+ const controlLook = { accentColor: themeColors.tertiary, mutedColor: fgMuted, dark };
838
827
  const p1Control = loadout.seatControl(1, controlsHost, { ...controlLook, seatColor: p1Drawn });
839
828
  const p1Panel = /* @__PURE__ */ jsx12(PlayerSetupPanel, { idPrefix: "p1", host: controlsHost, color: p1Color, onColorChange: handleP1ColorChange, swatches: 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: pack.guestLabel ?? "P1", nullLabel: pack.nullLabel, onProfileSelect: handleP1ProfileSelect, onManageProfiles: () => router2.push(PROFILES_HREF), previewValue: seatPreview, triggerStyle, useSeatPickerAlign: seatPickerAlign }, `p1-${layoutKey}`);
840
829
  const p2Control = p2IsHuman ? loadout.seatControl(2, p2Host, { ...controlLook, seatColor: p2Drawn }) : void 0;
841
830
  const p2Panel = game.seatTwo && /* @__PURE__ */ jsx12(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: 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, previewValue: seatPreview, triggerStyle, usePickerAlign: seatPickerAlign }, `p2-${layoutKey}`);
842
831
  return /* @__PURE__ */ jsxs5(View3, { style: [styles3.container, { backgroundColor: bg }], children: [
843
- /* @__PURE__ */ jsxs5(FakeLandscapeView, { orientationMode: panelLayout.orientationMode, p1OnRight: panelLayout.p1OnRight, upsideDown: panelLayout.upsideDown, style: styles3.rotatable, children: [
832
+ /* @__PURE__ */ jsxs5(View3, { style: styles3.frame, children: [
844
833
  /* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null, onPress: controlsHost.close }),
845
834
  p2ZoneStyle && /* @__PURE__ */ jsx12(PressAwayOverlay, { active: controlsHost.openId !== null || p2Host.openId !== null, onPress: p2Host.close, style: p2ZoneStyle }),
846
835
  !showMetaInSharedBand && /* @__PURE__ */ jsx12(CornerActionButtons, { onBack: safeBack, onSettings: () => setSettingsOpen(true), fg, insets }),
@@ -859,13 +848,13 @@ function LoadoutScreen({ game }) {
859
848
  ) : /* @__PURE__ */ jsxs5(View3, { style: styles3.stackedZone, children: [
860
849
  sharedControls,
861
850
  /* @__PURE__ */ jsxs5(Animated.View, { style: [styles3.playersRow, anyPlayerPickerOpen && styles3.playersRowOpen, playersRowFade], children: [
862
- !isPortrait && panelLayout.p1OnRight ? p2Panel : p1Panel,
863
- !isPortrait && panelLayout.p1OnRight ? p1Panel : p2Panel
851
+ p1Panel,
852
+ p2Panel
864
853
  ] }),
865
854
  /* @__PURE__ */ jsx12(ReadyButton2, { color: p1Drawn, ready: ready[1], onToggleReady: () => setReady((r) => ({ ...r, 1: !r[1] })), style: anyPlayerPickerOpen && styles3.readyButtonHidden })
866
855
  ] })
867
856
  ] }),
868
- /* @__PURE__ */ jsx12(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation, onShowHowToPlay: () => openFor(game.id) })
857
+ /* @__PURE__ */ jsx12(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), onShowHowToPlay: () => openFor(game.id) })
869
858
  ] });
870
859
  }
871
860
  function halfOf(color) {
@@ -876,12 +865,13 @@ var styles3 = StyleSheet3.create({
876
865
  container: {
877
866
  flex: 1
878
867
  },
879
- p2ZoneLeft: {
880
- bottom: 0,
881
- left: 0,
882
- position: "absolute",
883
- top: 0,
884
- width: "50%"
868
+ // A real full-bleed box to size the absolutely-positioned children against.
869
+ frame: {
870
+ alignItems: "center",
871
+ flex: 1,
872
+ gap: 32,
873
+ justifyContent: "center",
874
+ paddingHorizontal: 16
885
875
  },
886
876
  p2ZoneRight: {
887
877
  bottom: 0,
@@ -916,14 +906,6 @@ var styles3 = StyleSheet3.create({
916
906
  opacity: 0,
917
907
  pointerEvents: "none"
918
908
  },
919
- // A real full-bleed box for FakeLandscapeView to size its absolutely-positioned children against.
920
- rotatable: {
921
- alignItems: "center",
922
- flex: 1,
923
- gap: 32,
924
- justifyContent: "center",
925
- paddingHorizontal: 16
926
- },
927
909
  stackedZone: {
928
910
  alignItems: "center",
929
911
  gap: 28
@@ -964,13 +946,12 @@ function useResumePoint(gameId) {
964
946
  // src/screens/HomeScreen.tsx
965
947
  import { defaultColors as defaultColors2, FadingScrollView, useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
966
948
  import { IconButton, Pressable } from "@rific/feedback-press";
967
- import { FakeLandscapeView as FakeLandscapeView2, useRotation as useRotation4 } from "@tastic/core";
968
949
  import { PressAwayOverlay as PressAwayOverlay2, usePopoverHost as usePopoverHost2, useWellColors, WELL_SHADOW } from "@tastic/hud/chrome";
969
950
  import { getProfilePickerContentSize, ProfilePicker } from "@tastic/profile";
970
951
  import { Redirect as Redirect2, router as router3, useIsFocused } from "expo-router";
971
- import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
952
+ import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo6, useRef as useRef5, useState as useState6 } from "react";
972
953
  import { AppState as AppState2, StyleSheet as StyleSheet4, View as View4 } from "react-native";
973
- import { Icon, Portal, Text } from "react-native-paper";
954
+ import { Icon, Portal, SegmentedButtons, Text } from "react-native-paper";
974
955
 
975
956
  // src/hooks/useFrozenWhileHidden.ts
976
957
  import { useState as useState5 } from "react";
@@ -989,7 +970,8 @@ function useFrozenWhileHidden(value, hidden) {
989
970
  }
990
971
 
991
972
  // src/screens/homeLayout.ts
992
- import { clamp, rotateInsets as rotateInsets2, useRotatedWindowDimensions, useRotation as useRotation3 } from "@tastic/core";
973
+ import { clamp } from "@tastic/core";
974
+ import { useWindowDimensions } from "react-native";
993
975
  import { useSafeAreaInsets as useSafeAreaInsets2 } from "react-native-safe-area-context";
994
976
  var PREVIEW_ASPECT = 360 / 200;
995
977
  var ROW_PREVIEW_WIDTH = 92;
@@ -1043,9 +1025,8 @@ function computeHomeLayout(windowWidth, windowHeight, insets) {
1043
1025
  };
1044
1026
  }
1045
1027
  function useHomeLayout() {
1046
- const { width, height } = useRotatedWindowDimensions();
1047
- const rotation = useRotation3();
1048
- const insets = rotateInsets2(useSafeAreaInsets2(), rotation);
1028
+ const { width, height } = useWindowDimensions();
1029
+ const insets = useSafeAreaInsets2();
1049
1030
  return computeHomeLayout(width, height, insets);
1050
1031
  }
1051
1032
 
@@ -1067,7 +1048,6 @@ function HomeScreen() {
1067
1048
  }
1068
1049
  function HomeMenu() {
1069
1050
  const pack = usePack();
1070
- const rotation = useRotation4();
1071
1051
  const layout = useHomeLayout();
1072
1052
  const { insets } = layout;
1073
1053
  const { dark, colors } = useAutoPaperTheme4();
@@ -1097,12 +1077,19 @@ function HomeMenu() {
1097
1077
  const closePicker = pickerHost.close;
1098
1078
  useEffect6(() => {
1099
1079
  closePicker();
1100
- }, [isWide, rotation, layout.windowWidth, layout.windowHeight, closePicker]);
1080
+ }, [isWide, layout.windowWidth, layout.windowHeight, closePicker]);
1101
1081
  const bg = pack.Backdrop ? "transparent" : dark ? "#000000" : "#FFFFFF";
1102
1082
  const fg = pack.foreground ?? (dark ? "#FFFFFF" : "#000000");
1103
1083
  const { previewColor } = pack;
1104
1084
  const p1Drawn = previewColor ? previewColor(p1Color, dark) : p1Color;
1105
1085
  const triggerStyle = useMemo6(() => pack.Backdrop ? [styles4.pill, { backgroundColor: colors.surface }] : void 0, [pack.Backdrop, colors.surface]);
1086
+ const tabs = pack.homeTabs && pack.homeTabs.length > 1 ? pack.homeTabs : null;
1087
+ const [tabLabel, setTabLabel] = useState6(() => tabs?.[0].label ?? null);
1088
+ const tab = tabs ? tabs.find((t) => t.label === tabLabel) ?? tabs[0] : null;
1089
+ const tabButtons = useMemo6(() => tabs?.map(({ label }) => ({ value: label, label })) ?? [], [tabs]);
1090
+ const tabBar = tab && /* @__PURE__ */ jsx14(SegmentedButtons, { value: tab.label, onValueChange: setTabLabel, buttons: tabButtons, style: [styles4.tabs, pack.Backdrop && { backgroundColor: colors.surface }] });
1091
+ const columnClamp = useClampOnShrink();
1092
+ const gridClamp = useClampOnShrink();
1106
1093
  const openGame = useCallback10((game) => openFirstTime(game.id, () => router3.push(game.useLoadout ? loadoutHref(game.id) : playHref(game.id))), [openFirstTime]);
1107
1094
  const openRules = useCallback10((game) => game.Rules ? setRulesGame(game) : openFor(game.id), [openFor]);
1108
1095
  const colorSection = { value: p1Color, onChange: handleP1ColorChange, swatches: defaultColors2, caption: selectedProfile ? pack.colorCaption : void 0, previewValue: previewColor ? (hex) => previewColor(hex, dark) : void 0 };
@@ -1113,42 +1100,74 @@ function HomeMenu() {
1113
1100
  const pressAway = /* @__PURE__ */ jsx14(PressAwayOverlay2, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
1114
1101
  const RulesSheet = rulesGame?.Rules;
1115
1102
  return /* @__PURE__ */ jsxs6(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
1116
- /* @__PURE__ */ jsx14(FakeLandscapeView2, { style: styles4.rotatable, children: /* @__PURE__ */ jsxs6(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
1103
+ /* @__PURE__ */ jsx14(View4, { style: styles4.frame, children: /* @__PURE__ */ jsxs6(RulesHost, { enabled: pack.games.some((game) => game.Rules), children: [
1117
1104
  isWide ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
1118
1105
  pressAway,
1119
- /* @__PURE__ */ jsx14(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__ */ jsx14(View4, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ jsx14(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)) }) }),
1106
+ /* @__PURE__ */ jsxs6(FadingScrollView, { ...tab && gridClamp, 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: [
1107
+ tabBar && /* @__PURE__ */ jsx14(View4, { style: [styles4.wideTabs, { width: layout.rightPaneWidth }], children: tabBar }),
1108
+ /* @__PURE__ */ jsx14(View4, { style: [styles4.grid, { width: layout.rightPaneWidth }], children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameTile, { game, hidden: isHidden(tab, game), width: layout.tileWidth, previewWidth: layout.previewWidth, previewRevealed: previewsRevealed, dark, p1Color, p2Color, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
1109
+ ] }),
1120
1110
  /* @__PURE__ */ jsxs6(View4, { 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: [
1121
1111
  /* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive, fit: layout.heroFit }),
1122
1112
  /* @__PURE__ */ jsx14(View4, { style: styles4.widePicker, children: picker })
1123
1113
  ] })
1124
- ] }) : /* @__PURE__ */ jsxs6(FadingScrollView, { style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
1114
+ ] }) : /* @__PURE__ */ jsxs6(FadingScrollView, { ...tab && columnClamp, style: styles4.scroll, contentContainerStyle: [styles4.content, { paddingTop: 56 + insets.top, paddingBottom: 24 + insets.bottom, paddingLeft: 16 + insets.left, paddingRight: 16 + insets.right }], children: [
1125
1115
  pressAway,
1126
1116
  /* @__PURE__ */ jsx14(pack.HeroTitle, { words: pack.wordmark, color: fg, p1Color, p2Color, active: heroActive }),
1127
1117
  /* @__PURE__ */ jsx14(View4, { style: [styles4.picker, pickerHost.openId !== null && styles4.pickerOpen], children: picker }),
1128
- /* @__PURE__ */ jsx14(View4, { style: styles4.list, children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameRow, { game, dark, p1Color, p2Color, previewRevealed: previewsRevealed, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
1118
+ tabBar && /* @__PURE__ */ jsx14(View4, { style: styles4.listWidth, children: tabBar }),
1119
+ /* @__PURE__ */ jsx14(View4, { style: styles4.list, children: pack.games.map((game) => /* @__PURE__ */ jsx14(GameRow, { game, hidden: isHidden(tab, game), dark, p1Color, p2Color, previewRevealed: previewsRevealed, onPress: () => openGame(game), onRules: hasRules(game) ? () => openRules(game) : void 0 }, game.id)) })
1129
1120
  ] }),
1130
1121
  /* @__PURE__ */ jsx14(IconButton, { icon: "trophy", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, left: 8 + insets.left }], onPress: () => router3.push(STATS_HREF), accessibilityLabel: "Stats & Achievements" }),
1131
1122
  /* @__PURE__ */ jsx14(IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.corner, { top: 8 + insets.top, right: 8 + insets.right }], onPress: () => setSettingsOpen(true), accessibilityLabel: "Settings" }),
1132
1123
  RulesSheet && /* @__PURE__ */ jsx14(RulesSheet, { visible: true, onDismiss: () => setRulesGame(null) })
1133
1124
  ] }) }),
1134
- /* @__PURE__ */ jsx14(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), rotation, onShowHowToPlay: pack.guide ? openPack : void 0 })
1125
+ /* @__PURE__ */ jsx14(SettingsDialog, { visible: settingsOpen, onDismiss: () => setSettingsOpen(false), onShowHowToPlay: pack.guide ? openPack : void 0 })
1135
1126
  ] });
1136
1127
  }
1137
1128
  var SEAT_ONE_PICKER = "home-p1";
1138
1129
  var noPickerAlign = () => void 0;
1130
+ function isHidden(tab, game) {
1131
+ return !!tab && !tab.includes(game);
1132
+ }
1133
+ function useClampOnShrink() {
1134
+ const view = useRef5(null);
1135
+ const sizes = useRef5({ viewport: 0, offset: 0 });
1136
+ return useMemo6(
1137
+ () => ({
1138
+ ref: (instance) => {
1139
+ view.current = instance;
1140
+ sizes.current.offset = 0;
1141
+ },
1142
+ onLayout: (e) => {
1143
+ sizes.current.viewport = e.nativeEvent.layout.height;
1144
+ },
1145
+ onScroll: (e) => {
1146
+ sizes.current.offset = e.nativeEvent.contentOffset.y;
1147
+ },
1148
+ onContentSizeChange: (_width, height) => {
1149
+ const end = Math.max(0, height - sizes.current.viewport);
1150
+ if (sizes.current.offset <= end) return;
1151
+ sizes.current.offset = end;
1152
+ view.current?.scrollTo({ y: end, animated: false });
1153
+ }
1154
+ }),
1155
+ []
1156
+ );
1157
+ }
1139
1158
  function hasRules(game) {
1140
1159
  return !!(game.Rules || game.guide);
1141
1160
  }
1142
1161
  function RulesHost({ enabled, children }) {
1143
1162
  return enabled ? /* @__PURE__ */ jsx14(Portal.Host, { children }) : /* @__PURE__ */ jsx14(Fragment4, { children });
1144
1163
  }
1145
- function GameRow({ game, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1164
+ function GameRow({ game, hidden, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1146
1165
  const well = useWellColors();
1147
1166
  const GameIcon = game.icon;
1148
1167
  return (
1149
1168
  // The rules button sits beside the row's own button, laid over its end, not inside it: a button
1150
1169
  // inside a button is invalid on the web.
1151
- /* @__PURE__ */ jsxs6(View4, { children: [
1170
+ /* @__PURE__ */ jsxs6(View4, { style: hidden ? styles4.hidden : void 0, children: [
1152
1171
  /* @__PURE__ */ jsxs6(Pressable, { onPress, style: [styles4.row, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
1153
1172
  game.Preview ? /* @__PURE__ */ jsx14(PreviewBox, { game, width: ROW_PREVIEW_WIDTH, revealed: previewRevealed, dark, p1Color, p2Color }) : /* @__PURE__ */ jsx14(View4, { style: [styles4.rowIcon, { backgroundColor: dark ? ICON_DARK_BG : ICON_LIGHT_BG }], children: GameIcon && /* @__PURE__ */ jsx14(GameIcon, { size: ROW_ICON_SIZE, p1Color, p2Color, dark }) }),
1154
1173
  /* @__PURE__ */ jsxs6(View4, { style: styles4.rowText, children: [
@@ -1160,9 +1179,9 @@ function GameRow({ game, dark, p1Color, p2Color, previewRevealed, onPress, onRul
1160
1179
  ] })
1161
1180
  );
1162
1181
  }
1163
- function GameTile({ game, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1182
+ function GameTile({ game, hidden, width, previewWidth, dark, p1Color, p2Color, previewRevealed, onPress, onRules }) {
1164
1183
  const well = useWellColors();
1165
- return /* @__PURE__ */ jsxs6(View4, { style: { width }, children: [
1184
+ return /* @__PURE__ */ jsxs6(View4, { style: [{ width }, hidden && styles4.hidden], children: [
1166
1185
  /* @__PURE__ */ jsxs6(Pressable, { onPress, style: [styles4.tile, { backgroundColor: well.bg }], accessibilityRole: "button", accessibilityLabel: game.title, children: [
1167
1186
  /* @__PURE__ */ jsx14(PreviewBox, { game, width: previewWidth, revealed: previewRevealed, dark, p1Color, p2Color }),
1168
1187
  /* @__PURE__ */ jsx14(Text, { variant: "titleMedium", style: [styles4.rowTitle, { color: well.fg }], numberOfLines: 1, children: game.title }),
@@ -1192,18 +1211,30 @@ var styles4 = StyleSheet4.create({
1192
1211
  corner: {
1193
1212
  position: "absolute"
1194
1213
  },
1214
+ frame: {
1215
+ flex: 1
1216
+ },
1195
1217
  // The wide layout's tiles: wrapped rows of tiles the layout sizes to fill the pane's width.
1196
1218
  grid: {
1197
1219
  flexDirection: "row",
1198
1220
  flexWrap: "wrap",
1199
1221
  gap: HOME_GRID_GAP
1200
1222
  },
1223
+ // Out of the layout (a grid closes up around it) and hidden from screen readers.
1224
+ hidden: {
1225
+ display: "none"
1226
+ },
1201
1227
  // Solitaire's list: one column, never wider than a phone's, centred on a wide screen.
1202
1228
  list: {
1203
1229
  gap: 14,
1204
1230
  maxWidth: 480,
1205
1231
  width: "100%"
1206
1232
  },
1233
+ // The list's width, for the tabs over it.
1234
+ listWidth: {
1235
+ maxWidth: 480,
1236
+ width: "100%"
1237
+ },
1207
1238
  picker: {
1208
1239
  alignItems: "center"
1209
1240
  },
@@ -1229,9 +1260,6 @@ var styles4 = StyleSheet4.create({
1229
1260
  previewIcon: {
1230
1261
  overflow: "hidden"
1231
1262
  },
1232
- rotatable: {
1233
- flex: 1
1234
- },
1235
1263
  // A well (@tastic/hud's wellSurface): the theme's surface on MD3's level-1 shadow, the same surface
1236
1264
  // as Solitaire's game rows, the stats screen's sections and every dialog.
1237
1265
  row: {
@@ -1266,6 +1294,10 @@ var styles4 = StyleSheet4.create({
1266
1294
  scroll: {
1267
1295
  flex: 1
1268
1296
  },
1297
+ // SegmentedButtons' own rounding, so a surface behind it is the same pill (the stats screen's tabs).
1298
+ tabs: {
1299
+ borderRadius: 20
1300
+ },
1269
1301
  // The same well as a row, turned on its side.
1270
1302
  tile: {
1271
1303
  borderRadius: 12,
@@ -1304,6 +1336,10 @@ var styles4 = StyleSheet4.create({
1304
1336
  position: "absolute",
1305
1337
  right: 0,
1306
1338
  top: 0
1339
+ },
1340
+ // The tabs over the grid, a little further from it than the tiles are from each other.
1341
+ wideTabs: {
1342
+ marginBottom: 16
1307
1343
  }
1308
1344
  });
1309
1345
 
@@ -1329,7 +1365,7 @@ import { BaseStatsScreen, PressAwayOverlay as PressAwayOverlay3, RotatedFrame as
1329
1365
  import { getOptionPickerContentSize as getOptionPickerContentSize2, OptionPicker as OptionPicker2 } from "@tastic/profile";
1330
1366
  import { useMemo as useMemo7, useState as useState7 } from "react";
1331
1367
  import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
1332
- import { SegmentedButtons, Text as Text2 } from "react-native-paper";
1368
+ import { SegmentedButtons as SegmentedButtons2, Text as Text2 } from "react-native-paper";
1333
1369
  import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
1334
1370
  import { useDispatch as useDispatch10 } from "react-redux";
1335
1371
  import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -1387,7 +1423,7 @@ function StatsContent({ pickerHost }) {
1387
1423
  const PackTabContent = packTab?.Content;
1388
1424
  return /* @__PURE__ */ jsxs7(BaseStatsScreen, { title, onBack: 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: [
1389
1425
  /* @__PURE__ */ jsx16(PressAwayOverlay3, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles5.pressAway }),
1390
- /* @__PURE__ */ jsx16(SegmentedButtons, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
1426
+ /* @__PURE__ */ jsx16(SegmentedButtons2, { value: tab, onValueChange: setTab, buttons: tabButtons, style: [styles5.tabs, surface] }),
1391
1427
  /* @__PURE__ */ jsxs7(View5, { style: [styles5.filters, pickerHost.openId !== null && styles5.filtersOpen], onStartShouldSetResponder: () => pickerHost.openId !== null, children: [
1392
1428
  profiles.length > 0 && /* @__PURE__ */ jsx16(OptionPicker2, { 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 }),
1393
1429
  showGamePicker && /* @__PURE__ */ jsx16(OptionPicker2, { id: GAME_PICKER_ID, host: pickerHost, options: gameOptions, value: gameId, onChange: setGameId, color: colors.primary, dark, icon: "view-grid-outline", alignOverride: gameAlign, triggerStyle })