@tastic/shell 0.4.0 → 0.6.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,7 +946,6 @@ 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";
@@ -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,7 +1077,7 @@ 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;
@@ -1120,7 +1100,7 @@ function HomeMenu() {
1120
1100
  const pressAway = /* @__PURE__ */ jsx14(PressAwayOverlay2, { active: pickerHost.openId !== null, onPress: pickerHost.close, style: styles4.pressAway });
1121
1101
  const RulesSheet = rulesGame?.Rules;
1122
1102
  return /* @__PURE__ */ jsxs6(View4, { style: [styles4.container, { backgroundColor: bg }], children: [
1123
- /* @__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: [
1124
1104
  isWide ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
1125
1105
  pressAway,
1126
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: [
@@ -1142,7 +1122,7 @@ function HomeMenu() {
1142
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" }),
1143
1123
  RulesSheet && /* @__PURE__ */ jsx14(RulesSheet, { visible: true, onDismiss: () => setRulesGame(null) })
1144
1124
  ] }) }),
1145
- /* @__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 })
1146
1126
  ] });
1147
1127
  }
1148
1128
  var SEAT_ONE_PICKER = "home-p1";
@@ -1231,6 +1211,9 @@ var styles4 = StyleSheet4.create({
1231
1211
  corner: {
1232
1212
  position: "absolute"
1233
1213
  },
1214
+ frame: {
1215
+ flex: 1
1216
+ },
1234
1217
  // The wide layout's tiles: wrapped rows of tiles the layout sizes to fill the pane's width.
1235
1218
  grid: {
1236
1219
  flexDirection: "row",
@@ -1277,9 +1260,6 @@ var styles4 = StyleSheet4.create({
1277
1260
  previewIcon: {
1278
1261
  overflow: "hidden"
1279
1262
  },
1280
- rotatable: {
1281
- flex: 1
1282
- },
1283
1263
  // A well (@tastic/hud's wellSurface): the theme's surface on MD3's level-1 shadow, the same surface
1284
1264
  // as Solitaire's game rows, the stats screen's sections and every dialog.
1285
1265
  row: {
@@ -1512,6 +1492,46 @@ function imageGameIcon(images) {
1512
1492
  };
1513
1493
  }
1514
1494
 
1495
+ // src/components/MatchChips.tsx
1496
+ import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1497
+ import { IconButton as IconButton2 } from "@rific/feedback-press";
1498
+ import { useEffect as useEffect7 } from "react";
1499
+ import { Platform as Platform3, StyleSheet as StyleSheet6, useWindowDimensions as useWindowDimensions2 } from "react-native";
1500
+ import Animated2, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
1501
+ import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
1502
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
1503
+ var MATCH_CHIPS_DESKTOP_MIN_WIDTH = 768;
1504
+ var MATCH_CHIPS_FADE_MS = 150;
1505
+ var CHIP_SIZE = 48;
1506
+ var EDGE_PAD = 8;
1507
+ function MatchChips({ playing, faceToFace, centerLineY, onBack, onSettings }) {
1508
+ const { width, height } = useWindowDimensions2();
1509
+ const insets = useSafeAreaInsets4();
1510
+ const { dark } = useAutoPaperTheme6();
1511
+ const desktopWeb = Platform3.OS === "web" && width >= MATCH_CHIPS_DESKTOP_MIN_WIDTH;
1512
+ const visible = desktopWeb || !playing;
1513
+ const opacity = useSharedValue(visible ? 1 : 0);
1514
+ useEffect7(() => {
1515
+ opacity.value = withTiming(visible ? 1 : 0, { duration: MATCH_CHIPS_FADE_MS });
1516
+ }, [visible]);
1517
+ const fadeStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
1518
+ const iconColor = dark ? "#FFFFFF" : "#000000";
1519
+ const containerColor = dark ? "rgba(0,0,0,0.55)" : "rgba(255,255,255,0.55)";
1520
+ const between = faceToFace && !desktopWeb;
1521
+ const top = between ? (centerLineY ?? height / 2) - CHIP_SIZE / 2 : EDGE_PAD + insets.top;
1522
+ const backSlot = { top, left: EDGE_PAD + insets.left };
1523
+ const settingsSlot = { top, right: EDGE_PAD + insets.right };
1524
+ const pointerEvents = visible ? "box-none" : "none";
1525
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
1526
+ /* @__PURE__ */ jsx18(Animated2.View, { pointerEvents, style: [styles6.slot, backSlot, fadeStyle], testID: "match-chips-back", children: /* @__PURE__ */ jsx18(IconButton2, { icon: "arrow-left", iconColor, containerColor, style: styles6.chip, size: 24, onPress: onBack, accessibilityLabel: "Back" }) }),
1527
+ /* @__PURE__ */ jsx18(Animated2.View, { pointerEvents, style: [styles6.slot, settingsSlot, fadeStyle], testID: "match-chips-settings", children: /* @__PURE__ */ jsx18(IconButton2, { icon: "cog", iconColor, containerColor, style: styles6.chip, size: 24, onPress: onSettings, accessibilityLabel: "Settings" }) })
1528
+ ] });
1529
+ }
1530
+ var styles6 = StyleSheet6.create({
1531
+ chip: { margin: 0 },
1532
+ slot: { position: "absolute" }
1533
+ });
1534
+
1515
1535
  // src/hooks/useActiveRound.ts
1516
1536
  import { useSelector as useSelector8 } from "react-redux";
1517
1537
  function useActiveRound(game, defaults) {
@@ -1656,7 +1676,7 @@ function forgetProfileAchievements(dropStats) {
1656
1676
  // src/gameStats.tsx
1657
1677
  import { createAchievementsSlice, mapUnlocksBySeat, useAchievements } from "@tastic/achievements";
1658
1678
  import { createContext as createContext5, useCallback as useCallback11, useContext as useContext5, useMemo as useMemo8 } from "react";
1659
- import { jsx as jsx18 } from "react/jsx-runtime";
1679
+ import { jsx as jsx19 } from "react/jsx-runtime";
1660
1680
  var SEATS = [1, 2];
1661
1681
  function createGameStats({ key, defaultStats, isValidStats, catalog, getProfileStatsView }) {
1662
1682
  const { actions: actions6, reducer: sliceReducer } = createAchievementsSlice({
@@ -1689,7 +1709,7 @@ function createGameStats({ key, defaultStats, isValidStats, catalog, getProfileS
1689
1709
  [recordOutcome]
1690
1710
  );
1691
1711
  const value = useMemo8(() => ({ stats, unlockedAchievements, record, resetAll }), [stats, unlockedAchievements, record, resetAll]);
1692
- return /* @__PURE__ */ jsx18(Context.Provider, { value, children });
1712
+ return /* @__PURE__ */ jsx19(Context.Provider, { value, children });
1693
1713
  }
1694
1714
  function useStats() {
1695
1715
  const ctx = useContext5(Context);
@@ -1767,6 +1787,9 @@ export {
1767
1787
  GUEST_PLAYER_ID,
1768
1788
  HomeScreen,
1769
1789
  LoadoutRoute,
1790
+ MATCH_CHIPS_DESKTOP_MIN_WIDTH,
1791
+ MATCH_CHIPS_FADE_MS,
1792
+ MatchChips,
1770
1793
  PREVIEW_ASPECT,
1771
1794
  PackProvider,
1772
1795
  PlayRoute,