blacktrigram 0.7.47 → 0.7.49

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 (471) 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 +29 -25
  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.d.ts.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
  39. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +1 -1
  42. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  44. package/lib/components/screens/combat/components/hud/CombatTopHUD.d.ts.map +1 -1
  45. package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -1
  46. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  47. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  48. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  49. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  50. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  51. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  52. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  53. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  54. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  55. package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
  56. package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
  57. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  58. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  59. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  60. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  61. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  62. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  63. package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
  64. package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
  65. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  66. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  67. package/lib/components/screens/controls/ControlsScreen3D.js +1 -1
  68. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  69. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  72. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  74. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  75. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  76. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  77. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  78. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  79. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  84. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  86. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  87. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  88. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  90. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  93. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/philosophy/PhilosophyScreen3D.js +1 -1
  96. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  97. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  98. package/lib/components/screens/training/TrainingScreen3D.js +3 -11
  99. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  100. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  101. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  102. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  103. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  104. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  105. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  106. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  107. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  108. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  109. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  110. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  111. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  112. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  113. package/lib/components/screens/training/components/hud/TrainingBottomHUD.d.ts.map +1 -1
  114. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
  115. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  116. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  117. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  118. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  119. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  120. package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
  121. package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
  122. package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
  123. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  124. package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
  125. package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
  126. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  127. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  128. package/lib/components/shared/base/BaseButton.js.map +1 -1
  129. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  130. package/lib/components/shared/base/BasePanel.js.map +1 -1
  131. package/lib/components/shared/base/BaseText.js.map +1 -1
  132. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  133. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  134. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  135. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  136. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  137. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  138. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  139. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  140. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  141. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  142. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  143. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  144. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  145. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  146. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  147. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  148. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  149. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  150. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  151. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  152. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  153. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  154. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  155. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  156. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  157. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  158. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  159. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  160. package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
  161. package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
  162. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  163. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  164. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  165. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  166. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  167. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  168. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  169. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  170. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  171. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  172. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  173. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  174. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  175. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  176. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  177. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  178. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  179. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  180. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  181. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  182. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  183. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  184. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  185. package/lib/components/shared/ui/BackButton.js.map +1 -1
  186. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  187. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  188. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  189. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  190. package/lib/components/shared/ui/SplashScreen.js +2 -2
  191. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  192. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  193. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  194. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  195. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  196. package/lib/constants/bodyDimensions.js.map +1 -1
  197. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  198. package/lib/data/archetypeClothing.js.map +1 -1
  199. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  200. package/lib/data/techniqueMappings.js.map +1 -1
  201. package/lib/data/techniques.js.map +1 -1
  202. package/lib/hooks/useActionFeedback.js.map +1 -1
  203. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  204. package/lib/hooks/useCombatTimer.js.map +1 -1
  205. package/lib/hooks/useDebounce.js.map +1 -1
  206. package/lib/hooks/useHUDLayout.d.ts.map +1 -1
  207. package/lib/hooks/useHUDLayout.js +3 -2
  208. package/lib/hooks/useHUDLayout.js.map +1 -1
  209. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  210. package/lib/hooks/useKeyboardControls.js.map +1 -1
  211. package/lib/hooks/useMatchCountdown.js.map +1 -1
  212. package/lib/hooks/useMuscleActivation.js.map +1 -1
  213. package/lib/hooks/usePauseMenu.js.map +1 -1
  214. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  215. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  216. package/lib/hooks/useRoundTransition.js.map +1 -1
  217. package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
  218. package/lib/hooks/useSkeletalAnimation.js +1 -1
  219. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  220. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  221. package/lib/hooks/useThrottle.js.map +1 -1
  222. package/lib/hooks/useTouchControls.js.map +1 -1
  223. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  224. package/lib/hooks/useWindowSize.js.map +1 -1
  225. package/lib/systems/CombatSystem.js.map +1 -1
  226. package/lib/systems/EffectCalculator.js.map +1 -1
  227. package/lib/systems/LayoutSystem.js.map +1 -1
  228. package/lib/systems/PlayerEffectManager.js.map +1 -1
  229. package/lib/systems/ResponsiveScaling.js.map +1 -1
  230. package/lib/systems/TrigramSystem.js.map +1 -1
  231. package/lib/systems/VitalPointSystem.js.map +1 -1
  232. package/lib/systems/ai/AIPersonality.js.map +1 -1
  233. package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
  234. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  235. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  236. package/lib/systems/ai/ComboSystem.js.map +1 -1
  237. package/lib/systems/ai/DecisionTree.js.map +1 -1
  238. package/lib/systems/ai/TrainingAI.js.map +1 -1
  239. package/lib/systems/ai/types.js.map +1 -1
  240. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  241. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  242. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  243. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  244. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  245. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
  246. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
  247. package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
  248. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  249. package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
  250. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  251. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
  252. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
  253. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
  254. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  255. package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
  256. package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
  257. package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
  258. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  259. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  260. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  261. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  262. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  263. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  264. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  265. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  266. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  267. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  268. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  269. package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
  270. package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
  271. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  272. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  273. package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
  274. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
  275. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
  276. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  277. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  278. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  279. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  280. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
  281. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
  282. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
  283. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
  284. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  285. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  286. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  287. package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
  288. package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
  289. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  290. package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
  291. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  292. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  293. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  294. package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
  295. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  296. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  297. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  298. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  299. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  300. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  301. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  302. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  303. package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
  304. package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
  305. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  306. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  307. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  308. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  309. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  310. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  311. package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
  312. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  313. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  314. package/lib/systems/animation/core/AnimationPriority.js +15 -15
  315. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  316. package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
  317. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  318. package/lib/systems/animation/core/AnimationRegistry.js +74 -12
  319. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  320. package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
  321. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  322. package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
  323. package/lib/systems/animation/core/AnimationTransitions.js +34 -0
  324. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  325. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  326. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  327. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  328. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  329. package/lib/systems/animation/core/index.d.ts +1 -1
  330. package/lib/systems/animation/core/index.d.ts.map +1 -1
  331. package/lib/systems/animation/core/types.d.ts +24 -0
  332. package/lib/systems/animation/core/types.d.ts.map +1 -1
  333. package/lib/systems/animation/core/types.js +27 -11
  334. package/lib/systems/animation/core/types.js.map +1 -1
  335. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  336. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  337. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  338. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  339. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  340. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  341. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  342. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  343. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  344. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  345. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  346. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  347. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  348. package/lib/systems/bodypart/types.js.map +1 -1
  349. package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
  350. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  351. package/lib/systems/breathing/feedback.js.map +1 -1
  352. package/lib/systems/breathing/integration.js.map +1 -1
  353. package/lib/systems/combat/BalanceSystem.js +19 -19
  354. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  355. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  356. package/lib/systems/combat/CombatStateSystem.js +17 -17
  357. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  358. package/lib/systems/combat/ConsciousnessSystem.js +24 -24
  359. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  360. package/lib/systems/combat/FallIntegration.js.map +1 -1
  361. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  362. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  363. package/lib/systems/combat/PainResponseSystem.js +21 -21
  364. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  365. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  366. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  367. package/lib/systems/combat/typeGuards.js.map +1 -1
  368. package/lib/systems/effects.js.map +1 -1
  369. package/lib/systems/game.js.map +1 -1
  370. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  371. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  372. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  373. package/lib/systems/movement/integration.js.map +1 -1
  374. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  375. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  376. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  377. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  378. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  379. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  380. package/lib/systems/physics/SpeedModifierSystem.js +6 -6
  381. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  382. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  383. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  384. package/lib/systems/trigram/StanceManager.js.map +1 -1
  385. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  386. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  387. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  388. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  389. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  390. package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
  391. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  392. package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
  393. package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
  394. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  395. package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
  396. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  397. package/lib/systems/trigram/techniques/index.js.map +1 -1
  398. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  399. package/lib/systems/trigram/types.js.map +1 -1
  400. package/lib/systems/types.js.map +1 -1
  401. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  402. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  403. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  404. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  405. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  406. package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
  407. package/lib/types/AccessibilityTypes.js.map +1 -1
  408. package/lib/types/LayoutTypes.js.map +1 -1
  409. package/lib/types/PhysicsTypes.js.map +1 -1
  410. package/lib/types/common.js.map +1 -1
  411. package/lib/types/constants/animations.js.map +1 -1
  412. package/lib/types/constants/colors.js.map +1 -1
  413. package/lib/types/constants/designSystem.js.map +1 -1
  414. package/lib/types/constants/index.js.map +1 -1
  415. package/lib/types/constants/layout.d.ts +21 -0
  416. package/lib/types/constants/layout.d.ts.map +1 -1
  417. package/lib/types/constants/layout.js +22 -1
  418. package/lib/types/constants/layout.js.map +1 -1
  419. package/lib/types/constants/performance.js.map +1 -1
  420. package/lib/types/constants/typography.js.map +1 -1
  421. package/lib/types/constants/ui.js.map +1 -1
  422. package/lib/types/facial.js +19 -19
  423. package/lib/types/facial.js.map +1 -1
  424. package/lib/types/hand-animation.js.map +1 -1
  425. package/lib/types/injury.js.map +1 -1
  426. package/lib/types/muscle.js.map +1 -1
  427. package/lib/types/physics.js.map +1 -1
  428. package/lib/types/physicsConstants.js.map +1 -1
  429. package/lib/types/player-visual.d.ts +1 -1
  430. package/lib/types/player-visual.d.ts.map +1 -1
  431. package/lib/types/skeletal.js.map +1 -1
  432. package/lib/types/techniqueId.js.map +1 -1
  433. package/lib/utils/accessibility.js.map +1 -1
  434. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  435. package/lib/utils/assetConfig.js.map +1 -1
  436. package/lib/utils/characterScaling.js.map +1 -1
  437. package/lib/utils/colorHelpers.js.map +1 -1
  438. package/lib/utils/colorUtils.js.map +1 -1
  439. package/lib/utils/combatReadiness.js.map +1 -1
  440. package/lib/utils/controlMapping.js.map +1 -1
  441. package/lib/utils/deviceDetection.js +6 -7
  442. package/lib/utils/deviceDetection.js.map +1 -1
  443. package/lib/utils/effectUtils.js.map +1 -1
  444. package/lib/utils/fabricTextures.js.map +1 -1
  445. package/lib/utils/hapticFeedback.js.map +1 -1
  446. package/lib/utils/haptics.js.map +1 -1
  447. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  448. package/lib/utils/inputSystem.js.map +1 -1
  449. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  450. package/lib/utils/math.js.map +1 -1
  451. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  452. package/lib/utils/mobileUIUtils.js.map +1 -1
  453. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  454. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  455. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  456. package/lib/utils/performanceOptimization.js.map +1 -1
  457. package/lib/utils/player3DHelpers.js.map +1 -1
  458. package/lib/utils/playerUtils.js.map +1 -1
  459. package/lib/utils/responsiveLayout.js.map +1 -1
  460. package/lib/utils/responsiveLayoutHelpers.d.ts +7 -0
  461. package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
  462. package/lib/utils/responsiveLayoutHelpers.js +16 -2
  463. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  464. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  465. package/lib/utils/safeAreaUtils.js.map +1 -1
  466. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  467. package/lib/utils/skeletonScaling.js.map +1 -1
  468. package/lib/utils/stanceHelpers.js.map +1 -1
  469. package/lib/utils/threeObjectPool.js.map +1 -1
  470. package/lib/utils/visualEffects.js.map +1 -1
  471. package/package.json +7 -7
@@ -1,9 +1,11 @@
1
1
  import { getScreenSize } from "../../../../systems/ResponsiveScaling.js";
2
+ import { TRAINING_BOTTOM_HUD_HEIGHT_PERCENT, TRAINING_TOP_HUD_HEIGHT_PERCENT } from "../../../../types/constants/layout.js";
3
+ import { getHUDHeight } from "../../../../utils/responsiveLayout.js";
4
+ import { getDesktopArenaWidthBudget, getHUDPositionScale } from "../../../../utils/responsiveLayoutHelpers.js";
2
5
  import { calculateArenaWorldDimensions } from "../../../../utils/arenaWorldDimensions.js";
3
6
  import { shouldUseMobileControls } from "../../../../utils/deviceDetection.js";
4
7
  import { calculateMobileAreaBounds } from "../../../../utils/mobileLayoutHelpers.js";
5
8
  import { PORTRAIT_HYSTERESIS_FACTOR, mobileControlsBottomClearance } from "../../../../utils/responsiveOrientationConstants.js";
6
- import { getDesktopArenaWidthBudget } from "../../../../utils/responsiveLayoutHelpers.js";
7
9
  import { useMemo } from "react";
8
10
  //#region src/components/screens/training/hooks/useTrainingLayout.ts
