blacktrigram 0.7.39 → 0.7.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +8 -8
|
@@ -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":"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"}
|
|
@@ -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 * @public\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 * @public\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 * @public\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 /**\n * Button tap feedback\n * @korean 버튼탭피드백\n */\n buttonTap: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Menu hover/navigation feedback\n * @korean 메뉴호버피드백\n */\n menuHover: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Stance change feedback\n * @korean 자세변경피드백\n */\n stanceChange: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Attack execution feedback\n * @korean 공격실행피드백\n */\n attackExecution: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Critical hit feedback\n * @korean 치명타피드백\n */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n\n /**\n * Vital point strike feedback\n * @korean 급소타격피드백\n */\n vitalPointStrike: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n\n /**\n * Block/defend feedback\n * @korean 방어피드백\n */\n blockDefend: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Error/invalid action feedback\n * @korean 오류피드백\n */\n error: () => triggerHapticPattern([50, 50, 50]),\n\n /**\n * Success/achievement feedback\n * @korean 성공피드백\n */\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 /**\n * Normal hit feedback\n * @korean 일반타격피드백\n */\n normalHit: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Critical hit feedback with double pulse\n * @korean 치명타피드백\n */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n\n /**\n * Perfect strike feedback (triple pulse)\n * @korean 완벽타격피드백\n */\n perfectStrike: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n\n /**\n * Vital point strike feedback (intense pattern)\n * @korean 급소타격피드백\n */\n vitalPointHit: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n\n /**\n * Blocked attack feedback (sharp double pulse)\n * @korean 방어성공피드백\n */\n attackBlocked: () => triggerHapticPattern([30, 30, 30]),\n\n /**\n * Miss feedback (quick light pulse)\n * @korean 빗나감피드백\n */\n attackMissed: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Counter-attack feedback (building intensity)\n * @korean 반격피드백\n */\n counterAttack: () => triggerHapticPattern([30, 20, 50, 20, 100]),\n\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\n /**\n * Heavy damage taken feedback (long intense pulse)\n * @korean 큰피해피드백\n */\n heavyDamage: () => triggerHapticPattern([150, 50, 150]),\n\n /**\n * Knockout feedback (dramatic pattern)\n * @korean 녹다운피드백\n */\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 /**\n * Correct technique feedback\n * @korean 정확한기술피드백\n */\n correctTechnique: () => triggerHapticPattern([30, 30, 100]),\n\n /**\n * Incorrect technique feedback\n * @korean 부정확한기술피드백\n */\n incorrectTechnique: () => triggerHapticPattern([50, 50, 50]),\n\n /**\n * Target hit feedback\n * @korean 표적명중피드백\n */\n targetHit: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Perfect timing feedback\n * @korean 완벽한타이밍피드백\n */\n perfectTiming: () => triggerHapticPattern([20, 20, 20, 20, 80]),\n\n /**\n * New skill unlocked feedback\n * @korean 새기술해금피드백\n */\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;;;;;;;;;;;;;;;;AAiBD,SAAgB,oBAA6B;
|
|
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 * @public\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 * @public\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 * @public\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 /**\n * Button tap feedback\n * @korean 버튼탭피드백\n */\n buttonTap: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Menu hover/navigation feedback\n * @korean 메뉴호버피드백\n */\n menuHover: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Stance change feedback\n * @korean 자세변경피드백\n */\n stanceChange: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Attack execution feedback\n * @korean 공격실행피드백\n */\n attackExecution: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Critical hit feedback\n * @korean 치명타피드백\n */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n\n /**\n * Vital point strike feedback\n * @korean 급소타격피드백\n */\n vitalPointStrike: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n\n /**\n * Block/defend feedback\n * @korean 방어피드백\n */\n blockDefend: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Error/invalid action feedback\n * @korean 오류피드백\n */\n error: () => triggerHapticPattern([50, 50, 50]),\n\n /**\n * Success/achievement feedback\n * @korean 성공피드백\n */\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 /**\n * Normal hit feedback\n * @korean 일반타격피드백\n */\n normalHit: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Critical hit feedback with double pulse\n * @korean 치명타피드백\n */\n criticalHit: () => triggerHapticPattern([50, 30, 100]),\n\n /**\n * Perfect strike feedback (triple pulse)\n * @korean 완벽타격피드백\n */\n perfectStrike: () => triggerHapticPattern([30, 30, 30, 30, 100]),\n\n /**\n * Vital point strike feedback (intense pattern)\n * @korean 급소타격피드백\n */\n vitalPointHit: () => triggerHapticPattern([100, 50, 100, 50, 100]),\n\n /**\n * Blocked attack feedback (sharp double pulse)\n * @korean 방어성공피드백\n */\n attackBlocked: () => triggerHapticPattern([30, 30, 30]),\n\n /**\n * Miss feedback (quick light pulse)\n * @korean 빗나감피드백\n */\n attackMissed: () => triggerHapticFeedback(\"light\"),\n\n /**\n * Counter-attack feedback (building intensity)\n * @korean 반격피드백\n */\n counterAttack: () => triggerHapticPattern([30, 20, 50, 20, 100]),\n\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\n /**\n * Heavy damage taken feedback (long intense pulse)\n * @korean 큰피해피드백\n */\n heavyDamage: () => triggerHapticPattern([150, 50, 150]),\n\n /**\n * Knockout feedback (dramatic pattern)\n * @korean 녹다운피드백\n */\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 /**\n * Correct technique feedback\n * @korean 정확한기술피드백\n */\n correctTechnique: () => triggerHapticPattern([30, 30, 100]),\n\n /**\n * Incorrect technique feedback\n * @korean 부정확한기술피드백\n */\n incorrectTechnique: () => triggerHapticPattern([50, 50, 50]),\n\n /**\n * Target hit feedback\n * @korean 표적명중피드백\n */\n targetHit: () => triggerHapticFeedback(\"medium\"),\n\n /**\n * Perfect timing feedback\n * @korean 완벽한타이밍피드백\n */\n perfectTiming: () => triggerHapticPattern([20, 20, 20, 20, 80]),\n\n /**\n * New skill unlocked feedback\n * @korean 새기술해금피드백\n */\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;;;;;;;;;;;;;;;;AAiBD,SAAgB,oBAA6B;CAC3C,IAAI,OAAO,cAAc,aACvB,OAAO;CAIT,MAAM,MAAM;CACZ,OAAO,aAAa,aAAa,CAAC,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;AAwBzC,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;;;;;;;;;;;;;;;;;;AAmBT,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;;;;;CAKvB,iBAAiB,sBAAsB,SAAS;;;;;CAMhD,iBAAiB,sBAAsB,QAAQ;;;;;CAM/C,oBAAoB,sBAAsB,QAAQ;;;;;CAMlD,uBAAuB,sBAAsB,SAAS;;;;;CAMtD,mBAAmB,qBAAqB;EAAC;EAAI;EAAI;EAAI,CAAC;;;;;CAMtD,wBAAwB,qBAAqB;EAAC;EAAK;EAAI;EAAK;EAAI;EAAI,CAAC;;;;;CAMrE,mBAAmB,sBAAsB,SAAS;;;;;CAMlD,aAAa,qBAAqB;EAAC;EAAI;EAAI;EAAG,CAAC;;;;;CAM/C,eAAe,qBAAqB;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI,CAAC;CAC3D"}
|
package/lib/utils/haptics.js.map
CHANGED
|
@@ -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 * @public\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 * @public\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 * @public\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;
|
|
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 * @public\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 * @public\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 * @public\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8B1D,SAAgB,cAAc,WAAkC;CAC9D,IAAI,CAAC,mBAAmB,EACtB;CAGF,MAAM,UAAU,gBAAgB;CAChC,UAAU,QAAQ,QAAQ"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,kBACd,OACA,SAAiB,GACJ;CAab,MAAM,aAAa;EAXjB,YAAY,QAAQ;EACpB,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,SAAS,QAAQ;EACjB,KAAK,QAAQ;EACb,mBAAmB,QAAQ;EAC3B,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,OAAO,QAAQ;EAGE,CAAS;CAI5B,MAAM,eAAe,OAAO,OAAO,QAAQ,CAAC,QACzC,UAAU,OAAO,UAAU,SAC7B;CACD,MAAM,YAAY,KAAK,IAAI,GAAG,aAAa;CAC3C,MAAM,YAAY,KAAK,IAAI,GAAG,aAAa;CAE3C,MAAM,YAAY,aAAa;AAG/B,QAFsB,KAAK,IAAI,WAAW,KAAK,IAAI,WAAW,UAAU,CAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,sBACd,UACA,eACA,cACc;CACd,MAAM,CAAC,GAAG,KAAK;CACf,MAAM,SAAS,cAAc,UAAU;CACvC,MAAM,WAAW,aAAa,YAAY;EACxC,KAAK;EACL,OAAO;EACP,QAAQ;EACR,MAAM;EACP;CAGD,MAAM,OAAO,SAAS,OAAO;CAC7B,MAAM,OACJ,aAAa,QAAQ,SAAS,QAAQ,cAAc,QAAQ;CAC9D,MAAM,OAAO,SAAS,MAAM;CAC5B,MAAM,OACJ,aAAa,SAAS,SAAS,SAAS,cAAc,SAAS;CAGjE,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,CAAC;CAC/C,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,CAAC;AAK/C,QAAO;EACL,GAAG;EACH,GAAG;EACH,YALiB,UAAU,KAAK,UAAU;EAM3C;;;;;;;;;;;;;;;;;;;;;;;;AAyBH,SAAgB,kBACd,QACA,SACA,UACA,aAAqB,YAAY,QACjC,SAAoC,YACnB;CAGjB,MAAM,MADS,SAAS,cAAc,SAC1B,CAAO,WAAW,KAAK;AAEnC,KAAI,CAAC,IAEH,QAAO;EACL,OAAO,WAAW,KAAK,IAAI,OAAO,QAAQ,QAAQ,OAAO;EACzD,QAAQ,WAAW;EACnB,aAAa,WAAW,OAAO;EAC/B,cAAc,WAAW,QAAQ;EAClC;AAIH,KAAI,OAAO,QAAQ,SAAS,KAAK;CAEjC,MAAM,cADgB,IAAI,YAAY,OAClB,CAAc;CAGlC,MAAM,kBAAkB,WAAW;AACnC,KAAI,OAAO,UAAU,gBAAgB,KAAK;CAE1C,MAAM,eADiB,IAAI,YAAY,QAClB,CAAe;CAGpC,IAAI;CACJ,IAAI;AAEJ,KAAI,WAAW,YAAY;AAEzB,eAAa,KAAK,IAAI,aAAa,aAAa;AAChD,gBAAc,WAAW,MAAM,kBAAkB,MAAM;QAClD;AAEL,eAAa,cAAc,IAAI;AAC/B,gBAAc,KAAK,IAAI,WAAW,KAAK,kBAAkB,IAAI;;AAG/D,QAAO;EACL,OAAO;EACP,QAAQ;EACR;EACA;EACD;;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,SAAgB,uBACd,OACA,cAAuB,OACvB,iBAAyB,IACzB,SAAkB,MAClB,UAAmB,OACnB,eAAuB,GACL;AAClB,QAAO;EACL,QAAQ,kBAAkB,OAAO,aAAa;EAC9C,eAAe,cAAc,QAAQ;EACrC;EACA;EACA;EACA,WAAW;EACZ;;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,SAAgB,wBACd,SACmB;CACnB,MAAM,EACJ,UACA,OACA,cACA,eACA,WAAW,OACX,eAAe,MACb;CAGJ,IAAI;AACJ,KAAI,gBAAgB,cAClB,gBAAe,sBAAsB,UAAU,eAAe,aAAa;KAG3E,gBAAe;EACb,GAAG,SAAS;EACZ,GAAG,SAAS;EACZ,YAAY;EACb;CAIH,MAAM,QAAQ,uBACZ,OACA,OACA,WAAW,KAAK,IAChB,MACA,OACA,aACD;AAED,QAAO;EACL,UAAU;EACV;EACA,YAAY;EACb;;;;;;;;;;;;;;AAeH,SAAgB,mBAAmB,UAKjC;AACA,KAAI,CAAC,SACH,QAAO;EAAE,KAAK;EAAG,OAAO;EAAG,QAAQ;EAAG,MAAM;EAAG;AAIjD,QAAO;EACL,KAAK;EACL,OAAO;EACP,QAAQ;EACR,MAAM;EACP;;;;;;;;;;;;;;;;AAiBH,SAAgB,wBACd,aACA,aACA,UACQ;CAQR,MAAM,aAAa;EALjB,MAAM;EACN,QAAQ;EACR,OAAO;EAGU,CAAY;AAG/B,KAAI,SACF,QAAO,aAAa;AAItB,KAAI,cAAc,KAChB,QAAO,aAAa;AAGtB,QAAO"}
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inputSystem.js","names":[],"sources":["../../src/utils/inputSystem.ts"],"sourcesContent":["import { COMBAT_CONTROLS } from \"@/systems/types\";\nimport type { Position } from \"@/types/common\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport type { MovementInput } from \"../systems/physics/MovementPhysics\";\nimport { MovementPhysics } from \"../systems/physics/MovementPhysics\";\nimport { TrigramStance } from \"../types/common\";\nimport { calculateArenaBounds, DEFAULT_PHYSICS_ARENA_BOUNDS } from \"../types/PhysicsTypes\";\nimport type { MovementArenaBounds } from \"../types/PhysicsTypes\";\n\n/**\n * Configuration interface for the input system and player movement.\n * Uses physics-first approach: all positions and velocities are in meters.\n *\n * **Korean**: 입력 시스템 설정 (Input System Configuration)\n *\n * ## Physics-First Architecture\n *\n * This interface requires worldWidthMeters and worldDepthMeters to enable\n * the new physics-first coordinate system. Without these properties, the\n * movement system cannot properly convert between physics (meters) and\n * rendering (pixels).\n *\n * ### Migration Guide\n *\n * Existing code must be updated to pass world dimensions:\n *\n * ```typescript\n * // Before (incorrect):\n * const config = { bounds: { x: 0, y: 0, width: 960, height: 480 } };\n *\n * // After (correct):\n * const config = {\n * bounds: {\n * worldWidthMeters: 10, // From layout hook\n * worldDepthMeters: 10 // From layout hook\n * }\n * };\n * ```\n *\n * ### Fallback Behavior\n *\n * If worldWidthMeters/worldDepthMeters are not provided, the system falls back\n * to DEFAULT_PHYSICS_ARENA_BOUNDS (10m × 7.5m) to ensure movement stays bounded.\n * Callers SHOULD provide these values from their layout hooks (useCombatLayout, \n * useTrainingLayout) for proper arena sizing.\n */\nexport interface InputSystemConfig {\n /** Whether the input system is enabled and processing input */\n readonly enabled?: boolean;\n\n /**\n * Arena world dimensions in meters for physics calculations.\n *\n * **REQUIRED for physics-first coordinate system to work.**\n *\n * These values must come from layout hooks:\n * - CombatScreen3D: Use arenaBounds.worldWidthMeters/worldDepthMeters from useCombatLayout()\n * - TrainingScreen3D: Use trainingAreaBounds.worldWidthMeters/worldDepthMeters from useTrainingLayout()\n */\n readonly bounds?: {\n /** Physical arena width in meters (e.g., 6m mobile, 10m desktop, 14m 4K) */\n readonly worldWidthMeters: number;\n /** Physical arena depth in meters (e.g., 6m mobile, 10m desktop, 14m 4K) */\n readonly worldDepthMeters: number;\n };\n\n /** Callback invoked when player position changes (position in meters) */\n readonly onPositionChange?: (position: Position) => void;\n\n /** Initial player position in METERS (x = lateral, y = forward/backward) */\n readonly initialPositionMeters?: Position;\n\n // Physics-based movement parameters (always enabled)\n /** Current trigram stance affecting movement speed */\n readonly currentStance?: TrigramStance;\n\n /** Leg injury factor (0-1, where 1 is fully injured) affecting movement speed */\n readonly legInjuryFactor?: number;\n\n /** Whether player is running (sprint mode) */\n readonly isRunning?: boolean;\n\n /** Whether to use tactical step mode (30cm grid quantization) */\n readonly useTacticalSteps?: boolean;\n\n // Speed modifier overrides from SpeedModifierSystem\n /** Final calculated maximum speed in meters per second */\n readonly maxSpeedOverride?: number;\n\n /** Final calculated acceleration in meters per second squared */\n readonly accelerationOverride?: number;\n}\n\nexport interface MovementState {\n readonly up: boolean;\n readonly down: boolean;\n readonly left: boolean;\n readonly right: boolean;\n readonly position: Position;\n readonly isMoving: boolean; // Add isMoving to movement state\n}\n\nexport interface PlayerMovementResult {\n /** Player position in METERS (x = lateral, y = forward/backward in arena) */\n readonly playerPosition: Position;\n readonly movementState: MovementState;\n readonly isMoving: boolean;\n readonly isKeyPressed: (key: string) => boolean;\n /** Velocity in m/s (x = lateral, y = forward/backward) */\n readonly velocity?: { x: number; y: number };\n /** Current speed magnitude in m/s */\n readonly speed?: number;\n}\n\n/**\n * Hook for handling player movement with physics-first approach.\n * All positions and velocities are in METERS - no pixel conversions.\n *\n * **Korean**: 플레이어 이동 훅 (Player Movement Hook)\n *\n * @param config - Physics-first configuration with positions in meters\n * @returns Movement state and physics data (all in meters)\n */\nexport function usePlayerMovement(\n config: InputSystemConfig,\n): PlayerMovementResult {\n const {\n enabled = true,\n bounds,\n onPositionChange,\n initialPositionMeters = { x: 0, y: 0 },\n currentStance = TrigramStance.GEON,\n legInjuryFactor = 0,\n isRunning: isRunningProp = false,\n useTacticalSteps = false,\n maxSpeedOverride,\n accelerationOverride,\n } = config;\n\n // Position in METERS (x = lateral position, y = forward/backward position)\n const [playerPosition, setPlayerPosition] = useState<Position>(\n initialPositionMeters,\n );\n const [keyState, setKeyState] = useState({\n up: false,\n down: false,\n left: false,\n right: false,\n });\n // Physics state for render (velocity and speed in m/s)\n const [velocity, setVelocity] = useState<\n { x: number; y: number } | undefined\n >(undefined);\n const [speed, setSpeed] = useState<number | undefined>(undefined);\n\n // Auto-run detection: track how long movement keys have been held\n // After sustained movement, automatically transition from walking to running\n const movementStartTimeRef = useRef<number | null>(null);\n const AUTO_RUN_THRESHOLD_MS = 300; // Transition to run after 300ms of sustained movement\n\n // Physics-based movement state (always initialized for realistic combat)\n const physicsEngineRef = useRef<MovementPhysics | null>(null);\n const physicsStateRef = useRef<{\n position: THREE.Vector3;\n velocity: THREE.Vector3;\n acceleration: number;\n maxSpeed: number;\n currentStance: TrigramStance;\n legInjuryFactor: number;\n } | null>(null);\n\n // Initialize physics engine once on mount (always enabled)\n // All positions are in METERS - no pixel conversion needed\n useEffect(() => {\n if (!physicsEngineRef.current) {\n // Use arena width for physics-aware speed scaling\n // Validate and fall back to default if invalid\n const width = bounds?.worldWidthMeters;\n const arenaWidth =\n width != null && Number.isFinite(width) && width > 0\n ? width\n : DEFAULT_PHYSICS_ARENA_BOUNDS.worldWidthMeters;\n physicsEngineRef.current = new MovementPhysics(arenaWidth);\n // Initial position in meters (x = lateral, z = forward/backward)\n physicsStateRef.current = {\n position: new THREE.Vector3(\n initialPositionMeters.x,\n 0,\n initialPositionMeters.y,\n ),\n velocity: new THREE.Vector3(0, 0, 0),\n acceleration: 0,\n maxSpeed: 6.0, // Default to BASE_WALK_SPEED (6.0 m/s for responsive combat)\n currentStance,\n legInjuryFactor: legInjuryFactor ?? 0,\n };\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Compute arena bounds synchronously when bounds dimensions change\n // Uses useMemo to ensure bounds are available immediately (not after effect runs)\n // Falls back to default arena bounds if invalid or missing\n // Depend on the whole `bounds` object so the compiler's inferred property-access\n // dependencies (bounds.worldWidthMeters / bounds.worldDepthMeters) are covered.\n const arenaBoundsResult = useMemo<{\n bounds: MovementArenaBounds | undefined;\n error?: Error;\n }>(() => {\n if (bounds?.worldWidthMeters != null && bounds?.worldDepthMeters != null) {\n try {\n return {\n bounds: calculateArenaBounds(\n {\n worldWidthMeters: bounds.worldWidthMeters,\n worldDepthMeters: bounds.worldDepthMeters,\n },\n 0.3 // 0.3m character radius\n ),\n };\n } catch (error) {\n // If validation fails, fall back to default bounds\n // Error will be logged in useEffect to keep render pure\n return {\n bounds: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n // Fallback: use default arena bounds to ensure movement stays bounded\n try {\n return {\n bounds: calculateArenaBounds(\n {\n worldWidthMeters: DEFAULT_PHYSICS_ARENA_BOUNDS.worldWidthMeters,\n worldDepthMeters: DEFAULT_PHYSICS_ARENA_BOUNDS.worldDepthMeters,\n },\n 0.3 // 0.3m character radius\n ),\n };\n } catch (error) {\n // Should never happen with default bounds, but handle gracefully\n // Error will be logged in useEffect to keep render pure\n return {\n bounds: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }, [bounds]);\n\n const arenaBounds = arenaBoundsResult.bounds;\n\n // Log arena bounds calculation errors in an effect (not during render)\n useEffect(() => {\n if (arenaBoundsResult.error) {\n if (bounds?.worldWidthMeters != null && bounds?.worldDepthMeters != null) {\n // Custom bounds failed validation\n console.warn(\n \"Failed to calculate arena bounds, using defaults:\",\n arenaBoundsResult.error\n );\n } else {\n // Should never happen with default bounds\n console.error(\n \"Failed to calculate default arena bounds:\",\n arenaBoundsResult.error\n );\n }\n }\n }, [arenaBoundsResult.error, bounds?.worldWidthMeters, bounds?.worldDepthMeters]);\n\n // Update physics engine arena width when bounds change (legacy)\n useEffect(() => {\n if (!physicsEngineRef.current) {\n return;\n }\n\n const width = bounds?.worldWidthMeters;\n if (width == null) {\n return;\n }\n\n // Validate width before applying to physics engine to avoid runtime errors\n if (!Number.isFinite(width) || width <= 0) {\n console.warn(\n \"Ignoring invalid worldWidthMeters when updating arena width:\",\n width,\n );\n return;\n }\n\n try {\n physicsEngineRef.current.setArenaWidth(width);\n } catch (error) {\n console.warn(\"Failed to update physics arena width:\", error);\n }\n }, [bounds?.worldWidthMeters]);\n\n // Track pressed keys for combat system\n const pressedKeys = useRef<Set<string>>(new Set());\n // Use useState lazy initializer for performance.now() to avoid impure function during render\n const [initialTime] = useState(() => performance.now());\n const lastUpdateTime = useRef(initialTime);\n const animationFrameId = useRef<number | null>(null);\n\n // Refs to track last reported position/velocity to avoid useCallback dependency issues\n // This prevents the animation frame from being cancelled every frame due to callback recreation\n const lastReportedPositionRef = useRef<Position>(initialPositionMeters);\n const lastReportedVelocityRef = useRef<{ x: number; y: number } | undefined>(\n undefined,\n );\n const lastReportedSpeedRef = useRef<number | undefined>(undefined);\n\n // Ref to track keyState for physics loop - avoids recreating callback on key changes\n const keyStateRef = useRef({\n up: false,\n down: false,\n left: false,\n right: false,\n });\n\n // Calculate if currently moving\n const isMoving =\n keyState.up || keyState.down || keyState.left || keyState.right;\n\n // Create complete movement state\n const movementState: MovementState = {\n ...keyState,\n position: playerPosition,\n isMoving,\n };\n\n // Key press checker for combat system\n const isKeyPressed = useCallback((key: string): boolean => {\n return pressedKeys.current.has(key);\n }, []);\n\n // Enhanced keyboard event handlers\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n if (!enabled) return;\n\n const key = event.key.toLowerCase();\n pressedKeys.current.add(key);\n\n // ✅ FIXED: Add all movement keys including WASD and arrows\n // Update both ref (for physics loop) and state (for React re-render)\n switch (key) {\n case \"w\":\n case \"arrowup\":\n keyStateRef.current.up = true;\n setKeyState((prev) => ({ ...prev, up: true }));\n event.preventDefault();\n break;\n case \"s\":\n case \"arrowdown\":\n keyStateRef.current.down = true;\n setKeyState((prev) => ({ ...prev, down: true }));\n event.preventDefault();\n break;\n case \"a\":\n case \"arrowleft\":\n keyStateRef.current.left = true;\n setKeyState((prev) => ({ ...prev, left: true }));\n event.preventDefault();\n break;\n case \"d\":\n case \"arrowright\":\n keyStateRef.current.right = true;\n setKeyState((prev) => ({ ...prev, right: true }));\n event.preventDefault();\n break;\n }\n },\n [enabled],\n );\n\n const handleKeyUp = useCallback(\n (event: KeyboardEvent) => {\n if (!enabled) return;\n\n const key = event.key.toLowerCase();\n pressedKeys.current.delete(key);\n\n // ✅ FIXED: Handle key release for all movement keys\n // Update both ref (for physics loop) and state (for React re-render)\n switch (key) {\n case \"w\":\n case \"arrowup\":\n keyStateRef.current.up = false;\n setKeyState((prev) => ({ ...prev, up: false }));\n break;\n case \"s\":\n case \"arrowdown\":\n keyStateRef.current.down = false;\n setKeyState((prev) => ({ ...prev, down: false }));\n break;\n case \"a\":\n case \"arrowleft\":\n keyStateRef.current.left = false;\n setKeyState((prev) => ({ ...prev, left: false }));\n break;\n case \"d\":\n case \"arrowright\":\n keyStateRef.current.right = false;\n setKeyState((prev) => ({ ...prev, right: false }));\n break;\n }\n },\n [enabled],\n );\n\n // ✅ FIXED: Proper movement calculation with correct bounds\n // Use a ref to store the callback to avoid reference before declaration issue\n const updatePositionRef = useRef<(() => void) | null>(null);\n\n const updatePosition = useCallback(() => {\n // Check if any movement keys are pressed using ref (not stale state)\n const keys = keyStateRef.current;\n const isCurrentlyMoving = keys.up || keys.down || keys.left || keys.right;\n\n if (!enabled || !isCurrentlyMoving) {\n animationFrameId.current = null;\n return;\n }\n\n const now = performance.now();\n const deltaTime = Math.min(now - (lastUpdateTime.current ?? now), 50);\n lastUpdateTime.current = now;\n\n if (deltaTime <= 0) {\n animationFrameId.current = requestAnimationFrame(() =>\n updatePositionRef.current?.(),\n );\n return;\n }\n\n // Physics-based movement (always enabled for realistic combat)\n if (physicsEngineRef.current && physicsStateRef.current) {\n // Apply speed modifiers if provided by SpeedModifierSystem\n // BUG FIX: Now properly passing maxSpeedOverride to physics engine\n if (maxSpeedOverride !== undefined) {\n physicsEngineRef.current.setMaxSpeed(maxSpeedOverride);\n }\n\n if (accelerationOverride !== undefined) {\n physicsEngineRef.current.setAcceleration(accelerationOverride);\n }\n\n // Convert key state to physics input (using ref to avoid callback recreation)\n // Screen coordinates: UP/W = toward top of screen, DOWN/S = toward bottom\n // Physics Z-axis: negative Z = toward top, positive Z = toward bottom\n const keys = keyStateRef.current;\n const forward = keys.up ? -1 : keys.down ? 1 : 0;\n const lateral = keys.right ? 1 : keys.left ? -1 : 0;\n const isCurrentlyMoving = forward !== 0 || lateral !== 0;\n\n // Auto-run detection: transition to running after sustained movement\n const now = performance.now();\n if (isCurrentlyMoving) {\n movementStartTimeRef.current ??= now;\n } else {\n movementStartTimeRef.current = null;\n }\n\n // Determine if player should be running (auto-run after threshold)\n const movementDuration = movementStartTimeRef.current\n ? now - movementStartTimeRef.current\n : 0;\n const shouldRun =\n isRunningProp || movementDuration > AUTO_RUN_THRESHOLD_MS;\n\n const physicsInput: MovementInput = {\n forward,\n lateral,\n isRunning: shouldRun,\n isMoving: isCurrentlyMoving,\n useTacticalSteps,\n };\n\n // Update physics state\n const state = physicsStateRef.current;\n state.currentStance = currentStance;\n state.legInjuryFactor = legInjuryFactor;\n\n // Clamp delta time to 1/30s (≈33.33ms) to match usePlayerMovement and prevent instability\n const clampedDeltaTimeMs = Math.min(deltaTime, 1000 / 30);\n\n // Use arena bounds computed via useMemo (available synchronously)\n physicsEngineRef.current.updateMovement(\n state,\n physicsInput,\n clampedDeltaTimeMs / 1000,\n arenaBounds, // Use memoized bounds\n );\n\n // Position in meters (x = lateral, y = forward/backward)\n const newPosition = { x: state.position.x, y: state.position.z };\n\n // Velocity in m/s (x = lateral, y = forward/backward)\n const newVelocity = { x: state.velocity.x, y: state.velocity.z };\n const newSpeed = state.velocity.length();\n\n // Use refs for comparison to avoid recreating callback on every frame\n // This prevents the animation frame from being cancelled due to useCallback recreation\n const lastPos = lastReportedPositionRef.current;\n if (newPosition.x !== lastPos.x || newPosition.y !== lastPos.y) {\n lastReportedPositionRef.current = newPosition;\n setPlayerPosition(newPosition);\n onPositionChange?.(newPosition);\n }\n\n // Update velocity and speed if changed (with epsilon tolerance for floating-point stability)\n const EPSILON = 0.001;\n const lastVel = lastReportedVelocityRef.current;\n const velocityChanged =\n !lastVel ||\n Math.abs(lastVel.x - newVelocity.x) > EPSILON ||\n Math.abs(lastVel.y - newVelocity.y) > EPSILON;\n if (velocityChanged) {\n lastReportedVelocityRef.current = newVelocity;\n setVelocity(newVelocity);\n }\n // Initialize speed when undefined, then update only on significant changes\n const lastSpd = lastReportedSpeedRef.current;\n if (lastSpd === undefined || Math.abs(lastSpd - newSpeed) > EPSILON) {\n lastReportedSpeedRef.current = newSpeed;\n setSpeed(newSpeed);\n }\n }\n\n // Continue animation if still moving (check ref, not stale closure)\n const stillMoving =\n keyStateRef.current.up ||\n keyStateRef.current.down ||\n keyStateRef.current.left ||\n keyStateRef.current.right;\n if (stillMoving) {\n animationFrameId.current = requestAnimationFrame(() =>\n updatePositionRef.current?.(),\n );\n } else {\n animationFrameId.current = null;\n }\n // NOTE: playerPosition, velocity, speed, keyState, isMoving intentionally excluded from deps\n // Using refs (lastReportedPositionRef, lastReportedVelocityRef, lastReportedSpeedRef, keyStateRef)\n // for comparison to prevent animation frame cancellation on every state update.\n // arenaBounds is computed from bounds and automatically updates when bounds changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n enabled,\n // playerPosition - excluded, using ref\n // keyState - excluded, using keyStateRef\n // isMoving - excluded, using keyStateRef for movement check\n // arenaBounds - excluded, derived from bounds (below)\n bounds,\n onPositionChange,\n currentStance,\n legInjuryFactor,\n isRunningProp,\n useTacticalSteps,\n // velocity - excluded, using ref\n // speed - excluded, using ref\n maxSpeedOverride,\n accelerationOverride,\n ]);\n\n // Keep updatePositionRef in sync via useEffect (not during render)\n useEffect(() => {\n updatePositionRef.current = updatePosition;\n }, [updatePosition]);\n\n // Handle keyboard input\n useEffect(() => {\n if (!enabled) return;\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n if (animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n }\n };\n }, [enabled, handleKeyDown, handleKeyUp]);\n\n // Start animation loop when movement begins\n useEffect(() => {\n if (isMoving && !animationFrameId.current) {\n lastUpdateTime.current = performance.now();\n // Use ref to avoid dependency on updatePosition callback\n animationFrameId.current = requestAnimationFrame(() => {\n updatePositionRef.current?.();\n });\n } else if (!isMoving && animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n animationFrameId.current = null;\n }\n\n return () => {\n if (animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n animationFrameId.current = null;\n }\n };\n // Only depend on isMoving - updatePositionRef is stable\n }, [isMoving]);\n\n return {\n playerPosition,\n movementState,\n isMoving,\n isKeyPressed,\n velocity,\n speed,\n };\n}\n\nexport interface InputEvent {\n readonly type: \"keydown\" | \"keyup\" | \"click\" | \"touchstart\" | \"touchend\";\n readonly key?: string;\n readonly target?: EventTarget | null;\n readonly timestamp: number;\n}\n\nexport interface CombatInput {\n readonly stanceChange?: TrigramStance;\n readonly attack?: boolean;\n readonly block?: boolean;\n readonly movement?: MovementState;\n readonly timestamp: number;\n}\n\n/**\n * Input system for combat controls\n */\nexport class InputSystem {\n private actionCallbacks = new Map<string, (() => void)[]>();\n private isEnabled = true;\n\n constructor() {\n this.setupEventListeners();\n }\n\n private setupEventListeners() {\n window.addEventListener(\"keydown\", this.handleKeyDown.bind(this));\n window.addEventListener(\"keyup\", this.handleKeyUp.bind(this));\n }\n\n private handleKeyDown(event: KeyboardEvent) {\n if (!this.isEnabled) return;\n\n const key = event.key;\n this.triggerAction(`keydown:${key}`);\n this.triggerAction(\"keydown\");\n }\n\n private handleKeyUp(event: KeyboardEvent) {\n if (!this.isEnabled) return;\n\n const key = event.key;\n this.triggerAction(`keyup:${key}`);\n this.triggerAction(\"keyup\");\n }\n\n registerAction(action: string, callback: () => void) {\n if (!this.actionCallbacks.has(action)) {\n this.actionCallbacks.set(action, []);\n }\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n callbacks.push(callback);\n }\n }\n\n unregisterAction(action: string, callback?: () => void) {\n if (!this.actionCallbacks.has(action)) return;\n\n if (callback) {\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n const index = callbacks.indexOf(callback);\n if (index > -1) {\n callbacks.splice(index, 1);\n }\n }\n } else {\n this.actionCallbacks.delete(action);\n }\n }\n\n clearActions() {\n this.actionCallbacks.clear();\n }\n\n isActionActive(action: string): boolean {\n return this.actionCallbacks.has(action);\n }\n\n enable() {\n this.isEnabled = true;\n }\n\n disable() {\n this.isEnabled = false;\n }\n\n private triggerAction(action: string) {\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n callbacks.forEach((callback) => callback());\n }\n }\n\n destroy() {\n window.removeEventListener(\"keydown\", this.handleKeyDown.bind(this));\n window.removeEventListener(\"keyup\", this.handleKeyUp.bind(this));\n this.clearActions();\n }\n}\n\n/**\n * Get stance from keyboard input\n */\nexport function getStanceFromKey(key: string): TrigramStance | null {\n const stanceKey = key as keyof typeof COMBAT_CONTROLS.stanceControls;\n\n if (stanceKey in COMBAT_CONTROLS.stanceControls) {\n return COMBAT_CONTROLS.stanceControls[stanceKey].stance;\n }\n\n return null;\n}\n\n/**\n * Process combat input and return structured combat data\n */\nexport function processCombatInput(event: KeyboardEvent): CombatInput | null {\n const key = event.key;\n const timestamp = performance.now();\n\n // Check for stance change (1-8 keys)\n const stance = getStanceFromKey(key);\n if (stance) {\n return {\n stanceChange: stance,\n timestamp,\n };\n }\n\n // Check for combat actions\n switch (key.toLowerCase()) {\n case \" \": // Space for attack\n return {\n attack: true,\n timestamp,\n };\n case \"shift\":\n return {\n block: true,\n timestamp,\n };\n default:\n return null;\n }\n}\n\n/**\n * Hook for combat input handling\n */\nexport function useCombatInput(onCombatInput: (input: CombatInput) => void) {\n const isEnabled = useRef<boolean>(true);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (!isEnabled.current) return;\n\n const combatInput = processCombatInput(event);\n if (combatInput) {\n onCombatInput(combatInput);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onCombatInput]);\n\n return {\n enable: () => {\n isEnabled.current = true;\n },\n disable: () => {\n isEnabled.current = false;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inputSystem.js","names":[],"sources":["../../src/utils/inputSystem.ts"],"sourcesContent":["import { COMBAT_CONTROLS } from \"@/systems/types\";\nimport type { Position } from \"@/types/common\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport type { MovementInput } from \"../systems/physics/MovementPhysics\";\nimport { MovementPhysics } from \"../systems/physics/MovementPhysics\";\nimport { TrigramStance } from \"../types/common\";\nimport { calculateArenaBounds, DEFAULT_PHYSICS_ARENA_BOUNDS } from \"../types/PhysicsTypes\";\nimport type { MovementArenaBounds } from \"../types/PhysicsTypes\";\n\n/**\n * Configuration interface for the input system and player movement.\n * Uses physics-first approach: all positions and velocities are in meters.\n *\n * **Korean**: 입력 시스템 설정 (Input System Configuration)\n *\n * ## Physics-First Architecture\n *\n * This interface requires worldWidthMeters and worldDepthMeters to enable\n * the new physics-first coordinate system. Without these properties, the\n * movement system cannot properly convert between physics (meters) and\n * rendering (pixels).\n *\n * ### Migration Guide\n *\n * Existing code must be updated to pass world dimensions:\n *\n * ```typescript\n * // Before (incorrect):\n * const config = { bounds: { x: 0, y: 0, width: 960, height: 480 } };\n *\n * // After (correct):\n * const config = {\n * bounds: {\n * worldWidthMeters: 10, // From layout hook\n * worldDepthMeters: 10 // From layout hook\n * }\n * };\n * ```\n *\n * ### Fallback Behavior\n *\n * If worldWidthMeters/worldDepthMeters are not provided, the system falls back\n * to DEFAULT_PHYSICS_ARENA_BOUNDS (10m × 7.5m) to ensure movement stays bounded.\n * Callers SHOULD provide these values from their layout hooks (useCombatLayout, \n * useTrainingLayout) for proper arena sizing.\n */\nexport interface InputSystemConfig {\n /** Whether the input system is enabled and processing input */\n readonly enabled?: boolean;\n\n /**\n * Arena world dimensions in meters for physics calculations.\n *\n * **REQUIRED for physics-first coordinate system to work.**\n *\n * These values must come from layout hooks:\n * - CombatScreen3D: Use arenaBounds.worldWidthMeters/worldDepthMeters from useCombatLayout()\n * - TrainingScreen3D: Use trainingAreaBounds.worldWidthMeters/worldDepthMeters from useTrainingLayout()\n */\n readonly bounds?: {\n /** Physical arena width in meters (e.g., 6m mobile, 10m desktop, 14m 4K) */\n readonly worldWidthMeters: number;\n /** Physical arena depth in meters (e.g., 6m mobile, 10m desktop, 14m 4K) */\n readonly worldDepthMeters: number;\n };\n\n /** Callback invoked when player position changes (position in meters) */\n readonly onPositionChange?: (position: Position) => void;\n\n /** Initial player position in METERS (x = lateral, y = forward/backward) */\n readonly initialPositionMeters?: Position;\n\n // Physics-based movement parameters (always enabled)\n /** Current trigram stance affecting movement speed */\n readonly currentStance?: TrigramStance;\n\n /** Leg injury factor (0-1, where 1 is fully injured) affecting movement speed */\n readonly legInjuryFactor?: number;\n\n /** Whether player is running (sprint mode) */\n readonly isRunning?: boolean;\n\n /** Whether to use tactical step mode (30cm grid quantization) */\n readonly useTacticalSteps?: boolean;\n\n // Speed modifier overrides from SpeedModifierSystem\n /** Final calculated maximum speed in meters per second */\n readonly maxSpeedOverride?: number;\n\n /** Final calculated acceleration in meters per second squared */\n readonly accelerationOverride?: number;\n}\n\nexport interface MovementState {\n readonly up: boolean;\n readonly down: boolean;\n readonly left: boolean;\n readonly right: boolean;\n readonly position: Position;\n readonly isMoving: boolean; // Add isMoving to movement state\n}\n\nexport interface PlayerMovementResult {\n /** Player position in METERS (x = lateral, y = forward/backward in arena) */\n readonly playerPosition: Position;\n readonly movementState: MovementState;\n readonly isMoving: boolean;\n readonly isKeyPressed: (key: string) => boolean;\n /** Velocity in m/s (x = lateral, y = forward/backward) */\n readonly velocity?: { x: number; y: number };\n /** Current speed magnitude in m/s */\n readonly speed?: number;\n}\n\n/**\n * Hook for handling player movement with physics-first approach.\n * All positions and velocities are in METERS - no pixel conversions.\n *\n * **Korean**: 플레이어 이동 훅 (Player Movement Hook)\n *\n * @param config - Physics-first configuration with positions in meters\n * @returns Movement state and physics data (all in meters)\n */\nexport function usePlayerMovement(\n config: InputSystemConfig,\n): PlayerMovementResult {\n const {\n enabled = true,\n bounds,\n onPositionChange,\n initialPositionMeters = { x: 0, y: 0 },\n currentStance = TrigramStance.GEON,\n legInjuryFactor = 0,\n isRunning: isRunningProp = false,\n useTacticalSteps = false,\n maxSpeedOverride,\n accelerationOverride,\n } = config;\n\n // Position in METERS (x = lateral position, y = forward/backward position)\n const [playerPosition, setPlayerPosition] = useState<Position>(\n initialPositionMeters,\n );\n const [keyState, setKeyState] = useState({\n up: false,\n down: false,\n left: false,\n right: false,\n });\n // Physics state for render (velocity and speed in m/s)\n const [velocity, setVelocity] = useState<\n { x: number; y: number } | undefined\n >(undefined);\n const [speed, setSpeed] = useState<number | undefined>(undefined);\n\n // Auto-run detection: track how long movement keys have been held\n // After sustained movement, automatically transition from walking to running\n const movementStartTimeRef = useRef<number | null>(null);\n const AUTO_RUN_THRESHOLD_MS = 300; // Transition to run after 300ms of sustained movement\n\n // Physics-based movement state (always initialized for realistic combat)\n const physicsEngineRef = useRef<MovementPhysics | null>(null);\n const physicsStateRef = useRef<{\n position: THREE.Vector3;\n velocity: THREE.Vector3;\n acceleration: number;\n maxSpeed: number;\n currentStance: TrigramStance;\n legInjuryFactor: number;\n } | null>(null);\n\n // Initialize physics engine once on mount (always enabled)\n // All positions are in METERS - no pixel conversion needed\n useEffect(() => {\n if (!physicsEngineRef.current) {\n // Use arena width for physics-aware speed scaling\n // Validate and fall back to default if invalid\n const width = bounds?.worldWidthMeters;\n const arenaWidth =\n width != null && Number.isFinite(width) && width > 0\n ? width\n : DEFAULT_PHYSICS_ARENA_BOUNDS.worldWidthMeters;\n physicsEngineRef.current = new MovementPhysics(arenaWidth);\n // Initial position in meters (x = lateral, z = forward/backward)\n physicsStateRef.current = {\n position: new THREE.Vector3(\n initialPositionMeters.x,\n 0,\n initialPositionMeters.y,\n ),\n velocity: new THREE.Vector3(0, 0, 0),\n acceleration: 0,\n maxSpeed: 6.0, // Default to BASE_WALK_SPEED (6.0 m/s for responsive combat)\n currentStance,\n legInjuryFactor: legInjuryFactor ?? 0,\n };\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Compute arena bounds synchronously when bounds dimensions change\n // Uses useMemo to ensure bounds are available immediately (not after effect runs)\n // Falls back to default arena bounds if invalid or missing\n // Depend on the whole `bounds` object so the compiler's inferred property-access\n // dependencies (bounds.worldWidthMeters / bounds.worldDepthMeters) are covered.\n const arenaBoundsResult = useMemo<{\n bounds: MovementArenaBounds | undefined;\n error?: Error;\n }>(() => {\n if (bounds?.worldWidthMeters != null && bounds?.worldDepthMeters != null) {\n try {\n return {\n bounds: calculateArenaBounds(\n {\n worldWidthMeters: bounds.worldWidthMeters,\n worldDepthMeters: bounds.worldDepthMeters,\n },\n 0.3 // 0.3m character radius\n ),\n };\n } catch (error) {\n // If validation fails, fall back to default bounds\n // Error will be logged in useEffect to keep render pure\n return {\n bounds: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n // Fallback: use default arena bounds to ensure movement stays bounded\n try {\n return {\n bounds: calculateArenaBounds(\n {\n worldWidthMeters: DEFAULT_PHYSICS_ARENA_BOUNDS.worldWidthMeters,\n worldDepthMeters: DEFAULT_PHYSICS_ARENA_BOUNDS.worldDepthMeters,\n },\n 0.3 // 0.3m character radius\n ),\n };\n } catch (error) {\n // Should never happen with default bounds, but handle gracefully\n // Error will be logged in useEffect to keep render pure\n return {\n bounds: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }, [bounds]);\n\n const arenaBounds = arenaBoundsResult.bounds;\n\n // Log arena bounds calculation errors in an effect (not during render)\n useEffect(() => {\n if (arenaBoundsResult.error) {\n if (bounds?.worldWidthMeters != null && bounds?.worldDepthMeters != null) {\n // Custom bounds failed validation\n console.warn(\n \"Failed to calculate arena bounds, using defaults:\",\n arenaBoundsResult.error\n );\n } else {\n // Should never happen with default bounds\n console.error(\n \"Failed to calculate default arena bounds:\",\n arenaBoundsResult.error\n );\n }\n }\n }, [arenaBoundsResult.error, bounds?.worldWidthMeters, bounds?.worldDepthMeters]);\n\n // Update physics engine arena width when bounds change (legacy)\n useEffect(() => {\n if (!physicsEngineRef.current) {\n return;\n }\n\n const width = bounds?.worldWidthMeters;\n if (width == null) {\n return;\n }\n\n // Validate width before applying to physics engine to avoid runtime errors\n if (!Number.isFinite(width) || width <= 0) {\n console.warn(\n \"Ignoring invalid worldWidthMeters when updating arena width:\",\n width,\n );\n return;\n }\n\n try {\n physicsEngineRef.current.setArenaWidth(width);\n } catch (error) {\n console.warn(\"Failed to update physics arena width:\", error);\n }\n }, [bounds?.worldWidthMeters]);\n\n // Track pressed keys for combat system\n const pressedKeys = useRef<Set<string>>(new Set());\n // Use useState lazy initializer for performance.now() to avoid impure function during render\n const [initialTime] = useState(() => performance.now());\n const lastUpdateTime = useRef(initialTime);\n const animationFrameId = useRef<number | null>(null);\n\n // Refs to track last reported position/velocity to avoid useCallback dependency issues\n // This prevents the animation frame from being cancelled every frame due to callback recreation\n const lastReportedPositionRef = useRef<Position>(initialPositionMeters);\n const lastReportedVelocityRef = useRef<{ x: number; y: number } | undefined>(\n undefined,\n );\n const lastReportedSpeedRef = useRef<number | undefined>(undefined);\n\n // Ref to track keyState for physics loop - avoids recreating callback on key changes\n const keyStateRef = useRef({\n up: false,\n down: false,\n left: false,\n right: false,\n });\n\n // Calculate if currently moving\n const isMoving =\n keyState.up || keyState.down || keyState.left || keyState.right;\n\n // Create complete movement state\n const movementState: MovementState = {\n ...keyState,\n position: playerPosition,\n isMoving,\n };\n\n // Key press checker for combat system\n const isKeyPressed = useCallback((key: string): boolean => {\n return pressedKeys.current.has(key);\n }, []);\n\n // Enhanced keyboard event handlers\n const handleKeyDown = useCallback(\n (event: KeyboardEvent) => {\n if (!enabled) return;\n\n const key = event.key.toLowerCase();\n pressedKeys.current.add(key);\n\n // ✅ FIXED: Add all movement keys including WASD and arrows\n // Update both ref (for physics loop) and state (for React re-render)\n switch (key) {\n case \"w\":\n case \"arrowup\":\n keyStateRef.current.up = true;\n setKeyState((prev) => ({ ...prev, up: true }));\n event.preventDefault();\n break;\n case \"s\":\n case \"arrowdown\":\n keyStateRef.current.down = true;\n setKeyState((prev) => ({ ...prev, down: true }));\n event.preventDefault();\n break;\n case \"a\":\n case \"arrowleft\":\n keyStateRef.current.left = true;\n setKeyState((prev) => ({ ...prev, left: true }));\n event.preventDefault();\n break;\n case \"d\":\n case \"arrowright\":\n keyStateRef.current.right = true;\n setKeyState((prev) => ({ ...prev, right: true }));\n event.preventDefault();\n break;\n }\n },\n [enabled],\n );\n\n const handleKeyUp = useCallback(\n (event: KeyboardEvent) => {\n if (!enabled) return;\n\n const key = event.key.toLowerCase();\n pressedKeys.current.delete(key);\n\n // ✅ FIXED: Handle key release for all movement keys\n // Update both ref (for physics loop) and state (for React re-render)\n switch (key) {\n case \"w\":\n case \"arrowup\":\n keyStateRef.current.up = false;\n setKeyState((prev) => ({ ...prev, up: false }));\n break;\n case \"s\":\n case \"arrowdown\":\n keyStateRef.current.down = false;\n setKeyState((prev) => ({ ...prev, down: false }));\n break;\n case \"a\":\n case \"arrowleft\":\n keyStateRef.current.left = false;\n setKeyState((prev) => ({ ...prev, left: false }));\n break;\n case \"d\":\n case \"arrowright\":\n keyStateRef.current.right = false;\n setKeyState((prev) => ({ ...prev, right: false }));\n break;\n }\n },\n [enabled],\n );\n\n // ✅ FIXED: Proper movement calculation with correct bounds\n // Use a ref to store the callback to avoid reference before declaration issue\n const updatePositionRef = useRef<(() => void) | null>(null);\n\n const updatePosition = useCallback(() => {\n // Check if any movement keys are pressed using ref (not stale state)\n const keys = keyStateRef.current;\n const isCurrentlyMoving = keys.up || keys.down || keys.left || keys.right;\n\n if (!enabled || !isCurrentlyMoving) {\n animationFrameId.current = null;\n return;\n }\n\n const now = performance.now();\n const deltaTime = Math.min(now - (lastUpdateTime.current ?? now), 50);\n lastUpdateTime.current = now;\n\n if (deltaTime <= 0) {\n animationFrameId.current = requestAnimationFrame(() =>\n updatePositionRef.current?.(),\n );\n return;\n }\n\n // Physics-based movement (always enabled for realistic combat)\n if (physicsEngineRef.current && physicsStateRef.current) {\n // Apply speed modifiers if provided by SpeedModifierSystem\n // BUG FIX: Now properly passing maxSpeedOverride to physics engine\n if (maxSpeedOverride !== undefined) {\n physicsEngineRef.current.setMaxSpeed(maxSpeedOverride);\n }\n\n if (accelerationOverride !== undefined) {\n physicsEngineRef.current.setAcceleration(accelerationOverride);\n }\n\n // Convert key state to physics input (using ref to avoid callback recreation)\n // Screen coordinates: UP/W = toward top of screen, DOWN/S = toward bottom\n // Physics Z-axis: negative Z = toward top, positive Z = toward bottom\n const keys = keyStateRef.current;\n const forward = keys.up ? -1 : keys.down ? 1 : 0;\n const lateral = keys.right ? 1 : keys.left ? -1 : 0;\n const isCurrentlyMoving = forward !== 0 || lateral !== 0;\n\n // Auto-run detection: transition to running after sustained movement\n const now = performance.now();\n if (isCurrentlyMoving) {\n movementStartTimeRef.current ??= now;\n } else {\n movementStartTimeRef.current = null;\n }\n\n // Determine if player should be running (auto-run after threshold)\n const movementDuration = movementStartTimeRef.current\n ? now - movementStartTimeRef.current\n : 0;\n const shouldRun =\n isRunningProp || movementDuration > AUTO_RUN_THRESHOLD_MS;\n\n const physicsInput: MovementInput = {\n forward,\n lateral,\n isRunning: shouldRun,\n isMoving: isCurrentlyMoving,\n useTacticalSteps,\n };\n\n // Update physics state\n const state = physicsStateRef.current;\n state.currentStance = currentStance;\n state.legInjuryFactor = legInjuryFactor;\n\n // Clamp delta time to 1/30s (≈33.33ms) to match usePlayerMovement and prevent instability\n const clampedDeltaTimeMs = Math.min(deltaTime, 1000 / 30);\n\n // Use arena bounds computed via useMemo (available synchronously)\n physicsEngineRef.current.updateMovement(\n state,\n physicsInput,\n clampedDeltaTimeMs / 1000,\n arenaBounds, // Use memoized bounds\n );\n\n // Position in meters (x = lateral, y = forward/backward)\n const newPosition = { x: state.position.x, y: state.position.z };\n\n // Velocity in m/s (x = lateral, y = forward/backward)\n const newVelocity = { x: state.velocity.x, y: state.velocity.z };\n const newSpeed = state.velocity.length();\n\n // Use refs for comparison to avoid recreating callback on every frame\n // This prevents the animation frame from being cancelled due to useCallback recreation\n const lastPos = lastReportedPositionRef.current;\n if (newPosition.x !== lastPos.x || newPosition.y !== lastPos.y) {\n lastReportedPositionRef.current = newPosition;\n setPlayerPosition(newPosition);\n onPositionChange?.(newPosition);\n }\n\n // Update velocity and speed if changed (with epsilon tolerance for floating-point stability)\n const EPSILON = 0.001;\n const lastVel = lastReportedVelocityRef.current;\n const velocityChanged =\n !lastVel ||\n Math.abs(lastVel.x - newVelocity.x) > EPSILON ||\n Math.abs(lastVel.y - newVelocity.y) > EPSILON;\n if (velocityChanged) {\n lastReportedVelocityRef.current = newVelocity;\n setVelocity(newVelocity);\n }\n // Initialize speed when undefined, then update only on significant changes\n const lastSpd = lastReportedSpeedRef.current;\n if (lastSpd === undefined || Math.abs(lastSpd - newSpeed) > EPSILON) {\n lastReportedSpeedRef.current = newSpeed;\n setSpeed(newSpeed);\n }\n }\n\n // Continue animation if still moving (check ref, not stale closure)\n const stillMoving =\n keyStateRef.current.up ||\n keyStateRef.current.down ||\n keyStateRef.current.left ||\n keyStateRef.current.right;\n if (stillMoving) {\n animationFrameId.current = requestAnimationFrame(() =>\n updatePositionRef.current?.(),\n );\n } else {\n animationFrameId.current = null;\n }\n // NOTE: playerPosition, velocity, speed, keyState, isMoving intentionally excluded from deps\n // Using refs (lastReportedPositionRef, lastReportedVelocityRef, lastReportedSpeedRef, keyStateRef)\n // for comparison to prevent animation frame cancellation on every state update.\n // arenaBounds is computed from bounds and automatically updates when bounds changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n enabled,\n // playerPosition - excluded, using ref\n // keyState - excluded, using keyStateRef\n // isMoving - excluded, using keyStateRef for movement check\n // arenaBounds - excluded, derived from bounds (below)\n bounds,\n onPositionChange,\n currentStance,\n legInjuryFactor,\n isRunningProp,\n useTacticalSteps,\n // velocity - excluded, using ref\n // speed - excluded, using ref\n maxSpeedOverride,\n accelerationOverride,\n ]);\n\n // Keep updatePositionRef in sync via useEffect (not during render)\n useEffect(() => {\n updatePositionRef.current = updatePosition;\n }, [updatePosition]);\n\n // Handle keyboard input\n useEffect(() => {\n if (!enabled) return;\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n if (animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n }\n };\n }, [enabled, handleKeyDown, handleKeyUp]);\n\n // Start animation loop when movement begins\n useEffect(() => {\n if (isMoving && !animationFrameId.current) {\n lastUpdateTime.current = performance.now();\n // Use ref to avoid dependency on updatePosition callback\n animationFrameId.current = requestAnimationFrame(() => {\n updatePositionRef.current?.();\n });\n } else if (!isMoving && animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n animationFrameId.current = null;\n }\n\n return () => {\n if (animationFrameId.current) {\n cancelAnimationFrame(animationFrameId.current);\n animationFrameId.current = null;\n }\n };\n // Only depend on isMoving - updatePositionRef is stable\n }, [isMoving]);\n\n return {\n playerPosition,\n movementState,\n isMoving,\n isKeyPressed,\n velocity,\n speed,\n };\n}\n\nexport interface InputEvent {\n readonly type: \"keydown\" | \"keyup\" | \"click\" | \"touchstart\" | \"touchend\";\n readonly key?: string;\n readonly target?: EventTarget | null;\n readonly timestamp: number;\n}\n\nexport interface CombatInput {\n readonly stanceChange?: TrigramStance;\n readonly attack?: boolean;\n readonly block?: boolean;\n readonly movement?: MovementState;\n readonly timestamp: number;\n}\n\n/**\n * Input system for combat controls\n */\nexport class InputSystem {\n private actionCallbacks = new Map<string, (() => void)[]>();\n private isEnabled = true;\n\n constructor() {\n this.setupEventListeners();\n }\n\n private setupEventListeners() {\n window.addEventListener(\"keydown\", this.handleKeyDown.bind(this));\n window.addEventListener(\"keyup\", this.handleKeyUp.bind(this));\n }\n\n private handleKeyDown(event: KeyboardEvent) {\n if (!this.isEnabled) return;\n\n const key = event.key;\n this.triggerAction(`keydown:${key}`);\n this.triggerAction(\"keydown\");\n }\n\n private handleKeyUp(event: KeyboardEvent) {\n if (!this.isEnabled) return;\n\n const key = event.key;\n this.triggerAction(`keyup:${key}`);\n this.triggerAction(\"keyup\");\n }\n\n registerAction(action: string, callback: () => void) {\n if (!this.actionCallbacks.has(action)) {\n this.actionCallbacks.set(action, []);\n }\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n callbacks.push(callback);\n }\n }\n\n unregisterAction(action: string, callback?: () => void) {\n if (!this.actionCallbacks.has(action)) return;\n\n if (callback) {\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n const index = callbacks.indexOf(callback);\n if (index > -1) {\n callbacks.splice(index, 1);\n }\n }\n } else {\n this.actionCallbacks.delete(action);\n }\n }\n\n clearActions() {\n this.actionCallbacks.clear();\n }\n\n isActionActive(action: string): boolean {\n return this.actionCallbacks.has(action);\n }\n\n enable() {\n this.isEnabled = true;\n }\n\n disable() {\n this.isEnabled = false;\n }\n\n private triggerAction(action: string) {\n const callbacks = this.actionCallbacks.get(action);\n if (callbacks) {\n callbacks.forEach((callback) => callback());\n }\n }\n\n destroy() {\n window.removeEventListener(\"keydown\", this.handleKeyDown.bind(this));\n window.removeEventListener(\"keyup\", this.handleKeyUp.bind(this));\n this.clearActions();\n }\n}\n\n/**\n * Get stance from keyboard input\n */\nexport function getStanceFromKey(key: string): TrigramStance | null {\n const stanceKey = key as keyof typeof COMBAT_CONTROLS.stanceControls;\n\n if (stanceKey in COMBAT_CONTROLS.stanceControls) {\n return COMBAT_CONTROLS.stanceControls[stanceKey].stance;\n }\n\n return null;\n}\n\n/**\n * Process combat input and return structured combat data\n */\nexport function processCombatInput(event: KeyboardEvent): CombatInput | null {\n const key = event.key;\n const timestamp = performance.now();\n\n // Check for stance change (1-8 keys)\n const stance = getStanceFromKey(key);\n if (stance) {\n return {\n stanceChange: stance,\n timestamp,\n };\n }\n\n // Check for combat actions\n switch (key.toLowerCase()) {\n case \" \": // Space for attack\n return {\n attack: true,\n timestamp,\n };\n case \"shift\":\n return {\n block: true,\n timestamp,\n };\n default:\n return null;\n }\n}\n\n/**\n * Hook for combat input handling\n */\nexport function useCombatInput(onCombatInput: (input: CombatInput) => void) {\n const isEnabled = useRef<boolean>(true);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (!isEnabled.current) return;\n\n const combatInput = processCombatInput(event);\n if (combatInput) {\n onCombatInput(combatInput);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onCombatInput]);\n\n return {\n enable: () => {\n isEnabled.current = true;\n },\n disable: () => {\n isEnabled.current = false;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"koreanThemeHelpers.js","names":[],"sources":["../../src/utils/koreanThemeHelpers.ts"],"sourcesContent":["/**\n * Korean Theme Helper Utilities for HTML Overlays\n *\n * Provides consistent Korean martial arts cyberpunk theming across all HTML overlay components.\n * These utilities ensure color consistency, bilingual text formatting, and responsive spacing.\n *\n * @module utils/koreanThemeHelpers\n * @category UI Utilities\n * @korean 한국테마도우미\n */\n\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { SPACING, BORDER_RADIUS } from \"../types/constants/ui\";\nimport { hexToRgbaString } from \"./colorUtils\";\nimport {\n getNeonGlowEffect,\n getNeonTextShadow,\n getLayeredDepthEffect,\n getCyberpunkGradient,\n getSmoothTransition,\n getKoreanFontOptimization,\n getHoverStateStyles,\n getFocusStateStyles,\n getBackdropBlurEffect,\n getTrigramSymbolGlow as getTrigramGlowEffect,\n combineShadowEffects,\n type GlowIntensity,\n type HoverAnimationType,\n} from \"./visualEffects\";\n\n/**\n * Bilingual text format options\n * @korean 이중언어형식\n */\nexport type BilingualFormat = \"pipe\" | \"parentheses\" | \"bracket\" | \"slash\";\n\n/**\n * Button variant types for Korean theme\n * @korean 버튼변형\n */\nexport type KoreanButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"danger\"\n | \"success\"\n | \"warning\";\n\n/**\n * Responsive spacing size\n * @korean 반응형간격크기\n */\nexport type SpacingSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"xxl\";\n\n/**\n * Enhanced overlay styles configuration\n * @korean 향상된오버레이스타일설정\n */\nexport interface EnhancedOverlayConfig {\n readonly opacity?: number;\n readonly glowIntensity?: GlowIntensity;\n readonly includeGradient?: boolean;\n readonly includeBackdropBlur?: boolean;\n readonly depthLayers?: number;\n}\n\n/**\n * Base styles for all Korean-themed overlays\n *\n * Provides consistent dark background with cyan/gold accents\n *\n * @param opacity - Background opacity (0-1), default 0.9\n * @returns React.CSSProperties object with Korean theme\n *\n * @example\n * ```tsx\n * <div style={getKoreanOverlayBaseStyles(0.95)}>\n * Content\n * </div>\n * ```\n *\n * @korean 한국오버레이기본스타일얻기\n */\nexport function getKoreanOverlayBaseStyles(\n opacity: number = 0.9,\n): React.CSSProperties {\n return {\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, opacity),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: `${BORDER_RADIUS.MD}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n };\n}\n\n/**\n * Enhanced Korean overlay styles with visual effects\n *\n * Provides advanced cyberpunk styling with neon glow, depth effects,\n * gradients, and backdrop blur for Korean-themed overlays.\n *\n * @param config - Enhanced overlay configuration\n * @returns React.CSSProperties with advanced visual effects\n *\n * @example\n * ```tsx\n * const styles = getEnhancedKoreanOverlayStyles({\n * opacity: 0.95,\n * glowIntensity: 'medium',\n * includeGradient: true,\n * includeBackdropBlur: true,\n * depthLayers: 3,\n * });\n * <div style={styles}>Enhanced Content</div>\n * ```\n *\n * @korean 향상된한국오버레이스타일얻기\n */\nexport function getEnhancedKoreanOverlayStyles(\n config: EnhancedOverlayConfig = {},\n): React.CSSProperties {\n const {\n opacity = 0.9,\n glowIntensity = \"medium\",\n includeGradient = false,\n includeBackdropBlur = false,\n depthLayers = 2,\n } = config;\n\n // Base styles\n const baseStyles = getKoreanOverlayBaseStyles(opacity);\n\n // Neon glow effect\n const neonGlow = getNeonGlowEffect(\n KOREAN_COLORS.PRIMARY_CYAN,\n glowIntensity,\n true,\n );\n\n // Depth effect\n const depthShadow = getLayeredDepthEffect({\n layers: depthLayers,\n baseOffset: 2,\n baseBlur: 4,\n color: KOREAN_COLORS.BLACK_SOLID,\n opacity: 0.5,\n });\n\n // Combine shadows\n const boxShadow = combineShadowEffects([neonGlow, depthShadow]);\n\n // Optional gradient background\n let background = baseStyles.backgroundColor;\n if (includeGradient) {\n const gradient = getCyberpunkGradient(\n KOREAN_COLORS.PRIMARY_CYAN,\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n 135,\n );\n background = `${gradient}, ${background}`;\n }\n\n // Optional backdrop blur\n const backdropStyles = includeBackdropBlur\n ? getBackdropBlurEffect(10, 1.5)\n : {};\n\n // Combine all styles\n return {\n ...baseStyles,\n ...backdropStyles,\n background,\n boxShadow,\n transition: getSmoothTransition(\"all\", \"normal\"),\n ...getKoreanFontOptimization(16, \"normal\"),\n };\n}\n\n/**\n * Format bilingual text with Korean and English\n *\n * Supports multiple formatting styles:\n * - pipe: \"한글 | English\"\n * - parentheses: \"한글 (English)\"\n * - bracket: \"한글 [English]\"\n * - slash: \"한글 / English\"\n *\n * @param korean - Korean text\n * @param english - English text\n * @param format - Format style, default \"pipe\"\n * @returns Formatted bilingual string\n *\n * @example\n * ```tsx\n * formatBilingualText('공격', 'Attack', 'pipe') // \"공격 | Attack\"\n * formatBilingualText('방어', 'Defense', 'parentheses') // \"방어 (Defense)\"\n * ```\n *\n * @korean 이중언어텍스트형식화\n */\nexport function formatBilingualText(\n korean: string,\n english: string,\n format: BilingualFormat = \"pipe\",\n): string {\n switch (format) {\n case \"pipe\":\n return `${korean} | ${english}`;\n case \"parentheses\":\n return `${korean} (${english})`;\n case \"bracket\":\n return `${korean} [${english}]`;\n case \"slash\":\n return `${korean} / ${english}`;\n default:\n return `${korean} | ${english}`;\n }\n}\n\n/**\n * Get Korean button styles with variant support\n *\n * Returns consistent button styling based on variant:\n * - primary: Cyan border, gold text\n * - secondary: Gold border, white text\n * - danger: Red border, red text\n * - success: Green border, green text\n * - warning: Orange border, orange text\n *\n * @param variant - Button variant type\n * @param isHovered - Whether button is hovered\n * @param isPressed - Whether button is pressed\n * @returns React.CSSProperties for button\n *\n * @example\n * ```tsx\n * <button style={getKoreanButtonStyles('primary', isHovered, isPressed)}>\n * {formatBilingualText('확인', 'Confirm')}\n * </button>\n * ```\n *\n * @korean 한국버튼스타일얻기\n */\nexport function getKoreanButtonStyles(\n variant: KoreanButtonVariant = \"primary\",\n isHovered: boolean = false,\n isPressed: boolean = false,\n): React.CSSProperties {\n // Variant-specific colors\n const variantColors = {\n primary: {\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: KOREAN_COLORS.PRIMARY_CYAN,\n },\n secondary: {\n border: KOREAN_COLORS.ACCENT_GOLD,\n text: KOREAN_COLORS.TEXT_PRIMARY,\n hoverBg: KOREAN_COLORS.ACCENT_GOLD,\n },\n danger: {\n border: KOREAN_COLORS.ACCENT_RED,\n text: KOREAN_COLORS.ACCENT_RED,\n hoverBg: KOREAN_COLORS.ACCENT_RED,\n },\n success: {\n border: KOREAN_COLORS.ACCENT_GREEN,\n text: KOREAN_COLORS.ACCENT_GREEN,\n hoverBg: KOREAN_COLORS.ACCENT_GREEN,\n },\n warning: {\n border: KOREAN_COLORS.WARNING_ORANGE,\n text: KOREAN_COLORS.WARNING_ORANGE,\n hoverBg: KOREAN_COLORS.WARNING_ORANGE,\n },\n };\n\n const colors = variantColors[variant];\n\n let backgroundColor = hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.9,\n );\n let borderColor = hexToRgbaString(colors.border, 0.8);\n const textColor = hexToRgbaString(colors.text);\n let boxShadow = \"none\";\n let transform = \"scale(1)\";\n\n if (isPressed) {\n backgroundColor = hexToRgbaString(colors.hoverBg, 0.2);\n transform = \"scale(0.98)\";\n } else if (isHovered) {\n backgroundColor = hexToRgbaString(colors.hoverBg, 0.1);\n borderColor = hexToRgbaString(colors.border, 1.0);\n boxShadow = `0 0 10px ${hexToRgbaString(colors.border, 0.5)}`;\n }\n\n return {\n backgroundColor,\n border: `2px solid ${borderColor}`,\n borderRadius: `${BORDER_RADIUS.SM}px`,\n color: textColor,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n padding: `${SPACING.SM}px ${SPACING.MD}px`,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n boxShadow,\n transform,\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n };\n}\n\n/**\n * Enhanced Korean button configuration\n * @korean 향상된한국버튼설정\n */\nexport interface EnhancedButtonConfig {\n readonly variant?: KoreanButtonVariant;\n readonly isHovered?: boolean;\n readonly isPressed?: boolean;\n readonly isFocused?: boolean;\n readonly glowIntensity?: GlowIntensity;\n readonly hoverAnimation?: HoverAnimationType;\n}\n\n/**\n * Get enhanced Korean button styles with neon glow\n *\n * Provides advanced button styling with cyberpunk neon effects,\n * smooth transitions, and Korean font optimization.\n *\n * @param config - Enhanced button configuration\n * @returns React.CSSProperties with neon glow effects\n *\n * @example\n * ```tsx\n * const buttonStyle = getKoreanButtonWithGlow({\n * variant: 'primary',\n * isHovered: true,\n * glowIntensity: 'strong',\n * hoverAnimation: 'combined',\n * });\n * <button style={buttonStyle}>\n * {formatBilingualText('공격', 'Attack')}\n * </button>\n * ```\n *\n * @korean 네온글로우한국버튼스타일얻기\n */\nexport function getKoreanButtonWithGlow(\n config: EnhancedButtonConfig = {},\n): React.CSSProperties {\n const {\n variant = \"primary\",\n isHovered = false,\n isPressed = false,\n isFocused = false,\n glowIntensity = \"medium\",\n hoverAnimation = \"combined\",\n } = config;\n\n // Get base button styles\n const baseStyles = getKoreanButtonStyles(variant, false, isPressed);\n\n // Variant-specific glow colors\n const glowColors = {\n primary: KOREAN_COLORS.PRIMARY_CYAN,\n secondary: KOREAN_COLORS.ACCENT_GOLD,\n danger: KOREAN_COLORS.ACCENT_RED,\n success: KOREAN_COLORS.ACCENT_GREEN,\n warning: KOREAN_COLORS.WARNING_ORANGE,\n };\n\n const glowColor = glowColors[variant];\n\n // Hover state with visual effects\n let hoverStyles: React.CSSProperties = {};\n if (isHovered) {\n hoverStyles = getHoverStateStyles(glowColor, hoverAnimation, glowIntensity);\n }\n\n // Focus state\n let focusStyles: React.CSSProperties = {};\n if (isFocused) {\n focusStyles = getFocusStateStyles(glowColor, true);\n }\n\n // Extract font size from base styles (getKoreanButtonStyles always returns number | string)\n const baseFontSize =\n typeof baseStyles.fontSize === \"number\"\n ? baseStyles.fontSize\n : parseInt(String(baseStyles.fontSize), 10) || 14;\n\n // Neon text glow for button text\n const textGlow = getNeonTextShadow(\n glowColor,\n isHovered ? \"medium\" : \"subtle\",\n );\n\n // Korean font optimization\n const fontOptimization = getKoreanFontOptimization(baseFontSize, \"bold\");\n\n return {\n ...baseStyles,\n ...fontOptimization,\n ...hoverStyles,\n ...focusStyles,\n textShadow: textGlow,\n transition: getSmoothTransition(\"all\", \"normal\"),\n };\n}\n\n/**\n * Get button visual effects only (no color/background/border)\n *\n * Extracts only visual effects (boxShadow, transition, transform, textShadow) from\n * getKoreanButtonWithGlow without color-related properties. This is useful for cases\n * where buttons need custom colors (e.g., menu selection states) but want to preserve\n * the visual effects system.\n *\n * This function provides an explicit contract for visual effects extraction, preventing\n * fragile coupling that could occur with destructuring patterns. If getKoreanButtonWithGlow\n * adds new visual effect properties in the future, they should be explicitly added here.\n *\n * **Intentionally Excluded Properties:**\n * - Layout: fontSize, fontFamily, fontWeight, padding, width, height, cursor\n * - Typography: letterSpacing, lineHeight, textRendering, WebkitFontSmoothing, MozOsxFontSmoothing\n * - Colors: color, background, backgroundColor, border, borderColor\n *\n * **Included Visual Effects:**\n * - boxShadow: Neon glow effects\n * - transition: Smooth state changes (0.2s ease)\n * - transform: Hover/press animations (scale)\n * - textShadow: Text glow effects\n *\n * **Note:** If getKoreanButtonWithGlow adds new visual effect properties (e.g., 'filter',\n * 'opacity', 'willChange', 'backdropFilter'), they must be explicitly added to this function's\n * return type and extraction logic to maintain the explicit contract.\n *\n * @param config - Same configuration as getKoreanButtonWithGlow\n * @returns React.CSSProperties with only visual effects (boxShadow, transition, transform, textShadow)\n *\n * @example\n * ```tsx\n * const visualEffects = getButtonVisualEffectsOnly({\n * variant: 'primary',\n * glowIntensity: 'strong',\n * hoverAnimation: 'combined'\n * });\n *\n * <button style={{\n * ...visualEffects,\n * color: customColor, // Apply custom colors\n * background: customBg,\n * border: customBorder\n * }}>\n * Custom Button\n * </button>\n * ```\n *\n * @korean 버튼시각효과만얻기\n */\nexport function getButtonVisualEffectsOnly(\n config: Parameters<typeof getKoreanButtonWithGlow>[0],\n): Pick<\n React.CSSProperties,\n \"boxShadow\" | \"transition\" | \"transform\" | \"textShadow\"\n> {\n const fullStyles = getKoreanButtonWithGlow(config);\n\n // Explicitly extract only visual effect properties\n // Note: This explicitly excludes layout, typography, and color properties.\n // If new visual effects (filter, opacity, willChange, etc.) are added to\n // getKoreanButtonWithGlow, they must be added here to maintain the contract.\n return {\n boxShadow: fullStyles.boxShadow,\n transition: fullStyles.transition,\n transform: fullStyles.transform,\n textShadow: fullStyles.textShadow,\n };\n}\n\n/**\n * Get responsive spacing value\n *\n * Returns SPACING constant value for consistent spacing across components.\n * Optionally scales for mobile devices.\n *\n * **IMPORTANT**: This function accepts lowercase size parameters ('xs', 'sm', 'md', etc.)\n * to provide a more intuitive API, then internally converts to uppercase to match\n * SPACING constant keys ('XS', 'SM', 'MD', etc.). This design choice prioritizes\n * developer experience while maintaining compatibility with the SPACING constants.\n *\n * @param size - Spacing size constant ('xs', 'sm', 'md', 'lg', 'xl', 'xxl')\n * @param isMobile - Whether to apply mobile scaling (87.5% for mobile devices)\n * @returns Spacing value in pixels\n *\n * @example\n * ```tsx\n * const padding = getResponsiveSpacing('md', isMobile); // 16px desktop, 14px mobile\n * <div style={{ padding: `${padding}px` }}>Content</div>\n * ```\n *\n * @korean 반응형간격얻기\n */\nexport function getResponsiveSpacing(\n size: SpacingSize,\n isMobile: boolean = false,\n): number {\n // Convert lowercase API parameter to uppercase SPACING constant key\n // This provides a more ergonomic API while maintaining internal consistency\n const spacingKey = size.toUpperCase() as keyof typeof SPACING;\n const spacingValue = SPACING[spacingKey];\n const mobileScale = 0.875; // 87.5% for mobile\n\n // Runtime validation: While TypeScript prevents invalid sizes at compile time,\n // this check provides safety for JavaScript consumers and edge cases where\n // type assertions bypass TypeScript checks (e.g., 'as any', dynamic values)\n if (spacingValue === undefined) {\n const fallback = SPACING.MD;\n console.warn(\n `[koreanThemeHelpers:getResponsiveSpacing] Invalid spacing size \"${String(\n size,\n )}\" provided. Falling back to \"MD\".`,\n );\n return isMobile ? Math.round(fallback * mobileScale) : fallback;\n }\n\n return isMobile ? Math.round(spacingValue * mobileScale) : spacingValue;\n}\n\n/**\n * Get trigram symbol by name\n *\n * Returns Unicode trigram symbol for visual embellishment\n *\n * @param name - Trigram name in Korean\n * @returns Unicode trigram symbol\n *\n * @example\n * ```tsx\n * <div>{getTrigramSymbol('건')} 건 (Heaven)</div>\n * ```\n *\n * @korean 팔괘기호얻기\n */\nexport function getTrigramSymbol(\n name: \"건\" | \"태\" | \"리\" | \"진\" | \"손\" | \"감\" | \"간\" | \"곤\",\n): string {\n const symbols = {\n 건: \"☰\", // Heaven - 乾\n 태: \"☱\", // Lake - 兌\n 리: \"☲\", // Fire - 離\n 진: \"☳\", // Thunder - 震\n 손: \"☴\", // Wind - 巽\n 감: \"☵\", // Water - 坎\n 간: \"☶\", // Mountain - 艮\n 곤: \"☷\", // Earth - 坤\n };\n return symbols[name];\n}\n\n/**\n * Get trigram symbol styles with glow effect\n *\n * Enhances trigram symbols (☰☱☲☳☴☵☶☷) with cyberpunk neon glow\n * based on stance-specific colors and active state.\n *\n * @param config - Configuration object\n * @param config.stance - Trigram stance identifier (\"geon\", \"tae\", etc.)\n * @param config.isActive - Whether the trigram stance is currently active\n * @param config.size - Font size in pixels (optional, defaults based on active state)\n * @returns React.CSSProperties with trigram-specific glow\n *\n * @example\n * ```tsx\n * const trigramStyle = getTrigramSymbolWithGlow({ stance: 'geon', isActive: true });\n * <div style={trigramStyle}>\n * ☰ 건 | Geon\n * </div>\n * ```\n *\n * @korean 팔괘기호네온글로우스타일얻기\n */\nexport function getTrigramSymbolWithGlow(config: {\n readonly stance:\n | \"geon\"\n | \"tae\"\n | \"li\"\n | \"jin\"\n | \"son\"\n | \"gam\"\n | \"gan\"\n | \"gon\";\n readonly isActive?: boolean;\n readonly size?: number;\n}): React.CSSProperties & {\n WebkitUserSelect?: string;\n} {\n const { stance, isActive = false, size } = config;\n\n // Map English stance names to Korean\n const stanceToKorean = {\n geon: \"건\",\n tae: \"태\",\n li: \"리\",\n jin: \"진\",\n son: \"손\",\n gam: \"감\",\n gan: \"간\",\n gon: \"곤\",\n };\n\n const koreanName = stanceToKorean[stance];\n\n // Trigram-specific colors matching KOREAN_COLORS\n // Note: Comments reflect actual hex color values\n // TODO: Fix source constants (colors.ts lines 84, 87) - TRIGRAM_GEON_PRIMARY should say \"Gold\" not \"White\",\n // TRIGRAM_JIN_PRIMARY should say \"Medium Purple\" not \"Yellow\"\n const trigramColors: Record<string, number> = {\n 건: KOREAN_COLORS.TRIGRAM_GEON_PRIMARY, // Heaven - Gold (0xffd700)\n 태: KOREAN_COLORS.TRIGRAM_TAE_PRIMARY, // Lake - Sky Blue\n 리: KOREAN_COLORS.TRIGRAM_LI_PRIMARY, // Fire - Orange Red\n 진: KOREAN_COLORS.TRIGRAM_JIN_PRIMARY, // Thunder - Medium Purple (0x9370db)\n 손: KOREAN_COLORS.TRIGRAM_SON_PRIMARY, // Wind - Light Green\n 감: KOREAN_COLORS.TRIGRAM_GAM_PRIMARY, // Water - Blue\n 간: KOREAN_COLORS.TRIGRAM_GAN_PRIMARY, // Mountain - Brown\n 곤: KOREAN_COLORS.TRIGRAM_GON_PRIMARY, // Earth - Dark Gray\n };\n\n const trigramColor = trigramColors[koreanName];\n\n // Get glow effect from visualEffects\n const glowStyles = getTrigramGlowEffect(trigramColor, isActive);\n\n // Korean font optimization for trigram symbols\n const fontSize = size ?? (isActive ? 32 : 28);\n const fontStyles = getKoreanFontOptimization(fontSize, \"bold\");\n\n return {\n ...fontStyles,\n ...glowStyles,\n fontFamily: FONT_FAMILY.KOREAN,\n display: \"inline-block\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n };\n}\n\n/**\n * Get Korean color name for documentation\n *\n * Maps hex color to Korean color name for better documentation\n *\n * @param hexColor - Hex color value from KOREAN_COLORS\n * @returns Korean name and English translation\n *\n * @internal Used primarily for JSDoc documentation\n * @korean 한국색상이름얻기\n */\nexport function getKoreanColorName(hexColor: number): {\n korean: string;\n english: string;\n} {\n const colorNames: Record<number, { korean: string; english: string }> = {\n [KOREAN_COLORS.PRIMARY_CYAN]: { korean: \"청록\", english: \"Cyan\" },\n [KOREAN_COLORS.ACCENT_GOLD]: { korean: \"금색\", english: \"Gold\" },\n [KOREAN_COLORS.ACCENT_RED]: { korean: \"빨강\", english: \"Red\" },\n [KOREAN_COLORS.ACCENT_GREEN]: { korean: \"초록\", english: \"Green\" },\n [KOREAN_COLORS.WARNING_ORANGE]: { korean: \"주황\", english: \"Orange\" },\n [KOREAN_COLORS.TEXT_PRIMARY]: { korean: \"흰색\", english: \"White\" },\n [KOREAN_COLORS.UI_BACKGROUND_DARK]: {\n korean: \"어두운배경\",\n english: \"Dark Background\",\n },\n };\n\n return colorNames[hexColor] ?? { korean: \"알수없음\", english: \"Unknown\" };\n}\n\n/**\n * Format stat row with bilingual labels\n *\n * Creates consistent stat row styling for training/combat statistics\n *\n * @param korean - Korean label\n * @param english - English label\n * @param value - Stat value\n * @param valueColor - Hex color for value text\n * @param isMobile - Mobile responsive mode\n * @returns Stat row configuration object for React rendering\n *\n * @example\n * ```tsx\n * const statConfig = formatStatRow('점수', 'Score', 1500, KOREAN_COLORS.ACCENT_GOLD, false);\n * ```\n *\n * @korean 통계행형식화\n */\nexport function formatStatRow(\n korean: string,\n english: string,\n value: string | number,\n valueColor: number,\n isMobile: boolean,\n): {\n korean: string;\n english: string;\n value: string | number;\n valueColor: number;\n labelSize: string;\n subLabelSize: string;\n valueSize: string;\n} {\n const labelSize = isMobile ? \"11px\" : \"12px\";\n const subLabelSize = isMobile ? \"8px\" : \"9px\";\n const valueSize = isMobile ? \"16px\" : \"18px\";\n\n return {\n korean,\n english,\n value,\n valueColor,\n labelSize,\n subLabelSize,\n valueSize,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"koreanThemeHelpers.js","names":[],"sources":["../../src/utils/koreanThemeHelpers.ts"],"sourcesContent":["/**\n * Korean Theme Helper Utilities for HTML Overlays\n *\n * Provides consistent Korean martial arts cyberpunk theming across all HTML overlay components.\n * These utilities ensure color consistency, bilingual text formatting, and responsive spacing.\n *\n * @module utils/koreanThemeHelpers\n * @category UI Utilities\n * @korean 한국테마도우미\n */\n\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { SPACING, BORDER_RADIUS } from \"../types/constants/ui\";\nimport { hexToRgbaString } from \"./colorUtils\";\nimport {\n getNeonGlowEffect,\n getNeonTextShadow,\n getLayeredDepthEffect,\n getCyberpunkGradient,\n getSmoothTransition,\n getKoreanFontOptimization,\n getHoverStateStyles,\n getFocusStateStyles,\n getBackdropBlurEffect,\n getTrigramSymbolGlow as getTrigramGlowEffect,\n combineShadowEffects,\n type GlowIntensity,\n type HoverAnimationType,\n} from \"./visualEffects\";\n\n/**\n * Bilingual text format options\n * @korean 이중언어형식\n */\nexport type BilingualFormat = \"pipe\" | \"parentheses\" | \"bracket\" | \"slash\";\n\n/**\n * Button variant types for Korean theme\n * @korean 버튼변형\n */\nexport type KoreanButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"danger\"\n | \"success\"\n | \"warning\";\n\n/**\n * Responsive spacing size\n * @korean 반응형간격크기\n */\nexport type SpacingSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"xxl\";\n\n/**\n * Enhanced overlay styles configuration\n * @korean 향상된오버레이스타일설정\n */\nexport interface EnhancedOverlayConfig {\n readonly opacity?: number;\n readonly glowIntensity?: GlowIntensity;\n readonly includeGradient?: boolean;\n readonly includeBackdropBlur?: boolean;\n readonly depthLayers?: number;\n}\n\n/**\n * Base styles for all Korean-themed overlays\n *\n * Provides consistent dark background with cyan/gold accents\n *\n * @param opacity - Background opacity (0-1), default 0.9\n * @returns React.CSSProperties object with Korean theme\n *\n * @example\n * ```tsx\n * <div style={getKoreanOverlayBaseStyles(0.95)}>\n * Content\n * </div>\n * ```\n *\n * @korean 한국오버레이기본스타일얻기\n */\nexport function getKoreanOverlayBaseStyles(\n opacity: number = 0.9,\n): React.CSSProperties {\n return {\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, opacity),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: `${BORDER_RADIUS.MD}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n };\n}\n\n/**\n * Enhanced Korean overlay styles with visual effects\n *\n * Provides advanced cyberpunk styling with neon glow, depth effects,\n * gradients, and backdrop blur for Korean-themed overlays.\n *\n * @param config - Enhanced overlay configuration\n * @returns React.CSSProperties with advanced visual effects\n *\n * @example\n * ```tsx\n * const styles = getEnhancedKoreanOverlayStyles({\n * opacity: 0.95,\n * glowIntensity: 'medium',\n * includeGradient: true,\n * includeBackdropBlur: true,\n * depthLayers: 3,\n * });\n * <div style={styles}>Enhanced Content</div>\n * ```\n *\n * @korean 향상된한국오버레이스타일얻기\n */\nexport function getEnhancedKoreanOverlayStyles(\n config: EnhancedOverlayConfig = {},\n): React.CSSProperties {\n const {\n opacity = 0.9,\n glowIntensity = \"medium\",\n includeGradient = false,\n includeBackdropBlur = false,\n depthLayers = 2,\n } = config;\n\n // Base styles\n const baseStyles = getKoreanOverlayBaseStyles(opacity);\n\n // Neon glow effect\n const neonGlow = getNeonGlowEffect(\n KOREAN_COLORS.PRIMARY_CYAN,\n glowIntensity,\n true,\n );\n\n // Depth effect\n const depthShadow = getLayeredDepthEffect({\n layers: depthLayers,\n baseOffset: 2,\n baseBlur: 4,\n color: KOREAN_COLORS.BLACK_SOLID,\n opacity: 0.5,\n });\n\n // Combine shadows\n const boxShadow = combineShadowEffects([neonGlow, depthShadow]);\n\n // Optional gradient background\n let background = baseStyles.backgroundColor;\n if (includeGradient) {\n const gradient = getCyberpunkGradient(\n KOREAN_COLORS.PRIMARY_CYAN,\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n 135,\n );\n background = `${gradient}, ${background}`;\n }\n\n // Optional backdrop blur\n const backdropStyles = includeBackdropBlur\n ? getBackdropBlurEffect(10, 1.5)\n : {};\n\n // Combine all styles\n return {\n ...baseStyles,\n ...backdropStyles,\n background,\n boxShadow,\n transition: getSmoothTransition(\"all\", \"normal\"),\n ...getKoreanFontOptimization(16, \"normal\"),\n };\n}\n\n/**\n * Format bilingual text with Korean and English\n *\n * Supports multiple formatting styles:\n * - pipe: \"한글 | English\"\n * - parentheses: \"한글 (English)\"\n * - bracket: \"한글 [English]\"\n * - slash: \"한글 / English\"\n *\n * @param korean - Korean text\n * @param english - English text\n * @param format - Format style, default \"pipe\"\n * @returns Formatted bilingual string\n *\n * @example\n * ```tsx\n * formatBilingualText('공격', 'Attack', 'pipe') // \"공격 | Attack\"\n * formatBilingualText('방어', 'Defense', 'parentheses') // \"방어 (Defense)\"\n * ```\n *\n * @korean 이중언어텍스트형식화\n */\nexport function formatBilingualText(\n korean: string,\n english: string,\n format: BilingualFormat = \"pipe\",\n): string {\n switch (format) {\n case \"pipe\":\n return `${korean} | ${english}`;\n case \"parentheses\":\n return `${korean} (${english})`;\n case \"bracket\":\n return `${korean} [${english}]`;\n case \"slash\":\n return `${korean} / ${english}`;\n default:\n return `${korean} | ${english}`;\n }\n}\n\n/**\n * Get Korean button styles with variant support\n *\n * Returns consistent button styling based on variant:\n * - primary: Cyan border, gold text\n * - secondary: Gold border, white text\n * - danger: Red border, red text\n * - success: Green border, green text\n * - warning: Orange border, orange text\n *\n * @param variant - Button variant type\n * @param isHovered - Whether button is hovered\n * @param isPressed - Whether button is pressed\n * @returns React.CSSProperties for button\n *\n * @example\n * ```tsx\n * <button style={getKoreanButtonStyles('primary', isHovered, isPressed)}>\n * {formatBilingualText('확인', 'Confirm')}\n * </button>\n * ```\n *\n * @korean 한국버튼스타일얻기\n */\nexport function getKoreanButtonStyles(\n variant: KoreanButtonVariant = \"primary\",\n isHovered: boolean = false,\n isPressed: boolean = false,\n): React.CSSProperties {\n // Variant-specific colors\n const variantColors = {\n primary: {\n border: KOREAN_COLORS.PRIMARY_CYAN,\n text: KOREAN_COLORS.ACCENT_GOLD,\n hoverBg: KOREAN_COLORS.PRIMARY_CYAN,\n },\n secondary: {\n border: KOREAN_COLORS.ACCENT_GOLD,\n text: KOREAN_COLORS.TEXT_PRIMARY,\n hoverBg: KOREAN_COLORS.ACCENT_GOLD,\n },\n danger: {\n border: KOREAN_COLORS.ACCENT_RED,\n text: KOREAN_COLORS.ACCENT_RED,\n hoverBg: KOREAN_COLORS.ACCENT_RED,\n },\n success: {\n border: KOREAN_COLORS.ACCENT_GREEN,\n text: KOREAN_COLORS.ACCENT_GREEN,\n hoverBg: KOREAN_COLORS.ACCENT_GREEN,\n },\n warning: {\n border: KOREAN_COLORS.WARNING_ORANGE,\n text: KOREAN_COLORS.WARNING_ORANGE,\n hoverBg: KOREAN_COLORS.WARNING_ORANGE,\n },\n };\n\n const colors = variantColors[variant];\n\n let backgroundColor = hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.9,\n );\n let borderColor = hexToRgbaString(colors.border, 0.8);\n const textColor = hexToRgbaString(colors.text);\n let boxShadow = \"none\";\n let transform = \"scale(1)\";\n\n if (isPressed) {\n backgroundColor = hexToRgbaString(colors.hoverBg, 0.2);\n transform = \"scale(0.98)\";\n } else if (isHovered) {\n backgroundColor = hexToRgbaString(colors.hoverBg, 0.1);\n borderColor = hexToRgbaString(colors.border, 1.0);\n boxShadow = `0 0 10px ${hexToRgbaString(colors.border, 0.5)}`;\n }\n\n return {\n backgroundColor,\n border: `2px solid ${borderColor}`,\n borderRadius: `${BORDER_RADIUS.SM}px`,\n color: textColor,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n padding: `${SPACING.SM}px ${SPACING.MD}px`,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n boxShadow,\n transform,\n textShadow: `0 2px 4px ${hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.5)}`,\n };\n}\n\n/**\n * Enhanced Korean button configuration\n * @korean 향상된한국버튼설정\n */\nexport interface EnhancedButtonConfig {\n readonly variant?: KoreanButtonVariant;\n readonly isHovered?: boolean;\n readonly isPressed?: boolean;\n readonly isFocused?: boolean;\n readonly glowIntensity?: GlowIntensity;\n readonly hoverAnimation?: HoverAnimationType;\n}\n\n/**\n * Get enhanced Korean button styles with neon glow\n *\n * Provides advanced button styling with cyberpunk neon effects,\n * smooth transitions, and Korean font optimization.\n *\n * @param config - Enhanced button configuration\n * @returns React.CSSProperties with neon glow effects\n *\n * @example\n * ```tsx\n * const buttonStyle = getKoreanButtonWithGlow({\n * variant: 'primary',\n * isHovered: true,\n * glowIntensity: 'strong',\n * hoverAnimation: 'combined',\n * });\n * <button style={buttonStyle}>\n * {formatBilingualText('공격', 'Attack')}\n * </button>\n * ```\n *\n * @korean 네온글로우한국버튼스타일얻기\n */\nexport function getKoreanButtonWithGlow(\n config: EnhancedButtonConfig = {},\n): React.CSSProperties {\n const {\n variant = \"primary\",\n isHovered = false,\n isPressed = false,\n isFocused = false,\n glowIntensity = \"medium\",\n hoverAnimation = \"combined\",\n } = config;\n\n // Get base button styles\n const baseStyles = getKoreanButtonStyles(variant, false, isPressed);\n\n // Variant-specific glow colors\n const glowColors = {\n primary: KOREAN_COLORS.PRIMARY_CYAN,\n secondary: KOREAN_COLORS.ACCENT_GOLD,\n danger: KOREAN_COLORS.ACCENT_RED,\n success: KOREAN_COLORS.ACCENT_GREEN,\n warning: KOREAN_COLORS.WARNING_ORANGE,\n };\n\n const glowColor = glowColors[variant];\n\n // Hover state with visual effects\n let hoverStyles: React.CSSProperties = {};\n if (isHovered) {\n hoverStyles = getHoverStateStyles(glowColor, hoverAnimation, glowIntensity);\n }\n\n // Focus state\n let focusStyles: React.CSSProperties = {};\n if (isFocused) {\n focusStyles = getFocusStateStyles(glowColor, true);\n }\n\n // Extract font size from base styles (getKoreanButtonStyles always returns number | string)\n const baseFontSize =\n typeof baseStyles.fontSize === \"number\"\n ? baseStyles.fontSize\n : parseInt(String(baseStyles.fontSize), 10) || 14;\n\n // Neon text glow for button text\n const textGlow = getNeonTextShadow(\n glowColor,\n isHovered ? \"medium\" : \"subtle\",\n );\n\n // Korean font optimization\n const fontOptimization = getKoreanFontOptimization(baseFontSize, \"bold\");\n\n return {\n ...baseStyles,\n ...fontOptimization,\n ...hoverStyles,\n ...focusStyles,\n textShadow: textGlow,\n transition: getSmoothTransition(\"all\", \"normal\"),\n };\n}\n\n/**\n * Get button visual effects only (no color/background/border)\n *\n * Extracts only visual effects (boxShadow, transition, transform, textShadow) from\n * getKoreanButtonWithGlow without color-related properties. This is useful for cases\n * where buttons need custom colors (e.g., menu selection states) but want to preserve\n * the visual effects system.\n *\n * This function provides an explicit contract for visual effects extraction, preventing\n * fragile coupling that could occur with destructuring patterns. If getKoreanButtonWithGlow\n * adds new visual effect properties in the future, they should be explicitly added here.\n *\n * **Intentionally Excluded Properties:**\n * - Layout: fontSize, fontFamily, fontWeight, padding, width, height, cursor\n * - Typography: letterSpacing, lineHeight, textRendering, WebkitFontSmoothing, MozOsxFontSmoothing\n * - Colors: color, background, backgroundColor, border, borderColor\n *\n * **Included Visual Effects:**\n * - boxShadow: Neon glow effects\n * - transition: Smooth state changes (0.2s ease)\n * - transform: Hover/press animations (scale)\n * - textShadow: Text glow effects\n *\n * **Note:** If getKoreanButtonWithGlow adds new visual effect properties (e.g., 'filter',\n * 'opacity', 'willChange', 'backdropFilter'), they must be explicitly added to this function's\n * return type and extraction logic to maintain the explicit contract.\n *\n * @param config - Same configuration as getKoreanButtonWithGlow\n * @returns React.CSSProperties with only visual effects (boxShadow, transition, transform, textShadow)\n *\n * @example\n * ```tsx\n * const visualEffects = getButtonVisualEffectsOnly({\n * variant: 'primary',\n * glowIntensity: 'strong',\n * hoverAnimation: 'combined'\n * });\n *\n * <button style={{\n * ...visualEffects,\n * color: customColor, // Apply custom colors\n * background: customBg,\n * border: customBorder\n * }}>\n * Custom Button\n * </button>\n * ```\n *\n * @korean 버튼시각효과만얻기\n */\nexport function getButtonVisualEffectsOnly(\n config: Parameters<typeof getKoreanButtonWithGlow>[0],\n): Pick<\n React.CSSProperties,\n \"boxShadow\" | \"transition\" | \"transform\" | \"textShadow\"\n> {\n const fullStyles = getKoreanButtonWithGlow(config);\n\n // Explicitly extract only visual effect properties\n // Note: This explicitly excludes layout, typography, and color properties.\n // If new visual effects (filter, opacity, willChange, etc.) are added to\n // getKoreanButtonWithGlow, they must be added here to maintain the contract.\n return {\n boxShadow: fullStyles.boxShadow,\n transition: fullStyles.transition,\n transform: fullStyles.transform,\n textShadow: fullStyles.textShadow,\n };\n}\n\n/**\n * Get responsive spacing value\n *\n * Returns SPACING constant value for consistent spacing across components.\n * Optionally scales for mobile devices.\n *\n * **IMPORTANT**: This function accepts lowercase size parameters ('xs', 'sm', 'md', etc.)\n * to provide a more intuitive API, then internally converts to uppercase to match\n * SPACING constant keys ('XS', 'SM', 'MD', etc.). This design choice prioritizes\n * developer experience while maintaining compatibility with the SPACING constants.\n *\n * @param size - Spacing size constant ('xs', 'sm', 'md', 'lg', 'xl', 'xxl')\n * @param isMobile - Whether to apply mobile scaling (87.5% for mobile devices)\n * @returns Spacing value in pixels\n *\n * @example\n * ```tsx\n * const padding = getResponsiveSpacing('md', isMobile); // 16px desktop, 14px mobile\n * <div style={{ padding: `${padding}px` }}>Content</div>\n * ```\n *\n * @korean 반응형간격얻기\n */\nexport function getResponsiveSpacing(\n size: SpacingSize,\n isMobile: boolean = false,\n): number {\n // Convert lowercase API parameter to uppercase SPACING constant key\n // This provides a more ergonomic API while maintaining internal consistency\n const spacingKey = size.toUpperCase() as keyof typeof SPACING;\n const spacingValue = SPACING[spacingKey];\n const mobileScale = 0.875; // 87.5% for mobile\n\n // Runtime validation: While TypeScript prevents invalid sizes at compile time,\n // this check provides safety for JavaScript consumers and edge cases where\n // type assertions bypass TypeScript checks (e.g., 'as any', dynamic values)\n if (spacingValue === undefined) {\n const fallback = SPACING.MD;\n console.warn(\n `[koreanThemeHelpers:getResponsiveSpacing] Invalid spacing size \"${String(\n size,\n )}\" provided. Falling back to \"MD\".`,\n );\n return isMobile ? Math.round(fallback * mobileScale) : fallback;\n }\n\n return isMobile ? Math.round(spacingValue * mobileScale) : spacingValue;\n}\n\n/**\n * Get trigram symbol by name\n *\n * Returns Unicode trigram symbol for visual embellishment\n *\n * @param name - Trigram name in Korean\n * @returns Unicode trigram symbol\n *\n * @example\n * ```tsx\n * <div>{getTrigramSymbol('건')} 건 (Heaven)</div>\n * ```\n *\n * @korean 팔괘기호얻기\n */\nexport function getTrigramSymbol(\n name: \"건\" | \"태\" | \"리\" | \"진\" | \"손\" | \"감\" | \"간\" | \"곤\",\n): string {\n const symbols = {\n 건: \"☰\", // Heaven - 乾\n 태: \"☱\", // Lake - 兌\n 리: \"☲\", // Fire - 離\n 진: \"☳\", // Thunder - 震\n 손: \"☴\", // Wind - 巽\n 감: \"☵\", // Water - 坎\n 간: \"☶\", // Mountain - 艮\n 곤: \"☷\", // Earth - 坤\n };\n return symbols[name];\n}\n\n/**\n * Get trigram symbol styles with glow effect\n *\n * Enhances trigram symbols (☰☱☲☳☴☵☶☷) with cyberpunk neon glow\n * based on stance-specific colors and active state.\n *\n * @param config - Configuration object\n * @param config.stance - Trigram stance identifier (\"geon\", \"tae\", etc.)\n * @param config.isActive - Whether the trigram stance is currently active\n * @param config.size - Font size in pixels (optional, defaults based on active state)\n * @returns React.CSSProperties with trigram-specific glow\n *\n * @example\n * ```tsx\n * const trigramStyle = getTrigramSymbolWithGlow({ stance: 'geon', isActive: true });\n * <div style={trigramStyle}>\n * ☰ 건 | Geon\n * </div>\n * ```\n *\n * @korean 팔괘기호네온글로우스타일얻기\n */\nexport function getTrigramSymbolWithGlow(config: {\n readonly stance:\n | \"geon\"\n | \"tae\"\n | \"li\"\n | \"jin\"\n | \"son\"\n | \"gam\"\n | \"gan\"\n | \"gon\";\n readonly isActive?: boolean;\n readonly size?: number;\n}): React.CSSProperties & {\n WebkitUserSelect?: string;\n} {\n const { stance, isActive = false, size } = config;\n\n // Map English stance names to Korean\n const stanceToKorean = {\n geon: \"건\",\n tae: \"태\",\n li: \"리\",\n jin: \"진\",\n son: \"손\",\n gam: \"감\",\n gan: \"간\",\n gon: \"곤\",\n };\n\n const koreanName = stanceToKorean[stance];\n\n // Trigram-specific colors matching KOREAN_COLORS\n // Note: Comments reflect actual hex color values\n // TODO: Fix source constants (colors.ts lines 84, 87) - TRIGRAM_GEON_PRIMARY should say \"Gold\" not \"White\",\n // TRIGRAM_JIN_PRIMARY should say \"Medium Purple\" not \"Yellow\"\n const trigramColors: Record<string, number> = {\n 건: KOREAN_COLORS.TRIGRAM_GEON_PRIMARY, // Heaven - Gold (0xffd700)\n 태: KOREAN_COLORS.TRIGRAM_TAE_PRIMARY, // Lake - Sky Blue\n 리: KOREAN_COLORS.TRIGRAM_LI_PRIMARY, // Fire - Orange Red\n 진: KOREAN_COLORS.TRIGRAM_JIN_PRIMARY, // Thunder - Medium Purple (0x9370db)\n 손: KOREAN_COLORS.TRIGRAM_SON_PRIMARY, // Wind - Light Green\n 감: KOREAN_COLORS.TRIGRAM_GAM_PRIMARY, // Water - Blue\n 간: KOREAN_COLORS.TRIGRAM_GAN_PRIMARY, // Mountain - Brown\n 곤: KOREAN_COLORS.TRIGRAM_GON_PRIMARY, // Earth - Dark Gray\n };\n\n const trigramColor = trigramColors[koreanName];\n\n // Get glow effect from visualEffects\n const glowStyles = getTrigramGlowEffect(trigramColor, isActive);\n\n // Korean font optimization for trigram symbols\n const fontSize = size ?? (isActive ? 32 : 28);\n const fontStyles = getKoreanFontOptimization(fontSize, \"bold\");\n\n return {\n ...fontStyles,\n ...glowStyles,\n fontFamily: FONT_FAMILY.KOREAN,\n display: \"inline-block\",\n userSelect: \"none\",\n WebkitUserSelect: \"none\",\n };\n}\n\n/**\n * Get Korean color name for documentation\n *\n * Maps hex color to Korean color name for better documentation\n *\n * @param hexColor - Hex color value from KOREAN_COLORS\n * @returns Korean name and English translation\n *\n * @internal Used primarily for JSDoc documentation\n * @korean 한국색상이름얻기\n */\nexport function getKoreanColorName(hexColor: number): {\n korean: string;\n english: string;\n} {\n const colorNames: Record<number, { korean: string; english: string }> = {\n [KOREAN_COLORS.PRIMARY_CYAN]: { korean: \"청록\", english: \"Cyan\" },\n [KOREAN_COLORS.ACCENT_GOLD]: { korean: \"금색\", english: \"Gold\" },\n [KOREAN_COLORS.ACCENT_RED]: { korean: \"빨강\", english: \"Red\" },\n [KOREAN_COLORS.ACCENT_GREEN]: { korean: \"초록\", english: \"Green\" },\n [KOREAN_COLORS.WARNING_ORANGE]: { korean: \"주황\", english: \"Orange\" },\n [KOREAN_COLORS.TEXT_PRIMARY]: { korean: \"흰색\", english: \"White\" },\n [KOREAN_COLORS.UI_BACKGROUND_DARK]: {\n korean: \"어두운배경\",\n english: \"Dark Background\",\n },\n };\n\n return colorNames[hexColor] ?? { korean: \"알수없음\", english: \"Unknown\" };\n}\n\n/**\n * Format stat row with bilingual labels\n *\n * Creates consistent stat row styling for training/combat statistics\n *\n * @param korean - Korean label\n * @param english - English label\n * @param value - Stat value\n * @param valueColor - Hex color for value text\n * @param isMobile - Mobile responsive mode\n * @returns Stat row configuration object for React rendering\n *\n * @example\n * ```tsx\n * const statConfig = formatStatRow('점수', 'Score', 1500, KOREAN_COLORS.ACCENT_GOLD, false);\n * ```\n *\n * @korean 통계행형식화\n */\nexport function formatStatRow(\n korean: string,\n english: string,\n value: string | number,\n valueColor: number,\n isMobile: boolean,\n): {\n korean: string;\n english: string;\n value: string | number;\n valueColor: number;\n labelSize: string;\n subLabelSize: string;\n valueSize: string;\n} {\n const labelSize = isMobile ? \"11px\" : \"12px\";\n const subLabelSize = isMobile ? \"8px\" : \"9px\";\n const valueSize = isMobile ? \"16px\" : \"18px\";\n\n return {\n korean,\n english,\n value,\n valueColor,\n labelSize,\n subLabelSize,\n valueSize,\n };\n}\n"],"mappings":"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"}
|
package/lib/utils/math.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"math.js","names":[],"sources":["../../src/utils/math.ts"],"sourcesContent":["/**\n * Mathematical utility functions for Black Trigram\n * \n * Provides shared mathematical operations used across combat, training,\n * and physics systems to ensure consistency and follow DRY principles.\n * \n * **Korean**: 수학 유틸리티 함수\n * \n * @module utils/math\n * @korean 수학유틸리티\n */\n\n/**\n * Calculate 3D Euclidean distance between two positions\n * \n * Uses the standard 3D distance formula: √(dx² + dy² + dz²)\n * This is used throughout the combat and training systems to calculate\n * distance between combatants, ensuring consistent distance calculations.\n * \n * **Korean**: 3D 유클리드 거리 계산\n * \n * @param pos1 - First position as [x, y, z] tuple\n * @param pos2 - Second position as [x, y, z] tuple\n * @returns The 3D Euclidean distance in meters\n * \n * @example\n * const distance = calculateDistance3D([0, 0, 0], [3, 4, 0]);\n * // Returns: 5.0 (3-4-5 triangle)\n * \n * @public\n * @category Math Utilities\n * @korean 3D거리계산\n */\nexport function calculateDistance3D(\n pos1: [number, number, number],\n pos2: [number, number, number]\n): number {\n const dx = pos1[0] - pos2[0];\n const dy = pos1[1] - pos2[1];\n const dz = pos1[2] - pos2[2];\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n}\n\n/**\n * Calculate squared 3D distance between two positions\n * \n * Optimized version that avoids the expensive square root operation.\n * Useful when comparing distances (A > B) where the square root is unnecessary.\n * \n * **Korean**: 3D 거리 제곱 계산\n * \n * @param pos1 - First position as [x, y, z] tuple\n * @param pos2 - Second position as [x, y, z] tuple\n * @returns The squared 3D distance in meters²\n * \n * @example\n * const distSq = calculateDistance3DSquared([0, 0, 0], [3, 4, 0]);\n * // Returns: 25.0\n * \n * @public\n * @category Math Utilities\n * @korean 3D거리제곱계산\n */\nexport function calculateDistance3DSquared(\n pos1: [number, number, number],\n pos2: [number, number, number]\n): number {\n const dx = pos1[0] - pos2[0];\n const dy = pos1[1] - pos2[1];\n const dz = pos1[2] - pos2[2];\n return dx * dx + dy * dy + dz * dz;\n}\n\n/**\n * Convert degrees to radians\n * \n * Used throughout animation systems for bone rotations where angles are\n * specified in degrees for readability but need to be converted to radians\n * for Three.js rendering.\n * \n * **Korean**: 각도를 라디안으로 변환\n * \n * @param degrees - Angle in degrees (0-360)\n * @returns Angle in radians (0-2π)\n * \n * @example\n * const rightAngle = toRadians(90);\n * // Returns: approximately 1.5708 (π/2)\n * \n * @example\n * const straightAngle = toRadians(180);\n * // Returns: approximately 3.1416 (π)\n * \n * @public\n * @category Math Utilities\n * @korean 각도변환\n */\nexport function toRadians(degrees: number): number {\n return degrees * (Math.PI / 180);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,oBACd,MACA,MACQ;CACR,MAAM,KAAK,KAAK,KAAK,KAAK;CAC1B,MAAM,KAAK,KAAK,KAAK,KAAK;CAC1B,MAAM,KAAK,KAAK,KAAK,KAAK;
|
|
1
|
+
{"version":3,"file":"math.js","names":[],"sources":["../../src/utils/math.ts"],"sourcesContent":["/**\n * Mathematical utility functions for Black Trigram\n * \n * Provides shared mathematical operations used across combat, training,\n * and physics systems to ensure consistency and follow DRY principles.\n * \n * **Korean**: 수학 유틸리티 함수\n * \n * @module utils/math\n * @korean 수학유틸리티\n */\n\n/**\n * Calculate 3D Euclidean distance between two positions\n * \n * Uses the standard 3D distance formula: √(dx² + dy² + dz²)\n * This is used throughout the combat and training systems to calculate\n * distance between combatants, ensuring consistent distance calculations.\n * \n * **Korean**: 3D 유클리드 거리 계산\n * \n * @param pos1 - First position as [x, y, z] tuple\n * @param pos2 - Second position as [x, y, z] tuple\n * @returns The 3D Euclidean distance in meters\n * \n * @example\n * const distance = calculateDistance3D([0, 0, 0], [3, 4, 0]);\n * // Returns: 5.0 (3-4-5 triangle)\n * \n * @public\n * @category Math Utilities\n * @korean 3D거리계산\n */\nexport function calculateDistance3D(\n pos1: [number, number, number],\n pos2: [number, number, number]\n): number {\n const dx = pos1[0] - pos2[0];\n const dy = pos1[1] - pos2[1];\n const dz = pos1[2] - pos2[2];\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n}\n\n/**\n * Calculate squared 3D distance between two positions\n * \n * Optimized version that avoids the expensive square root operation.\n * Useful when comparing distances (A > B) where the square root is unnecessary.\n * \n * **Korean**: 3D 거리 제곱 계산\n * \n * @param pos1 - First position as [x, y, z] tuple\n * @param pos2 - Second position as [x, y, z] tuple\n * @returns The squared 3D distance in meters²\n * \n * @example\n * const distSq = calculateDistance3DSquared([0, 0, 0], [3, 4, 0]);\n * // Returns: 25.0\n * \n * @public\n * @category Math Utilities\n * @korean 3D거리제곱계산\n */\nexport function calculateDistance3DSquared(\n pos1: [number, number, number],\n pos2: [number, number, number]\n): number {\n const dx = pos1[0] - pos2[0];\n const dy = pos1[1] - pos2[1];\n const dz = pos1[2] - pos2[2];\n return dx * dx + dy * dy + dz * dz;\n}\n\n/**\n * Convert degrees to radians\n * \n * Used throughout animation systems for bone rotations where angles are\n * specified in degrees for readability but need to be converted to radians\n * for Three.js rendering.\n * \n * **Korean**: 각도를 라디안으로 변환\n * \n * @param degrees - Angle in degrees (0-360)\n * @returns Angle in radians (0-2π)\n * \n * @example\n * const rightAngle = toRadians(90);\n * // Returns: approximately 1.5708 (π/2)\n * \n * @example\n * const straightAngle = toRadians(180);\n * // Returns: approximately 3.1416 (π)\n * \n * @public\n * @category Math Utilities\n * @korean 각도변환\n */\nexport function toRadians(degrees: number): number {\n return degrees * (Math.PI / 180);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,oBACd,MACA,MACQ;CACR,MAAM,KAAK,KAAK,KAAK,KAAK;CAC1B,MAAM,KAAK,KAAK,KAAK,KAAK;CAC1B,MAAM,KAAK,KAAK,KAAK,KAAK;CAC1B,OAAO,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AAyD/C,SAAgB,UAAU,SAAyB;CACjD,OAAO,WAAW,KAAK,KAAK"}
|