blacktrigram 0.7.39 → 0.7.41

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 (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +8 -8
@@ -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\n// Visual effect constants for bloom optimization\n// Note: Emissive intensity values are balanced for visual clarity and performance.\n// When applied to many simultaneous overlays/markers, emissive intensities above ~2.0 can\n// increase rendering cost and bloom pass overhead. VitalPointMarker3D intentionally uses\n// higher emissive values (up to ~3.5) for a small number of selected markers, which is\n// acceptable because the total marker count is low. For dense anatomy overlays, prefer\n// keeping emissive intensities at or below ~2.0 and consider implementing LOD or\n// distance-based emissive scaling if brighter values or higher object counts are needed.\n// See VitalPointMarker3D for a concrete example: it exposes a configurable\n// `maxEmissiveIntensity` prop that caps per-marker glow. Use higher caps there for a\n// small number of critical vital point markers, while keeping dense overlay layers like\n// those in AnatomyOverlay3D within the ~2.0 guideline to maintain 60fps performance.\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\n// Transmission constants for glass-like anatomy layers\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 // Memoize skull geometry to prevent recreating on every render\n // 두개골 기하학 메모화 | Skull geometry memoization\n const skullGeometry = useMemo(() => new THREE.SphereGeometry(0.25, 16, 16), []);\n\n // 자원 정리 | Resource cleanup - Dispose skull geometry on unmount\n useEffect(() => {\n return () => {\n skullGeometry.dispose();\n };\n }, [skullGeometry]);\n\n // Pulsing emissive animation for skeleton layer\n useFrame((state) => {\n if (!groupRef.current) return;\n\n // Enhanced pulse for better visibility (amplitude increased from 0.2 to 0.4)\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.4 + 0.6;\n\n // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Pulsing animation for nerve pathways\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 // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Pulsing animation simulating blood flow\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 // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Memoize layer visibility checks\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\n// Visual effect constants for bloom optimization\n// Note: Emissive intensity values are balanced for visual clarity and performance.\n// When applied to many simultaneous overlays/markers, emissive intensities above ~2.0 can\n// increase rendering cost and bloom pass overhead. VitalPointMarker3D intentionally uses\n// higher emissive values (up to ~3.5) for a small number of selected markers, which is\n// acceptable because the total marker count is low. For dense anatomy overlays, prefer\n// keeping emissive intensities at or below ~2.0 and consider implementing LOD or\n// distance-based emissive scaling if brighter values or higher object counts are needed.\n// See VitalPointMarker3D for a concrete example: it exposes a configurable\n// `maxEmissiveIntensity` prop that caps per-marker glow. Use higher caps there for a\n// small number of critical vital point markers, while keeping dense overlay layers like\n// those in AnatomyOverlay3D within the ~2.0 guideline to maintain 60fps performance.\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\n// Transmission constants for glass-like anatomy layers\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 // Memoize skull geometry to prevent recreating on every render\n // 두개골 기하학 메모화 | Skull geometry memoization\n const skullGeometry = useMemo(() => new THREE.SphereGeometry(0.25, 16, 16), []);\n\n // 자원 정리 | Resource cleanup - Dispose skull geometry on unmount\n useEffect(() => {\n return () => {\n skullGeometry.dispose();\n };\n }, [skullGeometry]);\n\n // Pulsing emissive animation for skeleton layer\n useFrame((state) => {\n if (!groupRef.current) return;\n\n // Enhanced pulse for better visibility (amplitude increased from 0.2 to 0.4)\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.4 + 0.6;\n\n // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Bone surface detail\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 // Pulsing animation for nerve pathways\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 // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Pulsing animation simulating blood flow\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 // Rebuild mesh cache each frame to ensure all meshes are captured\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 // Update all meshes with pulsing emissive\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 // Memoize layer visibility checks\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":"FootPlacementMarkers3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootPlacementMarkers3D.tsx"],"sourcesContent":["/**\n * FootPlacementMarkers3D - 3D markers showing foot placement for footwork drills\n * \n * Renders visual indicators on the ground to guide footwork training,\n * showing target positions for Korean martial arts footwork patterns.\n * \n * @module components/screens/training/components/FootPlacementMarkers3D\n * @category Training 3D Components\n * @korean 발위치표시3D컴포넌트\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { FootworkDrill } from \"./FootworkDrillsOverlayHtml\";\n\n/**\n * Footwork drill pattern types for 3D visualization\n * \n * Derived from FootworkDrill, excluding \"free_practice\" (which has no pattern),\n * and adding a \"none\" sentinel for cases with no active pattern.\n */\nexport type FootworkDrillPattern = \n | Exclude<FootworkDrill, \"free_practice\">\n | \"none\";\n\n/**\n * Props for FootPlacementMarkers3D component\n */\nexport interface FootPlacementMarkers3DProps {\n /** Center position for the pattern */\n readonly centerPosition?: [number, number, number];\n /** Current drill pattern to display */\n readonly pattern: FootworkDrillPattern;\n /** Current step in pattern (0-based) */\n readonly currentStep?: number;\n /** Whether markers are visible */\n readonly visible?: boolean;\n /** Scale factor for markers */\n readonly scale?: number;\n /** Whether to animate markers */\n readonly animated?: boolean;\n}\n\n/**\n * Generate footwork pattern positions\n * \n * @korean 보법패턴위치생성\n */\nfunction getPatternPositions(\n pattern: FootworkDrillPattern,\n center: [number, number, number]\n): Array<{ position: [number, number, number]; label: string }> {\n const [cx, cy, cz] = center;\n const radius = 2.0; // 2 meters radius for circular patterns\n const stepDist = 0.3; // 30cm step distance\n\n switch (pattern) {\n case \"circular_left\":\n // 4 positions in a circle (left rotation)\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz - radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz + radius], label: \"4\" },\n ];\n\n case \"circular_right\":\n // 4 positions in a circle (right rotation)\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz + radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz - radius], label: \"4\" },\n ];\n\n case \"pivot_combo\":\n // Pivot left and right positions\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx - 0.5, cy, cz + 0.5], label: \"Pivot L\" },\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx + 0.5, cy, cz + 0.5], label: \"Pivot R\" },\n ];\n\n case \"triangle_step\":\n // Triangle stepping pattern\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - stepDist * 2], label: \"Forward\" },\n { position: [cx + stepDist * 1.5, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz], label: \"Back\" },\n ];\n\n case \"slide_drill\":\n // Four-direction slide pattern (cross shape)\n return [\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx, cy, cz - stepDist], label: \"Forward\" },\n { position: [cx + stepDist, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz + stepDist], label: \"Back\" },\n { position: [cx - stepDist, cy, cz], label: \"Left\" },\n ];\n\n case \"shuffle_practice\":\n // Quick forward shuffles\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - 0.15], label: \"Shuffle 1\" },\n { position: [cx, cy, cz - 0.30], label: \"Shuffle 2\" },\n { position: [cx, cy, cz - 0.45], label: \"Shuffle 3\" },\n ];\n\n case \"none\":\n default:\n return [];\n }\n}\n\n/**\n * Single foot placement marker\n */\nconst FootMarker: React.FC<{\n position: [number, number, number];\n label: string;\n isActive: boolean;\n isCompleted: boolean;\n animated: boolean;\n}> = ({ position, label, isActive, isCompleted, animated }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n // Pulsing animation for active marker\n useFrame((state) => {\n if (!animated) return;\n \n if (meshRef.current && isActive) {\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.1 + 1;\n meshRef.current.scale.setScalar(pulse);\n }\n\n if (ringRef.current && isActive) {\n const ringPulse = Math.sin(state.clock.elapsedTime * 2) * 0.2 + 1;\n ringRef.current.scale.setScalar(ringPulse);\n }\n });\n\n const markerColor = isCompleted\n ? KOREAN_COLORS.ACCENT_GREEN\n : isActive\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.ACCENT_GOLD;\n\n return (\n <group position={position}>\n {/* Ground circle marker */}\n <mesh ref={meshRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.01, 0]}>\n <circleGeometry args={[0.2, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.9 : 0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Outer ring */}\n <mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>\n <ringGeometry args={[0.2, 0.25, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.7 : 0.4}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Label */}\n <Html\n position={[0, 0.1, 0]}\n center\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: isActive ? \"#00ffff\" : \"#ffffff\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n background: \"rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n }}\n >\n {label}\n </div>\n </Html>\n\n {/* Vertical beam for active marker */}\n {isActive && (\n <mesh position={[0, 0.5, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 1, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * FootPlacementMarkers3D Component\n * \n * Displays 3D foot placement markers on the ground for footwork drills,\n * with Korean martial arts-themed visual feedback.\n * \n * @korean 발위치표시3D\n */\nexport const FootPlacementMarkers3D: React.FC<FootPlacementMarkers3DProps> = ({\n centerPosition = [5, 0, 0],\n pattern,\n currentStep = 0,\n visible = true,\n scale = 1.0,\n animated = true,\n}) => {\n const positions = useMemo(\n () => getPatternPositions(pattern, centerPosition),\n [pattern, centerPosition]\n );\n\n if (!visible || pattern === \"none\" || positions.length === 0) {\n return null;\n }\n\n return (\n <group scale={scale}>\n {positions.map((marker, index) => (\n <FootMarker\n key={`${pattern}-${index}`}\n position={marker.position}\n label={marker.label}\n isActive={index === currentStep}\n isCompleted={index < currentStep}\n animated={animated}\n />\n ))}\n </group>\n );\n};\n\nexport default FootPlacementMarkers3D;\n"],"mappings":"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"}
1
+ {"version":3,"file":"FootPlacementMarkers3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootPlacementMarkers3D.tsx"],"sourcesContent":["/**\n * FootPlacementMarkers3D - 3D markers showing foot placement for footwork drills\n * \n * Renders visual indicators on the ground to guide footwork training,\n * showing target positions for Korean martial arts footwork patterns.\n * \n * @module components/screens/training/components/FootPlacementMarkers3D\n * @category Training 3D Components\n * @korean 발위치표시3D컴포넌트\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { FootworkDrill } from \"./FootworkDrillsOverlayHtml\";\n\n/**\n * Footwork drill pattern types for 3D visualization\n * \n * Derived from FootworkDrill, excluding \"free_practice\" (which has no pattern),\n * and adding a \"none\" sentinel for cases with no active pattern.\n */\nexport type FootworkDrillPattern = \n | Exclude<FootworkDrill, \"free_practice\">\n | \"none\";\n\n/**\n * Props for FootPlacementMarkers3D component\n */\nexport interface FootPlacementMarkers3DProps {\n /** Center position for the pattern */\n readonly centerPosition?: [number, number, number];\n /** Current drill pattern to display */\n readonly pattern: FootworkDrillPattern;\n /** Current step in pattern (0-based) */\n readonly currentStep?: number;\n /** Whether markers are visible */\n readonly visible?: boolean;\n /** Scale factor for markers */\n readonly scale?: number;\n /** Whether to animate markers */\n readonly animated?: boolean;\n}\n\n/**\n * Generate footwork pattern positions\n * \n * @korean 보법패턴위치생성\n */\nfunction getPatternPositions(\n pattern: FootworkDrillPattern,\n center: [number, number, number]\n): Array<{ position: [number, number, number]; label: string }> {\n const [cx, cy, cz] = center;\n const radius = 2.0; // 2 meters radius for circular patterns\n const stepDist = 0.3; // 30cm step distance\n\n switch (pattern) {\n case \"circular_left\":\n // 4 positions in a circle (left rotation)\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz - radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz + radius], label: \"4\" },\n ];\n\n case \"circular_right\":\n // 4 positions in a circle (right rotation)\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz + radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz - radius], label: \"4\" },\n ];\n\n case \"pivot_combo\":\n // Pivot left and right positions\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx - 0.5, cy, cz + 0.5], label: \"Pivot L\" },\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx + 0.5, cy, cz + 0.5], label: \"Pivot R\" },\n ];\n\n case \"triangle_step\":\n // Triangle stepping pattern\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - stepDist * 2], label: \"Forward\" },\n { position: [cx + stepDist * 1.5, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz], label: \"Back\" },\n ];\n\n case \"slide_drill\":\n // Four-direction slide pattern (cross shape)\n return [\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx, cy, cz - stepDist], label: \"Forward\" },\n { position: [cx + stepDist, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz + stepDist], label: \"Back\" },\n { position: [cx - stepDist, cy, cz], label: \"Left\" },\n ];\n\n case \"shuffle_practice\":\n // Quick forward shuffles\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - 0.15], label: \"Shuffle 1\" },\n { position: [cx, cy, cz - 0.30], label: \"Shuffle 2\" },\n { position: [cx, cy, cz - 0.45], label: \"Shuffle 3\" },\n ];\n\n case \"none\":\n default:\n return [];\n }\n}\n\n/**\n * Single foot placement marker\n */\nconst FootMarker: React.FC<{\n position: [number, number, number];\n label: string;\n isActive: boolean;\n isCompleted: boolean;\n animated: boolean;\n}> = ({ position, label, isActive, isCompleted, animated }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n // Pulsing animation for active marker\n useFrame((state) => {\n if (!animated) return;\n \n if (meshRef.current && isActive) {\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.1 + 1;\n meshRef.current.scale.setScalar(pulse);\n }\n\n if (ringRef.current && isActive) {\n const ringPulse = Math.sin(state.clock.elapsedTime * 2) * 0.2 + 1;\n ringRef.current.scale.setScalar(ringPulse);\n }\n });\n\n const markerColor = isCompleted\n ? KOREAN_COLORS.ACCENT_GREEN\n : isActive\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.ACCENT_GOLD;\n\n return (\n <group position={position}>\n {/* Ground circle marker */}\n <mesh ref={meshRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.01, 0]}>\n <circleGeometry args={[0.2, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.9 : 0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Outer ring */}\n <mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>\n <ringGeometry args={[0.2, 0.25, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.7 : 0.4}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Label */}\n <Html\n position={[0, 0.1, 0]}\n center\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: isActive ? \"#00ffff\" : \"#ffffff\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n background: \"rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n }}\n >\n {label}\n </div>\n </Html>\n\n {/* Vertical beam for active marker */}\n {isActive && (\n <mesh position={[0, 0.5, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 1, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * FootPlacementMarkers3D Component\n * \n * Displays 3D foot placement markers on the ground for footwork drills,\n * with Korean martial arts-themed visual feedback.\n * \n * @korean 발위치표시3D\n */\nexport const FootPlacementMarkers3D: React.FC<FootPlacementMarkers3DProps> = ({\n centerPosition = [5, 0, 0],\n pattern,\n currentStep = 0,\n visible = true,\n scale = 1.0,\n animated = true,\n}) => {\n const positions = useMemo(\n () => getPatternPositions(pattern, centerPosition),\n [pattern, centerPosition]\n );\n\n if (!visible || pattern === \"none\" || positions.length === 0) {\n return null;\n }\n\n return (\n <group scale={scale}>\n {positions.map((marker, index) => (\n <FootMarker\n key={`${pattern}-${index}`}\n position={marker.position}\n label={marker.label}\n isActive={index === currentStep}\n isCompleted={index < currentStep}\n animated={animated}\n />\n ))}\n </group>\n );\n};\n\nexport default FootPlacementMarkers3D;\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 // onStepComplete, // TODO: Use this for drill pattern validation\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n // Auto-hide instructions after 5 seconds when drill is active\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 // Re-render when drill state, mobile state, or callbacks change\n // Including callback props prevents stale closures when parent provides\n // new functions that capture updated state.\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 // onStepComplete, // TODO: Use this for drill pattern validation\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n // Auto-hide instructions after 5 seconds when drill is active\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 // Re-render when drill state, mobile state, or callbacks change\n // Including callback props prevents stale closures when parent provides\n // new functions that capture updated state.\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 // Store velocities in a ref that persists across renders\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n // Store initial position for seeded random - use useState to capture at mount\n // Note: This intentionally ignores position prop changes to maintain consistent\n // particle behavior throughout the effect's lifetime. To update particles when\n // position changes, add a key prop to the parent component to force remount.\n const [initialPosition] = useState(position);\n\n // Initialize particle positions and velocities - use seed based on initial position\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n // Use initial position as seed for deterministic but varying particles\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n // Simple seeded random using position\n // Large multiplier (10000) ensures sufficient entropy for randomness while keeping values deterministic\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n // Use pooled vector for velocity calculations to reduce allocations\n // Pool strategy: Acquire once, reuse for all particles, release\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n // Start at center\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n // Random outward velocities using seeded random\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 // Add upward bias\n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n // Always release pooled vector\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 // Update velocities ref in useEffect to avoid ref access during render\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n // Animate particles\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 // Update positions\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 // Apply gravity\n // Note: Intentionally mutating velocitiesRef.current (Float32Array) for performance.\n // This is safe and won't trigger React re-renders since it's a ref.\n vel[i3 + 1] -= 9.8 * delta;\n\n // Fade out particles that go too far\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 // Initialize start time on mount using useEffect\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 // Expand ring - use pooled vector for scale to avoid allocation\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 // Fade out\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 // Initialize start time on mount using useEffect\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 // Float upward\n setOffset(progress * 1);\n\n // Fade out\n setOpacity(1 - progress);\n\n // Complete when done (only once)\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 // Get colors based on hit type\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 // Reduce particle counts on mobile to avoid frame drops\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 // Higher fidelity on non-mobile devices\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n // Track completion to prevent multiple calls\n const completedRef = useRef(false);\n\n // Handle effect completion (only once)\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n // Auto-complete after duration\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
+ {"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 // Store velocities in a ref that persists across renders\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n // Store initial position for seeded random - use useState to capture at mount\n // Note: This intentionally ignores position prop changes to maintain consistent\n // particle behavior throughout the effect's lifetime. To update particles when\n // position changes, add a key prop to the parent component to force remount.\n const [initialPosition] = useState(position);\n\n // Initialize particle positions and velocities - use seed based on initial position\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n // Use initial position as seed for deterministic but varying particles\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n // Simple seeded random using position\n // Large multiplier (10000) ensures sufficient entropy for randomness while keeping values deterministic\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n // Use pooled vector for velocity calculations to reduce allocations\n // Pool strategy: Acquire once, reuse for all particles, release\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n // Start at center\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n // Random outward velocities using seeded random\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 // Add upward bias\n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n // Always release pooled vector\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 // Update velocities ref in useEffect to avoid ref access during render\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n // Animate particles\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 // Update positions\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 // Apply gravity\n // Note: Intentionally mutating velocitiesRef.current (Float32Array) for performance.\n // This is safe and won't trigger React re-renders since it's a ref.\n vel[i3 + 1] -= 9.8 * delta;\n\n // Fade out particles that go too far\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 // Initialize start time on mount using useEffect\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 // Expand ring - use pooled vector for scale to avoid allocation\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 // Fade out\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 // Initialize start time on mount using useEffect\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 // Float upward\n setOffset(progress * 1);\n\n // Fade out\n setOpacity(1 - progress);\n\n // Complete when done (only once)\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 // Get colors based on hit type\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 // Reduce particle counts on mobile to avoid frame drops\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 // Higher fidelity on non-mobile devices\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n // Track completion to prevent multiple calls\n const completedRef = useRef(false);\n\n // Handle effect completion (only once)\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n // Auto-complete after duration\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;AACJ,QACE,oBAAC,uBAAD;EACE,QAAQ,WAAW,OAAO;EAC1B,SAAS,WAAW,SAAS;EACpB;EACK;EACd,SAAQ;EACR,MAAK;EACK;EACV,QAAO;EACP,CAAA;;;;;;;;;;;;;;;;;;;;AAiCN,IAAa,6BAER,EAAE,mBAAmB,mBAAmB,WAAW,eAAe;CACrE,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;EAAU,CAAC;CAE1E,MAAM,uBAAuB,aAC1B,cAA+B;AAC9B,oBAAkB,UAAU;AAC5B,cAAY,cAAc;IAE5B,CAAC,mBAAmB,UAAU,CAC/B;AAED,QACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,UAAU;GACV,KAAK;GACL,gBAAgB;GACjB;EACD,eAAY;YAEX,OAAO,OAAO,gBAAgB,CAAC,KAAK,SACnC,oBAAC,UAAD;GAEE,eAAe,qBAAqB,KAAK;GACzC,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,GAAI,GAC9C,gBAAgB,MAAM,OAAO,sBAAsB,GAAI;IAC7D,OACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,oBAAoB,EAAE,GACnD,gBAAgB,MAAM,OAAO,cAAc,EAAE;IACnD,QAAQ,aAAa,gBACnB,sBAAsB,OAClB,MAAM,OAAO,cACb,MAAM,OAAO,WACjB,GACD;IACD,cAAc;IACd,QAAQ;IACR,YAAY;IACb;GACD,eAAa,oBAAoB;GACjC,cAAY,UAAU,KAAK;GAC3B,gBAAc,sBAAsB;aAEnC,KAAK,aAAa;GACZ,EA9BF,KA8BE,CACT;EACE,CAAA"}
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;EACP,CAAA;;;;;;;;;;;;;;;;;;;;AAiCN,IAAa,6BAER,EAAE,mBAAmB,mBAAmB,WAAW,eAAe;CACrE,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;EAAU,CAAC;CAE1E,MAAM,uBAAuB,aAC1B,cAA+B;EAC9B,kBAAkB,UAAU;EAC5B,YAAY,cAAc;IAE5B,CAAC,mBAAmB,UAAU,CAC/B;CAED,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,UAAU;GACV,KAAK;GACL,gBAAgB;GACjB;EACD,eAAY;YAEX,OAAO,OAAO,gBAAgB,CAAC,KAAK,SACnC,oBAAC,UAAD;GAEE,eAAe,qBAAqB,KAAK;GACzC,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,GAAI,GAC9C,gBAAgB,MAAM,OAAO,sBAAsB,GAAI;IAC7D,OACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,oBAAoB,EAAE,GACnD,gBAAgB,MAAM,OAAO,cAAc,EAAE;IACnD,QAAQ,aAAa,gBACnB,sBAAsB,OAClB,MAAM,OAAO,cACb,MAAM,OAAO,WACjB,GACD;IACD,cAAc;IACd,QAAQ;IACR,YAAY;IACb;GACD,eAAa,oBAAoB;GACjC,cAAY,UAAU,KAAK;GAC3B,gBAAc,sBAAsB;aAEnC,KAAK,aAAa;GACZ,EA9BF,KA8BE,CACT;EACE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"TrainingControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingControlsOverlayHtml - Html overlay for training controls\n * \n * Displays start/stop button and training status 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 {\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getKoreanButtonWithGlow,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingControlsOverlayHtml component\n */\nexport interface TrainingControlsOverlayHtmlProps {\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Callback to start training */\n readonly onStartTraining: () => void;\n /** Callback to stop training */\n readonly onStopTraining: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Compact mode for embedding inside the slim top HUD */\n readonly variant?: \"panel\" | \"compact\";\n}\n\n/**\n * TrainingControlsOverlayHtml Component\n * \n * Html overlay showing training status and start/stop controls with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when isTraining state hasn't changed\n * - Callbacks expected to be stable (parent should use useCallback)\n * \n * @example\n * ```tsx\n * <TrainingControlsOverlayHtml\n * isTraining={true}\n * onStartTraining={() => console.log('start')}\n * onStopTraining={() => console.log('stop')}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 훈련제어오버레이컴포넌트\n */\nexport const TrainingControlsOverlayHtml = React.memo<TrainingControlsOverlayHtmlProps>(\n ({\n isTraining,\n onStartTraining,\n onStopTraining,\n isMobile,\n variant = \"panel\",\n }) => {\n const isCompact = variant === \"compact\";\n const panelWidth = isCompact ? (isMobile ? 180 : 210) : isMobile ? 200 : 220;\n const panelHeight = isCompact ? (isMobile ? 40 : 44) : isMobile ? 90 : 100;\n const padding = isCompact ? getResponsiveSpacing(\"xs\", isMobile) : getResponsiveSpacing(\"sm\", isMobile);\n\n // Use Korean colors for border based on training state\n const stateColor = isTraining ? KOREAN_COLORS.ACCENT_GREEN : KOREAN_COLORS.ACCENT_RED;\n const borderColor = hexToRgbaString(stateColor, 0.9);\n\n // Enhanced panel styles with neon glow\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: isTraining ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 2,\n }),\n width: `${panelWidth}px`,\n height: `${panelHeight}px`,\n padding: `${padding}px`,\n border: `2px solid ${borderColor}`,\n position: \"relative\",\n display: \"flex\",\n flexDirection: isCompact ? \"row\" : \"column\",\n alignItems: isCompact ? \"center\" : \"stretch\",\n justifyContent: isCompact ? \"space-between\" : \"flex-start\",\n gap: `${padding}px`,\n boxSizing: \"border-box\",\n fontFamily: FONT_FAMILY.KOREAN,\n };\n\n // Enhanced button styles (memoized, interaction states handled internally by getKoreanButtonWithGlow)\n const buttonStyles = React.useMemo(\n () =>\n getKoreanButtonWithGlow({\n variant: isTraining ? \"danger\" : \"success\",\n glowIntensity: \"strong\",\n hoverAnimation: \"combined\",\n }),\n [isTraining]\n );\n\n const titleFontSize = isCompact ? (isMobile ? 11 : 12) : isMobile ? 13 : 14;\n const infoFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-controls-html\">\n {/* Header with bilingual status */}\n <div style={{ marginBottom: isCompact ? 0 : `${SPACING.SM}px`, minWidth: 0 }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(stateColor),\n textShadow: getNeonTextShadow(stateColor, isTraining ? \"medium\" : \"subtle\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatBilingualText(\n isTraining ? \"훈련 진행중\" : \"훈련 대기\",\n isTraining ? \"Training Active\" : \"Training Stopped\",\n \"pipe\"\n )}\n </div>\n </div>\n\n {/* Status Indicator with CSS animation */}\n <div\n className={`status-indicator ${isTraining ? \"active\" : \"inactive\"}`}\n style={{\n position: \"absolute\",\n top: isCompact ? \"6px\" : \"12px\",\n right: isCompact ? \"6px\" : \"12px\",\n }}\n />\n\n {/* Start/Stop Button with Korean theming */}\n <button\n onClick={isTraining ? onStopTraining : onStartTraining}\n className={`training-button ${isTraining ? \"training-button-stop\" : \"training-button-start\"}`}\n style={{\n ...buttonStyles,\n // Note: fontSize from buttonStyles is intentionally overridden with titleFontSize\n // to maintain consistent sizing with the training header/title typography\n fontSize: `${titleFontSize}px`,\n height: isCompact ? \"30px\" : \"35px\",\n minWidth: isCompact ? \"72px\" : undefined,\n padding: isCompact ? \"4px 8px\" : buttonStyles.padding,\n flexShrink: 0,\n }}\n data-testid=\"training-toggle-button\"\n data-training-state={isTraining ? \"active\" : \"inactive\"}\n >\n <span>{isTraining ? \"⏹\" : \"▶\"}</span>\n <span>\n {formatBilingualText(\n isTraining ? \"중지\" : \"시작\",\n isTraining ? \"Stop\" : \"Start\",\n \"pipe\"\n )}\n </span>\n </button>\n\n {/* Info text about auto-restart with Korean colors */}\n {!isCompact && !isTraining && (\n <div\n style={{\n fontSize: `${infoFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n marginTop: `${SPACING.XS}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: \"1.4\",\n }}\n >\n <div>모드 변경시 자동 재시작</div>\n <div>Auto-restarts on mode change</div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n // Re-render when training state, mobile state, variant, or callbacks change.\n // variant must be compared so switching between \"panel\" and \"compact\"\n // triggers a re-render and the layout updates accordingly.\n // Including callback props here avoids stale-closure issues where the\n // component would keep calling outdated handlers that reference old state.\n return (\n prevProps.isTraining === nextProps.isTraining &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.variant === nextProps.variant &&\n prevProps.onStartTraining === nextProps.onStartTraining &&\n prevProps.onStopTraining === nextProps.onStopTraining\n );\n },\n);\n\nTrainingControlsOverlayHtml.displayName = \"TrainingControlsOverlayHtml\";\n\nexport default TrainingControlsOverlayHtml;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TrainingControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingControlsOverlayHtml - Html overlay for training controls\n * \n * Displays start/stop button and training status 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 {\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getKoreanButtonWithGlow,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingControlsOverlayHtml component\n */\nexport interface TrainingControlsOverlayHtmlProps {\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Callback to start training */\n readonly onStartTraining: () => void;\n /** Callback to stop training */\n readonly onStopTraining: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Compact mode for embedding inside the slim top HUD */\n readonly variant?: \"panel\" | \"compact\";\n}\n\n/**\n * TrainingControlsOverlayHtml Component\n * \n * Html overlay showing training status and start/stop controls with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when isTraining state hasn't changed\n * - Callbacks expected to be stable (parent should use useCallback)\n * \n * @example\n * ```tsx\n * <TrainingControlsOverlayHtml\n * isTraining={true}\n * onStartTraining={() => console.log('start')}\n * onStopTraining={() => console.log('stop')}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 훈련제어오버레이컴포넌트\n */\nexport const TrainingControlsOverlayHtml = React.memo<TrainingControlsOverlayHtmlProps>(\n ({\n isTraining,\n onStartTraining,\n onStopTraining,\n isMobile,\n variant = \"panel\",\n }) => {\n const isCompact = variant === \"compact\";\n const panelWidth = isCompact ? (isMobile ? 180 : 210) : isMobile ? 200 : 220;\n const panelHeight = isCompact ? (isMobile ? 40 : 44) : isMobile ? 90 : 100;\n const padding = isCompact ? getResponsiveSpacing(\"xs\", isMobile) : getResponsiveSpacing(\"sm\", isMobile);\n\n // Use Korean colors for border based on training state\n const stateColor = isTraining ? KOREAN_COLORS.ACCENT_GREEN : KOREAN_COLORS.ACCENT_RED;\n const borderColor = hexToRgbaString(stateColor, 0.9);\n\n // Enhanced panel styles with neon glow\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: isTraining ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 2,\n }),\n width: `${panelWidth}px`,\n height: `${panelHeight}px`,\n padding: `${padding}px`,\n border: `2px solid ${borderColor}`,\n position: \"relative\",\n display: \"flex\",\n flexDirection: isCompact ? \"row\" : \"column\",\n alignItems: isCompact ? \"center\" : \"stretch\",\n justifyContent: isCompact ? \"space-between\" : \"flex-start\",\n gap: `${padding}px`,\n boxSizing: \"border-box\",\n fontFamily: FONT_FAMILY.KOREAN,\n };\n\n // Enhanced button styles (memoized, interaction states handled internally by getKoreanButtonWithGlow)\n const buttonStyles = React.useMemo(\n () =>\n getKoreanButtonWithGlow({\n variant: isTraining ? \"danger\" : \"success\",\n glowIntensity: \"strong\",\n hoverAnimation: \"combined\",\n }),\n [isTraining]\n );\n\n const titleFontSize = isCompact ? (isMobile ? 11 : 12) : isMobile ? 13 : 14;\n const infoFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-controls-html\">\n {/* Header with bilingual status */}\n <div style={{ marginBottom: isCompact ? 0 : `${SPACING.SM}px`, minWidth: 0 }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(stateColor),\n textShadow: getNeonTextShadow(stateColor, isTraining ? \"medium\" : \"subtle\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatBilingualText(\n isTraining ? \"훈련 진행중\" : \"훈련 대기\",\n isTraining ? \"Training Active\" : \"Training Stopped\",\n \"pipe\"\n )}\n </div>\n </div>\n\n {/* Status Indicator with CSS animation */}\n <div\n className={`status-indicator ${isTraining ? \"active\" : \"inactive\"}`}\n style={{\n position: \"absolute\",\n top: isCompact ? \"6px\" : \"12px\",\n right: isCompact ? \"6px\" : \"12px\",\n }}\n />\n\n {/* Start/Stop Button with Korean theming */}\n <button\n onClick={isTraining ? onStopTraining : onStartTraining}\n className={`training-button ${isTraining ? \"training-button-stop\" : \"training-button-start\"}`}\n style={{\n ...buttonStyles,\n // Note: fontSize from buttonStyles is intentionally overridden with titleFontSize\n // to maintain consistent sizing with the training header/title typography\n fontSize: `${titleFontSize}px`,\n height: isCompact ? \"30px\" : \"35px\",\n minWidth: isCompact ? \"72px\" : undefined,\n padding: isCompact ? \"4px 8px\" : buttonStyles.padding,\n flexShrink: 0,\n }}\n data-testid=\"training-toggle-button\"\n data-training-state={isTraining ? \"active\" : \"inactive\"}\n >\n <span>{isTraining ? \"⏹\" : \"▶\"}</span>\n <span>\n {formatBilingualText(\n isTraining ? \"중지\" : \"시작\",\n isTraining ? \"Stop\" : \"Start\",\n \"pipe\"\n )}\n </span>\n </button>\n\n {/* Info text about auto-restart with Korean colors */}\n {!isCompact && !isTraining && (\n <div\n style={{\n fontSize: `${infoFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n marginTop: `${SPACING.XS}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: \"1.4\",\n }}\n >\n <div>모드 변경시 자동 재시작</div>\n <div>Auto-restarts on mode change</div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n // Re-render when training state, mobile state, variant, or callbacks change.\n // variant must be compared so switching between \"panel\" and \"compact\"\n // triggers a re-render and the layout updates accordingly.\n // Including callback props here avoids stale-closure issues where the\n // component would keep calling outdated handlers that reference old state.\n return (\n prevProps.isTraining === nextProps.isTraining &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.variant === nextProps.variant &&\n prevProps.onStartTraining === nextProps.onStartTraining &&\n prevProps.onStopTraining === nextProps.onStopTraining\n );\n },\n);\n\nTrainingControlsOverlayHtml.displayName = \"TrainingControlsOverlayHtml\";\n\nexport default TrainingControlsOverlayHtml;\n"],"mappings":"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"}
@@ -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 // Base positions calibrated for SkeletalPlayer3D skeletal rig\n // These align with the 28-bone humanoid rig structure\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 // Get base position or default to center torso\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n // Add deterministic offset for multiple points in same region\n // Uses character code of pointId for consistent positioning\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\n// Constants defined outside component to avoid recreation\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 // Health percentage\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n // Memoize geometries to avoid recreating on every render\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 // Determine health bar color based on health percentage\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 // Use scale instead of recreating geometry\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n // Cleanup geometries on unmount\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 // Select vital points to display based on count (expandable to 70)\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 // Calculate size multiplier based on difficulty\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 // Track previous health to detect defeat\n const prevHealthRef = useRef(health);\n\n // Store latest onDefeated callback in a ref to avoid stale closure\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n // Check for defeat and trigger stun effect on significant damage\n useEffect(() => {\n // Defeated check\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n // Stun on significant damage (more than 20 points)\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 // Subtle idle animation for training dummy\n useFrame((state) => {\n if (!groupRef.current) return;\n\n // Gentle breathing/swaying motion\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 // Subtle rotation as if waiting for attack\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n // Handle vital point hit\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAM,yBACJ,SACA,aAC6B;CAmB7B,MAAM,UAAU;EAfd,MAAM;GAAC;GAAG;GAAM;GAAK;EACrB,MAAM;GAAC;GAAG;GAAK;GAAI;EACnB,OAAO;GAAC;GAAG;GAAM;GAAK;EACtB,OAAO;GAAC;GAAG;GAAK;GAAI;EACpB,SAAS;GAAC;GAAG;GAAM;GAAK;EACxB,MAAM;GAAC;GAAG;GAAK;GAAM;EACrB,KAAK;GAAC;GAAO;GAAM;GAAE;EACrB,KAAK;GAAC;GAAO;GAAK;GAAK;EACvB,cAAc;GAAC;GAAG;GAAK;GAAK;EAC5B,UAAU;GAAC;GAAG;GAAM;GAAK;EACzB,UAAU;GAAC;GAAG;GAAK;GAAI;EACvB,UAAU;GAAC;GAAG;GAAK;GAAK;EAIV,CAAU,SAAS,aAAa,KAAK;EAAC;EAAG;EAAK;EAAK;CAInE,MAAM,OACJ,QAAQ,MAAM,GAAG,CAAC,QAAQ,KAAK,SAAS,MAAM,KAAK,WAAW,EAAE,EAAE,EAAE,GACpE;AACF,QAAO;EACL,QAAQ,KAAK,KAAK,IAAI,OAAO,IAAI,GAAG;EACpC,QAAQ,KAAK,KAAK,IAAI,OAAO,IAAI,GAAG;EACpC,QAAQ,KAAK,KAAK,IAAI,OAAO,IAAI,GAAG;EACrC;;;;;;;;AAWH,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAE1B,IAAM,kBAIA,EAAE,QAAQ,WAAW,eAAe;CAExC,MAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,SAAS,YAAa,IAAI,CAAC;CAG5E,MAAM,aAAa,cACX,IAAI,MAAM,YAAY,kBAAkB,mBAAmB,IAAK,EACtE,EAAE,CACH;CACD,MAAM,iBAAiB,cACf,IAAI,MAAM,YAAY,kBAAkB,mBAAmB,IAAK,EACtE,EAAE,CACH;CACD,MAAM,iBAAiB,cAEnB,IAAI,MAAM,YACR,mBAAmB,KACnB,oBAAoB,KACpB,IACD,EACH,EAAE,CACH;CAGD,MAAM,cAAc,cAAc;AAChC,MAAI,gBAAgB,GAAI,QAAO,cAAc;AAC7C,MAAI,gBAAgB,GAAI,QAAO,cAAc;AAC7C,MAAI,gBAAgB,GAAI,QAAO,cAAc;AAC7C,SAAO,cAAc;IACpB,CAAC,cAAc,CAAC;CAGnB,MAAM,cAAwC,cACtC;EAAC,gBAAgB;EAAK;EAAG;EAAE,EACjC,CAAC,cAAc,CAChB;AAGD,iBAAgB;AACd,eAAa;AACX,cAAW,SAAS;AACpB,kBAAe,SAAS;AACxB,kBAAe,SAAS;;IAEzB;EAAC;EAAY;EAAgB;EAAe,CAAC;AAEhD,QACE,qBAAC,SAAD;EAAiB;EAAU,MAAK;YAAhC;GAEE,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,aAAD,EAAW,QAAQ,YAAc,CAAA,EACjC,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,CAAA,CACG,EAAA,CAAA;GAGP,qBAAC,QAAD;IACE,UAAU;KACR,EAAE,oBAAoB,IAAI,gBAAgB,QAAQ;KAClD;KACA;KACD;IACD,OAAO;cANT,CAQE,oBAAC,aAAD,EAAW,QAAQ,gBAAkB,CAAA,EACrC,oBAAC,qBAAD;KAAmB,OAAO;KAAa,aAAA;KAAY,SAAS;KAAO,CAAA,CAC9D;;GAGP,qBAAC,gBAAD,EAAA,UAAA,CACE,oBAAC,iBAAD,EAAe,MAAM,CAAC,eAAe,EAAI,CAAA,EACzC,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,CAAA,CACW,EAAA,CAAA;GACT;;;;;;;;;;;;;;;;;;;;;;;AAwBZ,IAAa,mBAAmD,EAC9D,UACA,oBACA,YACA,SAAS,KACT,YAAY,KACZ,iBACA,YACA,aAAa,UACb,kBAAkB,IAClB,WAAW,OACX,YAAY,gBAAgB,MAC5B,SAAS,cAAc,WACnB;CACJ,MAAM,WAAW,OAAoB,KAAK;CAC1C,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CAGjD,MAAM,cAAc,cAEhB,oBAAoB,MAClB,GACA,KAAK,IAAI,iBAAiB,oBAAoB,OAAO,CACtD,EACH,CAAC,gBAAgB,CAClB;CAGD,MAAM,iBAAiB,cAAc;AACnC,UAAQ,YAAR;GACE,KAAK,OACH,QAAO;GACT,KAAK,SACH,QAAO;GACT,KAAK,OACH,QAAO;GACT,QACE,QAAO;;IAEV,CAAC,WAAW,CAAC;CAGhB,MAAM,gBAAgB,OAAO,OAAO;CAGpC,MAAM,gBAAgB,OAAO,WAAW;AACxC,iBAAgB;AACd,gBAAc,UAAU;IACvB,CAAC,WAAW,CAAC;AAGhB,iBAAgB;AAEd,MAAI,cAAc,UAAU,KAAK,UAAU,EACzC,eAAc,WAAW;AAI3B,MAAI,cAAc,UAAU,SAAS,IAAI;AACvC,gBAAa,KAAK;GAClB,MAAM,QAAQ,iBAAiB,aAAa,MAAM,EAAE,IAAI;AACxD,gBAAa,aAAa,MAAM;;AAGlC,gBAAc,UAAU;IAEvB,CAAC,OAAO,CAAC;AAGZ,WAAU,UAAU;AAClB,MAAI,CAAC,SAAS,QAAS;EAGvB,MAAM,OAAO,MAAM,MAAM;EACzB,MAAM,cAAc,KAAK,IAAI,OAAO,IAAI,GAAG,MAAO;AAClD,WAAS,QAAQ,MAAM,IAAI;AAG3B,WAAS,QAAQ,SAAS,IAAI,KAAK,IAAI,OAAO,GAAI,GAAG;GACrD;CAGF,MAAM,iBAAiB,aACpB,YAAoB;AACnB,oBAAkB,QAAQ;IAE5B,CAAC,gBAAgB,CAClB;AAED,QACE,qBAAC,SAAD;EAAO,KAAK;EAAoB;EAAU,MAAK;YAA/C;GAEE,oBAAC,kBAAD;IACE,UAAS;IACE;IACH;IACR,UAAU;KAAC;KAAG;KAAG;KAAE;IACnB,UAAU,KAAK;IACf,QAAO;IACC;IACG;IACX,SAAS;IACT,IAAI;IACJ,SAAQ;IACR,MAAM;IACN,eAAe;IACL;IACV,kBAAiB;IACjB,YAAY;IACD;IACX,eAAe;IACf,qBAAqB;IACrB,yBAAyB;IACzB,mBAAmB;IACnB,CAAA;GAGD,YAAY,KAAK,UAChB,oBAAC,SAAD;IAEE,UAAU,sBAAsB,MAAM,IAAI,MAAM,SAAS;cAEzD,oBAAC,oBAAD;KACE,YAAY;KACZ,YAAY,MAAM,OAAO;KACb;KACF;KACV,OAAO;KACS;KAChB,CAAA;IACI,EAXD,MAAM,GAWL,CACR;GAGD,cACC,oBAAC,gBAAD;IACU;IACG;IACX,UAAU;KAAC;KAAG;KAAK;KAAE;IACrB,CAAA;GAIH,cACC,oBAAC,cAAD;IACE,UAAU;KAAC;KAAG;KAAK;KAAI;IACvB,OAAO,cAAc;IACrB,WAAW;IACX,UAAU;IACV,OAAO;IACP,CAAA;GAEE"}
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 // Base positions calibrated for SkeletalPlayer3D skeletal rig\n // These align with the 28-bone humanoid rig structure\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 // Get base position or default to center torso\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n // Add deterministic offset for multiple points in same region\n // Uses character code of pointId for consistent positioning\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\n// Constants defined outside component to avoid recreation\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 // Health percentage\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n // Memoize geometries to avoid recreating on every render\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 // Determine health bar color based on health percentage\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 // Use scale instead of recreating geometry\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n // Cleanup geometries on unmount\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 // Select vital points to display based on count (expandable to 70)\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 // Calculate size multiplier based on difficulty\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 // Track previous health to detect defeat\n const prevHealthRef = useRef(health);\n\n // Store latest onDefeated callback in a ref to avoid stale closure\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n // Check for defeat and trigger stun effect on significant damage\n useEffect(() => {\n // Defeated check\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n // Stun on significant damage (more than 20 points)\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 // Subtle idle animation for training dummy\n useFrame((state) => {\n if (!groupRef.current) return;\n\n // Gentle breathing/swaying motion\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 // Subtle rotation as if waiting for attack\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n // Handle vital point hit\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":"TrainingFeedbackOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingFeedbackOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingFeedbackOverlayHtml - Html overlay for training feedback messages\n * \n * Displays temporary feedback messages for hits, misses, and achievements\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/TrainingFeedbackOverlayHtml\n * @category Training UI\n * @korean 훈련피드백오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingFeedbackOverlayHtml component\n */\nexport interface TrainingFeedbackOverlayHtmlProps {\n /** Feedback message to display */\n readonly message: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * TrainingFeedbackOverlayHtml Component\n * Html overlay for displaying training feedback with Korean theming\n * \n * @korean 훈련피드백오버레이컴포넌트\n */\nexport const TrainingFeedbackOverlayHtml = React.memo<TrainingFeedbackOverlayHtmlProps>(\n ({\n message,\n isMobile,\n }) => {\n return (\n <div\n className={`training-feedback ${isMobile ? \"mobile\" : \"desktop\"}`}\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n }}\n data-testid=\"training-feedback-html\"\n >\n {message}\n </div>\n );\n},\n(prevProps, nextProps) => {\n // Only re-render if message or mobile state changes\n return (\n prevProps.message === nextProps.message &&\n prevProps.isMobile === nextProps.isMobile\n );\n});\n\nTrainingFeedbackOverlayHtml.displayName = \"TrainingFeedbackOverlayHtml\";\n\nexport default TrainingFeedbackOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,8BAA8B,MAAM,MAC9C,EACC,SACA,eACI;AACN,QACE,oBAAC,OAAD;EACE,WAAW,qBAAqB,WAAW,WAAW;EACtD,OAAO;GACL,YAAY,YAAY;GACxB,YAAY;GACZ,OAAO,gBAAgB,cAAc,YAAY;GACjD,YAAY,cAAc,gBAAgB,cAAc,aAAa,GAAI;GAC1E;EACD,eAAY;YAEX;EACG,CAAA;IAGT,WAAW,cAAc;AAExB,QACE,UAAU,YAAY,UAAU,WAChC,UAAU,aAAa,UAAU;EAEnC;AAEF,4BAA4B,cAAc"}
1
+ {"version":3,"file":"TrainingFeedbackOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingFeedbackOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingFeedbackOverlayHtml - Html overlay for training feedback messages\n * \n * Displays temporary feedback messages for hits, misses, and achievements\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/TrainingFeedbackOverlayHtml\n * @category Training UI\n * @korean 훈련피드백오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingFeedbackOverlayHtml component\n */\nexport interface TrainingFeedbackOverlayHtmlProps {\n /** Feedback message to display */\n readonly message: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * TrainingFeedbackOverlayHtml Component\n * Html overlay for displaying training feedback with Korean theming\n * \n * @korean 훈련피드백오버레이컴포넌트\n */\nexport const TrainingFeedbackOverlayHtml = React.memo<TrainingFeedbackOverlayHtmlProps>(\n ({\n message,\n isMobile,\n }) => {\n return (\n <div\n className={`training-feedback ${isMobile ? \"mobile\" : \"desktop\"}`}\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n }}\n data-testid=\"training-feedback-html\"\n >\n {message}\n </div>\n );\n},\n(prevProps, nextProps) => {\n // Only re-render if message or mobile state changes\n return (\n prevProps.message === nextProps.message &&\n prevProps.isMobile === nextProps.isMobile\n );\n});\n\nTrainingFeedbackOverlayHtml.displayName = \"TrainingFeedbackOverlayHtml\";\n\nexport default TrainingFeedbackOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,8BAA8B,MAAM,MAC9C,EACC,SACA,eACI;CACN,OACE,oBAAC,OAAD;EACE,WAAW,qBAAqB,WAAW,WAAW;EACtD,OAAO;GACL,YAAY,YAAY;GACxB,YAAY;GACZ,OAAO,gBAAgB,cAAc,YAAY;GACjD,YAAY,cAAc,gBAAgB,cAAc,aAAa,GAAI;GAC1E;EACD,eAAY;YAEX;EACG,CAAA;IAGT,WAAW,cAAc;CAExB,OACE,UAAU,YAAY,UAAU,WAChC,UAAU,aAAa,UAAU;EAEnC;AAEF,4BAA4B,cAAc"}