9
11
  /**
@@ -58,16 +60,19 @@ function useTrainingLayout(width, height) {
58
60
  footerHeight: isMobile ? 60 : isTablet ? 70 : isLargeDesktop ? 90 : 80
59
61
  };
60
62
  }, [isMobile, screenSize]);
63
+ const positionScale = useMemo(() => getHUDPositionScale(screenSize, isMobile), [screenSize, isMobile]);
61
64
  return {
62
65
  layoutConstants,
63
66
  trainingAreaBounds: useMemo(() => {
64
- const areaY = layoutConstants.headerHeight + layoutConstants.padding;
67
+ const topHudHeight = getHUDHeight(height, TRAINING_TOP_HUD_HEIGHT_PERCENT) * positionScale;
68
+ const bottomHudHeight = getHUDHeight(height, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT) * positionScale;
69
+ const areaY = topHudHeight + layoutConstants.padding;
65
70
  const worldDimensions = calculateArenaWorldDimensions(width);
66
71
  if (isMobile) {
67
72
  const isExtraSmall = width < 380;
68
- return calculateMobileAreaBounds(width, height, isExtraSmall ? 75 : 80, mobileControlsBottomClearance(layoutConstants.controlsHeight, layoutConstants.footerHeight, isExtraSmall, isPortrait, "training"), areaY, isPortrait ? "portrait" : "landscape");
73
+ return calculateMobileAreaBounds(width, height, isExtraSmall ? 75 : 80, mobileControlsBottomClearance(layoutConstants.controlsHeight, Math.max(layoutConstants.footerHeight, bottomHudHeight), isExtraSmall, isPortrait, "training"), areaY, isPortrait ? "portrait" : "landscape");
69
74
  }
70
- const totalReservedHeight = layoutConstants.headerHeight + layoutConstants.controlsHeight + layoutConstants.footerHeight;
75
+ const totalReservedHeight = topHudHeight + bottomHudHeight;
71
76
  const totalPadding = layoutConstants.padding * 3;
72
77
  const availableHeight = height - totalReservedHeight - totalPadding;
73
78
  let arenaWidth = getDesktopArenaWidthBudget(width);
@@ -91,7 +96,8 @@ function useTrainingLayout(width, height) {
91
96
  height,
92
97
  layoutConstants,
93
98
  isMobile,
94
- isPortrait
99
+ isPortrait,
100
+ positionScale
95
101
  ]),
96
102
  isMobile,
97
103
  isPortrait,
@@ -1 +1 @@
1
- {"version":3,"file":"useTrainingLayout.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingLayout.ts"],"sourcesContent":["/**\n * useTrainingLayout Hook - Enhanced Responsive Training Layout\n *\n * Custom hook for managing responsive training screen layout calculations with\n * comprehensive support for all screen sizes from mobile to ultra-wide displays.\n *\n * Enhanced Features:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional scaling for consistent sizing across devices\n * - Optimized layout sizing for each device category\n * - Smooth transitions for resize operations\n * - 60fps performance maintained\n *\n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n *\n * Performance:\n * - Reduces recalculations by checking only breakpoint changes, not exact dimensions\n * - Memoizes layout constants to prevent cascading re-renders\n * - Targets <1ms execution time for layout calculations\n *\n * @param width - Screen width\n * @param height - Screen height\n *\n * @returns Layout constants and training area bounds\n *\n * @example\n * ```typescript\n * const { layoutConstants, trainingAreaBounds, isMobile, screenSize } = useTrainingLayout(1200, 800);\n * ```\n */\n\nimport { useMemo } from \"react\";\nimport { getScreenSize } from \"../../../../systems/ResponsiveScaling\";\nimport { calculateArenaWorldDimensions } from \"../../../../utils/arenaWorldDimensions\";\nimport { shouldUseMobileControls } from \"../../../../utils/deviceDetection\";\nimport { calculateMobileAreaBounds } from \"../../../../utils/mobileLayoutHelpers\";\nimport {\n mobileControlsBottomClearance,\n PORTRAIT_FORCE_MAX_WIDTH_PX,\n PORTRAIT_HYSTERESIS_FACTOR,\n} from \"../../../../utils/responsiveOrientationConstants\";\nimport { getDesktopArenaWidthBudget } from \"../../../../utils/responsiveLayoutHelpers\";\n\nimport type { ScreenSize } from \"../../../../systems/ResponsiveScaling\";\n\nexport interface TrainingLayoutConstants {\n readonly padding: number;\n readonly headerHeight: number;\n readonly buttonHeight: number;\n readonly sectionSpacing: number;\n readonly controlsHeight: number;\n readonly footerHeight: number;\n}\n\nexport interface TrainingAreaBounds {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly scale: number; // 3D scale factor for training area (1.0 = desktop, <1.0 = mobile)\n readonly worldWidthMeters: number; // Physical training area width in meters\n readonly worldDepthMeters: number; // Physical training area depth in meters\n}\n\nexport interface TrainingLayout {\n readonly layoutConstants: TrainingLayoutConstants;\n readonly trainingAreaBounds: TrainingAreaBounds;\n readonly isMobile: boolean;\n readonly isPortrait: boolean;\n readonly screenSize: ScreenSize;\n}\n\n/**\n * Custom hook for training screen layout calculations\n * Enhanced with centralized responsive scaling system\n * Optimized to reduce recalculations and improve 60fps performance\n */\nexport function useTrainingLayout(\n width: number,\n height: number,\n): TrainingLayout {\n const screenSize = useMemo(() => getScreenSize(width), [width]);\n\n const isPortrait = height > width * PORTRAIT_HYSTERESIS_FACTOR;\n\n const isMobile =\n shouldUseMobileControls() ||\n (isPortrait && width < PORTRAIT_FORCE_MAX_WIDTH_PX);\n\n const layoutConstants = useMemo<TrainingLayoutConstants>(() => {\n const isLargeDesktop = screenSize === \"xlarge\";\n const isTablet = screenSize === \"tablet\";\n\n return {\n padding: isMobile ? 20 : isTablet ? 25 : isLargeDesktop ? 35 : 30,\n headerHeight: isMobile ? 80 : isTablet ? 90 : isLargeDesktop ? 110 : 100,\n buttonHeight: isMobile ? 45 : isTablet ? 50 : isLargeDesktop ? 60 : 55,\n sectionSpacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 25 : 20,\n controlsHeight: isMobile\n ? 120\n : isTablet\n ? 110\n : isLargeDesktop\n ? 150\n : 130,\n footerHeight: isMobile ? 60 : isTablet ? 70 : isLargeDesktop ? 90 : 80,\n };\n }, [isMobile, screenSize]);\n\n const trainingAreaBounds = useMemo<TrainingAreaBounds>(() => {\n const areaY = layoutConstants.headerHeight + layoutConstants.padding;\n\n const worldDimensions = calculateArenaWorldDimensions(width);\n\n if (isMobile) {\n const isExtraSmall = width < 380;\n const topClearance = isExtraSmall ? 75 : 80;\n const bottomClearance = mobileControlsBottomClearance(\n layoutConstants.controlsHeight,\n layoutConstants.footerHeight,\n isExtraSmall,\n isPortrait,\n \"training\",\n );\n\n return calculateMobileAreaBounds(\n width,\n height,\n topClearance,\n bottomClearance,\n areaY,\n isPortrait ? \"portrait\" : \"landscape\",\n );\n }\n\n const totalReservedHeight =\n layoutConstants.headerHeight +\n layoutConstants.controlsHeight +\n layoutConstants.footerHeight;\n const totalPadding = layoutConstants.padding * 3;\n const availableHeight = height - totalReservedHeight - totalPadding;\n const availableWidth = getDesktopArenaWidthBudget(width);\n\n let arenaWidth = availableWidth;\n let arenaHeight = arenaWidth * (3 / 4); // 4:3 aspect ratio\n\n if (arenaHeight > availableHeight) {\n arenaHeight = availableHeight;\n arenaWidth = arenaHeight * (4 / 3);\n }\n\n const pixelsPerMeter = arenaWidth / worldDimensions.widthMeters;\n const referencePixelsPerMeter = 100;\n const scale = pixelsPerMeter / referencePixelsPerMeter;\n\n return {\n x: (width - arenaWidth) / 2, // Center horizontally\n y: areaY,\n width: arenaWidth,\n height: arenaHeight, // 4:3 aspect ratio\n scale,\n worldWidthMeters: worldDimensions.widthMeters,\n worldDepthMeters: worldDimensions.depthMeters,\n };\n }, [width, height, layoutConstants, isMobile, isPortrait]);\n\n return {\n layoutConstants,\n trainingAreaBounds,\n isMobile,\n isPortrait,\n screenSize,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,SAAgB,kBACd,OACA,QACgB;CAChB,MAAM,aAAa,cAAc,cAAc,MAAM,EAAE,CAAC,MAAM,CAAC;CAE/D,MAAM,aAAa,SAAS,QAAQ;CAEpC,MAAM,WACJ,yBAAyB,IACxB,cAAc,QAAA;CAEjB,MAAM,kBAAkB,cAAuC;EAC7D,MAAM,iBAAiB,eAAe;EACtC,MAAM,WAAW,eAAe;EAEhC,OAAO;GACL,SAAS,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GAC/D,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,MAAM;GACrE,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GACpE,gBAAgB,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GACtE,gBAAgB,WACZ,MACA,WACE,MACA,iBACE,MACA;GACR,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GACrE;IACA,CAAC,UAAU,WAAW,CAAC;CA2D1B,OAAO;EACL;EACA,oBA3DyB,cAAkC;GAC3D,MAAM,QAAQ,gBAAgB,eAAe,gBAAgB;GAE7D,MAAM,kBAAkB,8BAA8B,MAAM;GAE5D,IAAI,UAAU;IACZ,MAAM,eAAe,QAAQ;IAU7B,OAAO,0BACL,OACA,QAXmB,eAAe,KAAK,IACjB,8BACtB,gBAAgB,gBAChB,gBAAgB,cAChB,cACA,YACA,WAOA,EACA,OACA,aAAa,aAAa,YAC3B;;GAGH,MAAM,sBACJ,gBAAgB,eAChB,gBAAgB,iBAChB,gBAAgB;GAClB,MAAM,eAAe,gBAAgB,UAAU;GAC/C,MAAM,kBAAkB,SAAS,sBAAsB;GAGvD,IAAI,aAFmB,2BAA2B,MAEjC;GACjB,IAAI,cAAc,cAAc,IAAI;GAEpC,IAAI,cAAc,iBAAiB;IACjC,cAAc;IACd,aAAa,eAAe,IAAI;;GAKlC,MAAM,QAFiB,aAAa,gBAAgB,cAErB;GAE/B,OAAO;IACL,IAAI,QAAQ,cAAc;IAC1B,GAAG;IACH,OAAO;IACP,QAAQ;IACR;IACA,kBAAkB,gBAAgB;IAClC,kBAAkB,gBAAgB;IACnC;KACA;GAAC;GAAO;GAAQ;GAAiB;GAAU;GAAW,CAIvD;EACA;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useTrainingLayout.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingLayout.ts"],"sourcesContent":["/**\n * useTrainingLayout Hook - Enhanced Responsive Training Layout\n *\n * Custom hook for managing responsive training screen layout calculations with\n * comprehensive support for all screen sizes from mobile to ultra-wide displays.\n *\n * Enhanced Features:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional scaling for consistent sizing across devices\n * - Optimized layout sizing for each device category\n * - Smooth transitions for resize operations\n * - 60fps performance maintained\n *\n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n *\n * Performance:\n * - Reduces recalculations by checking only breakpoint changes, not exact dimensions\n * - Memoizes layout constants to prevent cascading re-renders\n * - Targets <1ms execution time for layout calculations\n *\n * @param width - Screen width\n * @param height - Screen height\n *\n * @returns Layout constants and training area bounds\n *\n * @example\n * ```typescript\n * const { layoutConstants, trainingAreaBounds, isMobile, screenSize } = useTrainingLayout(1200, 800);\n * ```\n */\n\nimport { useMemo } from \"react\";\nimport { getScreenSize } from \"../../../../systems/ResponsiveScaling\";\nimport { calculateArenaWorldDimensions } from \"../../../../utils/arenaWorldDimensions\";\nimport { shouldUseMobileControls } from \"../../../../utils/deviceDetection\";\nimport { calculateMobileAreaBounds } from \"../../../../utils/mobileLayoutHelpers\";\nimport {\n mobileControlsBottomClearance,\n PORTRAIT_FORCE_MAX_WIDTH_PX,\n PORTRAIT_HYSTERESIS_FACTOR,\n} from \"../../../../utils/responsiveOrientationConstants\";\nimport {\n getDesktopArenaWidthBudget,\n getHUDPositionScale,\n} from \"../../../../utils/responsiveLayoutHelpers\";\nimport {\n TRAINING_BOTTOM_HUD_HEIGHT_PERCENT,\n TRAINING_TOP_HUD_HEIGHT_PERCENT,\n} from \"../../../../types/constants/layout\";\nimport { getHUDHeight } from \"../../../../utils/responsiveLayout\";\n\nimport type { ScreenSize } from \"../../../../systems/ResponsiveScaling\";\n\nexport interface TrainingLayoutConstants {\n readonly padding: number;\n readonly headerHeight: number;\n readonly buttonHeight: number;\n readonly sectionSpacing: number;\n readonly controlsHeight: number;\n readonly footerHeight: number;\n}\n\nexport interface TrainingAreaBounds {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly scale: number; // 3D scale factor for training area (1.0 = desktop, <1.0 = mobile)\n readonly worldWidthMeters: number; // Physical training area width in meters\n readonly worldDepthMeters: number; // Physical training area depth in meters\n}\n\nexport interface TrainingLayout {\n readonly layoutConstants: TrainingLayoutConstants;\n readonly trainingAreaBounds: TrainingAreaBounds;\n readonly isMobile: boolean;\n readonly isPortrait: boolean;\n readonly screenSize: ScreenSize;\n}\n\n/**\n * Custom hook for training screen layout calculations\n * Enhanced with centralized responsive scaling system\n * Optimized to reduce recalculations and improve 60fps performance\n */\nexport function useTrainingLayout(\n width: number,\n height: number,\n): TrainingLayout {\n const screenSize = useMemo(() => getScreenSize(width), [width]);\n\n const isPortrait = height > width * PORTRAIT_HYSTERESIS_FACTOR;\n\n const isMobile =\n shouldUseMobileControls() ||\n (isPortrait && width < PORTRAIT_FORCE_MAX_WIDTH_PX);\n\n const layoutConstants = useMemo<TrainingLayoutConstants>(() => {\n const isLargeDesktop = screenSize === \"xlarge\";\n const isTablet = screenSize === \"tablet\";\n\n return {\n padding: isMobile ? 20 : isTablet ? 25 : isLargeDesktop ? 35 : 30,\n headerHeight: isMobile ? 80 : isTablet ? 90 : isLargeDesktop ? 110 : 100,\n buttonHeight: isMobile ? 45 : isTablet ? 50 : isLargeDesktop ? 60 : 55,\n sectionSpacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 25 : 20,\n controlsHeight: isMobile\n ? 120\n : isTablet\n ? 110\n : isLargeDesktop\n ? 150\n : 130,\n footerHeight: isMobile ? 60 : isTablet ? 70 : isLargeDesktop ? 90 : 80,\n };\n }, [isMobile, screenSize]);\n\n const positionScale = useMemo(\n () => getHUDPositionScale(screenSize, isMobile),\n [screenSize, isMobile],\n );\n\n const trainingAreaBounds = useMemo<TrainingAreaBounds>(() => {\n const topHudHeight =\n getHUDHeight(height, TRAINING_TOP_HUD_HEIGHT_PERCENT) * positionScale;\n const bottomHudHeight =\n getHUDHeight(height, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT) * positionScale;\n const areaY = topHudHeight + layoutConstants.padding;\n\n const worldDimensions = calculateArenaWorldDimensions(width);\n\n if (isMobile) {\n const isExtraSmall = width < 380;\n const topClearance = isExtraSmall ? 75 : 80;\n const bottomClearance = mobileControlsBottomClearance(\n layoutConstants.controlsHeight,\n Math.max(layoutConstants.footerHeight, bottomHudHeight),\n isExtraSmall,\n isPortrait,\n \"training\",\n );\n\n return calculateMobileAreaBounds(\n width,\n height,\n topClearance,\n bottomClearance,\n areaY,\n isPortrait ? \"portrait\" : \"landscape\",\n );\n }\n\n const totalReservedHeight = topHudHeight + bottomHudHeight;\n const totalPadding = layoutConstants.padding * 3;\n const availableHeight = height - totalReservedHeight - totalPadding;\n const availableWidth = getDesktopArenaWidthBudget(width);\n\n let arenaWidth = availableWidth;\n let arenaHeight = arenaWidth * (3 / 4); // 4:3 aspect ratio\n\n if (arenaHeight > availableHeight) {\n arenaHeight = availableHeight;\n arenaWidth = arenaHeight * (4 / 3);\n }\n\n const pixelsPerMeter = arenaWidth / worldDimensions.widthMeters;\n const referencePixelsPerMeter = 100;\n const scale = pixelsPerMeter / referencePixelsPerMeter;\n\n return {\n x: (width - arenaWidth) / 2, // Center horizontally\n y: areaY,\n width: arenaWidth,\n height: arenaHeight, // 4:3 aspect ratio\n scale,\n worldWidthMeters: worldDimensions.widthMeters,\n worldDepthMeters: worldDimensions.depthMeters,\n };\n }, [width, height, layoutConstants, isMobile, isPortrait, positionScale]);\n\n return {\n layoutConstants,\n trainingAreaBounds,\n isMobile,\n isPortrait,\n screenSize,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,SAAgB,kBACd,OACA,QACgB;CAChB,MAAM,aAAa,cAAc,cAAc,KAAK,GAAG,CAAC,KAAK,CAAC;CAE9D,MAAM,aAAa,SAAS,QAAQ;CAEpC,MAAM,WACJ,wBAAwB,KACvB,cAAc,QAAA;CAEjB,MAAM,kBAAkB,cAAuC;EAC7D,MAAM,iBAAiB,eAAe;EACtC,MAAM,WAAW,eAAe;EAEhC,OAAO;GACL,SAAS,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GAC/D,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,MAAM;GACrE,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GACpE,gBAAgB,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;GACtE,gBAAgB,WACZ,MACA,WACE,MACA,iBACE,MACA;GACR,cAAc,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK;EACtE;CACF,GAAG,CAAC,UAAU,UAAU,CAAC;CAEzB,MAAM,gBAAgB,cACd,oBAAoB,YAAY,QAAQ,GAC9C,CAAC,YAAY,QAAQ,CACvB;CA4DA,OAAO;EACL;EACA,oBA5DyB,cAAkC;GAC3D,MAAM,eACJ,aAAa,QAAQ,+BAA+B,IAAI;GAC1D,MAAM,kBACJ,aAAa,QAAQ,kCAAkC,IAAI;GAC7D,MAAM,QAAQ,eAAe,gBAAgB;GAE7C,MAAM,kBAAkB,8BAA8B,KAAK;GAE3D,IAAI,UAAU;IACZ,MAAM,eAAe,QAAQ;IAU7B,OAAO,0BACL,OACA,QAXmB,eAAe,KAAK,IACjB,8BACtB,gBAAgB,gBAChB,KAAK,IAAI,gBAAgB,cAAc,eAAe,GACtD,cACA,YACA,UAOA,GACA,OACA,aAAa,aAAa,WAC5B;GACF;GAEA,MAAM,sBAAsB,eAAe;GAC3C,MAAM,eAAe,gBAAgB,UAAU;GAC/C,MAAM,kBAAkB,SAAS,sBAAsB;GAGvD,IAAI,aAFmB,2BAA2B,KAEjC;GACjB,IAAI,cAAc,cAAc,IAAI;GAEpC,IAAI,cAAc,iBAAiB;IACjC,cAAc;IACd,aAAa,eAAe,IAAI;GAClC;GAIA,MAAM,QAFiB,aAAa,gBAAgB,cAErB;GAE/B,OAAO;IACL,IAAI,QAAQ,cAAc;IAC1B,GAAG;IACH,OAAO;IACP,QAAQ;IACR;IACA,kBAAkB,gBAAgB;IAClC,kBAAkB,gBAAgB;GACpC;EACF,GAAG;GAAC;GAAO;GAAQ;GAAiB;GAAU;GAAY;EAAa,CAIrE;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useTrainingState.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingState.ts"],"sourcesContent":["/**\n * useTrainingState Hook - Consolidated Training State Management\n *\n * Custom hook for managing training state using useReducer.\n * Mirrors useCombatState pattern for consistency.\n *\n * @korean 훈련상태관리훅 - useReducer를 사용한 통합 훈련 상태 관리\n */\n\nimport { useCallback, useMemo, useReducer } from \"react\";\nimport type { AnatomyLayer } from \"../components/AnatomyOverlay3D\";\nimport type { FootworkDrill } from \"../components/FootworkDrillsOverlayHtml\";\nimport type { TrainingMode } from \"../components/TrainingModeSelectorOverlayHtml\";\n\nexport type { AnatomyLayer, FootworkDrill, TrainingMode };\n\n/**\n * Training statistics\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Hit effect for training\n */\nexport interface TrainingHitEffect {\n readonly id: number;\n readonly position: [number, number, number];\n readonly type: \"success\" | \"perfect\" | \"miss\";\n readonly visible: boolean;\n readonly damage?: number;\n}\n\n/**\n * Training screen state managed by the reducer\n */\nexport interface TrainingScreenState {\n readonly trainingMode: TrainingMode;\n readonly isTraining: boolean;\n readonly selectedVitalPoint: string | null;\n readonly feedback: string;\n readonly showFeedback: boolean;\n readonly hitEffects: TrainingHitEffect[];\n readonly nextEffectId: number;\n readonly dummyHealth: number;\n readonly sessionStartTime: number | null;\n readonly sessionDuration: number;\n readonly perfectStrikes: number;\n readonly bestCombo: number;\n readonly stats: TrainingStats;\n readonly currentStanceIndex: number;\n readonly stanceWheelExpanded: boolean;\n readonly visibleAnatomyLayers: AnatomyLayer[];\n readonly footworkDrillActive: boolean;\n readonly footworkDrillType: FootworkDrill;\n readonly footworkDrillStep: number;\n}\n\n/**\n * Training state actions\n */\ntype TrainingAction =\n | { type: \"SET_TRAINING_MODE\"; payload: TrainingMode }\n | { type: \"START_TRAINING\" }\n | { type: \"STOP_TRAINING\" }\n | { type: \"SET_SELECTED_VITAL_POINT\"; payload: string | null }\n | { type: \"SET_FEEDBACK\"; payload: { feedback: string; show: boolean } }\n | { type: \"HIDE_FEEDBACK\" }\n | { type: \"ADD_HIT_EFFECT\"; payload: Omit<TrainingHitEffect, \"id\"> }\n | { type: \"REMOVE_HIT_EFFECT\"; payload: number }\n | { type: \"SET_DUMMY_HEALTH\"; payload: number }\n | { type: \"RESET_DUMMY\" }\n | { type: \"UPDATE_SESSION_DURATION\"; payload: number }\n | { type: \"INCREMENT_PERFECT_STRIKES\" }\n | { type: \"UPDATE_STATS\"; payload: Partial<TrainingStats> }\n | {\n type: \"REGISTER_HIT\";\n payload: { points: number; damage: number; isPerfect: boolean };\n }\n | { type: \"REGISTER_MISS\" }\n | { type: \"SET_STANCE_INDEX\"; payload: number }\n | { type: \"TOGGLE_STANCE_WHEEL\" }\n | { type: \"UPDATE_BEST_COMBO\"; payload: number }\n | { type: \"TOGGLE_ANATOMY_LAYER\"; payload: AnatomyLayer }\n | { type: \"SET_ANATOMY_LAYERS\"; payload: AnatomyLayer[] }\n | { type: \"START_FOOTWORK_DRILL\"; payload: FootworkDrill }\n | { type: \"STOP_FOOTWORK_DRILL\" }\n | { type: \"ADVANCE_FOOTWORK_STEP\" }\n | { type: \"RESET_FOOTWORK_DRILL\" };\n\n/**\n * Initial training state\n */\nconst initialState: TrainingScreenState = {\n trainingMode: \"basics\",\n isTraining: false,\n selectedVitalPoint: null,\n feedback: \"\",\n showFeedback: false,\n hitEffects: [],\n nextEffectId: 0,\n dummyHealth: 100,\n sessionStartTime: null,\n sessionDuration: 0,\n perfectStrikes: 0,\n bestCombo: 0,\n stats: {\n score: 0,\n combo: 0,\n hits: 0,\n misses: 0,\n accuracy: 0,\n },\n currentStanceIndex: 0,\n stanceWheelExpanded: false,\n visibleAnatomyLayers: [],\n footworkDrillActive: false,\n footworkDrillType: \"circular_left\",\n footworkDrillStep: 0,\n};\n\n/**\n * Training state reducer\n */\nfunction trainingReducer(\n state: TrainingScreenState,\n action: TrainingAction\n): TrainingScreenState {\n switch (action.type) {\n case \"SET_TRAINING_MODE\":\n return { ...state, trainingMode: action.payload };\n\n case \"START_TRAINING\":\n return {\n ...state,\n isTraining: true,\n sessionStartTime: Date.now(),\n dummyHealth: 100,\n perfectStrikes: 0,\n bestCombo: 0,\n stats: {\n score: 0,\n combo: 0,\n hits: 0,\n misses: 0,\n accuracy: 0,\n },\n feedback: \"훈련 시작! | Training Start!\",\n showFeedback: true,\n };\n\n case \"STOP_TRAINING\":\n return {\n ...state,\n isTraining: false,\n sessionStartTime: null,\n sessionDuration: 0,\n feedback: \"훈련 종료 | Training End\",\n showFeedback: true,\n };\n\n case \"SET_SELECTED_VITAL_POINT\":\n return { ...state, selectedVitalPoint: action.payload };\n\n case \"SET_FEEDBACK\":\n return {\n ...state,\n feedback: action.payload.feedback,\n showFeedback: action.payload.show,\n };\n\n case \"HIDE_FEEDBACK\":\n return { ...state, showFeedback: false };\n\n case \"ADD_HIT_EFFECT\":\n return {\n ...state,\n hitEffects: [\n ...state.hitEffects,\n { ...action.payload, id: state.nextEffectId },\n ],\n nextEffectId: state.nextEffectId + 1,\n };\n\n case \"REMOVE_HIT_EFFECT\":\n return {\n ...state,\n hitEffects: state.hitEffects.filter((e) => e.id !== action.payload),\n };\n\n case \"SET_DUMMY_HEALTH\":\n return { ...state, dummyHealth: action.payload };\n\n case \"RESET_DUMMY\":\n return {\n ...state,\n dummyHealth: 100,\n feedback: \"더미 재설정 | Dummy Reset\",\n showFeedback: true,\n };\n\n case \"UPDATE_SESSION_DURATION\":\n return { ...state, sessionDuration: action.payload };\n\n case \"INCREMENT_PERFECT_STRIKES\":\n return { ...state, perfectStrikes: state.perfectStrikes + 1 };\n\n case \"UPDATE_STATS\":\n return { ...state, stats: { ...state.stats, ...action.payload } };\n\n case \"REGISTER_HIT\": {\n const newHits = state.stats.hits + 1;\n const totalAttempts = newHits + state.stats.misses;\n const newCombo = state.stats.combo + 1;\n const newBestCombo = Math.max(state.bestCombo, newCombo);\n\n return {\n ...state,\n perfectStrikes: action.payload.isPerfect\n ? state.perfectStrikes + 1\n : state.perfectStrikes,\n bestCombo: newBestCombo,\n dummyHealth: Math.max(0, state.dummyHealth - action.payload.damage),\n stats: {\n ...state.stats,\n score: state.stats.score + action.payload.points,\n combo: newCombo,\n hits: newHits,\n accuracy: totalAttempts > 0 ? (newHits / totalAttempts) * 100 : 0,\n },\n };\n }\n\n case \"REGISTER_MISS\": {\n const newMisses = state.stats.misses + 1;\n const totalAttempts = state.stats.hits + newMisses;\n\n return {\n ...state,\n stats: {\n ...state.stats,\n combo: 0,\n misses: newMisses,\n accuracy:\n totalAttempts > 0 ? (state.stats.hits / totalAttempts) * 100 : 0,\n },\n };\n }\n\n case \"SET_STANCE_INDEX\":\n return { ...state, currentStanceIndex: action.payload };\n\n case \"TOGGLE_STANCE_WHEEL\":\n return { ...state, stanceWheelExpanded: !state.stanceWheelExpanded };\n\n case \"UPDATE_BEST_COMBO\":\n return {\n ...state,\n bestCombo: Math.max(state.bestCombo, action.payload),\n };\n\n case \"TOGGLE_ANATOMY_LAYER\": {\n const layer = action.payload;\n const currentLayers = state.visibleAnatomyLayers;\n const newLayers = currentLayers.includes(layer)\n ? currentLayers.filter((l) => l !== layer)\n : [...currentLayers, layer];\n return { ...state, visibleAnatomyLayers: newLayers };\n }\n\n case \"SET_ANATOMY_LAYERS\":\n return { ...state, visibleAnatomyLayers: action.payload };\n\n case \"START_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillActive: true,\n footworkDrillType: action.payload,\n footworkDrillStep: 0,\n feedback: \"보법 훈련 시작! | Footwork drill started!\",\n showFeedback: true,\n };\n\n case \"STOP_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillActive: false,\n footworkDrillStep: 0,\n feedback: \"보법 훈련 종료! | Footwork drill stopped!\",\n showFeedback: true,\n };\n\n case \"ADVANCE_FOOTWORK_STEP\":\n return {\n ...state,\n footworkDrillStep: state.footworkDrillStep + 1,\n };\n\n case \"RESET_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillStep: 0,\n };\n\n default:\n return state;\n }\n}\n\n/**\n * Training state actions interface\n */\nexport interface TrainingActions {\n readonly setTrainingMode: (mode: TrainingMode) => void;\n readonly startTraining: () => void;\n readonly stopTraining: () => void;\n readonly setSelectedVitalPoint: (point: string | null) => void;\n readonly setFeedback: (feedback: string, show?: boolean) => void;\n readonly hideFeedback: () => void;\n readonly addHitEffect: (effect: Omit<TrainingHitEffect, \"id\">) => void;\n readonly removeHitEffect: (id: number) => void;\n readonly setDummyHealth: (health: number) => void;\n readonly resetDummy: () => void;\n readonly updateSessionDuration: (duration: number) => void;\n readonly registerHit: (\n points: number,\n damage: number,\n isPerfect: boolean\n ) => void;\n readonly registerMiss: () => void;\n readonly setStanceIndex: (index: number) => void;\n readonly toggleStanceWheel: () => void;\n readonly updateBestCombo: (combo: number) => void;\n readonly toggleAnatomyLayer: (layer: AnatomyLayer) => void;\n readonly setAnatomyLayers: (layers: AnatomyLayer[]) => void;\n readonly startFootworkDrill: (drillType: FootworkDrill) => void;\n readonly stopFootworkDrill: () => void;\n readonly advanceFootworkStep: () => void;\n readonly resetFootworkDrill: () => void;\n}\n\n/**\n * Hook return type\n */\nexport interface UseTrainingStateReturn {\n readonly state: TrainingScreenState;\n readonly actions: TrainingActions;\n}\n\n/**\n * useTrainingState hook\n * Provides consolidated training state management using useReducer\n */\nexport function useTrainingState(): UseTrainingStateReturn {\n const [state, dispatch] = useReducer(trainingReducer, initialState);\n\n const setTrainingMode = useCallback(\n (mode: TrainingMode) =>\n dispatch({ type: \"SET_TRAINING_MODE\", payload: mode }),\n []\n );\n const startTraining = useCallback(\n () => dispatch({ type: \"START_TRAINING\" }),\n []\n );\n const stopTraining = useCallback(\n () => dispatch({ type: \"STOP_TRAINING\" }),\n []\n );\n const setSelectedVitalPoint = useCallback(\n (point: string | null) =>\n dispatch({ type: \"SET_SELECTED_VITAL_POINT\", payload: point }),\n []\n );\n const setFeedback = useCallback(\n (feedback: string, show = true) =>\n dispatch({ type: \"SET_FEEDBACK\", payload: { feedback, show } }),\n []\n );\n const hideFeedback = useCallback(\n () => dispatch({ type: \"HIDE_FEEDBACK\" }),\n []\n );\n const addHitEffect = useCallback(\n (effect: Omit<TrainingHitEffect, \"id\">) =>\n dispatch({ type: \"ADD_HIT_EFFECT\", payload: effect }),\n []\n );\n const removeHitEffect = useCallback(\n (id: number) => dispatch({ type: \"REMOVE_HIT_EFFECT\", payload: id }),\n []\n );\n const setDummyHealth = useCallback(\n (health: number) => dispatch({ type: \"SET_DUMMY_HEALTH\", payload: health }),\n []\n );\n const resetDummy = useCallback(() => dispatch({ type: \"RESET_DUMMY\" }), []);\n const updateSessionDuration = useCallback(\n (duration: number) =>\n dispatch({ type: \"UPDATE_SESSION_DURATION\", payload: duration }),\n []\n );\n const registerHit = useCallback(\n (points: number, damage: number, isPerfect: boolean) =>\n dispatch({\n type: \"REGISTER_HIT\",\n payload: { points, damage, isPerfect },\n }),\n []\n );\n const registerMiss = useCallback(\n () => dispatch({ type: \"REGISTER_MISS\" }),\n []\n );\n const setStanceIndex = useCallback(\n (index: number) => dispatch({ type: \"SET_STANCE_INDEX\", payload: index }),\n []\n );\n const toggleStanceWheel = useCallback(\n () => dispatch({ type: \"TOGGLE_STANCE_WHEEL\" }),\n []\n );\n const updateBestCombo = useCallback(\n (combo: number) => dispatch({ type: \"UPDATE_BEST_COMBO\", payload: combo }),\n []\n );\n const toggleAnatomyLayer = useCallback(\n (layer: AnatomyLayer) =>\n dispatch({ type: \"TOGGLE_ANATOMY_LAYER\", payload: layer }),\n []\n );\n const setAnatomyLayers = useCallback(\n (layers: AnatomyLayer[]) =>\n dispatch({ type: \"SET_ANATOMY_LAYERS\", payload: layers }),\n []\n );\n const startFootworkDrill = useCallback((drillType: FootworkDrill) => {\n dispatch({ type: \"START_FOOTWORK_DRILL\", payload: drillType });\n }, []);\n const stopFootworkDrill = useCallback(() => {\n dispatch({ type: \"STOP_FOOTWORK_DRILL\" });\n }, []);\n const advanceFootworkStep = useCallback(() => {\n dispatch({ type: \"ADVANCE_FOOTWORK_STEP\" });\n }, []);\n const resetFootworkDrill = useCallback(() => {\n dispatch({ type: \"RESET_FOOTWORK_DRILL\" });\n }, []);\n\n const actions: TrainingActions = useMemo(\n () => ({\n setTrainingMode,\n startTraining,\n stopTraining,\n setSelectedVitalPoint,\n setFeedback,\n hideFeedback,\n addHitEffect,\n removeHitEffect,\n setDummyHealth,\n resetDummy,\n updateSessionDuration,\n registerHit,\n registerMiss,\n setStanceIndex,\n toggleStanceWheel,\n updateBestCombo,\n toggleAnatomyLayer,\n setAnatomyLayers,\n startFootworkDrill,\n stopFootworkDrill,\n advanceFootworkStep,\n resetFootworkDrill,\n }),\n [\n setTrainingMode,\n startTraining,\n stopTraining,\n setSelectedVitalPoint,\n setFeedback,\n hideFeedback,\n addHitEffect,\n removeHitEffect,\n setDummyHealth,\n resetDummy,\n updateSessionDuration,\n registerHit,\n registerMiss,\n setStanceIndex,\n toggleStanceWheel,\n updateBestCombo,\n toggleAnatomyLayer,\n setAnatomyLayers,\n startFootworkDrill,\n stopFootworkDrill,\n advanceFootworkStep,\n resetFootworkDrill,\n ]\n );\n\n return { state, actions };\n}\n\nexport default useTrainingState;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useTrainingState.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingState.ts"],"sourcesContent":["/**\n * useTrainingState Hook - Consolidated Training State Management\n *\n * Custom hook for managing training state using useReducer.\n * Mirrors useCombatState pattern for consistency.\n *\n * @korean 훈련상태관리훅 - useReducer를 사용한 통합 훈련 상태 관리\n */\n\nimport { useCallback, useMemo, useReducer } from \"react\";\nimport type { AnatomyLayer } from \"../components/AnatomyOverlay3D\";\nimport type { FootworkDrill } from \"../components/FootworkDrillsOverlayHtml\";\nimport type { TrainingMode } from \"../components/TrainingModeSelectorOverlayHtml\";\n\nexport type { AnatomyLayer, FootworkDrill, TrainingMode };\n\n/**\n * Training statistics\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Hit effect for training\n */\nexport interface TrainingHitEffect {\n readonly id: number;\n readonly position: [number, number, number];\n readonly type: \"success\" | \"perfect\" | \"miss\";\n readonly visible: boolean;\n readonly damage?: number;\n}\n\n/**\n * Training screen state managed by the reducer\n */\nexport interface TrainingScreenState {\n readonly trainingMode: TrainingMode;\n readonly isTraining: boolean;\n readonly selectedVitalPoint: string | null;\n readonly feedback: string;\n readonly showFeedback: boolean;\n readonly hitEffects: TrainingHitEffect[];\n readonly nextEffectId: number;\n readonly dummyHealth: number;\n readonly sessionStartTime: number | null;\n readonly sessionDuration: number;\n readonly perfectStrikes: number;\n readonly bestCombo: number;\n readonly stats: TrainingStats;\n readonly currentStanceIndex: number;\n readonly stanceWheelExpanded: boolean;\n readonly visibleAnatomyLayers: AnatomyLayer[];\n readonly footworkDrillActive: boolean;\n readonly footworkDrillType: FootworkDrill;\n readonly footworkDrillStep: number;\n}\n\n/**\n * Training state actions\n */\ntype TrainingAction =\n | { type: \"SET_TRAINING_MODE\"; payload: TrainingMode }\n | { type: \"START_TRAINING\" }\n | { type: \"STOP_TRAINING\" }\n | { type: \"SET_SELECTED_VITAL_POINT\"; payload: string | null }\n | { type: \"SET_FEEDBACK\"; payload: { feedback: string; show: boolean } }\n | { type: \"HIDE_FEEDBACK\" }\n | { type: \"ADD_HIT_EFFECT\"; payload: Omit<TrainingHitEffect, \"id\"> }\n | { type: \"REMOVE_HIT_EFFECT\"; payload: number }\n | { type: \"SET_DUMMY_HEALTH\"; payload: number }\n | { type: \"RESET_DUMMY\" }\n | { type: \"UPDATE_SESSION_DURATION\"; payload: number }\n | { type: \"INCREMENT_PERFECT_STRIKES\" }\n | { type: \"UPDATE_STATS\"; payload: Partial<TrainingStats> }\n | {\n type: \"REGISTER_HIT\";\n payload: { points: number; damage: number; isPerfect: boolean };\n }\n | { type: \"REGISTER_MISS\" }\n | { type: \"SET_STANCE_INDEX\"; payload: number }\n | { type: \"TOGGLE_STANCE_WHEEL\" }\n | { type: \"UPDATE_BEST_COMBO\"; payload: number }\n | { type: \"TOGGLE_ANATOMY_LAYER\"; payload: AnatomyLayer }\n | { type: \"SET_ANATOMY_LAYERS\"; payload: AnatomyLayer[] }\n | { type: \"START_FOOTWORK_DRILL\"; payload: FootworkDrill }\n | { type: \"STOP_FOOTWORK_DRILL\" }\n | { type: \"ADVANCE_FOOTWORK_STEP\" }\n | { type: \"RESET_FOOTWORK_DRILL\" };\n\n/**\n * Initial training state\n */\nconst initialState: TrainingScreenState = {\n trainingMode: \"basics\",\n isTraining: false,\n selectedVitalPoint: null,\n feedback: \"\",\n showFeedback: false,\n hitEffects: [],\n nextEffectId: 0,\n dummyHealth: 100,\n sessionStartTime: null,\n sessionDuration: 0,\n perfectStrikes: 0,\n bestCombo: 0,\n stats: {\n score: 0,\n combo: 0,\n hits: 0,\n misses: 0,\n accuracy: 0,\n },\n currentStanceIndex: 0,\n stanceWheelExpanded: false,\n visibleAnatomyLayers: [],\n footworkDrillActive: false,\n footworkDrillType: \"circular_left\",\n footworkDrillStep: 0,\n};\n\n/**\n * Training state reducer\n */\nfunction trainingReducer(\n state: TrainingScreenState,\n action: TrainingAction\n): TrainingScreenState {\n switch (action.type) {\n case \"SET_TRAINING_MODE\":\n return { ...state, trainingMode: action.payload };\n\n case \"START_TRAINING\":\n return {\n ...state,\n isTraining: true,\n sessionStartTime: Date.now(),\n dummyHealth: 100,\n perfectStrikes: 0,\n bestCombo: 0,\n stats: {\n score: 0,\n combo: 0,\n hits: 0,\n misses: 0,\n accuracy: 0,\n },\n feedback: \"훈련 시작! | Training Start!\",\n showFeedback: true,\n };\n\n case \"STOP_TRAINING\":\n return {\n ...state,\n isTraining: false,\n sessionStartTime: null,\n sessionDuration: 0,\n feedback: \"훈련 종료 | Training End\",\n showFeedback: true,\n };\n\n case \"SET_SELECTED_VITAL_POINT\":\n return { ...state, selectedVitalPoint: action.payload };\n\n case \"SET_FEEDBACK\":\n return {\n ...state,\n feedback: action.payload.feedback,\n showFeedback: action.payload.show,\n };\n\n case \"HIDE_FEEDBACK\":\n return { ...state, showFeedback: false };\n\n case \"ADD_HIT_EFFECT\":\n return {\n ...state,\n hitEffects: [\n ...state.hitEffects,\n { ...action.payload, id: state.nextEffectId },\n ],\n nextEffectId: state.nextEffectId + 1,\n };\n\n case \"REMOVE_HIT_EFFECT\":\n return {\n ...state,\n hitEffects: state.hitEffects.filter((e) => e.id !== action.payload),\n };\n\n case \"SET_DUMMY_HEALTH\":\n return { ...state, dummyHealth: action.payload };\n\n case \"RESET_DUMMY\":\n return {\n ...state,\n dummyHealth: 100,\n feedback: \"더미 재설정 | Dummy Reset\",\n showFeedback: true,\n };\n\n case \"UPDATE_SESSION_DURATION\":\n return { ...state, sessionDuration: action.payload };\n\n case \"INCREMENT_PERFECT_STRIKES\":\n return { ...state, perfectStrikes: state.perfectStrikes + 1 };\n\n case \"UPDATE_STATS\":\n return { ...state, stats: { ...state.stats, ...action.payload } };\n\n case \"REGISTER_HIT\": {\n const newHits = state.stats.hits + 1;\n const totalAttempts = newHits + state.stats.misses;\n const newCombo = state.stats.combo + 1;\n const newBestCombo = Math.max(state.bestCombo, newCombo);\n\n return {\n ...state,\n perfectStrikes: action.payload.isPerfect\n ? state.perfectStrikes + 1\n : state.perfectStrikes,\n bestCombo: newBestCombo,\n dummyHealth: Math.max(0, state.dummyHealth - action.payload.damage),\n stats: {\n ...state.stats,\n score: state.stats.score + action.payload.points,\n combo: newCombo,\n hits: newHits,\n accuracy: totalAttempts > 0 ? (newHits / totalAttempts) * 100 : 0,\n },\n };\n }\n\n case \"REGISTER_MISS\": {\n const newMisses = state.stats.misses + 1;\n const totalAttempts = state.stats.hits + newMisses;\n\n return {\n ...state,\n stats: {\n ...state.stats,\n combo: 0,\n misses: newMisses,\n accuracy:\n totalAttempts > 0 ? (state.stats.hits / totalAttempts) * 100 : 0,\n },\n };\n }\n\n case \"SET_STANCE_INDEX\":\n return { ...state, currentStanceIndex: action.payload };\n\n case \"TOGGLE_STANCE_WHEEL\":\n return { ...state, stanceWheelExpanded: !state.stanceWheelExpanded };\n\n case \"UPDATE_BEST_COMBO\":\n return {\n ...state,\n bestCombo: Math.max(state.bestCombo, action.payload),\n };\n\n case \"TOGGLE_ANATOMY_LAYER\": {\n const layer = action.payload;\n const currentLayers = state.visibleAnatomyLayers;\n const newLayers = currentLayers.includes(layer)\n ? currentLayers.filter((l) => l !== layer)\n : [...currentLayers, layer];\n return { ...state, visibleAnatomyLayers: newLayers };\n }\n\n case \"SET_ANATOMY_LAYERS\":\n return { ...state, visibleAnatomyLayers: action.payload };\n\n case \"START_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillActive: true,\n footworkDrillType: action.payload,\n footworkDrillStep: 0,\n feedback: \"보법 훈련 시작! | Footwork drill started!\",\n showFeedback: true,\n };\n\n case \"STOP_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillActive: false,\n footworkDrillStep: 0,\n feedback: \"보법 훈련 종료! | Footwork drill stopped!\",\n showFeedback: true,\n };\n\n case \"ADVANCE_FOOTWORK_STEP\":\n return {\n ...state,\n footworkDrillStep: state.footworkDrillStep + 1,\n };\n\n case \"RESET_FOOTWORK_DRILL\":\n return {\n ...state,\n footworkDrillStep: 0,\n };\n\n default:\n return state;\n }\n}\n\n/**\n * Training state actions interface\n */\nexport interface TrainingActions {\n readonly setTrainingMode: (mode: TrainingMode) => void;\n readonly startTraining: () => void;\n readonly stopTraining: () => void;\n readonly setSelectedVitalPoint: (point: string | null) => void;\n readonly setFeedback: (feedback: string, show?: boolean) => void;\n readonly hideFeedback: () => void;\n readonly addHitEffect: (effect: Omit<TrainingHitEffect, \"id\">) => void;\n readonly removeHitEffect: (id: number) => void;\n readonly setDummyHealth: (health: number) => void;\n readonly resetDummy: () => void;\n readonly updateSessionDuration: (duration: number) => void;\n readonly registerHit: (\n points: number,\n damage: number,\n isPerfect: boolean\n ) => void;\n readonly registerMiss: () => void;\n readonly setStanceIndex: (index: number) => void;\n readonly toggleStanceWheel: () => void;\n readonly updateBestCombo: (combo: number) => void;\n readonly toggleAnatomyLayer: (layer: AnatomyLayer) => void;\n readonly setAnatomyLayers: (layers: AnatomyLayer[]) => void;\n readonly startFootworkDrill: (drillType: FootworkDrill) => void;\n readonly stopFootworkDrill: () => void;\n readonly advanceFootworkStep: () => void;\n readonly resetFootworkDrill: () => void;\n}\n\n/**\n * Hook return type\n */\nexport interface UseTrainingStateReturn {\n readonly state: TrainingScreenState;\n readonly actions: TrainingActions;\n}\n\n/**\n * useTrainingState hook\n * Provides consolidated training state management using useReducer\n */\nexport function useTrainingState(): UseTrainingStateReturn {\n const [state, dispatch] = useReducer(trainingReducer, initialState);\n\n const setTrainingMode = useCallback(\n (mode: TrainingMode) =>\n dispatch({ type: \"SET_TRAINING_MODE\", payload: mode }),\n []\n );\n const startTraining = useCallback(\n () => dispatch({ type: \"START_TRAINING\" }),\n []\n );\n const stopTraining = useCallback(\n () => dispatch({ type: \"STOP_TRAINING\" }),\n []\n );\n const setSelectedVitalPoint = useCallback(\n (point: string | null) =>\n dispatch({ type: \"SET_SELECTED_VITAL_POINT\", payload: point }),\n []\n );\n const setFeedback = useCallback(\n (feedback: string, show = true) =>\n dispatch({ type: \"SET_FEEDBACK\", payload: { feedback, show } }),\n []\n );\n const hideFeedback = useCallback(\n () => dispatch({ type: \"HIDE_FEEDBACK\" }),\n []\n );\n const addHitEffect = useCallback(\n (effect: Omit<TrainingHitEffect, \"id\">) =>\n dispatch({ type: \"ADD_HIT_EFFECT\", payload: effect }),\n []\n );\n const removeHitEffect = useCallback(\n (id: number) => dispatch({ type: \"REMOVE_HIT_EFFECT\", payload: id }),\n []\n );\n const setDummyHealth = useCallback(\n (health: number) => dispatch({ type: \"SET_DUMMY_HEALTH\", payload: health }),\n []\n );\n const resetDummy = useCallback(() => dispatch({ type: \"RESET_DUMMY\" }), []);\n const updateSessionDuration = useCallback(\n (duration: number) =>\n dispatch({ type: \"UPDATE_SESSION_DURATION\", payload: duration }),\n []\n );\n const registerHit = useCallback(\n (points: number, damage: number, isPerfect: boolean) =>\n dispatch({\n type: \"REGISTER_HIT\",\n payload: { points, damage, isPerfect },\n }),\n []\n );\n const registerMiss = useCallback(\n () => dispatch({ type: \"REGISTER_MISS\" }),\n []\n );\n const setStanceIndex = useCallback(\n (index: number) => dispatch({ type: \"SET_STANCE_INDEX\", payload: index }),\n []\n );\n const toggleStanceWheel = useCallback(\n () => dispatch({ type: \"TOGGLE_STANCE_WHEEL\" }),\n []\n );\n const updateBestCombo = useCallback(\n (combo: number) => dispatch({ type: \"UPDATE_BEST_COMBO\", payload: combo }),\n []\n );\n const toggleAnatomyLayer = useCallback(\n (layer: AnatomyLayer) =>\n dispatch({ type: \"TOGGLE_ANATOMY_LAYER\", payload: layer }),\n []\n );\n const setAnatomyLayers = useCallback(\n (layers: AnatomyLayer[]) =>\n dispatch({ type: \"SET_ANATOMY_LAYERS\", payload: layers }),\n []\n );\n const startFootworkDrill = useCallback((drillType: FootworkDrill) => {\n dispatch({ type: \"START_FOOTWORK_DRILL\", payload: drillType });\n }, []);\n const stopFootworkDrill = useCallback(() => {\n dispatch({ type: \"STOP_FOOTWORK_DRILL\" });\n }, []);\n const advanceFootworkStep = useCallback(() => {\n dispatch({ type: \"ADVANCE_FOOTWORK_STEP\" });\n }, []);\n const resetFootworkDrill = useCallback(() => {\n dispatch({ type: \"RESET_FOOTWORK_DRILL\" });\n }, []);\n\n const actions: TrainingActions = useMemo(\n () => ({\n setTrainingMode,\n startTraining,\n stopTraining,\n setSelectedVitalPoint,\n setFeedback,\n hideFeedback,\n addHitEffect,\n removeHitEffect,\n setDummyHealth,\n resetDummy,\n updateSessionDuration,\n registerHit,\n registerMiss,\n setStanceIndex,\n toggleStanceWheel,\n updateBestCombo,\n toggleAnatomyLayer,\n setAnatomyLayers,\n startFootworkDrill,\n stopFootworkDrill,\n advanceFootworkStep,\n resetFootworkDrill,\n }),\n [\n setTrainingMode,\n startTraining,\n stopTraining,\n setSelectedVitalPoint,\n setFeedback,\n hideFeedback,\n addHitEffect,\n removeHitEffect,\n setDummyHealth,\n resetDummy,\n updateSessionDuration,\n registerHit,\n registerMiss,\n setStanceIndex,\n toggleStanceWheel,\n updateBestCombo,\n toggleAnatomyLayer,\n setAnatomyLayers,\n startFootworkDrill,\n stopFootworkDrill,\n advanceFootworkStep,\n resetFootworkDrill,\n ]\n );\n\n return { state, actions };\n}\n\nexport default useTrainingState;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseButton.js","names":[],"sources":["../../../../src/components/shared/base/BaseButton.tsx"],"sourcesContent":["/**\n * BaseButton - Enhanced button component with Korean theming\n *\n * Builds on existing KoreanButton with extracted common logic\n * Provides consistent button styling across the application\n *\n * Now with Html overlay positioning helpers for:\n * - Consistent z-index management\n * - Performance optimization with distanceFactor\n * - GPU acceleration\n *\n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, {\n useCallback,\n useMemo,\n useState,\n useEffect,\n useRef,\n} from \"react\";\nimport { KOREAN_COLORS, UI_DIMENSIONS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../utils/htmlOverlayHelpers\";\nimport type { HtmlOverlayLayer } from \"../../../types/HtmlOverlayTypes\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BaseButton component\n */\nexport interface BaseButtonProps {\n readonly korean: string;\n readonly english: string;\n readonly onClick: () => void;\n readonly disabled?: boolean;\n readonly variant?: \"primary\" | \"secondary\" | \"danger\";\n readonly size?: \"sm\" | \"md\" | \"lg\";\n readonly position?: [number, number, number];\n readonly fullWidth?: boolean;\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** Html overlay layer for z-index (default: 'hud') */\n readonly layer?: HtmlOverlayLayer;\n /** Whether button should occlude behind 3D objects (default: false) */\n readonly occlude?: boolean;\n /** ARIA label for accessibility (optional, defaults to korean text) */\n readonly ariaLabel?: string;\n /** ARIA described by ID for additional context */\n readonly ariaDescribedBy?: string;\n /** Auto-focus on mount (default: false) */\n readonly autoFocus?: boolean;\n}\n\n/**\n * BaseButton Component\n *\n * Enhanced Korean-themed button with common functionality extracted.\n * Uses useKoreanTheme hook for consistent styling.\n * Now includes Html overlay positioning helpers for proper z-index and performance.\n *\n * WCAG 2.1 AA Accessibility Features:\n * - Proper ARIA labels and semantic HTML\n * - Keyboard navigation support (Enter and Space keys)\n * - Visible focus indicators with high contrast\n * - Disabled state properly communicated\n * - Minimum 44x44px touch targets on mobile\n *\n * Optimized with React.memo for performance\n *\n * @example\n * ```tsx\n * <BaseButton\n * korean=\"공격\"\n * english=\"Attack\"\n * onClick={() => console.log(\"Attack\")}\n * variant=\"primary\"\n * size=\"md\"\n * layer=\"hud\"\n * ariaLabel=\"Attack button\"\n * />\n * ```\n */\nconst BaseButtonComponent: React.FC<BaseButtonProps> = ({\n korean,\n english,\n onClick,\n disabled = false,\n variant = \"primary\",\n size = \"md\",\n position = [0, 0, 0],\n fullWidth = false,\n testId,\n isMobile = false,\n layer = \"hud\",\n occlude = false,\n ariaLabel,\n ariaDescribedBy,\n autoFocus = false,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n const [isPressed, setIsPressed] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n const { buttonVariant, buttonSize, fontFamily, accessibility } =\n useKoreanTheme({\n variant,\n size,\n disabled,\n isMobile,\n });\n\n useEffect(() => {\n if (autoFocus && buttonRef.current) {\n buttonRef.current.focus();\n }\n }, [autoFocus]);\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const distanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"button\", isMobile);\n }, [screenWidth, isMobile]);\n\n const overlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(layer, true, distanceFactor, true, occlude);\n }, [layer, distanceFactor, occlude]);\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n onClick();\n }\n }, [onClick, disabled]);\n\n const handleMouseEnter = useCallback(() => {\n if (!disabled) {\n setIsHovered(true);\n }\n }, [disabled]);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n setIsPressed(false);\n }, []);\n\n const handleMouseDown = useCallback(() => {\n if (!disabled) {\n setIsPressed(true);\n }\n }, [disabled]);\n\n const handleMouseUp = useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n }, []);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if ((e.key === \"Enter\" || e.key === \" \") && !disabled) {\n setIsPressed(true);\n }\n },\n [disabled],\n );\n\n const handleKeyUp = useCallback(\n (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if ((e.key === \"Enter\" || e.key === \" \") && !disabled) {\n setIsPressed(false);\n }\n },\n [disabled],\n );\n\n const buttonStyle = useMemo<React.CSSProperties>(() => {\n let background = hexToRgbaString(buttonVariant.background, 0.9);\n\n if (isPressed) {\n background = buttonVariant.activeBg;\n } else if (isHovered) {\n background = buttonVariant.hoverBg;\n }\n\n return {\n background,\n border: `${buttonSize.borderWidth} solid ${hexToRgbaString(buttonVariant.border)}`,\n color: hexToRgbaString(buttonVariant.text),\n padding: buttonSize.padding,\n fontSize: buttonSize.fontSize,\n fontFamily: fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n width: fullWidth ? \"100%\" : \"auto\",\n minWidth: isMobile ? accessibility.minTouchTarget : \"auto\",\n minHeight: isMobile ? accessibility.minTouchTarget : \"auto\",\n boxShadow:\n isHovered && !disabled\n ? `0 0 10px ${hexToRgbaString(buttonVariant.border, 0.5)}`\n : \"none\",\n transform: isPressed && !disabled ? \"scale(0.98)\" : \"scale(1)\",\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n WebkitTransform: overlayStyle.transform,\n zIndex: overlayStyle.zIndex,\n outline: isFocused && !disabled ? accessibility.focusOutline : \"none\",\n outlineOffset:\n isFocused && !disabled ? accessibility.focusOutlineOffset : \"0\",\n };\n }, [\n buttonVariant,\n buttonSize,\n fontFamily,\n disabled,\n fullWidth,\n isHovered,\n isPressed,\n isFocused,\n overlayStyle,\n isMobile,\n accessibility,\n ]);\n\n return (\n <Html\n position={position}\n center={overlayStyle.center}\n distanceFactor={overlayStyle.distanceFactor}\n occlude={overlayStyle.occlude}\n style={{ pointerEvents: overlayStyle.pointerEvents }}\n >\n <button\n ref={buttonRef}\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n disabled={disabled}\n type=\"button\"\n aria-label={ariaLabel ?? `${korean} ${english}`}\n aria-describedby={ariaDescribedBy}\n aria-disabled={disabled}\n style={buttonStyle}\n data-testid={testId ?? \"base-button\"}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n >\n <span lang=\"ko\" style={{ fontSize: \"1em\" }}>\n {korean}\n </span>\n <span\n lang=\"en\"\n style={{\n fontSize: \"0.75em\",\n opacity: 0.8,\n fontStyle: \"italic\",\n }}\n >\n {english}\n </span>\n </div>\n </button>\n </Html>\n );\n};\n\nexport const BaseButton = React.memo(BaseButtonComponent);\n\nBaseButton.displayName = \"BaseButton\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"BaseButton.js","names":[],"sources":["../../../../src/components/shared/base/BaseButton.tsx"],"sourcesContent":["/**\n * BaseButton - Enhanced button component with Korean theming\n *\n * Builds on existing KoreanButton with extracted common logic\n * Provides consistent button styling across the application\n *\n * Now with Html overlay positioning helpers for:\n * - Consistent z-index management\n * - Performance optimization with distanceFactor\n * - GPU acceleration\n *\n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, {\n useCallback,\n useMemo,\n useState,\n useEffect,\n useRef,\n} from \"react\";\nimport { KOREAN_COLORS, UI_DIMENSIONS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../utils/htmlOverlayHelpers\";\nimport type { HtmlOverlayLayer } from \"../../../types/HtmlOverlayTypes\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BaseButton component\n */\nexport interface BaseButtonProps {\n readonly korean: string;\n readonly english: string;\n readonly onClick: () => void;\n readonly disabled?: boolean;\n readonly variant?: \"primary\" | \"secondary\" | \"danger\";\n readonly size?: \"sm\" | \"md\" | \"lg\";\n readonly position?: [number, number, number];\n readonly fullWidth?: boolean;\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** Html overlay layer for z-index (default: 'hud') */\n readonly layer?: HtmlOverlayLayer;\n /** Whether button should occlude behind 3D objects (default: false) */\n readonly occlude?: boolean;\n /** ARIA label for accessibility (optional, defaults to korean text) */\n readonly ariaLabel?: string;\n /** ARIA described by ID for additional context */\n readonly ariaDescribedBy?: string;\n /** Auto-focus on mount (default: false) */\n readonly autoFocus?: boolean;\n}\n\n/**\n * BaseButton Component\n *\n * Enhanced Korean-themed button with common functionality extracted.\n * Uses useKoreanTheme hook for consistent styling.\n * Now includes Html overlay positioning helpers for proper z-index and performance.\n *\n * WCAG 2.1 AA Accessibility Features:\n * - Proper ARIA labels and semantic HTML\n * - Keyboard navigation support (Enter and Space keys)\n * - Visible focus indicators with high contrast\n * - Disabled state properly communicated\n * - Minimum 44x44px touch targets on mobile\n *\n * Optimized with React.memo for performance\n *\n * @example\n * ```tsx\n * <BaseButton\n * korean=\"공격\"\n * english=\"Attack\"\n * onClick={() => console.log(\"Attack\")}\n * variant=\"primary\"\n * size=\"md\"\n * layer=\"hud\"\n * ariaLabel=\"Attack button\"\n * />\n * ```\n */\nconst BaseButtonComponent: React.FC<BaseButtonProps> = ({\n korean,\n english,\n onClick,\n disabled = false,\n variant = \"primary\",\n size = \"md\",\n position = [0, 0, 0],\n fullWidth = false,\n testId,\n isMobile = false,\n layer = \"hud\",\n occlude = false,\n ariaLabel,\n ariaDescribedBy,\n autoFocus = false,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n const [isPressed, setIsPressed] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n const { buttonVariant, buttonSize, fontFamily, accessibility } =\n useKoreanTheme({\n variant,\n size,\n disabled,\n isMobile,\n });\n\n useEffect(() => {\n if (autoFocus && buttonRef.current) {\n buttonRef.current.focus();\n }\n }, [autoFocus]);\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const distanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"button\", isMobile);\n }, [screenWidth, isMobile]);\n\n const overlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(layer, true, distanceFactor, true, occlude);\n }, [layer, distanceFactor, occlude]);\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n onClick();\n }\n }, [onClick, disabled]);\n\n const handleMouseEnter = useCallback(() => {\n if (!disabled) {\n setIsHovered(true);\n }\n }, [disabled]);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n setIsPressed(false);\n }, []);\n\n const handleMouseDown = useCallback(() => {\n if (!disabled) {\n setIsPressed(true);\n }\n }, [disabled]);\n\n const handleMouseUp = useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n }, []);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if ((e.key === \"Enter\" || e.key === \" \") && !disabled) {\n setIsPressed(true);\n }\n },\n [disabled],\n );\n\n const handleKeyUp = useCallback(\n (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if ((e.key === \"Enter\" || e.key === \" \") && !disabled) {\n setIsPressed(false);\n }\n },\n [disabled],\n );\n\n const buttonStyle = useMemo<React.CSSProperties>(() => {\n let background = hexToRgbaString(buttonVariant.background, 0.9);\n\n if (isPressed) {\n background = buttonVariant.activeBg;\n } else if (isHovered) {\n background = buttonVariant.hoverBg;\n }\n\n return {\n background,\n border: `${buttonSize.borderWidth} solid ${hexToRgbaString(buttonVariant.border)}`,\n color: hexToRgbaString(buttonVariant.text),\n padding: buttonSize.padding,\n fontSize: buttonSize.fontSize,\n fontFamily: fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n width: fullWidth ? \"100%\" : \"auto\",\n minWidth: isMobile ? accessibility.minTouchTarget : \"auto\",\n minHeight: isMobile ? accessibility.minTouchTarget : \"auto\",\n boxShadow:\n isHovered && !disabled\n ? `0 0 10px ${hexToRgbaString(buttonVariant.border, 0.5)}`\n : \"none\",\n transform: isPressed && !disabled ? \"scale(0.98)\" : \"scale(1)\",\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n WebkitTransform: overlayStyle.transform,\n zIndex: overlayStyle.zIndex,\n outline: isFocused && !disabled ? accessibility.focusOutline : \"none\",\n outlineOffset:\n isFocused && !disabled ? accessibility.focusOutlineOffset : \"0\",\n };\n }, [\n buttonVariant,\n buttonSize,\n fontFamily,\n disabled,\n fullWidth,\n isHovered,\n isPressed,\n isFocused,\n overlayStyle,\n isMobile,\n accessibility,\n ]);\n\n return (\n <Html\n position={position}\n center={overlayStyle.center}\n distanceFactor={overlayStyle.distanceFactor}\n occlude={overlayStyle.occlude}\n style={{ pointerEvents: overlayStyle.pointerEvents }}\n >\n <button\n ref={buttonRef}\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n disabled={disabled}\n type=\"button\"\n aria-label={ariaLabel ?? `${korean} ${english}`}\n aria-describedby={ariaDescribedBy}\n aria-disabled={disabled}\n style={buttonStyle}\n data-testid={testId ?? \"base-button\"}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n >\n <span lang=\"ko\" style={{ fontSize: \"1em\" }}>\n {korean}\n </span>\n <span\n lang=\"en\"\n style={{\n fontSize: \"0.75em\",\n opacity: 0.8,\n fontStyle: \"italic\",\n }}\n >\n {english}\n </span>\n </div>\n </button>\n </Html>\n );\n};\n\nexport const BaseButton = React.memo(BaseButtonComponent);\n\nBaseButton.displayName = \"BaseButton\";\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseButtonOverlayHtml.js","names":[],"sources":["../../../../src/components/shared/base/BaseButtonOverlayHtml.tsx"],"sourcesContent":["/**\n * BaseButtonOverlayHtml - HTML button component with Korean theming (non-Three.js)\n * \n * A version of BaseButton that doesn't require Three.js/Canvas context\n * Can be used in regular DOM components\n * \n * @module components/base\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { getKoreanButtonWithGlow } from \"../../../utils/koreanThemeHelpers\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\nimport { UIHaptics } from \"../../../utils/hapticFeedback\";\n\n/**\n * Props for BaseButtonOverlayHtml component\n */\nexport interface BaseButtonOverlayHtmlProps {\n readonly korean: string;\n readonly english: string;\n readonly onClick: () => void;\n readonly onMouseEnter?: () => void;\n readonly disabled?: boolean;\n readonly variant?: \"primary\" | \"secondary\" | \"danger\";\n readonly size?: \"sm\" | \"md\" | \"lg\";\n readonly fullWidth?: boolean;\n readonly testId?: string;\n readonly isMobile?: boolean;\n readonly className?: string;\n readonly style?: React.CSSProperties;\n readonly autoFocus?: boolean;\n readonly ariaLabel?: string;\n readonly ariaCurrent?: React.AriaAttributes[\"aria-current\"];\n}\n\n/**\n * BaseButtonOverlayHtml Component\n * \n * HTML button with Korean theming (no Three.js dependency).\n * Uses useKoreanTheme hook for consistent styling.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when props haven't changed\n * - Event handlers already use useCallback internally\n * \n * @example\n * ```tsx\n * <BaseButtonOverlayHtml\n * korean=\"확인\"\n * english=\"Confirm\"\n * onClick={() => handleConfirm()}\n * variant=\"primary\"\n * size=\"md\"\n * />\n * ```\n */\nexport const BaseButtonOverlayHtml = React.memo<BaseButtonOverlayHtmlProps>(\n ({\n korean,\n english,\n onClick,\n onMouseEnter,\n disabled = false,\n variant = \"primary\",\n size = \"md\",\n fullWidth = false,\n testId,\n isMobile = false,\n className,\n style: customStyle,\n autoFocus = false,\n ariaLabel,\n ariaCurrent,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n const [isPressed, setIsPressed] = useState(false);\n\n const { buttonSize } = useKoreanTheme({\n variant,\n size,\n disabled,\n isMobile,\n });\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n UIHaptics.buttonTap(); // Add haptic feedback\n onClick();\n }\n }, [onClick, disabled]);\n\n const handleMouseEnterInternal = useCallback(() => {\n if (!disabled) {\n UIHaptics.menuHover(); // Add haptic feedback\n setIsHovered(true);\n onMouseEnter?.();\n }\n }, [disabled, onMouseEnter]);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n setIsPressed(false);\n }, []);\n\n const handleMouseDown = useCallback(() => {\n if (!disabled) {\n setIsPressed(true);\n }\n }, [disabled]);\n\n const handleMouseUp = useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const buttonStyle = useMemo<React.CSSProperties>(() => {\n const enhancedStyles = getKoreanButtonWithGlow({\n variant,\n isHovered: isHovered && !disabled,\n isPressed: isPressed && !disabled,\n isFocused: false,\n glowIntensity: \"medium\",\n hoverAnimation: \"combined\",\n });\n\n return {\n ...enhancedStyles,\n padding: buttonSize.padding,\n fontSize: buttonSize.fontSize, // Override for size consistency\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n borderRadius: \"4px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n WebkitUserSelect: \"none\" as const,\n width: fullWidth ? \"100%\" : \"auto\",\n ...customStyle,\n };\n }, [\n variant,\n buttonSize,\n disabled,\n fullWidth,\n isHovered,\n isPressed,\n customStyle,\n ]);\n\n return (\n <button\n onClick={handleClick}\n onMouseEnter={handleMouseEnterInternal}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n disabled={disabled}\n style={buttonStyle}\n className={className}\n data-testid={testId ?? \"base-button-html\"}\n autoFocus={autoFocus}\n aria-label={ariaLabel ?? `${korean} | ${english}`}\n aria-current={ariaCurrent}\n >\n <div style={{ \n display: \"flex\", \n flexDirection: \"column\", \n alignItems: \"center\",\n gap: \"2px\"\n }}>\n <span style={{ fontSize: \"1em\" }}>{korean}</span>\n <span style={{ \n fontSize: \"0.75em\", \n opacity: 0.8,\n fontStyle: \"italic\"\n }}>\n {english}\n </span>\n </div>\n </button>\n );\n});\n\nBaseButtonOverlayHtml.displayName = \"BaseButtonOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,IAAa,wBAAwB,MAAM,MACxC,EACC,QACA,SACA,SACA,cACA,WAAW,OACb,UAAU,WACV,OAAO,MACP,YAAY,OACZ,QACA,WAAW,OACX,WACA,OAAO,aACP,YAAY,OACZ,WACA,kBACI;CACJ,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CAEjD,MAAM,EAAE,eAAe,eAAe;EACpC;EACA;EACA;EACA;EACD,CAAC;CAiEF,OACE,oBAAC,UAAD;EACE,SAjEgB,kBAAkB;GACpC,IAAI,CAAC,UAAU;IACb,UAAU,WAAW;IACrB,SAAS;;KAEV,CAAC,SAAS,SAAS,CA4DT;EACT,cA3D6B,kBAAkB;GACjD,IAAI,CAAC,UAAU;IACb,UAAU,WAAW;IACrB,aAAa,KAAK;IAClB,gBAAgB;;KAEjB,CAAC,UAAU,aAAa,CAqDT;EACd,cApDqB,kBAAkB;GACzC,aAAa,MAAM;GACnB,aAAa,MAAM;KAClB,EAAE,CAiDa;EACd,aAhDoB,kBAAkB;GACxC,IAAI,CAAC,UACH,aAAa,KAAK;KAEnB,CAAC,SAAS,CA4CI;EACb,WA3CkB,kBAAkB;GACtC,aAAa,MAAM;KAClB,EAAE,CAyCU;EACD;EACV,OAzCgB,cAAmC;GAUrD,OAAO;IACL,GAVqB,wBAAwB;KAC7C;KACA,WAAW,aAAa,CAAC;KACzB,WAAW,aAAa,CAAC;KACzB,WAAW;KACX,eAAe;KACf,gBAAgB;KACjB,CAGI;IACH,SAAS,WAAW;IACpB,UAAU,WAAW;IACrB,QAAQ,WAAW,gBAAgB;IACnC,SAAS,WAAW,KAAM;IAC1B,cAAc;IACd,WAAW;IACX,YAAY;IACZ,kBAAkB;IAClB,OAAO,YAAY,SAAS;IAC5B,GAAG;IACJ;KACA;GACD;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAUU;EACI;EACX,eAAa,UAAU;EACZ;EACX,cAAY,aAAa,GAAG,OAAO,KAAK;EACxC,gBAAc;YAEd,qBAAC,OAAD;GAAK,OAAO;IACV,SAAS;IACT,eAAe;IACf,YAAY;IACZ,KAAK;IACN;aALD,CAME,oBAAC,QAAD;IAAM,OAAO,EAAE,UAAU,OAAO;cAAG;IAAc,CAAA,EACjD,oBAAC,QAAD;IAAM,OAAO;KACX,UAAU;KACV,SAAS;KACT,WAAW;KACZ;cACE;IACI,CAAA,CACH;;EACC,CAAA;EAEX;AAEF,sBAAsB,cAAc"}
1
+ {"version":3,"file":"BaseButtonOverlayHtml.js","names":[],"sources":["../../../../src/components/shared/base/BaseButtonOverlayHtml.tsx"],"sourcesContent":["/**\n * BaseButtonOverlayHtml - HTML button component with Korean theming (non-Three.js)\n * \n * A version of BaseButton that doesn't require Three.js/Canvas context\n * Can be used in regular DOM components\n * \n * @module components/base\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { getKoreanButtonWithGlow } from \"../../../utils/koreanThemeHelpers\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\nimport { UIHaptics } from \"../../../utils/hapticFeedback\";\n\n/**\n * Props for BaseButtonOverlayHtml component\n */\nexport interface BaseButtonOverlayHtmlProps {\n readonly korean: string;\n readonly english: string;\n readonly onClick: () => void;\n readonly onMouseEnter?: () => void;\n readonly disabled?: boolean;\n readonly variant?: \"primary\" | \"secondary\" | \"danger\";\n readonly size?: \"sm\" | \"md\" | \"lg\";\n readonly fullWidth?: boolean;\n readonly testId?: string;\n readonly isMobile?: boolean;\n readonly className?: string;\n readonly style?: React.CSSProperties;\n readonly autoFocus?: boolean;\n readonly ariaLabel?: string;\n readonly ariaCurrent?: React.AriaAttributes[\"aria-current\"];\n}\n\n/**\n * BaseButtonOverlayHtml Component\n * \n * HTML button with Korean theming (no Three.js dependency).\n * Uses useKoreanTheme hook for consistent styling.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when props haven't changed\n * - Event handlers already use useCallback internally\n * \n * @example\n * ```tsx\n * <BaseButtonOverlayHtml\n * korean=\"확인\"\n * english=\"Confirm\"\n * onClick={() => handleConfirm()}\n * variant=\"primary\"\n * size=\"md\"\n * />\n * ```\n */\nexport const BaseButtonOverlayHtml = React.memo<BaseButtonOverlayHtmlProps>(\n ({\n korean,\n english,\n onClick,\n onMouseEnter,\n disabled = false,\n variant = \"primary\",\n size = \"md\",\n fullWidth = false,\n testId,\n isMobile = false,\n className,\n style: customStyle,\n autoFocus = false,\n ariaLabel,\n ariaCurrent,\n}) => {\n const [isHovered, setIsHovered] = useState(false);\n const [isPressed, setIsPressed] = useState(false);\n\n const { buttonSize } = useKoreanTheme({\n variant,\n size,\n disabled,\n isMobile,\n });\n\n const handleClick = useCallback(() => {\n if (!disabled) {\n UIHaptics.buttonTap(); // Add haptic feedback\n onClick();\n }\n }, [onClick, disabled]);\n\n const handleMouseEnterInternal = useCallback(() => {\n if (!disabled) {\n UIHaptics.menuHover(); // Add haptic feedback\n setIsHovered(true);\n onMouseEnter?.();\n }\n }, [disabled, onMouseEnter]);\n\n const handleMouseLeave = useCallback(() => {\n setIsHovered(false);\n setIsPressed(false);\n }, []);\n\n const handleMouseDown = useCallback(() => {\n if (!disabled) {\n setIsPressed(true);\n }\n }, [disabled]);\n\n const handleMouseUp = useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const buttonStyle = useMemo<React.CSSProperties>(() => {\n const enhancedStyles = getKoreanButtonWithGlow({\n variant,\n isHovered: isHovered && !disabled,\n isPressed: isPressed && !disabled,\n isFocused: false,\n glowIntensity: \"medium\",\n hoverAnimation: \"combined\",\n });\n\n return {\n ...enhancedStyles,\n padding: buttonSize.padding,\n fontSize: buttonSize.fontSize, // Override for size consistency\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n borderRadius: \"4px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n WebkitUserSelect: \"none\" as const,\n width: fullWidth ? \"100%\" : \"auto\",\n ...customStyle,\n };\n }, [\n variant,\n buttonSize,\n disabled,\n fullWidth,\n isHovered,\n isPressed,\n customStyle,\n ]);\n\n return (\n <button\n onClick={handleClick}\n onMouseEnter={handleMouseEnterInternal}\n onMouseLeave={handleMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n disabled={disabled}\n style={buttonStyle}\n className={className}\n data-testid={testId ?? \"base-button-html\"}\n autoFocus={autoFocus}\n aria-label={ariaLabel ?? `${korean} | ${english}`}\n aria-current={ariaCurrent}\n >\n <div style={{ \n display: \"flex\", \n flexDirection: \"column\", \n alignItems: \"center\",\n gap: \"2px\"\n }}>\n <span style={{ fontSize: \"1em\" }}>{korean}</span>\n <span style={{ \n fontSize: \"0.75em\", \n opacity: 0.8,\n fontStyle: \"italic\"\n }}>\n {english}\n </span>\n </div>\n </button>\n );\n});\n\nBaseButtonOverlayHtml.displayName = \"BaseButtonOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,IAAa,wBAAwB,MAAM,MACxC,EACC,QACA,SACA,SACA,cACA,WAAW,OACb,UAAU,WACV,OAAO,MACP,YAAY,OACZ,QACA,WAAW,OACX,WACA,OAAO,aACP,YAAY,OACZ,WACA,kBACI;CACJ,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAEhD,MAAM,EAAE,eAAe,eAAe;EACpC;EACA;EACA;EACA;CACF,CAAC;CAiED,OACE,oBAAC,UAAD;EACE,SAjEgB,kBAAkB;GACpC,IAAI,CAAC,UAAU;IACb,UAAU,UAAU;IACpB,QAAQ;GACV;EACF,GAAG,CAAC,SAAS,QAAQ,CA4DR;EACT,cA3D6B,kBAAkB;GACjD,IAAI,CAAC,UAAU;IACb,UAAU,UAAU;IACpB,aAAa,IAAI;IACjB,eAAe;GACjB;EACF,GAAG,CAAC,UAAU,YAAY,CAqDR;EACd,cApDqB,kBAAkB;GACzC,aAAa,KAAK;GAClB,aAAa,KAAK;EACpB,GAAG,CAAC,CAiDc;EACd,aAhDoB,kBAAkB;GACxC,IAAI,CAAC,UACH,aAAa,IAAI;EAErB,GAAG,CAAC,QAAQ,CA4CK;EACb,WA3CkB,kBAAkB;GACtC,aAAa,KAAK;EACpB,GAAG,CAAC,CAyCW;EACD;EACV,OAzCgB,cAAmC;GAUrD,OAAO;IACL,GAVqB,wBAAwB;KAC7C;KACA,WAAW,aAAa,CAAC;KACzB,WAAW,aAAa,CAAC;KACzB,WAAW;KACX,eAAe;KACf,gBAAgB;IAClB,CAGK;IACH,SAAS,WAAW;IACpB,UAAU,WAAW;IACrB,QAAQ,WAAW,gBAAgB;IACnC,SAAS,WAAW,KAAM;IAC1B,cAAc;IACd,WAAW;IACX,YAAY;IACZ,kBAAkB;IAClB,OAAO,YAAY,SAAS;IAC5B,GAAG;GACL;EACF,GAAG;GACD;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAUW;EACI;EACX,eAAa,UAAU;EACZ;EACX,cAAY,aAAa,GAAG,OAAO,KAAK;EACxC,gBAAc;YAEd,qBAAC,OAAD;GAAK,OAAO;IACV,SAAS;IACT,eAAe;IACf,YAAY;IACZ,KAAK;GACP;aALA,CAME,oBAAC,QAAD;IAAM,OAAO,EAAE,UAAU,MAAM;cAAI;GAAa,CAAA,GAChD,oBAAC,QAAD;IAAM,OAAO;KACX,UAAU;KACV,SAAS;KACT,WAAW;IACb;cACG;GACG,CAAA,CACH;;CACC,CAAA;AAEZ,CAAC;AAED,sBAAsB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"BasePanel.js","names":[],"sources":["../../../../src/components/shared/base/BasePanel.tsx"],"sourcesContent":["/**\n * BasePanel - Enhanced panel component with Korean theming\n * \n * Builds on existing KoreanPanel with extracted common logic\n * Provides consistent panel styling across the application\n * \n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BasePanel component\n */\nexport interface BasePanelProps {\n readonly children: React.ReactNode;\n readonly position?: [number, number, number];\n readonly width?: number | string;\n readonly height?: number | string;\n readonly padding?: number;\n readonly variant?: \"default\" | \"bordered\" | \"elevated\";\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** ARIA role for semantic HTML (default: \"region\") */\n readonly ariaRole?: \"region\" | \"article\" | \"complementary\" | \"navigation\" | \"main\";\n /** ARIA label for accessibility */\n readonly ariaLabel?: string;\n /** ARIA described by ID for additional context */\n readonly ariaDescribedBy?: string;\n}\n\n/**\n * BasePanel Component\n * \n * Enhanced Korean-themed panel with common functionality extracted.\n * Uses useKoreanTheme hook for consistent styling.\n * \n * WCAG 2.1 AA Accessibility Features:\n * - Proper ARIA roles for semantic HTML\n * - ARIA labels for screen reader context\n * - Responsive padding for touch targets\n * \n * Optimized with React.memo for performance\n * \n * @example\n * ```tsx\n * <BasePanel \n * variant=\"bordered\" \n * padding={20}\n * ariaRole=\"region\"\n * ariaLabel=\"Combat statistics panel\"\n * >\n * <h1>Panel Content</h1>\n * </BasePanel>\n * ```\n */\nconst BasePanelComponent: React.FC<BasePanelProps> = ({\n children,\n position = [0, 0, 0],\n width = \"auto\",\n height = \"auto\",\n padding = 16,\n variant = \"default\",\n testId,\n isMobile = false,\n ariaRole = \"region\",\n ariaLabel,\n ariaDescribedBy,\n}) => {\n const { panelVariant, fontFamily } = useKoreanTheme({\n variant,\n isMobile,\n });\n\n const panelStyle = useMemo<React.CSSProperties>(() => {\n return {\n width,\n height,\n padding: `${padding}px`,\n borderRadius: \"8px\",\n fontFamily: fontFamily.KOREAN,\n background: panelVariant.background,\n border: panelVariant.border,\n boxShadow: panelVariant.boxShadow,\n };\n }, [width, height, padding, panelVariant, fontFamily]);\n\n return (\n <Html position={position} center>\n <div \n style={panelStyle} \n data-testid={testId ?? \"base-panel\"}\n role={ariaRole}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n >\n {children}\n </div>\n </Html>\n );\n};\n\nexport const BasePanel = React.memo(BasePanelComponent);\n\nBasePanel.displayName = \"BasePanel\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,IAAM,sBAAgD,EACpD,UACA,WAAW;CAAC;CAAG;CAAG;CAAE,EACpB,QAAQ,QACR,SAAS,QACT,UAAU,IACV,UAAU,WACV,QACA,WAAW,OACX,WAAW,UACX,WACA,sBACI;CACJ,MAAM,EAAE,cAAc,eAAe,eAAe;EAClD;EACA;EACD,CAAC;CAeF,OACE,oBAAC,MAAD;EAAgB;EAAU,QAAA;YACxB,oBAAC,OAAD;GACE,OAhBa,cAAmC;IACpD,OAAO;KACL;KACA;KACA,SAAS,GAAG,QAAQ;KACpB,cAAc;KACd,YAAY,WAAW;KACvB,YAAY,aAAa;KACzB,QAAQ,aAAa;KACrB,WAAW,aAAa;KACzB;MACA;IAAC;IAAO;IAAQ;IAAS;IAAc;IAAW,CAKxC;GACP,eAAa,UAAU;GACvB,MAAM;GACN,cAAY;GACZ,oBAAkB;GAEjB;GACG,CAAA;EACD,CAAA;;AAIX,IAAa,YAAY,MAAM,KAAK,mBAAmB;AAEvD,UAAU,cAAc"}
1
+ {"version":3,"file":"BasePanel.js","names":[],"sources":["../../../../src/components/shared/base/BasePanel.tsx"],"sourcesContent":["/**\n * BasePanel - Enhanced panel component with Korean theming\n * \n * Builds on existing KoreanPanel with extracted common logic\n * Provides consistent panel styling across the application\n * \n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BasePanel component\n */\nexport interface BasePanelProps {\n readonly children: React.ReactNode;\n readonly position?: [number, number, number];\n readonly width?: number | string;\n readonly height?: number | string;\n readonly padding?: number;\n readonly variant?: \"default\" | \"bordered\" | \"elevated\";\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** ARIA role for semantic HTML (default: \"region\") */\n readonly ariaRole?: \"region\" | \"article\" | \"complementary\" | \"navigation\" | \"main\";\n /** ARIA label for accessibility */\n readonly ariaLabel?: string;\n /** ARIA described by ID for additional context */\n readonly ariaDescribedBy?: string;\n}\n\n/**\n * BasePanel Component\n * \n * Enhanced Korean-themed panel with common functionality extracted.\n * Uses useKoreanTheme hook for consistent styling.\n * \n * WCAG 2.1 AA Accessibility Features:\n * - Proper ARIA roles for semantic HTML\n * - ARIA labels for screen reader context\n * - Responsive padding for touch targets\n * \n * Optimized with React.memo for performance\n * \n * @example\n * ```tsx\n * <BasePanel \n * variant=\"bordered\" \n * padding={20}\n * ariaRole=\"region\"\n * ariaLabel=\"Combat statistics panel\"\n * >\n * <h1>Panel Content</h1>\n * </BasePanel>\n * ```\n */\nconst BasePanelComponent: React.FC<BasePanelProps> = ({\n children,\n position = [0, 0, 0],\n width = \"auto\",\n height = \"auto\",\n padding = 16,\n variant = \"default\",\n testId,\n isMobile = false,\n ariaRole = \"region\",\n ariaLabel,\n ariaDescribedBy,\n}) => {\n const { panelVariant, fontFamily } = useKoreanTheme({\n variant,\n isMobile,\n });\n\n const panelStyle = useMemo<React.CSSProperties>(() => {\n return {\n width,\n height,\n padding: `${padding}px`,\n borderRadius: \"8px\",\n fontFamily: fontFamily.KOREAN,\n background: panelVariant.background,\n border: panelVariant.border,\n boxShadow: panelVariant.boxShadow,\n };\n }, [width, height, padding, panelVariant, fontFamily]);\n\n return (\n <Html position={position} center>\n <div \n style={panelStyle} \n data-testid={testId ?? \"base-panel\"}\n role={ariaRole}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n >\n {children}\n </div>\n </Html>\n );\n};\n\nexport const BasePanel = React.memo(BasePanelComponent);\n\nBasePanel.displayName = \"BasePanel\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,IAAM,sBAAgD,EACpD,UACA,WAAW;CAAC;CAAG;CAAG;AAAC,GACnB,QAAQ,QACR,SAAS,QACT,UAAU,IACV,UAAU,WACV,QACA,WAAW,OACX,WAAW,UACX,WACA,sBACI;CACJ,MAAM,EAAE,cAAc,eAAe,eAAe;EAClD;EACA;CACF,CAAC;CAeD,OACE,oBAAC,MAAD;EAAgB;EAAU,QAAA;YACxB,oBAAC,OAAD;GACE,OAhBa,cAAmC;IACpD,OAAO;KACL;KACA;KACA,SAAS,GAAG,QAAQ;KACpB,cAAc;KACd,YAAY,WAAW;KACvB,YAAY,aAAa;KACzB,QAAQ,aAAa;KACrB,WAAW,aAAa;IAC1B;GACF,GAAG;IAAC;IAAO;IAAQ;IAAS;IAAc;GAAU,CAKvC;GACP,eAAa,UAAU;GACvB,MAAM;GACN,cAAY;GACZ,oBAAkB;GAEjB;EACE,CAAA;CACD,CAAA;AAEV;AAEA,IAAa,YAAY,MAAM,KAAK,kBAAkB;AAEtD,UAAU,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"BaseText.js","names":[],"sources":["../../../../src/components/shared/base/BaseText.tsx"],"sourcesContent":["/**\n * BaseText - Enhanced bilingual text component with Korean theming\n *\n * Builds on existing KoreanText with extracted common logic\n * Provides consistent text styling across the application\n *\n * Now with Html overlay positioning helpers for:\n * - Consistent z-index management\n * - Performance optimization with distanceFactor\n * - GPU acceleration\n *\n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo, useState, useEffect } from \"react\";\nimport { KOREAN_COLORS, UI_DIMENSIONS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../utils/htmlOverlayHelpers\";\nimport type { HtmlOverlayLayer } from \"../../../types/HtmlOverlayTypes\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BaseText component\n */\nexport interface BaseTextProps {\n readonly korean: string;\n readonly english: string;\n readonly position?: [number, number, number];\n readonly size?: \"small\" | \"medium\" | \"large\" | \"xlarge\";\n readonly color?: number;\n readonly align?: \"left\" | \"center\" | \"right\";\n readonly weight?: \"normal\" | \"bold\";\n readonly layout?: \"vertical\" | \"horizontal\";\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** Html overlay layer for z-index (default: 'hud') */\n readonly layer?: HtmlOverlayLayer;\n /** Whether text should occlude behind 3D objects (default: false) */\n readonly occlude?: boolean;\n /** ARIA label for accessibility (optional) */\n readonly ariaLabel?: string;\n /** ARIA live region for dynamic content (optional) */\n readonly ariaLive?: \"polite\" | \"assertive\" | \"off\";\n}\n\n/**\n * BaseText Component\n *\n * Enhanced bilingual text component with Korean cyberpunk styling.\n * Uses useKoreanTheme hook for consistent text sizing and styling.\n * Now includes Html overlay positioning helpers for proper z-index and performance.\n *\n * Korean Typography Features:\n * - Optimized line height (1.6) for Korean character readability\n * - Letter spacing (-0.01em) for tighter Korean text\n * - Word break (keep-all) to prevent breaking Korean words mid-syllable\n * - Proper language attributes (lang=\"ko\" / lang=\"en\")\n *\n * WCAG 2.1 AA Accessibility Features:\n * - Proper language attributes for screen readers\n * - Optional ARIA labels for additional context\n * - ARIA live regions for dynamic content\n *\n * Optimized with React.memo for performance\n *\n * @example\n * ```tsx\n * <BaseText\n * korean=\"공격\"\n * english=\"Attack\"\n * size=\"large\"\n * layout=\"vertical\"\n * layer=\"hud\"\n * ariaLive=\"polite\"\n * />\n * ```\n */\nconst BaseTextComponent: React.FC<BaseTextProps> = ({\n korean,\n english,\n position = [0, 0, 0],\n size = \"medium\",\n color = KOREAN_COLORS.TEXT_PRIMARY,\n align = \"center\",\n weight = \"normal\",\n layout = \"vertical\",\n testId,\n isMobile = false,\n layer = \"hud\",\n occlude = false,\n ariaLabel,\n ariaLive = \"off\",\n}) => {\n const { textSize, koreanTypography } = useKoreanTheme({\n size,\n isMobile,\n });\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const distanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"text\", isMobile);\n }, [screenWidth, isMobile]);\n\n const overlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(layer, false, distanceFactor, true, occlude);\n }, [layer, distanceFactor, occlude]);\n\n const textStyle = useMemo<React.CSSProperties>(\n () => ({\n color: hexToRgbaString(color),\n fontFamily: koreanTypography.fontFamily,\n textAlign: align,\n fontWeight: weight === \"bold\" ? \"bold\" : \"normal\",\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n wordWrap: koreanTypography.wordWrap,\n }),\n [color, align, weight, koreanTypography],\n );\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"flex\",\n flexDirection: layout === \"vertical\" ? \"column\" : \"row\",\n alignItems: \"center\",\n gap: layout === \"vertical\" ? \"4px\" : \"8px\",\n transform: overlayStyle.transform,\n pointerEvents: overlayStyle.pointerEvents,\n zIndex: overlayStyle.zIndex,\n }),\n [layout, overlayStyle],\n );\n\n const koreanStyle = useMemo<React.CSSProperties>(\n () => ({\n ...textStyle,\n fontSize: textSize.korean,\n }),\n [textStyle, textSize.korean],\n );\n\n const englishStyle = useMemo<React.CSSProperties>(\n () => ({\n ...textStyle,\n fontSize: textSize.english,\n opacity: 0.8,\n fontStyle: \"italic\",\n }),\n [textStyle, textSize.english],\n );\n\n return (\n <Html\n position={position}\n center={overlayStyle.center}\n distanceFactor={overlayStyle.distanceFactor}\n occlude={overlayStyle.occlude}\n style={{ pointerEvents: overlayStyle.pointerEvents }}\n >\n <div\n style={containerStyle}\n data-testid={testId ?? \"base-text\"}\n aria-label={ariaLabel}\n aria-live={ariaLive}\n >\n <span lang=\"ko\" style={koreanStyle}>\n {korean}\n </span>\n {layout === \"vertical\" && (\n <span lang=\"en\" style={englishStyle}>\n {english}\n </span>\n )}\n {layout === \"horizontal\" && (\n <span lang=\"en\" style={englishStyle}>\n | {english}\n </span>\n )}\n </div>\n </Html>\n );\n};\n\nexport const BaseText = React.memo(BaseTextComponent);\n\nBaseText.displayName = \"BaseText\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFA,IAAM,qBAA8C,EAClD,QACA,SACA,WAAW;CAAC;CAAG;CAAG;CAAE,EACpB,OAAO,UACP,QAAQ,cAAc,cACtB,QAAQ,UACR,SAAS,UACT,SAAS,YACT,QACA,WAAW,OACX,QAAQ,OACR,UAAU,OACV,WACA,WAAW,YACP;CACJ,MAAM,EAAE,UAAU,qBAAqB,eAAe;EACpD;EACA;EACD,CAAC;CAEF,MAAM,CAAC,aAAa,kBAAkB,eACpC,OAAO,WAAW,cACd,OAAO,aACP,cAAc,qBACnB;CAED,gBAAgB;EACd,IAAI,OAAO,WAAW,aAAa;EAEnC,MAAM,qBAAqB;GACzB,eAAe,OAAO,WAAW;;EAGnC,OAAO,iBAAiB,UAAU,aAAa;EAC/C,aAAa,OAAO,oBAAoB,UAAU,aAAa;IAC9D,EAAE,CAAC;CAEN,MAAM,iBAAiB,cAAc;EACnC,OAAO,wBAAwB,aAAa,QAAQ,SAAS;IAC5D,CAAC,aAAa,SAAS,CAAC;CAE3B,MAAM,eAAe,cAAc;EACjC,OAAO,uBAAuB,OAAO,OAAO,gBAAgB,MAAM,QAAQ;IACzE;EAAC;EAAO;EAAgB;EAAQ,CAAC;CAEpC,MAAM,YAAY,eACT;EACL,OAAO,gBAAgB,MAAM;EAC7B,YAAY,iBAAiB;EAC7B,WAAW;EACX,YAAY,WAAW,SAAS,SAAS;EACzC,YAAY,aAAa,gBAAgB,cAAc,aAAa,GAAI;EACxE,YAAY;EACZ,kBAAkB;EAClB,YAAY,iBAAiB;EAC7B,eAAe,iBAAiB;EAChC,WAAW,iBAAiB;EAC5B,UAAU,iBAAiB;EAC5B,GACD;EAAC;EAAO;EAAO;EAAQ;EAAiB,CACzC;CAED,MAAM,iBAAiB,eACd;EACL,SAAS;EACT,eAAe,WAAW,aAAa,WAAW;EAClD,YAAY;EACZ,KAAK,WAAW,aAAa,QAAQ;EACrC,WAAW,aAAa;EACxB,eAAe,aAAa;EAC5B,QAAQ,aAAa;EACtB,GACD,CAAC,QAAQ,aAAa,CACvB;CAED,MAAM,cAAc,eACX;EACL,GAAG;EACH,UAAU,SAAS;EACpB,GACD,CAAC,WAAW,SAAS,OAAO,CAC7B;CAED,MAAM,eAAe,eACZ;EACL,GAAG;EACH,UAAU,SAAS;EACnB,SAAS;EACT,WAAW;EACZ,GACD,CAAC,WAAW,SAAS,QAAQ,CAC9B;CAED,OACE,oBAAC,MAAD;EACY;EACV,QAAQ,aAAa;EACrB,gBAAgB,aAAa;EAC7B,SAAS,aAAa;EACtB,OAAO,EAAE,eAAe,aAAa,eAAe;YAEpD,qBAAC,OAAD;GACE,OAAO;GACP,eAAa,UAAU;GACvB,cAAY;GACZ,aAAW;aAJb;IAME,oBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eACpB;KACI,CAAA;IACN,WAAW,cACV,oBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eACpB;KACI,CAAA;IAER,WAAW,gBACV,qBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eAAvB,CAAqC,MAChC,QACE;;IAEL;;EACD,CAAA;;AAIX,IAAa,WAAW,MAAM,KAAK,kBAAkB;AAErD,SAAS,cAAc"}
1
+ {"version":3,"file":"BaseText.js","names":[],"sources":["../../../../src/components/shared/base/BaseText.tsx"],"sourcesContent":["/**\n * BaseText - Enhanced bilingual text component with Korean theming\n *\n * Builds on existing KoreanText with extracted common logic\n * Provides consistent text styling across the application\n *\n * Now with Html overlay positioning helpers for:\n * - Consistent z-index management\n * - Performance optimization with distanceFactor\n * - GPU acceleration\n *\n * @module components/base\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo, useState, useEffect } from \"react\";\nimport { KOREAN_COLORS, UI_DIMENSIONS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../utils/htmlOverlayHelpers\";\nimport type { HtmlOverlayLayer } from \"../../../types/HtmlOverlayTypes\";\nimport { useKoreanTheme } from \"./useKoreanTheme\";\n\n/**\n * Props for BaseText component\n */\nexport interface BaseTextProps {\n readonly korean: string;\n readonly english: string;\n readonly position?: [number, number, number];\n readonly size?: \"small\" | \"medium\" | \"large\" | \"xlarge\";\n readonly color?: number;\n readonly align?: \"left\" | \"center\" | \"right\";\n readonly weight?: \"normal\" | \"bold\";\n readonly layout?: \"vertical\" | \"horizontal\";\n readonly testId?: string;\n readonly isMobile?: boolean;\n /** Html overlay layer for z-index (default: 'hud') */\n readonly layer?: HtmlOverlayLayer;\n /** Whether text should occlude behind 3D objects (default: false) */\n readonly occlude?: boolean;\n /** ARIA label for accessibility (optional) */\n readonly ariaLabel?: string;\n /** ARIA live region for dynamic content (optional) */\n readonly ariaLive?: \"polite\" | \"assertive\" | \"off\";\n}\n\n/**\n * BaseText Component\n *\n * Enhanced bilingual text component with Korean cyberpunk styling.\n * Uses useKoreanTheme hook for consistent text sizing and styling.\n * Now includes Html overlay positioning helpers for proper z-index and performance.\n *\n * Korean Typography Features:\n * - Optimized line height (1.6) for Korean character readability\n * - Letter spacing (-0.01em) for tighter Korean text\n * - Word break (keep-all) to prevent breaking Korean words mid-syllable\n * - Proper language attributes (lang=\"ko\" / lang=\"en\")\n *\n * WCAG 2.1 AA Accessibility Features:\n * - Proper language attributes for screen readers\n * - Optional ARIA labels for additional context\n * - ARIA live regions for dynamic content\n *\n * Optimized with React.memo for performance\n *\n * @example\n * ```tsx\n * <BaseText\n * korean=\"공격\"\n * english=\"Attack\"\n * size=\"large\"\n * layout=\"vertical\"\n * layer=\"hud\"\n * ariaLive=\"polite\"\n * />\n * ```\n */\nconst BaseTextComponent: React.FC<BaseTextProps> = ({\n korean,\n english,\n position = [0, 0, 0],\n size = \"medium\",\n color = KOREAN_COLORS.TEXT_PRIMARY,\n align = \"center\",\n weight = \"normal\",\n layout = \"vertical\",\n testId,\n isMobile = false,\n layer = \"hud\",\n occlude = false,\n ariaLabel,\n ariaLive = \"off\",\n}) => {\n const { textSize, koreanTypography } = useKoreanTheme({\n size,\n isMobile,\n });\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const distanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"text\", isMobile);\n }, [screenWidth, isMobile]);\n\n const overlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(layer, false, distanceFactor, true, occlude);\n }, [layer, distanceFactor, occlude]);\n\n const textStyle = useMemo<React.CSSProperties>(\n () => ({\n color: hexToRgbaString(color),\n fontFamily: koreanTypography.fontFamily,\n textAlign: align,\n fontWeight: weight === \"bold\" ? \"bold\" : \"normal\",\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n wordWrap: koreanTypography.wordWrap,\n }),\n [color, align, weight, koreanTypography],\n );\n\n const containerStyle = useMemo<React.CSSProperties>(\n () => ({\n display: \"flex\",\n flexDirection: layout === \"vertical\" ? \"column\" : \"row\",\n alignItems: \"center\",\n gap: layout === \"vertical\" ? \"4px\" : \"8px\",\n transform: overlayStyle.transform,\n pointerEvents: overlayStyle.pointerEvents,\n zIndex: overlayStyle.zIndex,\n }),\n [layout, overlayStyle],\n );\n\n const koreanStyle = useMemo<React.CSSProperties>(\n () => ({\n ...textStyle,\n fontSize: textSize.korean,\n }),\n [textStyle, textSize.korean],\n );\n\n const englishStyle = useMemo<React.CSSProperties>(\n () => ({\n ...textStyle,\n fontSize: textSize.english,\n opacity: 0.8,\n fontStyle: \"italic\",\n }),\n [textStyle, textSize.english],\n );\n\n return (\n <Html\n position={position}\n center={overlayStyle.center}\n distanceFactor={overlayStyle.distanceFactor}\n occlude={overlayStyle.occlude}\n style={{ pointerEvents: overlayStyle.pointerEvents }}\n >\n <div\n style={containerStyle}\n data-testid={testId ?? \"base-text\"}\n aria-label={ariaLabel}\n aria-live={ariaLive}\n >\n <span lang=\"ko\" style={koreanStyle}>\n {korean}\n </span>\n {layout === \"vertical\" && (\n <span lang=\"en\" style={englishStyle}>\n {english}\n </span>\n )}\n {layout === \"horizontal\" && (\n <span lang=\"en\" style={englishStyle}>\n | {english}\n </span>\n )}\n </div>\n </Html>\n );\n};\n\nexport const BaseText = React.memo(BaseTextComponent);\n\nBaseText.displayName = \"BaseText\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiFA,IAAM,qBAA8C,EAClD,QACA,SACA,WAAW;CAAC;CAAG;CAAG;AAAC,GACnB,OAAO,UACP,QAAQ,cAAc,cACtB,QAAQ,UACR,SAAS,UACT,SAAS,YACT,QACA,WAAW,OACX,QAAQ,OACR,UAAU,OACV,WACA,WAAW,YACP;CACJ,MAAM,EAAE,UAAU,qBAAqB,eAAe;EACpD;EACA;CACF,CAAC;CAED,MAAM,CAAC,aAAa,kBAAkB,eACpC,OAAO,WAAW,cACd,OAAO,aACP,cAAc,oBACpB;CAEA,gBAAgB;EACd,IAAI,OAAO,WAAW,aAAa;EAEnC,MAAM,qBAAqB;GACzB,eAAe,OAAO,UAAU;EAClC;EAEA,OAAO,iBAAiB,UAAU,YAAY;EAC9C,aAAa,OAAO,oBAAoB,UAAU,YAAY;CAChE,GAAG,CAAC,CAAC;CAEL,MAAM,iBAAiB,cAAc;EACnC,OAAO,wBAAwB,aAAa,QAAQ,QAAQ;CAC9D,GAAG,CAAC,aAAa,QAAQ,CAAC;CAE1B,MAAM,eAAe,cAAc;EACjC,OAAO,uBAAuB,OAAO,OAAO,gBAAgB,MAAM,OAAO;CAC3E,GAAG;EAAC;EAAO;EAAgB;CAAO,CAAC;CAEnC,MAAM,YAAY,eACT;EACL,OAAO,gBAAgB,KAAK;EAC5B,YAAY,iBAAiB;EAC7B,WAAW;EACX,YAAY,WAAW,SAAS,SAAS;EACzC,YAAY,aAAa,gBAAgB,cAAc,aAAa,EAAG;EACvE,YAAY;EACZ,kBAAkB;EAClB,YAAY,iBAAiB;EAC7B,eAAe,iBAAiB;EAChC,WAAW,iBAAiB;EAC5B,UAAU,iBAAiB;CAC7B,IACA;EAAC;EAAO;EAAO;EAAQ;CAAgB,CACzC;CAEA,MAAM,iBAAiB,eACd;EACL,SAAS;EACT,eAAe,WAAW,aAAa,WAAW;EAClD,YAAY;EACZ,KAAK,WAAW,aAAa,QAAQ;EACrC,WAAW,aAAa;EACxB,eAAe,aAAa;EAC5B,QAAQ,aAAa;CACvB,IACA,CAAC,QAAQ,YAAY,CACvB;CAEA,MAAM,cAAc,eACX;EACL,GAAG;EACH,UAAU,SAAS;CACrB,IACA,CAAC,WAAW,SAAS,MAAM,CAC7B;CAEA,MAAM,eAAe,eACZ;EACL,GAAG;EACH,UAAU,SAAS;EACnB,SAAS;EACT,WAAW;CACb,IACA,CAAC,WAAW,SAAS,OAAO,CAC9B;CAEA,OACE,oBAAC,MAAD;EACY;EACV,QAAQ,aAAa;EACrB,gBAAgB,aAAa;EAC7B,SAAS,aAAa;EACtB,OAAO,EAAE,eAAe,aAAa,cAAc;YAEnD,qBAAC,OAAD;GACE,OAAO;GACP,eAAa,UAAU;GACvB,cAAY;GACZ,aAAW;aAJb;IAME,oBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eACpB;IACG,CAAA;IACL,WAAW,cACV,oBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eACpB;IACG,CAAA;IAEP,WAAW,gBACV,qBAAC,QAAD;KAAM,MAAK;KAAK,OAAO;eAAvB,CAAqC,MAChC,OACC;;GAEL;;CACD,CAAA;AAEV;AAEA,IAAa,WAAW,MAAM,KAAK,iBAAiB;AAEpD,SAAS,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKoreanTheme.js","names":[],"sources":["../../../../src/components/shared/base/useKoreanTheme.ts"],"sourcesContent":["/**\n * useKoreanTheme - Custom hook for Korean cyberpunk theming\n * \n * Provides centralized Korean theme styling and responsive utilities\n * Eliminates code duplication across UI components\n * \n * Enhanced with:\n * - Korean typography optimization (line height, letter spacing, word break)\n * - Accessibility features (focus indicators, touch targets, high contrast)\n * - WCAG 2.1 AA compliant color contrast ratios\n * \n * @module components/base\n */\n\nimport { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\n\n/**\n * Button variant configuration\n */\nexport interface ButtonVariantConfig {\n readonly background: number;\n readonly border: number;\n readonly text: number;\n readonly hoverBg: string;\n readonly activeBg: string;\n}\n\n/**\n * Panel variant configuration\n */\nexport interface PanelVariantConfig {\n readonly background: string;\n readonly border: string;\n readonly boxShadow: string;\n}\n\n/**\n * Size dimension configuration\n */\nexport interface SizeDimensions {\n readonly padding: string;\n readonly fontSize: string;\n readonly borderWidth: string;\n}\n\n/**\n * Text size configuration\n */\nexport interface TextSizeConfig {\n readonly korean: string;\n readonly english: string;\n}\n\n/**\n * Korean typography configuration\n */\nexport interface KoreanTypographyConfig {\n readonly fontFamily: string;\n readonly lineHeight: number;\n readonly letterSpacing: string;\n readonly wordBreak: \"normal\" | \"break-all\" | \"keep-all\" | \"break-word\";\n readonly wordWrap: \"normal\" | \"break-word\";\n}\n\n/**\n * Accessibility configuration\n */\nexport interface AccessibilityConfig {\n readonly focusOutline: string;\n readonly focusOutlineOffset: string;\n readonly minTouchTarget: string;\n readonly highContrastFocusOutline: string;\n}\n\n/**\n * Korean theme hook configuration\n */\nexport interface UseKoreanThemeConfig {\n readonly variant?: \"primary\" | \"secondary\" | \"danger\" | \"default\" | \"bordered\" | \"elevated\";\n readonly size?: \"sm\" | \"md\" | \"lg\" | \"small\" | \"medium\" | \"large\" | \"xlarge\";\n readonly disabled?: boolean;\n readonly isMobile?: boolean;\n readonly highContrast?: boolean;\n}\n\n/**\n * Custom hook for Korean cyberpunk theming\n * \n * Provides consistent styling patterns for all Korean-themed components\n * Enhanced with Korean typography optimization and accessibility features\n * \n * @example\n * ```tsx\n * const { buttonVariant, sizeDimensions, koreanTypography } = useKoreanTheme({\n * variant: \"primary\",\n * size: \"md\"\n * });\n * ```\n */\nexport function useKoreanTheme(config: UseKoreanThemeConfig = {}) {\n const {\n variant = \"primary\",\n size = \"md\",\n disabled = false,\n isMobile = false,\n highContrast = false,\n } = config;\n\n /**\n * Get button variant colors\n */\n const buttonVariant = useMemo<ButtonVariantConfig>(() => {\n switch (variant) {\n case \"primary\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n };\n case \"secondary\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n border: KOREAN_COLORS.ACCENT_GOLD,\n text: KOREAN_COLORS.TEXT_PRIMARY,\n hoverBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.2),\n };\n case \"danger\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.ACCENT_RED,\n text: KOREAN_COLORS.ACCENT_RED,\n hoverBg: hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.2),\n };\n default:\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n };\n }\n }, [variant]);\n\n /**\n * Get panel variant styling\n */\n const panelVariant = useMemo<PanelVariantConfig>(() => {\n switch (variant) {\n case \"bordered\":\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n };\n case \"elevated\":\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.9),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n boxShadow: `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)},\n 0 0 20px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.2)}\n `,\n };\n case \"default\":\n default:\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.UI_BORDER, 0.5)}`,\n boxShadow: \"none\",\n };\n }\n }, [variant]);\n\n /**\n * Get size dimensions for buttons with touch-optimized mobile values\n * Mobile: 48px+ minimum touch targets with 16px+ Korean font\n */\n const buttonSize = useMemo<SizeDimensions>(() => {\n const scale = isMobile ? 1.0 : 1.0; // Keep full size on mobile for 48px+ targets\n \n switch (size) {\n case \"sm\":\n case \"small\":\n return {\n padding: `${Math.round(8 * scale)}px ${Math.round(16 * scale)}px`,\n fontSize: isMobile ? \"14px\" : `${Math.round(14 * scale)}px`, // Maintain readable size\n borderWidth: \"1px\",\n };\n case \"lg\":\n case \"large\":\n return {\n padding: `${Math.round(16 * scale)}px ${Math.round(32 * scale)}px`,\n fontSize: isMobile ? \"18px\" : `${Math.round(20 * scale)}px`, // 18px minimum for Korean\n borderWidth: \"3px\",\n };\n case \"md\":\n case \"medium\":\n default:\n return {\n padding: `${Math.round(12 * scale)}px ${Math.round(24 * scale)}px`,\n fontSize: isMobile ? \"16px\" : `${Math.round(16 * scale)}px`, // 16px minimum for Korean\n borderWidth: \"2px\",\n };\n }\n }, [size, isMobile]);\n\n /**\n * Get text size configuration\n */\n const textSize = useMemo<TextSizeConfig>(() => {\n const scale = isMobile ? 0.9 : 1.0;\n \n switch (size) {\n case \"small\":\n return {\n korean: `${Math.round(14 * scale)}px`,\n english: `${Math.round(12 * scale)}px`,\n };\n case \"large\":\n return {\n korean: `${Math.round(24 * scale)}px`,\n english: `${Math.round(18 * scale)}px`,\n };\n case \"xlarge\":\n return {\n korean: `${Math.round(32 * scale)}px`,\n english: `${Math.round(24 * scale)}px`,\n };\n case \"medium\":\n default:\n return {\n korean: `${Math.round(18 * scale)}px`,\n english: `${Math.round(14 * scale)}px`,\n };\n }\n }, [size, isMobile]);\n\n /**\n * Calculate responsive size\n */\n const calculateResponsiveSize = useMemo(() => {\n return (baseSize: number) => {\n return isMobile ? Math.round(baseSize * 0.8) : baseSize;\n };\n }, [isMobile]);\n\n /**\n * Apply Korean theme to base styles\n */\n const applyKoreanTheme = useMemo(() => {\n return (baseStyle: React.CSSProperties): React.CSSProperties => {\n return {\n ...baseStyle,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n opacity: disabled ? 0.5 : 1,\n };\n };\n }, [disabled]);\n\n /**\n * Korean typography configuration\n * Optimized for Korean character readability\n * \n * - Line height: 1.6 for Korean characters (vs 1.5 for Latin)\n * - Letter spacing: -0.01em for tighter Korean spacing\n * - Word break: keep-all to prevent breaking Korean words\n * - Word wrap: break-word for long words\n */\n const koreanTypography = useMemo<KoreanTypographyConfig>(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: 1.6, // Optimal for Korean character readability\n letterSpacing: \"-0.01em\", // Tighter spacing for Korean\n wordBreak: \"keep-all\", // Prevent breaking Korean words mid-syllable\n wordWrap: \"break-word\", // Wrap long words appropriately\n }), []);\n\n /**\n * Accessibility configuration\n * WCAG 2.1 AA compliant focus indicators and touch targets\n */\n const accessibility = useMemo<AccessibilityConfig>(() => ({\n focusOutline: highContrast\n ? `4px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}` // High contrast mode\n : `3px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN)}`, // Normal mode\n focusOutlineOffset: \"2px\",\n minTouchTarget: \"44px\", // WCAG 2.1 AA minimum touch target size\n highContrastFocusOutline: `4px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`,\n }), [highContrast]);\n\n return {\n buttonVariant,\n panelVariant,\n buttonSize,\n textSize,\n calculateResponsiveSize,\n applyKoreanTheme,\n koreanTypography,\n accessibility,\n colors: KOREAN_COLORS,\n fontFamily: FONT_FAMILY,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqGA,SAAgB,eAAe,SAA+B,EAAE,EAAE;CAChE,MAAM,EACJ,UAAU,WACV,OAAO,MACP,WAAW,OACX,WAAW,OACX,eAAe,UACb;CA6LJ,OAAO;EACL,eAzLoB,cAAmC;GACvD,QAAQ,SAAR;IACE,KAAK,WACH,OAAO;KACL,YAAY,cAAc;KAC1B,QAAQ,cAAc;KACtB,MAAM,cAAc;KACpB,SAAS,gBAAgB,cAAc,cAAc,GAAI;KACzD,UAAU,gBAAgB,cAAc,cAAc,GAAI;KAC3D;IACH,KAAK,aACH,OAAO;KACL,YAAY,cAAc;KAC1B,QAAQ,cAAc;KACtB,MAAM,cAAc;KACpB,SAAS,gBAAgB,cAAc,aAAa,GAAI;KACxD,UAAU,gBAAgB,cAAc,aAAa,GAAI;KAC1D;IACH,KAAK,UACH,OAAO;KACL,YAAY,cAAc;KAC1B,QAAQ,cAAc;KACtB,MAAM,cAAc;KACpB,SAAS,gBAAgB,cAAc,YAAY,GAAI;KACvD,UAAU,gBAAgB,cAAc,YAAY,GAAI;KACzD;IACH,SACE,OAAO;KACL,YAAY,cAAc;KAC1B,QAAQ,cAAc;KACtB,MAAM,cAAc;KACpB,SAAS,gBAAgB,cAAc,cAAc,GAAI;KACzD,UAAU,gBAAgB,cAAc,cAAc,GAAI;KAC3D;;KAEJ,CAAC,QAAQ,CAsJV;EACA,cAlJmB,cAAkC;GACrD,QAAQ,SAAR;IACE,KAAK,YACH,OAAO;KACL,YAAY,gBAAgB,cAAc,oBAAoB,IAAK;KACnE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;KACrE,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;KACxE;IACH,KAAK,YACH,OAAO;KACL,YAAY,gBAAgB,cAAc,sBAAsB,GAAI;KACpE,QAAQ,aAAa,gBAAgB,cAAc,aAAa,GAAI;KACpE,WAAW;yBACI,gBAAgB,cAAc,aAAa,GAAI,CAAC;uBAClD,gBAAgB,cAAc,aAAa,GAAI,CAAC;;KAE9D;IAEH,SACE,OAAO;KACL,YAAY,gBAAgB,cAAc,oBAAoB,IAAK;KACnE,QAAQ,aAAa,gBAAgB,cAAc,WAAW,GAAI;KAClE,WAAW;KACZ;;KAEJ,CAAC,QAAQ,CAyHV;EACA,YApHiB,cAA8B;GAC/C,MAAM,QAAQ,WAAW,IAAM;GAE/B,QAAQ,MAAR;IACE,KAAK;IACL,KAAK,SACH,OAAO;KACL,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,KAAK,MAAM,CAAC;KAC9D,UAAU,WAAW,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACxD,aAAa;KACd;IACH,KAAK;IACL,KAAK,SACH,OAAO;KACL,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC,KAAK,KAAK,MAAM,KAAK,MAAM,CAAC;KAC/D,UAAU,WAAW,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACxD,aAAa;KACd;IAGH,SACE,OAAO;KACL,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC,KAAK,KAAK,MAAM,KAAK,MAAM,CAAC;KAC/D,UAAU,WAAW,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACxD,aAAa;KACd;;KAEJ,CAAC,MAAM,SAAS,CAyFjB;EACA,UArFe,cAA8B;GAC7C,MAAM,QAAQ,WAAW,KAAM;GAE/B,QAAQ,MAAR;IACE,KAAK,SACH,OAAO;KACL,QAAQ,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KAClC,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACpC;IACH,KAAK,SACH,OAAO;KACL,QAAQ,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KAClC,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACpC;IACH,KAAK,UACH,OAAO;KACL,QAAQ,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KAClC,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACpC;IAEH,SACE,OAAO;KACL,QAAQ,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KAClC,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,CAAC;KACpC;;KAEJ,CAAC,MAAM,SAAS,CA2DjB;EACA,yBAvD8B,cAAc;GAC5C,QAAQ,aAAqB;IAC3B,OAAO,WAAW,KAAK,MAAM,WAAW,GAAI,GAAG;;KAEhD,CAAC,SAAS,CAmDX;EACA,kBA/CuB,cAAc;GACrC,QAAQ,cAAwD;IAC9D,OAAO;KACL,GAAG;KACH,YAAY,YAAY;KACxB,OAAO,gBAAgB,cAAc,aAAa;KAClD,SAAS,WAAW,KAAM;KAC3B;;KAEF,CAAC,SAAS,CAsCX;EACA,kBA5BuB,eAAuC;GAC9D,YAAY,YAAY;GACxB,YAAY;GACZ,eAAe;GACf,WAAW;GACX,UAAU;GACX,GAAG,EAAE,CAsBJ;EACA,eAjBoB,eAAoC;GACxD,cAAc,eACV,aAAa,gBAAgB,cAAc,YAAY,KACvD,aAAa,gBAAgB,cAAc,aAAa;GAC5D,oBAAoB;GACpB,gBAAgB;GAChB,0BAA0B,aAAa,gBAAgB,cAAc,YAAY;GAClF,GAAG,CAAC,aAAa,CAUhB;EACA,QAAQ;EACR,YAAY;EACb"}
1
+ {"version":3,"file":"useKoreanTheme.js","names":[],"sources":["../../../../src/components/shared/base/useKoreanTheme.ts"],"sourcesContent":["/**\n * useKoreanTheme - Custom hook for Korean cyberpunk theming\n * \n * Provides centralized Korean theme styling and responsive utilities\n * Eliminates code duplication across UI components\n * \n * Enhanced with:\n * - Korean typography optimization (line height, letter spacing, word break)\n * - Accessibility features (focus indicators, touch targets, high contrast)\n * - WCAG 2.1 AA compliant color contrast ratios\n * \n * @module components/base\n */\n\nimport { useMemo } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\n\n/**\n * Button variant configuration\n */\nexport interface ButtonVariantConfig {\n readonly background: number;\n readonly border: number;\n readonly text: number;\n readonly hoverBg: string;\n readonly activeBg: string;\n}\n\n/**\n * Panel variant configuration\n */\nexport interface PanelVariantConfig {\n readonly background: string;\n readonly border: string;\n readonly boxShadow: string;\n}\n\n/**\n * Size dimension configuration\n */\nexport interface SizeDimensions {\n readonly padding: string;\n readonly fontSize: string;\n readonly borderWidth: string;\n}\n\n/**\n * Text size configuration\n */\nexport interface TextSizeConfig {\n readonly korean: string;\n readonly english: string;\n}\n\n/**\n * Korean typography configuration\n */\nexport interface KoreanTypographyConfig {\n readonly fontFamily: string;\n readonly lineHeight: number;\n readonly letterSpacing: string;\n readonly wordBreak: \"normal\" | \"break-all\" | \"keep-all\" | \"break-word\";\n readonly wordWrap: \"normal\" | \"break-word\";\n}\n\n/**\n * Accessibility configuration\n */\nexport interface AccessibilityConfig {\n readonly focusOutline: string;\n readonly focusOutlineOffset: string;\n readonly minTouchTarget: string;\n readonly highContrastFocusOutline: string;\n}\n\n/**\n * Korean theme hook configuration\n */\nexport interface UseKoreanThemeConfig {\n readonly variant?: \"primary\" | \"secondary\" | \"danger\" | \"default\" | \"bordered\" | \"elevated\";\n readonly size?: \"sm\" | \"md\" | \"lg\" | \"small\" | \"medium\" | \"large\" | \"xlarge\";\n readonly disabled?: boolean;\n readonly isMobile?: boolean;\n readonly highContrast?: boolean;\n}\n\n/**\n * Custom hook for Korean cyberpunk theming\n * \n * Provides consistent styling patterns for all Korean-themed components\n * Enhanced with Korean typography optimization and accessibility features\n * \n * @example\n * ```tsx\n * const { buttonVariant, sizeDimensions, koreanTypography } = useKoreanTheme({\n * variant: \"primary\",\n * size: \"md\"\n * });\n * ```\n */\nexport function useKoreanTheme(config: UseKoreanThemeConfig = {}) {\n const {\n variant = \"primary\",\n size = \"md\",\n disabled = false,\n isMobile = false,\n highContrast = false,\n } = config;\n\n /**\n * Get button variant colors\n */\n const buttonVariant = useMemo<ButtonVariantConfig>(() => {\n switch (variant) {\n case \"primary\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n };\n case \"secondary\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n border: KOREAN_COLORS.ACCENT_GOLD,\n text: KOREAN_COLORS.TEXT_PRIMARY,\n hoverBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.2),\n };\n case \"danger\":\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.ACCENT_RED,\n text: KOREAN_COLORS.ACCENT_RED,\n hoverBg: hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.2),\n };\n default:\n return {\n background: KOREAN_COLORS.UI_BACKGROUND_DARK,\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.1),\n activeBg: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n };\n }\n }, [variant]);\n\n /**\n * Get panel variant styling\n */\n const panelVariant = useMemo<PanelVariantConfig>(() => {\n switch (variant) {\n case \"bordered\":\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n };\n case \"elevated\":\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.9),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n boxShadow: `\n 0 4px 12px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)},\n 0 0 20px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.2)}\n `,\n };\n case \"default\":\n default:\n return {\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.UI_BORDER, 0.5)}`,\n boxShadow: \"none\",\n };\n }\n }, [variant]);\n\n /**\n * Get size dimensions for buttons with touch-optimized mobile values\n * Mobile: 48px+ minimum touch targets with 16px+ Korean font\n */\n const buttonSize = useMemo<SizeDimensions>(() => {\n const scale = isMobile ? 1.0 : 1.0; // Keep full size on mobile for 48px+ targets\n \n switch (size) {\n case \"sm\":\n case \"small\":\n return {\n padding: `${Math.round(8 * scale)}px ${Math.round(16 * scale)}px`,\n fontSize: isMobile ? \"14px\" : `${Math.round(14 * scale)}px`, // Maintain readable size\n borderWidth: \"1px\",\n };\n case \"lg\":\n case \"large\":\n return {\n padding: `${Math.round(16 * scale)}px ${Math.round(32 * scale)}px`,\n fontSize: isMobile ? \"18px\" : `${Math.round(20 * scale)}px`, // 18px minimum for Korean\n borderWidth: \"3px\",\n };\n case \"md\":\n case \"medium\":\n default:\n return {\n padding: `${Math.round(12 * scale)}px ${Math.round(24 * scale)}px`,\n fontSize: isMobile ? \"16px\" : `${Math.round(16 * scale)}px`, // 16px minimum for Korean\n borderWidth: \"2px\",\n };\n }\n }, [size, isMobile]);\n\n /**\n * Get text size configuration\n */\n const textSize = useMemo<TextSizeConfig>(() => {\n const scale = isMobile ? 0.9 : 1.0;\n \n switch (size) {\n case \"small\":\n return {\n korean: `${Math.round(14 * scale)}px`,\n english: `${Math.round(12 * scale)}px`,\n };\n case \"large\":\n return {\n korean: `${Math.round(24 * scale)}px`,\n english: `${Math.round(18 * scale)}px`,\n };\n case \"xlarge\":\n return {\n korean: `${Math.round(32 * scale)}px`,\n english: `${Math.round(24 * scale)}px`,\n };\n case \"medium\":\n default:\n return {\n korean: `${Math.round(18 * scale)}px`,\n english: `${Math.round(14 * scale)}px`,\n };\n }\n }, [size, isMobile]);\n\n /**\n * Calculate responsive size\n */\n const calculateResponsiveSize = useMemo(() => {\n return (baseSize: number) => {\n return isMobile ? Math.round(baseSize * 0.8) : baseSize;\n };\n }, [isMobile]);\n\n /**\n * Apply Korean theme to base styles\n */\n const applyKoreanTheme = useMemo(() => {\n return (baseStyle: React.CSSProperties): React.CSSProperties => {\n return {\n ...baseStyle,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n opacity: disabled ? 0.5 : 1,\n };\n };\n }, [disabled]);\n\n /**\n * Korean typography configuration\n * Optimized for Korean character readability\n * \n * - Line height: 1.6 for Korean characters (vs 1.5 for Latin)\n * - Letter spacing: -0.01em for tighter Korean spacing\n * - Word break: keep-all to prevent breaking Korean words\n * - Word wrap: break-word for long words\n */\n const koreanTypography = useMemo<KoreanTypographyConfig>(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: 1.6, // Optimal for Korean character readability\n letterSpacing: \"-0.01em\", // Tighter spacing for Korean\n wordBreak: \"keep-all\", // Prevent breaking Korean words mid-syllable\n wordWrap: \"break-word\", // Wrap long words appropriately\n }), []);\n\n /**\n * Accessibility configuration\n * WCAG 2.1 AA compliant focus indicators and touch targets\n */\n const accessibility = useMemo<AccessibilityConfig>(() => ({\n focusOutline: highContrast\n ? `4px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}` // High contrast mode\n : `3px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN)}`, // Normal mode\n focusOutlineOffset: \"2px\",\n minTouchTarget: \"44px\", // WCAG 2.1 AA minimum touch target size\n highContrastFocusOutline: `4px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`,\n }), [highContrast]);\n\n return {\n buttonVariant,\n panelVariant,\n buttonSize,\n textSize,\n calculateResponsiveSize,\n applyKoreanTheme,\n koreanTypography,\n accessibility,\n colors: KOREAN_COLORS,\n fontFamily: FONT_FAMILY,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PerformanceDebugOverlayHtml.js","names":[],"sources":["../../../../src/components/shared/debug/PerformanceDebugOverlayHtml.tsx"],"sourcesContent":["/**\n * Performance Debug Overlay\n *\n * Shows real-time animation performance metrics in development mode.\n * Displays frame times, cache hit rate, and object pool status.\n *\n * Only visible in development mode.\n *\n * @module components/shared/debug/PerformanceDebugOverlayHtml\n * @category Debug\n * @korean 성능디버그오버레이\n */\n\nimport React, { useEffect, useState } from \"react\";\nimport { performanceMonitor } from \"../../../systems/animation\";\nimport { ThreeObjectPools } from \"../../../utils/threeObjectPool\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../utils/colorUtils\";\n\n/**\n * Performance metrics interface\n * @korean 성능지표\n */\ninterface PerformanceMetrics {\n avgFrameTime: number;\n maxFrameTime: number;\n cacheHitRate: number;\n cacheEntries: number;\n}\n\n/**\n * Pool status interface\n * @korean 풀상태\n */\ninterface PoolStatus {\n euler: number;\n vector3: number;\n matrix4: number;\n quaternion: number;\n}\n\n/**\n * Performance Debug Overlay Component\n *\n * Shows real-time animation performance metrics:\n * - Average/max frame times\n * - Cache hit rate\n * - Object pool utilization\n *\n * Only renders in development mode.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized to prevent parent re-renders from affecting it\n * - Internal state updates only via interval\n *\n * @returns Performance overlay or null in production\n * @korean 성능디버그오버레이컴포넌트\n */\nexport const PerformanceDebugOverlayHtml = React.memo(() => {\n const [metrics, setMetrics] = useState<PerformanceMetrics>({\n avgFrameTime: 0,\n maxFrameTime: 0,\n cacheHitRate: 0,\n cacheEntries: 0,\n });\n const [pools, setPools] = useState<PoolStatus>({\n euler: 0,\n vector3: 0,\n matrix4: 0,\n quaternion: 0,\n });\n\n useEffect(() => {\n const interval = setInterval(() => {\n const newMetrics = performanceMonitor.getMetrics();\n setMetrics({\n avgFrameTime: newMetrics.avgFrameTime,\n maxFrameTime: newMetrics.maxFrameTime,\n cacheHitRate: newMetrics.cacheHitRate,\n cacheEntries: newMetrics.cacheEntries,\n });\n setPools(ThreeObjectPools.getStatus());\n }, 1000);\n\n return () => clearInterval(interval);\n }, []);\n\n if (import.meta.env.PROD) {\n return null;\n }\n\n const frameTimeColor =\n metrics.avgFrameTime < 5\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) // Green: Target met\n : metrics.avgFrameTime < 8\n ? hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) // Yellow: Warning\n : hexColorToCSS(KOREAN_COLORS.ACCENT_RED); // Red: Critical\n\n const cacheColor =\n metrics.cacheHitRate > 0.9\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) // Green: Excellent\n : metrics.cacheHitRate > 0.7\n ? hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) // Yellow: Good\n : hexColorToCSS(KOREAN_COLORS.ACCENT_RED); // Red: Poor\n\n const poolColor = (available: number, threshold: number) =>\n available > threshold\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN)\n : hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW);\n\n return (\n <div\n style={{\n position: \"fixed\",\n top: 10,\n right: 10,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n color: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n padding: \"12px\",\n fontFamily: \"monospace\",\n fontSize: \"11px\",\n lineHeight: \"1.4\",\n zIndex: 9999,\n border: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"4px\",\n minWidth: \"200px\",\n userSelect: \"none\",\n pointerEvents: \"none\",\n }}\n data-testid=\"performance-debug-overlay\"\n >\n <div\n style={{\n fontWeight: \"bold\",\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n 🎯 Animation Performance\n </div>\n <div\n style={{\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginBottom: \"6px\",\n }}\n />\n\n {/* Frame Times */}\n <div style={{ marginBottom: \"4px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Avg Frame:{\" \"}\n </span>\n <span style={{ color: frameTimeColor, fontWeight: \"bold\" }}>\n {metrics.avgFrameTime.toFixed(2)}ms\n </span>\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT),\n fontSize: \"9px\",\n marginLeft: \"4px\",\n }}\n >\n (target: &lt;5ms)\n </span>\n </div>\n <div style={{ marginBottom: \"6px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Max Frame:{\" \"}\n </span>\n <span style={{ color: frameTimeColor }}>\n {metrics.maxFrameTime.toFixed(2)}ms\n </span>\n </div>\n\n {/* Cache Performance */}\n <div style={{ marginBottom: \"4px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Cache Hit:{\" \"}\n </span>\n <span style={{ color: cacheColor, fontWeight: \"bold\" }}>\n {(metrics.cacheHitRate * 100).toFixed(1)}%\n </span>\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT),\n fontSize: \"9px\",\n marginLeft: \"4px\",\n }}\n >\n (target: &gt;90%)\n </span>\n </div>\n <div style={{ marginBottom: \"8px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Cached:{\" \"}\n </span>\n <span>{metrics.cacheEntries} keyframes</span>\n </div>\n\n <div\n style={{\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginBottom: \"6px\",\n }}\n />\n\n {/* Object Pools */}\n <div\n style={{\n fontSize: \"10px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: \"4px\",\n }}\n >\n Object Pools (available)\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Euler:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.euler, 100) }}>\n {pools.euler}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Vector3:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.vector3, 100) }}>\n {pools.vector3}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Matrix4:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.matrix4, 50) }}>\n {pools.matrix4}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Quaternion:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.quaternion, 50) }}>\n {pools.quaternion}\n </span>\n </div>\n\n {/* Performance Status */}\n <div\n style={{\n borderTop: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginTop: \"8px\",\n paddingTop: \"6px\",\n }}\n >\n <div style={{ fontSize: \"10px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Status:{\" \"}\n </span>\n {metrics.avgFrameTime < 5 && metrics.cacheHitRate > 0.9 ? (\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN),\n fontWeight: \"bold\",\n }}\n >\n ✓ OPTIMAL\n </span>\n ) : metrics.avgFrameTime < 8 && metrics.cacheHitRate > 0.7 ? (\n <span\n style={{ color: hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) }}\n >\n ⚠ GOOD\n </span>\n ) : (\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.ACCENT_RED) }}>\n ✗ NEEDS OPTIMIZATION\n </span>\n )}\n </div>\n </div>\n </div>\n );\n});\n\nPerformanceDebugOverlayHtml.displayName = \"PerformanceDebugOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,IAAa,8BAA8B,MAAM,WAAW;CAC1D,MAAM,CAAC,SAAS,cAAc,SAA6B;EACzD,cAAc;EACd,cAAc;EACd,cAAc;EACd,cAAc;EACf,CAAC;CACF,MAAM,CAAC,OAAO,YAAY,SAAqB;EAC7C,OAAO;EACP,SAAS;EACT,SAAS;EACT,YAAY;EACb,CAAC;CAEF,gBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,MAAM,aAAa,mBAAmB,YAAY;GAClD,WAAW;IACT,cAAc,WAAW;IACzB,cAAc,WAAW;IACzB,cAAc,WAAW;IACzB,cAAc,WAAW;IAC1B,CAAC;GACF,SAAS,iBAAiB,WAAW,CAAC;KACrC,IAAK;EAER,aAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGJ,OAAO;EAqMT;AAEF,4BAA4B,cAAc"}
1
+ {"version":3,"file":"PerformanceDebugOverlayHtml.js","names":[],"sources":["../../../../src/components/shared/debug/PerformanceDebugOverlayHtml.tsx"],"sourcesContent":["/**\n * Performance Debug Overlay\n *\n * Shows real-time animation performance metrics in development mode.\n * Displays frame times, cache hit rate, and object pool status.\n *\n * Only visible in development mode.\n *\n * @module components/shared/debug/PerformanceDebugOverlayHtml\n * @category Debug\n * @korean 성능디버그오버레이\n */\n\nimport React, { useEffect, useState } from \"react\";\nimport { performanceMonitor } from \"../../../systems/animation\";\nimport { ThreeObjectPools } from \"../../../utils/threeObjectPool\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../utils/colorUtils\";\n\n/**\n * Performance metrics interface\n * @korean 성능지표\n */\ninterface PerformanceMetrics {\n avgFrameTime: number;\n maxFrameTime: number;\n cacheHitRate: number;\n cacheEntries: number;\n}\n\n/**\n * Pool status interface\n * @korean 풀상태\n */\ninterface PoolStatus {\n euler: number;\n vector3: number;\n matrix4: number;\n quaternion: number;\n}\n\n/**\n * Performance Debug Overlay Component\n *\n * Shows real-time animation performance metrics:\n * - Average/max frame times\n * - Cache hit rate\n * - Object pool utilization\n *\n * Only renders in development mode.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized to prevent parent re-renders from affecting it\n * - Internal state updates only via interval\n *\n * @returns Performance overlay or null in production\n * @korean 성능디버그오버레이컴포넌트\n */\nexport const PerformanceDebugOverlayHtml = React.memo(() => {\n const [metrics, setMetrics] = useState<PerformanceMetrics>({\n avgFrameTime: 0,\n maxFrameTime: 0,\n cacheHitRate: 0,\n cacheEntries: 0,\n });\n const [pools, setPools] = useState<PoolStatus>({\n euler: 0,\n vector3: 0,\n matrix4: 0,\n quaternion: 0,\n });\n\n useEffect(() => {\n const interval = setInterval(() => {\n const newMetrics = performanceMonitor.getMetrics();\n setMetrics({\n avgFrameTime: newMetrics.avgFrameTime,\n maxFrameTime: newMetrics.maxFrameTime,\n cacheHitRate: newMetrics.cacheHitRate,\n cacheEntries: newMetrics.cacheEntries,\n });\n setPools(ThreeObjectPools.getStatus());\n }, 1000);\n\n return () => clearInterval(interval);\n }, []);\n\n if (import.meta.env.PROD) {\n return null;\n }\n\n const frameTimeColor =\n metrics.avgFrameTime < 5\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) // Green: Target met\n : metrics.avgFrameTime < 8\n ? hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) // Yellow: Warning\n : hexColorToCSS(KOREAN_COLORS.ACCENT_RED); // Red: Critical\n\n const cacheColor =\n metrics.cacheHitRate > 0.9\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) // Green: Excellent\n : metrics.cacheHitRate > 0.7\n ? hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) // Yellow: Good\n : hexColorToCSS(KOREAN_COLORS.ACCENT_RED); // Red: Poor\n\n const poolColor = (available: number, threshold: number) =>\n available > threshold\n ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN)\n : hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW);\n\n return (\n <div\n style={{\n position: \"fixed\",\n top: 10,\n right: 10,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n color: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n padding: \"12px\",\n fontFamily: \"monospace\",\n fontSize: \"11px\",\n lineHeight: \"1.4\",\n zIndex: 9999,\n border: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"4px\",\n minWidth: \"200px\",\n userSelect: \"none\",\n pointerEvents: \"none\",\n }}\n data-testid=\"performance-debug-overlay\"\n >\n <div\n style={{\n fontWeight: \"bold\",\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n 🎯 Animation Performance\n </div>\n <div\n style={{\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginBottom: \"6px\",\n }}\n />\n\n {/* Frame Times */}\n <div style={{ marginBottom: \"4px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Avg Frame:{\" \"}\n </span>\n <span style={{ color: frameTimeColor, fontWeight: \"bold\" }}>\n {metrics.avgFrameTime.toFixed(2)}ms\n </span>\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT),\n fontSize: \"9px\",\n marginLeft: \"4px\",\n }}\n >\n (target: &lt;5ms)\n </span>\n </div>\n <div style={{ marginBottom: \"6px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Max Frame:{\" \"}\n </span>\n <span style={{ color: frameTimeColor }}>\n {metrics.maxFrameTime.toFixed(2)}ms\n </span>\n </div>\n\n {/* Cache Performance */}\n <div style={{ marginBottom: \"4px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Cache Hit:{\" \"}\n </span>\n <span style={{ color: cacheColor, fontWeight: \"bold\" }}>\n {(metrics.cacheHitRate * 100).toFixed(1)}%\n </span>\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT),\n fontSize: \"9px\",\n marginLeft: \"4px\",\n }}\n >\n (target: &gt;90%)\n </span>\n </div>\n <div style={{ marginBottom: \"8px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Cached:{\" \"}\n </span>\n <span>{metrics.cacheEntries} keyframes</span>\n </div>\n\n <div\n style={{\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginBottom: \"6px\",\n }}\n />\n\n {/* Object Pools */}\n <div\n style={{\n fontSize: \"10px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: \"4px\",\n }}\n >\n Object Pools (available)\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Euler:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.euler, 100) }}>\n {pools.euler}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Vector3:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.vector3, 100) }}>\n {pools.vector3}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Matrix4:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.matrix4, 50) }}>\n {pools.matrix4}\n </span>\n </div>\n <div style={{ marginBottom: \"2px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Quaternion:{\" \"}\n </span>\n <span style={{ color: poolColor(pools.quaternion, 50) }}>\n {pools.quaternion}\n </span>\n </div>\n\n {/* Performance Status */}\n <div\n style={{\n borderTop: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n marginTop: \"8px\",\n paddingTop: \"6px\",\n }}\n >\n <div style={{ fontSize: \"10px\" }}>\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n Status:{\" \"}\n </span>\n {metrics.avgFrameTime < 5 && metrics.cacheHitRate > 0.9 ? (\n <span\n style={{\n color: hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN),\n fontWeight: \"bold\",\n }}\n >\n ✓ OPTIMAL\n </span>\n ) : metrics.avgFrameTime < 8 && metrics.cacheHitRate > 0.7 ? (\n <span\n style={{ color: hexColorToCSS(KOREAN_COLORS.WARNING_YELLOW) }}\n >\n ⚠ GOOD\n </span>\n ) : (\n <span style={{ color: hexColorToCSS(KOREAN_COLORS.ACCENT_RED) }}>\n ✗ NEEDS OPTIMIZATION\n </span>\n )}\n </div>\n </div>\n </div>\n );\n});\n\nPerformanceDebugOverlayHtml.displayName = \"PerformanceDebugOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,IAAa,8BAA8B,MAAM,WAAW;CAC1D,MAAM,CAAC,SAAS,cAAc,SAA6B;EACzD,cAAc;EACd,cAAc;EACd,cAAc;EACd,cAAc;CAChB,CAAC;CACD,MAAM,CAAC,OAAO,YAAY,SAAqB;EAC7C,OAAO;EACP,SAAS;EACT,SAAS;EACT,YAAY;CACd,CAAC;CAED,gBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,MAAM,aAAa,mBAAmB,WAAW;GACjD,WAAW;IACT,cAAc,WAAW;IACzB,cAAc,WAAW;IACzB,cAAc,WAAW;IACzB,cAAc,WAAW;GAC3B,CAAC;GACD,SAAS,iBAAiB,UAAU,CAAC;EACvC,GAAG,GAAI;EAEP,aAAa,cAAc,QAAQ;CACrC,GAAG,CAAC,CAAC;CAGH,OAAO;AAqMX,CAAC;AAED,4BAA4B,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"ActionButtons.js","names":[],"sources":["../../../../src/components/shared/mobile/ActionButtons.tsx"],"sourcesContent":["/**\n * ActionButtons Component\n *\n * Touch-optimized action buttons for combat (Attack and Block)\n * Provides tactile combat controls with visual feedback and haptic response\n *\n * WCAG 2.1 Level AA Compliance:\n * - ARIA labels for screen readers\n * - Keyboard navigation (Enter, Space)\n * - Visible focus indicators (2px cyan border)\n * - 80x80px and 70x70px touch targets (exceeds 44x44px minimum)\n *\n * @module components/mobile/ActionButtons\n * @category Mobile Controls\n * @korean 액션 버튼\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, {\n useCallback,\n useState,\n useMemo,\n useRef,\n useEffect,\n} from \"react\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { triggerOptimizedHaptic } from \"./HapticController\";\nimport {\n applyOptimizedUpdate,\n createTransformStyle,\n createFilterStyle,\n} from \"./TouchOptimizer\";\nimport { getColorRGB } from \"../../../utils/colorHelpers\";\nimport { handleKeyboardNav, getFocusStyle } from \"../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../types/AccessibilityTypes\";\nimport { useThrottle } from \"../../../hooks/useThrottle\";\n\n/**\n * Event type for button interactions\n */\nexport type ButtonEventType = \"start\" | \"end\";\n\n/**\n * Props for ActionButtons component\n */\nexport interface ActionButtonsProps {\n /** Callback when attack button is pressed */\n readonly onAttack: () => void;\n /** Callback when block button is pressed/released */\n readonly onBlock: (eventType: ButtonEventType) => void;\n /** Whether buttons are disabled */\n readonly disabled?: boolean;\n /** Position from bottom in pixels (default: 34 for safe area) */\n readonly bottom?: number;\n /** Position from right in pixels (default: 20) */\n readonly right?: number;\n /** Opacity of buttons (default: 0.8) */\n readonly opacity?: number;\n}\n\n/**\n * ActionButtons Component\n *\n * Provides two primary combat action buttons:\n * - Attack Button (⚡): Primary combat action, 80x80px\n * - Block Button (🛡️): Defensive action, 70x70px\n *\n * Features:\n * - Touch-optimized with minimum 44x44px targets\n * - Attack button: 80x80px for primary action\n * - Block button: 70x70px for secondary action\n * - Visual feedback on press\n * - Haptic feedback for tactile response\n * - Korean cyberpunk theming\n * - Hold-to-block support\n *\n * Usage in Combat:\n * - Attack: Executes current stance technique\n * - Block: Activates defensive guard (hold for sustained block)\n *\n * @example\n * ```tsx\n * <ActionButtons\n * onAttack={() => executeTechnique()}\n * onBlock={(eventType) => {\n * if (eventType === 'start') {\n * activateBlock();\n * } else {\n * deactivateBlock();\n * }\n * }}\n * disabled={isPaused}\n * />\n * ```\n *\n * @korean 액션버튼\n */\nconst ActionButtonsComponent: React.FC<ActionButtonsProps> = ({\n onAttack,\n onBlock,\n disabled = false,\n bottom = 34,\n right = 20,\n opacity = 0.8,\n}) => {\n const [attackPressed, setAttackPressed] = useState(false);\n const [blockPressed, setBlockPressed] = useState(false);\n const [attackFocused, setAttackFocused] = useState(false);\n const [blockFocused, setBlockFocused] = useState(false);\n\n const attackButtonRef = useRef<HTMLButtonElement>(null);\n const blockButtonRef = useRef<HTMLButtonElement>(null);\n\n const throttledOnAttack = useThrottle(onAttack, 16);\n const throttledOnBlock = useThrottle(onBlock, 16);\n\n /**\n * Handle attack button press with optimized latency (<16ms)\n * Uses direct DOM manipulation for immediate visual feedback\n */\n const handleAttackStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n attackButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(true, 0.95);\n element.style.filter = createFilterStyle(true, 1.2);\n },\n () => {\n setAttackPressed(true);\n throttledOnAttack();\n triggerOptimizedHaptic(\"medium\");\n },\n );\n },\n [disabled, throttledOnAttack],\n );\n\n /**\n * Handle attack button release with optimized latency\n */\n const handleAttackEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n attackButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(false);\n element.style.filter = createFilterStyle(false);\n },\n () => {\n setAttackPressed(false);\n },\n );\n },\n [disabled],\n );\n\n /**\n * Handle block button press with optimized latency (<16ms)\n */\n const handleBlockStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n blockButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(true, 0.95);\n element.style.filter = createFilterStyle(true, 1.2);\n },\n () => {\n setBlockPressed(true);\n throttledOnBlock(\"start\");\n triggerOptimizedHaptic(\"light\");\n },\n );\n },\n [disabled, throttledOnBlock],\n );\n\n /**\n * Handle block button release with optimized latency\n */\n const handleBlockEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n blockButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(false);\n element.style.filter = createFilterStyle(false);\n },\n () => {\n setBlockPressed(false);\n throttledOnBlock(\"end\");\n },\n );\n },\n [disabled, throttledOnBlock],\n );\n\n /**\n * Cleanup on unmount - reset any pending visual states.\n * Note: Captures button refs at effect creation time to avoid stale closures.\n * If the buttons unmount before cleanup runs, the captured variables will still\n * reference the original DOM elements (now potentially detached), so style changes\n * are harmless but may not be visible. The null checks primarily guard against\n * refs that were never set in the first place.\n */\n useEffect(() => {\n const attackButton = attackButtonRef.current;\n const blockButton = blockButtonRef.current;\n\n return () => {\n if (attackButton) {\n attackButton.style.transform = createTransformStyle(false);\n attackButton.style.filter = createFilterStyle(false);\n }\n if (blockButton) {\n blockButton.style.transform = createTransformStyle(false);\n blockButton.style.filter = createFilterStyle(false);\n }\n };\n }, []);\n\n /**\n * Handle keyboard navigation for attack button\n */\n const handleAttackKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setAttackPressed(true);\n onAttack();\n triggerOptimizedHaptic(\"medium\");\n setTimeout(() => setAttackPressed(false), 150);\n },\n });\n },\n [disabled, onAttack],\n );\n\n /**\n * Handle keyboard navigation for block button\n */\n const handleBlockKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setBlockPressed(true);\n onBlock(\"start\");\n triggerOptimizedHaptic(\"light\");\n setTimeout(() => {\n setBlockPressed(false);\n onBlock(\"end\");\n }, 150);\n },\n });\n },\n [disabled, onBlock],\n );\n\n const colors = useMemo(\n () => ({\n gold: getColorRGB(KOREAN_COLORS.ACCENT_GOLD),\n blue: getColorRGB(KOREAN_COLORS.ACCENT_BLUE),\n primary: getColorRGB(KOREAN_COLORS.PRIMARY_CYAN),\n }),\n [],\n );\n\n return (\n <Html fullscreen>\n <div\n style={{\n position: \"absolute\",\n bottom: `${bottom}px`,\n right: `${right}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"10px\",\n opacity: disabled ? 0.3 : opacity,\n pointerEvents: disabled ? \"none\" : \"auto\",\n }}\n data-testid=\"action-buttons\"\n >\n {/* Primary Attack Button */}\n <button\n ref={attackButtonRef}\n onTouchStart={handleAttackStart}\n onTouchEnd={handleAttackEnd}\n onMouseDown={handleAttackStart}\n onMouseUp={handleAttackEnd}\n onMouseLeave={handleAttackEnd}\n onKeyDown={handleAttackKeyDown}\n onFocus={() => setAttackFocused(true)}\n onBlur={() => setAttackFocused(false)}\n style={{\n width: \"80px\",\n height: \"80px\",\n borderRadius: \"50%\",\n background: attackPressed\n ? `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1)`\n : `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.9)`,\n border: \"3px solid #fff\",\n fontSize: \"28px\",\n color: \"#000\",\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.1s ease-out, filter 0.1s ease-out\",\n transform: createTransformStyle(attackPressed, 0.95),\n filter: createFilterStyle(attackPressed, 1.2),\n willChange: \"transform, filter\", // GPU hint\n boxShadow: attackPressed\n ? `0 0 25px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1), inset 0 4px 8px rgba(0, 0, 0, 0.3)`\n : `0 4px 12px rgba(0, 0, 0, 0.5), 0 0 15px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.6)`,\n ...getFocusStyle(attackFocused, {\n outlineWidth: 3,\n boxShadow: `0 0 0 4px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.5), 0 0 25px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1)`,\n }),\n }}\n disabled={disabled}\n aria-label={createBilingualLabel(\"공격\", \"Attack\").label}\n aria-pressed={attackPressed}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n data-testid=\"attack-button\"\n >\n ⚡\n </button>\n\n {/* Block Button */}\n <button\n ref={blockButtonRef}\n onTouchStart={handleBlockStart}\n onTouchEnd={handleBlockEnd}\n onMouseDown={handleBlockStart}\n onMouseUp={handleBlockEnd}\n onMouseLeave={handleBlockEnd}\n onKeyDown={handleBlockKeyDown}\n onFocus={() => setBlockFocused(true)}\n onBlur={() => setBlockFocused(false)}\n style={{\n width: \"70px\",\n height: \"70px\",\n borderRadius: \"50%\",\n background: blockPressed\n ? `rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1)`\n : `rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 0.9)`,\n border: \"2px solid #fff\",\n fontSize: \"24px\",\n color: \"#fff\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.1s ease-out, filter 0.1s ease-out\",\n transform: createTransformStyle(blockPressed, 0.95),\n filter: createFilterStyle(blockPressed, 1.2),\n willChange: \"transform, filter\", // GPU hint\n boxShadow: blockPressed\n ? `0 0 20px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1), inset 0 4px 8px rgba(0, 0, 0, 0.3)`\n : `0 4px 10px rgba(0, 0, 0, 0.5), 0 0 12px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 0.6)`,\n ...getFocusStyle(blockFocused, {\n outlineWidth: 3,\n boxShadow: `0 0 0 4px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.5), 0 0 20px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1)`,\n }),\n }}\n disabled={disabled}\n aria-label={createBilingualLabel(\"방어\", \"Block\").label}\n aria-pressed={blockPressed}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n data-testid=\"block-button\"\n >\n 🛡️\n </button>\n\n {/* Button Labels (Korean + English) */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n alignItems: \"center\",\n fontSize: \"10px\",\n color: `rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.9)`,\n textShadow: \"0 1px 3px rgba(0, 0, 0, 0.8)\",\n fontWeight: \"bold\",\n marginTop: \"4px\",\n }}\n >\n <span>공격 | Attack</span>\n <span style={{ fontSize: \"9px\" }}>방어 | Block</span>\n </div>\n </div>\n </Html>\n );\n};\n\n/**\n * Memoized ActionButtons with custom comparison\n * Only re-renders when props change\n */\nexport const ActionButtons = React.memo(\n ActionButtonsComponent,\n (prevProps, nextProps) => {\n return (\n prevProps.disabled === nextProps.disabled &&\n prevProps.bottom === nextProps.bottom &&\n prevProps.right === nextProps.right &&\n prevProps.opacity === nextProps.opacity &&\n prevProps.onAttack === nextProps.onAttack &&\n prevProps.onBlock === nextProps.onBlock\n );\n },\n);\n\nActionButtons.displayName = \"ActionButtons\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActionButtons.js","names":[],"sources":["../../../../src/components/shared/mobile/ActionButtons.tsx"],"sourcesContent":["/**\n * ActionButtons Component\n *\n * Touch-optimized action buttons for combat (Attack and Block)\n * Provides tactile combat controls with visual feedback and haptic response\n *\n * WCAG 2.1 Level AA Compliance:\n * - ARIA labels for screen readers\n * - Keyboard navigation (Enter, Space)\n * - Visible focus indicators (2px cyan border)\n * - 80x80px and 70x70px touch targets (exceeds 44x44px minimum)\n *\n * @module components/mobile/ActionButtons\n * @category Mobile Controls\n * @korean 액션 버튼\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, {\n useCallback,\n useState,\n useMemo,\n useRef,\n useEffect,\n} from \"react\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { triggerOptimizedHaptic } from \"./HapticController\";\nimport {\n applyOptimizedUpdate,\n createTransformStyle,\n createFilterStyle,\n} from \"./TouchOptimizer\";\nimport { getColorRGB } from \"../../../utils/colorHelpers\";\nimport { handleKeyboardNav, getFocusStyle } from \"../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../types/AccessibilityTypes\";\nimport { useThrottle } from \"../../../hooks/useThrottle\";\n\n/**\n * Event type for button interactions\n */\nexport type ButtonEventType = \"start\" | \"end\";\n\n/**\n * Props for ActionButtons component\n */\nexport interface ActionButtonsProps {\n /** Callback when attack button is pressed */\n readonly onAttack: () => void;\n /** Callback when block button is pressed/released */\n readonly onBlock: (eventType: ButtonEventType) => void;\n /** Whether buttons are disabled */\n readonly disabled?: boolean;\n /** Position from bottom in pixels (default: 34 for safe area) */\n readonly bottom?: number;\n /** Position from right in pixels (default: 20) */\n readonly right?: number;\n /** Opacity of buttons (default: 0.8) */\n readonly opacity?: number;\n}\n\n/**\n * ActionButtons Component\n *\n * Provides two primary combat action buttons:\n * - Attack Button (⚡): Primary combat action, 80x80px\n * - Block Button (🛡️): Defensive action, 70x70px\n *\n * Features:\n * - Touch-optimized with minimum 44x44px targets\n * - Attack button: 80x80px for primary action\n * - Block button: 70x70px for secondary action\n * - Visual feedback on press\n * - Haptic feedback for tactile response\n * - Korean cyberpunk theming\n * - Hold-to-block support\n *\n * Usage in Combat:\n * - Attack: Executes current stance technique\n * - Block: Activates defensive guard (hold for sustained block)\n *\n * @example\n * ```tsx\n * <ActionButtons\n * onAttack={() => executeTechnique()}\n * onBlock={(eventType) => {\n * if (eventType === 'start') {\n * activateBlock();\n * } else {\n * deactivateBlock();\n * }\n * }}\n * disabled={isPaused}\n * />\n * ```\n *\n * @korean 액션버튼\n */\nconst ActionButtonsComponent: React.FC<ActionButtonsProps> = ({\n onAttack,\n onBlock,\n disabled = false,\n bottom = 34,\n right = 20,\n opacity = 0.8,\n}) => {\n const [attackPressed, setAttackPressed] = useState(false);\n const [blockPressed, setBlockPressed] = useState(false);\n const [attackFocused, setAttackFocused] = useState(false);\n const [blockFocused, setBlockFocused] = useState(false);\n\n const attackButtonRef = useRef<HTMLButtonElement>(null);\n const blockButtonRef = useRef<HTMLButtonElement>(null);\n\n const throttledOnAttack = useThrottle(onAttack, 16);\n const throttledOnBlock = useThrottle(onBlock, 16);\n\n /**\n * Handle attack button press with optimized latency (<16ms)\n * Uses direct DOM manipulation for immediate visual feedback\n */\n const handleAttackStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n attackButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(true, 0.95);\n element.style.filter = createFilterStyle(true, 1.2);\n },\n () => {\n setAttackPressed(true);\n throttledOnAttack();\n triggerOptimizedHaptic(\"medium\");\n },\n );\n },\n [disabled, throttledOnAttack],\n );\n\n /**\n * Handle attack button release with optimized latency\n */\n const handleAttackEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n attackButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(false);\n element.style.filter = createFilterStyle(false);\n },\n () => {\n setAttackPressed(false);\n },\n );\n },\n [disabled],\n );\n\n /**\n * Handle block button press with optimized latency (<16ms)\n */\n const handleBlockStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n blockButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(true, 0.95);\n element.style.filter = createFilterStyle(true, 1.2);\n },\n () => {\n setBlockPressed(true);\n throttledOnBlock(\"start\");\n triggerOptimizedHaptic(\"light\");\n },\n );\n },\n [disabled, throttledOnBlock],\n );\n\n /**\n * Handle block button release with optimized latency\n */\n const handleBlockEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n applyOptimizedUpdate(\n blockButtonRef.current,\n (element) => {\n element.style.transform = createTransformStyle(false);\n element.style.filter = createFilterStyle(false);\n },\n () => {\n setBlockPressed(false);\n throttledOnBlock(\"end\");\n },\n );\n },\n [disabled, throttledOnBlock],\n );\n\n /**\n * Cleanup on unmount - reset any pending visual states.\n * Note: Captures button refs at effect creation time to avoid stale closures.\n * If the buttons unmount before cleanup runs, the captured variables will still\n * reference the original DOM elements (now potentially detached), so style changes\n * are harmless but may not be visible. The null checks primarily guard against\n * refs that were never set in the first place.\n */\n useEffect(() => {\n const attackButton = attackButtonRef.current;\n const blockButton = blockButtonRef.current;\n\n return () => {\n if (attackButton) {\n attackButton.style.transform = createTransformStyle(false);\n attackButton.style.filter = createFilterStyle(false);\n }\n if (blockButton) {\n blockButton.style.transform = createTransformStyle(false);\n blockButton.style.filter = createFilterStyle(false);\n }\n };\n }, []);\n\n /**\n * Handle keyboard navigation for attack button\n */\n const handleAttackKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setAttackPressed(true);\n onAttack();\n triggerOptimizedHaptic(\"medium\");\n setTimeout(() => setAttackPressed(false), 150);\n },\n });\n },\n [disabled, onAttack],\n );\n\n /**\n * Handle keyboard navigation for block button\n */\n const handleBlockKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setBlockPressed(true);\n onBlock(\"start\");\n triggerOptimizedHaptic(\"light\");\n setTimeout(() => {\n setBlockPressed(false);\n onBlock(\"end\");\n }, 150);\n },\n });\n },\n [disabled, onBlock],\n );\n\n const colors = useMemo(\n () => ({\n gold: getColorRGB(KOREAN_COLORS.ACCENT_GOLD),\n blue: getColorRGB(KOREAN_COLORS.ACCENT_BLUE),\n primary: getColorRGB(KOREAN_COLORS.PRIMARY_CYAN),\n }),\n [],\n );\n\n return (\n <Html fullscreen>\n <div\n style={{\n position: \"absolute\",\n bottom: `${bottom}px`,\n right: `${right}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"10px\",\n opacity: disabled ? 0.3 : opacity,\n pointerEvents: disabled ? \"none\" : \"auto\",\n }}\n data-testid=\"action-buttons\"\n >\n {/* Primary Attack Button */}\n <button\n ref={attackButtonRef}\n onTouchStart={handleAttackStart}\n onTouchEnd={handleAttackEnd}\n onMouseDown={handleAttackStart}\n onMouseUp={handleAttackEnd}\n onMouseLeave={handleAttackEnd}\n onKeyDown={handleAttackKeyDown}\n onFocus={() => setAttackFocused(true)}\n onBlur={() => setAttackFocused(false)}\n style={{\n width: \"80px\",\n height: \"80px\",\n borderRadius: \"50%\",\n background: attackPressed\n ? `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1)`\n : `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.9)`,\n border: \"3px solid #fff\",\n fontSize: \"28px\",\n color: \"#000\",\n fontWeight: \"bold\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.1s ease-out, filter 0.1s ease-out\",\n transform: createTransformStyle(attackPressed, 0.95),\n filter: createFilterStyle(attackPressed, 1.2),\n willChange: \"transform, filter\", // GPU hint\n boxShadow: attackPressed\n ? `0 0 25px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1), inset 0 4px 8px rgba(0, 0, 0, 0.3)`\n : `0 4px 12px rgba(0, 0, 0, 0.5), 0 0 15px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.6)`,\n ...getFocusStyle(attackFocused, {\n outlineWidth: 3,\n boxShadow: `0 0 0 4px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.5), 0 0 25px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 1)`,\n }),\n }}\n disabled={disabled}\n aria-label={createBilingualLabel(\"공격\", \"Attack\").label}\n aria-pressed={attackPressed}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n data-testid=\"attack-button\"\n >\n ⚡\n </button>\n\n {/* Block Button */}\n <button\n ref={blockButtonRef}\n onTouchStart={handleBlockStart}\n onTouchEnd={handleBlockEnd}\n onMouseDown={handleBlockStart}\n onMouseUp={handleBlockEnd}\n onMouseLeave={handleBlockEnd}\n onKeyDown={handleBlockKeyDown}\n onFocus={() => setBlockFocused(true)}\n onBlur={() => setBlockFocused(false)}\n style={{\n width: \"70px\",\n height: \"70px\",\n borderRadius: \"50%\",\n background: blockPressed\n ? `rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1)`\n : `rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 0.9)`,\n border: \"2px solid #fff\",\n fontSize: \"24px\",\n color: \"#fff\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.1s ease-out, filter 0.1s ease-out\",\n transform: createTransformStyle(blockPressed, 0.95),\n filter: createFilterStyle(blockPressed, 1.2),\n willChange: \"transform, filter\", // GPU hint\n boxShadow: blockPressed\n ? `0 0 20px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1), inset 0 4px 8px rgba(0, 0, 0, 0.3)`\n : `0 4px 10px rgba(0, 0, 0, 0.5), 0 0 12px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 0.6)`,\n ...getFocusStyle(blockFocused, {\n outlineWidth: 3,\n boxShadow: `0 0 0 4px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.5), 0 0 20px rgba(${colors.blue.r}, ${colors.blue.g}, ${colors.blue.b}, 1)`,\n }),\n }}\n disabled={disabled}\n aria-label={createBilingualLabel(\"방어\", \"Block\").label}\n aria-pressed={blockPressed}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n data-testid=\"block-button\"\n >\n 🛡️\n </button>\n\n {/* Button Labels (Korean + English) */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n alignItems: \"center\",\n fontSize: \"10px\",\n color: `rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.9)`,\n textShadow: \"0 1px 3px rgba(0, 0, 0, 0.8)\",\n fontWeight: \"bold\",\n marginTop: \"4px\",\n }}\n >\n <span>공격 | Attack</span>\n <span style={{ fontSize: \"9px\" }}>방어 | Block</span>\n </div>\n </div>\n </Html>\n );\n};\n\n/**\n * Memoized ActionButtons with custom comparison\n * Only re-renders when props change\n */\nexport const ActionButtons = React.memo(\n ActionButtonsComponent,\n (prevProps, nextProps) => {\n return (\n prevProps.disabled === nextProps.disabled &&\n prevProps.bottom === nextProps.bottom &&\n prevProps.right === nextProps.right &&\n prevProps.opacity === nextProps.opacity &&\n prevProps.onAttack === nextProps.onAttack &&\n prevProps.onBlock === nextProps.onBlock\n );\n },\n);\n\nActionButtons.displayName = \"ActionButtons\";\n"],"mappings":"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"}