refactor: comprehensive navigation polish - prominence, contrast, cohesion

Major improvements across all navigation components for better visual hierarchy
and user experience:

**RoomInfo**
- Redesigned with Radix tooltip for join code (hover to reveal)
- Much better contrast: purple pill with bright text (rgba(196, 181, 253, 1))
- Click-to-copy in elegant tooltip with visual feedback
- Auto-closes after successful copy with "Share this code with friends!" message

**Player Avatars (ActivePlayersList & NetworkPlayerIndicator)**
- Increased size from 20px to 56px - now span both rows for prominence
- Enhanced drop shadows (0 6px 12px) for depth
- Larger, more polished action buttons (configure/remove):
  - 24-26px buttons with 3px borders and gradient backgrounds
  - Better hover effects with color glows
- Network players: larger frame borders, badges, and pulse indicators

**AddPlayerButton**
- Increased from 48px to 56px to match player avatars
- Enhanced with gradient backgrounds and stronger shadows
- Better hover states with 1.08 scale and green glow

**GameContextNav Layout**
- Added minHeight: 64px to row 2 for avatar accommodation
- Network players now in distinct purple-gradient container
- Your players container gets enhanced polish even when not emphasized
- Improved spacing and gaps throughout (12px between player groups)

Result: Navigation now has clear visual hierarchy with player avatars as the
dominant focal point, excellent contrast on all text, and cohesive translucent
design language throughout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-11 07:41:04 -05:00
parent 48ec451689
commit 1ba58b9547
5 changed files with 248 additions and 128 deletions

View File

@@ -38,10 +38,10 @@ export function ActivePlayersList({
<div
style={{
position: 'relative',
fontSize: shouldEmphasize ? '48px' : '20px',
fontSize: shouldEmphasize ? '56px' : '56px',
lineHeight: 1,
transition: 'font-size 0.4s cubic-bezier(0.4, 0, 0.2, 1), filter 0.4s ease',
filter: shouldEmphasize ? 'drop-shadow(0 4px 8px rgba(0,0,0,0.25))' : 'none',
filter: 'drop-shadow(0 6px 12px rgba(0,0,0,0.3))',
cursor: shouldEmphasize ? 'pointer' : 'default',
}}
onClick={() => shouldEmphasize && onConfigurePlayer(player.id)}
@@ -53,37 +53,40 @@ export function ActivePlayersList({
<>
{/* Configure button - bottom left */}
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onConfigurePlayer(player.id)
}}
style={{
position: 'absolute',
bottom: '-4px',
left: '-4px',
width: '18px',
height: '18px',
bottom: '-6px',
left: '-6px',
width: '24px',
height: '24px',
borderRadius: '50%',
border: '2px solid white',
background: '#6b7280',
border: '3px solid white',
background: 'linear-gradient(135deg, #6b7280, #4b5563)',
color: 'white',
fontSize: '10px',
fontSize: '12px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
transition: 'all 0.2s ease',
padding: 0,
lineHeight: 1,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#3b82f6'
e.currentTarget.style.transform = 'scale(1.15)'
e.currentTarget.style.background = 'linear-gradient(135deg, #3b82f6, #2563eb)'
e.currentTarget.style.transform = 'scale(1.2)'
e.currentTarget.style.boxShadow = '0 6px 16px rgba(59, 130, 246, 0.5)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = '#6b7280'
e.currentTarget.style.background = 'linear-gradient(135deg, #6b7280, #4b5563)'
e.currentTarget.style.transform = 'scale(1)'
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.4)'
}}
aria-label={`Configure ${player.name}`}
>
@@ -92,38 +95,41 @@ export function ActivePlayersList({
{/* Remove button - top right */}
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onRemovePlayer(player.id)
}}
style={{
position: 'absolute',
top: '-4px',
right: '-4px',
width: '20px',
height: '20px',
top: '-6px',
right: '-6px',
width: '26px',
height: '26px',
borderRadius: '50%',
border: '2px solid white',
background: '#ef4444',
border: '3px solid white',
background: 'linear-gradient(135deg, #ef4444, #dc2626)',
color: 'white',
fontSize: '12px',
fontSize: '14px',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
transition: 'all 0.2s ease',
padding: 0,
lineHeight: 1,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#dc2626'
e.currentTarget.style.transform = 'scale(1.1)'
e.currentTarget.style.background = 'linear-gradient(135deg, #dc2626, #b91c1c)'
e.currentTarget.style.transform = 'scale(1.15)'
e.currentTarget.style.boxShadow = '0 6px 16px rgba(239, 68, 68, 0.5)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = '#ef4444'
e.currentTarget.style.background = 'linear-gradient(135deg, #ef4444, #dc2626)'
e.currentTarget.style.transform = 'scale(1)'
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.4)'
}}
aria-label={`Remove ${player.name}`}
>

