Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use client'
import { useEffect, useState, type ReactNode } from 'react'
import { useGameLayoutMode } from '@/contexts/GameLayoutContext'
import { css } from '../../styled-system/css'
interface StandardGameLayoutProps {
children: ReactNode
className?: string
}
/**
* Standard game layout that ensures:
* 1. Exact 100vh height with no scrolling (vertical or horizontal)
* 2. Navigation never covers game elements (safe area padding)
* 3. Perfect viewport fit on all devices
* 4. Consistent experience across all games
* 5. Dynamically calculates nav height for proper spacing
*
* Layout modes (controlled via GameLayoutContext):
* - 'viewport' (default): Uses 100vh, calculates nav padding (arcade mode)
* - 'container': Uses 100% height, no nav padding (practice game break mode)
*/
export function StandardGameLayout({ children, className }: StandardGameLayoutProps) {
const layoutMode = useGameLayoutMode()
const isContainerMode = layoutMode === 'container'
const [navHeight, setNavHeight] = useState(80) // Default fallback
useEffect(() => {
// Skip nav measurement in container mode - parent handles positioning
if (isContainerMode) return
// Measure the actual nav height from the fixed header
const measureNavHeight = () => {
const header = document.querySelector('header')
if (header) {
const rect = header.getBoundingClientRect()
// Add extra spacing for safety (nav top position + nav height + margin)
const calculatedHeight = rect.top + rect.height + 20
setNavHeight(calculatedHeight)
}
}
// Measure on mount and when window resizes
measureNavHeight()
window.addEventListener('resize', measureNavHeight)
// Also measure after a short delay to catch any late-rendering nav elements
const timer = setTimeout(measureNavHeight, 100)
return () => {
window.removeEventListener('resize', measureNavHeight)
clearTimeout(timer)
}
}, [isContainerMode])
return (
<div
data-layout="standard-game-layout"
data-layout-mode={layoutMode}
data-nav-height={isContainerMode ? 0 : navHeight}
className={`${css({
// Sizing depends on layout mode
height: isContainerMode ? '100%' : '100vh',
width: isContainerMode ? '100%' : '100vw',
overflow: 'hidden',
paddingRight: '4px', // Ensure nav doesn't overlap content on right side
paddingBottom: '4px',
paddingLeft: '4px',
// Box sizing to include padding in dimensions
boxSizing: 'border-box',
// Flex container for game content
display: 'flex',
flexDirection: 'column',
// Transparent background - themes will be applied at nav level
background: 'transparent',
})} no-text-select ${className || ''}`}
style={{
// Dynamic padding based on measured nav height (only in viewport mode)
paddingTop: isContainerMode ? '4px' : `${navHeight}px`,
}}
>
{children}
</div>
)
}
|