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 },