From 182e57f05a92e3122b9a8a1df3f1b62943425739 Mon Sep 17 00:00:00 2001 From: ivyvi <130733091+yerxiiiii@users.noreply.github.com> Date: Wed, 9 Sep 2026 01:59:40 +0800 Subject: [PATCH] Fix mobile HUD layout so top chrome, scoreboard, and FPV no longer overlap. Stack the phone scoreboard under the control row, shrink side feeds and bottom board, and hide match chrome while the coach menu is open. Co-authored-by: Cursor --- app/src/football/CommentaryDanmaku.jsx | 6 +- app/src/football/FootballHud.jsx | 116 +++++++++++++++++-------- app/src/football/FootballTitle.jsx | 65 ++++++++++---- app/src/football/StrategyBoard.jsx | 32 ++++--- app/src/football/StrategyPanel.jsx | 24 +++-- app/src/football/TeamFpv.jsx | 23 ++--- 6 files changed, 175 insertions(+), 91 deletions(-) diff --git a/app/src/football/CommentaryDanmaku.jsx b/app/src/football/CommentaryDanmaku.jsx index e5eea8a..1478dc1 100644 --- a/app/src/football/CommentaryDanmaku.jsx +++ b/app/src/football/CommentaryDanmaku.jsx @@ -165,10 +165,10 @@ export default function CommentaryDanmaku() { aria-hidden sx={{ position: "fixed", - top: { xs: "5.5rem", md: "6.25rem" }, + top: { xs: "32%", md: "6.25rem" }, left: 0, right: 0, - height: { xs: "38%", md: "42%" }, + height: { xs: "28%", md: "42%" }, zIndex: 11, pointerEvents: "none", overflow: "hidden", @@ -183,7 +183,7 @@ export default function CommentaryDanmaku() { left: "100%", whiteSpace: "nowrap", fontFamily: ANTON, - fontSize: { xs: "0.95rem", md: "1.15rem" }, + fontSize: { xs: "0.82rem", md: "1.15rem" }, letterSpacing: "0.04em", color: it.team === "red" ? RED_ACCENT : it.team === "blue" ? BLUE_ACCENT : CREAM, textShadow: `0 1px 0 ${COMIC_INK}, 0 0 10px rgba(0,0,0,0.65)`, diff --git a/app/src/football/FootballHud.jsx b/app/src/football/FootballHud.jsx index f491dc7..cf3db22 100644 --- a/app/src/football/FootballHud.jsx +++ b/app/src/football/FootballHud.jsx @@ -103,12 +103,14 @@ function BackButton() { { uiClick(); useGame.setState({ menuOpen: true }); }} sx={{ appearance: "none", @@ -134,7 +137,7 @@ function BackButton() { color: CREAM, cursor: "pointer", fontFamily: ANTON, - fontSize: "0.82rem", + fontSize: { xs: "0.72rem", sm: "0.82rem" }, letterSpacing: "0.08em", textTransform: "uppercase", lineHeight: 1, @@ -150,7 +153,10 @@ function BackButton() { }, }} > - {t(locale, "back")} + + + {t(locale, "back")} + @@ -168,7 +174,7 @@ function DanmakuToggleButton() { ...scorePlateSx, flexDirection: "row", minWidth: "unset", - padding: "6px 12px", + padding: { xs: "5px 8px", sm: "6px 12px" }, }} > 1 ? ORANGE : CREAM, cursor: "pointer", fontFamily: MONO, - fontSize: "0.65rem", + fontSize: { xs: "0.55rem", sm: "0.65rem" }, letterSpacing: "0.08em", textTransform: "uppercase", lineHeight: 1, @@ -267,48 +273,61 @@ function Scoreboard() { - + {/* Score line */} - RED + RED + R {red} {blue} - BLUE + BLUE + B {/* Timer */} {time} @@ -318,13 +337,14 @@ function Scoreboard() { {label} @@ -383,16 +403,18 @@ export function MatchResultBanner() { aria-live="polite" sx={{ position: "fixed", - top: "8.75rem", + top: { xs: "5.5rem", sm: "8.75rem" }, left: "50%", translate: "-50% 0", - zIndex: 11, + zIndex: 13, pointerEvents: "none", display: "flex", flexDirection: "column", alignItems: "center", - gap: "0.65rem", - rotate: "-1.5deg", + gap: { xs: "0.45rem", sm: "0.65rem" }, + width: { xs: "calc(100vw - 1.25rem)", sm: "auto" }, + maxWidth: "28rem", + rotate: { xs: "-0.6deg", sm: "-1.5deg" }, animation: `${slamIn} 0.45s cubic-bezier(0.2, 1.4, 0.4, 1) both`, "@media (prefers-reduced-motion: reduce)": { animation: "none" }, }} @@ -407,7 +429,9 @@ export function MatchResultBanner() { outlineOffset: 0, borderRadius: 0, background: GLASS, - padding: "10px 34px 12px", + padding: { xs: "8px 16px 10px", sm: "10px 34px 12px" }, + width: { xs: "100%", sm: "auto" }, + boxSizing: "border-box", boxShadow: `7px 7px 0 ${COMIC_INK}, 7px 7px 0 2px ${accent}55`, "&::after": { content: '""', @@ -421,7 +445,7 @@ export function MatchResultBanner() { {headline} @@ -567,7 +592,11 @@ function EventTicker() { {events.map((ev, i) => ( @@ -583,15 +613,18 @@ function EventTicker() { key={`${ev.type}-${ev.time}-${i}`} sx={{ fontFamily: MONO, - fontSize: "0.7rem", + fontSize: { xs: "0.58rem", sm: "0.7rem" }, letterSpacing: "0.1em", textTransform: "uppercase", color: CREAM, background: "rgba(16, 16, 24, 0.75)", border: `1px solid rgba(255, 122, 47, 0.35)`, borderRadius: 0, - padding: "4px 12px", + padding: { xs: "3px 8px", sm: "4px 12px" }, whiteSpace: "nowrap", + maxWidth: "100%", + overflow: "hidden", + textOverflow: "ellipsis", opacity: 1 - i * 0.25, animation: `${fadeSlideUp} 0.3s ease both`, animationDelay: `${i * 0.08}s`, @@ -619,8 +652,9 @@ function PenaltyIndicator() { s.entered); + const menuOpen = useGame((s) => s.menuOpen); + // Title / pause overlay is opaque — keep match chrome out of the way so + // it cannot peek through or steal layout on phones. + if (!entered || menuOpen) return null; + return ( diff --git a/app/src/football/FootballTitle.jsx b/app/src/football/FootballTitle.jsx index be84119..f37e8b0 100644 --- a/app/src/football/FootballTitle.jsx +++ b/app/src/football/FootballTitle.jsx @@ -257,7 +257,7 @@ export default function FootballTitle({ onKickOff }) { flexDirection: "column", alignItems: "center", justifyContent: "center", - p: "2.4rem 1.4rem 1.8rem", + p: { xs: "1.1rem 0.85rem 1.1rem", sm: "2.4rem 1.4rem 1.8rem" }, background: INK, opacity: menuOpen ? 1 : 0, pointerEvents: menuOpen ? "auto" : "none", @@ -282,8 +282,8 @@ export default function FootballTitle({ onKickOff }) { aria-hidden sx={{ position: "absolute", - top: "1.25rem", - left: "1.5rem", + top: { xs: "0.75rem", sm: "1.25rem" }, + left: { xs: "0.75rem", sm: "1.5rem" }, zIndex: 1, display: "flex", alignItems: "center", @@ -299,7 +299,7 @@ export default function FootballTitle({ onKickOff }) { sx={{ position: "relative", display: "block", - height: "2.1rem", + height: { xs: "1.7rem", sm: "2.1rem" }, filter: "drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.5))", }} > @@ -341,9 +341,38 @@ export default function FootballTitle({ onKickOff }) { )} - {ready && } - {ready && } - {ready && } + {ready && ( + + )} + {ready && ( + + )} + {ready && ( + + )} {/* Boot gate: one centered spinner on bare ink with the match's step line until fonts, brand art and the game core are in. A @@ -400,7 +429,7 @@ export default function FootballTitle({ onKickOff }) { maxWidth: "min(48rem, 94vw)", flex: "1 1 auto", minHeight: 0, - pt: { xs: "2.6rem", md: "1rem" }, + pt: { xs: "3.4rem", sm: "2.6rem", md: "1rem" }, }} > {bootFailed ? ( @@ -428,7 +457,7 @@ export default function FootballTitle({ onKickOff }) { component="h1" tone="dark" accent={ORANGE} - fontSize="clamp(3.2rem, 8.5vw, 5.8rem)" + fontSize="clamp(2.35rem, 11vw, 5.8rem)" lines={[ { text: "Microduck" }, { text: "Football 3v3", variant: "outline", scale: 0.62 }, @@ -439,14 +468,15 @@ export default function FootballTitle({ onKickOff }) { @@ -463,8 +493,8 @@ export default function FootballTitle({ onKickOff }) { display: "inline-flex", flexDirection: "column", alignItems: "center", - mt: "1.4rem", - "@media (max-height: 700px)": { mt: "1rem" }, + mt: { xs: "0.9rem", sm: "1.4rem" }, + "@media (max-height: 700px)": { mt: "0.75rem" }, ...row(0.32), }} > @@ -544,9 +574,10 @@ export default function FootballTitle({ onKickOff }) { position: "relative", width: "100%", textAlign: "center", - mt: "1.2rem", - pt: "1.15rem", + mt: { xs: "0.85rem", sm: "1.2rem" }, + pt: { xs: "0.85rem", sm: "1.15rem" }, borderTop: "1px solid rgba(255, 255, 255, 0.08)", + display: { xs: "none", sm: "block" }, ...row(0.5), }} > diff --git a/app/src/football/StrategyBoard.jsx b/app/src/football/StrategyBoard.jsx index 67c1a9a..7a3b0dc 100644 --- a/app/src/football/StrategyBoard.jsx +++ b/app/src/football/StrategyBoard.jsx @@ -86,7 +86,7 @@ function TeamTags({ team, strategy, locale, isUser, onOpenCoach }) { border: `2px solid ${accent}`, boxShadow: `inset 0 0 0 1px ${COMIC_INK}`, background: GLASS, - p: "0.45rem 0.55rem", + p: { xs: "0.35rem 0.4rem", sm: "0.45rem 0.55rem" }, pointerEvents: isUser ? "auto" : "none", cursor: isUser ? "pointer" : "default", }} @@ -104,7 +104,7 @@ function TeamTags({ team, strategy, locale, isUser, onOpenCoach }) { *": { pointerEvents: "auto" }, }} @@ -177,24 +177,28 @@ export default function StrategyBoard() { border: `2px solid ${COMIC_INK}`, background: GLASS, boxShadow: `inset 0 0 0 1px rgba(255,255,255,0.08)`, - p: "0.5rem 0.7rem 0.55rem", + p: { xs: "0.3rem 0.45rem 0.35rem", sm: "0.5rem 0.7rem 0.55rem" }, display: "flex", - flexDirection: "column", - gap: "0.4rem", + flexDirection: { xs: "row", sm: "column" }, + alignItems: { xs: "center", sm: "stretch" }, + gap: { xs: "0.55rem", sm: "0.4rem" }, }} > - + + + {shotsR} @@ -202,7 +206,7 @@ export default function StrategyBoard() { component="span" sx={{ fontFamily: MONO, - fontSize: "0.5rem", + fontSize: { xs: "0.42rem", sm: "0.5rem" }, letterSpacing: "0.14em", textTransform: "uppercase", color: "rgba(255,255,255,0.45)", @@ -214,7 +218,7 @@ export default function StrategyBoard() { - + {t(locale, "fpvLabel")} @@ -120,21 +121,21 @@ export default function TeamFpv() { - + );