blacktrigram 0.7.45 → 0.7.48

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 (450) 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.d.ts.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +22 -11
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  21. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  22. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  31. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  32. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  36. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  44. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  45. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  49. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  50. package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
  51. package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
  52. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  53. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  56. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  57. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  58. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  59. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  60. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  61. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  62. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  63. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  64. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  65. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  66. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  67. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  68. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  69. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  74. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  75. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  76. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  78. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  79. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  84. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  87. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  88. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  89. package/lib/components/screens/training/TrainingScreen3D.js +1 -0
  90. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  91. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  93. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  94. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  96. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  97. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  99. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  100. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  101. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  102. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  103. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  104. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  105. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  106. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  107. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  108. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  109. package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
  110. package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
  111. package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
  112. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  113. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  114. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  115. package/lib/components/shared/base/BaseButton.js.map +1 -1
  116. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  117. package/lib/components/shared/base/BasePanel.js.map +1 -1
  118. package/lib/components/shared/base/BaseText.js.map +1 -1
  119. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  120. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  121. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  122. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  123. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  124. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  125. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  126. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  127. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  128. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  129. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  130. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  131. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  132. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  133. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  134. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  135. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  136. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  137. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  138. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  139. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  140. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  141. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  142. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  143. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  144. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  145. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  146. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  147. package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
  148. package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
  149. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  150. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  151. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  152. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  153. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  154. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  155. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  156. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  157. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  158. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  160. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  161. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  162. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  163. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  164. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  165. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  166. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  167. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  168. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  169. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  170. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  171. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  172. package/lib/components/shared/ui/BackButton.js.map +1 -1
  173. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  174. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  175. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  176. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  177. package/lib/components/shared/ui/SplashScreen.js +2 -2
  178. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  179. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  180. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  181. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  182. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  183. package/lib/constants/bodyDimensions.js.map +1 -1
  184. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  185. package/lib/data/archetypeClothing.js.map +1 -1
  186. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  187. package/lib/data/techniqueMappings.js.map +1 -1
  188. package/lib/data/techniques.js.map +1 -1
  189. package/lib/hooks/useActionFeedback.js.map +1 -1
  190. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  191. package/lib/hooks/useCombatTimer.js.map +1 -1
  192. package/lib/hooks/useDebounce.js.map +1 -1
  193. package/lib/hooks/useHUDLayout.js.map +1 -1
  194. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  195. package/lib/hooks/useKeyboardControls.js.map +1 -1
  196. package/lib/hooks/useMatchCountdown.js.map +1 -1
  197. package/lib/hooks/useMuscleActivation.js.map +1 -1
  198. package/lib/hooks/usePauseMenu.js.map +1 -1
  199. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  200. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  201. package/lib/hooks/useRoundTransition.js.map +1 -1
  202. package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
  203. package/lib/hooks/useSkeletalAnimation.js +1 -1
  204. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  205. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  206. package/lib/hooks/useThrottle.js.map +1 -1
  207. package/lib/hooks/useTouchControls.js.map +1 -1
  208. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  209. package/lib/hooks/useWindowSize.js.map +1 -1
  210. package/lib/systems/CombatSystem.js.map +1 -1
  211. package/lib/systems/EffectCalculator.js.map +1 -1
  212. package/lib/systems/LayoutSystem.js.map +1 -1
  213. package/lib/systems/PlayerEffectManager.js.map +1 -1
  214. package/lib/systems/ResponsiveScaling.js.map +1 -1
  215. package/lib/systems/TrigramSystem.js.map +1 -1
  216. package/lib/systems/VitalPointSystem.js.map +1 -1
  217. package/lib/systems/ai/AIPersonality.js.map +1 -1
  218. package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
  219. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  220. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  221. package/lib/systems/ai/ComboSystem.js.map +1 -1
  222. package/lib/systems/ai/DecisionTree.js.map +1 -1
  223. package/lib/systems/ai/TrainingAI.js.map +1 -1
  224. package/lib/systems/ai/types.js.map +1 -1
  225. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  226. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  227. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  228. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  229. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  230. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
  231. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
  232. package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
  233. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  234. package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
  235. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  236. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
  237. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
  238. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
  239. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  240. package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
  241. package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
  242. package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
  243. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  244. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  245. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  246. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  247. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  248. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  249. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  250. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  251. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  252. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  253. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  254. package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
  255. package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
  256. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  257. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  258. package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
  259. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
  260. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
  261. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  262. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  263. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  264. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  265. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
  266. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
  267. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
  268. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
  269. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  270. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  271. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  272. package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
  273. package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
  274. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  275. package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
  276. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  277. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  278. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  279. package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
  280. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  281. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  282. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  283. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  284. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  285. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  286. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  287. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  288. package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
  289. package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
  290. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  291. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  292. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  293. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  294. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  295. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  296. package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
  297. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  298. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  299. package/lib/systems/animation/core/AnimationPriority.js +15 -15
  300. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  301. package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
  302. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  303. package/lib/systems/animation/core/AnimationRegistry.js +74 -12
  304. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  305. package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
  306. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  307. package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
  308. package/lib/systems/animation/core/AnimationTransitions.js +34 -0
  309. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  310. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  311. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  312. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  313. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  314. package/lib/systems/animation/core/index.d.ts +1 -1
  315. package/lib/systems/animation/core/index.d.ts.map +1 -1
  316. package/lib/systems/animation/core/types.d.ts +24 -0
  317. package/lib/systems/animation/core/types.d.ts.map +1 -1
  318. package/lib/systems/animation/core/types.js +27 -11
  319. package/lib/systems/animation/core/types.js.map +1 -1
  320. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  321. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  322. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  323. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  324. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  325. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  326. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  327. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  328. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  329. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  330. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  331. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  332. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  333. package/lib/systems/bodypart/types.js.map +1 -1
  334. package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
  335. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  336. package/lib/systems/breathing/feedback.js.map +1 -1
  337. package/lib/systems/breathing/integration.js.map +1 -1
  338. package/lib/systems/combat/BalanceSystem.js +19 -19
  339. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  340. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  341. package/lib/systems/combat/CombatStateSystem.js +17 -17
  342. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  343. package/lib/systems/combat/ConsciousnessSystem.js +24 -24
  344. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  345. package/lib/systems/combat/FallIntegration.js.map +1 -1
  346. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  347. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  348. package/lib/systems/combat/PainResponseSystem.js +21 -21
  349. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  350. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  351. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  352. package/lib/systems/combat/typeGuards.js.map +1 -1
  353. package/lib/systems/effects.js.map +1 -1
  354. package/lib/systems/game.js.map +1 -1
  355. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  356. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  357. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  358. package/lib/systems/movement/integration.js.map +1 -1
  359. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  360. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  361. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  362. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  363. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  364. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  365. package/lib/systems/physics/SpeedModifierSystem.js +6 -6
  366. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  367. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  368. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  369. package/lib/systems/trigram/StanceManager.js.map +1 -1
  370. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  371. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  372. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  373. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  374. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  375. package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
  376. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  377. package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
  378. package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
  379. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  380. package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
  381. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  382. package/lib/systems/trigram/techniques/index.js.map +1 -1
  383. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  384. package/lib/systems/trigram/types.js.map +1 -1
  385. package/lib/systems/types.js.map +1 -1
  386. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  387. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  388. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  389. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  390. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  391. package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
  392. package/lib/types/AccessibilityTypes.js.map +1 -1
  393. package/lib/types/LayoutTypes.js.map +1 -1
  394. package/lib/types/PhysicsTypes.js.map +1 -1
  395. package/lib/types/common.js.map +1 -1
  396. package/lib/types/constants/animations.js.map +1 -1
  397. package/lib/types/constants/colors.js.map +1 -1
  398. package/lib/types/constants/designSystem.js.map +1 -1
  399. package/lib/types/constants/index.js.map +1 -1
  400. package/lib/types/constants/layout.js.map +1 -1
  401. package/lib/types/constants/performance.js.map +1 -1
  402. package/lib/types/constants/typography.js.map +1 -1
  403. package/lib/types/constants/ui.js.map +1 -1
  404. package/lib/types/facial.js +19 -19
  405. package/lib/types/facial.js.map +1 -1
  406. package/lib/types/hand-animation.js.map +1 -1
  407. package/lib/types/injury.js.map +1 -1
  408. package/lib/types/muscle.js.map +1 -1
  409. package/lib/types/physics.js.map +1 -1
  410. package/lib/types/physicsConstants.js.map +1 -1
  411. package/lib/types/player-visual.d.ts +1 -1
  412. package/lib/types/player-visual.d.ts.map +1 -1
  413. package/lib/types/skeletal.js.map +1 -1
  414. package/lib/types/techniqueId.js.map +1 -1
  415. package/lib/utils/accessibility.js.map +1 -1
  416. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  417. package/lib/utils/assetConfig.js.map +1 -1
  418. package/lib/utils/characterScaling.js.map +1 -1
  419. package/lib/utils/colorHelpers.js.map +1 -1
  420. package/lib/utils/colorUtils.js.map +1 -1
  421. package/lib/utils/combatReadiness.js.map +1 -1
  422. package/lib/utils/controlMapping.js.map +1 -1
  423. package/lib/utils/deviceDetection.js +6 -7
  424. package/lib/utils/deviceDetection.js.map +1 -1
  425. package/lib/utils/effectUtils.js.map +1 -1
  426. package/lib/utils/fabricTextures.js.map +1 -1
  427. package/lib/utils/hapticFeedback.js.map +1 -1
  428. package/lib/utils/haptics.js.map +1 -1
  429. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  430. package/lib/utils/inputSystem.js.map +1 -1
  431. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  432. package/lib/utils/math.js.map +1 -1
  433. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  434. package/lib/utils/mobileUIUtils.js.map +1 -1
  435. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  436. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  437. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  438. package/lib/utils/performanceOptimization.js.map +1 -1
  439. package/lib/utils/player3DHelpers.js.map +1 -1
  440. package/lib/utils/playerUtils.js.map +1 -1
  441. package/lib/utils/responsiveLayout.js.map +1 -1
  442. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  443. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  444. package/lib/utils/safeAreaUtils.js.map +1 -1
  445. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  446. package/lib/utils/skeletonScaling.js.map +1 -1
  447. package/lib/utils/stanceHelpers.js.map +1 -1
  448. package/lib/utils/threeObjectPool.js.map +1 -1
  449. package/lib/utils/visualEffects.js.map +1 -1
  450. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"KoreanSignage3D.js","names":[],"sources":["../../../../../src/components/shared/three/scene/KoreanSignage3D.tsx"],"sourcesContent":["/**\n * KoreanSignage3D - Three.js 3D Korean text signage with emissive glow\n *\n * Renders Korean text signs with neon emissive effects positioned around the arena\n * Creates an immersive cyberpunk Korean martial arts atmosphere\n *\n * Note: Uses drei's Text component with default Inter font which supports Korean\n * The `font` prop requires a font FILE URL (.ttf/.woff), not a CSS font-family string\n */\n\nimport { Text } from \"@react-three/drei\";\nimport React, { Suspense, useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Props for the KoreanSignage3D component.\n */\nexport interface KoreanSignage3DProps {\n /** Scale factor for signage size (1.0 = desktop, <1.0 = mobile). Defaults to 1.0 */\n readonly scale?: number;\n}\n\n/**\n * KoreanSignage3D Component\n * Creates Korean text signs with neon emissive glow around the combat arena\n *\n * Signs:\n * - \"전투\" (Combat) - Left wall, gold accent\n * - \"흑괘\" (Black Trigram) - Right wall, cyan accent\n * - \"급소격\" (Vital Point Strike) - Back wall, red accent\n */\nexport const KoreanSignage3D: React.FC<KoreanSignage3DProps> = ({\n scale = 1.0,\n}) => {\n const goldNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.ACCENT_GOLD,\n toneMapped: false, // Prevent tone mapping for bloom effect\n }),\n [],\n );\n\n const cyanNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.PRIMARY_CYAN,\n toneMapped: false,\n }),\n [],\n );\n\n const redNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.KOREAN_RED,\n toneMapped: false,\n }),\n [],\n );\n\n const leftWallX = -12 * scale;\n const rightWallX = 12 * scale;\n const backWallZ = -14 * scale;\n const signHeight = 5 * scale;\n const fontSize = 1.5 * scale;\n const outlineWidth = 0.05 * scale;\n\n useEffect(() => {\n return () => {\n goldNeonMaterial.dispose();\n cyanNeonMaterial.dispose();\n redNeonMaterial.dispose();\n };\n }, [goldNeonMaterial, cyanNeonMaterial, redNeonMaterial]);\n\n return (\n <Suspense fallback={null}>\n <group>\n {/* \"전투\" (Combat) sign - left wall */}\n {/* Note: material prop takes precedence over color prop in Text component */}\n {/* Note: Omitting font prop uses default Inter font which supports Korean */}\n <Text\n position={[leftWallX, signHeight, 0]}\n rotation={[0, Math.PI / 2, 0]}\n fontSize={fontSize}\n outlineColor={KOREAN_COLORS.PRIMARY_CYAN}\n outlineWidth={outlineWidth}\n material={goldNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 전투\n </Text>\n\n {/* \"흑괘\" (Black Trigram) sign - right wall */}\n <Text\n position={[rightWallX, signHeight, 0]}\n rotation={[0, -Math.PI / 2, 0]}\n fontSize={fontSize}\n outlineColor={KOREAN_COLORS.ACCENT_GOLD}\n outlineWidth={outlineWidth}\n material={cyanNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 흑괘\n </Text>\n\n {/* \"급소격\" (Vital Point Strike) sign - back wall */}\n <Text\n position={[0, signHeight, backWallZ]}\n rotation={[0, 0, 0]}\n fontSize={fontSize * 0.8} // Slightly smaller for back wall\n outlineColor={KOREAN_COLORS.ACCENT_GOLD}\n outlineWidth={outlineWidth}\n material={redNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 급소격\n </Text>\n </group>\n </Suspense>\n );\n};\n\nexport default KoreanSignage3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,mBAAmD,EAC9D,QAAQ,QACJ;CACJ,MAAM,mBAAmB,cAErB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;EACb,CAAC,EACJ,EAAE,CACH;CAED,MAAM,mBAAmB,cAErB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;EACb,CAAC,EACJ,EAAE,CACH;CAED,MAAM,kBAAkB,cAEpB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;EACb,CAAC,EACJ,EAAE,CACH;CAED,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,KAAK;CACxB,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,IAAI;CACvB,MAAM,WAAW,MAAM;CACvB,MAAM,eAAe,MAAO;CAE5B,gBAAgB;EACd,aAAa;GACX,iBAAiB,SAAS;GAC1B,iBAAiB,SAAS;GAC1B,gBAAgB,SAAS;;IAE1B;EAAC;EAAkB;EAAkB;EAAgB,CAAC;CAEzD,OACE,oBAAC,UAAD;EAAU,UAAU;YAClB,qBAAC,SAAD,EAAA,UAAA;GAIE,oBAAC,MAAD;IACE,UAAU;KAAC;KAAW;KAAY;KAAE;IACpC,UAAU;KAAC;KAAG,KAAK,KAAK;KAAG;KAAE;IACnB;IACV,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;IAEM,CAAA;GAGP,oBAAC,MAAD;IACE,UAAU;KAAC;KAAY;KAAY;KAAE;IACrC,UAAU;KAAC;KAAG,CAAC,KAAK,KAAK;KAAG;KAAE;IACpB;IACV,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;IAEM,CAAA;GAGP,oBAAC,MAAD;IACE,UAAU;KAAC;KAAG;KAAY;KAAU;IACpC,UAAU;KAAC;KAAG;KAAG;KAAE;IACnB,UAAU,WAAW;IACrB,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;IAEM,CAAA;GACD,EAAA,CAAA;EACC,CAAA"}
