feat: optimize card sorting for mobile displays
Mobile optimizations for Card Sorting game: **Spectator Mode Bottom Sheet (iPhone):** - Convert right sidebar to compact bottom sheet (120px tall) - 3-column horizontal grid layout for stats (Time/Cards/Accuracy) - Compact sizing: 8px padding, 10px labels, 16px values - Dual-orientation toggle: ▲/▼ on mobile, ◀/▶ on desktop - Emoji-only labels on mobile to save space - Full viewport width on mobile (no wasted space) **Results Screen:** - Vertical layout on mobile (score panel above cards) - Dynamic card sizing based on count (5→130%, 15→105%) - Horizontal action buttons with equal widths - 130px top padding to avoid mini app nav - Compact score display (80px circle vs 160px desktop) **Mini App Nav:** - Prevent text wrapping with whiteSpace: nowrap - Consistent height regardless of content length **Bug Fixes:** - Remove dead "Reveal Numbers" button code - Fix checkSolution onClick handler type error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -180,32 +180,9 @@ export function PlayingPhase() {
|
||||
</div>
|
||||
|
||||
<div className={css({ display: 'flex', gap: '0.5rem' })}>
|
||||
{state.showNumbers && !state.numbersRevealed && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={revealNumbers}
|
||||
disabled={isSpectating}
|
||||
className={css({
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '0.375rem',
|
||||
background: isSpectating ? 'gray.300' : 'orange.500',
|
||||
color: 'white',
|
||||
fontSize: 'sm',
|
||||
fontWeight: '600',
|
||||
border: 'none',
|
||||
cursor: isSpectating ? 'not-allowed' : 'pointer',
|
||||
opacity: isSpectating ? 0.5 : 1,
|
||||
_hover: {
|
||||
background: isSpectating ? 'gray.300' : 'orange.600',
|
||||
},
|
||||
})}
|
||||
>
|
||||
Reveal Numbers
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={checkSolution}
|
||||
onClick={() => checkSolution()}
|
||||
disabled={!canCheckSolution || isSpectating}
|
||||
className={css({
|
||||
padding: '0.5rem 1rem',
|
||||
@@ -337,23 +314,6 @@ export function PlayingPhase() {
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{state.numbersRevealed && (
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: '5px',
|
||||
right: '5px',
|
||||
background: '#ffc107',
|
||||
color: '#333',
|
||||
borderRadius: '4px',
|
||||
padding: '2px 8px',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
>
|
||||
{card.number}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -929,8 +929,9 @@ export function PlayingPhaseDrag() {
|
||||
const getEffectiveViewportWidth = () => {
|
||||
if (typeof window === 'undefined') return 1000
|
||||
const baseWidth = window.innerWidth
|
||||
if (isSpectating && !spectatorStatsCollapsed) {
|
||||
return baseWidth - 280 // Subtract stats sidebar width
|
||||
// Sidebar is hidden on mobile (< 768px), narrower on desktop
|
||||
if (isSpectating && !spectatorStatsCollapsed && baseWidth >= 768) {
|
||||
return baseWidth - 240 // Subtract stats sidebar width on desktop
|
||||
}
|
||||
return baseWidth
|
||||
}
|
||||
@@ -938,8 +939,10 @@ export function PlayingPhaseDrag() {
|
||||
const getEffectiveViewportHeight = () => {
|
||||
if (typeof window === 'undefined') return 800
|
||||
const baseHeight = window.innerHeight
|
||||
const baseWidth = window.innerWidth
|
||||
if (isSpectating) {
|
||||
return baseHeight - 56 // Subtract banner height
|
||||
// Banner is 170px on mobile (130px mini nav + 40px spectator banner), 56px on desktop
|
||||
return baseHeight - (baseWidth < 768 ? 170 : 56)
|
||||
}
|
||||
return baseHeight
|
||||
}
|
||||
@@ -1457,19 +1460,19 @@ export function PlayingPhaseDrag() {
|
||||
<div
|
||||
className={css({
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
top: { base: '130px', md: 0 },
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '56px',
|
||||
height: { base: '40px', md: '56px' },
|
||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||
color: 'white',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0 24px',
|
||||
padding: { base: '0 8px', md: '0 24px' },
|
||||
zIndex: 100,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
gap: '16px',
|
||||
gap: { base: '8px', md: '16px' },
|
||||
})}
|
||||
>
|
||||
{/* Player info */}
|
||||
@@ -1477,12 +1480,12 @@ export function PlayingPhaseDrag() {
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
fontSize: '16px',
|
||||
gap: { base: '6px', md: '12px' },
|
||||
fontSize: { base: '12px', md: '16px' },
|
||||
fontWeight: '600',
|
||||
})}
|
||||
>
|
||||
<span>👀 Spectating:</span>
|
||||
<span className={css({ display: { base: 'none', sm: 'inline' } })}>👀 Spectating:</span>
|
||||
<span>
|
||||
{state.playerMetadata.emoji} {state.playerMetadata.name}
|
||||
</span>
|
||||
@@ -1493,22 +1496,22 @@ export function PlayingPhaseDrag() {
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
fontSize: '14px',
|
||||
gap: { base: '8px', md: '16px' },
|
||||
fontSize: { base: '11px', md: '14px' },
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
gap: { base: '4px', md: '8px' },
|
||||
})}
|
||||
>
|
||||
<span>Progress:</span>
|
||||
<span className={css({ fontWeight: '600', fontSize: '16px' })}>
|
||||
<span className={css({ display: { base: 'none', sm: 'inline' } })}>Progress:</span>
|
||||
<span className={css({ fontWeight: '600', fontSize: { base: '12px', md: '16px' } })}>
|
||||
{state.placedCards.filter((c) => c !== null).length}/{state.cardCount}
|
||||
</span>
|
||||
<span>cards</span>
|
||||
<span className={css({ display: { base: 'none', sm: 'inline' } })}>cards</span>
|
||||
</div>
|
||||
|
||||
{/* Educational Mode Toggle */}
|
||||
@@ -1518,15 +1521,15 @@ export function PlayingPhaseDrag() {
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
padding: '6px 12px',
|
||||
gap: { base: '4px', md: '8px' },
|
||||
padding: { base: '4px 8px', md: '6px 12px' },
|
||||
borderRadius: '20px',
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)',
|
||||
background: spectatorEducationalMode
|
||||
? 'rgba(255, 255, 255, 0.2)'
|
||||
: 'rgba(255, 255, 255, 0.1)',
|
||||
color: 'white',
|
||||
fontSize: '14px',
|
||||
fontSize: { base: '11px', md: '14px' },
|
||||
fontWeight: '500',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
@@ -1537,24 +1540,32 @@ export function PlayingPhaseDrag() {
|
||||
})}
|
||||
>
|
||||
<span>{spectatorEducationalMode ? '✅' : '📚'}</span>
|
||||
<span>Educational Mode</span>
|
||||
<span className={css({ display: { base: 'none', sm: 'inline' } })}>
|
||||
Educational Mode
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spectator Stats Sidebar */}
|
||||
{/* Spectator Stats Sidebar/Bottom Sheet */}
|
||||
{isSpectating && (
|
||||
<div
|
||||
className={css({
|
||||
position: 'fixed',
|
||||
top: '56px', // Below banner
|
||||
right: spectatorStatsCollapsed ? '-280px' : '0',
|
||||
width: '280px',
|
||||
height: 'calc(100vh - 56px)',
|
||||
// Mobile: bottom sheet, Desktop: right sidebar
|
||||
top: { base: 'auto', md: '56px' },
|
||||
bottom: { base: spectatorStatsCollapsed ? '-120px' : '0', md: 'auto' },
|
||||
right: { base: '0', md: spectatorStatsCollapsed ? '-240px' : '0' },
|
||||
left: { base: '0', md: 'auto' },
|
||||
width: { base: '100%', md: '240px' },
|
||||
height: { base: '120px', md: 'calc(100vh - 56px)' },
|
||||
background: 'rgba(255, 255, 255, 0.95)',
|
||||
boxShadow: '-2px 0 12px rgba(0, 0, 0, 0.1)',
|
||||
transition: 'right 0.3s ease',
|
||||
boxShadow: {
|
||||
base: '0 -2px 12px rgba(0, 0, 0, 0.1)',
|
||||
md: '-2px 0 12px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
transition: { base: 'bottom 0.3s ease', md: 'right 0.3s ease' },
|
||||
zIndex: 90,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -1566,118 +1577,199 @@ export function PlayingPhaseDrag() {
|
||||
onClick={() => setSpectatorStatsCollapsed(!spectatorStatsCollapsed)}
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
left: '-40px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
width: '40px',
|
||||
height: '80px',
|
||||
// Mobile: top center, Desktop: left middle
|
||||
left: { base: '50%', md: '-40px' },
|
||||
top: { base: '-30px', md: '50%' },
|
||||
transform: { base: 'translateX(-50%)', md: 'translateY(-50%)' },
|
||||
width: { base: '80px', md: '40px' },
|
||||
height: { base: '30px', md: '80px' },
|
||||
background: 'rgba(255, 255, 255, 0.95)',
|
||||
border: 'none',
|
||||
borderRadius: '8px 0 0 8px',
|
||||
boxShadow: '-2px 0 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: { base: '8px 8px 0 0', md: '8px 0 0 8px' },
|
||||
boxShadow: {
|
||||
base: '0 -2px 8px rgba(0, 0, 0, 0.1)',
|
||||
md: '-2px 0 8px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '20px',
|
||||
fontSize: { base: '16px', md: '20px' },
|
||||
transition: 'all 0.2s',
|
||||
_hover: {
|
||||
background: 'rgba(255, 255, 255, 1)',
|
||||
width: '44px',
|
||||
},
|
||||
})}
|
||||
>
|
||||
{spectatorStatsCollapsed ? '◀' : '▶'}
|
||||
<span className={css({ display: { base: 'inline', md: 'none' } })}>
|
||||
{spectatorStatsCollapsed ? '▲' : '▼'}
|
||||
</span>
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>
|
||||
{spectatorStatsCollapsed ? '◀' : '▶'}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Stats Content */}
|
||||
<div
|
||||
className={css({
|
||||
padding: '24px',
|
||||
padding: { base: '8px 12px', md: '24px' },
|
||||
overflowY: 'auto',
|
||||
flex: 1,
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: '18px',
|
||||
fontSize: { base: '12px', md: '18px' },
|
||||
fontWeight: '700',
|
||||
marginBottom: '20px',
|
||||
marginBottom: { base: '6px', md: '20px' },
|
||||
color: '#1e293b',
|
||||
borderBottom: '2px solid #e2e8f0',
|
||||
paddingBottom: '8px',
|
||||
paddingBottom: { base: '3px', md: '8px' },
|
||||
})}
|
||||
>
|
||||
📊 Live Stats
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>
|
||||
📊 Live Stats
|
||||
</span>
|
||||
<span className={css({ display: { base: 'inline', md: 'none' } })}>📊 Stats</span>
|
||||
</h3>
|
||||
|
||||
{/* Time Elapsed */}
|
||||
{/* Mobile: horizontal layout, Desktop: vertical layout */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: '16px',
|
||||
padding: '12px',
|
||||
background: 'linear-gradient(135deg, #dbeafe, #bfdbfe)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #93c5fd',
|
||||
display: { base: 'grid', md: 'block' },
|
||||
gridTemplateColumns: { base: 'repeat(3, 1fr)', md: 'none' },
|
||||
gap: { base: '8px', md: '0' },
|
||||
})}
|
||||
>
|
||||
<div className={css({ fontSize: '12px', color: '#1e40af', marginBottom: '4px' })}>
|
||||
⏱️ Time Elapsed
|
||||
{/* Time Elapsed */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: { base: '0', md: '16px' },
|
||||
padding: { base: '8px', md: '12px' },
|
||||
background: 'linear-gradient(135deg, #dbeafe, #bfdbfe)',
|
||||
borderRadius: { base: '6px', md: '8px' },
|
||||
border: '1px solid #93c5fd',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '10px', md: '12px' },
|
||||
color: '#1e40af',
|
||||
marginBottom: '4px',
|
||||
})}
|
||||
>
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>
|
||||
⏱️ Time Elapsed
|
||||
</span>
|
||||
<span className={css({ display: { base: 'inline', md: 'none' } })}>⏱️</span>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '16px', md: '24px' },
|
||||
fontWeight: '700',
|
||||
color: '#1e3a8a',
|
||||
})}
|
||||
>
|
||||
{Math.floor(elapsedTime / 60)}:{(elapsedTime % 60).toString().padStart(2, '0')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={css({ fontSize: '24px', fontWeight: '700', color: '#1e3a8a' })}>
|
||||
{Math.floor(elapsedTime / 60)}:{(elapsedTime % 60).toString().padStart(2, '0')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cards Placed */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: '16px',
|
||||
padding: '12px',
|
||||
background: 'linear-gradient(135deg, #dcfce7, #bbf7d0)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #86efac',
|
||||
})}
|
||||
>
|
||||
<div className={css({ fontSize: '12px', color: '#15803d', marginBottom: '4px' })}>
|
||||
🎯 Cards Placed
|
||||
{/* Cards Placed */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: { base: '0', md: '16px' },
|
||||
padding: { base: '8px', md: '12px' },
|
||||
background: 'linear-gradient(135deg, #dcfce7, #bbf7d0)',
|
||||
borderRadius: { base: '6px', md: '8px' },
|
||||
border: '1px solid #86efac',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '10px', md: '12px' },
|
||||
color: '#15803d',
|
||||
marginBottom: '4px',
|
||||
})}
|
||||
>
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>
|
||||
🎯 Cards Placed
|
||||
</span>
|
||||
<span className={css({ display: { base: 'inline', md: 'none' } })}>🎯</span>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '16px', md: '24px' },
|
||||
fontWeight: '700',
|
||||
color: '#14532d',
|
||||
})}
|
||||
>
|
||||
{state.placedCards.filter((c) => c !== null).length} / {state.cardCount}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '11px',
|
||||
color: '#15803d',
|
||||
marginTop: '4px',
|
||||
display: { base: 'none', md: 'block' },
|
||||
})}
|
||||
>
|
||||
{Math.round(
|
||||
(state.placedCards.filter((c) => c !== null).length / state.cardCount) * 100
|
||||
)}
|
||||
% complete
|
||||
</div>
|
||||
</div>
|
||||
<div className={css({ fontSize: '24px', fontWeight: '700', color: '#14532d' })}>
|
||||
{state.placedCards.filter((c) => c !== null).length} / {state.cardCount}
|
||||
</div>
|
||||
<div className={css({ fontSize: '11px', color: '#15803d', marginTop: '4px' })}>
|
||||
{Math.round(
|
||||
(state.placedCards.filter((c) => c !== null).length / state.cardCount) * 100
|
||||
)}
|
||||
% complete
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Accuracy */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: '16px',
|
||||
padding: '12px',
|
||||
background: 'linear-gradient(135deg, #fef3c7, #fde68a)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #fbbf24',
|
||||
})}
|
||||
>
|
||||
<div className={css({ fontSize: '12px', color: '#92400e', marginBottom: '4px' })}>
|
||||
✨ Current Accuracy
|
||||
</div>
|
||||
<div className={css({ fontSize: '24px', fontWeight: '700', color: '#78350f' })}>
|
||||
{(() => {
|
||||
const placedCards = state.placedCards.filter((c): c is SortingCard => c !== null)
|
||||
if (placedCards.length === 0) return '0%'
|
||||
const correctCount = placedCards.filter(
|
||||
(c, i) => state.correctOrder[i]?.id === c.id
|
||||
).length
|
||||
return `${Math.round((correctCount / placedCards.length) * 100)}%`
|
||||
})()}
|
||||
</div>
|
||||
<div className={css({ fontSize: '11px', color: '#92400e', marginTop: '4px' })}>
|
||||
Cards in correct position
|
||||
{/* Current Accuracy */}
|
||||
<div
|
||||
className={css({
|
||||
marginBottom: { base: '0', md: '16px' },
|
||||
padding: { base: '8px', md: '12px' },
|
||||
background: 'linear-gradient(135deg, #fef3c7, #fde68a)',
|
||||
borderRadius: { base: '6px', md: '8px' },
|
||||
border: '1px solid #fbbf24',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '10px', md: '12px' },
|
||||
color: '#92400e',
|
||||
marginBottom: '4px',
|
||||
})}
|
||||
>
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>
|
||||
✨ Current Accuracy
|
||||
</span>
|
||||
<span className={css({ display: { base: 'inline', md: 'none' } })}>✨</span>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '16px', md: '24px' },
|
||||
fontWeight: '700',
|
||||
color: '#78350f',
|
||||
})}
|
||||
>
|
||||
{(() => {
|
||||
const placedCards = state.placedCards.filter(
|
||||
(c): c is SortingCard => c !== null
|
||||
)
|
||||
if (placedCards.length === 0) return '0%'
|
||||
const correctCount = placedCards.filter(
|
||||
(c, i) => state.correctOrder[i]?.id === c.id
|
||||
).length
|
||||
return `${Math.round((correctCount / placedCards.length) * 100)}%`
|
||||
})()}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '11px',
|
||||
color: '#92400e',
|
||||
marginTop: '4px',
|
||||
display: { base: 'none', md: 'block' },
|
||||
})}
|
||||
>
|
||||
Cards in correct position
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1791,15 +1883,31 @@ export function PlayingPhaseDrag() {
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={css({
|
||||
width: isSpectating && !spectatorStatsCollapsed ? 'calc(100vw - 280px)' : '100vw',
|
||||
height: isSpectating ? 'calc(100vh - 56px)' : '100vh',
|
||||
position: 'absolute',
|
||||
top: isSpectating ? '56px' : 0,
|
||||
left: 0,
|
||||
background: 'linear-gradient(135deg, #f0f9ff, #e0f2fe)',
|
||||
overflow: 'hidden',
|
||||
transition: 'width 0.3s ease, height 0.3s ease, top 0.3s ease',
|
||||
})}
|
||||
style={{
|
||||
width:
|
||||
isSpectating &&
|
||||
!spectatorStatsCollapsed &&
|
||||
typeof window !== 'undefined' &&
|
||||
window.innerWidth >= 768
|
||||
? 'calc(100vw - 240px)'
|
||||
: '100vw',
|
||||
height: isSpectating
|
||||
? typeof window !== 'undefined' && window.innerWidth < 768
|
||||
? 'calc(100vh - 170px)'
|
||||
: 'calc(100vh - 56px)'
|
||||
: '100vh',
|
||||
top: isSpectating
|
||||
? typeof window !== 'undefined' && window.innerWidth < 768
|
||||
? '170px'
|
||||
: '56px'
|
||||
: '0',
|
||||
}}
|
||||
>
|
||||
{/* Render continuous curved path through the entire sequence */}
|
||||
{inferredSequence.length > 1 && (
|
||||
|
||||
@@ -95,7 +95,7 @@ export function ResultsPhase() {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: { base: 'column-reverse', md: 'row' },
|
||||
flexDirection: { base: 'column', md: 'row' },
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
@@ -103,6 +103,8 @@ export function ResultsPhase() {
|
||||
bottom: 0,
|
||||
background: 'linear-gradient(135deg, #f0f9ff, #e0f2fe)',
|
||||
overflow: 'auto',
|
||||
paddingTop: { base: '130px', md: 0 },
|
||||
paddingBottom: { base: '70px', md: 0 },
|
||||
})}
|
||||
>
|
||||
{/* Cards Grid Area */}
|
||||
@@ -110,17 +112,23 @@ export function ResultsPhase() {
|
||||
className={css({
|
||||
flex: { base: '0 0 auto', md: 1 },
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
padding: { base: '16px 12px', md: '40px' },
|
||||
overflow: { base: 'visible', md: 'auto' },
|
||||
padding: { base: '8px', md: '40px' },
|
||||
overflow: { base: 'auto', md: 'auto' },
|
||||
order: { base: 2, md: 1 },
|
||||
maxHeight: { base: '50vh', md: 'none' },
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { base: 'repeat(2, 1fr)', sm: 'repeat(3, 1fr)', md: 'repeat(3, 1fr)' },
|
||||
gap: { base: '12px', md: '16px' },
|
||||
gridTemplateColumns: {
|
||||
base: 'repeat(3, 1fr)',
|
||||
sm: 'repeat(3, 1fr)',
|
||||
md: 'repeat(3, 1fr)',
|
||||
},
|
||||
gap: state.cardCount <= 5 ? { base: '8px', md: '16px' } : { base: '6px', md: '16px' },
|
||||
maxWidth: '600px',
|
||||
width: '100%',
|
||||
})}
|
||||
@@ -132,11 +140,18 @@ export function ResultsPhase() {
|
||||
return (
|
||||
<div
|
||||
key={card.id}
|
||||
className={css({
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
paddingBottom: '125%', // 5:4 aspect ratio
|
||||
})}
|
||||
paddingBottom:
|
||||
state.cardCount <= 5
|
||||
? '130%'
|
||||
: state.cardCount <= 8
|
||||
? '120%'
|
||||
: state.cardCount <= 12
|
||||
? '110%'
|
||||
: '105%',
|
||||
}}
|
||||
>
|
||||
{/* Card */}
|
||||
<div
|
||||
@@ -170,16 +185,31 @@ export function ResultsPhase() {
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: { base: '-8px', md: '-12px' },
|
||||
right: { base: '-8px', md: '-12px' },
|
||||
width: { base: '24px', md: '32px' },
|
||||
height: { base: '24px', md: '32px' },
|
||||
top:
|
||||
state.cardCount > 8
|
||||
? { base: '-6px', md: '-12px' }
|
||||
: { base: '-8px', md: '-12px' },
|
||||
right:
|
||||
state.cardCount > 8
|
||||
? { base: '-6px', md: '-12px' }
|
||||
: { base: '-8px', md: '-12px' },
|
||||
width:
|
||||
state.cardCount > 8
|
||||
? { base: '20px', md: '32px' }
|
||||
: { base: '24px', md: '32px' },
|
||||
height:
|
||||
state.cardCount > 8
|
||||
? { base: '20px', md: '32px' }
|
||||
: { base: '24px', md: '32px' },
|
||||
borderRadius: '50%',
|
||||
background: isCorrect ? '#22c55e' : '#ef4444',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: { base: '16px', md: '20px' },
|
||||
fontSize:
|
||||
state.cardCount > 8
|
||||
? { base: '12px', md: '20px' }
|
||||
: { base: '16px', md: '20px' },
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.2)',
|
||||
@@ -194,14 +224,23 @@ export function ResultsPhase() {
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
bottom: { base: '-6px', md: '-8px' },
|
||||
bottom:
|
||||
state.cardCount > 8
|
||||
? { base: '-5px', md: '-8px' }
|
||||
: { base: '-6px', md: '-8px' },
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
background: isCorrect ? '#22c55e' : showCorrections ? '#ef4444' : '#0369a1',
|
||||
color: 'white',
|
||||
padding: { base: '3px 6px', md: '4px 8px' },
|
||||
borderRadius: { base: '8px', md: '12px' },
|
||||
fontSize: { base: '10px', md: '12px' },
|
||||
padding:
|
||||
state.cardCount > 8
|
||||
? { base: '2px 5px', md: '4px 8px' }
|
||||
: { base: '3px 6px', md: '4px 8px' },
|
||||
borderRadius: { base: '6px', md: '12px' },
|
||||
fontSize:
|
||||
state.cardCount > 8
|
||||
? { base: '9px', md: '12px' }
|
||||
: { base: '10px', md: '12px' },
|
||||
fontWeight: 'bold',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
|
||||
})}
|
||||
@@ -221,30 +260,32 @@ export function ResultsPhase() {
|
||||
background: 'rgba(255, 255, 255, 0.95)',
|
||||
borderLeft: { base: 'none', md: '3px solid rgba(59, 130, 246, 0.3)' },
|
||||
borderBottom: { base: '3px solid rgba(59, 130, 246, 0.3)', md: 'none' },
|
||||
padding: { base: '20px 16px', md: '40px' },
|
||||
padding: { base: '12px', md: '40px' },
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: { base: '16px', md: '24px' },
|
||||
gap: { base: '10px', md: '24px' },
|
||||
boxShadow: {
|
||||
base: '0 4px 20px rgba(0, 0, 0, 0.1)',
|
||||
md: '-4px 0 20px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
order: { base: 1, md: 2 },
|
||||
})}
|
||||
>
|
||||
{/* Score Circle */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexDirection: { base: 'row', md: 'column' },
|
||||
alignItems: 'center',
|
||||
gap: { base: '12px', md: '16px' },
|
||||
justifyContent: { base: 'space-between', md: 'center' },
|
||||
gap: { base: '10px', md: '16px' },
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
width: { base: '120px', md: '160px' },
|
||||
height: { base: '120px', md: '160px' },
|
||||
width: { base: '80px', md: '160px' },
|
||||
height: { base: '80px', md: '160px' },
|
||||
borderRadius: '50%',
|
||||
background: isPerfect
|
||||
? 'linear-gradient(135deg, #fbbf24, #f59e0b)'
|
||||
@@ -261,6 +302,7 @@ export function ResultsPhase() {
|
||||
animation: isPerfect
|
||||
? 'perfectCelebrate 0.6s ease-in-out'
|
||||
: 'scoreReveal 0.6s ease-out',
|
||||
flexShrink: 0,
|
||||
})}
|
||||
style={{
|
||||
animationName: isPerfect ? 'perfectCelebrate' : 'scoreReveal',
|
||||
@@ -268,7 +310,7 @@ export function ResultsPhase() {
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '48px', md: '64px' },
|
||||
fontSize: { base: '32px', md: '64px' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
lineHeight: 1,
|
||||
@@ -279,7 +321,7 @@ export function ResultsPhase() {
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: { base: '16px', md: '20px' },
|
||||
fontSize: { base: '12px', md: '20px' },
|
||||
fontWeight: '600',
|
||||
color: 'white',
|
||||
opacity: 0.9,
|
||||
@@ -289,50 +331,61 @@ export function ResultsPhase() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Team/Solo Label */}
|
||||
{isCollaborative && (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: { base: 'flex-start', md: 'center' },
|
||||
gap: { base: '6px', md: '12px' },
|
||||
flex: { base: 1, md: 'none' },
|
||||
})}
|
||||
>
|
||||
{/* Team/Solo Label */}
|
||||
{isCollaborative && (
|
||||
<div
|
||||
className={css({
|
||||
padding: { base: '4px 10px', md: '6px 16px' },
|
||||
background: 'linear-gradient(135deg, #a78bfa, #8b5cf6)',
|
||||
borderRadius: '20px',
|
||||
fontSize: { base: '11px', md: '13px' },
|
||||
fontWeight: '700',
|
||||
color: 'white',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
boxShadow: '0 2px 8px rgba(139, 92, 246, 0.3)',
|
||||
})}
|
||||
>
|
||||
👥 Team Score
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={css({
|
||||
padding: { base: '5px 12px', md: '6px 16px' },
|
||||
background: 'linear-gradient(135deg, #a78bfa, #8b5cf6)',
|
||||
borderRadius: '20px',
|
||||
fontSize: { base: '12px', md: '13px' },
|
||||
fontWeight: '700',
|
||||
color: 'white',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
boxShadow: '0 2px 8px rgba(139, 92, 246, 0.3)',
|
||||
textAlign: { base: 'left', md: 'center' },
|
||||
fontSize: { base: '13px', md: '18px' },
|
||||
fontWeight: '600',
|
||||
color: '#0c4a6e',
|
||||
lineHeight: 1.2,
|
||||
display: { base: 'none', md: 'block' },
|
||||
})}
|
||||
>
|
||||
👥 Team Score
|
||||
{getMessage(scoreBreakdown.finalScore)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
fontSize: { base: '15px', md: '18px' },
|
||||
fontWeight: '600',
|
||||
color: '#0c4a6e',
|
||||
lineHeight: 1.3,
|
||||
})}
|
||||
>
|
||||
{getMessage(scoreBreakdown.finalScore)}
|
||||
</div>
|
||||
|
||||
{/* Time Badge */}
|
||||
<div
|
||||
className={css({
|
||||
padding: { base: '6px 16px', md: '8px 20px' },
|
||||
background: 'rgba(59, 130, 246, 0.1)',
|
||||
border: '2px solid rgba(59, 130, 246, 0.3)',
|
||||
borderRadius: '20px',
|
||||
fontSize: { base: '14px', md: '16px' },
|
||||
fontWeight: '600',
|
||||
color: '#0c4a6e',
|
||||
})}
|
||||
>
|
||||
⏱️ {formatTime(scoreBreakdown.elapsedTime)}
|
||||
{/* Time Badge */}
|
||||
<div
|
||||
className={css({
|
||||
padding: { base: '4px 12px', md: '8px 20px' },
|
||||
background: 'rgba(59, 130, 246, 0.1)',
|
||||
border: '2px solid rgba(59, 130, 246, 0.3)',
|
||||
borderRadius: '20px',
|
||||
fontSize: { base: '12px', md: '16px' },
|
||||
fontWeight: '600',
|
||||
color: '#0c4a6e',
|
||||
})}
|
||||
>
|
||||
⏱️ {formatTime(scoreBreakdown.elapsedTime)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -523,8 +576,8 @@ export function ResultsPhase() {
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: { base: '8px', md: '10px' },
|
||||
flexDirection: { base: 'row', md: 'column' },
|
||||
gap: { base: '6px', md: '10px' },
|
||||
marginTop: 'auto',
|
||||
})}
|
||||
>
|
||||
@@ -532,11 +585,11 @@ export function ResultsPhase() {
|
||||
type="button"
|
||||
onClick={startGame}
|
||||
className={css({
|
||||
padding: { base: '12px 20px', md: '14px 24px' },
|
||||
padding: { base: '10px 8px', md: '14px 24px' },
|
||||
background: 'linear-gradient(135deg, #86efac, #22c55e)',
|
||||
border: { base: '2px solid #22c55e', md: '3px solid #22c55e' },
|
||||
borderRadius: { base: '10px', md: '12px' },
|
||||
fontSize: { base: '14px', md: '16px' },
|
||||
borderRadius: { base: '8px', md: '12px' },
|
||||
fontSize: { base: '11px', md: '16px' },
|
||||
fontWeight: '700',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
@@ -544,61 +597,64 @@ export function ResultsPhase() {
|
||||
boxShadow: '0 4px 12px rgba(34, 197, 94, 0.3)',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
flex: { base: 1, md: 'none' },
|
||||
_hover: {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: '0 6px 20px rgba(34, 197, 94, 0.4)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
🎮 Play Again
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>🎮 </span>Play
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={goToSetup}
|
||||
className={css({
|
||||
padding: { base: '10px 16px', md: '12px 20px' },
|
||||
padding: { base: '10px 8px', md: '12px 20px' },
|
||||
background: 'white',
|
||||
border: '2px solid rgba(59, 130, 246, 0.3)',
|
||||
borderRadius: { base: '10px', md: '12px' },
|
||||
fontSize: { base: '13px', md: '14px' },
|
||||
borderRadius: { base: '8px', md: '12px' },
|
||||
fontSize: { base: '11px', md: '14px' },
|
||||
fontWeight: '700',
|
||||
color: '#0c4a6e',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
flex: { base: 1, md: 'none' },
|
||||
_hover: {
|
||||
borderColor: 'rgba(59, 130, 246, 0.5)',
|
||||
background: 'rgba(59, 130, 246, 0.05)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
⚙️ Settings
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>⚙️ </span>Settings
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={exitSession}
|
||||
className={css({
|
||||
padding: { base: '10px 16px', md: '12px 20px' },
|
||||
padding: { base: '10px 8px', md: '12px 20px' },
|
||||
background: 'white',
|
||||
border: '2px solid rgba(239, 68, 68, 0.3)',
|
||||
borderRadius: { base: '10px', md: '12px' },
|
||||
fontSize: { base: '13px', md: '14px' },
|
||||
borderRadius: { base: '8px', md: '12px' },
|
||||
fontSize: { base: '11px', md: '14px' },
|
||||
fontWeight: '700',
|
||||
color: '#991b1b',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.5px',
|
||||
flex: { base: 1, md: 'none' },
|
||||
_hover: {
|
||||
borderColor: 'rgba(239, 68, 68, 0.5)',
|
||||
background: 'rgba(239, 68, 68, 0.05)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
🚪 Exit
|
||||
<span className={css({ display: { base: 'none', md: 'inline' } })}>🚪 </span>Exit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -503,6 +503,8 @@ function MinimalNav({
|
||||
transition: 'opacity 0.3s ease',
|
||||
pointerEvents: 'auto',
|
||||
maxWidth: 'calc(100% - 128px)', // Leave space for hamburger + margin
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.opacity = '1'
|
||||
|
||||
@@ -183,6 +183,8 @@ export function GameContextNav({
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
width: 'auto',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Game Title Section - Always mounted, hidden when in room */}
|
||||
|
||||
Reference in New Issue
Block a user