diff --git a/src/app/page.tsx b/src/app/page.tsx
index eb9cef3..d535cf8 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -21,6 +21,7 @@ import { NotificationToaster } from "@/components/online/NotificationToaster";
import { ResumeGameBar } from "@/components/online/ResumeGameBar";
import { CelebrationOverlay } from "@/components/online/CelebrationOverlay";
import { MusicToggle } from "@/components/online/MusicToggle";
+import { ErrorBoundary } from "@/components/ErrorBoundary";
import { PublicProfileModal } from "@/components/online/PublicProfileModal";
import { CapacitorBack } from "@/components/CapacitorBack";
import { useSessionStore } from "@/lib/session-store";
@@ -193,13 +194,15 @@ export default function Page() {
// reducedMotion="user" makes every Framer Motion animation honor the OS
// "reduce motion" accessibility setting (coin rain, confetti, count-ups…).
- {renderScreen(screen)}
-
-
-
-
-
-
+
+ {renderScreen(screen)}
+
+
+
+
+
+
+
{loading && null}
diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..8baac46
--- /dev/null
+++ b/src/components/ErrorBoundary.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import React from "react";
+
+/**
+ * Catches React render errors so a crash shows a recoverable in-app message
+ * (with the cause) instead of the browser's blank "page couldn't load" screen.
+ */
+export class ErrorBoundary extends React.Component<
+ { children: React.ReactNode },
+ { error: Error | null }
+> {
+ state: { error: Error | null } = { error: null };
+
+ static getDerivedStateFromError(error: Error) {
+ return { error };
+ }
+
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
+ // Surface the real cause in the console for diagnosis.
+ console.error("[Hokm] render error:", error, info?.componentStack);
+ }
+
+ render() {
+ if (!this.state.error) return this.props.children;
+ return (
+
+
+
⚠️
+
مشکلی پیش آمد
+
برنامه به مشکل خورد؛ دوباره بارگذاری کنید.
+
+ {String(this.state.error?.message || this.state.error)}
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/GameTable.tsx b/src/components/GameTable.tsx
index 9ddcf3c..647c228 100644
--- a/src/components/GameTable.tsx
+++ b/src/components/GameTable.tsx
@@ -65,7 +65,7 @@ export function GameTable({
const exit = onExit ?? reset;
const vw = useViewportWidth();
// Pull the played-card pile inward on narrow screens so it clears the side stacks.
- const trickScale = vw < 360 ? 0.5 : vw < 460 ? 0.6 : 1;
+ const trickScale = vw < 400 ? 0.82 : 1;
// Smaller played cards on phones so the center pile stays clear of the side seats.
const trickCardSize: "sm" | "md" = vw < 480 ? "sm" : "md";
const { phase, players, hakem, trump, turn, currentTrick } = game;
@@ -410,11 +410,13 @@ function OpponentHand({
/* ----------------------------- Trick area ----------------------------- */
+// Compact, centered cross — small magnitudes keep the played pile in the middle of
+// the felt (clear of the side seats/stacks). Each card still nudges toward its player.
const TRICK_OFFSET: Record = {
- 0: { x: 0, y: 70 },
- 1: { x: 96, y: 0 },
- 2: { x: 0, y: -70 },
- 3: { x: -96, y: 0 },
+ 0: { x: 0, y: 52 },
+ 1: { x: 50, y: 0 },
+ 2: { x: 0, y: -52 },
+ 3: { x: -50, y: 0 },
};
const TRICK_ENTER: Record = {
0: { x: 0, y: 260 },