blacktrigram 0.7.39 → 0.7.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +5 -5
package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoundAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundAnnouncement Component - Displays round completion and transition UI\n *\n * Korean: 라운드 발표 (Round Announcement)\n *\n * Shows round winner, current score, statistics, and countdown to next round.\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/RoundAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Round statistics displayed between rounds\n *\n * Korean: 라운드 통계 (Round Statistics)\n */\nexport interface RoundStats {\n /** Total damage dealt by player */\n readonly damageDealt: number;\n /** Number of hits successfully landed */\n readonly hitsLanded: number;\n /** Number of vital points struck */\n readonly vitalPointsHit: number;\n /** Combat accuracy percentage */\n readonly accuracy: number;\n}\n\n/**\n * Props for the RoundAnnouncement component\n */\nexport interface RoundAnnouncementProps {\n /** Current round number (1-based) */\n readonly roundNumber: number;\n /** Round winner, or null if round was a draw */\n readonly roundWinner: PlayerState | null;\n /** Current match score (player1 wins, player2 wins) */\n readonly currentScore: { readonly player1: number; readonly player2: number };\n /** Round statistics to display */\n readonly roundStats?: RoundStats;\n /** Callback when countdown completes */\n readonly onCountdownComplete: () => void;\n /** Callback when skip button is pressed */\n readonly onSkip: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Total number of rounds in match (for match point detection) */\n readonly totalRounds?: number;\n /** Countdown duration in seconds */\n readonly countdownDuration?: number;\n}\n\n/**\n * RoundAnnouncement Component\n *\n * Displays round completion announcement with:\n * - Bilingual round completion title\n * - Round winner display\n * - Current match score\n * - Round statistics (damage, hits, accuracy)\n * - Countdown to next round\n * - Skip button for quick play\n * - Match point indicator for final rounds\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 종료 발표 컴포넌트\n */\nexport const RoundAnnouncement: React.FC<RoundAnnouncementProps> = ({\n roundNumber,\n roundWinner,\n currentScore,\n roundStats,\n onCountdownComplete,\n onSkip,\n isMobile,\n totalRounds = 3,\n countdownDuration = 3,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"large\", isMobile });\n const [countdown, setCountdown] = useState(countdownDuration);\n const [isVisible, setIsVisible] = useState(false);\n const hasCompletedRef = useRef(false);\n\n // Use ref to stabilize callback - prevents timer reset on re-renders\n const onCountdownCompleteRef = useRef(onCountdownComplete);\n useEffect(() => {\n onCountdownCompleteRef.current = onCountdownComplete;\n }, [onCountdownComplete]);\n\n // Stable callback that reads from ref\n const handleCountdownComplete = useCallback(() => {\n onCountdownCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n // Trigger fade in after a small delay\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Countdown logic with proper cleanup - uses stable callback\n useEffect(() => {\n if (countdown <= 0) {\n if (!hasCompletedRef.current) {\n hasCompletedRef.current = true;\n handleCountdownComplete();\n }\n return;\n }\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n // Countdown complete, callback will be handled by effect\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [countdown, handleCountdownComplete]);\n\n // Determine if this is match point\n const isMatchPoint = useMemo(() => {\n const maxScore = Math.max(currentScore.player1, currentScore.player2);\n const roundsToWin = Math.ceil(totalRounds / 2);\n return maxScore === roundsToWin - 1;\n }, [currentScore, totalRounds]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(\n () => hexColorToCSS(theme.colors.ACCENT_GOLD),\n [theme.colors.ACCENT_GOLD]\n );\n const cyanColor = useMemo(\n () => hexColorToCSS(theme.colors.PRIMARY_CYAN),\n [theme.colors.PRIMARY_CYAN]\n );\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n const textSecondary = useMemo(\n () => hexColorToCSS(theme.colors.TEXT_SECONDARY),\n [theme.colors.TEXT_SECONDARY]\n );\n\n // Memoize container style for performance\n const containerStyle = useMemo(\n () => ({\n position: \"fixed\" as const,\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\" as const,\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: `${darkBg}dd`,\n zIndex: 1000,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n }),\n [darkBg, isVisible]\n );\n\n return (\n <div data-testid=\"round-announcement\" style={containerStyle}>\n {/* Match Point Indicator */}\n {isMatchPoint && (\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"10px\" : \"20px\",\n textShadow: `0 0 20px ${goldColor}`,\n animation: \"pulse 1s infinite\",\n }}\n data-testid=\"match-point-indicator\"\n >\n 매치 포인트! | Match Point!\n </div>\n )}\n\n {/* Round Complete Title */}\n <h1\n style={{\n fontSize: isMobile ? \"36px\" : \"56px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textShadow: `0 0 30px ${goldColor}`,\n textAlign: \"center\",\n }}\n data-testid=\"round-complete-title\"\n >\n 라운드 {roundNumber} 완료!\n <br />\n Round {roundNumber} Complete!\n </h1>\n\n {/* Round Winner */}\n {roundWinner && (\n <div\n style={{\n fontSize: isMobile ? \"24px\" : \"36px\",\n color: cyanColor,\n fontFamily: theme.fontFamily.KOREAN,\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textAlign: \"center\",\n }}\n data-testid=\"round-winner\"\n >\n 승자 | Winner: {roundWinner.name.korean} | {roundWinner.name.english}\n </div>\n )}\n\n {/* Current Score */}\n <div\n style={{\n display: \"flex\",\n gap: isMobile ? \"40px\" : \"80px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"32px\" : \"48px\",\n fontWeight: \"bold\",\n }}\n data-testid=\"current-score\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 1\n </div>\n <div style={{ color: goldColor }}>{currentScore.player1}</div>\n </div>\n <div style={{ color: cyanColor }}>-</div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 2\n </div>\n <div style={{ color: goldColor }}>{currentScore.player2}</div>\n </div>\n </div>\n\n {/* Round Statistics */}\n {roundStats && (\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: isMobile ? \"12px\" : \"24px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: textSecondary,\n fontFamily: theme.fontFamily.KOREAN,\n }}\n data-testid=\"round-stats\"\n >\n <div>피해량 | Damage: {roundStats.damageDealt.toFixed(0)}</div>\n <div>명중 | Hits: {roundStats.hitsLanded}</div>\n <div>급소타격 | Vital Points: {roundStats.vitalPointsHit}</div>\n <div>정확도 | Accuracy: {roundStats.accuracy.toFixed(1)}%</div>\n </div>\n )}\n\n {/* Countdown */}\n <div\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: goldColor,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n textShadow: `0 0 40px ${goldColor}`,\n }}\n data-testid=\"countdown-display\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Countdown: ${countdown} seconds remaining`}\n >\n {countdown}\n </div>\n\n {/* Skip Button */}\n <button\n onClick={onSkip}\n data-testid=\"skip-countdown-button\"\n className=\"skip-countdown-button\"\n style={{\n padding: isMobile ? \"10px 24px\" : \"12px 32px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n backgroundColor: cyanColor,\n color: darkBg,\n border: \"none\",\n borderRadius: \"6px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n }}\n >\n 건너뛰기 | Skip\n </button>\n\n {/* CSS Animation for pulse effect and button hover */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.05);\n }\n }\n \n .skip-countdown-button {\n transition: all 0.2s ease;\n }\n \n .skip-countdown-button:hover {\n transform: scale(1.05);\n box-shadow: 0 0 20px ${cyanColor};\n }\n \n .skip-countdown-button:focus {\n outline: 2px solid ${cyanColor};\n outline-offset: 2px;\n }\n `}\n </style>\n </div>\n );\n};\n\nexport default RoundAnnouncement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,IAAa,qBAAuD,EAClE,aACA,aACA,cACA,YACA,qBACA,QACA,UACA,cAAc,GACd,oBAAoB,QAChB;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS;EAAU,CAAC;CAC7E,MAAM,CAAC,WAAW,gBAAgB,SAAS,kBAAkB;CAC7D,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,kBAAkB,OAAO,MAAM;CAGrC,MAAM,yBAAyB,OAAO,oBAAoB;AAC1D,iBAAgB;AACd,yBAAuB,UAAU;IAChC,CAAC,oBAAoB,CAAC;CAGzB,MAAM,0BAA0B,kBAAkB;AAChD,yBAAuB,SAAS;IAC/B,EAAE,CAAC;AAGN,iBAAgB;EAEd,MAAM,QAAQ,iBAAiB,aAAa,KAAK,EAAE,GAAG;AACtD,eAAa,aAAa,MAAM;IAC/B,EAAE,CAAC;AAGN,iBAAgB;AACd,MAAI,aAAa,GAAG;AAClB,OAAI,CAAC,gBAAgB,SAAS;AAC5B,oBAAgB,UAAU;AAC1B,6BAAyB;;AAE3B;;EAGF,MAAM,QAAQ,kBAAkB;AAC9B,iBAAc,SAAS;AACrB,QAAI,QAAQ,EAEV,QAAO;AAET,WAAO,OAAO;KACd;KACD,IAAK;AAER,eAAa,cAAc,MAAM;IAChC,CAAC,WAAW,wBAAwB,CAAC;CAGxC,MAAM,eAAe,cAAc;AAGjC,SAFiB,KAAK,IAAI,aAAa,SAAS,aAAa,QAEtD,KADa,KAAK,KAAK,cAAc,EACxB,GAAc;IACjC,CAAC,cAAc,YAAY,CAAC;CAG/B,MAAM,YAAY,cACV,cAAc,MAAM,OAAO,YAAY,EAC7C,CAAC,MAAM,OAAO,YAAY,CAC3B;CACD,MAAM,YAAY,cACV,cAAc,MAAM,OAAO,aAAa,EAC9C,CAAC,MAAM,OAAO,aAAa,CAC5B;CACD,MAAM,SAAS,cACP,cAAc,MAAM,OAAO,mBAAmB,EACpD,CAAC,MAAM,OAAO,mBAAmB,CAClC;CACD,MAAM,gBAAgB,cACd,cAAc,MAAM,OAAO,eAAe,EAChD,CAAC,MAAM,OAAO,eAAe,CAC9B;AAsBD,QACE,qBAAC,OAAD;EAAK,eAAY;EAAqB,OApBjB,eACd;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB,iBAAiB,GAAG,OAAO;GAC3B,QAAQ;GACR,SAAS,YAAY,IAAI;GACzB,YAAY;GACb,GACD,CAAC,QAAQ,UAAU,CAI0B;YAA7C;GAEG,gBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,cAAc,WAAW,SAAS;KAClC,YAAY,YAAY;KACxB,WAAW;KACZ;IACD,eAAY;cACb;IAEK,CAAA;GAIR,qBAAC,MAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,QAAQ,OAAO,WAAW,SAAS,OAAO;KAC1C,YAAY,YAAY;KACxB,WAAW;KACZ;IACD,eAAY;cAVd;KAWC;KACM;KAAY;KACjB,oBAAC,MAAD,EAAM,CAAA;;KACC;KAAY;KAChB;;GAGJ,eACC,qBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,QAAQ,OAAO,WAAW,SAAS,OAAO;KAC1C,WAAW;KACZ;IACD,eAAY;cARd;KASC;KACe,YAAY,KAAK;KAAO;KAAI,YAAY,KAAK;KACvD;;GAIR,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,KAAK,WAAW,SAAS;KACzB,cAAc,WAAW,SAAS;KAClC,UAAU,WAAW,SAAS;KAC9B,YAAY;KACb;IACD,eAAY;cARd;KAUE,qBAAC,OAAD;MAAK,OAAO,EAAE,WAAW,UAAU;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL,UAAU,WAAW,SAAS;QAC9B,OAAO;QACP,cAAc;QACf;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OAAK,OAAO,EAAE,OAAO,WAAW;iBAAG,aAAa;OAAc,CAAA,CAC1D;;KACN,oBAAC,OAAD;MAAK,OAAO,EAAE,OAAO,WAAW;gBAAE;MAAO,CAAA;KACzC,qBAAC,OAAD;MAAK,OAAO,EAAE,WAAW,UAAU;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL,UAAU,WAAW,SAAS;QAC9B,OAAO;QACP,cAAc;QACf;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OAAK,OAAO,EAAE,OAAO,WAAW;iBAAG,aAAa;OAAc,CAAA,CAC1D;;KACF;;GAGL,cACC,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe,WAAW,WAAW;KACrC,KAAK,WAAW,SAAS;KACzB,cAAc,WAAW,SAAS;KAClC,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC9B;IACD,eAAY;cAVd;KAYE,qBAAC,OAAD,EAAA,UAAA,CAAK,kBAAe,WAAW,YAAY,QAAQ,EAAE,CAAO,EAAA,CAAA;KAC5D,qBAAC,OAAD,EAAA,UAAA,CAAK,eAAY,WAAW,WAAiB,EAAA,CAAA;KAC7C,qBAAC,OAAD,EAAA,UAAA,CAAK,yBAAsB,WAAW,eAAqB,EAAA,CAAA;KAC3D,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAiB,WAAW,SAAS,QAAQ,EAAE;MAAC;MAAO,EAAA,CAAA;KACxD;;GAIR,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY;KACZ,cAAc,WAAW,SAAS;KAClC,YAAY,YAAY;KACzB;IACD,eAAY;IACZ,aAAU;IACV,eAAY;IACZ,cAAY,cAAc,UAAU;cAEnC;IACG,CAAA;GAGN,oBAAC,UAAD;IACE,SAAS;IACT,eAAY;IACZ,WAAU;IACV,OAAO;KACL,SAAS,WAAW,cAAc;KAClC,UAAU,WAAW,SAAS;KAC9B,iBAAiB;KACjB,OAAO;KACP,QAAQ;KACR,cAAc;KACd,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,QAAQ;KACT;cACF;IAEQ,CAAA;GAGT,oBAAC,SAAD,EAAA,UACG;;;;;;;;;;;;;;;;;;mCAkB0B,UAAU;;;;iCAIZ,UAAU;;;WAI7B,CAAA;GACJ"}
|
|
1
|
+
{"version":3,"file":"RoundAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundAnnouncement Component - Displays round completion and transition UI\n *\n * Korean: 라운드 발표 (Round Announcement)\n *\n * Shows round winner, current score, statistics, and countdown to next round.\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/RoundAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Round statistics displayed between rounds\n *\n * Korean: 라운드 통계 (Round Statistics)\n */\nexport interface RoundStats {\n /** Total damage dealt by player */\n readonly damageDealt: number;\n /** Number of hits successfully landed */\n readonly hitsLanded: number;\n /** Number of vital points struck */\n readonly vitalPointsHit: number;\n /** Combat accuracy percentage */\n readonly accuracy: number;\n}\n\n/**\n * Props for the RoundAnnouncement component\n */\nexport interface RoundAnnouncementProps {\n /** Current round number (1-based) */\n readonly roundNumber: number;\n /** Round winner, or null if round was a draw */\n readonly roundWinner: PlayerState | null;\n /** Current match score (player1 wins, player2 wins) */\n readonly currentScore: { readonly player1: number; readonly player2: number };\n /** Round statistics to display */\n readonly roundStats?: RoundStats;\n /** Callback when countdown completes */\n readonly onCountdownComplete: () => void;\n /** Callback when skip button is pressed */\n readonly onSkip: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Total number of rounds in match (for match point detection) */\n readonly totalRounds?: number;\n /** Countdown duration in seconds */\n readonly countdownDuration?: number;\n}\n\n/**\n * RoundAnnouncement Component\n *\n * Displays round completion announcement with:\n * - Bilingual round completion title\n * - Round winner display\n * - Current match score\n * - Round statistics (damage, hits, accuracy)\n * - Countdown to next round\n * - Skip button for quick play\n * - Match point indicator for final rounds\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 종료 발표 컴포넌트\n */\nexport const RoundAnnouncement: React.FC<RoundAnnouncementProps> = ({\n roundNumber,\n roundWinner,\n currentScore,\n roundStats,\n onCountdownComplete,\n onSkip,\n isMobile,\n totalRounds = 3,\n countdownDuration = 3,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"large\", isMobile });\n const [countdown, setCountdown] = useState(countdownDuration);\n const [isVisible, setIsVisible] = useState(false);\n const hasCompletedRef = useRef(false);\n\n // Use ref to stabilize callback - prevents timer reset on re-renders\n const onCountdownCompleteRef = useRef(onCountdownComplete);\n useEffect(() => {\n onCountdownCompleteRef.current = onCountdownComplete;\n }, [onCountdownComplete]);\n\n // Stable callback that reads from ref\n const handleCountdownComplete = useCallback(() => {\n onCountdownCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n // Trigger fade in after a small delay\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Countdown logic with proper cleanup - uses stable callback\n useEffect(() => {\n if (countdown <= 0) {\n if (!hasCompletedRef.current) {\n hasCompletedRef.current = true;\n handleCountdownComplete();\n }\n return;\n }\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n // Countdown complete, callback will be handled by effect\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [countdown, handleCountdownComplete]);\n\n // Determine if this is match point\n const isMatchPoint = useMemo(() => {\n const maxScore = Math.max(currentScore.player1, currentScore.player2);\n const roundsToWin = Math.ceil(totalRounds / 2);\n return maxScore === roundsToWin - 1;\n }, [currentScore, totalRounds]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(\n () => hexColorToCSS(theme.colors.ACCENT_GOLD),\n [theme.colors.ACCENT_GOLD]\n );\n const cyanColor = useMemo(\n () => hexColorToCSS(theme.colors.PRIMARY_CYAN),\n [theme.colors.PRIMARY_CYAN]\n );\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n const textSecondary = useMemo(\n () => hexColorToCSS(theme.colors.TEXT_SECONDARY),\n [theme.colors.TEXT_SECONDARY]\n );\n\n // Memoize container style for performance\n const containerStyle = useMemo(\n () => ({\n position: \"fixed\" as const,\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\" as const,\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: `${darkBg}dd`,\n zIndex: 1000,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n }),\n [darkBg, isVisible]\n );\n\n return (\n <div data-testid=\"round-announcement\" style={containerStyle}>\n {/* Match Point Indicator */}\n {isMatchPoint && (\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"10px\" : \"20px\",\n textShadow: `0 0 20px ${goldColor}`,\n animation: \"pulse 1s infinite\",\n }}\n data-testid=\"match-point-indicator\"\n >\n 매치 포인트! | Match Point!\n </div>\n )}\n\n {/* Round Complete Title */}\n <h1\n style={{\n fontSize: isMobile ? \"36px\" : \"56px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textShadow: `0 0 30px ${goldColor}`,\n textAlign: \"center\",\n }}\n data-testid=\"round-complete-title\"\n >\n 라운드 {roundNumber} 완료!\n <br />\n Round {roundNumber} Complete!\n </h1>\n\n {/* Round Winner */}\n {roundWinner && (\n <div\n style={{\n fontSize: isMobile ? \"24px\" : \"36px\",\n color: cyanColor,\n fontFamily: theme.fontFamily.KOREAN,\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textAlign: \"center\",\n }}\n data-testid=\"round-winner\"\n >\n 승자 | Winner: {roundWinner.name.korean} | {roundWinner.name.english}\n </div>\n )}\n\n {/* Current Score */}\n <div\n style={{\n display: \"flex\",\n gap: isMobile ? \"40px\" : \"80px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"32px\" : \"48px\",\n fontWeight: \"bold\",\n }}\n data-testid=\"current-score\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 1\n </div>\n <div style={{ color: goldColor }}>{currentScore.player1}</div>\n </div>\n <div style={{ color: cyanColor }}>-</div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 2\n </div>\n <div style={{ color: goldColor }}>{currentScore.player2}</div>\n </div>\n </div>\n\n {/* Round Statistics */}\n {roundStats && (\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: isMobile ? \"12px\" : \"24px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: textSecondary,\n fontFamily: theme.fontFamily.KOREAN,\n }}\n data-testid=\"round-stats\"\n >\n <div>피해량 | Damage: {roundStats.damageDealt.toFixed(0)}</div>\n <div>명중 | Hits: {roundStats.hitsLanded}</div>\n <div>급소타격 | Vital Points: {roundStats.vitalPointsHit}</div>\n <div>정확도 | Accuracy: {roundStats.accuracy.toFixed(1)}%</div>\n </div>\n )}\n\n {/* Countdown */}\n <div\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: goldColor,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n textShadow: `0 0 40px ${goldColor}`,\n }}\n data-testid=\"countdown-display\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Countdown: ${countdown} seconds remaining`}\n >\n {countdown}\n </div>\n\n {/* Skip Button */}\n <button\n onClick={onSkip}\n data-testid=\"skip-countdown-button\"\n className=\"skip-countdown-button\"\n style={{\n padding: isMobile ? \"10px 24px\" : \"12px 32px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n backgroundColor: cyanColor,\n color: darkBg,\n border: \"none\",\n borderRadius: \"6px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n }}\n >\n 건너뛰기 | Skip\n </button>\n\n {/* CSS Animation for pulse effect and button hover */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.05);\n }\n }\n \n .skip-countdown-button {\n transition: all 0.2s ease;\n }\n \n .skip-countdown-button:hover {\n transform: scale(1.05);\n box-shadow: 0 0 20px ${cyanColor};\n }\n \n .skip-countdown-button:focus {\n outline: 2px solid ${cyanColor};\n outline-offset: 2px;\n }\n `}\n </style>\n </div>\n );\n};\n\nexport default RoundAnnouncement;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoundDisplayStatus.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundDisplayStatus.tsx"],"sourcesContent":["/**\n * RoundDisplayStatus - Brief round status messages\n * \n * Displays quick status messages like \"라운드 시작!\", \"전투!\", \"라운드 종료\", \"K.O.!\"\n * Rendered as a fullscreen overlay outside the Canvas.\n * \n * @korean 라운드 상태 표시 - 간단한 라운드 상태 메시지\n */\n\nimport React from \"react\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport type RoundDisplayStatus = \"start\" | \"fight\" | \"end\" | \"ko\" | null;\n\nexport interface RoundDisplayStatusProps {\n /** Current round status to display */\n readonly status: RoundDisplayStatus;\n /** Whether mobile layout is active */\n readonly isMobile?: boolean;\n}\n\n/**\n * RoundDisplayStatus Component\n * \n * Large centered text overlay showing brief round status messages.\n * Auto-fades based on combatState.roundDisplayStatus timing.\n */\nexport const RoundDisplayStatus: React.FC<RoundDisplayStatusProps> = ({\n status,\n isMobile = false,\n}) => {\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n if (!status) {\n return null;\n }\n\n const statusMessages: Record<Exclude<RoundDisplayStatus, null>, string> = {\n start: \"라운드 시작!\",\n fight: \"전투!\",\n end: \"라운드 종료\",\n ko: \"K.O.!\",\n };\n\n const fontSize = isMobile ? \"48px\" : \"72px\";\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"round-display-status\"\n >\n <div\n style={{\n fontSize,\n fontWeight: \"bold\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n textAlign: \"center\",\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, 1),\n textShadow: \"0 0 20px rgba(255, 215, 0, 0.8)\",\n }}\n data-testid={`round-display-status-${status}`}\n >\n {statusMessages[status]}\n </div>\n </div>\n );\n};\n\nexport default RoundDisplayStatus;\n"],"mappings":";;;;;;;;;;;;AA6BA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CACJ,MAAM,QAAQ,eAAe;EAC3B,SAAS;EACT,MAAM;EACN;EACD,CAAC;
|
|
1
|
+
{"version":3,"file":"RoundDisplayStatus.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundDisplayStatus.tsx"],"sourcesContent":["/**\n * RoundDisplayStatus - Brief round status messages\n * \n * Displays quick status messages like \"라운드 시작!\", \"전투!\", \"라운드 종료\", \"K.O.!\"\n * Rendered as a fullscreen overlay outside the Canvas.\n * \n * @korean 라운드 상태 표시 - 간단한 라운드 상태 메시지\n */\n\nimport React from \"react\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport type RoundDisplayStatus = \"start\" | \"fight\" | \"end\" | \"ko\" | null;\n\nexport interface RoundDisplayStatusProps {\n /** Current round status to display */\n readonly status: RoundDisplayStatus;\n /** Whether mobile layout is active */\n readonly isMobile?: boolean;\n}\n\n/**\n * RoundDisplayStatus Component\n * \n * Large centered text overlay showing brief round status messages.\n * Auto-fades based on combatState.roundDisplayStatus timing.\n */\nexport const RoundDisplayStatus: React.FC<RoundDisplayStatusProps> = ({\n status,\n isMobile = false,\n}) => {\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n if (!status) {\n return null;\n }\n\n const statusMessages: Record<Exclude<RoundDisplayStatus, null>, string> = {\n start: \"라운드 시작!\",\n fight: \"전투!\",\n end: \"라운드 종료\",\n ko: \"K.O.!\",\n };\n\n const fontSize = isMobile ? \"48px\" : \"72px\";\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"round-display-status\"\n >\n <div\n style={{\n fontSize,\n fontWeight: \"bold\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n textAlign: \"center\",\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, 1),\n textShadow: \"0 0 20px rgba(255, 215, 0, 0.8)\",\n }}\n data-testid={`round-display-status-${status}`}\n >\n {statusMessages[status]}\n </div>\n </div>\n );\n};\n\nexport default RoundDisplayStatus;\n"],"mappings":";;;;;;;;;;;;AA6BA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CACJ,MAAM,QAAQ,eAAe;EAC3B,SAAS;EACT,MAAM;EACN;EACD,CAAC;CAEF,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,iBAAoE;EACxE,OAAO;EACP,OAAO;EACP,KAAK;EACL,IAAI;EACL;CAED,MAAM,WAAW,WAAW,SAAS;CAErC,OACE,oBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,gBAAgB;GAChB,YAAY;GACZ,eAAe;GACf,QAAQ,QAAQ;GACjB;EACD,eAAY;YAEZ,oBAAC,OAAD;GACE,OAAO;IACL;IACA,YAAY;IACZ,YAAY,MAAM,iBAAiB;IACnC,YAAY,MAAM,iBAAiB;IACnC,eAAe,MAAM,iBAAiB;IACtC,WAAW,MAAM,iBAAiB;IAClC,WAAW;IACX,OAAO,gBAAgB,MAAM,OAAO,aAAa,EAAE;IACnD,YAAY;IACb;GACD,eAAa,wBAAwB;aAEpC,eAAe;GACZ,CAAA;EACF,CAAA"}
|
package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoundStartAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundStartAnnouncement Component - Displays \"Round X Begin!\" announcement\n *\n * Korean: 라운드 시작 발표 (Round Start Announcement)\n *\n * Shows \"Round X Begin!\" for subsequent rounds (not the first round).\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme hook for consistent styling.\n *\n * @module components/combat/RoundStartAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../../../audio/AudioProvider\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Props for the RoundStartAnnouncement component\n */\nexport interface RoundStartAnnouncementProps {\n /** Round number (1-based) */\n readonly roundNumber: number;\n /** Duration to display announcement in seconds */\n readonly duration?: number;\n /** Callback when announcement completes */\n readonly onComplete: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n}\n\n/**\n * RoundStartAnnouncement Component\n *\n * Displays \"Round X Begin!\" announcement with:\n * - Bilingual round number and \"Begin!\" text\n * - Flash/pulse animation for impact\n * - Auto-dismiss after configured duration\n * - Audio cue for round start\n * - Responsive sizing for mobile/tablet/desktop\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 시작 발표 컴포넌트\n */\nexport const RoundStartAnnouncement: React.FC<RoundStartAnnouncementProps> = ({\n roundNumber,\n duration = 2,\n onComplete,\n isMobile,\n}) => {\n const audio = useAudio();\n const theme = useKoreanTheme({ variant: \"primary\", size: \"xlarge\", isMobile });\n const [isVisible, setIsVisible] = useState(false);\n\n // Use ref to stabilize onComplete callback - prevents timer reset on re-renders\n // This is critical because parent component may recreate onComplete due to state changes\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n // Stable callback that reads from ref\n const handleComplete = useCallback(() => {\n onCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Play audio cue on mount\n useEffect(() => {\n if (audio.isAudioReady) {\n audio.playSFX(\"attack_medium\"); // Using placeholder - will be round_start\n }\n }, [audio]);\n\n // Auto-dismiss after duration - uses stable handleComplete to prevent timer resets\n useEffect(() => {\n let isMounted = true;\n let innerTimer: ReturnType<typeof setTimeout> | null = null;\n\n const outerTimer = setTimeout(() => {\n setIsVisible(false);\n innerTimer = setTimeout(() => {\n if (isMounted) {\n handleComplete();\n }\n }, 300); // Wait for fade out\n }, duration * 1000);\n\n return () => {\n isMounted = false;\n clearTimeout(outerTimer);\n if (innerTimer) {\n clearTimeout(innerTimer);\n }\n };\n }, [duration, handleComplete]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(() => hexColorToCSS(theme.colors.ACCENT_GOLD), [theme.colors.ACCENT_GOLD]);\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n\n return (\n <>\n <div\n data-testid=\"round-start-announcement\"\n role=\"alert\"\n aria-live=\"assertive\"\n aria-label={`Round ${roundNumber} starting`}\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: `${darkBg}88`,\n zIndex: 900,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"56px\" : \"96px\",\n fontWeight: \"bold\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n textShadow: `0 0 40px ${goldColor}`,\n animation: \"roundStartFlash 0.5s ease-out\",\n textAlign: \"center\",\n userSelect: \"none\",\n }}\n data-testid=\"round-start-text\"\n >\n 라운드 {roundNumber} 시작!\n <br />\n <span\n style={{\n fontSize: isMobile ? \"40px\" : \"64px\",\n }}\n >\n Round {roundNumber} Begin!\n </span>\n </div>\n </div>\n\n {/* CSS Animation */}\n <style>\n {`\n @keyframes roundStartFlash {\n 0% {\n opacity: 0;\n transform: scale(1.5);\n }\n 30% {\n opacity: 1;\n transform: scale(1.2);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n }\n `}\n </style>\n </>\n );\n};\n\nexport default RoundStartAnnouncement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,0BAAiE,EAC5E,aACA,WAAW,GACX,YACA,eACI;CACJ,MAAM,QAAQ,UAAU;CACxB,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAU;EAAU,CAAC;CAC9E,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CAIjD,MAAM,gBAAgB,OAAO,WAAW;
|
|
1
|
+
{"version":3,"file":"RoundStartAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundStartAnnouncement Component - Displays \"Round X Begin!\" announcement\n *\n * Korean: 라운드 시작 발표 (Round Start Announcement)\n *\n * Shows \"Round X Begin!\" for subsequent rounds (not the first round).\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme hook for consistent styling.\n *\n * @module components/combat/RoundStartAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../../../audio/AudioProvider\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Props for the RoundStartAnnouncement component\n */\nexport interface RoundStartAnnouncementProps {\n /** Round number (1-based) */\n readonly roundNumber: number;\n /** Duration to display announcement in seconds */\n readonly duration?: number;\n /** Callback when announcement completes */\n readonly onComplete: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n}\n\n/**\n * RoundStartAnnouncement Component\n *\n * Displays \"Round X Begin!\" announcement with:\n * - Bilingual round number and \"Begin!\" text\n * - Flash/pulse animation for impact\n * - Auto-dismiss after configured duration\n * - Audio cue for round start\n * - Responsive sizing for mobile/tablet/desktop\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 시작 발표 컴포넌트\n */\nexport const RoundStartAnnouncement: React.FC<RoundStartAnnouncementProps> = ({\n roundNumber,\n duration = 2,\n onComplete,\n isMobile,\n}) => {\n const audio = useAudio();\n const theme = useKoreanTheme({ variant: \"primary\", size: \"xlarge\", isMobile });\n const [isVisible, setIsVisible] = useState(false);\n\n // Use ref to stabilize onComplete callback - prevents timer reset on re-renders\n // This is critical because parent component may recreate onComplete due to state changes\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n // Stable callback that reads from ref\n const handleComplete = useCallback(() => {\n onCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Play audio cue on mount\n useEffect(() => {\n if (audio.isAudioReady) {\n audio.playSFX(\"attack_medium\"); // Using placeholder - will be round_start\n }\n }, [audio]);\n\n // Auto-dismiss after duration - uses stable handleComplete to prevent timer resets\n useEffect(() => {\n let isMounted = true;\n let innerTimer: ReturnType<typeof setTimeout> | null = null;\n\n const outerTimer = setTimeout(() => {\n setIsVisible(false);\n innerTimer = setTimeout(() => {\n if (isMounted) {\n handleComplete();\n }\n }, 300); // Wait for fade out\n }, duration * 1000);\n\n return () => {\n isMounted = false;\n clearTimeout(outerTimer);\n if (innerTimer) {\n clearTimeout(innerTimer);\n }\n };\n }, [duration, handleComplete]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(() => hexColorToCSS(theme.colors.ACCENT_GOLD), [theme.colors.ACCENT_GOLD]);\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n\n return (\n <>\n <div\n data-testid=\"round-start-announcement\"\n role=\"alert\"\n aria-live=\"assertive\"\n aria-label={`Round ${roundNumber} starting`}\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: `${darkBg}88`,\n zIndex: 900,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"56px\" : \"96px\",\n fontWeight: \"bold\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n textShadow: `0 0 40px ${goldColor}`,\n animation: \"roundStartFlash 0.5s ease-out\",\n textAlign: \"center\",\n userSelect: \"none\",\n }}\n data-testid=\"round-start-text\"\n >\n 라운드 {roundNumber} 시작!\n <br />\n <span\n style={{\n fontSize: isMobile ? \"40px\" : \"64px\",\n }}\n >\n Round {roundNumber} Begin!\n </span>\n </div>\n </div>\n\n {/* CSS Animation */}\n <style>\n {`\n @keyframes roundStartFlash {\n 0% {\n opacity: 0;\n transform: scale(1.5);\n }\n 30% {\n opacity: 1;\n transform: scale(1.2);\n }\n 100% {\n opacity: 1;\n transform: scale(1);\n }\n }\n `}\n </style>\n </>\n );\n};\n\nexport default RoundStartAnnouncement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,IAAa,0BAAiE,EAC5E,aACA,WAAW,GACX,YACA,eACI;CACJ,MAAM,QAAQ,UAAU;CACxB,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAU;EAAU,CAAC;CAC9E,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CAIjD,MAAM,gBAAgB,OAAO,WAAW;CACxC,gBAAgB;EACd,cAAc,UAAU;IACvB,CAAC,WAAW,CAAC;CAGhB,MAAM,iBAAiB,kBAAkB;EACvC,cAAc,SAAS;IACtB,EAAE,CAAC;CAGN,gBAAgB;EACd,MAAM,QAAQ,iBAAiB,aAAa,KAAK,EAAE,GAAG;EACtD,aAAa,aAAa,MAAM;IAC/B,EAAE,CAAC;CAGN,gBAAgB;EACd,IAAI,MAAM,cACR,MAAM,QAAQ,gBAAgB;IAE/B,CAAC,MAAM,CAAC;CAGX,gBAAgB;EACd,IAAI,YAAY;EAChB,IAAI,aAAmD;EAEvD,MAAM,aAAa,iBAAiB;GAClC,aAAa,MAAM;GACnB,aAAa,iBAAiB;IAC5B,IAAI,WACF,gBAAgB;MAEjB,IAAI;KACN,WAAW,IAAK;EAEnB,aAAa;GACX,YAAY;GACZ,aAAa,WAAW;GACxB,IAAI,YACF,aAAa,WAAW;;IAG3B,CAAC,UAAU,eAAe,CAAC;CAG9B,MAAM,YAAY,cAAc,cAAc,MAAM,OAAO,YAAY,EAAE,CAAC,MAAM,OAAO,YAAY,CAAC;CACpG,MAAM,SAAS,cACP,cAAc,MAAM,OAAO,mBAAmB,EACpD,CAAC,MAAM,OAAO,mBAAmB,CAClC;CAED,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EACE,eAAY;EACZ,MAAK;EACL,aAAU;EACV,cAAY,SAAS,YAAY;EACjC,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,iBAAiB,GAAG,OAAO;GAC3B,QAAQ;GACR,SAAS,YAAY,IAAI;GACzB,YAAY;GACZ,eAAe;GAChB;YAED,qBAAC,OAAD;GACE,OAAO;IACL,UAAU,WAAW,SAAS;IAC9B,YAAY;IACZ,OAAO;IACP,YAAY,MAAM,WAAW;IAC7B,YAAY,YAAY;IACxB,WAAW;IACX,WAAW;IACX,YAAY;IACb;GACD,eAAY;aAXd;IAYC;IACM;IAAY;IACjB,oBAAC,MAAD,EAAM,CAAA;IACN,qBAAC,QAAD;KACE,OAAO,EACL,UAAU,WAAW,SAAS,QAC/B;eAHH;MAIC;MACQ;MAAY;MACd;;IACH;;EACF,CAAA,EAGN,oBAAC,SAAD,EAAA,UACG;;;;;;;;;;;;;;;WAgBK,CAAA,CACP,EAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatBottomHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatBottomHUD.tsx"],"sourcesContent":["/**\n * CombatBottomHUD - Bottom bar for combat screen\n *\n * Contains:\n * - Technique Bar (centered)\n * - Volume Control (bottom-right, compact)\n * - Combat Messages (above technique bar)\n *\n * Gaming Layout Best Practice:\n * - Width: 100% of screen\n * - Height: Resolution-based ~10% of screen height (40-120px range)\n *\n * @korean 전투화면 하단 바 - 기술 바, 음량, 전투 메시지\n */\n\nimport React from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { Technique } from \"../../../../../types\";\nimport { HUD_SIDE_CONTROL_RESERVES } from \"../../../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { SPACING, SPACING_ADJUSTMENTS, BORDER_RADIUS, TYPOGRAPHY, TYPOGRAPHY_NUMERIC, HIERARCHY, BORDERS, GRADIENTS, HUD_STYLE ,\n OPACITY,\n COMBAT_UI_DIMENSIONS,\n COMBAT_UI_DIMENSIONS_NUMERIC,\n TEXT_EFFECTS,\n FONT_SIZE_MULTIPLIERS,\n} from \"../../../../../types/constants/designSystem\";\nimport {\n BREAKPOINTS,\n getHUDHeight,\n getResponsiveFontSize,\n getResponsivePadding,\n parsePercentageToRatio,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { TechniqueBar } from \"../../../../shared/three/ui/TechniqueBar\";\nimport { VolumeControl } from \"../../../../shared/ui/VolumeControl\";\n\nexport interface CombatBottomHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Whether technique bar should be visible */\n readonly visible: boolean;\n /** Available techniques for the technique bar */\n readonly techniques: readonly Technique[];\n /** Player state for technique availability checks */\n readonly player: PlayerState;\n /** Currently selected technique index */\n readonly selectedIndex: number;\n /** Active technique cooldowns */\n readonly cooldowns: Map<string, number>;\n /** Handler for technique selection */\n readonly onTechniqueSelect: (index: number) => void;\n /** Combat messages to display */\n readonly combatMessages?: readonly string[];\n}\n\n/**\n * CombatBottomHUD Component\n *\n * Compact bottom bar with centered technique bar, volume control,\n * and combat messages. Uses resolution-based sizing for all elements.\n */\nexport const CombatBottomHUD: React.FC<CombatBottomHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n visible,\n techniques,\n player,\n selectedIndex,\n cooldowns,\n onTechniqueSelect,\n combatMessages = [],\n}) => {\n // isMobile only used for mobile controls visibility\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n\n const layout = React.useMemo(() => {\n // Resolution-based HUD height (10% of screen height, 40-120px range)\n const hudHeight = getHUDHeight(height, 0.1) * positionScale;\n \n // Resolution-based padding\n const padding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based font sizes (using design system as minimum)\n const baseFontSize = getResponsiveFontSize(width);\n const titleFontSize = Math.max(TYPOGRAPHY_NUMERIC.nano, baseFontSize * FONT_SIZE_MULTIPLIERS.titleSmall);\n const messageFontSize = Math.max(TYPOGRAPHY_NUMERIC.caption, baseFontSize * FONT_SIZE_MULTIPLIERS.messageSmall);\n \n // Resolution-based widths (using design system constants as reference)\n const minMessageWidth = width < BREAKPOINTS.mobile \n ? COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMinMobile\n : COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMinDesktop;\n const maxMessageWidth = width < BREAKPOINTS.mobile \n ? width * COMBAT_UI_DIMENSIONS.combatLogMaxWidthPercentMobile\n : COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMaxDesktop;\n const maxTechniqueBarWidth = width < BREAKPOINTS.mobile \n ? COMBAT_UI_DIMENSIONS.techniqueBarWidthMobile \n : COMBAT_UI_DIMENSIONS.techniqueBarWidthDesktop;\n \n // Resolution-based message padding (using design system spacing)\n const messagePadding = width < BREAKPOINTS.mobile \n ? `${SPACING_ADJUSTMENTS.compact} ${SPACING.sm}` \n : `${SPACING.xs} ${SPACING.md}`;\n\n // Actual pixel width available to TechniqueBar.\n // The wrapper applies maxWidth (\"100%\" mobile / \"70%\" desktop) and\n // marginRight (volume control reserve). Pre-computing a numeric value here\n // ensures TechniqueBar's scale/scroll decision matches the real layout.\n const usableWidth = width - padding * 2;\n const maxBarWidthPx = usableWidth * parsePercentageToRatio(maxTechniqueBarWidth);\n const volumeReserve = showMobileControls\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : HUD_SIDE_CONTROL_RESERVES.VOLUME_CONTROL;\n\n const techniqueBarContainerWidth = Math.max(\n 0,\n Math.min(\n maxBarWidthPx,\n usableWidth - volumeReserve,\n ),\n );\n\n return {\n hudHeight,\n padding,\n titleFontSize,\n messageFontSize,\n minMessageWidth,\n maxMessageWidth,\n maxTechniqueBarWidth,\n messagePadding,\n volumeReserve,\n techniqueBarContainerWidth,\n };\n }, [width, height, positionScale, showMobileControls]);\n\n // Only show last 3 combat messages\n const recentMessages = combatMessages.slice(-3);\n\n return (\n <div\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n padding: `${layout.padding}px`,\n boxSizing: \"border-box\",\n borderTop: BORDERS.default,\n background: GRADIENTS.verticalReverse(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"combat-bottom-hud\"\n >\n {/* Combat Messages - styled box above technique bar */}\n {recentMessages.length > 0 && (\n <div\n style={{\n position: \"absolute\",\n top: `${layout.padding}px`,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: SPACING_ADJUSTMENTS.micro,\n zIndex: Z_INDEX.HUD,\n padding: layout.messagePadding,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.md,\n boxShadow: HUD_STYLE.shadow,\n minWidth: `${layout.minMessageWidth}px`,\n maxWidth: typeof layout.maxMessageWidth === 'number' \n ? `${layout.maxMessageWidth}px` \n : layout.maxMessageWidth,\n }}\n data-testid=\"combat-bottom-hud-messages\"\n >\n <div\n style={{\n fontSize: `${layout.titleFontSize}px`,\n fontFamily: TYPOGRAPHY.caption.fontFamily,\n color: HIERARCHY.accent70.color,\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n marginBottom: SPACING_ADJUSTMENTS.tiny,\n }}\n >\n 전투 기록 | Combat Log\n </div>\n {recentMessages.map((message, index) => (\n <div\n key={index}\n style={{\n fontSize: `${layout.messageFontSize}px`,\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n color: HIERARCHY.primary.color,\n textShadow: TEXT_EFFECTS.darkShadow,\n opacity: OPACITY.base + index * OPACITY.increment,\n textAlign: \"center\",\n }}\n >\n {message}\n </div>\n ))}\n </div>\n )}\n\n {/* Technique Bar - centered, embedded mode for proper containment.\n Reserve space on the right for the absolute Volume Control so cards\n never visually overlap the volume button. */}\n {visible && (\n <div\n style={{\n pointerEvents: \"all\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n width: \"100%\",\n maxWidth: layout.maxTechniqueBarWidth,\n marginRight: layout.volumeReserve,\n }}\n data-testid=\"combat-bottom-hud-technique-section\"\n >\n <TechniqueBar\n techniques={techniques as Technique[]}\n player={player}\n selectedIndex={selectedIndex}\n cooldowns={cooldowns}\n onTechniqueSelect={onTechniqueSelect}\n onTechniqueHover={(_tech) => {}}\n isMobile={showMobileControls}\n screenWidth={width}\n screenHeight={height}\n embedded={true}\n containerWidth={layout.techniqueBarContainerWidth}\n />\n </div>\n )}\n\n {/* Volume Control - bottom right corner */}\n <div\n style={{\n position: \"absolute\",\n right: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n }}\n data-testid=\"combat-bottom-hud-volume-section\"\n >\n <VolumeControl position=\"custom\" compact={true} />\n </div>\n </div>\n );\n};\n\nexport default CombatBottomHUD;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatBottomHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatBottomHUD.tsx"],"sourcesContent":["/**\n * CombatBottomHUD - Bottom bar for combat screen\n *\n * Contains:\n * - Technique Bar (centered)\n * - Volume Control (bottom-right, compact)\n * - Combat Messages (above technique bar)\n *\n * Gaming Layout Best Practice:\n * - Width: 100% of screen\n * - Height: Resolution-based ~10% of screen height (40-120px range)\n *\n * @korean 전투화면 하단 바 - 기술 바, 음량, 전투 메시지\n */\n\nimport React from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { Technique } from \"../../../../../types\";\nimport { HUD_SIDE_CONTROL_RESERVES } from \"../../../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { SPACING, SPACING_ADJUSTMENTS, BORDER_RADIUS, TYPOGRAPHY, TYPOGRAPHY_NUMERIC, HIERARCHY, BORDERS, GRADIENTS, HUD_STYLE ,\n OPACITY,\n COMBAT_UI_DIMENSIONS,\n COMBAT_UI_DIMENSIONS_NUMERIC,\n TEXT_EFFECTS,\n FONT_SIZE_MULTIPLIERS,\n} from \"../../../../../types/constants/designSystem\";\nimport {\n BREAKPOINTS,\n getHUDHeight,\n getResponsiveFontSize,\n getResponsivePadding,\n parsePercentageToRatio,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { TechniqueBar } from \"../../../../shared/three/ui/TechniqueBar\";\nimport { VolumeControl } from \"../../../../shared/ui/VolumeControl\";\n\nexport interface CombatBottomHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Whether technique bar should be visible */\n readonly visible: boolean;\n /** Available techniques for the technique bar */\n readonly techniques: readonly Technique[];\n /** Player state for technique availability checks */\n readonly player: PlayerState;\n /** Currently selected technique index */\n readonly selectedIndex: number;\n /** Active technique cooldowns */\n readonly cooldowns: Map<string, number>;\n /** Handler for technique selection */\n readonly onTechniqueSelect: (index: number) => void;\n /** Combat messages to display */\n readonly combatMessages?: readonly string[];\n}\n\n/**\n * CombatBottomHUD Component\n *\n * Compact bottom bar with centered technique bar, volume control,\n * and combat messages. Uses resolution-based sizing for all elements.\n */\nexport const CombatBottomHUD: React.FC<CombatBottomHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n visible,\n techniques,\n player,\n selectedIndex,\n cooldowns,\n onTechniqueSelect,\n combatMessages = [],\n}) => {\n // isMobile only used for mobile controls visibility\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n\n const layout = React.useMemo(() => {\n // Resolution-based HUD height (10% of screen height, 40-120px range)\n const hudHeight = getHUDHeight(height, 0.1) * positionScale;\n \n // Resolution-based padding\n const padding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based font sizes (using design system as minimum)\n const baseFontSize = getResponsiveFontSize(width);\n const titleFontSize = Math.max(TYPOGRAPHY_NUMERIC.nano, baseFontSize * FONT_SIZE_MULTIPLIERS.titleSmall);\n const messageFontSize = Math.max(TYPOGRAPHY_NUMERIC.caption, baseFontSize * FONT_SIZE_MULTIPLIERS.messageSmall);\n \n // Resolution-based widths (using design system constants as reference)\n const minMessageWidth = width < BREAKPOINTS.mobile \n ? COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMinMobile\n : COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMinDesktop;\n const maxMessageWidth = width < BREAKPOINTS.mobile \n ? width * COMBAT_UI_DIMENSIONS.combatLogMaxWidthPercentMobile\n : COMBAT_UI_DIMENSIONS_NUMERIC.combatLogMaxDesktop;\n const maxTechniqueBarWidth = width < BREAKPOINTS.mobile \n ? COMBAT_UI_DIMENSIONS.techniqueBarWidthMobile \n : COMBAT_UI_DIMENSIONS.techniqueBarWidthDesktop;\n \n // Resolution-based message padding (using design system spacing)\n const messagePadding = width < BREAKPOINTS.mobile \n ? `${SPACING_ADJUSTMENTS.compact} ${SPACING.sm}` \n : `${SPACING.xs} ${SPACING.md}`;\n\n // Actual pixel width available to TechniqueBar.\n // The wrapper applies maxWidth (\"100%\" mobile / \"70%\" desktop) and\n // marginRight (volume control reserve). Pre-computing a numeric value here\n // ensures TechniqueBar's scale/scroll decision matches the real layout.\n const usableWidth = width - padding * 2;\n const maxBarWidthPx = usableWidth * parsePercentageToRatio(maxTechniqueBarWidth);\n const volumeReserve = showMobileControls\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : HUD_SIDE_CONTROL_RESERVES.VOLUME_CONTROL;\n\n const techniqueBarContainerWidth = Math.max(\n 0,\n Math.min(\n maxBarWidthPx,\n usableWidth - volumeReserve,\n ),\n );\n\n return {\n hudHeight,\n padding,\n titleFontSize,\n messageFontSize,\n minMessageWidth,\n maxMessageWidth,\n maxTechniqueBarWidth,\n messagePadding,\n volumeReserve,\n techniqueBarContainerWidth,\n };\n }, [width, height, positionScale, showMobileControls]);\n\n // Only show last 3 combat messages\n const recentMessages = combatMessages.slice(-3);\n\n return (\n <div\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n padding: `${layout.padding}px`,\n boxSizing: \"border-box\",\n borderTop: BORDERS.default,\n background: GRADIENTS.verticalReverse(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"combat-bottom-hud\"\n >\n {/* Combat Messages - styled box above technique bar */}\n {recentMessages.length > 0 && (\n <div\n style={{\n position: \"absolute\",\n top: `${layout.padding}px`,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: SPACING_ADJUSTMENTS.micro,\n zIndex: Z_INDEX.HUD,\n padding: layout.messagePadding,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.md,\n boxShadow: HUD_STYLE.shadow,\n minWidth: `${layout.minMessageWidth}px`,\n maxWidth: typeof layout.maxMessageWidth === 'number' \n ? `${layout.maxMessageWidth}px` \n : layout.maxMessageWidth,\n }}\n data-testid=\"combat-bottom-hud-messages\"\n >\n <div\n style={{\n fontSize: `${layout.titleFontSize}px`,\n fontFamily: TYPOGRAPHY.caption.fontFamily,\n color: HIERARCHY.accent70.color,\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n marginBottom: SPACING_ADJUSTMENTS.tiny,\n }}\n >\n 전투 기록 | Combat Log\n </div>\n {recentMessages.map((message, index) => (\n <div\n key={index}\n style={{\n fontSize: `${layout.messageFontSize}px`,\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n color: HIERARCHY.primary.color,\n textShadow: TEXT_EFFECTS.darkShadow,\n opacity: OPACITY.base + index * OPACITY.increment,\n textAlign: \"center\",\n }}\n >\n {message}\n </div>\n ))}\n </div>\n )}\n\n {/* Technique Bar - centered, embedded mode for proper containment.\n Reserve space on the right for the absolute Volume Control so cards\n never visually overlap the volume button. */}\n {visible && (\n <div\n style={{\n pointerEvents: \"all\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n width: \"100%\",\n maxWidth: layout.maxTechniqueBarWidth,\n marginRight: layout.volumeReserve,\n }}\n data-testid=\"combat-bottom-hud-technique-section\"\n >\n <TechniqueBar\n techniques={techniques as Technique[]}\n player={player}\n selectedIndex={selectedIndex}\n cooldowns={cooldowns}\n onTechniqueSelect={onTechniqueSelect}\n onTechniqueHover={(_tech) => {}}\n isMobile={showMobileControls}\n screenWidth={width}\n screenHeight={height}\n embedded={true}\n containerWidth={layout.techniqueBarContainerWidth}\n />\n </div>\n )}\n\n {/* Volume Control - bottom right corner */}\n <div\n style={{\n position: \"absolute\",\n right: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n }}\n data-testid=\"combat-bottom-hud-volume-section\"\n >\n <VolumeControl position=\"custom\" compact={true} />\n </div>\n </div>\n );\n};\n\nexport default CombatBottomHUD;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatLeftHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatLeftHUD.tsx"],"sourcesContent":["/**\n * CombatLeftHUD - Left side HUD for combat screen (Player 1)\n *\n * REUSES existing components:\n * - PlayerHUD: Archetype image, name, health/stamina bars\n * - SpeedIndicatorHUD: Movement speed percentage\n * - BodyPartHealthDisplay: Individual body part health bars\n * - GuardIndicator: Current stance guard status\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - Leaves approximately 64–72% center for arena depending on resolution (≈72% on desktop)\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 전투화면 왼쪽 HUD - 플레이어 1 상태\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { PlayerState } from \"../../../../../systems\";\nimport type { StanceLaterality } from \"../../../../../systems/trigram/types\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { GuardIndicator } from \"../../../../shared/three/indicators/GuardIndicator\";\nimport { PlayerHUD } from \"../../../../shared/three/ui/PlayerHUD\";\nimport { SpeedIndicatorHUD } from \"../../../../shared/three/ui/SpeedIndicatorHUD\";\nimport { BodyPartHealthDisplay } from \"../../../../shared/three/ui/BodyPartHealthDisplay\";\nimport { BreathingIndicator } from \"../../../../shared/three/ui/BreathingIndicator\";\n\nexport interface CombatLeftHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Player 1 state */\n readonly player: PlayerState;\n /** Player laterality (left/right foot forward) */\n readonly laterality: StanceLaterality;\n /** Whether player is in guard stance */\n readonly isInGuard: boolean;\n /** Player speed modifiers */\n readonly speedModifiers: {\n finalSpeed: number;\n baseSpeed: number;\n };\n}\n\n/**\n * CombatLeftHUD Component\n *\n * Left side of the combat screen containing Player 1's stats.\n * Occupies approximately 14–18% of screen width based on resolution breakpoints/interpolation,\n * positioned between the top and bottom HUDs.\n * REUSES existing PlayerHUD, SpeedIndicatorHUD, BodyPartHealthDisplay components.\n * Uses shared HUD utilities for consistent, resolution-based layout and styling.\n */\nexport const CombatLeftHUD: React.FC<CombatLeftHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n player,\n laterality,\n isInGuard,\n speedModifiers,\n}) => {\n // Use shared HUD layout hook\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'left',\n 'combat'\n );\n\n return (\n <BaseHUDContainer\n position=\"left\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"combat-left-hud\"\n >\n {/* Player 1 Stats - REUSING PlayerHUD component with embedded positioning */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-player-section\"\n >\n <PlayerHUD\n player={player}\n position=\"left\"\n isMobile={isMobile}\n laterality={laterality}\n />\n </div>\n\n {/* Speed Indicator - REUSING SpeedIndicatorHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-speed-section\"\n >\n <SpeedIndicatorHUD\n finalSpeed={speedModifiers.finalSpeed}\n baseSpeed={speedModifiers.baseSpeed}\n position=\"left\"\n isMobile={isMobile}\n visible={true}\n />\n </div>\n\n {/* Body Part Health - REUSING BodyPartHealthDisplay component */}\n {player.bodyPartHealth && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-bodypart-section\"\n >\n <BodyPartHealthDisplay\n bodyPartHealth={player.bodyPartHealth}\n playerId={player.id}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n )}\n\n {/* Breathing Disruption Indicator - Shows breathing difficulty status */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-breathing-section\"\n >\n <BreathingIndicator\n player={player}\n isMobile={isMobile}\n />\n </div>\n\n {/* Guard Indicator - at bottom of HUD */}\n <div\n style={{\n pointerEvents: \"none\",\n marginTop: \"auto\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-guard-section\"\n >\n <GuardIndicator\n currentStance={player.currentStance}\n isInGuard={isInGuard}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default CombatLeftHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAa,iBAA+C,EAC1D,OACA,QACA,UACA,eACA,QACA,YACA,WACA,qBACI;CAEJ,MAAM,SAAS,aACb,OACA,QACA,eACA,QACA,SACD;
|
|
1
|
+
{"version":3,"file":"CombatLeftHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatLeftHUD.tsx"],"sourcesContent":["/**\n * CombatLeftHUD - Left side HUD for combat screen (Player 1)\n *\n * REUSES existing components:\n * - PlayerHUD: Archetype image, name, health/stamina bars\n * - SpeedIndicatorHUD: Movement speed percentage\n * - BodyPartHealthDisplay: Individual body part health bars\n * - GuardIndicator: Current stance guard status\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - Leaves approximately 64–72% center for arena depending on resolution (≈72% on desktop)\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 전투화면 왼쪽 HUD - 플레이어 1 상태\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { PlayerState } from \"../../../../../systems\";\nimport type { StanceLaterality } from \"../../../../../systems/trigram/types\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { GuardIndicator } from \"../../../../shared/three/indicators/GuardIndicator\";\nimport { PlayerHUD } from \"../../../../shared/three/ui/PlayerHUD\";\nimport { SpeedIndicatorHUD } from \"../../../../shared/three/ui/SpeedIndicatorHUD\";\nimport { BodyPartHealthDisplay } from \"../../../../shared/three/ui/BodyPartHealthDisplay\";\nimport { BreathingIndicator } from \"../../../../shared/three/ui/BreathingIndicator\";\n\nexport interface CombatLeftHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Player 1 state */\n readonly player: PlayerState;\n /** Player laterality (left/right foot forward) */\n readonly laterality: StanceLaterality;\n /** Whether player is in guard stance */\n readonly isInGuard: boolean;\n /** Player speed modifiers */\n readonly speedModifiers: {\n finalSpeed: number;\n baseSpeed: number;\n };\n}\n\n/**\n * CombatLeftHUD Component\n *\n * Left side of the combat screen containing Player 1's stats.\n * Occupies approximately 14–18% of screen width based on resolution breakpoints/interpolation,\n * positioned between the top and bottom HUDs.\n * REUSES existing PlayerHUD, SpeedIndicatorHUD, BodyPartHealthDisplay components.\n * Uses shared HUD utilities for consistent, resolution-based layout and styling.\n */\nexport const CombatLeftHUD: React.FC<CombatLeftHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n player,\n laterality,\n isInGuard,\n speedModifiers,\n}) => {\n // Use shared HUD layout hook\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'left',\n 'combat'\n );\n\n return (\n <BaseHUDContainer\n position=\"left\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"combat-left-hud\"\n >\n {/* Player 1 Stats - REUSING PlayerHUD component with embedded positioning */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-player-section\"\n >\n <PlayerHUD\n player={player}\n position=\"left\"\n isMobile={isMobile}\n laterality={laterality}\n />\n </div>\n\n {/* Speed Indicator - REUSING SpeedIndicatorHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-speed-section\"\n >\n <SpeedIndicatorHUD\n finalSpeed={speedModifiers.finalSpeed}\n baseSpeed={speedModifiers.baseSpeed}\n position=\"left\"\n isMobile={isMobile}\n visible={true}\n />\n </div>\n\n {/* Body Part Health - REUSING BodyPartHealthDisplay component */}\n {player.bodyPartHealth && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-bodypart-section\"\n >\n <BodyPartHealthDisplay\n bodyPartHealth={player.bodyPartHealth}\n playerId={player.id}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n )}\n\n {/* Breathing Disruption Indicator - Shows breathing difficulty status */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-breathing-section\"\n >\n <BreathingIndicator\n player={player}\n isMobile={isMobile}\n />\n </div>\n\n {/* Guard Indicator - at bottom of HUD */}\n <div\n style={{\n pointerEvents: \"none\",\n marginTop: \"auto\",\n position: \"relative\",\n }}\n data-testid=\"combat-left-hud-guard-section\"\n >\n <GuardIndicator\n currentStance={player.currentStance}\n isInGuard={isInGuard}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default CombatLeftHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAa,iBAA+C,EAC1D,OACA,QACA,UACA,eACA,QACA,YACA,WACA,qBACI;CAEJ,MAAM,SAAS,aACb,OACA,QACA,eACA,QACA,SACD;CAED,OACE,qBAAC,kBAAD;EACE,UAAS;EACT,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,KAAK,OAAO;EACZ,OAAO,EAAE,UAAU,UAAU;EAC7B,YAAW;YARb;GAWE,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,WAAD;KACU;KACR,UAAS;KACC;KACE;KACZ,CAAA;IACE,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,mBAAD;KACE,YAAY,eAAe;KAC3B,WAAW,eAAe;KAC1B,UAAS;KACC;KACV,SAAS;KACT,CAAA;IACE,CAAA;GAGL,OAAO,kBACN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,uBAAD;KACE,gBAAgB,OAAO;KACvB,UAAU,OAAO;KACjB,UAAS;KACC;KACV,CAAA;IACE,CAAA;GAIR,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,oBAAD;KACU;KACE;KACV,CAAA;IACE,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,WAAW;KACX,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,gBAAD;KACE,eAAe,OAAO;KACX;KACX,UAAS;KACC;KACV,CAAA;IACE,CAAA;GACW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatPortraitStatusStrip.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatPortraitStatusStrip.tsx"],"sourcesContent":["/**\n * CombatPortraitStatusStrip - Compact two-player HP/stamina strip for portrait mobile.\n *\n * When the viewport is portrait mobile we hide `CombatLeftHUD` and\n * `CombatRightHUD` (they occlude the 3D arena on narrow screens). This strip\n * replaces them with a minimal, **always visible** health+stamina bar for\n * both players, rendered as a single horizontal band directly under the\n * top HUD.\n *\n * UX goals (game-UI best practice for narrow portrait):\n * - Both bars fit on a single row on viewports as narrow as 320 px.\n * - Clear left/right grouping (P1 on the left, P2 on the right) mirrors the\n * fighters' on-screen positions.\n * - Low visual weight (no side HUD backgrounds, no icons) so the strip does\n * not re-introduce the occlusion problem it was meant to solve.\n * - Korean + English bilingual micro-labels (체 / ST).\n *\n * 세로 모드 휴대폰 전용 상/하단 상태 바\n *\n * @module components/screens/combat/components/hud/CombatPortraitStatusStrip\n */\n\nimport React, { useMemo } from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport {\n BORDERS,\n FONT_SIZE_MULTIPLIERS,\n GRADIENTS,\n HIERARCHY,\n HUD_STYLE,\n TYPOGRAPHY,\n TYPOGRAPHY_NUMERIC,\n} from \"../../../../../types/constants/designSystem\";\nimport { KOREAN_COLORS } from \"../../../../../types/constants/colors\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport {\n getResponsiveFontSize,\n getResponsivePadding,\n} from \"../../../../../utils/responsiveLayout\";\n\nexport interface CombatPortraitStatusStripProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Player 1 state */\n readonly player1: PlayerState;\n /** Player 2 (AI) state */\n readonly player2: PlayerState;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Y offset from top of screen (should equal top HUD height) */\n readonly topOffset: number;\n}\n\n/** Single compact HP + stamina pill for one fighter. */\nconst PlayerPill: React.FC<{\n readonly player: PlayerState;\n readonly side: \"left\" | \"right\";\n readonly fontSize: number;\n readonly barHeight: number;\n readonly testId: string;\n}> = ({ player, side, fontSize, barHeight, testId }) => {\n // Guard against non-positive maxima (0 or negative) which would\n // otherwise yield Infinity/NaN percentages and render invalid widths.\n const healthPercent =\n player.maxHealth > 0\n ? Math.max(\n 0,\n Math.min(100, (player.health / player.maxHealth) * 100),\n )\n : 0;\n const staminaPercent =\n player.maxStamina > 0\n ? Math.max(\n 0,\n Math.min(100, (player.stamina / player.maxStamina) * 100),\n )\n : 0;\n\n const healthColor =\n healthPercent > 50\n ? KOREAN_COLORS.HEALTH_FULL\n : healthPercent > 25\n ? KOREAN_COLORS.HEALTH_MEDIUM\n : KOREAN_COLORS.HEALTH_LOW;\n\n return (\n <div\n data-testid={testId}\n style={{\n display: \"flex\",\n flex: 1,\n flexDirection: \"column\",\n alignItems: side === \"left\" ? \"flex-start\" : \"flex-end\",\n gap: \"2px\",\n minWidth: 0,\n }}\n >\n {/* Name + side indicator */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: HIERARCHY.accent.color,\n fontFamily: TYPOGRAPHY.body.fontFamily,\n fontWeight: 600,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n maxWidth: \"100%\",\n }}\n >\n {side === \"left\" ? \"P1\" : \"P2\"}\n </div>\n {/* HP bar */}\n <div\n role=\"progressbar\"\n aria-label={`Player ${side === \"left\" ? 1 : 2} health`}\n aria-valuenow={Math.max(\n 0,\n Math.min(player.maxHealth, Math.ceil(player.health)),\n )}\n aria-valuemin={0}\n aria-valuemax={player.maxHealth}\n style={{\n width: \"100%\",\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.5)}`,\n }}\n >\n <div\n data-testid={`${testId}-hp-fill`}\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition: \"width 0.2s ease-out, background-color 0.3s ease-out\",\n }}\n />\n </div>\n {/* Stamina bar (thinner) */}\n <div\n role=\"progressbar\"\n aria-label={`Player ${side === \"left\" ? 1 : 2} stamina`}\n aria-valuenow={Math.max(\n 0,\n Math.min(player.maxStamina, Math.ceil(player.stamina)),\n )}\n aria-valuemin={0}\n aria-valuemax={player.maxStamina}\n style={{\n width: \"100%\",\n height: `${Math.max(3, Math.floor(barHeight * 0.5))}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n <div\n data-testid={`${testId}-sp-fill`}\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n transition: \"width 0.2s ease-out\",\n }}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Compact two-player HP/stamina strip, only rendered for portrait mobile.\n */\nexport const CombatPortraitStatusStrip: React.FC<\n CombatPortraitStatusStripProps\n> = ({ width, height, player1, player2, positionScale, topOffset }) => {\n const layout = useMemo(() => {\n // Tighter strip on extra-small phones (<380 px) so the arena retains\n // usable vertical space.\n const isExtraSmall = width < 380;\n const minStripHeight = isExtraSmall ? 28 : 36;\n const stripHeight = Math.max(\n minStripHeight,\n Math.round(height * 0.055 * positionScale),\n );\n const padding = getResponsivePadding(width) * positionScale;\n const fontSize = Math.max(\n TYPOGRAPHY_NUMERIC.caption,\n getResponsiveFontSize(width) *\n positionScale *\n FONT_SIZE_MULTIPLIERS.bodySmall,\n );\n // Reserve a narrow center gap between the two pills.\n const centerGap = Math.max(6, padding);\n const barHeight = Math.max(6, Math.floor(stripHeight * 0.28));\n\n return { stripHeight, padding, fontSize, centerGap, barHeight };\n }, [width, height, positionScale]);\n\n return (\n <div\n data-testid=\"combat-portrait-status-strip\"\n style={{\n position: \"absolute\",\n top: `${topOffset}px`,\n left: 0,\n width: \"100%\",\n height: `${layout.stripHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: `${layout.centerGap}px`,\n padding: `4px ${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.muted,\n background: GRADIENTS.vertical(0.85),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n >\n <PlayerPill\n player={player1}\n side=\"left\"\n fontSize={layout.fontSize}\n barHeight={layout.barHeight}\n testId=\"combat-portrait-p1\"\n />\n <PlayerPill\n player={player2}\n side=\"right\"\n fontSize={layout.fontSize}\n barHeight={layout.barHeight}\n testId=\"combat-portrait-p2\"\n />\n </div>\n );\n};\n\nexport default CombatPortraitStatusStrip;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatPortraitStatusStrip.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatPortraitStatusStrip.tsx"],"sourcesContent":["/**\n * CombatPortraitStatusStrip - Compact two-player HP/stamina strip for portrait mobile.\n *\n * When the viewport is portrait mobile we hide `CombatLeftHUD` and\n * `CombatRightHUD` (they occlude the 3D arena on narrow screens). This strip\n * replaces them with a minimal, **always visible** health+stamina bar for\n * both players, rendered as a single horizontal band directly under the\n * top HUD.\n *\n * UX goals (game-UI best practice for narrow portrait):\n * - Both bars fit on a single row on viewports as narrow as 320 px.\n * - Clear left/right grouping (P1 on the left, P2 on the right) mirrors the\n * fighters' on-screen positions.\n * - Low visual weight (no side HUD backgrounds, no icons) so the strip does\n * not re-introduce the occlusion problem it was meant to solve.\n * - Korean + English bilingual micro-labels (체 / ST).\n *\n * 세로 모드 휴대폰 전용 상/하단 상태 바\n *\n * @module components/screens/combat/components/hud/CombatPortraitStatusStrip\n */\n\nimport React, { useMemo } from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport {\n BORDERS,\n FONT_SIZE_MULTIPLIERS,\n GRADIENTS,\n HIERARCHY,\n HUD_STYLE,\n TYPOGRAPHY,\n TYPOGRAPHY_NUMERIC,\n} from \"../../../../../types/constants/designSystem\";\nimport { KOREAN_COLORS } from \"../../../../../types/constants/colors\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport {\n getResponsiveFontSize,\n getResponsivePadding,\n} from \"../../../../../utils/responsiveLayout\";\n\nexport interface CombatPortraitStatusStripProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Player 1 state */\n readonly player1: PlayerState;\n /** Player 2 (AI) state */\n readonly player2: PlayerState;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Y offset from top of screen (should equal top HUD height) */\n readonly topOffset: number;\n}\n\n/** Single compact HP + stamina pill for one fighter. */\nconst PlayerPill: React.FC<{\n readonly player: PlayerState;\n readonly side: \"left\" | \"right\";\n readonly fontSize: number;\n readonly barHeight: number;\n readonly testId: string;\n}> = ({ player, side, fontSize, barHeight, testId }) => {\n // Guard against non-positive maxima (0 or negative) which would\n // otherwise yield Infinity/NaN percentages and render invalid widths.\n const healthPercent =\n player.maxHealth > 0\n ? Math.max(\n 0,\n Math.min(100, (player.health / player.maxHealth) * 100),\n )\n : 0;\n const staminaPercent =\n player.maxStamina > 0\n ? Math.max(\n 0,\n Math.min(100, (player.stamina / player.maxStamina) * 100),\n )\n : 0;\n\n const healthColor =\n healthPercent > 50\n ? KOREAN_COLORS.HEALTH_FULL\n : healthPercent > 25\n ? KOREAN_COLORS.HEALTH_MEDIUM\n : KOREAN_COLORS.HEALTH_LOW;\n\n return (\n <div\n data-testid={testId}\n style={{\n display: \"flex\",\n flex: 1,\n flexDirection: \"column\",\n alignItems: side === \"left\" ? \"flex-start\" : \"flex-end\",\n gap: \"2px\",\n minWidth: 0,\n }}\n >\n {/* Name + side indicator */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n color: HIERARCHY.accent.color,\n fontFamily: TYPOGRAPHY.body.fontFamily,\n fontWeight: 600,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n maxWidth: \"100%\",\n }}\n >\n {side === \"left\" ? \"P1\" : \"P2\"}\n </div>\n {/* HP bar */}\n <div\n role=\"progressbar\"\n aria-label={`Player ${side === \"left\" ? 1 : 2} health`}\n aria-valuenow={Math.max(\n 0,\n Math.min(player.maxHealth, Math.ceil(player.health)),\n )}\n aria-valuemin={0}\n aria-valuemax={player.maxHealth}\n style={{\n width: \"100%\",\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.5)}`,\n }}\n >\n <div\n data-testid={`${testId}-hp-fill`}\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition: \"width 0.2s ease-out, background-color 0.3s ease-out\",\n }}\n />\n </div>\n {/* Stamina bar (thinner) */}\n <div\n role=\"progressbar\"\n aria-label={`Player ${side === \"left\" ? 1 : 2} stamina`}\n aria-valuenow={Math.max(\n 0,\n Math.min(player.maxStamina, Math.ceil(player.stamina)),\n )}\n aria-valuemin={0}\n aria-valuemax={player.maxStamina}\n style={{\n width: \"100%\",\n height: `${Math.max(3, Math.floor(barHeight * 0.5))}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.4)}`,\n }}\n >\n <div\n data-testid={`${testId}-sp-fill`}\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 1),\n transition: \"width 0.2s ease-out\",\n }}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Compact two-player HP/stamina strip, only rendered for portrait mobile.\n */\nexport const CombatPortraitStatusStrip: React.FC<\n CombatPortraitStatusStripProps\n> = ({ width, height, player1, player2, positionScale, topOffset }) => {\n const layout = useMemo(() => {\n // Tighter strip on extra-small phones (<380 px) so the arena retains\n // usable vertical space.\n const isExtraSmall = width < 380;\n const minStripHeight = isExtraSmall ? 28 : 36;\n const stripHeight = Math.max(\n minStripHeight,\n Math.round(height * 0.055 * positionScale),\n );\n const padding = getResponsivePadding(width) * positionScale;\n const fontSize = Math.max(\n TYPOGRAPHY_NUMERIC.caption,\n getResponsiveFontSize(width) *\n positionScale *\n FONT_SIZE_MULTIPLIERS.bodySmall,\n );\n // Reserve a narrow center gap between the two pills.\n const centerGap = Math.max(6, padding);\n const barHeight = Math.max(6, Math.floor(stripHeight * 0.28));\n\n return { stripHeight, padding, fontSize, centerGap, barHeight };\n }, [width, height, positionScale]);\n\n return (\n <div\n data-testid=\"combat-portrait-status-strip\"\n style={{\n position: \"absolute\",\n top: `${topOffset}px`,\n left: 0,\n width: \"100%\",\n height: `${layout.stripHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: `${layout.centerGap}px`,\n padding: `4px ${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.muted,\n background: GRADIENTS.vertical(0.85),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n >\n <PlayerPill\n player={player1}\n side=\"left\"\n fontSize={layout.fontSize}\n barHeight={layout.barHeight}\n testId=\"combat-portrait-p1\"\n />\n <PlayerPill\n player={player2}\n side=\"right\"\n fontSize={layout.fontSize}\n barHeight={layout.barHeight}\n testId=\"combat-portrait-p2\"\n />\n </div>\n );\n};\n\nexport default CombatPortraitStatusStrip;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatRightHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatRightHUD.tsx"],"sourcesContent":["/**\n * CombatRightHUD - Right side HUD for combat screen (Player 2 / AI)\n *\n * REUSES existing components:\n * - PlayerHUD: Archetype image, name, health/stamina bars\n * - SpeedIndicatorHUD: Movement speed percentage\n * - BodyPartHealthDisplay: Individual body part health bars\n * - DifficultyIndicator: AI difficulty tier display\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - Leaves remaining center region for arena (width varies by resolution)\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 전투화면 오른쪽 HUD - 플레이어 2/AI 상태\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { PlayerState } from \"../../../../../systems\";\nimport type { StanceLaterality } from \"../../../../../systems/trigram/types\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { BodyPartHealthDisplay } from \"../../../../shared/three/ui/BodyPartHealthDisplay\";\nimport { PlayerHUD } from \"../../../../shared/three/ui/PlayerHUD\";\nimport { SpeedIndicatorHUD } from \"../../../../shared/three/ui/SpeedIndicatorHUD\";\nimport { BreathingIndicator } from \"../../../../shared/three/ui/BreathingIndicator\";\nimport { DifficultyIndicator } from \"./DifficultyIndicator\";\n\nexport interface CombatRightHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Player 2/AI state */\n readonly player: PlayerState;\n /** Player laterality (left/right foot forward) */\n readonly laterality: StanceLaterality;\n /** Current AI difficulty tier (1-5) */\n readonly difficultyTier: number;\n /** Player speed modifiers */\n readonly speedModifiers: {\n finalSpeed: number;\n baseSpeed: number;\n };\n}\n\n/**\n * CombatRightHUD Component\n *\n * Right side of the combat screen containing Player 2/AI stats.\n * Occupies approximately 14–18% of screen width based on resolution breakpoints/interpolation,\n * positioned between top and bottom HUDs.\n * REUSES existing PlayerHUD, SpeedIndicatorHUD, BodyPartHealthDisplay, DifficultyIndicator.\n * Uses shared HUD utilities for consistent, resolution-based layout and styling.\n */\nexport const CombatRightHUD: React.FC<CombatRightHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n player,\n laterality,\n difficultyTier,\n speedModifiers,\n}) => {\n // Use shared HUD layout hook\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'right',\n 'combat'\n );\n\n return (\n <BaseHUDContainer\n position=\"right\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"combat-right-hud\"\n >\n {/* Player 2/AI Stats - REUSING PlayerHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-player-section\"\n >\n <PlayerHUD\n player={player}\n position=\"right\"\n isMobile={isMobile}\n laterality={laterality}\n />\n </div>\n\n {/* Difficulty Tier - REUSING DifficultyIndicator component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-difficulty-section\"\n >\n <DifficultyIndicator tier={difficultyTier} isMobile={isMobile} />\n </div>\n\n {/* Speed Indicator - REUSING SpeedIndicatorHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-speed-section\"\n >\n <SpeedIndicatorHUD\n finalSpeed={speedModifiers.finalSpeed}\n baseSpeed={speedModifiers.baseSpeed}\n position=\"right\"\n isMobile={isMobile}\n visible={true}\n />\n </div>\n\n {/* Body Part Health - REUSING BodyPartHealthDisplay component */}\n {player.bodyPartHealth && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n marginTop: \"auto\",\n }}\n data-testid=\"combat-right-hud-bodypart-section\"\n >\n <BodyPartHealthDisplay\n bodyPartHealth={player.bodyPartHealth}\n playerId={player.id}\n position=\"right\"\n isMobile={isMobile}\n />\n </div>\n )}\n\n {/* Breathing Disruption Indicator - Shows breathing difficulty status */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-breathing-section\"\n >\n <BreathingIndicator\n player={player}\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default CombatRightHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAa,kBAAiD,EAC5D,OACA,QACA,UACA,eACA,QACA,YACA,gBACA,qBACI;CAEJ,MAAM,SAAS,aACb,OACA,QACA,eACA,SACA,SACD;
|
|
1
|
+
{"version":3,"file":"CombatRightHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatRightHUD.tsx"],"sourcesContent":["/**\n * CombatRightHUD - Right side HUD for combat screen (Player 2 / AI)\n *\n * REUSES existing components:\n * - PlayerHUD: Archetype image, name, health/stamina bars\n * - SpeedIndicatorHUD: Movement speed percentage\n * - BodyPartHealthDisplay: Individual body part health bars\n * - DifficultyIndicator: AI difficulty tier display\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - Leaves remaining center region for arena (width varies by resolution)\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 전투화면 오른쪽 HUD - 플레이어 2/AI 상태\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { PlayerState } from \"../../../../../systems\";\nimport type { StanceLaterality } from \"../../../../../systems/trigram/types\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { BodyPartHealthDisplay } from \"../../../../shared/three/ui/BodyPartHealthDisplay\";\nimport { PlayerHUD } from \"../../../../shared/three/ui/PlayerHUD\";\nimport { SpeedIndicatorHUD } from \"../../../../shared/three/ui/SpeedIndicatorHUD\";\nimport { BreathingIndicator } from \"../../../../shared/three/ui/BreathingIndicator\";\nimport { DifficultyIndicator } from \"./DifficultyIndicator\";\n\nexport interface CombatRightHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Player 2/AI state */\n readonly player: PlayerState;\n /** Player laterality (left/right foot forward) */\n readonly laterality: StanceLaterality;\n /** Current AI difficulty tier (1-5) */\n readonly difficultyTier: number;\n /** Player speed modifiers */\n readonly speedModifiers: {\n finalSpeed: number;\n baseSpeed: number;\n };\n}\n\n/**\n * CombatRightHUD Component\n *\n * Right side of the combat screen containing Player 2/AI stats.\n * Occupies approximately 14–18% of screen width based on resolution breakpoints/interpolation,\n * positioned between top and bottom HUDs.\n * REUSES existing PlayerHUD, SpeedIndicatorHUD, BodyPartHealthDisplay, DifficultyIndicator.\n * Uses shared HUD utilities for consistent, resolution-based layout and styling.\n */\nexport const CombatRightHUD: React.FC<CombatRightHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n player,\n laterality,\n difficultyTier,\n speedModifiers,\n}) => {\n // Use shared HUD layout hook\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'right',\n 'combat'\n );\n\n return (\n <BaseHUDContainer\n position=\"right\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"combat-right-hud\"\n >\n {/* Player 2/AI Stats - REUSING PlayerHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-player-section\"\n >\n <PlayerHUD\n player={player}\n position=\"right\"\n isMobile={isMobile}\n laterality={laterality}\n />\n </div>\n\n {/* Difficulty Tier - REUSING DifficultyIndicator component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-difficulty-section\"\n >\n <DifficultyIndicator tier={difficultyTier} isMobile={isMobile} />\n </div>\n\n {/* Speed Indicator - REUSING SpeedIndicatorHUD component */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-speed-section\"\n >\n <SpeedIndicatorHUD\n finalSpeed={speedModifiers.finalSpeed}\n baseSpeed={speedModifiers.baseSpeed}\n position=\"right\"\n isMobile={isMobile}\n visible={true}\n />\n </div>\n\n {/* Body Part Health - REUSING BodyPartHealthDisplay component */}\n {player.bodyPartHealth && (\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n marginTop: \"auto\",\n }}\n data-testid=\"combat-right-hud-bodypart-section\"\n >\n <BodyPartHealthDisplay\n bodyPartHealth={player.bodyPartHealth}\n playerId={player.id}\n position=\"right\"\n isMobile={isMobile}\n />\n </div>\n )}\n\n {/* Breathing Disruption Indicator - Shows breathing difficulty status */}\n <div\n style={{\n pointerEvents: \"none\",\n position: \"relative\",\n }}\n data-testid=\"combat-right-hud-breathing-section\"\n >\n <BreathingIndicator\n player={player}\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default CombatRightHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAa,kBAAiD,EAC5D,OACA,QACA,UACA,eACA,QACA,YACA,gBACA,qBACI;CAEJ,MAAM,SAAS,aACb,OACA,QACA,eACA,SACA,SACD;CAED,OACE,qBAAC,kBAAD;EACE,UAAS;EACT,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,KAAK,OAAO;EACZ,OAAO,EAAE,UAAU,UAAU;EAC7B,YAAW;YARb;GAWE,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,WAAD;KACU;KACR,UAAS;KACC;KACE;KACZ,CAAA;IACE,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,qBAAD;KAAqB,MAAM;KAA0B;KAAY,CAAA;IAC7D,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,mBAAD;KACE,YAAY,eAAe;KAC3B,WAAW,eAAe;KAC1B,UAAS;KACC;KACV,SAAS;KACT,CAAA;IACE,CAAA;GAGL,OAAO,kBACN,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACV,WAAW;KACZ;IACD,eAAY;cAEZ,oBAAC,uBAAD;KACE,gBAAgB,OAAO;KACvB,UAAU,OAAO;KACjB,UAAS;KACC;KACV,CAAA;IACE,CAAA;GAIR,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,UAAU;KACX;IACD,eAAY;cAEZ,oBAAC,oBAAD;KACU;KACE;KACV,CAAA;IACE,CAAA;GACW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatTopHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatTopHUD.tsx"],"sourcesContent":["/**\n * CombatTopHUD - Slim top bar for combat screen\n *\n * Gaming Best Practice - Minimal Top Bar:\n * - Round indicator (left)\n * - Timer (center)\n * - Return to Menu button (right)\n *\n * Layout:\n * - Width: 100% of screen\n * - Height: Resolution-based ~6% of screen height (40-80px)\n *\n * @korean 전투화면 상단 바 - 라운드, 타이머, 메뉴 복귀\n */\n\nimport React from \"react\";\nimport { UseCombatTimerReturn } from \"../../../../../hooks/useCombatTimer\";\nimport {\n BORDERS,\n FONT_SIZE_MULTIPLIERS,\n GRADIENTS,\n HIERARCHY,\n HUD_STYLE,\n LAYOUT_MULTIPLIERS,\n TYPOGRAPHY,\n TYPOGRAPHY_NUMERIC,\n} from \"../../../../../types/constants/designSystem\";\nimport {\n getHUDHeight,\n getResponsiveFontSize,\n getResponsivePadding,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { CombatTimer } from \"../../../../shared/ui/CombatTimer\";\nimport { CombatReturnToMenuButton } from \"../controls/CombatButtons\";\n\nexport interface CombatTopHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Current round number */\n readonly currentRound: number;\n /** Max rounds in match */\n readonly totalRounds: number;\n /** Timer state from useCombatTimer hook */\n readonly timerState: UseCombatTimerReturn;\n /** Whether to show timer */\n readonly showTimer: boolean;\n /** Handler for returning to menu */\n readonly onReturnToMenu: () => void;\n /** Whether the game is paused */\n readonly isPaused: boolean;\n}\n\n/**\n * CombatTopHUD Component\n *\n * Slim top bar containing round info, timer, and return to menu button.\n * Uses resolution-based sizing for all dimensions.\n */\nexport const CombatTopHUD: React.FC<CombatTopHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n currentRound,\n totalRounds,\n timerState,\n showTimer,\n onReturnToMenu,\n isPaused: _isPaused, // Reserved for future pause indicator\n}) => {\n // isMobile only used for mobile controls visibility\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n\n // Layout calculations for slim top bar with resolution-based sizing\n const layout = React.useMemo(() => {\n // Resolution-based HUD height (6% of screen height, 40-80px range)\n const hudHeight = getHUDHeight(height, 0.06) * positionScale;\n\n // Resolution-based padding (using design system spacing)\n const padding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap (slightly larger than padding)\n const gap = padding * LAYOUT_MULTIPLIERS.gapToPadding;\n \n // Resolution-based font sizes (using design system as baseline)\n const baseFontSize = getResponsiveFontSize(width) * positionScale;\n const fontSize = Math.max(TYPOGRAPHY_NUMERIC.bodySmall, baseFontSize * FONT_SIZE_MULTIPLIERS.bodySmall);\n const titleSize = Math.max(TYPOGRAPHY_NUMERIC.body, baseFontSize * FONT_SIZE_MULTIPLIERS.titleLarge);\n\n\n return {\n hudHeight,\n padding,\n gap,\n fontSize,\n titleSize,\n hudWidth: width,\n };\n }, [width, height, positionScale]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: `${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.default,\n background: GRADIENTS.vertical(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"combat-top-hud\"\n >\n {/* Left Section - Round Info */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n pointerEvents: \"none\",\n alignItems: \"flex-start\",\n }}\n data-testid=\"combat-top-hud-left-section\"\n >\n {/* Title */}\n <div\n style={{\n fontSize: `${layout.titleSize}px`,\n fontWeight: 600,\n fontFamily: TYPOGRAPHY.heading2.fontFamily,\n color: HIERARCHY.gold.color,\n textShadow: HUD_STYLE.accentGlow,\n }}\n >\n 전투 | Combat\n </div>\n\n {/* Round indicator */}\n <div\n style={{\n display: \"flex\",\n gap: `${layout.gap}px`,\n alignItems: \"center\",\n fontSize: `${layout.fontSize}px`,\n fontFamily: TYPOGRAPHY.body.fontFamily,\n color: HIERARCHY.accent.color,\n }}\n >\n <span>\n 라운드 {currentRound}/{totalRounds}\n </span>\n </div>\n </div>\n\n {/* Center Section - Timer */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n data-testid=\"combat-top-hud-center-section\"\n >\n {showTimer && (\n <CombatTimer\n formattedTime={timerState.formattedTime}\n warningLevel={timerState.warningLevel}\n isTimeUp={timerState.isTimeUp}\n isMobile={showMobileControls}\n style={{ position: \"relative\", top: 0 }}\n />\n )}\n </div>\n\n {/* Right Section - Return to Menu */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n pointerEvents: \"all\",\n }}\n data-testid=\"combat-top-hud-right-section\"\n >\n <CombatReturnToMenuButton\n onClick={onReturnToMenu}\n isMobile={showMobileControls}\n />\n </div>\n </div>\n );\n};\n\nexport default CombatTopHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAa,gBAA6C,EACxD,OACA,QACA,WAAW,OACX,eACA,cACA,aACA,YACA,WACA,gBACA,UAAU,gBACN;CAEJ,MAAM,qBAAqB,yBAAyB,OAAO,SAAS;CAGpE,MAAM,SAAS,MAAM,cAAc;EAEjC,MAAM,YAAY,aAAa,QAAQ,IAAK,GAAG;EAG/C,MAAM,UAAU,qBAAqB,MAAM,GAAG;EAG9C,MAAM,MAAM,UAAU,mBAAmB;EAGzC,MAAM,eAAe,sBAAsB,MAAM,GAAG;
|
|
1
|
+
{"version":3,"file":"CombatTopHUD.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/CombatTopHUD.tsx"],"sourcesContent":["/**\n * CombatTopHUD - Slim top bar for combat screen\n *\n * Gaming Best Practice - Minimal Top Bar:\n * - Round indicator (left)\n * - Timer (center)\n * - Return to Menu button (right)\n *\n * Layout:\n * - Width: 100% of screen\n * - Height: Resolution-based ~6% of screen height (40-80px)\n *\n * @korean 전투화면 상단 바 - 라운드, 타이머, 메뉴 복귀\n */\n\nimport React from \"react\";\nimport { UseCombatTimerReturn } from \"../../../../../hooks/useCombatTimer\";\nimport {\n BORDERS,\n FONT_SIZE_MULTIPLIERS,\n GRADIENTS,\n HIERARCHY,\n HUD_STYLE,\n LAYOUT_MULTIPLIERS,\n TYPOGRAPHY,\n TYPOGRAPHY_NUMERIC,\n} from \"../../../../../types/constants/designSystem\";\nimport {\n getHUDHeight,\n getResponsiveFontSize,\n getResponsivePadding,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { CombatTimer } from \"../../../../shared/ui/CombatTimer\";\nimport { CombatReturnToMenuButton } from \"../controls/CombatButtons\";\n\nexport interface CombatTopHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Current round number */\n readonly currentRound: number;\n /** Max rounds in match */\n readonly totalRounds: number;\n /** Timer state from useCombatTimer hook */\n readonly timerState: UseCombatTimerReturn;\n /** Whether to show timer */\n readonly showTimer: boolean;\n /** Handler for returning to menu */\n readonly onReturnToMenu: () => void;\n /** Whether the game is paused */\n readonly isPaused: boolean;\n}\n\n/**\n * CombatTopHUD Component\n *\n * Slim top bar containing round info, timer, and return to menu button.\n * Uses resolution-based sizing for all dimensions.\n */\nexport const CombatTopHUD: React.FC<CombatTopHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n currentRound,\n totalRounds,\n timerState,\n showTimer,\n onReturnToMenu,\n isPaused: _isPaused, // Reserved for future pause indicator\n}) => {\n // isMobile only used for mobile controls visibility\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n\n // Layout calculations for slim top bar with resolution-based sizing\n const layout = React.useMemo(() => {\n // Resolution-based HUD height (6% of screen height, 40-80px range)\n const hudHeight = getHUDHeight(height, 0.06) * positionScale;\n\n // Resolution-based padding (using design system spacing)\n const padding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap (slightly larger than padding)\n const gap = padding * LAYOUT_MULTIPLIERS.gapToPadding;\n \n // Resolution-based font sizes (using design system as baseline)\n const baseFontSize = getResponsiveFontSize(width) * positionScale;\n const fontSize = Math.max(TYPOGRAPHY_NUMERIC.bodySmall, baseFontSize * FONT_SIZE_MULTIPLIERS.bodySmall);\n const titleSize = Math.max(TYPOGRAPHY_NUMERIC.body, baseFontSize * FONT_SIZE_MULTIPLIERS.titleLarge);\n\n\n return {\n hudHeight,\n padding,\n gap,\n fontSize,\n titleSize,\n hudWidth: width,\n };\n }, [width, height, positionScale]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: `${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.default,\n background: GRADIENTS.vertical(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"combat-top-hud\"\n >\n {/* Left Section - Round Info */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n pointerEvents: \"none\",\n alignItems: \"flex-start\",\n }}\n data-testid=\"combat-top-hud-left-section\"\n >\n {/* Title */}\n <div\n style={{\n fontSize: `${layout.titleSize}px`,\n fontWeight: 600,\n fontFamily: TYPOGRAPHY.heading2.fontFamily,\n color: HIERARCHY.gold.color,\n textShadow: HUD_STYLE.accentGlow,\n }}\n >\n 전투 | Combat\n </div>\n\n {/* Round indicator */}\n <div\n style={{\n display: \"flex\",\n gap: `${layout.gap}px`,\n alignItems: \"center\",\n fontSize: `${layout.fontSize}px`,\n fontFamily: TYPOGRAPHY.body.fontFamily,\n color: HIERARCHY.accent.color,\n }}\n >\n <span>\n 라운드 {currentRound}/{totalRounds}\n </span>\n </div>\n </div>\n\n {/* Center Section - Timer */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n data-testid=\"combat-top-hud-center-section\"\n >\n {showTimer && (\n <CombatTimer\n formattedTime={timerState.formattedTime}\n warningLevel={timerState.warningLevel}\n isTimeUp={timerState.isTimeUp}\n isMobile={showMobileControls}\n style={{ position: \"relative\", top: 0 }}\n />\n )}\n </div>\n\n {/* Right Section - Return to Menu */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n pointerEvents: \"all\",\n }}\n data-testid=\"combat-top-hud-right-section\"\n >\n <CombatReturnToMenuButton\n onClick={onReturnToMenu}\n isMobile={showMobileControls}\n />\n </div>\n </div>\n );\n};\n\nexport default CombatTopHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAa,gBAA6C,EACxD,OACA,QACA,WAAW,OACX,eACA,cACA,aACA,YACA,WACA,gBACA,UAAU,gBACN;CAEJ,MAAM,qBAAqB,yBAAyB,OAAO,SAAS;CAGpE,MAAM,SAAS,MAAM,cAAc;EAEjC,MAAM,YAAY,aAAa,QAAQ,IAAK,GAAG;EAG/C,MAAM,UAAU,qBAAqB,MAAM,GAAG;EAG9C,MAAM,MAAM,UAAU,mBAAmB;EAGzC,MAAM,eAAe,sBAAsB,MAAM,GAAG;EAKpD,OAAO;GACL;GACA;GACA;GACA,UARe,KAAK,IAAI,mBAAmB,WAAW,eAAe,sBAAsB,UAQ3F;GACA,WARgB,KAAK,IAAI,mBAAmB,MAAM,eAAe,sBAAsB,WAQvF;GACA,UAAU;GACX;IACA;EAAC;EAAO;EAAQ;EAAc,CAAC;CAElC,OACE,qBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ,GAAG,OAAO,UAAU;GAC5B,SAAS;GACT,eAAe;GACf,gBAAgB;GAChB,YAAY;GACZ,SAAS,GAAG,OAAO,QAAQ;GAC3B,eAAe;GACf,WAAW;GACX,cAAc,QAAQ;GACtB,YAAY,UAAU,SAAS,GAAI;GACnC,gBAAgB,UAAU;GAC3B;EACD,eAAY;YAlBd;GAqBE,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,KAAK;KACL,eAAe;KACf,YAAY;KACb;IACD,eAAY;cARd,CAWE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,OAAO,UAAU;MAC9B,YAAY;MACZ,YAAY,WAAW,SAAS;MAChC,OAAO,UAAU,KAAK;MACtB,YAAY,UAAU;MACvB;eACF;KAEK,CAAA,EAGN,oBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,KAAK,GAAG,OAAO,IAAI;MACnB,YAAY;MACZ,UAAU,GAAG,OAAO,SAAS;MAC7B,YAAY,WAAW,KAAK;MAC5B,OAAO,UAAU,OAAO;MACzB;eAED,qBAAC,QAAD,EAAA,UAAA;MAAM;MACC;MAAa;MAAE;MACf,EAAA,CAAA;KACH,CAAA,CACF;;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,KAAK;KACN;IACD,eAAY;cAEX,aACC,oBAAC,aAAD;KACE,eAAe,WAAW;KAC1B,cAAc,WAAW;KACzB,UAAU,WAAW;KACrB,UAAU;KACV,OAAO;MAAE,UAAU;MAAY,KAAK;MAAG;KACvC,CAAA;IAEA,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,eAAe;KAChB;IACD,eAAY;cAEZ,oBAAC,0BAAD;KACE,SAAS;KACT,UAAU;KACV,CAAA;IACE,CAAA;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DifficultyIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/DifficultyIndicator.tsx"],"sourcesContent":["/**\n * DifficultyIndicator Component - AI Difficulty Tier Display\n *\n * Shows the current AI difficulty tier with Korean-English bilingual text\n * and color-coded visual feedback based on tier level.\n *\n * **Korean Philosophy (난이도 표시)**:\n * Provides transparent feedback to player about AI challenge level,\n * helping them understand their skill progression.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DifficultyTier } from \"../../../../../systems/ai\";\nimport {\n hexColorToCSS,\n hexToRgbaString,\n} from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface DifficultyIndicatorProps {\n /** Current difficulty tier (1-5) */\n readonly tier: DifficultyTier;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get tier display name with Korean and English\n */\nfunction getTierName(tier: DifficultyTier): {\n korean: string;\n english: string;\n} {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return { korean: \"초보\", english: \"Beginner\" };\n case DifficultyTier.NOVICE:\n return { korean: \"입문\", english: \"Novice\" };\n case DifficultyTier.INTERMEDIATE:\n return { korean: \"중급\", english: \"Intermediate\" };\n case DifficultyTier.ADVANCED:\n return { korean: \"고급\", english: \"Advanced\" };\n case DifficultyTier.EXPERT:\n return { korean: \"전문\", english: \"Expert\" };\n default:\n return { korean: \"중급\", english: \"Intermediate\" };\n }\n}\n\n/**\n * Get numeric color for difficulty tier using theme colors\n * Maps tiers to existing color scheme for consistency\n */\nfunction getTierColorValue(\n tier: DifficultyTier,\n theme: ReturnType<typeof useKoreanTheme>,\n): number {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return theme.colors.POSITIVE_GREEN; // Green - Easy\n case DifficultyTier.NOVICE:\n return theme.colors.ACCENT_GREEN; // Light Green\n case DifficultyTier.INTERMEDIATE:\n return theme.colors.ACCENT_GOLD; // Gold - Medium\n case DifficultyTier.ADVANCED:\n return theme.colors.SECONDARY_ORANGE; // Orange\n case DifficultyTier.EXPERT:\n return theme.colors.NEGATIVE_RED; // Red - Hard\n default:\n return theme.colors.ACCENT_GOLD; // Default to medium\n }\n}\n\n/**\n * DifficultyIndicator - Visual feedback for current AI difficulty tier\n *\n * Uses relative positioning for embedding in container HUDs\n */\nexport const DifficultyIndicator: React.FC<DifficultyIndicatorProps> = ({\n tier,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile });\n const tierName = getTierName(tier);\n const tierColorValue = getTierColorValue(tier, theme);\n\n // Memoize color calculations\n const tierColor = useMemo(\n () => hexColorToCSS(tierColorValue),\n [tierColorValue],\n );\n const tierBackground = useMemo(\n () => hexToRgbaString(tierColorValue, 0.13),\n [tierColorValue],\n );\n const tierBoxShadow = useMemo(\n () => hexToRgbaString(tierColorValue, 0.27),\n [tierColorValue],\n );\n\n // Responsive sizing\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"6px 10px\" : \"8px 12px\";\n\n return (\n <div\n data-testid=\"difficulty-indicator\"\n style={{\n position: \"relative\",\n padding,\n background: tierBackground, // 13% opacity background\n border: `2px solid ${tierColor}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: `${fontSize}px`,\n color: tierColor,\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n transition: \"all 0.3s ease-in-out\", // Smooth color/border transitions\n boxShadow: `0 0 8px ${tierBoxShadow}`, // Subtle glow effect (27% opacity)\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n }}\n >\n <div\n data-testid=\"difficulty-label\"\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n opacity: 0.8,\n letterSpacing: \"0.5px\",\n }}\n >\n AI Difficulty\n </div>\n <div\n data-testid=\"difficulty-tier\"\n style={{\n fontWeight: \"bold\",\n whiteSpace: \"nowrap\",\n letterSpacing: \"0.5px\",\n }}\n >\n {tierName.korean} | {tierName.english}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,SAAS,YAAY,MAGnB;
|
|
1
|
+
{"version":3,"file":"DifficultyIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/DifficultyIndicator.tsx"],"sourcesContent":["/**\n * DifficultyIndicator Component - AI Difficulty Tier Display\n *\n * Shows the current AI difficulty tier with Korean-English bilingual text\n * and color-coded visual feedback based on tier level.\n *\n * **Korean Philosophy (난이도 표시)**:\n * Provides transparent feedback to player about AI challenge level,\n * helping them understand their skill progression.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DifficultyTier } from \"../../../../../systems/ai\";\nimport {\n hexColorToCSS,\n hexToRgbaString,\n} from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface DifficultyIndicatorProps {\n /** Current difficulty tier (1-5) */\n readonly tier: DifficultyTier;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get tier display name with Korean and English\n */\nfunction getTierName(tier: DifficultyTier): {\n korean: string;\n english: string;\n} {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return { korean: \"초보\", english: \"Beginner\" };\n case DifficultyTier.NOVICE:\n return { korean: \"입문\", english: \"Novice\" };\n case DifficultyTier.INTERMEDIATE:\n return { korean: \"중급\", english: \"Intermediate\" };\n case DifficultyTier.ADVANCED:\n return { korean: \"고급\", english: \"Advanced\" };\n case DifficultyTier.EXPERT:\n return { korean: \"전문\", english: \"Expert\" };\n default:\n return { korean: \"중급\", english: \"Intermediate\" };\n }\n}\n\n/**\n * Get numeric color for difficulty tier using theme colors\n * Maps tiers to existing color scheme for consistency\n */\nfunction getTierColorValue(\n tier: DifficultyTier,\n theme: ReturnType<typeof useKoreanTheme>,\n): number {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return theme.colors.POSITIVE_GREEN; // Green - Easy\n case DifficultyTier.NOVICE:\n return theme.colors.ACCENT_GREEN; // Light Green\n case DifficultyTier.INTERMEDIATE:\n return theme.colors.ACCENT_GOLD; // Gold - Medium\n case DifficultyTier.ADVANCED:\n return theme.colors.SECONDARY_ORANGE; // Orange\n case DifficultyTier.EXPERT:\n return theme.colors.NEGATIVE_RED; // Red - Hard\n default:\n return theme.colors.ACCENT_GOLD; // Default to medium\n }\n}\n\n/**\n * DifficultyIndicator - Visual feedback for current AI difficulty tier\n *\n * Uses relative positioning for embedding in container HUDs\n */\nexport const DifficultyIndicator: React.FC<DifficultyIndicatorProps> = ({\n tier,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile });\n const tierName = getTierName(tier);\n const tierColorValue = getTierColorValue(tier, theme);\n\n // Memoize color calculations\n const tierColor = useMemo(\n () => hexColorToCSS(tierColorValue),\n [tierColorValue],\n );\n const tierBackground = useMemo(\n () => hexToRgbaString(tierColorValue, 0.13),\n [tierColorValue],\n );\n const tierBoxShadow = useMemo(\n () => hexToRgbaString(tierColorValue, 0.27),\n [tierColorValue],\n );\n\n // Responsive sizing\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"6px 10px\" : \"8px 12px\";\n\n return (\n <div\n data-testid=\"difficulty-indicator\"\n style={{\n position: \"relative\",\n padding,\n background: tierBackground, // 13% opacity background\n border: `2px solid ${tierColor}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: `${fontSize}px`,\n color: tierColor,\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n transition: \"all 0.3s ease-in-out\", // Smooth color/border transitions\n boxShadow: `0 0 8px ${tierBoxShadow}`, // Subtle glow effect (27% opacity)\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n }}\n >\n <div\n data-testid=\"difficulty-label\"\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n opacity: 0.8,\n letterSpacing: \"0.5px\",\n }}\n >\n AI Difficulty\n </div>\n <div\n data-testid=\"difficulty-tier\"\n style={{\n fontWeight: \"bold\",\n whiteSpace: \"nowrap\",\n letterSpacing: \"0.5px\",\n }}\n >\n {tierName.korean} | {tierName.english}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,SAAS,YAAY,MAGnB;CACA,QAAQ,MAAR;EACE,KAAK,eAAe,UAClB,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,QAClB,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,KAAK,eAAe,cAClB,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;EAClD,KAAK,eAAe,UAClB,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,QAClB,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,SACE,OAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;;;;;;;AAQtD,SAAS,kBACP,MACA,OACQ;CACR,QAAQ,MAAR;EACE,KAAK,eAAe,UAClB,OAAO,MAAM,OAAO;EACtB,KAAK,eAAe,QAClB,OAAO,MAAM,OAAO;EACtB,KAAK,eAAe,cAClB,OAAO,MAAM,OAAO;EACtB,KAAK,eAAe,UAClB,OAAO,MAAM,OAAO;EACtB,KAAK,eAAe,QAClB,OAAO,MAAM,OAAO;EACtB,SACE,OAAO,MAAM,OAAO;;;;;;;;AAS1B,IAAa,uBAA2D,EACtE,MACA,eACI;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS;EAAU,CAAC;CAC7E,MAAM,WAAW,YAAY,KAAK;CAClC,MAAM,iBAAiB,kBAAkB,MAAM,MAAM;CAGrD,MAAM,YAAY,cACV,cAAc,eAAe,EACnC,CAAC,eAAe,CACjB;CACD,MAAM,iBAAiB,cACf,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CACD,MAAM,gBAAgB,cACd,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CAGD,MAAM,WAAW,WAAW,KAAK;CAGjC,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,SAPU,WAAW,aAAa;GAQlC,YAAY;GACZ,QAAQ,aAAa;GACrB,cAAc;GACd,YAAY,MAAM,WAAW;GAC7B,UAAU,GAAG,SAAS;GACtB,OAAO;GACP,YAAY;GACZ,eAAe;GACf,SAAS;GACT,eAAe;GACf,YAAY;GACZ,KAAK;GACL,YAAY;GACZ,WAAW,WAAW;GACtB,OAAO;GACP,WAAW;GACZ;YArBH,CAuBE,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,WAAW,QAAQ;IAC7B,SAAS;IACT,eAAe;IAChB;aACF;GAEK,CAAA,EACN,qBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,YAAY;IACZ,YAAY;IACZ,eAAe;IAChB;aANH;IAQG,SAAS;IAAO;IAAI,SAAS;IAC1B;KACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FPSMonitor.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/FPSMonitor.tsx"],"sourcesContent":["/**\n * FPSMonitor - Real-time performance monitoring component\n *\n * Displays current FPS (frames per second) and provides performance warnings\n * when frame rate drops below target thresholds. Helps verify 60fps target\n * and identify performance bottlenecks during combat.\n *\n * @module components/combat/components/FPSMonitor\n * @category Performance Monitoring\n * @korean FPS모니터\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport { Html } from \"@react-three/drei\";\nimport React, { useRef, useState, useCallback, useEffect, useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface FPSMonitorProps {\n /** Whether to show the FPS monitor */\n readonly enabled?: boolean;\n /** FPS threshold for warning (default: 50) */\n readonly warningThreshold?: number;\n /** FPS threshold for critical warning (default: 30) */\n readonly criticalThreshold?: number;\n /** Callback when FPS drops below threshold */\n readonly onFPSDrop?: (fps: number) => void;\n /** Position from top in pixels (default: 10) */\n readonly top?: number;\n /** Position from right in pixels (default: 10) */\n readonly right?: number;\n}\n\n/**\n * FPSMonitor Component\n *\n * Monitors and displays real-time FPS during combat. Uses Three.js useFrame\n * to calculate frames per second and provides visual feedback:\n * - Green: 60+ fps (excellent)\n * - Yellow: 50-59 fps (warning)\n * - Orange: 30-49 fps (poor)\n * - Red: <30 fps (critical)\n *\n * @example\n * ```tsx\n * <FPSMonitor\n * enabled={showPerformanceStats}\n * warningThreshold={50}\n * onFPSDrop={(fps) => console.warn(`Performance drop: ${fps} fps`)}\n * />\n * ```\n */\nexport const FPSMonitor: React.FC<FPSMonitorProps> = ({\n enabled = true,\n warningThreshold = 50,\n criticalThreshold = 30,\n onFPSDrop,\n top = 10,\n right = 10,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile: false });\n const [fps, setFps] = useState(60);\n const frameCountRef = useRef(0);\n const lastTimeRef = useRef(0);\n const lastWarningTimeRef = useRef(0);\n const initializedRef = useRef(false);\n \n // Initialize performance.now() after mount to avoid impure function call during render\n useEffect(() => {\n lastTimeRef.current = performance.now();\n initializedRef.current = true;\n }, []);\n\n const updateFPS = useCallback(() => {\n // Skip FPS calculation until properly initialized\n if (!initializedRef.current) return;\n \n frameCountRef.current++;\n const currentTime = performance.now();\n const deltaTime = currentTime - lastTimeRef.current;\n\n // Update FPS every second\n if (deltaTime >= 1000) {\n const currentFps = Math.round((frameCountRef.current * 1000) / deltaTime);\n setFps(currentFps);\n \n // Trigger warning callback if FPS drops (max once per 3 seconds)\n if (onFPSDrop && currentFps < warningThreshold) {\n const timeSinceLastWarning = currentTime - lastWarningTimeRef.current;\n if (timeSinceLastWarning >= 3000) {\n onFPSDrop(currentFps);\n lastWarningTimeRef.current = currentTime;\n }\n }\n\n frameCountRef.current = 0;\n lastTimeRef.current = currentTime;\n }\n }, [onFPSDrop, warningThreshold]);\n\n useFrame(() => {\n if (enabled) {\n updateFPS();\n }\n });\n\n // Calculate color as hex string based on FPS (memoized to avoid repeated conversions)\n // Note: Returns a 24-bit RGB hex color with no alpha channel.\n // The conversion below is intentionally hex-only; if alpha support is added\n // in the future, this logic will need to be updated accordingly.\n const colorHex = useMemo(() => {\n let color: number;\n if (fps >= 60) {\n color = theme.colors.ACCENT_GOLD; // Excellent\n } else if (fps >= warningThreshold) {\n color = 0xffff00; // Warning (yellow)\n } else if (fps >= criticalThreshold) {\n color = 0xff8800; // Poor (orange)\n } else {\n color = theme.colors.PRIMARY_RED; // Critical (red)\n }\n return `#${color.toString(16).padStart(6, \"0\")}`;\n }, [fps, warningThreshold, criticalThreshold, theme.colors.ACCENT_GOLD, theme.colors.PRIMARY_RED]);\n\n const getStatus = () => {\n if (fps >= 60) return \"우수 | Excellent\";\n if (fps >= warningThreshold) return \"경고 | Warning\";\n if (fps >= criticalThreshold) return \"저하 | Poor\";\n return \"심각 | Critical\";\n };\n\n if (!enabled) {\n return null;\n }\n\n return (\n <Html fullscreen>\n <div\n data-testid=\"fps-monitor\"\n style={{\n position: \"absolute\",\n top: `${top}px`,\n right: `${right}px`,\n padding: \"8px 12px\",\n background: \"rgba(0, 0, 0, 0.8)\",\n border: `2px solid ${colorHex}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: \"12px\",\n color: colorHex,\n pointerEvents: \"none\",\n zIndex: 9999,\n }}\n >\n <div style={{ fontWeight: \"bold\", marginBottom: \"2px\" }}>\n FPS: {fps}\n </div>\n <div style={{ fontSize: \"10px\", opacity: 0.9 }}>\n {getStatus()}\n </div>\n </div>\n </Html>\n );\n};\n\nexport default FPSMonitor;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,cAAyC,EACpD,UAAU,MACV,mBAAmB,IACnB,oBAAoB,IACpB,WACA,MAAM,IACN,QAAQ,SACJ;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS,UAAU;EAAO,CAAC;CACpF,MAAM,CAAC,KAAK,UAAU,SAAS,GAAG;CAClC,MAAM,gBAAgB,OAAO,EAAE;CAC/B,MAAM,cAAc,OAAO,EAAE;CAC7B,MAAM,qBAAqB,OAAO,EAAE;CACpC,MAAM,iBAAiB,OAAO,MAAM;
|
|
1
|
+
{"version":3,"file":"FPSMonitor.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/FPSMonitor.tsx"],"sourcesContent":["/**\n * FPSMonitor - Real-time performance monitoring component\n *\n * Displays current FPS (frames per second) and provides performance warnings\n * when frame rate drops below target thresholds. Helps verify 60fps target\n * and identify performance bottlenecks during combat.\n *\n * @module components/combat/components/FPSMonitor\n * @category Performance Monitoring\n * @korean FPS모니터\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport { Html } from \"@react-three/drei\";\nimport React, { useRef, useState, useCallback, useEffect, useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface FPSMonitorProps {\n /** Whether to show the FPS monitor */\n readonly enabled?: boolean;\n /** FPS threshold for warning (default: 50) */\n readonly warningThreshold?: number;\n /** FPS threshold for critical warning (default: 30) */\n readonly criticalThreshold?: number;\n /** Callback when FPS drops below threshold */\n readonly onFPSDrop?: (fps: number) => void;\n /** Position from top in pixels (default: 10) */\n readonly top?: number;\n /** Position from right in pixels (default: 10) */\n readonly right?: number;\n}\n\n/**\n * FPSMonitor Component\n *\n * Monitors and displays real-time FPS during combat. Uses Three.js useFrame\n * to calculate frames per second and provides visual feedback:\n * - Green: 60+ fps (excellent)\n * - Yellow: 50-59 fps (warning)\n * - Orange: 30-49 fps (poor)\n * - Red: <30 fps (critical)\n *\n * @example\n * ```tsx\n * <FPSMonitor\n * enabled={showPerformanceStats}\n * warningThreshold={50}\n * onFPSDrop={(fps) => console.warn(`Performance drop: ${fps} fps`)}\n * />\n * ```\n */\nexport const FPSMonitor: React.FC<FPSMonitorProps> = ({\n enabled = true,\n warningThreshold = 50,\n criticalThreshold = 30,\n onFPSDrop,\n top = 10,\n right = 10,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile: false });\n const [fps, setFps] = useState(60);\n const frameCountRef = useRef(0);\n const lastTimeRef = useRef(0);\n const lastWarningTimeRef = useRef(0);\n const initializedRef = useRef(false);\n \n // Initialize performance.now() after mount to avoid impure function call during render\n useEffect(() => {\n lastTimeRef.current = performance.now();\n initializedRef.current = true;\n }, []);\n\n const updateFPS = useCallback(() => {\n // Skip FPS calculation until properly initialized\n if (!initializedRef.current) return;\n \n frameCountRef.current++;\n const currentTime = performance.now();\n const deltaTime = currentTime - lastTimeRef.current;\n\n // Update FPS every second\n if (deltaTime >= 1000) {\n const currentFps = Math.round((frameCountRef.current * 1000) / deltaTime);\n setFps(currentFps);\n \n // Trigger warning callback if FPS drops (max once per 3 seconds)\n if (onFPSDrop && currentFps < warningThreshold) {\n const timeSinceLastWarning = currentTime - lastWarningTimeRef.current;\n if (timeSinceLastWarning >= 3000) {\n onFPSDrop(currentFps);\n lastWarningTimeRef.current = currentTime;\n }\n }\n\n frameCountRef.current = 0;\n lastTimeRef.current = currentTime;\n }\n }, [onFPSDrop, warningThreshold]);\n\n useFrame(() => {\n if (enabled) {\n updateFPS();\n }\n });\n\n // Calculate color as hex string based on FPS (memoized to avoid repeated conversions)\n // Note: Returns a 24-bit RGB hex color with no alpha channel.\n // The conversion below is intentionally hex-only; if alpha support is added\n // in the future, this logic will need to be updated accordingly.\n const colorHex = useMemo(() => {\n let color: number;\n if (fps >= 60) {\n color = theme.colors.ACCENT_GOLD; // Excellent\n } else if (fps >= warningThreshold) {\n color = 0xffff00; // Warning (yellow)\n } else if (fps >= criticalThreshold) {\n color = 0xff8800; // Poor (orange)\n } else {\n color = theme.colors.PRIMARY_RED; // Critical (red)\n }\n return `#${color.toString(16).padStart(6, \"0\")}`;\n }, [fps, warningThreshold, criticalThreshold, theme.colors.ACCENT_GOLD, theme.colors.PRIMARY_RED]);\n\n const getStatus = () => {\n if (fps >= 60) return \"우수 | Excellent\";\n if (fps >= warningThreshold) return \"경고 | Warning\";\n if (fps >= criticalThreshold) return \"저하 | Poor\";\n return \"심각 | Critical\";\n };\n\n if (!enabled) {\n return null;\n }\n\n return (\n <Html fullscreen>\n <div\n data-testid=\"fps-monitor\"\n style={{\n position: \"absolute\",\n top: `${top}px`,\n right: `${right}px`,\n padding: \"8px 12px\",\n background: \"rgba(0, 0, 0, 0.8)\",\n border: `2px solid ${colorHex}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: \"12px\",\n color: colorHex,\n pointerEvents: \"none\",\n zIndex: 9999,\n }}\n >\n <div style={{ fontWeight: \"bold\", marginBottom: \"2px\" }}>\n FPS: {fps}\n </div>\n <div style={{ fontSize: \"10px\", opacity: 0.9 }}>\n {getStatus()}\n </div>\n </div>\n </Html>\n );\n};\n\nexport default FPSMonitor;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,cAAyC,EACpD,UAAU,MACV,mBAAmB,IACnB,oBAAoB,IACpB,WACA,MAAM,IACN,QAAQ,SACJ;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS,UAAU;EAAO,CAAC;CACpF,MAAM,CAAC,KAAK,UAAU,SAAS,GAAG;CAClC,MAAM,gBAAgB,OAAO,EAAE;CAC/B,MAAM,cAAc,OAAO,EAAE;CAC7B,MAAM,qBAAqB,OAAO,EAAE;CACpC,MAAM,iBAAiB,OAAO,MAAM;CAGpC,gBAAgB;EACd,YAAY,UAAU,YAAY,KAAK;EACvC,eAAe,UAAU;IACxB,EAAE,CAAC;CAEN,MAAM,YAAY,kBAAkB;EAElC,IAAI,CAAC,eAAe,SAAS;EAE7B,cAAc;EACd,MAAM,cAAc,YAAY,KAAK;EACrC,MAAM,YAAY,cAAc,YAAY;EAG5C,IAAI,aAAa,KAAM;GACrB,MAAM,aAAa,KAAK,MAAO,cAAc,UAAU,MAAQ,UAAU;GACzE,OAAO,WAAW;GAGlB,IAAI,aAAa,aAAa;QACC,cAAc,mBAAmB,WAClC,KAAM;KAChC,UAAU,WAAW;KACrB,mBAAmB,UAAU;;;GAIjC,cAAc,UAAU;GACxB,YAAY,UAAU;;IAEvB,CAAC,WAAW,iBAAiB,CAAC;CAEjC,eAAe;EACb,IAAI,SACF,WAAW;GAEb;CAMF,MAAM,WAAW,cAAc;EAC7B,IAAI;EACJ,IAAI,OAAO,IACT,QAAQ,MAAM,OAAO;OAChB,IAAI,OAAO,kBAChB,QAAQ;OACH,IAAI,OAAO,mBAChB,QAAQ;OAER,QAAQ,MAAM,OAAO;EAEvB,OAAO,IAAI,MAAM,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;IAC7C;EAAC;EAAK;EAAkB;EAAmB,MAAM,OAAO;EAAa,MAAM,OAAO;EAAY,CAAC;CAElG,MAAM,kBAAkB;EACtB,IAAI,OAAO,IAAI,OAAO;EACtB,IAAI,OAAO,kBAAkB,OAAO;EACpC,IAAI,OAAO,mBAAmB,OAAO;EACrC,OAAO;;CAGT,IAAI,CAAC,SACH,OAAO;CAGT,OACE,oBAAC,MAAD;EAAM,YAAA;YACJ,qBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU;IACV,KAAK,GAAG,IAAI;IACZ,OAAO,GAAG,MAAM;IAChB,SAAS;IACT,YAAY;IACZ,QAAQ,aAAa;IACrB,cAAc;IACd,YAAY,MAAM,WAAW;IAC7B,UAAU;IACV,OAAO;IACP,eAAe;IACf,QAAQ;IACT;aAfH,CAiBE,qBAAC,OAAD;IAAK,OAAO;KAAE,YAAY;KAAQ,cAAc;KAAO;cAAvD,CAAyD,SACjD,IACF;OACN,oBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAQ,SAAS;KAAK;cAC3C,WAAW;IACR,CAAA,CACF;;EACD,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileControlsWrapper.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/MobileControlsWrapper.tsx"],"sourcesContent":["/**\n * MobileControlsWrapper - Mobile touch controls container\n *\n * Wraps all mobile-specific touch controls including:\n * - Virtual D-Pad for movement\n * - Action buttons (attack/block)\n * - Stance wheel for trigram stance selection\n * - Gesture recognizer for swipe actions\n *\n * PRIORITY SUPPORT: Optimized for high-end mobile devices (Super HD, 2K+)\n * - Enhanced touch targets (56px on Super HD vs 48px standard)\n * - Larger fonts and better spacing on high-resolution displays\n * - Full feature parity with desktop experience\n *\n * Integrated with:\n * - CombatScreen3D: Full combat gameplay with mobile controls\n * - TrainingScreen3D: Training mode with mobile controls\n * - deviceDetection.ts: Robust mobile detection (handles 2712x1220 Super HD)\n * - mobileUIUtils.ts: Touch target and font sizing utilities\n *\n * Only rendered on mobile devices (isMobile=true)\n *\n * @module components/combat/components/MobileControlsWrapper\n * @category Combat UI\n * @korean 모바일컨트롤래퍼\n */\n\nimport React from \"react\";\nimport {\n ActionButtons,\n GestureRecognizerPure,\n StanceWheelPure,\n VirtualDPad,\n} from \"../../../../shared/mobile\";\nimport { ButtonEventType } from \"../../../../shared/mobile/ActionButtons\";\nimport { Direction, DPadEventType } from \"../../../../shared/mobile/VirtualDPad\";\nimport { GestureEvent } from \"../../../../../hooks/useTouchControls\";\nimport { getMobileControlsBottom } from \"../../../../../types/constants/layout\";\n\nexport interface MobileControlsWrapperProps {\n /** Whether mobile controls are enabled */\n readonly enabled: boolean;\n /** Current stance index (0-7) */\n readonly currentStanceIndex: number;\n /** Whether stance wheel is expanded */\n readonly stanceWheelExpanded: boolean;\n /** D-Pad movement handler */\n readonly onMove: (direction: Direction | null, eventType: DPadEventType) => void;\n /** Attack button handler */\n readonly onAttack: () => void;\n /** Block button handler */\n readonly onBlock: (eventType: ButtonEventType) => void;\n /** Stance change handler */\n readonly onStanceChange: (stanceIndex: number) => void;\n /** Stance wheel toggle handler */\n readonly onStanceWheelToggle: () => void;\n /** Gesture handler */\n readonly onGesture: (gesture: GestureEvent) => void;\n /**\n * Current viewport height in pixels.\n *\n * Forwarded to `getMobileControlsBottom()` so the D-Pad / ActionButtons\n * drop closer to the bottom edge on short viewports (e.g. landscape\n * phones < 500 px tall). When omitted, the default 200 px band is used.\n */\n readonly viewportHeight?: number;\n}\n\n/**\n * MobileControlsWrapper - Container for all mobile touch controls\n *\n * Provides virtual controls for mobile combat gameplay:\n * - Virtual D-Pad (bottom-left) for 8-direction movement\n * - Action Buttons (bottom-right) for attack/block\n * - Stance Wheel (right-center) for trigram stance selection\n * - Gesture Recognition for swipe-based actions\n *\n * Positioning Strategy:\n * - All controls positioned using centralized layout constants\n * - D-Pad and ActionButtons use getMobileControlsBottom() for consistency\n * - Z-index: MOBILE_CONTROLS (50) to ensure visibility over HUD elements\n *\n * @example\n * ```tsx\n * <MobileControlsWrapper\n * enabled={mobileControlsEnabled}\n * currentStanceIndex={currentStanceIndex}\n * stanceWheelExpanded={stanceWheelExpanded}\n * onMove={handleMobileMove}\n * onAttack={handleMobileAttack}\n * onBlock={handleMobileBlock}\n * onStanceChange={handleMobileStanceChange}\n * onStanceWheelToggle={() => setStanceWheelExpanded(!stanceWheelExpanded)}\n * onGesture={handleMobileGesture}\n * />\n * ```\n */\nexport const MobileControlsWrapper: React.FC<MobileControlsWrapperProps> = ({\n enabled,\n currentStanceIndex,\n stanceWheelExpanded,\n onMove,\n onAttack,\n onBlock,\n onStanceChange,\n onStanceWheelToggle,\n onGesture,\n viewportHeight,\n}) => {\n const controlsBottom = getMobileControlsBottom(viewportHeight);\n return (\n <>\n {/* Virtual D-Pad - Bottom-left for movement */}\n {/* Positioned using centralized constant for consistency */}\n <VirtualDPad\n onMove={onMove}\n disabled={!enabled}\n opacity={0.8}\n bottom={controlsBottom}\n />\n\n {/* Action Buttons - Bottom-right for attack/block */}\n {/* Positioned using centralized constant for consistency */}\n <ActionButtons\n onAttack={onAttack}\n onBlock={onBlock}\n disabled={!enabled}\n opacity={0.8}\n bottom={controlsBottom}\n />\n\n {/* Stance Wheel - Right-center for trigram stance selection */}\n <StanceWheelPure\n currentStance={currentStanceIndex}\n onStanceChange={onStanceChange}\n expanded={stanceWheelExpanded}\n onToggle={onStanceWheelToggle}\n disabled={!enabled}\n opacity={0.8}\n />\n\n {/* Gesture Recognizer - Full-screen swipe detection */}\n <GestureRecognizerPure\n onGesture={onGesture}\n enabled={enabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n );\n};\n\nexport default MobileControlsWrapper;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,IAAa,yBAA+D,EAC1E,SACA,oBACA,qBACA,QACA,UACA,SACA,gBACA,qBACA,WACA,qBACI;CACJ,MAAM,iBAAiB,wBAAwB,eAAe;
|
|
1
|
+
{"version":3,"file":"MobileControlsWrapper.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/MobileControlsWrapper.tsx"],"sourcesContent":["/**\n * MobileControlsWrapper - Mobile touch controls container\n *\n * Wraps all mobile-specific touch controls including:\n * - Virtual D-Pad for movement\n * - Action buttons (attack/block)\n * - Stance wheel for trigram stance selection\n * - Gesture recognizer for swipe actions\n *\n * PRIORITY SUPPORT: Optimized for high-end mobile devices (Super HD, 2K+)\n * - Enhanced touch targets (56px on Super HD vs 48px standard)\n * - Larger fonts and better spacing on high-resolution displays\n * - Full feature parity with desktop experience\n *\n * Integrated with:\n * - CombatScreen3D: Full combat gameplay with mobile controls\n * - TrainingScreen3D: Training mode with mobile controls\n * - deviceDetection.ts: Robust mobile detection (handles 2712x1220 Super HD)\n * - mobileUIUtils.ts: Touch target and font sizing utilities\n *\n * Only rendered on mobile devices (isMobile=true)\n *\n * @module components/combat/components/MobileControlsWrapper\n * @category Combat UI\n * @korean 모바일컨트롤래퍼\n */\n\nimport React from \"react\";\nimport {\n ActionButtons,\n GestureRecognizerPure,\n StanceWheelPure,\n VirtualDPad,\n} from \"../../../../shared/mobile\";\nimport { ButtonEventType } from \"../../../../shared/mobile/ActionButtons\";\nimport { Direction, DPadEventType } from \"../../../../shared/mobile/VirtualDPad\";\nimport { GestureEvent } from \"../../../../../hooks/useTouchControls\";\nimport { getMobileControlsBottom } from \"../../../../../types/constants/layout\";\n\nexport interface MobileControlsWrapperProps {\n /** Whether mobile controls are enabled */\n readonly enabled: boolean;\n /** Current stance index (0-7) */\n readonly currentStanceIndex: number;\n /** Whether stance wheel is expanded */\n readonly stanceWheelExpanded: boolean;\n /** D-Pad movement handler */\n readonly onMove: (direction: Direction | null, eventType: DPadEventType) => void;\n /** Attack button handler */\n readonly onAttack: () => void;\n /** Block button handler */\n readonly onBlock: (eventType: ButtonEventType) => void;\n /** Stance change handler */\n readonly onStanceChange: (stanceIndex: number) => void;\n /** Stance wheel toggle handler */\n readonly onStanceWheelToggle: () => void;\n /** Gesture handler */\n readonly onGesture: (gesture: GestureEvent) => void;\n /**\n * Current viewport height in pixels.\n *\n * Forwarded to `getMobileControlsBottom()` so the D-Pad / ActionButtons\n * drop closer to the bottom edge on short viewports (e.g. landscape\n * phones < 500 px tall). When omitted, the default 200 px band is used.\n */\n readonly viewportHeight?: number;\n}\n\n/**\n * MobileControlsWrapper - Container for all mobile touch controls\n *\n * Provides virtual controls for mobile combat gameplay:\n * - Virtual D-Pad (bottom-left) for 8-direction movement\n * - Action Buttons (bottom-right) for attack/block\n * - Stance Wheel (right-center) for trigram stance selection\n * - Gesture Recognition for swipe-based actions\n *\n * Positioning Strategy:\n * - All controls positioned using centralized layout constants\n * - D-Pad and ActionButtons use getMobileControlsBottom() for consistency\n * - Z-index: MOBILE_CONTROLS (50) to ensure visibility over HUD elements\n *\n * @example\n * ```tsx\n * <MobileControlsWrapper\n * enabled={mobileControlsEnabled}\n * currentStanceIndex={currentStanceIndex}\n * stanceWheelExpanded={stanceWheelExpanded}\n * onMove={handleMobileMove}\n * onAttack={handleMobileAttack}\n * onBlock={handleMobileBlock}\n * onStanceChange={handleMobileStanceChange}\n * onStanceWheelToggle={() => setStanceWheelExpanded(!stanceWheelExpanded)}\n * onGesture={handleMobileGesture}\n * />\n * ```\n */\nexport const MobileControlsWrapper: React.FC<MobileControlsWrapperProps> = ({\n enabled,\n currentStanceIndex,\n stanceWheelExpanded,\n onMove,\n onAttack,\n onBlock,\n onStanceChange,\n onStanceWheelToggle,\n onGesture,\n viewportHeight,\n}) => {\n const controlsBottom = getMobileControlsBottom(viewportHeight);\n return (\n <>\n {/* Virtual D-Pad - Bottom-left for movement */}\n {/* Positioned using centralized constant for consistency */}\n <VirtualDPad\n onMove={onMove}\n disabled={!enabled}\n opacity={0.8}\n bottom={controlsBottom}\n />\n\n {/* Action Buttons - Bottom-right for attack/block */}\n {/* Positioned using centralized constant for consistency */}\n <ActionButtons\n onAttack={onAttack}\n onBlock={onBlock}\n disabled={!enabled}\n opacity={0.8}\n bottom={controlsBottom}\n />\n\n {/* Stance Wheel - Right-center for trigram stance selection */}\n <StanceWheelPure\n currentStance={currentStanceIndex}\n onStanceChange={onStanceChange}\n expanded={stanceWheelExpanded}\n onToggle={onStanceWheelToggle}\n disabled={!enabled}\n opacity={0.8}\n />\n\n {/* Gesture Recognizer - Full-screen swipe detection */}\n <GestureRecognizerPure\n onGesture={onGesture}\n enabled={enabled}\n showFeedback={true}\n minSwipeDistance={50}\n />\n </>\n );\n};\n\nexport default MobileControlsWrapper;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,IAAa,yBAA+D,EAC1E,SACA,oBACA,qBACA,QACA,UACA,SACA,gBACA,qBACA,WACA,qBACI;CACJ,MAAM,iBAAiB,wBAAwB,eAAe;CAC9D,OACE,qBAAA,UAAA,EAAA,UAAA;EAGE,oBAAC,aAAD;GACU;GACR,UAAU,CAAC;GACX,SAAS;GACT,QAAQ;GACR,CAAA;EAIF,oBAAC,eAAD;GACY;GACD;GACT,UAAU,CAAC;GACX,SAAS;GACT,QAAQ;GACR,CAAA;EAGF,oBAAC,iBAAD;GACE,eAAe;GACC;GAChB,UAAU;GACV,UAAU;GACV,UAAU,CAAC;GACX,SAAS;GACT,CAAA;EAGF,oBAAC,uBAAD;GACa;GACF;GACT,cAAc;GACd,kBAAkB;GAClB,CAAA;EACD,EAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayerStateOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/PlayerStateOverlayHtml.tsx"],"sourcesContent":["/**\n * PlayerStateOverlayHtml Component - Unified player state visual indicators\n * \n * Combines all player state visual effects into a single overlay:\n * - Pain vignette\n * - Balance indicator\n * - Consciousness blur\n * - Blood loss warning\n * - Stamina warning\n * \n * @module components/combat/PlayerStateOverlayHtml\n * @category Combat UI\n * @korean 플레이어상태오버레이\n */\n\nimport React from \"react\";\nimport { PainVignette } from \"../effects/PainVignette\";\nimport { BalanceIndicator } from \"../indicators/BalanceIndicator\";\nimport { ConsciousnessBlur } from \"../effects/ConsciousnessBlur\";\nimport { BloodLossOverlayHtml } from \"../effects/BloodLossOverlayHtml\";\nimport { StaminaWarning } from \"../indicators/StaminaWarning\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface PlayerStateOverlayProps {\n /**\n * Current pain level (0-100)\n * @korean 통증\n */\n readonly pain: number;\n\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right')\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Current consciousness level (0-100)\n * @korean 의식\n */\n readonly consciousness: number;\n\n /**\n * Current blood loss (0-100, optional)\n * @korean 출혈\n */\n readonly bloodLoss?: number;\n\n /**\n * Current stamina (0-100)\n * @korean 체력\n */\n readonly stamina: number;\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n\n /**\n * Multiplier applied to every fullscreen effect's visual weight (0.0-1.0).\n *\n * Used by the parent screen to attenuate fullscreen vignette / blur / flash\n * effects when the 3D arena is already visually compressed (e.g. portrait\n * mobile, where the arena is rendered in a 3:4 aspect ratio and consumes\n * the majority of the viewport height). Default is `1.0` (no attenuation).\n *\n * Cascades to `PainVignette`, `ConsciousnessBlur`, `BloodLossOverlayHtml`\n * and `StaminaWarning`. Does not affect `BalanceIndicator`, which is an\n * informational indicator rather than a fullscreen overlay.\n *\n * @korean 효과강도배수\n */\n readonly intensityScale?: number;\n}\n\n/**\n * PlayerStateOverlayHtml - Unified visual effects for player state\n * \n * Combines all player state visual indicators into a single component\n * with optimal performance and consistent rendering. All effects use\n * smooth 0.5s transitions and are optimized for 60fps.\n *\n * Optimized with React.memo for performance:\n * - Prevents re-renders when props haven't changed\n * - Custom comparison function for precise control\n * - Reduces DOM updates for 60fps target\n * \n * @example\n * ```tsx\n * <PlayerStateOverlayHtml\n * pain={65}\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * consciousness={80}\n * bloodLoss={45}\n * stamina={15}\n * isMobile={false}\n * />\n * ```\n */\nexport const PlayerStateOverlayHtml = React.memo<PlayerStateOverlayProps>(\n ({\n pain,\n balanceState,\n position,\n consciousness,\n bloodLoss = 0,\n stamina,\n isMobile,\n intensityScale = 1,\n }) => {\n return (\n <div data-testid=\"player-state-overlay\" style={{ display: 'contents' }}>\n {/* Pain vignette - shows when pain >= 5 (see PainVignette.tsx) */}\n <PainVignette\n pain={pain}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Balance indicator - always visible, color-coded by state (see BalanceIndicator.tsx) */}\n <BalanceIndicator\n balanceState={balanceState}\n position={position}\n isMobile={isMobile}\n />\n\n {/* Consciousness blur - shows when consciousness <= 90 (see ConsciousnessBlur.tsx) */}\n <ConsciousnessBlur\n consciousness={consciousness}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Blood loss warning - pulses when bloodLoss >= 50 (see BloodLossOverlayHtml.tsx) */}\n <BloodLossOverlayHtml\n bloodLoss={bloodLoss}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Stamina warning - flashes when stamina < 20 (see StaminaWarning.tsx) */}\n <StaminaWarning\n stamina={stamina}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n </div>\n );\n },\n (prevProps, nextProps) => {\n // Custom comparison for optimal re-render prevention\n // Only re-render if any state value actually changed\n return (\n prevProps.pain === nextProps.pain &&\n prevProps.balanceState === nextProps.balanceState &&\n prevProps.position === nextProps.position &&\n prevProps.consciousness === nextProps.consciousness &&\n prevProps.bloodLoss === nextProps.bloodLoss &&\n prevProps.stamina === nextProps.stamina &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.intensityScale === nextProps.intensityScale\n );\n },\n);\n\nPlayerStateOverlayHtml.displayName = \"PlayerStateOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4GA,IAAa,yBAAyB,MAAM,MACzC,EACC,MACA,cACA,UACA,eACA,YAAY,GACZ,SACA,UACA,iBAAiB,QACb;
|
|
1
|
+
{"version":3,"file":"PlayerStateOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/PlayerStateOverlayHtml.tsx"],"sourcesContent":["/**\n * PlayerStateOverlayHtml Component - Unified player state visual indicators\n * \n * Combines all player state visual effects into a single overlay:\n * - Pain vignette\n * - Balance indicator\n * - Consciousness blur\n * - Blood loss warning\n * - Stamina warning\n * \n * @module components/combat/PlayerStateOverlayHtml\n * @category Combat UI\n * @korean 플레이어상태오버레이\n */\n\nimport React from \"react\";\nimport { PainVignette } from \"../effects/PainVignette\";\nimport { BalanceIndicator } from \"../indicators/BalanceIndicator\";\nimport { ConsciousnessBlur } from \"../effects/ConsciousnessBlur\";\nimport { BloodLossOverlayHtml } from \"../effects/BloodLossOverlayHtml\";\nimport { StaminaWarning } from \"../indicators/StaminaWarning\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface PlayerStateOverlayProps {\n /**\n * Current pain level (0-100)\n * @korean 통증\n */\n readonly pain: number;\n\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right')\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Current consciousness level (0-100)\n * @korean 의식\n */\n readonly consciousness: number;\n\n /**\n * Current blood loss (0-100, optional)\n * @korean 출혈\n */\n readonly bloodLoss?: number;\n\n /**\n * Current stamina (0-100)\n * @korean 체력\n */\n readonly stamina: number;\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n\n /**\n * Multiplier applied to every fullscreen effect's visual weight (0.0-1.0).\n *\n * Used by the parent screen to attenuate fullscreen vignette / blur / flash\n * effects when the 3D arena is already visually compressed (e.g. portrait\n * mobile, where the arena is rendered in a 3:4 aspect ratio and consumes\n * the majority of the viewport height). Default is `1.0` (no attenuation).\n *\n * Cascades to `PainVignette`, `ConsciousnessBlur`, `BloodLossOverlayHtml`\n * and `StaminaWarning`. Does not affect `BalanceIndicator`, which is an\n * informational indicator rather than a fullscreen overlay.\n *\n * @korean 효과강도배수\n */\n readonly intensityScale?: number;\n}\n\n/**\n * PlayerStateOverlayHtml - Unified visual effects for player state\n * \n * Combines all player state visual indicators into a single component\n * with optimal performance and consistent rendering. All effects use\n * smooth 0.5s transitions and are optimized for 60fps.\n *\n * Optimized with React.memo for performance:\n * - Prevents re-renders when props haven't changed\n * - Custom comparison function for precise control\n * - Reduces DOM updates for 60fps target\n * \n * @example\n * ```tsx\n * <PlayerStateOverlayHtml\n * pain={65}\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * consciousness={80}\n * bloodLoss={45}\n * stamina={15}\n * isMobile={false}\n * />\n * ```\n */\nexport const PlayerStateOverlayHtml = React.memo<PlayerStateOverlayProps>(\n ({\n pain,\n balanceState,\n position,\n consciousness,\n bloodLoss = 0,\n stamina,\n isMobile,\n intensityScale = 1,\n }) => {\n return (\n <div data-testid=\"player-state-overlay\" style={{ display: 'contents' }}>\n {/* Pain vignette - shows when pain >= 5 (see PainVignette.tsx) */}\n <PainVignette\n pain={pain}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Balance indicator - always visible, color-coded by state (see BalanceIndicator.tsx) */}\n <BalanceIndicator\n balanceState={balanceState}\n position={position}\n isMobile={isMobile}\n />\n\n {/* Consciousness blur - shows when consciousness <= 90 (see ConsciousnessBlur.tsx) */}\n <ConsciousnessBlur\n consciousness={consciousness}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Blood loss warning - pulses when bloodLoss >= 50 (see BloodLossOverlayHtml.tsx) */}\n <BloodLossOverlayHtml\n bloodLoss={bloodLoss}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n\n {/* Stamina warning - flashes when stamina < 20 (see StaminaWarning.tsx) */}\n <StaminaWarning\n stamina={stamina}\n isMobile={isMobile}\n intensityScale={intensityScale}\n />\n </div>\n );\n },\n (prevProps, nextProps) => {\n // Custom comparison for optimal re-render prevention\n // Only re-render if any state value actually changed\n return (\n prevProps.pain === nextProps.pain &&\n prevProps.balanceState === nextProps.balanceState &&\n prevProps.position === nextProps.position &&\n prevProps.consciousness === nextProps.consciousness &&\n prevProps.bloodLoss === nextProps.bloodLoss &&\n prevProps.stamina === nextProps.stamina &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.intensityScale === nextProps.intensityScale\n );\n },\n);\n\nPlayerStateOverlayHtml.displayName = \"PlayerStateOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4GA,IAAa,yBAAyB,MAAM,MACzC,EACC,MACA,cACA,UACA,eACA,YAAY,GACZ,SACA,UACA,iBAAiB,QACb;CACJ,OACE,qBAAC,OAAD;EAAK,eAAY;EAAuB,OAAO,EAAE,SAAS,YAAY;YAAtE;GAEE,oBAAC,cAAD;IACQ;IACI;IACM;IAChB,CAAA;GAGF,oBAAC,kBAAD;IACgB;IACJ;IACA;IACV,CAAA;GAGF,oBAAC,mBAAD;IACiB;IACL;IACM;IAChB,CAAA;GAGF,oBAAC,sBAAD;IACa;IACD;IACM;IAChB,CAAA;GAGF,oBAAC,gBAAD;IACW;IACC;IACM;IAChB,CAAA;GACE;;IAGT,WAAW,cAAc;CAGxB,OACE,UAAU,SAAS,UAAU,QAC7B,UAAU,iBAAiB,UAAU,gBACrC,UAAU,aAAa,UAAU,YACjC,UAAU,kBAAkB,UAAU,iBACtC,UAAU,cAAc,UAAU,aAClC,UAAU,YAAY,UAAU,WAChC,UAAU,aAAa,UAAU,YACjC,UAAU,mBAAmB,UAAU;EAG5C;AAED,uBAAuB,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/indicators/BalanceIndicator.tsx"],"sourcesContent":["/**\n * BalanceIndicator Component - Visual indicator for player balance state\n *\n * Displays a color-coded border around the player representing their\n * combat balance state: READY (green), SHAKEN (yellow), VULNERABLE (orange), HELPLESS (red).\n *\n * NOTE: This component is rendered OUTSIDE the Canvas as part of the HTML overlay.\n * It does NOT use Html from drei - it's a standard React component.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/BalanceIndicator\n * @category Combat UI\n * @korean 균형표시기\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface BalanceIndicatorProps {\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right' side of screen)\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Mobile responsive mode (thinner borders)\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n}\n\n/**\n * Get color for balance state using theme colors\n */\nfunction getBalanceColor(theme: ReturnType<typeof useKoreanTheme>, state: BalanceState): number {\n switch (state) {\n case \"READY\":\n return theme.colors.POSITIVE_GREEN; // 🟢 Green\n case \"SHAKEN\":\n return theme.colors.WARNING_YELLOW; // 🟡 Yellow\n case \"VULNERABLE\":\n return theme.colors.WARNING_ORANGE; // 🟠 Orange\n case \"HELPLESS\":\n return theme.colors.ACCENT_RED; // 🔴 Red\n }\n}\n\n/**\n * Get Korean label for balance state\n */\nfunction getBalanceLabel(state: BalanceState): {\n korean: string;\n english: string;\n} {\n switch (state) {\n case \"READY\":\n return { korean: \"준비완료\", english: \"READY\" };\n case \"SHAKEN\":\n return { korean: \"동요상태\", english: \"SHAKEN\" };\n case \"VULNERABLE\":\n return { korean: \"취약상태\", english: \"VULNERABLE\" };\n case \"HELPLESS\":\n return { korean: \"무력상태\", english: \"HELPLESS\" };\n }\n}\n\n/**\n * BalanceIndicator - Color-coded border indicator for player balance state\n *\n * Renders a border around the player HUD area with color matching the\n * current balance state. Uses smooth transitions for state changes.\n * Uses useKoreanTheme for consistent color scheme.\n *\n * @example\n * ```tsx\n * <BalanceIndicator\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicator: React.FC<BalanceIndicatorProps> = ({\n balanceState,\n position,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n \n const indicatorStyle = useMemo(() => {\n const color = getBalanceColor(theme, balanceState);\n const colorHex = `#${color.toString(16).padStart(6, \"0\")}`;\n\n // Mobile uses thinner border\n const borderWidth = isMobile ? \"3px\" : \"4px\";\n\n // Position based on player side\n const isLeft = position === \"left\";\n\n return {\n position: \"absolute\" as const,\n top: isMobile ? \"8px\" : \"12px\",\n left: isLeft ? (isMobile ? \"8px\" : \"12px\") : \"auto\",\n right: isLeft ? \"auto\" : isMobile ? \"8px\" : \"12px\",\n width: isMobile ? \"180px\" : \"220px\",\n height: isMobile ? \"80px\" : \"100px\",\n border: `${borderWidth} solid ${colorHex}`,\n borderRadius: \"8px\",\n boxShadow: `0 0 12px ${colorHex}`,\n pointerEvents: \"none\" as const,\n transition: \"border-color 0.5s ease-out, box-shadow 0.5s ease-out\",\n zIndex: 90, // Below HUD text but above game content\n };\n }, [balanceState, position, isMobile, theme]);\n\n const label = useMemo(() => getBalanceLabel(balanceState), [balanceState]);\n\n return (\n <div\n data-testid={`balance-indicator-${position}`}\n style={indicatorStyle}\n aria-label={`${label.korean} | ${label.english}`}\n role=\"status\"\n aria-live=\"polite\"\n />\n );\n};\n\nBalanceIndicator.displayName = \"BalanceIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAS,gBAAgB,OAA0C,OAA6B;
|
|
1
|
+
{"version":3,"file":"BalanceIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/indicators/BalanceIndicator.tsx"],"sourcesContent":["/**\n * BalanceIndicator Component - Visual indicator for player balance state\n *\n * Displays a color-coded border around the player representing their\n * combat balance state: READY (green), SHAKEN (yellow), VULNERABLE (orange), HELPLESS (red).\n *\n * NOTE: This component is rendered OUTSIDE the Canvas as part of the HTML overlay.\n * It does NOT use Html from drei - it's a standard React component.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/BalanceIndicator\n * @category Combat UI\n * @korean 균형표시기\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface BalanceIndicatorProps {\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right' side of screen)\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Mobile responsive mode (thinner borders)\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n}\n\n/**\n * Get color for balance state using theme colors\n */\nfunction getBalanceColor(theme: ReturnType<typeof useKoreanTheme>, state: BalanceState): number {\n switch (state) {\n case \"READY\":\n return theme.colors.POSITIVE_GREEN; // 🟢 Green\n case \"SHAKEN\":\n return theme.colors.WARNING_YELLOW; // 🟡 Yellow\n case \"VULNERABLE\":\n return theme.colors.WARNING_ORANGE; // 🟠 Orange\n case \"HELPLESS\":\n return theme.colors.ACCENT_RED; // 🔴 Red\n }\n}\n\n/**\n * Get Korean label for balance state\n */\nfunction getBalanceLabel(state: BalanceState): {\n korean: string;\n english: string;\n} {\n switch (state) {\n case \"READY\":\n return { korean: \"준비완료\", english: \"READY\" };\n case \"SHAKEN\":\n return { korean: \"동요상태\", english: \"SHAKEN\" };\n case \"VULNERABLE\":\n return { korean: \"취약상태\", english: \"VULNERABLE\" };\n case \"HELPLESS\":\n return { korean: \"무력상태\", english: \"HELPLESS\" };\n }\n}\n\n/**\n * BalanceIndicator - Color-coded border indicator for player balance state\n *\n * Renders a border around the player HUD area with color matching the\n * current balance state. Uses smooth transitions for state changes.\n * Uses useKoreanTheme for consistent color scheme.\n *\n * @example\n * ```tsx\n * <BalanceIndicator\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicator: React.FC<BalanceIndicatorProps> = ({\n balanceState,\n position,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n \n const indicatorStyle = useMemo(() => {\n const color = getBalanceColor(theme, balanceState);\n const colorHex = `#${color.toString(16).padStart(6, \"0\")}`;\n\n // Mobile uses thinner border\n const borderWidth = isMobile ? \"3px\" : \"4px\";\n\n // Position based on player side\n const isLeft = position === \"left\";\n\n return {\n position: \"absolute\" as const,\n top: isMobile ? \"8px\" : \"12px\",\n left: isLeft ? (isMobile ? \"8px\" : \"12px\") : \"auto\",\n right: isLeft ? \"auto\" : isMobile ? \"8px\" : \"12px\",\n width: isMobile ? \"180px\" : \"220px\",\n height: isMobile ? \"80px\" : \"100px\",\n border: `${borderWidth} solid ${colorHex}`,\n borderRadius: \"8px\",\n boxShadow: `0 0 12px ${colorHex}`,\n pointerEvents: \"none\" as const,\n transition: \"border-color 0.5s ease-out, box-shadow 0.5s ease-out\",\n zIndex: 90, // Below HUD text but above game content\n };\n }, [balanceState, position, isMobile, theme]);\n\n const label = useMemo(() => getBalanceLabel(balanceState), [balanceState]);\n\n return (\n <div\n data-testid={`balance-indicator-${position}`}\n style={indicatorStyle}\n aria-label={`${label.korean} | ${label.english}`}\n role=\"status\"\n aria-live=\"polite\"\n />\n );\n};\n\nBalanceIndicator.displayName = \"BalanceIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAS,gBAAgB,OAA0C,OAA6B;CAC9F,QAAQ,OAAR;EACE,KAAK,SACH,OAAO,MAAM,OAAO;EACtB,KAAK,UACH,OAAO,MAAM,OAAO;EACtB,KAAK,cACH,OAAO,MAAM,OAAO;EACtB,KAAK,YACH,OAAO,MAAM,OAAO;;;;;;AAO1B,SAAS,gBAAgB,OAGvB;CACA,QAAQ,OAAR;EACE,KAAK,SACH,OAAO;GAAE,QAAQ;GAAQ,SAAS;GAAS;EAC7C,KAAK,UACH,OAAO;GAAE,QAAQ;GAAQ,SAAS;GAAU;EAC9C,KAAK,cACH,OAAO;GAAE,QAAQ;GAAQ,SAAS;GAAc;EAClD,KAAK,YACH,OAAO;GAAE,QAAQ;GAAQ,SAAS;GAAY;;;;;;;;;;;;;;;;;;;AAoBpD,IAAa,oBAAqD,EAChE,cACA,UACA,eACI;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;EAAU,CAAC;CAE1E,MAAM,iBAAiB,cAAc;EAEnC,MAAM,WAAW,IADH,gBAAgB,OAAO,aAChB,CAAM,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;EAGxD,MAAM,cAAc,WAAW,QAAQ;EAGvC,MAAM,SAAS,aAAa;EAE5B,OAAO;GACL,UAAU;GACV,KAAK,WAAW,QAAQ;GACxB,MAAM,SAAU,WAAW,QAAQ,SAAU;GAC7C,OAAO,SAAS,SAAS,WAAW,QAAQ;GAC5C,OAAO,WAAW,UAAU;GAC5B,QAAQ,WAAW,SAAS;GAC5B,QAAQ,GAAG,YAAY,SAAS;GAChC,cAAc;GACd,WAAW,YAAY;GACvB,eAAe;GACf,YAAY;GACZ,QAAQ;GACT;IACA;EAAC;EAAc;EAAU;EAAU;EAAM,CAAC;CAE7C,MAAM,QAAQ,cAAc,gBAAgB,aAAa,EAAE,CAAC,aAAa,CAAC;CAE1E,OACE,oBAAC,OAAD;EACE,eAAa,qBAAqB;EAClC,OAAO;EACP,cAAY,GAAG,MAAM,OAAO,KAAK,MAAM;EACvC,MAAK;EACL,aAAU;EACV,CAAA;;AAIN,iBAAiB,cAAc"}
|