1
+ {"version":3,"file":"KoreanSignage3D.js","names":[],"sources":["../../../../../src/components/shared/three/scene/KoreanSignage3D.tsx"],"sourcesContent":["/**\n * KoreanSignage3D - Three.js 3D Korean text signage with emissive glow\n *\n * Renders Korean text signs with neon emissive effects positioned around the arena\n * Creates an immersive cyberpunk Korean martial arts atmosphere\n *\n * Note: Uses drei's Text component with default Inter font which supports Korean\n * The `font` prop requires a font FILE URL (.ttf/.woff), not a CSS font-family string\n */\n\nimport { Text } from \"@react-three/drei\";\nimport React, { Suspense, useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Props for the KoreanSignage3D component.\n */\nexport interface KoreanSignage3DProps {\n /** Scale factor for signage size (1.0 = desktop, <1.0 = mobile). Defaults to 1.0 */\n readonly scale?: number;\n}\n\n/**\n * KoreanSignage3D Component\n * Creates Korean text signs with neon emissive glow around the combat arena\n *\n * Signs:\n * - \"전투\" (Combat) - Left wall, gold accent\n * - \"흑괘\" (Black Trigram) - Right wall, cyan accent\n * - \"급소격\" (Vital Point Strike) - Back wall, red accent\n */\nexport const KoreanSignage3D: React.FC<KoreanSignage3DProps> = ({\n scale = 1.0,\n}) => {\n const goldNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.ACCENT_GOLD,\n toneMapped: false, // Prevent tone mapping for bloom effect\n }),\n [],\n );\n\n const cyanNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.PRIMARY_CYAN,\n toneMapped: false,\n }),\n [],\n );\n\n const redNeonMaterial = useMemo(\n () =>\n new THREE.MeshBasicMaterial({\n color: KOREAN_COLORS.KOREAN_RED,\n toneMapped: false,\n }),\n [],\n );\n\n const leftWallX = -12 * scale;\n const rightWallX = 12 * scale;\n const backWallZ = -14 * scale;\n const signHeight = 5 * scale;\n const fontSize = 1.5 * scale;\n const outlineWidth = 0.05 * scale;\n\n useEffect(() => {\n return () => {\n goldNeonMaterial.dispose();\n cyanNeonMaterial.dispose();\n redNeonMaterial.dispose();\n };\n }, [goldNeonMaterial, cyanNeonMaterial, redNeonMaterial]);\n\n return (\n <Suspense fallback={null}>\n <group>\n {/* \"전투\" (Combat) sign - left wall */}\n {/* Note: material prop takes precedence over color prop in Text component */}\n {/* Note: Omitting font prop uses default Inter font which supports Korean */}\n <Text\n position={[leftWallX, signHeight, 0]}\n rotation={[0, Math.PI / 2, 0]}\n fontSize={fontSize}\n outlineColor={KOREAN_COLORS.PRIMARY_CYAN}\n outlineWidth={outlineWidth}\n material={goldNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 전투\n </Text>\n\n {/* \"흑괘\" (Black Trigram) sign - right wall */}\n <Text\n position={[rightWallX, signHeight, 0]}\n rotation={[0, -Math.PI / 2, 0]}\n fontSize={fontSize}\n outlineColor={KOREAN_COLORS.ACCENT_GOLD}\n outlineWidth={outlineWidth}\n material={cyanNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 흑괘\n </Text>\n\n {/* \"급소격\" (Vital Point Strike) sign - back wall */}\n <Text\n position={[0, signHeight, backWallZ]}\n rotation={[0, 0, 0]}\n fontSize={fontSize * 0.8} // Slightly smaller for back wall\n outlineColor={KOREAN_COLORS.ACCENT_GOLD}\n outlineWidth={outlineWidth}\n material={redNeonMaterial}\n anchorX=\"center\"\n anchorY=\"middle\"\n >\n 급소격\n </Text>\n </group>\n </Suspense>\n );\n};\n\nexport default KoreanSignage3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,mBAAmD,EAC9D,QAAQ,QACJ;CACJ,MAAM,mBAAmB,cAErB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;CACd,CAAC,GACH,CAAC,CACH;CAEA,MAAM,mBAAmB,cAErB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;CACd,CAAC,GACH,CAAC,CACH;CAEA,MAAM,kBAAkB,cAEpB,IAAI,MAAM,kBAAkB;EAC1B,OAAO,cAAc;EACrB,YAAY;CACd,CAAC,GACH,CAAC,CACH;CAEA,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,KAAK;CACxB,MAAM,YAAY,MAAM;CACxB,MAAM,aAAa,IAAI;CACvB,MAAM,WAAW,MAAM;CACvB,MAAM,eAAe,MAAO;CAE5B,gBAAgB;EACd,aAAa;GACX,iBAAiB,QAAQ;GACzB,iBAAiB,QAAQ;GACzB,gBAAgB,QAAQ;EAC1B;CACF,GAAG;EAAC;EAAkB;EAAkB;CAAe,CAAC;CAExD,OACE,oBAAC,UAAD;EAAU,UAAU;YAClB,qBAAC,SAAD,EAAA,UAAA;GAIE,oBAAC,MAAD;IACE,UAAU;KAAC;KAAW;KAAY;IAAC;IACnC,UAAU;KAAC;KAAG,KAAK,KAAK;KAAG;IAAC;IAClB;IACV,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;GAEK,CAAA;GAGN,oBAAC,MAAD;IACE,UAAU;KAAC;KAAY;KAAY;IAAC;IACpC,UAAU;KAAC;KAAG,CAAC,KAAK,KAAK;KAAG;IAAC;IACnB;IACV,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;GAEK,CAAA;GAGN,oBAAC,MAAD;IACE,UAAU;KAAC;KAAG;KAAY;IAAS;IACnC,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,UAAU,WAAW;IACrB,cAAc,cAAc;IACd;IACd,UAAU;IACV,SAAQ;IACR,SAAQ;cACT;GAEK,CAAA;EACD,EAAA,CAAA;CACC,CAAA;AAEd"}
@@ -1 +1 @@
1
- {"version":3,"file":"ArchetypeCard.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ArchetypeCard.tsx"],"sourcesContent":["/**\n * ArchetypeCard - Three.js-compatible character archetype card\n * \n * Displays player archetype information with Korean theming\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../../systems/types\";\n\n/**\n * Props for ArchetypeCard component\n */\nexport interface ArchetypeCardProps {\n readonly archetype: PlayerArchetype;\n readonly onSelect?: (archetype: PlayerArchetype) => void;\n readonly isSelected?: boolean;\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly showStats?: boolean;\n readonly testId?: string;\n}\n\n/**\n * ArchetypeCard Component\n * \n * A card component for displaying player archetypes with Korean martial arts styling.\n * Includes archetype name, description, and optional stats.\n * \n * @example\n * ```tsx\n * <ArchetypeCard\n * archetype={PlayerArchetype.MUSA}\n * onSelect={(archetype) => console.log(archetype)}\n * showStats\n * />\n * ```\n */\nexport const ArchetypeCard: React.FC<ArchetypeCardProps> = ({\n archetype,\n onSelect,\n isSelected = false,\n position = [0, 0, 0],\n width = 320,\n showStats = true,\n testId,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n\n const archetypeData = useMemo(\n () => PLAYER_ARCHETYPES_DATA[archetype],\n [archetype]\n );\n\n const handleClick = useCallback(() => {\n if (onSelect) {\n onSelect(archetype);\n }\n }, [onSelect, archetype]);\n\n const handleMouseEnter = useCallback(() => {\n setIsHovered(true);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n }, []);\n\n const cardStyle = useMemo<React.CSSProperties>(() => {\n let background = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95);\n let borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4);\n let boxShadow = `0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.3)}`;\n\n if (isSelected) {\n background = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.15);\n borderColor = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9);\n boxShadow = `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)},\n 0 0 20px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}\n `;\n } else if (isHovered) {\n background = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.1);\n borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.7);\n boxShadow = `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.4)},\n 0 0 15px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3)}\n `;\n }\n\n return {\n width: `${width}px`,\n background,\n border: `2px solid ${borderColor}`,\n borderRadius: \"8px\",\n padding: \"16px\",\n cursor: onSelect ? \"pointer\" : \"default\",\n transition: \"all 0.3s ease\",\n boxShadow,\n transform: isHovered && onSelect ? \"translateY(-4px)\" : \"translateY(0)\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n };\n }, [width, isSelected, isHovered, onSelect]);\n\n const headerStyle = useMemo<React.CSSProperties>(\n () => ({\n marginBottom: \"12px\",\n paddingBottom: \"8px\",\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3)}`,\n }),\n []\n );\n\n const titleStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"20px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n marginBottom: \"4px\",\n }),\n []\n );\n\n const subtitleStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"14px\",\n fontStyle: \"italic\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY),\n opacity: 0.8,\n }),\n []\n );\n\n const descriptionStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"14px\",\n lineHeight: \"1.6\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n marginBottom: showStats ? \"12px\" : \"0\",\n }),\n [showStats]\n );\n\n const statsContainerStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: \"8px\",\n marginTop: \"12px\",\n }),\n []\n );\n\n const statItemStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"4px\",\n }),\n []\n );\n\n const statLabelStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"12px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY),\n fontWeight: \"bold\",\n }),\n []\n );\n\n const statValueStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n style={cardStyle}\n data-testid={testId ?? `archetype-card-${archetype}`}\n >\n {/* Header */}\n <div style={headerStyle}>\n <div style={titleStyle}>{archetypeData.name.korean}</div>\n <div style={subtitleStyle}>{archetypeData.name.english}</div>\n </div>\n\n {/* Description */}\n <div style={descriptionStyle}>\n {archetypeData.description.korean}\n </div>\n\n {/* Stats */}\n {showStats && (\n <div style={statsContainerStyle}>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>공격 | Attack</div>\n <div style={statValueStyle}>{archetypeData.stats.attackPower}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>방어 | Defense</div>\n <div style={statValueStyle}>{archetypeData.stats.defense}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>속도 | Speed</div>\n <div style={statValueStyle}>{archetypeData.stats.speed}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>기력 | Ki</div>\n <div style={statValueStyle}>{archetypeData.baseKi}</div>\n </div>\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nArchetypeCard.displayName = \"ArchetypeCard\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ArchetypeCard.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ArchetypeCard.tsx"],"sourcesContent":["/**\n * ArchetypeCard - Three.js-compatible character archetype card\n * \n * Displays player archetype information with Korean theming\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../../systems/types\";\n\n/**\n * Props for ArchetypeCard component\n */\nexport interface ArchetypeCardProps {\n readonly archetype: PlayerArchetype;\n readonly onSelect?: (archetype: PlayerArchetype) => void;\n readonly isSelected?: boolean;\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly showStats?: boolean;\n readonly testId?: string;\n}\n\n/**\n * ArchetypeCard Component\n * \n * A card component for displaying player archetypes with Korean martial arts styling.\n * Includes archetype name, description, and optional stats.\n * \n * @example\n * ```tsx\n * <ArchetypeCard\n * archetype={PlayerArchetype.MUSA}\n * onSelect={(archetype) => console.log(archetype)}\n * showStats\n * />\n * ```\n */\nexport const ArchetypeCard: React.FC<ArchetypeCardProps> = ({\n archetype,\n onSelect,\n isSelected = false,\n position = [0, 0, 0],\n width = 320,\n showStats = true,\n testId,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n\n const archetypeData = useMemo(\n () => PLAYER_ARCHETYPES_DATA[archetype],\n [archetype]\n );\n\n const handleClick = useCallback(() => {\n if (onSelect) {\n onSelect(archetype);\n }\n }, [onSelect, archetype]);\n\n const handleMouseEnter = useCallback(() => {\n setIsHovered(true);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n }, []);\n\n const cardStyle = useMemo<React.CSSProperties>(() => {\n let background = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95);\n let borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4);\n let boxShadow = `0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.3)}`;\n\n if (isSelected) {\n background = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.15);\n borderColor = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9);\n boxShadow = `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)},\n 0 0 20px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}\n `;\n } else if (isHovered) {\n background = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.1);\n borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.7);\n boxShadow = `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.4)},\n 0 0 15px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3)}\n `;\n }\n\n return {\n width: `${width}px`,\n background,\n border: `2px solid ${borderColor}`,\n borderRadius: \"8px\",\n padding: \"16px\",\n cursor: onSelect ? \"pointer\" : \"default\",\n transition: \"all 0.3s ease\",\n boxShadow,\n transform: isHovered && onSelect ? \"translateY(-4px)\" : \"translateY(0)\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n };\n }, [width, isSelected, isHovered, onSelect]);\n\n const headerStyle = useMemo<React.CSSProperties>(\n () => ({\n marginBottom: \"12px\",\n paddingBottom: \"8px\",\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3)}`,\n }),\n []\n );\n\n const titleStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"20px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n marginBottom: \"4px\",\n }),\n []\n );\n\n const subtitleStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"14px\",\n fontStyle: \"italic\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY),\n opacity: 0.8,\n }),\n []\n );\n\n const descriptionStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"14px\",\n lineHeight: \"1.6\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n marginBottom: showStats ? \"12px\" : \"0\",\n }),\n [showStats]\n );\n\n const statsContainerStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: \"8px\",\n marginTop: \"12px\",\n }),\n []\n );\n\n const statItemStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"4px\",\n }),\n []\n );\n\n const statLabelStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"12px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY),\n fontWeight: \"bold\",\n }),\n []\n );\n\n const statValueStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n style={cardStyle}\n data-testid={testId ?? `archetype-card-${archetype}`}\n >\n {/* Header */}\n <div style={headerStyle}>\n <div style={titleStyle}>{archetypeData.name.korean}</div>\n <div style={subtitleStyle}>{archetypeData.name.english}</div>\n </div>\n\n {/* Description */}\n <div style={descriptionStyle}>\n {archetypeData.description.korean}\n </div>\n\n {/* Stats */}\n {showStats && (\n <div style={statsContainerStyle}>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>공격 | Attack</div>\n <div style={statValueStyle}>{archetypeData.stats.attackPower}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>방어 | Defense</div>\n <div style={statValueStyle}>{archetypeData.stats.defense}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>속도 | Speed</div>\n <div style={statValueStyle}>{archetypeData.stats.speed}</div>\n </div>\n <div style={statItemStyle}>\n <div style={statLabelStyle}>기력 | Ki</div>\n <div style={statValueStyle}>{archetypeData.baseKi}</div>\n </div>\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nArchetypeCard.displayName = \"ArchetypeCard\";\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return KOREAN_COLORS.HEALTH_FULL;\n if (health >= 60) return KOREAN_COLORS.ACCENT_GOLD;\n if (health >= 40) return KOREAN_COLORS.WARNING_ORANGE;\n if (health >= 20) return KOREAN_COLORS.PAIN_INDICATOR;\n return KOREAN_COLORS.NEGATIVE_RED_DARK;\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return KOREAN_COLORS.HEALTH_FULL;\n if (health >= 60) return KOREAN_COLORS.ACCENT_GOLD;\n if (health >= 40) return KOREAN_COLORS.WARNING_ORANGE;\n if (health >= 20) return KOREAN_COLORS.PAIN_INDICATOR;\n return KOREAN_COLORS.NEGATIVE_RED_DARK;\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n if (!breathingState.visible) {\n return null;\n }\n\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, 0.8)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CACJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;CAEhE,gBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,eAAe,KAAK,KAAK,CAAC;KACzB,IAAI;EAEP,aAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAEN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;EAMtE,OAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAEJ,CAAa;IAClE,CAAC,QAAQ,YAAY,CAAC;CAEzB,IAAI,CAAC,eAAe,SAClB,OAAO;CAGT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAEvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;CAEvE,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;GAC1D,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,cAAc,gBAAgB,GAAI,GAClD,gBAAgB,cAAc,cAAc,GAAI;KACpD,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
1
+ {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n if (!breathingState.visible) {\n return null;\n }\n\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, 0.8)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CACJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,IAAI,CAAC;CAE/D,gBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,eAAe,KAAK,IAAI,CAAC;EAC3B,GAAG,GAAG;EAEN,aAAa,cAAc,QAAQ;CACrC,GAAG,CAAC,CAAC;CAEL,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,MAAM;EAC9D,MAAM,eAAe,0BAA0B,gBAAgB,MAAM;EAMrE,OAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,WAAW,IAC9C,GACiB,0BAA0B,WAAW,MAEJ,CAAY;CACpE,GAAG,CAAC,QAAQ,WAAW,CAAC;CAExB,IAAI,CAAC,eAAe,SAClB,OAAO;CAGT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAEvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,GAAI;CAEtE,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB,gBAAgB,cAAc,OAAO,EAAG;GACzD,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,OAAO;GACjF,WAAW,YAAY,gBAAgB,eAAe,OAAO,EAAG;GAChE,WAAW;GACX,eAAe;EACjB;YAbF,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,EAAG,EAAE;GAC5E;aAEC,eAAe;EACb,CAAA,GAGL,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;GACP;aALF,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,CAAC;KAC9C,YAAY,WAAW,gBAAgB,eAAe,OAAO,EAAG;KAChE,YAAY;IACd;cATF;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;IACnD;OAGL,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,cAAc,gBAAgB,EAAG,IACjD,gBAAgB,cAAc,cAAc,EAAG;KACnD,YAAY;IACd;cAEC,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;GACrE,CAAA,CACF;IACF;;AAEX;AAEA,mBAAmB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"CombatReadinessBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/CombatReadinessBar.tsx"],"sourcesContent":["/**\n * CombatReadinessBar Component - 10-bar segmented combat readiness display\n * \n * Displays comprehensive combat readiness with:\n * - 10 segmented bars representing 10% each\n * - Color transitions: Green (>80%), Yellow (60-79%), Orange (40-59%), Red (20-39%), Dark Red (<20%)\n * - Smooth 0.3s transition animations\n * - Korean/English bilingual labels\n * - Numeric percentage display\n * - Responsive sizing for mobile/tablet/desktop\n * - Real-time calculation from body health, pain, consciousness, and balance\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { \n calculateCombatReadiness, \n getCombatReadinessColor, \n getCombatReadinessLabel,\n getCombatReadinessBars\n} from \"../../../../utils/combatReadiness\";\nimport type { PlayerState } from \"../../../../systems/player\";\n\nexport interface CombatReadinessBarProps {\n /** Player state containing all combat factors */\n readonly player: PlayerState;\n /** Player identifier for test ID */\n readonly playerId: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * CombatReadinessBar - 10-bar segmented combat readiness display with Korean theming\n * \n * Calculates and displays overall combat readiness from multiple factors:\n * - Body part health (40% weight)\n * - Pain level (20% weight)\n * - Consciousness (20% weight)\n * - Balance state (20% weight)\n * \n * @example\n * ```tsx\n * <CombatReadinessBar \n * player={playerState} \n * playerId=\"player-1\"\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatReadinessBar: React.FC<CombatReadinessBarProps> = ({\n player,\n playerId,\n isMobile,\n}) => {\n const readiness = useMemo(\n () => calculateCombatReadiness(player),\n [player]\n );\n\n const segments = 10;\n const filledSegments = getCombatReadinessBars(readiness, segments);\n const readinessColor = getCombatReadinessColor(readiness);\n const readinessLabel = getCombatReadinessLabel(readiness);\n const shouldPulse = readiness < 20;\n\n const barWidth = isMobile ? 180 : 250;\n const barHeight = isMobile ? 16 : 20;\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"8px 12px\" : \"12px 16px\";\n\n const statusText = `${readiness}% ${readinessLabel.korean}`;\n\n return (\n <div\n data-testid={`combat-readiness-bar-${playerId}`}\n role=\"progressbar\"\n aria-label=\"전투 준비도 | Combat Readiness\"\n aria-valuenow={readiness}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={`${readiness}% Combat Readiness - ${readinessLabel.english}`}\n style={{\n width: `${barWidth}px`,\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n }}\n >\n {/* Label and numeric/status display */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n fontWeight: \"bold\",\n }}\n >\n <span>전투 준비도 | Combat Readiness</span>\n <span data-testid={`combat-readiness-value-${playerId}`}>\n {statusText}\n </span>\n </div>\n\n {/* 10-segment combat readiness bar */}\n <div\n style={{\n display: \"flex\",\n gap: \"3px\",\n height: `${barHeight}px`,\n animation: shouldPulse ? \"healthPulse 0.8s infinite\" : \"none\",\n }}\n >\n {Array.from({ length: segments }).map((_, index) => (\n <div\n key={index}\n data-testid={`combat-readiness-segment-${playerId}-${index}`}\n style={{\n flex: 1,\n backgroundColor:\n index < filledSegments\n ? hexToRgbaString(readinessColor, 1)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 1),\n borderRadius: \"2px\",\n transition: \"background-color 0.3s ease-in-out\",\n boxShadow:\n index < filledSegments\n ? `0 0 8px ${hexToRgbaString(readinessColor, 0.4)}`\n : \"none\",\n }}\n />\n ))}\n </div>\n\n {/* Breakdown tooltip (hover) - optional enhancement */}\n <div\n data-testid={`combat-readiness-breakdown-${playerId}`}\n style={{\n display: \"none\", // Hidden by default, can be shown on hover\n fontSize: `${fontSize - 2}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n marginTop: \"4px\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Detailed breakdown for debugging/advanced players */}\n <div>Body: {player.bodyPartHealth ? \"tracked\" : \"aggregate\"}</div>\n <div>Pain: {Math.round(player.pain)}%</div>\n <div>Consciousness: {Math.round(player.consciousness)}%</div>\n <div>Balance: {Math.round(player.balance)}%</div>\n </div>\n </div>\n );\n};\n\nexport default CombatReadinessBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,sBAAyD,EACpE,QACA,UACA,eACI;CACJ,MAAM,YAAY,cACV,yBAAyB,OAAO,EACtC,CAAC,OAAO,CACT;CAED,MAAM,WAAW;CACjB,MAAM,iBAAiB,uBAAuB,WAAW,SAAS;CAClE,MAAM,iBAAiB,wBAAwB,UAAU;CACzD,MAAM,iBAAiB,wBAAwB,UAAU;CACzD,MAAM,cAAc,YAAY;CAEhC,MAAM,WAAW,WAAW,MAAM;CAClC,MAAM,YAAY,WAAW,KAAK;CAClC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,aAAa;CAExC,MAAM,aAAa,GAAG,UAAU,IAAI,eAAe;CAEnD,OACE,qBAAC,OAAD;EACE,eAAa,wBAAwB;EACrC,MAAK;EACL,cAAW;EACX,iBAAe;EACf,iBAAe;EACf,iBAAe;EACf,kBAAgB,GAAG,UAAU,uBAAuB,eAAe;EACnE,OAAO;GACL,OAAO,GAAG,SAAS;GACnB;GACA,iBAAiB,gBAAgB,cAAc,oBAAoB,EAAE;GACrE,cAAc;GACd,QAAQ,aAAa,gBAAgB,cAAc,cAAc,EAAE;GACnE,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;GACxE;YAfH;GAkBE,qBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,OAAO,gBAAgB,cAAc,cAAc,EAAE;KACrD,YAAY,YAAY;KACxB,cAAc;KACd,SAAS;KACT,gBAAgB;KAChB,YAAY;KACb;cATH,CAWE,oBAAC,QAAD,EAAA,UAAM,6BAAgC,CAAA,EACtC,oBAAC,QAAD;KAAM,eAAa,0BAA0B;eAC1C;KACI,CAAA,CACH;;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,KAAK;KACL,QAAQ,GAAG,UAAU;KACrB,WAAW,cAAc,8BAA8B;KACxD;cAEA,MAAM,KAAK,EAAE,QAAQ,UAAU,CAAC,CAAC,KAAK,GAAG,UACxC,oBAAC,OAAD;KAEE,eAAa,4BAA4B,SAAS,GAAG;KACrD,OAAO;MACL,MAAM;MACN,iBACE,QAAQ,iBACJ,gBAAgB,gBAAgB,EAAE,GAClC,gBAAgB,cAAc,sBAAsB,EAAE;MAC5D,cAAc;MACd,YAAY;MACZ,WACE,QAAQ,iBACJ,WAAW,gBAAgB,gBAAgB,GAAI,KAC/C;MACP;KACD,EAfK,MAeL,CACF;IACE,CAAA;GAGN,qBAAC,OAAD;IACE,eAAa,8BAA8B;IAC3C,OAAO;KACL,SAAS;KACT,UAAU,GAAG,WAAW,EAAE;KAC1B,OAAO,gBAAgB,cAAc,gBAAgB,EAAE;KACvD,WAAW;KACX,YAAY,YAAY;KACzB;cARH;KAWE,qBAAC,OAAD,EAAA,UAAA,CAAK,UAAO,OAAO,iBAAiB,YAAY,YAAkB,EAAA,CAAA;KAClE,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAO,KAAK,MAAM,OAAO,KAAK;MAAC;MAAO,EAAA,CAAA;KAC3C,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAgB,KAAK,MAAM,OAAO,cAAc;MAAC;MAAO,EAAA,CAAA;KAC7D,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAU,KAAK,MAAM,OAAO,QAAQ;MAAC;MAAO,EAAA,CAAA;KAC7C;;GACF"}
1
+ {"version":3,"file":"CombatReadinessBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/CombatReadinessBar.tsx"],"sourcesContent":["/**\n * CombatReadinessBar Component - 10-bar segmented combat readiness display\n * \n * Displays comprehensive combat readiness with:\n * - 10 segmented bars representing 10% each\n * - Color transitions: Green (>80%), Yellow (60-79%), Orange (40-59%), Red (20-39%), Dark Red (<20%)\n * - Smooth 0.3s transition animations\n * - Korean/English bilingual labels\n * - Numeric percentage display\n * - Responsive sizing for mobile/tablet/desktop\n * - Real-time calculation from body health, pain, consciousness, and balance\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { \n calculateCombatReadiness, \n getCombatReadinessColor, \n getCombatReadinessLabel,\n getCombatReadinessBars\n} from \"../../../../utils/combatReadiness\";\nimport type { PlayerState } from \"../../../../systems/player\";\n\nexport interface CombatReadinessBarProps {\n /** Player state containing all combat factors */\n readonly player: PlayerState;\n /** Player identifier for test ID */\n readonly playerId: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * CombatReadinessBar - 10-bar segmented combat readiness display with Korean theming\n * \n * Calculates and displays overall combat readiness from multiple factors:\n * - Body part health (40% weight)\n * - Pain level (20% weight)\n * - Consciousness (20% weight)\n * - Balance state (20% weight)\n * \n * @example\n * ```tsx\n * <CombatReadinessBar \n * player={playerState} \n * playerId=\"player-1\"\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatReadinessBar: React.FC<CombatReadinessBarProps> = ({\n player,\n playerId,\n isMobile,\n}) => {\n const readiness = useMemo(\n () => calculateCombatReadiness(player),\n [player]\n );\n\n const segments = 10;\n const filledSegments = getCombatReadinessBars(readiness, segments);\n const readinessColor = getCombatReadinessColor(readiness);\n const readinessLabel = getCombatReadinessLabel(readiness);\n const shouldPulse = readiness < 20;\n\n const barWidth = isMobile ? 180 : 250;\n const barHeight = isMobile ? 16 : 20;\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"8px 12px\" : \"12px 16px\";\n\n const statusText = `${readiness}% ${readinessLabel.korean}`;\n\n return (\n <div\n data-testid={`combat-readiness-bar-${playerId}`}\n role=\"progressbar\"\n aria-label=\"전투 준비도 | Combat Readiness\"\n aria-valuenow={readiness}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={`${readiness}% Combat Readiness - ${readinessLabel.english}`}\n style={{\n width: `${barWidth}px`,\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n }}\n >\n {/* Label and numeric/status display */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n fontWeight: \"bold\",\n }}\n >\n <span>전투 준비도 | Combat Readiness</span>\n <span data-testid={`combat-readiness-value-${playerId}`}>\n {statusText}\n </span>\n </div>\n\n {/* 10-segment combat readiness bar */}\n <div\n style={{\n display: \"flex\",\n gap: \"3px\",\n height: `${barHeight}px`,\n animation: shouldPulse ? \"healthPulse 0.8s infinite\" : \"none\",\n }}\n >\n {Array.from({ length: segments }).map((_, index) => (\n <div\n key={index}\n data-testid={`combat-readiness-segment-${playerId}-${index}`}\n style={{\n flex: 1,\n backgroundColor:\n index < filledSegments\n ? hexToRgbaString(readinessColor, 1)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 1),\n borderRadius: \"2px\",\n transition: \"background-color 0.3s ease-in-out\",\n boxShadow:\n index < filledSegments\n ? `0 0 8px ${hexToRgbaString(readinessColor, 0.4)}`\n : \"none\",\n }}\n />\n ))}\n </div>\n\n {/* Breakdown tooltip (hover) - optional enhancement */}\n <div\n data-testid={`combat-readiness-breakdown-${playerId}`}\n style={{\n display: \"none\", // Hidden by default, can be shown on hover\n fontSize: `${fontSize - 2}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n marginTop: \"4px\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Detailed breakdown for debugging/advanced players */}\n <div>Body: {player.bodyPartHealth ? \"tracked\" : \"aggregate\"}</div>\n <div>Pain: {Math.round(player.pain)}%</div>\n <div>Consciousness: {Math.round(player.consciousness)}%</div>\n <div>Balance: {Math.round(player.balance)}%</div>\n </div>\n </div>\n );\n};\n\nexport default CombatReadinessBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,sBAAyD,EACpE,QACA,UACA,eACI;CACJ,MAAM,YAAY,cACV,yBAAyB,MAAM,GACrC,CAAC,MAAM,CACT;CAEA,MAAM,WAAW;CACjB,MAAM,iBAAiB,uBAAuB,WAAW,QAAQ;CACjE,MAAM,iBAAiB,wBAAwB,SAAS;CACxD,MAAM,iBAAiB,wBAAwB,SAAS;CACxD,MAAM,cAAc,YAAY;CAEhC,MAAM,WAAW,WAAW,MAAM;CAClC,MAAM,YAAY,WAAW,KAAK;CAClC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,aAAa;CAExC,MAAM,aAAa,GAAG,UAAU,IAAI,eAAe;CAEnD,OACE,qBAAC,OAAD;EACE,eAAa,wBAAwB;EACrC,MAAK;EACL,cAAW;EACX,iBAAe;EACf,iBAAe;EACf,iBAAe;EACf,kBAAgB,GAAG,UAAU,uBAAuB,eAAe;EACnE,OAAO;GACL,OAAO,GAAG,SAAS;GACnB;GACA,iBAAiB,gBAAgB,cAAc,oBAAoB,CAAC;GACpE,cAAc;GACd,QAAQ,aAAa,gBAAgB,cAAc,cAAc,CAAC;GAClE,WAAW,YAAY,gBAAgB,cAAc,cAAc,EAAG;EACxE;YAfF;GAkBE,qBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,OAAO,gBAAgB,cAAc,cAAc,CAAC;KACpD,YAAY,YAAY;KACxB,cAAc;KACd,SAAS;KACT,gBAAgB;KAChB,YAAY;IACd;cATF,CAWE,oBAAC,QAAD,EAAA,UAAM,4BAA+B,CAAA,GACrC,oBAAC,QAAD;KAAM,eAAa,0BAA0B;eAC1C;IACG,CAAA,CACH;;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,KAAK;KACL,QAAQ,GAAG,UAAU;KACrB,WAAW,cAAc,8BAA8B;IACzD;cAEC,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC,EAAE,KAAK,GAAG,UACxC,oBAAC,OAAD;KAEE,eAAa,4BAA4B,SAAS,GAAG;KACrD,OAAO;MACL,MAAM;MACN,iBACE,QAAQ,iBACJ,gBAAgB,gBAAgB,CAAC,IACjC,gBAAgB,cAAc,sBAAsB,CAAC;MAC3D,cAAc;MACd,YAAY;MACZ,WACE,QAAQ,iBACJ,WAAW,gBAAgB,gBAAgB,EAAG,MAC9C;KACR;IACD,GAfM,KAeN,CACF;GACE,CAAA;GAGL,qBAAC,OAAD;IACE,eAAa,8BAA8B;IAC3C,OAAO;KACL,SAAS;KACT,UAAU,GAAG,WAAW,EAAE;KAC1B,OAAO,gBAAgB,cAAc,gBAAgB,CAAC;KACtD,WAAW;KACX,YAAY,YAAY;IAC1B;cARF;KAWE,qBAAC,OAAD,EAAA,UAAA,CAAK,UAAO,OAAO,iBAAiB,YAAY,WAAiB,EAAA,CAAA;KACjE,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAO,KAAK,MAAM,OAAO,IAAI;MAAE;KAAM,EAAA,CAAA;KAC1C,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAgB,KAAK,MAAM,OAAO,aAAa;MAAE;KAAM,EAAA,CAAA;KAC5D,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAU,KAAK,MAAM,OAAO,OAAO;MAAE;KAAM,EAAA,CAAA;IAC7C;;EACF;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"ComboCounter.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ComboCounter.tsx"],"sourcesContent":["/**\n * ComboCounter - Combo counter display component\n *\n * Displays the current combo count with Korean-English bilingual text.\n * Animates on combo increment and shows milestone indicators.\n *\n * Uses Html overlay from @react-three/drei for rendering within 3D scenes.\n *\n * @module components/shared/three/ui/ComboCounter\n * @category Shared UI\n * @korean 콤보카운터\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./HUDAnimations.css\";\n\n/**\n * Props for the ComboCounter component\n */\nexport interface ComboCounterProps {\n /** Current combo count */\n readonly combo: number;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Minimum combo to display (default: 2) */\n readonly minDisplayCombo?: number;\n}\n\n/**\n * Get combo tier color based on combo count\n */\nfunction getComboColor(combo: number): string {\n if (combo >= 10) {\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n }\n if (combo >= 7) {\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n }\n if (combo >= 5) {\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n }\n if (combo >= 3) {\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }\n return hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY);\n}\n\n/**\n * Get glow color based on combo tier\n */\nfunction getGlowColor(combo: number): string {\n if (combo >= 10) {\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n }\n if (combo >= 7) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n }\n if (combo >= 5) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n }\n if (combo >= 3) {\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6);\n }\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.4);\n}\n\n/**\n * Get combo milestone text\n */\nfunction getComboMilestone(\n combo: number\n): { korean: string; english: string } | null {\n if (combo === 5) {\n return { korean: \"훌륭합니다!\", english: \"Great!\" };\n }\n if (combo === 10) {\n return { korean: \"놀라운 연속 공격!\", english: \"Amazing!\" };\n }\n if (combo === 15) {\n return { korean: \"전설적인 공격!\", english: \"Legendary!\" };\n }\n if (combo === 20) {\n return { korean: \"신의 일격!\", english: \"GODLIKE!\" };\n }\n return null;\n}\n\n/**\n * ComboCounter Component\n *\n * Displays the current combo count with animations and milestone indicators.\n * Only visible when combo >= minDisplayCombo (default: 2).\n *\n * @example\n * ```tsx\n * <ComboCounter\n * combo={5}\n * isMobile={isMobile}\n * />\n * ```\n */\nexport const ComboCounter: React.FC<ComboCounterProps> = ({\n combo,\n isMobile = false,\n minDisplayCombo = 2,\n}) => {\n const [scale, setScale] = useState(1);\n const [showMilestone, setShowMilestone] = useState(false);\n const [prevCombo, setPrevCombo] = useState(combo);\n const milestoneTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null\n );\n const scaleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const position3D: [number, number, number] = useMemo(() => {\n return [0, 4.5, 0];\n }, []);\n\n useEffect(() => {\n if (combo > prevCombo) {\n setTimeout(() => setScale(1.3), 0);\n\n if (scaleTimeoutRef.current) {\n clearTimeout(scaleTimeoutRef.current);\n }\n scaleTimeoutRef.current = setTimeout(() => {\n setScale(1);\n }, 150);\n\n const milestone = getComboMilestone(combo);\n if (milestone) {\n setTimeout(() => setShowMilestone(true), 0);\n if (milestoneTimeoutRef.current) {\n clearTimeout(milestoneTimeoutRef.current);\n }\n milestoneTimeoutRef.current = setTimeout(() => {\n setShowMilestone(false);\n }, 1500);\n }\n }\n setTimeout(() => setPrevCombo(combo), 0);\n }, [combo, prevCombo]);\n\n useEffect(() => {\n return () => {\n if (milestoneTimeoutRef.current) {\n clearTimeout(milestoneTimeoutRef.current);\n }\n if (scaleTimeoutRef.current) {\n clearTimeout(scaleTimeoutRef.current);\n }\n };\n }, []);\n\n if (combo < minDisplayCombo) {\n return null;\n }\n\n const milestone = getComboMilestone(combo);\n const comboColor = getComboColor(combo);\n const glowColor = getGlowColor(combo);\n const getMilestoneBackground = (comboVal: number): string => {\n if (comboVal >= 10) {\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.3);\n }\n if (comboVal >= 7) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.3);\n }\n if (comboVal >= 5) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3);\n }\n if (comboVal >= 3) {\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3);\n }\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.3);\n };\n\n const mainFontSize = isMobile ? 32 : 48;\n const subFontSize = isMobile ? 16 : 20;\n const milestoneFontSize = isMobile ? 20 : 28;\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid=\"combo-counter\"\n className=\"hud-animated\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n transform: `scale(${scale})`,\n transition: \"transform 0.15s ease-out\",\n animation: combo >= 5 ? \"comboFlash 0.8s ease-in-out infinite\" : \"none\",\n }}\n >\n {/* Main combo number */}\n <div\n style={{\n fontSize: `${mainFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `\n 0 0 15px ${glowColor},\n 0 0 30px ${glowColor},\n 3px 3px 6px rgba(0, 0, 0, 0.9)\n `,\n lineHeight: 1,\n }}\n >\n {combo}\n </div>\n\n {/* Korean text */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `0 0 10px ${glowColor}, 2px 2px 4px rgba(0, 0, 0, 0.8)`,\n marginTop: \"4px\",\n }}\n >\n 연속 타격!\n </div>\n\n {/* English text */}\n <div\n style={{\n fontSize: `${subFontSize - 4}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textShadow: \"2px 2px 4px rgba(0, 0, 0, 0.8)\",\n }}\n >\n {combo} HIT COMBO!\n </div>\n\n {/* Milestone indicator */}\n {showMilestone && milestone && (\n <div\n className=\"hud-animated\"\n style={{\n marginTop: \"8px\",\n padding: \"4px 16px\",\n background: getMilestoneBackground(combo),\n borderRadius: \"4px\",\n border: `2px solid ${comboColor}`,\n animation: \"comboFlash 1s ease-in-out infinite\",\n }}\n >\n <div\n style={{\n fontSize: `${milestoneFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `0 0 10px ${glowColor}`,\n }}\n >\n {milestone.korean}\n </div>\n <div\n style={{\n fontSize: `${milestoneFontSize - 6}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n {milestone.english}\n </div>\n </div>\n )}\n </div>\n\n {/* CSS Animation - kept for backwards compatibility */}\n <style>{`\n @keyframes pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.05); }\n }\n `}</style>\n </Html>\n );\n};\n\nexport default ComboCounter;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ComboCounter.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ComboCounter.tsx"],"sourcesContent":["/**\n * ComboCounter - Combo counter display component\n *\n * Displays the current combo count with Korean-English bilingual text.\n * Animates on combo increment and shows milestone indicators.\n *\n * Uses Html overlay from @react-three/drei for rendering within 3D scenes.\n *\n * @module components/shared/three/ui/ComboCounter\n * @category Shared UI\n * @korean 콤보카운터\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./HUDAnimations.css\";\n\n/**\n * Props for the ComboCounter component\n */\nexport interface ComboCounterProps {\n /** Current combo count */\n readonly combo: number;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Minimum combo to display (default: 2) */\n readonly minDisplayCombo?: number;\n}\n\n/**\n * Get combo tier color based on combo count\n */\nfunction getComboColor(combo: number): string {\n if (combo >= 10) {\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n }\n if (combo >= 7) {\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n }\n if (combo >= 5) {\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n }\n if (combo >= 3) {\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }\n return hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY);\n}\n\n/**\n * Get glow color based on combo tier\n */\nfunction getGlowColor(combo: number): string {\n if (combo >= 10) {\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n }\n if (combo >= 7) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n }\n if (combo >= 5) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n }\n if (combo >= 3) {\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6);\n }\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.4);\n}\n\n/**\n * Get combo milestone text\n */\nfunction getComboMilestone(\n combo: number\n): { korean: string; english: string } | null {\n if (combo === 5) {\n return { korean: \"훌륭합니다!\", english: \"Great!\" };\n }\n if (combo === 10) {\n return { korean: \"놀라운 연속 공격!\", english: \"Amazing!\" };\n }\n if (combo === 15) {\n return { korean: \"전설적인 공격!\", english: \"Legendary!\" };\n }\n if (combo === 20) {\n return { korean: \"신의 일격!\", english: \"GODLIKE!\" };\n }\n return null;\n}\n\n/**\n * ComboCounter Component\n *\n * Displays the current combo count with animations and milestone indicators.\n * Only visible when combo >= minDisplayCombo (default: 2).\n *\n * @example\n * ```tsx\n * <ComboCounter\n * combo={5}\n * isMobile={isMobile}\n * />\n * ```\n */\nexport const ComboCounter: React.FC<ComboCounterProps> = ({\n combo,\n isMobile = false,\n minDisplayCombo = 2,\n}) => {\n const [scale, setScale] = useState(1);\n const [showMilestone, setShowMilestone] = useState(false);\n const [prevCombo, setPrevCombo] = useState(combo);\n const milestoneTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null\n );\n const scaleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const position3D: [number, number, number] = useMemo(() => {\n return [0, 4.5, 0];\n }, []);\n\n useEffect(() => {\n if (combo > prevCombo) {\n setTimeout(() => setScale(1.3), 0);\n\n if (scaleTimeoutRef.current) {\n clearTimeout(scaleTimeoutRef.current);\n }\n scaleTimeoutRef.current = setTimeout(() => {\n setScale(1);\n }, 150);\n\n const milestone = getComboMilestone(combo);\n if (milestone) {\n setTimeout(() => setShowMilestone(true), 0);\n if (milestoneTimeoutRef.current) {\n clearTimeout(milestoneTimeoutRef.current);\n }\n milestoneTimeoutRef.current = setTimeout(() => {\n setShowMilestone(false);\n }, 1500);\n }\n }\n setTimeout(() => setPrevCombo(combo), 0);\n }, [combo, prevCombo]);\n\n useEffect(() => {\n return () => {\n if (milestoneTimeoutRef.current) {\n clearTimeout(milestoneTimeoutRef.current);\n }\n if (scaleTimeoutRef.current) {\n clearTimeout(scaleTimeoutRef.current);\n }\n };\n }, []);\n\n if (combo < minDisplayCombo) {\n return null;\n }\n\n const milestone = getComboMilestone(combo);\n const comboColor = getComboColor(combo);\n const glowColor = getGlowColor(combo);\n const getMilestoneBackground = (comboVal: number): string => {\n if (comboVal >= 10) {\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.3);\n }\n if (comboVal >= 7) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.3);\n }\n if (comboVal >= 5) {\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3);\n }\n if (comboVal >= 3) {\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3);\n }\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.3);\n };\n\n const mainFontSize = isMobile ? 32 : 48;\n const subFontSize = isMobile ? 16 : 20;\n const milestoneFontSize = isMobile ? 20 : 28;\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid=\"combo-counter\"\n className=\"hud-animated\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n transform: `scale(${scale})`,\n transition: \"transform 0.15s ease-out\",\n animation: combo >= 5 ? \"comboFlash 0.8s ease-in-out infinite\" : \"none\",\n }}\n >\n {/* Main combo number */}\n <div\n style={{\n fontSize: `${mainFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `\n 0 0 15px ${glowColor},\n 0 0 30px ${glowColor},\n 3px 3px 6px rgba(0, 0, 0, 0.9)\n `,\n lineHeight: 1,\n }}\n >\n {combo}\n </div>\n\n {/* Korean text */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `0 0 10px ${glowColor}, 2px 2px 4px rgba(0, 0, 0, 0.8)`,\n marginTop: \"4px\",\n }}\n >\n 연속 타격!\n </div>\n\n {/* English text */}\n <div\n style={{\n fontSize: `${subFontSize - 4}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textShadow: \"2px 2px 4px rgba(0, 0, 0, 0.8)\",\n }}\n >\n {combo} HIT COMBO!\n </div>\n\n {/* Milestone indicator */}\n {showMilestone && milestone && (\n <div\n className=\"hud-animated\"\n style={{\n marginTop: \"8px\",\n padding: \"4px 16px\",\n background: getMilestoneBackground(combo),\n borderRadius: \"4px\",\n border: `2px solid ${comboColor}`,\n animation: \"comboFlash 1s ease-in-out infinite\",\n }}\n >\n <div\n style={{\n fontSize: `${milestoneFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: comboColor,\n textShadow: `0 0 10px ${glowColor}`,\n }}\n >\n {milestone.korean}\n </div>\n <div\n style={{\n fontSize: `${milestoneFontSize - 6}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n {milestone.english}\n </div>\n </div>\n )}\n </div>\n\n {/* CSS Animation - kept for backwards compatibility */}\n <style>{`\n @keyframes pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.05); }\n }\n `}</style>\n </Html>\n );\n};\n\nexport default ComboCounter;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAS,cAAc,OAAuB;CAC5C,IAAI,SAAS,IACX,OAAO,cAAc,cAAc,iBAAiB;CAEtD,IAAI,SAAS,GACX,OAAO,cAAc,cAAc,UAAU;CAE/C,IAAI,SAAS,GACX,OAAO,cAAc,cAAc,WAAW;CAEhD,IAAI,SAAS,GACX,OAAO,cAAc,cAAc,YAAY;CAEjD,OAAO,cAAc,cAAc,YAAY;AACjD;;;;AAKA,SAAS,aAAa,OAAuB;CAC3C,IAAI,SAAS,IACX,OAAO,gBAAgB,cAAc,mBAAmB,EAAG;CAE7D,IAAI,SAAS,GACX,OAAO,gBAAgB,cAAc,YAAY,EAAG;CAEtD,IAAI,SAAS,GACX,OAAO,gBAAgB,cAAc,aAAa,EAAG;CAEvD,IAAI,SAAS,GACX,OAAO,gBAAgB,cAAc,cAAc,EAAG;CAExD,OAAO,gBAAgB,cAAc,cAAc,EAAG;AACxD;;;;AAKA,SAAS,kBACP,OAC4C;CAC5C,IAAI,UAAU,GACZ,OAAO;EAAE,QAAQ;EAAU,SAAS;CAAS;CAE/C,IAAI,UAAU,IACZ,OAAO;EAAE,QAAQ;EAAc,SAAS;CAAW;CAErD,IAAI,UAAU,IACZ,OAAO;EAAE,QAAQ;EAAY,SAAS;CAAa;CAErD,IAAI,UAAU,IACZ,OAAO;EAAE,QAAQ;EAAU,SAAS;CAAW;CAEjD,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,IAAa,gBAA6C,EACxD,OACA,WAAW,OACX,kBAAkB,QACd;CACJ,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CACpC,MAAM,CAAC,eAAe,oBAAoB,SAAS,KAAK;CACxD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,sBAAsB,OAC1B,IACF;CACA,MAAM,kBAAkB,OAA6C,IAAI;CAEzE,MAAM,aAAuC,cAAc;EACzD,OAAO;GAAC;GAAG;GAAK;EAAC;CACnB,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,IAAI,QAAQ,WAAW;GACrB,iBAAiB,SAAS,GAAG,GAAG,CAAC;GAEjC,IAAI,gBAAgB,SAClB,aAAa,gBAAgB,OAAO;GAEtC,gBAAgB,UAAU,iBAAiB;IACzC,SAAS,CAAC;GACZ,GAAG,GAAG;GAGN,IADkB,kBAAkB,KAChC,GAAW;IACb,iBAAiB,iBAAiB,IAAI,GAAG,CAAC;IAC1C,IAAI,oBAAoB,SACtB,aAAa,oBAAoB,OAAO;IAE1C,oBAAoB,UAAU,iBAAiB;KAC7C,iBAAiB,KAAK;IACxB,GAAG,IAAI;GACT;EACF;EACA,iBAAiB,aAAa,KAAK,GAAG,CAAC;CACzC,GAAG,CAAC,OAAO,SAAS,CAAC;CAErB,gBAAgB;EACd,aAAa;GACX,IAAI,oBAAoB,SACtB,aAAa,oBAAoB,OAAO;GAE1C,IAAI,gBAAgB,SAClB,aAAa,gBAAgB,OAAO;EAExC;CACF,GAAG,CAAC,CAAC;CAEL,IAAI,QAAQ,iBACV,OAAO;CAGT,MAAM,YAAY,kBAAkB,KAAK;CACzC,MAAM,aAAa,cAAc,KAAK;CACtC,MAAM,YAAY,aAAa,KAAK;CACpC,MAAM,0BAA0B,aAA6B;EAC3D,IAAI,YAAY,IACd,OAAO,gBAAgB,cAAc,mBAAmB,EAAG;EAE7D,IAAI,YAAY,GACd,OAAO,gBAAgB,cAAc,YAAY,EAAG;EAEtD,IAAI,YAAY,GACd,OAAO,gBAAgB,cAAc,aAAa,EAAG;EAEvD,IAAI,YAAY,GACd,OAAO,gBAAgB,cAAc,cAAc,EAAG;EAExD,OAAO,gBAAgB,cAAc,cAAc,EAAG;CACxD;CAEA,MAAM,eAAe,WAAW,KAAK;CACrC,MAAM,cAAc,WAAW,KAAK;CACpC,MAAM,oBAAoB,WAAW,KAAK;CAE1C,OACE,qBAAC,MAAD;EACE,UAAU;EACV,QAAA;EACA,gBAAgB;EAChB,OAAO,EAAE,eAAe,OAAO;YAJjC,CAME,qBAAC,OAAD;GACE,eAAY;GACZ,WAAU;GACV,OAAO;IACL,SAAS;IACT,eAAe;IACf,YAAY;IACZ,WAAW,SAAS,MAAM;IAC1B,YAAY;IACZ,WAAW,SAAS,IAAI,yCAAyC;GACnE;aAVF;IAaE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,aAAa;MAC1B,YAAY;MACZ,YAAY,YAAY;MACxB,OAAO;MACP,YAAY;yBACC,UAAU;yBACV,UAAU;;;MAGvB,YAAY;KACd;eAEC;IACE,CAAA;IAGL,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,YAAY;MACzB,YAAY;MACZ,YAAY,YAAY;MACxB,OAAO;MACP,YAAY,YAAY,UAAU;MAClC,WAAW;KACb;eACD;IAEI,CAAA;IAGL,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,cAAc,EAAE;MAC7B,YAAY;MACZ,YAAY,YAAY;MACxB,OAAO,cAAc,cAAc,cAAc;MACjD,YAAY;KACd;eAPF,CASG,OAAM,aACJ;;IAGJ,iBAAiB,aAChB,qBAAC,OAAD;KACE,WAAU;KACV,OAAO;MACL,WAAW;MACX,SAAS;MACT,YAAY,uBAAuB,KAAK;MACxC,cAAc;MACd,QAAQ,aAAa;MACrB,WAAW;KACb;eATF,CAWE,oBAAC,OAAD;MACE,OAAO;OACL,UAAU,GAAG,kBAAkB;OAC/B,YAAY;OACZ,YAAY,YAAY;OACxB,OAAO;OACP,YAAY,YAAY;MAC1B;gBAEC,UAAU;KACR,CAAA,GACL,oBAAC,OAAD;MACE,OAAO;OACL,UAAU,GAAG,oBAAoB,EAAE;OACnC,YAAY;OACZ,YAAY,YAAY;OACxB,OAAO,cAAc,cAAc,cAAc;MACnD;gBAEC,UAAU;KACR,CAAA,CACF;;GAEJ;MAGL,oBAAC,SAAD,EAAA,UAAQ;;;;;QAKC,CAAA,CACL;;AAEV"}
@@ -1 +1 @@
1
- {"version":3,"file":"HealthBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/HealthBar.tsx"],"sourcesContent":["/**\n * HealthBar Component - Segmented health display with Korean theming\n * \n * Displays player health with:\n * - 10 segmented bars\n * - Color transitions: Green (>50%), Yellow (25-50%), Red (<25%)\n * - Pulse animation when health <20%\n * - Korean/English bilingual labels\n * - Numeric value display (e.g., \"85/100\")\n * - Responsive sizing for mobile/tablet/desktop\n * - Smooth transitions and glow effects\n * \n * Performance: Uses React.memo with shallow comparison for 60fps optimization.\n * Note: React.memo uses shallow comparison by default, which works correctly\n * for this component since all props are primitives (number, string, boolean).\n * If object or function props are added in the future, consider adding a\n * custom comparison function or using useCallback/useMemo for prop stability.\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./HUDAnimations.css\";\n\nexport interface HealthBarProps {\n /** Current health value */\n readonly current: number;\n /** Maximum health capacity */\n readonly max: number;\n /** Player identifier for test ID */\n readonly playerId: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (percentage: number): number => {\n if (percentage > 50) return KOREAN_COLORS.HEALTH_FULL; // Green\n if (percentage > 25) return KOREAN_COLORS.HEALTH_MEDIUM; // Yellow\n return KOREAN_COLORS.HEALTH_CRITICAL; // Red\n};\n\n/**\n * HealthBar - Segmented health display with Korean theming\n * Performance optimized with React.memo\n */\nexport const HealthBar: React.FC<HealthBarProps> = React.memo(({\n current,\n max,\n playerId,\n isMobile,\n}) => {\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (current / max) * 100)),\n [current, max]\n );\n\n const segments = 10;\n const filledSegments = Math.ceil((healthPercent / 100) * segments);\n const healthColor = getHealthColor(healthPercent);\n const shouldPulse = healthPercent < 20;\n\n const layout = useMemo(() => ({\n barWidth: isMobile ? 180 : 250,\n barHeight: isMobile ? 16 : 20,\n fontSize: isMobile ? 11 : 13,\n padding: isMobile ? \"8px 12px\" : \"12px 16px\",\n }), [isMobile]);\n\n return (\n <div\n data-testid={`health-bar-${playerId}`}\n role=\"progressbar\"\n aria-label=\"체력 | Health\"\n aria-valuenow={Math.ceil(current)}\n aria-valuemin={0}\n aria-valuemax={max}\n aria-valuetext={`${Math.ceil(current)} out of ${max}`}\n className=\"hud-animated\"\n style={{\n width: `${layout.barWidth}px`,\n padding: layout.padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n transition: \"box-shadow 0.3s ease-in-out, border-color 0.3s ease-in-out\",\n }}\n >\n {/* Label and numeric display */}\n <div\n style={{\n fontSize: `${layout.fontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n fontWeight: \"bold\",\n transition: \"color 0.2s ease-in-out\",\n }}\n >\n <span>체력 | Health</span>\n <span data-testid={`health-value-${playerId}`}>\n {Math.ceil(current)}/{max}\n </span>\n </div>\n\n {/* Segmented health bar */}\n <div\n style={{\n display: \"flex\",\n gap: \"3px\",\n height: `${layout.barHeight}px`,\n animation: shouldPulse ? \"healthPulse 0.8s ease-in-out infinite\" : \"none\",\n }}\n >\n {Array.from({ length: segments }).map((_, index) => (\n <div\n key={index}\n data-testid={`health-segment-${playerId}-${index}`}\n style={{\n flex: 1,\n backgroundColor:\n index < filledSegments\n ? hexToRgbaString(healthColor, 1)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 1),\n borderRadius: \"2px\",\n transition: \"background-color 0.2s ease-in-out\",\n boxShadow:\n index < filledSegments\n ? `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`\n : \"none\",\n }}\n />\n ))}\n </div>\n </div>\n );\n});\n\nHealthBar.displayName = \"HealthBar\";\n\nexport default HealthBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAM,kBAAkB,eAA+B;CACrD,IAAI,aAAa,IAAI,OAAO,cAAc;CAC1C,IAAI,aAAa,IAAI,OAAO,cAAc;CAC1C,OAAO,cAAc;;;;;;AAOvB,IAAa,YAAsC,MAAM,MAAM,EAC7D,SACA,KACA,UACA,eACI;CACJ,MAAM,gBAAgB,cACd,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,UAAU,MAAO,IAAI,CAAC,EACvD,CAAC,SAAS,IAAI,CACf;CAED,MAAM,WAAW;CACjB,MAAM,iBAAiB,KAAK,KAAM,gBAAgB,MAAO,SAAS;CAClE,MAAM,cAAc,eAAe,cAAc;CACjD,MAAM,cAAc,gBAAgB;CAEpC,MAAM,SAAS,eAAe;EAC5B,UAAU,WAAW,MAAM;EAC3B,WAAW,WAAW,KAAK;EAC3B,UAAU,WAAW,KAAK;EAC1B,SAAS,WAAW,aAAa;EAClC,GAAG,CAAC,SAAS,CAAC;CAEf,OACE,qBAAC,OAAD;EACE,eAAa,cAAc;EAC3B,MAAK;EACL,cAAW;EACX,iBAAe,KAAK,KAAK,QAAQ;EACjC,iBAAe;EACf,iBAAe;EACf,kBAAgB,GAAG,KAAK,KAAK,QAAQ,CAAC,UAAU;EAChD,WAAU;EACV,OAAO;GACL,OAAO,GAAG,OAAO,SAAS;GAC1B,SAAS,OAAO;GAChB,iBAAiB,gBAAgB,cAAc,oBAAoB,EAAE;GACrE,cAAc;GACd,QAAQ,aAAa,gBAAgB,cAAc,cAAc,EAAE;GACnE,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;GACvE,YAAY;GACb;YAjBH,CAoBE,qBAAC,OAAD;GACE,OAAO;IACL,UAAU,GAAG,OAAO,SAAS;IAC7B,OAAO,gBAAgB,cAAc,cAAc,EAAE;IACrD,YAAY,YAAY;IACxB,cAAc;IACd,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACb;aAVH,CAYE,oBAAC,QAAD,EAAA,UAAM,eAAkB,CAAA,EACxB,qBAAC,QAAD;IAAM,eAAa,gBAAgB;cAAnC;KACG,KAAK,KAAK,QAAQ;KAAC;KAAE;KACjB;MACH;MAGN,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,KAAK;IACL,QAAQ,GAAG,OAAO,UAAU;IAC5B,WAAW,cAAc,0CAA0C;IACpE;aAEA,MAAM,KAAK,EAAE,QAAQ,UAAU,CAAC,CAAC,KAAK,GAAG,UACxC,oBAAC,OAAD;IAEE,eAAa,kBAAkB,SAAS,GAAG;IAC3C,OAAO;KACL,MAAM;KACN,iBACE,QAAQ,iBACJ,gBAAgB,aAAa,EAAE,GAC/B,gBAAgB,cAAc,sBAAsB,EAAE;KAC5D,cAAc;KACd,YAAY;KACZ,WACE,QAAQ,iBACJ,WAAW,gBAAgB,aAAa,GAAI,KAC5C;KACP;IACD,EAfK,MAeL,CACF;GACE,CAAA,CACF;;EAER;AAEF,UAAU,cAAc"}
1
+ {"version":3,"file":"HealthBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/HealthBar.tsx"],"sourcesContent":["/**\n * HealthBar Component - Segmented health display with Korean theming\n * \n * Displays player health with:\n * - 10 segmented bars\n * - Color transitions: Green (>50%), Yellow (25-50%), Red (<25%)\n * - Pulse animation when health <20%\n * - Korean/English bilingual labels\n * - Numeric value display (e.g., \"85/100\")\n * - Responsive sizing for mobile/tablet/desktop\n * - Smooth transitions and glow effects\n * \n * Performance: Uses React.memo with shallow comparison for 60fps optimization.\n * Note: React.memo uses shallow comparison by default, which works correctly\n * for this component since all props are primitives (number, string, boolean).\n * If object or function props are added in the future, consider adding a\n * custom comparison function or using useCallback/useMemo for prop stability.\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./HUDAnimations.css\";\n\nexport interface HealthBarProps {\n /** Current health value */\n readonly current: number;\n /** Maximum health capacity */\n readonly max: number;\n /** Player identifier for test ID */\n readonly playerId: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (percentage: number): number => {\n if (percentage > 50) return KOREAN_COLORS.HEALTH_FULL; // Green\n if (percentage > 25) return KOREAN_COLORS.HEALTH_MEDIUM; // Yellow\n return KOREAN_COLORS.HEALTH_CRITICAL; // Red\n};\n\n/**\n * HealthBar - Segmented health display with Korean theming\n * Performance optimized with React.memo\n */\nexport const HealthBar: React.FC<HealthBarProps> = React.memo(({\n current,\n max,\n playerId,\n isMobile,\n}) => {\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (current / max) * 100)),\n [current, max]\n );\n\n const segments = 10;\n const filledSegments = Math.ceil((healthPercent / 100) * segments);\n const healthColor = getHealthColor(healthPercent);\n const shouldPulse = healthPercent < 20;\n\n const layout = useMemo(() => ({\n barWidth: isMobile ? 180 : 250,\n barHeight: isMobile ? 16 : 20,\n fontSize: isMobile ? 11 : 13,\n padding: isMobile ? \"8px 12px\" : \"12px 16px\",\n }), [isMobile]);\n\n return (\n <div\n data-testid={`health-bar-${playerId}`}\n role=\"progressbar\"\n aria-label=\"체력 | Health\"\n aria-valuenow={Math.ceil(current)}\n aria-valuemin={0}\n aria-valuemax={max}\n aria-valuetext={`${Math.ceil(current)} out of ${max}`}\n className=\"hud-animated\"\n style={{\n width: `${layout.barWidth}px`,\n padding: layout.padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n transition: \"box-shadow 0.3s ease-in-out, border-color 0.3s ease-in-out\",\n }}\n >\n {/* Label and numeric display */}\n <div\n style={{\n fontSize: `${layout.fontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n fontWeight: \"bold\",\n transition: \"color 0.2s ease-in-out\",\n }}\n >\n <span>체력 | Health</span>\n <span data-testid={`health-value-${playerId}`}>\n {Math.ceil(current)}/{max}\n </span>\n </div>\n\n {/* Segmented health bar */}\n <div\n style={{\n display: \"flex\",\n gap: \"3px\",\n height: `${layout.barHeight}px`,\n animation: shouldPulse ? \"healthPulse 0.8s ease-in-out infinite\" : \"none\",\n }}\n >\n {Array.from({ length: segments }).map((_, index) => (\n <div\n key={index}\n data-testid={`health-segment-${playerId}-${index}`}\n style={{\n flex: 1,\n backgroundColor:\n index < filledSegments\n ? hexToRgbaString(healthColor, 1)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 1),\n borderRadius: \"2px\",\n transition: \"background-color 0.2s ease-in-out\",\n boxShadow:\n index < filledSegments\n ? `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`\n : \"none\",\n }}\n />\n ))}\n </div>\n </div>\n );\n});\n\nHealthBar.displayName = \"HealthBar\";\n\nexport default HealthBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAM,kBAAkB,eAA+B;CACrD,IAAI,aAAa,IAAI,OAAO,cAAc;CAC1C,IAAI,aAAa,IAAI,OAAO,cAAc;CAC1C,OAAO,cAAc;AACvB;;;;;AAMA,IAAa,YAAsC,MAAM,MAAM,EAC7D,SACA,KACA,UACA,eACI;CACJ,MAAM,gBAAgB,cACd,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,UAAU,MAAO,GAAG,CAAC,GACtD,CAAC,SAAS,GAAG,CACf;CAEA,MAAM,WAAW;CACjB,MAAM,iBAAiB,KAAK,KAAM,gBAAgB,MAAO,QAAQ;CACjE,MAAM,cAAc,eAAe,aAAa;CAChD,MAAM,cAAc,gBAAgB;CAEpC,MAAM,SAAS,eAAe;EAC5B,UAAU,WAAW,MAAM;EAC3B,WAAW,WAAW,KAAK;EAC3B,UAAU,WAAW,KAAK;EAC1B,SAAS,WAAW,aAAa;CACnC,IAAI,CAAC,QAAQ,CAAC;CAEd,OACE,qBAAC,OAAD;EACE,eAAa,cAAc;EAC3B,MAAK;EACL,cAAW;EACX,iBAAe,KAAK,KAAK,OAAO;EAChC,iBAAe;EACf,iBAAe;EACf,kBAAgB,GAAG,KAAK,KAAK,OAAO,EAAE,UAAU;EAChD,WAAU;EACV,OAAO;GACL,OAAO,GAAG,OAAO,SAAS;GAC1B,SAAS,OAAO;GAChB,iBAAiB,gBAAgB,cAAc,oBAAoB,CAAC;GACpE,cAAc;GACd,QAAQ,aAAa,gBAAgB,cAAc,cAAc,CAAC;GAClE,WAAW,YAAY,gBAAgB,cAAc,cAAc,EAAG;GACtE,YAAY;EACd;YAjBF,CAoBE,qBAAC,OAAD;GACE,OAAO;IACL,UAAU,GAAG,OAAO,SAAS;IAC7B,OAAO,gBAAgB,cAAc,cAAc,CAAC;IACpD,YAAY,YAAY;IACxB,cAAc;IACd,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,YAAY;GACd;aAVF,CAYE,oBAAC,QAAD,EAAA,UAAM,cAAiB,CAAA,GACvB,qBAAC,QAAD;IAAM,eAAa,gBAAgB;cAAnC;KACG,KAAK,KAAK,OAAO;KAAE;KAAE;IAClB;KACH;MAGL,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,KAAK;IACL,QAAQ,GAAG,OAAO,UAAU;IAC5B,WAAW,cAAc,0CAA0C;GACrE;aAEC,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC,EAAE,KAAK,GAAG,UACxC,oBAAC,OAAD;IAEE,eAAa,kBAAkB,SAAS,GAAG;IAC3C,OAAO;KACL,MAAM;KACN,iBACE,QAAQ,iBACJ,gBAAgB,aAAa,CAAC,IAC9B,gBAAgB,cAAc,sBAAsB,CAAC;KAC3D,cAAc;KACd,YAAY;KACZ,WACE,QAAQ,iBACJ,WAAW,gBAAgB,aAAa,EAAG,MAC3C;IACR;GACD,GAfM,KAeN,CACF;EACE,CAAA,CACF;;AAET,CAAC;AAED,UAAU,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"KoreanButton.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanButton.tsx"],"sourcesContent":["/**\n * KoreanButton - Three.js-compatible button component with Korean theming\n * \n * Provides bilingual button with cyberpunk Korean aesthetic\n * Supports both HTML overlay and 3D mesh rendering\n * \n * Now refactored to use BaseButton for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BaseButton, type BaseButtonProps } from \"../../base\";\n\n/**\n * Props for KoreanButton component\n * Extends BaseButtonProps for consistency\n */\nexport interface KoreanButtonProps extends Omit<BaseButtonProps, \"isMobile\"> {\n}\n\n/**\n * KoreanButton Component\n * \n * A bilingual button component with Korean cyberpunk theming.\n * Now uses BaseButton internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanButton\n * korean=\"공격\"\n * english=\"Attack\"\n * onClick={() => console.log(\"Attack clicked\")}\n * variant=\"primary\"\n * size=\"md\"\n * />\n * ```\n */\nexport const KoreanButton: React.FC<KoreanButtonProps> = ({ testId, ...rest }) => {\n return <BaseButton testId={testId ?? \"korean-button\"} {...rest} />;\n};\n\nKoreanButton.displayName = \"KoreanButton\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,IAAa,gBAA6C,EAAE,QAAQ,GAAG,WAAW;CAChF,OAAO,oBAAC,YAAD;EAAY,QAAQ,UAAU;EAAiB,GAAI;EAAQ,CAAA;;AAGpE,aAAa,cAAc"}
1
+ {"version":3,"file":"KoreanButton.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanButton.tsx"],"sourcesContent":["/**\n * KoreanButton - Three.js-compatible button component with Korean theming\n * \n * Provides bilingual button with cyberpunk Korean aesthetic\n * Supports both HTML overlay and 3D mesh rendering\n * \n * Now refactored to use BaseButton for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BaseButton, type BaseButtonProps } from \"../../base\";\n\n/**\n * Props for KoreanButton component\n * Extends BaseButtonProps for consistency\n */\nexport interface KoreanButtonProps extends Omit<BaseButtonProps, \"isMobile\"> {\n}\n\n/**\n * KoreanButton Component\n * \n * A bilingual button component with Korean cyberpunk theming.\n * Now uses BaseButton internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanButton\n * korean=\"공격\"\n * english=\"Attack\"\n * onClick={() => console.log(\"Attack clicked\")}\n * variant=\"primary\"\n * size=\"md\"\n * />\n * ```\n */\nexport const KoreanButton: React.FC<KoreanButtonProps> = ({ testId, ...rest }) => {\n return <BaseButton testId={testId ?? \"korean-button\"} {...rest} />;\n};\n\nKoreanButton.displayName = \"KoreanButton\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,IAAa,gBAA6C,EAAE,QAAQ,GAAG,WAAW;CAChF,OAAO,oBAAC,YAAD;EAAY,QAAQ,UAAU;EAAiB,GAAI;CAAO,CAAA;AACnE;AAEA,aAAa,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"KoreanPanel.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanPanel.tsx"],"sourcesContent":["/**\n * KoreanPanel - Three.js-compatible panel container component\n * \n * Provides a styled container with Korean cyberpunk theming\n * \n * Now refactored to use BasePanel for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BasePanel, type BasePanelProps } from \"../../base\";\n\n/**\n * Props for KoreanPanel component\n * Extends BasePanelProps for consistency\n */\nexport interface KoreanPanelProps extends Omit<BasePanelProps, \"isMobile\"> {\n}\n\n/**\n * KoreanPanel Component\n * \n * A container component with Korean cyberpunk styling.\n * Now uses BasePanel internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanPanel variant=\"bordered\" padding={20}>\n * <h1>Panel Content</h1>\n * </KoreanPanel>\n * ```\n */\nexport const KoreanPanel: React.FC<KoreanPanelProps> = ({ testId, ...rest }) => {\n return <BasePanel testId={testId ?? \"korean-panel\"} {...rest} />;\n};\n\nKoreanPanel.displayName = \"KoreanPanel\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,IAAa,eAA2C,EAAE,QAAQ,GAAG,WAAW;CAC9E,OAAO,oBAAC,WAAD;EAAW,QAAQ,UAAU;EAAgB,GAAI;EAAQ,CAAA;;AAGlE,YAAY,cAAc"}
1
+ {"version":3,"file":"KoreanPanel.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanPanel.tsx"],"sourcesContent":["/**\n * KoreanPanel - Three.js-compatible panel container component\n * \n * Provides a styled container with Korean cyberpunk theming\n * \n * Now refactored to use BasePanel for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BasePanel, type BasePanelProps } from \"../../base\";\n\n/**\n * Props for KoreanPanel component\n * Extends BasePanelProps for consistency\n */\nexport interface KoreanPanelProps extends Omit<BasePanelProps, \"isMobile\"> {\n}\n\n/**\n * KoreanPanel Component\n * \n * A container component with Korean cyberpunk styling.\n * Now uses BasePanel internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanPanel variant=\"bordered\" padding={20}>\n * <h1>Panel Content</h1>\n * </KoreanPanel>\n * ```\n */\nexport const KoreanPanel: React.FC<KoreanPanelProps> = ({ testId, ...rest }) => {\n return <BasePanel testId={testId ?? \"korean-panel\"} {...rest} />;\n};\n\nKoreanPanel.displayName = \"KoreanPanel\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,IAAa,eAA2C,EAAE,QAAQ,GAAG,WAAW;CAC9E,OAAO,oBAAC,WAAD;EAAW,QAAQ,UAAU;EAAgB,GAAI;CAAO,CAAA;AACjE;AAEA,YAAY,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"KoreanText.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanText.tsx"],"sourcesContent":["/**\n * KoreanText - Three.js-compatible text component with bilingual support\n * \n * Displays Korean and English text with cyberpunk styling\n * \n * Now refactored to use BaseText for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BaseText, type BaseTextProps } from \"../../base\";\n\n/**\n * Props for KoreanText component\n * Extends BaseTextProps for consistency\n */\nexport interface KoreanTextProps extends Omit<BaseTextProps, \"isMobile\"> {\n}\n\n/**\n * KoreanText Component\n * \n * A bilingual text component with Korean cyberpunk styling.\n * Now uses BaseText internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanText\n * korean=\"공격\"\n * english=\"Attack\"\n * size=\"large\"\n * layout=\"vertical\"\n * />\n * ```\n */\nexport const KoreanText: React.FC<KoreanTextProps> = ({ testId, ...rest }) => {\n return <BaseText testId={testId ?? \"korean-text\"} {...rest} />;\n};\n\nKoreanText.displayName = \"KoreanText\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoCA,IAAa,cAAyC,EAAE,QAAQ,GAAG,WAAW;CAC5E,OAAO,oBAAC,UAAD;EAAU,QAAQ,UAAU;EAAe,GAAI;EAAQ,CAAA;;AAGhE,WAAW,cAAc"}
1
+ {"version":3,"file":"KoreanText.js","names":[],"sources":["../../../../../src/components/shared/three/ui/KoreanText.tsx"],"sourcesContent":["/**\n * KoreanText - Three.js-compatible text component with bilingual support\n * \n * Displays Korean and English text with cyberpunk styling\n * \n * Now refactored to use BaseText for consistent styling\n * \n * @module components/three\n */\n\nimport React from \"react\";\nimport { BaseText, type BaseTextProps } from \"../../base\";\n\n/**\n * Props for KoreanText component\n * Extends BaseTextProps for consistency\n */\nexport interface KoreanTextProps extends Omit<BaseTextProps, \"isMobile\"> {\n}\n\n/**\n * KoreanText Component\n * \n * A bilingual text component with Korean cyberpunk styling.\n * Now uses BaseText internally for consistent styling and reduced duplication.\n * \n * @example\n * ```tsx\n * <KoreanText\n * korean=\"공격\"\n * english=\"Attack\"\n * size=\"large\"\n * layout=\"vertical\"\n * />\n * ```\n */\nexport const KoreanText: React.FC<KoreanTextProps> = ({ testId, ...rest }) => {\n return <BaseText testId={testId ?? \"korean-text\"} {...rest} />;\n};\n\nKoreanText.displayName = \"KoreanText\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoCA,IAAa,cAAyC,EAAE,QAAQ,GAAG,WAAW;CAC5E,OAAO,oBAAC,UAAD;EAAU,QAAQ,UAAU;EAAe,GAAI;CAAO,CAAA;AAC/D;AAEA,WAAW,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"MenuList.js","names":[],"sources":["../../../../../src/components/shared/three/ui/MenuList.tsx"],"sourcesContent":["/**\n * MenuList - Three.js-compatible menu list component\n * \n * Navigation menu with Korean theming and keyboard support\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Menu item interface\n */\nexport interface MenuItem {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly disabled?: boolean;\n}\n\n/**\n * Props for MenuList component\n */\nexport interface MenuListProps {\n readonly items: readonly MenuItem[];\n readonly onSelect: (id: string) => void;\n readonly selectedId?: string;\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly testId?: string;\n}\n\n/**\n * MenuList Component\n * \n * A navigational menu component with Korean cyberpunk styling.\n * Supports keyboard navigation and hover states.\n * \n * @example\n * ```tsx\n * <MenuList\n * items={[\n * { id: \"combat\", korean: \"대전\", english: \"Combat\" },\n * { id: \"training\", korean: \"훈련\", english: \"Training\" }\n * ]}\n * onSelect={(id) => console.log(id)}\n * />\n * ```\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n items,\n onSelect,\n selectedId,\n position = [0, 0, 0],\n width = 300,\n testId,\n}) => {\n const [hoveredId, setHoveredId] = useState<string | null>(null);\n\n const handleItemClick = useCallback(\n (id: string, disabled?: boolean) => {\n if (!disabled) {\n onSelect(id);\n }\n },\n [onSelect]\n );\n\n const handleMouseEnter = useCallback((id: string) => {\n setHoveredId(id);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n setHoveredId(null);\n }, []);\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"8px\",\n padding: \"12px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"8px\",\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }),\n [width]\n );\n\n const getItemStyle = useCallback(\n (id: string, disabled?: boolean): React.CSSProperties => {\n const isSelected = selectedId === id;\n const isHovered = hoveredId === id;\n\n let background = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5);\n let borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3);\n\n if (isSelected) {\n background = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2);\n borderColor = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8);\n } else if (isHovered && !disabled) {\n background = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.15);\n borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6);\n }\n\n return {\n padding: \"12px 16px\",\n background,\n border: `2px solid ${borderColor}`,\n borderRadius: \"4px\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.2s ease\",\n opacity: disabled ? 0.4 : 1,\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n transform: isHovered && !disabled ? \"translateX(4px)\" : \"translateX(0)\",\n };\n },\n [selectedId, hoveredId]\n );\n\n const getTextStyle = useCallback(\n (isKorean: boolean, isSelected: boolean): React.CSSProperties => ({\n display: \"block\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isKorean ? \"16px\" : \"14px\",\n fontWeight: isKorean ? \"bold\" : \"normal\",\n fontStyle: isKorean ? \"normal\" : \"italic\",\n color: hexToRgbaString(\n isSelected ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_PRIMARY\n ),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div style={containerStyle} data-testid={testId ?? \"menu-list\"}>\n {items.map((item) => {\n const isSelected = selectedId === item.id;\n return (\n <div\n key={item.id}\n onClick={() => handleItemClick(item.id, item.disabled)}\n onMouseEnter={() => handleMouseEnter(item.id)}\n onMouseLeave={handleMouseLeave}\n style={getItemStyle(item.id, item.disabled)}\n data-testid={`menu-item-${item.id}`}\n >\n <span style={getTextStyle(true, isSelected)}>\n {item.korean}\n </span>\n <span style={getTextStyle(false, isSelected)}>\n {item.english}\n </span>\n </div>\n );\n })}\n </div>\n </Html>\n );\n};\n\nMenuList.displayName = \"MenuList\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,YAAqC,EAChD,OACA,UACA,YACA,WAAW;CAAC;CAAG;CAAG;CAAE,EACpB,QAAQ,KACR,aACI;CACJ,MAAM,CAAC,WAAW,gBAAgB,SAAwB,KAAK;CAE/D,MAAM,kBAAkB,aACrB,IAAY,aAAuB;EAClC,IAAI,CAAC,UACH,SAAS,GAAG;IAGhB,CAAC,SAAS,CACX;CAED,MAAM,mBAAmB,aAAa,OAAe;EACnD,aAAa,GAAG;IACf,EAAE,CAAC;CAEN,MAAM,mBAAmB,kBAAkB;EACzC,aAAa,KAAK;IACjB,EAAE,CAAC;CAEN,MAAM,iBAAiB,eACd;EACL,OAAO,GAAG,MAAM;EAChB,SAAS;EACT,eAAe;EACf,KAAK;EACL,SAAS;EACT,YAAY,gBAAgB,cAAc,oBAAoB,GAAI;EAClE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;EACrE,cAAc;EACd,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;EACxE,GACD,CAAC,MAAM,CACR;CAED,MAAM,eAAe,aAClB,IAAY,aAA4C;EACvD,MAAM,aAAa,eAAe;EAClC,MAAM,YAAY,cAAc;EAEhC,IAAI,aAAa,gBAAgB,cAAc,sBAAsB,GAAI;EACzE,IAAI,cAAc,gBAAgB,cAAc,aAAa,GAAI;EAEjE,IAAI,YAAY;GACd,aAAa,gBAAgB,cAAc,cAAc,GAAI;GAC7D,cAAc,gBAAgB,cAAc,cAAc,GAAI;SACzD,IAAI,aAAa,CAAC,UAAU;GACjC,aAAa,gBAAgB,cAAc,aAAa,IAAK;GAC7D,cAAc,gBAAgB,cAAc,aAAa,GAAI;;EAG/D,OAAO;GACL,SAAS;GACT;GACA,QAAQ,aAAa;GACrB,cAAc;GACd,QAAQ,WAAW,gBAAgB;GACnC,YAAY;GACZ,SAAS,WAAW,KAAM;GAC1B,YAAY;GACZ,kBAAkB;GAClB,WAAW,aAAa,CAAC,WAAW,oBAAoB;GACzD;IAEH,CAAC,YAAY,UAAU,CACxB;CAED,MAAM,eAAe,aAClB,UAAmB,gBAA8C;EAChE,SAAS;EACT,YAAY,YAAY;EACxB,UAAU,WAAW,SAAS;EAC9B,YAAY,WAAW,SAAS;EAChC,WAAW,WAAW,WAAW;EACjC,OAAO,gBACL,aAAa,cAAc,cAAc,cAAc,aACxD;EACD,YAAY,aAAa,gBAAgB,cAAc,aAAa,GAAI;EACzE,GACD,EAAE,CACH;CAED,OACE,oBAAC,MAAD;EAAgB;EAAU,QAAA;YACxB,oBAAC,OAAD;GAAK,OAAO;GAAgB,eAAa,UAAU;aAChD,MAAM,KAAK,SAAS;IACnB,MAAM,aAAa,eAAe,KAAK;IACvC,OACE,qBAAC,OAAD;KAEE,eAAe,gBAAgB,KAAK,IAAI,KAAK,SAAS;KACtD,oBAAoB,iBAAiB,KAAK,GAAG;KAC7C,cAAc;KACd,OAAO,aAAa,KAAK,IAAI,KAAK,SAAS;KAC3C,eAAa,aAAa,KAAK;eANjC,CAQE,oBAAC,QAAD;MAAM,OAAO,aAAa,MAAM,WAAW;gBACxC,KAAK;MACD,CAAA,EACP,oBAAC,QAAD;MAAM,OAAO,aAAa,OAAO,WAAW;gBACzC,KAAK;MACD,CAAA,CACH;OAbC,KAAK,GAaN;KAER;GACE,CAAA;EACD,CAAA;;AAIX,SAAS,cAAc"}
1
+ {"version":3,"file":"MenuList.js","names":[],"sources":["../../../../../src/components/shared/three/ui/MenuList.tsx"],"sourcesContent":["/**\n * MenuList - Three.js-compatible menu list component\n * \n * Navigation menu with Korean theming and keyboard support\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Menu item interface\n */\nexport interface MenuItem {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly disabled?: boolean;\n}\n\n/**\n * Props for MenuList component\n */\nexport interface MenuListProps {\n readonly items: readonly MenuItem[];\n readonly onSelect: (id: string) => void;\n readonly selectedId?: string;\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly testId?: string;\n}\n\n/**\n * MenuList Component\n * \n * A navigational menu component with Korean cyberpunk styling.\n * Supports keyboard navigation and hover states.\n * \n * @example\n * ```tsx\n * <MenuList\n * items={[\n * { id: \"combat\", korean: \"대전\", english: \"Combat\" },\n * { id: \"training\", korean: \"훈련\", english: \"Training\" }\n * ]}\n * onSelect={(id) => console.log(id)}\n * />\n * ```\n */\nexport const MenuList: React.FC<MenuListProps> = ({\n items,\n onSelect,\n selectedId,\n position = [0, 0, 0],\n width = 300,\n testId,\n}) => {\n const [hoveredId, setHoveredId] = useState<string | null>(null);\n\n const handleItemClick = useCallback(\n (id: string, disabled?: boolean) => {\n if (!disabled) {\n onSelect(id);\n }\n },\n [onSelect]\n );\n\n const handleMouseEnter = useCallback((id: string) => {\n setHoveredId(id);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n setHoveredId(null);\n }, []);\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"8px\",\n padding: \"12px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"8px\",\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }),\n [width]\n );\n\n const getItemStyle = useCallback(\n (id: string, disabled?: boolean): React.CSSProperties => {\n const isSelected = selectedId === id;\n const isHovered = hoveredId === id;\n\n let background = hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5);\n let borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3);\n\n if (isSelected) {\n background = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2);\n borderColor = hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8);\n } else if (isHovered && !disabled) {\n background = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.15);\n borderColor = hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6);\n }\n\n return {\n padding: \"12px 16px\",\n background,\n border: `2px solid ${borderColor}`,\n borderRadius: \"4px\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.2s ease\",\n opacity: disabled ? 0.4 : 1,\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n transform: isHovered && !disabled ? \"translateX(4px)\" : \"translateX(0)\",\n };\n },\n [selectedId, hoveredId]\n );\n\n const getTextStyle = useCallback(\n (isKorean: boolean, isSelected: boolean): React.CSSProperties => ({\n display: \"block\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isKorean ? \"16px\" : \"14px\",\n fontWeight: isKorean ? \"bold\" : \"normal\",\n fontStyle: isKorean ? \"normal\" : \"italic\",\n color: hexToRgbaString(\n isSelected ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_PRIMARY\n ),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div style={containerStyle} data-testid={testId ?? \"menu-list\"}>\n {items.map((item) => {\n const isSelected = selectedId === item.id;\n return (\n <div\n key={item.id}\n onClick={() => handleItemClick(item.id, item.disabled)}\n onMouseEnter={() => handleMouseEnter(item.id)}\n onMouseLeave={handleMouseLeave}\n style={getItemStyle(item.id, item.disabled)}\n data-testid={`menu-item-${item.id}`}\n >\n <span style={getTextStyle(true, isSelected)}>\n {item.korean}\n </span>\n <span style={getTextStyle(false, isSelected)}>\n {item.english}\n </span>\n </div>\n );\n })}\n </div>\n </Html>\n );\n};\n\nMenuList.displayName = \"MenuList\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,YAAqC,EAChD,OACA,UACA,YACA,WAAW;CAAC;CAAG;CAAG;AAAC,GACnB,QAAQ,KACR,aACI;CACJ,MAAM,CAAC,WAAW,gBAAgB,SAAwB,IAAI;CAE9D,MAAM,kBAAkB,aACrB,IAAY,aAAuB;EAClC,IAAI,CAAC,UACH,SAAS,EAAE;CAEf,GACA,CAAC,QAAQ,CACX;CAEA,MAAM,mBAAmB,aAAa,OAAe;EACnD,aAAa,EAAE;CACjB,GAAG,CAAC,CAAC;CAEL,MAAM,mBAAmB,kBAAkB;EACzC,aAAa,IAAI;CACnB,GAAG,CAAC,CAAC;CAEL,MAAM,iBAAiB,eACd;EACL,OAAO,GAAG,MAAM;EAChB,SAAS;EACT,eAAe;EACf,KAAK;EACL,SAAS;EACT,YAAY,gBAAgB,cAAc,oBAAoB,EAAG;EACjE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,EAAG;EACpE,cAAc;EACd,WAAW,YAAY,gBAAgB,cAAc,cAAc,EAAG;CACxE,IACA,CAAC,KAAK,CACR;CAEA,MAAM,eAAe,aAClB,IAAY,aAA4C;EACvD,MAAM,aAAa,eAAe;EAClC,MAAM,YAAY,cAAc;EAEhC,IAAI,aAAa,gBAAgB,cAAc,sBAAsB,EAAG;EACxE,IAAI,cAAc,gBAAgB,cAAc,aAAa,EAAG;EAEhE,IAAI,YAAY;GACd,aAAa,gBAAgB,cAAc,cAAc,EAAG;GAC5D,cAAc,gBAAgB,cAAc,cAAc,EAAG;EAC/D,OAAO,IAAI,aAAa,CAAC,UAAU;GACjC,aAAa,gBAAgB,cAAc,aAAa,GAAI;GAC5D,cAAc,gBAAgB,cAAc,aAAa,EAAG;EAC9D;EAEA,OAAO;GACL,SAAS;GACT;GACA,QAAQ,aAAa;GACrB,cAAc;GACd,QAAQ,WAAW,gBAAgB;GACnC,YAAY;GACZ,SAAS,WAAW,KAAM;GAC1B,YAAY;GACZ,kBAAkB;GAClB,WAAW,aAAa,CAAC,WAAW,oBAAoB;EAC1D;CACF,GACA,CAAC,YAAY,SAAS,CACxB;CAEA,MAAM,eAAe,aAClB,UAAmB,gBAA8C;EAChE,SAAS;EACT,YAAY,YAAY;EACxB,UAAU,WAAW,SAAS;EAC9B,YAAY,WAAW,SAAS;EAChC,WAAW,WAAW,WAAW;EACjC,OAAO,gBACL,aAAa,cAAc,cAAc,cAAc,YACzD;EACA,YAAY,aAAa,gBAAgB,cAAc,aAAa,EAAG;CACzE,IACA,CAAC,CACH;CAEA,OACE,oBAAC,MAAD;EAAgB;EAAU,QAAA;YACxB,oBAAC,OAAD;GAAK,OAAO;GAAgB,eAAa,UAAU;aAChD,MAAM,KAAK,SAAS;IACnB,MAAM,aAAa,eAAe,KAAK;IACvC,OACE,qBAAC,OAAD;KAEE,eAAe,gBAAgB,KAAK,IAAI,KAAK,QAAQ;KACrD,oBAAoB,iBAAiB,KAAK,EAAE;KAC5C,cAAc;KACd,OAAO,aAAa,KAAK,IAAI,KAAK,QAAQ;KAC1C,eAAa,aAAa,KAAK;eANjC,CAQE,oBAAC,QAAD;MAAM,OAAO,aAAa,MAAM,UAAU;gBACvC,KAAK;KACF,CAAA,GACN,oBAAC,QAAD;MAAM,OAAO,aAAa,OAAO,UAAU;gBACxC,KAAK;KACF,CAAA,CACH;OAbE,KAAK,EAaP;GAET,CAAC;EACE,CAAA;CACD,CAAA;AAEV;AAEA,SAAS,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"PlayerHUD.js","names":[],"sources":["../../../../../src/components/shared/three/ui/PlayerHUD.tsx"],"sourcesContent":["/**\n * PlayerHUD Component - Combined combat readiness, health and stamina display\n *\n * Displays a complete player HUD with:\n * - Archetype icon/image\n * - Player name (Korean/English)\n * - Combat Readiness bar (10-segment, multi-factor)\n * - Health bar (segmented, color-coded)\n * - Stamina bar (segmented, cyan-themed)\n * - Current stance indicator\n * - Responsive positioning (top-left for player 1, top-right for player 2)\n *\n * Performance optimized with React.memo for 60fps rendering.\n */\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport type { StanceLaterality } from \"../../../../systems/trigram/types\";\nimport {\n ARCHETYPE_ASSETS,\n FALLBACK_ARCHETYPE_IMAGE,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BreathingIndicator } from \"./BreathingIndicator\";\nimport { CombatReadinessBar } from \"./CombatReadinessBar\";\nimport { HealthBar } from \"./HealthBar\";\nimport { StaminaBar } from \"./StaminaBar\";\n\nexport interface PlayerHUDProps {\n /** Player state with health, stamina, and other data */\n readonly player: PlayerState;\n /** Player position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n /** Stance laterality: left or right foot forward */\n readonly laterality?: StanceLaterality;\n}\n\n/**\n * Laterality Indicator Component - Shows L/R badge with Korean text\n * @korean 측면성표시기\n */\nconst LateralityIndicator: React.FC<{\n readonly laterality: StanceLaterality;\n readonly isMobile: boolean;\n}> = React.memo(({ laterality, isMobile }) => {\n const isLeft = laterality === \"left\";\n\n const badgeStyle = useMemo(\n () => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n }),\n [isMobile],\n );\n\n const labelStyle = useMemo(\n () => ({\n padding: isMobile ? \"1px 4px\" : \"2px 6px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"3px\",\n fontWeight: \"bold\",\n minWidth: isMobile ? \"16px\" : \"18px\",\n textAlign: \"center\" as const,\n }),\n [isMobile],\n );\n\n const textStyle = useMemo(\n () => ({\n opacity: 0.8,\n whiteSpace: \"nowrap\" as const,\n }),\n [],\n );\n\n return (\n <div style={badgeStyle} data-testid=\"laterality-indicator\">\n <span style={labelStyle} data-testid=\"laterality-badge\">\n {isLeft ? \"L\" : \"R\"}\n </span>\n <span style={textStyle} data-testid=\"laterality-text\">\n {isLeft ? \"왼발서기\" : \"오른발서기\"}\n </span>\n </div>\n );\n});\nLateralityIndicator.displayName = \"LateralityIndicator\";\n\n/**\n * PlayerHUD - Complete player status display with health and stamina bars\n * Performance optimized with React.memo\n */\nconst PlayerHUDComponent: React.FC<PlayerHUDProps> = ({\n player,\n position,\n isMobile,\n laterality,\n}) => {\n const playerId = player.id;\n const isLeft = position === \"left\";\n\n const layout = useMemo(\n () => ({\n fontSize: isMobile ? 11 : 13,\n gap: isMobile ? \"6px\" : \"8px\",\n iconSize: isMobile ? 40 : 50,\n top: isMobile ? \"8px\" : \"10px\",\n horizontal: isMobile ? \"8px\" : \"12px\",\n }),\n [isMobile],\n );\n\n const archetypeImagePath = useMemo(() => {\n const archetypeKey = player.archetype.toLowerCase();\n const assets =\n ARCHETYPE_ASSETS[archetypeKey as keyof typeof ARCHETYPE_ASSETS];\n return assets?.image ?? FALLBACK_ARCHETYPE_IMAGE;\n }, [player.archetype]);\n\n const containerStyle = useMemo(\n () => ({\n position: \"relative\" as const,\n display: \"flex\",\n flexDirection: \"column\" as const,\n gap: layout.gap,\n pointerEvents: \"none\" as const,\n width: \"100%\",\n }),\n [layout],\n );\n\n const iconContainerStyle = useMemo(() => {\n const direction = isLeft ? \"row\" : \"row-reverse\";\n return {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n flexDirection: direction as \"row\" | \"row-reverse\",\n };\n }, [isLeft]);\n\n const iconStyle = useMemo(\n () => ({\n width: `${layout.iconSize}px`,\n height: `${layout.iconSize}px`,\n borderRadius: \"8px\",\n overflow: \"hidden\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n flexShrink: 0,\n }),\n [layout.iconSize],\n );\n\n const nameStyle = useMemo(() => {\n const textAlign = isLeft ? \"left\" : \"right\";\n return {\n fontSize: `${layout.fontSize}px`,\n fontWeight: \"bold\" as const,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n textAlign: textAlign as \"left\" | \"right\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8), 0 0 8px rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7),\n borderRadius: \"4px\",\n whiteSpace: \"nowrap\" as const,\n };\n }, [layout.fontSize, isLeft]);\n\n const stanceStyle = useMemo(() => {\n const textAlign = isLeft ? \"left\" : \"right\";\n return {\n fontSize: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_CYAN, 1),\n textAlign: textAlign as \"left\" | \"right\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"4px 8px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n borderRadius: \"4px\",\n marginTop: \"2px\",\n };\n }, [isMobile, isLeft]);\n\n const handleImageError = useCallback(\n (e: React.SyntheticEvent<HTMLImageElement>) => {\n const target = e.target as HTMLImageElement;\n if (!target.src.endsWith(FALLBACK_ARCHETYPE_IMAGE)) {\n target.src = FALLBACK_ARCHETYPE_IMAGE;\n }\n },\n [],\n );\n\n return (\n <div data-testid={`player-hud-${playerId}`} style={containerStyle}>\n {/* Player Name with Archetype Icon */}\n <div data-testid={`player-name-${playerId}`} style={iconContainerStyle}>\n {/* Archetype Icon */}\n <div data-testid={`archetype-icon-${playerId}`} style={iconStyle}>\n <img\n src={archetypeImagePath}\n alt={`${player.name.english} archetype`}\n style={{\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n }}\n onError={handleImageError}\n />\n </div>\n {/* Player Name */}\n <div style={nameStyle}>\n {player.name.korean} | {player.name.english}\n </div>\n </div>\n\n {/* Combat Readiness Bar - shows overall combat capability */}\n <CombatReadinessBar\n player={player}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Health Bar - shows aggregate body health */}\n <HealthBar\n current={player.health}\n max={player.maxHealth}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Stamina Bar */}\n <StaminaBar\n current={player.stamina}\n max={player.maxStamina}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Breathing Disruption Indicator */}\n <BreathingIndicator player={player} isMobile={isMobile} />\n\n {/* Laterality Indicator */}\n {laterality && (\n <LateralityIndicator laterality={laterality} isMobile={isMobile} />\n )}\n\n {/* Current Stance Indicator */}\n <div data-testid={`stance-indicator-${playerId}`} style={stanceStyle}>\n 자세 | Stance: {player.currentStance}\n </div>\n </div>\n );\n};\n\n/**\n * Memoized PlayerHUD with custom comparison\n * Only re-renders when relevant props change\n */\nexport const PlayerHUD = React.memo(\n PlayerHUDComponent,\n (prevProps, nextProps) => {\n const healthSame = prevProps.player.health === nextProps.player.health;\n const maxHealthSame =\n prevProps.player.maxHealth === nextProps.player.maxHealth;\n const staminaSame = prevProps.player.stamina === nextProps.player.stamina;\n const maxStaminaSame =\n prevProps.player.maxStamina === nextProps.player.maxStamina;\n const archetypeSame =\n prevProps.player.archetype === nextProps.player.archetype;\n const stanceSame =\n prevProps.player.currentStance === nextProps.player.currentStance;\n const idSame = prevProps.player.id === nextProps.player.id;\n const nameSame =\n prevProps.player.name.korean === nextProps.player.name.korean &&\n prevProps.player.name.english === nextProps.player.name.english;\n\n const statusEffectsSame =\n prevProps.player.statusEffects.length ===\n nextProps.player.statusEffects.length &&\n prevProps.player.statusEffects.every(\n (effect, index) => effect === nextProps.player.statusEffects[index],\n );\n\n const bodyPartHealthSame =\n prevProps.player.bodyPartHealth === nextProps.player.bodyPartHealth;\n const painSame = prevProps.player.pain === nextProps.player.pain;\n const consciousnessSame =\n prevProps.player.consciousness === nextProps.player.consciousness;\n const balanceSame = prevProps.player.balance === nextProps.player.balance;\n\n const positionSame = prevProps.position === nextProps.position;\n const mobileSame = prevProps.isMobile === nextProps.isMobile;\n const lateralitySame = prevProps.laterality === nextProps.laterality;\n\n return (\n healthSame &&\n maxHealthSame &&\n staminaSame &&\n maxStaminaSame &&\n archetypeSame &&\n stanceSame &&\n idSame &&\n nameSame &&\n statusEffectsSame &&\n bodyPartHealthSame &&\n painSame &&\n consciousnessSame &&\n balanceSame &&\n positionSame &&\n mobileSame &&\n lateralitySame\n );\n },\n);\n\nexport default PlayerHUD;\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlayerHUD.js","names":[],"sources":["../../../../../src/components/shared/three/ui/PlayerHUD.tsx"],"sourcesContent":["/**\n * PlayerHUD Component - Combined combat readiness, health and stamina display\n *\n * Displays a complete player HUD with:\n * - Archetype icon/image\n * - Player name (Korean/English)\n * - Combat Readiness bar (10-segment, multi-factor)\n * - Health bar (segmented, color-coded)\n * - Stamina bar (segmented, cyan-themed)\n * - Current stance indicator\n * - Responsive positioning (top-left for player 1, top-right for player 2)\n *\n * Performance optimized with React.memo for 60fps rendering.\n */\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport type { StanceLaterality } from \"../../../../systems/trigram/types\";\nimport {\n ARCHETYPE_ASSETS,\n FALLBACK_ARCHETYPE_IMAGE,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BreathingIndicator } from \"./BreathingIndicator\";\nimport { CombatReadinessBar } from \"./CombatReadinessBar\";\nimport { HealthBar } from \"./HealthBar\";\nimport { StaminaBar } from \"./StaminaBar\";\n\nexport interface PlayerHUDProps {\n /** Player state with health, stamina, and other data */\n readonly player: PlayerState;\n /** Player position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n /** Stance laterality: left or right foot forward */\n readonly laterality?: StanceLaterality;\n}\n\n/**\n * Laterality Indicator Component - Shows L/R badge with Korean text\n * @korean 측면성표시기\n */\nconst LateralityIndicator: React.FC<{\n readonly laterality: StanceLaterality;\n readonly isMobile: boolean;\n}> = React.memo(({ laterality, isMobile }) => {\n const isLeft = laterality === \"left\";\n\n const badgeStyle = useMemo(\n () => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n }),\n [isMobile],\n );\n\n const labelStyle = useMemo(\n () => ({\n padding: isMobile ? \"1px 4px\" : \"2px 6px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"3px\",\n fontWeight: \"bold\",\n minWidth: isMobile ? \"16px\" : \"18px\",\n textAlign: \"center\" as const,\n }),\n [isMobile],\n );\n\n const textStyle = useMemo(\n () => ({\n opacity: 0.8,\n whiteSpace: \"nowrap\" as const,\n }),\n [],\n );\n\n return (\n <div style={badgeStyle} data-testid=\"laterality-indicator\">\n <span style={labelStyle} data-testid=\"laterality-badge\">\n {isLeft ? \"L\" : \"R\"}\n </span>\n <span style={textStyle} data-testid=\"laterality-text\">\n {isLeft ? \"왼발서기\" : \"오른발서기\"}\n </span>\n </div>\n );\n});\nLateralityIndicator.displayName = \"LateralityIndicator\";\n\n/**\n * PlayerHUD - Complete player status display with health and stamina bars\n * Performance optimized with React.memo\n */\nconst PlayerHUDComponent: React.FC<PlayerHUDProps> = ({\n player,\n position,\n isMobile,\n laterality,\n}) => {\n const playerId = player.id;\n const isLeft = position === \"left\";\n\n const layout = useMemo(\n () => ({\n fontSize: isMobile ? 11 : 13,\n gap: isMobile ? \"6px\" : \"8px\",\n iconSize: isMobile ? 40 : 50,\n top: isMobile ? \"8px\" : \"10px\",\n horizontal: isMobile ? \"8px\" : \"12px\",\n }),\n [isMobile],\n );\n\n const archetypeImagePath = useMemo(() => {\n const archetypeKey = player.archetype.toLowerCase();\n const assets =\n ARCHETYPE_ASSETS[archetypeKey as keyof typeof ARCHETYPE_ASSETS];\n return assets?.image ?? FALLBACK_ARCHETYPE_IMAGE;\n }, [player.archetype]);\n\n const containerStyle = useMemo(\n () => ({\n position: \"relative\" as const,\n display: \"flex\",\n flexDirection: \"column\" as const,\n gap: layout.gap,\n pointerEvents: \"none\" as const,\n width: \"100%\",\n }),\n [layout],\n );\n\n const iconContainerStyle = useMemo(() => {\n const direction = isLeft ? \"row\" : \"row-reverse\";\n return {\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n flexDirection: direction as \"row\" | \"row-reverse\",\n };\n }, [isLeft]);\n\n const iconStyle = useMemo(\n () => ({\n width: `${layout.iconSize}px`,\n height: `${layout.iconSize}px`,\n borderRadius: \"8px\",\n overflow: \"hidden\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n flexShrink: 0,\n }),\n [layout.iconSize],\n );\n\n const nameStyle = useMemo(() => {\n const textAlign = isLeft ? \"left\" : \"right\";\n return {\n fontSize: `${layout.fontSize}px`,\n fontWeight: \"bold\" as const,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n textAlign: textAlign as \"left\" | \"right\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8), 0 0 8px rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7),\n borderRadius: \"4px\",\n whiteSpace: \"nowrap\" as const,\n };\n }, [layout.fontSize, isLeft]);\n\n const stanceStyle = useMemo(() => {\n const textAlign = isLeft ? \"left\" : \"right\";\n return {\n fontSize: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_CYAN, 1),\n textAlign: textAlign as \"left\" | \"right\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"4px 8px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n borderRadius: \"4px\",\n marginTop: \"2px\",\n };\n }, [isMobile, isLeft]);\n\n const handleImageError = useCallback(\n (e: React.SyntheticEvent<HTMLImageElement>) => {\n const target = e.target as HTMLImageElement;\n if (!target.src.endsWith(FALLBACK_ARCHETYPE_IMAGE)) {\n target.src = FALLBACK_ARCHETYPE_IMAGE;\n }\n },\n [],\n );\n\n return (\n <div data-testid={`player-hud-${playerId}`} style={containerStyle}>\n {/* Player Name with Archetype Icon */}\n <div data-testid={`player-name-${playerId}`} style={iconContainerStyle}>\n {/* Archetype Icon */}\n <div data-testid={`archetype-icon-${playerId}`} style={iconStyle}>\n <img\n src={archetypeImagePath}\n alt={`${player.name.english} archetype`}\n style={{\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n }}\n onError={handleImageError}\n />\n </div>\n {/* Player Name */}\n <div style={nameStyle}>\n {player.name.korean} | {player.name.english}\n </div>\n </div>\n\n {/* Combat Readiness Bar - shows overall combat capability */}\n <CombatReadinessBar\n player={player}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Health Bar - shows aggregate body health */}\n <HealthBar\n current={player.health}\n max={player.maxHealth}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Stamina Bar */}\n <StaminaBar\n current={player.stamina}\n max={player.maxStamina}\n playerId={playerId}\n isMobile={isMobile}\n />\n\n {/* Breathing Disruption Indicator */}\n <BreathingIndicator player={player} isMobile={isMobile} />\n\n {/* Laterality Indicator */}\n {laterality && (\n <LateralityIndicator laterality={laterality} isMobile={isMobile} />\n )}\n\n {/* Current Stance Indicator */}\n <div data-testid={`stance-indicator-${playerId}`} style={stanceStyle}>\n 자세 | Stance: {player.currentStance}\n </div>\n </div>\n );\n};\n\n/**\n * Memoized PlayerHUD with custom comparison\n * Only re-renders when relevant props change\n */\nexport const PlayerHUD = React.memo(\n PlayerHUDComponent,\n (prevProps, nextProps) => {\n const healthSame = prevProps.player.health === nextProps.player.health;\n const maxHealthSame =\n prevProps.player.maxHealth === nextProps.player.maxHealth;\n const staminaSame = prevProps.player.stamina === nextProps.player.stamina;\n const maxStaminaSame =\n prevProps.player.maxStamina === nextProps.player.maxStamina;\n const archetypeSame =\n prevProps.player.archetype === nextProps.player.archetype;\n const stanceSame =\n prevProps.player.currentStance === nextProps.player.currentStance;\n const idSame = prevProps.player.id === nextProps.player.id;\n const nameSame =\n prevProps.player.name.korean === nextProps.player.name.korean &&\n prevProps.player.name.english === nextProps.player.name.english;\n\n const statusEffectsSame =\n prevProps.player.statusEffects.length ===\n nextProps.player.statusEffects.length &&\n prevProps.player.statusEffects.every(\n (effect, index) => effect === nextProps.player.statusEffects[index],\n );\n\n const bodyPartHealthSame =\n prevProps.player.bodyPartHealth === nextProps.player.bodyPartHealth;\n const painSame = prevProps.player.pain === nextProps.player.pain;\n const consciousnessSame =\n prevProps.player.consciousness === nextProps.player.consciousness;\n const balanceSame = prevProps.player.balance === nextProps.player.balance;\n\n const positionSame = prevProps.position === nextProps.position;\n const mobileSame = prevProps.isMobile === nextProps.isMobile;\n const lateralitySame = prevProps.laterality === nextProps.laterality;\n\n return (\n healthSame &&\n maxHealthSame &&\n staminaSame &&\n maxStaminaSame &&\n archetypeSame &&\n stanceSame &&\n idSame &&\n nameSame &&\n statusEffectsSame &&\n bodyPartHealthSame &&\n painSame &&\n consciousnessSame &&\n balanceSame &&\n positionSame &&\n mobileSame &&\n lateralitySame\n );\n },\n);\n\nexport default PlayerHUD;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProgressBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ProgressBar.tsx"],"sourcesContent":["/**\n * ProgressBar - Three.js-compatible progress bar component\n * \n * Displays health, ki, stamina with Korean theming\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Progress bar type\n */\nexport type ProgressBarType = \"health\" | \"ki\" | \"stamina\";\n\n/**\n * Props for ProgressBar component\n */\nexport interface ProgressBarProps {\n readonly type: ProgressBarType;\n readonly current: number;\n readonly max: number;\n readonly label?: { korean: string; english: string };\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly height?: number;\n readonly showText?: boolean;\n readonly animated?: boolean;\n readonly testId?: string;\n}\n\n/**\n * ProgressBar Component\n * \n * A progress bar component for displaying health, ki, and stamina.\n * Uses Korean cyberpunk theming with gradient fills.\n * \n * @example\n * ```tsx\n * <ProgressBar\n * type=\"health\"\n * current={75}\n * max={100}\n * label={{ korean: \"체력\", english: \"Health\" }}\n * />\n * ```\n */\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\n type,\n current,\n max,\n label,\n position = [0, 0, 0],\n width = 200,\n height = 24,\n showText = true,\n animated = true,\n testId,\n}) => {\n const percentage = useMemo(\n () => Math.max(0, Math.min(1, max > 0 ? current / max : 0)),\n [current, max]\n );\n\n const colors = useMemo(() => {\n switch (type) {\n case \"health\":\n if (percentage > 0.6) {\n return {\n start: KOREAN_COLORS.HEALTH_FULL,\n end: KOREAN_COLORS.HEALTH_MEDIUM,\n glow: KOREAN_COLORS.POSITIVE_GREEN,\n };\n } else if (percentage > 0.3) {\n return {\n start: KOREAN_COLORS.HEALTH_MEDIUM,\n end: KOREAN_COLORS.HEALTH_LOW,\n glow: KOREAN_COLORS.WARNING_ORANGE,\n };\n } else {\n return {\n start: KOREAN_COLORS.HEALTH_LOW,\n end: KOREAN_COLORS.HEALTH_CRITICAL,\n glow: KOREAN_COLORS.ACCENT_RED,\n };\n }\n case \"ki\":\n return {\n start: KOREAN_COLORS.KI_FULL,\n end: KOREAN_COLORS.KI_MEDIUM,\n glow: KOREAN_COLORS.PRIMARY_CYAN,\n };\n case \"stamina\":\n return {\n start: KOREAN_COLORS.STAMINA_FULL,\n end: KOREAN_COLORS.STAMINA_MEDIUM,\n glow: KOREAN_COLORS.SECONDARY_YELLOW,\n };\n default:\n return {\n start: KOREAN_COLORS.PRIMARY_CYAN,\n end: KOREAN_COLORS.ACCENT_BLUE,\n glow: KOREAN_COLORS.PRIMARY_CYAN,\n };\n }\n }, [type, percentage]);\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"4px\",\n }),\n [width]\n );\n\n const labelStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }),\n []\n );\n\n const barContainerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: \"100%\",\n height: `${height}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.UI_BORDER, 0.6)}`,\n borderRadius: \"4px\",\n overflow: \"hidden\",\n position: \"relative\",\n }),\n [height]\n );\n\n const fillStyle = useMemo<React.CSSProperties>(() => {\n return {\n width: `${percentage * 100}%`,\n height: \"100%\",\n background: `linear-gradient(to right, ${hexToRgbaString(colors.start)}, ${hexToRgbaString(colors.end)})`,\n transition: animated ? \"width 0.3s ease\" : \"none\",\n position: \"relative\",\n boxShadow: animated\n ? `0 0 10px ${hexToRgbaString(colors.glow, 0.5)}`\n : \"none\",\n };\n }, [percentage, colors, animated]);\n\n const shineStyle = useMemo<React.CSSProperties>(\n () => ({\n position: \"absolute\",\n top: \"0\",\n left: \"0\",\n width: \"60%\",\n height: \"40%\",\n background: hexToRgbaString(KOREAN_COLORS.WHITE_SOLID, 0.3),\n borderRadius: \"4px\",\n margin: \"2px\",\n }),\n []\n );\n\n const textStyle = useMemo<React.CSSProperties>(\n () => ({\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textShadow: `\n 0 1px 2px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8)},\n 0 0 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.6)}\n `,\n whiteSpace: \"nowrap\",\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div \n style={containerStyle} \n data-testid={testId ?? `progress-bar-${type}`}\n data-current={current}\n data-max={max}\n data-percentage={Math.round(percentage * 100)}\n >\n {/* Label */}\n {label && showText && (\n <div style={labelStyle}>\n <span>\n {label.korean} | {label.english}\n </span>\n <span>\n {Math.ceil(current)} / {max}\n </span>\n </div>\n )}\n\n {/* Bar Container */}\n <div style={barContainerStyle}>\n {/* Fill */}\n <div style={fillStyle}>\n {/* Shine effect */}\n <div style={shineStyle} />\n </div>\n\n {/* Percentage Text Overlay */}\n {showText && (\n <div style={textStyle}>\n {Math.round(percentage * 100)}%\n </div>\n )}\n </div>\n </div>\n </Html>\n );\n};\n\nProgressBar.displayName = \"ProgressBar\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProgressBar.js","names":[],"sources":["../../../../../src/components/shared/three/ui/ProgressBar.tsx"],"sourcesContent":["/**\n * ProgressBar - Three.js-compatible progress bar component\n * \n * Displays health, ki, stamina with Korean theming\n * \n * @module components/three\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Progress bar type\n */\nexport type ProgressBarType = \"health\" | \"ki\" | \"stamina\";\n\n/**\n * Props for ProgressBar component\n */\nexport interface ProgressBarProps {\n readonly type: ProgressBarType;\n readonly current: number;\n readonly max: number;\n readonly label?: { korean: string; english: string };\n readonly position?: [number, number, number];\n readonly width?: number;\n readonly height?: number;\n readonly showText?: boolean;\n readonly animated?: boolean;\n readonly testId?: string;\n}\n\n/**\n * ProgressBar Component\n * \n * A progress bar component for displaying health, ki, and stamina.\n * Uses Korean cyberpunk theming with gradient fills.\n * \n * @example\n * ```tsx\n * <ProgressBar\n * type=\"health\"\n * current={75}\n * max={100}\n * label={{ korean: \"체력\", english: \"Health\" }}\n * />\n * ```\n */\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\n type,\n current,\n max,\n label,\n position = [0, 0, 0],\n width = 200,\n height = 24,\n showText = true,\n animated = true,\n testId,\n}) => {\n const percentage = useMemo(\n () => Math.max(0, Math.min(1, max > 0 ? current / max : 0)),\n [current, max]\n );\n\n const colors = useMemo(() => {\n switch (type) {\n case \"health\":\n if (percentage > 0.6) {\n return {\n start: KOREAN_COLORS.HEALTH_FULL,\n end: KOREAN_COLORS.HEALTH_MEDIUM,\n glow: KOREAN_COLORS.POSITIVE_GREEN,\n };\n } else if (percentage > 0.3) {\n return {\n start: KOREAN_COLORS.HEALTH_MEDIUM,\n end: KOREAN_COLORS.HEALTH_LOW,\n glow: KOREAN_COLORS.WARNING_ORANGE,\n };\n } else {\n return {\n start: KOREAN_COLORS.HEALTH_LOW,\n end: KOREAN_COLORS.HEALTH_CRITICAL,\n glow: KOREAN_COLORS.ACCENT_RED,\n };\n }\n case \"ki\":\n return {\n start: KOREAN_COLORS.KI_FULL,\n end: KOREAN_COLORS.KI_MEDIUM,\n glow: KOREAN_COLORS.PRIMARY_CYAN,\n };\n case \"stamina\":\n return {\n start: KOREAN_COLORS.STAMINA_FULL,\n end: KOREAN_COLORS.STAMINA_MEDIUM,\n glow: KOREAN_COLORS.SECONDARY_YELLOW,\n };\n default:\n return {\n start: KOREAN_COLORS.PRIMARY_CYAN,\n end: KOREAN_COLORS.ACCENT_BLUE,\n glow: KOREAN_COLORS.PRIMARY_CYAN,\n };\n }\n }, [type, percentage]);\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"4px\",\n }),\n [width]\n );\n\n const labelStyle = useMemo<React.CSSProperties>(\n () => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n }),\n []\n );\n\n const barContainerStyle = useMemo<React.CSSProperties>(\n () => ({\n width: \"100%\",\n height: `${height}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.8),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.UI_BORDER, 0.6)}`,\n borderRadius: \"4px\",\n overflow: \"hidden\",\n position: \"relative\",\n }),\n [height]\n );\n\n const fillStyle = useMemo<React.CSSProperties>(() => {\n return {\n width: `${percentage * 100}%`,\n height: \"100%\",\n background: `linear-gradient(to right, ${hexToRgbaString(colors.start)}, ${hexToRgbaString(colors.end)})`,\n transition: animated ? \"width 0.3s ease\" : \"none\",\n position: \"relative\",\n boxShadow: animated\n ? `0 0 10px ${hexToRgbaString(colors.glow, 0.5)}`\n : \"none\",\n };\n }, [percentage, colors, animated]);\n\n const shineStyle = useMemo<React.CSSProperties>(\n () => ({\n position: \"absolute\",\n top: \"0\",\n left: \"0\",\n width: \"60%\",\n height: \"40%\",\n background: hexToRgbaString(KOREAN_COLORS.WHITE_SOLID, 0.3),\n borderRadius: \"4px\",\n margin: \"2px\",\n }),\n []\n );\n\n const textStyle = useMemo<React.CSSProperties>(\n () => ({\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textShadow: `\n 0 1px 2px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8)},\n 0 0 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.6)}\n `,\n whiteSpace: \"nowrap\",\n }),\n []\n );\n\n return (\n <Html position={position} center>\n <div \n style={containerStyle} \n data-testid={testId ?? `progress-bar-${type}`}\n data-current={current}\n data-max={max}\n data-percentage={Math.round(percentage * 100)}\n >\n {/* Label */}\n {label && showText && (\n <div style={labelStyle}>\n <span>\n {label.korean} | {label.english}\n </span>\n <span>\n {Math.ceil(current)} / {max}\n </span>\n </div>\n )}\n\n {/* Bar Container */}\n <div style={barContainerStyle}>\n {/* Fill */}\n <div style={fillStyle}>\n {/* Shine effect */}\n <div style={shineStyle} />\n </div>\n\n {/* Percentage Text Overlay */}\n {showText && (\n <div style={textStyle}>\n {Math.round(percentage * 100)}%\n </div>\n )}\n </div>\n </div>\n </Html>\n );\n};\n\nProgressBar.displayName = \"ProgressBar\";\n"],"mappings":"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"}