blacktrigram 0.7.47 → 0.7.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.d.ts.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +29 -25
- 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.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
- 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 +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.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -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.d.ts.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
- 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.d.ts.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
- 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 +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 +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +3 -11
- 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.d.ts.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
- 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.d.ts +1 -0
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
- 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.d.ts.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
- 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.d.ts.map +1 -1
- package/lib/hooks/useHUDLayout.js +3 -2
- 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.d.ts.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +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 +16 -16
- 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.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
- 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/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.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.d.ts +6 -6
- package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
- 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/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.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 +15 -15
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
- package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +74 -12
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js +34 -0
- 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/index.d.ts +1 -1
- package/lib/systems/animation/core/index.d.ts.map +1 -1
- package/lib/systems/animation/core/types.d.ts +24 -0
- package/lib/systems/animation/core/types.d.ts.map +1 -1
- package/lib/systems/animation/core/types.js +27 -11
- 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 +19 -19
- 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 +19 -19
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js +17 -17
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js +24 -24
- 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 +21 -21
- 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 +6 -6
- 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/GeonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.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/systems/vitalpoint/VitalPointsData.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/animations.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/index.js.map +1 -1
- package/lib/types/constants/layout.d.ts +21 -0
- package/lib/types/constants/layout.d.ts.map +1 -1
- package/lib/types/constants/layout.js +22 -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/constants/ui.js.map +1 -1
- package/lib/types/facial.js +19 -19
- 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/muscle.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/types/player-visual.d.ts +1 -1
- package/lib/types/player-visual.d.ts.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 +6 -7
- 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.d.ts +7 -0
- package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js +16 -2
- 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 +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FootPlacementMarkers3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootPlacementMarkers3D.tsx"],"sourcesContent":["/**\n * FootPlacementMarkers3D - 3D markers showing foot placement for footwork drills\n * \n * Renders visual indicators on the ground to guide footwork training,\n * showing target positions for Korean martial arts footwork patterns.\n * \n * @module components/screens/training/components/FootPlacementMarkers3D\n * @category Training 3D Components\n * @korean 발위치표시3D컴포넌트\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { FootworkDrill } from \"./FootworkDrillsOverlayHtml\";\n\n/**\n * Footwork drill pattern types for 3D visualization\n * \n * Derived from FootworkDrill, excluding \"free_practice\" (which has no pattern),\n * and adding a \"none\" sentinel for cases with no active pattern.\n */\nexport type FootworkDrillPattern = \n | Exclude<FootworkDrill, \"free_practice\">\n | \"none\";\n\n/**\n * Props for FootPlacementMarkers3D component\n */\nexport interface FootPlacementMarkers3DProps {\n /** Center position for the pattern */\n readonly centerPosition?: [number, number, number];\n /** Current drill pattern to display */\n readonly pattern: FootworkDrillPattern;\n /** Current step in pattern (0-based) */\n readonly currentStep?: number;\n /** Whether markers are visible */\n readonly visible?: boolean;\n /** Scale factor for markers */\n readonly scale?: number;\n /** Whether to animate markers */\n readonly animated?: boolean;\n}\n\n/**\n * Generate footwork pattern positions\n * \n * @korean 보법패턴위치생성\n */\nfunction getPatternPositions(\n pattern: FootworkDrillPattern,\n center: [number, number, number]\n): Array<{ position: [number, number, number]; label: string }> {\n const [cx, cy, cz] = center;\n const radius = 2.0; // 2 meters radius for circular patterns\n const stepDist = 0.3; // 30cm step distance\n\n switch (pattern) {\n case \"circular_left\":\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz - radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz + radius], label: \"4\" },\n ];\n\n case \"circular_right\":\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz + radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz - radius], label: \"4\" },\n ];\n\n case \"pivot_combo\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx - 0.5, cy, cz + 0.5], label: \"Pivot L\" },\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx + 0.5, cy, cz + 0.5], label: \"Pivot R\" },\n ];\n\n case \"triangle_step\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - stepDist * 2], label: \"Forward\" },\n { position: [cx + stepDist * 1.5, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz], label: \"Back\" },\n ];\n\n case \"slide_drill\":\n return [\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx, cy, cz - stepDist], label: \"Forward\" },\n { position: [cx + stepDist, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz + stepDist], label: \"Back\" },\n { position: [cx - stepDist, cy, cz], label: \"Left\" },\n ];\n\n case \"shuffle_practice\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - 0.15], label: \"Shuffle 1\" },\n { position: [cx, cy, cz - 0.30], label: \"Shuffle 2\" },\n { position: [cx, cy, cz - 0.45], label: \"Shuffle 3\" },\n ];\n\n case \"none\":\n default:\n return [];\n }\n}\n\n/**\n * Single foot placement marker\n */\nconst FootMarker: React.FC<{\n position: [number, number, number];\n label: string;\n isActive: boolean;\n isCompleted: boolean;\n animated: boolean;\n}> = ({ position, label, isActive, isCompleted, animated }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n useFrame((state) => {\n if (!animated) return;\n \n if (meshRef.current && isActive) {\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.1 + 1;\n meshRef.current.scale.setScalar(pulse);\n }\n\n if (ringRef.current && isActive) {\n const ringPulse = Math.sin(state.clock.elapsedTime * 2) * 0.2 + 1;\n ringRef.current.scale.setScalar(ringPulse);\n }\n });\n\n const markerColor = isCompleted\n ? KOREAN_COLORS.ACCENT_GREEN\n : isActive\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.ACCENT_GOLD;\n\n return (\n <group position={position}>\n {/* Ground circle marker */}\n <mesh ref={meshRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.01, 0]}>\n <circleGeometry args={[0.2, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.9 : 0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Outer ring */}\n <mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>\n <ringGeometry args={[0.2, 0.25, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.7 : 0.4}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Label */}\n <Html\n position={[0, 0.1, 0]}\n center\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: isActive ? \"#00ffff\" : \"#ffffff\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n background: \"rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n }}\n >\n {label}\n </div>\n </Html>\n\n {/* Vertical beam for active marker */}\n {isActive && (\n <mesh position={[0, 0.5, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 1, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * FootPlacementMarkers3D Component\n * \n * Displays 3D foot placement markers on the ground for footwork drills,\n * with Korean martial arts-themed visual feedback.\n * \n * @korean 발위치표시3D\n */\nexport const FootPlacementMarkers3D: React.FC<FootPlacementMarkers3DProps> = ({\n centerPosition = [5, 0, 0],\n pattern,\n currentStep = 0,\n visible = true,\n scale = 1.0,\n animated = true,\n}) => {\n const positions = useMemo(\n () => getPatternPositions(pattern, centerPosition),\n [pattern, centerPosition]\n );\n\n if (!visible || pattern === \"none\" || positions.length === 0) {\n return null;\n }\n\n return (\n <group scale={scale}>\n {positions.map((marker, index) => (\n <FootMarker\n key={`${pattern}-${index}`}\n position={marker.position}\n label={marker.label}\n isActive={index === currentStep}\n isCompleted={index < currentStep}\n animated={animated}\n />\n ))}\n </group>\n );\n};\n\nexport default FootPlacementMarkers3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FootPlacementMarkers3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootPlacementMarkers3D.tsx"],"sourcesContent":["/**\n * FootPlacementMarkers3D - 3D markers showing foot placement for footwork drills\n * \n * Renders visual indicators on the ground to guide footwork training,\n * showing target positions for Korean martial arts footwork patterns.\n * \n * @module components/screens/training/components/FootPlacementMarkers3D\n * @category Training 3D Components\n * @korean 발위치표시3D컴포넌트\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useRef, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { FootworkDrill } from \"./FootworkDrillsOverlayHtml\";\n\n/**\n * Footwork drill pattern types for 3D visualization\n * \n * Derived from FootworkDrill, excluding \"free_practice\" (which has no pattern),\n * and adding a \"none\" sentinel for cases with no active pattern.\n */\nexport type FootworkDrillPattern = \n | Exclude<FootworkDrill, \"free_practice\">\n | \"none\";\n\n/**\n * Props for FootPlacementMarkers3D component\n */\nexport interface FootPlacementMarkers3DProps {\n /** Center position for the pattern */\n readonly centerPosition?: [number, number, number];\n /** Current drill pattern to display */\n readonly pattern: FootworkDrillPattern;\n /** Current step in pattern (0-based) */\n readonly currentStep?: number;\n /** Whether markers are visible */\n readonly visible?: boolean;\n /** Scale factor for markers */\n readonly scale?: number;\n /** Whether to animate markers */\n readonly animated?: boolean;\n}\n\n/**\n * Generate footwork pattern positions\n * \n * @korean 보법패턴위치생성\n */\nfunction getPatternPositions(\n pattern: FootworkDrillPattern,\n center: [number, number, number]\n): Array<{ position: [number, number, number]; label: string }> {\n const [cx, cy, cz] = center;\n const radius = 2.0; // 2 meters radius for circular patterns\n const stepDist = 0.3; // 30cm step distance\n\n switch (pattern) {\n case \"circular_left\":\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz - radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz + radius], label: \"4\" },\n ];\n\n case \"circular_right\":\n return [\n { position: [cx + radius, cy, cz], label: \"1\" },\n { position: [cx, cy, cz + radius], label: \"2\" },\n { position: [cx - radius, cy, cz], label: \"3\" },\n { position: [cx, cy, cz - radius], label: \"4\" },\n ];\n\n case \"pivot_combo\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx - 0.5, cy, cz + 0.5], label: \"Pivot L\" },\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx + 0.5, cy, cz + 0.5], label: \"Pivot R\" },\n ];\n\n case \"triangle_step\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - stepDist * 2], label: \"Forward\" },\n { position: [cx + stepDist * 1.5, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz], label: \"Back\" },\n ];\n\n case \"slide_drill\":\n return [\n { position: [cx, cy, cz], label: \"Center\" },\n { position: [cx, cy, cz - stepDist], label: \"Forward\" },\n { position: [cx + stepDist, cy, cz], label: \"Right\" },\n { position: [cx, cy, cz + stepDist], label: \"Back\" },\n { position: [cx - stepDist, cy, cz], label: \"Left\" },\n ];\n\n case \"shuffle_practice\":\n return [\n { position: [cx, cy, cz], label: \"Start\" },\n { position: [cx, cy, cz - 0.15], label: \"Shuffle 1\" },\n { position: [cx, cy, cz - 0.30], label: \"Shuffle 2\" },\n { position: [cx, cy, cz - 0.45], label: \"Shuffle 3\" },\n ];\n\n case \"none\":\n default:\n return [];\n }\n}\n\n/**\n * Single foot placement marker\n */\nconst FootMarker: React.FC<{\n position: [number, number, number];\n label: string;\n isActive: boolean;\n isCompleted: boolean;\n animated: boolean;\n}> = ({ position, label, isActive, isCompleted, animated }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const ringRef = useRef<THREE.Mesh>(null);\n\n useFrame((state) => {\n if (!animated) return;\n \n if (meshRef.current && isActive) {\n const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.1 + 1;\n meshRef.current.scale.setScalar(pulse);\n }\n\n if (ringRef.current && isActive) {\n const ringPulse = Math.sin(state.clock.elapsedTime * 2) * 0.2 + 1;\n ringRef.current.scale.setScalar(ringPulse);\n }\n });\n\n const markerColor = isCompleted\n ? KOREAN_COLORS.ACCENT_GREEN\n : isActive\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.ACCENT_GOLD;\n\n return (\n <group position={position}>\n {/* Ground circle marker */}\n <mesh ref={meshRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.01, 0]}>\n <circleGeometry args={[0.2, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.9 : 0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Outer ring */}\n <mesh ref={ringRef} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>\n <ringGeometry args={[0.2, 0.25, 32]} />\n <meshBasicMaterial\n color={markerColor}\n transparent\n opacity={isActive ? 0.7 : 0.4}\n side={THREE.DoubleSide}\n />\n </mesh>\n\n {/* Label */}\n <Html\n position={[0, 0.1, 0]}\n center\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n color: isActive ? \"#00ffff\" : \"#ffffff\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n background: \"rgba(0,0,0,0.6)\",\n padding: \"2px 6px\",\n borderRadius: \"4px\",\n }}\n >\n {label}\n </div>\n </Html>\n\n {/* Vertical beam for active marker */}\n {isActive && (\n <mesh position={[0, 0.5, 0]}>\n <cylinderGeometry args={[0.02, 0.02, 1, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * FootPlacementMarkers3D Component\n * \n * Displays 3D foot placement markers on the ground for footwork drills,\n * with Korean martial arts-themed visual feedback.\n * \n * @korean 발위치표시3D\n */\nexport const FootPlacementMarkers3D: React.FC<FootPlacementMarkers3DProps> = ({\n centerPosition = [5, 0, 0],\n pattern,\n currentStep = 0,\n visible = true,\n scale = 1.0,\n animated = true,\n}) => {\n const positions = useMemo(\n () => getPatternPositions(pattern, centerPosition),\n [pattern, centerPosition]\n );\n\n if (!visible || pattern === \"none\" || positions.length === 0) {\n return null;\n }\n\n return (\n <group scale={scale}>\n {positions.map((marker, index) => (\n <FootMarker\n key={`${pattern}-${index}`}\n position={marker.position}\n label={marker.label}\n isActive={index === currentStep}\n isCompleted={index < currentStep}\n animated={animated}\n />\n ))}\n </group>\n );\n};\n\nexport default FootPlacementMarkers3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FootworkDrillsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootworkDrillsOverlayHtml.tsx"],"sourcesContent":["/**\n * FootworkDrillsOverlayHtml - Training component for footwork drills\n * \n * Provides specialized footwork training exercises for Korean martial arts\n * footwork patterns (보법, Bobeop).\n * \n * @module components/screens/training/components/FootworkDrillsOverlayHtml\n * @category Training Components\n * @korean 보법훈련컴포넌트\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Footwork drill types for training\n * \n * @korean 보법훈련타입\n */\nexport type FootworkDrill = \n | \"circular_left\" // 원형보 좌 - Circle left around target\n | \"circular_right\" // 원형보 우 - Circle right around target\n | \"pivot_combo\" // 축족회전 - Pivot left-right combo\n | \"triangle_step\" // 삼각보법 - Triangle stepping pattern\n | \"slide_drill\" // 미끄럼보 - Four-direction slide drill\n | \"shuffle_practice\" // 섞음보 - Quick shuffle adjustments\n | \"free_practice\"; // 자유 연습 - Free practice mode\n\n/**\n * Drill information with Korean terminology\n * \n * @korean 훈련정보\n */\nconst DRILL_INFO: Record<FootworkDrill, { \n korean: string; \n english: string; \n description: string;\n pattern: string[];\n keyHints: string;\n}> = {\n circular_left: {\n korean: \"원형보 좌회전\",\n english: \"Circular Left\",\n description: \"원형보 좌측 | Circle stepping left\",\n pattern: [\"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\"],\n keyHints: \"Hold Ctrl+A to circle left\",\n },\n circular_right: {\n korean: \"원형보 우회전\",\n english: \"Circular Right\",\n description: \"원형보 우측 | Circle stepping right\",\n pattern: [\"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\"],\n keyHints: \"Hold Ctrl+D to circle right\",\n },\n pivot_combo: {\n korean: \"축족회전 연속\",\n english: \"Pivot Combo\",\n description: \"좌우 연속 회전 | Continuous pivot rotations\",\n pattern: [\"Shift+Ctrl+A\", \"Shift+Ctrl+D\", \"Shift+Ctrl+A\", \"Shift+Ctrl+D\"],\n keyHints: \"Alternate Shift+Ctrl+A/D\",\n },\n triangle_step: {\n korean: \"삼각보법\",\n english: \"Triangle Step\",\n description: \"삼각형 발놀림 | Triangle footwork pattern\",\n pattern: [\"Ctrl+W\", \"Shift+Ctrl+D\", \"Ctrl+S\", \"Shift+Ctrl+A\"],\n keyHints: \"Forward → Pivot → Back → Pivot\",\n },\n slide_drill: {\n korean: \"미끄럼보 사방\",\n english: \"Slide Drill\",\n description: \"사방 미끄럼 | Four-direction slides\",\n pattern: [\"Ctrl+W\", \"Alt+D\", \"Ctrl+S\", \"Alt+A\"],\n keyHints: \"Slide in all four directions\",\n },\n shuffle_practice: {\n korean: \"섞음보 연습\",\n english: \"Shuffle Practice\",\n description: \"빠른 조정 | Quick micro-adjustments\",\n pattern: [\"Shift+Ctrl+W\", \"Shift+Ctrl+W\", \"Shift+Ctrl+W\"],\n keyHints: \"Rapid Shift+Ctrl+W/S\",\n },\n free_practice: {\n korean: \"자유 연습\",\n english: \"Free Practice\",\n description: \"자유 보법 | Free footwork exploration\",\n pattern: [],\n keyHints: \"Use any footwork combination\",\n },\n};\n\n/**\n * Props for FootworkDrillsOverlayHtml component\n */\nexport interface FootworkDrillsOverlayHtmlProps {\n /** Currently selected drill */\n readonly currentDrill: FootworkDrill;\n /** Callback when drill changes */\n readonly onDrillChange: (drill: FootworkDrill) => void;\n /** Current step in drill pattern (0-based) */\n readonly currentStep: number;\n /** Callback when drill step completes (optional, not yet implemented) */\n readonly onStepComplete?: () => void;\n /** Whether drill is currently active */\n readonly isActive: boolean;\n /** Callback to start/stop drill */\n readonly onToggleActive: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * FootworkDrillsOverlayHtml Component\n * \n * Provides UI for footwork training drills with step-by-step guidance\n * and Korean martial arts terminology.\n * \n * @korean 보법훈련UI컴포넌트\n */\nexport const FootworkDrillsOverlayHtml = React.memo<FootworkDrillsOverlayHtmlProps>(\n ({\n currentDrill,\n onDrillChange,\n currentStep,\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n useEffect(() => {\n if (isActive && showInstructions) {\n const timer = setTimeout(() => setShowInstructions(false), 5000);\n return () => clearTimeout(timer);\n }\n }, [isActive, showInstructions]);\n\n const handleDrillSelect = useCallback((drill: FootworkDrill) => {\n onDrillChange(drill);\n setShowInstructions(true);\n }, [onDrillChange]);\n\n const panelWidth = isMobile ? 280 : 340;\n const buttonFontSize = isMobile ? \"10px\" : \"11px\";\n const titleFontSize = isMobile ? \"13px\" : \"15px\";\n\n return (\n <div\n style={{\n width: `${panelWidth}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.5)}`,\n }}\n data-testid=\"footwork-drills-html\"\n >\n {/* Header */}\n <div style={{ marginBottom: \"12px\", textAlign: \"center\" }}>\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n marginBottom: \"4px\",\n }}\n >\n 보법 훈련 | Footwork Drills\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n }}\n >\n {drillInfo.korean} | {drillInfo.english}\n </div>\n </div>\n\n {/* Drill Selection Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"1fr 1fr 1fr\",\n gap: \"6px\",\n marginBottom: \"12px\",\n }}\n >\n {(Object.keys(DRILL_INFO) as FootworkDrill[]).map((drill) => (\n <button\n key={drill}\n onClick={() => handleDrillSelect(drill)}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n fontSize: buttonFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: currentDrill === drill ? \"bold\" : \"normal\",\n background: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.8),\n color: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n currentDrill === drill\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.UI_BORDER,\n 0.6\n )}`,\n borderRadius: \"6px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n lineHeight: 1.2,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n {DRILL_INFO[drill].korean.split(\" \")[0]}\n </button>\n ))}\n </div>\n\n {/* Drill Description */}\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n textAlign: \"center\",\n }}\n >\n {drillInfo.description}\n </div>\n\n {/* Pattern Steps (if drill has pattern) */}\n {drillInfo.pattern.length > 0 && (\n <div style={{ marginBottom: \"10px\" }}>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n marginBottom: \"6px\",\n textAlign: \"center\",\n }}\n >\n Pattern Steps:\n </div>\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n gap: \"4px\",\n flexWrap: \"wrap\",\n }}\n >\n {drillInfo.pattern.map((step, index) => (\n <div\n key={index}\n style={{\n padding: \"4px 8px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n fontFamily: FONT_FAMILY.KOREAN,\n background: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9)\n : index < currentStep\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.7),\n color: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n isActive && index === currentStep\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"4px\",\n fontWeight: isActive && index === currentStep ? \"bold\" : \"normal\",\n }}\n >\n {index + 1}. {step}\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Key Hints */}\n {showInstructions && (\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n textAlign: \"center\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n 💡 {drillInfo.keyHints}\n </div>\n )}\n\n {/* Start/Stop Button */}\n <button\n onClick={onToggleActive}\n style={{\n width: \"100%\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n background: isActive\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.9)\n : hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.9),\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: \"none\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 0 20px ${hexToRgbaString(\n isActive ? KOREAN_COLORS.ACCENT_RED : KOREAN_COLORS.ACCENT_GREEN,\n 0.8\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = \"none\";\n }}\n >\n {isActive ? \"훈련 중지 | Stop Drill\" : \"훈련 시작 | Start Drill\"}\n </button>\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.currentDrill === nextProps.currentDrill &&\n prevProps.currentStep === nextProps.currentStep &&\n prevProps.isActive === nextProps.isActive &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onDrillChange === nextProps.onDrillChange &&\n prevProps.onToggleActive === nextProps.onToggleActive\n );\n});\n\nFootworkDrillsOverlayHtml.displayName = \"FootworkDrillsOverlayHtml\";\n\nexport default FootworkDrillsOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FootworkDrillsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/FootworkDrillsOverlayHtml.tsx"],"sourcesContent":["/**\n * FootworkDrillsOverlayHtml - Training component for footwork drills\n * \n * Provides specialized footwork training exercises for Korean martial arts\n * footwork patterns (보법, Bobeop).\n * \n * @module components/screens/training/components/FootworkDrillsOverlayHtml\n * @category Training Components\n * @korean 보법훈련컴포넌트\n */\n\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/**\n * Footwork drill types for training\n * \n * @korean 보법훈련타입\n */\nexport type FootworkDrill = \n | \"circular_left\" // 원형보 좌 - Circle left around target\n | \"circular_right\" // 원형보 우 - Circle right around target\n | \"pivot_combo\" // 축족회전 - Pivot left-right combo\n | \"triangle_step\" // 삼각보법 - Triangle stepping pattern\n | \"slide_drill\" // 미끄럼보 - Four-direction slide drill\n | \"shuffle_practice\" // 섞음보 - Quick shuffle adjustments\n | \"free_practice\"; // 자유 연습 - Free practice mode\n\n/**\n * Drill information with Korean terminology\n * \n * @korean 훈련정보\n */\nconst DRILL_INFO: Record<FootworkDrill, { \n korean: string; \n english: string; \n description: string;\n pattern: string[];\n keyHints: string;\n}> = {\n circular_left: {\n korean: \"원형보 좌회전\",\n english: \"Circular Left\",\n description: \"원형보 좌측 | Circle stepping left\",\n pattern: [\"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\", \"Ctrl+A\"],\n keyHints: \"Hold Ctrl+A to circle left\",\n },\n circular_right: {\n korean: \"원형보 우회전\",\n english: \"Circular Right\",\n description: \"원형보 우측 | Circle stepping right\",\n pattern: [\"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\", \"Ctrl+D\"],\n keyHints: \"Hold Ctrl+D to circle right\",\n },\n pivot_combo: {\n korean: \"축족회전 연속\",\n english: \"Pivot Combo\",\n description: \"좌우 연속 회전 | Continuous pivot rotations\",\n pattern: [\"Shift+Ctrl+A\", \"Shift+Ctrl+D\", \"Shift+Ctrl+A\", \"Shift+Ctrl+D\"],\n keyHints: \"Alternate Shift+Ctrl+A/D\",\n },\n triangle_step: {\n korean: \"삼각보법\",\n english: \"Triangle Step\",\n description: \"삼각형 발놀림 | Triangle footwork pattern\",\n pattern: [\"Ctrl+W\", \"Shift+Ctrl+D\", \"Ctrl+S\", \"Shift+Ctrl+A\"],\n keyHints: \"Forward → Pivot → Back → Pivot\",\n },\n slide_drill: {\n korean: \"미끄럼보 사방\",\n english: \"Slide Drill\",\n description: \"사방 미끄럼 | Four-direction slides\",\n pattern: [\"Ctrl+W\", \"Alt+D\", \"Ctrl+S\", \"Alt+A\"],\n keyHints: \"Slide in all four directions\",\n },\n shuffle_practice: {\n korean: \"섞음보 연습\",\n english: \"Shuffle Practice\",\n description: \"빠른 조정 | Quick micro-adjustments\",\n pattern: [\"Shift+Ctrl+W\", \"Shift+Ctrl+W\", \"Shift+Ctrl+W\"],\n keyHints: \"Rapid Shift+Ctrl+W/S\",\n },\n free_practice: {\n korean: \"자유 연습\",\n english: \"Free Practice\",\n description: \"자유 보법 | Free footwork exploration\",\n pattern: [],\n keyHints: \"Use any footwork combination\",\n },\n};\n\n/**\n * Props for FootworkDrillsOverlayHtml component\n */\nexport interface FootworkDrillsOverlayHtmlProps {\n /** Currently selected drill */\n readonly currentDrill: FootworkDrill;\n /** Callback when drill changes */\n readonly onDrillChange: (drill: FootworkDrill) => void;\n /** Current step in drill pattern (0-based) */\n readonly currentStep: number;\n /** Callback when drill step completes (optional, not yet implemented) */\n readonly onStepComplete?: () => void;\n /** Whether drill is currently active */\n readonly isActive: boolean;\n /** Callback to start/stop drill */\n readonly onToggleActive: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * FootworkDrillsOverlayHtml Component\n * \n * Provides UI for footwork training drills with step-by-step guidance\n * and Korean martial arts terminology.\n * \n * @korean 보법훈련UI컴포넌트\n */\nexport const FootworkDrillsOverlayHtml = React.memo<FootworkDrillsOverlayHtmlProps>(\n ({\n currentDrill,\n onDrillChange,\n currentStep,\n isActive,\n onToggleActive,\n isMobile,\n }) => {\n const drillInfo = DRILL_INFO[currentDrill];\n const [showInstructions, setShowInstructions] = useState(true);\n\n useEffect(() => {\n if (isActive && showInstructions) {\n const timer = setTimeout(() => setShowInstructions(false), 5000);\n return () => clearTimeout(timer);\n }\n }, [isActive, showInstructions]);\n\n const handleDrillSelect = useCallback((drill: FootworkDrill) => {\n onDrillChange(drill);\n setShowInstructions(true);\n }, [onDrillChange]);\n\n const panelWidth = isMobile ? 280 : 340;\n const buttonFontSize = isMobile ? \"10px\" : \"11px\";\n const titleFontSize = isMobile ? \"13px\" : \"15px\";\n\n return (\n <div\n style={{\n width: `${panelWidth}px`,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n boxShadow: `0 4px 20px ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.5)}`,\n }}\n data-testid=\"footwork-drills-html\"\n >\n {/* Header */}\n <div style={{ marginBottom: \"12px\", textAlign: \"center\" }}>\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n marginBottom: \"4px\",\n }}\n >\n 보법 훈련 | Footwork Drills\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 1),\n }}\n >\n {drillInfo.korean} | {drillInfo.english}\n </div>\n </div>\n\n {/* Drill Selection Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"1fr 1fr 1fr\",\n gap: \"6px\",\n marginBottom: \"12px\",\n }}\n >\n {(Object.keys(DRILL_INFO) as FootworkDrill[]).map((drill) => (\n <button\n key={drill}\n onClick={() => handleDrillSelect(drill)}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n fontSize: buttonFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: currentDrill === drill ? \"bold\" : \"normal\",\n background: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.9)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.8),\n color: \n currentDrill === drill\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n currentDrill === drill\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.UI_BORDER,\n 0.6\n )}`,\n borderRadius: \"6px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n textAlign: \"center\",\n lineHeight: 1.2,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n {DRILL_INFO[drill].korean.split(\" \")[0]}\n </button>\n ))}\n </div>\n\n {/* Drill Description */}\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n textAlign: \"center\",\n }}\n >\n {drillInfo.description}\n </div>\n\n {/* Pattern Steps (if drill has pattern) */}\n {drillInfo.pattern.length > 0 && (\n <div style={{ marginBottom: \"10px\" }}>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n marginBottom: \"6px\",\n textAlign: \"center\",\n }}\n >\n Pattern Steps:\n </div>\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n gap: \"4px\",\n flexWrap: \"wrap\",\n }}\n >\n {drillInfo.pattern.map((step, index) => (\n <div\n key={index}\n style={{\n padding: \"4px 8px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n fontFamily: FONT_FAMILY.KOREAN,\n background: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.9)\n : index < currentStep\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6)\n : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.7),\n color: \n isActive && index === currentStep\n ? hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n isActive && index === currentStep\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"4px\",\n fontWeight: isActive && index === currentStep ? \"bold\" : \"normal\",\n }}\n >\n {index + 1}. {step}\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Key Hints */}\n {showInstructions && (\n <div\n style={{\n padding: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2),\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"6px\",\n marginBottom: \"10px\",\n fontSize: isMobile ? \"9px\" : \"10px\",\n textAlign: \"center\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n }}\n >\n 💡 {drillInfo.keyHints}\n </div>\n )}\n\n {/* Start/Stop Button */}\n <button\n onClick={onToggleActive}\n style={{\n width: \"100%\",\n padding: isMobile ? \"10px\" : \"12px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n background: isActive\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.9)\n : hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.9),\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 1),\n border: \"none\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 0 20px ${hexToRgbaString(\n isActive ? KOREAN_COLORS.ACCENT_RED : KOREAN_COLORS.ACCENT_GREEN,\n 0.8\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = \"none\";\n }}\n >\n {isActive ? \"훈련 중지 | Stop Drill\" : \"훈련 시작 | Start Drill\"}\n </button>\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.currentDrill === nextProps.currentDrill &&\n prevProps.currentStep === nextProps.currentStep &&\n prevProps.isActive === nextProps.isActive &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onDrillChange === nextProps.onDrillChange &&\n prevProps.onToggleActive === nextProps.onToggleActive\n );\n});\n\nFootworkDrillsOverlayHtml.displayName = \"FootworkDrillsOverlayHtml\";\n\nexport default FootworkDrillsOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HitFeedbackEffect3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/HitFeedbackEffect3D.tsx"],"sourcesContent":["/**\n * HitFeedbackEffect3D - Visual hit confirmation with damage numbers\n *\n * Provides particle effects, color flashes, and floating damage numbers\n * for training hit feedback\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { ThreeObjectPools } from \"../../../../utils/threeObjectPool\";\n\n/**\n * Props for HitFeedbackEffect3D component\n */\nexport interface HitFeedbackEffect3DProps {\n /** 3D position where hit occurred */\n readonly position: [number, number, number];\n /** Type of hit (affects visual style) */\n readonly type: \"success\" | \"perfect\" | \"miss\";\n /** Damage dealt (if applicable) */\n readonly damage?: number;\n /** Whether effect is visible */\n readonly visible?: boolean;\n /** Callback when effect completes */\n readonly onComplete?: () => void;\n /** Duration in milliseconds */\n readonly duration?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n}\n\n/**\n * Impact particle system\n */\nconst ImpactParticles: React.FC<{\n position: [number, number, number];\n color: number;\n count: number;\n}> = ({ position, color, count }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n const [initialPosition] = useState(position);\n\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n const theta = seededRandom(i * 3) * Math.PI * 2;\n const phi = seededRandom(i * 3 + 1) * Math.PI;\n const speed = 0.5 + seededRandom(i * 3 + 2) * 1.5;\n\n tempVel.set(\n Math.sin(phi) * Math.cos(theta),\n Math.cos(phi),\n Math.sin(phi) * Math.sin(theta)\n );\n tempVel.normalize().multiplyScalar(speed);\n \n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n ThreeObjectPools.vector3.release(tempVel);\n }\n\n return { positions: pos, velocities: vel };\n }, [count, initialPosition]); // initialPosition is captured at mount and won't change\n\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n useFrame((_, delta) => {\n if (!pointsRef.current || !velocitiesRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n const vel = velocitiesRef.current;\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n\n array[i3] += vel[i3] * delta * 10;\n array[i3 + 1] += vel[i3 + 1] * delta * 10;\n array[i3 + 2] += vel[i3 + 2] * delta * 10;\n\n vel[i3 + 1] -= 9.8 * delta;\n\n if (array[i3 + 1] < -2) {\n array[i3 + 1] = -2;\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef} position={position}>\n <bufferGeometry>\n <bufferAttribute attach=\"attributes-position\" args={[positions, 3]} />\n </bufferGeometry>\n <pointsMaterial\n size={0.08}\n color={color}\n transparent\n opacity={1.0}\n sizeAttenuation\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </points>\n );\n};\n\n/**\n * Expanding ring effect\n */\nconst RingEffect: React.FC<{\n position: [number, number, number];\n color: number;\n maxRadius: number;\n}> = ({ position, color, maxRadius }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const startTime = useRef<number>(0);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 0.5, 1); // 0.5 second duration\n\n const radius = progress * maxRadius;\n const tempScale = ThreeObjectPools.vector3.acquire();\n try {\n tempScale.setScalar(radius);\n meshRef.current.scale.copy(tempScale);\n } finally {\n ThreeObjectPools.vector3.release(tempScale);\n }\n\n const material = meshRef.current.material as THREE.MeshBasicMaterial;\n material.opacity = 1 - progress;\n });\n\n return (\n <mesh ref={meshRef} position={position} rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[0.9, 1.0, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n />\n </mesh>\n );\n};\n\n/**\n * Floating damage number\n */\nconst DamageNumber: React.FC<{\n position: [number, number, number];\n damage: number;\n type: \"perfect\" | \"normal\" | \"miss\";\n isMobile: boolean;\n onComplete: () => void;\n}> = ({ position, damage, type, isMobile, onComplete }) => {\n const [offset, setOffset] = useState(0);\n const [opacity, setOpacity] = useState(1);\n const startTime = useRef<number>(0);\n const completedRef = useRef(false);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 1.5, 1); // 1.5 second duration\n\n setOffset(progress * 1);\n\n setOpacity(1 - progress);\n\n if (progress >= 1 && !completedRef.current && onComplete) {\n completedRef.current = true;\n onComplete();\n }\n });\n\n const color =\n type === \"perfect\" ? \"#ffd700\" : type === \"normal\" ? \"#00ffff\" : \"#ff4444\";\n const text = type === \"miss\" ? \"빗나감 | MISS\" : `${damage}`; // Korean: 빗나감 = miss/deflected\n\n return (\n <Html\n position={[position[0], position[1] + offset, position[2]]}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\", opacity }}\n >\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: \"0 0 10px rgba(0, 0, 0, 0.8)\",\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"damage-number\"\n >\n {text}\n </div>\n </Html>\n );\n};\n\n/**\n * HitFeedbackEffect3D Component\n * Main hit feedback visualization\n */\nexport const HitFeedbackEffect3D: React.FC<HitFeedbackEffect3DProps> = ({\n position,\n type,\n damage,\n visible = true,\n onComplete,\n duration = 1500,\n isMobile = false,\n}) => {\n const [showEffect, setShowEffect] = useState(visible);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const effectColor = useMemo(() => {\n switch (type) {\n case \"perfect\":\n return KOREAN_COLORS.ACCENT_GOLD;\n case \"success\":\n return KOREAN_COLORS.PRIMARY_CYAN;\n case \"miss\":\n return KOREAN_COLORS.TEXT_SECONDARY;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n }, [type]);\n\n const particleCount = useMemo(() => {\n const isPerfect = type === \"perfect\";\n const isSuccess = type === \"success\";\n\n if (isMobile) {\n return isPerfect ? 30 : isSuccess ? 20 : 10;\n }\n\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n const completedRef = useRef(false);\n\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n useEffect(() => {\n if (visible && showEffect) {\n timeoutRef.current = setTimeout(handleComplete, duration);\n }\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [visible, showEffect, duration, handleComplete]);\n\n if (!showEffect) return null;\n\n return (\n <group name={`hit-feedback-${type}`}>\n {/* Particle burst */}\n <ImpactParticles\n position={position}\n color={effectColor}\n count={particleCount}\n />\n\n {/* Expanding ring */}\n <RingEffect\n position={position}\n color={effectColor}\n maxRadius={ringRadius}\n />\n\n {/* Floating damage number */}\n {damage !== undefined && type !== \"miss\" && (\n <DamageNumber\n position={position}\n damage={damage}\n type={type === \"perfect\" ? \"perfect\" : \"normal\"}\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n\n {/* Miss indicator */}\n {type === \"miss\" && (\n <DamageNumber\n position={position}\n damage={0}\n type=\"miss\"\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n </group>\n );\n};\n\nexport default HitFeedbackEffect3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HitFeedbackEffect3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/HitFeedbackEffect3D.tsx"],"sourcesContent":["/**\n * HitFeedbackEffect3D - Visual hit confirmation with damage numbers\n *\n * Provides particle effects, color flashes, and floating damage numbers\n * for training hit feedback\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { ThreeObjectPools } from \"../../../../utils/threeObjectPool\";\n\n/**\n * Props for HitFeedbackEffect3D component\n */\nexport interface HitFeedbackEffect3DProps {\n /** 3D position where hit occurred */\n readonly position: [number, number, number];\n /** Type of hit (affects visual style) */\n readonly type: \"success\" | \"perfect\" | \"miss\";\n /** Damage dealt (if applicable) */\n readonly damage?: number;\n /** Whether effect is visible */\n readonly visible?: boolean;\n /** Callback when effect completes */\n readonly onComplete?: () => void;\n /** Duration in milliseconds */\n readonly duration?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n}\n\n/**\n * Impact particle system\n */\nconst ImpactParticles: React.FC<{\n position: [number, number, number];\n color: number;\n count: number;\n}> = ({ position, color, count }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const velocitiesRef = useRef<Float32Array | null>(null);\n\n const [initialPosition] = useState(position);\n\n const { positions, velocities } = useMemo(() => {\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n const seed =\n initialPosition[0] + initialPosition[1] * 10 + initialPosition[2] * 100;\n\n function seededRandom(index: number): number {\n const x = Math.sin(seed + index) * 10000;\n return x - Math.floor(x);\n }\n\n const tempVel = ThreeObjectPools.vector3.acquire();\n \n try {\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = 0;\n pos[i3 + 1] = 0;\n pos[i3 + 2] = 0;\n\n const theta = seededRandom(i * 3) * Math.PI * 2;\n const phi = seededRandom(i * 3 + 1) * Math.PI;\n const speed = 0.5 + seededRandom(i * 3 + 2) * 1.5;\n\n tempVel.set(\n Math.sin(phi) * Math.cos(theta),\n Math.cos(phi),\n Math.sin(phi) * Math.sin(theta)\n );\n tempVel.normalize().multiplyScalar(speed);\n \n vel[i3] = tempVel.x;\n vel[i3 + 1] = tempVel.y + 1;\n vel[i3 + 2] = tempVel.z;\n }\n } finally {\n ThreeObjectPools.vector3.release(tempVel);\n }\n\n return { positions: pos, velocities: vel };\n }, [count, initialPosition]); // initialPosition is captured at mount and won't change\n\n useEffect(() => {\n velocitiesRef.current = velocities;\n }, [velocities]);\n\n useFrame((_, delta) => {\n if (!pointsRef.current || !velocitiesRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n const vel = velocitiesRef.current;\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n\n array[i3] += vel[i3] * delta * 10;\n array[i3 + 1] += vel[i3 + 1] * delta * 10;\n array[i3 + 2] += vel[i3 + 2] * delta * 10;\n\n vel[i3 + 1] -= 9.8 * delta;\n\n if (array[i3 + 1] < -2) {\n array[i3 + 1] = -2;\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef} position={position}>\n <bufferGeometry>\n <bufferAttribute attach=\"attributes-position\" args={[positions, 3]} />\n </bufferGeometry>\n <pointsMaterial\n size={0.08}\n color={color}\n transparent\n opacity={1.0}\n sizeAttenuation\n depthWrite={false}\n blending={THREE.AdditiveBlending}\n />\n </points>\n );\n};\n\n/**\n * Expanding ring effect\n */\nconst RingEffect: React.FC<{\n position: [number, number, number];\n color: number;\n maxRadius: number;\n}> = ({ position, color, maxRadius }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const startTime = useRef<number>(0);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 0.5, 1); // 0.5 second duration\n\n const radius = progress * maxRadius;\n const tempScale = ThreeObjectPools.vector3.acquire();\n try {\n tempScale.setScalar(radius);\n meshRef.current.scale.copy(tempScale);\n } finally {\n ThreeObjectPools.vector3.release(tempScale);\n }\n\n const material = meshRef.current.material as THREE.MeshBasicMaterial;\n material.opacity = 1 - progress;\n });\n\n return (\n <mesh ref={meshRef} position={position} rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[0.9, 1.0, 32]} />\n <meshBasicMaterial\n color={color}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n />\n </mesh>\n );\n};\n\n/**\n * Floating damage number\n */\nconst DamageNumber: React.FC<{\n position: [number, number, number];\n damage: number;\n type: \"perfect\" | \"normal\" | \"miss\";\n isMobile: boolean;\n onComplete: () => void;\n}> = ({ position, damage, type, isMobile, onComplete }) => {\n const [offset, setOffset] = useState(0);\n const [opacity, setOpacity] = useState(1);\n const startTime = useRef<number>(0);\n const completedRef = useRef(false);\n\n useEffect(() => {\n if (startTime.current === 0) {\n startTime.current = Date.now();\n }\n }, []);\n\n useFrame(() => {\n const elapsed = (Date.now() - startTime.current) / 1000;\n const progress = Math.min(elapsed / 1.5, 1); // 1.5 second duration\n\n setOffset(progress * 1);\n\n setOpacity(1 - progress);\n\n if (progress >= 1 && !completedRef.current && onComplete) {\n completedRef.current = true;\n onComplete();\n }\n });\n\n const color =\n type === \"perfect\" ? \"#ffd700\" : type === \"normal\" ? \"#00ffff\" : \"#ff4444\";\n const text = type === \"miss\" ? \"빗나감 | MISS\" : `${damage}`; // Korean: 빗나감 = miss/deflected\n\n return (\n <Html\n position={[position[0], position[1] + offset, position[2]]}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\", opacity }}\n >\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: \"0 0 10px rgba(0, 0, 0, 0.8)\",\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"damage-number\"\n >\n {text}\n </div>\n </Html>\n );\n};\n\n/**\n * HitFeedbackEffect3D Component\n * Main hit feedback visualization\n */\nexport const HitFeedbackEffect3D: React.FC<HitFeedbackEffect3DProps> = ({\n position,\n type,\n damage,\n visible = true,\n onComplete,\n duration = 1500,\n isMobile = false,\n}) => {\n const [showEffect, setShowEffect] = useState(visible);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const effectColor = useMemo(() => {\n switch (type) {\n case \"perfect\":\n return KOREAN_COLORS.ACCENT_GOLD;\n case \"success\":\n return KOREAN_COLORS.PRIMARY_CYAN;\n case \"miss\":\n return KOREAN_COLORS.TEXT_SECONDARY;\n default:\n return KOREAN_COLORS.PRIMARY_CYAN;\n }\n }, [type]);\n\n const particleCount = useMemo(() => {\n const isPerfect = type === \"perfect\";\n const isSuccess = type === \"success\";\n\n if (isMobile) {\n return isPerfect ? 30 : isSuccess ? 20 : 10;\n }\n\n return isPerfect ? 80 : isSuccess ? 50 : 25;\n }, [type, isMobile]);\n const ringRadius = type === \"perfect\" ? 1.5 : 1.0;\n\n const completedRef = useRef(false);\n\n const handleComplete = useCallback(() => {\n if (completedRef.current) return;\n completedRef.current = true;\n setShowEffect(false);\n onComplete?.();\n }, [onComplete]);\n\n useEffect(() => {\n if (visible && showEffect) {\n timeoutRef.current = setTimeout(handleComplete, duration);\n }\n\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, [visible, showEffect, duration, handleComplete]);\n\n if (!showEffect) return null;\n\n return (\n <group name={`hit-feedback-${type}`}>\n {/* Particle burst */}\n <ImpactParticles\n position={position}\n color={effectColor}\n count={particleCount}\n />\n\n {/* Expanding ring */}\n <RingEffect\n position={position}\n color={effectColor}\n maxRadius={ringRadius}\n />\n\n {/* Floating damage number */}\n {damage !== undefined && type !== \"miss\" && (\n <DamageNumber\n position={position}\n damage={damage}\n type={type === \"perfect\" ? \"perfect\" : \"normal\"}\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n\n {/* Miss indicator */}\n {type === \"miss\" && (\n <DamageNumber\n position={position}\n damage={0}\n type=\"miss\"\n isMobile={isMobile}\n onComplete={handleComplete}\n />\n )}\n </group>\n );\n};\n\nexport default HitFeedbackEffect3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingButtons - Reusable button components for TrainingScreen\n * \n * Provides return-to-menu button and archetype selection buttons.\n * Extracted from TrainingScreen3D to reduce code duplication.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련버튼\n */\n\nimport React, { useCallback } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface ReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from training screen.\n * Uses BaseButtonOverlayHtml for consistent Korean theming.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <ReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const ReturnToMenuButton: React.FC<ReturnToMenuButtonProps> = ({\n onClick,\n onMouseEnter,\n isMobile,\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={isMobile ? \"메뉴\" : \"메뉴로\"}\n english={isMobile ? \"Menu\" : \"Return to Menu\"}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId=\"return-to-menu-button\"\n />\n );\n};\n\nexport interface ArchetypeSelectionButtonsProps {\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Callback when archetype is selected */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ArchetypeSelectionButtons Component\n * \n * Grid of buttons to select player archetype during training.\n * Highlights selected archetype with gold background.\n * \n * Reduces code duplication by 35 lines from TrainingScreen3D (inline button logic)\n * \n * @example\n * ```tsx\n * <ArchetypeSelectionButtons\n * selectedArchetype={PlayerArchetype.MUSA}\n * onArchetypeSelect={(arch) => setArchetype(arch)}\n * onPlaySFX={(sound) => audio.play(sound)}\n * isMobile={false}\n * />\n * ```\n */\nexport const ArchetypeSelectionButtons: React.FC<\n ArchetypeSelectionButtonsProps\n> = ({ selectedArchetype, onArchetypeSelect, onPlaySFX, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n\n const handleArchetypeClick = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeSelect(archetype);\n onPlaySFX?.(\"menu_select\");\n },\n [onArchetypeSelect, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"4px\",\n justifyContent: \"center\",\n }}\n data-testid=\"archetype-selection-buttons\"\n >\n {Object.values(PlayerArchetype).map((arch) => (\n <button\n key={arch}\n onClick={() => handleArchetypeClick(arch)}\n style={{\n padding: isMobile ? \"4px 8px\" : \"6px 10px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n fontWeight: selectedArchetype === arch ? \"bold\" : \"normal\",\n background:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8)\n : hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, 0.8),\n color:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(theme.colors.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n selectedArchetype === arch\n ? theme.colors.ACCENT_GOLD\n : theme.colors.UI_BORDER,\n 0.6,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n data-testid={`archetype-button-${arch}`}\n aria-label={`Select ${arch} archetype`}\n aria-pressed={selectedArchetype === arch}\n >\n {arch.toUpperCase()}\n </button>\n ))}\n </div>\n );\n};\n\nexport default { ReturnToMenuButton, ArchetypeSelectionButtons };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,sBAAyD,EACpE,SACA,cACA,eACI;CACJ,OACE,oBAAC,uBAAD;EACE,QAAQ,WAAW,OAAO;EAC1B,SAAS,WAAW,SAAS;EACpB;EACK;EACd,SAAQ;EACR,MAAK;EACK;EACV,QAAO;
|
|
1
|
+
{"version":3,"file":"TrainingButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingButtons - Reusable button components for TrainingScreen\n * \n * Provides return-to-menu button and archetype selection buttons.\n * Extracted from TrainingScreen3D to reduce code duplication.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련버튼\n */\n\nimport React, { useCallback } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface ReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from training screen.\n * Uses BaseButtonOverlayHtml for consistent Korean theming.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <ReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const ReturnToMenuButton: React.FC<ReturnToMenuButtonProps> = ({\n onClick,\n onMouseEnter,\n isMobile,\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={isMobile ? \"메뉴\" : \"메뉴로\"}\n english={isMobile ? \"Menu\" : \"Return to Menu\"}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId=\"return-to-menu-button\"\n />\n );\n};\n\nexport interface ArchetypeSelectionButtonsProps {\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Callback when archetype is selected */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * ArchetypeSelectionButtons Component\n * \n * Grid of buttons to select player archetype during training.\n * Highlights selected archetype with gold background.\n * \n * Reduces code duplication by 35 lines from TrainingScreen3D (inline button logic)\n * \n * @example\n * ```tsx\n * <ArchetypeSelectionButtons\n * selectedArchetype={PlayerArchetype.MUSA}\n * onArchetypeSelect={(arch) => setArchetype(arch)}\n * onPlaySFX={(sound) => audio.play(sound)}\n * isMobile={false}\n * />\n * ```\n */\nexport const ArchetypeSelectionButtons: React.FC<\n ArchetypeSelectionButtonsProps\n> = ({ selectedArchetype, onArchetypeSelect, onPlaySFX, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n\n const handleArchetypeClick = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeSelect(archetype);\n onPlaySFX?.(\"menu_select\");\n },\n [onArchetypeSelect, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"4px\",\n justifyContent: \"center\",\n }}\n data-testid=\"archetype-selection-buttons\"\n >\n {Object.values(PlayerArchetype).map((arch) => (\n <button\n key={arch}\n onClick={() => handleArchetypeClick(arch)}\n style={{\n padding: isMobile ? \"4px 8px\" : \"6px 10px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n fontWeight: selectedArchetype === arch ? \"bold\" : \"normal\",\n background:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8)\n : hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, 0.8),\n color:\n selectedArchetype === arch\n ? hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 1)\n : hexToRgbaString(theme.colors.TEXT_PRIMARY, 1),\n border: `1px solid ${hexToRgbaString(\n selectedArchetype === arch\n ? theme.colors.ACCENT_GOLD\n : theme.colors.UI_BORDER,\n 0.6,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n }}\n data-testid={`archetype-button-${arch}`}\n aria-label={`Select ${arch} archetype`}\n aria-pressed={selectedArchetype === arch}\n >\n {arch.toUpperCase()}\n </button>\n ))}\n </div>\n );\n};\n\nexport default { ReturnToMenuButton, ArchetypeSelectionButtons };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,sBAAyD,EACpE,SACA,cACA,eACI;CACJ,OACE,oBAAC,uBAAD;EACE,QAAQ,WAAW,OAAO;EAC1B,SAAS,WAAW,SAAS;EACpB;EACK;EACd,SAAQ;EACR,MAAK;EACK;EACV,QAAO;CACR,CAAA;AAEL;;;;;;;;;;;;;;;;;;;AA+BA,IAAa,6BAER,EAAE,mBAAmB,mBAAmB,WAAW,eAAe;CACrE,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;CAAS,CAAC;CAEzE,MAAM,uBAAuB,aAC1B,cAA+B;EAC9B,kBAAkB,SAAS;EAC3B,YAAY,aAAa;CAC3B,GACA,CAAC,mBAAmB,SAAS,CAC/B;CAEA,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,UAAU;GACV,KAAK;GACL,gBAAgB;EAClB;EACA,eAAY;YAEX,OAAO,OAAO,eAAe,EAAE,KAAK,SACnC,oBAAC,UAAD;GAEE,eAAe,qBAAqB,IAAI;GACxC,OAAO;IACL,SAAS,WAAW,YAAY;IAChC,UAAU,WAAW,QAAQ;IAC7B,YAAY,MAAM,iBAAiB;IACnC,YAAY,sBAAsB,OAAO,SAAS;IAClD,YACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,aAAa,EAAG,IAC7C,gBAAgB,MAAM,OAAO,sBAAsB,EAAG;IAC5D,OACE,sBAAsB,OAClB,gBAAgB,MAAM,OAAO,oBAAoB,CAAC,IAClD,gBAAgB,MAAM,OAAO,cAAc,CAAC;IAClD,QAAQ,aAAa,gBACnB,sBAAsB,OAClB,MAAM,OAAO,cACb,MAAM,OAAO,WACjB,EACF;IACA,cAAc;IACd,QAAQ;IACR,YAAY;GACd;GACA,eAAa,oBAAoB;GACjC,cAAY,UAAU,KAAK;GAC3B,gBAAc,sBAAsB;aAEnC,KAAK,YAAY;EACZ,GA9BD,IA8BC,CACT;CACE,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingControlsOverlayHtml - Html overlay for training controls\n * \n * Displays start/stop button and training status with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련제어오버레이\n */\n\nimport React from \"react\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getKoreanButtonWithGlow,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingControlsOverlayHtml component\n */\nexport interface TrainingControlsOverlayHtmlProps {\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Callback to start training */\n readonly onStartTraining: () => void;\n /** Callback to stop training */\n readonly onStopTraining: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Compact mode for embedding inside the slim top HUD */\n readonly variant?: \"panel\" | \"compact\";\n}\n\n/**\n * TrainingControlsOverlayHtml Component\n * \n * Html overlay showing training status and start/stop controls with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when isTraining state hasn't changed\n * - Callbacks expected to be stable (parent should use useCallback)\n * \n * @example\n * ```tsx\n * <TrainingControlsOverlayHtml\n * isTraining={true}\n * onStartTraining={() => console.log('start')}\n * onStopTraining={() => console.log('stop')}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 훈련제어오버레이컴포넌트\n */\nexport const TrainingControlsOverlayHtml = React.memo<TrainingControlsOverlayHtmlProps>(\n ({\n isTraining,\n onStartTraining,\n onStopTraining,\n isMobile,\n variant = \"panel\",\n }) => {\n const isCompact = variant === \"compact\";\n const panelWidth = isCompact ? (isMobile ? 180 : 210) : isMobile ? 200 : 220;\n const panelHeight = isCompact ? (isMobile ? 40 : 44) : isMobile ? 90 : 100;\n const padding = isCompact ? getResponsiveSpacing(\"xs\", isMobile) : getResponsiveSpacing(\"sm\", isMobile);\n\n const stateColor = isTraining ? KOREAN_COLORS.ACCENT_GREEN : KOREAN_COLORS.ACCENT_RED;\n const borderColor = hexToRgbaString(stateColor, 0.9);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: isTraining ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 2,\n }),\n width: `${panelWidth}px`,\n height: `${panelHeight}px`,\n padding: `${padding}px`,\n border: `2px solid ${borderColor}`,\n position: \"relative\",\n display: \"flex\",\n flexDirection: isCompact ? \"row\" : \"column\",\n alignItems: isCompact ? \"center\" : \"stretch\",\n justifyContent: isCompact ? \"space-between\" : \"flex-start\",\n gap: `${padding}px`,\n boxSizing: \"border-box\",\n fontFamily: FONT_FAMILY.KOREAN,\n };\n\n const buttonStyles = React.useMemo(\n () =>\n getKoreanButtonWithGlow({\n variant: isTraining ? \"danger\" : \"success\",\n glowIntensity: \"strong\",\n hoverAnimation: \"combined\",\n }),\n [isTraining]\n );\n\n const titleFontSize = isCompact ? (isMobile ? 11 : 12) : isMobile ? 13 : 14;\n const infoFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-controls-html\">\n {/* Header with bilingual status */}\n <div style={{ marginBottom: isCompact ? 0 : `${SPACING.SM}px`, minWidth: 0 }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(stateColor),\n textShadow: getNeonTextShadow(stateColor, isTraining ? \"medium\" : \"subtle\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatBilingualText(\n isTraining ? \"훈련 진행중\" : \"훈련 대기\",\n isTraining ? \"Training Active\" : \"Training Stopped\",\n \"pipe\"\n )}\n </div>\n </div>\n\n {/* Status Indicator with CSS animation */}\n <div\n className={`status-indicator ${isTraining ? \"active\" : \"inactive\"}`}\n style={{\n position: \"absolute\",\n top: isCompact ? \"6px\" : \"12px\",\n right: isCompact ? \"6px\" : \"12px\",\n }}\n />\n\n {/* Start/Stop Button with Korean theming */}\n <button\n onClick={isTraining ? onStopTraining : onStartTraining}\n className={`training-button ${isTraining ? \"training-button-stop\" : \"training-button-start\"}`}\n style={{\n ...buttonStyles,\n fontSize: `${titleFontSize}px`,\n height: isCompact ? \"30px\" : \"35px\",\n minWidth: isCompact ? \"72px\" : undefined,\n padding: isCompact ? \"4px 8px\" : buttonStyles.padding,\n flexShrink: 0,\n }}\n data-testid=\"training-toggle-button\"\n data-training-state={isTraining ? \"active\" : \"inactive\"}\n >\n <span>{isTraining ? \"⏹\" : \"▶\"}</span>\n <span>\n {formatBilingualText(\n isTraining ? \"중지\" : \"시작\",\n isTraining ? \"Stop\" : \"Start\",\n \"pipe\"\n )}\n </span>\n </button>\n\n {/* Info text about auto-restart with Korean colors */}\n {!isCompact && !isTraining && (\n <div\n style={{\n fontSize: `${infoFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n marginTop: `${SPACING.XS}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: \"1.4\",\n }}\n >\n <div>모드 변경시 자동 재시작</div>\n <div>Auto-restarts on mode change</div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.isTraining === nextProps.isTraining &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.variant === nextProps.variant &&\n prevProps.onStartTraining === nextProps.onStartTraining &&\n prevProps.onStopTraining === nextProps.onStopTraining\n );\n },\n);\n\nTrainingControlsOverlayHtml.displayName = \"TrainingControlsOverlayHtml\";\n\nexport default TrainingControlsOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,IAAa,8BAA8B,MAAM,MAC9C,EACC,YACA,iBACA,gBACA,UACA,UAAU,cACN;CACN,MAAM,YAAY,YAAY;CAC9B,MAAM,aAAa,YAAa,WAAW,MAAM,MAAO,WAAW,MAAM;CACzE,MAAM,cAAc,YAAa,WAAW,KAAK,KAAM,WAAW,KAAK;CACvE,MAAM,UAAU,YAAY,qBAAqB,MAAM,
|
|
1
|
+
{"version":3,"file":"TrainingControlsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingControlsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingControlsOverlayHtml - Html overlay for training controls\n * \n * Displays start/stop button and training status with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n * \n * @module components/screens/training\n * @category Training UI\n * @korean 훈련제어오버레이\n */\n\nimport React from \"react\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getKoreanButtonWithGlow,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingControlsOverlayHtml component\n */\nexport interface TrainingControlsOverlayHtmlProps {\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Callback to start training */\n readonly onStartTraining: () => void;\n /** Callback to stop training */\n readonly onStopTraining: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Compact mode for embedding inside the slim top HUD */\n readonly variant?: \"panel\" | \"compact\";\n}\n\n/**\n * TrainingControlsOverlayHtml Component\n * \n * Html overlay showing training status and start/stop controls with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when isTraining state hasn't changed\n * - Callbacks expected to be stable (parent should use useCallback)\n * \n * @example\n * ```tsx\n * <TrainingControlsOverlayHtml\n * isTraining={true}\n * onStartTraining={() => console.log('start')}\n * onStopTraining={() => console.log('stop')}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 훈련제어오버레이컴포넌트\n */\nexport const TrainingControlsOverlayHtml = React.memo<TrainingControlsOverlayHtmlProps>(\n ({\n isTraining,\n onStartTraining,\n onStopTraining,\n isMobile,\n variant = \"panel\",\n }) => {\n const isCompact = variant === \"compact\";\n const panelWidth = isCompact ? (isMobile ? 180 : 210) : isMobile ? 200 : 220;\n const panelHeight = isCompact ? (isMobile ? 40 : 44) : isMobile ? 90 : 100;\n const padding = isCompact ? getResponsiveSpacing(\"xs\", isMobile) : getResponsiveSpacing(\"sm\", isMobile);\n\n const stateColor = isTraining ? KOREAN_COLORS.ACCENT_GREEN : KOREAN_COLORS.ACCENT_RED;\n const borderColor = hexToRgbaString(stateColor, 0.9);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: isTraining ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 2,\n }),\n width: `${panelWidth}px`,\n height: `${panelHeight}px`,\n padding: `${padding}px`,\n border: `2px solid ${borderColor}`,\n position: \"relative\",\n display: \"flex\",\n flexDirection: isCompact ? \"row\" : \"column\",\n alignItems: isCompact ? \"center\" : \"stretch\",\n justifyContent: isCompact ? \"space-between\" : \"flex-start\",\n gap: `${padding}px`,\n boxSizing: \"border-box\",\n fontFamily: FONT_FAMILY.KOREAN,\n };\n\n const buttonStyles = React.useMemo(\n () =>\n getKoreanButtonWithGlow({\n variant: isTraining ? \"danger\" : \"success\",\n glowIntensity: \"strong\",\n hoverAnimation: \"combined\",\n }),\n [isTraining]\n );\n\n const titleFontSize = isCompact ? (isMobile ? 11 : 12) : isMobile ? 13 : 14;\n const infoFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-controls-html\">\n {/* Header with bilingual status */}\n <div style={{ marginBottom: isCompact ? 0 : `${SPACING.SM}px`, minWidth: 0 }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(stateColor),\n textShadow: getNeonTextShadow(stateColor, isTraining ? \"medium\" : \"subtle\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {formatBilingualText(\n isTraining ? \"훈련 진행중\" : \"훈련 대기\",\n isTraining ? \"Training Active\" : \"Training Stopped\",\n \"pipe\"\n )}\n </div>\n </div>\n\n {/* Status Indicator with CSS animation */}\n <div\n className={`status-indicator ${isTraining ? \"active\" : \"inactive\"}`}\n style={{\n position: \"absolute\",\n top: isCompact ? \"6px\" : \"12px\",\n right: isCompact ? \"6px\" : \"12px\",\n }}\n />\n\n {/* Start/Stop Button with Korean theming */}\n <button\n onClick={isTraining ? onStopTraining : onStartTraining}\n className={`training-button ${isTraining ? \"training-button-stop\" : \"training-button-start\"}`}\n style={{\n ...buttonStyles,\n fontSize: `${titleFontSize}px`,\n height: isCompact ? \"30px\" : \"35px\",\n minWidth: isCompact ? \"72px\" : undefined,\n padding: isCompact ? \"4px 8px\" : buttonStyles.padding,\n flexShrink: 0,\n }}\n data-testid=\"training-toggle-button\"\n data-training-state={isTraining ? \"active\" : \"inactive\"}\n >\n <span>{isTraining ? \"⏹\" : \"▶\"}</span>\n <span>\n {formatBilingualText(\n isTraining ? \"중지\" : \"시작\",\n isTraining ? \"Stop\" : \"Start\",\n \"pipe\"\n )}\n </span>\n </button>\n\n {/* Info text about auto-restart with Korean colors */}\n {!isCompact && !isTraining && (\n <div\n style={{\n fontSize: `${infoFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n textAlign: \"center\",\n marginTop: `${SPACING.XS}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n lineHeight: \"1.4\",\n }}\n >\n <div>모드 변경시 자동 재시작</div>\n <div>Auto-restarts on mode change</div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.isTraining === nextProps.isTraining &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.variant === nextProps.variant &&\n prevProps.onStartTraining === nextProps.onStartTraining &&\n prevProps.onStopTraining === nextProps.onStopTraining\n );\n },\n);\n\nTrainingControlsOverlayHtml.displayName = \"TrainingControlsOverlayHtml\";\n\nexport default TrainingControlsOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingDummy3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingDummy3D.tsx"],"sourcesContent":["/**\n * TrainingDummy3D - 3D training dummy with vital points\n *\n * Provides anatomically accurate training dummy using SkeletalPlayer3D\n * for Korean martial arts practice. Supports anatomy overlays and difficulty modes.\n *\n * Refactored to extend SkeletalPlayer3D for visual consistency with player characters.\n *\n * @module components/screens/training/TrainingDummy3D\n * @category 3D Components\n * @korean 훈련인형3D컴포넌트\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport SkeletalPlayer3D from \"../../../shared/three/models/SkeletalPlayer3D\";\nimport VitalPointMarker3D from \"./VitalPointMarker3D\";\n\n/**\n * Difficulty mode for training\n * @korean 난이도모드\n */\nexport type DifficultyMode = \"easy\" | \"normal\" | \"hard\";\n\n/**\n * Props for TrainingDummy3D component\n * @korean 훈련인형3D속성\n */\nexport interface TrainingDummy3DProps {\n /** 3D world position of the dummy */\n readonly position: [number, number, number];\n /** Currently selected vital point for targeting */\n readonly selectedVitalPoint: string | null;\n /** Whether training is active */\n readonly isTraining: boolean;\n /** Current health of dummy (0-100) */\n readonly health?: number;\n /** Maximum health of dummy */\n readonly maxHealth?: number;\n /** Callback when vital point is hit */\n readonly onVitalPointHit?: (vitalPointId: string) => void;\n /** Callback when dummy is defeated */\n readonly onDefeated?: () => void;\n /** Difficulty mode (affects marker sizes) */\n readonly difficulty?: DifficultyMode;\n /** Number of vital points to display (3-70) */\n readonly vitalPointCount?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Archetype to display (affects body type and appearance) */\n readonly archetype?: PlayerArchetype;\n /** Current stance for the dummy */\n readonly stance?: TrigramStance;\n}\n\n/**\n * Map body region to 3D position on dummy\n *\n * Positions are calibrated for SkeletalPlayer3D bone structure.\n *\n * @param pointId - Unique identifier for the vital point\n * @param category - Anatomical category (head, neck, torso, etc.)\n * @returns 3D coordinates [x, y, z] relative to dummy center\n * @korean 급소위치계산\n */\nconst getVitalPointPosition = (\n pointId: string,\n category: string,\n): [number, number, number] => {\n const positions: Record<string, [number, number, number]> = {\n head: [0, 1.75, 0.12], // Head bone + offset\n neck: [0, 1.5, 0.1], // Neck bone\n torso: [0, 1.15, 0.18], // Chest/spine area\n chest: [0, 1.2, 0.2], // Upper chest\n abdomen: [0, 0.85, 0.18], // Lower torso\n back: [0, 1.1, -0.15], // Spine back\n arm: [-0.45, 1.15, 0], // Upper arm area\n leg: [-0.18, 0.4, 0.05], // Thigh area\n neurological: [0, 1.7, 0.08], // Temple/head neural points\n vascular: [0, 1.45, 0.12], // Neck vascular points\n muscular: [0, 1.1, 0.2], // Muscle groups\n skeletal: [0, 0.9, 0.15], // Joints/bones\n };\n\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n const hash =\n pointId.split(\"\").reduce((acc, char) => acc + char.charCodeAt(0), 0) *\n 0.001;\n return [\n basePos[0] + Math.sin(hash * 7.3) * 0.08,\n basePos[1] + Math.cos(hash * 5.1) * 0.08,\n basePos[2] + Math.sin(hash * 3.7) * 0.03,\n ];\n};\n\n/**\n * Health bar component for training dummy\n *\n * Displays above the dummy with Korean cyberpunk styling.\n * @korean 훈련인형체력바\n */\n\nconst HEALTH_BAR_WIDTH = 1.2;\nconst HEALTH_BAR_HEIGHT = 0.1;\n\nconst DummyHealthBar: React.FC<{\n readonly health: number;\n readonly maxHealth: number;\n readonly position: [number, number, number];\n}> = ({ health, maxHealth, position }) => {\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n const bgGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const healthGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const borderGeometry = useMemo(\n () =>\n new THREE.BoxGeometry(\n HEALTH_BAR_WIDTH + 0.04,\n HEALTH_BAR_HEIGHT + 0.04,\n 0.01,\n ),\n [],\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 70) return KOREAN_COLORS.HEALTH_FULL;\n if (healthPercent > 40) return KOREAN_COLORS.HEALTH_MEDIUM;\n if (healthPercent > 20) return KOREAN_COLORS.HEALTH_LOW;\n return KOREAN_COLORS.HEALTH_CRITICAL;\n }, [healthPercent]);\n\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n useEffect(() => {\n return () => {\n bgGeometry.dispose();\n healthGeometry.dispose();\n borderGeometry.dispose();\n };\n }, [bgGeometry, healthGeometry, borderGeometry]);\n\n return (\n <group position={position} name=\"dummy-health-bar\">\n {/* Background bar */}\n <mesh>\n <primitive object={bgGeometry} />\n <meshBasicMaterial\n color={KOREAN_COLORS.UI_BACKGROUND_DARK}\n transparent\n opacity={0.7}\n />\n </mesh>\n\n {/* Health bar (scaled based on health, anchored to left edge) */}\n <mesh\n position={[\n -(HEALTH_BAR_WIDTH * (1 - healthPercent / 100)) / 2,\n 0,\n 0.01,\n ]}\n scale={healthScale}\n >\n <primitive object={healthGeometry} />\n <meshBasicMaterial color={healthColor} transparent opacity={0.9} />\n </mesh>\n\n {/* Border frame - outline using EdgesGeometry for crisp border */}\n <lineSegments>\n <edgesGeometry args={[borderGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.8}\n />\n </lineSegments>\n </group>\n );\n};\n\n/**\n * TrainingDummy3D Component\n *\n * Main training dummy using SkeletalPlayer3D for consistent visual appearance.\n * Displays vital points for martial arts practice with difficulty-based sizing.\n *\n * @example\n * ```tsx\n * <TrainingDummy3D\n * position={[0, 0, 5]}\n * selectedVitalPoint=\"temple\"\n * isTraining={true}\n * health={75}\n * difficulty=\"normal\"\n * onVitalPointHit={(id) => console.log(`Hit ${id}`)}\n * />\n * ```\n *\n * @korean 훈련인형3D컴포넌트\n */\nexport const TrainingDummy3D: React.FC<TrainingDummy3DProps> = ({\n position,\n selectedVitalPoint,\n isTraining,\n health = 100,\n maxHealth = 100,\n onVitalPointHit,\n onDefeated,\n difficulty = \"normal\",\n vitalPointCount = 12,\n isMobile = false,\n archetype = PlayerArchetype.MUSA,\n stance = TrigramStance.GEON,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n const [isStunned, setIsStunned] = useState(false);\n\n const vitalPoints = useMemo(\n () =>\n KOREAN_VITAL_POINTS.slice(\n 0,\n Math.min(vitalPointCount, KOREAN_VITAL_POINTS.length),\n ),\n [vitalPointCount],\n );\n\n const sizeMultiplier = useMemo(() => {\n switch (difficulty) {\n case \"easy\":\n return 1.5; // Larger targets\n case \"normal\":\n return 1.0; // Standard size\n case \"hard\":\n return 0.7; // Smaller targets\n default:\n return 1.0;\n }\n }, [difficulty]);\n\n const prevHealthRef = useRef(health);\n\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n useEffect(() => {\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n if (prevHealthRef.current - health > 20) {\n setIsStunned(true);\n const timer = setTimeout(() => setIsStunned(false), 500);\n return () => clearTimeout(timer);\n }\n\n prevHealthRef.current = health;\n return undefined;\n }, [health]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n const breathScale = Math.sin(time * 1.5) * 0.01 + 1;\n groupRef.current.scale.y = breathScale;\n\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n const handlePointHit = useCallback(\n (pointId: string) => {\n onVitalPointHit?.(pointId);\n },\n [onVitalPointHit],\n );\n\n return (\n <group ref={groupRef} position={position} name=\"training-dummy-3d\">\n {/* SkeletalPlayer3D as the base character model */}\n <SkeletalPlayer3D\n playerId=\"training-dummy\"\n archetype={archetype}\n stance={stance}\n position={[0, 0, 0]}\n rotation={Math.PI} // Face the player\n facing=\"left\"\n health={health}\n maxHealth={maxHealth}\n stamina={100}\n ki={50}\n balance=\"READY\"\n pain={0}\n consciousness={100}\n isMobile={isMobile}\n currentAnimation=\"idle\"\n isBlocking={false}\n isStunned={isStunned}\n showHealthBar={false} // We use custom health bar\n showStanceIndicator={false}\n enableFacialExpressions={true}\n enableEyeTracking={true}\n />\n\n {/* Vital point markers overlaid on the skeletal model */}\n {vitalPoints.map((point) => (\n <group\n key={point.id}\n position={getVitalPointPosition(point.id, point.category)}\n >\n <VitalPointMarker3D\n vitalPoint={point}\n isSelected={point.id === selectedVitalPoint}\n isTraining={isTraining}\n isMobile={isMobile}\n onHit={handlePointHit}\n sizeMultiplier={sizeMultiplier}\n />\n </group>\n ))}\n\n {/* Health bar above dummy */}\n {isTraining && (\n <DummyHealthBar\n health={health}\n maxHealth={maxHealth}\n position={[0, 2.3, 0]}\n />\n )}\n\n {/* Training mode indicator glow */}\n {isTraining && (\n <pointLight\n position={[0, 1.2, 0.5]}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n intensity={0.3}\n distance={3}\n decay={2}\n />\n )}\n </group>\n );\n};\n\nexport default TrainingDummy3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingDummy3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingDummy3D.tsx"],"sourcesContent":["/**\n * TrainingDummy3D - 3D training dummy with vital points\n *\n * Provides anatomically accurate training dummy using SkeletalPlayer3D\n * for Korean martial arts practice. Supports anatomy overlays and difficulty modes.\n *\n * Refactored to extend SkeletalPlayer3D for visual consistency with player characters.\n *\n * @module components/screens/training/TrainingDummy3D\n * @category 3D Components\n * @korean 훈련인형3D컴포넌트\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport SkeletalPlayer3D from \"../../../shared/three/models/SkeletalPlayer3D\";\nimport VitalPointMarker3D from \"./VitalPointMarker3D\";\n\n/**\n * Difficulty mode for training\n * @korean 난이도모드\n */\nexport type DifficultyMode = \"easy\" | \"normal\" | \"hard\";\n\n/**\n * Props for TrainingDummy3D component\n * @korean 훈련인형3D속성\n */\nexport interface TrainingDummy3DProps {\n /** 3D world position of the dummy */\n readonly position: [number, number, number];\n /** Currently selected vital point for targeting */\n readonly selectedVitalPoint: string | null;\n /** Whether training is active */\n readonly isTraining: boolean;\n /** Current health of dummy (0-100) */\n readonly health?: number;\n /** Maximum health of dummy */\n readonly maxHealth?: number;\n /** Callback when vital point is hit */\n readonly onVitalPointHit?: (vitalPointId: string) => void;\n /** Callback when dummy is defeated */\n readonly onDefeated?: () => void;\n /** Difficulty mode (affects marker sizes) */\n readonly difficulty?: DifficultyMode;\n /** Number of vital points to display (3-70) */\n readonly vitalPointCount?: number;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /** Archetype to display (affects body type and appearance) */\n readonly archetype?: PlayerArchetype;\n /** Current stance for the dummy */\n readonly stance?: TrigramStance;\n}\n\n/**\n * Map body region to 3D position on dummy\n *\n * Positions are calibrated for SkeletalPlayer3D bone structure.\n *\n * @param pointId - Unique identifier for the vital point\n * @param category - Anatomical category (head, neck, torso, etc.)\n * @returns 3D coordinates [x, y, z] relative to dummy center\n * @korean 급소위치계산\n */\nconst getVitalPointPosition = (\n pointId: string,\n category: string,\n): [number, number, number] => {\n const positions: Record<string, [number, number, number]> = {\n head: [0, 1.75, 0.12], // Head bone + offset\n neck: [0, 1.5, 0.1], // Neck bone\n torso: [0, 1.15, 0.18], // Chest/spine area\n chest: [0, 1.2, 0.2], // Upper chest\n abdomen: [0, 0.85, 0.18], // Lower torso\n back: [0, 1.1, -0.15], // Spine back\n arm: [-0.45, 1.15, 0], // Upper arm area\n leg: [-0.18, 0.4, 0.05], // Thigh area\n neurological: [0, 1.7, 0.08], // Temple/head neural points\n vascular: [0, 1.45, 0.12], // Neck vascular points\n muscular: [0, 1.1, 0.2], // Muscle groups\n skeletal: [0, 0.9, 0.15], // Joints/bones\n };\n\n const basePos = positions[category.toLowerCase()] ?? [0, 1.1, 0.15];\n\n const hash =\n pointId.split(\"\").reduce((acc, char) => acc + char.charCodeAt(0), 0) *\n 0.001;\n return [\n basePos[0] + Math.sin(hash * 7.3) * 0.08,\n basePos[1] + Math.cos(hash * 5.1) * 0.08,\n basePos[2] + Math.sin(hash * 3.7) * 0.03,\n ];\n};\n\n/**\n * Health bar component for training dummy\n *\n * Displays above the dummy with Korean cyberpunk styling.\n * @korean 훈련인형체력바\n */\n\nconst HEALTH_BAR_WIDTH = 1.2;\nconst HEALTH_BAR_HEIGHT = 0.1;\n\nconst DummyHealthBar: React.FC<{\n readonly health: number;\n readonly maxHealth: number;\n readonly position: [number, number, number];\n}> = ({ health, maxHealth, position }) => {\n const healthPercent = Math.max(0, Math.min(100, (health / maxHealth) * 100));\n\n const bgGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const healthGeometry = useMemo(\n () => new THREE.BoxGeometry(HEALTH_BAR_WIDTH, HEALTH_BAR_HEIGHT, 0.02),\n [],\n );\n const borderGeometry = useMemo(\n () =>\n new THREE.BoxGeometry(\n HEALTH_BAR_WIDTH + 0.04,\n HEALTH_BAR_HEIGHT + 0.04,\n 0.01,\n ),\n [],\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 70) return KOREAN_COLORS.HEALTH_FULL;\n if (healthPercent > 40) return KOREAN_COLORS.HEALTH_MEDIUM;\n if (healthPercent > 20) return KOREAN_COLORS.HEALTH_LOW;\n return KOREAN_COLORS.HEALTH_CRITICAL;\n }, [healthPercent]);\n\n const healthScale: [number, number, number] = useMemo(\n () => [healthPercent / 100, 1, 1],\n [healthPercent],\n );\n\n useEffect(() => {\n return () => {\n bgGeometry.dispose();\n healthGeometry.dispose();\n borderGeometry.dispose();\n };\n }, [bgGeometry, healthGeometry, borderGeometry]);\n\n return (\n <group position={position} name=\"dummy-health-bar\">\n {/* Background bar */}\n <mesh>\n <primitive object={bgGeometry} />\n <meshBasicMaterial\n color={KOREAN_COLORS.UI_BACKGROUND_DARK}\n transparent\n opacity={0.7}\n />\n </mesh>\n\n {/* Health bar (scaled based on health, anchored to left edge) */}\n <mesh\n position={[\n -(HEALTH_BAR_WIDTH * (1 - healthPercent / 100)) / 2,\n 0,\n 0.01,\n ]}\n scale={healthScale}\n >\n <primitive object={healthGeometry} />\n <meshBasicMaterial color={healthColor} transparent opacity={0.9} />\n </mesh>\n\n {/* Border frame - outline using EdgesGeometry for crisp border */}\n <lineSegments>\n <edgesGeometry args={[borderGeometry]} />\n <lineBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.8}\n />\n </lineSegments>\n </group>\n );\n};\n\n/**\n * TrainingDummy3D Component\n *\n * Main training dummy using SkeletalPlayer3D for consistent visual appearance.\n * Displays vital points for martial arts practice with difficulty-based sizing.\n *\n * @example\n * ```tsx\n * <TrainingDummy3D\n * position={[0, 0, 5]}\n * selectedVitalPoint=\"temple\"\n * isTraining={true}\n * health={75}\n * difficulty=\"normal\"\n * onVitalPointHit={(id) => console.log(`Hit ${id}`)}\n * />\n * ```\n *\n * @korean 훈련인형3D컴포넌트\n */\nexport const TrainingDummy3D: React.FC<TrainingDummy3DProps> = ({\n position,\n selectedVitalPoint,\n isTraining,\n health = 100,\n maxHealth = 100,\n onVitalPointHit,\n onDefeated,\n difficulty = \"normal\",\n vitalPointCount = 12,\n isMobile = false,\n archetype = PlayerArchetype.MUSA,\n stance = TrigramStance.GEON,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n const [isStunned, setIsStunned] = useState(false);\n\n const vitalPoints = useMemo(\n () =>\n KOREAN_VITAL_POINTS.slice(\n 0,\n Math.min(vitalPointCount, KOREAN_VITAL_POINTS.length),\n ),\n [vitalPointCount],\n );\n\n const sizeMultiplier = useMemo(() => {\n switch (difficulty) {\n case \"easy\":\n return 1.5; // Larger targets\n case \"normal\":\n return 1.0; // Standard size\n case \"hard\":\n return 0.7; // Smaller targets\n default:\n return 1.0;\n }\n }, [difficulty]);\n\n const prevHealthRef = useRef(health);\n\n const onDefeatedRef = useRef(onDefeated);\n useEffect(() => {\n onDefeatedRef.current = onDefeated;\n }, [onDefeated]);\n\n useEffect(() => {\n if (prevHealthRef.current > 0 && health <= 0) {\n onDefeatedRef.current?.();\n }\n\n if (prevHealthRef.current - health > 20) {\n setIsStunned(true);\n const timer = setTimeout(() => setIsStunned(false), 500);\n return () => clearTimeout(timer);\n }\n\n prevHealthRef.current = health;\n return undefined;\n }, [health]);\n\n useFrame((state) => {\n if (!groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n const breathScale = Math.sin(time * 1.5) * 0.01 + 1;\n groupRef.current.scale.y = breathScale;\n\n groupRef.current.rotation.y = Math.sin(time * 0.5) * 0.02;\n });\n\n const handlePointHit = useCallback(\n (pointId: string) => {\n onVitalPointHit?.(pointId);\n },\n [onVitalPointHit],\n );\n\n return (\n <group ref={groupRef} position={position} name=\"training-dummy-3d\">\n {/* SkeletalPlayer3D as the base character model */}\n <SkeletalPlayer3D\n playerId=\"training-dummy\"\n archetype={archetype}\n stance={stance}\n position={[0, 0, 0]}\n rotation={Math.PI} // Face the player\n facing=\"left\"\n health={health}\n maxHealth={maxHealth}\n stamina={100}\n ki={50}\n balance=\"READY\"\n pain={0}\n consciousness={100}\n isMobile={isMobile}\n currentAnimation=\"idle\"\n isBlocking={false}\n isStunned={isStunned}\n showHealthBar={false} // We use custom health bar\n showStanceIndicator={false}\n enableFacialExpressions={true}\n enableEyeTracking={true}\n />\n\n {/* Vital point markers overlaid on the skeletal model */}\n {vitalPoints.map((point) => (\n <group\n key={point.id}\n position={getVitalPointPosition(point.id, point.category)}\n >\n <VitalPointMarker3D\n vitalPoint={point}\n isSelected={point.id === selectedVitalPoint}\n isTraining={isTraining}\n isMobile={isMobile}\n onHit={handlePointHit}\n sizeMultiplier={sizeMultiplier}\n />\n </group>\n ))}\n\n {/* Health bar above dummy */}\n {isTraining && (\n <DummyHealthBar\n health={health}\n maxHealth={maxHealth}\n position={[0, 2.3, 0]}\n />\n )}\n\n {/* Training mode indicator glow */}\n {isTraining && (\n <pointLight\n position={[0, 1.2, 0.5]}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n intensity={0.3}\n distance={3}\n decay={2}\n />\n )}\n </group>\n );\n};\n\nexport default TrainingDummy3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingFeedbackOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingFeedbackOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingFeedbackOverlayHtml - Html overlay for training feedback messages\n * \n * Displays temporary feedback messages for hits, misses, and achievements\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/TrainingFeedbackOverlayHtml\n * @category Training UI\n * @korean 훈련피드백오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingFeedbackOverlayHtml component\n */\nexport interface TrainingFeedbackOverlayHtmlProps {\n /** Feedback message to display */\n readonly message: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * TrainingFeedbackOverlayHtml Component\n * Html overlay for displaying training feedback with Korean theming\n * \n * @korean 훈련피드백오버레이컴포넌트\n */\nexport const TrainingFeedbackOverlayHtml = React.memo<TrainingFeedbackOverlayHtmlProps>(\n ({\n message,\n isMobile,\n }) => {\n return (\n <div\n className={`training-feedback ${isMobile ? \"mobile\" : \"desktop\"}`}\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n }}\n data-testid=\"training-feedback-html\"\n >\n {message}\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.message === nextProps.message &&\n prevProps.isMobile === nextProps.isMobile\n );\n});\n\nTrainingFeedbackOverlayHtml.displayName = \"TrainingFeedbackOverlayHtml\";\n\nexport default TrainingFeedbackOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,8BAA8B,MAAM,MAC9C,EACC,SACA,eACI;CACN,OACE,oBAAC,OAAD;EACE,WAAW,qBAAqB,WAAW,WAAW;EACtD,OAAO;GACL,YAAY,YAAY;GACxB,YAAY;GACZ,OAAO,gBAAgB,cAAc,
|
|
1
|
+
{"version":3,"file":"TrainingFeedbackOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingFeedbackOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingFeedbackOverlayHtml - Html overlay for training feedback messages\n * \n * Displays temporary feedback messages for hits, misses, and achievements\n * with consistent Korean martial arts cyberpunk theming.\n * \n * @module components/screens/training/components/TrainingFeedbackOverlayHtml\n * @category Training UI\n * @korean 훈련피드백오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"../training.css\";\n\n/**\n * Props for TrainingFeedbackOverlayHtml component\n */\nexport interface TrainingFeedbackOverlayHtmlProps {\n /** Feedback message to display */\n readonly message: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * TrainingFeedbackOverlayHtml Component\n * Html overlay for displaying training feedback with Korean theming\n * \n * @korean 훈련피드백오버레이컴포넌트\n */\nexport const TrainingFeedbackOverlayHtml = React.memo<TrainingFeedbackOverlayHtmlProps>(\n ({\n message,\n isMobile,\n }) => {\n return (\n <div\n className={`training-feedback ${isMobile ? \"mobile\" : \"desktop\"}`}\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n textShadow: `0 2px 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.5)}`,\n }}\n data-testid=\"training-feedback-html\"\n >\n {message}\n </div>\n );\n},\n(prevProps, nextProps) => {\n return (\n prevProps.message === nextProps.message &&\n prevProps.isMobile === nextProps.isMobile\n );\n});\n\nTrainingFeedbackOverlayHtml.displayName = \"TrainingFeedbackOverlayHtml\";\n\nexport default TrainingFeedbackOverlayHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,8BAA8B,MAAM,MAC9C,EACC,SACA,eACI;CACN,OACE,oBAAC,OAAD;EACE,WAAW,qBAAqB,WAAW,WAAW;EACtD,OAAO;GACL,YAAY,YAAY;GACxB,YAAY;GACZ,OAAO,gBAAgB,cAAc,WAAW;GAChD,YAAY,cAAc,gBAAgB,cAAc,aAAa,EAAG;EAC1E;EACA,eAAY;YAEX;CACE,CAAA;AAET,IACC,WAAW,cAAc;CACxB,OACE,UAAU,YAAY,UAAU,WAChC,UAAU,aAAa,UAAU;AAErC,CAAC;AAED,4BAA4B,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingModeSelectorOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingModeSelectorOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingModeSelectorOverlayHtml - Html overlay for training mode selection\n *\n * Allows switching between different training modes with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련모드선택오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Training mode types\n */\nexport type TrainingMode =\n | \"basics\" // Basic Training - Simple striking practice\n | \"advanced\" // Advanced Training - Vital point precision\n | \"free\" // Free Practice - Open combat vs AI\n | \"stance_training\" // Stance Training - Practice 8 trigrams\n | \"vital_point\" // Vital Point Training - Precision targeting\n | \"combo_practice\" // Combo Practice - Multi-hit techniques\n | \"footwork\"; // Footwork Training - Movement drills (보법 훈련)\n\n/**\n * Props for TrainingModeSelectorOverlayHtml component\n */\nexport interface TrainingModeSelectorOverlayHtmlProps {\n /** Currently selected training mode */\n readonly currentMode: TrainingMode;\n /** Callback when mode changes */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Mode information\n */\nconst MODE_INFO: Record<\n TrainingMode,\n { korean: string; english: string; description: string }\n> = {\n basics: {\n korean: \"기본 훈련\",\n english: \"Basic Training\",\n description: \"기초 타격 연습 | Basic striking practice\",\n },\n advanced: {\n korean: \"고급 훈련\",\n english: \"Advanced Training\",\n description: \"급소 정밀 타격 | Vital point precision\",\n },\n free: {\n korean: \"자유 훈련\",\n english: \"Free Practice\",\n description: \"AI 대련 | Combat vs AI opponent\",\n },\n stance_training: {\n korean: \"팔괘 수련\",\n english: \"Stance Training\",\n description: \"팔괘 전환 연습 | Eight trigrams mastery\",\n },\n vital_point: {\n korean: \"급소 훈련\",\n english: \"Vital Point Training\",\n description: \"정밀 타격 연습 | Precision targeting drill\",\n },\n combo_practice: {\n korean: \"연속 기술\",\n english: \"Combo Practice\",\n description: \"연속 타격 훈련 | Multi-hit techniques\",\n },\n footwork: {\n korean: \"보법 훈련\",\n english: \"Footwork Training\",\n description: \"보법 수련 | Movement drills\",\n },\n};\n\n/**\n * TrainingModeSelectorOverlayHtml Component\n *\n * Html overlay for selecting training mode with Korean theming.\n * Compact horizontal grid layout optimized for desktop and mobile.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when currentMode hasn't changed\n * - Callback expected to be stable (parent should use useCallback)\n *\n * @example\n * ```tsx\n * <TrainingModeSelectorOverlayHtml\n * currentMode=\"vital_point\"\n * onModeChange={(mode) => console.log(mode)}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련모드선택오버레이컴포넌트\n */\nexport const TrainingModeSelectorOverlayHtml =\n React.memo<TrainingModeSelectorOverlayHtmlProps>(\n ({ currentMode, onModeChange, isMobile }) => {\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.9,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n const titleFontSize = isMobile ? 13 : 15;\n const descFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-mode-selector-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${SPACING.SM}px`, textAlign: \"center\" }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.PRIMARY_CYAN,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 모드\", \"Training Mode\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: `${descFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontStyle: \"italic\",\n marginTop: \"2px\",\n minHeight: \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {MODE_INFO[currentMode].description}\n </div>\n </div>\n\n {/* Mode Buttons - Grid layout (2 columns to fit narrow side HUD) */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: `${gap}px`,\n }}\n >\n {(Object.keys(MODE_INFO) as TrainingMode[]).map((mode) => {\n const isSelected = mode === currentMode;\n const info = MODE_INFO[mode];\n\n return (\n <button\n key={mode}\n onClick={() => onModeChange(mode)}\n className={`mode-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n textAlign: \"center\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n minHeight: isMobile ? \"45px\" : \"50px\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid={`mode-${mode}`}\n >\n <div\n style={{\n fontWeight: \"bold\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_PRIMARY,\n ),\n fontSize: isMobile ? \"11px\" : \"12px\",\n marginBottom: \"2px\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.TEXT_TERTIARY,\n ),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.english}\n </div>\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.currentMode === nextProps.currentMode &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onModeChange === nextProps.onModeChange\n );\n },\n );\n\nTrainingModeSelectorOverlayHtml.displayName = \"TrainingModeSelectorOverlayHtml\";\n\nexport default TrainingModeSelectorOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingModeSelectorOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingModeSelectorOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingModeSelectorOverlayHtml - Html overlay for training mode selection\n *\n * Allows switching between different training modes with consistent Korean theming.\n * Uses KOREAN_COLORS constants and bilingual formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련모드선택오버레이\n */\n\nimport React from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Training mode types\n */\nexport type TrainingMode =\n | \"basics\" // Basic Training - Simple striking practice\n | \"advanced\" // Advanced Training - Vital point precision\n | \"free\" // Free Practice - Open combat vs AI\n | \"stance_training\" // Stance Training - Practice 8 trigrams\n | \"vital_point\" // Vital Point Training - Precision targeting\n | \"combo_practice\" // Combo Practice - Multi-hit techniques\n | \"footwork\"; // Footwork Training - Movement drills (보법 훈련)\n\n/**\n * Props for TrainingModeSelectorOverlayHtml component\n */\nexport interface TrainingModeSelectorOverlayHtmlProps {\n /** Currently selected training mode */\n readonly currentMode: TrainingMode;\n /** Callback when mode changes */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Mode information\n */\nconst MODE_INFO: Record<\n TrainingMode,\n { korean: string; english: string; description: string }\n> = {\n basics: {\n korean: \"기본 훈련\",\n english: \"Basic Training\",\n description: \"기초 타격 연습 | Basic striking practice\",\n },\n advanced: {\n korean: \"고급 훈련\",\n english: \"Advanced Training\",\n description: \"급소 정밀 타격 | Vital point precision\",\n },\n free: {\n korean: \"자유 훈련\",\n english: \"Free Practice\",\n description: \"AI 대련 | Combat vs AI opponent\",\n },\n stance_training: {\n korean: \"팔괘 수련\",\n english: \"Stance Training\",\n description: \"팔괘 전환 연습 | Eight trigrams mastery\",\n },\n vital_point: {\n korean: \"급소 훈련\",\n english: \"Vital Point Training\",\n description: \"정밀 타격 연습 | Precision targeting drill\",\n },\n combo_practice: {\n korean: \"연속 기술\",\n english: \"Combo Practice\",\n description: \"연속 타격 훈련 | Multi-hit techniques\",\n },\n footwork: {\n korean: \"보법 훈련\",\n english: \"Footwork Training\",\n description: \"보법 수련 | Movement drills\",\n },\n};\n\n/**\n * TrainingModeSelectorOverlayHtml Component\n *\n * Html overlay for selecting training mode with Korean theming.\n * Compact horizontal grid layout optimized for desktop and mobile.\n *\n * Optimized with React.memo for 60fps performance:\n * - Prevents re-renders when currentMode hasn't changed\n * - Callback expected to be stable (parent should use useCallback)\n *\n * @example\n * ```tsx\n * <TrainingModeSelectorOverlayHtml\n * currentMode=\"vital_point\"\n * onModeChange={(mode) => console.log(mode)}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련모드선택오버레이컴포넌트\n */\nexport const TrainingModeSelectorOverlayHtml =\n React.memo<TrainingModeSelectorOverlayHtmlProps>(\n ({ currentMode, onModeChange, isMobile }) => {\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.9,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: \"100%\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n };\n\n const titleFontSize = isMobile ? 13 : 15;\n const descFontSize = isMobile ? 9 : 10;\n\n return (\n <div style={panelStyle} data-testid=\"training-mode-selector-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${SPACING.SM}px`, textAlign: \"center\" }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.PRIMARY_CYAN,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 모드\", \"Training Mode\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: `${descFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontStyle: \"italic\",\n marginTop: \"2px\",\n minHeight: \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {MODE_INFO[currentMode].description}\n </div>\n </div>\n\n {/* Mode Buttons - Grid layout (2 columns to fit narrow side HUD) */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(2, 1fr)\",\n gap: `${gap}px`,\n }}\n >\n {(Object.keys(MODE_INFO) as TrainingMode[]).map((mode) => {\n const isSelected = mode === currentMode;\n const info = MODE_INFO[mode];\n\n return (\n <button\n key={mode}\n onClick={() => onModeChange(mode)}\n className={`mode-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n padding: isMobile ? \"6px 4px\" : \"8px 6px\",\n textAlign: \"center\",\n fontSize: isMobile ? \"10px\" : \"11px\",\n minHeight: isMobile ? \"45px\" : \"50px\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid={`mode-${mode}`}\n >\n <div\n style={{\n fontWeight: \"bold\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_PRIMARY,\n ),\n fontSize: isMobile ? \"11px\" : \"12px\",\n marginBottom: \"2px\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(\n isSelected\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.TEXT_TERTIARY,\n ),\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n width: \"100%\",\n minWidth: 0,\n }}\n >\n {info.english}\n </div>\n </button>\n );\n })}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.currentMode === nextProps.currentMode &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onModeChange === nextProps.onModeChange\n );\n },\n );\n\nTrainingModeSelectorOverlayHtml.displayName = \"TrainingModeSelectorOverlayHtml\";\n\nexport default TrainingModeSelectorOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingStatsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingStatsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingStatsOverlayHtml - Html overlay for training statistics\n *\n * Displays score, combo, hits, misses, and accuracy with consistent Korean theming.\n * Uses Korean cyberpunk color palette and bilingual text formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련통계오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\n\n/**\n * Training statistics interface\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Props for TrainingStatsOverlayHtml component\n */\nexport interface TrainingStatsOverlayHtmlProps {\n /** Current training statistics */\n readonly stats: TrainingStats;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Viewport width for Super HD font scaling */\n readonly width?: number;\n /** Distance to training dummy in meters (for distance-based hit feedback) */\n readonly distanceToDummy?: number;\n /** Effective reach for current technique in meters */\n readonly effectiveReach?: number;\n}\n\n/**\n * TrainingStatsOverlayHtml Component\n *\n * Html overlay displaying training performance metrics with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized with custom comparison function\n * - Only re-renders when stats actually change\n * - Reduces unnecessary DOM updates\n *\n * @example\n * ```tsx\n * <TrainingStatsOverlayHtml\n * stats={{ score: 1500, combo: 8, hits: 45, misses: 5, accuracy: 90 }}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련통계오버레이컴포넌트\n */\nexport const TrainingStatsOverlayHtml =\n React.memo<TrainingStatsOverlayHtmlProps>(\n ({\n stats,\n isMobile,\n width = 375,\n distanceToDummy,\n effectiveReach = 0.7, // Default punch reach\n }) => {\n const panelWidth = width > 10 ? width : isMobile ? 180 : 200;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const safeAreaStyles = useMemo(\n () => (isMobile ? getSafeAreaPadding([\"top\"], padding) : {}),\n [isMobile, padding],\n );\n\n const formattedAccuracy = useMemo(\n () => stats.accuracy.toFixed(1),\n [stats.accuracy],\n );\n\n const formattedDuration = useMemo(() => {\n if (!stats.sessionDuration) return \"00:00\";\n const minutes = Math.floor(stats.sessionDuration / 60);\n const seconds = stats.sessionDuration % 60;\n return `${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }, [stats.sessionDuration]);\n\n const distanceInfo = useMemo(() => {\n if (distanceToDummy === undefined) return null;\n const isInRange = distanceToDummy <= effectiveReach;\n return {\n formatted: distanceToDummy.toFixed(2),\n isInRange,\n rangeStatus: isInRange\n ? \"사정거리 내 | In Range\"\n : \"사정거리 밖 | Out of Range\",\n };\n }, [distanceToDummy, effectiveReach]);\n\n const perfectRate = useMemo(() => {\n const totalAttempts = stats.hits + stats.misses;\n if (totalAttempts === 0 || !stats.perfectStrikes) return \"0\";\n return ((stats.perfectStrikes / totalAttempts) * 100).toFixed(1);\n }, [stats.hits, stats.misses, stats.perfectStrikes]);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.92,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n ...safeAreaStyles,\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n };\n\n const titleFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375) // 16px minimum\n : 18;\n\n return (\n <div style={panelStyle} data-testid=\"training-stats-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${padding}px` }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.ACCENT_GOLD,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 통계\", \"Training Statistics\", \"pipe\")}\n </div>\n </div>\n\n {/* Stats Grid with consistent Korean theming */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${gap}px`,\n }}\n >\n {/* Score - 점수 */}\n <StatRow\n korean=\"점수\"\n english=\"Score\"\n value={stats.score.toLocaleString()}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Combo - 콤보 */}\n <StatRow\n korean=\"콤보\"\n english=\"Combo\"\n value={`${stats.combo}x`}\n color={\n stats.combo > 5\n ? KOREAN_COLORS.ACCENT_RED\n : KOREAN_COLORS.PRIMARY_CYAN\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Hits - 성공 */}\n <StatRow\n korean=\"성공\"\n english=\"Hits\"\n value={stats.hits.toString()}\n color={KOREAN_COLORS.ACCENT_GREEN}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Misses - 실패 */}\n <StatRow\n korean=\"실패\"\n english=\"Misses\"\n value={stats.misses.toString()}\n color={KOREAN_COLORS.TEXT_TERTIARY}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Accuracy - 정확도 */}\n <StatRow\n korean=\"정확도\"\n english=\"Accuracy\"\n value={`${formattedAccuracy}%`}\n color={\n stats.accuracy >= 80\n ? KOREAN_COLORS.ACCENT_GREEN\n : stats.accuracy >= 50\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Session Duration - 시간 */}\n {stats.sessionDuration !== undefined && (\n <StatRow\n korean=\"시간\"\n english=\"Duration\"\n value={formattedDuration}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Best Combo - 최고 콤보 */}\n {stats.bestCombo !== undefined && stats.bestCombo > 0 && (\n <StatRow\n korean=\"최고 콤보\"\n english=\"Best Combo\"\n value={`${stats.bestCombo}x`}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Perfect Rate - 완벽률 */}\n {stats.hits + stats.misses > 0 && (\n <StatRow\n korean=\"완벽률\"\n english=\"Perfect Rate\"\n value={`${perfectRate}%`}\n color={\n parseFloat(perfectRate) >= 30\n ? KOREAN_COLORS.ACCENT_GOLD\n : parseFloat(perfectRate) >= 10\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_TERTIARY\n }\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Distance to Dummy - 거리 */}\n {distanceInfo && (\n <>\n <StatRow\n korean=\"거리\"\n english=\"Distance\"\n value={`${distanceInfo.formatted}m`}\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n <StatRow\n korean=\"상태\"\n english=\"Status\"\n value={\n distanceInfo.rangeStatus.split(\" | \")[isMobile ? 0 : 1]\n }\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n </>\n )}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.stats.score === nextProps.stats.score &&\n prevProps.stats.combo === nextProps.stats.combo &&\n prevProps.stats.hits === nextProps.stats.hits &&\n prevProps.stats.misses === nextProps.stats.misses &&\n prevProps.stats.accuracy === nextProps.stats.accuracy &&\n prevProps.stats.sessionDuration === nextProps.stats.sessionDuration &&\n prevProps.stats.bestCombo === nextProps.stats.bestCombo &&\n prevProps.stats.perfectStrikes === nextProps.stats.perfectStrikes &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.distanceToDummy === nextProps.distanceToDummy &&\n prevProps.effectiveReach === nextProps.effectiveReach\n );\n },\n );\n\nTrainingStatsOverlayHtml.displayName = \"TrainingStatsOverlayHtml\";\n\n/**\n * Single stat row component with Korean theming\n *\n * Uses KOREAN_COLORS constants for all text colors\n * Enhanced with smooth transitions and neon glow on value\n *\n * Optimized with React.memo for performance\n *\n * @korean 통계행컴포넌트\n */\nconst StatRow = React.memo<{\n korean: string;\n english: string;\n value: string;\n color: number; // Numeric hex color from KOREAN_COLORS (e.g., 0x00ffff)\n isMobile: boolean;\n width: number; // Width for Super HD font scaling\n}>(({ korean, english, value, color, isMobile, width }) => {\n const labelFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width) // 16px minimum\n : 14;\n const sublabelFontSize = isMobile ? 12 : 11; // Increased from 8-9px\n const valueFontSize = isMobile\n ? getMobileKoreanFontSize(\"MEDIUM\", width) // 18px minimum\n : 20;\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingBottom: `${SPACING.SM}px`,\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.1)}`,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n <div>\n <div\n style={{\n fontSize: `${labelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"subtle\"),\n }}\n >\n {korean}\n </div>\n <div\n style={{\n fontSize: `${sublabelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {english}\n </div>\n </div>\n <div\n style={{\n fontSize: `${valueFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(color, \"medium\"),\n transition: getSmoothTransition(\"transform, color\", \"normal\"),\n transform: \"scale(1)\",\n }}\n >\n {value}\n </div>\n </div>\n );\n});\n\nStatRow.displayName = \"StatRow\";\n\nexport default TrainingStatsOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingStatsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/TrainingStatsOverlayHtml.tsx"],"sourcesContent":["/**\n * TrainingStatsOverlayHtml - Html overlay for training statistics\n *\n * Displays score, combo, hits, misses, and accuracy with consistent Korean theming.\n * Uses Korean cyberpunk color palette and bilingual text formatting.\n *\n * @module components/screens/training\n * @category Training UI\n * @korean 훈련통계오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\n\n/**\n * Training statistics interface\n */\nexport interface TrainingStats {\n readonly score: number;\n readonly combo: number;\n readonly hits: number;\n readonly misses: number;\n readonly accuracy: number;\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n}\n\n/**\n * Props for TrainingStatsOverlayHtml component\n */\nexport interface TrainingStatsOverlayHtmlProps {\n /** Current training statistics */\n readonly stats: TrainingStats;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Viewport width for Super HD font scaling */\n readonly width?: number;\n /** Distance to training dummy in meters (for distance-based hit feedback) */\n readonly distanceToDummy?: number;\n /** Effective reach for current technique in meters */\n readonly effectiveReach?: number;\n}\n\n/**\n * TrainingStatsOverlayHtml Component\n *\n * Html overlay displaying training performance metrics with Korean theming.\n * All colors use KOREAN_COLORS constants for consistency.\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized with custom comparison function\n * - Only re-renders when stats actually change\n * - Reduces unnecessary DOM updates\n *\n * @example\n * ```tsx\n * <TrainingStatsOverlayHtml\n * stats={{ score: 1500, combo: 8, hits: 45, misses: 5, accuracy: 90 }}\n * isMobile={false}\n * />\n * ```\n *\n * @korean 훈련통계오버레이컴포넌트\n */\nexport const TrainingStatsOverlayHtml =\n React.memo<TrainingStatsOverlayHtmlProps>(\n ({\n stats,\n isMobile,\n width = 375,\n distanceToDummy,\n effectiveReach = 0.7, // Default punch reach\n }) => {\n const panelWidth = width > 10 ? width : isMobile ? 180 : 200;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n const gap = getResponsiveSpacing(\"xs\", isMobile);\n\n const safeAreaStyles = useMemo(\n () => (isMobile ? getSafeAreaPadding([\"top\"], padding) : {}),\n [isMobile, padding],\n );\n\n const formattedAccuracy = useMemo(\n () => stats.accuracy.toFixed(1),\n [stats.accuracy],\n );\n\n const formattedDuration = useMemo(() => {\n if (!stats.sessionDuration) return \"00:00\";\n const minutes = Math.floor(stats.sessionDuration / 60);\n const seconds = stats.sessionDuration % 60;\n return `${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n }, [stats.sessionDuration]);\n\n const distanceInfo = useMemo(() => {\n if (distanceToDummy === undefined) return null;\n const isInRange = distanceToDummy <= effectiveReach;\n return {\n formatted: distanceToDummy.toFixed(2),\n isInRange,\n rangeStatus: isInRange\n ? \"사정거리 내 | In Range\"\n : \"사정거리 밖 | Out of Range\",\n };\n }, [distanceToDummy, effectiveReach]);\n\n const perfectRate = useMemo(() => {\n const totalAttempts = stats.hits + stats.misses;\n if (totalAttempts === 0 || !stats.perfectStrikes) return \"0\";\n return ((stats.perfectStrikes / totalAttempts) * 100).toFixed(1);\n }, [stats.hits, stats.misses, stats.perfectStrikes]);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.92,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n ...safeAreaStyles,\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n };\n\n const titleFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375) // 16px minimum\n : 18;\n\n return (\n <div style={panelStyle} data-testid=\"training-stats-html\">\n {/* Header with bilingual title */}\n <div style={{ marginBottom: `${padding}px` }}>\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.ACCENT_GOLD,\n \"medium\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n {formatBilingualText(\"훈련 통계\", \"Training Statistics\", \"pipe\")}\n </div>\n </div>\n\n {/* Stats Grid with consistent Korean theming */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${gap}px`,\n }}\n >\n {/* Score - 점수 */}\n <StatRow\n korean=\"점수\"\n english=\"Score\"\n value={stats.score.toLocaleString()}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Combo - 콤보 */}\n <StatRow\n korean=\"콤보\"\n english=\"Combo\"\n value={`${stats.combo}x`}\n color={\n stats.combo > 5\n ? KOREAN_COLORS.ACCENT_RED\n : KOREAN_COLORS.PRIMARY_CYAN\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Hits - 성공 */}\n <StatRow\n korean=\"성공\"\n english=\"Hits\"\n value={stats.hits.toString()}\n color={KOREAN_COLORS.ACCENT_GREEN}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Misses - 실패 */}\n <StatRow\n korean=\"실패\"\n english=\"Misses\"\n value={stats.misses.toString()}\n color={KOREAN_COLORS.TEXT_TERTIARY}\n isMobile={isMobile}\n width={width}\n />\n\n {/* Accuracy - 정확도 */}\n <StatRow\n korean=\"정확도\"\n english=\"Accuracy\"\n value={`${formattedAccuracy}%`}\n color={\n stats.accuracy >= 80\n ? KOREAN_COLORS.ACCENT_GREEN\n : stats.accuracy >= 50\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n\n {/* Session Duration - 시간 */}\n {stats.sessionDuration !== undefined && (\n <StatRow\n korean=\"시간\"\n english=\"Duration\"\n value={formattedDuration}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Best Combo - 최고 콤보 */}\n {stats.bestCombo !== undefined && stats.bestCombo > 0 && (\n <StatRow\n korean=\"최고 콤보\"\n english=\"Best Combo\"\n value={`${stats.bestCombo}x`}\n color={KOREAN_COLORS.ACCENT_GOLD}\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Perfect Rate - 완벽률 */}\n {stats.hits + stats.misses > 0 && (\n <StatRow\n korean=\"완벽률\"\n english=\"Perfect Rate\"\n value={`${perfectRate}%`}\n color={\n parseFloat(perfectRate) >= 30\n ? KOREAN_COLORS.ACCENT_GOLD\n : parseFloat(perfectRate) >= 10\n ? KOREAN_COLORS.PRIMARY_CYAN\n : KOREAN_COLORS.TEXT_TERTIARY\n }\n isMobile={isMobile}\n width={width}\n />\n )}\n\n {/* Distance to Dummy - 거리 */}\n {distanceInfo && (\n <>\n <StatRow\n korean=\"거리\"\n english=\"Distance\"\n value={`${distanceInfo.formatted}m`}\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n <StatRow\n korean=\"상태\"\n english=\"Status\"\n value={\n distanceInfo.rangeStatus.split(\" | \")[isMobile ? 0 : 1]\n }\n color={\n distanceInfo.isInRange\n ? KOREAN_COLORS.ACCENT_GREEN\n : KOREAN_COLORS.ACCENT_RED\n }\n isMobile={isMobile}\n width={width}\n />\n </>\n )}\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.stats.score === nextProps.stats.score &&\n prevProps.stats.combo === nextProps.stats.combo &&\n prevProps.stats.hits === nextProps.stats.hits &&\n prevProps.stats.misses === nextProps.stats.misses &&\n prevProps.stats.accuracy === nextProps.stats.accuracy &&\n prevProps.stats.sessionDuration === nextProps.stats.sessionDuration &&\n prevProps.stats.bestCombo === nextProps.stats.bestCombo &&\n prevProps.stats.perfectStrikes === nextProps.stats.perfectStrikes &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.width === nextProps.width &&\n prevProps.distanceToDummy === nextProps.distanceToDummy &&\n prevProps.effectiveReach === nextProps.effectiveReach\n );\n },\n );\n\nTrainingStatsOverlayHtml.displayName = \"TrainingStatsOverlayHtml\";\n\n/**\n * Single stat row component with Korean theming\n *\n * Uses KOREAN_COLORS constants for all text colors\n * Enhanced with smooth transitions and neon glow on value\n *\n * Optimized with React.memo for performance\n *\n * @korean 통계행컴포넌트\n */\nconst StatRow = React.memo<{\n korean: string;\n english: string;\n value: string;\n color: number; // Numeric hex color from KOREAN_COLORS (e.g., 0x00ffff)\n isMobile: boolean;\n width: number; // Width for Super HD font scaling\n}>(({ korean, english, value, color, isMobile, width }) => {\n const labelFontSize = isMobile\n ? getMobileKoreanFontSize(\"SMALL\", width) // 16px minimum\n : 14;\n const sublabelFontSize = isMobile ? 12 : 11; // Increased from 8-9px\n const valueFontSize = isMobile\n ? getMobileKoreanFontSize(\"MEDIUM\", width) // 18px minimum\n : 20;\n\n return (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n paddingBottom: `${SPACING.SM}px`,\n borderBottom: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.1)}`,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n >\n <div>\n <div\n style={{\n fontSize: `${labelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(KOREAN_COLORS.PRIMARY_CYAN, \"subtle\"),\n }}\n >\n {korean}\n </div>\n <div\n style={{\n fontSize: `${sublabelFontSize}px`,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {english}\n </div>\n </div>\n <div\n style={{\n fontSize: `${valueFontSize}px`,\n fontWeight: \"bold\",\n color: hexToRgbaString(color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: getNeonTextShadow(color, \"medium\"),\n transition: getSmoothTransition(\"transform, color\", \"normal\"),\n transform: \"scale(1)\",\n }}\n >\n {value}\n </div>\n </div>\n );\n});\n\nStatRow.displayName = \"StatRow\";\n\nexport default TrainingStatsOverlayHtml;\n"],"mappings":"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"}
|