blacktrigram 0.7.68 → 0.7.70

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.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +11 -11
@@ -1 +1 @@
1
- {"version":3,"file":"AnatomyOverlay3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/AnatomyOverlay3D.tsx"],"sourcesContent":["/**\n * AnatomyOverlay3D - Toggleable anatomy visualization layers\n *\n * Provides skeleton, nerves, vascular, and surface anatomy overlays\n * for educational training visualization\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\nconst SKELETON_EMISSIVE_INTENSITY = 1.0; // Enhanced glow for skeletal structure\nconst NERVE_EMISSIVE_INTENSITY = 1.5; // Balanced for bloom without performance impact\nconst VASCULAR_EMISSIVE_INTENSITY = 2.0; // Moderate intensity for blood vessels\nconst VASCULAR_PULSE_BASE = 1.0; // Base intensity for vascular pulse animation\nconst VASCULAR_PULSE_AMPLITUDE = 0.5; // Pulse variation amplitude (max 1.5 total)\n\nconst SKELETON_MAJOR_TRANSMISSION = 0.1; // Reduced transmission for more solid bone appearance (was 0.3)\nconst SKELETON_MAJOR_THICKNESS = 0.5; // Increased thickness for realistic bone structure (was 0.3)\nconst SKELETON_LIMB_TRANSMISSION = 0.05; // Reduced transmission for limbs (was 0.2)\nconst SKELETON_LIMB_THICKNESS = 0.4; // Increased thickness for limb bones (was 0.2)\nconst VASCULAR_TRANSMISSION = 0.2; // All vascular system meshes\nconst VASCULAR_THICKNESS = 0.2;\n\n/**\n * Anatomy layer types\n */\nexport type AnatomyLayer = \"skeleton\" | \"nerves\" | \"vascular\" | \"surface\";\n\n/**\n * Props for AnatomyOverlay3D component\n */\nexport interface AnatomyOverlay3DProps {\n /** Position of the anatomy overlay (typically the dummy position) */\n readonly position: [number, number, number];\n /** Which anatomy layers to display */\n readonly visibleLayers: readonly AnatomyLayer[];\n /** Opacity of the overlay (0-1) */\n readonly opacity?: number;\n /** Whether on mobile device (reserved for future mobile-specific optimizations) */\n readonly isMobile?: boolean;\n}\n\n/**\n * Skeleton Layer Component\n * Simplified skeletal structure visualization with glass-like transmission\n */\nconst SkeletonLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n const skullGeometry = useMemo(() => new THREE.SphereGeometry(0.25, 16, 16), []);\n\n useEffect(() => {\n return () => {\n skullGeometry.dispose();\n };\n }, [skullGeometry]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.4 + 0.6;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = pulse;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Spine - vertical line */}\n <mesh position={[0, 1.0, -0.05]}>\n <cylinderGeometry args={[0.03, 0.03, 1.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Increased roughness for bone texture (was 0.1)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Skull - wireframe sphere */}\n <lineSegments position={[0, 1.6, 0]}>\n <edgesGeometry args={[skullGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n />\n </lineSegments>\n\n {/* Rib cage - horizontal lines */}\n {[1.3, 1.15, 1.0, 0.85, 0.7].map((y, i) => (\n <mesh key={i} position={[0, y, 0]} rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry args={[0.25 - i * 0.02, 0.02, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.7}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n ))}\n\n {/* Pelvis - simplified structure */}\n <mesh position={[0, 0.5, 0]} rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry args={[0.25, 0.03, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Left arm bones */}\n <mesh position={[-0.4, 1.0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <cylinderGeometry args={[0.02, 0.02, 0.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Right arm bones */}\n <mesh position={[0.4, 1.0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <cylinderGeometry args={[0.02, 0.02, 0.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Leg bones */}\n <mesh position={[-0.2, 0.3, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 0.5, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n <mesh position={[0.2, 0.3, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 0.5, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n </group>\n );\n};\n\n/**\n * Nerves Layer Component\n * Nervous system pathways visualization\n */\nconst NervesLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.5 + 0.5;\n const targetIntensity = 1.0 + pulse * 0.5;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = targetIntensity;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Spinal cord - central nerve */}\n <mesh position={[0, 1.0, -0.08]}>\n <cylinderGeometry args={[0.025, 0.025, 1.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.SECONDARY_YELLOW}\n transparent\n opacity={opacity}\n emissive={KOREAN_COLORS.SECONDARY_YELLOW}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n\n {/* Nerve branches - from spine to limbs */}\n {/* Cervical nerves (neck area) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh\n key={`cervical-${i}`}\n position={[x, 1.4, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 4 : Math.PI / 4]}\n >\n <cylinderGeometry args={[0.015, 0.015, 0.2, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.8}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={1.5}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n\n {/* Brachial plexus (arm nerves) */}\n {[-0.3, 0.3].map((x, i) => (\n <mesh\n key={`brachial-${i}`}\n position={[x, 1.1, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 3 : Math.PI / 3]}\n >\n <cylinderGeometry args={[0.015, 0.01, 0.3, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.7}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n\n {/* Lumbar/sacral nerves (leg nerves) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh\n key={`lumbar-${i}`}\n position={[x, 0.5, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 6 : Math.PI / 6]}\n >\n <cylinderGeometry args={[0.015, 0.01, 0.4, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.7}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n </group>\n );\n};\n\n/**\n * Vascular Layer Component\n * Blood vessel system visualization with glass-like transmission\n */\nconst VascularLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.5 + 0.5;\n const targetIntensity = VASCULAR_PULSE_BASE + pulse * VASCULAR_PULSE_AMPLITUDE;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = targetIntensity;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Aorta - main artery */}\n <mesh position={[0, 1.0, -0.1]}>\n <cylinderGeometry args={[0.02, 0.02, 1.4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n\n {/* Carotid arteries (neck) */}\n {[-0.1, 0.1].map((x, i) => (\n <mesh key={`carotid-${i}`} position={[x, 1.4, -0.05]}>\n <cylinderGeometry args={[0.015, 0.015, 0.3, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.9}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n\n {/* Subclavian/axillary arteries (shoulder to arm) */}\n {[-0.25, 0.25].map((x, i) => (\n <mesh\n key={`subclavian-${i}`}\n position={[x, 1.15, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 4 : Math.PI / 4]}\n >\n <cylinderGeometry args={[0.012, 0.012, 0.25, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.8}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n\n {/* Femoral arteries (legs) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh key={`femoral-${i}`} position={[x, 0.4, -0.08]}>\n <cylinderGeometry args={[0.012, 0.012, 0.35, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.8}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n </group>\n );\n};\n\n/**\n * Surface Layer Component\n * Surface anatomy landmarks and skin layer\n */\nconst SurfaceLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n return (\n <group>\n {/* Glass Skin Shell - Slightly larger than dummy to envelop internals */}\n <mesh position={[0, 1.6, 0]}>\n <sphereGeometry args={[0.26, 32, 32]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n <mesh position={[0, 1.0, 0]}>\n <capsuleGeometry args={[0.31, 0.8, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n {/* Arms */}\n <mesh position={[-0.4, 1.0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <capsuleGeometry args={[0.11, 0.6, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n <mesh position={[0.4, 1.0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <capsuleGeometry args={[0.11, 0.6, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n {/* Legs */}\n <mesh position={[-0.2, 0.3, 0]}>\n <capsuleGeometry args={[0.13, 0.5, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n <mesh position={[0.2, 0.3, 0]}>\n <capsuleGeometry args={[0.13, 0.5, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n </group>\n );\n};\n\n/**\n * AnatomyOverlay3D Component\n * Main component that manages all anatomy layers\n */\nexport const AnatomyOverlay3D: React.FC<AnatomyOverlay3DProps> = ({\n position,\n visibleLayers,\n opacity = 0.7,\n}) => {\n const showSkeleton = useMemo(\n () => visibleLayers.includes(\"skeleton\"),\n [visibleLayers]\n );\n const showNerves = useMemo(\n () => visibleLayers.includes(\"nerves\"),\n [visibleLayers]\n );\n const showVascular = useMemo(\n () => visibleLayers.includes(\"vascular\"),\n [visibleLayers]\n );\n const showSurface = useMemo(\n () => visibleLayers.includes(\"surface\"),\n [visibleLayers]\n );\n\n return (\n <group position={position} name=\"anatomy-overlay-3d\">\n {showSkeleton && <SkeletonLayer opacity={opacity} />}\n {showNerves && <NervesLayer opacity={opacity} />}\n {showVascular && <VascularLayer opacity={opacity} />}\n {showSurface && <SurfaceLayer opacity={opacity} />}\n </group>\n );\n};\n\nexport default AnatomyOverlay3D;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnatomyOverlay3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/AnatomyOverlay3D.tsx"],"sourcesContent":["/**\n * AnatomyOverlay3D - Toggleable anatomy visualization layers\n *\n * Provides skeleton, nerves, vascular, and surface anatomy overlays\n * for educational training visualization\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\nconst SKELETON_EMISSIVE_INTENSITY = 1.0; // Enhanced glow for skeletal structure\nconst NERVE_EMISSIVE_INTENSITY = 1.5; // Balanced for bloom without performance impact\nconst VASCULAR_EMISSIVE_INTENSITY = 2.0; // Moderate intensity for blood vessels\nconst VASCULAR_PULSE_BASE = 1.0; // Base intensity for vascular pulse animation\nconst VASCULAR_PULSE_AMPLITUDE = 0.5; // Pulse variation amplitude (max 1.5 total)\n\nconst SKELETON_MAJOR_TRANSMISSION = 0.1; // Reduced transmission for more solid bone appearance (was 0.3)\nconst SKELETON_MAJOR_THICKNESS = 0.5; // Increased thickness for realistic bone structure (was 0.3)\nconst SKELETON_LIMB_TRANSMISSION = 0.05; // Reduced transmission for limbs (was 0.2)\nconst SKELETON_LIMB_THICKNESS = 0.4; // Increased thickness for limb bones (was 0.2)\nconst VASCULAR_TRANSMISSION = 0.2; // All vascular system meshes\nconst VASCULAR_THICKNESS = 0.2;\n\n/**\n * Anatomy layer types\n */\nexport type AnatomyLayer = \"skeleton\" | \"nerves\" | \"vascular\" | \"surface\";\n\n/**\n * Props for AnatomyOverlay3D component\n */\nexport interface AnatomyOverlay3DProps {\n /** Position of the anatomy overlay (typically the dummy position) */\n readonly position: [number, number, number];\n /** Which anatomy layers to display */\n readonly visibleLayers: readonly AnatomyLayer[];\n /** Opacity of the overlay (0-1) */\n readonly opacity?: number;\n /** Whether on mobile device (reserved for future mobile-specific optimizations) */\n readonly isMobile?: boolean;\n}\n\n/**\n * Skeleton Layer Component\n * Simplified skeletal structure visualization with glass-like transmission\n */\nconst SkeletonLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n const skullGeometry = useMemo(() => new THREE.SphereGeometry(0.25, 16, 16), []);\n\n useEffect(() => {\n return () => {\n skullGeometry.dispose();\n };\n }, [skullGeometry]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.4 + 0.6;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = pulse;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Spine - vertical line */}\n <mesh position={[0, 1.0, -0.05]}>\n <cylinderGeometry args={[0.03, 0.03, 1.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Increased roughness for bone texture (was 0.1)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Skull - wireframe sphere */}\n <lineSegments position={[0, 1.6, 0]}>\n <edgesGeometry args={[skullGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n />\n </lineSegments>\n\n {/* Rib cage - horizontal lines */}\n {[1.3, 1.15, 1.0, 0.85, 0.7].map((y, i) => (\n <mesh key={i} position={[0, y, 0]} rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry args={[0.25 - i * 0.02, 0.02, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.7}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n ))}\n\n {/* Pelvis - simplified structure */}\n <mesh position={[0, 0.5, 0]} rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry args={[0.25, 0.03, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity}\n transmission={SKELETON_MAJOR_TRANSMISSION}\n thickness={SKELETON_MAJOR_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n clearcoat={0.3}\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Left arm bones */}\n <mesh position={[-0.4, 1.0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <cylinderGeometry args={[0.02, 0.02, 0.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Right arm bones */}\n <mesh position={[0.4, 1.0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <cylinderGeometry args={[0.02, 0.02, 0.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n\n {/* Leg bones */}\n <mesh position={[-0.2, 0.3, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 0.5, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n <mesh position={[0.2, 0.3, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 0.5, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.WHITE_SOLID}\n transparent\n opacity={opacity * 0.8}\n transmission={SKELETON_LIMB_TRANSMISSION}\n thickness={SKELETON_LIMB_THICKNESS}\n roughness={0.4} // Bone texture (consistent with spine)\n metalness={0} // Bone is non-metallic\n emissive={KOREAN_COLORS.PRIMARY_CYAN}\n emissiveIntensity={SKELETON_EMISSIVE_INTENSITY}\n ior={1.55} // Index of refraction for bone\n sheen={0.1} // Slight sheen for bone surface\n sheenRoughness={0.9}\n />\n </mesh>\n </group>\n );\n};\n\n/**\n * Nerves Layer Component\n * Nervous system pathways visualization\n */\nconst NervesLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.5 + 0.5;\n const targetIntensity = 1.0 + pulse * 0.5;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = targetIntensity;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Spinal cord - central nerve */}\n <mesh position={[0, 1.0, -0.08]}>\n <cylinderGeometry args={[0.025, 0.025, 1.6, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.SECONDARY_YELLOW}\n transparent\n opacity={opacity}\n emissive={KOREAN_COLORS.SECONDARY_YELLOW}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n\n {/* Nerve branches - from spine to limbs */}\n {/* Cervical nerves (neck area) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh\n key={`cervical-${i}`}\n position={[x, 1.4, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 4 : Math.PI / 4]}\n >\n <cylinderGeometry args={[0.015, 0.015, 0.2, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.8}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={1.5}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n\n {/* Brachial plexus (arm nerves) */}\n {[-0.3, 0.3].map((x, i) => (\n <mesh\n key={`brachial-${i}`}\n position={[x, 1.1, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 3 : Math.PI / 3]}\n >\n <cylinderGeometry args={[0.015, 0.01, 0.3, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.7}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n\n {/* Lumbar/sacral nerves (leg nerves) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh\n key={`lumbar-${i}`}\n position={[x, 0.5, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 6 : Math.PI / 6]}\n >\n <cylinderGeometry args={[0.015, 0.01, 0.4, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={opacity * 0.7}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={NERVE_EMISSIVE_INTENSITY}\n roughness={0.2}\n clearcoat={1.0}\n />\n </mesh>\n ))}\n </group>\n );\n};\n\n/**\n * Vascular Layer Component\n * Blood vessel system visualization with glass-like transmission\n */\nconst VascularLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.5 + 0.5;\n const targetIntensity = VASCULAR_PULSE_BASE + pulse * VASCULAR_PULSE_AMPLITUDE;\n\n const meshes: THREE.Mesh[] = [];\n groupRef.current.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.material instanceof THREE.MeshPhysicalMaterial\n ) {\n meshes.push(child);\n }\n });\n\n meshes.forEach((mesh) => {\n if (mesh.material instanceof THREE.MeshPhysicalMaterial) {\n mesh.material.emissiveIntensity = targetIntensity;\n }\n });\n });\n\n return (\n <group ref={groupRef}>\n {/* Aorta - main artery */}\n <mesh position={[0, 1.0, -0.1]}>\n <cylinderGeometry args={[0.02, 0.02, 1.4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n\n {/* Carotid arteries (neck) */}\n {[-0.1, 0.1].map((x, i) => (\n <mesh key={`carotid-${i}`} position={[x, 1.4, -0.05]}>\n <cylinderGeometry args={[0.015, 0.015, 0.3, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.9}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n\n {/* Subclavian/axillary arteries (shoulder to arm) */}\n {[-0.25, 0.25].map((x, i) => (\n <mesh\n key={`subclavian-${i}`}\n position={[x, 1.15, -0.08]}\n rotation={[0, 0, x > 0 ? -Math.PI / 4 : Math.PI / 4]}\n >\n <cylinderGeometry args={[0.012, 0.012, 0.25, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.8}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n\n {/* Femoral arteries (legs) */}\n {[-0.15, 0.15].map((x, i) => (\n <mesh key={`femoral-${i}`} position={[x, 0.4, -0.08]}>\n <cylinderGeometry args={[0.012, 0.012, 0.35, 6]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={opacity * 0.8}\n transmission={VASCULAR_TRANSMISSION}\n thickness={VASCULAR_THICKNESS}\n roughness={0.2}\n clearcoat={0.8}\n emissive={KOREAN_COLORS.ACCENT_RED}\n emissiveIntensity={VASCULAR_EMISSIVE_INTENSITY}\n />\n </mesh>\n ))}\n </group>\n );\n};\n\n/**\n * Surface Layer Component\n * Surface anatomy landmarks and skin layer\n */\nconst SurfaceLayer: React.FC<{ opacity: number }> = ({ opacity }) => {\n return (\n <group>\n {/* Glass Skin Shell - Slightly larger than dummy to envelop internals */}\n <mesh position={[0, 1.6, 0]}>\n <sphereGeometry args={[0.26, 32, 32]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n <mesh position={[0, 1.0, 0]}>\n <capsuleGeometry args={[0.31, 0.8, 8, 16]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n {/* Arms */}\n <mesh position={[-0.4, 1.0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <capsuleGeometry args={[0.11, 0.6, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n <mesh position={[0.4, 1.0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <capsuleGeometry args={[0.11, 0.6, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n\n {/* Legs */}\n <mesh position={[-0.2, 0.3, 0]}>\n <capsuleGeometry args={[0.13, 0.5, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n <mesh position={[0.2, 0.3, 0]}>\n <capsuleGeometry args={[0.13, 0.5, 4, 8]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n roughness={0.2}\n transmission={0.9}\n thickness={0.5}\n transparent\n opacity={opacity * 0.5}\n />\n </mesh>\n </group>\n );\n};\n\n/**\n * AnatomyOverlay3D Component\n * Main component that manages all anatomy layers\n */\nexport const AnatomyOverlay3D: React.FC<AnatomyOverlay3DProps> = ({\n position,\n visibleLayers,\n opacity = 0.7,\n}) => {\n const showSkeleton = useMemo(\n () => visibleLayers.includes(\"skeleton\"),\n [visibleLayers]\n );\n const showNerves = useMemo(\n () => visibleLayers.includes(\"nerves\"),\n [visibleLayers]\n );\n const showVascular = useMemo(\n () => visibleLayers.includes(\"vascular\"),\n [visibleLayers]\n );\n const showSurface = useMemo(\n () => visibleLayers.includes(\"surface\"),\n [visibleLayers]\n );\n\n return (\n <group position={position} name=\"anatomy-overlay-3d\">\n {showSkeleton && <SkeletonLayer opacity={opacity} />}\n {showNerves && <NervesLayer opacity={opacity} />}\n {showVascular && <VascularLayer opacity={opacity} />}\n {showSurface && <SurfaceLayer opacity={opacity} />}\n </group>\n );\n};\n\nexport default AnatomyOverlay3D;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"FootworkDrillsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootworkDrillsOverlayHtml.tsx"],"sourcesContent":["/**\n * FootworkDrillsOverlayHtml - Training component for footwork drills\n * \n * Provides specialized footwork training exercises for Korean martial arts\n * footwork patterns (보법, Bobeop).\n * \n * @module components/screens/training/components/FootworkDrillsOverlayHtml\n * @category Training Components\n * @korean 보법훈련컴포넌트\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Footwork drill types for training\n * \n * @korean 보법훈련타입\n */\nexport type FootworkDrill = \n | \"circular_left\" // 원형보 좌 - Circle left around target\n | \"circular_right\" // 원형보 우 - Circle right around target\n | \"pivot_combo\" // 축족회전 - Pivot left-right combo\n | \"triangle_step\" // 삼각보법 - Triangle stepping pattern\n | \"slide_drill\" // 미끄럼보 - Four-direction slide drill\n | \"shuffle_practice\" // 섞음보 - Quick shuffle adjustments\n | \"free_practice\"; // 자유 연습 - Free practice mode\n\n/**\n * Drill information with Korean terminology\n * \n * @korean 훈련정보\n */\nconst DRILL_INFO: Record<FootworkDrill, { \n korean: string; \n english: string; \n description: string;\n pattern: string[];\n keyHints: string;\n}> = {\n circular_left: {\n korean: \"원형보 좌회전\",\n english: \"Circular Left\",\n description: \"원형보 좌측 | Circle stepping left\",\n pattern: [\"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\"],\n keyHints: \"Hold Ctrl+A to circle left\",\n },\n circular_right: {\n korean: \"원형보 우회전\",\n english: \"Circular Right\",\n description: \"원형보 우측 | Circle stepping right\",\n pattern: [\"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\"],\n keyHints: \"Hold Ctrl+D to circle right\",\n },\n pivot_combo: {\n korean: \"축족회전 연속\",\n english: \"Pivot Combo\",\n description: \"좌우 연속 회전 | Continuous pivot rotations\",\n pattern: [\"Shift+Ctrl+A\", \"Shift+Ctrl+D\", \"Shift+Ctrl+A\", \"Shift+Ctrl+D\"],\n keyHints: \"Alternate Shift+Ctrl+A/D\",\n },\n triangle_step: {\n korean: \"삼각보법\",\n english: \"Triangle Step\",\n description: \"삼각형 발놀림 | Triangle footwork pattern\",\n pattern: [\"Ctrl+W\", \"Shift+Ctrl+D\", \"Ctrl+S\", \"Shift+Ctrl+A\"],\n keyHints: \"Forward → Pivot → Back → Pivot\",\n },\n slide_drill: {\n korean: \"미끄럼보 사방\",\n english: \"Slide Drill\",\n description: \"사방 미끄럼 | Four-direction slides\",\n pattern: [\"Ctrl+W\", \"Alt+D\", \"Ctrl+S\", \"Alt+A\"],\n keyHints: \"Slide in all four directions\",\n },\n shuffle_practice: {\n korean: \"섞음보 연습\",\n english: \"Shuffle Practice\",\n description: \"빠른 조정 | Quick micro-adjustments\",\n pattern: [\"Shift+Ctrl+W\", \"Shift+Ctrl+W\", \"Shift+Ctrl+W\"],\n keyHints: \"Rapid Shift+Ctrl+W/S\",\n },\n free_practice: {\n korean: \"자유 연습\",\n english: \"Free Practice\",\n description: \"자유 보법 | Free footwork exploration\",\n pattern: [],\n keyHints: \"Use any footwork combination\",\n },\n};\n\n/**\n * Props for FootworkDrillsOverlayHtml component\n */\nexport interface FootworkDrillsOverlayHtmlProps {\n /** Currently selected drill */\n readonly currentDrill: FootworkDrill;\n /** Callback when drill changes */\n readonly onDrillChange: (drill: FootworkDrill) => void;\n /** Current step in drill pattern (0-based) */\n readonly currentStep: number;\n /** Callback when drill step completes (optional, not yet implemented) */\n readonly onStepComplete?: () => void;\n /** Whether drill is currently active */\n readonly isActive: boolean;\n /** Callback to start/stop drill */\n readonly onToggleActive: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * FootworkDrillsOverlayHtml Component\n * \n * Provides UI for footwork training drills with step-by-step guidance\n * and Korean martial arts terminology.\n * \n * @korean 보법훈련UI컴포넌트\n */\nexport const FootworkDrillsOverlayHtml = React.memo<FootworkDrillsOverlayHtmlProps>(\n ({\n currentDrill,\n onDrillChange,\n currentStep,\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n useEffect(() => {\n if (isActive && showInstructions) {\n const timer = setTimeout(() => setShowInstructions(false), 5000);\n return () => clearTimeout(timer);\n }\n }, [isActive, showInstructions]);\n\n const handleDrillSelect = useCallback((drill: FootworkDrill) => {\n onDrillChange(drill);\n setShowInstructions(true);\n }, [onDrillChange]);\n\n const panelWidth = isMobile ? 280 : 340;\n const buttonFontSize = isMobile ? \"10px\" : \"11px\";\n const titleFontSize = isMobile ? \"13px\" : \"15px\";\n\n return (\n <div\n style={{\n width: `${panelWidth}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.5)}`,\n }}\n data-testid=\"footwork-drills-html\"\n >\n {/* Header */}\n <div style={{ marginBottom: \"12px\", textAlign: \"center\" }}>\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n marginBottom: \"4px\",\n }}\n >\n 보법 훈련 | Footwork Drills\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n }}\n >\n {drillInfo.korean} | {drillInfo.english}\n </div>\n </div>\n\n {/* Drill Selection Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"1fr 1fr 1fr\",\n gap: \"6px\",\n marginBottom: \"12px\",\n }}\n >\n {(Object.keys(DRILL_INFO) as FootworkDrill[]).map((drill) => (\n <button\n key={drill}\n onClick={() => handleDrillSelect(drill)}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n fontSize: buttonFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: currentDrill === drill ? \"bold\" : \"normal\",\n background: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.8),\n color: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n currentDrill === drill\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.UI_BORDER,\n 0.6\n )}`,\n borderRadius: \"6px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n lineHeight: 1.2,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n {DRILL_INFO[drill].korean.split(\" \")[0]}\n </button>\n ))}\n </div>\n\n {/* Drill Description */}\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n textAlign: \"center\",\n }}\n >\n {drillInfo.description}\n </div>\n\n {/* Pattern Steps (if drill has pattern) */}\n {drillInfo.pattern.length > 0 && (\n <div style={{ marginBottom: \"10px\" }}>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n marginBottom: \"6px\",\n textAlign: \"center\",\n }}\n >\n Pattern Steps:\n </div>\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n gap: \"4px\",\n flexWrap: \"wrap\",\n }}\n >\n {drillInfo.pattern.map((step, index) => (\n <div\n key={index}\n style={{\n padding: \"4px 8px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n fontFamily: FONT_FAMILY.KOREAN,\n background: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9)\n : index < currentStep\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.7),\n color: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n isActive && index === currentStep\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"4px\",\n fontWeight: isActive && index === currentStep ? \"bold\" : \"normal\",\n }}\n >\n {index + 1}. {step}\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Key Hints */}\n {showInstructions && (\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n textAlign: \"center\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n 💡 {drillInfo.keyHints}\n </div>\n )}\n\n {/* Start/Stop Button */}\n <button\n onClick={onToggleActive}\n style={{\n width: \"100%\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n background: isActive\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.9)\n : hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.9),\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: \"none\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 0 20px ${hexToRgbaString(\n isActive ? KOREAN_COLORS.ACCENT_RED : KOREAN_COLORS.ACCENT_GREEN,\n 0.8\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = \"none\";\n }}\n >\n {isActive ? \"훈련 중지 | Stop Drill\" : \"훈련 시작 | Start Drill\"}\n </button>\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.currentDrill === nextProps.currentDrill &&\n prevProps.currentStep === nextProps.currentStep &&\n prevProps.isActive === nextProps.isActive &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onDrillChange === nextProps.onDrillChange &&\n prevProps.onToggleActive === nextProps.onToggleActive\n );\n});\n\nFootworkDrillsOverlayHtml.displayName = \"FootworkDrillsOverlayHtml\";\n\nexport default FootworkDrillsOverlayHtml;\n"],"mappings":"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"}
1
+ {"version":3,"file":"FootworkDrillsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootworkDrillsOverlayHtml.tsx"],"sourcesContent":["/**\n * FootworkDrillsOverlayHtml - Training component for footwork drills\n * \n * Provides specialized footwork training exercises for Korean martial arts\n * footwork patterns (보법, Bobeop).\n * \n * @module components/screens/training/components/FootworkDrillsOverlayHtml\n * @category Training Components\n * @korean 보법훈련컴포넌트\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Footwork drill types for training\n * \n * @korean 보법훈련타입\n */\nexport type FootworkDrill = \n | \"circular_left\" // 원형보 좌 - Circle left around target\n | \"circular_right\" // 원형보 우 - Circle right around target\n | \"pivot_combo\" // 축족회전 - Pivot left-right combo\n | \"triangle_step\" // 삼각보법 - Triangle stepping pattern\n | \"slide_drill\" // 미끄럼보 - Four-direction slide drill\n | \"shuffle_practice\" // 섞음보 - Quick shuffle adjustments\n | \"free_practice\"; // 자유 연습 - Free practice mode\n\n/**\n * Drill information with Korean terminology\n * \n * @korean 훈련정보\n */\nconst DRILL_INFO: Record<FootworkDrill, { \n korean: string; \n english: string; \n description: string;\n pattern: string[];\n keyHints: string;\n}> = {\n circular_left: {\n korean: \"원형보 좌회전\",\n english: \"Circular Left\",\n description: \"원형보 좌측 | Circle stepping left\",\n pattern: [\"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\"],\n keyHints: \"Hold Ctrl+A to circle left\",\n },\n circular_right: {\n korean: \"원형보 우회전\",\n english: \"Circular Right\",\n description: \"원형보 우측 | Circle stepping right\",\n pattern: [\"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\"],\n keyHints: \"Hold Ctrl+D to circle right\",\n },\n pivot_combo: {\n korean: \"축족회전 연속\",\n english: \"Pivot Combo\",\n description: \"좌우 연속 회전 | Continuous pivot rotations\",\n pattern: [\"Shift+Ctrl+A\", \"Shift+Ctrl+D\", \"Shift+Ctrl+A\", \"Shift+Ctrl+D\"],\n keyHints: \"Alternate Shift+Ctrl+A/D\",\n },\n triangle_step: {\n korean: \"삼각보법\",\n english: \"Triangle Step\",\n description: \"삼각형 발놀림 | Triangle footwork pattern\",\n pattern: [\"Ctrl+W\", \"Shift+Ctrl+D\", \"Ctrl+S\", \"Shift+Ctrl+A\"],\n keyHints: \"Forward → Pivot → Back → Pivot\",\n },\n slide_drill: {\n korean: \"미끄럼보 사방\",\n english: \"Slide Drill\",\n description: \"사방 미끄럼 | Four-direction slides\",\n pattern: [\"Ctrl+W\", \"Alt+D\", \"Ctrl+S\", \"Alt+A\"],\n keyHints: \"Slide in all four directions\",\n },\n shuffle_practice: {\n korean: \"섞음보 연습\",\n english: \"Shuffle Practice\",\n description: \"빠른 조정 | Quick micro-adjustments\",\n pattern: [\"Shift+Ctrl+W\", \"Shift+Ctrl+W\", \"Shift+Ctrl+W\"],\n keyHints: \"Rapid Shift+Ctrl+W/S\",\n },\n free_practice: {\n korean: \"자유 연습\",\n english: \"Free Practice\",\n description: \"자유 보법 | Free footwork exploration\",\n pattern: [],\n keyHints: \"Use any footwork combination\",\n },\n};\n\n/**\n * Props for FootworkDrillsOverlayHtml component\n */\nexport interface FootworkDrillsOverlayHtmlProps {\n /** Currently selected drill */\n readonly currentDrill: FootworkDrill;\n /** Callback when drill changes */\n readonly onDrillChange: (drill: FootworkDrill) => void;\n /** Current step in drill pattern (0-based) */\n readonly currentStep: number;\n /** Callback when drill step completes (optional, not yet implemented) */\n readonly onStepComplete?: () => void;\n /** Whether drill is currently active */\n readonly isActive: boolean;\n /** Callback to start/stop drill */\n readonly onToggleActive: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * FootworkDrillsOverlayHtml Component\n * \n * Provides UI for footwork training drills with step-by-step guidance\n * and Korean martial arts terminology.\n * \n * @korean 보법훈련UI컴포넌트\n */\nexport const FootworkDrillsOverlayHtml = React.memo<FootworkDrillsOverlayHtmlProps>(\n ({\n currentDrill,\n onDrillChange,\n currentStep,\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n useEffect(() => {\n if (isActive && showInstructions) {\n const timer = setTimeout(() => setShowInstructions(false), 5000);\n return () => clearTimeout(timer);\n }\n }, [isActive, showInstructions]);\n\n const handleDrillSelect = useCallback((drill: FootworkDrill) => {\n onDrillChange(drill);\n setShowInstructions(true);\n }, [onDrillChange]);\n\n const panelWidth = isMobile ? 280 : 340;\n const buttonFontSize = isMobile ? \"10px\" : \"11px\";\n const titleFontSize = isMobile ? \"13px\" : \"15px\";\n\n return (\n <div\n style={{\n width: `${panelWidth}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.5)}`,\n }}\n data-testid=\"footwork-drills-html\"\n >\n {/* Header */}\n <div style={{ marginBottom: \"12px\", textAlign: \"center\" }}>\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n marginBottom: \"4px\",\n }}\n >\n 보법 훈련 | Footwork Drills\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n }}\n >\n {drillInfo.korean} | {drillInfo.english}\n </div>\n </div>\n\n {/* Drill Selection Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"1fr 1fr 1fr\",\n gap: \"6px\",\n marginBottom: \"12px\",\n }}\n >\n {(Object.keys(DRILL_INFO) as FootworkDrill[]).map((drill) => (\n <button\n key={drill}\n onClick={() => handleDrillSelect(drill)}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n fontSize: buttonFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: currentDrill === drill ? \"bold\" : \"normal\",\n background: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.8),\n color: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n currentDrill === drill\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.UI_BORDER,\n 0.6\n )}`,\n borderRadius: \"6px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n lineHeight: 1.2,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n {DRILL_INFO[drill].korean.split(\" \")[0]}\n </button>\n ))}\n </div>\n\n {/* Drill Description */}\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n textAlign: \"center\",\n }}\n >\n {drillInfo.description}\n </div>\n\n {/* Pattern Steps (if drill has pattern) */}\n {drillInfo.pattern.length > 0 && (\n <div style={{ marginBottom: \"10px\" }}>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n marginBottom: \"6px\",\n textAlign: \"center\",\n }}\n >\n Pattern Steps:\n </div>\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n gap: \"4px\",\n flexWrap: \"wrap\",\n }}\n >\n {drillInfo.pattern.map((step, index) => (\n <div\n key={index}\n style={{\n padding: \"4px 8px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n fontFamily: FONT_FAMILY.KOREAN,\n background: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9)\n : index < currentStep\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.7),\n color: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n isActive && index === currentStep\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"4px\",\n fontWeight: isActive && index === currentStep ? \"bold\" : \"normal\",\n }}\n >\n {index + 1}. {step}\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Key Hints */}\n {showInstructions && (\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n textAlign: \"center\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n 💡 {drillInfo.keyHints}\n </div>\n )}\n\n {/* Start/Stop Button */}\n <button\n onClick={onToggleActive}\n style={{\n width: \"100%\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n background: isActive\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.9)\n : hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.9),\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: \"none\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 0 20px ${hexToRgbaString(\n isActive ? KOREAN_COLORS.ACCENT_RED : KOREAN_COLORS.ACCENT_GREEN,\n 0.8\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = \"none\";\n }}\n >\n {isActive ? \"훈련 중지 | Stop Drill\" : \"훈련 시작 | Start Drill\"}\n </button>\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.currentDrill === nextProps.currentDrill &&\n prevProps.currentStep === nextProps.currentStep &&\n prevProps.isActive === nextProps.isActive &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onDrillChange === nextProps.onDrillChange &&\n prevProps.onToggleActive === nextProps.onToggleActive\n );\n});\n\nFootworkDrillsOverlayHtml.displayName = \"FootworkDrillsOverlayHtml\";\n\nexport default FootworkDrillsOverlayHtml;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HitFeedbackEffect3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/HitFeedbackEffect3D.tsx"],"sourcesContent":["/**\n * HitFeedbackEffect3D - Visual hit confirmation with damage numbers\n *\n * Provides particle effects, color flashes, and floating damage numbers\n * for training hit feedback\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { ThreeObjectPools } from \"../../../../utils/threeObjectPool\";\n\n/**\n * Props for HitFeedbackEffect3D component\n */\nexport interface HitFeedbackEffect3DProps {\n /** 3D position where hit occurred */\n readonly position: [number, number, number];\n /** Type of hit (affects visual style) */\n readonly type: \"success\" | \"perfect\" | \"miss\";\n /** Damage dealt (if applicable) */\n readonly damage?: number;\n /** Whether effect is visible */\n readonly visible?: boolean;\n /** Callback when effect completes */\n readonly onComplete?: () => void;\n /** Duration in milliseconds */\n readonly duration?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n}\n\n/**\n * Impact particle system\n */\nconst ImpactParticles: React.FC<{\n position: [number, number, number];\n color: number;\n count: number;\n}> = ({ position, color, count }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n const [initialPosition] = useState(position);\n\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n const theta = seededRandom(i * 3) * Math.PI * 2;\n const phi = seededRandom(i * 3 + 1) * Math.PI;\n const speed = 0.5 + seededRandom(i * 3 + 2) * 1.5;\n\n tempVel.set(\n Math.sin(phi) * Math.cos(theta),\n Math.cos(phi),\n Math.sin(phi) * Math.sin(theta)\n );\n tempVel.normalize().multiplyScalar(speed);\n \n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n ThreeObjectPools.vector3.release(tempVel);\n }\n\n return { positions: pos, velocities: vel };\n }, [count, initialPosition]); // initialPosition is captured at mount and won't change\n\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n useFrame((_, delta) => {\n if (!pointsRef.current || !velocitiesRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n const vel = velocitiesRef.current;\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n\n array[i3] += vel[i3] * delta * 10;\n array[i3 + 1] += vel[i3 + 1] * delta * 10;\n array[i3 + 2] += vel[i3 + 2] * delta * 10;\n\n vel[i3 + 1] -= 9.8 * delta;\n\n if (array[i3 + 1] < -2) {\n array[i3 + 1] = -2;\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef} position={position}>\n <bufferGeometry>\n <bufferAttribute attach=\"attributes-position\" args={[positions, 3]} />\n </bufferGeometry>\n <pointsMaterial\n size={0.08}\n color={color}\n transparent\n opacity={1.0}\n sizeAttenuation\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </points>\n );\n};\n\n/**\n * Expanding ring effect\n */\nconst RingEffect: React.FC<{\n position: [number, number, number];\n color: number;\n maxRadius: number;\n}> = ({ position, color, maxRadius }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const startTime = useRef<number>(0);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 0.5, 1); // 0.5 second duration\n\n const radius = progress * maxRadius;\n const tempScale = ThreeObjectPools.vector3.acquire();\n try {\n tempScale.setScalar(radius);\n meshRef.current.scale.copy(tempScale);\n } finally {\n ThreeObjectPools.vector3.release(tempScale);\n }\n\n const material = meshRef.current.material as THREE.MeshBasicMaterial;\n material.opacity = 1 - progress;\n });\n\n return (\n <mesh ref={meshRef} position={position} rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[0.9, 1.0, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n />\n </mesh>\n );\n};\n\n/**\n * Floating damage number\n */\nconst DamageNumber: React.FC<{\n position: [number, number, number];\n damage: number;\n type: \"perfect\" | \"normal\" | \"miss\";\n isMobile: boolean;\n onComplete: () => void;\n}> = ({ position, damage, type, isMobile, onComplete }) => {\n const [offset, setOffset] = useState(0);\n const [opacity, setOpacity] = useState(1);\n const startTime = useRef<number>(0);\n const completedRef = useRef(false);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 1.5, 1); // 1.5 second duration\n\n setOffset(progress * 1);\n\n setOpacity(1 - progress);\n\n if (progress >= 1 && !completedRef.current && onComplete) {\n completedRef.current = true;\n onComplete();\n }\n });\n\n const color =\n type === \"perfect\" ? \"#ffd700\" : type === \"normal\" ? \"#00ffff\" : \"#ff4444\";\n const text = type === \"miss\" ? \"빗나감 | MISS\" : `${damage}`; // Korean: 빗나감 = miss/deflected\n\n return (\n <Html\n position={[position[0], position[1] + offset, position[2]]}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\", opacity }}\n >\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: \"0 0 10px rgba(0, 0, 0, 0.8)\",\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"damage-number\"\n >\n {text}\n </div>\n </Html>\n );\n};\n\n/**\n * HitFeedbackEffect3D Component\n * Main hit feedback visualization\n */\nexport const HitFeedbackEffect3D: React.FC<HitFeedbackEffect3DProps> = ({\n position,\n type,\n damage,\n visible = true,\n onComplete,\n duration = 1500,\n isMobile = false,\n}) => {\n const [showEffect, setShowEffect] = useState(visible);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const effectColor = useMemo(() => {\n switch (type) {\n case \"perfect\":\n return KOREAN_COLORS.ACCENT_GOLD;\n case \"success\":\n return KOREAN_COLORS.PRIMARY_CYAN;\n case \"miss\":\n return KOREAN_COLORS.TEXT_SECONDARY;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n }, [type]);\n\n const particleCount = useMemo(() => {\n const isPerfect = type === \"perfect\";\n const isSuccess = type === \"success\";\n\n if (isMobile) {\n return isPerfect ? 30 : isSuccess ? 20 : 10;\n }\n\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n const completedRef = useRef(false);\n\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n useEffect(() => {\n if (visible && showEffect) {\n timeoutRef.current = setTimeout(handleComplete, duration);\n }\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [visible, showEffect, duration, handleComplete]);\n\n if (!showEffect) return null;\n\n return (\n <group name={`hit-feedback-${type}`}>\n {/* Particle burst */}\n <ImpactParticles\n position={position}\n color={effectColor}\n count={particleCount}\n />\n\n {/* Expanding ring */}\n <RingEffect\n position={position}\n color={effectColor}\n maxRadius={ringRadius}\n />\n\n {/* Floating damage number */}\n {damage !== undefined && type !== \"miss\" && (\n <DamageNumber\n position={position}\n damage={damage}\n type={type === \"perfect\" ? \"perfect\" : \"normal\"}\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n\n {/* Miss indicator */}\n {type === \"miss\" && (\n <DamageNumber\n position={position}\n damage={0}\n type=\"miss\"\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n </group>\n );\n};\n\nexport default HitFeedbackEffect3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,IAAM,mBAIA,EAAE,UAAU,OAAO,YAAY;CACnC,MAAM,YAAY,OAAqB,IAAI;CAE3C,MAAM,gBAAgB,OAA4B,IAAI;CAEtD,MAAM,CAAC,mBAAmB,SAAS,QAAQ;CAE3C,MAAM,EAAE,WAAW,eAAe,cAAc;EAC9C,MAAM,MAAM,IAAI,aAAa,QAAQ,CAAC;EACtC,MAAM,MAAM,IAAI,aAAa,QAAQ,CAAC;EAEtC,MAAM,OACJ,gBAAgB,KAAK,gBAAgB,KAAK,KAAK,gBAAgB,KAAK;EAEtE,SAAS,aAAa,OAAuB;GAC3C,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI;GACnC,OAAO,IAAI,KAAK,MAAM,CAAC;EACzB;EAEA,MAAM,UAAU,iBAAiB,QAAQ,QAAQ;EAEjD,IAAI;GACF,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;IAC9B,MAAM,KAAK,IAAI;IACf,IAAI,MAAM;IACV,IAAI,KAAK,KAAK;IACd,IAAI,KAAK,KAAK;IAEd,MAAM,QAAQ,aAAa,IAAI,CAAC,IAAI,KAAK,KAAK;IAC9C,MAAM,MAAM,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK;IAC3C,MAAM,QAAQ,KAAM,aAAa,IAAI,IAAI,CAAC,IAAI;IAE9C,QAAQ,IACN,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,GAC9B,KAAK,IAAI,GAAG,GACZ,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,CAChC;IACA,QAAQ,UAAU,EAAE,eAAe,KAAK;IAExC,IAAI,MAAM,QAAQ;IAClB,IAAI,KAAK,KAAK,QAAQ,IAAI;IAC1B,IAAI,KAAK,KAAK,QAAQ;GACxB;EACF,UAAU;GACR,iBAAiB,QAAQ,QAAQ,OAAO;EAC1C;EAEA,OAAO;GAAE,WAAW;GAAK,YAAY;EAAI;CAC3C,GAAG,CAAC,OAAO,eAAe,CAAC;CAE3B,gBAAgB;EACd,cAAc,UAAU;CAC1B,GAAG,CAAC,UAAU,CAAC;CAEf,UAAU,GAAG,UAAU;EACrB,IAAI,CAAC,UAAU,WAAW,CAAC,cAAc,SAAS;EAElD,MAAM,OAAO,UAAU,QAAQ,SAAS,WAAW;EACnD,MAAM,QAAQ,KAAK;EACnB,MAAM,MAAM,cAAc;EAE1B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,KAAK,IAAI;GAEf,MAAM,OAAO,IAAI,MAAM,QAAQ;GAC/B,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ;GACvC,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ;GAEvC,IAAI,KAAK,MAAM,MAAM;GAErB,IAAI,MAAM,KAAK,KAAK,IAClB,MAAM,KAAK,KAAK;EAEpB;EAEA,KAAK,cAAc;CACrB,CAAC;CAED,OACE,qBAAC,UAAD;EAAQ,KAAK;EAAqB;YAAlC,CACE,oBAAC,kBAAD,EAAA,UACE,oBAAC,mBAAD;GAAiB,QAAO;GAAsB,MAAM,CAAC,WAAW,CAAC;EAAI,CAAA,EACvD,CAAA,GAChB,oBAAC,kBAAD;GACE,MAAM;GACC;GACP,aAAA;GACA,SAAS;GACT,iBAAA;GACA,YAAY;GACZ,UAAU,MAAM;EACjB,CAAA,CACK;;AAEZ;;;;AAKA,IAAM,cAIA,EAAE,UAAU,OAAO,gBAAgB;CACvC,MAAM,UAAU,OAAmB,IAAI;CACvC,MAAM,YAAY,OAAe,CAAC;CAElC,gBAAgB;EACd,IAAI,UAAU,YAAY,GACxB,UAAU,UAAU,KAAK,IAAI;CAEjC,GAAG,CAAC,CAAC;CAEL,eAAe;EACb,IAAI,CAAC,QAAQ,SAAS;EAEtB,MAAM,WAAW,KAAK,IAAI,IAAI,UAAU,WAAW;EACnD,MAAM,WAAW,KAAK,IAAI,UAAU,IAAK,CAAC;EAE1C,MAAM,SAAS,WAAW;EAC1B,MAAM,YAAY,iBAAiB,QAAQ,QAAQ;EACnD,IAAI;GACF,UAAU,UAAU,MAAM;GAC1B,QAAQ,QAAQ,MAAM,KAAK,SAAS;EACtC,UAAU;GACR,iBAAiB,QAAQ,QAAQ,SAAS;EAC5C;EAEA,MAAM,WAAW,QAAQ,QAAQ;EACjC,SAAS,UAAU,IAAI;CACzB,CAAC;CAED,OACE,qBAAC,QAAD;EAAM,KAAK;EAAmB;EAAU,UAAU;GAAC,KAAK,KAAK;GAAG;GAAG;EAAC;YAApE,CACE,oBAAC,gBAAD,EAAc,MAAM;GAAC;GAAK;GAAK;EAAE,EAAI,CAAA,GACrC,oBAAC,qBAAD;GACS;GACP,aAAA;GACA,SAAS;GACT,MAAM,MAAM;EACb,CAAA,CACG;;AAEV;;;;AAKA,IAAM,gBAMA,EAAE,UAAU,QAAQ,MAAM,UAAU,iBAAiB;CACzD,MAAM,CAAC,QAAQ,aAAa,SAAS,CAAC;CACtC,MAAM,CAAC,SAAS,cAAc,SAAS,CAAC;CACxC,MAAM,YAAY,OAAe,CAAC;CAClC,MAAM,eAAe,OAAO,KAAK;CAEjC,gBAAgB;EACd,IAAI,UAAU,YAAY,GACxB,UAAU,UAAU,KAAK,IAAI;CAEjC,GAAG,CAAC,CAAC;CAEL,eAAe;EACb,MAAM,WAAW,KAAK,IAAI,IAAI,UAAU,WAAW;EACnD,MAAM,WAAW,KAAK,IAAI,UAAU,KAAK,CAAC;EAE1C,UAAU,WAAW,CAAC;EAEtB,WAAW,IAAI,QAAQ;EAEvB,IAAI,YAAY,KAAK,CAAC,aAAa,WAAW,YAAY;GACxD,aAAa,UAAU;GACvB,WAAW;EACb;CACF,CAAC;CAED,MAAM,QACJ,SAAS,YAAY,YAAY,SAAS,WAAW,YAAY;CACnE,MAAM,OAAO,SAAS,SAAS,eAAe,GAAG;CAEjD,OACE,oBAAC,MAAD;EACE,UAAU;GAAC,SAAS;GAAI,SAAS,KAAK;GAAQ,SAAS;EAAE;EACzD,QAAA;EACA,gBAAgB;EAChB,OAAO;GAAE,eAAe;GAAQ;EAAQ;YAExC,oBAAC,OAAD;GACE,OAAO;IACL,UAAU,WAAW,SAAS;IAC9B,YAAY;IACZ,YAAY,YAAY;IACxB;IACA,YAAY;IACZ,YAAY;GACd;GACA,eAAY;aAEX;EACE,CAAA;CACD,CAAA;AAEV;;;;;AAMA,IAAa,uBAA2D,EACtE,UACA,MACA,QACA,UAAU,MACV,YACA,WAAW,MACX,WAAW,YACP;CACJ,MAAM,CAAC,YAAY,iBAAiB,SAAS,OAAO;CACpD,MAAM,aAAa,OAA6C,IAAI;CAEpE,MAAM,cAAc,cAAc;EAChC,QAAQ,MAAR;GACE,KAAK,WACH,OAAO,cAAc;GACvB,KAAK,WACH,OAAO,cAAc;GACvB,KAAK,QACH,OAAO,cAAc;GACvB,SACE,OAAO,cAAc;EACzB;CACF,GAAG,CAAC,IAAI,CAAC;CAET,MAAM,gBAAgB,cAAc;EAClC,MAAM,YAAY,SAAS;EAC3B,MAAM,YAAY,SAAS;EAE3B,IAAI,UACF,OAAO,YAAY,KAAK,YAAY,KAAK;EAG3C,OAAO,YAAY,KAAK,YAAY,KAAK;CAC3C,GAAG,CAAC,MAAM,QAAQ,CAAC;CACnB,MAAM,aAAa,SAAS,YAAY,MAAM;CAE9C,MAAM,eAAe,OAAO,KAAK;CAEjC,MAAM,iBAAiB,kBAAkB;EACvC,IAAI,aAAa,SAAS;EAC1B,aAAa,UAAU;EACvB,cAAc,KAAK;EACnB,aAAa;CACf,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,IAAI,WAAW,YACb,WAAW,UAAU,WAAW,gBAAgB,QAAQ;EAG1D,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG;EAAC;EAAS;EAAY;EAAU;CAAc,CAAC;CAElD,IAAI,CAAC,YAAY,OAAO;CAExB,OACE,qBAAC,SAAD;EAAO,MAAM,gBAAgB;YAA7B;GAEE,oBAAC,iBAAD;IACY;IACV,OAAO;IACP,OAAO;GACR,CAAA;GAGD,oBAAC,YAAD;IACY;IACV,OAAO;IACP,WAAW;GACZ,CAAA;GAGA,WAAW,KAAA,KAAa,SAAS,UAChC,oBAAC,cAAD;IACY;IACF;IACR,MAAM,SAAS,YAAY,YAAY;IAC7B;IACV,YAAY;GACb,CAAA;GAIF,SAAS,UACR,oBAAC,cAAD;IACY;IACV,QAAQ;IACR,MAAK;IACK;IACV,YAAY;GACb,CAAA;EAEE;;AAEX"}
1
+ {"version":3,"file":"HitFeedbackEffect3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/HitFeedbackEffect3D.tsx"],"sourcesContent":["/**\n * HitFeedbackEffect3D - Visual hit confirmation with damage numbers\n *\n * Provides particle effects, color flashes, and floating damage numbers\n * for training hit feedback\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { ThreeObjectPools } from \"../../../../utils/threeObjectPool\";\n\n/**\n * Props for HitFeedbackEffect3D component\n */\nexport interface HitFeedbackEffect3DProps {\n /** 3D position where hit occurred */\n readonly position: [number, number, number];\n /** Type of hit (affects visual style) */\n readonly type: \"success\" | \"perfect\" | \"miss\";\n /** Damage dealt (if applicable) */\n readonly damage?: number;\n /** Whether effect is visible */\n readonly visible?: boolean;\n /** Callback when effect completes */\n readonly onComplete?: () => void;\n /** Duration in milliseconds */\n readonly duration?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n}\n\n/**\n * Impact particle system\n */\nconst ImpactParticles: React.FC<{\n position: [number, number, number];\n color: number;\n count: number;\n}> = ({ position, color, count }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n const [initialPosition] = useState(position);\n\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n const theta = seededRandom(i * 3) * Math.PI * 2;\n const phi = seededRandom(i * 3 + 1) * Math.PI;\n const speed = 0.5 + seededRandom(i * 3 + 2) * 1.5;\n\n tempVel.set(\n Math.sin(phi) * Math.cos(theta),\n Math.cos(phi),\n Math.sin(phi) * Math.sin(theta)\n );\n tempVel.normalize().multiplyScalar(speed);\n \n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n ThreeObjectPools.vector3.release(tempVel);\n }\n\n return { positions: pos, velocities: vel };\n }, [count, initialPosition]); // initialPosition is captured at mount and won't change\n\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n useFrame((_, delta) => {\n if (!pointsRef.current || !velocitiesRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n const vel = velocitiesRef.current;\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n\n array[i3] += vel[i3] * delta * 10;\n array[i3 + 1] += vel[i3 + 1] * delta * 10;\n array[i3 + 2] += vel[i3 + 2] * delta * 10;\n\n vel[i3 + 1] -= 9.8 * delta;\n\n if (array[i3 + 1] < -2) {\n array[i3 + 1] = -2;\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef} position={position}>\n <bufferGeometry>\n <bufferAttribute attach=\"attributes-position\" args={[positions, 3]} />\n </bufferGeometry>\n <pointsMaterial\n size={0.08}\n color={color}\n transparent\n opacity={1.0}\n sizeAttenuation\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </points>\n );\n};\n\n/**\n * Expanding ring effect\n */\nconst RingEffect: React.FC<{\n position: [number, number, number];\n color: number;\n maxRadius: number;\n}> = ({ position, color, maxRadius }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const startTime = useRef<number>(0);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 0.5, 1); // 0.5 second duration\n\n const radius = progress * maxRadius;\n const tempScale = ThreeObjectPools.vector3.acquire();\n try {\n tempScale.setScalar(radius);\n meshRef.current.scale.copy(tempScale);\n } finally {\n ThreeObjectPools.vector3.release(tempScale);\n }\n\n const material = meshRef.current.material as THREE.MeshBasicMaterial;\n material.opacity = 1 - progress;\n });\n\n return (\n <mesh ref={meshRef} position={position} rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[0.9, 1.0, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n />\n </mesh>\n );\n};\n\n/**\n * Floating damage number\n */\nconst DamageNumber: React.FC<{\n position: [number, number, number];\n damage: number;\n type: \"perfect\" | \"normal\" | \"miss\";\n isMobile: boolean;\n onComplete: () => void;\n}> = ({ position, damage, type, isMobile, onComplete }) => {\n const [offset, setOffset] = useState(0);\n const [opacity, setOpacity] = useState(1);\n const startTime = useRef<number>(0);\n const completedRef = useRef(false);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 1.5, 1); // 1.5 second duration\n\n setOffset(progress * 1);\n\n setOpacity(1 - progress);\n\n if (progress >= 1 && !completedRef.current && onComplete) {\n completedRef.current = true;\n onComplete();\n }\n });\n\n const color =\n type === \"perfect\" ? \"#ffd700\" : type === \"normal\" ? \"#00ffff\" : \"#ff4444\";\n const text = type === \"miss\" ? \"빗나감 | MISS\" : `${damage}`; // Korean: 빗나감 = miss/deflected\n\n return (\n <Html\n position={[position[0], position[1] + offset, position[2]]}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\", opacity }}\n >\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: \"0 0 10px rgba(0, 0, 0, 0.8)\",\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"damage-number\"\n >\n {text}\n </div>\n </Html>\n );\n};\n\n/**\n * HitFeedbackEffect3D Component\n * Main hit feedback visualization\n */\nexport const HitFeedbackEffect3D: React.FC<HitFeedbackEffect3DProps> = ({\n position,\n type,\n damage,\n visible = true,\n onComplete,\n duration = 1500,\n isMobile = false,\n}) => {\n const [showEffect, setShowEffect] = useState(visible);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const effectColor = useMemo(() => {\n switch (type) {\n case \"perfect\":\n return KOREAN_COLORS.ACCENT_GOLD;\n case \"success\":\n return KOREAN_COLORS.PRIMARY_CYAN;\n case \"miss\":\n return KOREAN_COLORS.TEXT_SECONDARY;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n }, [type]);\n\n const particleCount = useMemo(() => {\n const isPerfect = type === \"perfect\";\n const isSuccess = type === \"success\";\n\n if (isMobile) {\n return isPerfect ? 30 : isSuccess ? 20 : 10;\n }\n\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n const completedRef = useRef(false);\n\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n useEffect(() => {\n if (visible && showEffect) {\n timeoutRef.current = setTimeout(handleComplete, duration);\n }\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [visible, showEffect, duration, handleComplete]);\n\n if (!showEffect) return null;\n\n return (\n <group name={`hit-feedback-${type}`}>\n {/* Particle burst */}\n <ImpactParticles\n position={position}\n color={effectColor}\n count={particleCount}\n />\n\n {/* Expanding ring */}\n <RingEffect\n position={position}\n color={effectColor}\n maxRadius={ringRadius}\n />\n\n {/* Floating damage number */}\n {damage !== undefined && type !== \"miss\" && (\n <DamageNumber\n position={position}\n damage={damage}\n type={type === \"perfect\" ? \"perfect\" : \"normal\"}\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n\n {/* Miss indicator */}\n {type === \"miss\" && (\n <DamageNumber\n position={position}\n damage={0}\n type=\"miss\"\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n </group>\n );\n};\n\nexport default HitFeedbackEffect3D;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TrainingButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingButtons - Reusable button components for TrainingScreen\n * \n * Provides return-to-menu button and archetype selection buttons.\n * Extracted from TrainingScreen3D to reduce code duplication.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련버튼\n */\n\nimport React, { useCallback } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface ReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from training screen.\n * Uses BaseButtonOverlayHtml for consistent Korean theming.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <ReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const ReturnToMenuButton: React.FC<ReturnToMenuButtonProps> = ({\n onClick,\n onMouseEnter,\n isMobile,\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={isMobile ? \"메뉴\" : \"메뉴로\"}\n english={isMobile ? \"Menu\" : \"Return to Menu\"}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId=\"return-to-menu-button\"\n />\n );\n};\n\nexport interface ArchetypeSelectionButtonsProps {\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Callback when archetype is selected */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ArchetypeSelectionButtons Component\n * \n * Grid of buttons to select player archetype during training.\n * Highlights selected archetype with gold background.\n * \n * Reduces code duplication by 35 lines from TrainingScreen3D (inline button logic)\n * \n * @example\n * ```tsx\n * <ArchetypeSelectionButtons\n * selectedArchetype={PlayerArchetype.MUSA}\n * onArchetypeSelect={(arch) => setArchetype(arch)}\n * onPlaySFX={(sound) => audio.play(sound)}\n * isMobile={false}\n * />\n * ```\n */\nexport const ArchetypeSelectionButtons: React.FC<\n ArchetypeSelectionButtonsProps\n> = ({ selectedArchetype, onArchetypeSelect, onPlaySFX, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n\n const handleArchetypeClick = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeSelect(archetype);\n onPlaySFX?.(\"menu_select\");\n },\n [onArchetypeSelect, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"4px\",\n justifyContent: \"center\",\n }}\n data-testid=\"archetype-selection-buttons\"\n >\n {Object.values(PlayerArchetype).map((arch) => (\n <button\n key={arch}\n onClick={() => handleArchetypeClick(arch)}\n style={{\n padding: isMobile ? \"4px 8px\" : \"6px 10px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n fontWeight: selectedArchetype === arch ? \"bold\" : \"normal\",\n background:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8)\n : hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, 0.8),\n color:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(theme.colors.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n selectedArchetype === arch\n ? theme.colors.ACCENT_GOLD\n : theme.colors.UI_BORDER,\n 0.6,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n data-testid={`archetype-button-${arch}`}\n aria-label={`Select ${arch} archetype`}\n aria-pressed={selectedArchetype === arch}\n >\n {arch.toUpperCase()}\n </button>\n ))}\n </div>\n );\n};\n\nexport default { ReturnToMenuButton, ArchetypeSelectionButtons };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,sBAAyD,EACpE,SACA,cACA,eACI;CACJ,OACE,oBAAC,uBAAD;EACE,QAAQ,WAAW,OAAO;EAC1B,SAAS,WAAW,SAAS;EACpB;EACK;EACd,SAAQ;EACR,MAAK;EACK;EACV,QAAO;CACR,CAAA;AAEL;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,6BAER,EAAE,mBAAmB,mBAAmB,WAAW,eAAe;CACrE,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;CAAS,CAAC;CAEzE,MAAM,uBAAuB,aAC1B,cAA+B;EAC9B,kBAAkB,SAAS;EAC3B,YAAY,aAAa;CAC3B,GACA,CAAC,mBAAmB,SAAS,CAC/B;CAEA,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,UAAU;GACV,KAAK;GACL,gBAAgB;EAClB;EACA,eAAY;YAEX,OAAO,OAAO,eAAe,EAAE,KAAK,SACnC,oBAAC,UAAD;GAEE,eAAe,qBAAqB,IAAI;GACxC,OAAO;IACL,SAAS,WAAW,YAAY;IAChC,UAAU,WAAW,QAAQ;IAC7B,YAAY,MAAM,iBAAiB;IACnC,YAAY,sBAAsB,OAAO,SAAS;IAClD,YACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,aAAa,EAAG,IAC7C,gBAAgB,MAAM,OAAO,sBAAsB,EAAG;IAC5D,OACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,oBAAoB,CAAC,IAClD,gBAAgB,MAAM,OAAO,cAAc,CAAC;IAClD,QAAQ,aAAa,gBACnB,sBAAsB,OAClB,MAAM,OAAO,cACb,MAAM,OAAO,WACjB,EACF;IACA,cAAc;IACd,QAAQ;IACR,YAAY;GACd;GACA,eAAa,oBAAoB;GACjC,cAAY,UAAU,KAAK;GAC3B,gBAAc,sBAAsB;aAEnC,KAAK,YAAY;EACZ,GA9BD,IA8BC,CACT;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"TrainingButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingButtons - Reusable button components for TrainingScreen\n * \n * Provides return-to-menu button and archetype selection buttons.\n * Extracted from TrainingScreen3D to reduce code duplication.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련버튼\n */\n\nimport React, { useCallback } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface ReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from training screen.\n * Uses BaseButtonOverlayHtml for consistent Korean theming.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <ReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const ReturnToMenuButton: React.FC<ReturnToMenuButtonProps> = ({\n onClick,\n onMouseEnter,\n isMobile,\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={isMobile ? \"메뉴\" : \"메뉴로\"}\n english={isMobile ? \"Menu\" : \"Return to Menu\"}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId=\"return-to-menu-button\"\n />\n );\n};\n\nexport interface ArchetypeSelectionButtonsProps {\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Callback when archetype is selected */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ArchetypeSelectionButtons Component\n * \n * Grid of buttons to select player archetype during training.\n * Highlights selected archetype with gold background.\n * \n * Reduces code duplication by 35 lines from TrainingScreen3D (inline button logic)\n * \n * @example\n * ```tsx\n * <ArchetypeSelectionButtons\n * selectedArchetype={PlayerArchetype.MUSA}\n * onArchetypeSelect={(arch) => setArchetype(arch)}\n * onPlaySFX={(sound) => audio.play(sound)}\n * isMobile={false}\n * />\n * ```\n */\nexport const ArchetypeSelectionButtons: React.FC<\n ArchetypeSelectionButtonsProps\n> = ({ selectedArchetype, onArchetypeSelect, onPlaySFX, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n\n const handleArchetypeClick = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeSelect(archetype);\n onPlaySFX?.(\"menu_select\");\n },\n [onArchetypeSelect, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"4px\",\n justifyContent: \"center\",\n }}\n data-testid=\"archetype-selection-buttons\"\n >\n {Object.values(PlayerArchetype).map((arch) => (\n <button\n key={arch}\n onClick={() => handleArchetypeClick(arch)}\n style={{\n padding: isMobile ? \"4px 8px\" : \"6px 10px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n fontWeight: selectedArchetype === arch ? \"bold\" : \"normal\",\n background:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8)\n : hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, 0.8),\n color:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(theme.colors.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n selectedArchetype === arch\n ? theme.colors.ACCENT_GOLD\n : theme.colors.UI_BORDER,\n 0.6,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n data-testid={`archetype-button-${arch}`}\n aria-label={`Select ${arch} archetype`}\n aria-pressed={selectedArchetype === arch}\n >\n {arch.toUpperCase()}\n </button>\n ))}\n </div>\n );\n};\n\nexport default { ReturnToMenuButton, ArchetypeSelectionButtons };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,sBAAyD,EACpE,SACA,cACA,eACI;CACJ,OACE,oBAAC,uBAAD;EACE,QAAQ,WAAW,OAAO;EAC1B,SAAS,WAAW,SAAS;EACpB;EACK;EACd,SAAQ;EACR,MAAK;EACK;EACV,QAAO;CACR,CAAA;AAEL;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,6BAER,EAAE,mBAAmB,mBAAmB,WAAW,eAAe;CACrE,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;CAAS,CAAC;CAEzE,MAAM,uBAAuB,aAC1B,cAA+B;EAC9B,kBAAkB,SAAS;EAC3B,YAAY,aAAa;CAC3B,GACA,CAAC,mBAAmB,SAAS,CAC/B;CAEA,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,UAAU;GACV,KAAK;GACL,gBAAgB;EAClB;EACA,eAAY;YAEX,OAAO,OAAO,eAAe,CAAC,CAAC,KAAK,SACnC,oBAAC,UAAD;GAEE,eAAe,qBAAqB,IAAI;GACxC,OAAO;IACL,SAAS,WAAW,YAAY;IAChC,UAAU,WAAW,QAAQ;IAC7B,YAAY,MAAM,iBAAiB;IACnC,YAAY,sBAAsB,OAAO,SAAS;IAClD,YACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,aAAa,EAAG,IAC7C,gBAAgB,MAAM,OAAO,sBAAsB,EAAG;IAC5D,OACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,oBAAoB,CAAC,IAClD,gBAAgB,MAAM,OAAO,cAAc,CAAC;IAClD,QAAQ,aAAa,gBACnB,sBAAsB,OAClB,MAAM,OAAO,cACb,MAAM,OAAO,WACjB,EACF;IACA,cAAc;IACd,QAAQ;IACR,YAAY;GACd;GACA,eAAa,oBAAoB;GACjC,cAAY,UAAU,KAAK;GAC3B,gBAAc,sBAAsB;aAEnC,KAAK,YAAY;EACZ,GA9BD,IA8BC,CACT;CACE,CAAA;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"TrainingDummy3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingDummy3D.tsx"],"sourcesContent":["/**\n * TrainingDummy3D - 3D training dummy with vital points\n *\n * Provides anatomically accurate training dummy using SkeletalPlayer3D\n * for Korean martial arts practice. Supports anatomy overlays and difficulty modes.\n *\n * Refactored to extend SkeletalPlayer3D for visual consistency with player characters.\n *\n * @module components/screens/training/TrainingDummy3D\n * @category 3D Components\n * @korean 훈련인형3D컴포넌트\n */\n\nimport { 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 { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport SkeletalPlayer3D from \"../../../shared/three/models/SkeletalPlayer3D\";\nimport VitalPointMarker3D from \"./VitalPointMarker3D\";\n\n/**\n * Difficulty mode for training\n * @korean 난이도모드\n */\nexport type DifficultyMode = \"easy\" | \"normal\" | \"hard\";\n\n/**\n * Props for TrainingDummy3D component\n * @korean 훈련인형3D속성\n */\nexport interface TrainingDummy3DProps {\n /** 3D world position of the dummy */\n readonly position: [number, number, number];\n /** Currently selected vital point for targeting */\n readonly selectedVitalPoint: string | null;\n /** Whether training is active */\n readonly isTraining: boolean;\n /** Current health of dummy (0-100) */\n readonly health?: number;\n /** Maximum health of dummy */\n readonly maxHealth?: number;\n /** Callback when vital point is hit */\n readonly onVitalPointHit?: (vitalPointId: string) => void;\n /** Callback when dummy is defeated */\n readonly onDefeated?: () => void;\n /** Difficulty mode (affects marker sizes) */\n readonly difficulty?: DifficultyMode;\n /** Number of vital points to display (3-70) */\n readonly vitalPointCount?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Archetype to display (affects body type and appearance) */\n readonly archetype?: PlayerArchetype;\n /** Current stance for the dummy */\n readonly stance?: TrigramStance;\n}\n\n/**\n * Map body region to 3D position on dummy\n *\n * Positions are calibrated for SkeletalPlayer3D bone structure.\n *\n * @param pointId - Unique identifier for the vital point\n * @param category - Anatomical category (head, neck, torso, etc.)\n * @returns 3D coordinates [x, y, z] relative to dummy center\n * @korean 급소위치계산\n */\nconst getVitalPointPosition = (\n pointId: string,\n category: string,\n): [number, number, number] => {\n const positions: Record<string, [number, number, number]> = {\n head: [0, 1.75, 0.12], // Head bone + offset\n neck: [0, 1.5, 0.1], // Neck bone\n torso: [0, 1.15, 0.18], // Chest/spine area\n chest: [0, 1.2, 0.2], // Upper chest\n abdomen: [0, 0.85, 0.18], // Lower torso\n back: [0, 1.1, -0.15], // Spine back\n arm: [-0.45, 1.15, 0], // Upper arm area\n leg: [-0.18, 0.4, 0.05], // Thigh area\n neurological: [0, 1.7, 0.08], // Temple/head neural points\n vascular: [0, 1.45, 0.12], // Neck vascular points\n muscular: [0, 1.1, 0.2], // Muscle groups\n skeletal: [0, 0.9, 0.15], // Joints/bones\n };\n\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n const hash =\n pointId.split(\"\").reduce((acc, char) => acc + char.charCodeAt(0), 0) *\n 0.001;\n return [\n basePos[0] + Math.sin(hash * 7.3) * 0.08,\n basePos[1] + Math.cos(hash * 5.1) * 0.08,\n basePos[2] + Math.sin(hash * 3.7) * 0.03,\n ];\n};\n\n/**\n * Health bar component for training dummy\n *\n * Displays above the dummy with Korean cyberpunk styling.\n * @korean 훈련인형체력바\n */\n\nconst HEALTH_BAR_WIDTH = 1.2;\nconst HEALTH_BAR_HEIGHT = 0.1;\n\nconst DummyHealthBar: React.FC<{\n readonly health: number;\n readonly maxHealth: number;\n readonly position: [number, number, number];\n}> = ({ health, maxHealth, position }) => {\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n const bgGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const healthGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const borderGeometry = useMemo(\n () =>\n new THREE.BoxGeometry(\n HEALTH_BAR_WIDTH + 0.04,\n HEALTH_BAR_HEIGHT + 0.04,\n 0.01,\n ),\n [],\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 70) return KOREAN_COLORS.HEALTH_FULL;\n if (healthPercent > 40) return KOREAN_COLORS.HEALTH_MEDIUM;\n if (healthPercent > 20) return KOREAN_COLORS.HEALTH_LOW;\n return KOREAN_COLORS.HEALTH_CRITICAL;\n }, [healthPercent]);\n\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n useEffect(() => {\n return () => {\n bgGeometry.dispose();\n healthGeometry.dispose();\n borderGeometry.dispose();\n };\n }, [bgGeometry, healthGeometry, borderGeometry]);\n\n return (\n <group position={position} name=\"dummy-health-bar\">\n {/* Background bar */}\n <mesh>\n <primitive object={bgGeometry} />\n <meshBasicMaterial\n color={KOREAN_COLORS.UI_BACKGROUND_DARK}\n transparent\n opacity={0.7}\n />\n </mesh>\n\n {/* Health bar (scaled based on health, anchored to left edge) */}\n <mesh\n position={[\n -(HEALTH_BAR_WIDTH * (1 - healthPercent / 100)) / 2,\n 0,\n 0.01,\n ]}\n scale={healthScale}\n >\n <primitive object={healthGeometry} />\n <meshBasicMaterial color={healthColor} transparent opacity={0.9} />\n </mesh>\n\n {/* Border frame - outline using EdgesGeometry for crisp border */}\n <lineSegments>\n <edgesGeometry args={[borderGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.8}\n />\n </lineSegments>\n </group>\n );\n};\n\n/**\n * TrainingDummy3D Component\n *\n * Main training dummy using SkeletalPlayer3D for consistent visual appearance.\n * Displays vital points for martial arts practice with difficulty-based sizing.\n *\n * @example\n * ```tsx\n * <TrainingDummy3D\n * position={[0, 0, 5]}\n * selectedVitalPoint=\"temple\"\n * isTraining={true}\n * health={75}\n * difficulty=\"normal\"\n * onVitalPointHit={(id) => console.log(`Hit ${id}`)}\n * />\n * ```\n *\n * @korean 훈련인형3D컴포넌트\n */\nexport const TrainingDummy3D: React.FC<TrainingDummy3DProps> = ({\n position,\n selectedVitalPoint,\n isTraining,\n health = 100,\n maxHealth = 100,\n onVitalPointHit,\n onDefeated,\n difficulty = \"normal\",\n vitalPointCount = 12,\n isMobile = false,\n archetype = PlayerArchetype.MUSA,\n stance = TrigramStance.GEON,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n const [isStunned, setIsStunned] = useState(false);\n\n const vitalPoints = useMemo(\n () =>\n KOREAN_VITAL_POINTS.slice(\n 0,\n Math.min(vitalPointCount, KOREAN_VITAL_POINTS.length),\n ),\n [vitalPointCount],\n );\n\n const sizeMultiplier = useMemo(() => {\n switch (difficulty) {\n case \"easy\":\n return 1.5; // Larger targets\n case \"normal\":\n return 1.0; // Standard size\n case \"hard\":\n return 0.7; // Smaller targets\n default:\n return 1.0;\n }\n }, [difficulty]);\n\n const prevHealthRef = useRef(health);\n\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n useEffect(() => {\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n if (prevHealthRef.current - health > 20) {\n setIsStunned(true);\n const timer = setTimeout(() => setIsStunned(false), 500);\n return () => clearTimeout(timer);\n }\n\n prevHealthRef.current = health;\n return undefined;\n }, [health]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n const breathScale = Math.sin(time * 1.5) * 0.01 + 1;\n groupRef.current.scale.y = breathScale;\n\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n const handlePointHit = useCallback(\n (pointId: string) => {\n onVitalPointHit?.(pointId);\n },\n [onVitalPointHit],\n );\n\n return (\n <group ref={groupRef} position={position} name=\"training-dummy-3d\">\n {/* SkeletalPlayer3D as the base character model */}\n <SkeletalPlayer3D\n playerId=\"training-dummy\"\n archetype={archetype}\n stance={stance}\n position={[0, 0, 0]}\n rotation={Math.PI} // Face the player\n facing=\"left\"\n health={health}\n maxHealth={maxHealth}\n stamina={100}\n ki={50}\n balance=\"READY\"\n pain={0}\n consciousness={100}\n isMobile={isMobile}\n currentAnimation=\"idle\"\n isBlocking={false}\n isStunned={isStunned}\n showHealthBar={false} // We use custom health bar\n showStanceIndicator={false}\n enableFacialExpressions={true}\n enableEyeTracking={true}\n />\n\n {/* Vital point markers overlaid on the skeletal model */}\n {vitalPoints.map((point) => (\n <group\n key={point.id}\n position={getVitalPointPosition(point.id, point.category)}\n >\n <VitalPointMarker3D\n vitalPoint={point}\n isSelected={point.id === selectedVitalPoint}\n isTraining={isTraining}\n isMobile={isMobile}\n onHit={handlePointHit}\n sizeMultiplier={sizeMultiplier}\n />\n </group>\n ))}\n\n {/* Health bar above dummy */}\n {isTraining && (\n <DummyHealthBar\n health={health}\n maxHealth={maxHealth}\n position={[0, 2.3, 0]}\n />\n )}\n\n {/* Training mode indicator glow */}\n {isTraining && (\n <pointLight\n position={[0, 1.2, 0.5]}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n intensity={0.3}\n distance={3}\n decay={2}\n />\n )}\n </group>\n );\n};\n\nexport default TrainingDummy3D;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TrainingDummy3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingDummy3D.tsx"],"sourcesContent":["/**\n * TrainingDummy3D - 3D training dummy with vital points\n *\n * Provides anatomically accurate training dummy using SkeletalPlayer3D\n * for Korean martial arts practice. Supports anatomy overlays and difficulty modes.\n *\n * Refactored to extend SkeletalPlayer3D for visual consistency with player characters.\n *\n * @module components/screens/training/TrainingDummy3D\n * @category 3D Components\n * @korean 훈련인형3D컴포넌트\n */\n\nimport { 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 { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport SkeletalPlayer3D from \"../../../shared/three/models/SkeletalPlayer3D\";\nimport VitalPointMarker3D from \"./VitalPointMarker3D\";\n\n/**\n * Difficulty mode for training\n * @korean 난이도모드\n */\nexport type DifficultyMode = \"easy\" | \"normal\" | \"hard\";\n\n/**\n * Props for TrainingDummy3D component\n * @korean 훈련인형3D속성\n */\nexport interface TrainingDummy3DProps {\n /** 3D world position of the dummy */\n readonly position: [number, number, number];\n /** Currently selected vital point for targeting */\n readonly selectedVitalPoint: string | null;\n /** Whether training is active */\n readonly isTraining: boolean;\n /** Current health of dummy (0-100) */\n readonly health?: number;\n /** Maximum health of dummy */\n readonly maxHealth?: number;\n /** Callback when vital point is hit */\n readonly onVitalPointHit?: (vitalPointId: string) => void;\n /** Callback when dummy is defeated */\n readonly onDefeated?: () => void;\n /** Difficulty mode (affects marker sizes) */\n readonly difficulty?: DifficultyMode;\n /** Number of vital points to display (3-70) */\n readonly vitalPointCount?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Archetype to display (affects body type and appearance) */\n readonly archetype?: PlayerArchetype;\n /** Current stance for the dummy */\n readonly stance?: TrigramStance;\n}\n\n/**\n * Map body region to 3D position on dummy\n *\n * Positions are calibrated for SkeletalPlayer3D bone structure.\n *\n * @param pointId - Unique identifier for the vital point\n * @param category - Anatomical category (head, neck, torso, etc.)\n * @returns 3D coordinates [x, y, z] relative to dummy center\n * @korean 급소위치계산\n */\nconst getVitalPointPosition = (\n pointId: string,\n category: string,\n): [number, number, number] => {\n const positions: Record<string, [number, number, number]> = {\n head: [0, 1.75, 0.12], // Head bone + offset\n neck: [0, 1.5, 0.1], // Neck bone\n torso: [0, 1.15, 0.18], // Chest/spine area\n chest: [0, 1.2, 0.2], // Upper chest\n abdomen: [0, 0.85, 0.18], // Lower torso\n back: [0, 1.1, -0.15], // Spine back\n arm: [-0.45, 1.15, 0], // Upper arm area\n leg: [-0.18, 0.4, 0.05], // Thigh area\n neurological: [0, 1.7, 0.08], // Temple/head neural points\n vascular: [0, 1.45, 0.12], // Neck vascular points\n muscular: [0, 1.1, 0.2], // Muscle groups\n skeletal: [0, 0.9, 0.15], // Joints/bones\n };\n\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n const hash =\n pointId.split(\"\").reduce((acc, char) => acc + char.charCodeAt(0), 0) *\n 0.001;\n return [\n basePos[0] + Math.sin(hash * 7.3) * 0.08,\n basePos[1] + Math.cos(hash * 5.1) * 0.08,\n basePos[2] + Math.sin(hash * 3.7) * 0.03,\n ];\n};\n\n/**\n * Health bar component for training dummy\n *\n * Displays above the dummy with Korean cyberpunk styling.\n * @korean 훈련인형체력바\n */\n\nconst HEALTH_BAR_WIDTH = 1.2;\nconst HEALTH_BAR_HEIGHT = 0.1;\n\nconst DummyHealthBar: React.FC<{\n readonly health: number;\n readonly maxHealth: number;\n readonly position: [number, number, number];\n}> = ({ health, maxHealth, position }) => {\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n const bgGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const healthGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const borderGeometry = useMemo(\n () =>\n new THREE.BoxGeometry(\n HEALTH_BAR_WIDTH + 0.04,\n HEALTH_BAR_HEIGHT + 0.04,\n 0.01,\n ),\n [],\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 70) return KOREAN_COLORS.HEALTH_FULL;\n if (healthPercent > 40) return KOREAN_COLORS.HEALTH_MEDIUM;\n if (healthPercent > 20) return KOREAN_COLORS.HEALTH_LOW;\n return KOREAN_COLORS.HEALTH_CRITICAL;\n }, [healthPercent]);\n\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n useEffect(() => {\n return () => {\n bgGeometry.dispose();\n healthGeometry.dispose();\n borderGeometry.dispose();\n };\n }, [bgGeometry, healthGeometry, borderGeometry]);\n\n return (\n <group position={position} name=\"dummy-health-bar\">\n {/* Background bar */}\n <mesh>\n <primitive object={bgGeometry} />\n <meshBasicMaterial\n color={KOREAN_COLORS.UI_BACKGROUND_DARK}\n transparent\n opacity={0.7}\n />\n </mesh>\n\n {/* Health bar (scaled based on health, anchored to left edge) */}\n <mesh\n position={[\n -(HEALTH_BAR_WIDTH * (1 - healthPercent / 100)) / 2,\n 0,\n 0.01,\n ]}\n scale={healthScale}\n >\n <primitive object={healthGeometry} />\n <meshBasicMaterial color={healthColor} transparent opacity={0.9} />\n </mesh>\n\n {/* Border frame - outline using EdgesGeometry for crisp border */}\n <lineSegments>\n <edgesGeometry args={[borderGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.8}\n />\n </lineSegments>\n </group>\n );\n};\n\n/**\n * TrainingDummy3D Component\n *\n * Main training dummy using SkeletalPlayer3D for consistent visual appearance.\n * Displays vital points for martial arts practice with difficulty-based sizing.\n *\n * @example\n * ```tsx\n * <TrainingDummy3D\n * position={[0, 0, 5]}\n * selectedVitalPoint=\"temple\"\n * isTraining={true}\n * health={75}\n * difficulty=\"normal\"\n * onVitalPointHit={(id) => console.log(`Hit ${id}`)}\n * />\n * ```\n *\n * @korean 훈련인형3D컴포넌트\n */\nexport const TrainingDummy3D: React.FC<TrainingDummy3DProps> = ({\n position,\n selectedVitalPoint,\n isTraining,\n health = 100,\n maxHealth = 100,\n onVitalPointHit,\n onDefeated,\n difficulty = \"normal\",\n vitalPointCount = 12,\n isMobile = false,\n archetype = PlayerArchetype.MUSA,\n stance = TrigramStance.GEON,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n const [isStunned, setIsStunned] = useState(false);\n\n const vitalPoints = useMemo(\n () =>\n KOREAN_VITAL_POINTS.slice(\n 0,\n Math.min(vitalPointCount, KOREAN_VITAL_POINTS.length),\n ),\n [vitalPointCount],\n );\n\n const sizeMultiplier = useMemo(() => {\n switch (difficulty) {\n case \"easy\":\n return 1.5; // Larger targets\n case \"normal\":\n return 1.0; // Standard size\n case \"hard\":\n return 0.7; // Smaller targets\n default:\n return 1.0;\n }\n }, [difficulty]);\n\n const prevHealthRef = useRef(health);\n\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n useEffect(() => {\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n if (prevHealthRef.current - health > 20) {\n setIsStunned(true);\n const timer = setTimeout(() => setIsStunned(false), 500);\n return () => clearTimeout(timer);\n }\n\n prevHealthRef.current = health;\n return undefined;\n }, [health]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n const breathScale = Math.sin(time * 1.5) * 0.01 + 1;\n groupRef.current.scale.y = breathScale;\n\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n const handlePointHit = useCallback(\n (pointId: string) => {\n onVitalPointHit?.(pointId);\n },\n [onVitalPointHit],\n );\n\n return (\n <group ref={groupRef} position={position} name=\"training-dummy-3d\">\n {/* SkeletalPlayer3D as the base character model */}\n <SkeletalPlayer3D\n playerId=\"training-dummy\"\n archetype={archetype}\n stance={stance}\n position={[0, 0, 0]}\n rotation={Math.PI} // Face the player\n facing=\"left\"\n health={health}\n maxHealth={maxHealth}\n stamina={100}\n ki={50}\n balance=\"READY\"\n pain={0}\n consciousness={100}\n isMobile={isMobile}\n currentAnimation=\"idle\"\n isBlocking={false}\n isStunned={isStunned}\n showHealthBar={false} // We use custom health bar\n showStanceIndicator={false}\n enableFacialExpressions={true}\n enableEyeTracking={true}\n />\n\n {/* Vital point markers overlaid on the skeletal model */}\n {vitalPoints.map((point) => (\n <group\n key={point.id}\n position={getVitalPointPosition(point.id, point.category)}\n >\n <VitalPointMarker3D\n vitalPoint={point}\n isSelected={point.id === selectedVitalPoint}\n isTraining={isTraining}\n isMobile={isMobile}\n onHit={handlePointHit}\n sizeMultiplier={sizeMultiplier}\n />\n </group>\n ))}\n\n {/* Health bar above dummy */}\n {isTraining && (\n <DummyHealthBar\n health={health}\n maxHealth={maxHealth}\n position={[0, 2.3, 0]}\n />\n )}\n\n {/* Training mode indicator glow */}\n {isTraining && (\n <pointLight\n position={[0, 1.2, 0.5]}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n intensity={0.3}\n distance={3}\n decay={2}\n />\n )}\n </group>\n );\n};\n\nexport default TrainingDummy3D;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TrainingModeSelectorOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingModeSelectorOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingModeSelectorOverlayHtml - Html overlay for training mode selection\n *\n * Allows switching between different training modes with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련모드선택오버레이\n */\n\nimport React 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 {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Training mode types\n */\nexport type TrainingMode =\n | \"basics\" // Basic Training - Simple striking practice\n | \"advanced\" // Advanced Training - Vital point precision\n | \"free\" // Free Practice - Open combat vs AI\n | \"stance_training\" // Stance Training - Practice 8 trigrams\n | \"vital_point\" // Vital Point Training - Precision targeting\n | \"combo_practice\" // Combo Practice - Multi-hit techniques\n | \"footwork\"; // Footwork Training - Movement drills (보법 훈련)\n\n/**\n * Props for TrainingModeSelectorOverlayHtml component\n */\nexport interface TrainingModeSelectorOverlayHtmlProps {\n /** Currently selected training mode */\n readonly currentMode: TrainingMode;\n /** Callback when mode changes */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Mode information\n */\nconst MODE_INFO: Record<\n TrainingMode,\n { korean: string; english: string; description: string }\n> = {\n basics: {\n korean: \"기본 훈련\",\n english: \"Basic Training\",\n description: \"기초 타격 연습 | Basic striking practice\",\n },\n advanced: {\n korean: \"고급 훈련\",\n english: \"Advanced Training\",\n description: \"급소 정밀 타격 | Vital point precision\",\n },\n free: {\n korean: \"자유 훈련\",\n english: \"Free Practice\",\n description: \"AI 대련 | Combat vs AI opponent\",\n },\n stance_training: {\n korean: \"팔괘 수련\",\n english: \"Stance Training\",\n description: \"팔괘 전환 연습 | Eight trigrams mastery\",\n },\n vital_point: {\n korean: \"급소 훈련\",\n english: \"Vital Point Training\",\n description: \"정밀 타격 연습 | Precision targeting drill\",\n },\n combo_practice: {\n korean: \"연속 기술\",\n english: \"Combo Practice\",\n description: \"연속 타격 훈련 | Multi-hit techniques\",\n },\n footwork: {\n korean: \"보법 훈련\",\n english: \"Footwork Training\",\n description: \"보법 수련 | Movement drills\",\n },\n};\n\n/**\n * TrainingModeSelectorOverlayHtml Component\n *\n * Html overlay for selecting training mode with Korean theming.\n * Compact horizontal grid layout optimized for desktop and mobile.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when currentMode hasn't changed\n * - Callback expected to be stable (parent should use useCallback)\n *\n * @example\n * ```tsx\n * <TrainingModeSelectorOverlayHtml\n * currentMode=\"vital_point\"\n * onModeChange={(mode) => console.log(mode)}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련모드선택오버레이컴포넌트\n */\nexport const TrainingModeSelectorOverlayHtml =\n React.memo<TrainingModeSelectorOverlayHtmlProps>(\n ({ currentMode, onModeChange, isMobile }) => {\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.9,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n const titleFontSize = isMobile ? 13 : 15;\n const descFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-mode-selector-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${SPACING.SM}px`, textAlign: \"center\" }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.PRIMARY_CYAN,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 모드\", \"Training Mode\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: `${descFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontStyle: \"italic\",\n marginTop: \"2px\",\n minHeight: \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {MODE_INFO[currentMode].description}\n </div>\n </div>\n\n {/* Mode Buttons - Grid layout (2 columns to fit narrow side HUD) */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: `${gap}px`,\n }}\n >\n {(Object.keys(MODE_INFO) as TrainingMode[]).map((mode) => {\n const isSelected = mode === currentMode;\n const info = MODE_INFO[mode];\n\n return (\n <button\n key={mode}\n onClick={() => onModeChange(mode)}\n className={`mode-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n textAlign: \"center\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n minHeight: isMobile ? \"45px\" : \"50px\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid={`mode-${mode}`}\n >\n <div\n style={{\n fontWeight: \"bold\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_PRIMARY,\n ),\n fontSize: isMobile ? \"11px\" : \"12px\",\n marginBottom: \"2px\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.TEXT_TERTIARY,\n ),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.english}\n </div>\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.currentMode === nextProps.currentMode &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onModeChange === nextProps.onModeChange\n );\n },\n );\n\nTrainingModeSelectorOverlayHtml.displayName = \"TrainingModeSelectorOverlayHtml\";\n\nexport default TrainingModeSelectorOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqDA,IAAM,YAGF;CACF,QAAQ;EACN,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,UAAU;EACR,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,MAAM;EACJ,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,iBAAiB;EACf,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,aAAa;EACX,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,gBAAgB;EACd,QAAQ;EACR,SAAS;EACT,aAAa;CACf;CACA,UAAU;EACR,QAAQ;EACR,SAAS;EACT,aAAa;CACf;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kCACX,MAAM,MACH,EAAE,aAAa,cAAc,eAAe;CAC3C,MAAM,UAAU,qBAAqB,MAAM,QAAQ;CACnD,MAAM,MAAM,qBAAqB,MAAM,QAAQ;CAE/C,MAAM,aAAkC;EACtC,GAAG,+BAA+B;GAChC,SAAS;GACT,eAAe;GACf,iBAAiB;GACjB,qBAAqB;GACrB,aAAa;EACf,CAAC;EACD,OAAO;EACP,SAAS,GAAG,QAAQ;EACpB,WAAW;CACb;CAEA,MAAM,gBAAgB,WAAW,KAAK;CACtC,MAAM,eAAe,WAAW,IAAI;CAEpC,OACE,qBAAC,OAAD;EAAK,OAAO;EAAY,eAAY;YAApC,CAEE,qBAAC,OAAD;GAAK,OAAO;IAAE,cAAc,GAAG,QAAQ,GAAG;IAAK,WAAW;GAAS;aAAnE,CACE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,cAAc;KAC3B,YAAY;KACZ,OAAO,gBAAgB,cAAc,YAAY;KACjD,YAAY,YAAY;KACxB,YAAY,kBACV,cAAc,cACd,QACF;KACA,YAAY,oBAAoB,OAAO,QAAQ;IACjD;cAEC,oBAAoB,SAAS,iBAAiB,MAAM;GAClD,CAAA,GACL,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,aAAa;KAC1B,OAAO,gBAAgB,cAAc,aAAa;KAClD,WAAW;KACX,WAAW;KACX,WAAW;KACX,YAAY,YAAY;KACxB,YAAY,oBAAoB,OAAO,QAAQ;IACjD;cAEC,UAAU,aAAa;GACrB,CAAA,CACF;MAGL,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,qBAAqB;IACrB,KAAK,GAAG,IAAI;GACd;aAEE,OAAO,KAAK,SAAS,EAAqB,KAAK,SAAS;IACxD,MAAM,aAAa,SAAS;IAC5B,MAAM,OAAO,UAAU;IAEvB,OACE,qBAAC,UAAD;KAEE,eAAe,aAAa,IAAI;KAChC,WAAW,eAAe,aAAa,aAAa;KACpD,OAAO;MACL,SAAS,WAAW,YAAY;MAChC,WAAW;MACX,UAAU,WAAW,SAAS;MAC9B,WAAW,WAAW,SAAS;MAC/B,SAAS;MACT,eAAe;MACf,gBAAgB;MAChB,YAAY;MACZ,YAAY,YAAY;KAC1B;KACA,eAAa,QAAQ;eAfvB,CAiBE,oBAAC,OAAD;MACE,OAAO;OACL,YAAY;OACZ,OAAO,gBACL,aACI,cAAc,eACd,cAAc,YACpB;OACA,UAAU,WAAW,SAAS;OAC9B,cAAc;OACd,UAAU;OACV,cAAc;OACd,OAAO;OACP,UAAU;MACZ;gBAEC,KAAK;KACH,CAAA,GACL,oBAAC,OAAD;MACE,OAAO;OACL,UAAU,WAAW,QAAQ;OAC7B,OAAO,gBACL,aACI,cAAc,cACd,cAAc,aACpB;OACA,UAAU;OACV,cAAc;OACd,OAAO;OACP,UAAU;MACZ;gBAEC,KAAK;KACH,CAAA,CACC;OAlDD,IAkDC;GAEZ,CAAC;EACE,CAAA,CACF;;AAET,IACC,WAAW,cAAc;CACxB,OACE,UAAU,gBAAgB,UAAU,eACpC,UAAU,aAAa,UAAU,YACjC,UAAU,iBAAiB,UAAU;AAEzC,CACF;AAEF,gCAAgC,cAAc"}
1
+ {"version":3,"file":"TrainingModeSelectorOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingModeSelectorOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingModeSelectorOverlayHtml - Html overlay for training mode selection\n *\n * Allows switching between different training modes with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련모드선택오버레이\n */\n\nimport React 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 {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Training mode types\n */\nexport type TrainingMode =\n | \"basics\" // Basic Training - Simple striking practice\n | \"advanced\" // Advanced Training - Vital point precision\n | \"free\" // Free Practice - Open combat vs AI\n | \"stance_training\" // Stance Training - Practice 8 trigrams\n | \"vital_point\" // Vital Point Training - Precision targeting\n | \"combo_practice\" // Combo Practice - Multi-hit techniques\n | \"footwork\"; // Footwork Training - Movement drills (보법 훈련)\n\n/**\n * Props for TrainingModeSelectorOverlayHtml component\n */\nexport interface TrainingModeSelectorOverlayHtmlProps {\n /** Currently selected training mode */\n readonly currentMode: TrainingMode;\n /** Callback when mode changes */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Mode information\n */\nconst MODE_INFO: Record<\n TrainingMode,\n { korean: string; english: string; description: string }\n> = {\n basics: {\n korean: \"기본 훈련\",\n english: \"Basic Training\",\n description: \"기초 타격 연습 | Basic striking practice\",\n },\n advanced: {\n korean: \"고급 훈련\",\n english: \"Advanced Training\",\n description: \"급소 정밀 타격 | Vital point precision\",\n },\n free: {\n korean: \"자유 훈련\",\n english: \"Free Practice\",\n description: \"AI 대련 | Combat vs AI opponent\",\n },\n stance_training: {\n korean: \"팔괘 수련\",\n english: \"Stance Training\",\n description: \"팔괘 전환 연습 | Eight trigrams mastery\",\n },\n vital_point: {\n korean: \"급소 훈련\",\n english: \"Vital Point Training\",\n description: \"정밀 타격 연습 | Precision targeting drill\",\n },\n combo_practice: {\n korean: \"연속 기술\",\n english: \"Combo Practice\",\n description: \"연속 타격 훈련 | Multi-hit techniques\",\n },\n footwork: {\n korean: \"보법 훈련\",\n english: \"Footwork Training\",\n description: \"보법 수련 | Movement drills\",\n },\n};\n\n/**\n * TrainingModeSelectorOverlayHtml Component\n *\n * Html overlay for selecting training mode with Korean theming.\n * Compact horizontal grid layout optimized for desktop and mobile.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when currentMode hasn't changed\n * - Callback expected to be stable (parent should use useCallback)\n *\n * @example\n * ```tsx\n * <TrainingModeSelectorOverlayHtml\n * currentMode=\"vital_point\"\n * onModeChange={(mode) => console.log(mode)}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련모드선택오버레이컴포넌트\n */\nexport const TrainingModeSelectorOverlayHtml =\n React.memo<TrainingModeSelectorOverlayHtmlProps>(\n ({ currentMode, onModeChange, isMobile }) => {\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.9,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n const titleFontSize = isMobile ? 13 : 15;\n const descFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-mode-selector-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${SPACING.SM}px`, textAlign: \"center\" }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.PRIMARY_CYAN,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 모드\", \"Training Mode\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: `${descFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontStyle: \"italic\",\n marginTop: \"2px\",\n minHeight: \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {MODE_INFO[currentMode].description}\n </div>\n </div>\n\n {/* Mode Buttons - Grid layout (2 columns to fit narrow side HUD) */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: `${gap}px`,\n }}\n >\n {(Object.keys(MODE_INFO) as TrainingMode[]).map((mode) => {\n const isSelected = mode === currentMode;\n const info = MODE_INFO[mode];\n\n return (\n <button\n key={mode}\n onClick={() => onModeChange(mode)}\n className={`mode-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n textAlign: \"center\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n minHeight: isMobile ? \"45px\" : \"50px\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid={`mode-${mode}`}\n >\n <div\n style={{\n fontWeight: \"bold\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_PRIMARY,\n ),\n fontSize: isMobile ? \"11px\" : \"12px\",\n marginBottom: \"2px\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.TEXT_TERTIARY,\n ),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.english}\n </div>\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.currentMode === nextProps.currentMode &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onModeChange === nextProps.onModeChange\n );\n },\n );\n\nTrainingModeSelectorOverlayHtml.displayName = \"TrainingModeSelectorOverlayHtml\";\n\nexport default TrainingModeSelectorOverlayHtml;\n"],"mappings":"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"}
@@ -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":[],"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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"}