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":"ErrorModal.js","names":[],"sources":["../../../../src/components/shared/ui/ErrorModal.tsx"],"sourcesContent":["/**\n * ErrorModal - Korean-themed error dialog component\n * Provides user-friendly error recovery with retry functionality\n * Follows Korean cyberpunk aesthetic and accessibility best practices\n *\n * Now uses BaseButtonOverlayHtml for consistent Korean theming\n */\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { toHex } from \"../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../base\";\n\ninterface ErrorModalProps {\n readonly message: string;\n readonly onRetry: () => void;\n readonly onContinue: () => void;\n}\n\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n UI_BACKGROUND_DARK: toHex(KOREAN_COLORS.UI_BACKGROUND_DARK),\n TEXT_ERROR: toHex(KOREAN_COLORS.TEXT_ERROR),\n} as const;\n\n/**\n * Error modal component with Korean cyberpunk styling\n * Provides retry and continue options for graceful error recovery\n * Includes keyboard navigation\n */\nexport const ErrorModal: React.FC<ErrorModalProps> = ({\n message,\n onRetry,\n onContinue,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleRetry = useCallback(() => {\n onRetry();\n }, [onRetry]);\n\n const handleContinue = useCallback(() => {\n onContinue();\n }, [onContinue]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n handleContinue();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleContinue]);\n\n return (\n <div\n role=\"alertdialog\"\n aria-labelledby=\"error-modal-title\"\n aria-describedby=\"error-modal-description\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n zIndex: 9999,\n fontFamily: FONT_FAMILY.CYBER,\n }}\n data-testid=\"error-modal\"\n >\n <div\n style={{\n backgroundColor: `#${HEX_COLORS.UI_BACKGROUND_DARK}`,\n border: `2px solid #${HEX_COLORS.TEXT_ERROR}`,\n borderRadius: \"8px\",\n padding: \"32px\",\n maxWidth: \"500px\",\n boxShadow: `0 8px 32px #${HEX_COLORS.TEXT_ERROR}40`,\n }}\n >\n {/* Error Icon */}\n <div\n style={{\n textAlign: \"center\",\n fontSize: \"48px\",\n marginBottom: \"16px\",\n }}\n aria-hidden=\"true\"\n >\n ⚠️\n </div>\n\n {/* Title */}\n <h2\n id=\"error-modal-title\"\n style={{\n color: `#${HEX_COLORS.TEXT_ERROR}`,\n fontSize: \"24px\",\n fontWeight: \"bold\",\n textAlign: \"center\",\n marginBottom: \"16px\",\n }}\n >\n 오류 발생 | Error Occurred\n </h2>\n\n {/* Message */}\n <p\n id=\"error-modal-description\"\n style={{\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n fontSize: \"16px\",\n textAlign: \"center\",\n marginBottom: \"32px\",\n lineHeight: \"1.5\",\n }}\n >\n {message}\n </p>\n\n {/* Action Buttons - Now using BaseButtonOverlayHtml */}\n <div\n ref={containerRef}\n style={{\n display: \"flex\",\n gap: \"16px\",\n justifyContent: \"center\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"재시도\"\n english=\"Retry\"\n onClick={handleRetry}\n variant=\"primary\"\n size=\"md\"\n testId=\"error-modal-retry\"\n autoFocus={true}\n />\n\n <BaseButtonOverlayHtml\n korean=\"무음으로 계속\"\n english=\"Continue Without Sound\"\n onClick={handleContinue}\n variant=\"secondary\"\n size=\"md\"\n testId=\"error-modal-continue\"\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ErrorModal;\n"],"mappings":";;;;;;;;;;;;;;AAmBA,IAAM,aAAa;CACjB,cAAc,MAAM,cAAc,aAAa;CAC/C,aAAa,MAAM,cAAc,YAAY;CAC7C,oBAAoB,MAAM,cAAc,mBAAmB;CAC3D,YAAY,MAAM,cAAc,WAAW;CAC5C;;;;;;AAOD,IAAa,cAAyC,EACpD,SACA,SACA,iBACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CAEjD,MAAM,cAAc,kBAAkB;EACpC,SAAS;IACR,CAAC,QAAQ,CAAC;CAEb,MAAM,iBAAiB,kBAAkB;EACvC,YAAY;IACX,CAAC,WAAW,CAAC;CAEhB,gBAAgB;EACd,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UACZ,gBAAgB;;EAIpB,SAAS,iBAAiB,WAAW,cAAc;EACnD,aAAa,SAAS,oBAAoB,WAAW,cAAc;IAClE,CAAC,eAAe,CAAC;CAEpB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,mBAAgB;EAChB,oBAAiB;EACjB,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,QAAQ;GACR,YAAY,YAAY;GACzB;EACD,eAAY;YAEZ,qBAAC,OAAD;GACE,OAAO;IACL,iBAAiB,IAAI,WAAW;IAChC,QAAQ,cAAc,WAAW;IACjC,cAAc;IACd,SAAS;IACT,UAAU;IACV,WAAW,eAAe,WAAW,WAAW;IACjD;aARH;IAWE,oBAAC,OAAD;KACE,OAAO;MACL,WAAW;MACX,UAAU;MACV,cAAc;MACf;KACD,eAAY;eACb;KAEK,CAAA;IAGN,oBAAC,MAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,YAAY;MACZ,WAAW;MACX,cAAc;MACf;eACF;KAEI,CAAA;IAGL,oBAAC,KAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,WAAW;MACX,cAAc;MACd,YAAY;MACb;eAEA;KACC,CAAA;IAGJ,qBAAC,OAAD;KACE,KAAK;KACL,OAAO;MACL,SAAS;MACT,KAAK;MACL,gBAAgB;MACjB;eANH,CAQE,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;MACP,WAAW;MACX,CAAA,EAEF,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;MACP,CAAA,CACE;;IACF;;EACF,CAAA"}
1
+ {"version":3,"file":"ErrorModal.js","names":[],"sources":["../../../../src/components/shared/ui/ErrorModal.tsx"],"sourcesContent":["/**\n * ErrorModal - Korean-themed error dialog component\n * Provides user-friendly error recovery with retry functionality\n * Follows Korean cyberpunk aesthetic and accessibility best practices\n *\n * Now uses BaseButtonOverlayHtml for consistent Korean theming\n */\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { toHex } from \"../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../base\";\n\ninterface ErrorModalProps {\n readonly message: string;\n readonly onRetry: () => void;\n readonly onContinue: () => void;\n}\n\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n UI_BACKGROUND_DARK: toHex(KOREAN_COLORS.UI_BACKGROUND_DARK),\n TEXT_ERROR: toHex(KOREAN_COLORS.TEXT_ERROR),\n} as const;\n\n/**\n * Error modal component with Korean cyberpunk styling\n * Provides retry and continue options for graceful error recovery\n * Includes keyboard navigation\n */\nexport const ErrorModal: React.FC<ErrorModalProps> = ({\n message,\n onRetry,\n onContinue,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleRetry = useCallback(() => {\n onRetry();\n }, [onRetry]);\n\n const handleContinue = useCallback(() => {\n onContinue();\n }, [onContinue]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n handleContinue();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleContinue]);\n\n return (\n <div\n role=\"alertdialog\"\n aria-labelledby=\"error-modal-title\"\n aria-describedby=\"error-modal-description\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n zIndex: 9999,\n fontFamily: FONT_FAMILY.CYBER,\n }}\n data-testid=\"error-modal\"\n >\n <div\n style={{\n backgroundColor: `#${HEX_COLORS.UI_BACKGROUND_DARK}`,\n border: `2px solid #${HEX_COLORS.TEXT_ERROR}`,\n borderRadius: \"8px\",\n padding: \"32px\",\n maxWidth: \"500px\",\n boxShadow: `0 8px 32px #${HEX_COLORS.TEXT_ERROR}40`,\n }}\n >\n {/* Error Icon */}\n <div\n style={{\n textAlign: \"center\",\n fontSize: \"48px\",\n marginBottom: \"16px\",\n }}\n aria-hidden=\"true\"\n >\n ⚠️\n </div>\n\n {/* Title */}\n <h2\n id=\"error-modal-title\"\n style={{\n color: `#${HEX_COLORS.TEXT_ERROR}`,\n fontSize: \"24px\",\n fontWeight: \"bold\",\n textAlign: \"center\",\n marginBottom: \"16px\",\n }}\n >\n 오류 발생 | Error Occurred\n </h2>\n\n {/* Message */}\n <p\n id=\"error-modal-description\"\n style={{\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n fontSize: \"16px\",\n textAlign: \"center\",\n marginBottom: \"32px\",\n lineHeight: \"1.5\",\n }}\n >\n {message}\n </p>\n\n {/* Action Buttons - Now using BaseButtonOverlayHtml */}\n <div\n ref={containerRef}\n style={{\n display: \"flex\",\n gap: \"16px\",\n justifyContent: \"center\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"재시도\"\n english=\"Retry\"\n onClick={handleRetry}\n variant=\"primary\"\n size=\"md\"\n testId=\"error-modal-retry\"\n autoFocus={true}\n />\n\n <BaseButtonOverlayHtml\n korean=\"무음으로 계속\"\n english=\"Continue Without Sound\"\n onClick={handleContinue}\n variant=\"secondary\"\n size=\"md\"\n testId=\"error-modal-continue\"\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ErrorModal;\n"],"mappings":";;;;;;;;;;;;;;AAmBA,IAAM,aAAa;CACjB,cAAc,MAAM,cAAc,YAAY;CAC9C,aAAa,MAAM,cAAc,WAAW;CAC5C,oBAAoB,MAAM,cAAc,kBAAkB;CAC1D,YAAY,MAAM,cAAc,UAAU;AAC5C;;;;;;AAOA,IAAa,cAAyC,EACpD,SACA,SACA,iBACI;CACJ,MAAM,eAAe,OAAuB,IAAI;CAEhD,MAAM,cAAc,kBAAkB;EACpC,QAAQ;CACV,GAAG,CAAC,OAAO,CAAC;CAEZ,MAAM,iBAAiB,kBAAkB;EACvC,WAAW;CACb,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UACZ,eAAe;EAEnB;EAEA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,cAAc,CAAC;CAEnB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,mBAAgB;EAChB,oBAAiB;EACjB,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,QAAQ;GACR,YAAY,YAAY;EAC1B;EACA,eAAY;YAEZ,qBAAC,OAAD;GACE,OAAO;IACL,iBAAiB,IAAI,WAAW;IAChC,QAAQ,cAAc,WAAW;IACjC,cAAc;IACd,SAAS;IACT,UAAU;IACV,WAAW,eAAe,WAAW,WAAW;GAClD;aARF;IAWE,oBAAC,OAAD;KACE,OAAO;MACL,WAAW;MACX,UAAU;MACV,cAAc;KAChB;KACA,eAAY;eACb;IAEI,CAAA;IAGL,oBAAC,MAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,YAAY;MACZ,WAAW;MACX,cAAc;KAChB;eACD;IAEG,CAAA;IAGJ,oBAAC,KAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,WAAW;MACX,cAAc;MACd,YAAY;KACd;eAEC;IACA,CAAA;IAGH,qBAAC,OAAD;KACE,KAAK;KACL,OAAO;MACL,SAAS;MACT,KAAK;MACL,gBAAgB;KAClB;eANF,CAQE,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;MACP,WAAW;KACZ,CAAA,GAED,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;KACR,CAAA,CACE;;GACF;;CACF,CAAA;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingState.js","names":[],"sources":["../../../../src/components/shared/ui/LoadingState.tsx"],"sourcesContent":["import React from 'react';\n\n/**\n * Props for the LoadingState component.\n *\n * @property progress Loading progress as a percentage (0-100), or undefined for indeterminate.\n * @property message Custom loading message to display.\n * @default '로드 중 | Loading...'\n * @property stage Current loading stage, determines the stage-specific message.\n * Possible values:\n * - 'assets': Loading game assets\n * - 'audio': Initializing audio system\n * - 'initialization': Preparing game\n * - 'complete': Loading complete\n * @default 'initialization'\n */\nexport interface LoadingStateProps {\n /**\n * Loading progress as a percentage (0-100), or undefined for indeterminate\n */\n readonly progress?: number;\n\n /**\n * Custom loading message to display\n * @default '로드 중 | Loading...'\n */\n readonly message?: string;\n\n /**\n * Current loading stage, determines the stage-specific message\n * - assets: Loading game assets\n * - audio: Initializing audio system\n * - initialization: Preparing game\n * - complete: Loading complete\n * @default 'initialization'\n */\n readonly stage?: 'assets' | 'audio' | 'initialization' | 'complete';\n}\n\n/**\n * LoadingState component with progress indication.\n * Provides user feedback during asset loading and initialization with Korean/English bilingual support.\n * \n * @example\n * ```tsx\n * <LoadingState\n * progress={50}\n * message=\"로드 중 | Loading...\"\n * stage=\"audio\"\n * />\n * ```\n */\nexport const LoadingState: React.FC<LoadingStateProps> = ({\n progress,\n message = '로드 중 | Loading...',\n stage = 'initialization',\n}) => {\n const stageText = {\n assets: '자산 로드 중 | Loading Assets',\n audio: '오디오 초기화 | Initializing Audio',\n initialization: '게임 준비 중 | Preparing Game',\n complete: '완료 | Complete',\n };\n\n const progressValue = progress !== undefined && !Number.isNaN(progress)\n ? Math.min(100, Math.max(0, progress))\n : undefined;\n\n return (\n <div\n className=\"loading-state\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-state\"\n >\n <div className=\"loading-state__logo\">\n <svg\n width=\"150\"\n height=\"150\"\n viewBox=\"0 0 150 150\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-label=\"Black Trigram Logo\"\n >\n {/* Black Trigram Symbol */}\n <rect x=\"50\" y=\"30\" width=\"20\" height=\"90\" fill=\"#00ffff\" />\n <rect x=\"80\" y=\"30\" width=\"20\" height=\"90\" fill=\"#ffd700\" />\n </svg>\n </div>\n\n <h1 className=\"loading-state__title\">\n 흑괘 | BLACK TRIGRAM\n </h1>\n\n <div\n className=\"loading-state__progress\"\n role=\"progressbar\"\n aria-valuenow={progressValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-progress-bar\"\n >\n <div\n className={`loading-state__progress-bar${progressValue === undefined ? ' loading-state__progress-bar--indeterminate' : ''}`}\n style={progressValue !== undefined ? { width: `${progressValue}%` } : undefined}\n />\n </div>\n\n <p className=\"loading-state__stage\">\n {stageText[stage]}\n </p>\n\n {message && (\n <p className=\"loading-state__message\">\n {message}\n </p>\n )}\n\n <div\n className=\"loading-state__spinner\"\n aria-label=\"Loading spinner\"\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAoDA,IAAa,gBAA6C,EACxD,UACA,UAAU,qBACV,QAAQ,uBACJ;CACJ,MAAM,YAAY;EAChB,QAAQ;EACR,OAAO;EACP,gBAAgB;EAChB,UAAU;EACX;CAED,MAAM,gBAAgB,aAAa,KAAA,KAAa,CAAC,OAAO,MAAM,SAAS,GACnE,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC,GACpC,KAAA;CAEJ,OACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,aAAU;EACV,aAAU;EACV,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;EAClF,eAAY;YANd;GAQE,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KACE,OAAM;KACN,QAAO;KACP,SAAQ;KACR,MAAK;KACL,OAAM;KACN,cAAW;eANb,CASE,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;MAAY,CAAA,EAC5D,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;MAAY,CAAA,CACxD;;IACF,CAAA;GAEN,oBAAC,MAAD;IAAI,WAAU;cAAuB;IAEhC,CAAA;GAEL,oBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,iBAAe;IACf,iBAAe;IACf,iBAAe;IACf,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;IAClF,eAAY;cAEZ,oBAAC,OAAD;KACE,WAAW,8BAA8B,kBAAkB,KAAA,IAAY,gDAAgD;KACvH,OAAO,kBAAkB,KAAA,IAAY,EAAE,OAAO,GAAG,cAAc,IAAI,GAAG,KAAA;KACtE,CAAA;IACE,CAAA;GAEN,oBAAC,KAAD;IAAG,WAAU;cACV,UAAU;IACT,CAAA;GAEH,WACC,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAGN,oBAAC,OAAD;IACE,WAAU;IACV,cAAW;IACX,CAAA;GACE"}
1
+ {"version":3,"file":"LoadingState.js","names":[],"sources":["../../../../src/components/shared/ui/LoadingState.tsx"],"sourcesContent":["import React from 'react';\n\n/**\n * Props for the LoadingState component.\n *\n * @property progress Loading progress as a percentage (0-100), or undefined for indeterminate.\n * @property message Custom loading message to display.\n * @default '로드 중 | Loading...'\n * @property stage Current loading stage, determines the stage-specific message.\n * Possible values:\n * - 'assets': Loading game assets\n * - 'audio': Initializing audio system\n * - 'initialization': Preparing game\n * - 'complete': Loading complete\n * @default 'initialization'\n */\nexport interface LoadingStateProps {\n /**\n * Loading progress as a percentage (0-100), or undefined for indeterminate\n */\n readonly progress?: number;\n\n /**\n * Custom loading message to display\n * @default '로드 중 | Loading...'\n */\n readonly message?: string;\n\n /**\n * Current loading stage, determines the stage-specific message\n * - assets: Loading game assets\n * - audio: Initializing audio system\n * - initialization: Preparing game\n * - complete: Loading complete\n * @default 'initialization'\n */\n readonly stage?: 'assets' | 'audio' | 'initialization' | 'complete';\n}\n\n/**\n * LoadingState component with progress indication.\n * Provides user feedback during asset loading and initialization with Korean/English bilingual support.\n * \n * @example\n * ```tsx\n * <LoadingState\n * progress={50}\n * message=\"로드 중 | Loading...\"\n * stage=\"audio\"\n * />\n * ```\n */\nexport const LoadingState: React.FC<LoadingStateProps> = ({\n progress,\n message = '로드 중 | Loading...',\n stage = 'initialization',\n}) => {\n const stageText = {\n assets: '자산 로드 중 | Loading Assets',\n audio: '오디오 초기화 | Initializing Audio',\n initialization: '게임 준비 중 | Preparing Game',\n complete: '완료 | Complete',\n };\n\n const progressValue = progress !== undefined && !Number.isNaN(progress)\n ? Math.min(100, Math.max(0, progress))\n : undefined;\n\n return (\n <div\n className=\"loading-state\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-state\"\n >\n <div className=\"loading-state__logo\">\n <svg\n width=\"150\"\n height=\"150\"\n viewBox=\"0 0 150 150\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-label=\"Black Trigram Logo\"\n >\n {/* Black Trigram Symbol */}\n <rect x=\"50\" y=\"30\" width=\"20\" height=\"90\" fill=\"#00ffff\" />\n <rect x=\"80\" y=\"30\" width=\"20\" height=\"90\" fill=\"#ffd700\" />\n </svg>\n </div>\n\n <h1 className=\"loading-state__title\">\n 흑괘 | BLACK TRIGRAM\n </h1>\n\n <div\n className=\"loading-state__progress\"\n role=\"progressbar\"\n aria-valuenow={progressValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-progress-bar\"\n >\n <div\n className={`loading-state__progress-bar${progressValue === undefined ? ' loading-state__progress-bar--indeterminate' : ''}`}\n style={progressValue !== undefined ? { width: `${progressValue}%` } : undefined}\n />\n </div>\n\n <p className=\"loading-state__stage\">\n {stageText[stage]}\n </p>\n\n {message && (\n <p className=\"loading-state__message\">\n {message}\n </p>\n )}\n\n <div\n className=\"loading-state__spinner\"\n aria-label=\"Loading spinner\"\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAoDA,IAAa,gBAA6C,EACxD,UACA,UAAU,qBACV,QAAQ,uBACJ;CACJ,MAAM,YAAY;EAChB,QAAQ;EACR,OAAO;EACP,gBAAgB;EAChB,UAAU;CACZ;CAEA,MAAM,gBAAgB,aAAa,KAAA,KAAa,CAAC,OAAO,MAAM,QAAQ,IAClE,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,QAAQ,CAAC,IACnC,KAAA;CAEJ,OACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,aAAU;EACV,aAAU;EACV,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;EAClF,eAAY;YANd;GAQE,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KACE,OAAM;KACN,QAAO;KACP,SAAQ;KACR,MAAK;KACL,OAAM;KACN,cAAW;eANb,CASE,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;KAAW,CAAA,GAC3D,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;KAAW,CAAA,CACxD;;GACF,CAAA;GAEL,oBAAC,MAAD;IAAI,WAAU;cAAuB;GAEjC,CAAA;GAEJ,oBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,iBAAe;IACf,iBAAe;IACf,iBAAe;IACf,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;IAClF,eAAY;cAEZ,oBAAC,OAAD;KACE,WAAW,8BAA8B,kBAAkB,KAAA,IAAY,gDAAgD;KACvH,OAAO,kBAAkB,KAAA,IAAY,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,KAAA;IACvE,CAAA;GACE,CAAA;GAEL,oBAAC,KAAD;IAAG,WAAU;cACV,UAAU;GACV,CAAA;GAEF,WACC,oBAAC,KAAD;IAAG,WAAU;cACV;GACA,CAAA;GAGL,oBAAC,OAAD;IACE,WAAU;IACV,cAAW;GACZ,CAAA;EACE;;AAET"}
@@ -183,7 +183,7 @@ var SplashScreen = ({ onStart, width, height }) => {
183
183
  }),
184
184
  /* @__PURE__ */ jsxs("div", {
185
185
  role: "contentinfo",
186
- "aria-label": `Application version 0.7.45`,
186
+ "aria-label": `Application version 0.7.48`,
187
187
  style: {
188
188
  position: "absolute",
189
189
  bottom: "20px",
@@ -192,7 +192,7 @@ var SplashScreen = ({ onStart, width, height }) => {
192
192
  fontSize: "10px",
193
193
  zIndex: 1
194
194
  },
195
- children: ["v", "0.7.45"]
195
+ children: ["v", "0.7.48"]
196
196
  })
197
197
  ]
198
198
  });
