'use client'

import { useState, useMemo, useRef, useEffect, useCallback } from 'react'
import { useSpring, animated, to } from '@react-spring/web'
import { css } from '@styled/css'
import { useTheme } from '@/contexts/ThemeContext'
import type { MapData, MapRegion } from '../types'
import {
  getRegionColor,
  getRegionStroke,
  getLabelTextColor,
  getLabelTextShadow,
} from '../mapColors'
import { forceSimulation, forceCollide, forceX, forceY, type SimulationNodeDatum } from 'd3-force'
import {
  WORLD_MAP,
  USA_MAP,
  filterRegionsByContinent,
  parseViewBox,
  calculateFitCropViewBox,
} from '../maps'
import type { ContinentId } from '../continents'
import {
  calculateScreenPixelRatio,
  calculateMaxZoomAtThreshold,
  isAboveThreshold,
} from '../utils/screenPixelRatio'
import { findOptimalZoom, type BoundingBox as DebugBoundingBox } from '../utils/adaptiveZoomSearch'
import { useRegionDetection } from '../hooks/useRegionDetection'
import { usePointerLock } from '../hooks/usePointerLock'
import { useMagnifierZoom } from '../hooks/useMagnifierZoom'
import { DevCropTool } from './DevCropTool'

// Debug flag: show technical info in magnifier (dev only)
const SHOW_MAGNIFIER_DEBUG_INFO = process.env.NODE_ENV === 'development'

// Debug flag: show bounding boxes with importance scores (dev only)
const SHOW_DEBUG_BOUNDING_BOXES = process.env.NODE_ENV === 'development'

// Precision mode threshold: screen pixel ratio that triggers pointer lock recommendation
const PRECISION_MODE_THRESHOLD = 20

// Label fade settings: labels fade near cursor to reduce clutter
const LABEL_FADE_RADIUS = 150 // pixels - labels within this radius fade
const LABEL_MIN_OPACITY = 0.08 // minimum opacity for faded labels

// Magnifier size as fraction of container dimensions (1/3 of width and height)
const MAGNIFIER_SIZE_RATIO = 1 / 3

/**
 * Calculate label opacity based on distance from cursor and animation state.
 * Labels fade to low opacity when cursor is near to reduce visual clutter.
 * During give-up animation, all labels are hidden so the flashing region is visible.
 * Exception: If cursor is over a found region, that region's label stays visible.
 */
function calculateLabelOpacity(
  labelX: number,
  labelY: number,
  labelRegionId: string,
  cursorPosition: { x: number; y: number } | null,
  hoveredRegion: string | null,
  regionsFound: string[],
  isGiveUpAnimating: boolean
): number {
  // During give-up animation, hide all labels so the flashing region is clearly visible
  if (isGiveUpAnimating) return 0

  // No cursor position = full opacity
  if (!cursorPosition) return 1

  // If hovering over this label's region AND it's been found, show at full opacity
  if (hoveredRegion === labelRegionId && regionsFound.includes(labelRegionId)) {
    return 1
  }

  // Calculate distance from cursor to label
  const dx = labelX - cursorPosition.x
  const dy = labelY - cursorPosition.y
  const distance = Math.sqrt(dx * dx + dy * dy)

  // Outside fade radius = full opacity
  if (distance >= LABEL_FADE_RADIUS) return 1

  // Inside fade radius = interpolate from min to full based on distance
  const t = distance / LABEL_FADE_RADIUS
  return LABEL_MIN_OPACITY + t * (1 - LABEL_MIN_OPACITY)
}

interface BoundingBox {
  minX: number
  maxX: number
  minY: number
  maxY: number
  width: number
  height: number
  area: number
}

interface MapRendererProps {
  mapData: MapData
  regionsFound: string[]
  currentPrompt: string | null
  difficulty: string // Difficulty level ID
  selectedMap: 'world' | 'usa' // Map ID for calculating excluded regions
  selectedContinent: string // Continent ID for calculating excluded regions
  onRegionClick: (regionId: string, regionName: string) => void
  guessHistory: Array<{
    playerId: string
    regionId: string
    correct: boolean
  }>
  playerMetadata: Record<
    string,
    {
      id: string
      name: string
      emoji: string
      color: string
      userId?: string // Session ID that owns this player
    }
  >
  // Give up reveal animation
  giveUpReveal: {
    regionId: string
    regionName: string
    timestamp: number
  } | null
  // Give up callback
  onGiveUp: () => void
  // Force simulation tuning parameters
  forceTuning?: {
    showArrows?: boolean
    centeringStrength?: number
    collisionPadding?: number
    simulationIterations?: number
    useObstacles?: boolean
    obstaclePadding?: number
  }
  // Debug flags
  showDebugBoundingBoxes?: boolean
  // Multiplayer cursor sharing
  gameMode?: 'cooperative' | 'race' | 'turn-based'
  currentPlayer?: string // The player whose turn it is (for turn-based mode)
  localPlayerId?: string // The local player's ID (to filter out our own cursor from others)
  otherPlayerCursors?: Record<string, { x: number; y: number; userId: string } | null>
  onCursorUpdate?: (cursorPosition: { x: number; y: number } | null) => void
  // Unanimous give-up voting (for cooperative multiplayer)
  giveUpVotes?: string[] // Session/viewer IDs (userIds) who have voted to give up
  activeUserIds?: string[] // All unique session IDs participating (to show "1/2 sessions voted")
  viewerId?: string // This viewer's userId (to check if local session has voted)
  // Member players mapping (userId -> players) for cursor emoji display
  memberPlayers?: Record<string, Array<{ id: string; name: string; emoji: string; color: string }>>
}

/**
 * Calculate bounding box from SVG path string
 */
function calculateBoundingBox(pathString: string): BoundingBox {
  const numbers = pathString.match(/-?\d+\.?\d*/g)?.map(Number) || []

  if (numbers.length === 0) {
    return { minX: 0, maxX: 0, minY: 0, maxY: 0, width: 0, height: 0, area: 0 }
  }

  const xCoords: number[] = []
  const yCoords: number[] = []

  for (let i = 0; i < numbers.length; i += 2) {
    xCoords.push(numbers[i])
    if (i + 1 < numbers.length) {
      yCoords.push(numbers[i + 1])
    }
  }

  const minX = Math.min(...xCoords)
  const maxX = Math.max(...xCoords)
  const minY = Math.min(...yCoords)
  const maxY = Math.max(...yCoords)
  const width = maxX - minX
  const height = maxY - minY
  const area = width * height

  return { minX, maxX, minY, maxY, width, height, area }
}

interface RegionLabelPosition {
  regionId: string
  regionName: string
  x: number // pixel position
  y: number // pixel position
  players: string[]
}

