From 3e37085d1852160cb1ec4c1d2192153df872378b Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Thu, 11 Jun 2026 00:33:21 +0330 Subject: [PATCH] Landscape: whole-app landscape-first + Home 2-column landscape layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move orientation lock + RotatePrompt to app root → whole app is landscape- first now (UNO-style), not just the game. Generalized rotate copy. - Home: portrait unchanged; in landscape it becomes a 2-column app layout (col A = branding + play actions, col B = tiles + footer) that fits the short height with no scroll (landscape: Tailwind variants, overflow-hidden). Co-Authored-By: Claude Opus 4.8 --- src/app/page.tsx | 10 ++++++++++ src/components/HomeScreen.tsx | 27 +++++++++++++++++++++------ src/components/screens/GameScreen.tsx | 18 ------------------ src/lib/i18n.tsx | 8 ++++---- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index ef08b51..b6cc709 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -22,6 +22,7 @@ import { ResumeGameBar } from "@/components/online/ResumeGameBar"; import { CelebrationOverlay } from "@/components/online/CelebrationOverlay"; import { ErrorBoundary } from "@/components/ErrorBoundary"; import { PublicProfileModal } from "@/components/online/PublicProfileModal"; +import { RotatePrompt } from "@/components/online/RotatePrompt"; import { CapacitorBack } from "@/components/CapacitorBack"; import { useSessionStore } from "@/lib/session-store"; import { useGameStore } from "@/lib/game-store"; @@ -191,6 +192,14 @@ export default function Page() { }; }, [init]); + // Landscape-first app (UNO-style): best-effort lock the whole app to landscape + // on Android / installed PWA. iOS & desktop reject it harmlessly; the + // RotatePrompt covers the portrait case there. + useEffect(() => { + const o = (screen as unknown as { orientation?: { lock?: (m: string) => Promise } }).orientation; + o?.lock?.("landscape").catch(() => {}); + }, []); + return ( // reducedMotion="user" makes every Framer Motion animation honor the OS // "reduce motion" accessibility setting (coin rain, confetti, count-ups…). @@ -202,6 +211,7 @@ export default function Page() { + {loading && null} diff --git a/src/components/HomeScreen.tsx b/src/components/HomeScreen.tsx index 25fc88a..25b1626 100644 --- a/src/components/HomeScreen.tsx +++ b/src/components/HomeScreen.tsx @@ -73,18 +73,24 @@ export function HomeScreen() { const Chevron = locale === "fa" ? ChevronLeft : ChevronRight; return ( -
+
-
+
+ {/* content: single column (portrait) → two columns (landscape) */} +
+ + {/* ===== column A: branding + play actions ===== */} +
+ {/* logo */} {/* logo + title on one row (no overflow); subtitle beneath the title */}
@@ -159,19 +165,24 @@ export function HomeScreen() {
+ {/* ===== end column A ===== */} +
+ + {/* ===== column B: tiles + footer ===== */} +
{/* tiles */} -
+
} label={t("menu.profile")} tint="teal" onClick={() => nav("profile")} /> } label={t("menu.friends")} tint="sky" onClick={() => nav(isAuthed ? "friends" : "auth")} /> } label={t("menu.leaderboard")} tint="gold" onClick={() => nav("leaderboard")} /> } label={t("menu.shop")} tint="rose" onClick={() => nav("shop")} />
-
+
{/* footer */} -
+
{isAuthed ? (
+ {/* ===== end column B ===== */} +
+ {/* ===== end content columns ===== */} +
); diff --git a/src/components/screens/GameScreen.tsx b/src/components/screens/GameScreen.tsx index cf87c57..4b2872e 100644 --- a/src/components/screens/GameScreen.tsx +++ b/src/components/screens/GameScreen.tsx @@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from "react"; import { GameTable } from "@/components/GameTable"; import { PostMatchRewardsModal } from "@/components/online/PostMatchRewardsModal"; import { MatchIntroOverlay } from "@/components/online/MatchIntroOverlay"; -import { RotatePrompt } from "@/components/online/RotatePrompt"; import { useGameStore } from "@/lib/game-store"; import { useSessionStore } from "@/lib/session-store"; import { useUIStore } from "@/lib/ui-store"; @@ -59,20 +58,6 @@ export function GameScreen() { }; }, []); - // Landscape-first table: best-effort lock to landscape on Android/PWA (iOS & - // desktop reject it — harmless). Restored to portrait/auto when leaving. - useEffect(() => { - const o = (screen as unknown as { orientation?: { lock?: (m: string) => Promise; unlock?: () => void } }).orientation; - o?.lock?.("landscape").catch(() => {}); - return () => { - try { - o?.unlock?.(); - } catch { - /* unsupported — ignore */ - } - }; - }, []); - const notifyAchievements = (r: RewardResult) => { for (const a of r.newAchievements) pushNotification({ @@ -157,9 +142,6 @@ export function GameScreen() { onForfeit={canForfeit ? () => useGameStore.getState().forfeit() : undefined} /> - {/* Landscape-first: nudge to rotate when held in portrait on a phone */} - - {/* UNO-style "players joining the table" intro (online matches, once) */} {introPending && mode === "online" && ( diff --git a/src/lib/i18n.tsx b/src/lib/i18n.tsx index 5145013..034d951 100644 --- a/src/lib/i18n.tsx +++ b/src/lib/i18n.tsx @@ -328,8 +328,8 @@ const fa: Dict = { "settings.sound": "افکت صدا", "settings.music": "موسیقی پس‌زمینه", "rotate.title": "گوشی را بچرخانید", - "rotate.desc": "برای تجربهٔ بهترِ میز بازی، گوشی را افقی (چرخانده) نگه دارید.", - "rotate.anyway": "همین‌طور عمودی بازی می‌کنم", + "rotate.desc": "برگ وسط در حالت افقی بهترین تجربه را دارد. گوشی را چرخانده (افقی) نگه دارید.", + "rotate.anyway": "ادامه در حالت عمودی", "settings.musicStyle": "سبک موسیقی", "settings.trackSantoor": "سنتی (سنتور)", "settings.trackPlayful": "شاد", @@ -667,8 +667,8 @@ const en: Dict = { "settings.sound": "Sound effects", "settings.music": "Background music", "rotate.title": "Rotate your phone", - "rotate.desc": "The table plays best in landscape — turn your phone sideways for the full experience.", - "rotate.anyway": "Keep playing in portrait", + "rotate.desc": "Barg-e Vasat is best in landscape — turn your phone sideways for the full experience.", + "rotate.anyway": "Continue in portrait", "settings.musicStyle": "Music style", "settings.trackSantoor": "Traditional (Santoor)", "settings.trackPlayful": "Playful",