@@ -1 +1 @@
1
- {"version":3,"file":"SplashScreen.js","names":[],"sources":["../../../../src/components/shared/ui/SplashScreen.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, toHex } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\n\ndeclare const APP_VERSION: string | undefined;\n\nconst LOADING_DELAY_MS = 100;\n\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n} as const;\n\nexport interface SplashScreenProps {\n readonly onStart: () => void;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Splash screen that requires user interaction before starting the game.\n * This is necessary to initialize AudioContext which requires a user gesture.\n */\nexport const SplashScreen: React.FC<SplashScreenProps> = ({\n onStart,\n width,\n height,\n}) => {\n const [isLoading, setIsLoading] = useState(false);\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n const layoutCalculation = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : 64,\n subtitleFontSize: isMobile ? 16 : 24,\n bodyFontSize: isMobile ? 12 : 14,\n instructionsFontSize: isMobile ? 11 : 12,\n buttonPadding: isMobile ? \"16px 48px\" : \"20px 60px\",\n buttonFontSize: isMobile ? 16 : 20,\n }),\n [isMobile],\n );\n\n const handleStart = useCallback(() => {\n setIsLoading(true);\n setTimeout(() => {\n onStart();\n }, LOADING_DELAY_MS);\n }, [onStart]);\n\n return (\n <div\n style={{\n width,\n height,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n background: `linear-gradient(180deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 0%, ${hexColorToCSS(KOREAN_COLORS.ARENA_BACKGROUND)} 100%)`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontFamily: FONT_FAMILY.CYBER,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"splash-screen\"\n >\n {/* Animated background grid - decorative only */}\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n background: `\n repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n ),\n repeating-linear-gradient(\n 90deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n )\n `,\n opacity: 0.3,\n }}\n />\n\n {/* Screen reader live region for loading state */}\n <div\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n left: \"-10000px\",\n width: \"1px\",\n height: \"1px\",\n overflow: \"hidden\",\n }}\n >\n {isLoading ? \"Initializing audio and loading game\" : \"Ready to start\"}\n </div>\n\n {/* Logo/Title */}\n <div\n style={{\n marginBottom: \"60px\",\n textAlign: \"center\",\n zIndex: 1,\n }}\n >\n <h1\n style={{\n fontSize: `${layoutCalculation.titleFontSize}px`,\n fontWeight: 900,\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n textShadow: `0 0 20px #${HEX_COLORS.PRIMARY_CYAN}80`,\n marginBottom: \"20px\",\n letterSpacing: \"4px\",\n }}\n >\n 흑괘\n </h1>\n <h2\n style={{\n fontSize: `${layoutCalculation.subtitleFontSize}px`,\n fontWeight: 400,\n color: `#${HEX_COLORS.ACCENT_GOLD}`,\n letterSpacing: \"2px\",\n marginTop: 0,\n }}\n >\n BLACK TRIGRAM\n </h2>\n <p\n style={{\n fontSize: `${layoutCalculation.bodyFontSize}px`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: \"20px\",\n letterSpacing: \"1px\",\n }}\n >\n Korean Martial Arts Dojang\n </p>\n </div>\n\n {/* Start Button */}\n <button\n onClick={handleStart}\n disabled={isLoading}\n aria-label={\n isLoading\n ? \"Starting game and initializing audio\"\n : \"Start game and initialize audio\"\n }\n aria-busy={isLoading}\n aria-describedby=\"splash-instructions\"\n style={{\n padding: layoutCalculation.buttonPadding,\n fontSize: `${layoutCalculation.buttonFontSize}px`,\n fontFamily: FONT_FAMILY.CYBER,\n fontWeight: 700,\n color: isLoading ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT) : hexColorToCSS(KOREAN_COLORS.BLACK),\n background: isLoading\n ? hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT)\n : `linear-gradient(135deg, #${HEX_COLORS.PRIMARY_CYAN} 0%, #${HEX_COLORS.ACCENT_GOLD} 100%)`,\n border: \"none\",\n borderRadius: \"8px\",\n cursor: isLoading ? \"not-allowed\" : \"pointer\",\n textTransform: \"uppercase\",\n letterSpacing: \"2px\",\n transition: \"all 0.3s ease\",\n boxShadow: isLoading\n ? \"none\"\n : `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`,\n position: \"relative\",\n zIndex: 1,\n opacity: isLoading ? 0.6 : 1,\n }}\n onMouseEnter={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 6px 30px #${HEX_COLORS.PRIMARY_CYAN}60`;\n }\n }}\n onMouseLeave={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`;\n }\n }}\n data-testid=\"splash-start-button\"\n >\n {isLoading ? \"시작 중... Starting...\" : \"시작 | Start\"}\n </button>\n\n {/* Instructions */}\n <div\n id=\"splash-instructions\"\n style={{\n marginTop: \"40px\",\n textAlign: \"center\",\n color: hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: `${layoutCalculation.instructionsFontSize}px`,\n maxWidth: \"600px\",\n padding: \"0 20px\",\n zIndex: 1,\n }}\n >\n <p style={{ margin: \"8px 0\" }}>\n Audio initialization requires user interaction\n </p>\n <p style={{ margin: \"8px 0\" }}>\n Click the button above to enable sound and start the game\n </p>\n </div>\n\n {/* Version info */}\n <div\n role=\"contentinfo\"\n aria-label={`Application version ${typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}`}\n style={{\n position: \"absolute\",\n bottom: \"20px\",\n right: \"20px\",\n color: hexColorToCSS(KOREAN_COLORS.UI_STEEL_GRAY),\n fontSize: \"10px\",\n zIndex: 1,\n }}\n >\n v{typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}\n </div>\n </div>\n );\n};\n\nexport default SplashScreen;\n"],"mappings":"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"}
1
+ {"version":3,"file":"SplashScreen.js","names":[],"sources":["../../../../src/components/shared/ui/SplashScreen.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, toHex } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\n\ndeclare const APP_VERSION: string | undefined;\n\nconst LOADING_DELAY_MS = 100;\n\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n} as const;\n\nexport interface SplashScreenProps {\n readonly onStart: () => void;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Splash screen that requires user interaction before starting the game.\n * This is necessary to initialize AudioContext which requires a user gesture.\n */\nexport const SplashScreen: React.FC<SplashScreenProps> = ({\n onStart,\n width,\n height,\n}) => {\n const [isLoading, setIsLoading] = useState(false);\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n const layoutCalculation = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : 64,\n subtitleFontSize: isMobile ? 16 : 24,\n bodyFontSize: isMobile ? 12 : 14,\n instructionsFontSize: isMobile ? 11 : 12,\n buttonPadding: isMobile ? \"16px 48px\" : \"20px 60px\",\n buttonFontSize: isMobile ? 16 : 20,\n }),\n [isMobile],\n );\n\n const handleStart = useCallback(() => {\n setIsLoading(true);\n setTimeout(() => {\n onStart();\n }, LOADING_DELAY_MS);\n }, [onStart]);\n\n return (\n <div\n style={{\n width,\n height,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n background: `linear-gradient(180deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 0%, ${hexColorToCSS(KOREAN_COLORS.ARENA_BACKGROUND)} 100%)`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontFamily: FONT_FAMILY.CYBER,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"splash-screen\"\n >\n {/* Animated background grid - decorative only */}\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n background: `\n repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n ),\n repeating-linear-gradient(\n 90deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n )\n `,\n opacity: 0.3,\n }}\n />\n\n {/* Screen reader live region for loading state */}\n <div\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n left: \"-10000px\",\n width: \"1px\",\n height: \"1px\",\n overflow: \"hidden\",\n }}\n >\n {isLoading ? \"Initializing audio and loading game\" : \"Ready to start\"}\n </div>\n\n {/* Logo/Title */}\n <div\n style={{\n marginBottom: \"60px\",\n textAlign: \"center\",\n zIndex: 1,\n }}\n >\n <h1\n style={{\n fontSize: `${layoutCalculation.titleFontSize}px`,\n fontWeight: 900,\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n textShadow: `0 0 20px #${HEX_COLORS.PRIMARY_CYAN}80`,\n marginBottom: \"20px\",\n letterSpacing: \"4px\",\n }}\n >\n 흑괘\n </h1>\n <h2\n style={{\n fontSize: `${layoutCalculation.subtitleFontSize}px`,\n fontWeight: 400,\n color: `#${HEX_COLORS.ACCENT_GOLD}`,\n letterSpacing: \"2px\",\n marginTop: 0,\n }}\n >\n BLACK TRIGRAM\n </h2>\n <p\n style={{\n fontSize: `${layoutCalculation.bodyFontSize}px`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: \"20px\",\n letterSpacing: \"1px\",\n }}\n >\n Korean Martial Arts Dojang\n </p>\n </div>\n\n {/* Start Button */}\n <button\n onClick={handleStart}\n disabled={isLoading}\n aria-label={\n isLoading\n ? \"Starting game and initializing audio\"\n : \"Start game and initialize audio\"\n }\n aria-busy={isLoading}\n aria-describedby=\"splash-instructions\"\n style={{\n padding: layoutCalculation.buttonPadding,\n fontSize: `${layoutCalculation.buttonFontSize}px`,\n fontFamily: FONT_FAMILY.CYBER,\n fontWeight: 700,\n color: isLoading ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT) : hexColorToCSS(KOREAN_COLORS.BLACK),\n background: isLoading\n ? hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT)\n : `linear-gradient(135deg, #${HEX_COLORS.PRIMARY_CYAN} 0%, #${HEX_COLORS.ACCENT_GOLD} 100%)`,\n border: \"none\",\n borderRadius: \"8px\",\n cursor: isLoading ? \"not-allowed\" : \"pointer\",\n textTransform: \"uppercase\",\n letterSpacing: \"2px\",\n transition: \"all 0.3s ease\",\n boxShadow: isLoading\n ? \"none\"\n : `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`,\n position: \"relative\",\n zIndex: 1,\n opacity: isLoading ? 0.6 : 1,\n }}\n onMouseEnter={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 6px 30px #${HEX_COLORS.PRIMARY_CYAN}60`;\n }\n }}\n onMouseLeave={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`;\n }\n }}\n data-testid=\"splash-start-button\"\n >\n {isLoading ? \"시작 중... Starting...\" : \"시작 | Start\"}\n </button>\n\n {/* Instructions */}\n <div\n id=\"splash-instructions\"\n style={{\n marginTop: \"40px\",\n textAlign: \"center\",\n color: hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: `${layoutCalculation.instructionsFontSize}px`,\n maxWidth: \"600px\",\n padding: \"0 20px\",\n zIndex: 1,\n }}\n >\n <p style={{ margin: \"8px 0\" }}>\n Audio initialization requires user interaction\n </p>\n <p style={{ margin: \"8px 0\" }}>\n Click the button above to enable sound and start the game\n </p>\n </div>\n\n {/* Version info */}\n <div\n role=\"contentinfo\"\n aria-label={`Application version ${typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}`}\n style={{\n position: \"absolute\",\n bottom: \"20px\",\n right: \"20px\",\n color: hexColorToCSS(KOREAN_COLORS.UI_STEEL_GRAY),\n fontSize: \"10px\",\n zIndex: 1,\n }}\n >\n v{typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}\n </div>\n </div>\n );\n};\n\nexport default SplashScreen;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
1
+ {"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VolumeControl.js","names":[],"sources":["../../../../src/components/shared/ui/VolumeControl.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString, toHex } from \"../../../utils/colorUtils\";\n\nexport interface VolumeControlProps {\n readonly position?:\n | \"top-right\"\n | \"bottom-right\"\n | \"top-left\"\n | \"bottom-left\"\n | \"custom\";\n readonly style?: React.CSSProperties;\n readonly showLabels?: boolean;\n readonly compact?: boolean;\n}\n\n/**\n * Volume Control Component\n *\n * Provides controls for:\n * - Master volume\n * - Music volume\n * - SFX volume\n * - Mute/unmute toggle\n *\n * Inspired by template game (https://github.com/Hack23/game)\n */\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\n position = \"top-right\",\n style,\n showLabels = true,\n compact = false,\n}) => {\n const audio = useAudio();\n\n const [masterVolume, setMasterVolume] = useState(audio.masterVolume ?? 1.0);\n const [musicVolume, setMusicVolume] = useState(audio.musicVolume ?? 0.7);\n const [sfxVolume, setSfxVolume] = useState(audio.sfxVolume ?? 0.8);\n const [isMuted, setIsMuted] = useState(audio.muted ?? false);\n\n React.useEffect(() => {\n setMasterVolume(audio.masterVolume ?? 1.0);\n setMusicVolume(audio.musicVolume ?? 0.7);\n setSfxVolume(audio.sfxVolume ?? 0.8);\n setIsMuted(audio.muted ?? false);\n }, [audio.masterVolume, audio.musicVolume, audio.sfxVolume, audio.muted]);\n\n const getPositionStyle = useMemo((): React.CSSProperties => {\n if (position === \"custom\") return {};\n\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n zIndex: 1000,\n padding: compact ? \"8px 12px\" : \"12px 16px\",\n };\n\n switch (position) {\n case \"top-right\":\n return { ...baseStyle, top: \"20px\", right: \"20px\" };\n case \"bottom-right\":\n return { ...baseStyle, bottom: \"20px\", right: \"20px\" };\n case \"top-left\":\n return { ...baseStyle, top: \"20px\", left: \"20px\" };\n case \"bottom-left\":\n return { ...baseStyle, bottom: \"20px\", left: \"20px\" };\n default:\n return baseStyle;\n }\n }, [position, compact]);\n\n const containerStyle = useMemo(\n (): React.CSSProperties => ({\n ...getPositionStyle,\n display: \"flex\",\n flexDirection: compact ? \"row\" : \"column\",\n alignItems: \"center\",\n gap: compact ? \"12px\" : \"8px\",\n background: \"rgba(33, 38, 45, 0.95)\",\n borderRadius: \"12px\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n pointerEvents: \"auto\", // Enable interaction even when parent has pointerEvents: none\n ...style,\n }),\n [getPositionStyle, compact, style],\n );\n\n const handleMasterVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMasterVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"master\", value);\n }\n },\n [audio],\n );\n\n const handleMusicVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMusicVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"music\", value);\n }\n },\n [audio],\n );\n\n const handleSfxVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setSfxVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"sfx\", value);\n }\n },\n [audio],\n );\n\n const handleMuteToggle = useCallback(() => {\n setIsMuted((prevMuted) => {\n const newMuted = !prevMuted;\n if (audio.isAudioReady) {\n if (newMuted) {\n audio.mute();\n } else {\n audio.unmute();\n }\n }\n return newMuted;\n });\n }, [audio]);\n\n const sliderStyle = useMemo(\n (): React.CSSProperties => ({\n width: compact ? \"60px\" : \"100px\",\n cursor: \"pointer\",\n accentColor: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }),\n [compact],\n );\n\n const labelStyle = useMemo(\n (): React.CSSProperties => ({\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: compact ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n minWidth: compact ? \"40px\" : \"50px\",\n textAlign: \"left\",\n }),\n [compact],\n );\n\n const valueStyle = useMemo(\n (): React.CSSProperties => ({\n color: `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`,\n fontSize: compact ? \"10px\" : \"11px\",\n minWidth: \"35px\",\n textAlign: \"right\",\n }),\n [compact],\n );\n\n const controlRowStyle = useMemo(\n (): React.CSSProperties => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n width: \"100%\",\n }),\n [],\n );\n\n if (compact) {\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"6px 12px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇\" : \"🔊\"}\n </button>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n aria-label=\"마스터 볼륨 | Master Volume\"\n style={sliderStyle}\n title=\"마스터 볼륨 | Master Volume\"\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n );\n }\n\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n {showLabels && (\n <div\n style={{\n color: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n fontSize: \"14px\",\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n textAlign: \"center\",\n }}\n >\n 🎵 음량 | Volume\n </div>\n )}\n\n {/* Master Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"master-volume\" style={labelStyle}>\n 전체 | Master\n </label>\n <input\n id=\"master-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n\n {/* Music Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"music-volume\" style={labelStyle}>\n 음악 | Music\n </label>\n <input\n id=\"music-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={musicVolume}\n onChange={handleMusicVolumeChange}\n data-testid=\"music-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(musicVolume * 100)}%</span>\n </div>\n\n {/* SFX Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"sfx-volume\" style={labelStyle}>\n 효과음 | SFX\n </label>\n <input\n id=\"sfx-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={sfxVolume}\n onChange={handleSfxVolumeChange}\n data-testid=\"sfx-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(sfxVolume * 100)}%</span>\n </div>\n\n {/* Mute Toggle */}\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"8px 16px\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n marginTop: \"4px\",\n width: \"100%\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇 음소거 해제 | Unmute\" : \"🔊 음소거 | Mute\"}\n </button>\n\n {/* Audio Status Indicator */}\n <div\n style={{\n color: audio.isAudioReady\n ? `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`\n : hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: \"10px\",\n marginTop: \"4px\",\n textAlign: \"center\",\n }}\n >\n {audio.isAudioReady\n ? \"✓ 오디오 준비됨 | Audio Ready\"\n : \"⏳ 초기화 중... | Initializing...\"}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"VolumeControl.js","names":[],"sources":["../../../../src/components/shared/ui/VolumeControl.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString, toHex } from \"../../../utils/colorUtils\";\n\nexport interface VolumeControlProps {\n readonly position?:\n | \"top-right\"\n | \"bottom-right\"\n | \"top-left\"\n | \"bottom-left\"\n | \"custom\";\n readonly style?: React.CSSProperties;\n readonly showLabels?: boolean;\n readonly compact?: boolean;\n}\n\n/**\n * Volume Control Component\n *\n * Provides controls for:\n * - Master volume\n * - Music volume\n * - SFX volume\n * - Mute/unmute toggle\n *\n * Inspired by template game (https://github.com/Hack23/game)\n */\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\n position = \"top-right\",\n style,\n showLabels = true,\n compact = false,\n}) => {\n const audio = useAudio();\n\n const [masterVolume, setMasterVolume] = useState(audio.masterVolume ?? 1.0);\n const [musicVolume, setMusicVolume] = useState(audio.musicVolume ?? 0.7);\n const [sfxVolume, setSfxVolume] = useState(audio.sfxVolume ?? 0.8);\n const [isMuted, setIsMuted] = useState(audio.muted ?? false);\n\n React.useEffect(() => {\n setMasterVolume(audio.masterVolume ?? 1.0);\n setMusicVolume(audio.musicVolume ?? 0.7);\n setSfxVolume(audio.sfxVolume ?? 0.8);\n setIsMuted(audio.muted ?? false);\n }, [audio.masterVolume, audio.musicVolume, audio.sfxVolume, audio.muted]);\n\n const getPositionStyle = useMemo((): React.CSSProperties => {\n if (position === \"custom\") return {};\n\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n zIndex: 1000,\n padding: compact ? \"8px 12px\" : \"12px 16px\",\n };\n\n switch (position) {\n case \"top-right\":\n return { ...baseStyle, top: \"20px\", right: \"20px\" };\n case \"bottom-right\":\n return { ...baseStyle, bottom: \"20px\", right: \"20px\" };\n case \"top-left\":\n return { ...baseStyle, top: \"20px\", left: \"20px\" };\n case \"bottom-left\":\n return { ...baseStyle, bottom: \"20px\", left: \"20px\" };\n default:\n return baseStyle;\n }\n }, [position, compact]);\n\n const containerStyle = useMemo(\n (): React.CSSProperties => ({\n ...getPositionStyle,\n display: \"flex\",\n flexDirection: compact ? \"row\" : \"column\",\n alignItems: \"center\",\n gap: compact ? \"12px\" : \"8px\",\n background: \"rgba(33, 38, 45, 0.95)\",\n borderRadius: \"12px\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n pointerEvents: \"auto\", // Enable interaction even when parent has pointerEvents: none\n ...style,\n }),\n [getPositionStyle, compact, style],\n );\n\n const handleMasterVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMasterVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"master\", value);\n }\n },\n [audio],\n );\n\n const handleMusicVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMusicVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"music\", value);\n }\n },\n [audio],\n );\n\n const handleSfxVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setSfxVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"sfx\", value);\n }\n },\n [audio],\n );\n\n const handleMuteToggle = useCallback(() => {\n setIsMuted((prevMuted) => {\n const newMuted = !prevMuted;\n if (audio.isAudioReady) {\n if (newMuted) {\n audio.mute();\n } else {\n audio.unmute();\n }\n }\n return newMuted;\n });\n }, [audio]);\n\n const sliderStyle = useMemo(\n (): React.CSSProperties => ({\n width: compact ? \"60px\" : \"100px\",\n cursor: \"pointer\",\n accentColor: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }),\n [compact],\n );\n\n const labelStyle = useMemo(\n (): React.CSSProperties => ({\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: compact ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n minWidth: compact ? \"40px\" : \"50px\",\n textAlign: \"left\",\n }),\n [compact],\n );\n\n const valueStyle = useMemo(\n (): React.CSSProperties => ({\n color: `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`,\n fontSize: compact ? \"10px\" : \"11px\",\n minWidth: \"35px\",\n textAlign: \"right\",\n }),\n [compact],\n );\n\n const controlRowStyle = useMemo(\n (): React.CSSProperties => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n width: \"100%\",\n }),\n [],\n );\n\n if (compact) {\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"6px 12px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇\" : \"🔊\"}\n </button>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n aria-label=\"마스터 볼륨 | Master Volume\"\n style={sliderStyle}\n title=\"마스터 볼륨 | Master Volume\"\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n );\n }\n\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n {showLabels && (\n <div\n style={{\n color: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n fontSize: \"14px\",\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n textAlign: \"center\",\n }}\n >\n 🎵 음량 | Volume\n </div>\n )}\n\n {/* Master Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"master-volume\" style={labelStyle}>\n 전체 | Master\n </label>\n <input\n id=\"master-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n\n {/* Music Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"music-volume\" style={labelStyle}>\n 음악 | Music\n </label>\n <input\n id=\"music-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={musicVolume}\n onChange={handleMusicVolumeChange}\n data-testid=\"music-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(musicVolume * 100)}%</span>\n </div>\n\n {/* SFX Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"sfx-volume\" style={labelStyle}>\n 효과음 | SFX\n </label>\n <input\n id=\"sfx-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={sfxVolume}\n onChange={handleSfxVolumeChange}\n data-testid=\"sfx-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(sfxVolume * 100)}%</span>\n </div>\n\n {/* Mute Toggle */}\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"8px 16px\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n marginTop: \"4px\",\n width: \"100%\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇 음소거 해제 | Unmute\" : \"🔊 음소거 | Mute\"}\n </button>\n\n {/* Audio Status Indicator */}\n <div\n style={{\n color: audio.isAudioReady\n ? `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`\n : hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: \"10px\",\n marginTop: \"4px\",\n textAlign: \"center\",\n }}\n >\n {audio.isAudioReady\n ? \"✓ 오디오 준비됨 | Audio Ready\"\n : \"⏳ 초기화 중... | Initializing...\"}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../../../src/components/shared/ui/shared/ConfirmDialog.tsx"],"sourcesContent":["/**\n * ConfirmDialog Component - Modal confirmation dialog\n * \n * Features:\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - Responsive sizing\n * - Keyboard shortcuts (Enter = confirm, Esc = cancel)\n * \n * Now uses BaseButtonOverlayHtml for consistent styling\n */\n\nimport React, { useEffect } from \"react\";\nimport { useAudio } from \"../../../../audio/AudioProvider\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../../base\";\n\nexport interface ConfirmDialogProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n readonly onCancel: () => void;\n readonly isMobile: boolean;\n}\n\n/**\n * ConfirmDialog - Modal confirmation dialog with Korean theming\n */\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = ({\n isOpen,\n title,\n titleKorean,\n message,\n messageKorean,\n onConfirm,\n onCancel,\n isMobile,\n}) => {\n const audio = useAudio();\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n onConfirm();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isOpen, onConfirm, onCancel]);\n\n if (!isOpen) return null;\n\n return (\n <div\n data-testid=\"confirm-dialog\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8),\n backdropFilter: \"blur(8px)\",\n zIndex: 1001,\n pointerEvents: \"auto\",\n }}\n onClick={(e) => {\n if (e.target === e.currentTarget) {\n onCancel();\n }\n }}\n >\n <div\n style={{\n padding: isMobile ? \"24px\" : \"32px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: \"12px\",\n maxWidth: isMobile ? \"320px\" : \"400px\",\n boxShadow: `0 0 30px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }}\n >\n {/* Title */}\n <h2\n data-testid=\"dialog-title\"\n style={{\n fontSize: isMobile ? \"20px\" : \"24px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n margin: \"0 0 16px 0\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n {titleKorean}\n <br />\n {title}\n </h2>\n\n {/* Message */}\n <p\n data-testid=\"dialog-message\"\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n margin: \"0 0 24px 0\",\n lineHeight: \"1.5\",\n textAlign: \"center\",\n }}\n >\n {messageKorean}\n <br />\n {message}\n </p>\n\n {/* Buttons - Now using BaseButtonOverlayHtml */}\n <div\n style={{\n display: \"flex\",\n gap: \"12px\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"취소\"\n english=\"Cancel\"\n onClick={() => {\n audio.playSFX(\"menu_back\");\n onCancel();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"cancel-button\"\n style={{ flex: 1 }}\n />\n <BaseButtonOverlayHtml\n korean=\"확인\"\n english=\"Confirm\"\n onClick={() => {\n audio.playSFX(\"menu_select\");\n onConfirm();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"confirm-button\"\n style={{ flex: 1 }}\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ConfirmDialog;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAA+C,EAC1D,QACA,OACA,aACA,SACA,eACA,WACA,UACA,eACI;CACJ,MAAM,QAAQ,UAAU;CAExB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EAEb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,SAAS;IACrB,EAAE,gBAAgB;IAClB,WAAW;UACN,IAAI,EAAE,QAAQ,UAAU;IAC7B,EAAE,gBAAgB;IAClB,UAAU;;;EAId,OAAO,iBAAiB,WAAW,cAAc;EACjD,aAAa,OAAO,oBAAoB,WAAW,cAAc;IAChE;EAAC;EAAQ;EAAW;EAAS,CAAC;CAEjC,IAAI,CAAC,QAAQ,OAAO;CAEpB,OACE,oBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,iBAAiB,gBAAgB,cAAc,aAAa,GAAI;GAChE,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;EACD,UAAU,MAAM;GACd,IAAI,EAAE,WAAW,EAAE,eACjB,UAAU;;YAId,qBAAC,OAAD;GACE,OAAO;IACL,SAAS,WAAW,SAAS;IAC7B,iBAAiB,gBAAgB,cAAc,oBAAoB,EAAE;IACrE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;IACrE,cAAc;IACd,UAAU,WAAW,UAAU;IAC/B,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;IACxE;aARH;IAWE,qBAAC,MAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,aAAa,EAAE;MACpD,YAAY,YAAY;MACxB,YAAY;MACZ,QAAQ;MACR,WAAW;MACX,YAAY,YAAY,gBAAgB,cAAc,aAAa,GAAI;MACxE;eAVH;MAYG;MACD,oBAAC,MAAD,EAAM,CAAA;MACL;MACE;;IAGL,qBAAC,KAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,cAAc,EAAE;MACrD,YAAY,YAAY;MACxB,QAAQ;MACR,YAAY;MACZ,WAAW;MACZ;eATH;MAWG;MACD,oBAAC,MAAD,EAAM,CAAA;MACL;MACC;;IAGJ,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,KAAK;MACN;eAJH,CAME,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,YAAY;OAC1B,UAAU;;MAEZ,oBAAoB,MAAM,QAAQ,aAAa;MAC/C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,GAAG;MAClB,CAAA,EACF,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,cAAc;OAC5B,WAAW;;MAEb,oBAAoB,MAAM,QAAQ,aAAa;MAC/C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,GAAG;MAClB,CAAA,CACE;;IACF;;EACF,CAAA"}
1
+ {"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../../../src/components/shared/ui/shared/ConfirmDialog.tsx"],"sourcesContent":["/**\n * ConfirmDialog Component - Modal confirmation dialog\n * \n * Features:\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - Responsive sizing\n * - Keyboard shortcuts (Enter = confirm, Esc = cancel)\n * \n * Now uses BaseButtonOverlayHtml for consistent styling\n */\n\nimport React, { useEffect } from \"react\";\nimport { useAudio } from \"../../../../audio/AudioProvider\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../../base\";\n\nexport interface ConfirmDialogProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n readonly onCancel: () => void;\n readonly isMobile: boolean;\n}\n\n/**\n * ConfirmDialog - Modal confirmation dialog with Korean theming\n */\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = ({\n isOpen,\n title,\n titleKorean,\n message,\n messageKorean,\n onConfirm,\n onCancel,\n isMobile,\n}) => {\n const audio = useAudio();\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n onConfirm();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isOpen, onConfirm, onCancel]);\n\n if (!isOpen) return null;\n\n return (\n <div\n data-testid=\"confirm-dialog\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8),\n backdropFilter: \"blur(8px)\",\n zIndex: 1001,\n pointerEvents: \"auto\",\n }}\n onClick={(e) => {\n if (e.target === e.currentTarget) {\n onCancel();\n }\n }}\n >\n <div\n style={{\n padding: isMobile ? \"24px\" : \"32px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: \"12px\",\n maxWidth: isMobile ? \"320px\" : \"400px\",\n boxShadow: `0 0 30px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }}\n >\n {/* Title */}\n <h2\n data-testid=\"dialog-title\"\n style={{\n fontSize: isMobile ? \"20px\" : \"24px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n margin: \"0 0 16px 0\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n {titleKorean}\n <br />\n {title}\n </h2>\n\n {/* Message */}\n <p\n data-testid=\"dialog-message\"\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n margin: \"0 0 24px 0\",\n lineHeight: \"1.5\",\n textAlign: \"center\",\n }}\n >\n {messageKorean}\n <br />\n {message}\n </p>\n\n {/* Buttons - Now using BaseButtonOverlayHtml */}\n <div\n style={{\n display: \"flex\",\n gap: \"12px\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"취소\"\n english=\"Cancel\"\n onClick={() => {\n audio.playSFX(\"menu_back\");\n onCancel();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"cancel-button\"\n style={{ flex: 1 }}\n />\n <BaseButtonOverlayHtml\n korean=\"확인\"\n english=\"Confirm\"\n onClick={() => {\n audio.playSFX(\"menu_select\");\n onConfirm();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"confirm-button\"\n style={{ flex: 1 }}\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ConfirmDialog;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAA+C,EAC1D,QACA,OACA,aACA,SACA,eACA,WACA,UACA,eACI;CACJ,MAAM,QAAQ,SAAS;CAEvB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EAEb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,SAAS;IACrB,EAAE,eAAe;IACjB,UAAU;GACZ,OAAO,IAAI,EAAE,QAAQ,UAAU;IAC7B,EAAE,eAAe;IACjB,SAAS;GACX;EACF;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAChD,aAAa,OAAO,oBAAoB,WAAW,aAAa;CAClE,GAAG;EAAC;EAAQ;EAAW;CAAQ,CAAC;CAEhC,IAAI,CAAC,QAAQ,OAAO;CAEpB,OACE,oBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,iBAAiB,gBAAgB,cAAc,aAAa,EAAG;GAC/D,gBAAgB;GAChB,QAAQ;GACR,eAAe;EACjB;EACA,UAAU,MAAM;GACd,IAAI,EAAE,WAAW,EAAE,eACjB,SAAS;EAEb;YAEA,qBAAC,OAAD;GACE,OAAO;IACL,SAAS,WAAW,SAAS;IAC7B,iBAAiB,gBAAgB,cAAc,oBAAoB,CAAC;IACpE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,EAAG;IACpE,cAAc;IACd,UAAU,WAAW,UAAU;IAC/B,WAAW,YAAY,gBAAgB,cAAc,cAAc,EAAG;GACxE;aARF;IAWE,qBAAC,MAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,aAAa,CAAC;MACnD,YAAY,YAAY;MACxB,YAAY;MACZ,QAAQ;MACR,WAAW;MACX,YAAY,YAAY,gBAAgB,cAAc,aAAa,EAAG;KACxE;eAVF;MAYG;MACD,oBAAC,MAAD,CAAK,CAAA;MACJ;KACC;;IAGJ,qBAAC,KAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,cAAc,CAAC;MACpD,YAAY,YAAY;MACxB,QAAQ;MACR,YAAY;MACZ,WAAW;KACb;eATF;MAWG;MACD,oBAAC,MAAD,CAAK,CAAA;MACJ;KACA;;IAGH,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,KAAK;KACP;eAJF,CAME,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,WAAW;OACzB,SAAS;MACX;MACA,oBAAoB,MAAM,QAAQ,YAAY;MAC9C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,EAAE;KAClB,CAAA,GACD,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,aAAa;OAC3B,UAAU;MACZ;MACA,oBAAoB,MAAM,QAAQ,YAAY;MAC9C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,EAAE;KAClB,CAAA,CACE;;GACF;;CACF,CAAA;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAS,gBAAgB,SAAyB;CAChD,IAAI,WAAW,IAAI,OAAO,IAAI,cAAc,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CACxF,IAAI,WAAW,IAAI,OAAO,IAAI,cAAc,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CACxF,IAAI,WAAW,IAAI,OAAO,IAAI,cAAc,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CACxF,OAAO,IAAI,cAAc,WAAW,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;;;;;AAMnE,SAAS,gBAAgB,SAGvB;CACA,IAAI,WAAW,IACb,OAAO;EAAE,QAAQ;EAAM,SAAS;EAAU;CAC5C,IAAI,WAAW,IACb,OAAO;EAAE,QAAQ;EAAO,SAAS;EAAY;CAC/C,IAAI,WAAW,IACb,OAAO;EAAE,QAAQ;EAAQ,SAAS;EAAe;CACnD,OAAO;EAAE,QAAQ;EAAO,SAAS;EAAW;;;;;;;;;;;;;;;;;;;;;;;;AAyB9C,IAAa,+BAER,EAAE,QAAQ,aAAa,WAAW;CAAC;CAAG;CAAG;CAAE,EAAE,WAAW,YAAY;CACvE,MAAM,eAAe,cAAc;EACjC,MAAM,kBAAkB,OAAO,iBAAiB,mBAAmB;EACnE,MAAM,eAAe,OAAO,UAAU;EACtC,MAAM,aACJ,OAAO,0BAA0B,KAAA,KACjC,cAAc,OAAO;EACvB,OAAO,mBAAmB,gBAAgB;IACzC;EAAC,OAAO;EAAS,OAAO;EAAiB,OAAO;EAAuB;EAAY,CAAC;CAEvF,MAAM,eAAe,cACb,gBAAgB,OAAO,QAAQ,EACrC,CAAC,OAAO,QAAQ,CACjB;CAED,MAAM,eAAe,cACb,gBAAgB,OAAO,QAAQ,EACrC,CAAC,OAAO,QAAQ,CACjB;CAED,MAAM,qBAAqB,IAAI,cAAc,WAAW,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CAErF,MAAM,iBAAiB,cAAc;EACnC,MAAM,YAAiC;GACrC,QAAQ,eACJ,aAAa,uBACb,aAAa;GACjB,SAAS,WAAW,aAAa;GACjC,iBAAiB;GACjB,cAAc;GACd,WAAW,eACP,YAAY,uBACZ,YAAY;GAChB,YAAY;GACZ,YAAY,YAAY;GACxB,UAAU,WAAW,SAAS;GAC9B,UAAU,WAAW,UAAU;GAC/B,WAAW;GACX,YAAY;GACZ,eAAe;GAChB;EAED,IAAI,cACF,UAAU,YAAY;EAGxB,OAAO;IACN;EAAC;EAAc;EAAc;EAAoB;EAAS,CAAC;CAE9D,MAAM,oBAAoB,KAAK,MAAM,OAAO,QAAQ;CAEpD,OACE,oBAAA,UAAA,EAAA,UAEE,qBAAC,MAAD;EAAgB;EAAU,QAAA;YAA1B,CACE,oBAAC,SAAD,EAAA,UACG;;;;;;aAOK,CAAA,EACR,qBAAC,OAAD;GAAK,OAAO;GAAgB,eAAY;aAAxC;IAEE,qBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,YAAY;MACZ,cAAc;MACf;eALH;MAMC;MACgB;MAAkB;MAC7B;;IAGN,qBAAC,OAAD;KACE,OAAO;MACL,OAAO,IAAI,cAAc,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;MACrE,UAAU,WAAW,SAAS;MAC/B;eAJH;MAMG,aAAa;MAAO;MAAI,aAAa;MAClC;;IAGL,OAAO,iBAAiB,mBACvB,oBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,UAAU,WAAW,SAAS;MAC9B,WAAW;MACX,YAAY;MACb;eACF;KAEK,CAAA;IAIP,OAAO,yBAAyB,cAAc,OAAO,yBACpD,oBAAC,OAAD;KACE,OAAO;MACL,OAAO,IAAI,cAAc,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;MACrE,UAAU,WAAW,QAAQ;MAC7B,WAAW;MACZ;eACF;KAEK,CAAA;IAEJ;KACD;KACN,CAAA;;AAIP,4BAA4B,cAAc"}
1
+ {"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bodyDimensions.js","names":[],"sources":["../../src/constants/bodyDimensions.ts"],"sourcesContent":["/**\n * Centralized body dimensions for consistent rendering across muscles and clothing.\n *\n * **Korean**: 신체 치수 (Body Dimensions)\n *\n * This module provides a single source of truth for body part radii and lengths.\n * Both the muscle system (BoneAttachedMuscles) and clothing system (BoneClothing)\n * import these values to ensure clothing properly wraps around muscles.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for muscular Korean martial arts fighter proportions\n * - Radii represent the thickness of each body part\n * - Lengths represent the extent along the bone axis\n * - Clothing should add a thin layer OUTSIDE these dimensions\n *\n * @module constants/bodyDimensions\n * @category Rendering\n * @korean 신체치수\n */\n\n// ============================================================================\n// SHOULDER DIMENSIONS (어깨 치수)\n// ============================================================================\n\n/**\n * Shoulder (deltoid) muscle radius.\n * Athletic martial artist proportions.\n * @korean 어깨반지름\n */\nexport const SHOULDER_RADIUS = 0.08;\n\n/**\n * Shoulder muscle length along bone axis.\n * @korean 어깨길이\n */\nexport const SHOULDER_LENGTH = 0.15;\n\n// ============================================================================\n// UPPER ARM DIMENSIONS (상완 치수)\n// ============================================================================\n\n/**\n * Bicep muscle radius.\n * Muscular martial artist proportions.\n * @korean 이두근반지름\n */\nexport const BICEP_RADIUS = 0.06;\n\n/**\n * Bicep muscle length.\n * @korean 이두근길이\n */\nexport const BICEP_LENGTH = 0.25;\n\n/**\n * Tricep muscle radius.\n * Slightly smaller than bicep.\n * @korean 삼두근반지름\n */\nexport const TRICEP_RADIUS = 0.055;\n\n/**\n * Tricep muscle length.\n * @korean 삼두근길이\n */\nexport const TRICEP_LENGTH = 0.22;\n\n/**\n * Combined upper arm radius for clothing calculations.\n * Uses the larger of bicep/tricep for proper fit.\n * @korean 상완반지름\n */\nexport const UPPER_ARM_RADIUS = Math.max(BICEP_RADIUS, TRICEP_RADIUS);\n\n// ============================================================================\n// FOREARM DIMENSIONS (전완 치수)\n// ============================================================================\n\n/**\n * Forearm muscle radius.\n * Athletic martial artist proportions.\n * @korean 전완반지름\n */\nexport const FOREARM_RADIUS = 0.045;\n\n/**\n * Forearm muscle length.\n * @korean 전완길이\n */\nexport const FOREARM_LENGTH = 0.2;\n\n// ============================================================================\n// TORSO DIMENSIONS (몸통 치수)\n// ============================================================================\n\n/**\n * Pectorals (chest) muscle radius/depth.\n * Athletic chest depth.\n * @korean 가슴반지름\n */\nexport const PECTORALS_RADIUS = 0.12;\n\n/**\n * Pectorals width (length in capsule terms).\n * @korean 가슴너비\n */\nexport const PECTORALS_LENGTH = 0.28;\n\n/**\n * Core (midsection) muscle radius.\n * Athletic core depth.\n * @korean 코어반지름\n */\nexport const CORE_RADIUS = 0.1;\n\n/**\n * Core muscle length.\n * @korean 코어길이\n */\nexport const CORE_LENGTH = 0.25;\n\n/**\n * Abdominals muscle radius.\n * Athletic abs depth.\n * @korean 복근반지름\n */\nexport const ABS_RADIUS = 0.08;\n\n/**\n * Abdominals muscle length.\n * @korean 복근길이\n */\nexport const ABS_LENGTH = 0.22;\n\n/**\n * Obliques muscle radius.\n * @korean 복사근반지름\n */\nexport const OBLIQUES_RADIUS = 0.06;\n\n/**\n * Obliques muscle length.\n * @korean 복사근길이\n */\nexport const OBLIQUES_LENGTH = 0.2;\n\n// ============================================================================\n// HIP/GLUTE DIMENSIONS (엉덩이 치수)\n// ============================================================================\n\n/**\n * Hip flexor muscle radius.\n * @korean 고관절굴근반지름\n */\nexport const HIP_FLEXOR_RADIUS = 0.04;\n\n/**\n * Hip flexor muscle length.\n * @korean 고관절굴근길이\n */\nexport const HIP_FLEXOR_LENGTH = 0.12;\n\n/**\n * Glute muscle radius.\n * Athletic glute proportions.\n * @korean 둔근반지름\n */\nexport const GLUTE_RADIUS = 0.06;\n\n/**\n * Glute muscle length.\n * @korean 둔근길이\n */\nexport const GLUTE_LENGTH = 0.14;\n\n// ============================================================================\n// THIGH DIMENSIONS (허벅지 치수)\n// ============================================================================\n\n/**\n * Quadriceps muscle radius.\n * Athletic thigh proportions.\n * @korean 대퇴사두근반지름\n */\nexport const QUAD_RADIUS = 0.055;\n\n/**\n * Quadriceps muscle length.\n * @korean 대퇴사두근길이\n */\nexport const QUAD_LENGTH = 0.25;\n\n/**\n * Hamstring muscle radius.\n * @korean 햄스트링반지름\n */\nexport const HAMSTRING_RADIUS = 0.05;\n\n/**\n * Hamstring muscle length.\n * @korean 햄스트링길이\n */\nexport const HAMSTRING_LENGTH = 0.23;\n\n/**\n * Combined thigh radius for clothing calculations.\n * Uses the larger of quad/hamstring for proper fit.\n * @korean 허벅지반지름\n */\nexport const THIGH_RADIUS = Math.max(QUAD_RADIUS, HAMSTRING_RADIUS);\n\n// ============================================================================\n// LOWER LEG DIMENSIONS (종아리 치수)\n// ============================================================================\n\n/**\n * Calf muscle radius.\n * Athletic calf proportions.\n * @korean 종아리반지름\n */\nexport const CALF_RADIUS = 0.04;\n\n/**\n * Calf muscle length.\n * @korean 종아리길이\n */\nexport const CALF_LENGTH = 0.2;\n\n// ============================================================================\n// BACK DIMENSIONS (등 치수)\n// ============================================================================\n\n/**\n * Latissimus dorsi muscle radius.\n * @korean 광배근반지름\n */\nexport const LAT_RADIUS = 0.05;\n\n/**\n * Latissimus dorsi muscle length.\n * @korean 광배근길이\n */\nexport const LAT_LENGTH = 0.2;\n\n/**\n * Trapezius muscle radius.\n * @korean 승모근반지름\n */\nexport const TRAPEZIUS_RADIUS = 0.045;\n\n/**\n * Trapezius muscle length.\n * @korean 승모근길이\n */\nexport const TRAPEZIUS_LENGTH = 0.14;\n\n/**\n * Erector spinae muscle radius.\n * @korean 척추기립근반지름\n */\nexport const ERECTOR_SPINAE_RADIUS = 0.035;\n\n/**\n * Erector spinae muscle length.\n * @korean 척추기립근길이\n */\nexport const ERECTOR_SPINAE_LENGTH = 0.18;\n\n// ============================================================================\n// CLOTHING CONSTANTS (의류 상수)\n// ============================================================================\n\n/**\n * Standard fabric thickness for tight-fitting clothing.\n * @korean 밀착의류두께\n */\nexport const CLOTHING_THICKNESS_TIGHT = 0.01;\n\n/**\n * Standard fabric thickness for fitted clothing.\n * @korean 맞춤의류두께\n */\nexport const CLOTHING_THICKNESS_FITTED = 0.015;\n\n/**\n * Standard fabric thickness for loose clothing.\n * @korean 헐렁한의류두께\n */\nexport const CLOTHING_THICKNESS_LOOSE = 0.025;\n\n/**\n * Standard fabric thickness for oversized clothing.\n * @korean 오버사이즈의류두께\n */\nexport const CLOTHING_THICKNESS_OVERSIZED = 0.04;\n\n/**\n * Get clothing thickness based on fit type.\n * @param fit - Clothing fit type\n * @returns Fabric thickness in meters\n * @korean 의류두께가져오기\n */\nexport const getClothingThickness = (\n fit: \"tight\" | \"fitted\" | \"loose\" | \"oversized\",\n): number => {\n switch (fit) {\n case \"tight\":\n return CLOTHING_THICKNESS_TIGHT;\n case \"fitted\":\n return CLOTHING_THICKNESS_FITTED;\n case \"loose\":\n return CLOTHING_THICKNESS_LOOSE;\n case \"oversized\":\n return CLOTHING_THICKNESS_OVERSIZED;\n default:\n return CLOTHING_THICKNESS_FITTED;\n }\n};\n\n/**\n * Calculate clothing radius for a body part.\n * Adds fabric thickness to body radius with body thickness scaling.\n *\n * @param bodyRadius - Base body part radius\n * @param bodyThickness - Body thickness multiplier (from muscle/fat mass)\n * @param clothingThickness - Fabric thickness\n * @returns Total clothing radius\n * @korean 의류반지름계산\n */\nexport const calculateClothingRadius = (\n bodyRadius: number,\n bodyThickness: number,\n clothingThickness: number,\n): number => {\n return bodyRadius * bodyThickness + clothingThickness;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;;AAW/B,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAO5B,IAAa,gBAAgB;;;;;AAM7B,IAAa,gBAAgB;;;;;;AAkB7B,IAAa,iBAAiB;;;;;AAM9B,IAAa,iBAAiB;;;;;;AAW9B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAOhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;;AAO3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;AAU/B,IAAa,oBAAoB;;;;;AAMjC,IAAa,oBAAoB;;;;;;AAOjC,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAW5B,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAM3B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAkBhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAU3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;AAMhC,IAAa,wBAAwB;;;;;AAMrC,IAAa,wBAAwB;;;;;AAgBrC,IAAa,4BAA4B;;;;;;;;;;;AA+CzC,IAAa,2BACX,YACA,eACA,sBACW;CACX,OAAO,aAAa,gBAAgB"}
1
+ {"version":3,"file":"bodyDimensions.js","names":[],"sources":["../../src/constants/bodyDimensions.ts"],"sourcesContent":["/**\n * Centralized body dimensions for consistent rendering across muscles and clothing.\n *\n * **Korean**: 신체 치수 (Body Dimensions)\n *\n * This module provides a single source of truth for body part radii and lengths.\n * Both the muscle system (BoneAttachedMuscles) and clothing system (BoneClothing)\n * import these values to ensure clothing properly wraps around muscles.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for muscular Korean martial arts fighter proportions\n * - Radii represent the thickness of each body part\n * - Lengths represent the extent along the bone axis\n * - Clothing should add a thin layer OUTSIDE these dimensions\n *\n * @module constants/bodyDimensions\n * @category Rendering\n * @korean 신체치수\n */\n\n// ============================================================================\n// SHOULDER DIMENSIONS (어깨 치수)\n// ============================================================================\n\n/**\n * Shoulder (deltoid) muscle radius.\n * Athletic martial artist proportions.\n * @korean 어깨반지름\n */\nexport const SHOULDER_RADIUS = 0.08;\n\n/**\n * Shoulder muscle length along bone axis.\n * @korean 어깨길이\n */\nexport const SHOULDER_LENGTH = 0.15;\n\n// ============================================================================\n// UPPER ARM DIMENSIONS (상완 치수)\n// ============================================================================\n\n/**\n * Bicep muscle radius.\n * Muscular martial artist proportions.\n * @korean 이두근반지름\n */\nexport const BICEP_RADIUS = 0.06;\n\n/**\n * Bicep muscle length.\n * @korean 이두근길이\n */\nexport const BICEP_LENGTH = 0.25;\n\n/**\n * Tricep muscle radius.\n * Slightly smaller than bicep.\n * @korean 삼두근반지름\n */\nexport const TRICEP_RADIUS = 0.055;\n\n/**\n * Tricep muscle length.\n * @korean 삼두근길이\n */\nexport const TRICEP_LENGTH = 0.22;\n\n/**\n * Combined upper arm radius for clothing calculations.\n * Uses the larger of bicep/tricep for proper fit.\n * @korean 상완반지름\n */\nexport const UPPER_ARM_RADIUS = Math.max(BICEP_RADIUS, TRICEP_RADIUS);\n\n// ============================================================================\n// FOREARM DIMENSIONS (전완 치수)\n// ============================================================================\n\n/**\n * Forearm muscle radius.\n * Athletic martial artist proportions.\n * @korean 전완반지름\n */\nexport const FOREARM_RADIUS = 0.045;\n\n/**\n * Forearm muscle length.\n * @korean 전완길이\n */\nexport const FOREARM_LENGTH = 0.2;\n\n// ============================================================================\n// TORSO DIMENSIONS (몸통 치수)\n// ============================================================================\n\n/**\n * Pectorals (chest) muscle radius/depth.\n * Athletic chest depth.\n * @korean 가슴반지름\n */\nexport const PECTORALS_RADIUS = 0.12;\n\n/**\n * Pectorals width (length in capsule terms).\n * @korean 가슴너비\n */\nexport const PECTORALS_LENGTH = 0.28;\n\n/**\n * Core (midsection) muscle radius.\n * Athletic core depth.\n * @korean 코어반지름\n */\nexport const CORE_RADIUS = 0.1;\n\n/**\n * Core muscle length.\n * @korean 코어길이\n */\nexport const CORE_LENGTH = 0.25;\n\n/**\n * Abdominals muscle radius.\n * Athletic abs depth.\n * @korean 복근반지름\n */\nexport const ABS_RADIUS = 0.08;\n\n/**\n * Abdominals muscle length.\n * @korean 복근길이\n */\nexport const ABS_LENGTH = 0.22;\n\n/**\n * Obliques muscle radius.\n * @korean 복사근반지름\n */\nexport const OBLIQUES_RADIUS = 0.06;\n\n/**\n * Obliques muscle length.\n * @korean 복사근길이\n */\nexport const OBLIQUES_LENGTH = 0.2;\n\n// ============================================================================\n// HIP/GLUTE DIMENSIONS (엉덩이 치수)\n// ============================================================================\n\n/**\n * Hip flexor muscle radius.\n * @korean 고관절굴근반지름\n */\nexport const HIP_FLEXOR_RADIUS = 0.04;\n\n/**\n * Hip flexor muscle length.\n * @korean 고관절굴근길이\n */\nexport const HIP_FLEXOR_LENGTH = 0.12;\n\n/**\n * Glute muscle radius.\n * Athletic glute proportions.\n * @korean 둔근반지름\n */\nexport const GLUTE_RADIUS = 0.06;\n\n/**\n * Glute muscle length.\n * @korean 둔근길이\n */\nexport const GLUTE_LENGTH = 0.14;\n\n// ============================================================================\n// THIGH DIMENSIONS (허벅지 치수)\n// ============================================================================\n\n/**\n * Quadriceps muscle radius.\n * Athletic thigh proportions.\n * @korean 대퇴사두근반지름\n */\nexport const QUAD_RADIUS = 0.055;\n\n/**\n * Quadriceps muscle length.\n * @korean 대퇴사두근길이\n */\nexport const QUAD_LENGTH = 0.25;\n\n/**\n * Hamstring muscle radius.\n * @korean 햄스트링반지름\n */\nexport const HAMSTRING_RADIUS = 0.05;\n\n/**\n * Hamstring muscle length.\n * @korean 햄스트링길이\n */\nexport const HAMSTRING_LENGTH = 0.23;\n\n/**\n * Combined thigh radius for clothing calculations.\n * Uses the larger of quad/hamstring for proper fit.\n * @korean 허벅지반지름\n */\nexport const THIGH_RADIUS = Math.max(QUAD_RADIUS, HAMSTRING_RADIUS);\n\n// ============================================================================\n// LOWER LEG DIMENSIONS (종아리 치수)\n// ============================================================================\n\n/**\n * Calf muscle radius.\n * Athletic calf proportions.\n * @korean 종아리반지름\n */\nexport const CALF_RADIUS = 0.04;\n\n/**\n * Calf muscle length.\n * @korean 종아리길이\n */\nexport const CALF_LENGTH = 0.2;\n\n// ============================================================================\n// BACK DIMENSIONS (등 치수)\n// ============================================================================\n\n/**\n * Latissimus dorsi muscle radius.\n * @korean 광배근반지름\n */\nexport const LAT_RADIUS = 0.05;\n\n/**\n * Latissimus dorsi muscle length.\n * @korean 광배근길이\n */\nexport const LAT_LENGTH = 0.2;\n\n/**\n * Trapezius muscle radius.\n * @korean 승모근반지름\n */\nexport const TRAPEZIUS_RADIUS = 0.045;\n\n/**\n * Trapezius muscle length.\n * @korean 승모근길이\n */\nexport const TRAPEZIUS_LENGTH = 0.14;\n\n/**\n * Erector spinae muscle radius.\n * @korean 척추기립근반지름\n */\nexport const ERECTOR_SPINAE_RADIUS = 0.035;\n\n/**\n * Erector spinae muscle length.\n * @korean 척추기립근길이\n */\nexport const ERECTOR_SPINAE_LENGTH = 0.18;\n\n// ============================================================================\n// CLOTHING CONSTANTS (의류 상수)\n// ============================================================================\n\n/**\n * Standard fabric thickness for tight-fitting clothing.\n * @korean 밀착의류두께\n */\nexport const CLOTHING_THICKNESS_TIGHT = 0.01;\n\n/**\n * Standard fabric thickness for fitted clothing.\n * @korean 맞춤의류두께\n */\nexport const CLOTHING_THICKNESS_FITTED = 0.015;\n\n/**\n * Standard fabric thickness for loose clothing.\n * @korean 헐렁한의류두께\n */\nexport const CLOTHING_THICKNESS_LOOSE = 0.025;\n\n/**\n * Standard fabric thickness for oversized clothing.\n * @korean 오버사이즈의류두께\n */\nexport const CLOTHING_THICKNESS_OVERSIZED = 0.04;\n\n/**\n * Get clothing thickness based on fit type.\n * @param fit - Clothing fit type\n * @returns Fabric thickness in meters\n * @korean 의류두께가져오기\n */\nexport const getClothingThickness = (\n fit: \"tight\" | \"fitted\" | \"loose\" | \"oversized\",\n): number => {\n switch (fit) {\n case \"tight\":\n return CLOTHING_THICKNESS_TIGHT;\n case \"fitted\":\n return CLOTHING_THICKNESS_FITTED;\n case \"loose\":\n return CLOTHING_THICKNESS_LOOSE;\n case \"oversized\":\n return CLOTHING_THICKNESS_OVERSIZED;\n default:\n return CLOTHING_THICKNESS_FITTED;\n }\n};\n\n/**\n * Calculate clothing radius for a body part.\n * Adds fabric thickness to body radius with body thickness scaling.\n *\n * @param bodyRadius - Base body part radius\n * @param bodyThickness - Body thickness multiplier (from muscle/fat mass)\n * @param clothingThickness - Fabric thickness\n * @returns Total clothing radius\n * @korean 의류반지름계산\n */\nexport const calculateClothingRadius = (\n bodyRadius: number,\n bodyThickness: number,\n clothingThickness: number,\n): number => {\n return bodyRadius * bodyThickness + clothingThickness;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;;AAW/B,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAO5B,IAAa,gBAAgB;;;;;AAM7B,IAAa,gBAAgB;;;;;;AAkB7B,IAAa,iBAAiB;;;;;AAM9B,IAAa,iBAAiB;;;;;;AAW9B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAOhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;;AAO3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;AAU/B,IAAa,oBAAoB;;;;;AAMjC,IAAa,oBAAoB;;;;;;AAOjC,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAW5B,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAM3B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAkBhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAU3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;AAMhC,IAAa,wBAAwB;;;;;AAMrC,IAAa,wBAAwB;;;;;AAgBrC,IAAa,4BAA4B;;;;;;;;;;;AA+CzC,IAAa,2BACX,YACA,eACA,sBACW;CACX,OAAO,aAAa,gBAAgB;AACtC"}