All files / web/src/components StandardGameLayout.tsx

0% Statements 0/91
0% Branches 0/1
0% Functions 0/1
0% Lines 0/91

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                                                                                                                                                                                       
'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>
  )
}