feat: show rithmomachia turn in nav
This commit is contained in:
parent
42a93e94a9
commit
7c89bfef9c
|
|
@ -5,6 +5,7 @@ import { animated, to, useSpring } from '@react-spring/web'
|
|||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
import type { PlayerBadge } from '@/components/nav/types'
|
||||
import { StandardGameLayout } from '@/components/StandardGameLayout'
|
||||
import { Z_INDEX } from '@/constants/zIndex'
|
||||
import { useGameMode } from '@/contexts/GameModeContext'
|
||||
|
|
@ -136,7 +137,7 @@ function CaptureErrorDialog({
|
|||
*/
|
||||
export function RithmomachiaGame() {
|
||||
const router = useRouter()
|
||||
const { state, resetGame, goToSetup } = useRithmomachia()
|
||||
const { state, resetGame, goToSetup, whitePlayerId, blackPlayerId } = useRithmomachia()
|
||||
const { setFullscreenElement } = useFullscreen()
|
||||
const gameRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
|
|
@ -147,6 +148,41 @@ export function RithmomachiaGame() {
|
|||
}
|
||||
}, [setFullscreenElement])
|
||||
|
||||
const currentPlayerId = useMemo(() => {
|
||||
if (state.turn === 'W') {
|
||||
return whitePlayerId ?? undefined
|
||||
}
|
||||
if (state.turn === 'B') {
|
||||
return blackPlayerId ?? undefined
|
||||
}
|
||||
return undefined
|
||||
}, [state.turn, whitePlayerId, blackPlayerId])
|
||||
|
||||
const playerBadges = useMemo<Record<string, PlayerBadge>>(() => {
|
||||
const badges: Record<string, PlayerBadge> = {}
|
||||
if (whitePlayerId) {
|
||||
badges[whitePlayerId] = {
|
||||
label: 'White',
|
||||
icon: '⚪',
|
||||
background: 'linear-gradient(135deg, rgba(248, 250, 252, 0.95), rgba(226, 232, 240, 0.9))',
|
||||
color: '#0f172a',
|
||||
borderColor: 'rgba(226, 232, 240, 0.8)',
|
||||
shadowColor: 'rgba(148, 163, 184, 0.35)',
|
||||
}
|
||||
}
|
||||
if (blackPlayerId) {
|
||||
badges[blackPlayerId] = {
|
||||
label: 'Black',
|
||||
icon: '⚫',
|
||||
background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.92), rgba(15, 23, 42, 0.94))',
|
||||
color: '#f8fafc',
|
||||
borderColor: 'rgba(30, 41, 59, 0.9)',
|
||||
shadowColor: 'rgba(15, 23, 42, 0.45)',
|
||||
}
|
||||
}
|
||||
return badges
|
||||
}, [whitePlayerId, blackPlayerId])
|
||||
|
||||
return (
|
||||
<PageWithNav
|
||||
navTitle="Rithmomachia"
|
||||
|
|
@ -157,6 +193,8 @@ export function RithmomachiaGame() {
|
|||
}}
|
||||
onNewGame={resetGame}
|
||||
onSetup={goToSetup}
|
||||
currentPlayerId={currentPlayerId}
|
||||
playerBadges={playerBadges}
|
||||
>
|
||||
<StandardGameLayout>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useRoomData } from '../hooks/useRoomData'
|
|||
import { useViewerId } from '../hooks/useViewerId'
|
||||
import { AppNavBar } from './AppNavBar'
|
||||
import { GameContextNav } from './nav/GameContextNav'
|
||||
import type { PlayerBadge } from './nav/types'
|
||||
import { PlayerConfigDialog } from './nav/PlayerConfigDialog'
|
||||
import { ModerationNotifications } from './nav/ModerationNotifications'
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ interface PageWithNavProps {
|
|||
currentPlayerId?: string
|
||||
playerScores?: Record<string, number>
|
||||
playerStreaks?: Record<string, number>
|
||||
playerBadges?: Record<string, PlayerBadge>
|
||||
}
|
||||
|
||||
export function PageWithNav({
|
||||
|
|
@ -36,6 +38,7 @@ export function PageWithNav({
|
|||
currentPlayerId,
|
||||
playerScores,
|
||||
playerStreaks,
|
||||
playerBadges,
|
||||
}: PageWithNavProps) {
|
||||
const { players, activePlayers, setActive, activePlayerCount } = useGameMode()
|
||||
const { roomData, isInRoom, moderationEvent, clearModerationEvent } = useRoomData()
|
||||
|
|
@ -168,6 +171,7 @@ export function PageWithNav({
|
|||
currentPlayerId={currentPlayerId}
|
||||
playerScores={playerScores}
|
||||
playerStreaks={playerStreaks}
|
||||
playerBadges={playerBadges}
|
||||
showPopover={showPopover}
|
||||
setShowPopover={setShowPopover}
|
||||
activeTab={activeTab}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react'
|
||||
import { PlayerTooltip } from './PlayerTooltip'
|
||||
import type { PlayerBadge } from './types'
|
||||
|
||||
interface Player {
|
||||
id: string
|
||||
|
|
@ -19,6 +20,7 @@ interface ActivePlayersListProps {
|
|||
currentPlayerId?: string
|
||||
playerScores?: Record<string, number>
|
||||
playerStreaks?: Record<string, number>
|
||||
playerBadges?: Record<string, PlayerBadge>
|
||||
}
|
||||
|
||||
export function ActivePlayersList({
|
||||
|
|
@ -29,6 +31,7 @@ export function ActivePlayersList({
|
|||
currentPlayerId,
|
||||
playerScores = {},
|
||||
playerStreaks = {},
|
||||
playerBadges = {},
|
||||
}: ActivePlayersListProps) {
|
||||
const [hoveredPlayerId, setHoveredPlayerId] = React.useState<string | null>(null)
|
||||
|
||||
|
|
@ -48,6 +51,7 @@ export function ActivePlayersList({
|
|||
const score = playerScores[player.id] || 0
|
||||
const streak = playerStreaks[player.id] || 0
|
||||
const celebrationLevel = getCelebrationLevel(streak)
|
||||
const badge = playerBadges[player.id]
|
||||
|
||||
return (
|
||||
<PlayerTooltip
|
||||
|
|
@ -153,6 +157,41 @@ export function ActivePlayersList({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{badge && (
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '999px',
|
||||
background: badge.background ?? 'rgba(148, 163, 184, 0.25)',
|
||||
color: badge.color ?? '#0f172a',
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.04em',
|
||||
textTransform: 'uppercase',
|
||||
boxShadow: badge.shadowColor
|
||||
? `0 4px 12px ${badge.shadowColor}`
|
||||
: '0 4px 12px rgba(15, 23, 42, 0.25)',
|
||||
border: badge.borderColor ? `2px solid ${badge.borderColor}` : '2px solid rgba(255,255,255,0.4)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
marginTop: '6px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{badge.icon && (
|
||||
<span
|
||||
aria-hidden
|
||||
style={{ fontSize: '14px', filter: 'drop-shadow(0 2px 4px rgba(15,23,42,0.35))' }}
|
||||
>
|
||||
{badge.icon}
|
||||
</span>
|
||||
)}
|
||||
<span>{badge.label}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldEmphasize && hoveredPlayerId === player.id && (
|
||||
<>
|
||||
{/* Configure button - bottom left */}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { GameTitleMenu } from './GameTitleMenu'
|
|||
import { NetworkPlayerIndicator } from './NetworkPlayerIndicator'
|
||||
import { PendingInvitations } from './PendingInvitations'
|
||||
import { RoomInfo } from './RoomInfo'
|
||||
import type { PlayerBadge } from './types'
|
||||
|
||||
type GameMode = 'none' | 'single' | 'battle' | 'tournament'
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ interface GameContextNavProps {
|
|||
currentPlayerId?: string
|
||||
playerScores?: Record<string, number>
|
||||
playerStreaks?: Record<string, number>
|
||||
playerBadges?: Record<string, PlayerBadge>
|
||||
// Lifted popover state from PageWithNav
|
||||
showPopover?: boolean
|
||||
setShowPopover?: (show: boolean) => void
|
||||
|
|
@ -82,6 +84,7 @@ export function GameContextNav({
|
|||
currentPlayerId,
|
||||
playerScores,
|
||||
playerStreaks,
|
||||
playerBadges,
|
||||
showPopover,
|
||||
setShowPopover,
|
||||
activeTab,
|
||||
|
|
@ -281,19 +284,20 @@ export function GameContextNav({
|
|||
margin: '0 4px',
|
||||
}}
|
||||
/>
|
||||
{networkPlayers.map((player) => (
|
||||
<NetworkPlayerIndicator
|
||||
key={player.id}
|
||||
player={player}
|
||||
shouldEmphasize={shouldEmphasize}
|
||||
currentPlayerId={currentPlayerId}
|
||||
playerScores={playerScores}
|
||||
playerStreaks={playerStreaks}
|
||||
roomId={roomInfo?.roomId}
|
||||
currentUserId={currentUserId ?? undefined}
|
||||
isCurrentUserHost={isCurrentUserHost}
|
||||
/>
|
||||
))}
|
||||
{networkPlayers.map((player) => (
|
||||
<NetworkPlayerIndicator
|
||||
key={player.id}
|
||||
player={player}
|
||||
shouldEmphasize={shouldEmphasize}
|
||||
currentPlayerId={currentPlayerId}
|
||||
playerScores={playerScores}
|
||||
playerStreaks={playerStreaks}
|
||||
playerBadges={playerBadges}
|
||||
roomId={roomInfo?.roomId}
|
||||
currentUserId={currentUserId ?? undefined}
|
||||
isCurrentUserHost={isCurrentUserHost}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -327,6 +331,7 @@ export function GameContextNav({
|
|||
currentPlayerId={currentPlayerId}
|
||||
playerScores={playerScores}
|
||||
playerStreaks={playerStreaks}
|
||||
playerBadges={playerBadges}
|
||||
/>
|
||||
|
||||
<AddPlayerButton
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from 'react'
|
||||
import { PlayerTooltip } from './PlayerTooltip'
|
||||
import { ReportPlayerModal } from './ReportPlayerModal'
|
||||
import type { PlayerBadge } from './types'
|
||||
|
||||
interface NetworkPlayer {
|
||||
id: string
|
||||
|
|
@ -19,6 +20,7 @@ interface NetworkPlayerIndicatorProps {
|
|||
currentPlayerId?: string
|
||||
playerScores?: Record<string, number>
|
||||
playerStreaks?: Record<string, number>
|
||||
playerBadges?: Record<string, PlayerBadge>
|
||||
// Moderation props
|
||||
roomId?: string
|
||||
currentUserId?: string
|
||||
|
|
@ -35,6 +37,7 @@ export function NetworkPlayerIndicator({
|
|||
currentPlayerId,
|
||||
playerScores = {},
|
||||
playerStreaks = {},
|
||||
playerBadges = {},
|
||||
roomId,
|
||||
currentUserId,
|
||||
isCurrentUserHost,
|
||||
|
|
@ -72,6 +75,7 @@ export function NetworkPlayerIndicator({
|
|||
return 'normal'
|
||||
}
|
||||
const celebrationLevel = getCelebrationLevel(streak)
|
||||
const badge = playerBadges[player.id]
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -252,10 +256,45 @@ export function NetworkPlayerIndicator({
|
|||
`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Turn label */}
|
||||
{isCurrentPlayer && hasGameState && (
|
||||
{badge && (
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '999px',
|
||||
background: badge.background ?? 'rgba(148, 163, 184, 0.25)',
|
||||
color: badge.color ?? '#0f172a',
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.04em',
|
||||
textTransform: 'uppercase',
|
||||
boxShadow: badge.shadowColor
|
||||
? `0 4px 12px ${badge.shadowColor}`
|
||||
: '0 4px 12px rgba(15, 23, 42, 0.25)',
|
||||
border: badge.borderColor ? `2px solid ${badge.borderColor}` : '2px solid rgba(255,255,255,0.4)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
marginTop: '6px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{badge.icon && (
|
||||
<span
|
||||
aria-hidden
|
||||
style={{ fontSize: '14px', filter: 'drop-shadow(0 2px 4px rgba(15,23,42,0.35))' }}
|
||||
>
|
||||
{badge.icon}
|
||||
</span>
|
||||
)}
|
||||
<span>{badge.label}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Turn label */}
|
||||
{isCurrentPlayer && hasGameState && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
export interface PlayerBadge {
|
||||
label: string
|
||||
icon?: string
|
||||
background?: string
|
||||
color?: string
|
||||
borderColor?: string
|
||||
shadowColor?: string
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue