blacktrigram 0.7.45 → 0.7.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +22 -11
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  21. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  22. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  31. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  32. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  36. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  44. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  45. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  49. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  50. package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
  51. package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
  52. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  53. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  56. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  57. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  58. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  59. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  60. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  61. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  62. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  63. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  64. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  65. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  66. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  67. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  68. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  69. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  74. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  75. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  76. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  78. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  79. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  84. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  87. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  88. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  89. package/lib/components/screens/training/TrainingScreen3D.js +1 -0
  90. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  91. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  93. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  94. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  96. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  97. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  99. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  100. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  101. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  102. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  103. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  104. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  105. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  106. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  107. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  108. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  109. package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
  110. package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
  111. package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
  112. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  113. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  114. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  115. package/lib/components/shared/base/BaseButton.js.map +1 -1
  116. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  117. package/lib/components/shared/base/BasePanel.js.map +1 -1
  118. package/lib/components/shared/base/BaseText.js.map +1 -1
  119. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  120. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  121. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  122. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  123. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  124. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  125. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  126. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  127. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  128. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  129. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  130. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  131. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  132. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  133. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  134. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  135. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  136. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  137. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  138. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  139. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  140. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  141. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  142. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  143. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  144. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  145. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  146. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  147. package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
  148. package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
  149. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  150. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  151. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  152. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  153. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  154. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  155. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  156. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  157. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  158. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  160. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  161. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  162. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  163. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  164. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  165. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  166. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  167. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  168. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  169. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  170. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  171. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  172. package/lib/components/shared/ui/BackButton.js.map +1 -1
  173. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  174. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  175. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  176. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  177. package/lib/components/shared/ui/SplashScreen.js +2 -2
  178. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  179. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  180. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  181. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  182. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  183. package/lib/constants/bodyDimensions.js.map +1 -1
  184. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  185. package/lib/data/archetypeClothing.js.map +1 -1
  186. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  187. package/lib/data/techniqueMappings.js.map +1 -1
  188. package/lib/data/techniques.js.map +1 -1
  189. package/lib/hooks/useActionFeedback.js.map +1 -1
  190. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  191. package/lib/hooks/useCombatTimer.js.map +1 -1
  192. package/lib/hooks/useDebounce.js.map +1 -1
  193. package/lib/hooks/useHUDLayout.js.map +1 -1
  194. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  195. package/lib/hooks/useKeyboardControls.js.map +1 -1
  196. package/lib/hooks/useMatchCountdown.js.map +1 -1
  197. package/lib/hooks/useMuscleActivation.js.map +1 -1
  198. package/lib/hooks/usePauseMenu.js.map +1 -1
  199. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  200. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  201. package/lib/hooks/useRoundTransition.js.map +1 -1
  202. package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
  203. package/lib/hooks/useSkeletalAnimation.js +1 -1
  204. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  205. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  206. package/lib/hooks/useThrottle.js.map +1 -1
  207. package/lib/hooks/useTouchControls.js.map +1 -1
  208. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  209. package/lib/hooks/useWindowSize.js.map +1 -1
  210. package/lib/systems/CombatSystem.js.map +1 -1
  211. package/lib/systems/EffectCalculator.js.map +1 -1
  212. package/lib/systems/LayoutSystem.js.map +1 -1
  213. package/lib/systems/PlayerEffectManager.js.map +1 -1
  214. package/lib/systems/ResponsiveScaling.js.map +1 -1
  215. package/lib/systems/TrigramSystem.js.map +1 -1
  216. package/lib/systems/VitalPointSystem.js.map +1 -1
  217. package/lib/systems/ai/AIPersonality.js.map +1 -1
  218. package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
  219. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  220. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  221. package/lib/systems/ai/ComboSystem.js.map +1 -1
  222. package/lib/systems/ai/DecisionTree.js.map +1 -1
  223. package/lib/systems/ai/TrainingAI.js.map +1 -1
  224. package/lib/systems/ai/types.js.map +1 -1
  225. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  226. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  227. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  228. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  229. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  230. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
  231. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
  232. package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
  233. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  234. package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
  235. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  236. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
  237. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
  238. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
  239. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  240. package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
  241. package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
  242. package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
  243. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  244. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  245. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  246. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  247. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  248. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  249. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  250. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  251. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  252. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  253. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  254. package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
  255. package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
  256. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  257. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  258. package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
  259. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
  260. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
  261. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  262. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  263. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  264. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  265. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
  266. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
  267. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
  268. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
  269. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  270. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  271. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  272. package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
  273. package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
  274. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  275. package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
  276. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  277. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  278. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  279. package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
  280. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  281. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  282. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  283. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  284. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  285. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  286. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  287. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  288. package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
  289. package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
  290. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  291. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  292. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  293. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  294. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  295. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  296. package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
  297. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  298. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  299. package/lib/systems/animation/core/AnimationPriority.js +15 -15
  300. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  301. package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
  302. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  303. package/lib/systems/animation/core/AnimationRegistry.js +74 -12
  304. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  305. package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
  306. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  307. package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
  308. package/lib/systems/animation/core/AnimationTransitions.js +34 -0
  309. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  310. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  311. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  312. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  313. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  314. package/lib/systems/animation/core/index.d.ts +1 -1
  315. package/lib/systems/animation/core/index.d.ts.map +1 -1
  316. package/lib/systems/animation/core/types.d.ts +24 -0
  317. package/lib/systems/animation/core/types.d.ts.map +1 -1
  318. package/lib/systems/animation/core/types.js +27 -11
  319. package/lib/systems/animation/core/types.js.map +1 -1
  320. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  321. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  322. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  323. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  324. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  325. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  326. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  327. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  328. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  329. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  330. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  331. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  332. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  333. package/lib/systems/bodypart/types.js.map +1 -1
  334. package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
  335. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  336. package/lib/systems/breathing/feedback.js.map +1 -1
  337. package/lib/systems/breathing/integration.js.map +1 -1
  338. package/lib/systems/combat/BalanceSystem.js +19 -19
  339. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  340. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  341. package/lib/systems/combat/CombatStateSystem.js +17 -17
  342. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  343. package/lib/systems/combat/ConsciousnessSystem.js +24 -24
  344. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  345. package/lib/systems/combat/FallIntegration.js.map +1 -1
  346. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  347. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  348. package/lib/systems/combat/PainResponseSystem.js +21 -21
  349. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  350. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  351. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  352. package/lib/systems/combat/typeGuards.js.map +1 -1
  353. package/lib/systems/effects.js.map +1 -1
  354. package/lib/systems/game.js.map +1 -1
  355. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  356. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  357. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  358. package/lib/systems/movement/integration.js.map +1 -1
  359. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  360. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  361. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  362. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  363. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  364. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  365. package/lib/systems/physics/SpeedModifierSystem.js +6 -6
  366. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  367. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  368. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  369. package/lib/systems/trigram/StanceManager.js.map +1 -1
  370. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  371. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  372. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  373. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  374. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  375. package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
  376. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  377. package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
  378. package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
  379. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  380. package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
  381. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  382. package/lib/systems/trigram/techniques/index.js.map +1 -1
  383. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  384. package/lib/systems/trigram/types.js.map +1 -1
  385. package/lib/systems/types.js.map +1 -1
  386. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  387. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  388. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  389. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  390. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  391. package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
  392. package/lib/types/AccessibilityTypes.js.map +1 -1
  393. package/lib/types/LayoutTypes.js.map +1 -1
  394. package/lib/types/PhysicsTypes.js.map +1 -1
  395. package/lib/types/common.js.map +1 -1
  396. package/lib/types/constants/animations.js.map +1 -1
  397. package/lib/types/constants/colors.js.map +1 -1
  398. package/lib/types/constants/designSystem.js.map +1 -1
  399. package/lib/types/constants/index.js.map +1 -1
  400. package/lib/types/constants/layout.js.map +1 -1
  401. package/lib/types/constants/performance.js.map +1 -1
  402. package/lib/types/constants/typography.js.map +1 -1
  403. package/lib/types/constants/ui.js.map +1 -1
  404. package/lib/types/facial.js +19 -19
  405. package/lib/types/facial.js.map +1 -1
  406. package/lib/types/hand-animation.js.map +1 -1
  407. package/lib/types/injury.js.map +1 -1
  408. package/lib/types/muscle.js.map +1 -1
  409. package/lib/types/physics.js.map +1 -1
  410. package/lib/types/physicsConstants.js.map +1 -1
  411. package/lib/types/player-visual.d.ts +1 -1
  412. package/lib/types/player-visual.d.ts.map +1 -1
  413. package/lib/types/skeletal.js.map +1 -1
  414. package/lib/types/techniqueId.js.map +1 -1
  415. package/lib/utils/accessibility.js.map +1 -1
  416. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  417. package/lib/utils/assetConfig.js.map +1 -1
  418. package/lib/utils/characterScaling.js.map +1 -1
  419. package/lib/utils/colorHelpers.js.map +1 -1
  420. package/lib/utils/colorUtils.js.map +1 -1
  421. package/lib/utils/combatReadiness.js.map +1 -1
  422. package/lib/utils/controlMapping.js.map +1 -1
  423. package/lib/utils/deviceDetection.js +6 -7
  424. package/lib/utils/deviceDetection.js.map +1 -1
  425. package/lib/utils/effectUtils.js.map +1 -1
  426. package/lib/utils/fabricTextures.js.map +1 -1
  427. package/lib/utils/hapticFeedback.js.map +1 -1
  428. package/lib/utils/haptics.js.map +1 -1
  429. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  430. package/lib/utils/inputSystem.js.map +1 -1
  431. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  432. package/lib/utils/math.js.map +1 -1
  433. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  434. package/lib/utils/mobileUIUtils.js.map +1 -1
  435. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  436. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  437. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  438. package/lib/utils/performanceOptimization.js.map +1 -1
  439. package/lib/utils/player3DHelpers.js.map +1 -1
  440. package/lib/utils/playerUtils.js.map +1 -1
  441. package/lib/utils/responsiveLayout.js.map +1 -1
  442. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  443. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  444. package/lib/utils/safeAreaUtils.js.map +1 -1
  445. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  446. package/lib/utils/skeletonScaling.js.map +1 -1
  447. package/lib/utils/stanceHelpers.js.map +1 -1
  448. package/lib/utils/threeObjectPool.js.map +1 -1
  449. package/lib/utils/visualEffects.js.map +1 -1
  450. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"controlMapping.js","names":[],"sources":["../../src/utils/controlMapping.ts"],"sourcesContent":["/**\n * Control Mapping System for Black Trigram\n * Manages keyboard control bindings with localStorage persistence\n * \n * @module utils/controlMapping\n * @category Input System\n * @korean 콘트롤 매핑 시스템\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { TRIGRAM_STANCES_ORDER } from \"../systems/trigram/types\";\n\n/**\n * Control binding configuration\n * Maps game actions to keyboard keys\n * \n * @category Input System\n * @korean 콘트롤 바인딩\n */\nexport interface ControlBinding {\n /** Stance selection keys (1-8 by default) */\n readonly stances: readonly string[];\n /** Technique execution keys (Q-E-R-T-Y-F-G-Z-X-C by default) */\n readonly techniques: readonly string[];\n /** Attack action key */\n readonly attack: string;\n /** Block/Guard action key */\n readonly block: string;\n /** Movement keys */\n readonly movement: {\n readonly up: string;\n readonly down: string;\n readonly left: string;\n readonly right: string;\n };\n /** Vital points overlay toggle key */\n readonly vitalPointsOverlay: string;\n /** Stance side switch key (swap front foot) */\n readonly stanceSideSwitch?: string;\n /** Pause/Menu keys */\n readonly pause: readonly string[];\n}\n\n/**\n * Storage key for localStorage persistence\n */\nconst STORAGE_KEY = \"blacktrigram_controls\";\n\n/**\n * Default control bindings following game design specifications\n * NEW: Conflict-free control scheme with Q-E-R-T-Y-F-G-Z-X-C for techniques\n * \n * Uses keys around WASD that are easy to reach with left hand\n * No conflicts with movement, browser shortcuts, or function keys\n * \n * All keys stored in lowercase for consistent comparison\n * \n * @see CONTROLS.md for complete documentation\n */\nconst DEFAULT_BINDINGS: ControlBinding = {\n stances: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n techniques: [\"q\", \"e\", \"r\", \"t\", \"y\", \"f\", \"g\", \"z\", \"x\", \"c\"],\n attack: \" \", // Spacebar\n block: \"b\",\n movement: {\n up: \"w\",\n down: \"s\",\n left: \"a\",\n right: \"d\",\n },\n vitalPointsOverlay: \"v\",\n stanceSideSwitch: \"h\", // H key for switching front foot\n pause: [\"escape\", \"m\"],\n};\n\n/**\n * Control Mapper class for managing keyboard bindings\n * Handles loading, saving, and querying control mappings\n * \n * @example\n * ```typescript\n * const mapper = new ControlMapper();\n * \n * // Get stance for key press\n * const stance = mapper.getStanceForKey(\"3\"); // Returns 2 (0-indexed)\n * \n * // Update bindings\n * mapper.saveBindings({\n * ...mapper.getBindings(),\n * stances: ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']\n * });\n * ```\n * \n * @korean 콘트롤매퍼\n */\nexport class ControlMapper {\n private bindings: ControlBinding;\n\n /**\n * Creates a new ControlMapper instance\n * Automatically loads bindings from localStorage\n */\n constructor() {\n this.bindings = this.loadBindings();\n }\n\n /**\n * Load bindings from localStorage\n * Returns default bindings if none exist\n * \n * @returns Control bindings from storage or defaults\n * @korean 바인딩 로드\n */\n loadBindings(): ControlBinding {\n try {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const parsed = JSON.parse(saved) as ControlBinding;\n // Validate loaded bindings\n if (this.validateBindings(parsed)) {\n return parsed;\n }\n }\n } catch (error) {\n console.warn(\"Failed to load control bindings:\", error);\n }\n return this.getDefaultBindings();\n }\n\n /**\n * Save bindings to localStorage\n * \n * @param bindings - Control bindings to save\n * @korean 바인딩 저장\n */\n saveBindings(bindings: ControlBinding): void {\n if (!this.validateBindings(bindings)) {\n console.error(\"Invalid control bindings, not saving\");\n return;\n }\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(bindings));\n this.bindings = bindings;\n } catch (error) {\n console.error(\"Failed to save control bindings:\", error);\n }\n }\n\n /**\n * Get default control bindings\n * \n * @returns Default control configuration\n * @korean 기본 바인딩\n */\n getDefaultBindings(): ControlBinding {\n return { ...DEFAULT_BINDINGS };\n }\n\n /**\n * Get current control bindings\n * \n * @returns Current control configuration\n * @korean 현재 바인딩\n */\n getBindings(): ControlBinding {\n return { ...this.bindings };\n }\n\n /**\n * Reset bindings to default\n * \n * @korean 기본값으로 리셋\n */\n resetToDefaults(): void {\n this.saveBindings(this.getDefaultBindings());\n }\n\n /**\n * Get stance index for a given key\n * Returns null if key is not bound to a stance\n * \n * @param key - Keyboard key to check\n * @returns Stance index (0-7) or null\n * @korean 키에 대한 자세 조회\n */\n getStanceForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.stances.findIndex(\n (k) => k.toLowerCase() === normalizedKey\n );\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for a given stance index\n * \n * @param stanceIndex - Stance index (0-7)\n * @returns Key bound to that stance\n * @korean 자세에 대한 키 조회\n */\n getKeyForStance(stanceIndex: number): string | null {\n if (stanceIndex < 0 || stanceIndex >= this.bindings.stances.length) {\n return null;\n }\n return this.bindings.stances[stanceIndex];\n }\n\n /**\n * Get TrigramStance enum for a given key\n * \n * @param key - Keyboard key to check\n * @returns TrigramStance or null\n * @korean 키에 대한 팔괘 자세\n */\n getTrigramStanceForKey(key: string): TrigramStance | null {\n const index = this.getStanceForKey(key);\n if (index === null) return null;\n return TRIGRAM_STANCES_ORDER[index] ?? null;\n }\n\n /**\n * Get technique index for a key\n * \n * @param key - Keyboard key\n * @returns Technique index (0-9) or null\n * @korean 기술 키 확인\n */\n getTechniqueForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.techniques.indexOf(normalizedKey);\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for technique index\n * \n * @param index - Technique index (0-9)\n * @returns Key or null\n * @korean 기술 인덱스로 키 조회\n */\n getKeyForTechnique(index: number): string | null {\n if (index < 0 || index >= this.bindings.techniques.length) {\n return null;\n }\n return this.bindings.techniques[index];\n }\n\n /**\n * Check if a key is bound to an action\n * \n * @param key - Keyboard key to check\n * @returns Action name or null\n * @korean 키 바인딩 확인\n */\n getActionForKey(key: string): string | null {\n const normalizedKey = key.toLowerCase();\n\n // Check stance keys\n if (this.getStanceForKey(key) !== null) {\n return \"stance\";\n }\n\n // Check other actions\n if (normalizedKey === this.bindings.attack.toLowerCase()) return \"attack\";\n if (normalizedKey === this.bindings.block.toLowerCase()) return \"block\";\n\n // Check movement\n const movement = this.bindings.movement;\n if (normalizedKey === movement.up.toLowerCase()) return \"move_up\";\n if (normalizedKey === movement.down.toLowerCase()) return \"move_down\";\n if (normalizedKey === movement.left.toLowerCase()) return \"move_left\";\n if (normalizedKey === movement.right.toLowerCase()) return \"move_right\";\n\n // Check vital points overlay\n if (normalizedKey === this.bindings.vitalPointsOverlay.toLowerCase())\n return \"vital_points_overlay\";\n\n // Check stance side switch\n if (\n normalizedKey === this.bindings.stanceSideSwitch?.toLowerCase()\n ) {\n return \"stance_side_switch\";\n }\n\n // Check pause keys\n if (\n this.bindings.pause.some((k) => k.toLowerCase() === normalizedKey)\n ) {\n return \"pause\";\n }\n\n return null;\n }\n\n /**\n * Validate control bindings\n * Ensures no duplicate keys and all required keys are present\n * \n * @param bindings - Bindings to validate\n * @returns True if valid\n * @korean 바인딩 검증\n */\n private validateBindings(bindings: ControlBinding): boolean {\n // Check that stances array has exactly 8 entries\n if (\n !bindings.stances ||\n !Array.isArray(bindings.stances) ||\n bindings.stances.length !== 8\n ) {\n return false;\n }\n\n // Check that techniques array has up to 10 entries\n if (\n !bindings.techniques ||\n !Array.isArray(bindings.techniques) ||\n bindings.techniques.length === 0 ||\n bindings.techniques.length > 10\n ) {\n return false;\n }\n\n // Check for duplicate keys\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n const uniqueKeys = new Set(allKeys.map((k) => k.toLowerCase()));\n if (uniqueKeys.size !== allKeys.length) {\n // Duplicate keys found\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if bindings have conflicts\n * Returns array of conflicting keys\n * \n * @param bindings - Bindings to check\n * @returns Array of duplicate keys\n * @korean 바인딩 충돌 확인\n */\n getConflicts(bindings: ControlBinding): string[] {\n const keyCount = new Map<string, number>();\n const conflicts: string[] = [];\n\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n allKeys.forEach((key) => {\n const normalized = key.toLowerCase();\n keyCount.set(normalized, (keyCount.get(normalized) ?? 0) + 1);\n });\n\n keyCount.forEach((count, key) => {\n if (count > 1) {\n conflicts.push(key);\n }\n });\n\n return conflicts;\n }\n}\n"],"mappings":";;;;;AA8CA,IAAM,cAAc;;;;;;;;;;;;AAapB,IAAM,mBAAmC;CACvC,SAAS;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAI;CACjD,YAAY;EAAC;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAK;EAAI;CAC9D,QAAQ;CACR,OAAO;CACP,UAAU;EACR,IAAI;EACJ,MAAM;EACN,MAAM;EACN,OAAO;EACR;CACD,oBAAoB;CACpB,kBAAkB;CAClB,OAAO,CAAC,UAAU,IAAI;CACvB;;;;;;;;;;;;;;;;;;;;;AAsBD,IAAa,gBAAb,MAA2B;CACzB;;;;;CAMA,cAAc;EACZ,KAAK,WAAW,KAAK,cAAc;;;;;;;;;CAUrC,eAA+B;EAC7B,IAAI;GACF,MAAM,QAAQ,aAAa,QAAQ,YAAY;GAC/C,IAAI,OAAO;IACT,MAAM,SAAS,KAAK,MAAM,MAAM;IAEhC,IAAI,KAAK,iBAAiB,OAAO,EAC/B,OAAO;;WAGJ,OAAO;GACd,QAAQ,KAAK,oCAAoC,MAAM;;EAEzD,OAAO,KAAK,oBAAoB;;;;;;;;CASlC,aAAa,UAAgC;EAC3C,IAAI,CAAC,KAAK,iBAAiB,SAAS,EAAE;GACpC,QAAQ,MAAM,uCAAuC;GACrD;;EAGF,IAAI;GACF,aAAa,QAAQ,aAAa,KAAK,UAAU,SAAS,CAAC;GAC3D,KAAK,WAAW;WACT,OAAO;GACd,QAAQ,MAAM,oCAAoC,MAAM;;;;;;;;;CAU5D,qBAAqC;EACnC,OAAO,EAAE,GAAG,kBAAkB;;;;;;;;CAShC,cAA8B;EAC5B,OAAO,EAAE,GAAG,KAAK,UAAU;;;;;;;CAQ7B,kBAAwB;EACtB,KAAK,aAAa,KAAK,oBAAoB,CAAC;;;;;;;;;;CAW9C,gBAAgB,KAA4B;EAC1C,MAAM,gBAAgB,IAAI,aAAa;EACvC,MAAM,QAAQ,KAAK,SAAS,QAAQ,WACjC,MAAM,EAAE,aAAa,KAAK,cAC5B;EACD,OAAO,SAAS,IAAI,QAAQ;;;;;;;;;CAU9B,gBAAgB,aAAoC;EAClD,IAAI,cAAc,KAAK,eAAe,KAAK,SAAS,QAAQ,QAC1D,OAAO;EAET,OAAO,KAAK,SAAS,QAAQ;;;;;;;;;CAU/B,uBAAuB,KAAmC;EACxD,MAAM,QAAQ,KAAK,gBAAgB,IAAI;EACvC,IAAI,UAAU,MAAM,OAAO;EAC3B,OAAO,sBAAsB,UAAU;;;;;;;;;CAUzC,mBAAmB,KAA4B;EAC7C,MAAM,gBAAgB,IAAI,aAAa;EACvC,MAAM,QAAQ,KAAK,SAAS,WAAW,QAAQ,cAAc;EAC7D,OAAO,SAAS,IAAI,QAAQ;;;;;;;;;CAU9B,mBAAmB,OAA8B;EAC/C,IAAI,QAAQ,KAAK,SAAS,KAAK,SAAS,WAAW,QACjD,OAAO;EAET,OAAO,KAAK,SAAS,WAAW;;;;;;;;;CAUlC,gBAAgB,KAA4B;EAC1C,MAAM,gBAAgB,IAAI,aAAa;EAGvC,IAAI,KAAK,gBAAgB,IAAI,KAAK,MAChC,OAAO;EAIT,IAAI,kBAAkB,KAAK,SAAS,OAAO,aAAa,EAAE,OAAO;EACjE,IAAI,kBAAkB,KAAK,SAAS,MAAM,aAAa,EAAE,OAAO;EAGhE,MAAM,WAAW,KAAK,SAAS;EAC/B,IAAI,kBAAkB,SAAS,GAAG,aAAa,EAAE,OAAO;EACxD,IAAI,kBAAkB,SAAS,KAAK,aAAa,EAAE,OAAO;EAC1D,IAAI,kBAAkB,SAAS,KAAK,aAAa,EAAE,OAAO;EAC1D,IAAI,kBAAkB,SAAS,MAAM,aAAa,EAAE,OAAO;EAG3D,IAAI,kBAAkB,KAAK,SAAS,mBAAmB,aAAa,EAClE,OAAO;EAGT,IACE,kBAAkB,KAAK,SAAS,kBAAkB,aAAa,EAE/D,OAAO;EAIT,IACE,KAAK,SAAS,MAAM,MAAM,MAAM,EAAE,aAAa,KAAK,cAAc,EAElE,OAAO;EAGT,OAAO;;;;;;;;;;CAWT,iBAAyB,UAAmC;EAE1D,IACE,CAAC,SAAS,WACV,CAAC,MAAM,QAAQ,SAAS,QAAQ,IAChC,SAAS,QAAQ,WAAW,GAE5B,OAAO;EAIT,IACE,CAAC,SAAS,cACV,CAAC,MAAM,QAAQ,SAAS,WAAW,IACnC,SAAS,WAAW,WAAW,KAC/B,SAAS,WAAW,SAAS,IAE7B,OAAO;EAIT,MAAM,UAAU;GACd,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ,SAAS;GACT,SAAS;GACT,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS;GACT,GAAG,SAAS;GACb;EAGD,IAAI,IADmB,IAAI,QAAQ,KAAK,MAAM,EAAE,aAAa,CAAC,CAC1D,CAAW,SAAS,QAAQ,QAE9B,OAAO;EAGT,OAAO;;;;;;;;;;CAWT,aAAa,UAAoC;EAC/C,MAAM,2BAAW,IAAI,KAAqB;EAC1C,MAAM,YAAsB,EAAE;EAe9B;GAZE,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ,SAAS;GACT,SAAS;GACT,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS,SAAS;GAClB,SAAS;GACT,GAAG,SAAS;GAGd,CAAQ,SAAS,QAAQ;GACvB,MAAM,aAAa,IAAI,aAAa;GACpC,SAAS,IAAI,aAAa,SAAS,IAAI,WAAW,IAAI,KAAK,EAAE;IAC7D;EAEF,SAAS,SAAS,OAAO,QAAQ;GAC/B,IAAI,QAAQ,GACV,UAAU,KAAK,IAAI;IAErB;EAEF,OAAO"}
1
+ {"version":3,"file":"controlMapping.js","names":[],"sources":["../../src/utils/controlMapping.ts"],"sourcesContent":["/**\n * Control Mapping System for Black Trigram\n * Manages keyboard control bindings with localStorage persistence\n * \n * @module utils/controlMapping\n * @category Input System\n * @korean 콘트롤 매핑 시스템\n */\n\nimport { TrigramStance } from \"../types/common\";\nimport { TRIGRAM_STANCES_ORDER } from \"../systems/trigram/types\";\n\n/**\n * Control binding configuration\n * Maps game actions to keyboard keys\n * \n * @category Input System\n * @korean 콘트롤 바인딩\n */\nexport interface ControlBinding {\n /** Stance selection keys (1-8 by default) */\n readonly stances: readonly string[];\n /** Technique execution keys (Q-E-R-T-Y-F-G-Z-X-C by default) */\n readonly techniques: readonly string[];\n /** Attack action key */\n readonly attack: string;\n /** Block/Guard action key */\n readonly block: string;\n /** Movement keys */\n readonly movement: {\n readonly up: string;\n readonly down: string;\n readonly left: string;\n readonly right: string;\n };\n /** Vital points overlay toggle key */\n readonly vitalPointsOverlay: string;\n /** Stance side switch key (swap front foot) */\n readonly stanceSideSwitch?: string;\n /** Pause/Menu keys */\n readonly pause: readonly string[];\n}\n\n/**\n * Storage key for localStorage persistence\n */\nconst STORAGE_KEY = \"blacktrigram_controls\";\n\n/**\n * Default control bindings following game design specifications\n * NEW: Conflict-free control scheme with Q-E-R-T-Y-F-G-Z-X-C for techniques\n * \n * Uses keys around WASD that are easy to reach with left hand\n * No conflicts with movement, browser shortcuts, or function keys\n * \n * All keys stored in lowercase for consistent comparison\n * \n * @see CONTROLS.md for complete documentation\n */\nconst DEFAULT_BINDINGS: ControlBinding = {\n stances: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n techniques: [\"q\", \"e\", \"r\", \"t\", \"y\", \"f\", \"g\", \"z\", \"x\", \"c\"],\n attack: \" \", // Spacebar\n block: \"b\",\n movement: {\n up: \"w\",\n down: \"s\",\n left: \"a\",\n right: \"d\",\n },\n vitalPointsOverlay: \"v\",\n stanceSideSwitch: \"h\", // H key for switching front foot\n pause: [\"escape\", \"m\"],\n};\n\n/**\n * Control Mapper class for managing keyboard bindings\n * Handles loading, saving, and querying control mappings\n * \n * @example\n * ```typescript\n * const mapper = new ControlMapper();\n * \n * // Get stance for key press\n * const stance = mapper.getStanceForKey(\"3\"); // Returns 2 (0-indexed)\n * \n * // Update bindings\n * mapper.saveBindings({\n * ...mapper.getBindings(),\n * stances: ['q', 'w', 'e', 'r', 'a', 's', 'd', 'f']\n * });\n * ```\n * \n * @korean 콘트롤매퍼\n */\nexport class ControlMapper {\n private bindings: ControlBinding;\n\n /**\n * Creates a new ControlMapper instance\n * Automatically loads bindings from localStorage\n */\n constructor() {\n this.bindings = this.loadBindings();\n }\n\n /**\n * Load bindings from localStorage\n * Returns default bindings if none exist\n * \n * @returns Control bindings from storage or defaults\n * @korean 바인딩 로드\n */\n loadBindings(): ControlBinding {\n try {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n const parsed = JSON.parse(saved) as ControlBinding;\n // Validate loaded bindings\n if (this.validateBindings(parsed)) {\n return parsed;\n }\n }\n } catch (error) {\n console.warn(\"Failed to load control bindings:\", error);\n }\n return this.getDefaultBindings();\n }\n\n /**\n * Save bindings to localStorage\n * \n * @param bindings - Control bindings to save\n * @korean 바인딩 저장\n */\n saveBindings(bindings: ControlBinding): void {\n if (!this.validateBindings(bindings)) {\n console.error(\"Invalid control bindings, not saving\");\n return;\n }\n\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(bindings));\n this.bindings = bindings;\n } catch (error) {\n console.error(\"Failed to save control bindings:\", error);\n }\n }\n\n /**\n * Get default control bindings\n * \n * @returns Default control configuration\n * @korean 기본 바인딩\n */\n getDefaultBindings(): ControlBinding {\n return { ...DEFAULT_BINDINGS };\n }\n\n /**\n * Get current control bindings\n * \n * @returns Current control configuration\n * @korean 현재 바인딩\n */\n getBindings(): ControlBinding {\n return { ...this.bindings };\n }\n\n /**\n * Reset bindings to default\n * \n * @korean 기본값으로 리셋\n */\n resetToDefaults(): void {\n this.saveBindings(this.getDefaultBindings());\n }\n\n /**\n * Get stance index for a given key\n * Returns null if key is not bound to a stance\n * \n * @param key - Keyboard key to check\n * @returns Stance index (0-7) or null\n * @korean 키에 대한 자세 조회\n */\n getStanceForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.stances.findIndex(\n (k) => k.toLowerCase() === normalizedKey\n );\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for a given stance index\n * \n * @param stanceIndex - Stance index (0-7)\n * @returns Key bound to that stance\n * @korean 자세에 대한 키 조회\n */\n getKeyForStance(stanceIndex: number): string | null {\n if (stanceIndex < 0 || stanceIndex >= this.bindings.stances.length) {\n return null;\n }\n return this.bindings.stances[stanceIndex];\n }\n\n /**\n * Get TrigramStance enum for a given key\n * \n * @param key - Keyboard key to check\n * @returns TrigramStance or null\n * @korean 키에 대한 팔괘 자세\n */\n getTrigramStanceForKey(key: string): TrigramStance | null {\n const index = this.getStanceForKey(key);\n if (index === null) return null;\n return TRIGRAM_STANCES_ORDER[index] ?? null;\n }\n\n /**\n * Get technique index for a key\n * \n * @param key - Keyboard key\n * @returns Technique index (0-9) or null\n * @korean 기술 키 확인\n */\n getTechniqueForKey(key: string): number | null {\n const normalizedKey = key.toLowerCase();\n const index = this.bindings.techniques.indexOf(normalizedKey);\n return index >= 0 ? index : null;\n }\n\n /**\n * Get key for technique index\n * \n * @param index - Technique index (0-9)\n * @returns Key or null\n * @korean 기술 인덱스로 키 조회\n */\n getKeyForTechnique(index: number): string | null {\n if (index < 0 || index >= this.bindings.techniques.length) {\n return null;\n }\n return this.bindings.techniques[index];\n }\n\n /**\n * Check if a key is bound to an action\n * \n * @param key - Keyboard key to check\n * @returns Action name or null\n * @korean 키 바인딩 확인\n */\n getActionForKey(key: string): string | null {\n const normalizedKey = key.toLowerCase();\n\n // Check stance keys\n if (this.getStanceForKey(key) !== null) {\n return \"stance\";\n }\n\n // Check other actions\n if (normalizedKey === this.bindings.attack.toLowerCase()) return \"attack\";\n if (normalizedKey === this.bindings.block.toLowerCase()) return \"block\";\n\n // Check movement\n const movement = this.bindings.movement;\n if (normalizedKey === movement.up.toLowerCase()) return \"move_up\";\n if (normalizedKey === movement.down.toLowerCase()) return \"move_down\";\n if (normalizedKey === movement.left.toLowerCase()) return \"move_left\";\n if (normalizedKey === movement.right.toLowerCase()) return \"move_right\";\n\n // Check vital points overlay\n if (normalizedKey === this.bindings.vitalPointsOverlay.toLowerCase())\n return \"vital_points_overlay\";\n\n // Check stance side switch\n if (\n normalizedKey === this.bindings.stanceSideSwitch?.toLowerCase()\n ) {\n return \"stance_side_switch\";\n }\n\n // Check pause keys\n if (\n this.bindings.pause.some((k) => k.toLowerCase() === normalizedKey)\n ) {\n return \"pause\";\n }\n\n return null;\n }\n\n /**\n * Validate control bindings\n * Ensures no duplicate keys and all required keys are present\n * \n * @param bindings - Bindings to validate\n * @returns True if valid\n * @korean 바인딩 검증\n */\n private validateBindings(bindings: ControlBinding): boolean {\n // Check that stances array has exactly 8 entries\n if (\n !bindings.stances ||\n !Array.isArray(bindings.stances) ||\n bindings.stances.length !== 8\n ) {\n return false;\n }\n\n // Check that techniques array has up to 10 entries\n if (\n !bindings.techniques ||\n !Array.isArray(bindings.techniques) ||\n bindings.techniques.length === 0 ||\n bindings.techniques.length > 10\n ) {\n return false;\n }\n\n // Check for duplicate keys\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n const uniqueKeys = new Set(allKeys.map((k) => k.toLowerCase()));\n if (uniqueKeys.size !== allKeys.length) {\n // Duplicate keys found\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if bindings have conflicts\n * Returns array of conflicting keys\n * \n * @param bindings - Bindings to check\n * @returns Array of duplicate keys\n * @korean 바인딩 충돌 확인\n */\n getConflicts(bindings: ControlBinding): string[] {\n const keyCount = new Map<string, number>();\n const conflicts: string[] = [];\n\n const allKeys = [\n ...bindings.stances,\n ...bindings.techniques,\n bindings.attack,\n bindings.block,\n bindings.movement.up,\n bindings.movement.down,\n bindings.movement.left,\n bindings.movement.right,\n bindings.vitalPointsOverlay,\n ...bindings.pause,\n ];\n\n allKeys.forEach((key) => {\n const normalized = key.toLowerCase();\n keyCount.set(normalized, (keyCount.get(normalized) ?? 0) + 1);\n });\n\n keyCount.forEach((count, key) => {\n if (count > 1) {\n conflicts.push(key);\n }\n });\n\n return conflicts;\n }\n}\n"],"mappings":"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"}
@@ -190,34 +190,33 @@ function detectPlatform() {
190
190
  let isMobile;
191
191
  let isTablet;
192
192
  if (isMobileUA && !isTabletUA) {
193
- deviceType = DeviceType.MOBILE;
193
+ deviceType = "mobile";
194
194
  isMobile = true;
195
195
  isTablet = false;
196
196
  } else if (isTabletUA) {
197
- deviceType = DeviceType.TABLET;
197
+ deviceType = "tablet";
198
198
  isMobile = false;
199
199
  isTablet = true;
200
200
  } else if (isMobileBySize) {
201
- deviceType = DeviceType.MOBILE;
201
+ deviceType = "mobile";
202
202
  isMobile = true;
203
203
  isTablet = false;
204
204
  } else if (isTabletBySize && hasTouch) {
205
- deviceType = DeviceType.TABLET;
205
+ deviceType = "tablet";
206
206
  isMobile = false;
207
207
  isTablet = true;
208
208
  } else {
209
- deviceType = DeviceType.DESKTOP;
209
+ deviceType = "desktop";
210
210
  isMobile = false;
211
211
  isTablet = false;
212
212
  }
213
- const isDesktop = deviceType === DeviceType.DESKTOP;
214
213
  const result = {
215
214
  os,
216
215
  deviceType,
217
216
  hasTouch,
218
217
  isMobile,
219
218
  isTablet,
220
- isDesktop,
219
+ isDesktop: deviceType === "desktop",
221
220
  screenWidth,
222
221
  screenHeight
223
222
  };
@@ -1 +1 @@
1
- {"version":3,"file":"deviceDetection.js","names":[],"sources":["../../src/utils/deviceDetection.ts"],"sourcesContent":["/**\n * Device Detection Utility\n *\n * Provides robust mobile device detection combining:\n * - User-agent string analysis\n * - Screen size detection\n * - Touch capability detection\n *\n * This ensures mobile controls are shown on all mobile devices,\n * including high-resolution phones that exceed typical mobile breakpoints.\n *\n * @module utils/deviceDetection\n * @category Mobile\n * @korean 기기감지유틸리티\n */\n\n/**\n * Device type classification\n */\nexport enum DeviceType {\n /** Desktop computer or laptop */\n DESKTOP = \"desktop\",\n /** Mobile phone (iOS, Android, etc.) */\n MOBILE = \"mobile\",\n /** Tablet device (iPad, Android tablets) */\n TABLET = \"tablet\",\n}\n\n/**\n * Platform detection results\n */\nexport interface PlatformInfo {\n /** Operating system type */\n readonly os: \"ios\" | \"android\" | \"windows\" | \"macos\" | \"linux\" | \"unknown\";\n /** Device type classification */\n readonly deviceType: DeviceType;\n /** Whether device has touch capability */\n readonly hasTouch: boolean;\n /** Whether device is mobile phone */\n readonly isMobile: boolean;\n /** Whether device is tablet */\n readonly isTablet: boolean;\n /** Whether device is desktop */\n readonly isDesktop: boolean;\n /** Screen width in pixels */\n readonly screenWidth: number;\n /** Screen height in pixels */\n readonly screenHeight: number;\n}\n\n/**\n * Detect if user-agent indicates a mobile device\n * Checks for common mobile device identifiers in user-agent string\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates mobile device\n */\nfunction isMobileUserAgent(userAgent: string): boolean {\n const mobileKeywords = [\n \"Android\",\n \"webOS\",\n \"iPhone\",\n \"iPod\",\n \"BlackBerry\",\n \"IEMobile\",\n \"Opera Mini\",\n \"Mobile\",\n \"mobile\",\n ];\n\n return mobileKeywords.some((keyword) => userAgent.includes(keyword));\n}\n\n/**\n * Detect if user-agent indicates a tablet device\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates tablet\n */\nfunction isTabletUserAgent(userAgent: string): boolean {\n if (userAgent.includes(\"iPad\")) {\n return true;\n }\n\n if (userAgent.includes(\"Android\")) {\n return userAgent.includes(\"Tablet\") || !userAgent.includes(\"Mobile\");\n }\n\n return false;\n}\n\n/**\n * Detect operating system from user-agent\n *\n * @param userAgent - Browser user-agent string\n * @returns Operating system identifier\n */\nfunction detectOS(userAgent: string): PlatformInfo[\"os\"] {\n if (\n userAgent.includes(\"iPhone\") ||\n userAgent.includes(\"iPad\") ||\n userAgent.includes(\"iPod\")\n ) {\n return \"ios\";\n }\n if (userAgent.includes(\"Android\")) {\n return \"android\";\n }\n if (userAgent.includes(\"Windows\")) {\n return \"windows\";\n }\n if (userAgent.includes(\"Mac\")) {\n const isLikelyIPadOSDesktop =\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints === \"number\" &&\n navigator.maxTouchPoints > 1 &&\n userAgent.includes(\"Macintosh\");\n\n if (isLikelyIPadOSDesktop) {\n return \"ios\";\n }\n return \"macos\";\n }\n if (userAgent.includes(\"Linux\")) {\n return \"linux\";\n }\n return \"unknown\";\n}\n\n/**\n * Detect if device has touch capability\n * Uses multiple methods for reliability\n *\n * @returns True if touch is supported\n */\nfunction hasTouchSupport(): boolean {\n if (\"ontouchstart\" in window) {\n return true;\n }\n\n if (\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints !== \"undefined\" &&\n navigator.maxTouchPoints > 0\n ) {\n return true;\n }\n\n if (\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(pointer: coarse)\")?.matches\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Mobile screen size breakpoint\n * Devices with width <= this value are considered mobile by size\n */\nexport const MOBILE_BREAKPOINT = 768;\n\n/**\n * Tablet screen size breakpoint\n * Devices with width > MOBILE_BREAKPOINT and <= TABLET_BREAKPOINT are tablets\n */\nexport const TABLET_BREAKPOINT = 1024;\n\n/**\n * Cached CSS environment variable insets\n */\nlet cachedCSSEnvInsets: { top: number; bottom: number } | null = null;\n\n/**\n * Read CSS environment variables for safe area insets\n * Results are cached as they don't change during a session\n */\nfunction readCSSEnvInsets(): { top: number; bottom: number } | null {\n if (cachedCSSEnvInsets !== null) {\n return cachedCSSEnvInsets;\n }\n\n if (typeof window !== \"undefined\" && typeof getComputedStyle === \"function\") {\n try {\n const root = document.documentElement;\n const style = getComputedStyle(root);\n const topEnv = style.getPropertyValue(\"env(safe-area-inset-top)\");\n const bottomEnv = style.getPropertyValue(\"env(safe-area-inset-bottom)\");\n\n if (topEnv || bottomEnv) {\n const result = {\n top: parseInt(topEnv || \"0\", 10) || 0,\n bottom: parseInt(bottomEnv || \"0\", 10) || 0,\n };\n cachedCSSEnvInsets = result;\n return result;\n }\n } catch {\n // intentional: fall through to null\n }\n }\n\n return null;\n}\n\n/**\n * Cached platform information to avoid re-parsing user-agent on every call\n */\nlet cachedPlatform: PlatformInfo | null = null;\nlet cachedScreenWidth = 0;\nlet cachedScreenHeight = 0;\n\n/**\n * Clear the cached platform information\n * Useful when window is resized or device emulation changes\n * Also clears CSS environment variable cache\n *\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n cachedScreenWidth = 0;\n cachedScreenHeight = 0;\n cachedCSSEnvInsets = null;\n}\n\n/**\n * Detect device type and platform information\n *\n * Combines multiple detection methods for reliability:\n * 1. User-agent string analysis (most reliable for device type)\n * 2. Screen dimensions\n * 3. Touch capability\n *\n * This ensures mobile controls are shown on:\n * - Standard mobile phones (< 768px width)\n * - High-resolution phones (>= 768px width but mobile user-agent)\n * - Android 15/16 devices with 2K/4K resolutions (1200px+, 1440px+)\n * - Tablets (user preference via touch support)\n *\n * **User-agent detection takes priority over screen size**, ensuring that\n * high-end Android phones with desktop-class resolutions (e.g., Galaxy S23 Ultra,\n * Pixel 9 Pro) are correctly identified as mobile devices.\n *\n * Results are cached to avoid re-parsing user-agent on every call.\n * Cache is invalidated when screen dimensions change.\n *\n * @returns Complete platform information\n *\n * @example\n * ```typescript\n * const platform = detectPlatform();\n *\n * if (platform.isMobile) {\n * // Show mobile controls even on 4K Android phones\n * return <MobileControls />;\n * }\n * ```\n *\n * @korean 플랫폼감지\n */\nexport function detectPlatform(): PlatformInfo {\n const userAgent = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n const screenWidth = typeof window !== \"undefined\" ? window.innerWidth : 1920;\n const screenHeight =\n typeof window !== \"undefined\" ? window.innerHeight : 1080;\n\n if (\n cachedPlatform !== null &&\n cachedScreenWidth === screenWidth &&\n cachedScreenHeight === screenHeight\n ) {\n return cachedPlatform;\n }\n\n const os = detectOS(userAgent);\n const hasTouch = hasTouchSupport();\n const isMobileUA = isMobileUserAgent(userAgent);\n const isTabletUA = isTabletUserAgent(userAgent);\n const isMobileBySize = screenWidth <= MOBILE_BREAKPOINT;\n const isTabletBySize =\n screenWidth > MOBILE_BREAKPOINT && screenWidth <= TABLET_BREAKPOINT;\n\n let deviceType: DeviceType;\n let isMobile: boolean;\n let isTablet: boolean;\n\n if (isMobileUA && !isTabletUA) {\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletUA) {\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else if (isMobileBySize) {\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletBySize && hasTouch) {\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else {\n deviceType = DeviceType.DESKTOP;\n isMobile = false;\n isTablet = false;\n }\n\n const isDesktop = deviceType === DeviceType.DESKTOP;\n\n const result: PlatformInfo = {\n os,\n deviceType,\n hasTouch,\n isMobile,\n isTablet,\n isDesktop,\n screenWidth,\n screenHeight,\n };\n\n cachedPlatform = result;\n cachedScreenWidth = screenWidth;\n cachedScreenHeight = screenHeight;\n\n return result;\n}\n\n/**\n * Simple mobile check for backward compatibility\n * Returns true for both mobile phones and tablets\n *\n * @returns True if device is mobile or tablet\n *\n * @korean 모바일확인\n */\nexport function isMobileDevice(): boolean {\n const platform = detectPlatform();\n return platform.isMobile || platform.isTablet;\n}\n\n/**\n * Check if device should use mobile controls\n * Takes into account device type, screen size, and touch capability\n *\n * Uses user-agent detection to correctly identify mobile devices regardless\n * of screen resolution. This ensures high-end Android 15/16 phones with\n * 2K/4K displays (1200px+, 1440px+) show mobile controls.\n *\n * Also ensures tablets and touch-enabled devices always show mobile controls\n * regardless of resolution, for better UX on touch devices.\n *\n * @returns True if mobile controls should be shown\n *\n * @example\n * ```typescript\n * // High-res Android phone (1440x3168) → returns true via user-agent\n * // Desktop with 1440px screen → returns false (no mobile user-agent)\n * // iPad Pro 12.9\" (1024x1366) → returns true (tablet user-agent)\n * // Surface Pro in tablet mode → returns true (touch + tablet size)\n * if (shouldUseMobileControls()) {\n * return <VirtualDPad />; // Touch-optimized controls\n * }\n * ```\n *\n * @korean 모바일컨트롤사용\n */\nexport function shouldUseMobileControls(): boolean {\n const platform = detectPlatform();\n\n if (platform.isMobile) {\n return true;\n }\n\n if (platform.isTablet) {\n return true;\n }\n\n if (platform.hasTouch && platform.screenWidth <= TABLET_BREAKPOINT) {\n return true;\n }\n\n if (platform.screenWidth <= MOBILE_BREAKPOINT && platform.hasTouch) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Get safe area insets for device\n * Returns appropriate values based on device type and OS\n *\n * For iOS devices, attempts to detect if device has a notch by checking\n * screen dimensions. Falls back to CSS environment variables if available.\n *\n * @returns Safe area insets in pixels\n *\n * @korean 안전영역인셋\n */\nexport function getSafeAreaInsets() {\n const platform = detectPlatform();\n\n if (platform.os === \"ios\" && platform.isMobile) {\n const cssEnvInsets = readCSSEnvInsets();\n if (cssEnvInsets) {\n return {\n top: cssEnvInsets.top,\n bottom: cssEnvInsets.bottom,\n left: 0,\n right: 0,\n };\n }\n\n const isLandscape = platform.screenWidth > platform.screenHeight;\n\n const hasNotch =\n platform.screenHeight >= 812 || platform.screenWidth >= 812;\n\n if (hasNotch) {\n if (isLandscape) {\n return {\n top: 0,\n bottom: 21,\n left: 44,\n right: 44,\n };\n } else {\n return {\n top: 44,\n bottom: 34,\n left: 0,\n right: 0,\n };\n }\n } else {\n return {\n top: 20,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n }\n\n if (platform.os === \"android\" && platform.isMobile) {\n return {\n top: 24,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n\n return {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"deviceDetection.js","names":[],"sources":["../../src/utils/deviceDetection.ts"],"sourcesContent":["/**\n * Device Detection Utility\n *\n * Provides robust mobile device detection combining:\n * - User-agent string analysis\n * - Screen size detection\n * - Touch capability detection\n *\n * This ensures mobile controls are shown on all mobile devices,\n * including high-resolution phones that exceed typical mobile breakpoints.\n *\n * @module utils/deviceDetection\n * @category Mobile\n * @korean 기기감지유틸리티\n */\n\n/**\n * Device type classification\n */\nexport enum DeviceType {\n /** Desktop computer or laptop */\n DESKTOP = \"desktop\",\n /** Mobile phone (iOS, Android, etc.) */\n MOBILE = \"mobile\",\n /** Tablet device (iPad, Android tablets) */\n TABLET = \"tablet\",\n}\n\n/**\n * Platform detection results\n */\nexport interface PlatformInfo {\n /** Operating system type */\n readonly os: \"ios\" | \"android\" | \"windows\" | \"macos\" | \"linux\" | \"unknown\";\n /** Device type classification */\n readonly deviceType: DeviceType;\n /** Whether device has touch capability */\n readonly hasTouch: boolean;\n /** Whether device is mobile phone */\n readonly isMobile: boolean;\n /** Whether device is tablet */\n readonly isTablet: boolean;\n /** Whether device is desktop */\n readonly isDesktop: boolean;\n /** Screen width in pixels */\n readonly screenWidth: number;\n /** Screen height in pixels */\n readonly screenHeight: number;\n}\n\n/**\n * Detect if user-agent indicates a mobile device\n * Checks for common mobile device identifiers in user-agent string\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates mobile device\n */\nfunction isMobileUserAgent(userAgent: string): boolean {\n const mobileKeywords = [\n \"Android\",\n \"webOS\",\n \"iPhone\",\n \"iPod\",\n \"BlackBerry\",\n \"IEMobile\",\n \"Opera Mini\",\n \"Mobile\",\n \"mobile\",\n ];\n\n return mobileKeywords.some((keyword) => userAgent.includes(keyword));\n}\n\n/**\n * Detect if user-agent indicates a tablet device\n *\n * @param userAgent - Browser user-agent string\n * @returns True if user-agent indicates tablet\n */\nfunction isTabletUserAgent(userAgent: string): boolean {\n if (userAgent.includes(\"iPad\")) {\n return true;\n }\n\n if (userAgent.includes(\"Android\")) {\n return userAgent.includes(\"Tablet\") || !userAgent.includes(\"Mobile\");\n }\n\n return false;\n}\n\n/**\n * Detect operating system from user-agent\n *\n * @param userAgent - Browser user-agent string\n * @returns Operating system identifier\n */\nfunction detectOS(userAgent: string): PlatformInfo[\"os\"] {\n if (\n userAgent.includes(\"iPhone\") ||\n userAgent.includes(\"iPad\") ||\n userAgent.includes(\"iPod\")\n ) {\n return \"ios\";\n }\n if (userAgent.includes(\"Android\")) {\n return \"android\";\n }\n if (userAgent.includes(\"Windows\")) {\n return \"windows\";\n }\n if (userAgent.includes(\"Mac\")) {\n const isLikelyIPadOSDesktop =\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints === \"number\" &&\n navigator.maxTouchPoints > 1 &&\n userAgent.includes(\"Macintosh\");\n\n if (isLikelyIPadOSDesktop) {\n return \"ios\";\n }\n return \"macos\";\n }\n if (userAgent.includes(\"Linux\")) {\n return \"linux\";\n }\n return \"unknown\";\n}\n\n/**\n * Detect if device has touch capability\n * Uses multiple methods for reliability\n *\n * @returns True if touch is supported\n */\nfunction hasTouchSupport(): boolean {\n if (\"ontouchstart\" in window) {\n return true;\n }\n\n if (\n typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints !== \"undefined\" &&\n navigator.maxTouchPoints > 0\n ) {\n return true;\n }\n\n if (\n typeof window !== \"undefined\" &&\n window.matchMedia?.(\"(pointer: coarse)\")?.matches\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Mobile screen size breakpoint\n * Devices with width <= this value are considered mobile by size\n */\nexport const MOBILE_BREAKPOINT = 768;\n\n/**\n * Tablet screen size breakpoint\n * Devices with width > MOBILE_BREAKPOINT and <= TABLET_BREAKPOINT are tablets\n */\nexport const TABLET_BREAKPOINT = 1024;\n\n/**\n * Cached CSS environment variable insets\n */\nlet cachedCSSEnvInsets: { top: number; bottom: number } | null = null;\n\n/**\n * Read CSS environment variables for safe area insets\n * Results are cached as they don't change during a session\n */\nfunction readCSSEnvInsets(): { top: number; bottom: number } | null {\n if (cachedCSSEnvInsets !== null) {\n return cachedCSSEnvInsets;\n }\n\n if (typeof window !== \"undefined\" && typeof getComputedStyle === \"function\") {\n try {\n const root = document.documentElement;\n const style = getComputedStyle(root);\n const topEnv = style.getPropertyValue(\"env(safe-area-inset-top)\");\n const bottomEnv = style.getPropertyValue(\"env(safe-area-inset-bottom)\");\n\n if (topEnv || bottomEnv) {\n const result = {\n top: parseInt(topEnv || \"0\", 10) || 0,\n bottom: parseInt(bottomEnv || \"0\", 10) || 0,\n };\n cachedCSSEnvInsets = result;\n return result;\n }\n } catch {\n // intentional: fall through to null\n }\n }\n\n return null;\n}\n\n/**\n * Cached platform information to avoid re-parsing user-agent on every call\n */\nlet cachedPlatform: PlatformInfo | null = null;\nlet cachedScreenWidth = 0;\nlet cachedScreenHeight = 0;\n\n/**\n * Clear the cached platform information\n * Useful when window is resized or device emulation changes\n * Also clears CSS environment variable cache\n *\n */\nexport function clearPlatformCache(): void {\n cachedPlatform = null;\n cachedScreenWidth = 0;\n cachedScreenHeight = 0;\n cachedCSSEnvInsets = null;\n}\n\n/**\n * Detect device type and platform information\n *\n * Combines multiple detection methods for reliability:\n * 1. User-agent string analysis (most reliable for device type)\n * 2. Screen dimensions\n * 3. Touch capability\n *\n * This ensures mobile controls are shown on:\n * - Standard mobile phones (< 768px width)\n * - High-resolution phones (>= 768px width but mobile user-agent)\n * - Android 15/16 devices with 2K/4K resolutions (1200px+, 1440px+)\n * - Tablets (user preference via touch support)\n *\n * **User-agent detection takes priority over screen size**, ensuring that\n * high-end Android phones with desktop-class resolutions (e.g., Galaxy S23 Ultra,\n * Pixel 9 Pro) are correctly identified as mobile devices.\n *\n * Results are cached to avoid re-parsing user-agent on every call.\n * Cache is invalidated when screen dimensions change.\n *\n * @returns Complete platform information\n *\n * @example\n * ```typescript\n * const platform = detectPlatform();\n *\n * if (platform.isMobile) {\n * // Show mobile controls even on 4K Android phones\n * return <MobileControls />;\n * }\n * ```\n *\n * @korean 플랫폼감지\n */\nexport function detectPlatform(): PlatformInfo {\n const userAgent = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n const screenWidth = typeof window !== \"undefined\" ? window.innerWidth : 1920;\n const screenHeight =\n typeof window !== \"undefined\" ? window.innerHeight : 1080;\n\n if (\n cachedPlatform !== null &&\n cachedScreenWidth === screenWidth &&\n cachedScreenHeight === screenHeight\n ) {\n return cachedPlatform;\n }\n\n const os = detectOS(userAgent);\n const hasTouch = hasTouchSupport();\n const isMobileUA = isMobileUserAgent(userAgent);\n const isTabletUA = isTabletUserAgent(userAgent);\n const isMobileBySize = screenWidth <= MOBILE_BREAKPOINT;\n const isTabletBySize =\n screenWidth > MOBILE_BREAKPOINT && screenWidth <= TABLET_BREAKPOINT;\n\n let deviceType: DeviceType;\n let isMobile: boolean;\n let isTablet: boolean;\n\n if (isMobileUA && !isTabletUA) {\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletUA) {\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else if (isMobileBySize) {\n deviceType = DeviceType.MOBILE;\n isMobile = true;\n isTablet = false;\n } else if (isTabletBySize && hasTouch) {\n deviceType = DeviceType.TABLET;\n isMobile = false;\n isTablet = true;\n } else {\n deviceType = DeviceType.DESKTOP;\n isMobile = false;\n isTablet = false;\n }\n\n const isDesktop = deviceType === DeviceType.DESKTOP;\n\n const result: PlatformInfo = {\n os,\n deviceType,\n hasTouch,\n isMobile,\n isTablet,\n isDesktop,\n screenWidth,\n screenHeight,\n };\n\n cachedPlatform = result;\n cachedScreenWidth = screenWidth;\n cachedScreenHeight = screenHeight;\n\n return result;\n}\n\n/**\n * Simple mobile check for backward compatibility\n * Returns true for both mobile phones and tablets\n *\n * @returns True if device is mobile or tablet\n *\n * @korean 모바일확인\n */\nexport function isMobileDevice(): boolean {\n const platform = detectPlatform();\n return platform.isMobile || platform.isTablet;\n}\n\n/**\n * Check if device should use mobile controls\n * Takes into account device type, screen size, and touch capability\n *\n * Uses user-agent detection to correctly identify mobile devices regardless\n * of screen resolution. This ensures high-end Android 15/16 phones with\n * 2K/4K displays (1200px+, 1440px+) show mobile controls.\n *\n * Also ensures tablets and touch-enabled devices always show mobile controls\n * regardless of resolution, for better UX on touch devices.\n *\n * @returns True if mobile controls should be shown\n *\n * @example\n * ```typescript\n * // High-res Android phone (1440x3168) → returns true via user-agent\n * // Desktop with 1440px screen → returns false (no mobile user-agent)\n * // iPad Pro 12.9\" (1024x1366) → returns true (tablet user-agent)\n * // Surface Pro in tablet mode → returns true (touch + tablet size)\n * if (shouldUseMobileControls()) {\n * return <VirtualDPad />; // Touch-optimized controls\n * }\n * ```\n *\n * @korean 모바일컨트롤사용\n */\nexport function shouldUseMobileControls(): boolean {\n const platform = detectPlatform();\n\n if (platform.isMobile) {\n return true;\n }\n\n if (platform.isTablet) {\n return true;\n }\n\n if (platform.hasTouch && platform.screenWidth <= TABLET_BREAKPOINT) {\n return true;\n }\n\n if (platform.screenWidth <= MOBILE_BREAKPOINT && platform.hasTouch) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Get safe area insets for device\n * Returns appropriate values based on device type and OS\n *\n * For iOS devices, attempts to detect if device has a notch by checking\n * screen dimensions. Falls back to CSS environment variables if available.\n *\n * @returns Safe area insets in pixels\n *\n * @korean 안전영역인셋\n */\nexport function getSafeAreaInsets() {\n const platform = detectPlatform();\n\n if (platform.os === \"ios\" && platform.isMobile) {\n const cssEnvInsets = readCSSEnvInsets();\n if (cssEnvInsets) {\n return {\n top: cssEnvInsets.top,\n bottom: cssEnvInsets.bottom,\n left: 0,\n right: 0,\n };\n }\n\n const isLandscape = platform.screenWidth > platform.screenHeight;\n\n const hasNotch =\n platform.screenHeight >= 812 || platform.screenWidth >= 812;\n\n if (hasNotch) {\n if (isLandscape) {\n return {\n top: 0,\n bottom: 21,\n left: 44,\n right: 44,\n };\n } else {\n return {\n top: 44,\n bottom: 34,\n left: 0,\n right: 0,\n };\n }\n } else {\n return {\n top: 20,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n }\n\n if (platform.os === \"android\" && platform.isMobile) {\n return {\n top: 24,\n bottom: 0,\n left: 0,\n right: 0,\n };\n }\n\n return {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"effectUtils.js","names":[],"sources":["../../src/utils/effectUtils.ts"],"sourcesContent":["import { HitEffect, StatusEffect } from \"../systems\";\nimport { EffectIntensity, EffectType, HitEffectType } from \"../systems/effects\";\nimport type { KoreanText, Position } from \"../types\";\n\n/**\n * Create a hit effect for visual feedback\n */\nexport function createHitEffect(\n id: string,\n type: HitEffectType,\n position: Position,\n intensity: number,\n duration: number = 1000\n): HitEffect {\n return {\n id,\n type,\n attackerId: \"unknown\",\n defenderId: \"unknown\",\n timestamp: Date.now(),\n duration,\n position,\n intensity,\n lifespan: duration,\n alpha: 1.0,\n size: 1.0,\n startTime: Date.now(),\n };\n}\n\n/**\n * Get hit effect color based on type\n */\nexport function getHitEffectColor(type: HitEffectType): number {\n switch (type) {\n case HitEffectType.CRITICAL_HIT:\n return 0xff6b00; // Orange\n case HitEffectType.VITAL_POINT_STRIKE:\n return 0xff0000; // Red\n case HitEffectType.BLOCK:\n return 0x4a90e2; // Blue\n case HitEffectType.MISS:\n return 0x888888; // Gray\n default:\n return 0xffffff; // White\n }\n}\n\n/**\n * Create a status effect\n */\nexport function createStatusEffect(\n id: string,\n type: EffectType,\n intensity: EffectIntensity,\n duration: number,\n description: KoreanText,\n source: string = \"unknown\"\n): StatusEffect {\n const currentTime = Date.now();\n return {\n id,\n type,\n intensity,\n duration,\n description,\n stackable: false,\n source,\n startTime: currentTime,\n endTime: currentTime + duration,\n };\n}\n\n/**\n * Calculate effect intensity based on damage\n */\nexport function calculateEffectIntensity(damage: number): EffectIntensity {\n // Fix: Use proper EffectIntensity values that match the type definition\n if (damage >= 80) return \"severe\" as EffectIntensity; // was \"extreme\"\n if (damage >= 50) return \"severe\" as EffectIntensity; // was \"critical\"\n if (damage >= 30) return \"moderate\" as EffectIntensity; // was \"high\"\n if (damage >= 15) return \"medium\" as EffectIntensity; // this one is correct\n if (damage >= 5) return \"minor\" as EffectIntensity; // was \"low\"\n return \"weak\" as EffectIntensity; // this one is correct\n}\n\n/**\n * Get effect duration modifier based on type\n */\nexport function getEffectDurationModifier(type: EffectType): number {\n switch (type) {\n case \"bleed\": // Fix: Use string literal instead of enum\n return 1.5;\n case \"poison\":\n return 2.0;\n case \"stun\":\n return 0.5;\n case \"burn\":\n return 1.2;\n default:\n return 1.0;\n }\n}\n\n/**\n * Apply effect to player stats\n */\nexport function applyEffectModifiers(\n baseValue: number,\n effects: readonly StatusEffect[],\n statType: \"attack\" | \"defense\" | \"speed\"\n): number {\n let modifier = 1.0;\n\n effects.forEach((effect) => {\n switch (effect.type) {\n case \"bleed\":\n if (statType === \"attack\") modifier *= 0.9;\n break;\n case \"strengthened\":\n if (statType === \"attack\") modifier *= 1.2;\n break;\n case \"weakened\":\n modifier *= 0.8;\n break;\n case \"exhausted\":\n if (statType === \"speed\") modifier *= 0.7;\n break;\n }\n });\n\n return Math.floor(baseValue * modifier);\n}\n\n/**\n * Check if effect is beneficial or harmful\n */\nexport function isEffectBeneficial(type: EffectType): boolean {\n const beneficialEffects: EffectType[] = [\n \"focused\",\n \"rage\",\n \"defensive\",\n \"strengthened\",\n ];\n return beneficialEffects.includes(type);\n}\n\n/**\n * Get effect description based on type and intensity\n */\nexport function getEffectDescription(\n type: EffectType,\n intensity: EffectIntensity\n): KoreanText {\n const descriptions: Record<\n EffectType,\n Record<EffectIntensity, KoreanText>\n > = {\n stun: {\n weak: { korean: \"가벼운 기절\", english: \"Light Stun\" },\n minor: { korean: \"경미한 기절\", english: \"Minor Stun\" },\n medium: { korean: \"기절\", english: \"Stun\" },\n moderate: { korean: \"기절\", english: \"Stun\" },\n high: { korean: \"심한 기절\", english: \"Heavy Stun\" },\n severe: { korean: \"심한 기절\", english: \"Severe Stun\" },\n critical: { korean: \"완전 기절\", english: \"Complete Stun\" },\n extreme: { korean: \"완전 기절\", english: \"Complete Stun\" },\n low: { korean: \"약한 기절\", english: \"Weak Stun\" },\n },\n poison: {\n weak: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n minor: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n medium: { korean: \"중독\", english: \"Poison\" },\n moderate: { korean: \"중독\", english: \"Poison\" },\n high: { korean: \"심한 중독\", english: \"Severe Poison\" },\n severe: { korean: \"심한 중독\", english: \"Severe Poison\" },\n critical: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n extreme: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n low: { korean: \"약한 중독\", english: \"Weak Poison\" },\n },\n weakened: {\n weak: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n minor: { korean: \"경미한 약화\", english: \"Minor Weakness\" },\n medium: { korean: \"약화\", english: \"Weakened\" },\n moderate: { korean: \"약화\", english: \"Weakened\" },\n high: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n severe: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n critical: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n extreme: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n low: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n },\n burn: {\n weak: { korean: \"가벼운 화상\", english: \"Minor Burn\" },\n minor: { korean: \"경미한 화상\", english: \"Minor Burn\" },\n medium: { korean: \"화상\", english: \"Burn\" },\n moderate: { korean: \"화상\", english: \"Burn\" },\n high: { korean: \"심한 화상\", english: \"Severe Burn\" },\n severe: { korean: \"심한 화상\", english: \"Severe Burn\" },\n critical: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n extreme: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n low: { korean: \"약한 화상\", english: \"Weak Burn\" },\n },\n bleed: {\n weak: { korean: \"가벼운 출혈\", english: \"Minor Bleeding\" },\n minor: { korean: \"경미한 출혈\", english: \"Minor Bleeding\" },\n medium: { korean: \"출혈\", english: \"Bleeding\" },\n moderate: { korean: \"출혈\", english: \"Bleeding\" },\n high: { korean: \"심한 출혈\", english: \"Heavy Bleeding\" },\n severe: { korean: \"심한 출혈\", english: \"Severe Bleeding\" },\n critical: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n extreme: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n low: { korean: \"약한 출혈\", english: \"Weak Bleeding\" },\n },\n exhausted: {\n weak: { korean: \"약간 지침\", english: \"Slightly Tired\" },\n minor: { korean: \"경미한 피로\", english: \"Minor Fatigue\" },\n medium: { korean: \"지침\", english: \"Exhausted\" },\n moderate: { korean: \"지침\", english: \"Exhausted\" },\n high: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n severe: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n critical: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n extreme: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n low: { korean: \"약간 피로\", english: \"Slightly Fatigued\" },\n },\n focused: {\n weak: { korean: \"약간 집중\", english: \"Slightly Focused\" },\n minor: { korean: \"경미한 집중\", english: \"Minor Focus\" },\n medium: { korean: \"집중\", english: \"Focused\" },\n moderate: { korean: \"집중\", english: \"Focused\" },\n high: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n severe: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n critical: { korean: \"완전 집중\", english: \"Completely Focused\" },\n extreme: { korean: \"완전 집중\", english: \"Completely Focused\" },\n low: { korean: \"약한 집중\", english: \"Weak Focus\" },\n },\n rage: {\n weak: { korean: \"약간 분노\", english: \"Slightly Enraged\" },\n minor: { korean: \"경미한 분노\", english: \"Minor Rage\" },\n medium: { korean: \"분노\", english: \"Enraged\" },\n moderate: { korean: \"분노\", english: \"Enraged\" },\n high: { korean: \"맹렬한 분노\", english: \"Furious\" },\n severe: { korean: \"맹렬한 분노\", english: \"Furious\" },\n critical: { korean: \"광분\", english: \"Berserk\" },\n extreme: { korean: \"광분\", english: \"Berserk\" },\n low: { korean: \"약한 분노\", english: \"Weak Rage\" },\n },\n defensive: {\n weak: { korean: \"약간 방어적\", english: \"Slightly Defensive\" },\n minor: { korean: \"경미한 방어\", english: \"Minor Defense\" },\n medium: { korean: \"방어적\", english: \"Defensive\" },\n moderate: { korean: \"방어적\", english: \"Defensive\" },\n high: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n severe: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n critical: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n extreme: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n low: { korean: \"약한 방어\", english: \"Weak Defense\" },\n },\n strengthened: {\n weak: { korean: \"약간 강화\", english: \"Slightly Strengthened\" },\n minor: { korean: \"경미한 강화\", english: \"Minor Strength\" },\n medium: { korean: \"강화\", english: \"Strengthened\" },\n moderate: { korean: \"강화\", english: \"Strengthened\" },\n high: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n severe: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n critical: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n extreme: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n low: { korean: \"약한 강화\", english: \"Weak Strength\" },\n },\n paralysis: {\n weak: { korean: \"가벼운 마비\", english: \"Minor Paralysis\" },\n minor: { korean: \"경미한 마비\", english: \"Minor Paralysis\" },\n medium: { korean: \"마비\", english: \"Paralysis\" },\n moderate: { korean: \"마비\", english: \"Paralysis\" },\n high: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n severe: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n critical: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n extreme: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n low: { korean: \"약한 마비\", english: \"Weak Paralysis\" },\n },\n confusion: {\n weak: { korean: \"약간 혼란\", english: \"Slight Confusion\" },\n minor: { korean: \"경미한 혼란\", english: \"Minor Confusion\" },\n medium: { korean: \"혼란\", english: \"Confusion\" },\n moderate: { korean: \"혼란\", english: \"Confusion\" },\n high: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n severe: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n critical: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n extreme: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n low: { korean: \"약한 혼란\", english: \"Weak Confusion\" },\n },\n vulnerability: {\n weak: { korean: \"약간 취약\", english: \"Slightly Vulnerable\" },\n minor: { korean: \"경미한 취약\", english: \"Minor Vulnerability\" },\n medium: { korean: \"취약\", english: \"Vulnerable\" },\n moderate: { korean: \"취약\", english: \"Vulnerable\" },\n high: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n severe: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n critical: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n extreme: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n low: { korean: \"약한 취약\", english: \"Weak Vulnerability\" },\n },\n stamina_drain: {\n weak: { korean: \"체력 소모\", english: \"Stamina Drain\" },\n minor: { korean: \"경미한 체력 소모\", english: \"Minor Stamina Drain\" },\n medium: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n moderate: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n high: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n severe: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n critical: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n extreme: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n low: { korean: \"약한 체력 소모\", english: \"Weak Stamina Drain\" },\n },\n };\n\n return (\n descriptions[type]?.[intensity] || {\n korean: `${type} 효과`,\n english: `${type} effect`,\n }\n );\n}\n\n/**\n * Update effect over time\n */\nexport function updateEffect(\n effect: StatusEffect,\n currentTime: number\n): StatusEffect | null {\n if (currentTime >= effect.endTime) {\n return null; // Effect has expired\n }\n\n return effect; // Effect is still active\n}\n\n/**\n * Combine multiple effects of same type\n */\nexport function combineEffects(effects: StatusEffect[]): StatusEffect[] {\n const combinedEffects: StatusEffect[] = [];\n // Fix: Use string type instead of EffectType since StatusEffect.type is string\n const effectsByType = new Map<string, StatusEffect[]>();\n\n // Group effects by type\n effects.forEach((effect) => {\n // Fix: effect.type is string, so use string methods\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n });\n\n // Combine stackable effects, keep latest non-stackable\n effectsByType.forEach((typeEffects, _) => {\n if (typeEffects.length === 0) return;\n\n const firstEffect = typeEffects[0];\n if (firstEffect.stackable) {\n // Keep all stackable effects\n combinedEffects.push(...typeEffects);\n } else {\n // Keep only the latest non-stackable effect\n const latestEffect = typeEffects.reduce((latest, current) =>\n current.startTime > latest.startTime ? current : latest\n );\n combinedEffects.push(latestEffect);\n }\n });\n\n return combinedEffects;\n}\n\n/**\n * Remove expired effects\n */\nexport function removeExpiredEffects(effects: StatusEffect[]): StatusEffect[] {\n const currentTime = Date.now();\n return effects.filter((effect) => effect.endTime > currentTime);\n}\n\n/**\n * Create effect from Korean text\n */\nexport function createEffectFromKoreanText(\n koreanText: string,\n englishText: string,\n duration: number = 3000\n): StatusEffect {\n // Fix: Provide all required arguments\n return createStatusEffect(\n koreanText.toLowerCase(),\n \"stun\" as EffectType, // Use proper EffectType\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: koreanText,\n english: englishText,\n }\n );\n}\n\n/**\n * Get effect display text\n */\nexport function getEffectDisplayText(\n effect: StatusEffect,\n preferKorean: boolean = true\n): string {\n return preferKorean ? effect.description.korean : effect.description.english;\n}\n\n// Fix: Korean martial arts specific effect utilities\nexport function createKoreanStatusEffect(\n koreanName: string,\n englishName: string,\n intensity: EffectIntensity = \"medium\" as EffectIntensity,\n duration: number = 3000\n): StatusEffect {\n return createStatusEffect(\n koreanName.toLowerCase().replace(/\\s+/g, \"_\"),\n \"stun\" as EffectType, // This is correct - keep as is\n intensity,\n duration,\n { korean: koreanName, english: englishName }\n );\n}\n\nexport function createTrigramEffect(\n stanceName: string,\n effectType: EffectType,\n duration: number = 2000\n): StatusEffect {\n return createStatusEffect(\n `trigram_${stanceName}_${effectType}`,\n effectType, // This is correct - use the parameter directly\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: `${stanceName}괘 효과`,\n english: `${stanceName} trigram effect`,\n }\n );\n}\n\n// Fix: Group effects by type using proper typing\nexport function groupEffectsByTypeEnum(\n effects: StatusEffect[]\n): Map<EffectType, StatusEffect[]> {\n const effectsByType = new Map<EffectType, StatusEffect[]>();\n\n for (const effect of effects) {\n // Cast string to EffectType for enum operations\n const effectType = effect.type as EffectType;\n if (!effectsByType.has(effectType)) {\n effectsByType.set(effectType, []);\n }\n const typeEffects = effectsByType.get(effectType);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n\n/**\n * Group effects by type using string keys (works with StatusEffect.type)\n */\nexport function groupEffectsByType(\n effects: StatusEffect[]\n): Map<string, StatusEffect[]> {\n const effectsByType = new Map<string, StatusEffect[]>();\n\n for (const effect of effects) {\n // Fix: Use string type directly since StatusEffect.type is string\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"effectUtils.js","names":[],"sources":["../../src/utils/effectUtils.ts"],"sourcesContent":["import { HitEffect, StatusEffect } from \"../systems\";\nimport { EffectIntensity, EffectType, HitEffectType } from \"../systems/effects\";\nimport type { KoreanText, Position } from \"../types\";\n\n/**\n * Create a hit effect for visual feedback\n */\nexport function createHitEffect(\n id: string,\n type: HitEffectType,\n position: Position,\n intensity: number,\n duration: number = 1000\n): HitEffect {\n return {\n id,\n type,\n attackerId: \"unknown\",\n defenderId: \"unknown\",\n timestamp: Date.now(),\n duration,\n position,\n intensity,\n lifespan: duration,\n alpha: 1.0,\n size: 1.0,\n startTime: Date.now(),\n };\n}\n\n/**\n * Get hit effect color based on type\n */\nexport function getHitEffectColor(type: HitEffectType): number {\n switch (type) {\n case HitEffectType.CRITICAL_HIT:\n return 0xff6b00; // Orange\n case HitEffectType.VITAL_POINT_STRIKE:\n return 0xff0000; // Red\n case HitEffectType.BLOCK:\n return 0x4a90e2; // Blue\n case HitEffectType.MISS:\n return 0x888888; // Gray\n default:\n return 0xffffff; // White\n }\n}\n\n/**\n * Create a status effect\n */\nexport function createStatusEffect(\n id: string,\n type: EffectType,\n intensity: EffectIntensity,\n duration: number,\n description: KoreanText,\n source: string = \"unknown\"\n): StatusEffect {\n const currentTime = Date.now();\n return {\n id,\n type,\n intensity,\n duration,\n description,\n stackable: false,\n source,\n startTime: currentTime,\n endTime: currentTime + duration,\n };\n}\n\n/**\n * Calculate effect intensity based on damage\n */\nexport function calculateEffectIntensity(damage: number): EffectIntensity {\n // Fix: Use proper EffectIntensity values that match the type definition\n if (damage >= 80) return \"severe\" as EffectIntensity; // was \"extreme\"\n if (damage >= 50) return \"severe\" as EffectIntensity; // was \"critical\"\n if (damage >= 30) return \"moderate\" as EffectIntensity; // was \"high\"\n if (damage >= 15) return \"medium\" as EffectIntensity; // this one is correct\n if (damage >= 5) return \"minor\" as EffectIntensity; // was \"low\"\n return \"weak\" as EffectIntensity; // this one is correct\n}\n\n/**\n * Get effect duration modifier based on type\n */\nexport function getEffectDurationModifier(type: EffectType): number {\n switch (type) {\n case \"bleed\": // Fix: Use string literal instead of enum\n return 1.5;\n case \"poison\":\n return 2.0;\n case \"stun\":\n return 0.5;\n case \"burn\":\n return 1.2;\n default:\n return 1.0;\n }\n}\n\n/**\n * Apply effect to player stats\n */\nexport function applyEffectModifiers(\n baseValue: number,\n effects: readonly StatusEffect[],\n statType: \"attack\" | \"defense\" | \"speed\"\n): number {\n let modifier = 1.0;\n\n effects.forEach((effect) => {\n switch (effect.type) {\n case \"bleed\":\n if (statType === \"attack\") modifier *= 0.9;\n break;\n case \"strengthened\":\n if (statType === \"attack\") modifier *= 1.2;\n break;\n case \"weakened\":\n modifier *= 0.8;\n break;\n case \"exhausted\":\n if (statType === \"speed\") modifier *= 0.7;\n break;\n }\n });\n\n return Math.floor(baseValue * modifier);\n}\n\n/**\n * Check if effect is beneficial or harmful\n */\nexport function isEffectBeneficial(type: EffectType): boolean {\n const beneficialEffects: EffectType[] = [\n \"focused\",\n \"rage\",\n \"defensive\",\n \"strengthened\",\n ];\n return beneficialEffects.includes(type);\n}\n\n/**\n * Get effect description based on type and intensity\n */\nexport function getEffectDescription(\n type: EffectType,\n intensity: EffectIntensity\n): KoreanText {\n const descriptions: Record<\n EffectType,\n Record<EffectIntensity, KoreanText>\n > = {\n stun: {\n weak: { korean: \"가벼운 기절\", english: \"Light Stun\" },\n minor: { korean: \"경미한 기절\", english: \"Minor Stun\" },\n medium: { korean: \"기절\", english: \"Stun\" },\n moderate: { korean: \"기절\", english: \"Stun\" },\n high: { korean: \"심한 기절\", english: \"Heavy Stun\" },\n severe: { korean: \"심한 기절\", english: \"Severe Stun\" },\n critical: { korean: \"완전 기절\", english: \"Complete Stun\" },\n extreme: { korean: \"완전 기절\", english: \"Complete Stun\" },\n low: { korean: \"약한 기절\", english: \"Weak Stun\" },\n },\n poison: {\n weak: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n minor: { korean: \"경미한 중독\", english: \"Minor Poison\" },\n medium: { korean: \"중독\", english: \"Poison\" },\n moderate: { korean: \"중독\", english: \"Poison\" },\n high: { korean: \"심한 중독\", english: \"Severe Poison\" },\n severe: { korean: \"심한 중독\", english: \"Severe Poison\" },\n critical: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n extreme: { korean: \"치명적 중독\", english: \"Lethal Poison\" },\n low: { korean: \"약한 중독\", english: \"Weak Poison\" },\n },\n weakened: {\n weak: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n minor: { korean: \"경미한 약화\", english: \"Minor Weakness\" },\n medium: { korean: \"약화\", english: \"Weakened\" },\n moderate: { korean: \"약화\", english: \"Weakened\" },\n high: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n severe: { korean: \"심하게 약화\", english: \"Severely Weakened\" },\n critical: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n extreme: { korean: \"극도로 약화\", english: \"Critically Weakened\" },\n low: { korean: \"약간 약화\", english: \"Slightly Weakened\" },\n },\n burn: {\n weak: { korean: \"가벼운 화상\", english: \"Minor Burn\" },\n minor: { korean: \"경미한 화상\", english: \"Minor Burn\" },\n medium: { korean: \"화상\", english: \"Burn\" },\n moderate: { korean: \"화상\", english: \"Burn\" },\n high: { korean: \"심한 화상\", english: \"Severe Burn\" },\n severe: { korean: \"심한 화상\", english: \"Severe Burn\" },\n critical: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n extreme: { korean: \"치명적 화상\", english: \"Critical Burn\" },\n low: { korean: \"약한 화상\", english: \"Weak Burn\" },\n },\n bleed: {\n weak: { korean: \"가벼운 출혈\", english: \"Minor Bleeding\" },\n minor: { korean: \"경미한 출혈\", english: \"Minor Bleeding\" },\n medium: { korean: \"출혈\", english: \"Bleeding\" },\n moderate: { korean: \"출혈\", english: \"Bleeding\" },\n high: { korean: \"심한 출혈\", english: \"Heavy Bleeding\" },\n severe: { korean: \"심한 출혈\", english: \"Severe Bleeding\" },\n critical: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n extreme: { korean: \"치명적 출혈\", english: \"Critical Bleeding\" },\n low: { korean: \"약한 출혈\", english: \"Weak Bleeding\" },\n },\n exhausted: {\n weak: { korean: \"약간 지침\", english: \"Slightly Tired\" },\n minor: { korean: \"경미한 피로\", english: \"Minor Fatigue\" },\n medium: { korean: \"지침\", english: \"Exhausted\" },\n moderate: { korean: \"지침\", english: \"Exhausted\" },\n high: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n severe: { korean: \"심하게 지침\", english: \"Severely Exhausted\" },\n critical: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n extreme: { korean: \"완전 탈진\", english: \"Completely Drained\" },\n low: { korean: \"약간 피로\", english: \"Slightly Fatigued\" },\n },\n focused: {\n weak: { korean: \"약간 집중\", english: \"Slightly Focused\" },\n minor: { korean: \"경미한 집중\", english: \"Minor Focus\" },\n medium: { korean: \"집중\", english: \"Focused\" },\n moderate: { korean: \"집중\", english: \"Focused\" },\n high: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n severe: { korean: \"깊은 집중\", english: \"Deeply Focused\" },\n critical: { korean: \"완전 집중\", english: \"Completely Focused\" },\n extreme: { korean: \"완전 집중\", english: \"Completely Focused\" },\n low: { korean: \"약한 집중\", english: \"Weak Focus\" },\n },\n rage: {\n weak: { korean: \"약간 분노\", english: \"Slightly Enraged\" },\n minor: { korean: \"경미한 분노\", english: \"Minor Rage\" },\n medium: { korean: \"분노\", english: \"Enraged\" },\n moderate: { korean: \"분노\", english: \"Enraged\" },\n high: { korean: \"맹렬한 분노\", english: \"Furious\" },\n severe: { korean: \"맹렬한 분노\", english: \"Furious\" },\n critical: { korean: \"광분\", english: \"Berserk\" },\n extreme: { korean: \"광분\", english: \"Berserk\" },\n low: { korean: \"약한 분노\", english: \"Weak Rage\" },\n },\n defensive: {\n weak: { korean: \"약간 방어적\", english: \"Slightly Defensive\" },\n minor: { korean: \"경미한 방어\", english: \"Minor Defense\" },\n medium: { korean: \"방어적\", english: \"Defensive\" },\n moderate: { korean: \"방어적\", english: \"Defensive\" },\n high: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n severe: { korean: \"높은 방어\", english: \"Highly Defensive\" },\n critical: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n extreme: { korean: \"완벽한 방어\", english: \"Perfect Defense\" },\n low: { korean: \"약한 방어\", english: \"Weak Defense\" },\n },\n strengthened: {\n weak: { korean: \"약간 강화\", english: \"Slightly Strengthened\" },\n minor: { korean: \"경미한 강화\", english: \"Minor Strength\" },\n medium: { korean: \"강화\", english: \"Strengthened\" },\n moderate: { korean: \"강화\", english: \"Strengthened\" },\n high: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n severe: { korean: \"크게 강화\", english: \"Greatly Strengthened\" },\n critical: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n extreme: { korean: \"극대 강화\", english: \"Maximally Strengthened\" },\n low: { korean: \"약한 강화\", english: \"Weak Strength\" },\n },\n paralysis: {\n weak: { korean: \"가벼운 마비\", english: \"Minor Paralysis\" },\n minor: { korean: \"경미한 마비\", english: \"Minor Paralysis\" },\n medium: { korean: \"마비\", english: \"Paralysis\" },\n moderate: { korean: \"마비\", english: \"Paralysis\" },\n high: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n severe: { korean: \"심한 마비\", english: \"Severe Paralysis\" },\n critical: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n extreme: { korean: \"완전 마비\", english: \"Complete Paralysis\" },\n low: { korean: \"약한 마비\", english: \"Weak Paralysis\" },\n },\n confusion: {\n weak: { korean: \"약간 혼란\", english: \"Slight Confusion\" },\n minor: { korean: \"경미한 혼란\", english: \"Minor Confusion\" },\n medium: { korean: \"혼란\", english: \"Confusion\" },\n moderate: { korean: \"혼란\", english: \"Confusion\" },\n high: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n severe: { korean: \"심한 혼란\", english: \"Severe Confusion\" },\n critical: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n extreme: { korean: \"완전 혼란\", english: \"Complete Confusion\" },\n low: { korean: \"약한 혼란\", english: \"Weak Confusion\" },\n },\n vulnerability: {\n weak: { korean: \"약간 취약\", english: \"Slightly Vulnerable\" },\n minor: { korean: \"경미한 취약\", english: \"Minor Vulnerability\" },\n medium: { korean: \"취약\", english: \"Vulnerable\" },\n moderate: { korean: \"취약\", english: \"Vulnerable\" },\n high: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n severe: { korean: \"매우 취약\", english: \"Very Vulnerable\" },\n critical: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n extreme: { korean: \"극도로 취약\", english: \"Extremely Vulnerable\" },\n low: { korean: \"약한 취약\", english: \"Weak Vulnerability\" },\n },\n stamina_drain: {\n weak: { korean: \"체력 소모\", english: \"Stamina Drain\" },\n minor: { korean: \"경미한 체력 소모\", english: \"Minor Stamina Drain\" },\n medium: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n moderate: { korean: \"체력 고갈\", english: \"Stamina Depletion\" },\n high: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n severe: { korean: \"심한 체력 고갈\", english: \"Severe Stamina Loss\" },\n critical: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n extreme: { korean: \"완전 체력 고갈\", english: \"Complete Stamina Loss\" },\n low: { korean: \"약한 체력 소모\", english: \"Weak Stamina Drain\" },\n },\n };\n\n return (\n descriptions[type]?.[intensity] || {\n korean: `${type} 효과`,\n english: `${type} effect`,\n }\n );\n}\n\n/**\n * Update effect over time\n */\nexport function updateEffect(\n effect: StatusEffect,\n currentTime: number\n): StatusEffect | null {\n if (currentTime >= effect.endTime) {\n return null; // Effect has expired\n }\n\n return effect; // Effect is still active\n}\n\n/**\n * Combine multiple effects of same type\n */\nexport function combineEffects(effects: StatusEffect[]): StatusEffect[] {\n const combinedEffects: StatusEffect[] = [];\n // Fix: Use string type instead of EffectType since StatusEffect.type is string\n const effectsByType = new Map<string, StatusEffect[]>();\n\n // Group effects by type\n effects.forEach((effect) => {\n // Fix: effect.type is string, so use string methods\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n });\n\n // Combine stackable effects, keep latest non-stackable\n effectsByType.forEach((typeEffects, _) => {\n if (typeEffects.length === 0) return;\n\n const firstEffect = typeEffects[0];\n if (firstEffect.stackable) {\n // Keep all stackable effects\n combinedEffects.push(...typeEffects);\n } else {\n // Keep only the latest non-stackable effect\n const latestEffect = typeEffects.reduce((latest, current) =>\n current.startTime > latest.startTime ? current : latest\n );\n combinedEffects.push(latestEffect);\n }\n });\n\n return combinedEffects;\n}\n\n/**\n * Remove expired effects\n */\nexport function removeExpiredEffects(effects: StatusEffect[]): StatusEffect[] {\n const currentTime = Date.now();\n return effects.filter((effect) => effect.endTime > currentTime);\n}\n\n/**\n * Create effect from Korean text\n */\nexport function createEffectFromKoreanText(\n koreanText: string,\n englishText: string,\n duration: number = 3000\n): StatusEffect {\n // Fix: Provide all required arguments\n return createStatusEffect(\n koreanText.toLowerCase(),\n \"stun\" as EffectType, // Use proper EffectType\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: koreanText,\n english: englishText,\n }\n );\n}\n\n/**\n * Get effect display text\n */\nexport function getEffectDisplayText(\n effect: StatusEffect,\n preferKorean: boolean = true\n): string {\n return preferKorean ? effect.description.korean : effect.description.english;\n}\n\n// Fix: Korean martial arts specific effect utilities\nexport function createKoreanStatusEffect(\n koreanName: string,\n englishName: string,\n intensity: EffectIntensity = \"medium\" as EffectIntensity,\n duration: number = 3000\n): StatusEffect {\n return createStatusEffect(\n koreanName.toLowerCase().replace(/\\s+/g, \"_\"),\n \"stun\" as EffectType, // This is correct - keep as is\n intensity,\n duration,\n { korean: koreanName, english: englishName }\n );\n}\n\nexport function createTrigramEffect(\n stanceName: string,\n effectType: EffectType,\n duration: number = 2000\n): StatusEffect {\n return createStatusEffect(\n `trigram_${stanceName}_${effectType}`,\n effectType, // This is correct - use the parameter directly\n \"medium\" as EffectIntensity,\n duration,\n {\n korean: `${stanceName}괘 효과`,\n english: `${stanceName} trigram effect`,\n }\n );\n}\n\n// Fix: Group effects by type using proper typing\nexport function groupEffectsByTypeEnum(\n effects: StatusEffect[]\n): Map<EffectType, StatusEffect[]> {\n const effectsByType = new Map<EffectType, StatusEffect[]>();\n\n for (const effect of effects) {\n // Cast string to EffectType for enum operations\n const effectType = effect.type as EffectType;\n if (!effectsByType.has(effectType)) {\n effectsByType.set(effectType, []);\n }\n const typeEffects = effectsByType.get(effectType);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n\n/**\n * Group effects by type using string keys (works with StatusEffect.type)\n */\nexport function groupEffectsByType(\n effects: StatusEffect[]\n): Map<string, StatusEffect[]> {\n const effectsByType = new Map<string, StatusEffect[]>();\n\n for (const effect of effects) {\n // Fix: Use string type directly since StatusEffect.type is string\n if (!effectsByType.has(effect.type)) {\n effectsByType.set(effect.type, []);\n }\n const typeEffects = effectsByType.get(effect.type);\n if (typeEffects) {\n typeEffects.push(effect);\n }\n }\n\n return effectsByType;\n}\n"],"mappings":";;;;;AAOA,SAAgB,gBACd,IACA,MACA,UACA,WACA,WAAmB,KACR;CACX,OAAO;EACL;EACA;EACA,YAAY;EACZ,YAAY;EACZ,WAAW,KAAK,IAAI;EACpB;EACA;EACA;EACA,UAAU;EACV,OAAO;EACP,MAAM;EACN,WAAW,KAAK,IAAI;CACtB;AACF;;;;AAKA,SAAgB,kBAAkB,MAA6B;CAC7D,QAAQ,MAAR;EACE,KAAK,cAAc,cACjB,OAAO;EACT,KAAK,cAAc,oBACjB,OAAO;EACT,KAAK,cAAc,OACjB,OAAO;EACT,KAAK,cAAc,MACjB,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;AAKA,SAAgB,mBACd,IACA,MACA,WACA,UACA,aACA,SAAiB,WACH;CACd,MAAM,cAAc,KAAK,IAAI;CAC7B,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,WAAW;EACX,SAAS,cAAc;CACzB;AACF;;;;AAKA,SAAgB,yBAAyB,QAAiC;CAExE,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,GAAG,OAAO;CACxB,OAAO;AACT;;;;AAKA,SAAgB,0BAA0B,MAA0B;CAClE,QAAQ,MAAR;EACE,KAAK,SACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;AAKA,SAAgB,qBACd,WACA,SACA,UACQ;CACR,IAAI,WAAW;CAEf,QAAQ,SAAS,WAAW;EAC1B,QAAQ,OAAO,MAAf;GACE,KAAK;IACH,IAAI,aAAa,UAAU,YAAY;IACvC;GACF,KAAK;IACH,IAAI,aAAa,UAAU,YAAY;IACvC;GACF,KAAK;IACH,YAAY;IACZ;GACF,KAAK;IACH,IAAI,aAAa,SAAS,YAAY;IACtC;EACJ;CACF,CAAC;CAED,OAAO,KAAK,MAAM,YAAY,QAAQ;AACxC;;;;AAKA,SAAgB,mBAAmB,MAA2B;CAO5D,OAAO;EALL;EACA;EACA;EACA;CAEK,EAAkB,SAAS,IAAI;AACxC;;;;AAKA,SAAgB,qBACd,MACA,WACY;CAiKZ,OACE;EA7JA,MAAM;GACJ,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAa;GAChD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAa;GACjD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAO;GACxC,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAO;GAC1C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAa;GAC/C,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAc;GAClD,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAgB;GACtD,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAgB;GACrD,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAY;EAC/C;EACA,QAAQ;GACN,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAe;GAClD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAe;GACnD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAS;GAC1C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAS;GAC5C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAgB;GAClD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAgB;GACpD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACvD,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACtD,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAc;EACjD;EACA,UAAU;GACR,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAoB;GACtD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAiB;GACrD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAW;GAC5C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAW;GAC9C,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAoB;GACvD,QAAQ;IAAE,QAAQ;IAAU,SAAS;GAAoB;GACzD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAsB;GAC7D,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAsB;GAC5D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAoB;EACvD;EACA,MAAM;GACJ,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAa;GAChD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAa;GACjD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAO;GACxC,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAO;GAC1C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAc;GAChD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAc;GAClD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACvD,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACtD,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAY;EAC/C;EACA,OAAO;GACL,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAiB;GACpD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAiB;GACrD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAW;GAC5C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAW;GAC9C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAiB;GACnD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAkB;GACtD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAoB;GAC3D,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAoB;GAC1D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAgB;EACnD;EACA,WAAW;GACT,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAiB;GACnD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACpD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC7C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC/C,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAqB;GACxD,QAAQ;IAAE,QAAQ;IAAU,SAAS;GAAqB;GAC1D,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC3D,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC1D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAoB;EACvD;EACA,SAAS;GACP,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAc;GAClD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC3C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC7C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAiB;GACnD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAiB;GACrD,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC3D,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC1D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAa;EAChD;EACA,MAAM;GACJ,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAa;GACjD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC3C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC7C,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAU;GAC7C,QAAQ;IAAE,QAAQ;IAAU,SAAS;GAAU;GAC/C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC7C,SAAS;IAAE,QAAQ;IAAM,SAAS;GAAU;GAC5C,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAY;EAC/C;EACA,WAAW;GACT,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAqB;GACxD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAgB;GACpD,QAAQ;IAAE,QAAQ;IAAO,SAAS;GAAY;GAC9C,UAAU;IAAE,QAAQ;IAAO,SAAS;GAAY;GAChD,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACvD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAkB;GACzD,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAkB;GACxD,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAe;EAClD;EACA,cAAc;GACZ,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAwB;GAC1D,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAiB;GACrD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAe;GAChD,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAe;GAClD,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAuB;GACzD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAuB;GAC3D,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAyB;GAC/D,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAyB;GAC9D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAgB;EACnD;EACA,WAAW;GACT,MAAM;IAAE,QAAQ;IAAU,SAAS;GAAkB;GACrD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAkB;GACtD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC7C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC/C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACvD,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC3D,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC1D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAiB;EACpD;EACA,WAAW;GACT,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAkB;GACtD,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC7C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAY;GAC/C,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACrD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAmB;GACvD,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC3D,SAAS;IAAE,QAAQ;IAAS,SAAS;GAAqB;GAC1D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAiB;EACpD;EACA,eAAe;GACb,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAsB;GACxD,OAAO;IAAE,QAAQ;IAAU,SAAS;GAAsB;GAC1D,QAAQ;IAAE,QAAQ;IAAM,SAAS;GAAa;GAC9C,UAAU;IAAE,QAAQ;IAAM,SAAS;GAAa;GAChD,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAkB;GACpD,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAkB;GACtD,UAAU;IAAE,QAAQ;IAAU,SAAS;GAAuB;GAC9D,SAAS;IAAE,QAAQ;IAAU,SAAS;GAAuB;GAC7D,KAAK;IAAE,QAAQ;IAAS,SAAS;GAAqB;EACxD;EACA,eAAe;GACb,MAAM;IAAE,QAAQ;IAAS,SAAS;GAAgB;GAClD,OAAO;IAAE,QAAQ;IAAa,SAAS;GAAsB;GAC7D,QAAQ;IAAE,QAAQ;IAAS,SAAS;GAAoB;GACxD,UAAU;IAAE,QAAQ;IAAS,SAAS;GAAoB;GAC1D,MAAM;IAAE,QAAQ;IAAY,SAAS;GAAsB;GAC3D,QAAQ;IAAE,QAAQ;IAAY,SAAS;GAAsB;GAC7D,UAAU;IAAE,QAAQ;IAAY,SAAS;GAAwB;GACjE,SAAS;IAAE,QAAQ;IAAY,SAAS;GAAwB;GAChE,KAAK;IAAE,QAAQ;IAAY,SAAS;GAAqB;EAC3D;CAIA,EAAa,QAAQ,cAAc;EACjC,QAAQ,GAAG,KAAK;EAChB,SAAS,GAAG,KAAK;CACnB;AAEJ;;;;AAKA,SAAgB,aACd,QACA,aACqB;CACrB,IAAI,eAAe,OAAO,SACxB,OAAO;CAGT,OAAO;AACT;;;;AAKA,SAAgB,eAAe,SAAyC;CACtE,MAAM,kBAAkC,CAAC;CAEzC,MAAM,gCAAgB,IAAI,IAA4B;CAGtD,QAAQ,SAAS,WAAW;EAE1B,IAAI,CAAC,cAAc,IAAI,OAAO,IAAI,GAChC,cAAc,IAAI,OAAO,MAAM,CAAC,CAAC;EAEnC,MAAM,cAAc,cAAc,IAAI,OAAO,IAAI;EACjD,IAAI,aACF,YAAY,KAAK,MAAM;CAE3B,CAAC;CAGD,cAAc,SAAS,aAAa,MAAM;EACxC,IAAI,YAAY,WAAW,GAAG;EAG9B,IADoB,YAAY,GAChB,WAEd,gBAAgB,KAAK,GAAG,WAAW;OAC9B;GAEL,MAAM,eAAe,YAAY,QAAQ,QAAQ,YAC/C,QAAQ,YAAY,OAAO,YAAY,UAAU,MACnD;GACA,gBAAgB,KAAK,YAAY;EACnC;CACF,CAAC;CAED,OAAO;AACT;;;;AAKA,SAAgB,qBAAqB,SAAyC;CAC5E,MAAM,cAAc,KAAK,IAAI;CAC7B,OAAO,QAAQ,QAAQ,WAAW,OAAO,UAAU,WAAW;AAChE;;;;AAKA,SAAgB,2BACd,YACA,aACA,WAAmB,KACL;CAEd,OAAO,mBACL,WAAW,YAAY,GACvB,QACA,UACA,UACA;EACE,QAAQ;EACR,SAAS;CACX,CACF;AACF;;;;AAKA,SAAgB,qBACd,QACA,eAAwB,MAChB;CACR,OAAO,eAAe,OAAO,YAAY,SAAS,OAAO,YAAY;AACvE;AAGA,SAAgB,yBACd,YACA,aACA,YAA6B,UAC7B,WAAmB,KACL;CACd,OAAO,mBACL,WAAW,YAAY,EAAE,QAAQ,QAAQ,GAAG,GAC5C,QACA,WACA,UACA;EAAE,QAAQ;EAAY,SAAS;CAAY,CAC7C;AACF;AAEA,SAAgB,oBACd,YACA,YACA,WAAmB,KACL;CACd,OAAO,mBACL,WAAW,WAAW,GAAG,cACzB,YACA,UACA,UACA;EACE,QAAQ,GAAG,WAAW;EACtB,SAAS,GAAG,WAAW;CACzB,CACF;AACF;AAGA,SAAgB,uBACd,SACiC;CACjC,MAAM,gCAAgB,IAAI,IAAgC;CAE1D,KAAK,MAAM,UAAU,SAAS;EAE5B,MAAM,aAAa,OAAO;EAC1B,IAAI,CAAC,cAAc,IAAI,UAAU,GAC/B,cAAc,IAAI,YAAY,CAAC,CAAC;EAElC,MAAM,cAAc,cAAc,IAAI,UAAU;EAChD,IAAI,aACF,YAAY,KAAK,MAAM;CAE3B;CAEA,OAAO;AACT;;;;AAKA,SAAgB,mBACd,SAC6B;CAC7B,MAAM,gCAAgB,IAAI,IAA4B;CAEtD,KAAK,MAAM,UAAU,SAAS;EAE5B,IAAI,CAAC,cAAc,IAAI,OAAO,IAAI,GAChC,cAAc,IAAI,OAAO,MAAM,CAAC,CAAC;EAEnC,MAAM,cAAc,cAAc,IAAI,OAAO,IAAI;EACjD,IAAI,aACF,YAAY,KAAK,MAAM;CAE3B;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"fabricTextures.js","names":[],"sources":["../../src/utils/fabricTextures.ts"],"sourcesContent":["/**\n * Procedural fabric texture generation for realistic clothing\n *\n * **Korean**: 절차적 직물 텍스처 (Procedural Fabric Textures)\n *\n * Generates canvas-based textures for realistic dobok (도복) martial arts\n * uniform rendering without external image assets. Uses Three.js CanvasTexture\n * for efficient GPU-based texture mapping.\n *\n * **Features**:\n * - Procedural weave patterns for fabric realism\n * - Normal maps for surface detail and depth\n * - Roughness maps for material variation\n * - Memory-safe with proper cleanup\n *\n * @module utils/fabricTextures\n * @category Visual Effects\n * @korean 직물텍스처유틸\n */\n\nimport * as THREE from \"three\";\n\n/**\n * Fabric texture configuration\n */\nexport interface FabricTextureConfig {\n /** Base color for the fabric */\n readonly baseColor: string;\n /** Weave pattern density (higher = finer weave) */\n readonly weaveDensity: number;\n /** Texture resolution (power of 2 recommended) */\n readonly resolution: number;\n /** Thread variation intensity (0-1) */\n readonly threadVariation: number;\n /** Whether to generate normal map */\n readonly generateNormalMap: boolean;\n /** Whether to generate roughness map */\n readonly generateRoughnessMap: boolean;\n}\n\n/**\n * Generated fabric texture set\n */\nexport interface FabricTextureSet {\n /** Color/diffuse map */\n readonly colorMap: THREE.CanvasTexture;\n /** Normal map for surface detail (optional) */\n readonly normalMap?: THREE.CanvasTexture;\n /** Roughness map for material variation (optional) */\n readonly roughnessMap?: THREE.CanvasTexture;\n /** Cleanup function to dispose all textures */\n readonly dispose: () => void;\n}\n\n/**\n * Default fabric texture configurations for different materials\n */\nexport const FABRIC_PRESETS: Record<string, Partial<FabricTextureConfig>> = {\n /** Traditional cotton dobok (도복) */\n dobok: {\n weaveDensity: 32,\n threadVariation: 0.15,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n /** Tactical synthetic fabric */\n tactical: {\n weaveDensity: 48,\n threadVariation: 0.08,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n /** Leather material */\n leather: {\n weaveDensity: 16,\n threadVariation: 0.25,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: false,\n },\n /** Silk/satin material */\n silk: {\n weaveDensity: 64,\n threadVariation: 0.05,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n};\n\n/**\n * Convert hex color to RGB components\n */\nconst hexToRgb = (hex: string): { r: number; g: number; b: number } => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? {\n r: parseInt(result[1], 16),\n g: parseInt(result[2], 16),\n b: parseInt(result[3], 16),\n }\n : { r: 128, g: 128, b: 128 };\n};\n\n/**\n * Simple seeded random for reproducible textures\n */\nconst seededRandom = (seed: number): number => {\n const x = Math.sin(seed) * 10000;\n return x - Math.floor(x);\n};\n\n/**\n * Check if we're in a browser environment with canvas support\n */\nconst canCreateCanvas = (): boolean => {\n if (typeof document === \"undefined\") return false;\n try {\n const canvas = document.createElement(\"canvas\");\n return canvas.getContext(\"2d\") !== null;\n } catch {\n return false;\n }\n};\n\n/**\n * Create a fallback texture for test environments\n * Uses a minimal texture that works in all environments\n */\nconst createFallbackTexture = (): THREE.CanvasTexture => {\n // In test environments, THREE mocks may not have all features\n // Create a minimal mock that satisfies the interface\n try {\n const data = new Uint8Array([128, 128, 128, 255]);\n const dataTexture = new THREE.DataTexture(data, 1, 1, THREE.RGBAFormat);\n dataTexture.needsUpdate = true;\n return dataTexture as unknown as THREE.CanvasTexture;\n } catch {\n // Ultimate fallback - create a minimal mock texture object\n // Use numeric constants instead of THREE constants for test compatibility\n const mockTexture = {\n dispose: () => {},\n needsUpdate: true,\n wrapS: 1000, // THREE.RepeatWrapping value\n wrapT: 1000,\n repeat: { set: () => {} },\n uuid: \"mock-fabric-texture\",\n isTexture: true,\n };\n return mockTexture as unknown as THREE.CanvasTexture;\n }\n};\n\n/**\n * Generate a procedural fabric weave color map\n *\n * Creates a canvas-based texture with realistic thread patterns\n * simulating woven fabric like cotton dobok material.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture with weave pattern\n *\n * @korean 직물색상맵생성\n */\nexport const generateFabricColorMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { baseColor, weaveDensity, resolution, threadVariation } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n const rgb = hexToRgb(baseColor);\n\n // Fill base color\n ctx.fillStyle = baseColor;\n ctx.fillRect(0, 0, resolution, resolution);\n\n // Create weave pattern\n const threadWidth = resolution / weaveDensity;\n\n // Horizontal threads (weft)\n for (let y = 0; y < weaveDensity; y++) {\n const yPos = y * threadWidth;\n const variation = (seededRandom(y * 17) - 0.5) * threadVariation * 255;\n\n const r = Math.max(0, Math.min(255, rgb.r + variation));\n const g = Math.max(0, Math.min(255, rgb.g + variation));\n const b = Math.max(0, Math.min(255, rgb.b + variation));\n\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n\n // Draw thread with slight offset pattern\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n // Alternating over/under pattern\n if ((x + y) % 2 === 0) {\n ctx.fillRect(xPos, yPos, threadWidth * 0.95, threadWidth * 0.45);\n }\n }\n }\n\n // Vertical threads (warp)\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const variation = (seededRandom(x * 31) - 0.5) * threadVariation * 255;\n\n const r = Math.max(0, Math.min(255, rgb.r + variation * 0.8));\n const g = Math.max(0, Math.min(255, rgb.g + variation * 0.8));\n const b = Math.max(0, Math.min(255, rgb.b + variation * 0.8));\n\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n\n for (let y = 0; y < weaveDensity; y++) {\n const yPos = y * threadWidth;\n // Opposite pattern for weave effect\n if ((x + y) % 2 === 1) {\n ctx.fillRect(xPos, yPos, threadWidth * 0.45, threadWidth * 0.95);\n }\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4); // Tile the texture\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate a normal map for fabric surface detail\n *\n * Creates subtle bumps and ridges that simulate thread\n * texture on the fabric surface for enhanced realism.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture normal map\n *\n * @korean 법선맵생성\n */\nexport const generateFabricNormalMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { weaveDensity, resolution } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n // Neutral normal (pointing straight out)\n ctx.fillStyle = \"rgb(128, 128, 255)\";\n ctx.fillRect(0, 0, resolution, resolution);\n\n const threadWidth = resolution / weaveDensity;\n\n // Create normal variations for weave pattern\n for (let y = 0; y < weaveDensity; y++) {\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const yPos = y * threadWidth;\n\n // Create subtle normal variation based on weave position\n if ((x + y) % 2 === 0) {\n // Horizontal thread - slight upward normal\n ctx.fillStyle = \"rgb(128, 140, 255)\";\n ctx.fillRect(xPos, yPos, threadWidth * 0.9, threadWidth * 0.4);\n } else {\n // Vertical thread - slight rightward normal\n ctx.fillStyle = \"rgb(140, 128, 255)\";\n ctx.fillRect(xPos, yPos, threadWidth * 0.4, threadWidth * 0.9);\n }\n\n // Thread edge highlights\n const edgeIntensity = 20;\n ctx.fillStyle = `rgb(${128 + edgeIntensity}, ${128 + edgeIntensity}, 255)`;\n ctx.fillRect(xPos, yPos, threadWidth * 0.1, threadWidth);\n ctx.fillRect(xPos, yPos, threadWidth, threadWidth * 0.1);\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4);\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate a roughness map for fabric material variation\n *\n * Creates subtle roughness variations that simulate the\n * different reflective properties of woven threads.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture roughness map\n *\n * @korean 거칠기맵생성\n */\nexport const generateFabricRoughnessMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { weaveDensity, resolution, threadVariation } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n // Base roughness (white = rough, black = smooth)\n ctx.fillStyle = \"rgb(180, 180, 180)\";\n ctx.fillRect(0, 0, resolution, resolution);\n\n const threadWidth = resolution / weaveDensity;\n\n // Create roughness variation for weave pattern\n for (let y = 0; y < weaveDensity; y++) {\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const yPos = y * threadWidth;\n\n // Random roughness variation per thread\n const variation = seededRandom(x * 13 + y * 29) * threadVariation * 50;\n const roughness = Math.max(128, Math.min(230, 180 + variation));\n\n ctx.fillStyle = `rgb(${roughness}, ${roughness}, ${roughness})`;\n ctx.fillRect(xPos, yPos, threadWidth * 0.9, threadWidth * 0.9);\n\n // Thread gaps are slightly smoother\n ctx.fillStyle = \"rgb(160, 160, 160)\";\n ctx.fillRect(\n xPos + threadWidth * 0.9,\n yPos,\n threadWidth * 0.1,\n threadWidth,\n );\n ctx.fillRect(\n xPos,\n yPos + threadWidth * 0.9,\n threadWidth,\n threadWidth * 0.1,\n );\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4);\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate complete fabric texture set with all maps\n *\n * Creates a set of textures (color, normal, roughness) for\n * realistic fabric rendering with proper memory management.\n *\n * @param baseColor - Base color for the fabric (hex string)\n * @param preset - Preset name or custom config\n * @returns FabricTextureSet with all generated textures\n *\n * @example\n * ```typescript\n * const textures = generateFabricTextureSet(\"#2d2d2d\", \"dobok\");\n *\n * const material = new THREE.MeshPhysicalMaterial({\n * map: textures.colorMap,\n * normalMap: textures.normalMap,\n * roughnessMap: textures.roughnessMap,\n * });\n *\n * // Cleanup when done\n * textures.dispose();\n * ```\n *\n * @korean 완전직물텍스처세트생성\n */\nexport const generateFabricTextureSet = (\n baseColor: string,\n preset: keyof typeof FABRIC_PRESETS | Partial<FabricTextureConfig> = \"dobok\",\n): FabricTextureSet => {\n // Merge preset with defaults\n const presetConfig =\n typeof preset === \"string\" ? FABRIC_PRESETS[preset] : preset;\n\n const config: FabricTextureConfig = {\n baseColor,\n weaveDensity: presetConfig?.weaveDensity ?? 32,\n resolution: presetConfig?.resolution ?? 256,\n threadVariation: presetConfig?.threadVariation ?? 0.15,\n generateNormalMap: presetConfig?.generateNormalMap ?? true,\n generateRoughnessMap: presetConfig?.generateRoughnessMap ?? true,\n };\n\n // Generate textures\n const colorMap = generateFabricColorMap(config);\n const normalMap = config.generateNormalMap\n ? generateFabricNormalMap(config)\n : undefined;\n const roughnessMap = config.generateRoughnessMap\n ? generateFabricRoughnessMap(config)\n : undefined;\n\n // Cleanup function\n const dispose = () => {\n colorMap.dispose();\n normalMap?.dispose();\n roughnessMap?.dispose();\n };\n\n return {\n colorMap,\n normalMap,\n roughnessMap,\n dispose,\n };\n};\n\n/**\n * Pre-defined dobok colors for Korean martial arts uniforms\n */\nexport const DOBOK_COLORS = {\n /** Traditional white dobok (흰 도복) */\n WHITE: \"#f5f5f5\",\n /** Black dobok for masters (검정 도복) */\n BLACK: \"#1a1a1a\",\n /** Navy blue tactical (남색) */\n NAVY: \"#1a2744\",\n /** Traditional Korean gray (회색) */\n GRAY: \"#2d2d2d\",\n /** Dark red for elite (암적색) */\n DARK_RED: \"#4a1a1a\",\n /** Cyber cyan accent */\n CYBER_CYAN: \"#003333\",\n} as const;\n\nexport default generateFabricTextureSet;\n"],"mappings":"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"}
1
+ {"version":3,"file":"fabricTextures.js","names":[],"sources":["../../src/utils/fabricTextures.ts"],"sourcesContent":["/**\n * Procedural fabric texture generation for realistic clothing\n *\n * **Korean**: 절차적 직물 텍스처 (Procedural Fabric Textures)\n *\n * Generates canvas-based textures for realistic dobok (도복) martial arts\n * uniform rendering without external image assets. Uses Three.js CanvasTexture\n * for efficient GPU-based texture mapping.\n *\n * **Features**:\n * - Procedural weave patterns for fabric realism\n * - Normal maps for surface detail and depth\n * - Roughness maps for material variation\n * - Memory-safe with proper cleanup\n *\n * @module utils/fabricTextures\n * @category Visual Effects\n * @korean 직물텍스처유틸\n */\n\nimport * as THREE from \"three\";\n\n/**\n * Fabric texture configuration\n */\nexport interface FabricTextureConfig {\n /** Base color for the fabric */\n readonly baseColor: string;\n /** Weave pattern density (higher = finer weave) */\n readonly weaveDensity: number;\n /** Texture resolution (power of 2 recommended) */\n readonly resolution: number;\n /** Thread variation intensity (0-1) */\n readonly threadVariation: number;\n /** Whether to generate normal map */\n readonly generateNormalMap: boolean;\n /** Whether to generate roughness map */\n readonly generateRoughnessMap: boolean;\n}\n\n/**\n * Generated fabric texture set\n */\nexport interface FabricTextureSet {\n /** Color/diffuse map */\n readonly colorMap: THREE.CanvasTexture;\n /** Normal map for surface detail (optional) */\n readonly normalMap?: THREE.CanvasTexture;\n /** Roughness map for material variation (optional) */\n readonly roughnessMap?: THREE.CanvasTexture;\n /** Cleanup function to dispose all textures */\n readonly dispose: () => void;\n}\n\n/**\n * Default fabric texture configurations for different materials\n */\nexport const FABRIC_PRESETS: Record<string, Partial<FabricTextureConfig>> = {\n /** Traditional cotton dobok (도복) */\n dobok: {\n weaveDensity: 32,\n threadVariation: 0.15,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n /** Tactical synthetic fabric */\n tactical: {\n weaveDensity: 48,\n threadVariation: 0.08,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n /** Leather material */\n leather: {\n weaveDensity: 16,\n threadVariation: 0.25,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: false,\n },\n /** Silk/satin material */\n silk: {\n weaveDensity: 64,\n threadVariation: 0.05,\n resolution: 256,\n generateNormalMap: true,\n generateRoughnessMap: true,\n },\n};\n\n/**\n * Convert hex color to RGB components\n */\nconst hexToRgb = (hex: string): { r: number; g: number; b: number } => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? {\n r: parseInt(result[1], 16),\n g: parseInt(result[2], 16),\n b: parseInt(result[3], 16),\n }\n : { r: 128, g: 128, b: 128 };\n};\n\n/**\n * Simple seeded random for reproducible textures\n */\nconst seededRandom = (seed: number): number => {\n const x = Math.sin(seed) * 10000;\n return x - Math.floor(x);\n};\n\n/**\n * Check if we're in a browser environment with canvas support\n */\nconst canCreateCanvas = (): boolean => {\n if (typeof document === \"undefined\") return false;\n try {\n const canvas = document.createElement(\"canvas\");\n return canvas.getContext(\"2d\") !== null;\n } catch {\n return false;\n }\n};\n\n/**\n * Create a fallback texture for test environments\n * Uses a minimal texture that works in all environments\n */\nconst createFallbackTexture = (): THREE.CanvasTexture => {\n // In test environments, THREE mocks may not have all features\n // Create a minimal mock that satisfies the interface\n try {\n const data = new Uint8Array([128, 128, 128, 255]);\n const dataTexture = new THREE.DataTexture(data, 1, 1, THREE.RGBAFormat);\n dataTexture.needsUpdate = true;\n return dataTexture as unknown as THREE.CanvasTexture;\n } catch {\n // Ultimate fallback - create a minimal mock texture object\n // Use numeric constants instead of THREE constants for test compatibility\n const mockTexture = {\n dispose: () => {},\n needsUpdate: true,\n wrapS: 1000, // THREE.RepeatWrapping value\n wrapT: 1000,\n repeat: { set: () => {} },\n uuid: \"mock-fabric-texture\",\n isTexture: true,\n };\n return mockTexture as unknown as THREE.CanvasTexture;\n }\n};\n\n/**\n * Generate a procedural fabric weave color map\n *\n * Creates a canvas-based texture with realistic thread patterns\n * simulating woven fabric like cotton dobok material.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture with weave pattern\n *\n * @korean 직물색상맵생성\n */\nexport const generateFabricColorMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { baseColor, weaveDensity, resolution, threadVariation } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n const rgb = hexToRgb(baseColor);\n\n // Fill base color\n ctx.fillStyle = baseColor;\n ctx.fillRect(0, 0, resolution, resolution);\n\n // Create weave pattern\n const threadWidth = resolution / weaveDensity;\n\n // Horizontal threads (weft)\n for (let y = 0; y < weaveDensity; y++) {\n const yPos = y * threadWidth;\n const variation = (seededRandom(y * 17) - 0.5) * threadVariation * 255;\n\n const r = Math.max(0, Math.min(255, rgb.r + variation));\n const g = Math.max(0, Math.min(255, rgb.g + variation));\n const b = Math.max(0, Math.min(255, rgb.b + variation));\n\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n\n // Draw thread with slight offset pattern\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n // Alternating over/under pattern\n if ((x + y) % 2 === 0) {\n ctx.fillRect(xPos, yPos, threadWidth * 0.95, threadWidth * 0.45);\n }\n }\n }\n\n // Vertical threads (warp)\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const variation = (seededRandom(x * 31) - 0.5) * threadVariation * 255;\n\n const r = Math.max(0, Math.min(255, rgb.r + variation * 0.8));\n const g = Math.max(0, Math.min(255, rgb.g + variation * 0.8));\n const b = Math.max(0, Math.min(255, rgb.b + variation * 0.8));\n\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n\n for (let y = 0; y < weaveDensity; y++) {\n const yPos = y * threadWidth;\n // Opposite pattern for weave effect\n if ((x + y) % 2 === 1) {\n ctx.fillRect(xPos, yPos, threadWidth * 0.45, threadWidth * 0.95);\n }\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4); // Tile the texture\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate a normal map for fabric surface detail\n *\n * Creates subtle bumps and ridges that simulate thread\n * texture on the fabric surface for enhanced realism.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture normal map\n *\n * @korean 법선맵생성\n */\nexport const generateFabricNormalMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { weaveDensity, resolution } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n // Neutral normal (pointing straight out)\n ctx.fillStyle = \"rgb(128, 128, 255)\";\n ctx.fillRect(0, 0, resolution, resolution);\n\n const threadWidth = resolution / weaveDensity;\n\n // Create normal variations for weave pattern\n for (let y = 0; y < weaveDensity; y++) {\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const yPos = y * threadWidth;\n\n // Create subtle normal variation based on weave position\n if ((x + y) % 2 === 0) {\n // Horizontal thread - slight upward normal\n ctx.fillStyle = \"rgb(128, 140, 255)\";\n ctx.fillRect(xPos, yPos, threadWidth * 0.9, threadWidth * 0.4);\n } else {\n // Vertical thread - slight rightward normal\n ctx.fillStyle = \"rgb(140, 128, 255)\";\n ctx.fillRect(xPos, yPos, threadWidth * 0.4, threadWidth * 0.9);\n }\n\n // Thread edge highlights\n const edgeIntensity = 20;\n ctx.fillStyle = `rgb(${128 + edgeIntensity}, ${128 + edgeIntensity}, 255)`;\n ctx.fillRect(xPos, yPos, threadWidth * 0.1, threadWidth);\n ctx.fillRect(xPos, yPos, threadWidth, threadWidth * 0.1);\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4);\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate a roughness map for fabric material variation\n *\n * Creates subtle roughness variations that simulate the\n * different reflective properties of woven threads.\n *\n * @param config - Fabric texture configuration\n * @returns CanvasTexture roughness map\n *\n * @korean 거칠기맵생성\n */\nexport const generateFabricRoughnessMap = (\n config: FabricTextureConfig,\n): THREE.CanvasTexture => {\n // Return fallback for test environments without canvas support\n if (!canCreateCanvas()) {\n return createFallbackTexture();\n }\n\n const { weaveDensity, resolution, threadVariation } = config;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = resolution;\n canvas.height = resolution;\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n return createFallbackTexture();\n }\n\n // Base roughness (white = rough, black = smooth)\n ctx.fillStyle = \"rgb(180, 180, 180)\";\n ctx.fillRect(0, 0, resolution, resolution);\n\n const threadWidth = resolution / weaveDensity;\n\n // Create roughness variation for weave pattern\n for (let y = 0; y < weaveDensity; y++) {\n for (let x = 0; x < weaveDensity; x++) {\n const xPos = x * threadWidth;\n const yPos = y * threadWidth;\n\n // Random roughness variation per thread\n const variation = seededRandom(x * 13 + y * 29) * threadVariation * 50;\n const roughness = Math.max(128, Math.min(230, 180 + variation));\n\n ctx.fillStyle = `rgb(${roughness}, ${roughness}, ${roughness})`;\n ctx.fillRect(xPos, yPos, threadWidth * 0.9, threadWidth * 0.9);\n\n // Thread gaps are slightly smoother\n ctx.fillStyle = \"rgb(160, 160, 160)\";\n ctx.fillRect(\n xPos + threadWidth * 0.9,\n yPos,\n threadWidth * 0.1,\n threadWidth,\n );\n ctx.fillRect(\n xPos,\n yPos + threadWidth * 0.9,\n threadWidth,\n threadWidth * 0.1,\n );\n }\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n texture.wrapS = THREE.RepeatWrapping;\n texture.wrapT = THREE.RepeatWrapping;\n texture.repeat.set(4, 4);\n texture.needsUpdate = true;\n\n return texture;\n};\n\n/**\n * Generate complete fabric texture set with all maps\n *\n * Creates a set of textures (color, normal, roughness) for\n * realistic fabric rendering with proper memory management.\n *\n * @param baseColor - Base color for the fabric (hex string)\n * @param preset - Preset name or custom config\n * @returns FabricTextureSet with all generated textures\n *\n * @example\n * ```typescript\n * const textures = generateFabricTextureSet(\"#2d2d2d\", \"dobok\");\n *\n * const material = new THREE.MeshPhysicalMaterial({\n * map: textures.colorMap,\n * normalMap: textures.normalMap,\n * roughnessMap: textures.roughnessMap,\n * });\n *\n * // Cleanup when done\n * textures.dispose();\n * ```\n *\n * @korean 완전직물텍스처세트생성\n */\nexport const generateFabricTextureSet = (\n baseColor: string,\n preset: keyof typeof FABRIC_PRESETS | Partial<FabricTextureConfig> = \"dobok\",\n): FabricTextureSet => {\n // Merge preset with defaults\n const presetConfig =\n typeof preset === \"string\" ? FABRIC_PRESETS[preset] : preset;\n\n const config: FabricTextureConfig = {\n baseColor,\n weaveDensity: presetConfig?.weaveDensity ?? 32,\n resolution: presetConfig?.resolution ?? 256,\n threadVariation: presetConfig?.threadVariation ?? 0.15,\n generateNormalMap: presetConfig?.generateNormalMap ?? true,\n generateRoughnessMap: presetConfig?.generateRoughnessMap ?? true,\n };\n\n // Generate textures\n const colorMap = generateFabricColorMap(config);\n const normalMap = config.generateNormalMap\n ? generateFabricNormalMap(config)\n : undefined;\n const roughnessMap = config.generateRoughnessMap\n ? generateFabricRoughnessMap(config)\n : undefined;\n\n // Cleanup function\n const dispose = () => {\n colorMap.dispose();\n normalMap?.dispose();\n roughnessMap?.dispose();\n };\n\n return {\n colorMap,\n normalMap,\n roughnessMap,\n dispose,\n };\n};\n\n/**\n * Pre-defined dobok colors for Korean martial arts uniforms\n */\nexport const DOBOK_COLORS = {\n /** Traditional white dobok (흰 도복) */\n WHITE: \"#f5f5f5\",\n /** Black dobok for masters (검정 도복) */\n BLACK: \"#1a1a1a\",\n /** Navy blue tactical (남색) */\n NAVY: \"#1a2744\",\n /** Traditional Korean gray (회색) */\n GRAY: \"#2d2d2d\",\n /** Dark red for elite (암적색) */\n DARK_RED: \"#4a1a1a\",\n /** Cyber cyan accent */\n CYBER_CYAN: \"#003333\",\n} as const;\n\nexport default generateFabricTextureSet;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,iBAA+D;;CAE1E,OAAO;EACL,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,mBAAmB;EACnB,sBAAsB;CACxB;;CAEA,UAAU;EACR,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,mBAAmB;EACnB,sBAAsB;CACxB;;CAEA,SAAS;EACP,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,mBAAmB;EACnB,sBAAsB;CACxB;;CAEA,MAAM;EACJ,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,mBAAmB;EACnB,sBAAsB;CACxB;AACF;;;;AAKA,IAAM,YAAY,QAAqD;CACrE,MAAM,SAAS,4CAA4C,KAAK,GAAG;CACnE,OAAO,SACH;EACE,GAAG,SAAS,OAAO,IAAI,EAAE;EACzB,GAAG,SAAS,OAAO,IAAI,EAAE;EACzB,GAAG,SAAS,OAAO,IAAI,EAAE;CAC3B,IACA;EAAE,GAAG;EAAK,GAAG;EAAK,GAAG;CAAI;AAC/B;;;;AAKA,IAAM,gBAAgB,SAAyB;CAC7C,MAAM,IAAI,KAAK,IAAI,IAAI,IAAI;CAC3B,OAAO,IAAI,KAAK,MAAM,CAAC;AACzB;;;;AAKA,IAAM,wBAAiC;CACrC,IAAI,OAAO,aAAa,aAAa,OAAO;CAC5C,IAAI;EAEF,OADe,SAAS,cAAc,QAC/B,EAAO,WAAW,IAAI,MAAM;CACrC,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,IAAM,8BAAmD;CAGvD,IAAI;EACF,MAAM,OAAO,IAAI,WAAW;GAAC;GAAK;GAAK;GAAK;EAAG,CAAC;EAChD,MAAM,cAAc,IAAI,MAAM,YAAY,MAAM,GAAG,GAAG,MAAM,UAAU;EACtE,YAAY,cAAc;EAC1B,OAAO;CACT,QAAQ;EAYN,OAAO;GARL,eAAe,CAAC;GAChB,aAAa;GACb,OAAO;GACP,OAAO;GACP,QAAQ,EAAE,WAAW,CAAC,EAAE;GACxB,MAAM;GACN,WAAW;EAEN;CACT;AACF;;;;;;;;;;;;AAaA,IAAa,0BACX,WACwB;CAExB,IAAI,CAAC,gBAAgB,GACnB,OAAO,sBAAsB;CAG/B,MAAM,EAAE,WAAW,cAAc,YAAY,oBAAoB;CAEjE,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,QAAQ;CACf,OAAO,SAAS;CAChB,MAAM,MAAM,OAAO,WAAW,IAAI;CAElC,IAAI,CAAC,KACH,OAAO,sBAAsB;CAG/B,MAAM,MAAM,SAAS,SAAS;CAG9B,IAAI,YAAY;CAChB,IAAI,SAAS,GAAG,GAAG,YAAY,UAAU;CAGzC,MAAM,cAAc,aAAa;CAGjC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,OAAO,IAAI;EACjB,MAAM,aAAa,aAAa,IAAI,EAAE,IAAI,MAAO,kBAAkB;EAMnE,IAAI,YAAY,OAJN,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,SAAS,CAI9B,EAAE,IAHf,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,SAAS,CAGxB,EAAE,IAFrB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,SAAS,CAElB,EAAE;EAGrC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,MAAM,OAAO,IAAI;GAEjB,KAAK,IAAI,KAAK,MAAM,GAClB,IAAI,SAAS,MAAM,MAAM,cAAc,KAAM,cAAc,GAAI;EAEnE;CACF;CAGA,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,OAAO,IAAI;EACjB,MAAM,aAAa,aAAa,IAAI,EAAE,IAAI,MAAO,kBAAkB;EAMnE,IAAI,YAAY,OAJN,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,YAAY,EAAG,CAIpC,EAAE,IAHf,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,YAAY,EAAG,CAG9B,EAAE,IAFrB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,YAAY,EAAG,CAExB,EAAE;EAErC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,MAAM,OAAO,IAAI;GAEjB,KAAK,IAAI,KAAK,MAAM,GAClB,IAAI,SAAS,MAAM,MAAM,cAAc,KAAM,cAAc,GAAI;EAEnE;CACF;CAEA,MAAM,UAAU,IAAI,MAAM,cAAc,MAAM;CAC9C,QAAQ,QAAQ,MAAM;CACtB,QAAQ,QAAQ,MAAM;CACtB,QAAQ,OAAO,IAAI,GAAG,CAAC;CACvB,QAAQ,cAAc;CAEtB,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,2BACX,WACwB;CAExB,IAAI,CAAC,gBAAgB,GACnB,OAAO,sBAAsB;CAG/B,MAAM,EAAE,cAAc,eAAe;CAErC,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,QAAQ;CACf,OAAO,SAAS;CAChB,MAAM,MAAM,OAAO,WAAW,IAAI;CAElC,IAAI,CAAC,KACH,OAAO,sBAAsB;CAI/B,IAAI,YAAY;CAChB,IAAI,SAAS,GAAG,GAAG,YAAY,UAAU;CAEzC,MAAM,cAAc,aAAa;CAGjC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAChC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,OAAO,IAAI;EACjB,MAAM,OAAO,IAAI;EAGjB,KAAK,IAAI,KAAK,MAAM,GAAG;GAErB,IAAI,YAAY;GAChB,IAAI,SAAS,MAAM,MAAM,cAAc,IAAK,cAAc,EAAG;EAC/D,OAAO;GAEL,IAAI,YAAY;GAChB,IAAI,SAAS,MAAM,MAAM,cAAc,IAAK,cAAc,EAAG;EAC/D;EAGA,MAAM,gBAAgB;EACtB,IAAI,YAAY,OAAO,MAAM,cAAc,IAAI,MAAM,cAAc;EACnE,IAAI,SAAS,MAAM,MAAM,cAAc,IAAK,WAAW;EACvD,IAAI,SAAS,MAAM,MAAM,aAAa,cAAc,EAAG;CACzD;CAGF,MAAM,UAAU,IAAI,MAAM,cAAc,MAAM;CAC9C,QAAQ,QAAQ,MAAM;CACtB,QAAQ,QAAQ,MAAM;CACtB,QAAQ,OAAO,IAAI,GAAG,CAAC;CACvB,QAAQ,cAAc;CAEtB,OAAO;AACT;;;;;;;;;;;;AAaA,IAAa,8BACX,WACwB;CAExB,IAAI,CAAC,gBAAgB,GACnB,OAAO,sBAAsB;CAG/B,MAAM,EAAE,cAAc,YAAY,oBAAoB;CAEtD,MAAM,SAAS,SAAS,cAAc,QAAQ;CAC9C,OAAO,QAAQ;CACf,OAAO,SAAS;CAChB,MAAM,MAAM,OAAO,WAAW,IAAI;CAElC,IAAI,CAAC,KACH,OAAO,sBAAsB;CAI/B,IAAI,YAAY;CAChB,IAAI,SAAS,GAAG,GAAG,YAAY,UAAU;CAEzC,MAAM,cAAc,aAAa;CAGjC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAChC,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,OAAO,IAAI;EACjB,MAAM,OAAO,IAAI;EAGjB,MAAM,YAAY,aAAa,IAAI,KAAK,IAAI,EAAE,IAAI,kBAAkB;EACpE,MAAM,YAAY,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,SAAS,CAAC;EAE9D,IAAI,YAAY,OAAO,UAAU,IAAI,UAAU,IAAI,UAAU;EAC7D,IAAI,SAAS,MAAM,MAAM,cAAc,IAAK,cAAc,EAAG;EAG7D,IAAI,YAAY;EAChB,IAAI,SACF,OAAO,cAAc,IACrB,MACA,cAAc,IACd,WACF;EACA,IAAI,SACF,MACA,OAAO,cAAc,IACrB,aACA,cAAc,EAChB;CACF;CAGF,MAAM,UAAU,IAAI,MAAM,cAAc,MAAM;CAC9C,QAAQ,QAAQ,MAAM;CACtB,QAAQ,QAAQ,MAAM;CACtB,QAAQ,OAAO,IAAI,GAAG,CAAC;CACvB,QAAQ,cAAc;CAEtB,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,4BACX,WACA,SAAqE,YAChD;CAErB,MAAM,eACJ,OAAO,WAAW,WAAW,eAAe,UAAU;CAExD,MAAM,SAA8B;EAClC;EACA,cAAc,cAAc,gBAAgB;EAC5C,YAAY,cAAc,cAAc;EACxC,iBAAiB,cAAc,mBAAmB;EAClD,mBAAmB,cAAc,qBAAqB;EACtD,sBAAsB,cAAc,wBAAwB;CAC9D;CAGA,MAAM,WAAW,uBAAuB,MAAM;CAC9C,MAAM,YAAY,OAAO,oBACrB,wBAAwB,MAAM,IAC9B,KAAA;CACJ,MAAM,eAAe,OAAO,uBACxB,2BAA2B,MAAM,IACjC,KAAA;CAGJ,MAAM,gBAAgB;EACpB,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,cAAc,QAAQ;CACxB;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"hapticFeedback.js","names":[],"sources":["../../src/utils/hapticFeedback.ts"],"sourcesContent":["/**\n * Haptic feedback utilities for mobile touch interactions\n * \n * Provides vibration feedback for button presses and combat actions\n * with support for different intensity levels on iOS and Android.\n * \n * This module provides an enhanced haptic feedback API with typed intensity levels\n * and pattern support. For legacy compatibility with basic vibration patterns,\n * see src/utils/haptics.ts\n * \n * @module utils/hapticFeedback\n * @category Mobile Utilities\n * @korean 햅틱피드백유틸리티\n */\n\n/**\n * Haptic feedback intensity levels\n * \n * @category Haptic Feedback\n * @korean 햅틱강도\n */\nexport type HapticIntensity = \"light\" | \"medium\" | \"heavy\";\n\n/**\n * Haptic feedback pattern configuration\n * \n * @category Haptic Feedback\n * @korean 햅틱패턴설정\n */\ninterface HapticPattern {\n readonly duration: number; // Duration in milliseconds\n readonly pattern?: readonly number[]; // For Android pattern vibration\n}\n\n/**\n * Haptic patterns for different intensities\n * \n * @category Haptic Feedback\n * @korean 햅틱패턴\n */\nconst HAPTIC_PATTERNS: Record<HapticIntensity, HapticPattern> = {\n light: {\n duration: 10,\n pattern: [10],\n },\n medium: {\n duration: 50,\n pattern: [50],\n },\n heavy: {\n duration: 100,\n pattern: [100],\n },\n};\n\n/**\n * Check if haptic feedback is supported\n * \n * @returns Whether haptic feedback is available\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerHapticFeedback('medium');\n * }\n * ```\n * \n * @korean 햅틱지원여부\n */\nexport function isHapticSupported(): boolean {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n \n // Check for modern vibrate API or webkit prefixed version\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n return \"vibrate\" in navigator || !!nav.webkitVibrate;\n}\n\n/**\n * Trigger haptic feedback with specified intensity\n * \n * @param intensity - Feedback intensity level\n * @returns Whether feedback was triggered successfully\n * \n * @example\n * ```typescript\n * // Light feedback for menu navigation\n * triggerHapticFeedback('light');\n * \n * // Medium feedback for button press\n * triggerHapticFeedback('medium');\n * \n * // Heavy feedback for critical hit\n * triggerHapticFeedback('heavy');\n * ```\n * \n * @korean 햅틱피드백트리거\n */\nexport function triggerHapticFeedback(intensity: HapticIntensity): boolean {\n if (!isHapticSupported()) {\n return false;\n }\n\n const pattern = HAPTIC_PATTERNS[intensity];\n\n try {\n // Try modern Vibration API\n if (\"vibrate\" in navigator) {\n return navigator.vibrate(pattern.duration);\n }\n\n // Try webkit prefixed version for older devices\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n if (nav.webkitVibrate) {\n return nav.webkitVibrate(pattern.duration);\n }\n } catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\"Haptic feedback failed:\", error);\n }\n }\n\n return false;\n}\n\n/**\n * Trigger custom haptic pattern\n * Useful for complex feedback sequences\n * \n * @param pattern - Array of vibration durations and pauses (ms)\n * @returns Whether pattern was triggered successfully\n * \n * @example\n * ```typescript\n * // Double tap feedback: vibrate 50ms, pause 50ms, vibrate 50ms\n * triggerHapticPattern([50, 50, 50]);\n * ```\n * \n * @korean 햅틱패턴트리거\n */\nexport function triggerHapticPattern(pattern: readonly number[]): boolean {\n if (!isHapticSupported()) {\n return false;\n }\n\n try {\n if (\"vibrate\" in navigator) {\n return navigator.vibrate(pattern as number[]);\n }\n\n // Try webkit prefixed version for older devices\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n if (nav.webkitVibrate) {\n return nav.webkitVibrate(pattern as number[]);\n }\n } catch (error) {\n console.warn(\"Haptic pattern failed:\", error);\n }\n\n return false;\n}\n\n/**\n * Haptic feedback for UI interactions\n * Pre-configured patterns for common UI elements\n * \n * @category Haptic Feedback\n * @korean UI햅틱피드백\n */\nexport const UIHaptics = {\n /** Button tap feedback @korean 버튼탭피드백 */\n buttonTap: () => triggerHapticFeedback(\"medium\"),\n /** Menu hover/navigation feedback @korean 메뉴호버피드백 */\n menuHover: () => triggerHapticFeedback(\"light\"),\n /** Stance change feedback @korean 자세변경피드백 */\n stanceChange: () => triggerHapticFeedback(\"light\"),\n /** Attack execution feedback @korean 공격실행피드백 */\n attackExecution: () => triggerHapticFeedback(\"medium\"),\n /** Critical hit feedback @korean 치명타피드백 */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n /** Vital point strike feedback @korean 급소타격피드백 */\n vitalPointStrike: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n /** Block/defend feedback @korean 방어피드백 */\n blockDefend: () => triggerHapticFeedback(\"medium\"),\n /** Error/invalid action feedback @korean 오류피드백 */\n error: () => triggerHapticPattern([50, 50, 50]),\n /** Success/achievement feedback @korean 성공피드백 */\n success: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n} as const;\n\n/**\n * Combat haptic feedback manager\n * Provides context-aware haptic feedback for combat actions\n * \n * @category Haptic Feedback\n * @korean 전투햅틱피드백\n */\nexport const CombatHaptics = {\n /** Normal hit feedback @korean 일반타격피드백 */\n normalHit: () => triggerHapticFeedback(\"medium\"),\n /** Critical hit feedback with double pulse @korean 치명타피드백 */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n /** Perfect strike feedback (triple pulse) @korean 완벽타격피드백 */\n perfectStrike: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n /** Vital point strike feedback (intense pattern) @korean 급소타격피드백 */\n vitalPointHit: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n /** Blocked attack feedback (sharp double pulse) @korean 방어성공피드백 */\n attackBlocked: () => triggerHapticPattern([30, 30, 30]),\n /** Miss feedback (quick light pulse) @korean 빗나감피드백 */\n attackMissed: () => triggerHapticFeedback(\"light\"),\n /** Counter-attack feedback (building intensity) @korean 반격피드백 */\n counterAttack: () => triggerHapticPattern([30, 20, 50, 20, 100]),\n /**\n * Combo hit feedback (rhythmic pattern)\n * @korean 콤보타격피드백\n */\n comboHit: (comboCount: number) => {\n const pattern = Array(Math.min(comboCount, 5))\n .fill(null)\n .flatMap(() => [30, 20]);\n pattern.push(50); // Final pulse\n return triggerHapticPattern(pattern);\n },\n /** Heavy damage taken feedback (long intense pulse) @korean 큰피해피드백 */\n heavyDamage: () => triggerHapticPattern([150, 50, 150]),\n /** Knockout feedback (dramatic pattern) @korean 녹다운피드백 */\n knockout: () => triggerHapticPattern([100, 50, 100, 50, 200]),\n} as const;\n\n/**\n * Training mode haptic feedback\n * Provides feedback for training exercises\n * \n * @category Haptic Feedback\n * @korean 훈련햅틱피드백\n */\nexport const TrainingHaptics = {\n /** Correct technique feedback @korean 정확한기술피드백 */\n correctTechnique: () => triggerHapticPattern([30, 30, 100]),\n /** Incorrect technique feedback @korean 부정확한기술피드백 */\n incorrectTechnique: () => triggerHapticPattern([50, 50, 50]),\n /** Target hit feedback @korean 표적명중피드백 */\n targetHit: () => triggerHapticFeedback(\"medium\"),\n /** Perfect timing feedback @korean 완벽한타이밍피드백 */\n perfectTiming: () => triggerHapticPattern([20, 20, 20, 20, 80]),\n /** New skill unlocked feedback @korean 새기술해금피드백 */\n skillUnlocked: () => triggerHapticPattern([50, 30, 50, 30, 50, 30, 150]),\n} as const;\n"],"mappings":";;;;;;;AAwCA,IAAM,kBAA0D;CAC9D,OAAO;EACL,UAAU;EACV,SAAS,CAAC,GAAG;EACd;CACD,QAAQ;EACN,UAAU;EACV,SAAS,CAAC,GAAG;EACd;CACD,OAAO;EACL,UAAU;EACV,SAAS,CAAC,IAAI;EACf;CACF;;;;;;;;;;;;;;;AAgBD,SAAgB,oBAA6B;CAC3C,IAAI,OAAO,cAAc,aACvB,OAAO;CAIT,MAAM,MAAM;CACZ,OAAO,aAAa,aAAa,CAAC,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;AAuBzC,SAAgB,sBAAsB,WAAqC;CACzE,IAAI,CAAC,mBAAmB,EACtB,OAAO;CAGT,MAAM,UAAU,gBAAgB;CAEhC,IAAI;EAEF,IAAI,aAAa,WACf,OAAO,UAAU,QAAQ,QAAQ,SAAS;EAI5C,MAAM,MAAM;EACZ,IAAI,IAAI,eACN,OAAO,IAAI,cAAc,QAAQ,SAAS;UAErC,OAAO;EACd,IAAA,QAAA,IAAA,aAA6B,cAC3B,QAAQ,KAAK,2BAA2B,MAAM;;CAIlD,OAAO;;;;;;;;;;;;;;;;;AAkBT,SAAgB,qBAAqB,SAAqC;CACxE,IAAI,CAAC,mBAAmB,EACtB,OAAO;CAGT,IAAI;EACF,IAAI,aAAa,WACf,OAAO,UAAU,QAAQ,QAAoB;EAI/C,MAAM,MAAM;EACZ,IAAI,IAAI,eACN,OAAO,IAAI,cAAc,QAAoB;UAExC,OAAO;EACd,QAAQ,KAAK,0BAA0B,MAAM;;CAG/C,OAAO;;;;;;;;;AAUT,IAAa,YAAY;;CAEvB,iBAAiB,sBAAsB,SAAS;;CAEhD,iBAAiB,sBAAsB,QAAQ;;CAE/C,oBAAoB,sBAAsB,QAAQ;;CAElD,uBAAuB,sBAAsB,SAAS;;CAEtD,mBAAmB,qBAAqB;EAAC;EAAI;EAAI;EAAI,CAAC;;CAEtD,wBAAwB,qBAAqB;EAAC;EAAK;EAAI;EAAK;EAAI;EAAI,CAAC;;CAErE,mBAAmB,sBAAsB,SAAS;;CAElD,aAAa,qBAAqB;EAAC;EAAI;EAAI;EAAG,CAAC;;CAE/C,eAAe,qBAAqB;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI,CAAC;CAC3D"}
1
+ {"version":3,"file":"hapticFeedback.js","names":[],"sources":["../../src/utils/hapticFeedback.ts"],"sourcesContent":["/**\n * Haptic feedback utilities for mobile touch interactions\n * \n * Provides vibration feedback for button presses and combat actions\n * with support for different intensity levels on iOS and Android.\n * \n * This module provides an enhanced haptic feedback API with typed intensity levels\n * and pattern support. For legacy compatibility with basic vibration patterns,\n * see src/utils/haptics.ts\n * \n * @module utils/hapticFeedback\n * @category Mobile Utilities\n * @korean 햅틱피드백유틸리티\n */\n\n/**\n * Haptic feedback intensity levels\n * \n * @category Haptic Feedback\n * @korean 햅틱강도\n */\nexport type HapticIntensity = \"light\" | \"medium\" | \"heavy\";\n\n/**\n * Haptic feedback pattern configuration\n * \n * @category Haptic Feedback\n * @korean 햅틱패턴설정\n */\ninterface HapticPattern {\n readonly duration: number; // Duration in milliseconds\n readonly pattern?: readonly number[]; // For Android pattern vibration\n}\n\n/**\n * Haptic patterns for different intensities\n * \n * @category Haptic Feedback\n * @korean 햅틱패턴\n */\nconst HAPTIC_PATTERNS: Record<HapticIntensity, HapticPattern> = {\n light: {\n duration: 10,\n pattern: [10],\n },\n medium: {\n duration: 50,\n pattern: [50],\n },\n heavy: {\n duration: 100,\n pattern: [100],\n },\n};\n\n/**\n * Check if haptic feedback is supported\n * \n * @returns Whether haptic feedback is available\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerHapticFeedback('medium');\n * }\n * ```\n * \n * @korean 햅틱지원여부\n */\nexport function isHapticSupported(): boolean {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n \n // Check for modern vibrate API or webkit prefixed version\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n return \"vibrate\" in navigator || !!nav.webkitVibrate;\n}\n\n/**\n * Trigger haptic feedback with specified intensity\n * \n * @param intensity - Feedback intensity level\n * @returns Whether feedback was triggered successfully\n * \n * @example\n * ```typescript\n * // Light feedback for menu navigation\n * triggerHapticFeedback('light');\n * \n * // Medium feedback for button press\n * triggerHapticFeedback('medium');\n * \n * // Heavy feedback for critical hit\n * triggerHapticFeedback('heavy');\n * ```\n * \n * @korean 햅틱피드백트리거\n */\nexport function triggerHapticFeedback(intensity: HapticIntensity): boolean {\n if (!isHapticSupported()) {\n return false;\n }\n\n const pattern = HAPTIC_PATTERNS[intensity];\n\n try {\n // Try modern Vibration API\n if (\"vibrate\" in navigator) {\n return navigator.vibrate(pattern.duration);\n }\n\n // Try webkit prefixed version for older devices\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n if (nav.webkitVibrate) {\n return nav.webkitVibrate(pattern.duration);\n }\n } catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\"Haptic feedback failed:\", error);\n }\n }\n\n return false;\n}\n\n/**\n * Trigger custom haptic pattern\n * Useful for complex feedback sequences\n * \n * @param pattern - Array of vibration durations and pauses (ms)\n * @returns Whether pattern was triggered successfully\n * \n * @example\n * ```typescript\n * // Double tap feedback: vibrate 50ms, pause 50ms, vibrate 50ms\n * triggerHapticPattern([50, 50, 50]);\n * ```\n * \n * @korean 햅틱패턴트리거\n */\nexport function triggerHapticPattern(pattern: readonly number[]): boolean {\n if (!isHapticSupported()) {\n return false;\n }\n\n try {\n if (\"vibrate\" in navigator) {\n return navigator.vibrate(pattern as number[]);\n }\n\n // Try webkit prefixed version for older devices\n const nav = navigator as Navigator & { webkitVibrate?: (pattern: number | number[]) => boolean };\n if (nav.webkitVibrate) {\n return nav.webkitVibrate(pattern as number[]);\n }\n } catch (error) {\n console.warn(\"Haptic pattern failed:\", error);\n }\n\n return false;\n}\n\n/**\n * Haptic feedback for UI interactions\n * Pre-configured patterns for common UI elements\n * \n * @category Haptic Feedback\n * @korean UI햅틱피드백\n */\nexport const UIHaptics = {\n /** Button tap feedback @korean 버튼탭피드백 */\n buttonTap: () => triggerHapticFeedback(\"medium\"),\n /** Menu hover/navigation feedback @korean 메뉴호버피드백 */\n menuHover: () => triggerHapticFeedback(\"light\"),\n /** Stance change feedback @korean 자세변경피드백 */\n stanceChange: () => triggerHapticFeedback(\"light\"),\n /** Attack execution feedback @korean 공격실행피드백 */\n attackExecution: () => triggerHapticFeedback(\"medium\"),\n /** Critical hit feedback @korean 치명타피드백 */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n /** Vital point strike feedback @korean 급소타격피드백 */\n vitalPointStrike: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n /** Block/defend feedback @korean 방어피드백 */\n blockDefend: () => triggerHapticFeedback(\"medium\"),\n /** Error/invalid action feedback @korean 오류피드백 */\n error: () => triggerHapticPattern([50, 50, 50]),\n /** Success/achievement feedback @korean 성공피드백 */\n success: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n} as const;\n\n/**\n * Combat haptic feedback manager\n * Provides context-aware haptic feedback for combat actions\n * \n * @category Haptic Feedback\n * @korean 전투햅틱피드백\n */\nexport const CombatHaptics = {\n /** Normal hit feedback @korean 일반타격피드백 */\n normalHit: () => triggerHapticFeedback(\"medium\"),\n /** Critical hit feedback with double pulse @korean 치명타피드백 */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n /** Perfect strike feedback (triple pulse) @korean 완벽타격피드백 */\n perfectStrike: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n /** Vital point strike feedback (intense pattern) @korean 급소타격피드백 */\n vitalPointHit: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n /** Blocked attack feedback (sharp double pulse) @korean 방어성공피드백 */\n attackBlocked: () => triggerHapticPattern([30, 30, 30]),\n /** Miss feedback (quick light pulse) @korean 빗나감피드백 */\n attackMissed: () => triggerHapticFeedback(\"light\"),\n /** Counter-attack feedback (building intensity) @korean 반격피드백 */\n counterAttack: () => triggerHapticPattern([30, 20, 50, 20, 100]),\n /**\n * Combo hit feedback (rhythmic pattern)\n * @korean 콤보타격피드백\n */\n comboHit: (comboCount: number) => {\n const pattern = Array(Math.min(comboCount, 5))\n .fill(null)\n .flatMap(() => [30, 20]);\n pattern.push(50); // Final pulse\n return triggerHapticPattern(pattern);\n },\n /** Heavy damage taken feedback (long intense pulse) @korean 큰피해피드백 */\n heavyDamage: () => triggerHapticPattern([150, 50, 150]),\n /** Knockout feedback (dramatic pattern) @korean 녹다운피드백 */\n knockout: () => triggerHapticPattern([100, 50, 100, 50, 200]),\n} as const;\n\n/**\n * Training mode haptic feedback\n * Provides feedback for training exercises\n * \n * @category Haptic Feedback\n * @korean 훈련햅틱피드백\n */\nexport const TrainingHaptics = {\n /** Correct technique feedback @korean 정확한기술피드백 */\n correctTechnique: () => triggerHapticPattern([30, 30, 100]),\n /** Incorrect technique feedback @korean 부정확한기술피드백 */\n incorrectTechnique: () => triggerHapticPattern([50, 50, 50]),\n /** Target hit feedback @korean 표적명중피드백 */\n targetHit: () => triggerHapticFeedback(\"medium\"),\n /** Perfect timing feedback @korean 완벽한타이밍피드백 */\n perfectTiming: () => triggerHapticPattern([20, 20, 20, 20, 80]),\n /** New skill unlocked feedback @korean 새기술해금피드백 */\n skillUnlocked: () => triggerHapticPattern([50, 30, 50, 30, 50, 30, 150]),\n} as const;\n"],"mappings":";;;;;;;AAwCA,IAAM,kBAA0D;CAC9D,OAAO;EACL,UAAU;EACV,SAAS,CAAC,EAAE;CACd;CACA,QAAQ;EACN,UAAU;EACV,SAAS,CAAC,EAAE;CACd;CACA,OAAO;EACL,UAAU;EACV,SAAS,CAAC,GAAG;CACf;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,oBAA6B;CAC3C,IAAI,OAAO,cAAc,aACvB,OAAO;CAIT,MAAM,MAAM;CACZ,OAAO,aAAa,aAAa,CAAC,CAAC,IAAI;AACzC;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,WAAqC;CACzE,IAAI,CAAC,kBAAkB,GACrB,OAAO;CAGT,MAAM,UAAU,gBAAgB;CAEhC,IAAI;EAEF,IAAI,aAAa,WACf,OAAO,UAAU,QAAQ,QAAQ,QAAQ;EAI3C,MAAM,MAAM;EACZ,IAAI,IAAI,eACN,OAAO,IAAI,cAAc,QAAQ,QAAQ;CAE7C,SAAS,OAAO;EACd,IAAA,QAAA,IAAA,aAA6B,cAC3B,QAAQ,KAAK,2BAA2B,KAAK;CAEjD;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,SAAgB,qBAAqB,SAAqC;CACxE,IAAI,CAAC,kBAAkB,GACrB,OAAO;CAGT,IAAI;EACF,IAAI,aAAa,WACf,OAAO,UAAU,QAAQ,OAAmB;EAI9C,MAAM,MAAM;EACZ,IAAI,IAAI,eACN,OAAO,IAAI,cAAc,OAAmB;CAEhD,SAAS,OAAO;EACd,QAAQ,KAAK,0BAA0B,KAAK;CAC9C;CAEA,OAAO;AACT;;;;;;;;AASA,IAAa,YAAY;;CAEvB,iBAAiB,sBAAsB,QAAQ;;CAE/C,iBAAiB,sBAAsB,OAAO;;CAE9C,oBAAoB,sBAAsB,OAAO;;CAEjD,uBAAuB,sBAAsB,QAAQ;;CAErD,mBAAmB,qBAAqB;EAAC;EAAI;EAAI;CAAG,CAAC;;CAErD,wBAAwB,qBAAqB;EAAC;EAAK;EAAI;EAAK;EAAI;CAAG,CAAC;;CAEpE,mBAAmB,sBAAsB,QAAQ;;CAEjD,aAAa,qBAAqB;EAAC;EAAI;EAAI;CAAE,CAAC;;CAE9C,eAAe,qBAAqB;EAAC;EAAI;EAAI;EAAI;EAAI;CAAG,CAAC;AAC3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"haptics.js","names":[],"sources":["../../src/utils/haptics.ts"],"sourcesContent":["/**\n * Haptic Feedback Utility\n * \n * Provides tactile feedback for mobile touch interactions\n * Uses Vibration API for physical response on button presses and combat hits\n * \n * @module utils/haptics\n * @category Mobile Controls\n * @korean 햅틱 피드백 유틸리티\n */\n\n/**\n * Haptic intensity levels for different interactions\n */\nexport type HapticIntensity = 'light' | 'medium' | 'heavy';\n\n/**\n * Vibration patterns for different haptic intensities\n * - light: Quick tap (10ms) - UI interactions\n * - medium: Moderate pulse (50ms) - Combat actions\n * - heavy: Strong pulse (100ms) - Critical hits\n * \n * @korean 햅틱 강도 패턴\n */\nconst HAPTIC_PATTERNS: Record<HapticIntensity, number[]> = {\n light: [10],\n medium: [50],\n heavy: [100],\n} as const;\n\n/**\n * Check if haptic feedback is supported by the device\n * \n * @returns True if Vibration API is available\n * @korean 햅틱 지원 확인\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerHaptic('medium');\n * }\n * ```\n */\nexport function isHapticSupported(): boolean {\n return typeof navigator !== 'undefined' && 'vibrate' in navigator;\n}\n\n/**\n * Trigger haptic feedback with specified intensity\n * \n * Provides tactile response for:\n * - Light: Menu selections, button taps\n * - Medium: Attack actions, stance changes\n * - Heavy: Critical hits, successful vital point strikes\n * \n * Falls back gracefully if Vibration API is not supported\n * \n * @param intensity - Haptic intensity level\n * @korean 햅틱 피드백 실행\n * \n * @example\n * ```typescript\n * // Light feedback for UI interaction\n * triggerHaptic('light');\n * \n * // Medium feedback for combat action\n * triggerHaptic('medium');\n * \n * // Heavy feedback for critical hit\n * triggerHaptic('heavy');\n * ```\n * \n */\nexport function triggerHaptic(intensity: HapticIntensity): void {\n if (!isHapticSupported()) {\n return;\n }\n\n const pattern = HAPTIC_PATTERNS[intensity];\n navigator.vibrate(pattern);\n}\n\n/**\n * Custom haptic pattern for specific game events\n * Allows creating complex vibration sequences\n * \n * @param pattern - Array of vibration durations in milliseconds\n * @korean 커스텀 햅틱 패턴\n * \n * @example\n * ```typescript\n * // Combo hit feedback: buzz, pause, buzz\n * triggerCustomHaptic([30, 20, 30]);\n * \n * // Critical vital point strike: long buzz\n * triggerCustomHaptic([200]);\n * ```\n * \n */\nexport function triggerCustomHaptic(pattern: number[]): void {\n if (!isHapticSupported()) {\n return;\n }\n\n navigator.vibrate(pattern);\n}\n\n/**\n * Stop any ongoing haptic feedback\n * Useful for interrupting long vibrations\n * \n * @korean 햅틱 피드백 중지\n * \n * @example\n * ```typescript\n * // Cancel ongoing vibration\n * stopHaptic();\n * ```\n * \n */\nexport function stopHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n navigator.vibrate(0);\n}\n\n/**\n * Combat-specific haptic feedback patterns\n * Pre-configured patterns for common combat scenarios\n * \n * @korean 전투 햅틱 패턴\n */\nexport const CombatHaptics = {\n /**\n * Standard attack hit feedback\n * @korean 일반 공격\n */\n attack: () => triggerHaptic('medium'),\n\n /**\n * Block successful feedback\n * @korean 방어 성공\n */\n block: () => triggerHaptic('light'),\n\n /**\n * Critical hit feedback with double pulse\n * @korean 크리티컬 히트\n */\n criticalHit: () => triggerCustomHaptic([50, 30, 100]),\n\n /**\n * Vital point strike feedback\n * @korean 급소 타격\n */\n vitalPointStrike: () => triggerHaptic('heavy'),\n\n /**\n * Stance change feedback\n * @korean 자세 변경\n */\n stanceChange: () => triggerHaptic('light'),\n\n /**\n * Combo counter increment\n * @korean 콤보 카운터\n */\n comboIncrement: () => triggerHaptic('light'),\n\n /**\n * Player KO feedback with extended pattern\n * @korean 플레이어 KO\n */\n knockout: () => triggerCustomHaptic([100, 50, 100, 50, 200]),\n\n /**\n * Error or invalid action feedback\n * @korean 오류 피드백\n */\n error: () => triggerCustomHaptic([20, 10, 20]),\n} as const;\n"],"mappings":";;;;;;;;;AAwBA,IAAM,kBAAqD;CACzD,OAAO,CAAC,GAAG;CACX,QAAQ,CAAC,GAAG;CACZ,OAAO,CAAC,IAAI;CACb;;;;;;;;;;;;;;AAeD,SAAgB,oBAA6B;CAC3C,OAAO,OAAO,cAAc,eAAe,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B1D,SAAgB,cAAc,WAAkC;CAC9D,IAAI,CAAC,mBAAmB,EACtB;CAGF,MAAM,UAAU,gBAAgB;CAChC,UAAU,QAAQ,QAAQ"}
1
+ {"version":3,"file":"haptics.js","names":[],"sources":["../../src/utils/haptics.ts"],"sourcesContent":["/**\n * Haptic Feedback Utility\n * \n * Provides tactile feedback for mobile touch interactions\n * Uses Vibration API for physical response on button presses and combat hits\n * \n * @module utils/haptics\n * @category Mobile Controls\n * @korean 햅틱 피드백 유틸리티\n */\n\n/**\n * Haptic intensity levels for different interactions\n */\nexport type HapticIntensity = 'light' | 'medium' | 'heavy';\n\n/**\n * Vibration patterns for different haptic intensities\n * - light: Quick tap (10ms) - UI interactions\n * - medium: Moderate pulse (50ms) - Combat actions\n * - heavy: Strong pulse (100ms) - Critical hits\n * \n * @korean 햅틱 강도 패턴\n */\nconst HAPTIC_PATTERNS: Record<HapticIntensity, number[]> = {\n light: [10],\n medium: [50],\n heavy: [100],\n} as const;\n\n/**\n * Check if haptic feedback is supported by the device\n * \n * @returns True if Vibration API is available\n * @korean 햅틱 지원 확인\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerHaptic('medium');\n * }\n * ```\n */\nexport function isHapticSupported(): boolean {\n return typeof navigator !== 'undefined' && 'vibrate' in navigator;\n}\n\n/**\n * Trigger haptic feedback with specified intensity\n * \n * Provides tactile response for:\n * - Light: Menu selections, button taps\n * - Medium: Attack actions, stance changes\n * - Heavy: Critical hits, successful vital point strikes\n * \n * Falls back gracefully if Vibration API is not supported\n * \n * @param intensity - Haptic intensity level\n * @korean 햅틱 피드백 실행\n * \n * @example\n * ```typescript\n * // Light feedback for UI interaction\n * triggerHaptic('light');\n * \n * // Medium feedback for combat action\n * triggerHaptic('medium');\n * \n * // Heavy feedback for critical hit\n * triggerHaptic('heavy');\n * ```\n * \n */\nexport function triggerHaptic(intensity: HapticIntensity): void {\n if (!isHapticSupported()) {\n return;\n }\n\n const pattern = HAPTIC_PATTERNS[intensity];\n navigator.vibrate(pattern);\n}\n\n/**\n * Custom haptic pattern for specific game events\n * Allows creating complex vibration sequences\n * \n * @param pattern - Array of vibration durations in milliseconds\n * @korean 커스텀 햅틱 패턴\n * \n * @example\n * ```typescript\n * // Combo hit feedback: buzz, pause, buzz\n * triggerCustomHaptic([30, 20, 30]);\n * \n * // Critical vital point strike: long buzz\n * triggerCustomHaptic([200]);\n * ```\n * \n */\nexport function triggerCustomHaptic(pattern: number[]): void {\n if (!isHapticSupported()) {\n return;\n }\n\n navigator.vibrate(pattern);\n}\n\n/**\n * Stop any ongoing haptic feedback\n * Useful for interrupting long vibrations\n * \n * @korean 햅틱 피드백 중지\n * \n * @example\n * ```typescript\n * // Cancel ongoing vibration\n * stopHaptic();\n * ```\n * \n */\nexport function stopHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n navigator.vibrate(0);\n}\n\n/**\n * Combat-specific haptic feedback patterns\n * Pre-configured patterns for common combat scenarios\n * \n * @korean 전투 햅틱 패턴\n */\nexport const CombatHaptics = {\n /**\n * Standard attack hit feedback\n * @korean 일반 공격\n */\n attack: () => triggerHaptic('medium'),\n\n /**\n * Block successful feedback\n * @korean 방어 성공\n */\n block: () => triggerHaptic('light'),\n\n /**\n * Critical hit feedback with double pulse\n * @korean 크리티컬 히트\n */\n criticalHit: () => triggerCustomHaptic([50, 30, 100]),\n\n /**\n * Vital point strike feedback\n * @korean 급소 타격\n */\n vitalPointStrike: () => triggerHaptic('heavy'),\n\n /**\n * Stance change feedback\n * @korean 자세 변경\n */\n stanceChange: () => triggerHaptic('light'),\n\n /**\n * Combo counter increment\n * @korean 콤보 카운터\n */\n comboIncrement: () => triggerHaptic('light'),\n\n /**\n * Player KO feedback with extended pattern\n * @korean 플레이어 KO\n */\n knockout: () => triggerCustomHaptic([100, 50, 100, 50, 200]),\n\n /**\n * Error or invalid action feedback\n * @korean 오류 피드백\n */\n error: () => triggerCustomHaptic([20, 10, 20]),\n} as const;\n"],"mappings":";;;;;;;;;AAwBA,IAAM,kBAAqD;CACzD,OAAO,CAAC,EAAE;CACV,QAAQ,CAAC,EAAE;CACX,OAAO,CAAC,GAAG;AACb;;;;;;;;;;;;;;AAeA,SAAgB,oBAA6B;CAC3C,OAAO,OAAO,cAAc,eAAe,aAAa;AAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,cAAc,WAAkC;CAC9D,IAAI,CAAC,kBAAkB,GACrB;CAGF,MAAM,UAAU,gBAAgB;CAChC,UAAU,QAAQ,OAAO;AAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlOverlayHelpers.js","names":[],"sources":["../../src/utils/htmlOverlayHelpers.ts"],"sourcesContent":["/**\n * Html Overlay Positioning Helpers for Three.js\n *\n * Provides utilities for positioning Html overlays from @react-three/drei\n * with bounds checking, z-index management, and Korean text measurement.\n *\n * Fixes:\n * - Z-fighting between Html overlays and 3D elements\n * - Text clipping at screen edges\n * - Inconsistent overlay positioning\n * - Performance issues with many overlays\n *\n * @module utils/htmlOverlayHelpers\n * @category Utilities\n * @korean HTML오버레이헬퍼\n */\n\n// Note: Using Z_INDEX from LayoutTypes which is designed for Three.js Html overlays.\n// This is distinct from the legacy Z_INDEX in ui.ts which is for traditional DOM elements.\nimport { Z_INDEX, type ZIndexValue } from \"../types/LayoutTypes\";\nimport type {\n HtmlOverlayLayer,\n ScreenBounds,\n ElementBounds,\n SafePosition,\n HtmlOverlayStyle,\n TextMeasurement,\n HtmlOverlayPositionOptions,\n HtmlOverlayConfig,\n} from \"../types/HtmlOverlayTypes\";\nimport { FONT_FAMILY } from \"@/types/constants\";\n\n/**\n * Get z-index value for Html overlay layer\n *\n * Maps layer names to Z_INDEX constants for consistent stacking order.\n * Prevents z-fighting by maintaining clear hierarchy.\n *\n * @param layer - Html overlay layer category\n * @param offset - Optional offset to add to base z-index (default: 0)\n * @returns Z-index value for the layer\n *\n * @example\n * ```typescript\n * const zIndex = getZIndexForLayer('hud'); // Returns 40\n * const zIndexWithOffset = getZIndexForLayer('hud', 5); // Returns 45\n * ```\n *\n * @category Html Overlay Helpers\n * @korean Z인덱스가져오기\n */\nexport function getZIndexForLayer(\n layer: HtmlOverlayLayer,\n offset: number = 0,\n): ZIndexValue {\n const layerMap: Record<HtmlOverlayLayer, ZIndexValue> = {\n background: Z_INDEX.BACKGROUND,\n arena: Z_INDEX.ARENA,\n players: Z_INDEX.PLAYERS,\n effects: Z_INDEX.EFFECTS,\n hud: Z_INDEX.HUD,\n \"mobile-controls\": Z_INDEX.MOBILE_CONTROLS,\n modal: Z_INDEX.MODAL,\n tooltip: Z_INDEX.TOOLTIP,\n debug: Z_INDEX.DEBUG,\n };\n\n const baseZIndex = layerMap[layer];\n\n // Derive the valid numeric z-index range from Z_INDEX to ensure we never\n // return an out-of-bounds value, even if a large offset is provided.\n const zIndexValues = Object.values(Z_INDEX).filter(\n (value) => typeof value === \"number\",\n ) as number[];\n const minZIndex = Math.min(...zIndexValues);\n const maxZIndex = Math.max(...zIndexValues);\n\n const rawZIndex = baseZIndex + offset;\n const clampedZIndex = Math.max(minZIndex, Math.min(maxZIndex, rawZIndex));\n\n return clampedZIndex as ZIndexValue;\n}\n\n/**\n * Calculate safe position that prevents clipping at screen edges\n *\n * Takes a desired position and element bounds, then ensures the element\n * stays within screen bounds by clamping to safe area.\n *\n * @param position - Desired 3D position [x, y, z]\n * @param elementBounds - Size of the element\n * @param screenBounds - Screen size and safe area insets\n * @returns Safe position with clipping prevention\n *\n * @example\n * ```typescript\n * const safePos = calculateSafePosition(\n * [100, 50, 0],\n * { width: 200, height: 100, margin: 10 },\n * { width: 1920, height: 1080 }\n * );\n * // Returns: { x: 100, y: 50, wasClamped: false }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 안전위치계산\n */\nexport function calculateSafePosition(\n position: [number, number, number],\n elementBounds: ElementBounds,\n screenBounds: ScreenBounds,\n): SafePosition {\n const [x, y] = position;\n const margin = elementBounds.margin ?? 0;\n const safeArea = screenBounds.safeArea ?? {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n };\n\n // Calculate effective bounds with safe area and margin\n const minX = safeArea.left + margin;\n const maxX =\n screenBounds.width - safeArea.right - elementBounds.width - margin;\n const minY = safeArea.top + margin;\n const maxY =\n screenBounds.height - safeArea.bottom - elementBounds.height - margin;\n\n // Clamp position to safe bounds\n const safeX = Math.max(minX, Math.min(x, maxX));\n const safeY = Math.max(minY, Math.min(y, maxY));\n\n // Check if position was clamped\n const wasClamped = safeX !== x || safeY !== y;\n\n return {\n x: safeX,\n y: safeY,\n wasClamped,\n };\n}\n\n/**\n * Measure Korean and English text dimensions\n *\n * Uses canvas-based text measurement to calculate accurate bounds\n * for bilingual text before rendering. Prevents text overflow.\n *\n * @param korean - Korean text content\n * @param english - English text content\n * @param fontSize - Font size in pixels\n * @param fontFamily - Font family to use (default: FONT_FAMILY.KOREAN)\n * @param layout - Text layout direction ('vertical' | 'horizontal')\n * @returns Text measurement with width and height\n *\n * @example\n * ```typescript\n * const bounds = measureTextBounds('공격', 'Attack', 16, FONT_FAMILY.KOREAN, 'vertical');\n * // Returns: { width: 48, height: 42, koreanWidth: 32, englishWidth: 48 }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 텍스트경계측정\n */\nexport function measureTextBounds(\n korean: string,\n english: string,\n fontSize: number,\n fontFamily: string = FONT_FAMILY.KOREAN,\n layout: \"vertical\" | \"horizontal\" = \"vertical\",\n): TextMeasurement {\n // Create canvas for text measurement\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n // Fallback if canvas not available\n return {\n width: fontSize * Math.max(korean.length, english.length),\n height: fontSize * 2.4, // Line height * 2 lines\n koreanWidth: fontSize * korean.length,\n englishWidth: fontSize * english.length,\n };\n }\n\n // Measure Korean text (larger, bold)\n ctx.font = `bold ${fontSize}px ${fontFamily}`;\n const koreanMetrics = ctx.measureText(korean);\n const koreanWidth = koreanMetrics.width;\n\n // Measure English text (smaller, italic)\n const englishFontSize = fontSize * 0.75;\n ctx.font = `italic ${englishFontSize}px ${fontFamily}`;\n const englishMetrics = ctx.measureText(english);\n const englishWidth = englishMetrics.width;\n\n // Calculate total dimensions based on layout\n let totalWidth: number;\n let totalHeight: number;\n\n if (layout === \"vertical\") {\n // Vertical layout: max width, sum heights\n totalWidth = Math.max(koreanWidth, englishWidth);\n totalHeight = fontSize * 1.2 + englishFontSize * 1.2 + 4; // Line heights + gap\n } else {\n // Horizontal layout: sum widths, max height\n totalWidth = koreanWidth + 8 + englishWidth; // Add gap between texts\n totalHeight = Math.max(fontSize * 1.2, englishFontSize * 1.2);\n }\n\n return {\n width: totalWidth,\n height: totalHeight,\n koreanWidth,\n englishWidth,\n };\n}\n\n/**\n * Apply standardized Html overlay styles\n *\n * Creates consistent styling for Html overlays with proper z-index,\n * pointer events, and performance optimizations.\n *\n * @param layer - Html overlay layer for z-index\n * @param interactive - Whether the overlay should receive pointer events\n * @param distanceFactor - Distance scaling factor (default: 10)\n * @param center - Whether to center the overlay (default: true)\n * @param occlude - Whether to occlude behind 3D objects (default: false)\n * @param zIndexOffset - Optional z-index offset (default: 0)\n * @returns Html overlay style configuration\n *\n * @example\n * ```typescript\n * const style = applyHtmlOverlayStyles('hud', false, 10, true);\n * // Returns: { zIndex: 40, pointerEvents: 'none', distanceFactor: 10, center: true, ... }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 오버레이스타일적용\n */\nexport function applyHtmlOverlayStyles(\n layer: HtmlOverlayLayer,\n interactive: boolean = false,\n distanceFactor: number = 10,\n center: boolean = true,\n occlude: boolean = false,\n zIndexOffset: number = 0,\n): HtmlOverlayStyle {\n return {\n zIndex: getZIndexForLayer(layer, zIndexOffset),\n pointerEvents: interactive ? \"all\" : \"none\",\n distanceFactor,\n center,\n occlude,\n transform: \"translateZ(0)\", // GPU acceleration\n };\n}\n\n/**\n * Create complete Html overlay configuration\n *\n * Combines position calculation, bounds checking, and styling\n * into a single configuration object for Html overlays.\n *\n * @param options - Html overlay positioning options\n * @returns Complete Html overlay configuration\n *\n * @example\n * ```typescript\n * const config = createHtmlOverlayConfig({\n * position: [0, 2, 0],\n * layer: 'hud',\n * screenBounds: { width: 1920, height: 1080 },\n * elementBounds: { width: 200, height: 100 },\n * isMobile: false,\n * });\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 오버레이설정생성\n */\nexport function createHtmlOverlayConfig(\n options: HtmlOverlayPositionOptions,\n): HtmlOverlayConfig {\n const {\n position,\n layer,\n screenBounds,\n elementBounds,\n isMobile = false,\n zIndexOffset = 0,\n } = options;\n\n // Calculate safe position if bounds provided\n let safePosition: SafePosition;\n if (screenBounds && elementBounds) {\n safePosition = calculateSafePosition(position, elementBounds, screenBounds);\n } else {\n // No bounds checking needed\n safePosition = {\n x: position[0],\n y: position[1],\n wasClamped: false,\n };\n }\n\n // Create style configuration\n const style = applyHtmlOverlayStyles(\n layer,\n false, // Default to non-interactive for performance\n isMobile ? 15 : 10, // Larger distance factor for mobile\n true, // Center by default\n false, // Don't occlude by default\n zIndexOffset,\n );\n\n return {\n position: safePosition,\n style,\n position3D: position,\n };\n}\n\n/**\n * Get default safe area insets for mobile devices\n *\n * Provides standard safe area values for devices with notches\n * and home indicators (iPhone X+, modern Android).\n *\n * @param isMobile - Whether device is mobile\n * @returns Safe area insets\n *\n * @category Html Overlay Helpers\n * @korean 기본안전영역\n */\nexport function getDefaultSafeArea(isMobile: boolean): {\n readonly top: number;\n readonly right: number;\n readonly bottom: number;\n readonly left: number;\n} {\n if (!isMobile) {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n }\n\n // Standard safe area for mobile devices with notches\n return {\n top: 44, // Status bar + notch\n right: 0,\n bottom: 34, // Home indicator\n left: 0,\n };\n}\n\n/**\n * Calculate optimal distance factor for Html overlay\n *\n * Determines appropriate distance scaling based on screen size\n * and overlay purpose. Prevents overlays from being too small or large.\n *\n * @param screenWidth - Screen width in pixels\n * @param overlayType - Type of overlay ('text' | 'button' | 'panel')\n * @param isMobile - Whether on mobile device\n * @returns Optimal distance factor\n *\n * @category Html Overlay Helpers\n * @korean 거리인수계산\n */\nexport function calculateDistanceFactor(\n screenWidth: number,\n overlayType: \"text\" | \"button\" | \"panel\",\n isMobile: boolean,\n): number {\n // Base distance factors by type\n const baseFactors = {\n text: 10,\n button: 12,\n panel: 15,\n };\n\n const baseFactor = baseFactors[overlayType];\n\n // Adjust for mobile\n if (isMobile) {\n return baseFactor * 1.5; // 50% larger on mobile\n }\n\n // Adjust for screen width\n if (screenWidth < 1200) {\n return baseFactor * 1.2; // 20% larger on smaller screens\n }\n\n return baseFactor;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"htmlOverlayHelpers.js","names":[],"sources":["../../src/utils/htmlOverlayHelpers.ts"],"sourcesContent":["/**\n * Html Overlay Positioning Helpers for Three.js\n *\n * Provides utilities for positioning Html overlays from @react-three/drei\n * with bounds checking, z-index management, and Korean text measurement.\n *\n * Fixes:\n * - Z-fighting between Html overlays and 3D elements\n * - Text clipping at screen edges\n * - Inconsistent overlay positioning\n * - Performance issues with many overlays\n *\n * @module utils/htmlOverlayHelpers\n * @category Utilities\n * @korean HTML오버레이헬퍼\n */\n\n// Note: Using Z_INDEX from LayoutTypes which is designed for Three.js Html overlays.\n// This is distinct from the legacy Z_INDEX in ui.ts which is for traditional DOM elements.\nimport { Z_INDEX, type ZIndexValue } from \"../types/LayoutTypes\";\nimport type {\n HtmlOverlayLayer,\n ScreenBounds,\n ElementBounds,\n SafePosition,\n HtmlOverlayStyle,\n TextMeasurement,\n HtmlOverlayPositionOptions,\n HtmlOverlayConfig,\n} from \"../types/HtmlOverlayTypes\";\nimport { FONT_FAMILY } from \"@/types/constants\";\n\n/**\n * Get z-index value for Html overlay layer\n *\n * Maps layer names to Z_INDEX constants for consistent stacking order.\n * Prevents z-fighting by maintaining clear hierarchy.\n *\n * @param layer - Html overlay layer category\n * @param offset - Optional offset to add to base z-index (default: 0)\n * @returns Z-index value for the layer\n *\n * @example\n * ```typescript\n * const zIndex = getZIndexForLayer('hud'); // Returns 40\n * const zIndexWithOffset = getZIndexForLayer('hud', 5); // Returns 45\n * ```\n *\n * @category Html Overlay Helpers\n * @korean Z인덱스가져오기\n */\nexport function getZIndexForLayer(\n layer: HtmlOverlayLayer,\n offset: number = 0,\n): ZIndexValue {\n const layerMap: Record<HtmlOverlayLayer, ZIndexValue> = {\n background: Z_INDEX.BACKGROUND,\n arena: Z_INDEX.ARENA,\n players: Z_INDEX.PLAYERS,\n effects: Z_INDEX.EFFECTS,\n hud: Z_INDEX.HUD,\n \"mobile-controls\": Z_INDEX.MOBILE_CONTROLS,\n modal: Z_INDEX.MODAL,\n tooltip: Z_INDEX.TOOLTIP,\n debug: Z_INDEX.DEBUG,\n };\n\n const baseZIndex = layerMap[layer];\n\n // Derive the valid numeric z-index range from Z_INDEX to ensure we never\n // return an out-of-bounds value, even if a large offset is provided.\n const zIndexValues = Object.values(Z_INDEX).filter(\n (value) => typeof value === \"number\",\n ) as number[];\n const minZIndex = Math.min(...zIndexValues);\n const maxZIndex = Math.max(...zIndexValues);\n\n const rawZIndex = baseZIndex + offset;\n const clampedZIndex = Math.max(minZIndex, Math.min(maxZIndex, rawZIndex));\n\n return clampedZIndex as ZIndexValue;\n}\n\n/**\n * Calculate safe position that prevents clipping at screen edges\n *\n * Takes a desired position and element bounds, then ensures the element\n * stays within screen bounds by clamping to safe area.\n *\n * @param position - Desired 3D position [x, y, z]\n * @param elementBounds - Size of the element\n * @param screenBounds - Screen size and safe area insets\n * @returns Safe position with clipping prevention\n *\n * @example\n * ```typescript\n * const safePos = calculateSafePosition(\n * [100, 50, 0],\n * { width: 200, height: 100, margin: 10 },\n * { width: 1920, height: 1080 }\n * );\n * // Returns: { x: 100, y: 50, wasClamped: false }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 안전위치계산\n */\nexport function calculateSafePosition(\n position: [number, number, number],\n elementBounds: ElementBounds,\n screenBounds: ScreenBounds,\n): SafePosition {\n const [x, y] = position;\n const margin = elementBounds.margin ?? 0;\n const safeArea = screenBounds.safeArea ?? {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n };\n\n // Calculate effective bounds with safe area and margin\n const minX = safeArea.left + margin;\n const maxX =\n screenBounds.width - safeArea.right - elementBounds.width - margin;\n const minY = safeArea.top + margin;\n const maxY =\n screenBounds.height - safeArea.bottom - elementBounds.height - margin;\n\n // Clamp position to safe bounds\n const safeX = Math.max(minX, Math.min(x, maxX));\n const safeY = Math.max(minY, Math.min(y, maxY));\n\n // Check if position was clamped\n const wasClamped = safeX !== x || safeY !== y;\n\n return {\n x: safeX,\n y: safeY,\n wasClamped,\n };\n}\n\n/**\n * Measure Korean and English text dimensions\n *\n * Uses canvas-based text measurement to calculate accurate bounds\n * for bilingual text before rendering. Prevents text overflow.\n *\n * @param korean - Korean text content\n * @param english - English text content\n * @param fontSize - Font size in pixels\n * @param fontFamily - Font family to use (default: FONT_FAMILY.KOREAN)\n * @param layout - Text layout direction ('vertical' | 'horizontal')\n * @returns Text measurement with width and height\n *\n * @example\n * ```typescript\n * const bounds = measureTextBounds('공격', 'Attack', 16, FONT_FAMILY.KOREAN, 'vertical');\n * // Returns: { width: 48, height: 42, koreanWidth: 32, englishWidth: 48 }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 텍스트경계측정\n */\nexport function measureTextBounds(\n korean: string,\n english: string,\n fontSize: number,\n fontFamily: string = FONT_FAMILY.KOREAN,\n layout: \"vertical\" | \"horizontal\" = \"vertical\",\n): TextMeasurement {\n // Create canvas for text measurement\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n\n if (!ctx) {\n // Fallback if canvas not available\n return {\n width: fontSize * Math.max(korean.length, english.length),\n height: fontSize * 2.4, // Line height * 2 lines\n koreanWidth: fontSize * korean.length,\n englishWidth: fontSize * english.length,\n };\n }\n\n // Measure Korean text (larger, bold)\n ctx.font = `bold ${fontSize}px ${fontFamily}`;\n const koreanMetrics = ctx.measureText(korean);\n const koreanWidth = koreanMetrics.width;\n\n // Measure English text (smaller, italic)\n const englishFontSize = fontSize * 0.75;\n ctx.font = `italic ${englishFontSize}px ${fontFamily}`;\n const englishMetrics = ctx.measureText(english);\n const englishWidth = englishMetrics.width;\n\n // Calculate total dimensions based on layout\n let totalWidth: number;\n let totalHeight: number;\n\n if (layout === \"vertical\") {\n // Vertical layout: max width, sum heights\n totalWidth = Math.max(koreanWidth, englishWidth);\n totalHeight = fontSize * 1.2 + englishFontSize * 1.2 + 4; // Line heights + gap\n } else {\n // Horizontal layout: sum widths, max height\n totalWidth = koreanWidth + 8 + englishWidth; // Add gap between texts\n totalHeight = Math.max(fontSize * 1.2, englishFontSize * 1.2);\n }\n\n return {\n width: totalWidth,\n height: totalHeight,\n koreanWidth,\n englishWidth,\n };\n}\n\n/**\n * Apply standardized Html overlay styles\n *\n * Creates consistent styling for Html overlays with proper z-index,\n * pointer events, and performance optimizations.\n *\n * @param layer - Html overlay layer for z-index\n * @param interactive - Whether the overlay should receive pointer events\n * @param distanceFactor - Distance scaling factor (default: 10)\n * @param center - Whether to center the overlay (default: true)\n * @param occlude - Whether to occlude behind 3D objects (default: false)\n * @param zIndexOffset - Optional z-index offset (default: 0)\n * @returns Html overlay style configuration\n *\n * @example\n * ```typescript\n * const style = applyHtmlOverlayStyles('hud', false, 10, true);\n * // Returns: { zIndex: 40, pointerEvents: 'none', distanceFactor: 10, center: true, ... }\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 오버레이스타일적용\n */\nexport function applyHtmlOverlayStyles(\n layer: HtmlOverlayLayer,\n interactive: boolean = false,\n distanceFactor: number = 10,\n center: boolean = true,\n occlude: boolean = false,\n zIndexOffset: number = 0,\n): HtmlOverlayStyle {\n return {\n zIndex: getZIndexForLayer(layer, zIndexOffset),\n pointerEvents: interactive ? \"all\" : \"none\",\n distanceFactor,\n center,\n occlude,\n transform: \"translateZ(0)\", // GPU acceleration\n };\n}\n\n/**\n * Create complete Html overlay configuration\n *\n * Combines position calculation, bounds checking, and styling\n * into a single configuration object for Html overlays.\n *\n * @param options - Html overlay positioning options\n * @returns Complete Html overlay configuration\n *\n * @example\n * ```typescript\n * const config = createHtmlOverlayConfig({\n * position: [0, 2, 0],\n * layer: 'hud',\n * screenBounds: { width: 1920, height: 1080 },\n * elementBounds: { width: 200, height: 100 },\n * isMobile: false,\n * });\n * ```\n *\n * @category Html Overlay Helpers\n * @korean 오버레이설정생성\n */\nexport function createHtmlOverlayConfig(\n options: HtmlOverlayPositionOptions,\n): HtmlOverlayConfig {\n const {\n position,\n layer,\n screenBounds,\n elementBounds,\n isMobile = false,\n zIndexOffset = 0,\n } = options;\n\n // Calculate safe position if bounds provided\n let safePosition: SafePosition;\n if (screenBounds && elementBounds) {\n safePosition = calculateSafePosition(position, elementBounds, screenBounds);\n } else {\n // No bounds checking needed\n safePosition = {\n x: position[0],\n y: position[1],\n wasClamped: false,\n };\n }\n\n // Create style configuration\n const style = applyHtmlOverlayStyles(\n layer,\n false, // Default to non-interactive for performance\n isMobile ? 15 : 10, // Larger distance factor for mobile\n true, // Center by default\n false, // Don't occlude by default\n zIndexOffset,\n );\n\n return {\n position: safePosition,\n style,\n position3D: position,\n };\n}\n\n/**\n * Get default safe area insets for mobile devices\n *\n * Provides standard safe area values for devices with notches\n * and home indicators (iPhone X+, modern Android).\n *\n * @param isMobile - Whether device is mobile\n * @returns Safe area insets\n *\n * @category Html Overlay Helpers\n * @korean 기본안전영역\n */\nexport function getDefaultSafeArea(isMobile: boolean): {\n readonly top: number;\n readonly right: number;\n readonly bottom: number;\n readonly left: number;\n} {\n if (!isMobile) {\n return { top: 0, right: 0, bottom: 0, left: 0 };\n }\n\n // Standard safe area for mobile devices with notches\n return {\n top: 44, // Status bar + notch\n right: 0,\n bottom: 34, // Home indicator\n left: 0,\n };\n}\n\n/**\n * Calculate optimal distance factor for Html overlay\n *\n * Determines appropriate distance scaling based on screen size\n * and overlay purpose. Prevents overlays from being too small or large.\n *\n * @param screenWidth - Screen width in pixels\n * @param overlayType - Type of overlay ('text' | 'button' | 'panel')\n * @param isMobile - Whether on mobile device\n * @returns Optimal distance factor\n *\n * @category Html Overlay Helpers\n * @korean 거리인수계산\n */\nexport function calculateDistanceFactor(\n screenWidth: number,\n overlayType: \"text\" | \"button\" | \"panel\",\n isMobile: boolean,\n): number {\n // Base distance factors by type\n const baseFactors = {\n text: 10,\n button: 12,\n panel: 15,\n };\n\n const baseFactor = baseFactors[overlayType];\n\n // Adjust for mobile\n if (isMobile) {\n return baseFactor * 1.5; // 50% larger on mobile\n }\n\n // Adjust for screen width\n if (screenWidth < 1200) {\n return baseFactor * 1.2; // 20% larger on smaller screens\n }\n\n return baseFactor;\n}\n"],"mappings":"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"}