export function MapRenderer({
  mapData,
  regionsFound,
  currentPrompt,
  difficulty,
  selectedMap,
  selectedContinent,
  onRegionClick,
  guessHistory,
  playerMetadata,
  giveUpReveal,
  onGiveUp,
  forceTuning = {},
  showDebugBoundingBoxes = SHOW_DEBUG_BOUNDING_BOXES,
  gameMode,
  currentPlayer,
  localPlayerId,
  otherPlayerCursors = {},
  onCursorUpdate,
  giveUpVotes = [],
  activeUserIds = [],
  viewerId,
  memberPlayers = {},
}: MapRendererProps) {
  // Extract force tuning parameters with defaults
  const {
    showArrows = false,
    centeringStrength = 2.0,
    collisionPadding = 5,
    simulationIterations = 200,
    useObstacles = true,
    obstaclePadding = 10,
  } = forceTuning
  const { resolvedTheme } = useTheme()
  const isDark = resolvedTheme === 'dark'

  // Calculate excluded regions (regions filtered out by difficulty/continent)
  const excludedRegions = useMemo(() => {
    // Get full unfiltered map data
    const fullMapData = selectedMap === 'world' ? WORLD_MAP : USA_MAP
    let allRegions = fullMapData.regions

    // Apply continent filter if world map
    if (selectedMap === 'world' && selectedContinent !== 'all') {
      allRegions = filterRegionsByContinent(allRegions, selectedContinent as ContinentId)
    }

    // Find regions in full data that aren't in filtered data
    const includedRegionIds = new Set(mapData.regions.map((r) => r.id))
    const excluded = allRegions.filter((r) => !includedRegionIds.has(r.id))

    console.log('[MapRenderer] Excluded regions by difficulty:', {
      total: allRegions.length,
      included: includedRegionIds.size,
      excluded: excluded.length,
      excludedNames: excluded.map((r) => r.name),
    })

    // Debug: Check if Gibraltar is included or excluded
    const gibraltar = allRegions.find((r) => r.id === 'gi')
    const gibraltarIncluded = includedRegionIds.has('gi')
    if (gibraltar) {
      console.log('[Gibraltar Debug]', gibraltarIncluded ? '✅ INCLUDED' : '❌ EXCLUDED', {
        inFilteredMap: gibraltarIncluded,
        difficulty,
        continent: selectedContinent,
      })
    }

    return excluded
  }, [mapData, selectedMap, selectedContinent, difficulty])

  // Create a set of excluded region IDs for quick lookup
  const excludedRegionIds = useMemo(
    () => new Set(excludedRegions.map((r) => r.id)),
    [excludedRegions]
  )

  const svgRef = useRef<SVGSVGElement>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  // Pre-computed largest piece sizes for multi-piece regions
  // Maps regionId -> {width, height} of the largest piece
  // Defined early because useRegionDetection needs it
  const largestPieceSizesRef = useRef<Map<string, { width: number; height: number }>>(new Map())

  // Region detection hook
  const { detectRegions, hoveredRegion, setHoveredRegion } = useRegionDetection({
    svgRef,
    containerRef,
    mapData,
    detectionBoxSize: 50,
    smallRegionThreshold: 15,
    smallRegionAreaThreshold: 200,
    largestPieceSizesCache: largestPieceSizesRef.current,
    regionsFound,
  })

  // State that needs to be available for hooks
  const cursorPositionRef = useRef<{ x: number; y: number } | null>(null)
  const initialCapturePositionRef = useRef<{ x: number; y: number } | null>(null)
  // Ref to track Give Up button bounds for pointer lock click detection
  const giveUpButtonBoundsRef = useRef<{
    left: number
    top: number
    right: number
    bottom: number
  } | null>(null)
  // Track if fake cursor is hovering over Give Up button (for pointer lock mode)
  const [isFakeCursorOverGiveUp, setIsFakeCursorOverGiveUp] = useState(false)
  const [cursorSquish, setCursorSquish] = useState({ x: 1, y: 1 })
  const [isReleasingPointerLock, setIsReleasingPointerLock] = useState(false)

  // Memoize pointer lock callbacks to prevent render loop
  const handleLockAcquired = useCallback(() => {
    // Save initial cursor position
    if (cursorPositionRef.current) {
      initialCapturePositionRef.current = { ...cursorPositionRef.current }
      console.log(
        '[Pointer Lock] 📍 Saved initial capture position:',
        initialCapturePositionRef.current
      )
    }
    // Note: Zoom update now handled by useMagnifierZoom hook
  }, [])

  const handleLockReleased = useCallback(() => {
    console.log('[Pointer Lock] 🔓 RELEASED - Starting cleanup')

    // Reset cursor squish
    setCursorSquish({ x: 1, y: 1 })
    setIsReleasingPointerLock(false)
    // Reset fake cursor hover state
    setIsFakeCursorOverGiveUp(false)
    // Note: Zoom recalculation now handled by useMagnifierZoom hook
  }, [])

  // Pointer lock hook (needed by zoom hook)
  const { pointerLocked, requestPointerLock, exitPointerLock } = usePointerLock({
    containerRef,
    onLockAcquired: handleLockAcquired,
    onLockReleased: handleLockReleased,
  })

  // Magnifier zoom hook
  const { targetZoom, setTargetZoom, zoomSpring, getCurrentZoom, uncappedAdaptiveZoomRef } =
    useMagnifierZoom({
      containerRef,
      svgRef,
      viewBox: mapData.viewBox,
      threshold: PRECISION_MODE_THRESHOLD,
      pointerLocked,
      initialZoom: 10,
    })

  const [svgDimensions, setSvgDimensions] = useState({ width: 1000, height: 500 })
  const [cursorPosition, setCursorPosition] = useState<{ x: number; y: number } | null>(null)
  const [showMagnifier, setShowMagnifier] = useState(false)
  const [targetOpacity, setTargetOpacity] = useState(0)
  const [targetTop, setTargetTop] = useState(20)
  const [targetLeft, setTargetLeft] = useState(20)
  const [smallestRegionSize, setSmallestRegionSize] = useState<number>(Infinity)
  const [shiftPressed, setShiftPressed] = useState(false)

  // Track whether current target region needs magnification
  const [targetNeedsMagnification, setTargetNeedsMagnification] = useState(false)

  // Give up reveal animation state
  const [giveUpFlashProgress, setGiveUpFlashProgress] = useState(0) // 0-1 pulsing value
  const [isGiveUpAnimating, setIsGiveUpAnimating] = useState(false) // Track if animation in progress
  // Saved button position to prevent jumping during zoom animation
  const [savedButtonPosition, setSavedButtonPosition] = useState<{
    top: number
    right: number
  } | null>(null)

  // Debug: Track bounding boxes for visualization
  const [debugBoundingBoxes, setDebugBoundingBoxes] = useState<DebugBoundingBox[]>([])
  // Debug: Track full zoom search result for detailed panel
  const [zoomSearchDebugInfo, setZoomSearchDebugInfo] = useState<ReturnType<
    typeof findOptimalZoom
  > | null>(null)

  // Configuration
  const MAX_ZOOM = 1000 // Maximum zoom level (for Gibraltar at 0.08px!)
  const HIGH_ZOOM_THRESHOLD = 100 // Show gold border above this zoom level

  // Movement speed multiplier based on smallest region size
  // When pointer lock is active, apply this multiplier to movementX/movementY
  // For sub-pixel regions (< 1px): 3% speed (ultra precision)
  // For tiny regions (1-5px): 10% speed (high precision)
  // For small regions (5-15px): 25% speed (moderate precision)
  const getMovementMultiplier = (size: number): number => {
    if (size < 1) return 0.03 // Ultra precision for sub-pixel regions like Gibraltar (0.08px)
    if (size < 5) return 0.1 // High precision for regions like Jersey (0.82px)
    if (size < 15) return 0.25 // Moderate precision for regions like Rhode Island (11px)
    return 1.0 // Normal speed for larger regions
  }

  // Pre-compute largest piece sizes for multi-piece regions
  useEffect(() => {
    if (!svgRef.current) return

    const largestPieceSizes = new Map<string, { width: number; height: number }>()

    mapData.regions.forEach((region) => {
      const pathData = region.path
      const pieceSeparatorRegex = /(?<=z)\s*m\s*/i
      const rawPieces = pathData.split(pieceSeparatorRegex)

      if (rawPieces.length > 1) {
        // Multi-piece region: use the FIRST piece (mainland), not largest
        // The first piece is typically the mainland, with islands as subsequent pieces
        const svg = svgRef.current
        if (!svg) return

        // Just measure the first piece
        const tempPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
        tempPath.setAttribute('d', rawPieces[0]) // First piece already has 'm' command
        tempPath.style.visibility = 'hidden'
        svg.appendChild(tempPath)

        const bbox = tempPath.getBoundingClientRect()
        const firstPieceSize = { width: bbox.width, height: bbox.height }

        svg.removeChild(tempPath)

        largestPieceSizes.set(region.id, firstPieceSize)

        // Log multi-piece regions for debugging
        if (region.id === 'pt' || region.id === 'nl') {
          console.log(
            `[Pre-compute] ${region.id}: ${rawPieces.length} pieces, using first piece: ${firstPieceSize.width.toFixed(2)}px × ${firstPieceSize.height.toFixed(2)}px`
          )
        }
      }
    })

    largestPieceSizesRef.current = largestPieceSizes
  }, [mapData])

  // Request pointer lock on first click
  const handleContainerClick = (e: React.MouseEvent<HTMLDivElement>) => {
    // Silently request pointer lock if not already locked
    // This makes the first gameplay click also enable precision mode
    if (!pointerLocked) {
      requestPointerLock()
      console.log('[Pointer Lock] 🔒 Silently requested (user clicked map)')
      return // Don't process region click on the first click that requests lock
    }

    // When pointer lock is active, browser doesn't deliver click events to SVG children
    // We need to manually detect which region is under the cursor
    if (pointerLocked && cursorPositionRef.current && containerRef.current && svgRef.current) {
      const { x: cursorX, y: cursorY } = cursorPositionRef.current

      console.log('[CLICK] Pointer lock click at cursor position:', { cursorX, cursorY })

      // Check if clicking on Give Up button (pointer lock mode requires manual detection)
      const buttonBounds = giveUpButtonBoundsRef.current
      if (
        buttonBounds &&
        !isGiveUpAnimating &&
        cursorX >= buttonBounds.left &&
        cursorX <= buttonBounds.right &&
        cursorY >= buttonBounds.top &&
        cursorY <= buttonBounds.bottom
      ) {
        console.log('[CLICK] Give Up button clicked via pointer lock')
        onGiveUp()
        return
      }

      // Use the same detection logic as hover tracking (50px detection box)
      // This checks the main map SVG at the cursor position
      const { detectedRegions, regionUnderCursor } = detectRegions(cursorX, cursorY)

      console.log('[CLICK] Detection results:', {
        detectedRegions: detectedRegions.map((r) => r.id),
        regionUnderCursor,
        detectedCount: detectedRegions.length,
      })

      if (regionUnderCursor) {
        // Find the region data to get the name
        const region = mapData.regions.find((r) => r.id === regionUnderCursor)
        if (region) {
          console.log('[CLICK] Detected region under cursor:', {
            regionId: regionUnderCursor,
            regionName: region.name,
          })
          onRegionClick(regionUnderCursor, region.name)
        } else {
          console.log('[CLICK] Region ID found but not in mapData:', regionUnderCursor)
        }
      } else {
        // No region directly under cursor - don't count as a click
        // This prevents sea/ocean clicks from being counted as wrong guesses
        console.log('[CLICK] No region under cursor - click ignored (sea/empty area)')
      }
    }
  }

  // Animated spring values for smooth transitions
  // Note: Zoom animation is now handled by useMagnifierZoom hook
  // This spring only handles: opacity, position, and movement multiplier
  const [magnifierSpring, magnifierApi] = useSpring(
    () => ({
      opacity: targetOpacity,
      top: targetTop,
      left: targetLeft,
      movementMultiplier: getMovementMultiplier(smallestRegionSize),
      config: (key) => {
        if (key === 'opacity') {
          return targetOpacity === 1
            ? { duration: 100 } // Fade in: 0.1 seconds
            : { duration: 1000 } // Fade out: 1 second
        }
        if (key === 'movementMultiplier') {
          // Movement multiplier: slower transitions for smooth damping changes
          // Lower tension = slower animation, higher friction = less overshoot
          return { tension: 60, friction: 20 }
        }
        // Position: medium speed
        return { tension: 200, friction: 25 }
      },
    }),
    [targetOpacity, targetTop, targetLeft, smallestRegionSize]
  )

  // Calculate the display viewBox using fit-crop-with-fill strategy
  // This ensures the custom crop region is visible while filling the container
  const displayViewBox = useMemo(() => {
    // If no custom crop, use the regular viewBox (which may be a calculated bounding box)
    if (!mapData.customCrop) {
      return mapData.viewBox
    }

    // Need container dimensions to calculate aspect ratio
    if (svgDimensions.width <= 0 || svgDimensions.height <= 0) {
      return mapData.viewBox
    }

    const containerAspect = svgDimensions.width / svgDimensions.height
    const originalBounds = parseViewBox(mapData.originalViewBox)
    const cropRegion = parseViewBox(mapData.customCrop)

    const result = calculateFitCropViewBox(originalBounds, cropRegion, containerAspect)
    console.log('[MapRenderer] Calculated displayViewBox:', {
      customCrop: mapData.customCrop,
      originalViewBox: mapData.originalViewBox,
      containerAspect: containerAspect.toFixed(2),
      result,
    })
    return result
  }, [mapData.customCrop, mapData.originalViewBox, mapData.viewBox, svgDimensions])

  // Parse the display viewBox for animation and calculations
  const defaultViewBoxParts = useMemo(() => {
    const parts = displayViewBox.split(' ').map(Number)
    return {
      x: parts[0] || 0,
      y: parts[1] || 0,
      width: parts[2] || 1000,
      height: parts[3] || 500,
    }
  }, [displayViewBox])

  // Compute which regions network cursors are hovering over
  // Returns a map of regionId -> { playerId, color } for regions with network hovers
  const networkHoveredRegions = useMemo(() => {
    const result: Record<string, { playerId: string; color: string }> = {}

    // Skip if no SVG ref or no cursors
    if (!svgRef.current) return result

    Object.entries(otherPlayerCursors).forEach(([playerId, position]) => {
      // Skip our own cursor and null positions
      if (playerId === localPlayerId || !position) return

      // In turn-based mode, only show hover when it's not our turn
      if (gameMode === 'turn-based' && currentPlayer === localPlayerId) return

      // Get player color
      const player = playerMetadata[playerId]
      if (!player) return

      // Use SVG's native hit testing
      // Create an SVGPoint and use getIntersectionList or check each path
      const svg = svgRef.current
      if (!svg) return

      // Find the region element under this point using elementFromPoint
      // First convert SVG coords to screen coords
      const viewBoxParts = displayViewBox.split(' ').map(Number)
      const viewBoxX = viewBoxParts[0] || 0
      const viewBoxY = viewBoxParts[1] || 0
      const viewBoxW = viewBoxParts[2] || 1000
      const viewBoxH = viewBoxParts[3] || 500
      const svgRect = svg.getBoundingClientRect()
      const scaleX = svgRect.width / viewBoxW
      const scaleY = svgRect.height / viewBoxH
      const screenX = (position.x - viewBoxX) * scaleX + svgRect.left
      const screenY = (position.y - viewBoxY) * scaleY + svgRect.top

      // Get element at this screen position
      const element = document.elementFromPoint(screenX, screenY)
      if (element && element.hasAttribute('data-region-id')) {
        const regionId = element.getAttribute('data-region-id')
        if (regionId) {
          result[regionId] = { playerId, color: player.color }
        }
      }
    })

    return result
  }, [
    otherPlayerCursors,
    localPlayerId,
    gameMode,
    currentPlayer,
    playerMetadata,
    displayViewBox,
    svgDimensions, // Re-run when SVG size changes
  ])

  // State for give-up zoom animation target values
  const [giveUpZoomTarget, setGiveUpZoomTarget] = useState({
    scale: 1,
    translateX: 0,
    translateY: 0,
  })

  // Spring for main map zoom animation (used during give-up reveal)
  // Uses CSS transform for reliable animation instead of viewBox manipulation
  const mainMapSpring = useSpring({
    scale: giveUpZoomTarget.scale,
    translateX: giveUpZoomTarget.translateX,
    translateY: giveUpZoomTarget.translateY,
    config: { tension: 120, friction: 20 },
    onChange: (result) => {
      console.log('[GiveUp Zoom] Spring animating:', result.value)
    },
  })

  // Note: Zoom animation with pause/resume is now handled by useMagnifierZoom hook
  // This effect only updates the remaining spring properties: opacity, position, movement multiplier
  useEffect(() => {
    magnifierApi.start({
      opacity: targetOpacity,
      top: targetTop,
      left: targetLeft,
      movementMultiplier: getMovementMultiplier(smallestRegionSize),
    })
  }, [targetOpacity, targetTop, targetLeft, smallestRegionSize, magnifierApi])

  // Check if current target region needs magnification
  useEffect(() => {
    if (!currentPrompt || !svgRef.current || !containerRef.current) {
      setTargetNeedsMagnification(false)
      return
    }

    // Find the path element for the target region
    const svgElement = svgRef.current
    const path = svgElement.querySelector(`path[data-region-id="${currentPrompt}"]`)
    if (!path || !(path instanceof SVGGeometryElement)) {
      setTargetNeedsMagnification(false)
      return
    }

    // Get the bounding box size
    const bbox = path.getBoundingClientRect()
    const pixelWidth = bbox.width
    const pixelHeight = bbox.height
    const pixelArea = pixelWidth * pixelHeight

    // Use same thresholds as region detection
    const SMALL_REGION_THRESHOLD = 15 // pixels
    const SMALL_REGION_AREA_THRESHOLD = 200 // px²

    const isVerySmall =
      pixelWidth < SMALL_REGION_THRESHOLD ||
      pixelHeight < SMALL_REGION_THRESHOLD ||
      pixelArea < SMALL_REGION_AREA_THRESHOLD

    setTargetNeedsMagnification(isVerySmall)

    console.log('[MapRenderer] Target region magnification check:', {
      regionId: currentPrompt,
      pixelWidth: pixelWidth.toFixed(2),
      pixelHeight: pixelHeight.toFixed(2),
      pixelArea: pixelArea.toFixed(2),
      isVerySmall,
      needsMagnification: isVerySmall,
    })
  }, [currentPrompt, svgDimensions]) // Re-check when prompt or SVG size changes

  // Give up reveal animation effect
  useEffect(() => {
    if (!giveUpReveal) {
      setGiveUpFlashProgress(0)
      setIsGiveUpAnimating(false)
      setSavedButtonPosition(null)
      // Reset transform to default when animation clears
      setGiveUpZoomTarget({ scale: 1, translateX: 0, translateY: 0 })
      return
    }

    // Track if this effect has been cleaned up (prevents stale animations)
    let isCancelled = false
    let animationFrameId: number | null = null
    let timeoutId: ReturnType<typeof setTimeout> | null = null

    // Start animation
    setIsGiveUpAnimating(true)
    console.log('[GiveUp Zoom] giveUpReveal triggered:', giveUpReveal)

    // Save current button position before zoom changes the layout
    if (svgRef.current && containerRef.current) {
      const svgRect = svgRef.current.getBoundingClientRect()
      const containerRect = containerRef.current.getBoundingClientRect()
      const svgOffsetX = svgRect.left - containerRect.left
      const svgOffsetY = svgRect.top - containerRect.top
      const buttonTop = svgOffsetY + 8
      const buttonRight = containerRect.width - (svgOffsetX + svgRect.width) + 8
      setSavedButtonPosition({ top: buttonTop, right: buttonRight })
    }

    // Calculate CSS transform to zoom and center on the revealed region
    if (svgRef.current && containerRef.current) {
      const path = svgRef.current.querySelector(`path[data-region-id="${giveUpReveal.regionId}"]`)
      console.log('[GiveUp Zoom] Found path:', path, 'for regionId:', giveUpReveal.regionId)
      if (path && path instanceof SVGGeometryElement) {
        const bbox = path.getBoundingClientRect()
        const svgRect = svgRef.current.getBoundingClientRect()

        // Calculate CSS transform for zoom animation
        // Region center relative to SVG element
        const regionCenterX = bbox.left + bbox.width / 2 - svgRect.left
        const regionCenterY = bbox.top + bbox.height / 2 - svgRect.top

        // SVG center
        const svgCenterX = svgRect.width / 2
        const svgCenterY = svgRect.height / 2

        // Calculate scale: zoom in so region is clearly visible
        // For tiny regions, zoom more; for larger ones, zoom less
        const regionSize = Math.max(bbox.width, bbox.height)
        const targetSize = Math.min(svgRect.width, svgRect.height) * 0.3 // Region should be ~30% of viewport
        const scale = Math.min(8, Math.max(2, targetSize / Math.max(regionSize, 1)))

        // Calculate translation to center the region
        // After scaling, we need to translate so the region center is at SVG center
        const translateX = (svgCenterX - regionCenterX) * scale
        const translateY = (svgCenterY - regionCenterY) * scale

        console.log('[GiveUp Zoom] Starting CSS transform animation:', {
          regionCenter: { x: regionCenterX, y: regionCenterY },
          svgCenter: { x: svgCenterX, y: svgCenterY },
          regionSize,
          scale,
          translate: { x: translateX, y: translateY },
        })

        // Start zoom-in animation using CSS transform
        console.log('[GiveUp Zoom] Setting zoom target:', { scale, translateX, translateY })
        setGiveUpZoomTarget({ scale, translateX, translateY })
      }
    }

    // Animation: 3 pulses over 2 seconds
    const duration = 2000
    const pulses = 3
    const startTime = Date.now()

    const animate = () => {
      // Check if this animation has been cancelled (new give-up started)
      if (isCancelled) {
        console.log('[GiveUp Zoom] Animation cancelled - new give-up started')
        return
      }

      const elapsed = Date.now() - startTime
      const progress = Math.min(elapsed / duration, 1)

      // Create pulsing effect: sin wave for smooth on/off
      const pulseProgress = Math.sin(progress * Math.PI * pulses * 2) * 0.5 + 0.5
      setGiveUpFlashProgress(pulseProgress)

      if (progress < 1) {
        animationFrameId = requestAnimationFrame(animate)
      } else {
        // Animation complete - zoom back out to default
        console.log('[GiveUp Zoom] Zooming back out to default')
        setGiveUpZoomTarget({ scale: 1, translateX: 0, translateY: 0 })

        // Clear reveal state after a short delay to let zoom-out start
        timeoutId = setTimeout(() => {
          if (!isCancelled) {
            setGiveUpFlashProgress(0)
            setIsGiveUpAnimating(false)
            setSavedButtonPosition(null)
          }
        }, 100)
      }
    }

    animationFrameId = requestAnimationFrame(animate)

    // Cleanup: cancel animation if giveUpReveal changes before animation completes
    return () => {
      isCancelled = true
      if (animationFrameId !== null) {
        cancelAnimationFrame(animationFrameId)
      }
      if (timeoutId !== null) {
        clearTimeout(timeoutId)
      }
      console.log('[GiveUp Zoom] Cleanup - cancelling previous animation')
    }
  }, [giveUpReveal?.timestamp]) // Re-run when timestamp changes

  // Shift key listener - show magnifier when Shift is held
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Shift' && !e.repeat) {
        setShiftPressed(true)
      }
    }

    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.key === 'Shift') {
        setShiftPressed(false)
      }
    }

    window.addEventListener('keydown', handleKeyDown)
    window.addEventListener('keyup', handleKeyUp)

    return () => {
      window.removeEventListener('keydown', handleKeyDown)
      window.removeEventListener('keyup', handleKeyUp)
    }
  }, [])

  const [labelPositions, setLabelPositions] = useState<RegionLabelPosition[]>([])
  const [smallRegionLabelPositions, setSmallRegionLabelPositions] = useState<
    Array<{
      regionId: string
      regionName: string
      isFound: boolean
      labelX: number // pixel position for label
      labelY: number // pixel position for label
      lineStartX: number // pixel position for line start
      lineStartY: number // pixel position for line start
      lineEndX: number // pixel position for line end (region center)
      lineEndY: number // pixel position for line end
    }>
  >([])

  // Measure container element to get available space for viewBox calculation
  // IMPORTANT: We measure the container, not the SVG, to avoid circular dependency:
  // The SVG fills the container, and the viewBox is calculated based on container aspect ratio
  useEffect(() => {
    if (!containerRef.current) return

    const updateDimensions = () => {
      const rect = containerRef.current?.getBoundingClientRect()
      if (rect) {
        setSvgDimensions({ width: rect.width, height: rect.height })
      }
    }

    // Use ResizeObserver to detect panel resizing (not just window resize)
    const observer = new ResizeObserver(() => {
      requestAnimationFrame(() => {
        updateDimensions()
      })
    })

    observer.observe(containerRef.current)

    // Initial measurement
    updateDimensions()

    return () => observer.disconnect()
  }, []) // No dependencies - container size doesn't depend on viewBox

  // Calculate label positions using ghost elements
  useEffect(() => {
    if (!svgRef.current || !containerRef.current) return

    const updateLabelPositions = () => {
      const containerRect = containerRef.current?.getBoundingClientRect()
      if (!containerRect) return

      const positions: RegionLabelPosition[] = []
      const smallPositions: typeof smallRegionLabelPositions = []

      // Parse viewBox for scale calculations
      const viewBoxParts = displayViewBox.split(' ').map(Number)
      const viewBoxX = viewBoxParts[0] || 0
      const viewBoxY = viewBoxParts[1] || 0
      const viewBoxWidth = viewBoxParts[2] || 1000
      const viewBoxHeight = viewBoxParts[3] || 1000

      const svgRect = svgRef.current?.getBoundingClientRect()
      if (!svgRect) return

      const scaleX = svgRect.width / viewBoxWidth
      const scaleY = svgRect.height / viewBoxHeight

      // Calculate SVG offset within container (accounts for padding)
      const svgOffsetX = svgRect.left - containerRect.left
      const svgOffsetY = svgRect.top - containerRect.top

      // Collect all regions with their info for force simulation
      interface LabelNode extends SimulationNodeDatum {
        id: string
        name: string
        x: number
        y: number
        targetX: number
        targetY: number
        width: number
        height: number
        isFound: boolean
        isSmall: boolean
        players?: string[]
      }

      const allLabelNodes: LabelNode[] = []

      // Process both included regions and excluded regions for labeling
      ;[...mapData.regions, ...excludedRegions].forEach((region) => {
        // Calculate centroid pixel position directly from SVG coordinates
        // Account for SVG offset within container (padding, etc.)
        const centroidPixelX = (region.center[0] - viewBoxX) * scaleX + svgOffsetX
        const centroidPixelY = (region.center[1] - viewBoxY) * scaleY + svgOffsetY

        const pixelX = centroidPixelX
        const pixelY = centroidPixelY

        // Get the actual region path element to measure its TRUE screen dimensions
        const regionPath = svgRef.current?.querySelector(`path[data-region-id="${region.id}"]`)
        if (!regionPath) return

        const pathRect = regionPath.getBoundingClientRect()
        const pixelWidth = pathRect.width
        const pixelHeight = pathRect.height
        const pixelArea = pathRect.width * pathRect.height

        // Check if this is a small region using ACTUAL screen pixels
        const isSmall = pixelWidth < 10 || pixelHeight < 10 || pixelArea < 100

        // Debug logging ONLY for Gibraltar and ultra-tiny regions (< 1px)
        if (region.id === 'gi' || pixelWidth < 1 || pixelHeight < 1) {
          console.log(
            `[MapRenderer] ${region.id === 'gi' ? '🎯 GIBRALTAR' : '🔍 TINY'}: ${region.name} - ` +
              `W:${pixelWidth.toFixed(2)}px H:${pixelHeight.toFixed(2)}px ` +
              `Area:${pixelArea.toFixed(2)}px²`
          )
        }

        // Collect label nodes for regions that need labels
        // Only show arrow labels for small regions if showArrows flag is enabled
        // Exception: Washington DC always gets arrow label (too small on USA map)
        const isDC = region.id === 'dc'
        const isExcluded = excludedRegionIds.has(region.id)
        // Show label if: region is found, OR region is excluded (pre-found), OR it's small and arrows enabled
        const shouldShowLabel =
          regionsFound.includes(region.id) || isExcluded || (isSmall && (showArrows || isDC))

        if (shouldShowLabel) {
          const players = regionsFound.includes(region.id)
            ? guessHistory
                .filter((guess) => guess.regionId === region.id && guess.correct)
                .map((guess) => guess.playerId)
                .filter((playerId, index, self) => self.indexOf(playerId) === index)
            : undefined

          const labelWidth = region.name.length * 7 + 15
          const labelHeight = isSmall ? 25 : 30

          // Regular found states (non-small) get positioned exactly at centroid
          // Only small regions go through force simulation
          if (isSmall) {
            allLabelNodes.push({
              id: region.id,
              name: region.name,
              x: pixelX, // Start directly on region - will spread out to avoid collisions
              y: pixelY,
              targetX: pixelX, // Anchor point to pull back toward
              targetY: pixelY,
              width: labelWidth,
              height: labelHeight,
              isFound: regionsFound.includes(region.id),
              isSmall,
              players,
            })
          } else {
            // Add directly to positions array - no force simulation
            positions.push({
              regionId: region.id,
              regionName: region.name,
              x: pixelX,
              y: pixelY,
              players: players || [],
            })
          }
        }
      })

      // Add region obstacles to repel labels away from the map itself
      interface ObstacleNode extends SimulationNodeDatum {
        id: string
        x: number
        y: number
        isObstacle: true
        radius: number
      }

      const obstacleNodes: ObstacleNode[] = []

      // Add all regions (including unlabeled ones) as obstacles (if enabled)
      if (useObstacles) {
        mapData.regions.forEach((region) => {
          const ghostElement = svgRef.current?.querySelector(`[data-ghost-region="${region.id}"]`)
          if (!ghostElement) return

          const ghostRect = ghostElement.getBoundingClientRect()
          const pixelX = ghostRect.left - containerRect.left + ghostRect.width / 2
          const pixelY = ghostRect.top - containerRect.top + ghostRect.height / 2

          const regionPath = svgRef.current?.querySelector(`path[data-region-id="${region.id}"]`)
          if (!regionPath) return

          const pathRect = regionPath.getBoundingClientRect()
          const regionRadius = Math.max(pathRect.width, pathRect.height) / 2

          obstacleNodes.push({
            id: `obstacle-${region.id}`,
            isObstacle: true,
            x: pixelX,
            y: pixelY,
            radius: regionRadius + obstaclePadding,
          })
        })
      }

      // Combine labels and obstacles for simulation
      const allNodes = [...allLabelNodes, ...obstacleNodes]

      // Run force simulation to position labels without overlaps
      if (allLabelNodes.length > 0) {
        const simulation = forceSimulation(allNodes)
          .force(
            'collide',
            forceCollide<LabelNode | ObstacleNode>().radius((d) => {
              if ('isObstacle' in d && d.isObstacle) {
                return (d as ObstacleNode).radius
              }
              const label = d as LabelNode
              return Math.max(label.width, label.height) / 2 + collisionPadding
            })
          )
          .force(
            'x',
            forceX<LabelNode | ObstacleNode>((d) => {
              if ('isObstacle' in d && d.isObstacle) return d.x
              return (d as LabelNode).targetX
            }).strength(centeringStrength)
          )
          .force(
            'y',
            forceY<LabelNode | ObstacleNode>((d) => {
              if ('isObstacle' in d && d.isObstacle) return d.y
              return (d as LabelNode).targetY
            }).strength(centeringStrength)
          )
          .stop()

        // Run simulation - labels start on regions and only move as needed
        for (let i = 0; i < simulationIterations; i++) {
          simulation.tick()
        }

        // Helper: Calculate arrow start point on label edge closest to region
        const getArrowStartPoint = (
          labelX: number,
          labelY: number,
          labelWidth: number,
          labelHeight: number,
          targetX: number,
          targetY: number
        ): { x: number; y: number } => {
          // Direction from label to region
          const dx = targetX - labelX
          const dy = targetY - labelY

          // Label edges
          const halfWidth = labelWidth / 2
          const halfHeight = labelHeight / 2

          // Calculate intersection with label box
          // Use parametric line equation: point = (labelX, labelY) + t * (dx, dy)
          // Find t where line intersects rectangle edges

          let bestT = 0
          const epsilon = 1e-10

          // Check each edge
          if (Math.abs(dx) > epsilon) {
            // Right edge: x = labelX + halfWidth
            const tRight = halfWidth / dx
            if (tRight > 0 && tRight <= 1) {
              const y = labelY + tRight * dy
              if (Math.abs(y - labelY) <= halfHeight) {
                bestT = tRight
              }
            }
            // Left edge: x = labelX - halfWidth
            const tLeft = -halfWidth / dx
            if (tLeft > 0 && tLeft <= 1) {
              const y = labelY + tLeft * dy
              if (Math.abs(y - labelY) <= halfHeight) {
                if (bestT === 0 || tLeft < bestT) bestT = tLeft
              }
            }
          }

          if (Math.abs(dy) > epsilon) {
            // Bottom edge: y = labelY + halfHeight
            const tBottom = halfHeight / dy
            if (tBottom > 0 && tBottom <= 1) {
              const x = labelX + tBottom * dx
              if (Math.abs(x - labelX) <= halfWidth) {
                if (bestT === 0 || tBottom < bestT) bestT = tBottom
              }
            }
            // Top edge: y = labelY - halfHeight
            const tTop = -halfHeight / dy
            if (tTop > 0 && tTop <= 1) {
              const x = labelX + tTop * dx
              if (Math.abs(x - labelX) <= halfWidth) {
                if (bestT === 0 || tTop < bestT) bestT = tTop
              }
            }
          }

          return {
            x: labelX + bestT * dx,
            y: labelY + bestT * dy,
          }
        }

        // Extract positions from simulation results (only small regions now)
        for (const node of allLabelNodes) {
          // Special handling for Washington DC - position off the map to avoid blocking other states
          if (node.id === 'dc') {
            // Position DC label to the right of the map, outside the main map area
            const containerWidth = containerRect.width
            const labelX = containerWidth - 80 // 80px from right edge
            const labelY = svgOffsetY + svgRect.height * 0.35 // Upper-middle area

            const arrowStart = getArrowStartPoint(
              labelX,
              labelY,
              node.width,
              node.height,
              node.targetX,
              node.targetY
            )

            smallPositions.push({
              regionId: node.id,
              regionName: node.name,
              isFound: node.isFound,
              labelX: labelX,
              labelY: labelY,
              lineStartX: arrowStart.x,
              lineStartY: arrowStart.y,
              lineEndX: node.targetX,
              lineEndY: node.targetY,
            })
            continue // Skip normal processing
          }

          // All remaining nodes are small regions (non-small are added directly to positions)
          const arrowStart = getArrowStartPoint(
            node.x!,
            node.y!,
            node.width,
            node.height,
            node.targetX,
            node.targetY
          )

          smallPositions.push({
            regionId: node.id,
            regionName: node.name,
            isFound: node.isFound,
            labelX: node.x!,
            labelY: node.y!,
            lineStartX: arrowStart.x,
            lineStartY: arrowStart.y,
            lineEndX: node.targetX,
            lineEndY: node.targetY,
          })
        }
      }

      setLabelPositions(positions)
      setSmallRegionLabelPositions(smallPositions)

      // Debug: Log summary
      console.log('[MapRenderer] Label positions updated:', {
        mapId: mapData.id,
        totalRegions: mapData.regions.length,
        regularLabels: positions.length,
        smallRegionLabels: smallPositions.length,
        viewBox: mapData.viewBox,
        svgDimensions,
      })
    }

    // Small delay to ensure ghost elements are rendered
    const timeoutId = setTimeout(updateLabelPositions, 0)

    return () => {
      clearTimeout(timeoutId)
    }
  }, [
    mapData,
    regionsFound,
    guessHistory,
    svgDimensions,
    excludedRegions,
    excludedRegionIds,
    displayViewBox,
  ])

  // Calculate viewBox dimensions for label offset calculations and sea background
  const viewBoxParts = displayViewBox.split(' ').map(Number)
  const viewBoxX = viewBoxParts[0] || 0
  const viewBoxY = viewBoxParts[1] || 0
  const viewBoxWidth = viewBoxParts[2] || 1000
  const viewBoxHeight = viewBoxParts[3] || 1000

  const showOutline = (region: MapRegion): boolean => {
    // Easy mode: always show outlines
    if (difficulty === 'easy') return true

    // Medium/Hard mode: only show outline on hover or if found
    return hoveredRegion === region.id || regionsFound.includes(region.id)
  }

  // Helper: Get the player who found a specific region
  const getPlayerWhoFoundRegion = (regionId: string): string | null => {
    const guess = guessHistory.find((g) => g.regionId === regionId && g.correct)
    return guess?.playerId || null
  }

  // Handle mouse movement to track cursor and show magnifier when needed
  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!svgRef.current || !containerRef.current) return

    // Don't process mouse movement during pointer lock release animation
    if (isReleasingPointerLock) return

    const containerRect = containerRef.current.getBoundingClientRect()
    const svgRect = svgRef.current.getBoundingClientRect()

    // Get cursor position relative to container
    let cursorX: number
    let cursorY: number

    if (pointerLocked) {
      // When pointer is locked, use movement deltas with precision multiplier
      const lastX = cursorPositionRef.current?.x ?? containerRect.width / 2
      const lastY = cursorPositionRef.current?.y ?? containerRect.height / 2

      // Apply smoothly animated movement multiplier for gradual cursor dampening transitions
      // This prevents jarring changes when moving between regions of different sizes
      const currentMultiplier = magnifierSpring.movementMultiplier.get()

      // Boundary dampening and squish effect
      // As cursor approaches edge, dampen movement and visually squish the cursor
      // When squished enough, the cursor "escapes" through the boundary and releases pointer lock
      const dampenZone = 40 // Distance from edge where dampening starts (px)
      const squishZone = 20 // Distance from edge where squish becomes visible (px)
      const escapeThreshold = 2 // When within this distance, escape! (px)

      // Calculate SVG offset within container (SVG may be smaller due to aspect ratio)
      const svgOffsetX = svgRect.left - containerRect.left
      const svgOffsetY = svgRect.top - containerRect.top

      // First, calculate undampened position to check how close we are to edges
      const undampenedX = lastX + e.movementX * currentMultiplier
      const undampenedY = lastY + e.movementY * currentMultiplier

      // Calculate distance from SVG edges (not container edges!)
      // This is critical - the interactive area is the SVG, not the container
      const distLeft = undampenedX - svgOffsetX
      const distRight = svgOffsetX + svgRect.width - undampenedX
      const distTop = undampenedY - svgOffsetY
      const distBottom = svgOffsetY + svgRect.height - undampenedY

      // Find closest edge distance
      const minDist = Math.min(distLeft, distRight, distTop, distBottom)

      // Calculate dampening factor based on proximity to edge
      let dampenFactor = 1.0
      if (minDist < dampenZone) {
        // Quadratic easing for smooth dampening
        const t = minDist / dampenZone
        dampenFactor = t * t // Squared for stronger dampening near edge
      }

      // Apply dampening to movement - this is the actual cursor position we'll use
      const dampenedDeltaX = e.movementX * currentMultiplier * dampenFactor
      const dampenedDeltaY = e.movementY * currentMultiplier * dampenFactor
      cursorX = lastX + dampenedDeltaX
      cursorY = lastY + dampenedDeltaY

      // Now check escape threshold using the DAMPENED position (not undampened!)
      // This is critical - we need to check where the cursor actually is, not where it would be without dampening
      // And we must use SVG bounds, not container bounds!
      const dampenedDistLeft = cursorX - svgOffsetX
      const dampenedDistRight = svgOffsetX + svgRect.width - cursorX
      const dampenedDistTop = cursorY - svgOffsetY
      const dampenedDistBottom = svgOffsetY + svgRect.height - cursorY
      const dampenedMinDist = Math.min(
        dampenedDistLeft,
        dampenedDistRight,
        dampenedDistTop,
        dampenedDistBottom
      )

      // Debug logging for boundary proximity
      if (dampenedMinDist < squishZone) {
        console.log('[Squish Debug]', {
          cursorPos: { x: cursorX.toFixed(1), y: cursorY.toFixed(1) },
          containerSize: {
            width: containerRect.width.toFixed(1),
            height: containerRect.height.toFixed(1),
          },
          svgSize: { width: svgRect.width.toFixed(1), height: svgRect.height.toFixed(1) },
          svgOffset: { x: svgOffsetX.toFixed(1), y: svgOffsetY.toFixed(1) },
          distances: {
            left: dampenedDistLeft.toFixed(1),
            right: dampenedDistRight.toFixed(1),
            top: dampenedDistTop.toFixed(1),
            bottom: dampenedDistBottom.toFixed(1),
            min: dampenedMinDist.toFixed(1),
          },
          dampenFactor: dampenFactor.toFixed(3),
          thresholds: {
            squishZone,
            escapeThreshold,
          },
          willEscape: dampenedMinDist < escapeThreshold,
        })
      }

      // Check if cursor has squished through and should escape (using dampened position!)
      if (dampenedMinDist < escapeThreshold && !isReleasingPointerLock) {
        console.log('[Pointer Lock] 🔓 ESCAPING (squished through boundary):', {
          dampenedMinDist,
          escapeThreshold,
          cursorX,
          cursorY,
          whichEdge: {
            left: dampenedDistLeft === dampenedMinDist,
            right: dampenedDistRight === dampenedMinDist,
            top: dampenedDistTop === dampenedMinDist,
            bottom: dampenedDistBottom === dampenedMinDist,
          },
        })

        // Start animation back to initial capture position
        setIsReleasingPointerLock(true)

        // Animate cursor back to initial position before releasing
        if (initialCapturePositionRef.current) {
          const startPos = { x: cursorX, y: cursorY }
          const endPos = initialCapturePositionRef.current
          const duration = 200 // ms
          const startTime = performance.now()

          const animate = (currentTime: number) => {
            const elapsed = currentTime - startTime
            const progress = Math.min(elapsed / duration, 1)

            // Ease out cubic for smooth deceleration
            const eased = 1 - (1 - progress) ** 3

            const interpolatedX = startPos.x + (endPos.x - startPos.x) * eased
            const interpolatedY = startPos.y + (endPos.y - startPos.y) * eased

            // Update cursor position
            cursorPositionRef.current = { x: interpolatedX, y: interpolatedY }
            setCursorPosition({ x: interpolatedX, y: interpolatedY })

            if (progress < 1) {
              requestAnimationFrame(animate)
            } else {
              // Animation complete - now release pointer lock
              console.log('[Pointer Lock] 🔓 Animation complete, releasing pointer lock')
              document.exitPointerLock()
            }
          }

          requestAnimationFrame(animate)
        } else {
          // No initial position saved, release immediately
          document.exitPointerLock()
        }

        // Don't update cursor position in this frame - animation will handle it
        return
      }

      // Calculate squish effect based on proximity to edges (using dampened position!)
      // Handle horizontal and vertical squishing independently to support corners
      let squishX = 1.0
      let squishY = 1.0

      // Horizontal squishing (left/right edges)
      if (dampenedDistLeft < squishZone) {
        // Squishing against left edge - compress horizontally
        const t = 1 - dampenedDistLeft / squishZone
        squishX = Math.min(squishX, 1.0 - t * 0.5) // Compress to 50% width
      } else if (dampenedDistRight < squishZone) {
        // Squishing against right edge - compress horizontally
        const t = 1 - dampenedDistRight / squishZone
        squishX = Math.min(squishX, 1.0 - t * 0.5)
      }

      // Vertical squishing (top/bottom edges)
      if (dampenedDistTop < squishZone) {
        // Squishing against top edge - compress vertically
        const t = 1 - dampenedDistTop / squishZone
        squishY = Math.min(squishY, 1.0 - t * 0.5)
      } else if (dampenedDistBottom < squishZone) {
        // Squishing against bottom edge - compress vertically
        const t = 1 - dampenedDistBottom / squishZone
        squishY = Math.min(squishY, 1.0 - t * 0.5)
      }

      // Update squish state
      setCursorSquish({ x: squishX, y: squishY })

      // Clamp to SVG bounds (not container bounds!)
      // Allow cursor to reach escape threshold at SVG edges
      cursorX = Math.max(svgOffsetX, Math.min(svgOffsetX + svgRect.width, cursorX))
      cursorY = Math.max(svgOffsetY, Math.min(svgOffsetY + svgRect.height, cursorY))
    } else {
      // Normal mode: use absolute position
      cursorX = e.clientX - containerRect.left
      cursorY = e.clientY - containerRect.top
    }

    // Check if cursor is over the SVG
    const isOverSvg =
      cursorX >= svgRect.left - containerRect.left &&
      cursorX <= svgRect.right - containerRect.left &&
      cursorY >= svgRect.top - containerRect.top &&
      cursorY <= svgRect.bottom - containerRect.top

    // Don't hide magnifier if mouse is still in container (just moved to padding/magnifier area)
    // Only update cursor position and check for regions if over SVG
    if (!isOverSvg) {
      // Keep magnifier visible but frozen at last position
      // It will be hidden by handleMouseLeave when mouse exits container
      return
    }

    // No velocity tracking needed - zoom adapts immediately to region size

    // Update cursor position ref for next frame
    cursorPositionRef.current = { x: cursorX, y: cursorY }
    setCursorPosition({ x: cursorX, y: cursorY })

    // Send cursor position to other players (in SVG coordinates)
    // In turn-based mode, only broadcast when it's our turn
    const shouldBroadcastCursor =
      onCursorUpdate &&
      svgRef.current &&
      (gameMode !== 'turn-based' || currentPlayer === localPlayerId)

    if (shouldBroadcastCursor) {
      const viewBoxParts = displayViewBox.split(' ').map(Number)
      const viewBoxX = viewBoxParts[0] || 0
      const viewBoxY = viewBoxParts[1] || 0
      const viewBoxW = viewBoxParts[2] || 1000
      const viewBoxH = viewBoxParts[3] || 500
      const svgOffsetX = svgRect.left - containerRect.left
      const svgOffsetY = svgRect.top - containerRect.top
      const scaleX = viewBoxW / svgRect.width
      const scaleY = viewBoxH / svgRect.height
      const cursorSvgX = (cursorX - svgOffsetX) * scaleX + viewBoxX
      const cursorSvgY = (cursorY - svgOffsetY) * scaleY + viewBoxY
      onCursorUpdate({ x: cursorSvgX, y: cursorSvgY })
    }

    // Check if fake cursor is hovering over Give Up button (for pointer lock mode)
    if (pointerLocked) {
      const buttonBounds = giveUpButtonBoundsRef.current
      const isOverButton =
        buttonBounds &&
        cursorX >= buttonBounds.left &&
        cursorX <= buttonBounds.right &&
        cursorY >= buttonBounds.top &&
        cursorY <= buttonBounds.bottom
      setIsFakeCursorOverGiveUp(!!isOverButton)
    }

    // Use region detection hook to find regions near cursor
    const detectionResult = detectRegions(cursorX, cursorY)
    const {
      detectedRegions: detectedRegionObjects,
      regionUnderCursor,
      regionUnderCursorArea,
      regionsInBox,
      hasSmallRegion,
      detectedSmallestSize,
      totalRegionArea,
    } = detectionResult

    if (pointerLocked && detectedRegionObjects.length > 0) {
      const sortedSizes = detectedRegionObjects.map((r) => `${r.id}: ${r.screenSize.toFixed(2)}px`)
      console.log('[Zoom Search] Sorted regions (smallest first):', sortedSizes)
    }

    // Show magnifier when:
    // 1. Shift key is held down (manual override)
    // 2. Current target region needs magnification AND there's a small region nearby
    const shouldShow = shiftPressed || (targetNeedsMagnification && hasSmallRegion)

    // Update smallest region size for adaptive cursor dampening
    // Use hysteresis to prevent rapid flickering at boundaries
    if (shouldShow && detectedSmallestSize !== Infinity) {
      // Only update if the new size is significantly different (>20% change)
      // This prevents jitter when moving near region boundaries
      const currentSize = smallestRegionSize
      const sizeRatio = currentSize === Infinity ? 0 : detectedSmallestSize / currentSize
      const significantChange = currentSize === Infinity || sizeRatio < 0.8 || sizeRatio > 1.25
      if (significantChange) {
        setSmallestRegionSize(detectedSmallestSize)
      }
    } else if (smallestRegionSize !== Infinity) {
      // When leaving precision area, don't immediately jump to Infinity
      // Instead, set to a large value that will smoothly transition via spring
      setSmallestRegionSize(100) // Large enough that multiplier becomes 1.0
    }

    // Set hover highlighting based on cursor position
    // This ensures the crosshairs match what's highlighted
    if (regionUnderCursor !== hoveredRegion) {
      setHoveredRegion(regionUnderCursor)
    }

    if (shouldShow) {
      // Filter out found regions from zoom calculations
      // Found regions shouldn't influence how much we zoom in
      const unfoundRegionObjects = detectedRegionObjects.filter((r) => !regionsFound.includes(r.id))

      // Use adaptive zoom search utility to find optimal zoom
      const zoomSearchResult = findOptimalZoom({
        detectedRegions: unfoundRegionObjects,
        detectedSmallestSize,
        cursorX,
        cursorY,
        containerRect,
        svgRect,
        mapData,
        svgElement: svgRef.current!,
        largestPieceSizesCache: largestPieceSizesRef.current,
        maxZoom: MAX_ZOOM,
        minZoom: 1,
        pointerLocked,
      })

      let adaptiveZoom = zoomSearchResult.zoom
      const boundingBoxes = zoomSearchResult.boundingBoxes

      // Save bounding boxes for rendering
      setDebugBoundingBoxes(boundingBoxes)
      // Save full zoom search result for debug panel
      setZoomSearchDebugInfo(zoomSearchResult)

      // Calculate magnifier dimensions (needed for positioning)
      const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
      const magnifierHeight = containerRect.height * MAGNIFIER_SIZE_RATIO

      // Calculate magnifier position (opposite corner from cursor)
      // magnifierWidth and magnifierHeight already declared above
      const isLeftHalf = cursorX < containerRect.width / 2
      const isTopHalf = cursorY < containerRect.height / 2

      const newTop = isTopHalf ? containerRect.height - magnifierHeight - 20 : 20
      const newLeft = isLeftHalf ? containerRect.width - magnifierWidth - 20 : 20

      if (pointerLocked) {
        console.log(
          '[Magnifier] SHOWING with zoom:',
          adaptiveZoom,
          '| Setting opacity to 1, position:',
          { top: newTop, left: newLeft }
        )
      }

      // Store uncapped adaptive zoom before potentially capping it
      uncappedAdaptiveZoomRef.current = adaptiveZoom

      // Cap zoom if not in pointer lock mode to prevent excessive screen pixel ratios
      if (!pointerLocked && containerRef.current && svgRef.current) {
        const containerRect = containerRef.current.getBoundingClientRect()
        const svgRect = svgRef.current.getBoundingClientRect()
        const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
        const viewBoxParts = displayViewBox.split(' ').map(Number)
        const viewBoxWidth = viewBoxParts[2]

        if (viewBoxWidth && !Number.isNaN(viewBoxWidth)) {
          // Calculate what the screen pixel ratio would be at this zoom
          const screenPixelRatio = calculateScreenPixelRatio({
            magnifierWidth,
            viewBoxWidth,
            svgWidth: svgRect.width,
            zoom: adaptiveZoom,
          })

          // If it exceeds threshold, cap the zoom to stay at threshold
          if (isAboveThreshold(screenPixelRatio, PRECISION_MODE_THRESHOLD)) {
            const maxZoom = calculateMaxZoomAtThreshold(
              PRECISION_MODE_THRESHOLD,
              magnifierWidth,
              svgRect.width
            )
            adaptiveZoom = Math.min(adaptiveZoom, maxZoom)
            console.log(
              `[Magnifier] Capping zoom at ${adaptiveZoom.toFixed(1)}× (threshold: ${PRECISION_MODE_THRESHOLD} px/px, would have been ${screenPixelRatio.toFixed(1)} px/px)`
            )
          }
        }
      }

      setTargetZoom(adaptiveZoom)
      setShowMagnifier(true)
      setTargetOpacity(1)
      setTargetTop(newTop)
      setTargetLeft(newLeft)
    } else {
      if (pointerLocked) {
        console.log('[Magnifier] HIDING - not enough regions or too large | Setting opacity to 0')
      }
      setShowMagnifier(false)
      setTargetOpacity(0)
      setDebugBoundingBoxes([]) // Clear bounding boxes when hiding
    }
  }

  const handleMouseLeave = () => {
    // Don't hide magnifier when pointer is locked
    // The cursor is locked to the container, so mouse leave events are not meaningful
    if (pointerLocked) {
      console.log('[Mouse Leave] Ignoring - pointer is locked')
      return
    }

    setShowMagnifier(false)
    setTargetOpacity(0)
    setCursorPosition(null)
    setDebugBoundingBoxes([]) // Clear bounding boxes when leaving
    cursorPositionRef.current = null

    // Notify other players that cursor left
    // In turn-based mode, only broadcast when it's our turn
    if (onCursorUpdate && (gameMode !== 'turn-based' || currentPlayer === localPlayerId)) {
      onCursorUpdate(null)
    }
  }

  return (
    <div
      ref={containerRef}
      data-component="map-renderer"
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      onClick={handleContainerClick}
      className={css({
        position: 'relative',
        width: '100%',
        height: '100%',
        flex: 1, // Fill available space in parent flex container
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      })}
      style={{
        // Sea/ocean background with wavy CSS pattern at screen pixel scale
        backgroundColor: isDark ? '#1e3a5f' : '#a8d4f0',
        backgroundImage: isDark
          ? `repeating-linear-gradient(
              15deg,
              transparent,
              transparent 18px,
              rgba(45, 74, 111, 0.5) 18px,
              rgba(45, 74, 111, 0.5) 20px,
              transparent 20px,
              transparent 48px,
              rgba(45, 74, 111, 0.4) 48px,
              rgba(45, 74, 111, 0.4) 50px,
              transparent 50px,
              transparent 78px,
              rgba(45, 74, 111, 0.3) 78px,
              rgba(45, 74, 111, 0.3) 80px
            )`
          : `repeating-linear-gradient(
              15deg,
              transparent,
              transparent 18px,
              rgba(143, 196, 232, 0.5) 18px,
              rgba(143, 196, 232, 0.5) 20px,
              transparent 20px,
              transparent 48px,
              rgba(143, 196, 232, 0.4) 48px,
              rgba(143, 196, 232, 0.4) 50px,
              transparent 50px,
              transparent 78px,
              rgba(143, 196, 232, 0.3) 78px,
              rgba(143, 196, 232, 0.3) 80px
            )`,
        backgroundSize: '100px 100px',
      }}
    >
      <animated.svg
        ref={svgRef}
        viewBox={displayViewBox}
        className={css({
          // Fill the entire container - viewBox controls what portion of map is visible
          width: '100%',
          height: '100%',
          cursor: pointerLocked ? 'crosshair' : 'pointer',
          transformOrigin: 'center center',
        })}
        style={{
          // No aspectRatio - the SVG fills the container and viewBox is calculated
          // to match the container's aspect ratio via calculateFitCropViewBox
          // CSS transform for zoom animation during give-up reveal
          transform: to(
            [mainMapSpring.scale, mainMapSpring.translateX, mainMapSpring.translateY],
            (s, tx, ty) => `scale(${s}) translate(${tx / s}px, ${ty / s}px)`
          ),
        }}
      >
        {/* Render all regions (included + excluded) */}
        {[...mapData.regions, ...excludedRegions].map((region) => {
          const isExcluded = excludedRegionIds.has(region.id)
          const isFound = regionsFound.includes(region.id) || isExcluded // Treat excluded as pre-found
          const playerId = !isExcluded && isFound ? getPlayerWhoFoundRegion(region.id) : null
          const isBeingRevealed = giveUpReveal?.regionId === region.id

          // Special styling for excluded regions (grayed out, pre-labeled)
          // Bright gold flash for give up reveal with high contrast
          const fill = isBeingRevealed
            ? `rgba(255, 200, 0, ${0.6 + giveUpFlashProgress * 0.4})` // Brighter gold, higher base opacity
            : isExcluded
              ? isDark
                ? '#374151' // gray-700
                : '#d1d5db' // gray-300
              : isFound && playerId
                ? `url(#player-pattern-${playerId})`
                : getRegionColor(region.id, isFound, hoveredRegion === region.id, isDark)

          // During give-up animation, dim all non-revealed regions
          const dimmedOpacity = isGiveUpAnimating && !isBeingRevealed ? 0.25 : 1

          // Revealed region gets a prominent stroke
          // Unfound regions get thicker borders for better visibility against sea
          const stroke = isBeingRevealed
            ? `rgba(255, 140, 0, ${0.8 + giveUpFlashProgress * 0.2})` // Orange stroke for contrast
            : getRegionStroke(isFound, isDark)
          const strokeWidth = isBeingRevealed ? 3 : isFound ? 1 : 1.5

          // Check if a network cursor is hovering over this region
          const networkHover = networkHoveredRegions[region.id]

          return (
            <g key={region.id} style={{ opacity: dimmedOpacity }}>
              {/* Glow effect for network-hovered region (other player's cursor) */}
              {networkHover && !isBeingRevealed && (
                <path
                  d={region.path}
                  fill="none"
                  stroke={networkHover.color}
                  strokeWidth={6}
                  vectorEffect="non-scaling-stroke"
                  opacity={0.5}
                  style={{ filter: 'blur(3px)' }}
                  pointerEvents="none"
                />
              )}
              {/* Glow effect for revealed region */}
              {isBeingRevealed && (
                <path
                  d={region.path}
                  fill="none"
                  stroke={`rgba(255, 215, 0, ${0.3 + giveUpFlashProgress * 0.5})`}
                  strokeWidth={8}
                  vectorEffect="non-scaling-stroke"
                  style={{ filter: 'blur(4px)' }}
                />
              )}
              {/* Network hover border (crisp outline in player color) */}
              {networkHover && !isBeingRevealed && (
                <path
                  d={region.path}
                  fill="none"
                  stroke={networkHover.color}
                  strokeWidth={2}
                  vectorEffect="non-scaling-stroke"
                  opacity={0.8}
                  strokeDasharray="4,2"
                  pointerEvents="none"
                />
              )}
              {/* Region path */}
              <path
                data-region-id={region.id}
                d={region.path}
                fill={fill}
                stroke={stroke}
                strokeWidth={strokeWidth}
                vectorEffect="non-scaling-stroke"
                opacity={showOutline(region) ? 1 : 0.7} // Increased from 0.3 to 0.7 for better visibility
                // When pointer lock is active, hover is controlled by cursor position tracking
                // Otherwise, use native mouse events
                onMouseEnter={() => !isExcluded && !pointerLocked && setHoveredRegion(region.id)}
                onMouseLeave={() => !pointerLocked && setHoveredRegion(null)}
                onClick={() => {
                  console.log('[CLICK] Region clicked:', {
                    regionId: region.id,
                    regionName: region.name,
                    isExcluded,
                    willCall: !isExcluded,
                  })
                  if (!isExcluded) {
                    onRegionClick(region.id, region.name)
                  }
                }} // Disable clicks on excluded regions
                style={{
                  cursor: isExcluded ? 'default' : 'pointer',
                  transition: 'all 0.2s ease',
                }}
              />

              {/* Ghost element for region center position tracking */}
              <circle
                cx={region.center[0]}
                cy={region.center[1]}
                r={0.1}
                fill="none"
                pointerEvents="none"
                data-ghost-region={region.id}
              />
            </g>
          )
        })}

        {/* Debug: Render bounding boxes (only if enabled) */}
        {showDebugBoundingBoxes &&
          debugBoundingBoxes.map((bbox) => {
            // Color based on acceptance and importance
            // Green = accepted, Orange = high importance, Yellow = medium, Gray = low
            const importance = bbox.importance ?? 0
            let strokeColor = '#888888' // Default gray for low importance
            let fillColor = 'rgba(136, 136, 136, 0.1)'

            if (bbox.wasAccepted) {
              strokeColor = '#00ff00' // Green for accepted region
              fillColor = 'rgba(0, 255, 0, 0.15)'
            } else if (importance > 1.5) {
              strokeColor = '#ff6600' // Orange for high importance (2.0× boost + close)
              fillColor = 'rgba(255, 102, 0, 0.1)'
            } else if (importance > 0.5) {
              strokeColor = '#ffcc00' // Yellow for medium importance
              fillColor = 'rgba(255, 204, 0, 0.1)'
            }

            return (
              <g key={`bbox-${bbox.regionId}`}>
                <rect
                  x={bbox.x}
                  y={bbox.y}
                  width={bbox.width}
                  height={bbox.height}
                  fill={fillColor}
                  stroke={strokeColor}
                  strokeWidth={viewBoxWidth / 500}
                  vectorEffect="non-scaling-stroke"
                  strokeDasharray="3,3"
                  pointerEvents="none"
                  opacity={0.9}
                />
              </g>
            )
          })}

        {/* Arrow marker definition */}
        <defs>
          <marker id="arrowhead" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
            <polygon points="0 0, 10 3, 0 6" fill={isDark ? '#60a5fa' : '#3b82f6'} />
          </marker>
          <marker
            id="arrowhead-found"
            markerWidth="10"
            markerHeight="10"
            refX="8"
            refY="3"
            orient="auto"
          >
            <polygon points="0 0, 10 3, 0 6" fill="#16a34a" />
          </marker>

          {/* Player emoji patterns for region backgrounds */}
          {Object.values(playerMetadata).map((player) => (
            <pattern
              key={`pattern-${player.id}`}
              id={`player-pattern-${player.id}`}
              width="60"
              height="60"
              patternUnits="userSpaceOnUse"
            >
              <rect width="60" height="60" fill={player.color} opacity="0.2" />
              <text
                x="30"
                y="30"
                fontSize="50"
                textAnchor="middle"
                dominantBaseline="middle"
                opacity="0.5"
              >
                {player.emoji}
              </text>
            </pattern>
          ))}
        </defs>

        {/* Magnifier region indicator on main map */}
        {showMagnifier && cursorPosition && svgRef.current && containerRef.current && (
          <animated.rect
            x={zoomSpring.to((zoom: number) => {
              const containerRect = containerRef.current!.getBoundingClientRect()
              const svgRect = svgRef.current!.getBoundingClientRect()
              const viewBoxParts = displayViewBox.split(' ').map(Number)
              const viewBoxX = viewBoxParts[0] || 0
              const viewBoxY = viewBoxParts[1] || 0
              const viewBoxWidth = viewBoxParts[2] || 1000
              const viewBoxHeight = viewBoxParts[3] || 1000
              const scaleX = viewBoxWidth / svgRect.width
              const scaleY = viewBoxHeight / svgRect.height
              const svgOffsetX = svgRect.left - containerRect.left
              const svgOffsetY = svgRect.top - containerRect.top
              const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
              const magnifiedWidth = viewBoxWidth / zoom
              return cursorSvgX - magnifiedWidth / 2
            })}
            y={zoomSpring.to((zoom: number) => {
              const containerRect = containerRef.current!.getBoundingClientRect()
              const svgRect = svgRef.current!.getBoundingClientRect()
              const viewBoxParts = displayViewBox.split(' ').map(Number)
              const viewBoxX = viewBoxParts[0] || 0
              const viewBoxY = viewBoxParts[1] || 0
              const viewBoxWidth = viewBoxParts[2] || 1000
              const viewBoxHeight = viewBoxParts[3] || 1000
              const scaleX = viewBoxWidth / svgRect.width
              const scaleY = viewBoxHeight / svgRect.height
              const svgOffsetX = svgRect.left - containerRect.left
              const svgOffsetY = svgRect.top - containerRect.top
              const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY
              const magnifiedHeight = viewBoxHeight / zoom
              return cursorSvgY - magnifiedHeight / 2
            })}
            width={zoomSpring.to((zoom: number) => {
              const viewBoxParts = displayViewBox.split(' ').map(Number)
              const viewBoxWidth = viewBoxParts[2] || 1000
              return viewBoxWidth / zoom
            })}
            height={zoomSpring.to((zoom: number) => {
              const viewBoxParts = displayViewBox.split(' ').map(Number)
              const viewBoxHeight = viewBoxParts[3] || 1000
              return viewBoxHeight / zoom
            })}
            fill="none"
            stroke={isDark ? '#60a5fa' : '#3b82f6'}
            strokeWidth={viewBoxWidth / 500}
            vectorEffect="non-scaling-stroke"
            strokeDasharray="5,5"
            pointerEvents="none"
            opacity={0.8}
          />
        )}
      </animated.svg>

      {/* HTML labels positioned absolutely over the SVG */}
      {labelPositions.map((label) => {
        const labelOpacity = calculateLabelOpacity(
          label.x,
          label.y,
          label.regionId,
          cursorPosition,
          hoveredRegion,
          regionsFound,
          isGiveUpAnimating
        )
        return (
          <div
            key={label.regionId}
            style={{
              position: 'absolute',
              left: `${label.x}px`,
              top: `${label.y}px`,
              transform: 'translate(-50%, -50%)',
              pointerEvents: 'none',
              zIndex: 10,
              opacity: labelOpacity,
              transition: 'opacity 0.15s ease-out',
            }}
          >
            {/* Region name */}
            <div
              style={{
                fontSize: '14px',
                fontWeight: 'bold',
                color: getLabelTextColor(isDark, true),
                textShadow: getLabelTextShadow(isDark, true),
                whiteSpace: 'nowrap',
                textAlign: 'center',
                pointerEvents: 'none',
              }}
            >
              {label.regionName}
            </div>

            {/* Player avatars */}
            {label.players.length > 0 && (
              <div
                style={{
                  display: 'flex',
                  gap: '2px',
                  marginTop: '2px',
                  justifyContent: 'center',
                  pointerEvents: 'none',
                }}
              >
                {label.players.map((playerId) => {
                  const player = playerMetadata[playerId]
                  if (!player) return null

                  return (
                    <div
                      key={playerId}
                      style={{
                        width: '14px',
                        height: '14px',
                        borderRadius: '50%',
                        backgroundColor: player.color || '#3b82f6',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'center',
                        fontSize: '10px',
                        opacity: 0.9,
                        pointerEvents: 'none',
                      }}
                    >
                      {player.emoji}
                    </div>
                  )
                })}
              </div>
            )}
          </div>
        )
      })}

      {/* Small region labels with arrows positioned absolutely over the SVG */}
      {smallRegionLabelPositions.map((label) => {
        const labelOpacity = calculateLabelOpacity(
          label.labelX,
          label.labelY,
          label.regionId,
          cursorPosition,
          hoveredRegion,
          regionsFound,
          isGiveUpAnimating
        )
        return (
          <div
            key={`small-${label.regionId}`}
            style={{
              opacity: labelOpacity,
              transition: 'opacity 0.15s ease-out',
            }}
          >
            {/* Arrow line - use SVG positioned absolutely */}
            <svg
              style={{
                position: 'absolute',
                left: 0,
                top: 0,
                width: '100%',
                height: '100%',
                pointerEvents: 'none',
                overflow: 'visible',
              }}
            >
              <line
                x1={label.lineStartX}
                y1={label.lineStartY}
                x2={label.lineEndX}
                y2={label.lineEndY}
                stroke={label.isFound ? '#16a34a' : isDark ? '#60a5fa' : '#3b82f6'}
                strokeWidth={1.5}
                markerEnd={label.isFound ? 'url(#arrowhead-found)' : 'url(#arrowhead)'}
              />
              {/* Debug: Show arrow endpoint (region centroid) */}
              <circle cx={label.lineEndX} cy={label.lineEndY} r={3} fill="red" opacity={0.8} />
            </svg>

            {/* Label box and text */}
            <div
              style={{
                position: 'absolute',
                left: `${label.labelX}px`,
                top: `${label.labelY}px`,
                transform: 'translate(-50%, -50%)',
                pointerEvents: 'all',
                cursor: 'pointer',
                zIndex: 20,
              }}
              onClick={() => onRegionClick(label.regionId, label.regionName)}
              onMouseEnter={() => setHoveredRegion(label.regionId)}
              onMouseLeave={() => setHoveredRegion(null)}
            >
              {/* Background box */}
              <div
                style={{
                  padding: '2px 5px',
                  backgroundColor: label.isFound
                    ? isDark
                      ? '#22c55e'
                      : '#86efac'
                    : isDark
                      ? '#1f2937'
                      : '#ffffff',
                  border: `1px solid ${label.isFound ? '#16a34a' : isDark ? '#60a5fa' : '#3b82f6'}`,
                  borderRadius: '4px',
                  fontSize: '11px',
                  fontWeight: '600',
                  color: getLabelTextColor(isDark, label.isFound),
                  textShadow: label.isFound
                    ? getLabelTextShadow(isDark, true)
                    : '0 0 2px rgba(0,0,0,0.5)',
                  whiteSpace: 'nowrap',
                  userSelect: 'none',
                  transition: 'all 0.2s ease',
                }}
              >
                {label.regionName}
              </div>
            </div>
          </div>
        )
      })}

      {/* Debug: Bounding box labels as HTML overlays */}
      {showDebugBoundingBoxes &&
        containerRef.current &&
        svgRef.current &&
        debugBoundingBoxes.map((bbox) => {
          const importance = bbox.importance ?? 0
          let strokeColor = '#888888'

          if (bbox.wasAccepted) {
            strokeColor = '#00ff00'
          } else if (importance > 1.5) {
            strokeColor = '#ff6600'
          } else if (importance > 0.5) {
            strokeColor = '#ffcc00'
          }

          // Convert SVG coordinates to pixel coordinates
          const containerRect = containerRef.current!.getBoundingClientRect()
          const svgRect = svgRef.current!.getBoundingClientRect()
          const viewBoxParts = displayViewBox.split(' ').map(Number)
          const viewBoxX = viewBoxParts[0] || 0
          const viewBoxY = viewBoxParts[1] || 0
          const viewBoxWidth = viewBoxParts[2] || 1000
          const viewBoxHeight = viewBoxParts[3] || 1000

          const scaleX = svgRect.width / viewBoxWidth
          const scaleY = svgRect.height / viewBoxHeight
          const svgOffsetX = svgRect.left - containerRect.left
          const svgOffsetY = svgRect.top - containerRect.top

          // Convert bbox center from SVG coords to pixels
          const centerX = (bbox.x + bbox.width / 2 - viewBoxX) * scaleX + svgOffsetX
          const centerY = (bbox.y + bbox.height / 2 - viewBoxY) * scaleY + svgOffsetY

          return (
            <div
              key={`bbox-label-${bbox.regionId}`}
              style={{
                position: 'absolute',
                left: `${centerX}px`,
                top: `${centerY}px`,
                transform: 'translate(-50%, -50%)',
                pointerEvents: 'none',
                zIndex: 15,
                fontSize: '10px',
                fontWeight: 'bold',
                color: strokeColor,
                textAlign: 'center',
                textShadow: '0 0 2px black, 0 0 2px black, 0 0 2px black',
                whiteSpace: 'nowrap',
              }}
            >
              <div>{bbox.regionId}</div>
              <div style={{ fontSize: '8px', fontWeight: 'normal' }}>{importance.toFixed(2)}</div>
            </div>
          )
        })}

      {/* Custom Cursor - Visible when pointer lock is active */}
      {(() => {
        // Debug logging removed - was flooding console
        return pointerLocked && cursorPosition ? (
          <div
            data-element="custom-cursor"
            style={{
              position: 'absolute',
              left: `${cursorPosition.x}px`,
              top: `${cursorPosition.y}px`,
              width: '20px',
              height: '20px',
              border: `2px solid ${isDark ? '#60a5fa' : '#3b82f6'}`,
              borderRadius: '50%',
              pointerEvents: 'none',
              zIndex: 200,
              transform: `translate(-50%, -50%) scale(${cursorSquish.x}, ${cursorSquish.y})`,
              backgroundColor: 'transparent',
              boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.3)',
              transition: 'transform 0.1s ease-out', // Smooth squish animation
            }}
          >
            {/* Crosshair - Vertical line */}
            <div
              style={{
                position: 'absolute',
                left: '50%',
                top: '0',
                width: '2px',
                height: '100%',
                backgroundColor: isDark ? '#60a5fa' : '#3b82f6',
                transform: 'translateX(-50%)',
              }}
            />
            {/* Crosshair - Horizontal line */}
            <div
              style={{
                position: 'absolute',
                left: '0',
                top: '50%',
                width: '100%',
                height: '2px',
                backgroundColor: isDark ? '#60a5fa' : '#3b82f6',
                transform: 'translateY(-50%)',
              }}
            />
          </div>
        ) : null
      })()}

      {/* Magnifier overlay - centers on cursor position */}
      {(() => {
        if (!cursorPosition || !svgRef.current || !containerRef.current) {
          return null
        }

        return (
          <animated.div
            data-element="magnifier"
            style={{
              position: 'absolute',
              // Animated positioning - smoothly moves to opposite corner from cursor
              top: magnifierSpring.top,
              left: magnifierSpring.left,
              width: '50%',
              aspectRatio: '2/1',
              // High zoom (>60x) gets gold border, normal zoom gets blue border
              border: zoomSpring.to(
                (zoom: number) =>
                  zoom > HIGH_ZOOM_THRESHOLD
                    ? `4px solid ${isDark ? '#fbbf24' : '#f59e0b'}` // gold-400/gold-500
                    : `3px solid ${isDark ? '#60a5fa' : '#3b82f6'}` // blue-400/blue-600
              ),
              borderRadius: '12px',
              overflow: 'hidden',
              pointerEvents: 'none',
              zIndex: 100,
              boxShadow: zoomSpring.to((zoom: number) =>
                zoom > HIGH_ZOOM_THRESHOLD
                  ? '0 10px 40px rgba(251, 191, 36, 0.4), 0 0 20px rgba(251, 191, 36, 0.2)' // Gold glow
                  : '0 10px 40px rgba(0, 0, 0, 0.5)'
              ),
              background: isDark ? '#111827' : '#f3f4f6',
              opacity: magnifierSpring.opacity,
            }}
          >
            <animated.svg
              viewBox={zoomSpring.to((zoom: number) => {
                // Calculate magnified viewBox centered on cursor
                const containerRect = containerRef.current!.getBoundingClientRect()
                const svgRect = svgRef.current!.getBoundingClientRect()

                // Convert cursor position to SVG coordinates
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                const viewBoxX = viewBoxParts[0] || 0
                const viewBoxY = viewBoxParts[1] || 0
                const viewBoxWidth = viewBoxParts[2] || 1000
                const viewBoxHeight = viewBoxParts[3] || 1000

                const scaleX = viewBoxWidth / svgRect.width
                const scaleY = viewBoxHeight / svgRect.height

                // Center position relative to SVG (uses reveal center during give-up animation)
                const svgOffsetX = svgRect.left - containerRect.left
                const svgOffsetY = svgRect.top - containerRect.top
                const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
                const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY

                // Magnified view: adaptive zoom (using animated value)
                const magnifiedWidth = viewBoxWidth / zoom
                const magnifiedHeight = viewBoxHeight / zoom

                // Center the magnified viewBox on the cursor
                const magnifiedViewBoxX = cursorSvgX - magnifiedWidth / 2
                const magnifiedViewBoxY = cursorSvgY - magnifiedHeight / 2

                return `${magnifiedViewBoxX} ${magnifiedViewBoxY} ${magnifiedWidth} ${magnifiedHeight}`
              })}
              style={{
                width: '100%',
                height: '100%',
                filter: (() => {
                  // Apply "disabled" visual effect when at threshold but not in precision mode
                  if (pointerLocked) return 'none'

                  const containerRect = containerRef.current?.getBoundingClientRect()
                  const svgRect = svgRef.current?.getBoundingClientRect()
                  if (!containerRect || !svgRect) return 'none'

                  const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
                  const viewBoxParts = displayViewBox.split(' ').map(Number)
                  const viewBoxWidth = viewBoxParts[2]
                  if (!viewBoxWidth || Number.isNaN(viewBoxWidth)) return 'none'

                  const currentZoom = getCurrentZoom()
                  const screenPixelRatio = calculateScreenPixelRatio({
                    magnifierWidth,
                    viewBoxWidth,
                    svgWidth: svgRect.width,
                    zoom: currentZoom,
                  })

                  // When at or above threshold (but not in precision mode), add disabled effect
                  if (isAboveThreshold(screenPixelRatio, PRECISION_MODE_THRESHOLD)) {
                    return 'brightness(0.6) saturate(0.5)'
                  }

                  return 'none'
                })(),
              }}
            >
              {/* Sea/ocean background for magnifier - solid color to match container */}
              {(() => {
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                return (
                  <rect
                    x={viewBoxParts[0] || 0}
                    y={viewBoxParts[1] || 0}
                    width={viewBoxParts[2] || 1000}
                    height={viewBoxParts[3] || 1000}
                    fill={isDark ? '#1e3a5f' : '#a8d4f0'}
                  />
                )
              })()}

              {/* Render all regions in magnified view */}
              {mapData.regions.map((region) => {
                const isFound = regionsFound.includes(region.id)
                const playerId = isFound ? getPlayerWhoFoundRegion(region.id) : null
                const isBeingRevealed = giveUpReveal?.regionId === region.id

                // Bright gold flash for give up reveal in magnifier too
                const fill = isBeingRevealed
                  ? `rgba(255, 200, 0, ${0.6 + giveUpFlashProgress * 0.4})`
                  : isFound && playerId
                    ? `url(#player-pattern-${playerId})`
                    : getRegionColor(region.id, isFound, hoveredRegion === region.id, isDark)

                // During give-up animation, dim all non-revealed regions
                const dimmedOpacity = isGiveUpAnimating && !isBeingRevealed ? 0.25 : 1

                // Revealed region gets a prominent stroke
                // Unfound regions get thicker borders for better visibility against sea
                const stroke = isBeingRevealed
                  ? `rgba(255, 140, 0, ${0.8 + giveUpFlashProgress * 0.2})`
                  : getRegionStroke(isFound, isDark)
                const strokeWidth = isBeingRevealed ? 2 : isFound ? 0.5 : 1

                return (
                  <g key={`mag-${region.id}`} style={{ opacity: dimmedOpacity }}>
                    {/* Glow effect for revealed region in magnifier */}
                    {isBeingRevealed && (
                      <path
                        d={region.path}
                        fill="none"
                        stroke={`rgba(255, 215, 0, ${0.3 + giveUpFlashProgress * 0.5})`}
                        strokeWidth={5}
                        vectorEffect="non-scaling-stroke"
                        style={{ filter: 'blur(2px)' }}
                      />
                    )}
                    <path
                      d={region.path}
                      fill={fill}
                      stroke={stroke}
                      strokeWidth={strokeWidth}
                      vectorEffect="non-scaling-stroke"
                      opacity={showOutline(region) ? 1 : 0.3}
                    />
                  </g>
                )
              })}

              {/* Crosshair at center position (cursor or reveal center during animation) */}
              <g>
                {(() => {
                  const containerRect = containerRef.current!.getBoundingClientRect()
                  const svgRect = svgRef.current!.getBoundingClientRect()
                  const viewBoxParts = displayViewBox.split(' ').map(Number)
                  const viewBoxX = viewBoxParts[0] || 0
                  const viewBoxY = viewBoxParts[1] || 0
                  const viewBoxWidth = viewBoxParts[2] || 1000
                  const viewBoxHeight = viewBoxParts[3] || 1000
                  const scaleX = viewBoxWidth / svgRect.width
                  const scaleY = viewBoxHeight / svgRect.height
                  const svgOffsetX = svgRect.left - containerRect.left
                  const svgOffsetY = svgRect.top - containerRect.top
                  const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
                  const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY

                  return (
                    <>
                      <circle
                        cx={cursorSvgX}
                        cy={cursorSvgY}
                        r={viewBoxWidth / 100}
                        fill="none"
                        stroke={isDark ? '#60a5fa' : '#3b82f6'}
                        strokeWidth={viewBoxWidth / 500}
                        vectorEffect="non-scaling-stroke"
                      />
                      <line
                        x1={cursorSvgX - viewBoxWidth / 50}
                        y1={cursorSvgY}
                        x2={cursorSvgX + viewBoxWidth / 50}
                        y2={cursorSvgY}
                        stroke={isDark ? '#60a5fa' : '#3b82f6'}
                        strokeWidth={viewBoxWidth / 1000}
                        vectorEffect="non-scaling-stroke"
                      />
                      <line
                        x1={cursorSvgX}
                        y1={cursorSvgY - viewBoxHeight / 50}
                        x2={cursorSvgX}
                        y2={cursorSvgY + viewBoxHeight / 50}
                        stroke={isDark ? '#60a5fa' : '#3b82f6'}
                        strokeWidth={viewBoxWidth / 1000}
                        vectorEffect="non-scaling-stroke"
                      />
                    </>
                  )
                })()}
              </g>

              {/* Pixel grid overlay - shows when approaching/at/above precision mode threshold */}
              {(() => {
                const containerRect = containerRef.current?.getBoundingClientRect()
                const svgRect = svgRef.current?.getBoundingClientRect()
                if (!containerRect || !svgRect) return null

                const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                const viewBoxWidth = viewBoxParts[2]
                const viewBoxHeight = viewBoxParts[3]
                const viewBoxX = viewBoxParts[0] || 0
                const viewBoxY = viewBoxParts[1] || 0

                if (!viewBoxWidth || Number.isNaN(viewBoxWidth)) return null

                const currentZoom = getCurrentZoom()
                const screenPixelRatio = calculateScreenPixelRatio({
                  magnifierWidth,
                  viewBoxWidth,
                  svgWidth: svgRect.width,
                  zoom: currentZoom,
                })

                // Fade grid in/out within 30% range on both sides of threshold
                // Visible from 70% to 130% of threshold (14 to 26 px/px at threshold=20)
                const fadeStartRatio = PRECISION_MODE_THRESHOLD * 0.7
                const fadeEndRatio = PRECISION_MODE_THRESHOLD * 1.3

                if (screenPixelRatio < fadeStartRatio || screenPixelRatio > fadeEndRatio)
                  return null

                // Calculate opacity: 0 at edges (70% and 130%), 1 at threshold (100%)
                let gridOpacity: number
                if (screenPixelRatio <= PRECISION_MODE_THRESHOLD) {
                  // Fading in: 0 at 70%, 1 at 100%
                  gridOpacity =
                    (screenPixelRatio - fadeStartRatio) /
                    (PRECISION_MODE_THRESHOLD - fadeStartRatio)
                } else {
                  // Fading out: 1 at 100%, 0 at 130%
                  gridOpacity =
                    (fadeEndRatio - screenPixelRatio) / (fadeEndRatio - PRECISION_MODE_THRESHOLD)
                }

                // Calculate grid spacing in SVG units
                // Each grid cell represents one screen pixel of mouse movement on the main map
                const mainMapSvgUnitsPerScreenPixel = viewBoxWidth / svgRect.width
                const gridSpacingSvgUnits = mainMapSvgUnitsPerScreenPixel

                // Calculate magnified viewport dimensions for grid bounds
                const magnifiedViewBoxWidth = viewBoxWidth / currentZoom

                // Get center position in SVG coordinates (uses reveal center during give-up animation)
                const scaleX = viewBoxWidth / svgRect.width
                const scaleY = viewBoxHeight / svgRect.height
                const svgOffsetX = svgRect.left - containerRect.left
                const svgOffsetY = svgRect.top - containerRect.top
                const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
                const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY

                // Calculate grid bounds (magnifier viewport)
                const magnifiedHeight = viewBoxHeight / currentZoom
                const gridLeft = cursorSvgX - magnifiedViewBoxWidth / 2
                const gridRight = cursorSvgX + magnifiedViewBoxWidth / 2
                const gridTop = cursorSvgY - magnifiedHeight / 2
                const gridBottom = cursorSvgY + magnifiedHeight / 2

                // Calculate grid line positions aligned with cursor (crosshair center)
                const lines: Array<{ type: 'h' | 'v'; pos: number }> = []

                // Vertical lines (aligned with cursor X)
                const firstVerticalLine =
                  Math.floor((gridLeft - cursorSvgX) / gridSpacingSvgUnits) * gridSpacingSvgUnits +
                  cursorSvgX
                for (let x = firstVerticalLine; x <= gridRight; x += gridSpacingSvgUnits) {
                  lines.push({ type: 'v', pos: x })
                }

                // Horizontal lines (aligned with cursor Y)
                const firstHorizontalLine =
                  Math.floor((gridTop - cursorSvgY) / gridSpacingSvgUnits) * gridSpacingSvgUnits +
                  cursorSvgY
                for (let y = firstHorizontalLine; y <= gridBottom; y += gridSpacingSvgUnits) {
                  lines.push({ type: 'h', pos: y })
                }

                // Apply opacity to grid color
                const baseOpacity = isDark ? 0.5 : 0.6
                const finalOpacity = baseOpacity * gridOpacity
                const gridColor = `rgba(251, 191, 36, ${finalOpacity})`

                return (
                  <g data-element="pixel-grid-overlay">
                    {lines.map((line, i) =>
                      line.type === 'v' ? (
                        <line
                          key={`vgrid-${i}`}
                          x1={line.pos}
                          y1={gridTop}
                          x2={line.pos}
                          y2={gridBottom}
                          stroke={gridColor}
                          strokeWidth={viewBoxWidth / 2000}
                          vectorEffect="non-scaling-stroke"
                        />
                      ) : (
                        <line
                          key={`hgrid-${i}`}
                          x1={gridLeft}
                          y1={line.pos}
                          x2={gridRight}
                          y2={line.pos}
                          stroke={gridColor}
                          strokeWidth={viewBoxWidth / 2000}
                          vectorEffect="non-scaling-stroke"
                        />
                      )
                    )}
                  </g>
                )
              })()}

              {/* Debug: Bounding boxes for detected regions in magnifier */}
              {SHOW_DEBUG_BOUNDING_BOXES &&
                debugBoundingBoxes.map((bbox) => {
                  const importance = bbox.importance ?? 0

                  // Color-code by importance
                  let strokeColor = '#888888' // Gray for low importance
                  if (bbox.wasAccepted) {
                    strokeColor = '#00ff00' // Green for accepted region
                  } else if (importance > 1.5) {
                    strokeColor = '#ff6600' // Orange for high importance
                  } else if (importance > 0.5) {
                    strokeColor = '#ffcc00' // Yellow for medium importance
                  }

                  return (
                    <rect
                      key={`mag-bbox-${bbox.regionId}`}
                      x={bbox.x}
                      y={bbox.y}
                      width={bbox.width}
                      height={bbox.height}
                      fill="none"
                      stroke={strokeColor}
                      strokeWidth={1}
                      vectorEffect="non-scaling-stroke"
                      pointerEvents="none"
                    />
                  )
                })}
            </animated.svg>

            {/* Debug: Bounding box labels as HTML overlays - positioned using animated values */}
            {SHOW_DEBUG_BOUNDING_BOXES &&
              debugBoundingBoxes.map((bbox) => {
                const importance = bbox.importance ?? 0
                let strokeColor = '#888888'

                if (bbox.wasAccepted) {
                  strokeColor = '#00ff00'
                } else if (importance > 1.5) {
                  strokeColor = '#ff6600'
                } else if (importance > 0.5) {
                  strokeColor = '#ffcc00'
                }

                // Parse viewBox - these are stable values from mapData
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                const viewBoxX = viewBoxParts[0] || 0
                const viewBoxY = viewBoxParts[1] || 0
                const viewBoxWidth = viewBoxParts[2] || 1000
                const viewBoxHeight = viewBoxParts[3] || 1000

                // Calculate bbox center in SVG coordinates
                const bboxCenterSvgX = bbox.x + bbox.width / 2
                const bboxCenterSvgY = bbox.y + bbox.height / 2

                // Use animated interpolation to sync with magnifier viewBox
                // ALL measurements must be taken inside the callback to stay in sync
                return (
                  <animated.div
                    key={`mag-bbox-label-${bbox.regionId}`}
                    style={{
                      position: 'absolute',
                      // Calculate position using the same spring that controls the magnifier viewBox
                      left: zoomSpring.to((zoom: number) => {
                        const containerRect = containerRef.current?.getBoundingClientRect()
                        const svgRect = svgRef.current?.getBoundingClientRect()
                        if (!containerRect || !svgRect || !cursorPosition) return '-9999px'

                        // Magnifier dimensions
                        const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO

                        // Convert cursor to SVG coordinates (same as magnifier viewBox calc)
                        const scaleX = viewBoxWidth / svgRect.width
                        const svgOffsetX = svgRect.left - containerRect.left
                        const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX

                        // Magnified viewport in SVG coordinates
                        const magnifiedWidth = viewBoxWidth / zoom
                        const magnifiedViewBoxX = cursorSvgX - magnifiedWidth / 2

                        // Position of bbox center relative to magnified viewport (0-1)
                        const relativeX = (bboxCenterSvgX - magnifiedViewBoxX) / magnifiedWidth
                        if (relativeX < 0 || relativeX > 1) return '-9999px'

                        return `${relativeX * magnifierWidth}px`
                      }),
                      top: zoomSpring.to((zoom: number) => {
                        const containerRect = containerRef.current?.getBoundingClientRect()
                        const svgRect = svgRef.current?.getBoundingClientRect()
                        if (!containerRect || !svgRect || !cursorPosition) return '-9999px'

                        // Magnifier dimensions (2:1 aspect ratio)
                        const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
                        const magnifierHeight = containerRect.height * MAGNIFIER_SIZE_RATIO

                        // Convert cursor to SVG coordinates (same as magnifier viewBox calc)
                        const scaleY = viewBoxHeight / svgRect.height
                        const svgOffsetY = svgRect.top - containerRect.top
                        const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY

                        // Magnified viewport in SVG coordinates
                        const magnifiedHeight = viewBoxHeight / zoom
                        const magnifiedViewBoxY = cursorSvgY - magnifiedHeight / 2

                        // Position of bbox center relative to magnified viewport (0-1)
                        const relativeY = (bboxCenterSvgY - magnifiedViewBoxY) / magnifiedHeight
                        if (relativeY < 0 || relativeY > 1) return '-9999px'

                        return `${relativeY * magnifierHeight}px`
                      }),
                      transform: 'translate(-50%, -50%)',
                      pointerEvents: 'none',
                      zIndex: 15,
                      fontSize: '10px',
                      fontWeight: 'bold',
                      color: strokeColor,
                      textAlign: 'center',
                      textShadow: '0 0 2px black, 0 0 2px black, 0 0 2px black',
                      whiteSpace: 'nowrap',
                    }}
                  >
                    <div>{bbox.regionId}</div>
                    <div style={{ fontSize: '8px', fontWeight: 'normal' }}>
                      {importance.toFixed(2)}
                    </div>
                  </animated.div>
                )
              })}

            {/* Magnifier label */}
            <animated.div
              style={{
                position: 'absolute',
                top: '8px',
                left: '8px',
                padding: '4px 8px',
                background: isDark ? 'rgba(31, 41, 55, 0.9)' : 'rgba(255, 255, 255, 0.9)',
                borderRadius: '6px',
                fontSize: '11px',
                fontWeight: 'bold',
                color: isDark ? '#60a5fa' : '#3b82f6',
                pointerEvents: pointerLocked ? 'none' : 'auto',
                cursor: pointerLocked ? 'default' : 'pointer',
              }}
              onClick={(e) => {
                // Request pointer lock when user clicks on notice
                if (!pointerLocked && containerRef.current) {
                  e.stopPropagation() // Prevent click from bubbling to map
                  containerRef.current.requestPointerLock()
                }
              }}
              data-element="magnifier-label"
            >
              {zoomSpring.to((z: number) => {
                const multiplier = magnifierSpring.movementMultiplier.get()

                // When in pointer lock mode, show "Precision mode active" notice
                if (pointerLocked) {
                  return 'Precision mode active'
                }

                // When NOT in pointer lock, calculate screen pixel ratio
                const containerRect = containerRef.current?.getBoundingClientRect()
                const svgRect = svgRef.current?.getBoundingClientRect()
                if (!containerRect || !svgRect) {
                  return `${z.toFixed(1)}×`
                }

                const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                const viewBoxWidth = viewBoxParts[2]

                if (!viewBoxWidth || Number.isNaN(viewBoxWidth)) {
                  return `${z.toFixed(1)}×`
                }

                const screenPixelRatio = calculateScreenPixelRatio({
                  magnifierWidth,
                  viewBoxWidth,
                  svgWidth: svgRect.width,
                  zoom: z,
                })

                // If at or above threshold, show notice about activating precision controls
                if (isAboveThreshold(screenPixelRatio, PRECISION_MODE_THRESHOLD)) {
                  return 'Click to activate precision mode'
                }

                // Below threshold - show debug info in dev, simple zoom in prod
                if (SHOW_MAGNIFIER_DEBUG_INFO) {
                  return `${z.toFixed(1)}× | ${screenPixelRatio.toFixed(1)} px/px`
                }

                return `${z.toFixed(1)}×`
              })}
            </animated.div>

            {/* Scrim overlay - shows when at threshold to indicate barrier */}
            {!pointerLocked &&
              (() => {
                const containerRect = containerRef.current?.getBoundingClientRect()
                const svgRect = svgRef.current?.getBoundingClientRect()
                if (!containerRect || !svgRect) return null

                const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
                const viewBoxParts = displayViewBox.split(' ').map(Number)
                const viewBoxWidth = viewBoxParts[2]
                if (!viewBoxWidth || Number.isNaN(viewBoxWidth)) return null

                const currentZoom = getCurrentZoom()
                const screenPixelRatio = calculateScreenPixelRatio({
                  magnifierWidth,
                  viewBoxWidth,
                  svgWidth: svgRect.width,
                  zoom: currentZoom,
                })

                // Only show scrim when at or above threshold
                if (!isAboveThreshold(screenPixelRatio, PRECISION_MODE_THRESHOLD)) return null

                return (
                  <div
                    data-element="precision-mode-scrim"
                    style={{
                      position: 'absolute',
                      inset: 0,
                      background: 'rgba(251, 191, 36, 0.15)', // Gold scrim
                      pointerEvents: 'none',
                      borderRadius: '12px',
                    }}
                  />
                )
              })()}
          </animated.div>
        )
      })()}

      {/* Zoom lines connecting indicator to magnifier - creates "pop out" effect */}
      {(() => {
        if (!showMagnifier || !cursorPosition || !svgRef.current || !containerRef.current) {
          return null
        }

        const containerRect = containerRef.current.getBoundingClientRect()
        const svgRect = svgRef.current.getBoundingClientRect()

        // Get magnifier dimensions and position
        const magnifierWidth = containerRect.width * MAGNIFIER_SIZE_RATIO
        const magnifierHeight = containerRect.height * MAGNIFIER_SIZE_RATIO // aspectRatio 2/1

        // Magnifier position (animated via spring, but we use target for calculation)
        const magTop = targetTop
        const magLeft = targetLeft

        // Calculate indicator box position in screen coordinates
        const viewBoxParts = displayViewBox.split(' ').map(Number)
        const viewBoxX = viewBoxParts[0] || 0
        const viewBoxY = viewBoxParts[1] || 0
        const viewBoxWidth = viewBoxParts[2] || 1000
        const viewBoxHeight = viewBoxParts[3] || 1000

        const currentZoom = getCurrentZoom()
        const indicatorWidth = viewBoxWidth / currentZoom
        const indicatorHeight = viewBoxHeight / currentZoom

        // Convert cursor to SVG coordinates
        const scaleX = viewBoxWidth / svgRect.width
        const scaleY = viewBoxHeight / svgRect.height
        const svgOffsetX = svgRect.left - containerRect.left
        const svgOffsetY = svgRect.top - containerRect.top

        const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
        const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY

        // Indicator box in SVG coordinates
        const indSvgLeft = cursorSvgX - indicatorWidth / 2
        const indSvgTop = cursorSvgY - indicatorHeight / 2
        const indSvgRight = indSvgLeft + indicatorWidth
        const indSvgBottom = indSvgTop + indicatorHeight

        // Convert indicator corners to screen coordinates
        const svgToScreen = (svgX: number, svgY: number) => ({
          x: (svgX - viewBoxX) / scaleX + svgOffsetX,
          y: (svgY - viewBoxY) / scaleY + svgOffsetY,
        })

        const indTL = svgToScreen(indSvgLeft, indSvgTop)
        const indTR = svgToScreen(indSvgRight, indSvgTop)
        const indBL = svgToScreen(indSvgLeft, indSvgBottom)
        const indBR = svgToScreen(indSvgRight, indSvgBottom)

        // Magnifier corners in screen coordinates
        const magTL = { x: magLeft, y: magTop }
        const magTR = { x: magLeft + magnifierWidth, y: magTop }
        const magBL = { x: magLeft, y: magTop + magnifierHeight }
        const magBR = { x: magLeft + magnifierWidth, y: magTop + magnifierHeight }

        // Check if a line segment passes through a rectangle (excluding endpoints)
        const linePassesThroughRect = (
          from: { x: number; y: number },
          to: { x: number; y: number },
          rectLeft: number,
          rectTop: number,
          rectRight: number,
          rectBottom: number
        ) => {
          // Sample points along the line (excluding endpoints)
          for (let t = 0.1; t <= 0.9; t += 0.1) {
            const px = from.x + (to.x - from.x) * t
            const py = from.y + (to.y - from.y) * t
            if (px > rectLeft && px < rectRight && py > rectTop && py < rectBottom) {
              return true
            }
          }
          return false
        }

        // Create bezier paths with elegant curves
        const createBezierPath = (from: { x: number; y: number }, to: { x: number; y: number }) => {
          const dx = to.x - from.x
          const dy = to.y - from.y
          const dist = Math.sqrt(dx * dx + dy * dy)

          // Perpendicular offset creates gentle outward bow
          const bowAmount = dist * 0.06
          const perpX = (-dy / dist) * bowAmount
          const perpY = (dx / dist) * bowAmount

          const midX = (from.x + to.x) / 2 + perpX
          const midY = (from.y + to.y) / 2 + perpY

          // Quadratic bezier for smooth curve
          return `M ${from.x} ${from.y} Q ${midX} ${midY}, ${to.x} ${to.y}`
        }

        // Define the corner pairs with identifiers
        const cornerPairs = [
          { from: indTL, to: magTL, corner: indTL },
          { from: indTR, to: magTR, corner: indTR },
          { from: indBL, to: magBL, corner: indBL },
          { from: indBR, to: magBR, corner: indBR },
        ]

        // Filter out lines that pass through either rectangle
        const visibleCornerPairs = cornerPairs.filter(({ from, to }) => {
          // Check if line passes through magnifier
          const passesThroughMag = linePassesThroughRect(
            from,
            to,
            magLeft,
            magTop,
            magLeft + magnifierWidth,
            magTop + magnifierHeight
          )
          // Check if line passes through indicator
          const passesThroughInd = linePassesThroughRect(
            from,
            to,
            indTL.x,
            indTL.y,
            indBR.x,
            indBR.y
          )
          return !passesThroughMag && !passesThroughInd
        })

        const paths = visibleCornerPairs.map(({ from, to }) => createBezierPath(from, to))
        const visibleCorners = visibleCornerPairs.map(({ corner }) => corner)

        // Color based on zoom level (matches magnifier border)
        const isHighZoom = currentZoom > HIGH_ZOOM_THRESHOLD
        const lineColor = isHighZoom
          ? isDark
            ? '#fbbf24'
            : '#f59e0b' // gold
          : isDark
            ? '#60a5fa'
            : '#3b82f6' // blue
        const glowColor = isHighZoom ? 'rgba(251, 191, 36, 0.6)' : 'rgba(96, 165, 250, 0.6)'

        return (
          <svg
            data-element="zoom-lines"
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: '100%',
              pointerEvents: 'none',
              zIndex: 99, // Just below magnifier (100)
              overflow: 'visible',
            }}
          >
            <defs>
              {/* Gradient for lines - fades toward magnifier */}
              <linearGradient id="zoom-line-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
                <stop offset="0%" stopColor={lineColor} stopOpacity="0.8" />
                <stop offset="40%" stopColor={lineColor} stopOpacity="0.5" />
                <stop offset="100%" stopColor={lineColor} stopOpacity="0.2" />
              </linearGradient>

              {/* Glow filter for premium effect */}
              <filter id="zoom-line-glow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
                <feMerge>
                  <feMergeNode in="blur" />
                  <feMergeNode in="SourceGraphic" />
                </feMerge>
              </filter>

              {/* Animated dash pattern */}
              <pattern id="dash-pattern" patternUnits="userSpaceOnUse" width="12" height="1">
                <rect width="8" height="1" fill={lineColor} opacity="0.6" />
              </pattern>
            </defs>

            {/* Glow layer (underneath) */}
            <g filter="url(#zoom-line-glow)" opacity={0.4}>
              {paths.map((d, i) => (
                <path
                  key={`glow-${i}`}
                  d={d}
                  fill="none"
                  stroke={glowColor}
                  strokeWidth="6"
                  strokeLinecap="round"
                />
              ))}
            </g>

            {/* Main lines with gradient */}
            <g opacity={targetOpacity}>
              {paths.map((d, i) => (
                <path
                  key={`line-${i}`}
                  d={d}
                  fill="none"
                  stroke="url(#zoom-line-gradient)"
                  strokeWidth="2"
                  strokeLinecap="round"
                  style={{
                    // Subtle animation for the lines
                    strokeDasharray: '8 4',
                    strokeDashoffset: '0',
                    animation: 'zoom-line-flow 1s linear infinite',
                  }}
                />
              ))}
            </g>

            {/* Corner dots on indicator for visible lines only */}
            <g opacity={targetOpacity * 0.8}>
              {visibleCorners.map((corner, i) => (
                <circle
                  key={`corner-${i}`}
                  cx={corner.x}
                  cy={corner.y}
                  r="3"
                  fill={lineColor}
                  opacity="0.7"
                />
              ))}
            </g>

            <style>
              {`
                @keyframes zoom-line-flow {
                  from { stroke-dashoffset: 12; }
                  to { stroke-dashoffset: 0; }
                }
              `}
            </style>
          </svg>
        )
      })()}

      {/* Debug: Auto zoom detection visualization (dev only) */}
      {SHOW_MAGNIFIER_DEBUG_INFO && cursorPosition && containerRef.current && (
        <>
          {/* Detection box - 50px box around cursor */}
          <div
            style={{
              position: 'absolute',
              left: `${cursorPosition.x - 25}px`,
              top: `${cursorPosition.y - 25}px`,
              width: '50px',
              height: '50px',
              border: '2px dashed yellow',
              pointerEvents: 'none',
              zIndex: 150,
            }}
          />

          {/* Detection info overlay - top left corner */}
          {(() => {
            const { detectedRegions, hasSmallRegion, detectedSmallestSize } = detectRegions(
              cursorPosition.x,
              cursorPosition.y
            )

            return (
              <div
                style={{
                  position: 'absolute',
                  top: '10px',
                  left: '10px',
                  backgroundColor: 'rgba(0, 0, 0, 0.8)',
                  color: 'white',
                  padding: '10px',
                  borderRadius: '4px',
                  fontSize: '12px',
                  fontFamily: 'monospace',
                  pointerEvents: 'none',
                  zIndex: 150,
                  maxWidth: '300px',
                }}
              >
                <div>
                  <strong>Detection Box (50px)</strong>
                </div>
                <div>Regions detected: {detectedRegions.length}</div>
                <div>Has small region: {hasSmallRegion ? 'YES' : 'NO'}</div>
                <div>
                  Smallest size:{' '}
                  {detectedSmallestSize === Infinity ? '∞' : `${detectedSmallestSize.toFixed(1)}px`}
                </div>
                {/* Zoom Decision Details */}
                {zoomSearchDebugInfo && (
                  <>
                    <div
                      style={{ marginTop: '8px', paddingTop: '8px', borderTop: '1px solid #444' }}
                    >
                      <strong>Zoom Decision:</strong>
                    </div>
                    <div style={{ fontSize: '10px', marginLeft: '8px' }}>
                      Final zoom: <strong>{zoomSearchDebugInfo.zoom.toFixed(1)}×</strong>
                      {!zoomSearchDebugInfo.foundGoodZoom && ' (fallback to min)'}
                    </div>
                    <div style={{ fontSize: '10px', marginLeft: '8px' }}>
                      Accepted: <strong>{zoomSearchDebugInfo.acceptedRegionId || 'none'}</strong>
                    </div>
                    <div style={{ fontSize: '10px', marginLeft: '8px' }}>
                      Thresholds: {(zoomSearchDebugInfo.acceptanceThresholds.min * 100).toFixed(0)}%
                      - {(zoomSearchDebugInfo.acceptanceThresholds.max * 100).toFixed(0)}% of
                      magnifier
                    </div>

                    <div style={{ marginTop: '8px' }}>
                      <strong>Region Analysis (top 3):</strong>
                    </div>
                    {Array.from(
                      new Map(
                        zoomSearchDebugInfo.regionDecisions.map((d) => [d.regionId, d])
                      ).values()
                    )
                      .sort((a, b) => b.importance - a.importance)
                      .slice(0, 3)
                      .map((decision) => {
                        const marker = decision.wasAccepted ? '✓' : '✗'
                        const color = decision.wasAccepted ? '#0f0' : '#888'
                        return (
                          <div
                            key={`decision-${decision.regionId}`}
                            style={{
                              fontSize: '9px',
                              marginLeft: '8px',
                              color,
                            }}
                          >
                            {marker} {decision.regionId}: {decision.currentSize.width.toFixed(0)}×
                            {decision.currentSize.height.toFixed(0)}px
                            {decision.rejectionReason && ` (${decision.rejectionReason})`}
                          </div>
                        )
                      })}
                  </>
                )}

                <div style={{ marginTop: '8px', paddingTop: '8px', borderTop: '1px solid #444' }}>
                  <strong>Detected Regions ({detectedRegions.length}):</strong>
                </div>
                {detectedRegions.map((region) => (
                  <div key={region.id} style={{ fontSize: '10px', marginLeft: '8px' }}>
                    • {region.id}: {region.pixelWidth.toFixed(1)}×{region.pixelHeight.toFixed(1)}px
                    {region.isVerySmall ? ' (SMALL)' : ''}
                  </div>
                ))}
                <div style={{ marginTop: '8px' }}>
                  <strong>Current Zoom:</strong> {getCurrentZoom().toFixed(1)}×
                </div>
                <div>
                  <strong>Target Zoom:</strong> {targetZoom.toFixed(1)}×
                </div>
              </div>
            )
          })()}
        </>
      )}

      {/* Other players' cursors - show in multiplayer when not exclusively our turn */}
      {svgRef.current &&
        containerRef.current &&
        Object.entries(otherPlayerCursors).map(([playerId, position]) => {
          // Skip our own cursor and null positions
          if (playerId === localPlayerId || !position) return null

          // In turn-based mode, only show other cursors when it's not our turn
          if (gameMode === 'turn-based' && currentPlayer === localPlayerId) return null

          // Get player metadata for emoji and color
          const player = playerMetadata[playerId]
          if (!player) return null

          // In collaborative mode, find all players from the same session and show all their emojis
          // Use memberPlayers (from roomData) which is the canonical source of player ownership
          const cursorUserId = position.userId
          const sessionPlayers =
            gameMode === 'cooperative' && cursorUserId && memberPlayers[cursorUserId]
              ? memberPlayers[cursorUserId]
              : [player]

          // Convert SVG coordinates to screen coordinates
          const svgRect = svgRef.current!.getBoundingClientRect()
          const containerRect = containerRef.current!.getBoundingClientRect()
          const viewBoxParts = displayViewBox.split(' ').map(Number)
          const viewBoxX = viewBoxParts[0] || 0
          const viewBoxY = viewBoxParts[1] || 0
          const viewBoxW = viewBoxParts[2] || 1000
          const viewBoxH = viewBoxParts[3] || 500
          const svgOffsetX = svgRect.left - containerRect.left
          const svgOffsetY = svgRect.top - containerRect.top
          const scaleX = svgRect.width / viewBoxW
          const scaleY = svgRect.height / viewBoxH
          const screenX = (position.x - viewBoxX) * scaleX + svgOffsetX
          const screenY = (position.y - viewBoxY) * scaleY + svgOffsetY

          // Check if cursor is within SVG bounds
          if (
            screenX < svgOffsetX ||
            screenX > svgOffsetX + svgRect.width ||
            screenY < svgOffsetY ||
            screenY > svgOffsetY + svgRect.height
          ) {
            return null
          }

          return (
            <div
              key={`cursor-${playerId}`}
              data-element="other-player-cursor"
              data-player-id={playerId}
              style={{
                position: 'absolute',
                left: screenX,
                top: screenY,
                pointerEvents: 'none',
                zIndex: 100,
              }}
            >
              {/* Crosshair - centered on the cursor position */}
              <svg
                width="24"
                height="24"
                viewBox="0 0 24 24"
                style={{
                  position: 'absolute',
                  left: -12, // Half of width to center
                  top: -12, // Half of height to center
                  filter: 'drop-shadow(0 1px 2px rgba(0,0,0,0.5))',
                }}
              >
                {/* Outer ring */}
                <circle
                  cx="12"
                  cy="12"
                  r="8"
                  fill="none"
                  stroke={player.color}
                  strokeWidth="2"
                  opacity="0.8"
                />
                {/* Cross lines */}
                <line
                  x1="12"
                  y1="2"
                  x2="12"
                  y2="8"
                  stroke={player.color}
                  strokeWidth="2"
                  strokeLinecap="round"
                />
                <line
                  x1="12"
                  y1="16"
                  x2="12"
                  y2="22"
                  stroke={player.color}
                  strokeWidth="2"
                  strokeLinecap="round"
                />
                <line
                  x1="2"
                  y1="12"
                  x2="8"
                  y2="12"
                  stroke={player.color}
                  strokeWidth="2"
                  strokeLinecap="round"
                />
                <line
                  x1="16"
                  y1="12"
                  x2="22"
                  y2="12"
                  stroke={player.color}
                  strokeWidth="2"
                  strokeLinecap="round"
                />
                {/* Center dot */}
                <circle cx="12" cy="12" r="2" fill={player.color} />
              </svg>
              {/* Player emoji label(s) - positioned below crosshair */}
              {/* In collaborative mode, show all emojis from the same session */}
              <div
                style={{
                  position: 'absolute',
                  left: '50%',
                  top: 14, // Below the crosshair (12px half-height + 2px gap)
                  transform: 'translateX(-50%)',
                  fontSize: '16px',
                  textShadow: '0 1px 2px rgba(0,0,0,0.5)',
                  whiteSpace: 'nowrap',
                }}
              >
                {sessionPlayers.map((p) => p.emoji).join('')}
              </div>
            </div>
          )
        })}

      {/* Give Up button overlay - positioned within SVG bounds for pointer lock accessibility */}
      {(() => {
        // Use svgDimensions to trigger re-render on resize, but get actual rect for positioning
        if (!svgRef.current || !containerRef.current || svgDimensions.width === 0) return null

        // During give-up animation, use saved position to prevent jumping due to SVG scale transform
        let buttonTop: number
        let buttonRight: number

        if (isGiveUpAnimating && savedButtonPosition) {
          // Use saved position during animation
          buttonTop = savedButtonPosition.top
          buttonRight = savedButtonPosition.right
        } else {
          // Calculate position normally
          const svgRect = svgRef.current.getBoundingClientRect()
          const containerRect = containerRef.current.getBoundingClientRect()
          // Position relative to SVG, not container (cursor is clamped to SVG bounds during pointer lock)
          const svgOffsetX = svgRect.left - containerRect.left
          const svgOffsetY = svgRect.top - containerRect.top
          // Position in top-right corner of SVG with some padding
          buttonTop = svgOffsetY + 8
          buttonRight = containerRect.width - (svgOffsetX + svgRect.width) + 8
        }

        return (
          <button
            ref={(el) => {
              // Update button bounds for pointer lock click detection
              if (el && containerRef.current) {
                const buttonRect = el.getBoundingClientRect()
                const containerRect = containerRef.current.getBoundingClientRect()
                giveUpButtonBoundsRef.current = {
                  left: buttonRect.left - containerRect.left,
                  top: buttonRect.top - containerRect.top,
                  right: buttonRect.right - containerRect.left,
                  bottom: buttonRect.bottom - containerRect.top,
                }
              }
            }}
            onClick={(e) => {
              e.stopPropagation() // Don't trigger map click
              if (!isGiveUpAnimating) {
                onGiveUp()
              }
            }}
            disabled={isGiveUpAnimating}
            data-action="give-up-overlay"
            title="Press 'G' to give up"
            style={{
              position: 'absolute',
              top: `${buttonTop}px`,
              right: `${buttonRight}px`,
              // Apply hover styles when fake cursor is over button (pointer lock mode)
              ...(isFakeCursorOverGiveUp && !isGiveUpAnimating
                ? {
                    backgroundColor: isDark ? '#a16207' : '#fef08a', // yellow.700 / yellow.200
                    transform: 'scale(1.05)',
                  }
                : {}),
              // Apply disabled styles
              ...(isGiveUpAnimating
                ? {
                    opacity: 0.5,
                    cursor: 'not-allowed',
                    transform: 'none',
                  }
                : {}),
            }}
            className={css({
              padding: '2 3',
              fontSize: 'sm',
              cursor: 'pointer',
              bg: isDark ? 'yellow.800' : 'yellow.100',
              color: isDark ? 'yellow.200' : 'yellow.800',
              rounded: 'md',
              border: '2px solid',
              borderColor: isDark ? 'yellow.600' : 'yellow.400',
              fontWeight: 'bold',
              transition: 'all 0.2s',
              zIndex: 50,
              boxShadow: 'md',
              _hover: {
                bg: isDark ? 'yellow.700' : 'yellow.200',
                transform: 'scale(1.05)',
              },
              _disabled: {
                opacity: 0.5,
                cursor: 'not-allowed',
                transform: 'none',
              },
            })}
          >
            {(() => {
              // Determine button text based on game mode and voting state
              // Voting is per-session (userId), not per-player
              const isCooperativeMultiplayer =
                gameMode === 'cooperative' && activeUserIds.length > 1
              const hasLocalSessionVoted = viewerId && giveUpVotes.includes(viewerId)
              const voteCount = giveUpVotes.length
              const totalSessions = activeUserIds.length

              if (isCooperativeMultiplayer) {
                if (hasLocalSessionVoted) {
                  return (
                    <span>
                      ✓ Voted ({voteCount}/{totalSessions})
                    </span>
                  )
                }
                if (voteCount > 0) {
                  return (
                    <span>
                      Give Up ({voteCount}/{totalSessions}) (G)
                    </span>
                  )
                }
              }
              return 'Give Up (G)'
            })()}
          </button>
        )
      })()}

      {/* Show waiting message for give up voting (cooperative multiplayer with multiple sessions) */}
      {gameMode === 'cooperative' &&
        activeUserIds.length > 1 &&
        giveUpVotes.length > 0 &&
        giveUpVotes.length < activeUserIds.length &&
        viewerId &&
        giveUpVotes.includes(viewerId) &&
        (() => {
          if (!svgRef.current || !containerRef.current || svgDimensions.width === 0) return null

          const svgRect = svgRef.current.getBoundingClientRect()
          const containerRect = containerRef.current.getBoundingClientRect()
          const svgOffsetY = svgRect.top - containerRect.top
          const buttonRight =
            containerRect.width - (svgRect.left - containerRect.left + svgRect.width) + 8

          const remaining = activeUserIds.length - giveUpVotes.length

          return (
            <div
              data-element="give-up-voters"
              className={css({
                position: 'absolute',
                fontSize: 'xs',
                color: isDark ? 'yellow.300' : 'yellow.700',
                bg: isDark ? 'gray.800/90' : 'white/90',
                padding: '1 2',
                rounded: 'md',
                border: '1px solid',
                borderColor: isDark ? 'yellow.600' : 'yellow.400',
                zIndex: 49,
              })}
              style={{
                top: `${svgOffsetY + 44}px`, // Below the Give Up button
                right: `${buttonRight}px`,
              }}
            >
              Waiting for {remaining} other {remaining === 1 ? 'player' : 'players'}...
            </div>
          )
        })()}

      {/* Dev-only crop tool for getting custom viewBox coordinates */}
      <DevCropTool
        svgRef={svgRef}
        containerRef={containerRef}
        viewBox={displayViewBox}
        mapId={selectedMap}
        continentId={selectedContinent}
      />
    </div>
  )
}
