diff --git a/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts b/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts index 58a1db8..d5cab4c 100644 --- a/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts +++ b/apps/client/src/scenes/game/application/runtime/GameSceneRuntime.ts @@ -7,7 +7,10 @@ import { AppearanceResolver } from "../AppearanceResolver"; import { GameNetworkSync } from "../GameNetworkSync"; import { GameLoop } from "../GameLoop"; -import { type GameSceneEventPorts, type GameSceneFactoryOptions } from "../orchestrators/GameSceneOrchestrator"; +import { + type GameSceneEventPorts, + type GameSceneFactoryOptions, +} from "../orchestrators/GameSceneOrchestrator"; import type { GamePlayers } from "../game.types"; import type { BombManager } from "../../entities/bomb/BombManager"; import type { MoveSender } from "../network/PlayerMoveSender"; @@ -177,7 +180,7 @@ this.lifecycleState = "destroyed"; if (this.tickerHandler) { - this.app.ticker.remove(this.tickerHandler); + this.app.ticker?.remove(this.tickerHandler); this.tickerHandler = null; } this.disposableRegistry.disposeAll(); diff --git a/apps/client/src/scenes/result/ResultScene.tsx b/apps/client/src/scenes/result/ResultScene.tsx index c1c9546..67873ce 100644 --- a/apps/client/src/scenes/result/ResultScene.tsx +++ b/apps/client/src/scenes/result/ResultScene.tsx @@ -5,6 +5,7 @@ */ import type { GameResultPayload } from "@repo/shared"; import type { CSSProperties } from "react"; +import { useEffect, useState } from "react"; import { config } from "../../config"; type Props = { @@ -12,6 +13,10 @@ onBackToTitle: () => void; }; +type GameResultWithFinalMap = GameResultPayload & { + finalGridColors?: number[]; +}; + const formatPaintRate = (value: number): string => `${value.toFixed(1)}%`; const ROW_GRID_TEMPLATE = "120px 1fr 180px"; @@ -66,43 +71,36 @@ pointerEvents: "none", }; +const MAP_BACKGROUND_LAYER_STYLE: CSSProperties = { + ...EFFECT_LAYER_BASE_STYLE, + display: "flex", + alignItems: "center", + justifyContent: "center", + zIndex: 0, + opacity: 0.9, +}; + +const BACKGROUND_DARK_OVERLAY_STYLE: CSSProperties = { + ...EFFECT_LAYER_BASE_STYLE, + background: "rgba(0, 0, 0, 0.62)", +}; + const CONFETTI_LAYER_STYLE: CSSProperties = { ...EFFECT_LAYER_BASE_STYLE, overflow: "hidden", zIndex: 0, }; -const BACKGROUND_IMAGE_LAYER_STYLE: CSSProperties = { - ...EFFECT_LAYER_BASE_STYLE, - backgroundImage: "url('/LobbyAni.webp')", - backgroundSize: "cover", - backgroundPosition: "center", - backgroundRepeat: "no-repeat", - opacity: 0.45, +const TAP_GUIDE_STYLE: CSSProperties = { + marginTop: "6px", + fontSize: "clamp(1rem, 2.8vw, 1.35rem)", + letterSpacing: "0.14em", + fontWeight: 700, + color: "rgba(255, 255, 255, 0.92)", + textShadow: "0 2px 10px rgba(0, 0, 0, 0.6)", + animation: "tapPromptPulse 1.6s ease-in-out infinite", }; -const BACKGROUND_DARK_OVERLAY_STYLE: CSSProperties = { - ...EFFECT_LAYER_BASE_STYLE, - background: - "linear-gradient(180deg, rgba(0, 0, 0, 0.56) 0%, rgba(0, 0, 0, 0.72) 55%, rgba(0, 0, 0, 0.82) 100%)", -}; - -const getPulseLayerStyle = ( - winnerColorRgba: string, - phase: "a" | "b", -): CSSProperties => ({ - ...EFFECT_LAYER_BASE_STYLE, - background: - phase === "a" - ? `radial-gradient(circle at 25% 25%, ${winnerColorRgba} 0%, rgba(0, 0, 0, 0) 55%)` - : `radial-gradient(circle at 80% 75%, ${winnerColorRgba} 0%, rgba(0, 0, 0, 0) 60%)`, - opacity: phase === "a" ? 0.22 : 0.16, - animation: - phase === "a" - ? "winnerPulseA 4.8s ease-in-out infinite" - : "winnerPulseB 6.2s ease-in-out infinite", -}); - const TABLE_STYLE: CSSProperties = { width: "100%", maxWidth: "720px", @@ -226,25 +224,27 @@ }; }; -const toRgba = (hex: string, alpha: number): string => { - const normalized = hex.replace("#", ""); - if (normalized.length !== 6) { - return `rgba(255, 255, 255, ${alpha})`; - } +const getMapWrapperStyle = (cols: number, rows: number): CSSProperties => ({ + width: `min(94vw, calc(92dvh * ${cols / rows}))`, + maxHeight: "92dvh", + aspectRatio: `${cols} / ${rows}`, + display: "grid", + gridTemplateColumns: `repeat(${cols}, 1fr)`, + border: "1px solid rgba(255, 255, 255, 0.14)", +}); - const r = Number.parseInt(normalized.slice(0, 2), 16); - const g = Number.parseInt(normalized.slice(2, 4), 16); - const b = Number.parseInt(normalized.slice(4, 6), 16); - - if ([r, g, b].some((value) => Number.isNaN(value))) { - return `rgba(255, 255, 255, ${alpha})`; - } - - return `rgba(${r}, ${g}, ${b}, ${alpha})`; -}; +const getMapCellStyle = (teamId: number): CSSProperties => ({ + background: config.GAME_CONFIG.TEAM_COLORS[teamId] ?? "#1b1b1b", +}); /** 最終結果データを受け取り,順位一覧を表示する */ export const ResultScene = ({ result, onBackToTitle }: Props) => { + const [isRankingVisible, setIsRankingVisible] = useState(false); + + useEffect(() => { + setIsRankingVisible(false); + }, [result]); + if (!result) { return (