View File

@@ -46,14 +46,17 @@ export function AddPlayerButton({
return (
<div style={{ position: 'relative' }} ref={popoverRef}>
<button
type="button"
onClick={() => setShowPopover(!showPopover)}
style={{
fontSize: shouldEmphasize ? '32px' : '16px',
width: shouldEmphasize ? '48px' : '24px',
height: shouldEmphasize ? '48px' : '24px',
fontSize: shouldEmphasize ? '36px' : '36px',
width: shouldEmphasize ? '56px' : '56px',
height: shouldEmphasize ? '56px' : '56px',
borderRadius: '50%',
border: '2px solid #10b981',
background: showPopover ? '#10b981' : 'rgba(16, 185, 129, 0.1)',
border: '3px solid #10b981',
background: showPopover
? 'linear-gradient(135deg, #10b981, #059669)'
: 'linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.1))',
color: showPopover ? 'white' : '#10b981',
display: 'flex',
alignItems: 'center',
@@ -63,20 +66,24 @@ export function AddPlayerButton({
padding: 0,
lineHeight: 1,
fontWeight: 'bold',
boxShadow: showPopover ? '0 4px 12px rgba(16, 185, 129, 0.4)' : 'none',
boxShadow: showPopover
? '0 6px 16px rgba(16, 185, 129, 0.5)'
: '0 6px 12px rgba(0,0,0,0.3)',
}}
onMouseEnter={(e) => {
if (!showPopover) {
e.currentTarget.style.background = '#10b981'
e.currentTarget.style.background = 'linear-gradient(135deg, #10b981, #059669)'
e.currentTarget.style.color = 'white'
e.currentTarget.style.transform = 'scale(1.1)'
e.currentTarget.style.transform = 'scale(1.08)'
e.currentTarget.style.boxShadow = '0 6px 16px rgba(16, 185, 129, 0.5)'
}
}}
onMouseLeave={(e) => {
if (!showPopover) {
e.currentTarget.style.background = 'rgba(16, 185, 129, 0.1)'
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.1))'
e.currentTarget.style.color = '#10b981'
e.currentTarget.style.transform = 'scale(1)'
e.currentTarget.style.boxShadow = '0 6px 12px rgba(0,0,0,0.3)'
}
}}
title="Add player"

View File

@@ -200,10 +200,11 @@ export function GameContextNav({
alignItems: 'center',
gap: '16px',
justifyContent: 'space-between',
minHeight: '64px', // Accommodate larger avatars
}}
>
{/* Left: Control buttons */}
<div>
<div style={{ display: 'flex', alignItems: 'center' }}>
{!canModifyPlayers && (
<GameControlButtons onSetup={onSetup} onNewGame={onNewGame} onQuit={onExitSession} />
)}
@@ -214,7 +215,7 @@ export function GameContextNav({
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '8px',
gap: shouldEmphasize ? '16px' : '12px',
}}
>
{/* Network Players */}
@@ -223,7 +224,12 @@ export function GameContextNav({
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
gap: '8px',
padding: '6px 12px',
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(147, 51, 234, 0.12))',
borderRadius: '12px',
border: '2px solid rgba(147, 51, 234, 0.25)',
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)',
}}
>
{networkPlayers.map((player) => (
@@ -238,16 +244,16 @@ export function GameContextNav({
style={{
display: 'flex',
alignItems: 'center',
gap: shouldEmphasize ? '12px' : '2px',
padding: shouldEmphasize ? '12px 20px' : '0',
gap: shouldEmphasize ? '12px' : '8px',
padding: shouldEmphasize ? '12px 20px' : '6px 12px',
background: shouldEmphasize
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.08))'
: 'transparent',
borderRadius: shouldEmphasize ? '16px' : '0',
border: shouldEmphasize ? '3px solid rgba(255, 255, 255, 0.25)' : 'none',
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.10))'
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.05))',
borderRadius: shouldEmphasize ? '16px' : '12px',
border: shouldEmphasize ? '3px solid rgba(255, 255, 255, 0.3)' : '2px solid rgba(255, 255, 255, 0.15)',
boxShadow: shouldEmphasize
? '0 6px 20px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255,255,255,0.3)'
: 'none',
? '0 8px 24px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255,255,255,0.3)'
: '0 4px 12px rgba(0, 0, 0, 0.1)',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
transform: shouldEmphasize ? 'scale(1.05)' : 'scale(1)',
opacity: canModifyPlayers ? 1 : 0.6,

View File

@@ -33,7 +33,7 @@ export function NetworkPlayerIndicator({ player, shouldEmphasize }: NetworkPlaye
<div
style={{
position: 'relative',
fontSize: shouldEmphasize ? '48px' : '20px',
fontSize: shouldEmphasize ? '56px' : '56px',
lineHeight: 1,
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
cursor: 'default',
@@ -41,35 +41,37 @@ export function NetworkPlayerIndicator({ player, shouldEmphasize }: NetworkPlaye
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Network frame border */}
{/* Network frame border - larger and more prominent */}
<div
style={{
position: 'absolute',
inset: '-6px',
borderRadius: '8px',
inset: '-8px',
borderRadius: '12px',
background: `
linear-gradient(135deg,
rgba(59, 130, 246, 0.4),
rgba(147, 51, 234, 0.4),
rgba(236, 72, 153, 0.4))
rgba(59, 130, 246, 0.5),
rgba(147, 51, 234, 0.5),
rgba(236, 72, 153, 0.5))
`,
opacity: isHovered ? 1 : 0.7,
opacity: isHovered ? 1 : 0.8,
transition: 'opacity 0.2s ease',
boxShadow: '0 6px 16px rgba(59, 130, 246, 0.3)',
zIndex: -1,
}}
/>
{/* Animated network signal indicator */}
{/* Animated network signal indicator - larger */}
<div
style={{
position: 'absolute',
top: '-8px',
right: '-8px',
width: '12px',
height: '12px',
top: '-10px',
right: '-10px',
width: '16px',
height: '16px',
borderRadius: '50%',
background: 'rgba(34, 197, 94, 0.9)',
boxShadow: '0 0 8px rgba(34, 197, 94, 0.6)',
background: 'rgba(34, 197, 94, 0.95)',
border: '2px solid rgba(255, 255, 255, 0.8)',
boxShadow: '0 0 12px rgba(34, 197, 94, 0.8)',
animation: 'networkPulse 2s ease-in-out infinite',
zIndex: 1,
}}
@@ -79,29 +81,29 @@ export function NetworkPlayerIndicator({ player, shouldEmphasize }: NetworkPlaye
<div
style={{
position: 'relative',
filter: shouldEmphasize ? 'drop-shadow(0 4px 8px rgba(0,0,0,0.25))' : 'none',
filter: 'drop-shadow(0 6px 12px rgba(0,0,0,0.3))',
}}
>
{player.emoji || '🌐'}
</div>
{/* Network icon badge */}
{/* Network icon badge - larger */}
<div
style={{
position: 'absolute',
bottom: '-4px',
left: '-4px',
width: '16px',
height: '16px',
bottom: '-6px',
left: '-6px',
width: '22px',
height: '22px',
borderRadius: '50%',
border: '2px solid white',
border: '3px solid white',
background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
color: 'white',
fontSize: '8px',
fontSize: '11px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
boxShadow: '0 4px 10px rgba(0,0,0,0.4)',
zIndex: 1,
}}
>

View File

@@ -1,3 +1,4 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { useState } from 'react'
interface RoomInfoProps {
@@ -9,7 +10,7 @@ interface RoomInfoProps {
}
/**
* Displays current arcade room/session information in a compact inline format
* Displays current arcade room/session information with tooltip for join code
*/
export function RoomInfo({
roomName,
@@ -19,78 +20,176 @@ export function RoomInfo({
shouldEmphasize,
}: RoomInfoProps) {
const [copied, setCopied] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const handleCodeClick = () => {
if (!joinCode) return
navigator.clipboard.writeText(joinCode)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
setTimeout(() => {
setCopied(false)
setIsOpen(false)
}, 1500)
}
const displayName = roomName || gameName
return (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '4px 10px',
background: 'rgba(59, 130, 246, 0.15)',
borderRadius: '8px',
border: '1px solid rgba(59, 130, 246, 0.3)',
fontSize: '13px',
fontWeight: '500',
color: 'rgba(255, 255, 255, 0.9)',
transition: 'all 0.2s ease',
whiteSpace: 'nowrap',
}}
title="Active Arcade Room"
>
{/* Room icon and name */}
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<span style={{ fontSize: '14px' }}>🎮</span>
<span style={{ fontWeight: '600' }}>{roomName || gameName}</span>
</span>
{/* Join code with click-to-copy */}
{joinCode && (
<>
<span style={{ opacity: 0.5 }}></span>
<button
type="button"
onClick={handleCodeClick}
<Tooltip.Provider delayDuration={200}>
<Tooltip.Root open={isOpen} onOpenChange={setIsOpen}>
<Tooltip.Trigger asChild>
<div
style={{
background: 'rgba(255, 255, 255, 0.15)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '4px',
padding: '2px 6px',
fontFamily: 'monospace',
display: 'flex',
alignItems: 'center',
gap: '6px',
padding: '4px 10px',
background: 'rgba(139, 92, 246, 0.2)',
borderRadius: '6px',
border: '2px solid rgba(139, 92, 246, 0.4)',
fontSize: '12px',
color: 'rgba(255, 255, 255, 0.95)',
cursor: 'pointer',
fontWeight: 'bold',
color: 'rgba(196, 181, 253, 1)',
transition: 'all 0.2s ease',
fontWeight: '600',
whiteSpace: 'nowrap',
cursor: joinCode ? 'pointer' : 'default',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)'
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.4)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)'
e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.2)'
}}
title={copied ? 'Copied!' : 'Click to copy join code'}
onMouseEnter={() => joinCode && setIsOpen(true)}
onMouseLeave={() => !copied && setIsOpen(false)}
>
{copied ? '✓ Copied' : joinCode}
</button>
</>
)}
{/* Room icon and name */}
<span style={{ fontSize: '12px' }}>🎮</span>
<span>{displayName}</span>
{/* Player count */}
<span style={{ opacity: 0.5 }}></span>
<span style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
<span style={{ fontSize: '12px' }}>👥</span>
<span>{playerCount}</span>
</span>
</div>
{/* Player count */}
<span style={{ opacity: 0.6 }}></span>
<span style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
<span style={{ fontSize: '11px' }}>👥</span>
<span>{playerCount}</span>
</span>
</div>
</Tooltip.Trigger>
{joinCode && (
<Tooltip.Portal>
<Tooltip.Content
side="bottom"
sideOffset={8}
style={{
background: 'linear-gradient(135deg, rgba(17, 24, 39, 0.97), rgba(31, 41, 55, 0.97))',
backdropFilter: 'blur(12px)',
borderRadius: '12px',
padding: '12px 16px',
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(139, 92, 246, 0.3)',
maxWidth: '280px',
zIndex: 9999,
animation: 'tooltipFadeIn 0.2s ease-out',
}}
>
{/* Join code label */}
<div
style={{
fontSize: '11px',
fontWeight: '600',
color: 'rgba(196, 181, 253, 0.8)',
marginBottom: '6px',
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
Room Join Code
</div>
{/* Click-to-copy button */}
<button
type="button"
onClick={handleCodeClick}
style={{
width: '100%',
background: copied
? 'linear-gradient(135deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.3))'
: 'linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(139, 92, 246, 0.3))',
border: copied
? '2px solid rgba(34, 197, 94, 0.5)'
: '2px solid rgba(139, 92, 246, 0.4)',
borderRadius: '8px',
padding: '10px 16px',
fontFamily: 'monospace',
fontSize: '18px',
fontWeight: 'bold',
color: copied ? 'rgba(134, 239, 172, 1)' : 'rgba(196, 181, 253, 1)',
cursor: 'pointer',
transition: 'all 0.2s ease',
letterSpacing: '2px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
}}
onMouseEnter={(e) => {
if (!copied) {
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(139, 92, 246, 0.4))'
e.currentTarget.style.borderColor = 'rgba(139, 92, 246, 0.6)'
}
}}
onMouseLeave={(e) => {
if (!copied) {
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(139, 92, 246, 0.3))'
e.currentTarget.style.borderColor = 'rgba(139, 92, 246, 0.4)'
}
}}
>
{copied ? (
<>
<span style={{ fontSize: '16px' }}></span>
<span>Copied!</span>
</>
) : (
<>
<span>{joinCode}</span>
<span style={{ fontSize: '14px', opacity: 0.7 }}>📋</span>
</>
)}
</button>
{/* Helper text */}
<div
style={{
fontSize: '11px',
color: 'rgba(156, 163, 175, 0.8)',
marginTop: '8px',
textAlign: 'center',
}}
>
{copied ? 'Share this code with friends!' : 'Click to copy'}
</div>
<Tooltip.Arrow
style={{
fill: 'rgba(17, 24, 39, 0.97)',
}}
/>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes tooltipFadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`,
}}
/>
</Tooltip.Provider>
)
}