blacktrigram 0.7.39 → 0.7.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeControl.js","names":[],"sources":["../../../../src/components/shared/ui/VolumeControl.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString, toHex } from \"../../../utils/colorUtils\";\n\nexport interface VolumeControlProps {\n readonly position?:\n | \"top-right\"\n | \"bottom-right\"\n | \"top-left\"\n | \"bottom-left\"\n | \"custom\";\n readonly style?: React.CSSProperties;\n readonly showLabels?: boolean;\n readonly compact?: boolean;\n}\n\n/**\n * Volume Control Component\n *\n * Provides controls for:\n * - Master volume\n * - Music volume\n * - SFX volume\n * - Mute/unmute toggle\n *\n * Inspired by template game (https://github.com/Hack23/game)\n */\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\n position = \"top-right\",\n style,\n showLabels = true,\n compact = false,\n}) => {\n const audio = useAudio();\n\n // Local state to track values for UI (prevents issues if audio not ready)\n const [masterVolume, setMasterVolume] = useState(audio.masterVolume ?? 1.0);\n const [musicVolume, setMusicVolume] = useState(audio.musicVolume ?? 0.7);\n const [sfxVolume, setSfxVolume] = useState(audio.sfxVolume ?? 0.8);\n const [isMuted, setIsMuted] = useState(audio.muted ?? false);\n\n // Sync local state with audio manager state changes\n React.useEffect(() => {\n setMasterVolume(audio.masterVolume ?? 1.0);\n setMusicVolume(audio.musicVolume ?? 0.7);\n setSfxVolume(audio.sfxVolume ?? 0.8);\n setIsMuted(audio.muted ?? false);\n }, [audio.masterVolume, audio.musicVolume, audio.sfxVolume, audio.muted]);\n\n // Get position styles (memoized)\n const getPositionStyle = useMemo((): React.CSSProperties => {\n if (position === \"custom\") return {};\n\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n zIndex: 1000,\n padding: compact ? \"8px 12px\" : \"12px 16px\",\n };\n\n switch (position) {\n case \"top-right\":\n return { ...baseStyle, top: \"20px\", right: \"20px\" };\n case \"bottom-right\":\n return { ...baseStyle, bottom: \"20px\", right: \"20px\" };\n case \"top-left\":\n return { ...baseStyle, top: \"20px\", left: \"20px\" };\n case \"bottom-left\":\n return { ...baseStyle, bottom: \"20px\", left: \"20px\" };\n default:\n return baseStyle;\n }\n }, [position, compact]);\n\n const containerStyle = useMemo(\n (): React.CSSProperties => ({\n ...getPositionStyle,\n display: \"flex\",\n flexDirection: compact ? \"row\" : \"column\",\n alignItems: \"center\",\n gap: compact ? \"12px\" : \"8px\",\n background: \"rgba(33, 38, 45, 0.95)\",\n borderRadius: \"12px\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n pointerEvents: \"auto\", // Enable interaction even when parent has pointerEvents: none\n ...style,\n }),\n [getPositionStyle, compact, style],\n );\n\n const handleMasterVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMasterVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"master\", value);\n }\n },\n [audio],\n );\n\n const handleMusicVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMusicVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"music\", value);\n }\n },\n [audio],\n );\n\n const handleSfxVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setSfxVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"sfx\", value);\n }\n },\n [audio],\n );\n\n const handleMuteToggle = useCallback(() => {\n setIsMuted((prevMuted) => {\n const newMuted = !prevMuted;\n if (audio.isAudioReady) {\n if (newMuted) {\n audio.mute();\n } else {\n audio.unmute();\n }\n }\n return newMuted;\n });\n }, [audio]);\n\n const sliderStyle = useMemo(\n (): React.CSSProperties => ({\n width: compact ? \"60px\" : \"100px\",\n cursor: \"pointer\",\n accentColor: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }),\n [compact],\n );\n\n const labelStyle = useMemo(\n (): React.CSSProperties => ({\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: compact ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n minWidth: compact ? \"40px\" : \"50px\",\n textAlign: \"left\",\n }),\n [compact],\n );\n\n const valueStyle = useMemo(\n (): React.CSSProperties => ({\n color: `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`,\n fontSize: compact ? \"10px\" : \"11px\",\n minWidth: \"35px\",\n textAlign: \"right\",\n }),\n [compact],\n );\n\n const controlRowStyle = useMemo(\n (): React.CSSProperties => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n width: \"100%\",\n }),\n [],\n );\n\n if (compact) {\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"6px 12px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇\" : \"🔊\"}\n </button>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n aria-label=\"마스터 볼륨 | Master Volume\"\n style={sliderStyle}\n title=\"마스터 볼륨 | Master Volume\"\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n );\n }\n\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n {showLabels && (\n <div\n style={{\n color: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n fontSize: \"14px\",\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n textAlign: \"center\",\n }}\n >\n 🎵 음량 | Volume\n </div>\n )}\n\n {/* Master Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"master-volume\" style={labelStyle}>\n 전체 | Master\n </label>\n <input\n id=\"master-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n\n {/* Music Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"music-volume\" style={labelStyle}>\n 음악 | Music\n </label>\n <input\n id=\"music-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={musicVolume}\n onChange={handleMusicVolumeChange}\n data-testid=\"music-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(musicVolume * 100)}%</span>\n </div>\n\n {/* SFX Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"sfx-volume\" style={labelStyle}>\n 효과음 | SFX\n </label>\n <input\n id=\"sfx-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={sfxVolume}\n onChange={handleSfxVolumeChange}\n data-testid=\"sfx-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(sfxVolume * 100)}%</span>\n </div>\n\n {/* Mute Toggle */}\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"8px 16px\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n marginTop: \"4px\",\n width: \"100%\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇 음소거 해제 | Unmute\" : \"🔊 음소거 | Mute\"}\n </button>\n\n {/* Audio Status Indicator */}\n <div\n style={{\n color: audio.isAudioReady\n ? `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`\n : hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: \"10px\",\n marginTop: \"4px\",\n textAlign: \"center\",\n }}\n >\n {audio.isAudioReady\n ? \"✓ 오디오 준비됨 | Audio Ready\"\n : \"⏳ 초기화 중... | Initializing...\"}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VolumeControl.js","names":[],"sources":["../../../../src/components/shared/ui/VolumeControl.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString, toHex } from \"../../../utils/colorUtils\";\n\nexport interface VolumeControlProps {\n readonly position?:\n | \"top-right\"\n | \"bottom-right\"\n | \"top-left\"\n | \"bottom-left\"\n | \"custom\";\n readonly style?: React.CSSProperties;\n readonly showLabels?: boolean;\n readonly compact?: boolean;\n}\n\n/**\n * Volume Control Component\n *\n * Provides controls for:\n * - Master volume\n * - Music volume\n * - SFX volume\n * - Mute/unmute toggle\n *\n * Inspired by template game (https://github.com/Hack23/game)\n */\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\n position = \"top-right\",\n style,\n showLabels = true,\n compact = false,\n}) => {\n const audio = useAudio();\n\n // Local state to track values for UI (prevents issues if audio not ready)\n const [masterVolume, setMasterVolume] = useState(audio.masterVolume ?? 1.0);\n const [musicVolume, setMusicVolume] = useState(audio.musicVolume ?? 0.7);\n const [sfxVolume, setSfxVolume] = useState(audio.sfxVolume ?? 0.8);\n const [isMuted, setIsMuted] = useState(audio.muted ?? false);\n\n // Sync local state with audio manager state changes\n React.useEffect(() => {\n setMasterVolume(audio.masterVolume ?? 1.0);\n setMusicVolume(audio.musicVolume ?? 0.7);\n setSfxVolume(audio.sfxVolume ?? 0.8);\n setIsMuted(audio.muted ?? false);\n }, [audio.masterVolume, audio.musicVolume, audio.sfxVolume, audio.muted]);\n\n // Get position styles (memoized)\n const getPositionStyle = useMemo((): React.CSSProperties => {\n if (position === \"custom\") return {};\n\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n zIndex: 1000,\n padding: compact ? \"8px 12px\" : \"12px 16px\",\n };\n\n switch (position) {\n case \"top-right\":\n return { ...baseStyle, top: \"20px\", right: \"20px\" };\n case \"bottom-right\":\n return { ...baseStyle, bottom: \"20px\", right: \"20px\" };\n case \"top-left\":\n return { ...baseStyle, top: \"20px\", left: \"20px\" };\n case \"bottom-left\":\n return { ...baseStyle, bottom: \"20px\", left: \"20px\" };\n default:\n return baseStyle;\n }\n }, [position, compact]);\n\n const containerStyle = useMemo(\n (): React.CSSProperties => ({\n ...getPositionStyle,\n display: \"flex\",\n flexDirection: compact ? \"row\" : \"column\",\n alignItems: \"center\",\n gap: compact ? \"12px\" : \"8px\",\n background: \"rgba(33, 38, 45, 0.95)\",\n borderRadius: \"12px\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,\n pointerEvents: \"auto\", // Enable interaction even when parent has pointerEvents: none\n ...style,\n }),\n [getPositionStyle, compact, style],\n );\n\n const handleMasterVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMasterVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"master\", value);\n }\n },\n [audio],\n );\n\n const handleMusicVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setMusicVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"music\", value);\n }\n },\n [audio],\n );\n\n const handleSfxVolumeChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = parseFloat(event.target.value);\n setSfxVolume(value);\n if (audio.isAudioReady) {\n audio.setVolume(\"sfx\", value);\n }\n },\n [audio],\n );\n\n const handleMuteToggle = useCallback(() => {\n setIsMuted((prevMuted) => {\n const newMuted = !prevMuted;\n if (audio.isAudioReady) {\n if (newMuted) {\n audio.mute();\n } else {\n audio.unmute();\n }\n }\n return newMuted;\n });\n }, [audio]);\n\n const sliderStyle = useMemo(\n (): React.CSSProperties => ({\n width: compact ? \"60px\" : \"100px\",\n cursor: \"pointer\",\n accentColor: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }),\n [compact],\n );\n\n const labelStyle = useMemo(\n (): React.CSSProperties => ({\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: compact ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n minWidth: compact ? \"40px\" : \"50px\",\n textAlign: \"left\",\n }),\n [compact],\n );\n\n const valueStyle = useMemo(\n (): React.CSSProperties => ({\n color: `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`,\n fontSize: compact ? \"10px\" : \"11px\",\n minWidth: \"35px\",\n textAlign: \"right\",\n }),\n [compact],\n );\n\n const controlRowStyle = useMemo(\n (): React.CSSProperties => ({\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n width: \"100%\",\n }),\n [],\n );\n\n if (compact) {\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"6px 12px\",\n borderRadius: \"6px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇\" : \"🔊\"}\n </button>\n <input\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n aria-label=\"마스터 볼륨 | Master Volume\"\n style={sliderStyle}\n title=\"마스터 볼륨 | Master Volume\"\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n );\n }\n\n return (\n <div style={containerStyle} data-testid=\"volume-control\">\n {showLabels && (\n <div\n style={{\n color: `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n fontSize: \"14px\",\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n textAlign: \"center\",\n }}\n >\n 🎵 음량 | Volume\n </div>\n )}\n\n {/* Master Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"master-volume\" style={labelStyle}>\n 전체 | Master\n </label>\n <input\n id=\"master-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={masterVolume}\n onChange={handleMasterVolumeChange}\n data-testid=\"master-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(masterVolume * 100)}%</span>\n </div>\n\n {/* Music Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"music-volume\" style={labelStyle}>\n 음악 | Music\n </label>\n <input\n id=\"music-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={musicVolume}\n onChange={handleMusicVolumeChange}\n data-testid=\"music-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(musicVolume * 100)}%</span>\n </div>\n\n {/* SFX Volume */}\n <div style={controlRowStyle}>\n <label htmlFor=\"sfx-volume\" style={labelStyle}>\n 효과음 | SFX\n </label>\n <input\n id=\"sfx-volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n value={sfxVolume}\n onChange={handleSfxVolumeChange}\n data-testid=\"sfx-volume-slider\"\n style={sliderStyle}\n />\n <span style={valueStyle}>{Math.round(sfxVolume * 100)}%</span>\n </div>\n\n {/* Mute Toggle */}\n <button\n onClick={handleMuteToggle}\n data-testid=\"mute-toggle-button\"\n aria-label={isMuted ? \"Unmute audio\" : \"Mute audio\"}\n style={{\n background: isMuted\n ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)\n : `#${toHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n border: \"none\",\n padding: \"8px 16px\",\n borderRadius: \"8px\",\n cursor: \"pointer\",\n fontWeight: \"bold\",\n fontSize: \"14px\",\n marginTop: \"4px\",\n width: \"100%\",\n }}\n title={isMuted ? \"음소거 해제 | Unmute\" : \"음소거 | Mute\"}\n >\n {isMuted ? \"🔇 음소거 해제 | Unmute\" : \"🔊 음소거 | Mute\"}\n </button>\n\n {/* Audio Status Indicator */}\n <div\n style={{\n color: audio.isAudioReady\n ? `#${toHex(KOREAN_COLORS.ACCENT_GOLD)}`\n : hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: \"10px\",\n marginTop: \"4px\",\n textAlign: \"center\",\n }}\n >\n {audio.isAudioReady\n ? \"✓ 오디오 준비됨 | Audio Ready\"\n : \"⏳ 초기화 중... | Initializing...\"}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../../../src/components/shared/ui/shared/ConfirmDialog.tsx"],"sourcesContent":["/**\n * ConfirmDialog Component - Modal confirmation dialog\n * \n * Features:\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - Responsive sizing\n * - Keyboard shortcuts (Enter = confirm, Esc = cancel)\n * \n * Now uses BaseButtonOverlayHtml for consistent styling\n */\n\nimport React, { useEffect } from \"react\";\nimport { useAudio } from \"../../../../audio/AudioProvider\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../../base\";\n\nexport interface ConfirmDialogProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n readonly onCancel: () => void;\n readonly isMobile: boolean;\n}\n\n/**\n * ConfirmDialog - Modal confirmation dialog with Korean theming\n */\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = ({\n isOpen,\n title,\n titleKorean,\n message,\n messageKorean,\n onConfirm,\n onCancel,\n isMobile,\n}) => {\n const audio = useAudio();\n\n // Handle keyboard shortcuts\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n onConfirm();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isOpen, onConfirm, onCancel]);\n\n if (!isOpen) return null;\n\n return (\n <div\n data-testid=\"confirm-dialog\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8),\n backdropFilter: \"blur(8px)\",\n zIndex: 1001,\n pointerEvents: \"auto\",\n }}\n onClick={(e) => {\n // Close on backdrop click\n if (e.target === e.currentTarget) {\n onCancel();\n }\n }}\n >\n <div\n style={{\n padding: isMobile ? \"24px\" : \"32px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: \"12px\",\n maxWidth: isMobile ? \"320px\" : \"400px\",\n boxShadow: `0 0 30px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }}\n >\n {/* Title */}\n <h2\n data-testid=\"dialog-title\"\n style={{\n fontSize: isMobile ? \"20px\" : \"24px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n margin: \"0 0 16px 0\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n {titleKorean}\n <br />\n {title}\n </h2>\n\n {/* Message */}\n <p\n data-testid=\"dialog-message\"\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n margin: \"0 0 24px 0\",\n lineHeight: \"1.5\",\n textAlign: \"center\",\n }}\n >\n {messageKorean}\n <br />\n {message}\n </p>\n\n {/* Buttons - Now using BaseButtonOverlayHtml */}\n <div\n style={{\n display: \"flex\",\n gap: \"12px\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"취소\"\n english=\"Cancel\"\n onClick={() => {\n audio.playSFX(\"menu_back\");\n onCancel();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"cancel-button\"\n style={{ flex: 1 }}\n />\n <BaseButtonOverlayHtml\n korean=\"확인\"\n english=\"Confirm\"\n onClick={() => {\n audio.playSFX(\"menu_select\");\n onConfirm();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"confirm-button\"\n style={{ flex: 1 }}\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ConfirmDialog;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAA+C,EAC1D,QACA,OACA,aACA,SACA,eACA,WACA,UACA,eACI;CACJ,MAAM,QAAQ,UAAU;
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../../../src/components/shared/ui/shared/ConfirmDialog.tsx"],"sourcesContent":["/**\n * ConfirmDialog Component - Modal confirmation dialog\n * \n * Features:\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - Responsive sizing\n * - Keyboard shortcuts (Enter = confirm, Esc = cancel)\n * \n * Now uses BaseButtonOverlayHtml for consistent styling\n */\n\nimport React, { useEffect } from \"react\";\nimport { useAudio } from \"../../../../audio/AudioProvider\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../../base\";\n\nexport interface ConfirmDialogProps {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n readonly onCancel: () => void;\n readonly isMobile: boolean;\n}\n\n/**\n * ConfirmDialog - Modal confirmation dialog with Korean theming\n */\nexport const ConfirmDialog: React.FC<ConfirmDialogProps> = ({\n isOpen,\n title,\n titleKorean,\n message,\n messageKorean,\n onConfirm,\n onCancel,\n isMobile,\n}) => {\n const audio = useAudio();\n\n // Handle keyboard shortcuts\n useEffect(() => {\n if (!isOpen) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n onConfirm();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onCancel();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isOpen, onConfirm, onCancel]);\n\n if (!isOpen) return null;\n\n return (\n <div\n data-testid=\"confirm-dialog\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK_SOLID, 0.8),\n backdropFilter: \"blur(8px)\",\n zIndex: 1001,\n pointerEvents: \"auto\",\n }}\n onClick={(e) => {\n // Close on backdrop click\n if (e.target === e.currentTarget) {\n onCancel();\n }\n }}\n >\n <div\n style={{\n padding: isMobile ? \"24px\" : \"32px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8)}`,\n borderRadius: \"12px\",\n maxWidth: isMobile ? \"320px\" : \"400px\",\n boxShadow: `0 0 30px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.3)}`,\n }}\n >\n {/* Title */}\n <h2\n data-testid=\"dialog-title\"\n style={{\n fontSize: isMobile ? \"20px\" : \"24px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n margin: \"0 0 16px 0\",\n textAlign: \"center\",\n textShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n {titleKorean}\n <br />\n {title}\n </h2>\n\n {/* Message */}\n <p\n data-testid=\"dialog-message\"\n style={{\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n fontFamily: FONT_FAMILY.KOREAN,\n margin: \"0 0 24px 0\",\n lineHeight: \"1.5\",\n textAlign: \"center\",\n }}\n >\n {messageKorean}\n <br />\n {message}\n </p>\n\n {/* Buttons - Now using BaseButtonOverlayHtml */}\n <div\n style={{\n display: \"flex\",\n gap: \"12px\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"취소\"\n english=\"Cancel\"\n onClick={() => {\n audio.playSFX(\"menu_back\");\n onCancel();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"cancel-button\"\n style={{ flex: 1 }}\n />\n <BaseButtonOverlayHtml\n korean=\"확인\"\n english=\"Confirm\"\n onClick={() => {\n audio.playSFX(\"menu_select\");\n onConfirm();\n }}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n isMobile={isMobile}\n testId=\"confirm-button\"\n style={{ flex: 1 }}\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ConfirmDialog;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,iBAA+C,EAC1D,QACA,OACA,aACA,SACA,eACA,WACA,UACA,eACI;CACJ,MAAM,QAAQ,UAAU;CAGxB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EAEb,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,SAAS;IACrB,EAAE,gBAAgB;IAClB,WAAW;UACN,IAAI,EAAE,QAAQ,UAAU;IAC7B,EAAE,gBAAgB;IAClB,UAAU;;;EAId,OAAO,iBAAiB,WAAW,cAAc;EACjD,aAAa,OAAO,oBAAoB,WAAW,cAAc;IAChE;EAAC;EAAQ;EAAW;EAAS,CAAC;CAEjC,IAAI,CAAC,QAAQ,OAAO;CAEpB,OACE,oBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,iBAAiB,gBAAgB,cAAc,aAAa,GAAI;GAChE,gBAAgB;GAChB,QAAQ;GACR,eAAe;GAChB;EACD,UAAU,MAAM;GAEd,IAAI,EAAE,WAAW,EAAE,eACjB,UAAU;;YAId,qBAAC,OAAD;GACE,OAAO;IACL,SAAS,WAAW,SAAS;IAC7B,iBAAiB,gBAAgB,cAAc,oBAAoB,EAAE;IACrE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;IACrE,cAAc;IACd,UAAU,WAAW,UAAU;IAC/B,WAAW,YAAY,gBAAgB,cAAc,cAAc,GAAI;IACxE;aARH;IAWE,qBAAC,MAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,aAAa,EAAE;MACpD,YAAY,YAAY;MACxB,YAAY;MACZ,QAAQ;MACR,WAAW;MACX,YAAY,YAAY,gBAAgB,cAAc,aAAa,GAAI;MACxE;eAVH;MAYG;MACD,oBAAC,MAAD,EAAM,CAAA;MACL;MACE;;IAGL,qBAAC,KAAD;KACE,eAAY;KACZ,OAAO;MACL,UAAU,WAAW,SAAS;MAC9B,OAAO,gBAAgB,cAAc,cAAc,EAAE;MACrD,YAAY,YAAY;MACxB,QAAQ;MACR,YAAY;MACZ,WAAW;MACZ;eATH;MAWG;MACD,oBAAC,MAAD,EAAM,CAAA;MACL;MACC;;IAGJ,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,KAAK;MACN;eAJH,CAME,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,YAAY;OAC1B,UAAU;;MAEZ,oBAAoB,MAAM,QAAQ,aAAa;MAC/C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,GAAG;MAClB,CAAA,EACF,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,eAAe;OACb,MAAM,QAAQ,cAAc;OAC5B,WAAW;;MAEb,oBAAoB,MAAM,QAAQ,aAAa;MAC/C,SAAQ;MACR,MAAM,WAAW,OAAO;MACd;MACV,QAAO;MACP,OAAO,EAAE,MAAM,GAAG;MAClB,CAAA,CACE;;IACF;;EACF,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n // Calculate vulnerability state\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n // Container style with vulnerability effects\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n // Add shake animation when vulnerable\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAS,gBAAgB,SAAyB;
|
|
1
|
+
{"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n // Calculate vulnerability state\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n // Container style with vulnerability effects\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n // Add shake animation when vulnerable\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bodyDimensions.js","names":[],"sources":["../../src/constants/bodyDimensions.ts"],"sourcesContent":["/**\n * Centralized body dimensions for consistent rendering across muscles and clothing.\n *\n * **Korean**: 신체 치수 (Body Dimensions)\n *\n * This module provides a single source of truth for body part radii and lengths.\n * Both the muscle system (BoneAttachedMuscles) and clothing system (BoneClothing)\n * import these values to ensure clothing properly wraps around muscles.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for muscular Korean martial arts fighter proportions\n * - Radii represent the thickness of each body part\n * - Lengths represent the extent along the bone axis\n * - Clothing should add a thin layer OUTSIDE these dimensions\n *\n * @module constants/bodyDimensions\n * @category Rendering\n * @korean 신체치수\n */\n\n// ============================================================================\n// SHOULDER DIMENSIONS (어깨 치수)\n// ============================================================================\n\n/**\n * Shoulder (deltoid) muscle radius.\n * Athletic martial artist proportions.\n * @korean 어깨반지름\n */\nexport const SHOULDER_RADIUS = 0.08;\n\n/**\n * Shoulder muscle length along bone axis.\n * @korean 어깨길이\n */\nexport const SHOULDER_LENGTH = 0.15;\n\n// ============================================================================\n// UPPER ARM DIMENSIONS (상완 치수)\n// ============================================================================\n\n/**\n * Bicep muscle radius.\n * Muscular martial artist proportions.\n * @korean 이두근반지름\n */\nexport const BICEP_RADIUS = 0.06;\n\n/**\n * Bicep muscle length.\n * @korean 이두근길이\n */\nexport const BICEP_LENGTH = 0.25;\n\n/**\n * Tricep muscle radius.\n * Slightly smaller than bicep.\n * @korean 삼두근반지름\n */\nexport const TRICEP_RADIUS = 0.055;\n\n/**\n * Tricep muscle length.\n * @korean 삼두근길이\n */\nexport const TRICEP_LENGTH = 0.22;\n\n/**\n * Combined upper arm radius for clothing calculations.\n * Uses the larger of bicep/tricep for proper fit.\n * @korean 상완반지름\n */\nexport const UPPER_ARM_RADIUS = Math.max(BICEP_RADIUS, TRICEP_RADIUS);\n\n// ============================================================================\n// FOREARM DIMENSIONS (전완 치수)\n// ============================================================================\n\n/**\n * Forearm muscle radius.\n * Athletic martial artist proportions.\n * @korean 전완반지름\n */\nexport const FOREARM_RADIUS = 0.045;\n\n/**\n * Forearm muscle length.\n * @korean 전완길이\n */\nexport const FOREARM_LENGTH = 0.2;\n\n// ============================================================================\n// TORSO DIMENSIONS (몸통 치수)\n// ============================================================================\n\n/**\n * Pectorals (chest) muscle radius/depth.\n * Athletic chest depth.\n * @korean 가슴반지름\n */\nexport const PECTORALS_RADIUS = 0.12;\n\n/**\n * Pectorals width (length in capsule terms).\n * @korean 가슴너비\n */\nexport const PECTORALS_LENGTH = 0.28;\n\n/**\n * Core (midsection) muscle radius.\n * Athletic core depth.\n * @korean 코어반지름\n */\nexport const CORE_RADIUS = 0.1;\n\n/**\n * Core muscle length.\n * @korean 코어길이\n */\nexport const CORE_LENGTH = 0.25;\n\n/**\n * Abdominals muscle radius.\n * Athletic abs depth.\n * @korean 복근반지름\n */\nexport const ABS_RADIUS = 0.08;\n\n/**\n * Abdominals muscle length.\n * @korean 복근길이\n */\nexport const ABS_LENGTH = 0.22;\n\n/**\n * Obliques muscle radius.\n * @korean 복사근반지름\n */\nexport const OBLIQUES_RADIUS = 0.06;\n\n/**\n * Obliques muscle length.\n * @korean 복사근길이\n */\nexport const OBLIQUES_LENGTH = 0.2;\n\n// ============================================================================\n// HIP/GLUTE DIMENSIONS (엉덩이 치수)\n// ============================================================================\n\n/**\n * Hip flexor muscle radius.\n * @korean 고관절굴근반지름\n */\nexport const HIP_FLEXOR_RADIUS = 0.04;\n\n/**\n * Hip flexor muscle length.\n * @korean 고관절굴근길이\n */\nexport const HIP_FLEXOR_LENGTH = 0.12;\n\n/**\n * Glute muscle radius.\n * Athletic glute proportions.\n * @korean 둔근반지름\n */\nexport const GLUTE_RADIUS = 0.06;\n\n/**\n * Glute muscle length.\n * @korean 둔근길이\n */\nexport const GLUTE_LENGTH = 0.14;\n\n// ============================================================================\n// THIGH DIMENSIONS (허벅지 치수)\n// ============================================================================\n\n/**\n * Quadriceps muscle radius.\n * Athletic thigh proportions.\n * @korean 대퇴사두근반지름\n */\nexport const QUAD_RADIUS = 0.055;\n\n/**\n * Quadriceps muscle length.\n * @korean 대퇴사두근길이\n */\nexport const QUAD_LENGTH = 0.25;\n\n/**\n * Hamstring muscle radius.\n * @korean 햄스트링반지름\n */\nexport const HAMSTRING_RADIUS = 0.05;\n\n/**\n * Hamstring muscle length.\n * @korean 햄스트링길이\n */\nexport const HAMSTRING_LENGTH = 0.23;\n\n/**\n * Combined thigh radius for clothing calculations.\n * Uses the larger of quad/hamstring for proper fit.\n * @korean 허벅지반지름\n */\nexport const THIGH_RADIUS = Math.max(QUAD_RADIUS, HAMSTRING_RADIUS);\n\n// ============================================================================\n// LOWER LEG DIMENSIONS (종아리 치수)\n// ============================================================================\n\n/**\n * Calf muscle radius.\n * Athletic calf proportions.\n * @korean 종아리반지름\n */\nexport const CALF_RADIUS = 0.04;\n\n/**\n * Calf muscle length.\n * @korean 종아리길이\n */\nexport const CALF_LENGTH = 0.2;\n\n// ============================================================================\n// BACK DIMENSIONS (등 치수)\n// ============================================================================\n\n/**\n * Latissimus dorsi muscle radius.\n * @korean 광배근반지름\n */\nexport const LAT_RADIUS = 0.05;\n\n/**\n * Latissimus dorsi muscle length.\n * @korean 광배근길이\n */\nexport const LAT_LENGTH = 0.2;\n\n/**\n * Trapezius muscle radius.\n * @korean 승모근반지름\n */\nexport const TRAPEZIUS_RADIUS = 0.045;\n\n/**\n * Trapezius muscle length.\n * @korean 승모근길이\n */\nexport const TRAPEZIUS_LENGTH = 0.14;\n\n/**\n * Erector spinae muscle radius.\n * @korean 척추기립근반지름\n */\nexport const ERECTOR_SPINAE_RADIUS = 0.035;\n\n/**\n * Erector spinae muscle length.\n * @korean 척추기립근길이\n */\nexport const ERECTOR_SPINAE_LENGTH = 0.18;\n\n// ============================================================================\n// CLOTHING CONSTANTS (의류 상수)\n// ============================================================================\n\n/**\n * Standard fabric thickness for tight-fitting clothing.\n * @korean 밀착의류두께\n */\nexport const CLOTHING_THICKNESS_TIGHT = 0.01;\n\n/**\n * Standard fabric thickness for fitted clothing.\n * @korean 맞춤의류두께\n */\nexport const CLOTHING_THICKNESS_FITTED = 0.015;\n\n/**\n * Standard fabric thickness for loose clothing.\n * @korean 헐렁한의류두께\n */\nexport const CLOTHING_THICKNESS_LOOSE = 0.025;\n\n/**\n * Standard fabric thickness for oversized clothing.\n * @korean 오버사이즈의류두께\n */\nexport const CLOTHING_THICKNESS_OVERSIZED = 0.04;\n\n/**\n * Get clothing thickness based on fit type.\n * @param fit - Clothing fit type\n * @returns Fabric thickness in meters\n * @korean 의류두께가져오기\n */\nexport const getClothingThickness = (\n fit: \"tight\" | \"fitted\" | \"loose\" | \"oversized\",\n): number => {\n switch (fit) {\n case \"tight\":\n return CLOTHING_THICKNESS_TIGHT;\n case \"fitted\":\n return CLOTHING_THICKNESS_FITTED;\n case \"loose\":\n return CLOTHING_THICKNESS_LOOSE;\n case \"oversized\":\n return CLOTHING_THICKNESS_OVERSIZED;\n default:\n return CLOTHING_THICKNESS_FITTED;\n }\n};\n\n/**\n * Calculate clothing radius for a body part.\n * Adds fabric thickness to body radius with body thickness scaling.\n *\n * @param bodyRadius - Base body part radius\n * @param bodyThickness - Body thickness multiplier (from muscle/fat mass)\n * @param clothingThickness - Fabric thickness\n * @returns Total clothing radius\n * @korean 의류반지름계산\n */\nexport const calculateClothingRadius = (\n bodyRadius: number,\n bodyThickness: number,\n clothingThickness: number,\n): number => {\n return bodyRadius * bodyThickness + clothingThickness;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;;AAW/B,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAO5B,IAAa,gBAAgB;;;;;AAM7B,IAAa,gBAAgB;;;;;;AAkB7B,IAAa,iBAAiB;;;;;AAM9B,IAAa,iBAAiB;;;;;;AAW9B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAOhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;;AAO3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;AAU/B,IAAa,oBAAoB;;;;;AAMjC,IAAa,oBAAoB;;;;;;AAOjC,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAW5B,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAM3B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAkBhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAU3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;AAMhC,IAAa,wBAAwB;;;;;AAMrC,IAAa,wBAAwB;;;;;AAgBrC,IAAa,4BAA4B;;;;;;;;;;;AA+CzC,IAAa,2BACX,YACA,eACA,sBACW;
|
|
1
|
+
{"version":3,"file":"bodyDimensions.js","names":[],"sources":["../../src/constants/bodyDimensions.ts"],"sourcesContent":["/**\n * Centralized body dimensions for consistent rendering across muscles and clothing.\n *\n * **Korean**: 신체 치수 (Body Dimensions)\n *\n * This module provides a single source of truth for body part radii and lengths.\n * Both the muscle system (BoneAttachedMuscles) and clothing system (BoneClothing)\n * import these values to ensure clothing properly wraps around muscles.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for muscular Korean martial arts fighter proportions\n * - Radii represent the thickness of each body part\n * - Lengths represent the extent along the bone axis\n * - Clothing should add a thin layer OUTSIDE these dimensions\n *\n * @module constants/bodyDimensions\n * @category Rendering\n * @korean 신체치수\n */\n\n// ============================================================================\n// SHOULDER DIMENSIONS (어깨 치수)\n// ============================================================================\n\n/**\n * Shoulder (deltoid) muscle radius.\n * Athletic martial artist proportions.\n * @korean 어깨반지름\n */\nexport const SHOULDER_RADIUS = 0.08;\n\n/**\n * Shoulder muscle length along bone axis.\n * @korean 어깨길이\n */\nexport const SHOULDER_LENGTH = 0.15;\n\n// ============================================================================\n// UPPER ARM DIMENSIONS (상완 치수)\n// ============================================================================\n\n/**\n * Bicep muscle radius.\n * Muscular martial artist proportions.\n * @korean 이두근반지름\n */\nexport const BICEP_RADIUS = 0.06;\n\n/**\n * Bicep muscle length.\n * @korean 이두근길이\n */\nexport const BICEP_LENGTH = 0.25;\n\n/**\n * Tricep muscle radius.\n * Slightly smaller than bicep.\n * @korean 삼두근반지름\n */\nexport const TRICEP_RADIUS = 0.055;\n\n/**\n * Tricep muscle length.\n * @korean 삼두근길이\n */\nexport const TRICEP_LENGTH = 0.22;\n\n/**\n * Combined upper arm radius for clothing calculations.\n * Uses the larger of bicep/tricep for proper fit.\n * @korean 상완반지름\n */\nexport const UPPER_ARM_RADIUS = Math.max(BICEP_RADIUS, TRICEP_RADIUS);\n\n// ============================================================================\n// FOREARM DIMENSIONS (전완 치수)\n// ============================================================================\n\n/**\n * Forearm muscle radius.\n * Athletic martial artist proportions.\n * @korean 전완반지름\n */\nexport const FOREARM_RADIUS = 0.045;\n\n/**\n * Forearm muscle length.\n * @korean 전완길이\n */\nexport const FOREARM_LENGTH = 0.2;\n\n// ============================================================================\n// TORSO DIMENSIONS (몸통 치수)\n// ============================================================================\n\n/**\n * Pectorals (chest) muscle radius/depth.\n * Athletic chest depth.\n * @korean 가슴반지름\n */\nexport const PECTORALS_RADIUS = 0.12;\n\n/**\n * Pectorals width (length in capsule terms).\n * @korean 가슴너비\n */\nexport const PECTORALS_LENGTH = 0.28;\n\n/**\n * Core (midsection) muscle radius.\n * Athletic core depth.\n * @korean 코어반지름\n */\nexport const CORE_RADIUS = 0.1;\n\n/**\n * Core muscle length.\n * @korean 코어길이\n */\nexport const CORE_LENGTH = 0.25;\n\n/**\n * Abdominals muscle radius.\n * Athletic abs depth.\n * @korean 복근반지름\n */\nexport const ABS_RADIUS = 0.08;\n\n/**\n * Abdominals muscle length.\n * @korean 복근길이\n */\nexport const ABS_LENGTH = 0.22;\n\n/**\n * Obliques muscle radius.\n * @korean 복사근반지름\n */\nexport const OBLIQUES_RADIUS = 0.06;\n\n/**\n * Obliques muscle length.\n * @korean 복사근길이\n */\nexport const OBLIQUES_LENGTH = 0.2;\n\n// ============================================================================\n// HIP/GLUTE DIMENSIONS (엉덩이 치수)\n// ============================================================================\n\n/**\n * Hip flexor muscle radius.\n * @korean 고관절굴근반지름\n */\nexport const HIP_FLEXOR_RADIUS = 0.04;\n\n/**\n * Hip flexor muscle length.\n * @korean 고관절굴근길이\n */\nexport const HIP_FLEXOR_LENGTH = 0.12;\n\n/**\n * Glute muscle radius.\n * Athletic glute proportions.\n * @korean 둔근반지름\n */\nexport const GLUTE_RADIUS = 0.06;\n\n/**\n * Glute muscle length.\n * @korean 둔근길이\n */\nexport const GLUTE_LENGTH = 0.14;\n\n// ============================================================================\n// THIGH DIMENSIONS (허벅지 치수)\n// ============================================================================\n\n/**\n * Quadriceps muscle radius.\n * Athletic thigh proportions.\n * @korean 대퇴사두근반지름\n */\nexport const QUAD_RADIUS = 0.055;\n\n/**\n * Quadriceps muscle length.\n * @korean 대퇴사두근길이\n */\nexport const QUAD_LENGTH = 0.25;\n\n/**\n * Hamstring muscle radius.\n * @korean 햄스트링반지름\n */\nexport const HAMSTRING_RADIUS = 0.05;\n\n/**\n * Hamstring muscle length.\n * @korean 햄스트링길이\n */\nexport const HAMSTRING_LENGTH = 0.23;\n\n/**\n * Combined thigh radius for clothing calculations.\n * Uses the larger of quad/hamstring for proper fit.\n * @korean 허벅지반지름\n */\nexport const THIGH_RADIUS = Math.max(QUAD_RADIUS, HAMSTRING_RADIUS);\n\n// ============================================================================\n// LOWER LEG DIMENSIONS (종아리 치수)\n// ============================================================================\n\n/**\n * Calf muscle radius.\n * Athletic calf proportions.\n * @korean 종아리반지름\n */\nexport const CALF_RADIUS = 0.04;\n\n/**\n * Calf muscle length.\n * @korean 종아리길이\n */\nexport const CALF_LENGTH = 0.2;\n\n// ============================================================================\n// BACK DIMENSIONS (등 치수)\n// ============================================================================\n\n/**\n * Latissimus dorsi muscle radius.\n * @korean 광배근반지름\n */\nexport const LAT_RADIUS = 0.05;\n\n/**\n * Latissimus dorsi muscle length.\n * @korean 광배근길이\n */\nexport const LAT_LENGTH = 0.2;\n\n/**\n * Trapezius muscle radius.\n * @korean 승모근반지름\n */\nexport const TRAPEZIUS_RADIUS = 0.045;\n\n/**\n * Trapezius muscle length.\n * @korean 승모근길이\n */\nexport const TRAPEZIUS_LENGTH = 0.14;\n\n/**\n * Erector spinae muscle radius.\n * @korean 척추기립근반지름\n */\nexport const ERECTOR_SPINAE_RADIUS = 0.035;\n\n/**\n * Erector spinae muscle length.\n * @korean 척추기립근길이\n */\nexport const ERECTOR_SPINAE_LENGTH = 0.18;\n\n// ============================================================================\n// CLOTHING CONSTANTS (의류 상수)\n// ============================================================================\n\n/**\n * Standard fabric thickness for tight-fitting clothing.\n * @korean 밀착의류두께\n */\nexport const CLOTHING_THICKNESS_TIGHT = 0.01;\n\n/**\n * Standard fabric thickness for fitted clothing.\n * @korean 맞춤의류두께\n */\nexport const CLOTHING_THICKNESS_FITTED = 0.015;\n\n/**\n * Standard fabric thickness for loose clothing.\n * @korean 헐렁한의류두께\n */\nexport const CLOTHING_THICKNESS_LOOSE = 0.025;\n\n/**\n * Standard fabric thickness for oversized clothing.\n * @korean 오버사이즈의류두께\n */\nexport const CLOTHING_THICKNESS_OVERSIZED = 0.04;\n\n/**\n * Get clothing thickness based on fit type.\n * @param fit - Clothing fit type\n * @returns Fabric thickness in meters\n * @korean 의류두께가져오기\n */\nexport const getClothingThickness = (\n fit: \"tight\" | \"fitted\" | \"loose\" | \"oversized\",\n): number => {\n switch (fit) {\n case \"tight\":\n return CLOTHING_THICKNESS_TIGHT;\n case \"fitted\":\n return CLOTHING_THICKNESS_FITTED;\n case \"loose\":\n return CLOTHING_THICKNESS_LOOSE;\n case \"oversized\":\n return CLOTHING_THICKNESS_OVERSIZED;\n default:\n return CLOTHING_THICKNESS_FITTED;\n }\n};\n\n/**\n * Calculate clothing radius for a body part.\n * Adds fabric thickness to body radius with body thickness scaling.\n *\n * @param bodyRadius - Base body part radius\n * @param bodyThickness - Body thickness multiplier (from muscle/fat mass)\n * @param clothingThickness - Fabric thickness\n * @returns Total clothing radius\n * @korean 의류반지름계산\n */\nexport const calculateClothingRadius = (\n bodyRadius: number,\n bodyThickness: number,\n clothingThickness: number,\n): number => {\n return bodyRadius * bodyThickness + clothingThickness;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;;AAW/B,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAO5B,IAAa,gBAAgB;;;;;AAM7B,IAAa,gBAAgB;;;;;;AAkB7B,IAAa,iBAAiB;;;;;AAM9B,IAAa,iBAAiB;;;;;;AAW9B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAOhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;;AAO3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,kBAAkB;;;;;AAM/B,IAAa,kBAAkB;;;;;AAU/B,IAAa,oBAAoB;;;;;AAMjC,IAAa,oBAAoB;;;;;;AAOjC,IAAa,eAAe;;;;;AAM5B,IAAa,eAAe;;;;;;AAW5B,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAM3B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;;AAkBhC,IAAa,cAAc;;;;;AAM3B,IAAa,cAAc;;;;;AAU3B,IAAa,aAAa;;;;;AAM1B,IAAa,aAAa;;;;;AAM1B,IAAa,mBAAmB;;;;;AAMhC,IAAa,mBAAmB;;;;;AAMhC,IAAa,wBAAwB;;;;;AAMrC,IAAa,wBAAwB;;;;;AAgBrC,IAAa,4BAA4B;;;;;;;;;;;AA+CzC,IAAa,2BACX,YACA,eACA,sBACW;CACX,OAAO,aAAa,gBAAgB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bodyRenderingConstants.js","names":[],"sources":["../../src/constants/bodyRenderingConstants.ts"],"sourcesContent":["/**\n * Centralized body rendering constants for Black Trigram.\n *\n * **Korean**: 신체 렌더링 상수 (Body Rendering Constants)\n *\n * This module consolidates all constants related to character body rendering,\n * including bone thickness, muscle scaling, and visual amplification factors.\n * Having them centralized makes it easier to tune the visual appearance.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for realistic Korean martial arts fighter proportions\n * - Constants are grouped by system (bones, muscles, clothing, etc.)\n * - Amplification is kept minimal for anatomical accuracy\n *\n * @module constants/bodyRenderingConstants\n * @category Rendering\n * @korean 신체렌더링상수\n */\n\n// ============================================================================\n// BONE RENDERING CONSTANTS (뼈 렌더링)\n// ============================================================================\n\n/**\n * Base bone radius as fraction of bone length.\n *\n * Anatomically, limb bones have diameter roughly 8-12% of their length.\n * Using 0.10 for realistic slim proportions - athletic fighter build.\n * Reduced from 0.16 which was too puffy/bubble-like.\n *\n * @korean 기본뼈반지름비율\n */\nexport const BASE_BONE_RADIUS_RATIO = 0.1;\n\n/**\n * Minimum bone thickness multiplier.\n *\n * Ensures even lean archetypes maintain visible body mass.\n * Using 0.75 for lean athletic appearance.\n *\n * @korean 최소뼈두께배수\n */\nexport const MIN_BONE_THICKNESS_MULTIPLIER = 0.75;\n\n/**\n * Maximum bone thickness multiplier.\n *\n * Prevents overly bulky appearance for high muscle mass archetypes.\n *\n * @korean 최대뼈두께배수\n */\nexport const MAX_BONE_THICKNESS_MULTIPLIER = 1.3;\n\n/**\n * Visual amplification for bone thickness differences.\n *\n * Amplifies the visual difference between lean and muscular archetypes.\n * Lower values = more subtle differences, higher = more dramatic.\n *\n * @korean 뼈두께시각적증폭\n */\nexport const BONE_THICKNESS_AMPLIFICATION = 1.5;\n\n/**\n * Reference muscle mass for bone thickness calculation (kg).\n *\n * @korean 기준근육량\n */\nexport const REFERENCE_MUSCLE_MASS = 35;\n\n/**\n * Reference fat mass for bone thickness calculation (kg).\n *\n * @korean 기준지방량\n */\nexport const REFERENCE_FAT_MASS = 12;\n\n/**\n * Muscle contribution percentage to bone thickness.\n *\n * @korean 근육기여율\n */\nexport const MUSCLE_THICKNESS_CONTRIBUTION = 0.7;\n\n/**\n * Fat contribution percentage to bone thickness.\n *\n * @korean 지방기여율\n */\nexport const FAT_THICKNESS_CONTRIBUTION = 0.3;\n\n// ============================================================================\n// MUSCLE RENDERING CONSTANTS (근육 렌더링)\n// ============================================================================\n\n/**\n * Muscle geometry normalization factor.\n *\n * When muscles are the primary body shape (bones hidden), this should be 1.0\n * to use radius values directly. The muscle radii (0.10-0.16) are already\n * sized appropriately for athletic martial artist proportions.\n *\n * Previous values (0.35, 0.55) caused double-scaling issues when combined\n * with mesh scale transforms, resulting in skeleton showing through muscles.\n *\n * @korean 근육정규화계수\n */\nexport const MUSCLE_GEOMETRY_NORMALIZATION = 1.0;\n\n/**\n * Base muscle amplification factor for visual differences.\n *\n * Amplifies muscle size differences between archetypes.\n * Previous: 4.0 (caused 0.64x-1.91x range, too extreme)\n * Current: 0.8 (produces 0.84x-1.30x range, realistic)\n *\n * @korean 근육증폭기본계수\n */\nexport const MUSCLE_AMPLIFICATION_BASE = 0.8;\n\n/**\n * Exponent for muscle scaling curve.\n *\n * Previous: 1.5 (exponential, too extreme for lean archetypes)\n * Current: 1.0 (linear, proportional differences)\n *\n * @korean 근육증폭지수\n */\nexport const MUSCLE_AMPLIFICATION_EXPONENT = 1.0;\n\n/**\n * Minimum muscle scale factor.\n *\n * Floor to prevent muscles from vanishing on lean archetypes.\n * 0.82 ensures Hacker (28kg) still has visible musculature.\n *\n * @korean 최소근육크기\n */\nexport const MIN_MUSCLE_SCALE = 0.82;\n\n/**\n * Muscle contraction intensity during combat.\n *\n * How much muscles visually contract during attacks/blocks.\n *\n * @korean 근육수축강도\n */\nexport const MUSCLE_CONTRACTION_INTENSITY = 0.15;\n\n// ============================================================================\n// SKELETON SCALING CONSTANTS (골격 스케일링)\n// ============================================================================\n\n/**\n * Visual amplification for archetype dimension differences.\n *\n * Raw physical attribute differences are subtle (2-12%).\n * This factor amplifies them for more visible silhouettes.\n *\n * @korean 시각적증폭계수\n */\nexport const VISUAL_AMPLIFICATION_FACTOR = 1.2;\n\n/**\n * Height scaling amplification (kept subtle for realism).\n *\n * @korean 키증폭계수\n */\nexport const HEIGHT_AMPLIFICATION = 1.5;\n\n/**\n * Shoulder width amplification for silhouette distinction.\n *\n * @korean 어깨증폭계수\n */\nexport const SHOULDER_AMPLIFICATION = 1.15;\n\n// ============================================================================\n// REFERENCE PHYSICAL ATTRIBUTES (기준 신체 속성)\n// ============================================================================\n\n/**\n * Reference Korean male fighter attributes.\n *\n * Used as baseline for all scaling calculations.\n *\n * @korean 기준신체속성\n */\nexport const REFERENCE_ATTRIBUTES = {\n weight: 75, // kg\n totalHeight: 178, // cm\n legLength: 95, // cm\n armLength: 75, // cm\n torsoLength: 58, // cm\n headSize: 22, // cm\n neckLength: 10, // cm\n shoulderWidth: 43, // cm\n muscleMass: 35, // kg\n fatMass: 12, // kg\n age: 30,\n walkSpeed: 6.0, // m/s\n runSpeed: 9.5, // m/s\n acceleration: 12.0, // m/s²\n} as const;\n\n// ============================================================================\n// ANATOMICAL PROPORTION RATIOS (해부학적 비율)\n// ============================================================================\n\n/**\n * Body proportions as fractions of total height.\n *\n * Based on 8-head canon anatomical proportions.\n *\n * @korean 신체비율\n */\nexport const BODY_PROPORTIONS = {\n HEAD_RATIO: 0.125, // Head is 1/8 of height\n NECK_RATIO: 0.055, // Neck is ~5.5% of height\n TORSO_RATIO: 0.33, // Torso is ~33% of height\n LEG_RATIO: 0.48, // Legs are ~48% of height\n UPPER_ARM_RATIO: 0.55, // Upper arm is 55% of total arm\n FOREARM_RATIO: 0.45, // Forearm is 45% of total arm\n THIGH_RATIO: 0.55, // Thigh is 55% of leg length\n SHIN_RATIO: 0.45, // Shin is 45% of leg length\n HAND_RATIO: 0.105, // Hand is ~10.5% of height\n FOOT_RATIO: 0.15, // Foot length is ~15% of height\n SHOULDER_WIDTH_RATIO: 0.255, // Shoulder width 23-25% of height\n HIP_WIDTH_RATIO: 0.17, // Hip width 16-18% of height\n} as const;\n\n// ============================================================================\n// KOREAN ANATOMY SPECIFICS (한국인 해부학적 특성)\n// ============================================================================\n\n/**\n * Korean adult male anatomical adjustments.\n *\n * Based on Korean physical anthropology studies.\n * These modify proportions for authentic Korean body types.\n *\n * @korean 한국인체형비율\n */\nexport const KOREAN_ANATOMY_ADJUSTMENTS = {\n /** Slightly shorter torso relative to Western proportions */\n TORSO_ADJUSTMENT: 0.98,\n /** Proportionally longer legs for height */\n LEG_ADJUSTMENT: 1.02,\n /** Narrower shoulder width relative to height */\n SHOULDER_ADJUSTMENT: 0.97,\n /** More compact hip structure */\n HIP_ADJUSTMENT: 0.95,\n /** Flatter chest profile */\n CHEST_DEPTH_ADJUSTMENT: 0.92,\n} as const;\n\n// ============================================================================\n// CLOTHING RENDERING CONSTANTS (의복 렌더링)\n// ============================================================================\n\n/**\n * Base clothing offset from body surface.\n *\n * Gap between body and clothing geometry for natural appearance.\n *\n * @korean 의복기본오프셋\n */\nexport const CLOTHING_OFFSET = 0.01; // 1cm gap\n\n/**\n * Clothing thickness (affects layering).\n *\n * @korean 의복두께\n */\nexport const CLOTHING_THICKNESS = 0.005; // 5mm\n\n/**\n * Clothing drape factor for loose garments.\n *\n * @korean 의복드레이프계수\n */\nexport const CLOTHING_DRAPE_FACTOR = 1.1;\n\n// ============================================================================\n// FOOT RENDERING CONSTANTS (발 렌더링)\n// ============================================================================\n\n/**\n * Base foot length in meters (for average 180cm person).\n *\n * @korean 기본발길이\n */\nexport const BASE_FOOT_LENGTH = 0.26; // 26cm\n\n/**\n * Foot width ratio relative to length.\n *\n * @korean 발너비비율\n */\nexport const FOOT_WIDTH_RATIO = 0.385; // ~10cm for 26cm foot\n\n/**\n * Foot height ratio at ankle.\n *\n * @korean 발높이비율\n */\nexport const FOOT_HEIGHT_RATIO = 0.308; // ~8cm for 26cm foot\n\n// ============================================================================\n// HAND RENDERING CONSTANTS (손 렌더링)\n// ============================================================================\n\n/**\n * Base palm width in meters.\n *\n * @korean 기본손바닥너비\n */\nexport const BASE_PALM_WIDTH = 0.085; // 8.5cm\n\n/**\n * Palm length as ratio of palm width.\n *\n * @korean 손바닥길이비율\n */\nexport const PALM_LENGTH_RATIO = 1.12; // ~9.5cm\n\n/**\n * Palm thickness as ratio of width.\n *\n * @korean 손바닥두께비율\n */\nexport const PALM_THICKNESS_RATIO = 0.294; // ~2.5cm\n\n// ============================================================================\n// HELPER FUNCTIONS (도우미 함수)\n// ============================================================================\n\n/**\n * Calculate bone thickness multiplier from physical attributes.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Thickness multiplier (0.85 - 1.3)\n * @korean 뼈두께배수계산\n */\nexport function calculateBoneThickness(\n muscleMass: number,\n fatMass: number,\n): number {\n const muscleRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const fatRatio = fatMass / REFERENCE_FAT_MASS;\n\n const muscleContribution =\n Math.sqrt(muscleRatio) * MUSCLE_THICKNESS_CONTRIBUTION;\n const fatContribution = Math.sqrt(fatRatio) * FAT_THICKNESS_CONTRIBUTION;\n\n const rawMultiplier = muscleContribution + fatContribution;\n const deviation = rawMultiplier - 1.0;\n const amplified = 1.0 + deviation * BONE_THICKNESS_AMPLIFICATION;\n\n return Math.max(\n MIN_BONE_THICKNESS_MULTIPLIER,\n Math.min(MAX_BONE_THICKNESS_MULTIPLIER, amplified),\n );\n}\n\n/**\n * Calculate muscle scale factor from muscle mass.\n *\n * @param muscleMass - Muscle mass in kg\n * @returns Scale factor (0.5 - 2.0+)\n * @korean 근육크기계산\n */\nexport function calculateMuscleScale(muscleMass: number): number {\n const massRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const deviation = massRatio - 1.0;\n\n const exponentialDeviation =\n Math.sign(deviation) *\n Math.pow(Math.abs(deviation), MUSCLE_AMPLIFICATION_EXPONENT);\n\n return Math.max(\n MIN_MUSCLE_SCALE,\n 1.0 + exponentialDeviation * MUSCLE_AMPLIFICATION_BASE,\n );\n}\n\n/**\n * Apply visual amplification to a scaling factor.\n *\n * @param rawFactor - Raw scaling factor (1.0 = reference)\n * @returns Amplified scaling factor\n * @korean 시각적증폭적용\n */\nexport function amplifyScaling(rawFactor: number): number {\n const deviation = rawFactor - 1.0;\n return 1.0 + deviation * VISUAL_AMPLIFICATION_FACTOR;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,yBAAyB;;;;;;;;;AAUtC,IAAa,gCAAgC;;;;;;;;AAS7C,IAAa,gCAAgC;;;;;;;;;AAU7C,IAAa,+BAA+B;;;;;;AAqB5C,IAAa,gCAAgC;;;;;;AAO7C,IAAa,6BAA6B;;;;;;;;;;AA6B1C,IAAa,4BAA4B;;;;;;;;;AAoBzC,IAAa,mBAAmB;;;;;;;;AAkDhC,IAAa,uBAAuB;CAClC,QAAQ;CACR,aAAa;CACb,WAAW;CACX,WAAW;CACX,aAAa;CACb,UAAU;CACV,YAAY;CACZ,eAAe;CACf,YAAY;CACZ,SAAS;CACT,KAAK;CACL,WAAW;CACX,UAAU;CACV,cAAc;CACf;;;;;;;;;AA8ID,SAAgB,uBACd,YACA,SACQ;CACR,MAAM,cAAc,aAAA;CACpB,MAAM,WAAW,UAAA;CAQjB,MAAM,YAAY,KALhB,KAAK,KAAK,YAAY,GAAG,gCACH,KAAK,KAAK,SAAS,GAAG,6BAGZ,KACE;AAEpC,QAAO,KAAK,IACV,+BACA,KAAK,IAAI,+BAA+B,UAAU,CACnD"}
|
|
1
|
+
{"version":3,"file":"bodyRenderingConstants.js","names":[],"sources":["../../src/constants/bodyRenderingConstants.ts"],"sourcesContent":["/**\n * Centralized body rendering constants for Black Trigram.\n *\n * **Korean**: 신체 렌더링 상수 (Body Rendering Constants)\n *\n * This module consolidates all constants related to character body rendering,\n * including bone thickness, muscle scaling, and visual amplification factors.\n * Having them centralized makes it easier to tune the visual appearance.\n *\n * ## Design Philosophy\n *\n * - All values are tuned for realistic Korean martial arts fighter proportions\n * - Constants are grouped by system (bones, muscles, clothing, etc.)\n * - Amplification is kept minimal for anatomical accuracy\n *\n * @module constants/bodyRenderingConstants\n * @category Rendering\n * @korean 신체렌더링상수\n */\n\n// ============================================================================\n// BONE RENDERING CONSTANTS (뼈 렌더링)\n// ============================================================================\n\n/**\n * Base bone radius as fraction of bone length.\n *\n * Anatomically, limb bones have diameter roughly 8-12% of their length.\n * Using 0.10 for realistic slim proportions - athletic fighter build.\n * Reduced from 0.16 which was too puffy/bubble-like.\n *\n * @korean 기본뼈반지름비율\n */\nexport const BASE_BONE_RADIUS_RATIO = 0.1;\n\n/**\n * Minimum bone thickness multiplier.\n *\n * Ensures even lean archetypes maintain visible body mass.\n * Using 0.75 for lean athletic appearance.\n *\n * @korean 최소뼈두께배수\n */\nexport const MIN_BONE_THICKNESS_MULTIPLIER = 0.75;\n\n/**\n * Maximum bone thickness multiplier.\n *\n * Prevents overly bulky appearance for high muscle mass archetypes.\n *\n * @korean 최대뼈두께배수\n */\nexport const MAX_BONE_THICKNESS_MULTIPLIER = 1.3;\n\n/**\n * Visual amplification for bone thickness differences.\n *\n * Amplifies the visual difference between lean and muscular archetypes.\n * Lower values = more subtle differences, higher = more dramatic.\n *\n * @korean 뼈두께시각적증폭\n */\nexport const BONE_THICKNESS_AMPLIFICATION = 1.5;\n\n/**\n * Reference muscle mass for bone thickness calculation (kg).\n *\n * @korean 기준근육량\n */\nexport const REFERENCE_MUSCLE_MASS = 35;\n\n/**\n * Reference fat mass for bone thickness calculation (kg).\n *\n * @korean 기준지방량\n */\nexport const REFERENCE_FAT_MASS = 12;\n\n/**\n * Muscle contribution percentage to bone thickness.\n *\n * @korean 근육기여율\n */\nexport const MUSCLE_THICKNESS_CONTRIBUTION = 0.7;\n\n/**\n * Fat contribution percentage to bone thickness.\n *\n * @korean 지방기여율\n */\nexport const FAT_THICKNESS_CONTRIBUTION = 0.3;\n\n// ============================================================================\n// MUSCLE RENDERING CONSTANTS (근육 렌더링)\n// ============================================================================\n\n/**\n * Muscle geometry normalization factor.\n *\n * When muscles are the primary body shape (bones hidden), this should be 1.0\n * to use radius values directly. The muscle radii (0.10-0.16) are already\n * sized appropriately for athletic martial artist proportions.\n *\n * Previous values (0.35, 0.55) caused double-scaling issues when combined\n * with mesh scale transforms, resulting in skeleton showing through muscles.\n *\n * @korean 근육정규화계수\n */\nexport const MUSCLE_GEOMETRY_NORMALIZATION = 1.0;\n\n/**\n * Base muscle amplification factor for visual differences.\n *\n * Amplifies muscle size differences between archetypes.\n * Previous: 4.0 (caused 0.64x-1.91x range, too extreme)\n * Current: 0.8 (produces 0.84x-1.30x range, realistic)\n *\n * @korean 근육증폭기본계수\n */\nexport const MUSCLE_AMPLIFICATION_BASE = 0.8;\n\n/**\n * Exponent for muscle scaling curve.\n *\n * Previous: 1.5 (exponential, too extreme for lean archetypes)\n * Current: 1.0 (linear, proportional differences)\n *\n * @korean 근육증폭지수\n */\nexport const MUSCLE_AMPLIFICATION_EXPONENT = 1.0;\n\n/**\n * Minimum muscle scale factor.\n *\n * Floor to prevent muscles from vanishing on lean archetypes.\n * 0.82 ensures Hacker (28kg) still has visible musculature.\n *\n * @korean 최소근육크기\n */\nexport const MIN_MUSCLE_SCALE = 0.82;\n\n/**\n * Muscle contraction intensity during combat.\n *\n * How much muscles visually contract during attacks/blocks.\n *\n * @korean 근육수축강도\n */\nexport const MUSCLE_CONTRACTION_INTENSITY = 0.15;\n\n// ============================================================================\n// SKELETON SCALING CONSTANTS (골격 스케일링)\n// ============================================================================\n\n/**\n * Visual amplification for archetype dimension differences.\n *\n * Raw physical attribute differences are subtle (2-12%).\n * This factor amplifies them for more visible silhouettes.\n *\n * @korean 시각적증폭계수\n */\nexport const VISUAL_AMPLIFICATION_FACTOR = 1.2;\n\n/**\n * Height scaling amplification (kept subtle for realism).\n *\n * @korean 키증폭계수\n */\nexport const HEIGHT_AMPLIFICATION = 1.5;\n\n/**\n * Shoulder width amplification for silhouette distinction.\n *\n * @korean 어깨증폭계수\n */\nexport const SHOULDER_AMPLIFICATION = 1.15;\n\n// ============================================================================\n// REFERENCE PHYSICAL ATTRIBUTES (기준 신체 속성)\n// ============================================================================\n\n/**\n * Reference Korean male fighter attributes.\n *\n * Used as baseline for all scaling calculations.\n *\n * @korean 기준신체속성\n */\nexport const REFERENCE_ATTRIBUTES = {\n weight: 75, // kg\n totalHeight: 178, // cm\n legLength: 95, // cm\n armLength: 75, // cm\n torsoLength: 58, // cm\n headSize: 22, // cm\n neckLength: 10, // cm\n shoulderWidth: 43, // cm\n muscleMass: 35, // kg\n fatMass: 12, // kg\n age: 30,\n walkSpeed: 6.0, // m/s\n runSpeed: 9.5, // m/s\n acceleration: 12.0, // m/s²\n} as const;\n\n// ============================================================================\n// ANATOMICAL PROPORTION RATIOS (해부학적 비율)\n// ============================================================================\n\n/**\n * Body proportions as fractions of total height.\n *\n * Based on 8-head canon anatomical proportions.\n *\n * @korean 신체비율\n */\nexport const BODY_PROPORTIONS = {\n HEAD_RATIO: 0.125, // Head is 1/8 of height\n NECK_RATIO: 0.055, // Neck is ~5.5% of height\n TORSO_RATIO: 0.33, // Torso is ~33% of height\n LEG_RATIO: 0.48, // Legs are ~48% of height\n UPPER_ARM_RATIO: 0.55, // Upper arm is 55% of total arm\n FOREARM_RATIO: 0.45, // Forearm is 45% of total arm\n THIGH_RATIO: 0.55, // Thigh is 55% of leg length\n SHIN_RATIO: 0.45, // Shin is 45% of leg length\n HAND_RATIO: 0.105, // Hand is ~10.5% of height\n FOOT_RATIO: 0.15, // Foot length is ~15% of height\n SHOULDER_WIDTH_RATIO: 0.255, // Shoulder width 23-25% of height\n HIP_WIDTH_RATIO: 0.17, // Hip width 16-18% of height\n} as const;\n\n// ============================================================================\n// KOREAN ANATOMY SPECIFICS (한국인 해부학적 특성)\n// ============================================================================\n\n/**\n * Korean adult male anatomical adjustments.\n *\n * Based on Korean physical anthropology studies.\n * These modify proportions for authentic Korean body types.\n *\n * @korean 한국인체형비율\n */\nexport const KOREAN_ANATOMY_ADJUSTMENTS = {\n /** Slightly shorter torso relative to Western proportions */\n TORSO_ADJUSTMENT: 0.98,\n /** Proportionally longer legs for height */\n LEG_ADJUSTMENT: 1.02,\n /** Narrower shoulder width relative to height */\n SHOULDER_ADJUSTMENT: 0.97,\n /** More compact hip structure */\n HIP_ADJUSTMENT: 0.95,\n /** Flatter chest profile */\n CHEST_DEPTH_ADJUSTMENT: 0.92,\n} as const;\n\n// ============================================================================\n// CLOTHING RENDERING CONSTANTS (의복 렌더링)\n// ============================================================================\n\n/**\n * Base clothing offset from body surface.\n *\n * Gap between body and clothing geometry for natural appearance.\n *\n * @korean 의복기본오프셋\n */\nexport const CLOTHING_OFFSET = 0.01; // 1cm gap\n\n/**\n * Clothing thickness (affects layering).\n *\n * @korean 의복두께\n */\nexport const CLOTHING_THICKNESS = 0.005; // 5mm\n\n/**\n * Clothing drape factor for loose garments.\n *\n * @korean 의복드레이프계수\n */\nexport const CLOTHING_DRAPE_FACTOR = 1.1;\n\n// ============================================================================\n// FOOT RENDERING CONSTANTS (발 렌더링)\n// ============================================================================\n\n/**\n * Base foot length in meters (for average 180cm person).\n *\n * @korean 기본발길이\n */\nexport const BASE_FOOT_LENGTH = 0.26; // 26cm\n\n/**\n * Foot width ratio relative to length.\n *\n * @korean 발너비비율\n */\nexport const FOOT_WIDTH_RATIO = 0.385; // ~10cm for 26cm foot\n\n/**\n * Foot height ratio at ankle.\n *\n * @korean 발높이비율\n */\nexport const FOOT_HEIGHT_RATIO = 0.308; // ~8cm for 26cm foot\n\n// ============================================================================\n// HAND RENDERING CONSTANTS (손 렌더링)\n// ============================================================================\n\n/**\n * Base palm width in meters.\n *\n * @korean 기본손바닥너비\n */\nexport const BASE_PALM_WIDTH = 0.085; // 8.5cm\n\n/**\n * Palm length as ratio of palm width.\n *\n * @korean 손바닥길이비율\n */\nexport const PALM_LENGTH_RATIO = 1.12; // ~9.5cm\n\n/**\n * Palm thickness as ratio of width.\n *\n * @korean 손바닥두께비율\n */\nexport const PALM_THICKNESS_RATIO = 0.294; // ~2.5cm\n\n// ============================================================================\n// HELPER FUNCTIONS (도우미 함수)\n// ============================================================================\n\n/**\n * Calculate bone thickness multiplier from physical attributes.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Thickness multiplier (0.85 - 1.3)\n * @korean 뼈두께배수계산\n */\nexport function calculateBoneThickness(\n muscleMass: number,\n fatMass: number,\n): number {\n const muscleRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const fatRatio = fatMass / REFERENCE_FAT_MASS;\n\n const muscleContribution =\n Math.sqrt(muscleRatio) * MUSCLE_THICKNESS_CONTRIBUTION;\n const fatContribution = Math.sqrt(fatRatio) * FAT_THICKNESS_CONTRIBUTION;\n\n const rawMultiplier = muscleContribution + fatContribution;\n const deviation = rawMultiplier - 1.0;\n const amplified = 1.0 + deviation * BONE_THICKNESS_AMPLIFICATION;\n\n return Math.max(\n MIN_BONE_THICKNESS_MULTIPLIER,\n Math.min(MAX_BONE_THICKNESS_MULTIPLIER, amplified),\n );\n}\n\n/**\n * Calculate muscle scale factor from muscle mass.\n *\n * @param muscleMass - Muscle mass in kg\n * @returns Scale factor (0.5 - 2.0+)\n * @korean 근육크기계산\n */\nexport function calculateMuscleScale(muscleMass: number): number {\n const massRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const deviation = massRatio - 1.0;\n\n const exponentialDeviation =\n Math.sign(deviation) *\n Math.pow(Math.abs(deviation), MUSCLE_AMPLIFICATION_EXPONENT);\n\n return Math.max(\n MIN_MUSCLE_SCALE,\n 1.0 + exponentialDeviation * MUSCLE_AMPLIFICATION_BASE,\n );\n}\n\n/**\n * Apply visual amplification to a scaling factor.\n *\n * @param rawFactor - Raw scaling factor (1.0 = reference)\n * @returns Amplified scaling factor\n * @korean 시각적증폭적용\n */\nexport function amplifyScaling(rawFactor: number): number {\n const deviation = rawFactor - 1.0;\n return 1.0 + deviation * VISUAL_AMPLIFICATION_FACTOR;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAa,yBAAyB;;;;;;;;;AAUtC,IAAa,gCAAgC;;;;;;;;AAS7C,IAAa,gCAAgC;;;;;;;;;AAU7C,IAAa,+BAA+B;;;;;;AAqB5C,IAAa,gCAAgC;;;;;;AAO7C,IAAa,6BAA6B;;;;;;;;;;AA6B1C,IAAa,4BAA4B;;;;;;;;;AAoBzC,IAAa,mBAAmB;;;;;;;;AAkDhC,IAAa,uBAAuB;CAClC,QAAQ;CACR,aAAa;CACb,WAAW;CACX,WAAW;CACX,aAAa;CACb,UAAU;CACV,YAAY;CACZ,eAAe;CACf,YAAY;CACZ,SAAS;CACT,KAAK;CACL,WAAW;CACX,UAAU;CACV,cAAc;CACf;;;;;;;;;AA8ID,SAAgB,uBACd,YACA,SACQ;CACR,MAAM,cAAc,aAAA;CACpB,MAAM,WAAW,UAAA;CAQjB,MAAM,YAAY,KALhB,KAAK,KAAK,YAAY,GAAG,gCACH,KAAK,KAAK,SAAS,GAAG,6BAGZ,KACE;CAEpC,OAAO,KAAK,IACV,+BACA,KAAK,IAAI,+BAA+B,UAAU,CACnD"}
|