blacktrigram 0.7.39 → 0.7.40
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 +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HitEffects3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/HitEffects3D.tsx"],"sourcesContent":["/**\n * HitEffects3D - Three.js particle effects for combat\n *\n * Maintains Korean theming and visual feedback for combat actions\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { HitEffect } from \"../../../../systems\";\nimport { HitEffectType } from \"../../../../systems/effects\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\n\n/**\n * Props for the HitEffects3D component.\n * Controls which effects are displayed and callbacks for effect lifecycle.\n */\nexport interface HitEffects3DProps {\n /** Array of active hit effects to render in the scene */\n readonly effects: HitEffect[];\n /** Callback invoked when an effect completes its duration */\n readonly onEffectComplete?: (effectId: string) => void;\n /** Arena bounds for accurate coordinate conversion (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n}\n\ninterface ActiveEffect extends HitEffect {\n progress: number;\n}\n\n/**\n * Individual Hit Effect Component\n * Renders a single effect with Three.js primitives\n */\nconst HitEffectVisual: React.FC<{\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n arenaBounds?: PhysicsArenaBounds;\n}> = ({ effect, effectRef, arenaBounds }) => {\n const groupRef = useRef<THREE.Group>(null);\n // Use ref for alpha to avoid setState in useFrame (eliminates 60 rerenders/sec)\n const alphaRef = useRef(1);\n\n // Position in 3D space - use meter coordinates directly\n const position3D: [number, number, number] = useMemo(() => {\n if (!effect.position) return [0, 1, 0];\n\n // Use arena bounds if available, otherwise use default values for 10m arena\n const bounds = arenaBounds ?? DEFAULT_PHYSICS_ARENA_BOUNDS;\n \n // Position is in meters relative to arena center (0, 0)\n // Player models use meter coordinates directly: position={[playerPos.x, 0, playerPos.y]}\n // So we use meter coordinates directly too for alignment\n const halfWidth = bounds.worldWidthMeters / 2;\n const halfDepth = bounds.worldDepthMeters / 2;\n \n // Clamp position to arena boundaries in meters\n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, effect.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, effect.position.y));\n \n // Use clamped meter coordinates directly in 3D space (no remapping)\n const x = clampedX; // Meter position X\n const y = 1.5; // Mid-height for effects\n const z = clampedZ; // Meter position Z (depth)\n\n return [x, y, z];\n }, [effect.position, arenaBounds]);\n\n // Animate effect based on type - Update materials directly in the group\n useFrame(() => {\n if (!groupRef.current || !effectRef.current) return;\n\n // Access fresh progress value from ref and update alpha ref (no setState!)\n const progress = effectRef.current.progress;\n alphaRef.current = 1 - progress;\n\n // Update all material opacities and positions in the group hierarchy\n let sparkIndex = 0;\n groupRef.current.traverse((object) => {\n if (object instanceof THREE.Mesh && object.material instanceof THREE.MeshBasicMaterial) {\n const baseOpacity = object.material.userData.baseOpacity ?? 1;\n object.material.opacity = alphaRef.current * baseOpacity;\n \n // For BLOCK effect, animate spark particle positions\n if (effect.type === HitEffectType.BLOCK && object.geometry instanceof THREE.SphereGeometry) {\n if (object.geometry.parameters?.radius === 0.05) { // Spark particles have radius 0.05\n const i = sparkIndex++;\n if (i < 3) { // Only update the 3 spark particles\n object.position.y = Math.sin((1 - alphaRef.current) * Math.PI) * 0.3;\n }\n }\n }\n }\n });\n\n // Rotate for some effects\n if (\n effect.type === HitEffectType.COUNTER ||\n effect.type === HitEffectType.VITAL_POINT_STRIKE\n ) {\n groupRef.current.rotation.y += 0.1;\n }\n\n // Scale pulse for critical hits\n if (effect.type === HitEffectType.CRITICAL_HIT) {\n const pulse =\n 1 + Math.sin(effectRef.current.progress * Math.PI * 4) * 0.2;\n groupRef.current.scale.set(pulse, pulse, pulse);\n }\n });\n\n // Render based on effect type\n switch (effect.type) {\n case HitEffectType.HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Impact flash sphere */}\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Expanding ring */}\n <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry\n args={[0.3 * effect.intensity, 0.35 * effect.intensity, 32]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.CRITICAL_HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Large impact sphere */}\n <mesh>\n <sphereGeometry args={[0.5 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.7}\n userData={{ baseOpacity: 0.7 }}\n />\n </mesh>\n {/* Star burst lines */}\n {[0, 1, 2, 3].map((i) => {\n const angle = (i * Math.PI) / 2;\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.3, 0, Math.sin(angle) * 0.3]}\n rotation={[0, angle, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.BLOCK:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Shield arc */}\n <mesh rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry\n args={[0.4 * effect.intensity, 0.05, 8, 16, Math.PI]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Spark particles - positions will be updated in useFrame */}\n {[0, 1, 2].map((i) => (\n <mesh\n key={i}\n position={[\n (i - 1) * 0.2,\n 0,\n 0,\n ]}\n >\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.MISS:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Swish trail lines */}\n {[0, 1].map((i) => (\n <mesh\n key={i}\n position={[(i - 0.5) * 0.2, i * 0.1, 0]}\n rotation={[0, 0, (i - 0.5) * 0.3]}\n >\n <boxGeometry args={[0.6, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.TEXT_TERTIARY}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.VITAL_POINT_STRIKE:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Pulsing sphere */}\n <mesh>\n <sphereGeometry args={[0.35 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Concentric rings */}\n {[0.2, 0.3, 0.4].map((radius, i) => (\n <mesh key={i} rotation={[-Math.PI / 2, 0, 0]}>\n <ringGeometry args={[radius, radius + 0.02, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n {/* Crosshair */}\n <mesh position={[0, 0, 0]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n <mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.PARRY:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Deflection arc */}\n <mesh>\n <torusGeometry\n args={[0.35 * effect.intensity, 0.05, 8, 16, Math.PI / 2]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Sparks */}\n {[0, 1, 2].map((i) => {\n const angle = (Math.PI / 6) * (i - 1);\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.4, Math.sin(angle) * 0.4, 0]}\n >\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.COUNTER:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Spinning energy blades */}\n {[0, 1, 2, 3].map((i) => (\n <mesh\n key={i}\n rotation={[0, (i * Math.PI) / 2, 0]}\n position={[0, 0, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.GENERAL_DAMAGE:\n case HitEffectType.STATUS_EFFECT:\n default:\n return (\n <group ref={groupRef} position={position3D}>\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GREEN}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n </group>\n );\n }\n};\n\n/**\n * HitEffects3D Component\n * Manages all active hit effects in the combat scene\n * Uses refs to avoid triggering React re-renders at 60fps\n */\nexport const HitEffects3D: React.FC<HitEffects3DProps> = ({\n effects,\n onEffectComplete,\n arenaBounds,\n}) => {\n // Use refs to track effects without causing re-renders\n const effectRefsMap = useRef<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n const completedEffectsRef = useRef<Set<string>>(new Set());\n\n // Store effectRefs as state to avoid ref access during render\n // This is updated in useEffect, not during render\n const [effectRefsSnapshot, setEffectRefsSnapshot] = useState<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n\n // Update effect refs when effects change (minimal state updates)\n useEffect(() => {\n // Clean up refs for removed effects\n const currentIdSet = new Set(effects.map((e) => e.id));\n effectRefsMap.current.forEach((_ref, id) => {\n if (!currentIdSet.has(id)) {\n effectRefsMap.current.delete(id);\n completedEffectsRef.current.delete(id);\n }\n });\n\n // Initialize refs for new effects\n effects.forEach((effect) => {\n if (!effectRefsMap.current.has(effect.id)) {\n effectRefsMap.current.set(effect.id, {\n current: { ...effect, progress: 0 },\n });\n }\n });\n\n // Create a snapshot of the refs for use in render\n // This is a new Map with the same refs (not a deep copy)\n setEffectRefsSnapshot(new Map(effectRefsMap.current));\n }, [effects]);\n\n // Update progress using refs (no setState in useFrame)\n useFrame(() => {\n const now = Date.now();\n\n effectRefsMap.current.forEach((ref, id) => {\n if (!ref.current) return;\n\n const progress = Math.min(\n (now - ref.current.startTime) / ref.current.duration,\n 1\n );\n ref.current.progress = progress;\n\n // Handle completion\n const isExpired = progress >= 1;\n if (\n isExpired &&\n onEffectComplete &&\n !completedEffectsRef.current.has(id)\n ) {\n completedEffectsRef.current.add(id);\n onEffectComplete(id);\n }\n });\n });\n\n // Pre-compute effect data to avoid ref access during render\n const effectsToRender = useMemo(() => {\n return effects\n .map((effect) => {\n const effectRef = effectRefsSnapshot.get(effect.id);\n return { effect, effectRef };\n })\n .filter(\n (\n item\n ): item is {\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n } => item.effectRef !== undefined\n );\n }, [effects, effectRefsSnapshot]);\n\n return (\n <group>\n {effectsToRender.map(({ effect, effectRef }) => {\n return (\n <HitEffectVisual\n key={effect.id}\n effect={effect}\n effectRef={effectRef}\n arenaBounds={arenaBounds}\n />\n );\n })}\n </group>\n );\n};\n\nexport default HitEffects3D;\n"],"mappings":";;;;;;;;;;;;;;;;;AAmCA,IAAM,mBAIA,EAAE,QAAQ,WAAW,kBAAkB;CAC3C,MAAM,WAAW,OAAoB,KAAK;CAE1C,MAAM,WAAW,OAAO,EAAE;CAG1B,MAAM,aAAuC,cAAc;AACzD,MAAI,CAAC,OAAO,SAAU,QAAO;GAAC;GAAG;GAAG;GAAE;EAGtC,MAAM,SAAS,eAAe;EAK9B,MAAM,YAAY,OAAO,mBAAmB;EAC5C,MAAM,YAAY,OAAO,mBAAmB;AAW5C,SAAO;GARU,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,OAAO,SAAS,EAAE,CAQpE;GAAG;GAPM,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,OAAO,SAAS,EAAE,CAO9D;GAAE;IACf,CAAC,OAAO,UAAU,YAAY,CAAC;AAGlC,gBAAe;AACb,MAAI,CAAC,SAAS,WAAW,CAAC,UAAU,QAAS;AAI7C,WAAS,UAAU,IADF,UAAU,QAAQ;EAInC,IAAI,aAAa;AACjB,WAAS,QAAQ,UAAU,WAAW;AACpC,OAAI,kBAAkB,MAAM,QAAQ,OAAO,oBAAoB,MAAM,mBAAmB;IACtF,MAAM,cAAc,OAAO,SAAS,SAAS,eAAe;AAC5D,WAAO,SAAS,UAAU,SAAS,UAAU;AAG7C,QAAI,OAAO,SAAS,cAAc,SAAS,OAAO,oBAAoB,MAAM;SACtE,OAAO,SAAS,YAAY,WAAW;UAErC,eAAI,EACN,QAAO,SAAS,IAAI,KAAK,KAAK,IAAI,SAAS,WAAW,KAAK,GAAG,GAAG;;;;IAKzE;AAGF,MACE,OAAO,SAAS,cAAc,WAC9B,OAAO,SAAS,cAAc,mBAE9B,UAAS,QAAQ,SAAS,KAAK;AAIjC,MAAI,OAAO,SAAS,cAAc,cAAc;GAC9C,MAAM,QACJ,IAAI,KAAK,IAAI,UAAU,QAAQ,WAAW,KAAK,KAAK,EAAE,GAAG;AAC3D,YAAS,QAAQ,MAAM,IAAI,OAAO,OAAO,MAAM;;GAEjD;AAGF,SAAQ,OAAO,MAAf;EACE,KAAK,cAAc,IACjB,QACE,qBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAAhC,CAEE,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,kBAAD,EAAgB,MAAM;IAAC,KAAM,OAAO;IAAW;IAAI;IAAG,EAAI,CAAA,EAC1D,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,UAAU,EAAE,aAAa,IAAK;IAC9B,CAAA,CACG,EAAA,CAAA,EAEP,qBAAC,QAAD;IAAM,UAAU;KAAC,CAAC,KAAK,KAAK;KAAG;KAAG;KAAE;IAAE,UAAU;KAAC;KAAG;KAAG;KAAE;cAAzD,CACE,oBAAC,gBAAD,EACE,MAAM;KAAC,KAAM,OAAO;KAAW,MAAO,OAAO;KAAW;KAAG,EAC3D,CAAA,EACF,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,MAAM,MAAM;KACZ,UAAU,EAAE,aAAa,GAAK;KAC9B,CAAA,CACG;MACD;;EAGZ,KAAK,cAAc,aACjB,QACE,qBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAAhC,CAEE,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,kBAAD,EAAgB,MAAM;IAAC,KAAM,OAAO;IAAW;IAAI;IAAG,EAAI,CAAA,EAC1D,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,UAAU,EAAE,aAAa,IAAK;IAC9B,CAAA,CACG,EAAA,CAAA,EAEN;IAAC;IAAG;IAAG;IAAG;IAAE,CAAC,KAAK,MAAM;IACvB,MAAM,QAAS,IAAI,KAAK,KAAM;AAC9B,WACE,qBAAC,QAAD;KAEE,UAAU;MAAC,KAAK,IAAI,MAAM,GAAG;MAAK;MAAG,KAAK,IAAI,MAAM,GAAG;MAAI;KAC3D,UAAU;MAAC;MAAG;MAAO;MAAE;eAHzB,CAKE,oBAAC,eAAD,EAAa,MAAM;MAAC;MAAK;MAAM;MAAK,EAAI,CAAA,EACxC,oBAAC,qBAAD;MACE,OAAO,cAAc;MACrB,aAAA;MACA,SAAS;MACT,UAAU,EAAE,aAAa,GAAK;MAC9B,CAAA,CACG;OAXA,EAWA;KAET,CACI;;EAGZ,KAAK,cAAc,MACjB,QACE,qBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAAhC,CAEE,qBAAC,QAAD;IAAM,UAAU;KAAC;KAAG;KAAG,KAAK,KAAK;KAAE;cAAnC,CACE,oBAAC,iBAAD,EACE,MAAM;KAAC,KAAM,OAAO;KAAW;KAAM;KAAG;KAAI,KAAK;KAAG,EACpD,CAAA,EACF,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,UAAU,EAAE,aAAa,GAAK;KAC9B,CAAA,CACG;OAEN;IAAC;IAAG;IAAG;IAAE,CAAC,KAAK,MACd,qBAAC,QAAD;IAEE,UAAU;MACP,IAAI,KAAK;KACV;KACA;KACD;cANH,CAQE,oBAAC,kBAAD,EAAgB,MAAM;KAAC;KAAM;KAAG;KAAE,EAAI,CAAA,EACtC,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,UAAU,EAAE,aAAa,IAAK;KAC9B,CAAA,CACG;MAdA,EAcA,CACP,CACI;;EAGZ,KAAK,cAAc,KACjB,QACE,oBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAE7B,CAAC,GAAG,EAAE,CAAC,KAAK,MACX,qBAAC,QAAD;IAEE,UAAU;MAAE,IAAI,MAAO;KAAK,IAAI;KAAK;KAAE;IACvC,UAAU;KAAC;KAAG;MAAI,IAAI,MAAO;KAAI;cAHnC,CAKE,oBAAC,eAAD,EAAa,MAAM;KAAC;KAAK;KAAM;KAAK,EAAI,CAAA,EACxC,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,UAAU,EAAE,aAAa,GAAK;KAC9B,CAAA,CACG;MAXA,EAWA,CACP;GACI,CAAA;EAGZ,KAAK,cAAc,mBACjB,QACE,qBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAAhC;IAEE,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,kBAAD,EAAgB,MAAM;KAAC,MAAO,OAAO;KAAW;KAAI;KAAG,EAAI,CAAA,EAC3D,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,UAAU,EAAE,aAAa,IAAK;KAC9B,CAAA,CACG,EAAA,CAAA;IAEN;KAAC;KAAK;KAAK;KAAI,CAAC,KAAK,QAAQ,MAC5B,qBAAC,QAAD;KAAc,UAAU;MAAC,CAAC,KAAK,KAAK;MAAG;MAAG;MAAE;eAA5C,CACE,oBAAC,gBAAD,EAAc,MAAM;MAAC;MAAQ,SAAS;MAAM;MAAG,EAAI,CAAA,EACnD,oBAAC,qBAAD;MACE,OAAO,cAAc;MACrB,aAAA;MACA,SAAS;MACT,MAAM,MAAM;MACZ,UAAU,EAAE,aAAa,GAAK;MAC9B,CAAA,CACG;OATI,EASJ,CACP;IAEF,qBAAC,QAAD;KAAM,UAAU;MAAC;MAAG;MAAG;MAAE;eAAzB,CACE,oBAAC,eAAD,EAAa,MAAM;MAAC;MAAK;MAAM;MAAK,EAAI,CAAA,EACxC,oBAAC,qBAAD;MACE,OAAO,cAAc;MACrB,aAAA;MACA,SAAS;MACT,UAAU,EAAE,aAAa,IAAK;MAC9B,CAAA,CACG;;IACP,qBAAC,QAAD;KAAM,UAAU;MAAC;MAAG;MAAG;MAAE;KAAE,UAAU;MAAC;MAAG;MAAG,KAAK,KAAK;MAAE;eAAxD,CACE,oBAAC,eAAD,EAAa,MAAM;MAAC;MAAK;MAAM;MAAK,EAAI,CAAA,EACxC,oBAAC,qBAAD;MACE,OAAO,cAAc;MACrB,aAAA;MACA,SAAS;MACT,UAAU,EAAE,aAAa,IAAK;MAC9B,CAAA,CACG;;IACD;;EAGZ,KAAK,cAAc,MACjB,QACE,qBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAAhC,CAEE,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,iBAAD,EACE,MAAM;IAAC,MAAO,OAAO;IAAW;IAAM;IAAG;IAAI,KAAK,KAAK;IAAE,EACzD,CAAA,EACF,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,UAAU,EAAE,aAAa,GAAK;IAC9B,CAAA,CACG,EAAA,CAAA,EAEN;IAAC;IAAG;IAAG;IAAE,CAAC,KAAK,MAAM;IACpB,MAAM,QAAS,KAAK,KAAK,KAAM,IAAI;AACnC,WACE,qBAAC,QAAD;KAEE,UAAU;MAAC,KAAK,IAAI,MAAM,GAAG;MAAK,KAAK,IAAI,MAAM,GAAG;MAAK;MAAE;eAF7D,CAIE,oBAAC,kBAAD,EAAgB,MAAM;MAAC;MAAM;MAAG;MAAE,EAAI,CAAA,EACtC,oBAAC,qBAAD;MACE,OAAO,cAAc;MACrB,aAAA;MACA,SAAS;MACT,UAAU,EAAE,aAAa,IAAK;MAC9B,CAAA,CACG;OAVA,EAUA;KAET,CACI;;EAGZ,KAAK,cAAc,QACjB,QACE,oBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAE7B;IAAC;IAAG;IAAG;IAAG;IAAE,CAAC,KAAK,MACjB,qBAAC,QAAD;IAEE,UAAU;KAAC;KAAI,IAAI,KAAK,KAAM;KAAG;KAAE;IACnC,UAAU;KAAC;KAAG;KAAG;KAAE;cAHrB,CAKE,oBAAC,eAAD,EAAa,MAAM;KAAC;KAAK;KAAM;KAAK,EAAI,CAAA,EACxC,oBAAC,qBAAD;KACE,OAAO,cAAc;KACrB,aAAA;KACA,SAAS;KACT,UAAU,EAAE,aAAa,GAAK;KAC9B,CAAA,CACG;MAXA,EAWA,CACP;GACI,CAAA;EAGZ,KAAK,cAAc;EACnB,KAAK,cAAc;EACnB,QACE,QACE,oBAAC,SAAD;GAAO,KAAK;GAAU,UAAU;aAC9B,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,kBAAD,EAAgB,MAAM;IAAC,KAAM,OAAO;IAAW;IAAI;IAAG,EAAI,CAAA,EAC1D,oBAAC,qBAAD;IACE,OAAO,cAAc;IACrB,aAAA;IACA,SAAS;IACT,UAAU,EAAE,aAAa,IAAK;IAC9B,CAAA,CACG,EAAA,CAAA;GACD,CAAA;;;;;;;;AAUhB,IAAa,gBAA6C,EACxD,SACA,kBACA,kBACI;CAEJ,MAAM,gBAAgB,uBAEpB,IAAI,KAAK,CAAC;CACZ,MAAM,sBAAsB,uBAAoB,IAAI,KAAK,CAAC;CAI1D,MAAM,CAAC,oBAAoB,yBAAyB,yBAElD,IAAI,KAAK,CAAC;AAGZ,iBAAgB;EAEd,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,GAAG,CAAC;AACtD,gBAAc,QAAQ,SAAS,MAAM,OAAO;AAC1C,OAAI,CAAC,aAAa,IAAI,GAAG,EAAE;AACzB,kBAAc,QAAQ,OAAO,GAAG;AAChC,wBAAoB,QAAQ,OAAO,GAAG;;IAExC;AAGF,UAAQ,SAAS,WAAW;AAC1B,OAAI,CAAC,cAAc,QAAQ,IAAI,OAAO,GAAG,CACvC,eAAc,QAAQ,IAAI,OAAO,IAAI,EACnC,SAAS;IAAE,GAAG;IAAQ,UAAU;IAAG,EACpC,CAAC;IAEJ;AAIF,wBAAsB,IAAI,IAAI,cAAc,QAAQ,CAAC;IACpD,CAAC,QAAQ,CAAC;AAGb,gBAAe;EACb,MAAM,MAAM,KAAK,KAAK;AAEtB,gBAAc,QAAQ,SAAS,KAAK,OAAO;AACzC,OAAI,CAAC,IAAI,QAAS;GAElB,MAAM,WAAW,KAAK,KACnB,MAAM,IAAI,QAAQ,aAAa,IAAI,QAAQ,UAC5C,EACD;AACD,OAAI,QAAQ,WAAW;AAIvB,OADkB,YAAY,KAG5B,oBACA,CAAC,oBAAoB,QAAQ,IAAI,GAAG,EACpC;AACA,wBAAoB,QAAQ,IAAI,GAAG;AACnC,qBAAiB,GAAG;;IAEtB;GACF;AAmBF,QACE,oBAAC,SAAD,EAAA,UAjBsB,cAAc;AACpC,SAAO,QACJ,KAAK,WAAW;AAEf,UAAO;IAAE;IAAQ,WADC,mBAAmB,IAAI,OAAO,GAC/B;IAAW;IAC5B,CACD,QAEG,SAIG,KAAK,cAAc,KAAA,EACzB;IACF,CAAC,SAAS,mBAAmB,CAI3B,CAAgB,KAAK,EAAE,QAAQ,gBAAgB;AAC9C,SACE,oBAAC,iBAAD;GAEU;GACG;GACE;GACb,EAJK,OAAO,GAIZ;GAEJ,EACI,CAAA"}
|
|
1
|
+
{"version":3,"file":"HitEffects3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/HitEffects3D.tsx"],"sourcesContent":["/**\n * HitEffects3D - Three.js particle effects for combat\n *\n * Maintains Korean theming and visual feedback for combat actions\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { HitEffect } from \"../../../../systems\";\nimport { HitEffectType } from \"../../../../systems/effects\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\n\n/**\n * Props for the HitEffects3D component.\n * Controls which effects are displayed and callbacks for effect lifecycle.\n */\nexport interface HitEffects3DProps {\n /** Array of active hit effects to render in the scene */\n readonly effects: HitEffect[];\n /** Callback invoked when an effect completes its duration */\n readonly onEffectComplete?: (effectId: string) => void;\n /** Arena bounds for accurate coordinate conversion (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n}\n\ninterface ActiveEffect extends HitEffect {\n progress: number;\n}\n\n/**\n * Individual Hit Effect Component\n * Renders a single effect with Three.js primitives\n */\nconst HitEffectVisual: React.FC<{\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n arenaBounds?: PhysicsArenaBounds;\n}> = ({ effect, effectRef, arenaBounds }) => {\n const groupRef = useRef<THREE.Group>(null);\n // Use ref for alpha to avoid setState in useFrame (eliminates 60 rerenders/sec)\n const alphaRef = useRef(1);\n\n // Position in 3D space - use meter coordinates directly\n const position3D: [number, number, number] = useMemo(() => {\n if (!effect.position) return [0, 1, 0];\n\n // Use arena bounds if available, otherwise use default values for 10m arena\n const bounds = arenaBounds ?? DEFAULT_PHYSICS_ARENA_BOUNDS;\n \n // Position is in meters relative to arena center (0, 0)\n // Player models use meter coordinates directly: position={[playerPos.x, 0, playerPos.y]}\n // So we use meter coordinates directly too for alignment\n const halfWidth = bounds.worldWidthMeters / 2;\n const halfDepth = bounds.worldDepthMeters / 2;\n \n // Clamp position to arena boundaries in meters\n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, effect.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, effect.position.y));\n \n // Use clamped meter coordinates directly in 3D space (no remapping)\n const x = clampedX; // Meter position X\n const y = 1.5; // Mid-height for effects\n const z = clampedZ; // Meter position Z (depth)\n\n return [x, y, z];\n }, [effect.position, arenaBounds]);\n\n // Animate effect based on type - Update materials directly in the group\n useFrame(() => {\n if (!groupRef.current || !effectRef.current) return;\n\n // Access fresh progress value from ref and update alpha ref (no setState!)\n const progress = effectRef.current.progress;\n alphaRef.current = 1 - progress;\n\n // Update all material opacities and positions in the group hierarchy\n let sparkIndex = 0;\n groupRef.current.traverse((object) => {\n if (object instanceof THREE.Mesh && object.material instanceof THREE.MeshBasicMaterial) {\n const baseOpacity = object.material.userData.baseOpacity ?? 1;\n object.material.opacity = alphaRef.current * baseOpacity;\n \n // For BLOCK effect, animate spark particle positions\n if (effect.type === HitEffectType.BLOCK && object.geometry instanceof THREE.SphereGeometry) {\n if (object.geometry.parameters?.radius === 0.05) { // Spark particles have radius 0.05\n const i = sparkIndex++;\n if (i < 3) { // Only update the 3 spark particles\n object.position.y = Math.sin((1 - alphaRef.current) * Math.PI) * 0.3;\n }\n }\n }\n }\n });\n\n // Rotate for some effects\n if (\n effect.type === HitEffectType.COUNTER ||\n effect.type === HitEffectType.VITAL_POINT_STRIKE\n ) {\n groupRef.current.rotation.y += 0.1;\n }\n\n // Scale pulse for critical hits\n if (effect.type === HitEffectType.CRITICAL_HIT) {\n const pulse =\n 1 + Math.sin(effectRef.current.progress * Math.PI * 4) * 0.2;\n groupRef.current.scale.set(pulse, pulse, pulse);\n }\n });\n\n // Render based on effect type\n switch (effect.type) {\n case HitEffectType.HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Impact flash sphere */}\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Expanding ring */}\n <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry\n args={[0.3 * effect.intensity, 0.35 * effect.intensity, 32]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.CRITICAL_HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Large impact sphere */}\n <mesh>\n <sphereGeometry args={[0.5 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.7}\n userData={{ baseOpacity: 0.7 }}\n />\n </mesh>\n {/* Star burst lines */}\n {[0, 1, 2, 3].map((i) => {\n const angle = (i * Math.PI) / 2;\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.3, 0, Math.sin(angle) * 0.3]}\n rotation={[0, angle, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.BLOCK:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Shield arc */}\n <mesh rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry\n args={[0.4 * effect.intensity, 0.05, 8, 16, Math.PI]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Spark particles - positions will be updated in useFrame */}\n {[0, 1, 2].map((i) => (\n <mesh\n key={i}\n position={[\n (i - 1) * 0.2,\n 0,\n 0,\n ]}\n >\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.MISS:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Swish trail lines */}\n {[0, 1].map((i) => (\n <mesh\n key={i}\n position={[(i - 0.5) * 0.2, i * 0.1, 0]}\n rotation={[0, 0, (i - 0.5) * 0.3]}\n >\n <boxGeometry args={[0.6, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.TEXT_TERTIARY}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.VITAL_POINT_STRIKE:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Pulsing sphere */}\n <mesh>\n <sphereGeometry args={[0.35 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Concentric rings */}\n {[0.2, 0.3, 0.4].map((radius, i) => (\n <mesh key={i} rotation={[-Math.PI / 2, 0, 0]}>\n <ringGeometry args={[radius, radius + 0.02, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n {/* Crosshair */}\n <mesh position={[0, 0, 0]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n <mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.PARRY:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Deflection arc */}\n <mesh>\n <torusGeometry\n args={[0.35 * effect.intensity, 0.05, 8, 16, Math.PI / 2]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Sparks */}\n {[0, 1, 2].map((i) => {\n const angle = (Math.PI / 6) * (i - 1);\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.4, Math.sin(angle) * 0.4, 0]}\n >\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.COUNTER:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Spinning energy blades */}\n {[0, 1, 2, 3].map((i) => (\n <mesh\n key={i}\n rotation={[0, (i * Math.PI) / 2, 0]}\n position={[0, 0, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.GENERAL_DAMAGE:\n case HitEffectType.STATUS_EFFECT:\n default:\n return (\n <group ref={groupRef} position={position3D}>\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GREEN}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n </group>\n );\n }\n};\n\n/**\n * HitEffects3D Component\n * Manages all active hit effects in the combat scene\n * Uses refs to avoid triggering React re-renders at 60fps\n */\nexport const HitEffects3D: React.FC<HitEffects3DProps> = ({\n effects,\n onEffectComplete,\n arenaBounds,\n}) => {\n // Use refs to track effects without causing re-renders\n const effectRefsMap = useRef<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n const completedEffectsRef = useRef<Set<string>>(new Set());\n\n // Store effectRefs as state to avoid ref access during render\n // This is updated in useEffect, not during render\n const [effectRefsSnapshot, setEffectRefsSnapshot] = useState<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n\n // Update effect refs when effects change (minimal state updates)\n useEffect(() => {\n // Clean up refs for removed effects\n const currentIdSet = new Set(effects.map((e) => e.id));\n effectRefsMap.current.forEach((_ref, id) => {\n if (!currentIdSet.has(id)) {\n effectRefsMap.current.delete(id);\n completedEffectsRef.current.delete(id);\n }\n });\n\n // Initialize refs for new effects\n effects.forEach((effect) => {\n if (!effectRefsMap.current.has(effect.id)) {\n effectRefsMap.current.set(effect.id, {\n current: { ...effect, progress: 0 },\n });\n }\n });\n\n // Create a snapshot of the refs for use in render\n // This is a new Map with the same refs (not a deep copy)\n setEffectRefsSnapshot(new Map(effectRefsMap.current));\n }, [effects]);\n\n // Update progress using refs (no setState in useFrame)\n useFrame(() => {\n const now = Date.now();\n\n effectRefsMap.current.forEach((ref, id) => {\n if (!ref.current) return;\n\n const progress = Math.min(\n (now - ref.current.startTime) / ref.current.duration,\n 1\n );\n ref.current.progress = progress;\n\n // Handle completion\n const isExpired = progress >= 1;\n if (\n isExpired &&\n onEffectComplete &&\n !completedEffectsRef.current.has(id)\n ) {\n completedEffectsRef.current.add(id);\n onEffectComplete(id);\n }\n });\n });\n\n // Pre-compute effect data to avoid ref access during render\n const effectsToRender = useMemo(() => {\n return effects\n .map((effect) => {\n const effectRef = effectRefsSnapshot.get(effect.id);\n return { effect, effectRef };\n })\n .filter(\n (\n item\n ): item is {\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n } => item.effectRef !== undefined\n );\n }, [effects, effectRefsSnapshot]);\n\n return (\n <group>\n {effectsToRender.map(({ effect, effectRef }) => {\n return (\n <HitEffectVisual\n key={effect.id}\n effect={effect}\n effectRef={effectRef}\n arenaBounds={arenaBounds}\n />\n );\n })}\n </group>\n );\n};\n\nexport default HitEffects3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayerStateIndicators.js","names":[],"sources":["../../../../../src/components/shared/three/effects/PlayerStateIndicators.tsx"],"sourcesContent":["/**\n * PlayerStateIndicators - Html overlay for player stats\n * \n * Displays health, stamina, Ki, balance state, and other combat metrics\n * as an Html overlay above the 3D player model.\n * \n * @module components/three/PlayerStateIndicators\n * @category 3D Components\n * @korean 플레이어상태표시기\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { PlayerStateIndicatorsProps, BalanceState } from \"../../../../types/player-visual\";\nimport { toHexColor } from \"../../../../utils/colorHelpers\";\n\n/**\n * Get color for balance state\n * \n * @param balance - Current balance state\n * @returns CSS color string\n * @korean 균형색상가져오기\n */\nconst getBalanceColor = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"#00cc44\"; // Green - ready for combat\n case \"SHAKEN\":\n return \"#ffcc00\"; // Yellow - slightly compromised\n case \"VULNERABLE\":\n return \"#ff8800\"; // Orange - significantly exposed\n case \"HELPLESS\":\n return \"#cc0000\"; // Red - complete vulnerability\n default:\n return \"#00cc44\";\n }\n};\n\n/**\n * Get Korean text for balance state\n * \n * @param balance - Current balance state\n * @returns Korean text\n * @korean 균형한글가져오기\n */\nconst getBalanceText = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"준비완료\";\n case \"SHAKEN\":\n return \"동요상태\";\n case \"VULNERABLE\":\n return \"취약상태\";\n case \"HELPLESS\":\n return \"무력상태\";\n default:\n return \"준비완료\";\n }\n};\n\n/**\n * PlayerStateIndicators Component\n * \n * Renders an Html overlay with health bar, stamina bar, Ki indicator,\n * balance state, and consciousness level.\n * \n * @example\n * ```tsx\n * <PlayerStateIndicators\n * health={85}\n * maxHealth={100}\n * stamina={60}\n * ki={40}\n * balance=\"READY\"\n * consciousness={100}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 플레이어상태표시기컴포넌트\n */\nexport const PlayerStateIndicators: React.FC<PlayerStateIndicatorsProps> = ({\n health,\n maxHealth,\n stamina,\n ki,\n balance,\n consciousness,\n pain = 0,\n bloodLoss = 0,\n isMobile,\n}) => {\n // Calculate percentages\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (health / maxHealth) * 100)),\n [health, maxHealth]\n );\n\n const staminaPercent = useMemo(\n () => Math.max(0, Math.min(100, stamina)),\n [stamina]\n );\n\n const kiPercent = useMemo(() => Math.max(0, Math.min(100, ki)), [ki]);\n\n const consciousnessPercent = useMemo(\n () => Math.max(0, Math.min(100, consciousness)),\n [consciousness]\n );\n\n // Responsive sizing\n const sizing = useMemo(\n () => ({\n width: isMobile ? \"60px\" : \"80px\",\n barHeight: isMobile ? \"4px\" : \"6px\",\n thinBarHeight: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"8px\" : \"10px\",\n gap: isMobile ? \"2px\" : \"4px\",\n }),\n [isMobile]\n );\n\n // Health bar color based on percentage\n const healthColor = useMemo(() => {\n if (healthPercent > 50) return \"#00ff00\"; // Green\n if (healthPercent > 25) return \"#ffff00\"; // Yellow\n return \"#ff0000\"; // Red\n }, [healthPercent]);\n\n // Balance state color\n const balanceColor = useMemo(() => getBalanceColor(balance), [balance]);\n const balanceTextKorean = useMemo(() => getBalanceText(balance), [balance]);\n\n return (\n <Html\n position={[0, 2.5, 0]}\n center\n distanceFactor={isMobile ? 15 : 10}\n occlude={false}\n style={{ pointerEvents: \"none\", userSelect: \"none\" }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: sizing.gap,\n minWidth: sizing.width,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid=\"player-state-indicators\"\n >\n {/* Health bar */}\n <div\n style={{\n height: sizing.barHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"health-bar\"\n title={`Health: ${health}/${maxHealth}`}\n >\n <div\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n background: healthColor,\n transition: \"width 0.3s ease, background-color 0.3s ease\",\n }}\n />\n </div>\n\n {/* Stamina bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.ACCENT_GOLD)}`,\n }}\n data-testid=\"stamina-bar\"\n title={`Stamina: ${stamina}%`}\n >\n <div\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.ACCENT_GOLD),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n\n {/* Ki bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"ki-bar\"\n title={`Ki: ${ki}%`}\n >\n <div\n style={{\n width: `${kiPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.PRIMARY_CYAN),\n transition: \"width 0.3s ease\",\n boxShadow:\n kiPercent > 80\n ? `0 0 ${isMobile ? \"4px\" : \"6px\"} ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`\n : \"none\",\n }}\n />\n </div>\n\n {/* Balance state indicator */}\n <div\n style={{\n fontSize: sizing.fontSize,\n color: balanceColor,\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"2px 4px\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: \"2px\",\n }}\n data-testid=\"balance-indicator\"\n title={`Balance: ${balance}`}\n >\n {balanceTextKorean}\n </div>\n\n {/* Consciousness indicator (if below 100%) */}\n {consciousnessPercent < 100 && (\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE)}`,\n }}\n data-testid=\"consciousness-bar\"\n title={`Consciousness: ${consciousness}%`}\n >\n <div\n style={{\n width: `${consciousnessPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n )}\n\n {/* Pain indicator (if above 20%) */}\n {pain > 20 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.PAIN_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"pain-indicator\"\n title={`Pain: ${pain}%`}\n >\n 통증 {Math.round(pain)}%\n </div>\n )}\n\n {/* Blood loss indicator (if above 10%) */}\n {bloodLoss && bloodLoss > 10 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.BLOODLOSS_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"bloodloss-indicator\"\n title={`Blood Loss: ${bloodLoss}%`}\n >\n 출혈 {Math.round(bloodLoss)}%\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nexport default PlayerStateIndicators;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,mBAAmB,YAAkC;AACzD,SAAQ,SAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;;;;;;;;AAWb,IAAM,kBAAkB,YAAkC;AACxD,SAAQ,SAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBb,IAAa,yBAA+D,EAC1E,QACA,WACA,SACA,IACA,SACA,eACA,OAAO,GACP,YAAY,GACZ,eACI;CAEJ,MAAM,gBAAgB,cACd,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,SAAS,YAAa,IAAI,CAAC,EAC5D,CAAC,QAAQ,UAAU,CACpB;CAED,MAAM,iBAAiB,cACf,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,CAAC,EACzC,CAAC,QAAQ,CACV;CAED,MAAM,YAAY,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC;CAErE,MAAM,uBAAuB,cACrB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,cAAc,CAAC,EAC/C,CAAC,cAAc,CAChB;CAGD,MAAM,SAAS,eACN;EACL,OAAO,WAAW,SAAS;EAC3B,WAAW,WAAW,QAAQ;EAC9B,eAAe,WAAW,QAAQ;EAClC,UAAU,WAAW,QAAQ;EAC7B,KAAK,WAAW,QAAQ;EACzB,GACD,CAAC,SAAS,CACX;CAGD,MAAM,cAAc,cAAc;AAChC,MAAI,gBAAgB,GAAI,QAAO;AAC/B,MAAI,gBAAgB,GAAI,QAAO;AAC/B,SAAO;IACN,CAAC,cAAc,CAAC;CAGnB,MAAM,eAAe,cAAc,gBAAgB,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACvE,MAAM,oBAAoB,cAAc,eAAe,QAAQ,EAAE,CAAC,QAAQ,CAAC;AAE3E,QACE,oBAAC,MAAD;EACE,UAAU;GAAC;GAAG;GAAK;GAAE;EACrB,QAAA;EACA,gBAAgB,WAAW,KAAK;EAChC,SAAS;EACT,OAAO;GAAE,eAAe;GAAQ,YAAY;GAAQ;YAEpD,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK,OAAO;IACZ,UAAU,OAAO;IACjB,YAAY,YAAY;IACzB;GACD,eAAY;aARd;IAWE,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,aAAa;MAC5D;KACD,eAAY;KACZ,OAAO,WAAW,OAAO,GAAG;eAE5B,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,cAAc;MACxB,QAAQ;MACR,YAAY;MACZ,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,YAAY;MAC3D;KACD,eAAY;KACZ,OAAO,YAAY,QAAQ;eAE3B,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,eAAe;MACzB,QAAQ;MACR,YAAY,WAAW,cAAc,YAAY;MACjD,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,aAAa;MAC5D;KACD,eAAY;KACZ,OAAO,OAAO,GAAG;eAEjB,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,UAAU;MACpB,QAAQ;MACR,YAAY,WAAW,cAAc,aAAa;MAClD,YAAY;MACZ,WACE,YAAY,KACR,OAAO,WAAW,QAAQ,MAAM,GAAG,WAAW,cAAc,aAAa,KACzE;MACP,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO;MACP,WAAW;MACX,YAAY;MACZ,YAAY;MACZ,SAAS;MACT,YAAY;MACZ,cAAc;MACf;KACD,eAAY;KACZ,OAAO,YAAY;eAElB;KACG,CAAA;IAGL,uBAAuB,OACtB,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,qBAAqB;MACpE;KACD,eAAY;KACZ,OAAO,kBAAkB,cAAc;eAEvC,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,qBAAqB;MAC/B,QAAQ;MACR,YAAY,WAAW,cAAc,qBAAqB;MAC1D,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAIP,OAAO,MACN,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO,WAAW,cAAc,eAAe;MAC/C,WAAW;MACX,YAAY;MACZ,YAAY;MACb;KACD,eAAY;KACZ,OAAO,SAAS,KAAK;eATvB;MAUC;MACK,KAAK,MAAM,KAAK;MAAC;MACjB;;IAIP,aAAa,YAAY,MACxB,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO,WAAW,cAAc,oBAAoB;MACpD,WAAW;MACX,YAAY;MACZ,YAAY;MACb;KACD,eAAY;KACZ,OAAO,eAAe,UAAU;eATlC;MAUC;MACK,KAAK,MAAM,UAAU;MAAC;MACtB;;IAEJ;;EACD,CAAA"}
|
|
1
|
+
{"version":3,"file":"PlayerStateIndicators.js","names":[],"sources":["../../../../../src/components/shared/three/effects/PlayerStateIndicators.tsx"],"sourcesContent":["/**\n * PlayerStateIndicators - Html overlay for player stats\n * \n * Displays health, stamina, Ki, balance state, and other combat metrics\n * as an Html overlay above the 3D player model.\n * \n * @module components/three/PlayerStateIndicators\n * @category 3D Components\n * @korean 플레이어상태표시기\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { PlayerStateIndicatorsProps, BalanceState } from \"../../../../types/player-visual\";\nimport { toHexColor } from \"../../../../utils/colorHelpers\";\n\n/**\n * Get color for balance state\n * \n * @param balance - Current balance state\n * @returns CSS color string\n * @korean 균형색상가져오기\n */\nconst getBalanceColor = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"#00cc44\"; // Green - ready for combat\n case \"SHAKEN\":\n return \"#ffcc00\"; // Yellow - slightly compromised\n case \"VULNERABLE\":\n return \"#ff8800\"; // Orange - significantly exposed\n case \"HELPLESS\":\n return \"#cc0000\"; // Red - complete vulnerability\n default:\n return \"#00cc44\";\n }\n};\n\n/**\n * Get Korean text for balance state\n * \n * @param balance - Current balance state\n * @returns Korean text\n * @korean 균형한글가져오기\n */\nconst getBalanceText = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"준비완료\";\n case \"SHAKEN\":\n return \"동요상태\";\n case \"VULNERABLE\":\n return \"취약상태\";\n case \"HELPLESS\":\n return \"무력상태\";\n default:\n return \"준비완료\";\n }\n};\n\n/**\n * PlayerStateIndicators Component\n * \n * Renders an Html overlay with health bar, stamina bar, Ki indicator,\n * balance state, and consciousness level.\n * \n * @example\n * ```tsx\n * <PlayerStateIndicators\n * health={85}\n * maxHealth={100}\n * stamina={60}\n * ki={40}\n * balance=\"READY\"\n * consciousness={100}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 플레이어상태표시기컴포넌트\n */\nexport const PlayerStateIndicators: React.FC<PlayerStateIndicatorsProps> = ({\n health,\n maxHealth,\n stamina,\n ki,\n balance,\n consciousness,\n pain = 0,\n bloodLoss = 0,\n isMobile,\n}) => {\n // Calculate percentages\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (health / maxHealth) * 100)),\n [health, maxHealth]\n );\n\n const staminaPercent = useMemo(\n () => Math.max(0, Math.min(100, stamina)),\n [stamina]\n );\n\n const kiPercent = useMemo(() => Math.max(0, Math.min(100, ki)), [ki]);\n\n const consciousnessPercent = useMemo(\n () => Math.max(0, Math.min(100, consciousness)),\n [consciousness]\n );\n\n // Responsive sizing\n const sizing = useMemo(\n () => ({\n width: isMobile ? \"60px\" : \"80px\",\n barHeight: isMobile ? \"4px\" : \"6px\",\n thinBarHeight: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"8px\" : \"10px\",\n gap: isMobile ? \"2px\" : \"4px\",\n }),\n [isMobile]\n );\n\n // Health bar color based on percentage\n const healthColor = useMemo(() => {\n if (healthPercent > 50) return \"#00ff00\"; // Green\n if (healthPercent > 25) return \"#ffff00\"; // Yellow\n return \"#ff0000\"; // Red\n }, [healthPercent]);\n\n // Balance state color\n const balanceColor = useMemo(() => getBalanceColor(balance), [balance]);\n const balanceTextKorean = useMemo(() => getBalanceText(balance), [balance]);\n\n return (\n <Html\n position={[0, 2.5, 0]}\n center\n distanceFactor={isMobile ? 15 : 10}\n occlude={false}\n style={{ pointerEvents: \"none\", userSelect: \"none\" }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: sizing.gap,\n minWidth: sizing.width,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid=\"player-state-indicators\"\n >\n {/* Health bar */}\n <div\n style={{\n height: sizing.barHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"health-bar\"\n title={`Health: ${health}/${maxHealth}`}\n >\n <div\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n background: healthColor,\n transition: \"width 0.3s ease, background-color 0.3s ease\",\n }}\n />\n </div>\n\n {/* Stamina bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.ACCENT_GOLD)}`,\n }}\n data-testid=\"stamina-bar\"\n title={`Stamina: ${stamina}%`}\n >\n <div\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.ACCENT_GOLD),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n\n {/* Ki bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"ki-bar\"\n title={`Ki: ${ki}%`}\n >\n <div\n style={{\n width: `${kiPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.PRIMARY_CYAN),\n transition: \"width 0.3s ease\",\n boxShadow:\n kiPercent > 80\n ? `0 0 ${isMobile ? \"4px\" : \"6px\"} ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`\n : \"none\",\n }}\n />\n </div>\n\n {/* Balance state indicator */}\n <div\n style={{\n fontSize: sizing.fontSize,\n color: balanceColor,\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"2px 4px\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: \"2px\",\n }}\n data-testid=\"balance-indicator\"\n title={`Balance: ${balance}`}\n >\n {balanceTextKorean}\n </div>\n\n {/* Consciousness indicator (if below 100%) */}\n {consciousnessPercent < 100 && (\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE)}`,\n }}\n data-testid=\"consciousness-bar\"\n title={`Consciousness: ${consciousness}%`}\n >\n <div\n style={{\n width: `${consciousnessPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n )}\n\n {/* Pain indicator (if above 20%) */}\n {pain > 20 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.PAIN_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"pain-indicator\"\n title={`Pain: ${pain}%`}\n >\n 통증 {Math.round(pain)}%\n </div>\n )}\n\n {/* Blood loss indicator (if above 10%) */}\n {bloodLoss && bloodLoss > 10 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.BLOODLOSS_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"bloodloss-indicator\"\n title={`Blood Loss: ${bloodLoss}%`}\n >\n 출혈 {Math.round(bloodLoss)}%\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nexport default PlayerStateIndicators;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StanceSymbol3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceSymbol3D.tsx"],"sourcesContent":["/**\n * StanceSymbol3D - Floating trigram symbol above player\n *\n * Displays the Unicode trigram symbol (☰☱☲☳☴☵☶☷) floating above the player's head,\n * with rotation animation and pulsing glow effect. Provides immediate visual feedback\n * of the current stance to the player and observers.\n *\n * @module components/three/StanceSymbol3D\n * @category 3D Components\n * @korean 자세기호3D\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport {\n getStanceColorHex,\n getStanceKoreanName,\n getTrigramSymbol,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceSymbol3D component\n */\nexport interface StanceSymbol3DProps {\n /** Current trigram stance to display */\n readonly stance: TrigramStance;\n /** Height offset above player (default: 2.5) */\n readonly heightOffset?: number;\n /** Whether symbol should rotate */\n readonly animated?: boolean;\n /** Symbol scale multiplier */\n readonly scale?: number;\n /** Show Korean name below symbol */\n readonly showName?: boolean;\n}\n\n/**\n * Animation constants for stance symbol\n */\nconst ANIMATION_CONSTANTS = {\n ROTATION_SPEED: 0.5,\n BOB_AMPLITUDE: 0.1,\n BOB_FREQUENCY: 2,\n} as const;\n\n/**\n * StanceSymbol3D Component\n *\n * Renders a floating trigram symbol above the player with:\n * - Rotation animation\n * - Pulsing glow effect\n * - Stance-specific coloring\n * - Optional Korean name display\n *\n * Uses Html from @react-three/drei for crisp text rendering that always faces camera.\n *\n * @example\n * ```tsx\n * <StanceSymbol3D\n * stance={TrigramStance.GEON}\n * heightOffset={2.5}\n * animated={true}\n * showName={true}\n * />\n * ```\n */\nexport const StanceSymbol3D: React.FC<StanceSymbol3DProps> = ({\n stance,\n heightOffset = 2.5,\n animated = true,\n scale = 1.0,\n showName = true,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n // Get stance properties\n const symbol = useMemo(() => getTrigramSymbol(stance), [stance]);\n const koreanName = useMemo(() => getStanceKoreanName(stance), [stance]);\n const colorHex = useMemo(() => getStanceColorHex(stance), [stance]);\n\n // Animation loop - rotation and pulse\n useFrame((state) => {\n if (!animated || !groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n\n // Rotate symbol slowly\n groupRef.current.rotation.y = time * ANIMATION_CONSTANTS.ROTATION_SPEED;\n\n // Gentle vertical bob - oscillate around heightOffset\n groupRef.current.position.y =\n heightOffset +\n Math.sin(time * ANIMATION_CONSTANTS.BOB_FREQUENCY) *\n ANIMATION_CONSTANTS.BOB_AMPLITUDE;\n });\n\n return (\n <group\n ref={groupRef}\n position={[0, heightOffset, 0]}\n name=\"stance-symbol-3d\"\n >\n {/* Interactive Light Source - illuminates player head/shoulders */}\n <pointLight color={colorHex} intensity={2.5} distance={4} decay={2} />\n\n {/* Trigram symbol with glow effect */}\n <Html\n center\n distanceFactor={10}\n zIndexRange={[100, 0]}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n }}\n >\n {/* Main trigram symbol */}\n <div\n style={{\n fontSize: `${48 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `\n 0 0 10px ${colorHex},\n 0 0 20px ${colorHex},\n 0 0 30px ${colorHex}\n `,\n fontWeight: \"bold\",\n lineHeight: \"1\",\n animation: \"pulse 2s ease-in-out infinite\",\n }}\n data-testid=\"trigram-symbol\"\n >\n {symbol}\n </div>\n\n {/* Korean name below symbol */}\n {showName && (\n <div\n style={{\n fontSize: `${16 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `0 0 5px ${colorHex}`,\n fontWeight: \"bold\",\n letterSpacing: \"2px\",\n }}\n data-testid=\"stance-korean-name\"\n >\n {koreanName}\n </div>\n )}\n </div>\n\n {/* CSS animation for pulse effect */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.8; transform: scale(1.1); }\n }\n `}\n </style>\n </Html>\n </group>\n );\n};\n\nexport default StanceSymbol3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,sBAAsB;CAC1B,gBAAgB;CAChB,eAAe;CACf,eAAe;CAChB;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAa,kBAAiD,EAC5D,QACA,eAAe,KACf,WAAW,MACX,QAAQ,GACR,WAAW,WACP;CACJ,MAAM,WAAW,OAAoB,KAAK;CAG1C,MAAM,SAAS,cAAc,iBAAiB,OAAO,EAAE,CAAC,OAAO,CAAC;CAChE,MAAM,aAAa,cAAc,oBAAoB,OAAO,EAAE,CAAC,OAAO,CAAC;CACvE,MAAM,WAAW,cAAc,kBAAkB,OAAO,EAAE,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"StanceSymbol3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceSymbol3D.tsx"],"sourcesContent":["/**\n * StanceSymbol3D - Floating trigram symbol above player\n *\n * Displays the Unicode trigram symbol (☰☱☲☳☴☵☶☷) floating above the player's head,\n * with rotation animation and pulsing glow effect. Provides immediate visual feedback\n * of the current stance to the player and observers.\n *\n * @module components/three/StanceSymbol3D\n * @category 3D Components\n * @korean 자세기호3D\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport {\n getStanceColorHex,\n getStanceKoreanName,\n getTrigramSymbol,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceSymbol3D component\n */\nexport interface StanceSymbol3DProps {\n /** Current trigram stance to display */\n readonly stance: TrigramStance;\n /** Height offset above player (default: 2.5) */\n readonly heightOffset?: number;\n /** Whether symbol should rotate */\n readonly animated?: boolean;\n /** Symbol scale multiplier */\n readonly scale?: number;\n /** Show Korean name below symbol */\n readonly showName?: boolean;\n}\n\n/**\n * Animation constants for stance symbol\n */\nconst ANIMATION_CONSTANTS = {\n ROTATION_SPEED: 0.5,\n BOB_AMPLITUDE: 0.1,\n BOB_FREQUENCY: 2,\n} as const;\n\n/**\n * StanceSymbol3D Component\n *\n * Renders a floating trigram symbol above the player with:\n * - Rotation animation\n * - Pulsing glow effect\n * - Stance-specific coloring\n * - Optional Korean name display\n *\n * Uses Html from @react-three/drei for crisp text rendering that always faces camera.\n *\n * @example\n * ```tsx\n * <StanceSymbol3D\n * stance={TrigramStance.GEON}\n * heightOffset={2.5}\n * animated={true}\n * showName={true}\n * />\n * ```\n */\nexport const StanceSymbol3D: React.FC<StanceSymbol3DProps> = ({\n stance,\n heightOffset = 2.5,\n animated = true,\n scale = 1.0,\n showName = true,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n // Get stance properties\n const symbol = useMemo(() => getTrigramSymbol(stance), [stance]);\n const koreanName = useMemo(() => getStanceKoreanName(stance), [stance]);\n const colorHex = useMemo(() => getStanceColorHex(stance), [stance]);\n\n // Animation loop - rotation and pulse\n useFrame((state) => {\n if (!animated || !groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n\n // Rotate symbol slowly\n groupRef.current.rotation.y = time * ANIMATION_CONSTANTS.ROTATION_SPEED;\n\n // Gentle vertical bob - oscillate around heightOffset\n groupRef.current.position.y =\n heightOffset +\n Math.sin(time * ANIMATION_CONSTANTS.BOB_FREQUENCY) *\n ANIMATION_CONSTANTS.BOB_AMPLITUDE;\n });\n\n return (\n <group\n ref={groupRef}\n position={[0, heightOffset, 0]}\n name=\"stance-symbol-3d\"\n >\n {/* Interactive Light Source - illuminates player head/shoulders */}\n <pointLight color={colorHex} intensity={2.5} distance={4} decay={2} />\n\n {/* Trigram symbol with glow effect */}\n <Html\n center\n distanceFactor={10}\n zIndexRange={[100, 0]}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n }}\n >\n {/* Main trigram symbol */}\n <div\n style={{\n fontSize: `${48 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `\n 0 0 10px ${colorHex},\n 0 0 20px ${colorHex},\n 0 0 30px ${colorHex}\n `,\n fontWeight: \"bold\",\n lineHeight: \"1\",\n animation: \"pulse 2s ease-in-out infinite\",\n }}\n data-testid=\"trigram-symbol\"\n >\n {symbol}\n </div>\n\n {/* Korean name below symbol */}\n {showName && (\n <div\n style={{\n fontSize: `${16 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `0 0 5px ${colorHex}`,\n fontWeight: \"bold\",\n letterSpacing: \"2px\",\n }}\n data-testid=\"stance-korean-name\"\n >\n {koreanName}\n </div>\n )}\n </div>\n\n {/* CSS animation for pulse effect */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.8; transform: scale(1.1); }\n }\n `}\n </style>\n </Html>\n </group>\n );\n};\n\nexport default StanceSymbol3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,sBAAsB;CAC1B,gBAAgB;CAChB,eAAe;CACf,eAAe;CAChB;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAa,kBAAiD,EAC5D,QACA,eAAe,KACf,WAAW,MACX,QAAQ,GACR,WAAW,WACP;CACJ,MAAM,WAAW,OAAoB,KAAK;CAG1C,MAAM,SAAS,cAAc,iBAAiB,OAAO,EAAE,CAAC,OAAO,CAAC;CAChE,MAAM,aAAa,cAAc,oBAAoB,OAAO,EAAE,CAAC,OAAO,CAAC;CACvE,MAAM,WAAW,cAAc,kBAAkB,OAAO,EAAE,CAAC,OAAO,CAAC;CAGnE,UAAU,UAAU;EAClB,IAAI,CAAC,YAAY,CAAC,SAAS,SAAS;EAEpC,MAAM,OAAO,MAAM,MAAM;EAGzB,SAAS,QAAQ,SAAS,IAAI,OAAO,oBAAoB;EAGzD,SAAS,QAAQ,SAAS,IACxB,eACA,KAAK,IAAI,OAAO,oBAAoB,cAAc,GAChD,oBAAoB;GACxB;CAEF,OACE,qBAAC,SAAD;EACE,KAAK;EACL,UAAU;GAAC;GAAG;GAAc;GAAE;EAC9B,MAAK;YAHP,CAME,oBAAC,cAAD;GAAY,OAAO;GAAU,WAAW;GAAK,UAAU;GAAG,OAAO;GAAK,CAAA,EAGtE,qBAAC,MAAD;GACE,QAAA;GACA,gBAAgB;GAChB,aAAa,CAAC,KAAK,EAAE;GACrB,OAAO;IACL,eAAe;IACf,YAAY;IACb;aAPH,CASE,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,KAAK;KACN;cANH,CASE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,KAAK,MAAM;MACxB,YAAY,YAAY;MACxB,OAAO;MACP,YAAY;2BACC,SAAS;2BACT,SAAS;2BACT,SAAS;;MAEtB,YAAY;MACZ,YAAY;MACZ,WAAW;MACZ;KACD,eAAY;eAEX;KACG,CAAA,EAGL,YACC,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,KAAK,MAAM;MACxB,YAAY,YAAY;MACxB,OAAO;MACP,YAAY,WAAW;MACvB,YAAY;MACZ,eAAe;MAChB;KACD,eAAY;eAEX;KACG,CAAA,CAEJ;OAGN,oBAAC,SAAD,EAAA,UACG;;;;;aAMK,CAAA,CACH;KACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StanceTransitionEffect.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceTransitionEffect.tsx"],"sourcesContent":["/**\n * StanceTransitionEffect - Smooth visual transition between trigram stances\n *\n * Manages the visual transition when a player changes stance, providing:\n * - 0.5s smooth color fade between old and new stance colors\n * - Expanding energy ring effect\n * - Bilingual stance name display (Korean + English) for 1s\n * - Audio synchronization for stance change SFX\n *\n * @module components/three/StanceTransitionEffect\n * @category 3D Components\n * @korean 자세전환효과\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { colorUtils } from \"../../../../types/constants/colors\";\nimport {\n getStanceColor,\n getStanceNames,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceTransitionEffect component\n */\nexport interface StanceTransitionEffectProps {\n /** Previous stance (for color interpolation) */\n readonly fromStance: TrigramStance | null;\n /** New stance being transitioned to */\n readonly toStance: TrigramStance;\n /** Callback when transition completes */\n readonly onTransitionComplete?: () => void;\n /** Transition duration in seconds (default: 0.5) */\n readonly duration?: number;\n /** Show stance name overlay (default: true) */\n readonly showNameOverlay?: boolean;\n}\n\n/**\n * StanceTransitionEffect Component\n *\n * Provides smooth visual feedback during stance changes:\n * 1. Expanding energy ring effect from player center\n * 2. Color interpolation from old to new stance\n * 3. Bilingual stance name overlay (1 second display)\n *\n * Performance optimized:\n * - Single animation frame callback\n * - Auto-cleanup after transition completes\n * - Reuses Three.js materials and geometries\n *\n * @example\n * ```tsx\n * <StanceTransitionEffect\n * fromStance={TrigramStance.GEON}\n * toStance={TrigramStance.TAE}\n * onTransitionComplete={() => console.log('Transition done')}\n * duration={0.5}\n * />\n * ```\n */\nexport const StanceTransitionEffect: React.FC<StanceTransitionEffectProps> = ({\n fromStance,\n toStance,\n onTransitionComplete,\n duration = 0.5,\n showNameOverlay = true,\n}) => {\n const ringRef = useRef<THREE.Mesh>(null);\n const startTimeRef = useRef<number>(0);\n const isInitializedRef = useRef(false);\n const [isTransitioning, setIsTransitioning] = useState(true);\n const [showName, setShowName] = useState(showNameOverlay);\n\n // Get colors and names\n const fromColor = useMemo(\n () => (fromStance ? getStanceColor(fromStance) : getStanceColor(toStance)),\n [fromStance, toStance]\n );\n const toColor = useMemo(() => getStanceColor(toStance), [toStance]);\n const stanceNames = useMemo(() => getStanceNames(toStance), [toStance]);\n\n // Handle transitions - external timer effect justifies useEffect\n\n useEffect(() => {\n // Reset for new transition\n isInitializedRef.current = false;\n startTimeRef.current = 0;\n // These setState calls are intentional - triggered by prop change, not creating infinite loops\n setIsTransitioning(true);\n setShowName(showNameOverlay);\n\n // External system: timer for name overlay\n if (showNameOverlay) {\n const nameTimer = setTimeout(() => {\n setShowName(false);\n }, 1000);\n\n return () => clearTimeout(nameTimer);\n }\n }, [toStance, showNameOverlay]);\n\n // Animation loop\n useFrame((state) => {\n if (!isTransitioning || !ringRef.current) return;\n\n // Initialize start time on first frame for consistency with clock\n if (!isInitializedRef.current) {\n startTimeRef.current = state.clock.elapsedTime;\n isInitializedRef.current = true;\n }\n\n const elapsed = state.clock.elapsedTime - startTimeRef.current;\n const progress = Math.min(elapsed / duration, 1.0);\n\n // Interpolate color\n const currentColor = colorUtils.blend(fromColor, toColor, progress);\n (ringRef.current.material as THREE.MeshBasicMaterial).color.setHex(\n currentColor\n );\n\n // Expand ring outward\n const scale = 0.5 + progress * 2.5; // From 0.5 to 3.0\n ringRef.current.scale.setScalar(scale);\n\n // Fade out as it expands\n const opacity = 1.0 - progress * 0.7; // From 1.0 to 0.3\n (ringRef.current.material as THREE.MeshBasicMaterial).opacity = opacity;\n\n // Complete transition\n if (progress >= 1.0) {\n setIsTransitioning(false);\n onTransitionComplete?.();\n }\n });\n\n // Convert color to hex string for CSS\n const toColorHex = `#${toColor.toString(16).padStart(6, \"0\")}`;\n\n return (\n <group name=\"stance-transition-effect\">\n {/* Expanding energy ring */}\n <mesh\n ref={ringRef}\n position={[0, 0.05, 0]}\n rotation={[-Math.PI / 2, 0, 0]}\n name=\"transition-ring\"\n >\n <ringGeometry args={[0.8, 1.0, 32]} />\n <meshBasicMaterial\n color={fromColor}\n transparent\n opacity={1.0}\n side={THREE.DoubleSide}\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </mesh>\n\n {/* Stance name overlay (Korean + English) */}\n {showName && (\n <Html\n position={[0, 2.0, 0]}\n center\n distanceFactor={10}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n data-testid=\"stance-name-overlay\"\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n padding: \"8px 16px\",\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n borderRadius: \"8px\",\n border: `2px solid ${toColorHex}`,\n boxShadow: `0 0 20px ${toColorHex}`,\n animation: \"fadeInOut 1s ease-in-out\",\n }}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: \"24px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: toColorHex,\n fontWeight: \"bold\",\n textShadow: `0 0 10px ${toColorHex}`,\n }}\n >\n {stanceNames.korean}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: toColorHex,\n fontWeight: \"normal\",\n opacity: 0.8,\n }}\n >\n {stanceNames.english}\n </div>\n </div>\n\n {/* CSS animation */}\n <style>\n {`\n @keyframes fadeInOut {\n 0% { opacity: 0; transform: translateY(10px); }\n 20% { opacity: 1; transform: translateY(0); }\n 80% { opacity: 1; transform: translateY(0); }\n 100% { opacity: 0; transform: translateY(-10px); }\n }\n `}\n </style>\n </Html>\n )}\n </group>\n );\n};\n\nexport default StanceTransitionEffect;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAa,0BAAiE,EAC5E,YACA,UACA,sBACA,WAAW,IACX,kBAAkB,WACd;CACJ,MAAM,UAAU,OAAmB,KAAK;CACxC,MAAM,eAAe,OAAe,EAAE;CACtC,MAAM,mBAAmB,OAAO,MAAM;CACtC,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,KAAK;CAC5D,MAAM,CAAC,UAAU,eAAe,SAAS,gBAAgB;CAGzD,MAAM,YAAY,cACT,aAAa,eAAe,WAAW,GAAG,eAAe,SAAS,EACzE,CAAC,YAAY,SAAS,CACvB;CACD,MAAM,UAAU,cAAc,eAAe,SAAS,EAAE,CAAC,SAAS,CAAC;CACnE,MAAM,cAAc,cAAc,eAAe,SAAS,EAAE,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"StanceTransitionEffect.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceTransitionEffect.tsx"],"sourcesContent":["/**\n * StanceTransitionEffect - Smooth visual transition between trigram stances\n *\n * Manages the visual transition when a player changes stance, providing:\n * - 0.5s smooth color fade between old and new stance colors\n * - Expanding energy ring effect\n * - Bilingual stance name display (Korean + English) for 1s\n * - Audio synchronization for stance change SFX\n *\n * @module components/three/StanceTransitionEffect\n * @category 3D Components\n * @korean 자세전환효과\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { colorUtils } from \"../../../../types/constants/colors\";\nimport {\n getStanceColor,\n getStanceNames,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceTransitionEffect component\n */\nexport interface StanceTransitionEffectProps {\n /** Previous stance (for color interpolation) */\n readonly fromStance: TrigramStance | null;\n /** New stance being transitioned to */\n readonly toStance: TrigramStance;\n /** Callback when transition completes */\n readonly onTransitionComplete?: () => void;\n /** Transition duration in seconds (default: 0.5) */\n readonly duration?: number;\n /** Show stance name overlay (default: true) */\n readonly showNameOverlay?: boolean;\n}\n\n/**\n * StanceTransitionEffect Component\n *\n * Provides smooth visual feedback during stance changes:\n * 1. Expanding energy ring effect from player center\n * 2. Color interpolation from old to new stance\n * 3. Bilingual stance name overlay (1 second display)\n *\n * Performance optimized:\n * - Single animation frame callback\n * - Auto-cleanup after transition completes\n * - Reuses Three.js materials and geometries\n *\n * @example\n * ```tsx\n * <StanceTransitionEffect\n * fromStance={TrigramStance.GEON}\n * toStance={TrigramStance.TAE}\n * onTransitionComplete={() => console.log('Transition done')}\n * duration={0.5}\n * />\n * ```\n */\nexport const StanceTransitionEffect: React.FC<StanceTransitionEffectProps> = ({\n fromStance,\n toStance,\n onTransitionComplete,\n duration = 0.5,\n showNameOverlay = true,\n}) => {\n const ringRef = useRef<THREE.Mesh>(null);\n const startTimeRef = useRef<number>(0);\n const isInitializedRef = useRef(false);\n const [isTransitioning, setIsTransitioning] = useState(true);\n const [showName, setShowName] = useState(showNameOverlay);\n\n // Get colors and names\n const fromColor = useMemo(\n () => (fromStance ? getStanceColor(fromStance) : getStanceColor(toStance)),\n [fromStance, toStance]\n );\n const toColor = useMemo(() => getStanceColor(toStance), [toStance]);\n const stanceNames = useMemo(() => getStanceNames(toStance), [toStance]);\n\n // Handle transitions - external timer effect justifies useEffect\n\n useEffect(() => {\n // Reset for new transition\n isInitializedRef.current = false;\n startTimeRef.current = 0;\n // These setState calls are intentional - triggered by prop change, not creating infinite loops\n setIsTransitioning(true);\n setShowName(showNameOverlay);\n\n // External system: timer for name overlay\n if (showNameOverlay) {\n const nameTimer = setTimeout(() => {\n setShowName(false);\n }, 1000);\n\n return () => clearTimeout(nameTimer);\n }\n }, [toStance, showNameOverlay]);\n\n // Animation loop\n useFrame((state) => {\n if (!isTransitioning || !ringRef.current) return;\n\n // Initialize start time on first frame for consistency with clock\n if (!isInitializedRef.current) {\n startTimeRef.current = state.clock.elapsedTime;\n isInitializedRef.current = true;\n }\n\n const elapsed = state.clock.elapsedTime - startTimeRef.current;\n const progress = Math.min(elapsed / duration, 1.0);\n\n // Interpolate color\n const currentColor = colorUtils.blend(fromColor, toColor, progress);\n (ringRef.current.material as THREE.MeshBasicMaterial).color.setHex(\n currentColor\n );\n\n // Expand ring outward\n const scale = 0.5 + progress * 2.5; // From 0.5 to 3.0\n ringRef.current.scale.setScalar(scale);\n\n // Fade out as it expands\n const opacity = 1.0 - progress * 0.7; // From 1.0 to 0.3\n (ringRef.current.material as THREE.MeshBasicMaterial).opacity = opacity;\n\n // Complete transition\n if (progress >= 1.0) {\n setIsTransitioning(false);\n onTransitionComplete?.();\n }\n });\n\n // Convert color to hex string for CSS\n const toColorHex = `#${toColor.toString(16).padStart(6, \"0\")}`;\n\n return (\n <group name=\"stance-transition-effect\">\n {/* Expanding energy ring */}\n <mesh\n ref={ringRef}\n position={[0, 0.05, 0]}\n rotation={[-Math.PI / 2, 0, 0]}\n name=\"transition-ring\"\n >\n <ringGeometry args={[0.8, 1.0, 32]} />\n <meshBasicMaterial\n color={fromColor}\n transparent\n opacity={1.0}\n side={THREE.DoubleSide}\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </mesh>\n\n {/* Stance name overlay (Korean + English) */}\n {showName && (\n <Html\n position={[0, 2.0, 0]}\n center\n distanceFactor={10}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n data-testid=\"stance-name-overlay\"\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n padding: \"8px 16px\",\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n borderRadius: \"8px\",\n border: `2px solid ${toColorHex}`,\n boxShadow: `0 0 20px ${toColorHex}`,\n animation: \"fadeInOut 1s ease-in-out\",\n }}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: \"24px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: toColorHex,\n fontWeight: \"bold\",\n textShadow: `0 0 10px ${toColorHex}`,\n }}\n >\n {stanceNames.korean}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: toColorHex,\n fontWeight: \"normal\",\n opacity: 0.8,\n }}\n >\n {stanceNames.english}\n </div>\n </div>\n\n {/* CSS animation */}\n <style>\n {`\n @keyframes fadeInOut {\n 0% { opacity: 0; transform: translateY(10px); }\n 20% { opacity: 1; transform: translateY(0); }\n 80% { opacity: 1; transform: translateY(0); }\n 100% { opacity: 0; transform: translateY(-10px); }\n }\n `}\n </style>\n </Html>\n )}\n </group>\n );\n};\n\nexport default StanceTransitionEffect;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAa,0BAAiE,EAC5E,YACA,UACA,sBACA,WAAW,IACX,kBAAkB,WACd;CACJ,MAAM,UAAU,OAAmB,KAAK;CACxC,MAAM,eAAe,OAAe,EAAE;CACtC,MAAM,mBAAmB,OAAO,MAAM;CACtC,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,KAAK;CAC5D,MAAM,CAAC,UAAU,eAAe,SAAS,gBAAgB;CAGzD,MAAM,YAAY,cACT,aAAa,eAAe,WAAW,GAAG,eAAe,SAAS,EACzE,CAAC,YAAY,SAAS,CACvB;CACD,MAAM,UAAU,cAAc,eAAe,SAAS,EAAE,CAAC,SAAS,CAAC;CACnE,MAAM,cAAc,cAAc,eAAe,SAAS,EAAE,CAAC,SAAS,CAAC;CAIvE,gBAAgB;EAEd,iBAAiB,UAAU;EAC3B,aAAa,UAAU;EAEvB,mBAAmB,KAAK;EACxB,YAAY,gBAAgB;EAG5B,IAAI,iBAAiB;GACnB,MAAM,YAAY,iBAAiB;IACjC,YAAY,MAAM;MACjB,IAAK;GAER,aAAa,aAAa,UAAU;;IAErC,CAAC,UAAU,gBAAgB,CAAC;CAG/B,UAAU,UAAU;EAClB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,SAAS;EAG1C,IAAI,CAAC,iBAAiB,SAAS;GAC7B,aAAa,UAAU,MAAM,MAAM;GACnC,iBAAiB,UAAU;;EAG7B,MAAM,UAAU,MAAM,MAAM,cAAc,aAAa;EACvD,MAAM,WAAW,KAAK,IAAI,UAAU,UAAU,EAAI;EAGlD,MAAM,eAAe,WAAW,MAAM,WAAW,SAAS,SAAS;EACnE,QAAS,QAAQ,SAAqC,MAAM,OAC1D,aACD;EAGD,MAAM,QAAQ,KAAM,WAAW;EAC/B,QAAQ,QAAQ,MAAM,UAAU,MAAM;EAGtC,MAAM,UAAU,IAAM,WAAW;EACjC,QAAS,QAAQ,SAAqC,UAAU;EAGhE,IAAI,YAAY,GAAK;GACnB,mBAAmB,MAAM;GACzB,wBAAwB;;GAE1B;CAGF,MAAM,aAAa,IAAI,QAAQ,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;CAE5D,OACE,qBAAC,SAAD;EAAO,MAAK;YAAZ,CAEE,qBAAC,QAAD;GACE,KAAK;GACL,UAAU;IAAC;IAAG;IAAM;IAAE;GACtB,UAAU;IAAC,CAAC,KAAK,KAAK;IAAG;IAAG;IAAE;GAC9B,MAAK;aAJP,CAME,oBAAC,gBAAD,EAAc,MAAM;IAAC;IAAK;IAAK;IAAG,EAAI,CAAA,EACtC,oBAAC,qBAAD;IACE,OAAO;IACP,aAAA;IACA,SAAS;IACT,MAAM,MAAM;IACZ,YAAY;IACZ,UAAU,MAAM;IAChB,CAAA,CACG;MAGN,YACC,qBAAC,MAAD;GACE,UAAU;IAAC;IAAG;IAAK;IAAE;GACrB,QAAA;GACA,gBAAgB;GAChB,OAAO;IACL,eAAe;IACf,YAAY;IACb;GACD,eAAY;aARd,CAUE,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,KAAK;KACL,SAAS;KACT,iBAAiB;KACjB,cAAc;KACd,QAAQ,aAAa;KACrB,WAAW,YAAY;KACvB,WAAW;KACZ;cAZH,CAeE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU;MACV,YAAY,YAAY;MACxB,OAAO;MACP,YAAY;MACZ,YAAY,YAAY;MACzB;eAEA,YAAY;KACT,CAAA,EAGN,oBAAC,OAAD;KACE,OAAO;MACL,UAAU;MACV,YAAY,YAAY;MACxB,OAAO;MACP,YAAY;MACZ,SAAS;MACV;eAEA,YAAY;KACT,CAAA,CACF;OAGN,oBAAC,SAAD,EAAA,UACG;;;;;;;eAQK,CAAA,CACH;KAEH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointMarkers3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/VitalPointMarkers3D.tsx"],"sourcesContent":["/**\n * VitalPointMarkers3D - 3D vital point visualization\n *\n * Renders anatomical vital points (급소) in 3D space around character models\n * Provides Korean martial arts targeting system visualization\n * Note: Currently displays points from KOREAN_VITAL_POINTS data (expandable to 70 points)\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPoint } from \"../../../../systems/vitalpoint/types\";\nimport { Position, VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Body region filter options\n */\nexport type BodyRegionFilter = \"all\" | \"head\" | \"torso\" | \"arms\" | \"legs\";\n\n/**\n * Props for the VitalPointMarkers3D component.\n * Controls visibility and interaction with anatomical targeting points.\n */\nexport interface VitalPointMarkers3DProps {\n /** 3D world position of the character [x, y, z]. Defaults to [0, 0, 0] */\n readonly position?: [number, number, number];\n /** Whether markers are visible. Defaults to true */\n readonly visible?: boolean;\n /** Selected vital point ID for highlighting */\n readonly selectedPoint?: string | null;\n /** Callback when a vital point is clicked */\n readonly onPointClick?: (vitalPointId: string) => void;\n /** Callback when a vital point is hovered */\n readonly onPointHover?: (vitalPointId: string | null) => void;\n /** Filter points by severity level */\n readonly severityFilter?: VitalPointSeverity[];\n /** Filter points by body region */\n readonly regionFilter?: BodyRegionFilter;\n /** Search query to filter points by name */\n readonly searchQuery?: string;\n /** Whether to show point labels with Korean names */\n readonly showLabels?: boolean;\n /** Scale multiplier for marker size. Defaults to 1.0 */\n readonly scale?: number;\n /** Whether to enable pulsing animation. Defaults to true */\n readonly animated?: boolean;\n}\n\n/**\n * Get color based on vital point severity\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return KOREAN_COLORS.ACCENT_RED;\n case VitalPointSeverity.CRITICAL:\n return KOREAN_COLORS.SECONDARY_MAGENTA;\n case VitalPointSeverity.MAJOR:\n return KOREAN_COLORS.ACCENT_GOLD;\n case VitalPointSeverity.MODERATE:\n return KOREAN_COLORS.SECONDARY_YELLOW;\n case VitalPointSeverity.MINOR:\n return KOREAN_COLORS.ACCENT_CYAN;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n};\n\n// Coordinate conversion constants\n// The vital point data uses pixel coordinates where:\n// - X: ~50-150 range, centered around 100 (character center)\n// - Y: 0 at top of head, ~200 at feet\n// The 3D character is:\n// - X: centered at 0, width ~0.8 units\n// - Y: 0 at feet, ~2.8 at top of head\nconst CHARACTER_CENTER_X = 100; // Pixel center of character sprite\nconst CHARACTER_SPRITE_HEIGHT = 200; // Pixel height of character sprite\nconst CHARACTER_3D_HEIGHT = 2.8; // 3D model height in world units\nconst CHARACTER_3D_WIDTH = 0.8; // Approximate 3D model width\nconst SPRITE_WIDTH = 100; // Half-width of sprite (total ~200 pixels)\n\n// Label styling constants\nconst LABEL_STYLES = {\n padding: \"4px 8px\",\n borderRadius: \"4px\",\n fontSize: \"10px\",\n subtextSize: \"8px\",\n subtextOpacity: 0.8,\n borderWidth: \"1px\",\n} as const;\n\n/**\n * Convert color number to RGBA hex string\n * @param color - Color as number (e.g., 0xFF0000)\n * @param alpha - Alpha channel as hex string (e.g., \"dd\" for semi-transparent)\n * @returns RGBA hex string (e.g., \"#ff0000dd\")\n */\nconst colorToRgbaHex = (color: number, alpha: string = \"ff\"): string => {\n return `#${color.toString(16).padStart(6, \"0\")}${alpha}`;\n};\n\n/**\n * Convert 2D sprite coordinates to 3D body position\n * Maps vital point pixel coordinates to 3D character model space\n *\n * Input: 2D pixel coordinates where:\n * - X: ~50-150 (100 = center), positive = right side of character\n * - Y: 0 = top of head, 200 = feet (y increases downward)\n *\n * Output: 3D world coordinates relative to character position where:\n * - X: horizontal offset from character center\n * - Y: height from ground (0 = feet, 2.8 = top)\n * - Z: depth offset (positive = in front of character)\n */\nconst convert2DTo3D = (\n pos2D: Position,\n basePosition: [number, number, number]\n): [number, number, number] => {\n // Convert X from pixel (100=center) to 3D offset\n // Map from [50, 150] to [-0.4, 0.4] approximately\n const normalizedX = (pos2D.x - CHARACTER_CENTER_X) / SPRITE_WIDTH;\n const x3D = normalizedX * CHARACTER_3D_WIDTH;\n\n // Convert Y from pixel (0=top, 200=bottom) to 3D height (0=feet, 2.8=head)\n // Invert Y axis and scale to 3D height\n const normalizedY = 1 - pos2D.y / CHARACTER_SPRITE_HEIGHT;\n const y3D = normalizedY * CHARACTER_3D_HEIGHT;\n\n // Z offset - slight forward position for visibility\n const z3D = 0.15;\n\n return [basePosition[0] + x3D, basePosition[1] + y3D, basePosition[2] + z3D];\n};\n\n/**\n * Individual Vital Point Marker Component\n */\ninterface VitalPointMarkerProps {\n readonly vitalPoint: VitalPoint;\n readonly position3D: [number, number, number];\n readonly selected: boolean;\n readonly hovered: boolean;\n readonly showLabel: boolean;\n readonly scale: number;\n readonly animated: boolean;\n readonly onHover: (hovered: boolean) => void;\n readonly onClick: () => void;\n}\n\nconst VitalPointMarker: React.FC<VitalPointMarkerProps> = ({\n vitalPoint,\n position3D,\n selected,\n hovered,\n showLabel,\n scale,\n animated,\n onHover,\n onClick,\n}) => {\n const sphereRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n // Animate marker\n useFrame((state) => {\n if (!sphereRef.current || !animated) return;\n\n // Pulsing animation\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.1 + 1;\n sphereRef.current.scale.setScalar(pulse * scale);\n\n // Rotate ring for selected/hovered\n if (ringRef.current && (selected || hovered)) {\n ringRef.current.rotation.z += 0.05;\n }\n });\n\n const color = useMemo(() => {\n if (selected) return KOREAN_COLORS.ACCENT_GOLD;\n if (hovered) return KOREAN_COLORS.PRIMARY_CYAN;\n return getSeverityColor(vitalPoint.severity);\n }, [selected, hovered, vitalPoint.severity]);\n\n const markerSize = useMemo(() => {\n // Base marker size and severity multipliers\n // Increased base size for better visibility in combat\n const DEFAULT_MARKER_SIZE = 0.08;\n\n switch (vitalPoint.severity) {\n case VitalPointSeverity.LETHAL:\n case VitalPointSeverity.CRITICAL:\n return DEFAULT_MARKER_SIZE * 1.6 * scale; // 0.128\n case VitalPointSeverity.MAJOR:\n return DEFAULT_MARKER_SIZE * 1.3 * scale; // 0.104\n case VitalPointSeverity.MODERATE:\n return DEFAULT_MARKER_SIZE * 1.0 * scale; // 0.08\n case VitalPointSeverity.MINOR:\n return DEFAULT_MARKER_SIZE * 0.8 * scale; // 0.064\n default:\n return DEFAULT_MARKER_SIZE * scale;\n }\n }, [vitalPoint.severity, scale]);\n\n return (\n <group position={position3D}>\n {/* Main sphere marker */}\n <mesh\n ref={sphereRef}\n onPointerOver={() => onHover(true)}\n onPointerOut={() => onHover(false)}\n onClick={(e) => {\n e.stopPropagation();\n onClick();\n }}\n >\n <sphereGeometry args={[markerSize, 16, 16]} />\n <meshStandardMaterial\n color={color}\n emissive={color}\n emissiveIntensity={hovered || selected ? 0.8 : 0.4}\n transparent\n opacity={hovered || selected ? 1.0 : 0.85}\n />\n </mesh>\n\n {/* Outer ring for selected/hovered */}\n {(selected || hovered) && (\n <mesh ref={ringRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry args={[markerSize * 1.5, markerSize * 1.8, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={0.5}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n\n {/* Label overlay */}\n {showLabel && (hovered || selected) && (\n <Html\n position={[0, markerSize * 2, 0]}\n center\n distanceFactor={5}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n background: colorToRgbaHex(color, \"dd\"),\n color: \"#ffffff\",\n padding: LABEL_STYLES.padding,\n borderRadius: LABEL_STYLES.borderRadius,\n fontSize: LABEL_STYLES.fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n whiteSpace: \"nowrap\",\n textAlign: \"center\",\n border: `${LABEL_STYLES.borderWidth} solid ${colorToRgbaHex(\n color\n )}`,\n }}\n >\n <div>{vitalPoint.names.korean}</div>\n <div\n style={{\n fontSize: LABEL_STYLES.subtextSize,\n opacity: LABEL_STYLES.subtextOpacity,\n }}\n >\n {vitalPoint.names.english}\n </div>\n </div>\n </Html>\n )}\n </group>\n );\n};\n\n/**\n * VitalPointMarkers3D Component\n * Renders all vital points as 3D markers around a character\n */\nexport const VitalPointMarkers3D: React.FC<VitalPointMarkers3DProps> = ({\n position = [0, 0, 0],\n visible = true,\n selectedPoint = null,\n onPointClick,\n onPointHover,\n severityFilter,\n regionFilter = \"all\",\n searchQuery = \"\",\n showLabels = true,\n scale = 1.0,\n animated = true,\n}) => {\n const [hoveredPoint, setHoveredPoint] = useState<string | null>(null);\n\n // Filter vital points based on severity, region, and search\n const visiblePoints = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilter && severityFilter.length > 0) {\n points = points.filter((vp) => severityFilter.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points;\n }, [severityFilter, regionFilter, searchQuery]);\n\n // Handle point hover\n const handlePointHover = (vitalPointId: string, hovered: boolean) => {\n const newHovered = hovered ? vitalPointId : null;\n setHoveredPoint(newHovered);\n onPointHover?.(newHovered);\n };\n\n // Handle point click\n const handlePointClick = (vitalPointId: string) => {\n onPointClick?.(vitalPointId);\n };\n\n if (!visible) return null;\n\n return (\n <group position={position}>\n {visiblePoints.map((vitalPoint) => {\n const position3D = convert2DTo3D(vitalPoint.position, [0, 0, 0]);\n\n return (\n <VitalPointMarker\n key={vitalPoint.id}\n vitalPoint={vitalPoint}\n position3D={position3D}\n selected={selectedPoint === vitalPoint.id}\n hovered={hoveredPoint === vitalPoint.id}\n showLabel={showLabels}\n scale={scale}\n animated={animated}\n onHover={(hovered) => handlePointHover(vitalPoint.id, hovered)}\n onClick={() => handlePointClick(vitalPoint.id)}\n />\n );\n })}\n </group>\n );\n};\n\nexport default VitalPointMarkers3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VitalPointMarkers3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/VitalPointMarkers3D.tsx"],"sourcesContent":["/**\n * VitalPointMarkers3D - 3D vital point visualization\n *\n * Renders anatomical vital points (급소) in 3D space around character models\n * Provides Korean martial arts targeting system visualization\n * Note: Currently displays points from KOREAN_VITAL_POINTS data (expandable to 70 points)\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPoint } from \"../../../../systems/vitalpoint/types\";\nimport { Position, VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Body region filter options\n */\nexport type BodyRegionFilter = \"all\" | \"head\" | \"torso\" | \"arms\" | \"legs\";\n\n/**\n * Props for the VitalPointMarkers3D component.\n * Controls visibility and interaction with anatomical targeting points.\n */\nexport interface VitalPointMarkers3DProps {\n /** 3D world position of the character [x, y, z]. Defaults to [0, 0, 0] */\n readonly position?: [number, number, number];\n /** Whether markers are visible. Defaults to true */\n readonly visible?: boolean;\n /** Selected vital point ID for highlighting */\n readonly selectedPoint?: string | null;\n /** Callback when a vital point is clicked */\n readonly onPointClick?: (vitalPointId: string) => void;\n /** Callback when a vital point is hovered */\n readonly onPointHover?: (vitalPointId: string | null) => void;\n /** Filter points by severity level */\n readonly severityFilter?: VitalPointSeverity[];\n /** Filter points by body region */\n readonly regionFilter?: BodyRegionFilter;\n /** Search query to filter points by name */\n readonly searchQuery?: string;\n /** Whether to show point labels with Korean names */\n readonly showLabels?: boolean;\n /** Scale multiplier for marker size. Defaults to 1.0 */\n readonly scale?: number;\n /** Whether to enable pulsing animation. Defaults to true */\n readonly animated?: boolean;\n}\n\n/**\n * Get color based on vital point severity\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return KOREAN_COLORS.ACCENT_RED;\n case VitalPointSeverity.CRITICAL:\n return KOREAN_COLORS.SECONDARY_MAGENTA;\n case VitalPointSeverity.MAJOR:\n return KOREAN_COLORS.ACCENT_GOLD;\n case VitalPointSeverity.MODERATE:\n return KOREAN_COLORS.SECONDARY_YELLOW;\n case VitalPointSeverity.MINOR:\n return KOREAN_COLORS.ACCENT_CYAN;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n};\n\n// Coordinate conversion constants\n// The vital point data uses pixel coordinates where:\n// - X: ~50-150 range, centered around 100 (character center)\n// - Y: 0 at top of head, ~200 at feet\n// The 3D character is:\n// - X: centered at 0, width ~0.8 units\n// - Y: 0 at feet, ~2.8 at top of head\nconst CHARACTER_CENTER_X = 100; // Pixel center of character sprite\nconst CHARACTER_SPRITE_HEIGHT = 200; // Pixel height of character sprite\nconst CHARACTER_3D_HEIGHT = 2.8; // 3D model height in world units\nconst CHARACTER_3D_WIDTH = 0.8; // Approximate 3D model width\nconst SPRITE_WIDTH = 100; // Half-width of sprite (total ~200 pixels)\n\n// Label styling constants\nconst LABEL_STYLES = {\n padding: \"4px 8px\",\n borderRadius: \"4px\",\n fontSize: \"10px\",\n subtextSize: \"8px\",\n subtextOpacity: 0.8,\n borderWidth: \"1px\",\n} as const;\n\n/**\n * Convert color number to RGBA hex string\n * @param color - Color as number (e.g., 0xFF0000)\n * @param alpha - Alpha channel as hex string (e.g., \"dd\" for semi-transparent)\n * @returns RGBA hex string (e.g., \"#ff0000dd\")\n */\nconst colorToRgbaHex = (color: number, alpha: string = \"ff\"): string => {\n return `#${color.toString(16).padStart(6, \"0\")}${alpha}`;\n};\n\n/**\n * Convert 2D sprite coordinates to 3D body position\n * Maps vital point pixel coordinates to 3D character model space\n *\n * Input: 2D pixel coordinates where:\n * - X: ~50-150 (100 = center), positive = right side of character\n * - Y: 0 = top of head, 200 = feet (y increases downward)\n *\n * Output: 3D world coordinates relative to character position where:\n * - X: horizontal offset from character center\n * - Y: height from ground (0 = feet, 2.8 = top)\n * - Z: depth offset (positive = in front of character)\n */\nconst convert2DTo3D = (\n pos2D: Position,\n basePosition: [number, number, number]\n): [number, number, number] => {\n // Convert X from pixel (100=center) to 3D offset\n // Map from [50, 150] to [-0.4, 0.4] approximately\n const normalizedX = (pos2D.x - CHARACTER_CENTER_X) / SPRITE_WIDTH;\n const x3D = normalizedX * CHARACTER_3D_WIDTH;\n\n // Convert Y from pixel (0=top, 200=bottom) to 3D height (0=feet, 2.8=head)\n // Invert Y axis and scale to 3D height\n const normalizedY = 1 - pos2D.y / CHARACTER_SPRITE_HEIGHT;\n const y3D = normalizedY * CHARACTER_3D_HEIGHT;\n\n // Z offset - slight forward position for visibility\n const z3D = 0.15;\n\n return [basePosition[0] + x3D, basePosition[1] + y3D, basePosition[2] + z3D];\n};\n\n/**\n * Individual Vital Point Marker Component\n */\ninterface VitalPointMarkerProps {\n readonly vitalPoint: VitalPoint;\n readonly position3D: [number, number, number];\n readonly selected: boolean;\n readonly hovered: boolean;\n readonly showLabel: boolean;\n readonly scale: number;\n readonly animated: boolean;\n readonly onHover: (hovered: boolean) => void;\n readonly onClick: () => void;\n}\n\nconst VitalPointMarker: React.FC<VitalPointMarkerProps> = ({\n vitalPoint,\n position3D,\n selected,\n hovered,\n showLabel,\n scale,\n animated,\n onHover,\n onClick,\n}) => {\n const sphereRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n // Animate marker\n useFrame((state) => {\n if (!sphereRef.current || !animated) return;\n\n // Pulsing animation\n const pulse = Math.sin(state.clock.elapsedTime * 2) * 0.1 + 1;\n sphereRef.current.scale.setScalar(pulse * scale);\n\n // Rotate ring for selected/hovered\n if (ringRef.current && (selected || hovered)) {\n ringRef.current.rotation.z += 0.05;\n }\n });\n\n const color = useMemo(() => {\n if (selected) return KOREAN_COLORS.ACCENT_GOLD;\n if (hovered) return KOREAN_COLORS.PRIMARY_CYAN;\n return getSeverityColor(vitalPoint.severity);\n }, [selected, hovered, vitalPoint.severity]);\n\n const markerSize = useMemo(() => {\n // Base marker size and severity multipliers\n // Increased base size for better visibility in combat\n const DEFAULT_MARKER_SIZE = 0.08;\n\n switch (vitalPoint.severity) {\n case VitalPointSeverity.LETHAL:\n case VitalPointSeverity.CRITICAL:\n return DEFAULT_MARKER_SIZE * 1.6 * scale; // 0.128\n case VitalPointSeverity.MAJOR:\n return DEFAULT_MARKER_SIZE * 1.3 * scale; // 0.104\n case VitalPointSeverity.MODERATE:\n return DEFAULT_MARKER_SIZE * 1.0 * scale; // 0.08\n case VitalPointSeverity.MINOR:\n return DEFAULT_MARKER_SIZE * 0.8 * scale; // 0.064\n default:\n return DEFAULT_MARKER_SIZE * scale;\n }\n }, [vitalPoint.severity, scale]);\n\n return (\n <group position={position3D}>\n {/* Main sphere marker */}\n <mesh\n ref={sphereRef}\n onPointerOver={() => onHover(true)}\n onPointerOut={() => onHover(false)}\n onClick={(e) => {\n e.stopPropagation();\n onClick();\n }}\n >\n <sphereGeometry args={[markerSize, 16, 16]} />\n <meshStandardMaterial\n color={color}\n emissive={color}\n emissiveIntensity={hovered || selected ? 0.8 : 0.4}\n transparent\n opacity={hovered || selected ? 1.0 : 0.85}\n />\n </mesh>\n\n {/* Outer ring for selected/hovered */}\n {(selected || hovered) && (\n <mesh ref={ringRef} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry args={[markerSize * 1.5, markerSize * 1.8, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={0.5}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n\n {/* Label overlay */}\n {showLabel && (hovered || selected) && (\n <Html\n position={[0, markerSize * 2, 0]}\n center\n distanceFactor={5}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n background: colorToRgbaHex(color, \"dd\"),\n color: \"#ffffff\",\n padding: LABEL_STYLES.padding,\n borderRadius: LABEL_STYLES.borderRadius,\n fontSize: LABEL_STYLES.fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n whiteSpace: \"nowrap\",\n textAlign: \"center\",\n border: `${LABEL_STYLES.borderWidth} solid ${colorToRgbaHex(\n color\n )}`,\n }}\n >\n <div>{vitalPoint.names.korean}</div>\n <div\n style={{\n fontSize: LABEL_STYLES.subtextSize,\n opacity: LABEL_STYLES.subtextOpacity,\n }}\n >\n {vitalPoint.names.english}\n </div>\n </div>\n </Html>\n )}\n </group>\n );\n};\n\n/**\n * VitalPointMarkers3D Component\n * Renders all vital points as 3D markers around a character\n */\nexport const VitalPointMarkers3D: React.FC<VitalPointMarkers3DProps> = ({\n position = [0, 0, 0],\n visible = true,\n selectedPoint = null,\n onPointClick,\n onPointHover,\n severityFilter,\n regionFilter = \"all\",\n searchQuery = \"\",\n showLabels = true,\n scale = 1.0,\n animated = true,\n}) => {\n const [hoveredPoint, setHoveredPoint] = useState<string | null>(null);\n\n // Filter vital points based on severity, region, and search\n const visiblePoints = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilter && severityFilter.length > 0) {\n points = points.filter((vp) => severityFilter.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points;\n }, [severityFilter, regionFilter, searchQuery]);\n\n // Handle point hover\n const handlePointHover = (vitalPointId: string, hovered: boolean) => {\n const newHovered = hovered ? vitalPointId : null;\n setHoveredPoint(newHovered);\n onPointHover?.(newHovered);\n };\n\n // Handle point click\n const handlePointClick = (vitalPointId: string) => {\n onPointClick?.(vitalPointId);\n };\n\n if (!visible) return null;\n\n return (\n <group position={position}>\n {visiblePoints.map((vitalPoint) => {\n const position3D = convert2DTo3D(vitalPoint.position, [0, 0, 0]);\n\n return (\n <VitalPointMarker\n key={vitalPoint.id}\n vitalPoint={vitalPoint}\n position3D={position3D}\n selected={selectedPoint === vitalPoint.id}\n hovered={hoveredPoint === vitalPoint.id}\n showLabel={showLabels}\n scale={scale}\n animated={animated}\n onHover={(hovered) => handlePointHover(vitalPoint.id, hovered)}\n onClick={() => handlePointClick(vitalPoint.id)}\n />\n );\n })}\n </group>\n );\n};\n\nexport default VitalPointMarkers3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElementalColorSystem.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/ElementalColorSystem.ts"],"sourcesContent":["/**\n * Elemental Color System for Korean Trigram Stances\n * Based on 오행 (Wu Xing / Five Elements) philosophy\n * \n * Maps each of the eight trigram stances to one of the five elements,\n * providing color-coded visual feedback for combat and training.\n * \n * Note: This module provides the correct element mappings based on traditional\n * Bagua philosophy. The element property in TRIGRAM_DATA currently uses placeholder\n * \"metal\" values and should be updated to use this system.\n * \n * @module components/shared/three/indicators/ElementalColorSystem\n * @category Combat UI\n * @korean 오행색체계\n */\n\nimport { TrigramStance } from \"../../../../types/common\";\nimport { TRIGRAM_DATA } from \"../../../../systems/trigram/types\";\n\n/**\n * Five Elements (오행) color palette\n * \n * Based on traditional Korean/Chinese five element theory:\n * - Wood (木): Growth, expansion - Green tones\n * - Fire (火): Energy, passion - Red/Orange tones \n * - Earth (土): Stability, grounding - Yellow/Brown tones\n * - Metal (金): Precision, strength - White/Gold tones\n * - Water (水): Flow, adaptation - Blue/Cyan tones\n * \n * @korean 오행색상\n */\nexport const ELEMENT_COLORS = {\n /** Wood element - Growth and expansion (木) */\n wood: 0x228b22, // Forest green\n /** Fire element - Energy and passion (火) */\n fire: 0xff4444, // Bright red\n /** Earth element - Stability and grounding (土) */\n earth: 0xffd700, // Gold/Yellow\n /** Metal element - Precision and strength (金) */\n metal: 0xffffff, // White/Silver\n /** Water element - Flow and adaptation (水) */\n water: 0x00ffff, // Cyan\n} as const;\n\n/**\n * Element type from the five elements system\n * @korean 원소타입\n */\nexport type Element = keyof typeof ELEMENT_COLORS;\n\n/**\n * Mapping of trigram stances to five elements\n * \n * Based on traditional Bagua (八卦) associations:\n * - Geon (Heaven) ☰: Metal - Represents strength and clarity\n * - Tae (Lake) ☱: Metal - Represents joy and openness\n * - Li (Fire) ☲: Fire - Represents illumination and awareness\n * - Jin (Thunder) ☳: Wood - Represents movement and initiative\n * - Son (Wind) ☴: Wood - Represents gentleness and penetration\n * - Gam (Water) ☵: Water - Represents depth and danger\n * - Gan (Mountain) ☶: Earth - Represents stillness and rest\n * - Gon (Earth) ☷: Earth - Represents receptivity and nourishment\n * \n * @korean 팔괘원소대응\n */\nexport const TRIGRAM_TO_ELEMENT: Record<TrigramStance, Element> = {\n [TrigramStance.GEON]: \"metal\", // ☰ Heaven (건)\n [TrigramStance.TAE]: \"metal\", // ☱ Lake (태)\n [TrigramStance.LI]: \"fire\", // ☲ Fire (리)\n [TrigramStance.JIN]: \"wood\", // ☳ Thunder (진)\n [TrigramStance.SON]: \"wood\", // ☴ Wind (손)\n [TrigramStance.GAM]: \"water\", // ☵ Water (감)\n [TrigramStance.GAN]: \"earth\", // ☶ Mountain (간)\n [TrigramStance.GON]: \"earth\", // ☷ Earth (곤)\n};\n\n\n\n/**\n * Get the elemental color for a trigram stance\n * \n * Returns the hexadecimal color value associated with the stance's element.\n * \n * @param stance - The trigram stance to get the color for\n * @returns Hexadecimal color value (e.g., 0x00ffff)\n * \n * @example\n * ```typescript\n * const color = getTrigramElementColor(TrigramStance.GEON);\n * // Returns 0xffffff (white/metal)\n * ```\n * \n * @public\n * @korean 팔괘원소색상얻기\n */\nexport function getTrigramElementColor(stance: TrigramStance): number {\n const element = TRIGRAM_TO_ELEMENT[stance];\n return ELEMENT_COLORS[element];\n}\n\n/**\n * Get the Unicode symbol for a trigram stance\n * \n * Returns the Unicode trigram symbol character (☰☱☲☳☴☵☶☷).\n * Uses the symbol from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the symbol for\n * @returns Unicode trigram symbol\n * \n * @example\n * ```typescript\n * const symbol = getTrigramSymbol(TrigramStance.GEON);\n * // Returns \"☰\" (Heaven)\n * ```\n * \n * @public\n * @korean 팔괘기호얻기\n */\nexport function getTrigramSymbol(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].symbol;\n}\n\n/**\n * Get the element associated with a trigram stance\n * \n * @param stance - The trigram stance to get the element for\n * @returns Element name (wood, fire, earth, metal, water)\n * \n * @example\n * ```typescript\n * const element = getTrigramElement(TrigramStance.LI);\n * // Returns \"fire\"\n * ```\n * \n * @public\n * @korean 팔괘원소얻기\n */\nexport function getTrigramElement(stance: TrigramStance): Element {\n return TRIGRAM_TO_ELEMENT[stance];\n}\n\n/**\n * Get Korean name for a trigram stance\n * \n * Uses the Korean name from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the name for\n * @returns Korean name (e.g., \"건\" for Heaven)\n * \n * @example\n * ```typescript\n * const koreanName = getTrigramKoreanName(TrigramStance.GEON);\n * // Returns \"건\"\n * ```\n * \n * @public\n * @korean 팔괘한글명얻기\n */\nexport function getTrigramKoreanName(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].name.korean;\n}\n\n/**\n * Get English name for a trigram stance\n * \n * Uses the English name from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the name for\n * @returns English name (e.g., \"Heaven\" for Geon)\n * \n * @example\n * ```typescript\n * const englishName = getTrigramEnglishName(TrigramStance.GEON);\n * // Returns \"Heaven\"\n * ```\n * \n * @public\n * @korean 팔괘영문명얻기\n */\nexport function getTrigramEnglishName(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].name.english;\n}\n\n/**\n * Get all trigram information for a stance\n * \n * Returns a complete set of information about the trigram stance including\n * symbol, colors, names, and element association.\n * \n * @param stance - The trigram stance to get information for\n * @returns Complete trigram information object\n * \n * @example\n * ```typescript\n * const info = getTrigramInfo(TrigramStance.LI);\n * // Returns {\n * // stance: TrigramStance.LI,\n * // symbol: \"☲\",\n * // element: \"fire\",\n * // color: 0xff4444,\n * // koreanName: \"리\",\n * // englishName: \"Fire\"\n * // }\n * ```\n * \n * @public\n * @korean 팔괘정보얻기\n */\nexport function getTrigramInfo(stance: TrigramStance): {\n readonly stance: TrigramStance;\n readonly symbol: string;\n readonly element: Element;\n readonly color: number;\n readonly koreanName: string;\n readonly englishName: string;\n} {\n return {\n stance,\n symbol: getTrigramSymbol(stance),\n element: getTrigramElement(stance),\n color: getTrigramElementColor(stance),\n koreanName: getTrigramKoreanName(stance),\n englishName: getTrigramEnglishName(stance),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,iBAAiB;;CAE5B,MAAM;;CAEN,MAAM;;CAEN,OAAO;;CAEP,OAAO;;CAEP,OAAO;CACR;;;;;;;;;;;;;;;;AAuBD,IAAa,qBAAqD;EAC/D,cAAc,OAAO;EACrB,cAAc,MAAM;EACpB,cAAc,KAAK;EACnB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;CACtB;;;;;;;;;;;;;;;;;;AAqBD,SAAgB,uBAAuB,QAA+B;
|
|
1
|
+
{"version":3,"file":"ElementalColorSystem.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/ElementalColorSystem.ts"],"sourcesContent":["/**\n * Elemental Color System for Korean Trigram Stances\n * Based on 오행 (Wu Xing / Five Elements) philosophy\n * \n * Maps each of the eight trigram stances to one of the five elements,\n * providing color-coded visual feedback for combat and training.\n * \n * Note: This module provides the correct element mappings based on traditional\n * Bagua philosophy. The element property in TRIGRAM_DATA currently uses placeholder\n * \"metal\" values and should be updated to use this system.\n * \n * @module components/shared/three/indicators/ElementalColorSystem\n * @category Combat UI\n * @korean 오행색체계\n */\n\nimport { TrigramStance } from \"../../../../types/common\";\nimport { TRIGRAM_DATA } from \"../../../../systems/trigram/types\";\n\n/**\n * Five Elements (오행) color palette\n * \n * Based on traditional Korean/Chinese five element theory:\n * - Wood (木): Growth, expansion - Green tones\n * - Fire (火): Energy, passion - Red/Orange tones \n * - Earth (土): Stability, grounding - Yellow/Brown tones\n * - Metal (金): Precision, strength - White/Gold tones\n * - Water (水): Flow, adaptation - Blue/Cyan tones\n * \n * @korean 오행색상\n */\nexport const ELEMENT_COLORS = {\n /** Wood element - Growth and expansion (木) */\n wood: 0x228b22, // Forest green\n /** Fire element - Energy and passion (火) */\n fire: 0xff4444, // Bright red\n /** Earth element - Stability and grounding (土) */\n earth: 0xffd700, // Gold/Yellow\n /** Metal element - Precision and strength (金) */\n metal: 0xffffff, // White/Silver\n /** Water element - Flow and adaptation (水) */\n water: 0x00ffff, // Cyan\n} as const;\n\n/**\n * Element type from the five elements system\n * @korean 원소타입\n */\nexport type Element = keyof typeof ELEMENT_COLORS;\n\n/**\n * Mapping of trigram stances to five elements\n * \n * Based on traditional Bagua (八卦) associations:\n * - Geon (Heaven) ☰: Metal - Represents strength and clarity\n * - Tae (Lake) ☱: Metal - Represents joy and openness\n * - Li (Fire) ☲: Fire - Represents illumination and awareness\n * - Jin (Thunder) ☳: Wood - Represents movement and initiative\n * - Son (Wind) ☴: Wood - Represents gentleness and penetration\n * - Gam (Water) ☵: Water - Represents depth and danger\n * - Gan (Mountain) ☶: Earth - Represents stillness and rest\n * - Gon (Earth) ☷: Earth - Represents receptivity and nourishment\n * \n * @korean 팔괘원소대응\n */\nexport const TRIGRAM_TO_ELEMENT: Record<TrigramStance, Element> = {\n [TrigramStance.GEON]: \"metal\", // ☰ Heaven (건)\n [TrigramStance.TAE]: \"metal\", // ☱ Lake (태)\n [TrigramStance.LI]: \"fire\", // ☲ Fire (리)\n [TrigramStance.JIN]: \"wood\", // ☳ Thunder (진)\n [TrigramStance.SON]: \"wood\", // ☴ Wind (손)\n [TrigramStance.GAM]: \"water\", // ☵ Water (감)\n [TrigramStance.GAN]: \"earth\", // ☶ Mountain (간)\n [TrigramStance.GON]: \"earth\", // ☷ Earth (곤)\n};\n\n\n\n/**\n * Get the elemental color for a trigram stance\n * \n * Returns the hexadecimal color value associated with the stance's element.\n * \n * @param stance - The trigram stance to get the color for\n * @returns Hexadecimal color value (e.g., 0x00ffff)\n * \n * @example\n * ```typescript\n * const color = getTrigramElementColor(TrigramStance.GEON);\n * // Returns 0xffffff (white/metal)\n * ```\n * \n * @public\n * @korean 팔괘원소색상얻기\n */\nexport function getTrigramElementColor(stance: TrigramStance): number {\n const element = TRIGRAM_TO_ELEMENT[stance];\n return ELEMENT_COLORS[element];\n}\n\n/**\n * Get the Unicode symbol for a trigram stance\n * \n * Returns the Unicode trigram symbol character (☰☱☲☳☴☵☶☷).\n * Uses the symbol from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the symbol for\n * @returns Unicode trigram symbol\n * \n * @example\n * ```typescript\n * const symbol = getTrigramSymbol(TrigramStance.GEON);\n * // Returns \"☰\" (Heaven)\n * ```\n * \n * @public\n * @korean 팔괘기호얻기\n */\nexport function getTrigramSymbol(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].symbol;\n}\n\n/**\n * Get the element associated with a trigram stance\n * \n * @param stance - The trigram stance to get the element for\n * @returns Element name (wood, fire, earth, metal, water)\n * \n * @example\n * ```typescript\n * const element = getTrigramElement(TrigramStance.LI);\n * // Returns \"fire\"\n * ```\n * \n * @public\n * @korean 팔괘원소얻기\n */\nexport function getTrigramElement(stance: TrigramStance): Element {\n return TRIGRAM_TO_ELEMENT[stance];\n}\n\n/**\n * Get Korean name for a trigram stance\n * \n * Uses the Korean name from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the name for\n * @returns Korean name (e.g., \"건\" for Heaven)\n * \n * @example\n * ```typescript\n * const koreanName = getTrigramKoreanName(TrigramStance.GEON);\n * // Returns \"건\"\n * ```\n * \n * @public\n * @korean 팔괘한글명얻기\n */\nexport function getTrigramKoreanName(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].name.korean;\n}\n\n/**\n * Get English name for a trigram stance\n * \n * Uses the English name from TRIGRAM_DATA to maintain single source of truth.\n * \n * @param stance - The trigram stance to get the name for\n * @returns English name (e.g., \"Heaven\" for Geon)\n * \n * @example\n * ```typescript\n * const englishName = getTrigramEnglishName(TrigramStance.GEON);\n * // Returns \"Heaven\"\n * ```\n * \n * @public\n * @korean 팔괘영문명얻기\n */\nexport function getTrigramEnglishName(stance: TrigramStance): string {\n return TRIGRAM_DATA[stance].name.english;\n}\n\n/**\n * Get all trigram information for a stance\n * \n * Returns a complete set of information about the trigram stance including\n * symbol, colors, names, and element association.\n * \n * @param stance - The trigram stance to get information for\n * @returns Complete trigram information object\n * \n * @example\n * ```typescript\n * const info = getTrigramInfo(TrigramStance.LI);\n * // Returns {\n * // stance: TrigramStance.LI,\n * // symbol: \"☲\",\n * // element: \"fire\",\n * // color: 0xff4444,\n * // koreanName: \"리\",\n * // englishName: \"Fire\"\n * // }\n * ```\n * \n * @public\n * @korean 팔괘정보얻기\n */\nexport function getTrigramInfo(stance: TrigramStance): {\n readonly stance: TrigramStance;\n readonly symbol: string;\n readonly element: Element;\n readonly color: number;\n readonly koreanName: string;\n readonly englishName: string;\n} {\n return {\n stance,\n symbol: getTrigramSymbol(stance),\n element: getTrigramElement(stance),\n color: getTrigramElementColor(stance),\n koreanName: getTrigramKoreanName(stance),\n englishName: getTrigramEnglishName(stance),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,iBAAiB;;CAE5B,MAAM;;CAEN,MAAM;;CAEN,OAAO;;CAEP,OAAO;;CAEP,OAAO;CACR;;;;;;;;;;;;;;;;AAuBD,IAAa,qBAAqD;EAC/D,cAAc,OAAO;EACrB,cAAc,MAAM;EACpB,cAAc,KAAK;EACnB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;EACpB,cAAc,MAAM;CACtB;;;;;;;;;;;;;;;;;;AAqBD,SAAgB,uBAAuB,QAA+B;CAEpE,OAAO,eADS,mBAAmB;;;;;;;;;;;;;;;;;;;;AAsBrC,SAAgB,iBAAiB,QAA+B;CAC9D,OAAO,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;AAuC9B,SAAgB,qBAAqB,QAA+B;CAClE,OAAO,aAAa,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;AAoBnC,SAAgB,sBAAsB,QAA+B;CACnE,OAAO,aAAa,QAAQ,KAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GuardIndicator.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/GuardIndicator.tsx"],"sourcesContent":["/**\n * GuardIndicator - Visual indicator showing current fighting stance guard position\n * Displays Korean traditional stance name, English translation, and guard characteristics\n *\n * @module components/shared/three/indicators/GuardIndicator\n * @category Combat UI\n * @korean 방어자세표시기\n */\n\nimport React, { useMemo } from \"react\";\nimport { STANCE_GUARD_CONFIGS } from \"../../../../systems/animation\";\nimport { TRIGRAM_DATA } from \"../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Props for GuardIndicator component\n */\nexport interface GuardIndicatorProps {\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Whether player is in guard animation state */\n readonly isInGuard: boolean;\n /** Player position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get guard height label based on arm positions\n *\n * Guard heights based on shoulder.x (vertical angle in radians):\n * - High guard (고위): shoulder at chin level (x <= -0.8)\n * GEON (-1.0), LI (-1.6), GAN (-1.0)\n * - Mid guard (중위): shoulder at chest level (-0.8 < x < -0.4)\n * TAE (-0.7), JIN (-0.6), SON (-0.8), GAM (-0.8)\n * - Low guard (저위): shoulder low (x >= -0.4)\n * GON (-0.4)\n */\nfunction getGuardHeight(stance: TrigramStance): {\n korean: string;\n english: string;\n} {\n const config = STANCE_GUARD_CONFIGS[stance];\n const shoulderY = config.guardPose.leftArm.shoulder.x; // x is vertical in Euler\n\n if (shoulderY <= -0.8) {\n return { korean: \"고위\", english: \"High\" };\n } else if (shoulderY < -0.4) {\n return { korean: \"중위\", english: \"Mid\" };\n } else {\n return { korean: \"저위\", english: \"Low\" };\n }\n}\n\n/**\n * Get traditional Korean stance name from guard config\n */\nfunction getTraditionalStanceName(stance: TrigramStance): {\n korean: string;\n romanized: string;\n} {\n const stanceNames: Record<\n TrigramStance,\n { korean: string; romanized: string }\n > = {\n [TrigramStance.GEON]: { korean: \"앞서기\", romanized: \"Ap Seogi\" },\n [TrigramStance.TAE]: { korean: \"범서기\", romanized: \"Beom Seogi\" },\n [TrigramStance.LI]: { korean: \"겨루기 준비\", romanized: \"Gyeorugi Junbi\" },\n [TrigramStance.JIN]: { korean: \"주춤서기\", romanized: \"Juchum Seogi\" },\n [TrigramStance.SON]: { korean: \"학다리서기\", romanized: \"Hakdari Seogi\" },\n [TrigramStance.GAM]: { korean: \"뒷발서기\", romanized: \"Dwitbal Seogi\" },\n [TrigramStance.GAN]: { korean: \"모아서기\", romanized: \"Moa Seogi\" },\n [TrigramStance.GON]: { korean: \"중하\", romanized: \"Joong Ha Seogi\" },\n };\n return stanceNames[stance];\n}\n\n/**\n * Get weight distribution icon\n */\nfunction getWeightIcon(weight: \"forward\" | \"neutral\" | \"back\"): string {\n switch (weight) {\n case \"forward\":\n return \"▲\"; // Forward lean\n case \"back\":\n return \"▼\"; // Back lean\n case \"neutral\":\n return \"●\"; // Centered\n }\n}\n\n/**\n * GuardIndicator Component\n *\n * Displays current guard position information with Korean martial arts terminology.\n * Shows only when player is in a stance guard animation state.\n *\n * Features:\n * - Traditional Korean stance name (앞서기, 앞굽이, etc.)\n * - Romanized pronunciation\n * - Guard height indicator (High/Mid/Low)\n * - Weight distribution visualization\n * - Korean cyberpunk styling with glow effects\n * - Responsive mobile layout\n * - Accessible with proper ARIA labels\n *\n * @example\n * ```tsx\n * <GuardIndicator\n * currentStance={TrigramStance.GEON}\n * isInGuard={true}\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n *\n * @public\n * @korean 방어자세표시기\n */\nexport const GuardIndicator: React.FC<GuardIndicatorProps> = ({\n currentStance,\n isInGuard,\n position: _position, // Currently unused - indicator always positioned bottom-right\n isMobile = false,\n}) => {\n // Get guard configuration\n const config = useMemo(\n () => STANCE_GUARD_CONFIGS[currentStance],\n [currentStance],\n );\n\n const trigramData = useMemo(\n () => TRIGRAM_DATA[currentStance],\n [currentStance],\n );\n\n const stanceName = useMemo(\n () => getTraditionalStanceName(currentStance),\n [currentStance],\n );\n\n const guardHeight = useMemo(\n () => getGuardHeight(currentStance),\n [currentStance],\n );\n\n const weightIcon = useMemo(\n () => getWeightIcon(config.guardPose.weight),\n [config.guardPose.weight],\n );\n\n // Memoize responsive sizing\n const layout = useMemo(\n () => ({\n fontSize: isMobile ? 10 : 12,\n titleSize: isMobile ? 13 : 16,\n iconSize: isMobile ? 14 : 18,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n gap: isMobile ? \"3px\" : \"4px\",\n }),\n [isMobile],\n );\n\n // Container style - uses relative positioning for embedding in container HUDs\n const containerStyle = useMemo(\n () => ({\n position: \"relative\" as const,\n display: \"flex\",\n flexDirection: \"column\" as const,\n gap: layout.gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n padding: layout.padding,\n pointerEvents: \"none\" as const,\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }),\n [layout],\n );\n\n // Don't render if not in guard state\n if (!isInGuard) return null;\n\n return (\n <div\n data-testid=\"guard-indicator\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={`Guard position: ${stanceName.romanized}, ${guardHeight.english} guard, ${config.guardPose.weight} weight`}\n style={containerStyle}\n >\n {/* Title: GUARD with trigram symbol */}\n <div\n style={{\n fontSize: layout.titleSize,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontWeight: \"bold\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.6,\n )}`,\n borderBottom: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.4,\n )}`,\n paddingBottom: layout.gap,\n }}\n >\n {trigramData.symbol} GUARD\n </div>\n\n {/* Traditional stance name (Korean) */}\n <div\n style={{\n fontSize: layout.fontSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: \"center\",\n fontWeight: \"600\",\n }}\n >\n {stanceName.korean}\n </div>\n\n {/* Romanized name */}\n <div\n style={{\n fontSize: layout.fontSize,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n >\n {stanceName.romanized}\n </div>\n\n {/* Guard characteristics row */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-around\",\n marginTop: layout.gap,\n paddingTop: layout.gap,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n }}\n >\n {/* Guard height */}\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: layout.iconSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n ⚔️\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 1,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9),\n }}\n >\n {guardHeight.korean}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 2,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6),\n }}\n >\n {guardHeight.english}\n </div>\n </div>\n\n {/* Weight distribution */}\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: layout.iconSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n {weightIcon}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 1,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9),\n }}\n >\n {config.guardPose.weight === \"forward\" && \"전방\"}\n {config.guardPose.weight === \"neutral\" && \"중립\"}\n {config.guardPose.weight === \"back\" && \"후방\"}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 2,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6),\n textTransform: \"capitalize\" as const,\n }}\n >\n {config.guardPose.weight}\n </div>\n </div>\n </div>\n </div>\n );\n};\n\n/**\n * Memoized GuardIndicator to prevent unnecessary re-renders\n */\nexport default React.memo(GuardIndicator);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GuardIndicator.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/GuardIndicator.tsx"],"sourcesContent":["/**\n * GuardIndicator - Visual indicator showing current fighting stance guard position\n * Displays Korean traditional stance name, English translation, and guard characteristics\n *\n * @module components/shared/three/indicators/GuardIndicator\n * @category Combat UI\n * @korean 방어자세표시기\n */\n\nimport React, { useMemo } from \"react\";\nimport { STANCE_GUARD_CONFIGS } from \"../../../../systems/animation\";\nimport { TRIGRAM_DATA } from \"../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Props for GuardIndicator component\n */\nexport interface GuardIndicatorProps {\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Whether player is in guard animation state */\n readonly isInGuard: boolean;\n /** Player position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get guard height label based on arm positions\n *\n * Guard heights based on shoulder.x (vertical angle in radians):\n * - High guard (고위): shoulder at chin level (x <= -0.8)\n * GEON (-1.0), LI (-1.6), GAN (-1.0)\n * - Mid guard (중위): shoulder at chest level (-0.8 < x < -0.4)\n * TAE (-0.7), JIN (-0.6), SON (-0.8), GAM (-0.8)\n * - Low guard (저위): shoulder low (x >= -0.4)\n * GON (-0.4)\n */\nfunction getGuardHeight(stance: TrigramStance): {\n korean: string;\n english: string;\n} {\n const config = STANCE_GUARD_CONFIGS[stance];\n const shoulderY = config.guardPose.leftArm.shoulder.x; // x is vertical in Euler\n\n if (shoulderY <= -0.8) {\n return { korean: \"고위\", english: \"High\" };\n } else if (shoulderY < -0.4) {\n return { korean: \"중위\", english: \"Mid\" };\n } else {\n return { korean: \"저위\", english: \"Low\" };\n }\n}\n\n/**\n * Get traditional Korean stance name from guard config\n */\nfunction getTraditionalStanceName(stance: TrigramStance): {\n korean: string;\n romanized: string;\n} {\n const stanceNames: Record<\n TrigramStance,\n { korean: string; romanized: string }\n > = {\n [TrigramStance.GEON]: { korean: \"앞서기\", romanized: \"Ap Seogi\" },\n [TrigramStance.TAE]: { korean: \"범서기\", romanized: \"Beom Seogi\" },\n [TrigramStance.LI]: { korean: \"겨루기 준비\", romanized: \"Gyeorugi Junbi\" },\n [TrigramStance.JIN]: { korean: \"주춤서기\", romanized: \"Juchum Seogi\" },\n [TrigramStance.SON]: { korean: \"학다리서기\", romanized: \"Hakdari Seogi\" },\n [TrigramStance.GAM]: { korean: \"뒷발서기\", romanized: \"Dwitbal Seogi\" },\n [TrigramStance.GAN]: { korean: \"모아서기\", romanized: \"Moa Seogi\" },\n [TrigramStance.GON]: { korean: \"중하\", romanized: \"Joong Ha Seogi\" },\n };\n return stanceNames[stance];\n}\n\n/**\n * Get weight distribution icon\n */\nfunction getWeightIcon(weight: \"forward\" | \"neutral\" | \"back\"): string {\n switch (weight) {\n case \"forward\":\n return \"▲\"; // Forward lean\n case \"back\":\n return \"▼\"; // Back lean\n case \"neutral\":\n return \"●\"; // Centered\n }\n}\n\n/**\n * GuardIndicator Component\n *\n * Displays current guard position information with Korean martial arts terminology.\n * Shows only when player is in a stance guard animation state.\n *\n * Features:\n * - Traditional Korean stance name (앞서기, 앞굽이, etc.)\n * - Romanized pronunciation\n * - Guard height indicator (High/Mid/Low)\n * - Weight distribution visualization\n * - Korean cyberpunk styling with glow effects\n * - Responsive mobile layout\n * - Accessible with proper ARIA labels\n *\n * @example\n * ```tsx\n * <GuardIndicator\n * currentStance={TrigramStance.GEON}\n * isInGuard={true}\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n *\n * @public\n * @korean 방어자세표시기\n */\nexport const GuardIndicator: React.FC<GuardIndicatorProps> = ({\n currentStance,\n isInGuard,\n position: _position, // Currently unused - indicator always positioned bottom-right\n isMobile = false,\n}) => {\n // Get guard configuration\n const config = useMemo(\n () => STANCE_GUARD_CONFIGS[currentStance],\n [currentStance],\n );\n\n const trigramData = useMemo(\n () => TRIGRAM_DATA[currentStance],\n [currentStance],\n );\n\n const stanceName = useMemo(\n () => getTraditionalStanceName(currentStance),\n [currentStance],\n );\n\n const guardHeight = useMemo(\n () => getGuardHeight(currentStance),\n [currentStance],\n );\n\n const weightIcon = useMemo(\n () => getWeightIcon(config.guardPose.weight),\n [config.guardPose.weight],\n );\n\n // Memoize responsive sizing\n const layout = useMemo(\n () => ({\n fontSize: isMobile ? 10 : 12,\n titleSize: isMobile ? 13 : 16,\n iconSize: isMobile ? 14 : 18,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n gap: isMobile ? \"3px\" : \"4px\",\n }),\n [isMobile],\n );\n\n // Container style - uses relative positioning for embedding in container HUDs\n const containerStyle = useMemo(\n () => ({\n position: \"relative\" as const,\n display: \"flex\",\n flexDirection: \"column\" as const,\n gap: layout.gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n padding: layout.padding,\n pointerEvents: \"none\" as const,\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n boxShadow: `0 0 15px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }),\n [layout],\n );\n\n // Don't render if not in guard state\n if (!isInGuard) return null;\n\n return (\n <div\n data-testid=\"guard-indicator\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={`Guard position: ${stanceName.romanized}, ${guardHeight.english} guard, ${config.guardPose.weight} weight`}\n style={containerStyle}\n >\n {/* Title: GUARD with trigram symbol */}\n <div\n style={{\n fontSize: layout.titleSize,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontWeight: \"bold\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.6,\n )}`,\n borderBottom: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.4,\n )}`,\n paddingBottom: layout.gap,\n }}\n >\n {trigramData.symbol} GUARD\n </div>\n\n {/* Traditional stance name (Korean) */}\n <div\n style={{\n fontSize: layout.fontSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: \"center\",\n fontWeight: \"600\",\n }}\n >\n {stanceName.korean}\n </div>\n\n {/* Romanized name */}\n <div\n style={{\n fontSize: layout.fontSize,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n >\n {stanceName.romanized}\n </div>\n\n {/* Guard characteristics row */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-around\",\n marginTop: layout.gap,\n paddingTop: layout.gap,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n }}\n >\n {/* Guard height */}\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: layout.iconSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n ⚔️\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 1,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9),\n }}\n >\n {guardHeight.korean}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 2,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6),\n }}\n >\n {guardHeight.english}\n </div>\n </div>\n\n {/* Weight distribution */}\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: layout.iconSize,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n {weightIcon}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 1,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9),\n }}\n >\n {config.guardPose.weight === \"forward\" && \"전방\"}\n {config.guardPose.weight === \"neutral\" && \"중립\"}\n {config.guardPose.weight === \"back\" && \"후방\"}\n </div>\n <div\n style={{\n fontSize: layout.fontSize - 2,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6),\n textTransform: \"capitalize\" as const,\n }}\n >\n {config.guardPose.weight}\n </div>\n </div>\n </div>\n </div>\n );\n};\n\n/**\n * Memoized GuardIndicator to prevent unnecessary re-renders\n */\nexport default React.memo(GuardIndicator);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HapticFeedback.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/HapticFeedback.ts"],"sourcesContent":["/**\n * Haptic Feedback System for Combat UI\n * \n * Provides tactile feedback for mobile devices during combat interactions\n * such as guard activation, stance changes, and guard breaks.\n * \n * Uses the Vibration API with fallback for unsupported devices.\n * \n * @module components/shared/three/indicators/HapticFeedback\n * @category Combat UI\n * @korean 햅틱피드백\n */\n\n/**\n * Haptic pattern type\n * @korean 햅틱패턴타입\n */\nexport type HapticPattern = number | number[];\n\n/**\n * Check if haptic feedback is supported on this device\n * \n * @returns True if the Vibration API is available\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerGuardHaptic('activate');\n * }\n * ```\n * \n * @public\n * @korean 햅틱지원여부\n */\nexport function isHapticSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n \"vibrate\" in navigator &&\n typeof navigator.vibrate === \"function\"\n );\n}\n\n/**\n * Trigger haptic feedback for guard activation or break\n * \n * Provides tactile feedback when a player activates their guard\n * or when their guard is broken by an opponent.\n * \n * Patterns:\n * - **activate**: Light single vibration (50ms) for guard activation\n * - **break**: Strong triple-pulse pattern (100ms, 50ms pause, 100ms) for guard break\n * \n * @param type - Type of guard haptic feedback\n * \n * @example\n * ```typescript\n * // When player activates guard\n * triggerGuardHaptic('activate');\n * \n * // When guard is broken\n * triggerGuardHaptic('break');\n * ```\n * \n * @public\n * @korean 방어햅틱트리거\n */\nexport function triggerGuardHaptic(type: \"activate\" | \"break\"): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n if (type === \"activate\") {\n // Light haptic - single short vibration\n navigator.vibrate(50);\n } else if (type === \"break\") {\n // Strong haptic - triple pulse pattern for impact\n // Pattern: vibrate 100ms, pause 50ms, vibrate 100ms\n navigator.vibrate([100, 50, 100]);\n }\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Trigger haptic feedback for stance change\n * \n * Provides medium-strength tactile feedback when a player transitions\n * between trigram stances (건→태→리→진→손→감→간→곤).\n * \n * @example\n * ```typescript\n * // When stance changes from Geon to Tae\n * triggerStanceChangeHaptic();\n * ```\n * \n * @public\n * @korean 자세변경햅틱트리거\n */\nexport function triggerStanceChangeHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n // Medium haptic - single medium vibration\n navigator.vibrate(75);\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Trigger custom haptic pattern\n * \n * Allows for custom vibration patterns using the Vibration API.\n * Can specify either a single duration or a pattern array.\n * \n * Pattern arrays alternate between vibration and pause:\n * - [200, 100, 200] = vibrate 200ms, pause 100ms, vibrate 200ms\n * \n * @param pattern - Vibration duration in ms or pattern array\n * \n * @example\n * ```typescript\n * // Single vibration\n * triggerCustomHaptic(200);\n * \n * // Complex pattern\n * triggerCustomHaptic([100, 50, 100, 50, 100]);\n * ```\n * \n * @public\n * @korean 사용자정의햅틱트리거\n */\nexport function triggerCustomHaptic(pattern: HapticPattern): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n navigator.vibrate(pattern);\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Stop all haptic feedback\n * \n * Immediately stops any ongoing vibration. Useful for interrupting\n * long or repeated patterns.\n * \n * @example\n * ```typescript\n * // Stop any ongoing haptic feedback\n * stopHaptic();\n * ```\n * \n * @public\n * @korean 햅틱중지\n */\nexport function stopHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n // Passing 0 or empty array stops vibration\n navigator.vibrate(0);\n } catch (error) {\n // Silently fail if vibration stop fails\n console.warn(\"Haptic stop failed:\", error);\n }\n}\n\n/**\n * Check if device is mobile\n * \n * Simple heuristic to detect mobile devices based on screen size,\n * user agent, and touch support.\n * \n * @returns True if device is likely mobile\n * \n * @example\n * ```typescript\n * if (isMobileDevice() && isHapticSupported()) {\n * triggerStanceChangeHaptic();\n * }\n * ```\n * \n * @public\n * @korean 모바일기기여부\n */\nexport function isMobileDevice(): boolean {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return false;\n }\n\n // Check screen size (mobile typically < 768px)\n const isMobileSize = window.innerWidth < 768;\n\n // Check user agent for mobile indicators\n const mobileKeywords = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;\n const isMobileUA = mobileKeywords.test(navigator.userAgent);\n\n // Check for touch support (modern browsers)\n const hasTouch =\n \"ontouchstart\" in window ||\n navigator.maxTouchPoints > 0;\n\n // Device is mobile if it meets size OR user agent criteria AND has touch\n return (isMobileSize || isMobileUA) && hasTouch;\n}\n\n/**\n * Haptic feedback settings\n * \n * Allows for global configuration of haptic feedback intensity\n * and enable/disable state.\n * \n * @korean 햅틱설정\n */\nexport interface HapticSettings {\n /** Whether haptic feedback is enabled */\n readonly enabled: boolean;\n /** Intensity multiplier (0.0 to 1.0) */\n readonly intensity: number;\n}\n\n/**\n * Default haptic settings\n * @korean 기본햅틱설정\n */\nexport const DEFAULT_HAPTIC_SETTINGS: HapticSettings = {\n enabled: true,\n intensity: 1.0,\n};\n\n/**\n * Apply intensity modifier to haptic pattern\n * \n * Scales vibration durations based on intensity setting.\n * \n * @param pattern - Original haptic pattern\n * @param intensity - Intensity multiplier (0.0 to 1.0)\n * @returns Scaled haptic pattern\n * \n * @internal\n * @korean 햅틱강도적용\n */\nexport function applyIntensity(\n pattern: HapticPattern,\n intensity: number\n): HapticPattern {\n const clampedIntensity = Math.max(0, Math.min(1, intensity));\n\n if (typeof pattern === \"number\") {\n return Math.round(pattern * clampedIntensity);\n }\n\n return pattern.map((duration) => Math.round(duration * clampedIntensity));\n}\n\n/**\n * Trigger haptic with settings\n * \n * Wrapper function that applies haptic settings before triggering.\n * \n * @param pattern - Haptic pattern to trigger\n * @param settings - Haptic settings to apply\n * \n * @example\n * ```typescript\n * const settings = { enabled: true, intensity: 0.7 };\n * triggerWithSettings(100, settings); // Triggers 70ms vibration\n * ```\n * \n * @public\n * @korean 설정포함햅틱트리거\n */\nexport function triggerWithSettings(\n pattern: HapticPattern,\n settings: HapticSettings = DEFAULT_HAPTIC_SETTINGS\n): void {\n if (!settings.enabled || !isHapticSupported()) {\n return;\n }\n\n const scaledPattern = applyIntensity(pattern, settings.intensity);\n triggerCustomHaptic(scaledPattern);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,SAAgB,oBAA6B;
|
|
1
|
+
{"version":3,"file":"HapticFeedback.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/HapticFeedback.ts"],"sourcesContent":["/**\n * Haptic Feedback System for Combat UI\n * \n * Provides tactile feedback for mobile devices during combat interactions\n * such as guard activation, stance changes, and guard breaks.\n * \n * Uses the Vibration API with fallback for unsupported devices.\n * \n * @module components/shared/three/indicators/HapticFeedback\n * @category Combat UI\n * @korean 햅틱피드백\n */\n\n/**\n * Haptic pattern type\n * @korean 햅틱패턴타입\n */\nexport type HapticPattern = number | number[];\n\n/**\n * Check if haptic feedback is supported on this device\n * \n * @returns True if the Vibration API is available\n * \n * @example\n * ```typescript\n * if (isHapticSupported()) {\n * triggerGuardHaptic('activate');\n * }\n * ```\n * \n * @public\n * @korean 햅틱지원여부\n */\nexport function isHapticSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n \"vibrate\" in navigator &&\n typeof navigator.vibrate === \"function\"\n );\n}\n\n/**\n * Trigger haptic feedback for guard activation or break\n * \n * Provides tactile feedback when a player activates their guard\n * or when their guard is broken by an opponent.\n * \n * Patterns:\n * - **activate**: Light single vibration (50ms) for guard activation\n * - **break**: Strong triple-pulse pattern (100ms, 50ms pause, 100ms) for guard break\n * \n * @param type - Type of guard haptic feedback\n * \n * @example\n * ```typescript\n * // When player activates guard\n * triggerGuardHaptic('activate');\n * \n * // When guard is broken\n * triggerGuardHaptic('break');\n * ```\n * \n * @public\n * @korean 방어햅틱트리거\n */\nexport function triggerGuardHaptic(type: \"activate\" | \"break\"): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n if (type === \"activate\") {\n // Light haptic - single short vibration\n navigator.vibrate(50);\n } else if (type === \"break\") {\n // Strong haptic - triple pulse pattern for impact\n // Pattern: vibrate 100ms, pause 50ms, vibrate 100ms\n navigator.vibrate([100, 50, 100]);\n }\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Trigger haptic feedback for stance change\n * \n * Provides medium-strength tactile feedback when a player transitions\n * between trigram stances (건→태→리→진→손→감→간→곤).\n * \n * @example\n * ```typescript\n * // When stance changes from Geon to Tae\n * triggerStanceChangeHaptic();\n * ```\n * \n * @public\n * @korean 자세변경햅틱트리거\n */\nexport function triggerStanceChangeHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n // Medium haptic - single medium vibration\n navigator.vibrate(75);\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Trigger custom haptic pattern\n * \n * Allows for custom vibration patterns using the Vibration API.\n * Can specify either a single duration or a pattern array.\n * \n * Pattern arrays alternate between vibration and pause:\n * - [200, 100, 200] = vibrate 200ms, pause 100ms, vibrate 200ms\n * \n * @param pattern - Vibration duration in ms or pattern array\n * \n * @example\n * ```typescript\n * // Single vibration\n * triggerCustomHaptic(200);\n * \n * // Complex pattern\n * triggerCustomHaptic([100, 50, 100, 50, 100]);\n * ```\n * \n * @public\n * @korean 사용자정의햅틱트리거\n */\nexport function triggerCustomHaptic(pattern: HapticPattern): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n navigator.vibrate(pattern);\n } catch (error) {\n // Silently fail if vibration fails\n console.warn(\"Haptic feedback failed:\", error);\n }\n}\n\n/**\n * Stop all haptic feedback\n * \n * Immediately stops any ongoing vibration. Useful for interrupting\n * long or repeated patterns.\n * \n * @example\n * ```typescript\n * // Stop any ongoing haptic feedback\n * stopHaptic();\n * ```\n * \n * @public\n * @korean 햅틱중지\n */\nexport function stopHaptic(): void {\n if (!isHapticSupported()) {\n return;\n }\n\n try {\n // Passing 0 or empty array stops vibration\n navigator.vibrate(0);\n } catch (error) {\n // Silently fail if vibration stop fails\n console.warn(\"Haptic stop failed:\", error);\n }\n}\n\n/**\n * Check if device is mobile\n * \n * Simple heuristic to detect mobile devices based on screen size,\n * user agent, and touch support.\n * \n * @returns True if device is likely mobile\n * \n * @example\n * ```typescript\n * if (isMobileDevice() && isHapticSupported()) {\n * triggerStanceChangeHaptic();\n * }\n * ```\n * \n * @public\n * @korean 모바일기기여부\n */\nexport function isMobileDevice(): boolean {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return false;\n }\n\n // Check screen size (mobile typically < 768px)\n const isMobileSize = window.innerWidth < 768;\n\n // Check user agent for mobile indicators\n const mobileKeywords = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;\n const isMobileUA = mobileKeywords.test(navigator.userAgent);\n\n // Check for touch support (modern browsers)\n const hasTouch =\n \"ontouchstart\" in window ||\n navigator.maxTouchPoints > 0;\n\n // Device is mobile if it meets size OR user agent criteria AND has touch\n return (isMobileSize || isMobileUA) && hasTouch;\n}\n\n/**\n * Haptic feedback settings\n * \n * Allows for global configuration of haptic feedback intensity\n * and enable/disable state.\n * \n * @korean 햅틱설정\n */\nexport interface HapticSettings {\n /** Whether haptic feedback is enabled */\n readonly enabled: boolean;\n /** Intensity multiplier (0.0 to 1.0) */\n readonly intensity: number;\n}\n\n/**\n * Default haptic settings\n * @korean 기본햅틱설정\n */\nexport const DEFAULT_HAPTIC_SETTINGS: HapticSettings = {\n enabled: true,\n intensity: 1.0,\n};\n\n/**\n * Apply intensity modifier to haptic pattern\n * \n * Scales vibration durations based on intensity setting.\n * \n * @param pattern - Original haptic pattern\n * @param intensity - Intensity multiplier (0.0 to 1.0)\n * @returns Scaled haptic pattern\n * \n * @internal\n * @korean 햅틱강도적용\n */\nexport function applyIntensity(\n pattern: HapticPattern,\n intensity: number\n): HapticPattern {\n const clampedIntensity = Math.max(0, Math.min(1, intensity));\n\n if (typeof pattern === \"number\") {\n return Math.round(pattern * clampedIntensity);\n }\n\n return pattern.map((duration) => Math.round(duration * clampedIntensity));\n}\n\n/**\n * Trigger haptic with settings\n * \n * Wrapper function that applies haptic settings before triggering.\n * \n * @param pattern - Haptic pattern to trigger\n * @param settings - Haptic settings to apply\n * \n * @example\n * ```typescript\n * const settings = { enabled: true, intensity: 0.7 };\n * triggerWithSettings(100, settings); // Triggers 70ms vibration\n * ```\n * \n * @public\n * @korean 설정포함햅틱트리거\n */\nexport function triggerWithSettings(\n pattern: HapticPattern,\n settings: HapticSettings = DEFAULT_HAPTIC_SETTINGS\n): void {\n if (!settings.enabled || !isHapticSupported()) {\n return;\n }\n\n const scaledPattern = applyIntensity(pattern, settings.intensity);\n triggerCustomHaptic(scaledPattern);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkCA,SAAgB,oBAA6B;CAC3C,OACE,OAAO,cAAc,eACrB,aAAa,aACb,OAAO,UAAU,YAAY;;;;;;;;;;;;;;;;;AA+DjC,SAAgB,4BAAkC;CAChD,IAAI,CAAC,mBAAmB,EACtB;CAGF,IAAI;EAEF,UAAU,QAAQ,GAAG;UACd,OAAO;EAEd,QAAQ,KAAK,2BAA2B,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StanceChangeIndicator.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/StanceChangeIndicator.tsx"],"sourcesContent":["/**\n * StanceChangeIndicator - Visual feedback for stance changes\n * Displays Korean and English stance names with trigram symbols\n * \n * @module components/shared/three/indicators/StanceChangeIndicator\n * @category Combat UI\n * @korean 자세변경표시기\n */\n\n \nimport { Html } from \"@react-three/drei\";\nimport React, { useEffect, useState, useMemo, useRef } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { \n getTrigramElementColor, \n getTrigramKoreanName, \n getTrigramEnglishName,\n getTrigramSymbol\n} from \"./ElementalColorSystem\";\nimport { triggerStanceChangeHaptic } from \"./HapticFeedback\";\n\n/**\n * Props for StanceChangeIndicator component\n */\nexport interface StanceChangeIndicatorProps {\n /** Current stance index (0-7) */\n readonly currentStance: number;\n /** Previous stance index (0-7) for change detection */\n readonly previousStance: number;\n /** Mobile layout flag */\n readonly isMobile?: boolean;\n /** Display duration in milliseconds (default: 1000ms) */\n readonly duration?: number;\n /** Show transition progress bar (default: true) */\n readonly showProgress?: boolean;\n /** Transition duration in milliseconds for progress bar (default: 600ms) */\n readonly transitionDuration?: number;\n}\n\n/**\n * StanceChangeIndicator Component\n * \n * Displays a temporary overlay showing the current trigram stance\n * with Korean name, English name, and trigram symbol.\n * \n * Features:\n * - Fade in/out animation\n * - Korean cyberpunk styling\n * - Responsive mobile layout\n * - Trigram symbol display\n * - Glow effect for visual emphasis\n * \n * @example\n * ```tsx\n * <StanceChangeIndicator\n * currentStance={player.stance}\n * previousStance={previousStance}\n * isMobile={isMobile}\n * />\n * ```\n * \n * @public\n * @korean 자세변경표시기\n */\nexport const StanceChangeIndicator: React.FC<StanceChangeIndicatorProps> = ({\n currentStance,\n previousStance,\n isMobile = false,\n duration = 1000,\n showProgress = true,\n transitionDuration = 600,\n}) => {\n const [showIndicator, setShowIndicator] = useState(false);\n const [progress, setProgress] = useState(0);\n const isMountedRef = useRef(true);\n const startTimeRef = useRef<number>(0);\n const animationFrameRef = useRef<number | undefined>(undefined);\n\n // Track component mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // Show/hide indicator based on stance change\n // This effect intentionally sets state synchronously for immediate visual feedback\n useEffect(() => {\n // Cancel any existing animation frame first\n if (animationFrameRef.current !== undefined) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = undefined;\n }\n\n if (currentStance !== previousStance) {\n setShowIndicator(true);\n setProgress(0);\n startTimeRef.current = 0;\n\n // Trigger haptic feedback for stance change\n triggerStanceChangeHaptic();\n\n // Animation loop for progress bar\n if (showProgress) {\n const animate = (timestamp: number) => {\n if (!isMountedRef.current) return;\n \n // Initialize start time on first frame\n if (startTimeRef.current === 0) {\n startTimeRef.current = timestamp;\n }\n \n const elapsed = timestamp - startTimeRef.current;\n const newProgress = Math.min((elapsed / transitionDuration) * 100, 100);\n \n setProgress(newProgress);\n\n if (newProgress < 100) {\n animationFrameRef.current = requestAnimationFrame(animate);\n }\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n }\n\n const timer = setTimeout(() => {\n if (isMountedRef.current) {\n setShowIndicator(false);\n }\n }, duration);\n\n return () => {\n clearTimeout(timer);\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }\n }, [currentStance, previousStance, duration, showProgress, transitionDuration]);\n\n // Get trigram stance from index\n const stance = useMemo(\n () => TRIGRAM_STANCES_ORDER[currentStance] ?? TrigramStance.GEON,\n [currentStance]\n );\n\n // Get elemental color for stance\n const elementalColor = useMemo(\n () => getTrigramElementColor(stance),\n [stance]\n );\n\n // Get trigram names using elemental color system\n const koreanName = useMemo(\n () => getTrigramKoreanName(stance),\n [stance]\n );\n\n const englishName = useMemo(\n () => getTrigramEnglishName(stance),\n [stance]\n );\n\n const trigramSymbol = useMemo(\n () => getTrigramSymbol(stance),\n [stance]\n );\n\n // Memoize animation styles to prevent redefinition on every render\n const animationStyles = useMemo(() => (\n <style>\n {`\n @keyframes fadeInOut {\n 0% {\n opacity: 0;\n transform: translateY(-20px);\n }\n 10% {\n opacity: 1;\n transform: translateY(0);\n }\n 90% {\n opacity: 1;\n transform: translateY(0);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px);\n }\n }\n `}\n </style>\n ), []);\n\n if (!showIndicator) return null;\n\n const fontSize = isMobile ? 24 : 36;\n const subFontSize = isMobile ? 14 : 18;\n const top = isMobile ? \"30%\" : \"20%\";\n\n // Use elemental color system\n const primaryColorHex = hexToRgbaString(elementalColor, 1);\n\n return (\n <Html fullscreen>\n <div\n data-testid=\"stance-change-indicator\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={`Stance changed to ${englishName}`}\n style={{\n position: \"absolute\",\n top,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n textAlign: \"center\",\n animation: \"fadeInOut 1s\",\n pointerEvents: \"none\",\n zIndex: 1000,\n }}\n >\n {/* Main stance display */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: primaryColorHex,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n textShadow: `0 0 20px ${hexToRgbaString(elementalColor, 0.8)}, \n 0 0 40px ${hexToRgbaString(elementalColor, 0.5)}`,\n marginBottom: \"8px\",\n }}\n >\n {koreanName} {trigramSymbol}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6)}`,\n marginBottom: showProgress ? \"12px\" : \"0\",\n }}\n >\n {englishName}\n </div>\n\n {/* Transition progress bar (600ms) */}\n {showProgress && (\n <div\n data-testid=\"stance-transition-progress\"\n style={{\n width: \"200px\",\n margin: \"0 auto\",\n padding: \"8px 0\",\n }}\n >\n {/* Progress label */}\n <div\n style={{\n fontSize: \"11px\",\n color: \"rgba(255, 255, 255, 0.7)\",\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n letterSpacing: \"1px\",\n }}\n >\n 팔괘전환 | Transition\n </div>\n \n {/* Progress bar container */}\n <div\n style={{\n width: \"100%\",\n height: \"6px\",\n backgroundColor: \"rgba(0, 0, 0, 0.6)\",\n borderRadius: \"3px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(elementalColor, 0.3)}`,\n }}\n >\n {/* Progress bar fill */}\n <div\n style={{\n width: `${progress}%`,\n height: \"100%\",\n backgroundColor: primaryColorHex,\n transition: \"width 0.05s linear\",\n boxShadow: `0 0 8px ${primaryColorHex}`,\n }}\n />\n </div>\n \n {/* Time remaining */}\n <div\n style={{\n fontSize: \"10px\",\n color: \"rgba(255, 255, 255, 0.5)\",\n fontFamily: FONT_FAMILY.KOREAN,\n marginTop: \"4px\",\n }}\n >\n {Math.max(0, Math.ceil(transitionDuration * (1 - progress / 100)))}ms\n </div>\n </div>\n )}\n\n {/* CSS Animation - Memoized to prevent redefinition */}\n {animationStyles}\n </div>\n </Html>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StanceChangeIndicator.js","names":[],"sources":["../../../../../src/components/shared/three/indicators/StanceChangeIndicator.tsx"],"sourcesContent":["/**\n * StanceChangeIndicator - Visual feedback for stance changes\n * Displays Korean and English stance names with trigram symbols\n * \n * @module components/shared/three/indicators/StanceChangeIndicator\n * @category Combat UI\n * @korean 자세변경표시기\n */\n\n \nimport { Html } from \"@react-three/drei\";\nimport React, { useEffect, useState, useMemo, useRef } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { \n getTrigramElementColor, \n getTrigramKoreanName, \n getTrigramEnglishName,\n getTrigramSymbol\n} from \"./ElementalColorSystem\";\nimport { triggerStanceChangeHaptic } from \"./HapticFeedback\";\n\n/**\n * Props for StanceChangeIndicator component\n */\nexport interface StanceChangeIndicatorProps {\n /** Current stance index (0-7) */\n readonly currentStance: number;\n /** Previous stance index (0-7) for change detection */\n readonly previousStance: number;\n /** Mobile layout flag */\n readonly isMobile?: boolean;\n /** Display duration in milliseconds (default: 1000ms) */\n readonly duration?: number;\n /** Show transition progress bar (default: true) */\n readonly showProgress?: boolean;\n /** Transition duration in milliseconds for progress bar (default: 600ms) */\n readonly transitionDuration?: number;\n}\n\n/**\n * StanceChangeIndicator Component\n * \n * Displays a temporary overlay showing the current trigram stance\n * with Korean name, English name, and trigram symbol.\n * \n * Features:\n * - Fade in/out animation\n * - Korean cyberpunk styling\n * - Responsive mobile layout\n * - Trigram symbol display\n * - Glow effect for visual emphasis\n * \n * @example\n * ```tsx\n * <StanceChangeIndicator\n * currentStance={player.stance}\n * previousStance={previousStance}\n * isMobile={isMobile}\n * />\n * ```\n * \n * @public\n * @korean 자세변경표시기\n */\nexport const StanceChangeIndicator: React.FC<StanceChangeIndicatorProps> = ({\n currentStance,\n previousStance,\n isMobile = false,\n duration = 1000,\n showProgress = true,\n transitionDuration = 600,\n}) => {\n const [showIndicator, setShowIndicator] = useState(false);\n const [progress, setProgress] = useState(0);\n const isMountedRef = useRef(true);\n const startTimeRef = useRef<number>(0);\n const animationFrameRef = useRef<number | undefined>(undefined);\n\n // Track component mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // Show/hide indicator based on stance change\n // This effect intentionally sets state synchronously for immediate visual feedback\n useEffect(() => {\n // Cancel any existing animation frame first\n if (animationFrameRef.current !== undefined) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = undefined;\n }\n\n if (currentStance !== previousStance) {\n setShowIndicator(true);\n setProgress(0);\n startTimeRef.current = 0;\n\n // Trigger haptic feedback for stance change\n triggerStanceChangeHaptic();\n\n // Animation loop for progress bar\n if (showProgress) {\n const animate = (timestamp: number) => {\n if (!isMountedRef.current) return;\n \n // Initialize start time on first frame\n if (startTimeRef.current === 0) {\n startTimeRef.current = timestamp;\n }\n \n const elapsed = timestamp - startTimeRef.current;\n const newProgress = Math.min((elapsed / transitionDuration) * 100, 100);\n \n setProgress(newProgress);\n\n if (newProgress < 100) {\n animationFrameRef.current = requestAnimationFrame(animate);\n }\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n }\n\n const timer = setTimeout(() => {\n if (isMountedRef.current) {\n setShowIndicator(false);\n }\n }, duration);\n\n return () => {\n clearTimeout(timer);\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }\n }, [currentStance, previousStance, duration, showProgress, transitionDuration]);\n\n // Get trigram stance from index\n const stance = useMemo(\n () => TRIGRAM_STANCES_ORDER[currentStance] ?? TrigramStance.GEON,\n [currentStance]\n );\n\n // Get elemental color for stance\n const elementalColor = useMemo(\n () => getTrigramElementColor(stance),\n [stance]\n );\n\n // Get trigram names using elemental color system\n const koreanName = useMemo(\n () => getTrigramKoreanName(stance),\n [stance]\n );\n\n const englishName = useMemo(\n () => getTrigramEnglishName(stance),\n [stance]\n );\n\n const trigramSymbol = useMemo(\n () => getTrigramSymbol(stance),\n [stance]\n );\n\n // Memoize animation styles to prevent redefinition on every render\n const animationStyles = useMemo(() => (\n <style>\n {`\n @keyframes fadeInOut {\n 0% {\n opacity: 0;\n transform: translateY(-20px);\n }\n 10% {\n opacity: 1;\n transform: translateY(0);\n }\n 90% {\n opacity: 1;\n transform: translateY(0);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px);\n }\n }\n `}\n </style>\n ), []);\n\n if (!showIndicator) return null;\n\n const fontSize = isMobile ? 24 : 36;\n const subFontSize = isMobile ? 14 : 18;\n const top = isMobile ? \"30%\" : \"20%\";\n\n // Use elemental color system\n const primaryColorHex = hexToRgbaString(elementalColor, 1);\n\n return (\n <Html fullscreen>\n <div\n data-testid=\"stance-change-indicator\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={`Stance changed to ${englishName}`}\n style={{\n position: \"absolute\",\n top,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n textAlign: \"center\",\n animation: \"fadeInOut 1s\",\n pointerEvents: \"none\",\n zIndex: 1000,\n }}\n >\n {/* Main stance display */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: primaryColorHex,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n textShadow: `0 0 20px ${hexToRgbaString(elementalColor, 0.8)}, \n 0 0 40px ${hexToRgbaString(elementalColor, 0.5)}`,\n marginBottom: \"8px\",\n }}\n >\n {koreanName} {trigramSymbol}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.6)}`,\n marginBottom: showProgress ? \"12px\" : \"0\",\n }}\n >\n {englishName}\n </div>\n\n {/* Transition progress bar (600ms) */}\n {showProgress && (\n <div\n data-testid=\"stance-transition-progress\"\n style={{\n width: \"200px\",\n margin: \"0 auto\",\n padding: \"8px 0\",\n }}\n >\n {/* Progress label */}\n <div\n style={{\n fontSize: \"11px\",\n color: \"rgba(255, 255, 255, 0.7)\",\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: \"4px\",\n letterSpacing: \"1px\",\n }}\n >\n 팔괘전환 | Transition\n </div>\n \n {/* Progress bar container */}\n <div\n style={{\n width: \"100%\",\n height: \"6px\",\n backgroundColor: \"rgba(0, 0, 0, 0.6)\",\n borderRadius: \"3px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(elementalColor, 0.3)}`,\n }}\n >\n {/* Progress bar fill */}\n <div\n style={{\n width: `${progress}%`,\n height: \"100%\",\n backgroundColor: primaryColorHex,\n transition: \"width 0.05s linear\",\n boxShadow: `0 0 8px ${primaryColorHex}`,\n }}\n />\n </div>\n \n {/* Time remaining */}\n <div\n style={{\n fontSize: \"10px\",\n color: \"rgba(255, 255, 255, 0.5)\",\n fontFamily: FONT_FAMILY.KOREAN,\n marginTop: \"4px\",\n }}\n >\n {Math.max(0, Math.ceil(transitionDuration * (1 - progress / 100)))}ms\n </div>\n </div>\n )}\n\n {/* CSS Animation - Memoized to prevent redefinition */}\n {animationStyles}\n </div>\n </Html>\n );\n};\n"],"mappings":"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"}
|