blacktrigram 0.7.131 → 0.7.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/types.d.ts +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/index.d.ts +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.d.ts +4 -4
- package/lib/components/screens/combat/hooks/useCombatActions.d.ts +8 -8
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.d.ts +2 -2
- package/lib/components/screens/combat/hooks/useCombatState.d.ts +2 -2
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.d.ts +2 -2
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.d.ts +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/data/archetypeClothing.d.ts +2 -2
- package/lib/data/archetypePhysicalAttributes.d.ts +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/systems/ai/AIPersonality.d.ts +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.d.ts +1 -1
- package/lib/systems/ai/ComboSystem.d.ts +3 -3
- package/lib/systems/ai/TrainingAI.d.ts +1 -1
- package/lib/systems/ai/types.d.ts +3 -3
- package/lib/systems/animation/builders/AnimationBuilder.d.ts +2 -2
- package/lib/systems/animation/builders/HandPoseApplicator.d.ts +1 -1
- package/lib/systems/animation/builders/HandPoses.d.ts +2 -2
- package/lib/systems/animation/builders/KeyframeConfig.d.ts +2 -2
- package/lib/systems/animation/builders/KeyframeInterpolation.d.ts +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +2 -2
- package/lib/systems/animation/builders/MartialPoseApplicator.d.ts +1 -1
- package/lib/systems/animation/builders/SkeletonRig.d.ts +3 -3
- package/lib/systems/animation/builders/TrigramGuardApplicator.d.ts +2 -2
- package/lib/systems/animation/catalogs/AttackAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GonGuardPoses.d.ts +1 -1
- package/lib/systems/animation/catalogs/GonStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/GrapplingAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +2 -2
- package/lib/systems/animation/catalogs/StanceIdleAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/TaeJointLockAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.d.ts +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.d.ts +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.d.ts +2 -2
- package/lib/systems/animation/core/AnimationTransitions.d.ts +2 -2
- package/lib/systems/animation/core/LateralityTransform.d.ts +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.d.ts +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.d.ts +1 -1
- package/lib/systems/animation/core/TrigramAnimationMapping.d.ts +2 -2
- package/lib/systems/animation/core/TrigramStanceTransitions.d.ts +2 -2
- package/lib/systems/animation/systems/BodyFacingSystem.d.ts +1 -1
- package/lib/systems/animation/systems/FacialExpressions.d.ts +1 -1
- package/lib/systems/animation/systems/FallAnimations.d.ts +1 -1
- package/lib/systems/animation/systems/HeadMovements.d.ts +1 -1
- package/lib/systems/animation/systems/MuscleActivation.d.ts +2 -2
- package/lib/systems/animation/systems/RecoveryVisualization.d.ts +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.d.ts +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.d.ts +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.d.ts +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.d.ts +1 -1
- package/lib/systems/bodypart/types.d.ts +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.d.ts +3 -3
- package/lib/systems/breathing/feedback.d.ts +1 -1
- package/lib/systems/combat/BalanceSystem.d.ts +2 -2
- package/lib/systems/combat/ConsciousnessSystem.d.ts +2 -2
- package/lib/systems/combat/GrappleSystem.d.ts +2 -2
- package/lib/systems/combat/PainResponseSystem.d.ts +1 -1
- package/lib/systems/combat/painConsciousnessUtils.d.ts +1 -1
- package/lib/systems/combat/types.d.ts +1 -1
- package/lib/systems/game.d.ts +1 -1
- package/lib/systems/movement/InjuryMovementModifier.d.ts +1 -1
- package/lib/systems/movement/integration.d.ts +1 -1
- package/lib/systems/physics/AttackMovementPhysics.d.ts +2 -2
- package/lib/systems/physics/CoordinateMapper.d.ts +2 -2
- package/lib/systems/physics/KnockbackPhysics.d.ts +1 -1
- package/lib/systems/physics/MovementPhysics.d.ts +2 -2
- package/lib/systems/physics/PhysicalReachCalculator.d.ts +2 -2
- package/lib/systems/physics/SpeedModifierSystem.d.ts +1 -1
- package/lib/systems/player.d.ts +8 -8
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/GeonTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.d.ts +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.d.ts +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.d.ts +1 -1
- package/lib/systems/types.d.ts +3 -3
- package/lib/systems/vitalpoint/KoreanAnatomy.d.ts +1 -1
- package/lib/systems/vitalpoint/VitalPointsData.d.ts +1 -1
- package/lib/test/test-utils.d.ts +1 -1
- package/lib/types/physicsConstants.d.ts +1 -1
- package/lib/utils/characterScaling.d.ts +1 -1
- package/lib/utils/clothingMaterials.d.ts +1 -1
- package/lib/utils/inputSystem.d.ts +1 -1
- package/lib/utils/particlePool.d.ts +1 -1
- package/lib/utils/physicalAttributeValidation.d.ts +1 -1
- package/lib/utils/sharedPhysicsConfig.d.ts +1 -1
- package/lib/utils/skeletonScaling.d.ts +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/package.json +13 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EndScreen3D.js","names":[],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EndScreen3D.js","names":["_Fragment"],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -21,7 +21,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
21
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
22
22
|
import { Canvas } from "@react-three/fiber";
|
|
23
23
|
//#region src/components/screens/intro/IntroScreen3D.tsx
|
|
24
|
-
var APP_VERSION = "0.7.
|
|
24
|
+
var APP_VERSION = "0.7.133";
|
|
25
25
|
var MENU_ITEMS = [
|
|
26
26
|
{
|
|
27
27
|
mode: GameMode.VERSUS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbilityListOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/AbilityListOverlayHtml.tsx"],"sourcesContent":["/**\n * AbilityList - Enhanced ability list component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays a list of special abilities with bilingual support\n * \n * Performance optimized with React.memo and useMemo\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 능력리스트\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../../utils/colorUtils\";\n\nexport interface Ability {\n readonly korean: string;\n readonly english: string;\n readonly description?: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\nexport interface AbilityListProps {\n readonly abilities: readonly string[] | readonly Ability[];\n readonly maxAbilities?: number; // Maximum abilities to show\n readonly color?: number; // Accent color for abilities\n readonly isMobile?: boolean;\n}\n\n/**\n * AbilityList component - Displays a list of special abilities\n * \n * Refactored to use useKoreanTheme for consistent Korean theming:\n * - Uses Korean typography configuration\n * - Applies Korean color palette\n * - Responsive sizing based on device type\n * - Memoized for optimal performance\n * \n * Used in archetype cards to show key techniques and skills\n * \n * @example\n * ```tsx\n * <AbilityList\n * abilities={[\n * { korean: \"급소격\", english: \"Vital Strike\" },\n * { korean: \"연격\", english: \"Chain Attack\" }\n * ]}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const AbilityList: React.FC<AbilityListProps> = React.memo(\n ({ abilities, maxAbilities = 3, color = KOREAN_COLORS.ACCENT_GOLD, isMobile = false }) => {\n const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\n return {\n id: `ability-${index}`,\n english: ability.english,\n korean: ability.korean,\n description: ability.description,\n };\n }\n });\n }, [abilities, maxAbilities]);\n\n const colors = useMemo(\n () => ({\n abilityBorder: hexToRgbaString(color, 0.5),\n abilityBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.7\n ),\n abilityText: hexColorToCSS(color),\n descriptionText: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }),\n [color]\n );\n\n const fontSize = calculateResponsiveSize(isMobile ? 10 : 12);\n const descFontSize = calculateResponsiveSize(isMobile ? 8 : 10);\n const padding = isMobile ? `${calculateResponsiveSize(6)}px ${calculateResponsiveSize(10)}px` : `${calculateResponsiveSize(8)}px ${calculateResponsiveSize(12)}px`;\n\n if (normalizedAbilities.length === 0) {\n return null;\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(8)}px`,\n }}\n data-testid=\"ability-list\"\n >\n {/* Header with Korean typography */}\n <div\n style={{\n fontSize: `${calculateResponsiveSize(isMobile ? 12 : 14)}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n }}\n data-testid=\"ability-list-header\"\n >\n 특수 능력 | Special Abilities\n </div>\n\n {/* Ability items */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(6)}px`,\n }}\n >\n {normalizedAbilities.map((ability) => (\n <div\n key={ability.id}\n style={{\n padding,\n background: colors.abilityBackground,\n border: `1px solid ${colors.abilityBorder}`,\n borderRadius: \"4px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(4)}px`,\n }}\n data-testid={ability.id}\n >\n {/* Ability name with Korean typography */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-name`}\n >\n {ability.korean} | {ability.english}\n </div>\n\n {/* Ability description (if available) */}\n {ability.description && (\n <div\n style={{\n fontSize: `${descFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: fontFamily.KOREAN,\n color: colors.descriptionText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-description`}\n >\n {ability.description.korean}\n {ability.description.english && (\n <>\n <br />\n {ability.description.english}\n </>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n }\n);\n\nAbilityList.displayName = \"AbilityList\";\n\nexport default AbilityList;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,cAA0C,MAAM,MAC1D,EAAE,WAAW,eAAe,GAAG,QAAQ,cAAc,aAAa,WAAW,YAAY;CACxF,MAAM,EAAE,kBAAkB,yBAAyB,eAAe,eAAe;EAC/E,MAAM;EACN;CACF,CAAC;CAED,MAAM,sBAAsB,cAAc;EACxC,OAAO,UAAU,MAAM,GAAG,YAAY,CAAC,CAAC,KAAK,SAAS,UAAU;GAC9D,IAAI,OAAO,YAAY,UACrB,OAAO;IACL,IAAI,WAAW;IACf,SAAS;IACT,QAAQ;GACV;QAEA,OAAO;IACL,IAAI,WAAW;IACf,SAAS,QAAQ;IACjB,QAAQ,QAAQ;IAChB,aAAa,QAAQ;GACvB;EAEJ,CAAC;CACH,GAAG,CAAC,WAAW,YAAY,CAAC;CAE5B,MAAM,SAAS,eACN;EACL,eAAe,gBAAgB,OAAO,EAAG;EACzC,mBAAmB,gBACjB,cAAc,sBACd,EACF;EACA,aAAa,cAAc,KAAK;EAChC,iBAAiB,cAAc,cAAc,cAAc;CAC7D,IACA,CAAC,KAAK,CACR;CAEA,MAAM,WAAW,wBAAwB,WAAW,KAAK,EAAE;CAC3D,MAAM,eAAe,wBAAwB,WAAW,IAAI,EAAE;CAC9D,MAAM,UAAU,WAAW,GAAG,wBAAwB,CAAC,EAAE,KAAK,wBAAwB,EAAE,EAAE,MAAM,GAAG,wBAAwB,CAAC,EAAE,KAAK,wBAAwB,EAAE,EAAE;CAE/J,IAAI,oBAAoB,WAAW,GACjC,OAAO;CAGT,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,SAAS;GACT,eAAe;GACf,KAAK,GAAG,wBAAwB,CAAC,EAAE;EACrC;EACA,eAAY;EAPd,UAAA,CAUE,oBAAC,OAAD;GACE,OAAO;IACL,UAAU,GAAG,wBAAwB,WAAW,KAAK,EAAE,EAAE;IACzD,YAAY;IACZ,YAAY,WAAW;IACvB,OAAO,OAAO;IACd,YAAY,iBAAiB;IAC7B,eAAe,iBAAiB;GAClC;GACA,eAAY;GACb,UAAA;EAEI,CAAA,GAGL,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK,GAAG,wBAAwB,CAAC,EAAE;GACrC;GAEC,UAAA,oBAAoB,KAAK,YACxB,qBAAC,OAAD;IAEE,OAAO;KACL;KACA,YAAY,OAAO;KACnB,QAAQ,aAAa,OAAO;KAC5B,cAAc;KACd,SAAS;KACT,eAAe;KACf,KAAK,GAAG,wBAAwB,CAAC,EAAE;IACrC;IACA,eAAa,QAAQ;IAXvB,UAAA,CAcE,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,SAAS;MACtB,YAAY;MACZ,YAAY,WAAW;MACvB,OAAO,OAAO;MACd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;KAC9B;KACA,eAAa,GAAG,QAAQ,GAAG;KAV7B,UAAA;MAYG,QAAQ;MAAO;MAAI,QAAQ;KACzB;IAGJ,CAAA,GAAA,QAAQ,eACP,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,aAAa;MAC1B,WAAW;MACX,YAAY,WAAW;MACvB,OAAO,OAAO;MACd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;KAC9B;KACA,eAAa,GAAG,QAAQ,GAAG;KAV7B,UAAA,CAYG,QAAQ,YAAY,QACpB,QAAQ,YAAY,WACnB,
|
|
1
|
+
{"version":3,"file":"AbilityListOverlayHtml.js","names":["_Fragment"],"sources":["../../../../../src/components/screens/intro/components/AbilityListOverlayHtml.tsx"],"sourcesContent":["/**\n * AbilityList - Enhanced ability list component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays a list of special abilities with bilingual support\n * \n * Performance optimized with React.memo and useMemo\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 능력리스트\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../../utils/colorUtils\";\n\nexport interface Ability {\n readonly korean: string;\n readonly english: string;\n readonly description?: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\nexport interface AbilityListProps {\n readonly abilities: readonly string[] | readonly Ability[];\n readonly maxAbilities?: number; // Maximum abilities to show\n readonly color?: number; // Accent color for abilities\n readonly isMobile?: boolean;\n}\n\n/**\n * AbilityList component - Displays a list of special abilities\n * \n * Refactored to use useKoreanTheme for consistent Korean theming:\n * - Uses Korean typography configuration\n * - Applies Korean color palette\n * - Responsive sizing based on device type\n * - Memoized for optimal performance\n * \n * Used in archetype cards to show key techniques and skills\n * \n * @example\n * ```tsx\n * <AbilityList\n * abilities={[\n * { korean: \"급소격\", english: \"Vital Strike\" },\n * { korean: \"연격\", english: \"Chain Attack\" }\n * ]}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const AbilityList: React.FC<AbilityListProps> = React.memo(\n ({ abilities, maxAbilities = 3, color = KOREAN_COLORS.ACCENT_GOLD, isMobile = false }) => {\n const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\n return {\n id: `ability-${index}`,\n english: ability.english,\n korean: ability.korean,\n description: ability.description,\n };\n }\n });\n }, [abilities, maxAbilities]);\n\n const colors = useMemo(\n () => ({\n abilityBorder: hexToRgbaString(color, 0.5),\n abilityBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.7\n ),\n abilityText: hexColorToCSS(color),\n descriptionText: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }),\n [color]\n );\n\n const fontSize = calculateResponsiveSize(isMobile ? 10 : 12);\n const descFontSize = calculateResponsiveSize(isMobile ? 8 : 10);\n const padding = isMobile ? `${calculateResponsiveSize(6)}px ${calculateResponsiveSize(10)}px` : `${calculateResponsiveSize(8)}px ${calculateResponsiveSize(12)}px`;\n\n if (normalizedAbilities.length === 0) {\n return null;\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(8)}px`,\n }}\n data-testid=\"ability-list\"\n >\n {/* Header with Korean typography */}\n <div\n style={{\n fontSize: `${calculateResponsiveSize(isMobile ? 12 : 14)}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n }}\n data-testid=\"ability-list-header\"\n >\n 특수 능력 | Special Abilities\n </div>\n\n {/* Ability items */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(6)}px`,\n }}\n >\n {normalizedAbilities.map((ability) => (\n <div\n key={ability.id}\n style={{\n padding,\n background: colors.abilityBackground,\n border: `1px solid ${colors.abilityBorder}`,\n borderRadius: \"4px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(4)}px`,\n }}\n data-testid={ability.id}\n >\n {/* Ability name with Korean typography */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-name`}\n >\n {ability.korean} | {ability.english}\n </div>\n\n {/* Ability description (if available) */}\n {ability.description && (\n <div\n style={{\n fontSize: `${descFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: fontFamily.KOREAN,\n color: colors.descriptionText,\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-description`}\n >\n {ability.description.korean}\n {ability.description.english && (\n <>\n <br />\n {ability.description.english}\n </>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n }\n);\n\nAbilityList.displayName = \"AbilityList\";\n\nexport default AbilityList;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingScreen3D.js","names":[],"sources":["../../../../src/components/screens/training/TrainingScreen3D.tsx"],"sourcesContent":["/**\n * TrainingScreen3D - Three.js-based training screen\n *\n * Refactored to use consolidated hooks matching CombatScreen architecture.\n * Provides 3D training dummy with vital point targeting and UI overlays.\n *\n * UI Rendering: All HUD elements are rendered in an absolute-positioned div\n * OUTSIDE the Canvas, matching CombatScreen's reliable rendering pattern.\n * This eliminates the need for Html overlays inside Three.js and ensures\n * HUDs appear immediately without waiting for Canvas initialization.\n *\n * Architecture (Consolidated in PR #1394 + Issue #1398):\n * - TrainingLeftHUD: Anatomy controls, guard indicator\n * - TrainingRightHUD: Training stats, mode selector, vital point selection\n * - TrainingTopHUD: Training controls, archetype selector, return button\n * - TrainingBottomHUD: Technique bar, feedback messages, mobile controls\n * - VitalPointOverlayControlsPure: Vital point overlay controls (pure DOM)\n *\n * All UI components render as pure DOM in the HUD overlay div (lines 1230+).\n * NO Html components from @react-three/drei are used inside the Canvas.\n * This ensures clean separation of 3D rendering and UI layers.\n *\n * @korean 훈련화면3D - 훈련 상태 훅을 사용한 리팩토링된 3D 훈련 화면\n */\n\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { AccelerationUpdater } from \"../../../systems/movement/helpers/AccelerationUpdater\";\nimport {\n isRunningSpeed,\n STEP_DISTANCE_THRESHOLDS,\n} from \"../../../systems/movement/helpers/accelerationUtils\";\nimport {\n Bloom,\n EffectComposer,\n Noise,\n Vignette,\n} from \"@react-three/postprocessing\";\nimport * as THREE from \"three\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useCombatAudio } from \"../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../data/archetypePhysicalAttributes\";\nimport { usePlayerAnimation } from \"../../../hooks/usePlayerAnimation\";\nimport { useTechniqueSelection } from \"../../../hooks/useTechniqueSelection\";\nimport { GestureEvent } from \"../../../hooks/useTouchControls\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { PlayerState } from \"../../../systems\";\nimport {\n AnimationEvents,\n AnimationState,\n AnimationType,\n resolveTechniqueAnimation,\n} from \"../../../systems/animation\";\nimport { getAnimationForTechniqueOrDefault } from \"../../../systems/animation/core/TechniqueAnimationMapping\";\nimport { physicalReachCalculator } from \"../../../systems/physics\";\nimport {\n MovementType,\n SpeedModifierSystem,\n} from \"../../../systems/physics/SpeedModifierSystem\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../systems/trigram/types\";\nimport {\n CombatState,\n PlayerArchetype,\n Position,\n Technique,\n TrigramStance,\n} from \"../../../types\";\nimport { getPerformanceSettings } from \"../../../types/constants\";\nimport { getMobileControlsBottom } from \"../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { DEFAULT_BODY_RADIUS_METERS } from \"../../../types/physicsConstants\";\nimport { usePlayerMovement } from \"../../../utils/inputSystem\";\nimport { calculateDistance3D } from \"../../../utils/math\";\nimport { getHUDPositionScale } from \"../../../utils/responsiveLayoutHelpers\";\nimport { createCameraConfig } from \"../../../utils/sharedPhysicsConfig\";\nimport {\n animationStateToPlayerAnimation,\n convertPlayerStateToProps,\n} from \"../../../utils/player3DHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport {\n GestureRecognizerPure,\n StanceWheelPure,\n} from \"../../shared/mobile\";\nimport {\n MobileControlsOverlay,\n type ButtonEventType,\n type Direction,\n type DPadEventType,\n} from \"../../shared/mobile/MobileControlsPure\";\nimport {\n Player3DWithTransitions,\n VitalPointMarkers3D,\n type BodyRegionFilter,\n} from \"../../shared/three\";\nimport { StanceChangeIndicator } from \"../../shared/three/indicators/StanceChangeIndicator\";\nimport { CombatArena3D } from \"../../shared/three/scene/CombatArena3D\";\nimport { VitalPointOverlayControlsPure } from \"../../shared/ui/VitalPointOverlayControlsPure\";\nimport AnatomyOverlay3D, {\n type AnatomyLayer,\n} from \"./components/AnatomyOverlay3D\";\nimport FootPlacementMarkers3D from \"./components/FootPlacementMarkers3D\";\nimport HitFeedbackEffect3D from \"./components/HitFeedbackEffect3D\";\nimport type { DifficultyMode } from \"./components/TrainingDummy3D\";\nimport TrainingDummy3D from \"./components/TrainingDummy3D\";\nimport {\n TrainingBottomHUD,\n TrainingLeftHUD,\n TrainingRightHUD,\n TrainingTopHUD,\n} from \"./components/hud\";\nimport { useAttackMovement } from \"./hooks/useAttackMovement\";\nimport useTrainingActions from \"./hooks/useTrainingActions\";\nimport { useTrainingLayout } from \"./hooks/useTrainingLayout\";\nimport useTrainingState from \"./hooks/useTrainingState\";\n\n/**\n * AnimationUpdater - Component that updates player animation at 60fps\n *\n * @korean 훈련애니메이션업데이터 - 60fps로 플레이어 애니메이션을 업데이트하는 컴포넌트\n */\ninterface TrainingAnimationUpdaterProps {\n readonly playerAnimation: ReturnType<typeof usePlayerAnimation>;\n}\n\nconst TrainingAnimationUpdater: React.FC<TrainingAnimationUpdaterProps> = ({\n playerAnimation,\n}) => {\n useFrame((_state, delta) => {\n playerAnimation.update(delta);\n });\n\n return null;\n};\n\n/**\n * Props for the TrainingScreen3D component\n */\nexport interface TrainingScreen3DProps {\n /** Callback to update player state */\n readonly onPlayerUpdate: (updates: Partial<PlayerState>) => void;\n /** Callback when returning to menu */\n readonly onReturnToMenu: () => void;\n /** Canvas width in pixels. Defaults to 1200 */\n readonly width?: number;\n /** Canvas height in pixels. Defaults to 800 */\n readonly height?: number;\n /** Initial archetype from IntroScreen selection. Defaults to MUSA */\n readonly initialArchetype?: PlayerArchetype;\n}\n\n/**\n * TrainingScreen3D Component\n * Three.js-based training screen with 3D dummy and Html UI\n *\n * Uses consolidated hooks for state management matching CombatScreen architecture.\n */\nexport const TrainingScreen3D: React.FC<TrainingScreen3DProps> = ({\n onPlayerUpdate,\n onReturnToMenu,\n width = 1200,\n height = 800,\n initialArchetype = PlayerArchetype.MUSA,\n}) => {\n\n\n const { state: trainingState, actions: trainingActions } = useTrainingState();\n\n const audio = useAudio();\n \n const { playBoneImpactSound, playAttackSound, playStanceChangeSound } =\n useCombatAudio();\n\n const { trainingAreaBounds, isMobile, isPortrait, screenSize } =\n useTrainingLayout(width, height);\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n const positionScale = React.useMemo(\n () => getHUDPositionScale(screenSize, isMobile),\n [screenSize, isMobile],\n );\n\n const difficulty: DifficultyMode = \"normal\";\n const vitalPointCount = 70; // Show all 70 vital points\n\n const [selectedArchetype, setSelectedArchetype] =\n React.useState<PlayerArchetype>(initialArchetype);\n\n const [overlayVisible, setOverlayVisible] = React.useState(false);\n const [severityFilters, setSeverityFilters] = React.useState<\n import(\"../../../types/common\").VitalPointSeverity[]\n >([]);\n const [regionFilter, setRegionFilter] =\n React.useState<BodyRegionFilter>(\"all\");\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [showLabels, setShowLabels] = React.useState(true);\n const [animated, setAnimated] = React.useState(true);\n const [scale, setScale] = React.useState(1.2);\n\n\n const [attackAnimation, setAttackAnimation] = React.useState<\n string | undefined\n >(undefined);\n\n React.useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"v\" || e.key === \"V\") {\n setOverlayVisible((prev) => !prev);\n audio.playSFX(\"menu_select\");\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [audio]);\n\n\n const contextLossCountRef = useRef(0);\n\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in TrainingScreen\");\n contextLossCountRef.current += 1;\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in TrainingScreen\");\n },\n autoRestore: true,\n });\n\n\n const speedModifierSystem = useMemo(() => new SpeedModifierSystem(), []);\n\n const [speedModifiers, setSpeedModifiers] = useState({\n finalSpeed: 6.0, // BASE_WALK_SPEED (6.0 m/s for responsive combat)\n baseSpeed: 6.0,\n finalAcceleration: 12.0, // BASE_ACCELERATION (12.0 m/s² for quick response)\n });\n\n const [walkRunSpeeds, setWalkRunSpeeds] = useState({\n walkSpeed: 6.0,\n runSpeed: 10.0,\n });\n\n\n const initialPositionMeters = useMemo<Position>(\n () => ({\n x: trainingAreaBounds.worldWidthMeters * 0.0, // Centered laterally\n y: 0, // Centered vertically\n }),\n [trainingAreaBounds],\n );\n\n const handlePositionChange = useCallback(\n (newPosition: Position) => {\n onPlayerUpdate({ position: newPosition });\n },\n [onPlayerUpdate],\n );\n\n const movementBounds = useMemo(\n () => ({\n worldWidthMeters: trainingAreaBounds.worldWidthMeters,\n worldDepthMeters: trainingAreaBounds.worldDepthMeters,\n }),\n [trainingAreaBounds.worldWidthMeters, trainingAreaBounds.worldDepthMeters],\n );\n\n \n const movementTimeRef = useRef(0);\n const lastDirectionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n \n const [accelerationBasedSpeed, setAccelerationBasedSpeed] = useState(\n walkRunSpeeds.walkSpeed\n );\n \n const isRunning = isRunningSpeed(accelerationBasedSpeed, walkRunSpeeds.runSpeed);\n\n const { playerPosition, isMoving, velocity } = usePlayerMovement({\n enabled: true, // Always allow movement in training screen\n bounds: movementBounds, // Use memoized bounds object\n onPositionChange: handlePositionChange, // Use memoized callback\n initialPositionMeters,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n legInjuryFactor: 0, // No injury in training mode\n isRunning, // Use computed acceleration-based running state\n maxSpeedOverride: accelerationBasedSpeed,\n accelerationOverride: speedModifiers.finalAcceleration,\n });\n\n const player3DPosition = useMemo<[number, number, number]>(() => {\n return [playerPosition.x, 0, playerPosition.y];\n }, [playerPosition]);\n\n const dummyPosition = useMemo<[number, number, number]>(\n () => [trainingAreaBounds.worldWidthMeters * 0.15, 0, 0],\n [trainingAreaBounds.worldWidthMeters],\n );\n\n const centerToCenterDistance = useMemo(\n () => calculateDistance3D(player3DPosition, dummyPosition),\n [player3DPosition, dummyPosition],\n );\n\n const distanceToDummy = useMemo(\n () => Math.max(0, centerToCenterDistance - DEFAULT_BODY_RADIUS_METERS),\n [centerToCenterDistance],\n );\n\n const lastFacingRotationRef = useRef<number>(0);\n\n const playerRotation = useMemo(() => {\n if (isMoving && velocity && (velocity.x !== 0 || velocity.y !== 0)) {\n return Math.atan2(velocity.x, velocity.y);\n } else {\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return Math.atan2(dx, dz);\n }\n }, [isMoving, velocity, player3DPosition, dummyPosition]);\n\n useEffect(() => {\n lastFacingRotationRef.current = playerRotation;\n }, [playerRotation]);\n\n\n const [currentLaterality, setCurrentLaterality] = useState<\"left\" | \"right\">(\"right\");\n \n const stepCounterRef = useRef(0);\n const lastPositionRef = useRef<Position>(playerPosition);\n \n useEffect(() => {\n if (!isMoving) {\n stepCounterRef.current = 0;\n lastPositionRef.current = playerPosition;\n return;\n }\n\n const dx = playerPosition.x - lastPositionRef.current.x;\n const dy = playerPosition.y - lastPositionRef.current.y;\n const distanceMoved = Math.sqrt(dx * dx + dy * dy);\n \n const stepThreshold = isRunning \n ? STEP_DISTANCE_THRESHOLDS.RUN \n : STEP_DISTANCE_THRESHOLDS.WALK;\n stepCounterRef.current += distanceMoved;\n \n const stepsCrossed = Math.floor(stepCounterRef.current / stepThreshold);\n if (stepsCrossed > 0) {\n if (stepsCrossed % 2 === 1) {\n setCurrentLaterality(prev => prev === \"right\" ? \"left\" : \"right\");\n }\n stepCounterRef.current -= stepsCrossed * stepThreshold;\n }\n \n lastPositionRef.current = playerPosition;\n }, [playerPosition, isMoving, isRunning]);\n\n\n const pendingAttackRef = useRef<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>(null);\n\n const handleDummyHitRef = useRef<\n (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean\n >(() => false);\n\n const playerAnimationRef = useRef<ReturnType<\n typeof usePlayerAnimation\n > | null>(null);\n\n const playerAnimationEvents = useMemo<AnimationEvents>(\n () => ({\n onFrame: (frame, state) => {\n if (state === \"attack\" && frame === 6 && pendingAttackRef.current) {\n const attackData = pendingAttackRef.current;\n handleDummyHitRef.current(attackData.vitalPoint, {\n animationType: attackData.animationType,\n techniqueId: attackData.techniqueId,\n });\n pendingAttackRef.current = null;\n }\n },\n onAnimationComplete: (state) => {\n if (state === \"stance_change\") {\n playStanceChangeSound();\n const currentStance =\n TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex];\n if (currentStance && playerAnimationRef.current) {\n playerAnimationRef.current.transitionToStanceGuard(currentStance);\n }\n }\n },\n }),\n [playStanceChangeSound, trainingState.currentStanceIndex],\n );\n\n const playerAnimation = usePlayerAnimation({\n events: playerAnimationEvents,\n });\n\n useEffect(() => {\n playerAnimationRef.current = playerAnimation;\n }, [playerAnimation]);\n\n\n const currentStance = useMemo(\n () => TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n [trainingState.currentStanceIndex],\n );\n\n const [previousStanceIndex, setPreviousStanceIndex] = useState<number>(0);\n\n const currentTechniqueAnimationTypeRef = useRef<AnimationType>(\n AnimationType.JAB,\n );\n\n\n const trainingPlayerState = useMemo<PlayerState>(() => {\n return {\n id: \"training-player\",\n name: { korean: \"훈련생\", english: \"Trainee\" },\n archetype: selectedArchetype,\n health: 100,\n maxHealth: 100,\n ki: 100,\n maxKi: 100,\n stamina: 100,\n maxStamina: 100,\n energy: 100,\n maxEnergy: 100,\n attackPower: 10,\n defense: 10,\n speed: 10,\n technique: 10,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n combatState: CombatState.IDLE,\n position: playerPosition,\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n statusEffects: [],\n activeEffects: [],\n vitalPoints: [],\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: trainingState.stats.hits,\n perfectStrikes: trainingState.perfectStrikes,\n vitalPointHits: 0,\n misses: trainingState.stats.misses,\n accuracy: trainingState.stats.accuracy,\n comboCount: trainingState.stats.combo,\n };\n }, [playerPosition, trainingState, selectedArchetype]);\n\n useEffect(() => {\n const updateSpeedModifiers = () => {\n const walkModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.WALKING,\n false, // isCrouching\n );\n\n const runModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.RUNNING,\n false, // isCrouching\n );\n\n setSpeedModifiers({\n finalSpeed: walkModifiers.finalSpeed,\n baseSpeed: walkModifiers.baseSpeed,\n finalAcceleration: walkModifiers.finalAcceleration,\n });\n\n setWalkRunSpeeds({\n walkSpeed: walkModifiers.finalSpeed,\n runSpeed: runModifiers.finalSpeed,\n });\n };\n\n updateSpeedModifiers();\n\n const intervalId = setInterval(updateSpeedModifiers, 200);\n\n return () => clearInterval(intervalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [trainingPlayerState]); // speedModifierSystem is memoized and never changes\n\n\n const isPlayerAttacking = useMemo(\n () => playerAnimation?.currentState === \"attack\",\n [playerAnimation],\n );\n\n const attackDirection = useMemo(() => {\n if (!isPlayerAttacking) {\n return new THREE.Vector3(0, 0, 1); // Default forward direction\n }\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return new THREE.Vector3(dx, 0, dz).normalize();\n }, [dummyPosition, player3DPosition, isPlayerAttacking]);\n\n const {\n currentPosition: player3DPositionWithAttackMovement,\n } = useAttackMovement({\n isAttacking: isPlayerAttacking,\n // eslint-disable-next-line react-hooks/refs -- ref value is set synchronously before isAttacking becomes true; hook only reads this at attack start\n animationType: currentTechniqueAnimationTypeRef.current,\n currentStance: trainingPlayerState.currentStance,\n basePosition: player3DPosition,\n attackDirection,\n animationDuration: 0.4,\n });\n\n const finalPlayer3DPosition = isPlayerAttacking\n ? player3DPositionWithAttackMovement\n : player3DPosition;\n\n\n const handleAttackRef = useRef<(() => void) | null>(null);\n\n const techniqueSelection = useTechniqueSelection({\n player: trainingPlayerState,\n enabled: trainingState.isTraining,\n onTechniqueExecute: useCallback(\n (technique: Technique) => {\n trainingActions.setFeedback(\n `${technique.name.korean} 사용! | Used ${technique.name.english}!`,\n );\n\n const animationName = resolveTechniqueAnimation(technique);\n setAttackAnimation(animationName);\n\n\n handleAttackRef.current?.();\n },\n [trainingActions],\n ),\n });\n\n const selectedTechniqueId = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) {\n return undefined;\n }\n return techniques[selectedIdx]?.id;\n }, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]);\n\n const {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n } = useTrainingActions({\n state: trainingState,\n actions: trainingActions,\n playerPosition,\n player3DPosition,\n dummyPosition,\n playerArchetype: selectedArchetype,\n playerStance: currentStance,\n currentTechniqueAnimationTypeRef, // Ref for technique's animation type\n audio,\n playBoneImpactSound, // Pass bone impact audio function from useCombatAudio\n playAttackSound, // Pass attack sound function from useCombatAudio\n selectedTechniqueId, // Pass selected technique ID for intensity-based attack sounds\n onPlayerUpdate: (updates) => {\n onPlayerUpdate(updates);\n },\n playerAnimation: {\n transitionTo: playerAnimation.transitionTo,\n transitionToAttack: playerAnimation.transitionToAttack,\n transitionToStanceGuard: playerAnimation.transitionToStanceGuard,\n currentState: playerAnimation.currentState,\n },\n pendingAttackRef, // Share the ref with animation events\n });\n\n useEffect(() => {\n handleAttackRef.current = handleAttack;\n }, [handleAttack]);\n\n useEffect(() => {\n handleDummyHitRef.current = handleDummyHit;\n }, [handleDummyHit]);\n\n const handleStanceChangeWithVisualFeedback = useCallback(\n (stanceIndex: number) => {\n setPreviousStanceIndex(trainingState.currentStanceIndex);\n handleStanceChange(stanceIndex);\n },\n [handleStanceChange, trainingState.currentStanceIndex],\n );\n\n\n const prevIsMovingRef = useRef<boolean>(isMoving);\n const prevIsRunningRef = useRef<boolean>(isRunning);\n const prevStanceRef = useRef<TrigramStance>(currentStance);\n \n useEffect(() => {\n const isMovingChanged = prevIsMovingRef.current !== isMoving;\n const isRunningChanged = prevIsRunningRef.current !== isRunning;\n const stanceChanged = prevStanceRef.current !== currentStance;\n \n if (isMovingChanged || isRunningChanged) {\n if (isMoving) {\n if (isRunning) {\n playerAnimation.transitionTo(AnimationState.RUN);\n } else {\n playerAnimation.transitionTo(AnimationState.WALK);\n }\n } else if (playerAnimation.currentState === AnimationState.WALK || \n playerAnimation.currentState === AnimationState.RUN) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevIsMovingRef.current = isMoving;\n prevIsRunningRef.current = isRunning;\n }\n \n if (stanceChanged && !isMoving) {\n if (playerAnimation.currentState === AnimationState.IDLE || \n playerAnimation.isInStanceGuard()) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevStanceRef.current = currentStance;\n }\n }, [isMoving, isRunning, currentStance, playerAnimation]);\n\n\n const cooldownsMap = useMemo(() => {\n const map = new Map<string, number>();\n techniqueSelection.activeCooldowns.forEach((cd) => {\n map.set(cd.techniqueId, cd.remaining);\n });\n return map;\n }, [techniqueSelection.activeCooldowns]);\n\n const { currentTechniqueReach, currentAnimationType } = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const currentTechnique =\n techniques[Math.min(selectedIdx, techniques.length - 1)];\n if (!currentTechnique) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const animConfig = getAnimationForTechniqueOrDefault(currentTechnique.id);\n const physicalAttributes =\n getArchetypePhysicalAttributes(selectedArchetype);\n const reach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animConfig.type,\n currentStance,\n );\n return {\n currentTechniqueReach: reach,\n currentAnimationType: animConfig.type,\n };\n }, [\n techniqueSelection.availableTechniques,\n techniqueSelection.selectedIndex,\n selectedArchetype,\n currentStance,\n ]);\n\n useEffect(() => {\n currentTechniqueAnimationTypeRef.current = currentAnimationType;\n }, [currentAnimationType]);\n\n\n const activeMobileKeyRef = useRef<string | null>(null);\n\n const mobileControlsEnabled = isMobile;\n\n const handleMobileMove = useCallback(\n (direction: Direction | null, eventType: DPadEventType) => {\n const directionMap: Record<Direction, string> = {\n up: \"w\",\n \"up-right\": \"w\",\n right: \"d\",\n \"down-right\": \"s\",\n down: \"s\",\n \"down-left\": \"s\",\n left: \"a\",\n \"up-left\": \"w\",\n };\n\n if (eventType === \"start\" && direction) {\n if (\n activeMobileKeyRef.current &&\n activeMobileKeyRef.current !== directionMap[direction]\n ) {\n const prevKey = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key: prevKey,\n code: `Key${prevKey.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n }\n\n const key = directionMap[direction];\n activeMobileKeyRef.current = key;\n window.dispatchEvent(\n new KeyboardEvent(\"keydown\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n } else if (eventType === \"end\") {\n if (activeMobileKeyRef.current) {\n const key = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n activeMobileKeyRef.current = null;\n }\n }\n },\n [],\n );\n\n const handleMobileAttack = useCallback(() => {\n handleAttack();\n }, [handleAttack]);\n\n const handleMobileBlock = useCallback(\n (eventType: ButtonEventType) => {\n if (eventType === \"start\") {\n audio.playSFX(\"block\");\n }\n },\n [audio],\n );\n\n const handleMobileGesture = useCallback(\n (gesture: GestureEvent) => {\n switch (gesture.type) {\n case \"swipe-right\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"d\" }));\n break;\n case \"swipe-left\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"a\" }));\n break;\n case \"swipe-up\":\n if (trainingState.isTraining) {\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \" \" }));\n }\n break;\n case \"swipe-down\":\n trainingActions.resetDummy();\n break;\n case \"two-finger-tap\":\n trainingActions.setTrainingMode(\n trainingState.trainingMode === \"vital_point\"\n ? \"basics\"\n : \"vital_point\",\n );\n audio.playSFX(\"menu_select\");\n break;\n }\n },\n [trainingState, trainingActions, audio],\n );\n\n const handleMobileStanceChange = useCallback(\n (stanceIndex: number) => {\n handleStanceChangeWithVisualFeedback(stanceIndex);\n },\n [handleStanceChangeWithVisualFeedback],\n );\n\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const key = event.key.toLowerCase();\n\n if (key === \"escape\") {\n onReturnToMenu();\n return;\n }\n\n if (key >= \"1\" && key <= \"8\") {\n const stanceIndex = parseInt(key) - 1;\n handleStanceChangeWithVisualFeedback(stanceIndex);\n event.preventDefault();\n return;\n }\n\n if (key === \" \") {\n handleAttack();\n event.preventDefault();\n return;\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, handleStanceChangeWithVisualFeedback, handleAttack]);\n\n\n const hasMountedRef = useRef(false);\n\n useEffect(() => {\n let audioStarted = false;\n\n const startMusic = async () => {\n try {\n await audio.fadeIn(\"cyberpunk_fusion\", 2000);\n audioStarted = true;\n } catch (err) {\n console.warn(\"Failed to start training music:\", err);\n trainingActions.setFeedback(\n \"오디오 초기화 실패 | Audio initialization failed\",\n );\n }\n };\n\n void startMusic();\n\n return () => {\n if (audioStarted) {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop training music:\", err));\n }\n };\n }, [audio, trainingActions]);\n\n useEffect(() => {\n if (!hasMountedRef.current) {\n hasMountedRef.current = true;\n handleStartTraining();\n }\n }, [handleStartTraining]);\n\n\n useEffect(() => {\n if (trainingState.showFeedback) {\n const timer = setTimeout(() => trainingActions.hideFeedback(), 1500);\n return () => clearTimeout(timer);\n }\n }, [trainingState.showFeedback, trainingState.feedback, trainingActions]);\n\n useEffect(() => {\n if (!trainingState.isTraining || !trainingState.sessionStartTime) return;\n\n const interval = setInterval(() => {\n trainingActions.updateSessionDuration(\n Math.floor((Date.now() - (trainingState.sessionStartTime ?? 0)) / 1000),\n );\n }, 1000);\n\n return () => clearInterval(interval);\n }, [\n trainingState.isTraining,\n trainingState.sessionStartTime,\n trainingActions,\n ]);\n\n const prevTrainingModeRef = useRef<typeof trainingState.trainingMode>(\n trainingState.trainingMode,\n );\n const isFirstModeEffectRef = useRef<boolean>(true);\n const isTrainingRef = useRef<boolean>(trainingState.isTraining);\n const modeChangeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n isTrainingRef.current = trainingState.isTraining;\n }, [trainingState.isTraining]);\n\n const handleStartTrainingRef = useRef(handleStartTraining);\n const handleStopTrainingRef = useRef(handleStopTraining);\n\n useEffect(() => {\n handleStartTrainingRef.current = handleStartTraining;\n handleStopTrainingRef.current = handleStopTraining;\n }, [handleStartTraining, handleStopTraining]);\n\n useEffect(() => {\n if (isFirstModeEffectRef.current) {\n isFirstModeEffectRef.current = false;\n prevTrainingModeRef.current = trainingState.trainingMode;\n return;\n }\n\n const previousMode = prevTrainingModeRef.current;\n const modeChanged = previousMode !== trainingState.trainingMode;\n\n if (!modeChanged) {\n return;\n }\n\n prevTrainingModeRef.current = trainingState.trainingMode;\n\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n\n if (isTrainingRef.current) {\n handleStopTrainingRef.current();\n }\n\n modeChangeTimerRef.current = setTimeout(() => {\n handleStartTrainingRef.current();\n modeChangeTimerRef.current = null;\n }, 100);\n\n return () => {\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n };\n }, [trainingState.trainingMode]); // Only depend on training mode to avoid unnecessary re-runs\n\n\n const handleEffectComplete = useCallback(\n (effectId: number) => {\n trainingActions.removeHitEffect(effectId);\n },\n [trainingActions],\n );\n\n\n const handleAnatomyLayerToggle = useCallback(\n (layer: AnatomyLayer) => {\n trainingActions.toggleAnatomyLayer(layer);\n audio.playSFX(\"menu_click\");\n },\n [trainingActions, audio],\n );\n\n const handleVitalPointClick = useCallback(\n (pointId: string) => {\n trainingActions.setSelectedVitalPoint(pointId);\n audio.playSFX(\"menu_select\");\n },\n [trainingActions, audio],\n );\n\n\n const cameraConfig = useMemo(() => {\n const base = createCameraConfig(isMobile);\n if (!isPortrait) return base;\n return {\n ...base,\n fov: Math.min(80, base.fov + 15),\n position: [base.position[0], base.position[1], base.position[2] + 4] as [\n number,\n number,\n number,\n ],\n };\n }, [isMobile, isPortrait]);\n\n\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(width, isMobile);\n }, [width, isMobile]);\n\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n position: \"relative\",\n overflow: \"hidden\", // Prevent content from extending beyond container\n }}\n data-testid=\"training-screen-3d\"\n >\n <Canvas\n style={{ width: `${width}px`, height: `${height}px` }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: false,\n powerPreference: \"high-performance\",\n failIfMajorPerformanceCaveat: false, // Don't fail in software renderer\n preserveDrawingBuffer: true, // Help with context stability\n }}\n dpr={performanceSettings.dpr}\n shadows={false} // Temporarily disable shadows\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n camera={cameraConfig}\n >\n {/* Lighting - base lighting, arena provides additional */}\n <ambientLight intensity={0.6} />\n <directionalLight position={[10, 10, 5]} intensity={1.2} />\n\n {/* Combat Arena 3D Environment - uses physics-based world dimensions */}\n <CombatArena3D\n lighting=\"cyberpunk\"\n scale={trainingAreaBounds.scale}\n worldWidthMeters={trainingAreaBounds.worldWidthMeters}\n worldDepthMeters={trainingAreaBounds.worldDepthMeters}\n />\n\n {/* Animation updater - 60fps updates */}\n <TrainingAnimationUpdater playerAnimation={playerAnimation} />\n\n {/* Acceleration updater - tracks movement time and updates speed */}\n <AccelerationUpdater\n isMoving={isMoving}\n velocity={velocity}\n movementTimeRef={movementTimeRef}\n lastDirectionRef={lastDirectionRef}\n onSpeedUpdate={setAccelerationBasedSpeed}\n walkSpeed={walkRunSpeeds.walkSpeed}\n runSpeed={walkRunSpeeds.runSpeed}\n />\n\n {/* Training dummy at fixed position */}\n <TrainingDummy3D\n position={dummyPosition}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n isTraining={trainingState.isTraining}\n health={trainingState.dummyHealth}\n onVitalPointHit={handleDummyHit}\n onDefeated={handleDummyDefeated}\n difficulty={difficulty}\n vitalPointCount={vitalPointCount}\n isMobile={isMobile}\n />\n\n {/* Anatomy overlay for educational visualization */}\n {trainingState.visibleAnatomyLayers.length > 0 && (\n <AnatomyOverlay3D\n position={dummyPosition}\n visibleLayers={trainingState.visibleAnatomyLayers}\n opacity={0.6}\n isMobile={isMobile}\n />\n )}\n\n {/* Vital Point Overlay - Show all 70 points on dummy */}\n {overlayVisible && (\n <VitalPointMarkers3D\n position={dummyPosition}\n visible={overlayVisible}\n severityFilter={severityFilters}\n regionFilter={regionFilter}\n searchQuery={searchQuery}\n showLabels={showLabels}\n scale={scale}\n animated={animated}\n selectedPoint={trainingState.selectedVitalPoint}\n onPointClick={handleVitalPointClick}\n />\n )}\n\n {/* Player model */}\n <Player3DWithTransitions\n {...convertPlayerStateToProps(\n trainingPlayerState,\n finalPlayer3DPosition,\n playerRotation,\n {\n isMobile,\n facing: \"right\",\n enableFacialExpressions: true,\n enableEyeTracking: true,\n opponentPosition: dummyPosition,\n },\n )}\n currentAnimation={animationStateToPlayerAnimation(\n playerAnimation.currentState,\n )}\n attackAnimation={attackAnimation}\n laterality={currentLaterality}\n enableTransitionEffects={!isMobile}\n enableStanceSymbol={true}\n enableStanceAudio={true}\n />\n\n {/* Foot Placement Markers for Footwork Drills */}\n {trainingState.trainingMode === \"footwork\" &&\n trainingState.footworkDrillActive && (\n <FootPlacementMarkers3D\n centerPosition={dummyPosition}\n pattern={\n trainingState.footworkDrillType === \"free_practice\"\n ? \"none\"\n : trainingState.footworkDrillType\n }\n currentStep={trainingState.footworkDrillStep}\n visible={true}\n scale={1.0}\n animated={true}\n />\n )}\n\n {/* Hit effects */}\n {trainingState.hitEffects.map((effect) => (\n <HitFeedbackEffect3D\n key={effect.id}\n position={effect.position}\n type={effect.type}\n damage={effect.damage}\n visible={effect.visible}\n onComplete={() => handleEffectComplete(effect.id)}\n isMobile={isMobile}\n />\n ))}\n\n {/* Stance Change Visual Indicator */}\n <StanceChangeIndicator\n currentStance={trainingState.currentStanceIndex}\n previousStance={previousStanceIndex}\n isMobile={isMobile}\n />\n\n {/* NOTE: Mobile controls moved OUTSIDE Canvas for reliable touch events */}\n {/* See MobileControlsPure component rendered after HUDs */}\n\n {/* Post-processing Effects - desktop high tier only for Android WebGL stability */}\n {performanceSettings.postProcessing && (\n <EffectComposer multisampling={4}>\n <Bloom\n luminanceThreshold={0.9}\n luminanceSmoothing={0.9}\n mipmapBlur\n intensity={0.8}\n radius={0.4}\n />\n <Noise opacity={0.03} />\n <Vignette eskil={false} offset={0.1} darkness={0.3} />\n </EffectComposer>\n )}\n </Canvas>\n\n {/* Html UI Overlays (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n overflow: \"clip\",\n }}\n data-testid=\"training-hud-overlay\"\n >\n {/* Left HUD - Anatomy Controls, Guard Indicator.\n Hidden on mobile because the side HUD occludes the compressed\n arena in both portrait and landscape. Anatomy layer toggles remain\n available on larger viewports where there is room for side panels. */}\n {!isMobile && (\n <TrainingLeftHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n visibleAnatomyLayers={trainingState.visibleAnatomyLayers}\n onAnatomyLayerToggle={handleAnatomyLayerToggle}\n currentStanceIndex={trainingState.currentStanceIndex}\n isInGuard={playerAnimation.isInStanceGuard()}\n />\n )}\n\n {/* Top HUD - Training Controls, Archetype Selector, Return Button. */}\n <TrainingTopHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n isTraining={trainingState.isTraining}\n onStartTraining={handleStartTraining}\n onStopTraining={handleStopTraining}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n overlayVisible={overlayVisible}\n onReturnToMenu={onReturnToMenu}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Right HUD - Mode Selector, Stats, Vital Point Selection.\n Hidden on mobile to keep the training dojang visible and usable.\n The core start/stop, archetype, vital-point toggle, technique bar,\n stance wheel, gestures, and touch controls remain available. */}\n {!isMobile && (\n <TrainingRightHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n trainingMode={trainingState.trainingMode}\n onModeChange={trainingActions.setTrainingMode}\n stats={{\n ...trainingState.stats,\n sessionDuration: trainingState.sessionDuration,\n bestCombo: trainingState.bestCombo,\n perfectStrikes: trainingState.perfectStrikes,\n }}\n distanceToDummy={distanceToDummy}\n effectiveReach={currentTechniqueReach}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n onVitalPointSelect={trainingActions.setSelectedVitalPoint}\n footworkDrillType={trainingState.footworkDrillType}\n footworkDrillStep={trainingState.footworkDrillStep}\n footworkDrillActive={trainingState.footworkDrillActive}\n onStartFootworkDrill={trainingActions.startFootworkDrill}\n onStopFootworkDrill={trainingActions.stopFootworkDrill}\n onAdvanceFootworkStep={trainingActions.advanceFootworkStep}\n />\n )}\n {/* Bottom HUD - Technique Bar, Feedback Messages, Mobile Controls */}\n <TrainingBottomHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n techniques={techniqueSelection.availableTechniques}\n player={trainingPlayerState}\n selectedIndex={techniqueSelection.selectedIndex}\n cooldowns={cooldownsMap}\n onTechniqueSelect={techniqueSelection.selectTechnique}\n showFeedback={trainingState.showFeedback}\n feedbackMessage={trainingState.feedback}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Vital Point Overlay Controls - Pure DOM overlay (outside Canvas) */}\n {overlayVisible && (\n <VitalPointOverlayControlsPure\n visible={overlayVisible}\n onVisibleChange={setOverlayVisible}\n severityFilters={severityFilters}\n onSeverityFiltersChange={setSeverityFilters}\n regionFilter={regionFilter}\n onRegionFilterChange={setRegionFilter}\n searchQuery={searchQuery}\n onSearchQueryChange={setSearchQuery}\n showLabels={showLabels}\n onShowLabelsChange={setShowLabels}\n animated={animated}\n onAnimatedChange={setAnimated}\n scale={scale}\n onScaleChange={setScale}\n screenPosition={{ top: \"180px\", left: \"20px\" }}\n isMobile={isMobile}\n />\n )}\n\n {/* Mobile Controls - Pure DOM overlay (outside Canvas for reliable touch) */}\n {isMobile && (\n <>\n <MobileControlsOverlay\n onMove={handleMobileMove}\n onAttack={handleMobileAttack}\n onBlock={handleMobileBlock}\n disabled={!mobileControlsEnabled}\n bottom={getMobileControlsBottom(height)}\n opacity={0.85}\n viewportWidth={width}\n viewportHeight={height}\n />\n\n <StanceWheelPure\n currentStance={trainingState.currentStanceIndex}\n onStanceChange={handleMobileStanceChange}\n expanded={trainingState.stanceWheelExpanded}\n onToggle={trainingActions.toggleStanceWheel}\n disabled={!mobileControlsEnabled}\n opacity={0.8}\n />\n\n <GestureRecognizerPure\n onGesture={handleMobileGesture}\n enabled={mobileControlsEnabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n )}\n </div>\n </div>\n );\n};\n\nexport default TrainingScreen3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingScreen3D.js","names":["_Fragment"],"sources":["../../../../src/components/screens/training/TrainingScreen3D.tsx"],"sourcesContent":["/**\n * TrainingScreen3D - Three.js-based training screen\n *\n * Refactored to use consolidated hooks matching CombatScreen architecture.\n * Provides 3D training dummy with vital point targeting and UI overlays.\n *\n * UI Rendering: All HUD elements are rendered in an absolute-positioned div\n * OUTSIDE the Canvas, matching CombatScreen's reliable rendering pattern.\n * This eliminates the need for Html overlays inside Three.js and ensures\n * HUDs appear immediately without waiting for Canvas initialization.\n *\n * Architecture (Consolidated in PR #1394 + Issue #1398):\n * - TrainingLeftHUD: Anatomy controls, guard indicator\n * - TrainingRightHUD: Training stats, mode selector, vital point selection\n * - TrainingTopHUD: Training controls, archetype selector, return button\n * - TrainingBottomHUD: Technique bar, feedback messages, mobile controls\n * - VitalPointOverlayControlsPure: Vital point overlay controls (pure DOM)\n *\n * All UI components render as pure DOM in the HUD overlay div (lines 1230+).\n * NO Html components from @react-three/drei are used inside the Canvas.\n * This ensures clean separation of 3D rendering and UI layers.\n *\n * @korean 훈련화면3D - 훈련 상태 훅을 사용한 리팩토링된 3D 훈련 화면\n */\n\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport { AccelerationUpdater } from \"../../../systems/movement/helpers/AccelerationUpdater\";\nimport {\n isRunningSpeed,\n STEP_DISTANCE_THRESHOLDS,\n} from \"../../../systems/movement/helpers/accelerationUtils\";\nimport {\n Bloom,\n EffectComposer,\n Noise,\n Vignette,\n} from \"@react-three/postprocessing\";\nimport * as THREE from \"three\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useCombatAudio } from \"../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../data/archetypePhysicalAttributes\";\nimport { usePlayerAnimation } from \"../../../hooks/usePlayerAnimation\";\nimport { useTechniqueSelection } from \"../../../hooks/useTechniqueSelection\";\nimport { GestureEvent } from \"../../../hooks/useTouchControls\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { PlayerState } from \"../../../systems\";\nimport {\n AnimationEvents,\n AnimationState,\n AnimationType,\n resolveTechniqueAnimation,\n} from \"../../../systems/animation\";\nimport { getAnimationForTechniqueOrDefault } from \"../../../systems/animation/core/TechniqueAnimationMapping\";\nimport { physicalReachCalculator } from \"../../../systems/physics\";\nimport {\n MovementType,\n SpeedModifierSystem,\n} from \"../../../systems/physics/SpeedModifierSystem\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../systems/trigram/types\";\nimport {\n CombatState,\n PlayerArchetype,\n Position,\n Technique,\n TrigramStance,\n} from \"../../../types\";\nimport { getPerformanceSettings } from \"../../../types/constants\";\nimport { getMobileControlsBottom } from \"../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { DEFAULT_BODY_RADIUS_METERS } from \"../../../types/physicsConstants\";\nimport { usePlayerMovement } from \"../../../utils/inputSystem\";\nimport { calculateDistance3D } from \"../../../utils/math\";\nimport { getHUDPositionScale } from \"../../../utils/responsiveLayoutHelpers\";\nimport { createCameraConfig } from \"../../../utils/sharedPhysicsConfig\";\nimport {\n animationStateToPlayerAnimation,\n convertPlayerStateToProps,\n} from \"../../../utils/player3DHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport {\n GestureRecognizerPure,\n StanceWheelPure,\n} from \"../../shared/mobile\";\nimport {\n MobileControlsOverlay,\n type ButtonEventType,\n type Direction,\n type DPadEventType,\n} from \"../../shared/mobile/MobileControlsPure\";\nimport {\n Player3DWithTransitions,\n VitalPointMarkers3D,\n type BodyRegionFilter,\n} from \"../../shared/three\";\nimport { StanceChangeIndicator } from \"../../shared/three/indicators/StanceChangeIndicator\";\nimport { CombatArena3D } from \"../../shared/three/scene/CombatArena3D\";\nimport { VitalPointOverlayControlsPure } from \"../../shared/ui/VitalPointOverlayControlsPure\";\nimport AnatomyOverlay3D, {\n type AnatomyLayer,\n} from \"./components/AnatomyOverlay3D\";\nimport FootPlacementMarkers3D from \"./components/FootPlacementMarkers3D\";\nimport HitFeedbackEffect3D from \"./components/HitFeedbackEffect3D\";\nimport type { DifficultyMode } from \"./components/TrainingDummy3D\";\nimport TrainingDummy3D from \"./components/TrainingDummy3D\";\nimport {\n TrainingBottomHUD,\n TrainingLeftHUD,\n TrainingRightHUD,\n TrainingTopHUD,\n} from \"./components/hud\";\nimport { useAttackMovement } from \"./hooks/useAttackMovement\";\nimport useTrainingActions from \"./hooks/useTrainingActions\";\nimport { useTrainingLayout } from \"./hooks/useTrainingLayout\";\nimport useTrainingState from \"./hooks/useTrainingState\";\n\n/**\n * AnimationUpdater - Component that updates player animation at 60fps\n *\n * @korean 훈련애니메이션업데이터 - 60fps로 플레이어 애니메이션을 업데이트하는 컴포넌트\n */\ninterface TrainingAnimationUpdaterProps {\n readonly playerAnimation: ReturnType<typeof usePlayerAnimation>;\n}\n\nconst TrainingAnimationUpdater: React.FC<TrainingAnimationUpdaterProps> = ({\n playerAnimation,\n}) => {\n useFrame((_state, delta) => {\n playerAnimation.update(delta);\n });\n\n return null;\n};\n\n/**\n * Props for the TrainingScreen3D component\n */\nexport interface TrainingScreen3DProps {\n /** Callback to update player state */\n readonly onPlayerUpdate: (updates: Partial<PlayerState>) => void;\n /** Callback when returning to menu */\n readonly onReturnToMenu: () => void;\n /** Canvas width in pixels. Defaults to 1200 */\n readonly width?: number;\n /** Canvas height in pixels. Defaults to 800 */\n readonly height?: number;\n /** Initial archetype from IntroScreen selection. Defaults to MUSA */\n readonly initialArchetype?: PlayerArchetype;\n}\n\n/**\n * TrainingScreen3D Component\n * Three.js-based training screen with 3D dummy and Html UI\n *\n * Uses consolidated hooks for state management matching CombatScreen architecture.\n */\nexport const TrainingScreen3D: React.FC<TrainingScreen3DProps> = ({\n onPlayerUpdate,\n onReturnToMenu,\n width = 1200,\n height = 800,\n initialArchetype = PlayerArchetype.MUSA,\n}) => {\n\n\n const { state: trainingState, actions: trainingActions } = useTrainingState();\n\n const audio = useAudio();\n \n const { playBoneImpactSound, playAttackSound, playStanceChangeSound } =\n useCombatAudio();\n\n const { trainingAreaBounds, isMobile, isPortrait, screenSize } =\n useTrainingLayout(width, height);\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n const positionScale = React.useMemo(\n () => getHUDPositionScale(screenSize, isMobile),\n [screenSize, isMobile],\n );\n\n const difficulty: DifficultyMode = \"normal\";\n const vitalPointCount = 70; // Show all 70 vital points\n\n const [selectedArchetype, setSelectedArchetype] =\n React.useState<PlayerArchetype>(initialArchetype);\n\n const [overlayVisible, setOverlayVisible] = React.useState(false);\n const [severityFilters, setSeverityFilters] = React.useState<\n import(\"../../../types/common\").VitalPointSeverity[]\n >([]);\n const [regionFilter, setRegionFilter] =\n React.useState<BodyRegionFilter>(\"all\");\n const [searchQuery, setSearchQuery] = React.useState(\"\");\n const [showLabels, setShowLabels] = React.useState(true);\n const [animated, setAnimated] = React.useState(true);\n const [scale, setScale] = React.useState(1.2);\n\n\n const [attackAnimation, setAttackAnimation] = React.useState<\n string | undefined\n >(undefined);\n\n React.useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"v\" || e.key === \"V\") {\n setOverlayVisible((prev) => !prev);\n audio.playSFX(\"menu_select\");\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [audio]);\n\n\n const contextLossCountRef = useRef(0);\n\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in TrainingScreen\");\n contextLossCountRef.current += 1;\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in TrainingScreen\");\n },\n autoRestore: true,\n });\n\n\n const speedModifierSystem = useMemo(() => new SpeedModifierSystem(), []);\n\n const [speedModifiers, setSpeedModifiers] = useState({\n finalSpeed: 6.0, // BASE_WALK_SPEED (6.0 m/s for responsive combat)\n baseSpeed: 6.0,\n finalAcceleration: 12.0, // BASE_ACCELERATION (12.0 m/s² for quick response)\n });\n\n const [walkRunSpeeds, setWalkRunSpeeds] = useState({\n walkSpeed: 6.0,\n runSpeed: 10.0,\n });\n\n\n const initialPositionMeters = useMemo<Position>(\n () => ({\n x: trainingAreaBounds.worldWidthMeters * 0.0, // Centered laterally\n y: 0, // Centered vertically\n }),\n [trainingAreaBounds],\n );\n\n const handlePositionChange = useCallback(\n (newPosition: Position) => {\n onPlayerUpdate({ position: newPosition });\n },\n [onPlayerUpdate],\n );\n\n const movementBounds = useMemo(\n () => ({\n worldWidthMeters: trainingAreaBounds.worldWidthMeters,\n worldDepthMeters: trainingAreaBounds.worldDepthMeters,\n }),\n [trainingAreaBounds.worldWidthMeters, trainingAreaBounds.worldDepthMeters],\n );\n\n \n const movementTimeRef = useRef(0);\n const lastDirectionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n \n const [accelerationBasedSpeed, setAccelerationBasedSpeed] = useState(\n walkRunSpeeds.walkSpeed\n );\n \n const isRunning = isRunningSpeed(accelerationBasedSpeed, walkRunSpeeds.runSpeed);\n\n const { playerPosition, isMoving, velocity } = usePlayerMovement({\n enabled: true, // Always allow movement in training screen\n bounds: movementBounds, // Use memoized bounds object\n onPositionChange: handlePositionChange, // Use memoized callback\n initialPositionMeters,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n legInjuryFactor: 0, // No injury in training mode\n isRunning, // Use computed acceleration-based running state\n maxSpeedOverride: accelerationBasedSpeed,\n accelerationOverride: speedModifiers.finalAcceleration,\n });\n\n const player3DPosition = useMemo<[number, number, number]>(() => {\n return [playerPosition.x, 0, playerPosition.y];\n }, [playerPosition]);\n\n const dummyPosition = useMemo<[number, number, number]>(\n () => [trainingAreaBounds.worldWidthMeters * 0.15, 0, 0],\n [trainingAreaBounds.worldWidthMeters],\n );\n\n const centerToCenterDistance = useMemo(\n () => calculateDistance3D(player3DPosition, dummyPosition),\n [player3DPosition, dummyPosition],\n );\n\n const distanceToDummy = useMemo(\n () => Math.max(0, centerToCenterDistance - DEFAULT_BODY_RADIUS_METERS),\n [centerToCenterDistance],\n );\n\n const lastFacingRotationRef = useRef<number>(0);\n\n const playerRotation = useMemo(() => {\n if (isMoving && velocity && (velocity.x !== 0 || velocity.y !== 0)) {\n return Math.atan2(velocity.x, velocity.y);\n } else {\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return Math.atan2(dx, dz);\n }\n }, [isMoving, velocity, player3DPosition, dummyPosition]);\n\n useEffect(() => {\n lastFacingRotationRef.current = playerRotation;\n }, [playerRotation]);\n\n\n const [currentLaterality, setCurrentLaterality] = useState<\"left\" | \"right\">(\"right\");\n \n const stepCounterRef = useRef(0);\n const lastPositionRef = useRef<Position>(playerPosition);\n \n useEffect(() => {\n if (!isMoving) {\n stepCounterRef.current = 0;\n lastPositionRef.current = playerPosition;\n return;\n }\n\n const dx = playerPosition.x - lastPositionRef.current.x;\n const dy = playerPosition.y - lastPositionRef.current.y;\n const distanceMoved = Math.sqrt(dx * dx + dy * dy);\n \n const stepThreshold = isRunning \n ? STEP_DISTANCE_THRESHOLDS.RUN \n : STEP_DISTANCE_THRESHOLDS.WALK;\n stepCounterRef.current += distanceMoved;\n \n const stepsCrossed = Math.floor(stepCounterRef.current / stepThreshold);\n if (stepsCrossed > 0) {\n if (stepsCrossed % 2 === 1) {\n setCurrentLaterality(prev => prev === \"right\" ? \"left\" : \"right\");\n }\n stepCounterRef.current -= stepsCrossed * stepThreshold;\n }\n \n lastPositionRef.current = playerPosition;\n }, [playerPosition, isMoving, isRunning]);\n\n\n const pendingAttackRef = useRef<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>(null);\n\n const handleDummyHitRef = useRef<\n (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean\n >(() => false);\n\n const playerAnimationRef = useRef<ReturnType<\n typeof usePlayerAnimation\n > | null>(null);\n\n const playerAnimationEvents = useMemo<AnimationEvents>(\n () => ({\n onFrame: (frame, state) => {\n if (state === \"attack\" && frame === 6 && pendingAttackRef.current) {\n const attackData = pendingAttackRef.current;\n handleDummyHitRef.current(attackData.vitalPoint, {\n animationType: attackData.animationType,\n techniqueId: attackData.techniqueId,\n });\n pendingAttackRef.current = null;\n }\n },\n onAnimationComplete: (state) => {\n if (state === \"stance_change\") {\n playStanceChangeSound();\n const currentStance =\n TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex];\n if (currentStance && playerAnimationRef.current) {\n playerAnimationRef.current.transitionToStanceGuard(currentStance);\n }\n }\n },\n }),\n [playStanceChangeSound, trainingState.currentStanceIndex],\n );\n\n const playerAnimation = usePlayerAnimation({\n events: playerAnimationEvents,\n });\n\n useEffect(() => {\n playerAnimationRef.current = playerAnimation;\n }, [playerAnimation]);\n\n\n const currentStance = useMemo(\n () => TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n [trainingState.currentStanceIndex],\n );\n\n const [previousStanceIndex, setPreviousStanceIndex] = useState<number>(0);\n\n const currentTechniqueAnimationTypeRef = useRef<AnimationType>(\n AnimationType.JAB,\n );\n\n\n const trainingPlayerState = useMemo<PlayerState>(() => {\n return {\n id: \"training-player\",\n name: { korean: \"훈련생\", english: \"Trainee\" },\n archetype: selectedArchetype,\n health: 100,\n maxHealth: 100,\n ki: 100,\n maxKi: 100,\n stamina: 100,\n maxStamina: 100,\n energy: 100,\n maxEnergy: 100,\n attackPower: 10,\n defense: 10,\n speed: 10,\n technique: 10,\n pain: 0,\n consciousness: 100,\n balance: 100,\n momentum: 0,\n currentStance: TRIGRAM_STANCES_ORDER[trainingState.currentStanceIndex],\n combatState: CombatState.IDLE,\n position: playerPosition,\n isBlocking: false,\n isStunned: false,\n isCountering: false,\n lastActionTime: 0,\n recoveryTime: 0,\n lastStanceChangeTime: 0,\n statusEffects: [],\n activeEffects: [],\n vitalPoints: [],\n totalDamageReceived: 0,\n totalDamageDealt: 0,\n hitsTaken: 0,\n hitsLanded: trainingState.stats.hits,\n perfectStrikes: trainingState.perfectStrikes,\n vitalPointHits: 0,\n misses: trainingState.stats.misses,\n accuracy: trainingState.stats.accuracy,\n comboCount: trainingState.stats.combo,\n };\n }, [playerPosition, trainingState, selectedArchetype]);\n\n useEffect(() => {\n const updateSpeedModifiers = () => {\n const walkModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.WALKING,\n false, // isCrouching\n );\n\n const runModifiers = speedModifierSystem.calculateSpeedModifiers(\n trainingPlayerState,\n MovementType.RUNNING,\n false, // isCrouching\n );\n\n setSpeedModifiers({\n finalSpeed: walkModifiers.finalSpeed,\n baseSpeed: walkModifiers.baseSpeed,\n finalAcceleration: walkModifiers.finalAcceleration,\n });\n\n setWalkRunSpeeds({\n walkSpeed: walkModifiers.finalSpeed,\n runSpeed: runModifiers.finalSpeed,\n });\n };\n\n updateSpeedModifiers();\n\n const intervalId = setInterval(updateSpeedModifiers, 200);\n\n return () => clearInterval(intervalId);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [trainingPlayerState]); // speedModifierSystem is memoized and never changes\n\n\n const isPlayerAttacking = useMemo(\n () => playerAnimation?.currentState === \"attack\",\n [playerAnimation],\n );\n\n const attackDirection = useMemo(() => {\n if (!isPlayerAttacking) {\n return new THREE.Vector3(0, 0, 1); // Default forward direction\n }\n const dx = dummyPosition[0] - player3DPosition[0];\n const dz = dummyPosition[2] - player3DPosition[2];\n return new THREE.Vector3(dx, 0, dz).normalize();\n }, [dummyPosition, player3DPosition, isPlayerAttacking]);\n\n const {\n currentPosition: player3DPositionWithAttackMovement,\n } = useAttackMovement({\n isAttacking: isPlayerAttacking,\n // eslint-disable-next-line react-hooks/refs -- ref value is set synchronously before isAttacking becomes true; hook only reads this at attack start\n animationType: currentTechniqueAnimationTypeRef.current,\n currentStance: trainingPlayerState.currentStance,\n basePosition: player3DPosition,\n attackDirection,\n animationDuration: 0.4,\n });\n\n const finalPlayer3DPosition = isPlayerAttacking\n ? player3DPositionWithAttackMovement\n : player3DPosition;\n\n\n const handleAttackRef = useRef<(() => void) | null>(null);\n\n const techniqueSelection = useTechniqueSelection({\n player: trainingPlayerState,\n enabled: trainingState.isTraining,\n onTechniqueExecute: useCallback(\n (technique: Technique) => {\n trainingActions.setFeedback(\n `${technique.name.korean} 사용! | Used ${technique.name.english}!`,\n );\n\n const animationName = resolveTechniqueAnimation(technique);\n setAttackAnimation(animationName);\n\n\n handleAttackRef.current?.();\n },\n [trainingActions],\n ),\n });\n\n const selectedTechniqueId = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) {\n return undefined;\n }\n return techniques[selectedIdx]?.id;\n }, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]);\n\n const {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n } = useTrainingActions({\n state: trainingState,\n actions: trainingActions,\n playerPosition,\n player3DPosition,\n dummyPosition,\n playerArchetype: selectedArchetype,\n playerStance: currentStance,\n currentTechniqueAnimationTypeRef, // Ref for technique's animation type\n audio,\n playBoneImpactSound, // Pass bone impact audio function from useCombatAudio\n playAttackSound, // Pass attack sound function from useCombatAudio\n selectedTechniqueId, // Pass selected technique ID for intensity-based attack sounds\n onPlayerUpdate: (updates) => {\n onPlayerUpdate(updates);\n },\n playerAnimation: {\n transitionTo: playerAnimation.transitionTo,\n transitionToAttack: playerAnimation.transitionToAttack,\n transitionToStanceGuard: playerAnimation.transitionToStanceGuard,\n currentState: playerAnimation.currentState,\n },\n pendingAttackRef, // Share the ref with animation events\n });\n\n useEffect(() => {\n handleAttackRef.current = handleAttack;\n }, [handleAttack]);\n\n useEffect(() => {\n handleDummyHitRef.current = handleDummyHit;\n }, [handleDummyHit]);\n\n const handleStanceChangeWithVisualFeedback = useCallback(\n (stanceIndex: number) => {\n setPreviousStanceIndex(trainingState.currentStanceIndex);\n handleStanceChange(stanceIndex);\n },\n [handleStanceChange, trainingState.currentStanceIndex],\n );\n\n\n const prevIsMovingRef = useRef<boolean>(isMoving);\n const prevIsRunningRef = useRef<boolean>(isRunning);\n const prevStanceRef = useRef<TrigramStance>(currentStance);\n \n useEffect(() => {\n const isMovingChanged = prevIsMovingRef.current !== isMoving;\n const isRunningChanged = prevIsRunningRef.current !== isRunning;\n const stanceChanged = prevStanceRef.current !== currentStance;\n \n if (isMovingChanged || isRunningChanged) {\n if (isMoving) {\n if (isRunning) {\n playerAnimation.transitionTo(AnimationState.RUN);\n } else {\n playerAnimation.transitionTo(AnimationState.WALK);\n }\n } else if (playerAnimation.currentState === AnimationState.WALK || \n playerAnimation.currentState === AnimationState.RUN) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevIsMovingRef.current = isMoving;\n prevIsRunningRef.current = isRunning;\n }\n \n if (stanceChanged && !isMoving) {\n if (playerAnimation.currentState === AnimationState.IDLE || \n playerAnimation.isInStanceGuard()) {\n playerAnimation.transitionToStanceGuard(currentStance);\n }\n prevStanceRef.current = currentStance;\n }\n }, [isMoving, isRunning, currentStance, playerAnimation]);\n\n\n const cooldownsMap = useMemo(() => {\n const map = new Map<string, number>();\n techniqueSelection.activeCooldowns.forEach((cd) => {\n map.set(cd.techniqueId, cd.remaining);\n });\n return map;\n }, [techniqueSelection.activeCooldowns]);\n\n const { currentTechniqueReach, currentAnimationType } = useMemo(() => {\n const techniques = techniqueSelection.availableTechniques;\n const selectedIdx = techniqueSelection.selectedIndex;\n if (techniques.length === 0) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const currentTechnique =\n techniques[Math.min(selectedIdx, techniques.length - 1)];\n if (!currentTechnique) {\n return {\n currentTechniqueReach: 0.7,\n currentAnimationType: AnimationType.JAB,\n };\n }\n const animConfig = getAnimationForTechniqueOrDefault(currentTechnique.id);\n const physicalAttributes =\n getArchetypePhysicalAttributes(selectedArchetype);\n const reach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animConfig.type,\n currentStance,\n );\n return {\n currentTechniqueReach: reach,\n currentAnimationType: animConfig.type,\n };\n }, [\n techniqueSelection.availableTechniques,\n techniqueSelection.selectedIndex,\n selectedArchetype,\n currentStance,\n ]);\n\n useEffect(() => {\n currentTechniqueAnimationTypeRef.current = currentAnimationType;\n }, [currentAnimationType]);\n\n\n const activeMobileKeyRef = useRef<string | null>(null);\n\n const mobileControlsEnabled = isMobile;\n\n const handleMobileMove = useCallback(\n (direction: Direction | null, eventType: DPadEventType) => {\n const directionMap: Record<Direction, string> = {\n up: \"w\",\n \"up-right\": \"w\",\n right: \"d\",\n \"down-right\": \"s\",\n down: \"s\",\n \"down-left\": \"s\",\n left: \"a\",\n \"up-left\": \"w\",\n };\n\n if (eventType === \"start\" && direction) {\n if (\n activeMobileKeyRef.current &&\n activeMobileKeyRef.current !== directionMap[direction]\n ) {\n const prevKey = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key: prevKey,\n code: `Key${prevKey.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n }\n\n const key = directionMap[direction];\n activeMobileKeyRef.current = key;\n window.dispatchEvent(\n new KeyboardEvent(\"keydown\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n } else if (eventType === \"end\") {\n if (activeMobileKeyRef.current) {\n const key = activeMobileKeyRef.current;\n window.dispatchEvent(\n new KeyboardEvent(\"keyup\", {\n key,\n code: `Key${key.toUpperCase()}`,\n bubbles: true,\n cancelable: true,\n }),\n );\n activeMobileKeyRef.current = null;\n }\n }\n },\n [],\n );\n\n const handleMobileAttack = useCallback(() => {\n handleAttack();\n }, [handleAttack]);\n\n const handleMobileBlock = useCallback(\n (eventType: ButtonEventType) => {\n if (eventType === \"start\") {\n audio.playSFX(\"block\");\n }\n },\n [audio],\n );\n\n const handleMobileGesture = useCallback(\n (gesture: GestureEvent) => {\n switch (gesture.type) {\n case \"swipe-right\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"d\" }));\n break;\n case \"swipe-left\":\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"a\" }));\n break;\n case \"swipe-up\":\n if (trainingState.isTraining) {\n window.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \" \" }));\n }\n break;\n case \"swipe-down\":\n trainingActions.resetDummy();\n break;\n case \"two-finger-tap\":\n trainingActions.setTrainingMode(\n trainingState.trainingMode === \"vital_point\"\n ? \"basics\"\n : \"vital_point\",\n );\n audio.playSFX(\"menu_select\");\n break;\n }\n },\n [trainingState, trainingActions, audio],\n );\n\n const handleMobileStanceChange = useCallback(\n (stanceIndex: number) => {\n handleStanceChangeWithVisualFeedback(stanceIndex);\n },\n [handleStanceChangeWithVisualFeedback],\n );\n\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const key = event.key.toLowerCase();\n\n if (key === \"escape\") {\n onReturnToMenu();\n return;\n }\n\n if (key >= \"1\" && key <= \"8\") {\n const stanceIndex = parseInt(key) - 1;\n handleStanceChangeWithVisualFeedback(stanceIndex);\n event.preventDefault();\n return;\n }\n\n if (key === \" \") {\n handleAttack();\n event.preventDefault();\n return;\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, handleStanceChangeWithVisualFeedback, handleAttack]);\n\n\n const hasMountedRef = useRef(false);\n\n useEffect(() => {\n let audioStarted = false;\n\n const startMusic = async () => {\n try {\n await audio.fadeIn(\"cyberpunk_fusion\", 2000);\n audioStarted = true;\n } catch (err) {\n console.warn(\"Failed to start training music:\", err);\n trainingActions.setFeedback(\n \"오디오 초기화 실패 | Audio initialization failed\",\n );\n }\n };\n\n void startMusic();\n\n return () => {\n if (audioStarted) {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop training music:\", err));\n }\n };\n }, [audio, trainingActions]);\n\n useEffect(() => {\n if (!hasMountedRef.current) {\n hasMountedRef.current = true;\n handleStartTraining();\n }\n }, [handleStartTraining]);\n\n\n useEffect(() => {\n if (trainingState.showFeedback) {\n const timer = setTimeout(() => trainingActions.hideFeedback(), 1500);\n return () => clearTimeout(timer);\n }\n }, [trainingState.showFeedback, trainingState.feedback, trainingActions]);\n\n useEffect(() => {\n if (!trainingState.isTraining || !trainingState.sessionStartTime) return;\n\n const interval = setInterval(() => {\n trainingActions.updateSessionDuration(\n Math.floor((Date.now() - (trainingState.sessionStartTime ?? 0)) / 1000),\n );\n }, 1000);\n\n return () => clearInterval(interval);\n }, [\n trainingState.isTraining,\n trainingState.sessionStartTime,\n trainingActions,\n ]);\n\n const prevTrainingModeRef = useRef<typeof trainingState.trainingMode>(\n trainingState.trainingMode,\n );\n const isFirstModeEffectRef = useRef<boolean>(true);\n const isTrainingRef = useRef<boolean>(trainingState.isTraining);\n const modeChangeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n isTrainingRef.current = trainingState.isTraining;\n }, [trainingState.isTraining]);\n\n const handleStartTrainingRef = useRef(handleStartTraining);\n const handleStopTrainingRef = useRef(handleStopTraining);\n\n useEffect(() => {\n handleStartTrainingRef.current = handleStartTraining;\n handleStopTrainingRef.current = handleStopTraining;\n }, [handleStartTraining, handleStopTraining]);\n\n useEffect(() => {\n if (isFirstModeEffectRef.current) {\n isFirstModeEffectRef.current = false;\n prevTrainingModeRef.current = trainingState.trainingMode;\n return;\n }\n\n const previousMode = prevTrainingModeRef.current;\n const modeChanged = previousMode !== trainingState.trainingMode;\n\n if (!modeChanged) {\n return;\n }\n\n prevTrainingModeRef.current = trainingState.trainingMode;\n\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n\n if (isTrainingRef.current) {\n handleStopTrainingRef.current();\n }\n\n modeChangeTimerRef.current = setTimeout(() => {\n handleStartTrainingRef.current();\n modeChangeTimerRef.current = null;\n }, 100);\n\n return () => {\n if (modeChangeTimerRef.current) {\n clearTimeout(modeChangeTimerRef.current);\n modeChangeTimerRef.current = null;\n }\n };\n }, [trainingState.trainingMode]); // Only depend on training mode to avoid unnecessary re-runs\n\n\n const handleEffectComplete = useCallback(\n (effectId: number) => {\n trainingActions.removeHitEffect(effectId);\n },\n [trainingActions],\n );\n\n\n const handleAnatomyLayerToggle = useCallback(\n (layer: AnatomyLayer) => {\n trainingActions.toggleAnatomyLayer(layer);\n audio.playSFX(\"menu_click\");\n },\n [trainingActions, audio],\n );\n\n const handleVitalPointClick = useCallback(\n (pointId: string) => {\n trainingActions.setSelectedVitalPoint(pointId);\n audio.playSFX(\"menu_select\");\n },\n [trainingActions, audio],\n );\n\n\n const cameraConfig = useMemo(() => {\n const base = createCameraConfig(isMobile);\n if (!isPortrait) return base;\n return {\n ...base,\n fov: Math.min(80, base.fov + 15),\n position: [base.position[0], base.position[1], base.position[2] + 4] as [\n number,\n number,\n number,\n ],\n };\n }, [isMobile, isPortrait]);\n\n\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(width, isMobile);\n }, [width, isMobile]);\n\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n position: \"relative\",\n overflow: \"hidden\", // Prevent content from extending beyond container\n }}\n data-testid=\"training-screen-3d\"\n >\n <Canvas\n style={{ width: `${width}px`, height: `${height}px` }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: false,\n powerPreference: \"high-performance\",\n failIfMajorPerformanceCaveat: false, // Don't fail in software renderer\n preserveDrawingBuffer: true, // Help with context stability\n }}\n dpr={performanceSettings.dpr}\n shadows={false} // Temporarily disable shadows\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n camera={cameraConfig}\n >\n {/* Lighting - base lighting, arena provides additional */}\n <ambientLight intensity={0.6} />\n <directionalLight position={[10, 10, 5]} intensity={1.2} />\n\n {/* Combat Arena 3D Environment - uses physics-based world dimensions */}\n <CombatArena3D\n lighting=\"cyberpunk\"\n scale={trainingAreaBounds.scale}\n worldWidthMeters={trainingAreaBounds.worldWidthMeters}\n worldDepthMeters={trainingAreaBounds.worldDepthMeters}\n />\n\n {/* Animation updater - 60fps updates */}\n <TrainingAnimationUpdater playerAnimation={playerAnimation} />\n\n {/* Acceleration updater - tracks movement time and updates speed */}\n <AccelerationUpdater\n isMoving={isMoving}\n velocity={velocity}\n movementTimeRef={movementTimeRef}\n lastDirectionRef={lastDirectionRef}\n onSpeedUpdate={setAccelerationBasedSpeed}\n walkSpeed={walkRunSpeeds.walkSpeed}\n runSpeed={walkRunSpeeds.runSpeed}\n />\n\n {/* Training dummy at fixed position */}\n <TrainingDummy3D\n position={dummyPosition}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n isTraining={trainingState.isTraining}\n health={trainingState.dummyHealth}\n onVitalPointHit={handleDummyHit}\n onDefeated={handleDummyDefeated}\n difficulty={difficulty}\n vitalPointCount={vitalPointCount}\n isMobile={isMobile}\n />\n\n {/* Anatomy overlay for educational visualization */}\n {trainingState.visibleAnatomyLayers.length > 0 && (\n <AnatomyOverlay3D\n position={dummyPosition}\n visibleLayers={trainingState.visibleAnatomyLayers}\n opacity={0.6}\n isMobile={isMobile}\n />\n )}\n\n {/* Vital Point Overlay - Show all 70 points on dummy */}\n {overlayVisible && (\n <VitalPointMarkers3D\n position={dummyPosition}\n visible={overlayVisible}\n severityFilter={severityFilters}\n regionFilter={regionFilter}\n searchQuery={searchQuery}\n showLabels={showLabels}\n scale={scale}\n animated={animated}\n selectedPoint={trainingState.selectedVitalPoint}\n onPointClick={handleVitalPointClick}\n />\n )}\n\n {/* Player model */}\n <Player3DWithTransitions\n {...convertPlayerStateToProps(\n trainingPlayerState,\n finalPlayer3DPosition,\n playerRotation,\n {\n isMobile,\n facing: \"right\",\n enableFacialExpressions: true,\n enableEyeTracking: true,\n opponentPosition: dummyPosition,\n },\n )}\n currentAnimation={animationStateToPlayerAnimation(\n playerAnimation.currentState,\n )}\n attackAnimation={attackAnimation}\n laterality={currentLaterality}\n enableTransitionEffects={!isMobile}\n enableStanceSymbol={true}\n enableStanceAudio={true}\n />\n\n {/* Foot Placement Markers for Footwork Drills */}\n {trainingState.trainingMode === \"footwork\" &&\n trainingState.footworkDrillActive && (\n <FootPlacementMarkers3D\n centerPosition={dummyPosition}\n pattern={\n trainingState.footworkDrillType === \"free_practice\"\n ? \"none\"\n : trainingState.footworkDrillType\n }\n currentStep={trainingState.footworkDrillStep}\n visible={true}\n scale={1.0}\n animated={true}\n />\n )}\n\n {/* Hit effects */}\n {trainingState.hitEffects.map((effect) => (\n <HitFeedbackEffect3D\n key={effect.id}\n position={effect.position}\n type={effect.type}\n damage={effect.damage}\n visible={effect.visible}\n onComplete={() => handleEffectComplete(effect.id)}\n isMobile={isMobile}\n />\n ))}\n\n {/* Stance Change Visual Indicator */}\n <StanceChangeIndicator\n currentStance={trainingState.currentStanceIndex}\n previousStance={previousStanceIndex}\n isMobile={isMobile}\n />\n\n {/* NOTE: Mobile controls moved OUTSIDE Canvas for reliable touch events */}\n {/* See MobileControlsPure component rendered after HUDs */}\n\n {/* Post-processing Effects - desktop high tier only for Android WebGL stability */}\n {performanceSettings.postProcessing && (\n <EffectComposer multisampling={4}>\n <Bloom\n luminanceThreshold={0.9}\n luminanceSmoothing={0.9}\n mipmapBlur\n intensity={0.8}\n radius={0.4}\n />\n <Noise opacity={0.03} />\n <Vignette eskil={false} offset={0.1} darkness={0.3} />\n </EffectComposer>\n )}\n </Canvas>\n\n {/* Html UI Overlays (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n overflow: \"clip\",\n }}\n data-testid=\"training-hud-overlay\"\n >\n {/* Left HUD - Anatomy Controls, Guard Indicator.\n Hidden on mobile because the side HUD occludes the compressed\n arena in both portrait and landscape. Anatomy layer toggles remain\n available on larger viewports where there is room for side panels. */}\n {!isMobile && (\n <TrainingLeftHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n visibleAnatomyLayers={trainingState.visibleAnatomyLayers}\n onAnatomyLayerToggle={handleAnatomyLayerToggle}\n currentStanceIndex={trainingState.currentStanceIndex}\n isInGuard={playerAnimation.isInStanceGuard()}\n />\n )}\n\n {/* Top HUD - Training Controls, Archetype Selector, Return Button. */}\n <TrainingTopHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n isTraining={trainingState.isTraining}\n onStartTraining={handleStartTraining}\n onStopTraining={handleStopTraining}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n overlayVisible={overlayVisible}\n onReturnToMenu={onReturnToMenu}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Right HUD - Mode Selector, Stats, Vital Point Selection.\n Hidden on mobile to keep the training dojang visible and usable.\n The core start/stop, archetype, vital-point toggle, technique bar,\n stance wheel, gestures, and touch controls remain available. */}\n {!isMobile && (\n <TrainingRightHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n trainingMode={trainingState.trainingMode}\n onModeChange={trainingActions.setTrainingMode}\n stats={{\n ...trainingState.stats,\n sessionDuration: trainingState.sessionDuration,\n bestCombo: trainingState.bestCombo,\n perfectStrikes: trainingState.perfectStrikes,\n }}\n distanceToDummy={distanceToDummy}\n effectiveReach={currentTechniqueReach}\n selectedVitalPoint={trainingState.selectedVitalPoint}\n onVitalPointSelect={trainingActions.setSelectedVitalPoint}\n footworkDrillType={trainingState.footworkDrillType}\n footworkDrillStep={trainingState.footworkDrillStep}\n footworkDrillActive={trainingState.footworkDrillActive}\n onStartFootworkDrill={trainingActions.startFootworkDrill}\n onStopFootworkDrill={trainingActions.stopFootworkDrill}\n onAdvanceFootworkStep={trainingActions.advanceFootworkStep}\n />\n )}\n {/* Bottom HUD - Technique Bar, Feedback Messages, Mobile Controls */}\n <TrainingBottomHUD\n width={width}\n height={height}\n isMobile={isMobile}\n positionScale={positionScale}\n techniques={techniqueSelection.availableTechniques}\n player={trainingPlayerState}\n selectedIndex={techniqueSelection.selectedIndex}\n cooldowns={cooldownsMap}\n onTechniqueSelect={techniqueSelection.selectTechnique}\n showFeedback={trainingState.showFeedback}\n feedbackMessage={trainingState.feedback}\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={setSelectedArchetype}\n onPlaySFX={(sound) => audio.playSFX(sound)}\n />\n\n {/* Vital Point Overlay Controls - Pure DOM overlay (outside Canvas) */}\n {overlayVisible && (\n <VitalPointOverlayControlsPure\n visible={overlayVisible}\n onVisibleChange={setOverlayVisible}\n severityFilters={severityFilters}\n onSeverityFiltersChange={setSeverityFilters}\n regionFilter={regionFilter}\n onRegionFilterChange={setRegionFilter}\n searchQuery={searchQuery}\n onSearchQueryChange={setSearchQuery}\n showLabels={showLabels}\n onShowLabelsChange={setShowLabels}\n animated={animated}\n onAnimatedChange={setAnimated}\n scale={scale}\n onScaleChange={setScale}\n screenPosition={{ top: \"180px\", left: \"20px\" }}\n isMobile={isMobile}\n />\n )}\n\n {/* Mobile Controls - Pure DOM overlay (outside Canvas for reliable touch) */}\n {isMobile && (\n <>\n <MobileControlsOverlay\n onMove={handleMobileMove}\n onAttack={handleMobileAttack}\n onBlock={handleMobileBlock}\n disabled={!mobileControlsEnabled}\n bottom={getMobileControlsBottom(height)}\n opacity={0.85}\n viewportWidth={width}\n viewportHeight={height}\n />\n\n <StanceWheelPure\n currentStance={trainingState.currentStanceIndex}\n onStanceChange={handleMobileStanceChange}\n expanded={trainingState.stanceWheelExpanded}\n onToggle={trainingActions.toggleStanceWheel}\n disabled={!mobileControlsEnabled}\n opacity={0.8}\n />\n\n <GestureRecognizerPure\n onGesture={handleMobileGesture}\n enabled={mobileControlsEnabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n )}\n </div>\n </div>\n );\n};\n\nexport default TrainingScreen3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmIA,IAAM,4BAAqE,EACzE,sBACI;CACJ,UAAU,QAAQ,UAAU;EAC1B,gBAAgB,OAAO,KAAK;CAC9B,CAAC;CAED,OAAO;AACT;;;;;;;AAwBA,IAAa,oBAAqD,EAChE,gBACA,gBACA,QAAQ,MACR,SAAS,KACT,mBAAmB,gBAAgB,WAC/B;CAGJ,MAAM,EAAE,OAAO,eAAe,SAAS,oBAAoB,iBAAiB;CAE5E,MAAM,QAAQ,SAAS;CAEvB,MAAM,EAAE,qBAAqB,iBAAiB,0BAC5C,eAAe;CAEjB,MAAM,EAAE,oBAAoB,UAAU,YAAY,eAChD,kBAAkB,OAAO,MAAM;CAEjC,MAAM,QAAQ,eAAe;EAC3B,SAAS;EACT,MAAM;EACN;CACF,CAAC;CAED,MAAM,gBAAgB,MAAM,cACpB,oBAAoB,YAAY,QAAQ,GAC9C,CAAC,YAAY,QAAQ,CACvB;CAEA,MAAM,aAA6B;CACnC,MAAM,kBAAkB;CAExB,MAAM,CAAC,mBAAmB,wBACxB,MAAM,SAA0B,gBAAgB;CAElD,MAAM,CAAC,gBAAgB,qBAAqB,MAAM,SAAS,KAAK;CAChE,MAAM,CAAC,iBAAiB,sBAAsB,MAAM,SAElD,CAAC,CAAC;CACJ,MAAM,CAAC,cAAc,mBACnB,MAAM,SAA2B,KAAK;CACxC,MAAM,CAAC,aAAa,kBAAkB,MAAM,SAAS,EAAE;CACvD,MAAM,CAAC,YAAY,iBAAiB,MAAM,SAAS,IAAI;CACvD,MAAM,CAAC,UAAU,eAAe,MAAM,SAAS,IAAI;CACnD,MAAM,CAAC,OAAO,YAAY,MAAM,SAAS,GAAG;CAG5C,MAAM,CAAC,iBAAiB,sBAAsB,MAAM,SAElD,KAAA,CAAS;CAEX,MAAM,gBAAgB;EACpB,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,OAAO,EAAE,QAAQ,KAAK;IAClC,mBAAmB,SAAS,CAAC,IAAI;IACjC,MAAM,QAAQ,aAAa;GAC7B;EACF;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAChD,aAAa;GACX,OAAO,oBAAoB,WAAW,aAAa;EACrD;CACF,GAAG,CAAC,KAAK,CAAC;CAGV,MAAM,sBAAsB,OAAO,CAAC;CAEpC,2BAA2B;EACzB,qBAAqB;GACnB,QAAQ,KAAK,yCAAyC;GACtD,oBAAoB,WAAW;EACjC;EACA,yBAAyB;GACvB,QAAQ,IAAI,4CAA4C;EAC1D;EACA,aAAa;CACf,CAAC;CAGD,MAAM,sBAAsB,cAAc,IAAI,oBAAoB,GAAG,CAAC,CAAC;CAEvE,MAAM,CAAC,gBAAgB,qBAAqB,SAAS;EACnD,YAAY;EACZ,WAAW;EACX,mBAAmB;CACrB,CAAC;CAED,MAAM,CAAC,eAAe,oBAAoB,SAAS;EACjD,WAAW;EACX,UAAU;CACZ,CAAC;CAGD,MAAM,wBAAwB,eACrB;EACL,GAAG,mBAAmB,mBAAmB;EACzC,GAAG;CACL,IACA,CAAC,kBAAkB,CACrB;CAEA,MAAM,uBAAuB,aAC1B,gBAA0B;EACzB,eAAe,EAAE,UAAU,YAAY,CAAC;CAC1C,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,iBAAiB,eACd;EACL,kBAAkB,mBAAmB;EACrC,kBAAkB,mBAAmB;CACvC,IACA,CAAC,mBAAmB,kBAAkB,mBAAmB,gBAAgB,CAC3E;CAGA,MAAM,kBAAkB,OAAO,CAAC;CAChC,MAAM,mBAAmB,OAAiC;EAAE,GAAG;EAAG,GAAG;CAAE,CAAC;CAExE,MAAM,CAAC,wBAAwB,6BAA6B,SAC1D,cAAc,SAChB;CAEA,MAAM,YAAY,eAAe,wBAAwB,cAAc,QAAQ;CAE/E,MAAM,EAAE,gBAAgB,UAAU,aAAa,kBAAkB;EAC/D,SAAS;EACT,QAAQ;EACR,kBAAkB;EAClB;EACA,eAAe,sBAAsB,cAAc;EACnD,iBAAiB;EACjB;EACA,kBAAkB;EAClB,sBAAsB,eAAe;CACvC,CAAC;CAED,MAAM,mBAAmB,cAAwC;EAC/D,OAAO;GAAC,eAAe;GAAG;GAAG,eAAe;EAAC;CAC/C,GAAG,CAAC,cAAc,CAAC;CAEnB,MAAM,gBAAgB,cACd;EAAC,mBAAmB,mBAAmB;EAAM;EAAG;CAAC,GACvD,CAAC,mBAAmB,gBAAgB,CACtC;CAEA,MAAM,yBAAyB,cACvB,oBAAoB,kBAAkB,aAAa,GACzD,CAAC,kBAAkB,aAAa,CAClC;CAEA,MAAM,kBAAkB,cAChB,KAAK,IAAI,GAAG,yBAAyB,0BAA0B,GACrE,CAAC,sBAAsB,CACzB;CAEA,MAAM,wBAAwB,OAAe,CAAC;CAE9C,MAAM,iBAAiB,cAAc;EACnC,IAAI,YAAY,aAAa,SAAS,MAAM,KAAK,SAAS,MAAM,IAC9D,OAAO,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;OACnC;GACL,MAAM,KAAK,cAAc,KAAK,iBAAiB;GAC/C,MAAM,KAAK,cAAc,KAAK,iBAAiB;GAC/C,OAAO,KAAK,MAAM,IAAI,EAAE;EAC1B;CACF,GAAG;EAAC;EAAU;EAAU;EAAkB;CAAa,CAAC;CAExD,gBAAgB;EACd,sBAAsB,UAAU;CAClC,GAAG,CAAC,cAAc,CAAC;CAGnB,MAAM,CAAC,mBAAmB,wBAAwB,SAA2B,OAAO;CAEpF,MAAM,iBAAiB,OAAO,CAAC;CAC/B,MAAM,kBAAkB,OAAiB,cAAc;CAEvD,gBAAgB;EACd,IAAI,CAAC,UAAU;GACb,eAAe,UAAU;GACzB,gBAAgB,UAAU;GAC1B;EACF;EAEA,MAAM,KAAK,eAAe,IAAI,gBAAgB,QAAQ;EACtD,MAAM,KAAK,eAAe,IAAI,gBAAgB,QAAQ;EACtD,MAAM,gBAAgB,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;EAEjD,MAAM,gBAAgB,YAClB,yBAAyB,MACzB,yBAAyB;EAC7B,eAAe,WAAW;EAE1B,MAAM,eAAe,KAAK,MAAM,eAAe,UAAU,aAAa;EACtE,IAAI,eAAe,GAAG;GACpB,IAAI,eAAe,MAAM,GACvB,sBAAqB,SAAQ,SAAS,UAAU,SAAS,OAAO;GAElE,eAAe,WAAW,eAAe;EAC3C;EAEA,gBAAgB,UAAU;CAC5B,GAAG;EAAC;EAAgB;EAAU;CAAS,CAAC;CAGxC,MAAM,mBAAmB,OAMf,IAAI;CAEd,MAAM,oBAAoB,aAQlB,KAAK;CAEb,MAAM,qBAAqB,OAEjB,IAAI;CAEd,MAAM,wBAAwB,eACrB;EACL,UAAU,OAAO,UAAU;GACzB,IAAI,UAAU,YAAY,UAAU,KAAK,iBAAiB,SAAS;IACjE,MAAM,aAAa,iBAAiB;IACpC,kBAAkB,QAAQ,WAAW,YAAY;KAC/C,eAAe,WAAW;KAC1B,aAAa,WAAW;IAC1B,CAAC;IACD,iBAAiB,UAAU;GAC7B;EACF;EACA,sBAAsB,UAAU;GAC9B,IAAI,UAAU,iBAAiB;IAC7B,sBAAsB;IACtB,MAAM,gBACJ,sBAAsB,cAAc;IACtC,IAAI,iBAAiB,mBAAmB,SACtC,mBAAmB,QAAQ,wBAAwB,aAAa;GAEpE;EACF;CACF,IACA,CAAC,uBAAuB,cAAc,kBAAkB,CAC1D;CAEA,MAAM,kBAAkB,mBAAmB,EACzC,QAAQ,sBACV,CAAC;CAED,gBAAgB;EACd,mBAAmB,UAAU;CAC/B,GAAG,CAAC,eAAe,CAAC;CAGpB,MAAM,gBAAgB,cACd,sBAAsB,cAAc,qBAC1C,CAAC,cAAc,kBAAkB,CACnC;CAEA,MAAM,CAAC,qBAAqB,0BAA0B,SAAiB,CAAC;CAExE,MAAM,mCAAmC,OACvC,cAAc,GAChB;CAGA,MAAM,sBAAsB,cAA2B;EACrD,OAAO;GACL,IAAI;GACJ,MAAM;IAAE,QAAQ;IAAO,SAAS;GAAU;GAC1C,WAAW;GACX,QAAQ;GACR,WAAW;GACX,IAAI;GACJ,OAAO;GACP,SAAS;GACT,YAAY;GACZ,QAAQ;GACR,WAAW;GACX,aAAa;GACb,SAAS;GACT,OAAO;GACP,WAAW;GACX,MAAM;GACN,eAAe;GACf,SAAS;GACT,UAAU;GACV,eAAe,sBAAsB,cAAc;GACnD,aAAa,YAAY;GACzB,UAAU;GACV,YAAY;GACZ,WAAW;GACX,cAAc;GACd,gBAAgB;GAChB,cAAc;GACd,sBAAsB;GACtB,eAAe,CAAC;GAChB,eAAe,CAAC;GAChB,aAAa,CAAC;GACd,qBAAqB;GACrB,kBAAkB;GAClB,WAAW;GACX,YAAY,cAAc,MAAM;GAChC,gBAAgB,cAAc;GAC9B,gBAAgB;GAChB,QAAQ,cAAc,MAAM;GAC5B,UAAU,cAAc,MAAM;GAC9B,YAAY,cAAc,MAAM;EAClC;CACF,GAAG;EAAC;EAAgB;EAAe;CAAiB,CAAC;CAErD,gBAAgB;EACd,MAAM,6BAA6B;GACjC,MAAM,gBAAgB,oBAAoB,wBACxC,qBACA,aAAa,SACb,KACF;GAEA,MAAM,eAAe,oBAAoB,wBACvC,qBACA,aAAa,SACb,KACF;GAEA,kBAAkB;IAChB,YAAY,cAAc;IAC1B,WAAW,cAAc;IACzB,mBAAmB,cAAc;GACnC,CAAC;GAED,iBAAiB;IACf,WAAW,cAAc;IACzB,UAAU,aAAa;GACzB,CAAC;EACH;EAEA,qBAAqB;EAErB,MAAM,aAAa,YAAY,sBAAsB,GAAG;EAExD,aAAa,cAAc,UAAU;CAEvC,GAAG,CAAC,mBAAmB,CAAC;CAGxB,MAAM,oBAAoB,cAClB,iBAAiB,iBAAiB,UACxC,CAAC,eAAe,CAClB;CAEA,MAAM,kBAAkB,cAAc;EACpC,IAAI,CAAC,mBACH,OAAO,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;EAElC,MAAM,KAAK,cAAc,KAAK,iBAAiB;EAC/C,MAAM,KAAK,cAAc,KAAK,iBAAiB;EAC/C,OAAO,IAAI,MAAM,QAAQ,IAAI,GAAG,EAAE,CAAC,CAAC,UAAU;CAChD,GAAG;EAAC;EAAe;EAAkB;CAAiB,CAAC;CAEvD,MAAM,EACJ,iBAAiB,uCACf,kBAAkB;EACpB,aAAa;EAEb,eAAe,iCAAiC;EAChD,eAAe,oBAAoB;EACnC,cAAc;EACd;EACA,mBAAmB;CACrB,CAAC;CAED,MAAM,wBAAwB,oBAC1B,qCACA;CAGJ,MAAM,kBAAkB,OAA4B,IAAI;CAExD,MAAM,qBAAqB,sBAAsB;EAC/C,QAAQ;EACR,SAAS,cAAc;EACvB,oBAAoB,aACjB,cAAyB;GACxB,gBAAgB,YACd,GAAG,UAAU,KAAK,OAAO,cAAc,UAAU,KAAK,QAAQ,EAChE;GAEA,MAAM,gBAAgB,0BAA0B,SAAS;GACzD,mBAAmB,aAAa;GAGhC,gBAAgB,UAAU;EAC5B,GACA,CAAC,eAAe,CAClB;CACF,CAAC;CAED,MAAM,sBAAsB,cAAc;EACxC,MAAM,aAAa,mBAAmB;EACtC,MAAM,cAAc,mBAAmB;EACvC,IAAI,WAAW,WAAW,KAAK,cAAc,KAAK,eAAe,WAAW,QAC1E;EAEF,OAAO,WAAW,YAAY,EAAE;CAClC,GAAG,CAAC,mBAAmB,qBAAqB,mBAAmB,aAAa,CAAC;CAE7E,MAAM,EACJ,qBACA,oBACA,gBACA,qBACA,oBACA,iBACE,mBAAmB;EACrB,OAAO;EACP,SAAS;EACT;EACA;EACA;EACA,iBAAiB;EACjB,cAAc;EACd;EACA;EACA;EACA;EACA;EACA,iBAAiB,YAAY;GAC3B,eAAe,OAAO;EACxB;EACA,iBAAiB;GACf,cAAc,gBAAgB;GAC9B,oBAAoB,gBAAgB;GACpC,yBAAyB,gBAAgB;GACzC,cAAc,gBAAgB;EAChC;EACA;CACF,CAAC;CAED,gBAAgB;EACd,gBAAgB,UAAU;CAC5B,GAAG,CAAC,YAAY,CAAC;CAEjB,gBAAgB;EACd,kBAAkB,UAAU;CAC9B,GAAG,CAAC,cAAc,CAAC;CAEnB,MAAM,uCAAuC,aAC1C,gBAAwB;EACvB,uBAAuB,cAAc,kBAAkB;EACvD,mBAAmB,WAAW;CAChC,GACA,CAAC,oBAAoB,cAAc,kBAAkB,CACvD;CAGA,MAAM,kBAAkB,OAAgB,QAAQ;CAChD,MAAM,mBAAmB,OAAgB,SAAS;CAClD,MAAM,gBAAgB,OAAsB,aAAa;CAEzD,gBAAgB;EACd,MAAM,kBAAkB,gBAAgB,YAAY;EACpD,MAAM,mBAAmB,iBAAiB,YAAY;EACtD,MAAM,gBAAgB,cAAc,YAAY;EAEhD,IAAI,mBAAmB,kBAAkB;GACvC,IAAI,UAAU;IACZ,IAAI,WACF,gBAAgB,aAAa,eAAe,GAAG;SAE/C,gBAAgB,aAAa,eAAe,IAAI;GAEpD,OAAO,IAAI,gBAAgB,iBAAiB,eAAe,QAChD,gBAAgB,iBAAiB,eAAe,KACzD,gBAAgB,wBAAwB,aAAa;GAEvD,gBAAgB,UAAU;GAC1B,iBAAiB,UAAU;EAC7B;EAEA,IAAI,iBAAiB,CAAC,UAAU;GAC9B,IAAI,gBAAgB,iBAAiB,eAAe,QAChD,gBAAgB,gBAAgB,GAClC,gBAAgB,wBAAwB,aAAa;GAEvD,cAAc,UAAU;EAC1B;CACF,GAAG;EAAC;EAAU;EAAW;EAAe;CAAe,CAAC;CAGxD,MAAM,eAAe,cAAc;EACjC,MAAM,sBAAM,IAAI,IAAoB;EACpC,mBAAmB,gBAAgB,SAAS,OAAO;GACjD,IAAI,IAAI,GAAG,aAAa,GAAG,SAAS;EACtC,CAAC;EACD,OAAO;CACT,GAAG,CAAC,mBAAmB,eAAe,CAAC;CAEvC,MAAM,EAAE,uBAAuB,yBAAyB,cAAc;EACpE,MAAM,aAAa,mBAAmB;EACtC,MAAM,cAAc,mBAAmB;EACvC,IAAI,WAAW,WAAW,GACxB,OAAO;GACL,uBAAuB;GACvB,sBAAsB,cAAc;EACtC;EAEF,MAAM,mBACJ,WAAW,KAAK,IAAI,aAAa,WAAW,SAAS,CAAC;EACxD,IAAI,CAAC,kBACH,OAAO;GACL,uBAAuB;GACvB,sBAAsB,cAAc;EACtC;EAEF,MAAM,aAAa,kCAAkC,iBAAiB,EAAE;EACxE,MAAM,qBACJ,+BAA+B,iBAAiB;EAMlD,OAAO;GACL,uBANY,wBAAwB,kBACpC,oBACA,WAAW,MACX,aAGuB;GACvB,sBAAsB,WAAW;EACnC;CACF,GAAG;EACD,mBAAmB;EACnB,mBAAmB;EACnB;EACA;CACF,CAAC;CAED,gBAAgB;EACd,iCAAiC,UAAU;CAC7C,GAAG,CAAC,oBAAoB,CAAC;CAGzB,MAAM,qBAAqB,OAAsB,IAAI;CAErD,MAAM,wBAAwB;CAE9B,MAAM,mBAAmB,aACtB,WAA6B,cAA6B;EACzD,MAAM,eAA0C;GAC9C,IAAI;GACJ,YAAY;GACZ,OAAO;GACP,cAAc;GACd,MAAM;GACN,aAAa;GACb,MAAM;GACN,WAAW;EACb;EAEA,IAAI,cAAc,WAAW,WAAW;GACtC,IACE,mBAAmB,WACnB,mBAAmB,YAAY,aAAa,YAC5C;IACA,MAAM,UAAU,mBAAmB;IACnC,OAAO,cACL,IAAI,cAAc,SAAS;KACzB,KAAK;KACL,MAAM,MAAM,QAAQ,YAAY;KAChC,SAAS;KACT,YAAY;IACd,CAAC,CACH;GACF;GAEA,MAAM,MAAM,aAAa;GACzB,mBAAmB,UAAU;GAC7B,OAAO,cACL,IAAI,cAAc,WAAW;IAC3B;IACA,MAAM,MAAM,IAAI,YAAY;IAC5B,SAAS;IACT,YAAY;GACd,CAAC,CACH;EACF,OAAO,IAAI,cAAc,OACnB;OAAA,mBAAmB,SAAS;IAC9B,MAAM,MAAM,mBAAmB;IAC/B,OAAO,cACL,IAAI,cAAc,SAAS;KACzB;KACA,MAAM,MAAM,IAAI,YAAY;KAC5B,SAAS;KACT,YAAY;IACd,CAAC,CACH;IACA,mBAAmB,UAAU;GAC/B;;CAEJ,GACA,CAAC,CACH;CAEA,MAAM,qBAAqB,kBAAkB;EAC3C,aAAa;CACf,GAAG,CAAC,YAAY,CAAC;CAEjB,MAAM,oBAAoB,aACvB,cAA+B;EAC9B,IAAI,cAAc,SAChB,MAAM,QAAQ,OAAO;CAEzB,GACA,CAAC,KAAK,CACR;CAEA,MAAM,sBAAsB,aACzB,YAA0B;EACzB,QAAQ,QAAQ,MAAhB;GACE,KAAK;IACH,OAAO,cAAc,IAAI,cAAc,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC;IAC/D;GACF,KAAK;IACH,OAAO,cAAc,IAAI,cAAc,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC;IAC/D;GACF,KAAK;IACH,IAAI,cAAc,YAChB,OAAO,cAAc,IAAI,cAAc,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC;IAEjE;GACF,KAAK;IACH,gBAAgB,WAAW;IAC3B;GACF,KAAK;IACH,gBAAgB,gBACd,cAAc,iBAAiB,gBAC3B,WACA,aACN;IACA,MAAM,QAAQ,aAAa;EAE/B;CACF,GACA;EAAC;EAAe;EAAiB;CAAK,CACxC;CAEA,MAAM,2BAA2B,aAC9B,gBAAwB;EACvB,qCAAqC,WAAW;CAClD,GACA,CAAC,oCAAoC,CACvC;CAGA,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAC9C,MAAM,MAAM,MAAM,IAAI,YAAY;GAElC,IAAI,QAAQ,UAAU;IACpB,eAAe;IACf;GACF;GAEA,IAAI,OAAO,OAAO,OAAO,KAAK;IAC5B,MAAM,cAAc,SAAS,GAAG,IAAI;IACpC,qCAAqC,WAAW;IAChD,MAAM,eAAe;IACrB;GACF;GAEA,IAAI,QAAQ,KAAK;IACf,aAAa;IACb,MAAM,eAAe;IACrB;GACF;EACF;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAChD,aAAa,OAAO,oBAAoB,WAAW,aAAa;CAClE,GAAG;EAAC;EAAgB;EAAsC;CAAY,CAAC;CAGvE,MAAM,gBAAgB,OAAO,KAAK;CAElC,gBAAgB;EACd,IAAI,eAAe;EAEnB,MAAM,aAAa,YAAY;GAC7B,IAAI;IACF,MAAM,MAAM,OAAO,oBAAoB,GAAI;IAC3C,eAAe;GACjB,SAAS,KAAK;IACZ,QAAQ,KAAK,mCAAmC,GAAG;IACnD,gBAAgB,YACd,0CACF;GACF;EACF;EAEA,WAAgB;EAEhB,aAAa;GACX,IAAI,cACF,MACG,QAAQ,GAAI,CAAC,CACb,WAAW,MAAM,UAAU,CAAC,CAAC,CAC7B,OAAO,QAAQ,QAAQ,KAAK,kCAAkC,GAAG,CAAC;EAEzE;CACF,GAAG,CAAC,OAAO,eAAe,CAAC;CAE3B,gBAAgB;EACd,IAAI,CAAC,cAAc,SAAS;GAC1B,cAAc,UAAU;GACxB,oBAAoB;EACtB;CACF,GAAG,CAAC,mBAAmB,CAAC;CAGxB,gBAAgB;EACd,IAAI,cAAc,cAAc;GAC9B,MAAM,QAAQ,iBAAiB,gBAAgB,aAAa,GAAG,IAAI;GACnE,aAAa,aAAa,KAAK;EACjC;CACF,GAAG;EAAC,cAAc;EAAc,cAAc;EAAU;CAAe,CAAC;CAExE,gBAAgB;EACd,IAAI,CAAC,cAAc,cAAc,CAAC,cAAc,kBAAkB;EAElE,MAAM,WAAW,kBAAkB;GACjC,gBAAgB,sBACd,KAAK,OAAO,KAAK,IAAI,KAAK,cAAc,oBAAoB,MAAM,GAAI,CACxE;EACF,GAAG,GAAI;EAEP,aAAa,cAAc,QAAQ;CACrC,GAAG;EACD,cAAc;EACd,cAAc;EACd;CACF,CAAC;CAED,MAAM,sBAAsB,OAC1B,cAAc,YAChB;CACA,MAAM,uBAAuB,OAAgB,IAAI;CACjD,MAAM,gBAAgB,OAAgB,cAAc,UAAU;CAC9D,MAAM,qBAAqB,OAA6C,IAAI;CAE5E,gBAAgB;EACd,cAAc,UAAU,cAAc;CACxC,GAAG,CAAC,cAAc,UAAU,CAAC;CAE7B,MAAM,yBAAyB,OAAO,mBAAmB;CACzD,MAAM,wBAAwB,OAAO,kBAAkB;CAEvD,gBAAgB;EACd,uBAAuB,UAAU;EACjC,sBAAsB,UAAU;CAClC,GAAG,CAAC,qBAAqB,kBAAkB,CAAC;CAE5C,gBAAgB;EACd,IAAI,qBAAqB,SAAS;GAChC,qBAAqB,UAAU;GAC/B,oBAAoB,UAAU,cAAc;GAC5C;EACF;EAKA,IAAI,EAHiB,oBAAoB,YACJ,cAAc,eAGjD;EAGF,oBAAoB,UAAU,cAAc;EAE5C,IAAI,mBAAmB,SAAS;GAC9B,aAAa,mBAAmB,OAAO;GACvC,mBAAmB,UAAU;EAC/B;EAEA,IAAI,cAAc,SAChB,sBAAsB,QAAQ;EAGhC,mBAAmB,UAAU,iBAAiB;GAC5C,uBAAuB,QAAQ;GAC/B,mBAAmB,UAAU;EAC/B,GAAG,GAAG;EAEN,aAAa;GACX,IAAI,mBAAmB,SAAS;IAC9B,aAAa,mBAAmB,OAAO;IACvC,mBAAmB,UAAU;GAC/B;EACF;CACF,GAAG,CAAC,cAAc,YAAY,CAAC;CAG/B,MAAM,uBAAuB,aAC1B,aAAqB;EACpB,gBAAgB,gBAAgB,QAAQ;CAC1C,GACA,CAAC,eAAe,CAClB;CAGA,MAAM,2BAA2B,aAC9B,UAAwB;EACvB,gBAAgB,mBAAmB,KAAK;EACxC,MAAM,QAAQ,YAAY;CAC5B,GACA,CAAC,iBAAiB,KAAK,CACzB;CAEA,MAAM,wBAAwB,aAC3B,YAAoB;EACnB,gBAAgB,sBAAsB,OAAO;EAC7C,MAAM,QAAQ,aAAa;CAC7B,GACA,CAAC,iBAAiB,KAAK,CACzB;CAGA,MAAM,eAAe,cAAc;EACjC,MAAM,OAAO,mBAAmB,QAAQ;EACxC,IAAI,CAAC,YAAY,OAAO;EACxB,OAAO;GACL,GAAG;GACH,KAAK,KAAK,IAAI,IAAI,KAAK,MAAM,EAAE;GAC/B,UAAU;IAAC,KAAK,SAAS;IAAI,KAAK,SAAS;IAAI,KAAK,SAAS,KAAK;GAAC;EAKrE;CACF,GAAG,CAAC,UAAU,UAAU,CAAC;CAGzB,MAAM,sBAAsB,cAAc;EACxC,OAAO,uBAAuB,OAAO,QAAQ;CAC/C,GAAG,CAAC,OAAO,QAAQ,CAAC;CAGpB,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO,GAAG,MAAM;GAChB,QAAQ,GAAG,OAAO;GAClB,UAAU;GACV,UAAU;EACZ;EACA,eAAY;EAPd,UAAA,CASE,qBAAC,QAAD;GACE,OAAO;IAAE,OAAO,GAAG,MAAM;IAAK,QAAQ,GAAG,OAAO;GAAI;GACpD,IAAI;IACF,WAAW,oBAAoB;IAC/B,OAAO;IACP,iBAAiB;IACjB,8BAA8B;IAC9B,uBAAuB;GACzB;GACA,KAAK,oBAAoB;GACzB,SAAS;GACT,YAAY,EAAE,SAAS;IACrB,GAAG,cAAc,MAAM,OAAO,oBAAoB,CAAC;GACrD;GACA,QAAQ;GAdV,UAAA;IAiBE,oBAAC,gBAAD,EAAc,WAAW,GAAM,CAAA;IAC/B,oBAAC,oBAAD;KAAkB,UAAU;MAAC;MAAI;MAAI;KAAC;KAAG,WAAW;IAAM,CAAA;IAG1D,oBAAC,eAAD;KACE,UAAS;KACT,OAAO,mBAAmB;KAC1B,kBAAkB,mBAAmB;KACrC,kBAAkB,mBAAmB;IACtC,CAAA;IAGD,oBAAC,0BAAD,EAA2C,gBAAkB,CAAA;IAG7D,oBAAC,qBAAD;KACY;KACA;KACO;KACC;KAClB,eAAe;KACf,WAAW,cAAc;KACzB,UAAU,cAAc;IACzB,CAAA;IAGD,oBAAC,iBAAD;KACE,UAAU;KACV,oBAAoB,cAAc;KAClC,YAAY,cAAc;KAC1B,QAAQ,cAAc;KACtB,iBAAiB;KACjB,YAAY;KACA;KACK;KACP;IACX,CAAA;IAGA,cAAc,qBAAqB,SAAS,KAC3C,oBAAC,kBAAD;KACE,UAAU;KACV,eAAe,cAAc;KAC7B,SAAS;KACC;IACX,CAAA;IAIF,kBACC,oBAAC,qBAAD;KACE,UAAU;KACV,SAAS;KACT,gBAAgB;KACF;KACD;KACD;KACL;KACG;KACV,eAAe,cAAc;KAC7B,cAAc;IACf,CAAA;IAIH,oBAAC,yBAAD;KACE,GAAI,0BACF,qBACA,uBACA,gBACA;MACE;MACA,QAAQ;MACR,yBAAyB;MACzB,mBAAmB;MACnB,kBAAkB;KACpB,CACF;KACA,kBAAkB,gCAChB,gBAAgB,YAClB;KACiB;KACjB,YAAY;KACZ,yBAAyB,CAAC;KAC1B,oBAAoB;KACpB,mBAAmB;IACpB,CAAA;IAGA,cAAc,iBAAiB,cAC9B,cAAc,uBACZ,oBAAC,wBAAD;KACE,gBAAgB;KAChB,SACE,cAAc,sBAAsB,kBAChC,SACA,cAAc;KAEpB,aAAa,cAAc;KAC3B,SAAS;KACT,OAAO;KACP,UAAU;IACX,CAAA;IAIJ,cAAc,WAAW,KAAK,WAC7B,oBAAC,qBAAD;KAEE,UAAU,OAAO;KACjB,MAAM,OAAO;KACb,QAAQ,OAAO;KACf,SAAS,OAAO;KAChB,kBAAkB,qBAAqB,OAAO,EAAE;KACtC;IACX,GAPM,OAAO,EAOb,CACF;IAGD,oBAAC,uBAAD;KACE,eAAe,cAAc;KAC7B,gBAAgB;KACN;IACX,CAAA;IAMA,oBAAoB,kBACnB,qBAAC,gBAAD;KAAgB,eAAe;KAA/B,UAAA;MACE,oBAAC,OAAD;OACE,oBAAoB;OACpB,oBAAoB;OACpB,YAAA;OACA,WAAW;OACX,QAAQ;MACT,CAAA;MACD,oBAAC,OAAD,EAAO,SAAS,IAAO,CAAA;MACvB,oBAAC,UAAD;OAAU,OAAO;OAAO,QAAQ;OAAK,UAAU;MAAM,CAAA;KACvC;;GAEZ;EAGR,CAAA,GAAA,qBAAC,OAAD;GACE,OAAO;IACL,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,eAAe;IACf,QAAQ,QAAQ;IAChB,UAAU;GACZ;GACA,eAAY;GAXd,UAAA;IAiBG,CAAC,YACA,oBAAC,iBAAD;KACS;KACC;KACE;KACK;KACf,sBAAsB,cAAc;KACpC,sBAAsB;KACtB,oBAAoB,cAAc;KAClC,WAAW,gBAAgB,gBAAgB;IAC5C,CAAA;IAIH,oBAAC,gBAAD;KACS;KACC;KACE;KACK;KACf,YAAY,cAAc;KAC1B,iBAAiB;KACjB,gBAAgB;KACG;KACnB,mBAAmB;KACH;KACA;KAChB,YAAY,UAAU,MAAM,QAAQ,KAAK;IAC1C,CAAA;IAMA,CAAC,YACA,oBAAC,kBAAD;KACS;KACC;KACE;KACK;KACf,cAAc,cAAc;KAC5B,cAAc,gBAAgB;KAC9B,OAAO;MACL,GAAG,cAAc;MACjB,iBAAiB,cAAc;MAC/B,WAAW,cAAc;MACzB,gBAAgB,cAAc;KAChC;KACiB;KACjB,gBAAgB;KAChB,oBAAoB,cAAc;KAClC,oBAAoB,gBAAgB;KACpC,mBAAmB,cAAc;KACjC,mBAAmB,cAAc;KACjC,qBAAqB,cAAc;KACnC,sBAAsB,gBAAgB;KACtC,qBAAqB,gBAAgB;KACrC,uBAAuB,gBAAgB;IACxC,CAAA;IAGH,oBAAC,mBAAD;KACS;KACC;KACE;KACK;KACf,YAAY,mBAAmB;KAC/B,QAAQ;KACR,eAAe,mBAAmB;KAClC,WAAW;KACX,mBAAmB,mBAAmB;KACtC,cAAc,cAAc;KAC5B,iBAAiB,cAAc;KACZ;KACnB,mBAAmB;KACnB,YAAY,UAAU,MAAM,QAAQ,KAAK;IAC1C,CAAA;IAGA,kBACC,oBAAC,+BAAD;KACE,SAAS;KACT,iBAAiB;KACA;KACjB,yBAAyB;KACX;KACd,sBAAsB;KACT;KACb,qBAAqB;KACT;KACZ,oBAAoB;KACV;KACV,kBAAkB;KACX;KACP,eAAe;KACf,gBAAgB;MAAE,KAAK;MAAS,MAAM;KAAO;KACnC;IACX,CAAA;IAIF,YACC,qBAAAA,UAAA,EAAA,UAAA;KACE,oBAAC,uBAAD;MACE,QAAQ;MACR,UAAU;MACV,SAAS;MACT,UAAU,CAAC;MACX,QAAQ,wBAAwB,MAAM;MACtC,SAAS;MACT,eAAe;MACf,gBAAgB;KACjB,CAAA;KAED,oBAAC,iBAAD;MACE,eAAe,cAAc;MAC7B,gBAAgB;MAChB,UAAU,cAAc;MACxB,UAAU,gBAAgB;MAC1B,UAAU,CAAC;MACX,SAAS;KACV,CAAA;KAED,oBAAC,uBAAD;MACE,WAAW;MACX,SAAS;MACT,cAAc;MACd,kBAAkB;KACnB,CAAA;IACD,EAAA,CAAA;GAED;EACF,CAAA,CAAA;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingStatsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingStatsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingStatsOverlayHtml - Html overlay for training statistics\n *\n * Displays score, combo, hits, misses, and accuracy with consistent Korean theming.\n * Uses Korean cyberpunk color palette and bilingual text formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련통계오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\n\n/**\n * Training statistics interface\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Props for TrainingStatsOverlayHtml component\n */\nexport interface TrainingStatsOverlayHtmlProps {\n /** Current training statistics */\n readonly stats: TrainingStats;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Viewport width for Super HD font scaling */\n readonly width?: number;\n /** Distance to training dummy in meters (for distance-based hit feedback) */\n readonly distanceToDummy?: number;\n /** Effective reach for current technique in meters */\n readonly effectiveReach?: number;\n}\n\n/**\n * TrainingStatsOverlayHtml Component\n *\n * Html overlay displaying training performance metrics with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized with custom comparison function\n * - Only re-renders when stats actually change\n * - Reduces unnecessary DOM updates\n *\n * @example\n * ```tsx\n * <TrainingStatsOverlayHtml\n * stats={{ score: 1500, combo: 8, hits: 45, misses: 5, accuracy: 90 }}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련통계오버레이컴포넌트\n */\nexport const TrainingStatsOverlayHtml =\n React.memo<TrainingStatsOverlayHtmlProps>(\n ({\n stats,\n isMobile,\n width = 375,\n distanceToDummy,\n effectiveReach = 0.7, // Default punch reach\n }) => {\n const panelWidth = width > 10 ? width : isMobile ? 180 : 200;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const safeAreaStyles = useMemo(\n () => (isMobile ? getSafeAreaPadding([\"top\"], padding) : {}),\n [isMobile, padding],\n );\n\n const formattedAccuracy = useMemo(\n () => stats.accuracy.toFixed(1),\n [stats.accuracy],\n );\n\n const formattedDuration = useMemo(() => {\n if (!stats.sessionDuration) return \"00:00\";\n const minutes = Math.floor(stats.sessionDuration / 60);\n const seconds = stats.sessionDuration % 60;\n return `${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }, [stats.sessionDuration]);\n\n const distanceInfo = useMemo(() => {\n if (distanceToDummy === undefined) return null;\n const isInRange = distanceToDummy <= effectiveReach;\n return {\n formatted: distanceToDummy.toFixed(2),\n isInRange,\n rangeStatus: isInRange\n ? \"사정거리 내 | In Range\"\n : \"사정거리 밖 | Out of Range\",\n };\n }, [distanceToDummy, effectiveReach]);\n\n const perfectRate = useMemo(() => {\n const totalAttempts = stats.hits + stats.misses;\n if (totalAttempts === 0 || !stats.perfectStrikes) return \"0\";\n return ((stats.perfectStrikes / totalAttempts) * 100).toFixed(1);\n }, [stats.hits, stats.misses, stats.perfectStrikes]);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.92,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n ...safeAreaStyles,\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n };\n\n const titleFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375) // 16px minimum\n : 18;\n\n return (\n <div style={panelStyle} data-testid=\"training-stats-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${padding}px` }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.ACCENT_GOLD,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 통계\", \"Training Statistics\", \"pipe\")}\n </div>\n </div>\n\n {/* Stats Grid with consistent Korean theming */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${gap}px`,\n }}\n >\n {/* Score - 점수 */}\n <StatRow\n korean=\"점수\"\n english=\"Score\"\n value={stats.score.toLocaleString()}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Combo - 콤보 */}\n <StatRow\n korean=\"콤보\"\n english=\"Combo\"\n value={`${stats.combo}x`}\n color={\n stats.combo > 5\n ? KOREAN_COLORS.ACCENT_RED\n : KOREAN_COLORS.PRIMARY_CYAN\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Hits - 성공 */}\n <StatRow\n korean=\"성공\"\n english=\"Hits\"\n value={stats.hits.toString()}\n color={KOREAN_COLORS.ACCENT_GREEN}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Misses - 실패 */}\n <StatRow\n korean=\"실패\"\n english=\"Misses\"\n value={stats.misses.toString()}\n color={KOREAN_COLORS.TEXT_TERTIARY}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Accuracy - 정확도 */}\n <StatRow\n korean=\"정확도\"\n english=\"Accuracy\"\n value={`${formattedAccuracy}%`}\n color={\n stats.accuracy >= 80\n ? KOREAN_COLORS.ACCENT_GREEN\n : stats.accuracy >= 50\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Session Duration - 시간 */}\n {stats.sessionDuration !== undefined && (\n <StatRow\n korean=\"시간\"\n english=\"Duration\"\n value={formattedDuration}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Best Combo - 최고 콤보 */}\n {stats.bestCombo !== undefined && stats.bestCombo > 0 && (\n <StatRow\n korean=\"최고 콤보\"\n english=\"Best Combo\"\n value={`${stats.bestCombo}x`}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Perfect Rate - 완벽률 */}\n {stats.hits + stats.misses > 0 && (\n <StatRow\n korean=\"완벽률\"\n english=\"Perfect Rate\"\n value={`${perfectRate}%`}\n color={\n parseFloat(perfectRate) >= 30\n ? KOREAN_COLORS.ACCENT_GOLD\n : parseFloat(perfectRate) >= 10\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_TERTIARY\n }\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Distance to Dummy - 거리 */}\n {distanceInfo && (\n <>\n <StatRow\n korean=\"거리\"\n english=\"Distance\"\n value={`${distanceInfo.formatted}m`}\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n <StatRow\n korean=\"상태\"\n english=\"Status\"\n value={\n distanceInfo.rangeStatus.split(\" | \")[isMobile ? 0 : 1]\n }\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n </>\n )}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.stats.score === nextProps.stats.score &&\n prevProps.stats.combo === nextProps.stats.combo &&\n prevProps.stats.hits === nextProps.stats.hits &&\n prevProps.stats.misses === nextProps.stats.misses &&\n prevProps.stats.accuracy === nextProps.stats.accuracy &&\n prevProps.stats.sessionDuration === nextProps.stats.sessionDuration &&\n prevProps.stats.bestCombo === nextProps.stats.bestCombo &&\n prevProps.stats.perfectStrikes === nextProps.stats.perfectStrikes &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.distanceToDummy === nextProps.distanceToDummy &&\n prevProps.effectiveReach === nextProps.effectiveReach\n );\n },\n );\n\nTrainingStatsOverlayHtml.displayName = \"TrainingStatsOverlayHtml\";\n\n/**\n * Single stat row component with Korean theming\n *\n * Uses KOREAN_COLORS constants for all text colors\n * Enhanced with smooth transitions and neon glow on value\n *\n * Optimized with React.memo for performance\n *\n * @korean 통계행컴포넌트\n */\nconst StatRow = React.memo<{\n korean: string;\n english: string;\n value: string;\n color: number; // Numeric hex color from KOREAN_COLORS (e.g., 0x00ffff)\n isMobile: boolean;\n width: number; // Width for Super HD font scaling\n}>(({ korean, english, value, color, isMobile, width }) => {\n const labelFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width) // 16px minimum\n : 14;\n const sublabelFontSize = isMobile ? 12 : 11; // Increased from 8-9px\n const valueFontSize = isMobile\n ? getMobileKoreanFontSize(\"MEDIUM\", width) // 18px minimum\n : 20;\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingBottom: `${SPACING.SM}px`,\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.1)}`,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n <div>\n <div\n style={{\n fontSize: `${labelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"subtle\"),\n }}\n >\n {korean}\n </div>\n <div\n style={{\n fontSize: `${sublabelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {english}\n </div>\n </div>\n <div\n style={{\n fontSize: `${valueFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(color, \"medium\"),\n transition: getSmoothTransition(\"transform, color\", \"normal\"),\n transform: \"scale(1)\",\n }}\n >\n {value}\n </div>\n </div>\n );\n});\n\nStatRow.displayName = \"StatRow\";\n\nexport default TrainingStatsOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingStatsOverlayHtml.js","names":["_Fragment"],"sources":["../../../../../src/components/screens/training/components/TrainingStatsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingStatsOverlayHtml - Html overlay for training statistics\n *\n * Displays score, combo, hits, misses, and accuracy with consistent Korean theming.\n * Uses Korean cyberpunk color palette and bilingual text formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련통계오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\n\n/**\n * Training statistics interface\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Props for TrainingStatsOverlayHtml component\n */\nexport interface TrainingStatsOverlayHtmlProps {\n /** Current training statistics */\n readonly stats: TrainingStats;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Viewport width for Super HD font scaling */\n readonly width?: number;\n /** Distance to training dummy in meters (for distance-based hit feedback) */\n readonly distanceToDummy?: number;\n /** Effective reach for current technique in meters */\n readonly effectiveReach?: number;\n}\n\n/**\n * TrainingStatsOverlayHtml Component\n *\n * Html overlay displaying training performance metrics with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized with custom comparison function\n * - Only re-renders when stats actually change\n * - Reduces unnecessary DOM updates\n *\n * @example\n * ```tsx\n * <TrainingStatsOverlayHtml\n * stats={{ score: 1500, combo: 8, hits: 45, misses: 5, accuracy: 90 }}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련통계오버레이컴포넌트\n */\nexport const TrainingStatsOverlayHtml =\n React.memo<TrainingStatsOverlayHtmlProps>(\n ({\n stats,\n isMobile,\n width = 375,\n distanceToDummy,\n effectiveReach = 0.7, // Default punch reach\n }) => {\n const panelWidth = width > 10 ? width : isMobile ? 180 : 200;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const safeAreaStyles = useMemo(\n () => (isMobile ? getSafeAreaPadding([\"top\"], padding) : {}),\n [isMobile, padding],\n );\n\n const formattedAccuracy = useMemo(\n () => stats.accuracy.toFixed(1),\n [stats.accuracy],\n );\n\n const formattedDuration = useMemo(() => {\n if (!stats.sessionDuration) return \"00:00\";\n const minutes = Math.floor(stats.sessionDuration / 60);\n const seconds = stats.sessionDuration % 60;\n return `${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }, [stats.sessionDuration]);\n\n const distanceInfo = useMemo(() => {\n if (distanceToDummy === undefined) return null;\n const isInRange = distanceToDummy <= effectiveReach;\n return {\n formatted: distanceToDummy.toFixed(2),\n isInRange,\n rangeStatus: isInRange\n ? \"사정거리 내 | In Range\"\n : \"사정거리 밖 | Out of Range\",\n };\n }, [distanceToDummy, effectiveReach]);\n\n const perfectRate = useMemo(() => {\n const totalAttempts = stats.hits + stats.misses;\n if (totalAttempts === 0 || !stats.perfectStrikes) return \"0\";\n return ((stats.perfectStrikes / totalAttempts) * 100).toFixed(1);\n }, [stats.hits, stats.misses, stats.perfectStrikes]);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.92,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n ...safeAreaStyles,\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n };\n\n const titleFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375) // 16px minimum\n : 18;\n\n return (\n <div style={panelStyle} data-testid=\"training-stats-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${padding}px` }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.ACCENT_GOLD,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 통계\", \"Training Statistics\", \"pipe\")}\n </div>\n </div>\n\n {/* Stats Grid with consistent Korean theming */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${gap}px`,\n }}\n >\n {/* Score - 점수 */}\n <StatRow\n korean=\"점수\"\n english=\"Score\"\n value={stats.score.toLocaleString()}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Combo - 콤보 */}\n <StatRow\n korean=\"콤보\"\n english=\"Combo\"\n value={`${stats.combo}x`}\n color={\n stats.combo > 5\n ? KOREAN_COLORS.ACCENT_RED\n : KOREAN_COLORS.PRIMARY_CYAN\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Hits - 성공 */}\n <StatRow\n korean=\"성공\"\n english=\"Hits\"\n value={stats.hits.toString()}\n color={KOREAN_COLORS.ACCENT_GREEN}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Misses - 실패 */}\n <StatRow\n korean=\"실패\"\n english=\"Misses\"\n value={stats.misses.toString()}\n color={KOREAN_COLORS.TEXT_TERTIARY}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Accuracy - 정확도 */}\n <StatRow\n korean=\"정확도\"\n english=\"Accuracy\"\n value={`${formattedAccuracy}%`}\n color={\n stats.accuracy >= 80\n ? KOREAN_COLORS.ACCENT_GREEN\n : stats.accuracy >= 50\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Session Duration - 시간 */}\n {stats.sessionDuration !== undefined && (\n <StatRow\n korean=\"시간\"\n english=\"Duration\"\n value={formattedDuration}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Best Combo - 최고 콤보 */}\n {stats.bestCombo !== undefined && stats.bestCombo > 0 && (\n <StatRow\n korean=\"최고 콤보\"\n english=\"Best Combo\"\n value={`${stats.bestCombo}x`}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Perfect Rate - 완벽률 */}\n {stats.hits + stats.misses > 0 && (\n <StatRow\n korean=\"완벽률\"\n english=\"Perfect Rate\"\n value={`${perfectRate}%`}\n color={\n parseFloat(perfectRate) >= 30\n ? KOREAN_COLORS.ACCENT_GOLD\n : parseFloat(perfectRate) >= 10\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_TERTIARY\n }\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Distance to Dummy - 거리 */}\n {distanceInfo && (\n <>\n <StatRow\n korean=\"거리\"\n english=\"Distance\"\n value={`${distanceInfo.formatted}m`}\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n <StatRow\n korean=\"상태\"\n english=\"Status\"\n value={\n distanceInfo.rangeStatus.split(\" | \")[isMobile ? 0 : 1]\n }\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n </>\n )}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.stats.score === nextProps.stats.score &&\n prevProps.stats.combo === nextProps.stats.combo &&\n prevProps.stats.hits === nextProps.stats.hits &&\n prevProps.stats.misses === nextProps.stats.misses &&\n prevProps.stats.accuracy === nextProps.stats.accuracy &&\n prevProps.stats.sessionDuration === nextProps.stats.sessionDuration &&\n prevProps.stats.bestCombo === nextProps.stats.bestCombo &&\n prevProps.stats.perfectStrikes === nextProps.stats.perfectStrikes &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.distanceToDummy === nextProps.distanceToDummy &&\n prevProps.effectiveReach === nextProps.effectiveReach\n );\n },\n );\n\nTrainingStatsOverlayHtml.displayName = \"TrainingStatsOverlayHtml\";\n\n/**\n * Single stat row component with Korean theming\n *\n * Uses KOREAN_COLORS constants for all text colors\n * Enhanced with smooth transitions and neon glow on value\n *\n * Optimized with React.memo for performance\n *\n * @korean 통계행컴포넌트\n */\nconst StatRow = React.memo<{\n korean: string;\n english: string;\n value: string;\n color: number; // Numeric hex color from KOREAN_COLORS (e.g., 0x00ffff)\n isMobile: boolean;\n width: number; // Width for Super HD font scaling\n}>(({ korean, english, value, color, isMobile, width }) => {\n const labelFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width) // 16px minimum\n : 14;\n const sublabelFontSize = isMobile ? 12 : 11; // Increased from 8-9px\n const valueFontSize = isMobile\n ? getMobileKoreanFontSize(\"MEDIUM\", width) // 18px minimum\n : 20;\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingBottom: `${SPACING.SM}px`,\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.1)}`,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n <div>\n <div\n style={{\n fontSize: `${labelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"subtle\"),\n }}\n >\n {korean}\n </div>\n <div\n style={{\n fontSize: `${sublabelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {english}\n </div>\n </div>\n <div\n style={{\n fontSize: `${valueFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(color, \"medium\"),\n transition: getSmoothTransition(\"transform, color\", \"normal\"),\n transform: \"scale(1)\",\n }}\n >\n {value}\n </div>\n </div>\n );\n});\n\nStatRow.displayName = \"StatRow\";\n\nexport default TrainingStatsOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* @korean 공격이동훅 - 공격 이동 추적
|
|
8
8
|
*/
|
|
9
9
|
import * as THREE from "three";
|
|
10
|
-
import { AnimationType } from "../../../../systems/animation/index.
|
|
11
|
-
import { TrigramStance } from "../../../../types/common.
|
|
10
|
+
import { AnimationType } from "../../../../systems/animation/index.js";
|
|
11
|
+
import { TrigramStance } from "../../../../types/common.js";
|
|
12
12
|
/**
|
|
13
13
|
* Configuration for useAttackMovement hook
|
|
14
14
|
*/
|