blacktrigram 0.7.39 → 0.7.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,IAAM,oBAAoB,aAAyC;AACjE,SAAQ,UAAR;EACE,KAAK,mBAAmB,OACtB,QAAO,cAAc,cAAc,aAAa;EAClD,KAAK,mBAAmB,SACtB,QAAO,cAAc,cAAc,WAAW;EAChD,KAAK,mBAAmB,MACtB,QAAO,cAAc,cAAc,YAAY;EACjD,KAAK,mBAAmB,SACtB,QAAO,cAAc,cAAc,qBAAqB;EAC1D,KAAK,mBAAmB,MACtB,QAAO,cAAc,cAAc,eAAe;EACpD,QACE,QAAO,cAAc,cAAc,UAAU;;;;;;;AAQnD,IAAa,iCAER,EACH,SACA,iBACA,iBACA,yBACA,cACA,sBACA,aAAa,qBACb,qBACA,YACA,oBACA,UACA,kBACA,OACA,eACA,WAAW,OACX,qBACI;CACJ,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAG/C,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,GAAG;CAClE,MAAM,cAAc,uBAAuB;CAC3C,MAAM,iBAAiB,uBAAuB;CAG9C,MAAM,QAAQ,cAAc,qBAAqB,EAAE,EAAE,CAAC;CAGtD,MAAM,kBAKF,eACK;EACL,KAAK,WAAW,UAAU;EAC1B,MAAM,WAAW,SAAS;EAC3B,GACD,CAAC,SAAS,CACX;CAED,MAAM,gBAAgB,kBAAkB;CAGxC,MAAM,gBAAgB,cAAc;EAClC,IAAI,SAAS,CAAC,GAAG,oBAAoB;AAGrC,MAAI,gBAAgB,SAAS,EAC3B,UAAS,OAAO,QAAQ,OAAO,gBAAgB,SAAS,GAAG,SAAS,CAAC;AAIvE,MAAI,iBAAiB,MACnB,KAAI,iBAAiB,OAEnB,UAAS,OAAO,QACb,OACC,GAAG,GAAG,WAAW,YAAY,IAAI,GAAG,GAAG,WAAW,aAAa,CAClE;WACQ,iBAAiB,OAE1B,UAAS,OAAO,QACb,OACC,GAAG,GAAG,WAAW,YAAY,IAAI,GAAG,GAAG,WAAW,aAAa,CAClE;OACI;GAEL,MAAM,SAAS,GAAG,aAAa;AAC/B,YAAS,OAAO,QAAQ,OAAO,GAAG,GAAG,WAAW,OAAO,CAAC;;AAK5D,MAAI,aAAa;GACf,MAAM,QAAQ,YAAY,aAAa;AACvC,YAAS,OAAO,QACb,OACC,GAAG,MAAM,OAAO,aAAa,CAAC,SAAS,MAAM,IAC7C,GAAG,MAAM,QAAQ,aAAa,CAAC,SAAS,MAAM,IAC9C,GAAG,MAAM,UAAU,aAAa,CAAC,SAAS,MAAM,IAChD,GAAG,GAAG,aAAa,CAAC,SAAS,MAAM,CACtC;;AAGH,SAAO,OAAO;IACb;EAAC;EAAiB;EAAc;EAAY,CAAC;CAGhD,MAAM,uBAAuB,aAC1B,aAAiC;AAIhC,0BAHmB,gBAAgB,SAAS,SAAS,GACjD,gBAAgB,QAAQ,MAAM,MAAM,SAAS,GAC7C,CAAC,GAAG,iBAAiB,SAAS,CACC;IAErC,CAAC,iBAAiB,wBAAwB,CAC3C;CAGD,MAAM,kBAAwC;EAC5C,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACpB;CAGD,MAAM,gBAIA;EACJ;GAAE,OAAO;GAAO,OAAO;GAAe,QAAQ;GAAM;EACpD;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;GAAM;EAC9C;GAAE,OAAO;GAAS,OAAO;GAAS,QAAQ;GAAM;EAChD;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;GAAK;EAC7C;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;GAAM;EAC/C;CAGD,MAAM,aAAa,WAAW,MAAM;CACpC,MAAM,eAAe,WAAW,KAAK;CACrC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,gBAAgB,WAAW,IAAI;AAErC,QACE,oBAAC,MAAD;EAAM,YAAA;EAAW,OAAO,EAAE,eAAe,QAAQ;YAC/C,qBAAC,OAAD;GACE,OAAO;IACL,UAAU;IACV,KAAK,cAAc;IACnB,MAAM,cAAc;IACpB,OAAO,cAAc;IACrB,QAAQ,cAAc;IACtB,OAAO;IACP,YAAY,GAAG,cAAc,cAAc,mBAAmB,CAAC;IAC/D,QAAQ,aAAa,cAAc,cAAc,aAAa;IAC9D,cAAc;IACd,SAAS,WAAW,SAAS;IAC7B,YAAY,YAAY;IACxB,OAAO,cAAc,cAAc,aAAa;IAChD,WAAW,YAAY,cACrB,cAAc,aACf,CAAC,qBAAqB,cACrB,cAAc,mBACf,CAAC;IACF,YAAY;IACZ,eAAe;IACf,QAAQ;IACT;GACD,eAAY;aAvBd;IA0BE,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,gBAAgB;MAChB,YAAY;MACZ,cAAc;MACd,eAAe;MACf,cAAc,aAAa,cACzB,cAAc,aACf,CAAC;MACF,YAAY,0BAA0B,cACpC,cAAc,mBACf,CAAC,SAAS,cACT,cAAc,aACf,CAAC,UAAU,cAAc,cAAc,mBAAmB,CAAC;MAC7D;eAfH,CAiBE,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,OAAD;MAAK,OAAO;OAAE,UAAU,WAAW,KAAK;OAAI,YAAY;OAAQ;gBAAE;MAE5D,CAAA,EACN,qBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,OAAO,cAAc,cAAc,eAAe;OAClD,WAAW;OACZ;gBALH;OAOG;OAAc;OAAI,MAAM;OAAM;OAC3B;QACF,EAAA,CAAA,EACN,oBAAC,UAAD;MACE,eAAe,YAAY,CAAC,SAAS;MACrC,OAAO;OACL,YAAY,2BAA2B,cACrC,cAAc,qBACf,CAAC,OAAO,cAAc,cAAc,mBAAmB,CAAC;OACzD,QAAQ,aAAa,cAAc,cAAc,aAAa;OAC9D,cAAc;OACd,SAAS;OACT,OAAO,cAAc,cAAc,aAAa;OAChD;OACA,QAAQ;OACR,YAAY;OACZ,WAAW,aAAa,cACtB,cAAc,aACf,CAAC;OACH;MACD,eAAe,MAAM;AACnB,SAAE,cAAc,MAAM,YAAY;AAClC,SAAE,cAAc,MAAM,YAAY,cAAc,cAC9C,cAAc,aACf,CAAC;;MAEJ,eAAe,MAAM;AACnB,SAAE,cAAc,MAAM,YAAY;AAClC,SAAE,cAAc,MAAM,YAAY,aAAa,cAC7C,cAAc,aACf,CAAC;;MAEJ,eAAY;gBAEX,WAAW,MAAM;MACX,CAAA,CACL;;IAGN,oBAAC,OAAD;KAAK,OAAO,EAAE,cAAc,QAAQ;eAClC,oBAAC,UAAD;MACE,eAAe,gBAAgB,CAAC,QAAQ;MACxC,OAAO;OACL,OAAO;OACP,QAAQ;OACR,YAAY,UACR,2BAA2B,cACzB,cAAc,YACf,CAAC,OAAO,cAAc,cAAc,iBAAiB,CAAC,UACvD,2BAA2B,cACzB,cAAc,qBACf,CAAC,OAAO,cAAc,cAAc,mBAAmB,CAAC;OAC7D,QAAQ,aACN,UACI,cAAc,cAAc,YAAY,GACxC,cAAc,cAAc,aAAa;OAE/C,cAAc;OACd,OAAO,UAAU,cAAc,cAAc,aAAa,GAAG,cAAc,cAAc,aAAa;OACtG,UAAU,WAAW,KAAK;OAC1B,YAAY;OACZ,QAAQ;OACR,YAAY;OACZ,WAAW,UACP,cAAc,cACZ,cAAc,YACf,CAAC,sBAAsB,gBAAgB,cAAc,cAAc,GAAI,KACxE,aAAa,cAAc,cAAc,aAAa,CAAC;OAC3D,eAAe;OACf,eAAe;OAChB;MACD,eAAe,MAAM;AACnB,SAAE,cAAc,MAAM,YAAY;AAClC,SAAE,cAAc,MAAM,YAAY,UAC9B,cAAc,cAAc,cAAc,YAAY,CAAC,MACvD,cAAc,cAAc,cAAc,aAAa,CAAC;;MAE9D,eAAe,MAAM;AACnB,SAAE,cAAc,MAAM,YAAY;AAClC,SAAE,cAAc,MAAM,YAAY,UAC9B,cAAc,cAAc,cAAc,YAAY,CAAC,MACvD,aAAa,cAAc,cAAc,aAAa,CAAC;;MAE7D,eAAY;gBAEX,UAAU,oBAAoB;MACxB,CAAA;KACL,CAAA;IAGL,YAAY,WACX,qBAAA,UAAA,EAAA,UAAA;KAEE,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,QAAQ;gBAApC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,YAAY;QAC/C,YAAY;QACZ,eAAe;QACf,eAAe;QAChB;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,UAAU;QACV,KAAK;QACN;iBAEA,gBAAgB,KAAK,aAAa;QACjC,MAAM,WAAW,gBAAgB,SAAS,SAAS;QACnD,MAAM,gBAAgB,iBAAiB,SAAS;AAChD,eACE,oBAAC,UAAD;SAEE,eAAe,qBAAqB,SAAS;SAC7C,OAAO;UACL,YAAY,WACR,2BAA2B,cAAc,OAAO,cAAc,YAC9D,GAAG,cAAc,cAAc,qBAAqB;UACxD,QAAQ,aAAa;UACrB,cAAc;UACd,SAAS;UACT,OAAO,cAAc,cAAc,aAAa;UAChD,UAAU;UACV,QAAQ;UACR,SAAS,WAAW,IAAI;UACxB,YAAY;UACZ,YAAY,WAAW,SAAS;UAChC,WAAW,WACP,aAAa,cAAc,MAC3B;UACL;SACD,eAAe,MAAM;AACnB,YAAE,cAAc,MAAM,UAAU;AAChC,YAAE,cAAc,MAAM,YAAY;;SAEpC,eAAe,MAAM;AACnB,YAAE,cAAc,MAAM,UAAU,WAAW,MAAM;AACjD,YAAE,cAAc,MAAM,YAAY;;SAEpC,eAAa,mBAAmB;mBAE/B;SACM,EA9BF,SA8BE;SAEX;OACE,CAAA,CACF;;KAGN,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,QAAQ;gBAApC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,YAAY;QAC/C,YAAY;QACZ,eAAe;QACf,eAAe;QAChB;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,UAAU;QACV,KAAK;QACN;iBAEA,cAAc,KAAK,WAAW;QAC7B,MAAM,WAAW,iBAAiB,OAAO;AACzC,eACE,qBAAC,UAAD;SAEE,eAAe,qBAAqB,OAAO,MAAM;SACjD,OAAO;UACL,YAAY,WACR,2BAA2B,cACzB,cAAc,aACf,CAAC,OAAO,cACP,cAAc,YACf,CAAC,UACF,GAAG,cAAc,cAAc,qBAAqB;UACxD,QAAQ,aAAa,cACnB,cAAc,aACf;UACD,cAAc;UACd,SAAS;UACT,OAAO,cAAc,cAAc,aAAa;UAChD,UAAU;UACV,QAAQ;UACR,SAAS,WAAW,IAAI;UACxB,YAAY;UACZ,YAAY,WAAW,SAAS;UAChC,WAAW,WACP,aAAa,cACX,cAAc,aACf,CAAC,MACF;UACL;SACD,eAAe,MAAM;AACnB,YAAE,cAAc,MAAM,UAAU;AAChC,YAAE,cAAc,MAAM,YAAY;;SAEpC,eAAe,MAAM;AACnB,YAAE,cAAc,MAAM,UAAU,WAAW,MAAM;AACjD,YAAE,cAAc,MAAM,YAAY;;SAEpC,eAAa,iBAAiB,OAAO;mBApCvC;UAsCG,OAAO;UAAO;UAAI,OAAO;UACnB;WAtCF,OAAO,MAsCL;SAEX;OACE,CAAA,CACF;;KAGN,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,QAAQ;gBAApC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,YAAY;QAC/C,YAAY;QACZ,eAAe;QACf,eAAe;QAChB;iBACF;OAEK,CAAA,EACN,qBAAC,OAAD;OAAK,OAAO,EAAE,UAAU,YAAY;iBAApC,CACE,oBAAC,SAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,eAAe,EAAE,OAAO,MAAM;QAC/C,aAAY;QACZ,OAAO;SACL,OAAO;SACP,QAAQ;SACR,YAAY,GAAG,cACb,cAAc,qBACf;SACD,QAAQ,aAAa,cACnB,cAAc,aACf,CAAC;SACF,cAAc;SACd,SAAS;SACT,OAAO,cAAc,cAAc,aAAa;SAChD;SACA,YAAY,YAAY;SACxB,YAAY;SACZ,SAAS;SACV;QACD,UAAU,MAAM;AACd,WAAE,cAAc,MAAM,cAAc,cAClC,cAAc,aACf;AACD,WAAE,cAAc,MAAM,YAAY,YAAY,cAC5C,cAAc,aACf,CAAC;;QAEJ,SAAS,MAAM;AACb,WAAE,cAAc,MAAM,cAAc,GAAG,cACrC,cAAc,aACf,CAAC;AACF,WAAE,cAAc,MAAM,YAAY;;QAEpC,eAAY;QACZ,CAAA,EAED,eACC,oBAAC,UAAD;QACE,eAAe,eAAe,GAAG;QACjC,OAAO;SACL,UAAU;SACV,OAAO;SACP,KAAK;SACL,WAAW;SACX,YAAY;SACZ,QAAQ;SACR,OAAO,cAAc,cAAc,eAAe;SAClD,QAAQ;SACR,UAAU;SACV,SAAS;SACT,SAAS;SACT,YAAY;SACZ,gBAAgB;SAChB,cAAc;SACd,YAAY;SACb;QACD,eAAe,MAAM;AACnB,WAAE,cAAc,MAAM,QAAQ,cAC5B,cAAc,WACf;AACD,WAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,qBACf;;QAEH,eAAe,MAAM;AACnB,WAAE,cAAc,MAAM,QAAQ,cAC5B,cAAc,eACf;AACD,WAAE,cAAc,MAAM,aAAa;;QAErC,eAAY;QACZ,OAAM;kBACP;QAEQ,CAAA,CAEP;SACF;;KAGN,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,QAAQ;gBAApC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,YAAY;QAC/C,YAAY;QACZ,eAAe;QACf,eAAe;QAChB;iBACF;OAEK,CAAA,EACN,qBAAC,OAAD;OACE,OAAO;QAAE,SAAS;QAAQ,eAAe;QAAU,KAAK;QAAO;iBADjE,CAGE,qBAAC,SAAD;QACE,OAAO;SACL,SAAS;SACT,YAAY;SACZ,KAAK;SACL,QAAQ;SACT;kBANH,CAQE,oBAAC,SAAD;SACE,MAAK;SACL,SAAS;SACT,WAAW,MAAM,mBAAmB,EAAE,OAAO,QAAQ;SACrD,OAAO;UACL,OAAO;UACP,QAAQ;UACR,aAAa,cAAc,cAAc,aAAa;UACvD;SACD,eAAY;SACZ,CAAA,EACF,oBAAC,QAAD;SAAM,OAAO,EAAE,UAAU;mBAAE;SAA0B,CAAA,CAC/C;WACR,qBAAC,SAAD;QACE,OAAO;SACL,SAAS;SACT,YAAY;SACZ,KAAK;SACL,QAAQ;SACT;kBANH,CAQE,oBAAC,SAAD;SACE,MAAK;SACL,SAAS;SACT,WAAW,MAAM,iBAAiB,EAAE,OAAO,QAAQ;SACnD,OAAO;UACL,OAAO;UACP,QAAQ;UACR,aAAa,cAAc,cAAc,aAAa;UACvD;SACD,eAAY;SACZ,CAAA,EACF,oBAAC,QAAD;SAAM,OAAO,EAAE,UAAU;mBAAE;SAAyB,CAAA,CAC9C;UACJ;SACF;;MAGJ,gBAAgB,SAAS,KACzB,iBAAiB,SACjB,gBAAgB,OAChB,oBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,QAAQ;gBAClC,oBAAC,UAAD;OACE,eAAe;AACb,gCAAwB,EAAE,CAAC;AAC3B,6BAAqB,MAAM;AAC3B,uBAAe,GAAG;;OAEpB,OAAO;QACL,OAAO;QACP,QAAQ,eAAe;QACvB,YAAY,GAAG,cACb,cAAc,qBACf;QACD,QAAQ,aAAa,cACnB,cAAc,cACf;QACD,cAAc;QACd,OAAO,cAAc,cAAc,cAAc;QACjD,UAAU;QACV,QAAQ;QACR,YAAY;QACZ,YAAY;QACb;OACD,eAAe,MAAM;AACnB,UAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,cACf,CAAC;AACF,UAAE,cAAc,MAAM,YAAY;;OAEpC,eAAe,MAAM;AACnB,UAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,qBACf;AACD,UAAE,cAAc,MAAM,YAAY;;OAEpC,eAAY;iBACb;OAEQ,CAAA;MACL,CAAA;KAIR,qBAAC,OAAD,EAAA,UAAA,CACE,qBAAC,OAAD;MACE,OAAO;OACL;OACA,cAAc;OACd,OAAO,cAAc,cAAc,YAAY;OAChD;gBALH;OAMC;OACc,MAAM,QAAQ,EAAE;OAAC;OAC1B;SACN,oBAAC,SAAD;MACE,MAAK;MACL,KAAI;MACJ,KAAI;MACJ,MAAK;MACL,OAAO;MACP,WAAW,MAAM,cAAc,WAAW,EAAE,OAAO,MAAM,CAAC;MAC1D,OAAO;OACL,OAAO;OACP,aAAa,cAAc,cAAc,aAAa;OACvD;MACD,eAAY;MACZ,CAAA,CACE,EAAA,CAAA;KAGN,qBAAC,OAAD;MACE,OAAO;OACL,WAAW;OACX,YAAY;OACZ,WAAW,aAAa,cACtB,cAAc,aACf,CAAC;OACF,UAAU;OACV,OAAO,cAAc,cAAc,eAAe;OACnD;gBATH,CAWE,qBAAC,OAAD,EAAA,UAAA;OAAK;OACE,MAAM,SAAS;OAAK;OAAQ,MAAM,SAAS;OAC5C,EAAA,CAAA,EACN,qBAAC,OAAD,EAAA,UAAA;OAAK;OACC,MAAM,SAAS;OAAK;OAAQ,MAAM,SAAS;OAC3C,EAAA,CAAA,CACF;;KACL,EAAA,CAAA;IAED;;EACD,CAAA"}
|
|
1
|
+
{"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackButton.js","names":[],"sources":["../../../../src/components/shared/ui/BackButton.tsx"],"sourcesContent":["/**\n * BackButton - Shared back/return button for screens\n * \n * Provides a consistent bilingual button for returning to menu or previous screen.\n * Uses BaseButtonOverlayHtml for consistency and maintainability.\n * \n * @module components/shared/ui\n * @category UI Components\n * @korean 뒤로가기버튼\n */\n\nimport React from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface BackButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Korean text for the button */\n readonly korean?: string;\n /** English text for the button */\n readonly english?: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Test ID for the button */\n readonly testId?: string;\n}\n\n/**\n * BackButton Component\n * \n * Reusable bilingual back/return button using BaseButtonOverlayHtml.\n * Provides consistent Korean theming and responsive sizing.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <BackButton\n * onClick={() => navigate('/menu')}\n * korean=\"돌아가기\"\n * english=\"Return\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BackButton: React.FC<BackButtonProps> = ({\n onClick,\n korean = \"돌아가기\",\n english = \"Return\",\n isMobile,\n testId = \"back-button\",\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={korean}\n english={english}\n onClick={onClick}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId={testId}\n />\n );\n};\n\nexport interface LinkButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Korean text for the button */\n readonly korean: string;\n /** English text for the button */\n readonly english: string;\n /** Icon/emoji to display */\n readonly icon?: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Test ID for the button */\n readonly testId?: string;\n}\n\n/**\n * LinkButton Component\n * \n * Secondary action button using BaseButtonOverlayHtml with secondary variant.\n * Used for links and secondary actions like ISMS policy links.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <LinkButton\n * onClick={() => window.open(url)}\n * korean=\"보안 정책\"\n * english=\"Security Policy\"\n * icon=\"🔐\"\n * isMobile={false}\n * />\n * ```\n */\nexport const LinkButton: React.FC<LinkButtonProps> = ({\n onClick,\n korean,\n english,\n icon,\n isMobile,\n testId = \"link-button\",\n}) => {\n const labelKorean = icon ? `${icon} ${korean}` : korean;\n \n return (\n <BaseButtonOverlayHtml\n korean={labelKorean}\n english={english}\n onClick={onClick}\n variant=\"secondary\"\n size=\"sm\"\n isMobile={isMobile}\n testId={testId}\n />\n );\n};\n\nexport default { BackButton, LinkButton };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,cAAyC,EACpD,SACA,SAAS,QACT,UAAU,UACV,UACA,SAAS,oBACL;
|
|
1
|
+
{"version":3,"file":"BackButton.js","names":[],"sources":["../../../../src/components/shared/ui/BackButton.tsx"],"sourcesContent":["/**\n * BackButton - Shared back/return button for screens\n * \n * Provides a consistent bilingual button for returning to menu or previous screen.\n * Uses BaseButtonOverlayHtml for consistency and maintainability.\n * \n * @module components/shared/ui\n * @category UI Components\n * @korean 뒤로가기버튼\n */\n\nimport React from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface BackButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Korean text for the button */\n readonly korean?: string;\n /** English text for the button */\n readonly english?: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Test ID for the button */\n readonly testId?: string;\n}\n\n/**\n * BackButton Component\n * \n * Reusable bilingual back/return button using BaseButtonOverlayHtml.\n * Provides consistent Korean theming and responsive sizing.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <BackButton\n * onClick={() => navigate('/menu')}\n * korean=\"돌아가기\"\n * english=\"Return\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BackButton: React.FC<BackButtonProps> = ({\n onClick,\n korean = \"돌아가기\",\n english = \"Return\",\n isMobile,\n testId = \"back-button\",\n}) => {\n return (\n <BaseButtonOverlayHtml\n korean={korean}\n english={english}\n onClick={onClick}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId={testId}\n />\n );\n};\n\nexport interface LinkButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Korean text for the button */\n readonly korean: string;\n /** English text for the button */\n readonly english: string;\n /** Icon/emoji to display */\n readonly icon?: string;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n /** Test ID for the button */\n readonly testId?: string;\n}\n\n/**\n * LinkButton Component\n * \n * Secondary action button using BaseButtonOverlayHtml with secondary variant.\n * Used for links and secondary actions like ISMS policy links.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <LinkButton\n * onClick={() => window.open(url)}\n * korean=\"보안 정책\"\n * english=\"Security Policy\"\n * icon=\"🔐\"\n * isMobile={false}\n * />\n * ```\n */\nexport const LinkButton: React.FC<LinkButtonProps> = ({\n onClick,\n korean,\n english,\n icon,\n isMobile,\n testId = \"link-button\",\n}) => {\n const labelKorean = icon ? `${icon} ${korean}` : korean;\n \n return (\n <BaseButtonOverlayHtml\n korean={labelKorean}\n english={english}\n onClick={onClick}\n variant=\"secondary\"\n size=\"sm\"\n isMobile={isMobile}\n testId={testId}\n />\n );\n};\n\nexport default { BackButton, LinkButton };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,cAAyC,EACpD,SACA,SAAS,QACT,UAAU,UACV,UACA,SAAS,oBACL;CACJ,OACE,oBAAC,uBAAD;EACU;EACC;EACA;EACT,SAAQ;EACR,MAAK;EACK;EACF;EACR,CAAA;;;;;;;;;;;;;;;;;;;;;AAsCN,IAAa,cAAyC,EACpD,SACA,QACA,SACA,MACA,UACA,SAAS,oBACL;CAGJ,OACE,oBAAC,uBAAD;EACE,QAJgB,OAAO,GAAG,KAAK,GAAG,WAAW;EAKpC;EACA;EACT,SAAQ;EACR,MAAK;EACK;EACF;EACR,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseHUDContainer.js","names":[],"sources":["../../../../src/components/shared/ui/BaseHUDContainer.tsx"],"sourcesContent":["/**\n * BaseHUDContainer - Reusable HUD container with common patterns\n *\n * Provides consistent container styling and positioning for all HUD components.\n * Eliminates code duplication across Training and Combat HUDs.\n *\n * Features:\n * - Responsive positioning based on HUD position (left, right, top, bottom)\n * - Korean cyberpunk theming with gradients and borders\n * - Pointer events handling\n * - Backdrop blur effects\n *\n * ## Z-Index Stacking Order (Combat HUD Layers)\n *\n * The combat screen renders multiple overlapping HUD panels. The stacking\n * order is managed by the Z_INDEX constants from LayoutTypes.ts:\n *\n * | Layer | Z-Index | Description |\n * |---------------------|---------|------------------------------------|\n * | BACKGROUND | 0 | 3D scene background |\n * | ARENA | 10 | Combat arena mesh |\n * | PLAYERS | 20 | Character models |\n * | EFFECTS | 30 | Particles and VFX |\n * | HUD_BACKGROUND | 40 | HUD panel backgrounds |\n * | HUD (default) | 50 | Left/Right/Top/Bottom HUD panels |\n * | TECHNIQUE_BAR | 55 | Technique bar overlay |\n * | HUD_OVERLAY | 60 | PlayerStateOverlay and sub-HUDs |\n * | MOBILE_CONTROLS | 100 | Touch controls on mobile |\n * | MODAL | 200 | Modal dialogs |\n * | TOOLTIP | 300 | Tooltips and hints |\n * | PAUSE_MENU | 1000 | Pause menu overlay |\n * | LOADING | 2000 | Loading screens |\n * | DEBUG | 9000 | Performance debug overlay |\n *\n * All BaseHUDContainer instances default to Z_INDEX.HUD (50). Parent\n * screens can override via the `zIndex` prop when a different layer\n * is needed (e.g., overlays at HUD_OVERLAY = 60).\n *\n * ## Viewport Breakpoints (Expected HUD Sizes)\n *\n * | Viewport | Width | Left/Right HUD | Top HUD | Bottom HUD |\n * |---------------------|----------|----------------|---------|------------|\n * | Small Phone (≤375) | ≤375px | ~120-150px | ~50px | ~90px |\n * | Mobile (<768) | <768px | ~180-200px | ~60px | ~110px |\n * | Tablet (768-1199) | 768-1199 | ~220-260px | ~65px | ~120px |\n * | Desktop (≥1200) | ≥1200px | ~260-300px | ~70px | ~130px |\n * | 4K (≥1920) | ≥1920px | ~300-400px | ~80px | ~140px |\n *\n * Width/height values are passed by the parent screen and scaled via\n * positionScale multipliers. This table documents the expected ranges\n * produced by CombatScreen3D and TrainingScreen3D layout calculations.\n *\n * @module components/shared/ui\n * @korean 기본HUD컨테이너 - 공통 패턴을 가진 재사용 가능한 HUD 컨테이너\n */\n\nimport React from \"react\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\n\n/**\n * HUD position type\n */\nexport type HUDPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/**\n * Props for BaseHUDContainer component\n */\nexport interface BaseHUDContainerProps {\n /** Position of the HUD (left, right, top, bottom) */\n readonly position: HUDPosition;\n /** Width in pixels (used for left/right positions; top/bottom use 100% width) */\n readonly width: number;\n /** Height in pixels */\n readonly height: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset?: number;\n /** Internal padding in pixels */\n readonly padding?: number;\n /** Gap between sections in pixels */\n readonly gap?: number;\n /** Z-index for layering */\n readonly zIndex?: number;\n /** Additional CSS styles */\n readonly style?: React.CSSProperties;\n /** Child elements */\n readonly children: React.ReactNode;\n /** Test ID for testing */\n readonly dataTestId?: string;\n}\n\n/**\n * BaseHUDContainer Component\n *\n * Reusable container for HUD elements with consistent styling and positioning.\n * Handles left, right, top, and bottom positions with appropriate borders,\n * gradients, and responsive behavior.\n *\n * @example\n * ```tsx\n * <BaseHUDContainer\n * position=\"left\"\n * width={268}\n * height={940}\n * topOffset={70}\n * padding={12}\n * gap={14}\n * dataTestId=\"combat-left-hud\"\n * >\n * <PlayerHUD player={player} />\n * <SpeedIndicator speed={speed} />\n * </BaseHUDContainer>\n * ```\n */\nexport const BaseHUDContainer: React.FC<BaseHUDContainerProps> = ({\n position,\n width,\n height,\n topOffset = 0,\n padding = 10,\n gap = 12,\n zIndex = Z_INDEX.HUD,\n style = {},\n children,\n dataTestId,\n}) => {\n // Calculate position-specific styles\n // Using KOREAN_COLORS directly for stable memoization instead of useKoreanTheme\n //\n // Z-index stacking: defaults to Z_INDEX.HUD (50). Combat screen layers\n // are ordered as: HUD_BACKGROUND (40) < HUD (50) < TECHNIQUE_BAR (55) < HUD_OVERLAY (60).\n // Left/Right/Top/Bottom HUDs all share the same z-index (HUD = 50) and\n // rely on DOM order for overlap resolution. The PlayerStateOverlay sits\n // at HUD_OVERLAY (60) to appear above all HUD panels.\n //\n // Safe area note: left/right HUD panels are anchored to the viewport edge\n // with left: 0 / right: 0. Reducing the width prop alone will not move a\n // panel out of a landscape notch or horizontal safe-area cutout. If the\n // parent needs to avoid env(safe-area-inset-left) /\n // env(safe-area-inset-right), it should also provide a horizontal offset\n // via the existing style prop (for example, left/right or horizontal\n // padding) in addition to any width adjustments. Horizontal safe-area\n // insets affect left/right panel widths and offsets, not topOffset (which\n // is purely vertical). For portrait notch/status-bar avoidance, the parent\n // should incorporate layout.safeArea.top into the topOffset calculation.\n const containerStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"stretch\",\n pointerEvents: \"none\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n gap: `${gap}px`,\n zIndex,\n backdropFilter: \"blur(8px)\",\n };\n\n switch (position) {\n case \"left\":\n return {\n ...baseStyle,\n left: 0,\n top: `${topOffset}px`,\n width: `${width}px`,\n height: `${height}px`,\n justifyContent: \"flex-start\",\n borderRight: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(90deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.4)} 100%)`,\n };\n\n case \"right\":\n return {\n ...baseStyle,\n right: 0,\n top: `${topOffset}px`,\n width: `${width}px`,\n height: `${height}px`,\n justifyContent: \"flex-start\",\n borderLeft: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(270deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.4)} 100%)`,\n };\n\n case \"top\":\n return {\n ...baseStyle,\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${height}px`,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n borderBottom: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(180deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7)} 100%)`,\n };\n\n case \"bottom\":\n return {\n ...baseStyle,\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${height}px`,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n borderTop: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(0deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7)} 100%)`,\n };\n }\n }, [position, width, height, topOffset, padding, gap, zIndex]);\n\n return (\n <div style={{ ...containerStyle, ...style }} data-testid={dataTestId}>\n {children}\n </div>\n );\n};\n\nexport default BaseHUDContainer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmHA,IAAa,oBAAqD,EAChE,UACA,OACA,QACA,YAAY,GACZ,UAAU,IACV,MAAM,IACN,SAAS,QAAQ,KACjB,QAAQ,EAAE,EACV,UACA,iBACI;AAyFJ,QACE,oBAAC,OAAD;EAAK,OAAO;GAAE,GAtEO,MAAM,cAAmC;IAC9D,MAAM,YAAiC;KACrC,UAAU;KACV,SAAS;KACT,eAAe;KACf,YAAY;KACZ,eAAe;KACf,SAAS,GAAG,QAAQ;KACpB,WAAW;KACX,KAAK,GAAG,IAAI;KACZ;KACA,gBAAgB;KACjB;AAED,YAAQ,UAAR;KACE,KAAK,OACH,QAAO;MACL,GAAG;MACH,MAAM;MACN,KAAK,GAAG,UAAU;MAClB,OAAO,GAAG,MAAM;MAChB,QAAQ,GAAG,OAAO;MAClB,gBAAgB;MAChB,aAAa,aAAa,gBAAgB,cAAc,cAAc,GAAI;MAC1E,YAAY,0BAA0B,gBAAgB,cAAc,oBAAoB,IAAK,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC7J;KAEH,KAAK,QACH,QAAO;MACL,GAAG;MACH,OAAO;MACP,KAAK,GAAG,UAAU;MAClB,OAAO,GAAG,MAAM;MAChB,QAAQ,GAAG,OAAO;MAClB,gBAAgB;MAChB,YAAY,aAAa,gBAAgB,cAAc,cAAc,GAAI;MACzE,YAAY,2BAA2B,gBAAgB,cAAc,oBAAoB,IAAK,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC9J;KAEH,KAAK,MACH,QAAO;MACL,GAAG;MACH,KAAK;MACL,MAAM;MACN,OAAO;MACP,QAAQ,GAAG,OAAO;MAClB,eAAe;MACf,gBAAgB;MAChB,YAAY;MACZ,cAAc,aAAa,gBAAgB,cAAc,cAAc,GAAI;MAC3E,YAAY,2BAA2B,gBAAgB,cAAc,oBAAoB,GAAI,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC7J;KAEH,KAAK,SACH,QAAO;MACL,GAAG;MACH,QAAQ;MACR,MAAM;MACN,OAAO;MACP,QAAQ,GAAG,OAAO;MAClB,eAAe;MACf,gBAAgB;MAChB,YAAY;MACZ,WAAW,aAAa,gBAAgB,cAAc,cAAc,GAAI;MACxE,YAAY,yBAAyB,gBAAgB,cAAc,oBAAoB,GAAI,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC3J;;MAEJ;IAAC;IAAU;IAAO;IAAQ;IAAW;IAAS;IAAK;IAAO,CAG1C;GAAgB,GAAG;GAAO;EAAE,eAAa;EACvD;EACG,CAAA"}
|
|
1
|
+
{"version":3,"file":"BaseHUDContainer.js","names":[],"sources":["../../../../src/components/shared/ui/BaseHUDContainer.tsx"],"sourcesContent":["/**\n * BaseHUDContainer - Reusable HUD container with common patterns\n *\n * Provides consistent container styling and positioning for all HUD components.\n * Eliminates code duplication across Training and Combat HUDs.\n *\n * Features:\n * - Responsive positioning based on HUD position (left, right, top, bottom)\n * - Korean cyberpunk theming with gradients and borders\n * - Pointer events handling\n * - Backdrop blur effects\n *\n * ## Z-Index Stacking Order (Combat HUD Layers)\n *\n * The combat screen renders multiple overlapping HUD panels. The stacking\n * order is managed by the Z_INDEX constants from LayoutTypes.ts:\n *\n * | Layer | Z-Index | Description |\n * |---------------------|---------|------------------------------------|\n * | BACKGROUND | 0 | 3D scene background |\n * | ARENA | 10 | Combat arena mesh |\n * | PLAYERS | 20 | Character models |\n * | EFFECTS | 30 | Particles and VFX |\n * | HUD_BACKGROUND | 40 | HUD panel backgrounds |\n * | HUD (default) | 50 | Left/Right/Top/Bottom HUD panels |\n * | TECHNIQUE_BAR | 55 | Technique bar overlay |\n * | HUD_OVERLAY | 60 | PlayerStateOverlay and sub-HUDs |\n * | MOBILE_CONTROLS | 100 | Touch controls on mobile |\n * | MODAL | 200 | Modal dialogs |\n * | TOOLTIP | 300 | Tooltips and hints |\n * | PAUSE_MENU | 1000 | Pause menu overlay |\n * | LOADING | 2000 | Loading screens |\n * | DEBUG | 9000 | Performance debug overlay |\n *\n * All BaseHUDContainer instances default to Z_INDEX.HUD (50). Parent\n * screens can override via the `zIndex` prop when a different layer\n * is needed (e.g., overlays at HUD_OVERLAY = 60).\n *\n * ## Viewport Breakpoints (Expected HUD Sizes)\n *\n * | Viewport | Width | Left/Right HUD | Top HUD | Bottom HUD |\n * |---------------------|----------|----------------|---------|------------|\n * | Small Phone (≤375) | ≤375px | ~120-150px | ~50px | ~90px |\n * | Mobile (<768) | <768px | ~180-200px | ~60px | ~110px |\n * | Tablet (768-1199) | 768-1199 | ~220-260px | ~65px | ~120px |\n * | Desktop (≥1200) | ≥1200px | ~260-300px | ~70px | ~130px |\n * | 4K (≥1920) | ≥1920px | ~300-400px | ~80px | ~140px |\n *\n * Width/height values are passed by the parent screen and scaled via\n * positionScale multipliers. This table documents the expected ranges\n * produced by CombatScreen3D and TrainingScreen3D layout calculations.\n *\n * @module components/shared/ui\n * @korean 기본HUD컨테이너 - 공통 패턴을 가진 재사용 가능한 HUD 컨테이너\n */\n\nimport React from \"react\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\n\n/**\n * HUD position type\n */\nexport type HUDPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/**\n * Props for BaseHUDContainer component\n */\nexport interface BaseHUDContainerProps {\n /** Position of the HUD (left, right, top, bottom) */\n readonly position: HUDPosition;\n /** Width in pixels (used for left/right positions; top/bottom use 100% width) */\n readonly width: number;\n /** Height in pixels */\n readonly height: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset?: number;\n /** Internal padding in pixels */\n readonly padding?: number;\n /** Gap between sections in pixels */\n readonly gap?: number;\n /** Z-index for layering */\n readonly zIndex?: number;\n /** Additional CSS styles */\n readonly style?: React.CSSProperties;\n /** Child elements */\n readonly children: React.ReactNode;\n /** Test ID for testing */\n readonly dataTestId?: string;\n}\n\n/**\n * BaseHUDContainer Component\n *\n * Reusable container for HUD elements with consistent styling and positioning.\n * Handles left, right, top, and bottom positions with appropriate borders,\n * gradients, and responsive behavior.\n *\n * @example\n * ```tsx\n * <BaseHUDContainer\n * position=\"left\"\n * width={268}\n * height={940}\n * topOffset={70}\n * padding={12}\n * gap={14}\n * dataTestId=\"combat-left-hud\"\n * >\n * <PlayerHUD player={player} />\n * <SpeedIndicator speed={speed} />\n * </BaseHUDContainer>\n * ```\n */\nexport const BaseHUDContainer: React.FC<BaseHUDContainerProps> = ({\n position,\n width,\n height,\n topOffset = 0,\n padding = 10,\n gap = 12,\n zIndex = Z_INDEX.HUD,\n style = {},\n children,\n dataTestId,\n}) => {\n // Calculate position-specific styles\n // Using KOREAN_COLORS directly for stable memoization instead of useKoreanTheme\n //\n // Z-index stacking: defaults to Z_INDEX.HUD (50). Combat screen layers\n // are ordered as: HUD_BACKGROUND (40) < HUD (50) < TECHNIQUE_BAR (55) < HUD_OVERLAY (60).\n // Left/Right/Top/Bottom HUDs all share the same z-index (HUD = 50) and\n // rely on DOM order for overlap resolution. The PlayerStateOverlay sits\n // at HUD_OVERLAY (60) to appear above all HUD panels.\n //\n // Safe area note: left/right HUD panels are anchored to the viewport edge\n // with left: 0 / right: 0. Reducing the width prop alone will not move a\n // panel out of a landscape notch or horizontal safe-area cutout. If the\n // parent needs to avoid env(safe-area-inset-left) /\n // env(safe-area-inset-right), it should also provide a horizontal offset\n // via the existing style prop (for example, left/right or horizontal\n // padding) in addition to any width adjustments. Horizontal safe-area\n // insets affect left/right panel widths and offsets, not topOffset (which\n // is purely vertical). For portrait notch/status-bar avoidance, the parent\n // should incorporate layout.safeArea.top into the topOffset calculation.\n const containerStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"stretch\",\n pointerEvents: \"none\",\n padding: `${padding}px`,\n boxSizing: \"border-box\",\n gap: `${gap}px`,\n zIndex,\n backdropFilter: \"blur(8px)\",\n };\n\n switch (position) {\n case \"left\":\n return {\n ...baseStyle,\n left: 0,\n top: `${topOffset}px`,\n width: `${width}px`,\n height: `${height}px`,\n justifyContent: \"flex-start\",\n borderRight: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(90deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.4)} 100%)`,\n };\n\n case \"right\":\n return {\n ...baseStyle,\n right: 0,\n top: `${topOffset}px`,\n width: `${width}px`,\n height: `${height}px`,\n justifyContent: \"flex-start\",\n borderLeft: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(270deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.85)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.4)} 100%)`,\n };\n\n case \"top\":\n return {\n ...baseStyle,\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${height}px`,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n borderBottom: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(180deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7)} 100%)`,\n };\n\n case \"bottom\":\n return {\n ...baseStyle,\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${height}px`,\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n borderTop: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.4)}`,\n background: `linear-gradient(0deg, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9)} 0%, ${hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.7)} 100%)`,\n };\n }\n }, [position, width, height, topOffset, padding, gap, zIndex]);\n\n return (\n <div style={{ ...containerStyle, ...style }} data-testid={dataTestId}>\n {children}\n </div>\n );\n};\n\nexport default BaseHUDContainer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmHA,IAAa,oBAAqD,EAChE,UACA,OACA,QACA,YAAY,GACZ,UAAU,IACV,MAAM,IACN,SAAS,QAAQ,KACjB,QAAQ,EAAE,EACV,UACA,iBACI;CAyFJ,OACE,oBAAC,OAAD;EAAK,OAAO;GAAE,GAtEO,MAAM,cAAmC;IAC9D,MAAM,YAAiC;KACrC,UAAU;KACV,SAAS;KACT,eAAe;KACf,YAAY;KACZ,eAAe;KACf,SAAS,GAAG,QAAQ;KACpB,WAAW;KACX,KAAK,GAAG,IAAI;KACZ;KACA,gBAAgB;KACjB;IAED,QAAQ,UAAR;KACE,KAAK,QACH,OAAO;MACL,GAAG;MACH,MAAM;MACN,KAAK,GAAG,UAAU;MAClB,OAAO,GAAG,MAAM;MAChB,QAAQ,GAAG,OAAO;MAClB,gBAAgB;MAChB,aAAa,aAAa,gBAAgB,cAAc,cAAc,GAAI;MAC1E,YAAY,0BAA0B,gBAAgB,cAAc,oBAAoB,IAAK,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC7J;KAEH,KAAK,SACH,OAAO;MACL,GAAG;MACH,OAAO;MACP,KAAK,GAAG,UAAU;MAClB,OAAO,GAAG,MAAM;MAChB,QAAQ,GAAG,OAAO;MAClB,gBAAgB;MAChB,YAAY,aAAa,gBAAgB,cAAc,cAAc,GAAI;MACzE,YAAY,2BAA2B,gBAAgB,cAAc,oBAAoB,IAAK,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC9J;KAEH,KAAK,OACH,OAAO;MACL,GAAG;MACH,KAAK;MACL,MAAM;MACN,OAAO;MACP,QAAQ,GAAG,OAAO;MAClB,eAAe;MACf,gBAAgB;MAChB,YAAY;MACZ,cAAc,aAAa,gBAAgB,cAAc,cAAc,GAAI;MAC3E,YAAY,2BAA2B,gBAAgB,cAAc,oBAAoB,GAAI,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC7J;KAEH,KAAK,UACH,OAAO;MACL,GAAG;MACH,QAAQ;MACR,MAAM;MACN,OAAO;MACP,QAAQ,GAAG,OAAO;MAClB,eAAe;MACf,gBAAgB;MAChB,YAAY;MACZ,WAAW,aAAa,gBAAgB,cAAc,cAAc,GAAI;MACxE,YAAY,yBAAyB,gBAAgB,cAAc,oBAAoB,GAAI,CAAC,OAAO,gBAAgB,cAAc,oBAAoB,GAAI,CAAC;MAC3J;;MAEJ;IAAC;IAAU;IAAO;IAAQ;IAAW;IAAS;IAAK;IAAO,CAG1C;GAAgB,GAAG;GAAO;EAAE,eAAa;EACvD;EACG,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatTimer.js","names":[],"sources":["../../../../src/components/shared/ui/CombatTimer.tsx"],"sourcesContent":["/**\n * CombatTimer Component - Displays combat round timer\n *\n * Korean: 전투 타이머 (Combat Timer)\n *\n * Shows remaining time in MM:SS format with:\n * - Color changes based on time remaining\n * - Pulsing animation when time is critical\n * - Korean cyberpunk aesthetic\n * - Responsive sizing\n *\n * @module components/shared/ui/CombatTimer\n * @category Shared UI\n */\n\nimport React, { useMemo, useEffect } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { hexColorToCSS } from \"../../../utils/colorUtils\";\nimport { TimerWarningLevel } from \"../../../hooks/useCombatTimer\";\nimport \"../three/ui/HUDAnimations.css\";\n\n// Define CSS animation once at module level to avoid re-insertion\nconst PULSE_ANIMATION_ID = \"combat-timer-pulse-animation\";\n\nconst injectPulseAnimation = () => {\n // Check if style already exists in DOM instead of using module-level flag\n if (document.getElementById(PULSE_ANIMATION_ID)) return;\n\n const style = document.createElement(\"style\");\n style.id = PULSE_ANIMATION_ID;\n style.textContent = `\n @keyframes pulse {\n 0% {\n transform: translateX(-50%) scale(1);\n opacity: 1;\n }\n 50% {\n transform: translateX(-50%) scale(1.05);\n opacity: 0.9;\n }\n 100% {\n transform: translateX(-50%) scale(1);\n opacity: 1;\n }\n }\n `;\n document.head.appendChild(style);\n};\n\n/**\n * Props for the CombatTimer component\n */\nexport interface CombatTimerProps {\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether time is up */\n readonly isTimeUp: boolean;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Optional custom position styling */\n readonly style?: React.CSSProperties;\n}\n\n/**\n * Get timer color based on warning level\n */\nfunction getTimerColor(\n warningLevel: TimerWarningLevel,\n isTimeUp: boolean,\n): number {\n if (isTimeUp) {\n return KOREAN_COLORS.NEGATIVE_RED; // Red for time up\n }\n switch (warningLevel) {\n case \"urgent\":\n return KOREAN_COLORS.NEGATIVE_RED; // Red (≤5s)\n case \"warning\":\n return KOREAN_COLORS.SECONDARY_YELLOW; // Yellow (≤10s)\n case \"none\":\n default:\n return KOREAN_COLORS.PRIMARY_CYAN; // Cyan (>10s)\n }\n}\n\n/**\n * CombatTimer Component\n *\n * Displays round timer with Korean cyberpunk aesthetic and responsive design.\n *\n * Features:\n * - MM:SS format display\n * - Color changes: cyan (>10s), yellow (≤10s), red (≤5s)\n * - Pulsing animation when time ≤5s\n * - \"Time's Up!\" message when timer reaches 0\n * - Responsive sizing for mobile/tablet/desktop\n * - Korean-English bilingual support\n *\n * Korean: 전투 타이머 컴포넌트\n *\n * @example\n * ```tsx\n * <CombatTimer\n * formattedTime=\"03:45\"\n * warningLevel=\"none\"\n * isTimeUp={false}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatTimer: React.FC<CombatTimerProps> = ({\n formattedTime,\n warningLevel,\n isTimeUp,\n isMobile,\n style = {},\n}) => {\n // Inject CSS animation once on mount\n useEffect(() => {\n injectPulseAnimation();\n }, []);\n\n // Get timer color\n const timerColor = getTimerColor(warningLevel, isTimeUp);\n const timerColorCSS = useMemo(() => hexColorToCSS(timerColor), [timerColor]);\n\n // Background color with opacity\n const bgColor = useMemo(\n () => hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK),\n [],\n );\n\n // Border color based on warning level\n const borderColor = useMemo(() => {\n if (isTimeUp) return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n if (warningLevel === \"urgent\")\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n if (warningLevel === \"warning\")\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW);\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }, [warningLevel, isTimeUp]);\n\n // Should flash when urgent or time is up\n const shouldFlash = warningLevel === \"urgent\" || isTimeUp;\n\n // Font sizes based on screen size\n const fontSize = isMobile ? \"32px\" : \"48px\";\n const labelFontSize = isMobile ? \"12px\" : \"14px\";\n\n // Display text\n const displayText = isTimeUp ? \"시간 종료 | Time's Up!\" : formattedTime;\n\n return (\n <div\n data-testid=\"combat-timer\"\n role=\"timer\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Time remaining: ${formattedTime}`}\n className=\"hud-animated\"\n style={{\n position: \"absolute\",\n top: isMobile ? \"8px\" : \"12px\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n fontSize,\n fontFamily: \"monospace\",\n fontWeight: \"bold\",\n color: timerColorCSS,\n textShadow: `0 0 ${isMobile ? \"8px\" : \"12px\"} ${timerColorCSS}, 0 0 ${\n isMobile ? \"16px\" : \"24px\"\n } ${timerColorCSS}`,\n padding: isMobile ? \"8px 16px\" : \"12px 24px\",\n backgroundColor: `${bgColor}dd`,\n border: `2px solid ${borderColor}`,\n borderRadius: isMobile ? \"6px\" : \"8px\",\n animation: shouldFlash ? \"timerFlash 1s ease-in-out infinite\" : \"none\",\n zIndex: 100,\n pointerEvents: \"none\",\n userSelect: \"none\",\n minWidth: isMobile ? \"120px\" : \"160px\",\n textAlign: \"center\",\n boxShadow: `0 0 ${isMobile ? \"12px\" : \"20px\"} ${borderColor}40`,\n transition: \"all 0.3s ease-in-out\",\n ...style,\n }}\n >\n {/* Label */}\n {!isTimeUp && (\n <div\n style={{\n fontSize: labelFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n color: timerColorCSS,\n marginBottom: \"2px\",\n opacity: 0.8,\n }}\n >\n 시간 | TIME\n </div>\n )}\n\n {/* Timer display */}\n <div>{displayText}</div>\n </div>\n );\n};\n\nexport default CombatTimer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,IAAM,qBAAqB;AAE3B,IAAM,6BAA6B;
|
|
1
|
+
{"version":3,"file":"CombatTimer.js","names":[],"sources":["../../../../src/components/shared/ui/CombatTimer.tsx"],"sourcesContent":["/**\n * CombatTimer Component - Displays combat round timer\n *\n * Korean: 전투 타이머 (Combat Timer)\n *\n * Shows remaining time in MM:SS format with:\n * - Color changes based on time remaining\n * - Pulsing animation when time is critical\n * - Korean cyberpunk aesthetic\n * - Responsive sizing\n *\n * @module components/shared/ui/CombatTimer\n * @category Shared UI\n */\n\nimport React, { useMemo, useEffect } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { hexColorToCSS } from \"../../../utils/colorUtils\";\nimport { TimerWarningLevel } from \"../../../hooks/useCombatTimer\";\nimport \"../three/ui/HUDAnimations.css\";\n\n// Define CSS animation once at module level to avoid re-insertion\nconst PULSE_ANIMATION_ID = \"combat-timer-pulse-animation\";\n\nconst injectPulseAnimation = () => {\n // Check if style already exists in DOM instead of using module-level flag\n if (document.getElementById(PULSE_ANIMATION_ID)) return;\n\n const style = document.createElement(\"style\");\n style.id = PULSE_ANIMATION_ID;\n style.textContent = `\n @keyframes pulse {\n 0% {\n transform: translateX(-50%) scale(1);\n opacity: 1;\n }\n 50% {\n transform: translateX(-50%) scale(1.05);\n opacity: 0.9;\n }\n 100% {\n transform: translateX(-50%) scale(1);\n opacity: 1;\n }\n }\n `;\n document.head.appendChild(style);\n};\n\n/**\n * Props for the CombatTimer component\n */\nexport interface CombatTimerProps {\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether time is up */\n readonly isTimeUp: boolean;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Optional custom position styling */\n readonly style?: React.CSSProperties;\n}\n\n/**\n * Get timer color based on warning level\n */\nfunction getTimerColor(\n warningLevel: TimerWarningLevel,\n isTimeUp: boolean,\n): number {\n if (isTimeUp) {\n return KOREAN_COLORS.NEGATIVE_RED; // Red for time up\n }\n switch (warningLevel) {\n case \"urgent\":\n return KOREAN_COLORS.NEGATIVE_RED; // Red (≤5s)\n case \"warning\":\n return KOREAN_COLORS.SECONDARY_YELLOW; // Yellow (≤10s)\n case \"none\":\n default:\n return KOREAN_COLORS.PRIMARY_CYAN; // Cyan (>10s)\n }\n}\n\n/**\n * CombatTimer Component\n *\n * Displays round timer with Korean cyberpunk aesthetic and responsive design.\n *\n * Features:\n * - MM:SS format display\n * - Color changes: cyan (>10s), yellow (≤10s), red (≤5s)\n * - Pulsing animation when time ≤5s\n * - \"Time's Up!\" message when timer reaches 0\n * - Responsive sizing for mobile/tablet/desktop\n * - Korean-English bilingual support\n *\n * Korean: 전투 타이머 컴포넌트\n *\n * @example\n * ```tsx\n * <CombatTimer\n * formattedTime=\"03:45\"\n * warningLevel=\"none\"\n * isTimeUp={false}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatTimer: React.FC<CombatTimerProps> = ({\n formattedTime,\n warningLevel,\n isTimeUp,\n isMobile,\n style = {},\n}) => {\n // Inject CSS animation once on mount\n useEffect(() => {\n injectPulseAnimation();\n }, []);\n\n // Get timer color\n const timerColor = getTimerColor(warningLevel, isTimeUp);\n const timerColorCSS = useMemo(() => hexColorToCSS(timerColor), [timerColor]);\n\n // Background color with opacity\n const bgColor = useMemo(\n () => hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK),\n [],\n );\n\n // Border color based on warning level\n const borderColor = useMemo(() => {\n if (isTimeUp) return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n if (warningLevel === \"urgent\")\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n if (warningLevel === \"warning\")\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW);\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }, [warningLevel, isTimeUp]);\n\n // Should flash when urgent or time is up\n const shouldFlash = warningLevel === \"urgent\" || isTimeUp;\n\n // Font sizes based on screen size\n const fontSize = isMobile ? \"32px\" : \"48px\";\n const labelFontSize = isMobile ? \"12px\" : \"14px\";\n\n // Display text\n const displayText = isTimeUp ? \"시간 종료 | Time's Up!\" : formattedTime;\n\n return (\n <div\n data-testid=\"combat-timer\"\n role=\"timer\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Time remaining: ${formattedTime}`}\n className=\"hud-animated\"\n style={{\n position: \"absolute\",\n top: isMobile ? \"8px\" : \"12px\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n fontSize,\n fontFamily: \"monospace\",\n fontWeight: \"bold\",\n color: timerColorCSS,\n textShadow: `0 0 ${isMobile ? \"8px\" : \"12px\"} ${timerColorCSS}, 0 0 ${\n isMobile ? \"16px\" : \"24px\"\n } ${timerColorCSS}`,\n padding: isMobile ? \"8px 16px\" : \"12px 24px\",\n backgroundColor: `${bgColor}dd`,\n border: `2px solid ${borderColor}`,\n borderRadius: isMobile ? \"6px\" : \"8px\",\n animation: shouldFlash ? \"timerFlash 1s ease-in-out infinite\" : \"none\",\n zIndex: 100,\n pointerEvents: \"none\",\n userSelect: \"none\",\n minWidth: isMobile ? \"120px\" : \"160px\",\n textAlign: \"center\",\n boxShadow: `0 0 ${isMobile ? \"12px\" : \"20px\"} ${borderColor}40`,\n transition: \"all 0.3s ease-in-out\",\n ...style,\n }}\n >\n {/* Label */}\n {!isTimeUp && (\n <div\n style={{\n fontSize: labelFontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n color: timerColorCSS,\n marginBottom: \"2px\",\n opacity: 0.8,\n }}\n >\n 시간 | TIME\n </div>\n )}\n\n {/* Timer display */}\n <div>{displayText}</div>\n </div>\n );\n};\n\nexport default CombatTimer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,IAAM,qBAAqB;AAE3B,IAAM,6BAA6B;CAEjC,IAAI,SAAS,eAAe,mBAAmB,EAAE;CAEjD,MAAM,QAAQ,SAAS,cAAc,QAAQ;CAC7C,MAAM,KAAK;CACX,MAAM,cAAc;;;;;;;;;;;;;;;;CAgBpB,SAAS,KAAK,YAAY,MAAM;;;;;AAsBlC,SAAS,cACP,cACA,UACQ;CACR,IAAI,UACF,OAAO,cAAc;CAEvB,QAAQ,cAAR;EACE,KAAK,UACH,OAAO,cAAc;EACvB,KAAK,WACH,OAAO,cAAc;EAEvB,SACE,OAAO,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B3B,IAAa,eAA2C,EACtD,eACA,cACA,UACA,UACA,QAAQ,EAAE,OACN;CAEJ,gBAAgB;EACd,sBAAsB;IACrB,EAAE,CAAC;CAGN,MAAM,aAAa,cAAc,cAAc,SAAS;CACxD,MAAM,gBAAgB,cAAc,cAAc,WAAW,EAAE,CAAC,WAAW,CAAC;CAG5E,MAAM,UAAU,cACR,cAAc,cAAc,mBAAmB,EACrD,EAAE,CACH;CAGD,MAAM,cAAc,cAAc;EAChC,IAAI,UAAU,OAAO,cAAc,cAAc,aAAa;EAC9D,IAAI,iBAAiB,UACnB,OAAO,cAAc,cAAc,aAAa;EAClD,IAAI,iBAAiB,WACnB,OAAO,cAAc,cAAc,iBAAiB;EACtD,OAAO,cAAc,cAAc,aAAa;IAC/C,CAAC,cAAc,SAAS,CAAC;CAG5B,MAAM,cAAc,iBAAiB,YAAY;CAGjD,MAAM,WAAW,WAAW,SAAS;CACrC,MAAM,gBAAgB,WAAW,SAAS;CAG1C,MAAM,cAAc,WAAW,uBAAuB;CAEtD,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,MAAK;EACL,aAAU;EACV,eAAY;EACZ,cAAY,mBAAmB;EAC/B,WAAU;EACV,OAAO;GACL,UAAU;GACV,KAAK,WAAW,QAAQ;GACxB,MAAM;GACN,WAAW;GACX;GACA,YAAY;GACZ,YAAY;GACZ,OAAO;GACP,YAAY,OAAO,WAAW,QAAQ,OAAO,GAAG,cAAc,QAC5D,WAAW,SAAS,OACrB,GAAG;GACJ,SAAS,WAAW,aAAa;GACjC,iBAAiB,GAAG,QAAQ;GAC5B,QAAQ,aAAa;GACrB,cAAc,WAAW,QAAQ;GACjC,WAAW,cAAc,uCAAuC;GAChE,QAAQ;GACR,eAAe;GACf,YAAY;GACZ,UAAU,WAAW,UAAU;GAC/B,WAAW;GACX,WAAW,OAAO,WAAW,SAAS,OAAO,GAAG,YAAY;GAC5D,YAAY;GACZ,GAAG;GACJ;YAhCH,CAmCG,CAAC,YACA,oBAAC,OAAD;GACE,OAAO;IACL,UAAU;IACV,YAAY,YAAY;IACxB,OAAO;IACP,cAAc;IACd,SAAS;IACV;aACF;GAEK,CAAA,EAIR,oBAAC,OAAD,EAAA,UAAM,aAAkB,CAAA,CACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorModal.js","names":[],"sources":["../../../../src/components/shared/ui/ErrorModal.tsx"],"sourcesContent":["/**\n * ErrorModal - Korean-themed error dialog component\n * Provides user-friendly error recovery with retry functionality\n * Follows Korean cyberpunk aesthetic and accessibility best practices\n *\n * Now uses BaseButtonOverlayHtml for consistent Korean theming\n */\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { toHex } from \"../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../base\";\n\ninterface ErrorModalProps {\n readonly message: string;\n readonly onRetry: () => void;\n readonly onContinue: () => void;\n}\n\n// Pre-compute hex colors from Korean color constants\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n UI_BACKGROUND_DARK: toHex(KOREAN_COLORS.UI_BACKGROUND_DARK),\n TEXT_ERROR: toHex(KOREAN_COLORS.TEXT_ERROR),\n} as const;\n\n/**\n * Error modal component with Korean cyberpunk styling\n * Provides retry and continue options for graceful error recovery\n * Includes keyboard navigation\n */\nexport const ErrorModal: React.FC<ErrorModalProps> = ({\n message,\n onRetry,\n onContinue,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleRetry = useCallback(() => {\n onRetry();\n }, [onRetry]);\n\n const handleContinue = useCallback(() => {\n onContinue();\n }, [onContinue]);\n\n // Handle keyboard events (Escape key to close)\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n handleContinue();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleContinue]);\n\n return (\n <div\n role=\"alertdialog\"\n aria-labelledby=\"error-modal-title\"\n aria-describedby=\"error-modal-description\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n zIndex: 9999,\n fontFamily: FONT_FAMILY.CYBER,\n }}\n data-testid=\"error-modal\"\n >\n <div\n style={{\n backgroundColor: `#${HEX_COLORS.UI_BACKGROUND_DARK}`,\n border: `2px solid #${HEX_COLORS.TEXT_ERROR}`,\n borderRadius: \"8px\",\n padding: \"32px\",\n maxWidth: \"500px\",\n boxShadow: `0 8px 32px #${HEX_COLORS.TEXT_ERROR}40`,\n }}\n >\n {/* Error Icon */}\n <div\n style={{\n textAlign: \"center\",\n fontSize: \"48px\",\n marginBottom: \"16px\",\n }}\n aria-hidden=\"true\"\n >\n ⚠️\n </div>\n\n {/* Title */}\n <h2\n id=\"error-modal-title\"\n style={{\n color: `#${HEX_COLORS.TEXT_ERROR}`,\n fontSize: \"24px\",\n fontWeight: \"bold\",\n textAlign: \"center\",\n marginBottom: \"16px\",\n }}\n >\n 오류 발생 | Error Occurred\n </h2>\n\n {/* Message */}\n <p\n id=\"error-modal-description\"\n style={{\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n fontSize: \"16px\",\n textAlign: \"center\",\n marginBottom: \"32px\",\n lineHeight: \"1.5\",\n }}\n >\n {message}\n </p>\n\n {/* Action Buttons - Now using BaseButtonOverlayHtml */}\n <div\n ref={containerRef}\n style={{\n display: \"flex\",\n gap: \"16px\",\n justifyContent: \"center\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"재시도\"\n english=\"Retry\"\n onClick={handleRetry}\n variant=\"primary\"\n size=\"md\"\n testId=\"error-modal-retry\"\n autoFocus={true}\n />\n\n <BaseButtonOverlayHtml\n korean=\"무음으로 계속\"\n english=\"Continue Without Sound\"\n onClick={handleContinue}\n variant=\"secondary\"\n size=\"md\"\n testId=\"error-modal-continue\"\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ErrorModal;\n"],"mappings":";;;;;;;;;;;;;;AAoBA,IAAM,aAAa;CACjB,cAAc,MAAM,cAAc,aAAa;CAC/C,aAAa,MAAM,cAAc,YAAY;CAC7C,oBAAoB,MAAM,cAAc,mBAAmB;CAC3D,YAAY,MAAM,cAAc,WAAW;CAC5C;;;;;;AAOD,IAAa,cAAyC,EACpD,SACA,SACA,iBACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CAEjD,MAAM,cAAc,kBAAkB;
|
|
1
|
+
{"version":3,"file":"ErrorModal.js","names":[],"sources":["../../../../src/components/shared/ui/ErrorModal.tsx"],"sourcesContent":["/**\n * ErrorModal - Korean-themed error dialog component\n * Provides user-friendly error recovery with retry functionality\n * Follows Korean cyberpunk aesthetic and accessibility best practices\n *\n * Now uses BaseButtonOverlayHtml for consistent Korean theming\n */\n\nimport React, { useCallback, useEffect, useRef } from \"react\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport { toHex } from \"../../../utils/colorUtils\";\nimport { BaseButtonOverlayHtml } from \"../base\";\n\ninterface ErrorModalProps {\n readonly message: string;\n readonly onRetry: () => void;\n readonly onContinue: () => void;\n}\n\n// Pre-compute hex colors from Korean color constants\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n UI_BACKGROUND_DARK: toHex(KOREAN_COLORS.UI_BACKGROUND_DARK),\n TEXT_ERROR: toHex(KOREAN_COLORS.TEXT_ERROR),\n} as const;\n\n/**\n * Error modal component with Korean cyberpunk styling\n * Provides retry and continue options for graceful error recovery\n * Includes keyboard navigation\n */\nexport const ErrorModal: React.FC<ErrorModalProps> = ({\n message,\n onRetry,\n onContinue,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleRetry = useCallback(() => {\n onRetry();\n }, [onRetry]);\n\n const handleContinue = useCallback(() => {\n onContinue();\n }, [onContinue]);\n\n // Handle keyboard events (Escape key to close)\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n handleContinue();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [handleContinue]);\n\n return (\n <div\n role=\"alertdialog\"\n aria-labelledby=\"error-modal-title\"\n aria-describedby=\"error-modal-description\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n zIndex: 9999,\n fontFamily: FONT_FAMILY.CYBER,\n }}\n data-testid=\"error-modal\"\n >\n <div\n style={{\n backgroundColor: `#${HEX_COLORS.UI_BACKGROUND_DARK}`,\n border: `2px solid #${HEX_COLORS.TEXT_ERROR}`,\n borderRadius: \"8px\",\n padding: \"32px\",\n maxWidth: \"500px\",\n boxShadow: `0 8px 32px #${HEX_COLORS.TEXT_ERROR}40`,\n }}\n >\n {/* Error Icon */}\n <div\n style={{\n textAlign: \"center\",\n fontSize: \"48px\",\n marginBottom: \"16px\",\n }}\n aria-hidden=\"true\"\n >\n ⚠️\n </div>\n\n {/* Title */}\n <h2\n id=\"error-modal-title\"\n style={{\n color: `#${HEX_COLORS.TEXT_ERROR}`,\n fontSize: \"24px\",\n fontWeight: \"bold\",\n textAlign: \"center\",\n marginBottom: \"16px\",\n }}\n >\n 오류 발생 | Error Occurred\n </h2>\n\n {/* Message */}\n <p\n id=\"error-modal-description\"\n style={{\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n fontSize: \"16px\",\n textAlign: \"center\",\n marginBottom: \"32px\",\n lineHeight: \"1.5\",\n }}\n >\n {message}\n </p>\n\n {/* Action Buttons - Now using BaseButtonOverlayHtml */}\n <div\n ref={containerRef}\n style={{\n display: \"flex\",\n gap: \"16px\",\n justifyContent: \"center\",\n }}\n >\n <BaseButtonOverlayHtml\n korean=\"재시도\"\n english=\"Retry\"\n onClick={handleRetry}\n variant=\"primary\"\n size=\"md\"\n testId=\"error-modal-retry\"\n autoFocus={true}\n />\n\n <BaseButtonOverlayHtml\n korean=\"무음으로 계속\"\n english=\"Continue Without Sound\"\n onClick={handleContinue}\n variant=\"secondary\"\n size=\"md\"\n testId=\"error-modal-continue\"\n />\n </div>\n </div>\n </div>\n );\n};\n\nexport default ErrorModal;\n"],"mappings":";;;;;;;;;;;;;;AAoBA,IAAM,aAAa;CACjB,cAAc,MAAM,cAAc,aAAa;CAC/C,aAAa,MAAM,cAAc,YAAY;CAC7C,oBAAoB,MAAM,cAAc,mBAAmB;CAC3D,YAAY,MAAM,cAAc,WAAW;CAC5C;;;;;;AAOD,IAAa,cAAyC,EACpD,SACA,SACA,iBACI;CACJ,MAAM,eAAe,OAAuB,KAAK;CAEjD,MAAM,cAAc,kBAAkB;EACpC,SAAS;IACR,CAAC,QAAQ,CAAC;CAEb,MAAM,iBAAiB,kBAAkB;EACvC,YAAY;IACX,CAAC,WAAW,CAAC;CAGhB,gBAAgB;EACd,MAAM,iBAAiB,MAAqB;GAC1C,IAAI,EAAE,QAAQ,UACZ,gBAAgB;;EAIpB,SAAS,iBAAiB,WAAW,cAAc;EACnD,aAAa,SAAS,oBAAoB,WAAW,cAAc;IAClE,CAAC,eAAe,CAAC;CAEpB,OACE,oBAAC,OAAD;EACE,MAAK;EACL,mBAAgB;EAChB,oBAAiB;EACjB,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,QAAQ;GACR,YAAY,YAAY;GACzB;EACD,eAAY;YAEZ,qBAAC,OAAD;GACE,OAAO;IACL,iBAAiB,IAAI,WAAW;IAChC,QAAQ,cAAc,WAAW;IACjC,cAAc;IACd,SAAS;IACT,UAAU;IACV,WAAW,eAAe,WAAW,WAAW;IACjD;aARH;IAWE,oBAAC,OAAD;KACE,OAAO;MACL,WAAW;MACX,UAAU;MACV,cAAc;MACf;KACD,eAAY;eACb;KAEK,CAAA;IAGN,oBAAC,MAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,YAAY;MACZ,WAAW;MACX,cAAc;MACf;eACF;KAEI,CAAA;IAGL,oBAAC,KAAD;KACE,IAAG;KACH,OAAO;MACL,OAAO,IAAI,WAAW;MACtB,UAAU;MACV,WAAW;MACX,cAAc;MACd,YAAY;MACb;eAEA;KACC,CAAA;IAGJ,qBAAC,OAAD;KACE,KAAK;KACL,OAAO;MACL,SAAS;MACT,KAAK;MACL,gBAAgB;MACjB;eANH,CAQE,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;MACP,WAAW;MACX,CAAA,EAEF,oBAAC,uBAAD;MACE,QAAO;MACP,SAAQ;MACR,SAAS;MACT,SAAQ;MACR,MAAK;MACL,QAAO;MACP,CAAA,CACE;;IACF;;EACF,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../../src/components/shared/ui/LoadingState.tsx"],"sourcesContent":["import React from 'react';\n\n/**\n * Props for the LoadingState component.\n *\n * @property progress Loading progress as a percentage (0-100), or undefined for indeterminate.\n * @property message Custom loading message to display.\n * @default '로드 중 | Loading...'\n * @property stage Current loading stage, determines the stage-specific message.\n * Possible values:\n * - 'assets': Loading game assets\n * - 'audio': Initializing audio system\n * - 'initialization': Preparing game\n * - 'complete': Loading complete\n * @default 'initialization'\n */\nexport interface LoadingStateProps {\n /**\n * Loading progress as a percentage (0-100), or undefined for indeterminate\n */\n readonly progress?: number;\n\n /**\n * Custom loading message to display\n * @default '로드 중 | Loading...'\n */\n readonly message?: string;\n\n /**\n * Current loading stage, determines the stage-specific message\n * - assets: Loading game assets\n * - audio: Initializing audio system\n * - initialization: Preparing game\n * - complete: Loading complete\n * @default 'initialization'\n */\n readonly stage?: 'assets' | 'audio' | 'initialization' | 'complete';\n}\n\n/**\n * LoadingState component with progress indication.\n * Provides user feedback during asset loading and initialization with Korean/English bilingual support.\n * \n * @example\n * ```tsx\n * <LoadingState\n * progress={50}\n * message=\"로드 중 | Loading...\"\n * stage=\"audio\"\n * />\n * ```\n */\nexport const LoadingState: React.FC<LoadingStateProps> = ({\n progress,\n message = '로드 중 | Loading...',\n stage = 'initialization',\n}) => {\n const stageText = {\n assets: '자산 로드 중 | Loading Assets',\n audio: '오디오 초기화 | Initializing Audio',\n initialization: '게임 준비 중 | Preparing Game',\n complete: '완료 | Complete',\n };\n\n // Calculate progress value, undefined means indeterminate\n // Handle NaN by treating it as indeterminate\n const progressValue = progress !== undefined && !Number.isNaN(progress)\n ? Math.min(100, Math.max(0, progress))\n : undefined;\n\n return (\n <div\n className=\"loading-state\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-state\"\n >\n <div className=\"loading-state__logo\">\n <svg\n width=\"150\"\n height=\"150\"\n viewBox=\"0 0 150 150\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-label=\"Black Trigram Logo\"\n >\n {/* Black Trigram Symbol */}\n <rect x=\"50\" y=\"30\" width=\"20\" height=\"90\" fill=\"#00ffff\" />\n <rect x=\"80\" y=\"30\" width=\"20\" height=\"90\" fill=\"#ffd700\" />\n </svg>\n </div>\n\n <h1 className=\"loading-state__title\">\n 흑괘 | BLACK TRIGRAM\n </h1>\n\n <div\n className=\"loading-state__progress\"\n role=\"progressbar\"\n aria-valuenow={progressValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-progress-bar\"\n >\n <div\n className={`loading-state__progress-bar${progressValue === undefined ? ' loading-state__progress-bar--indeterminate' : ''}`}\n style={progressValue !== undefined ? { width: `${progressValue}%` } : undefined}\n />\n </div>\n\n <p className=\"loading-state__stage\">\n {stageText[stage]}\n </p>\n\n {message && (\n <p className=\"loading-state__message\">\n {message}\n </p>\n )}\n\n <div\n className=\"loading-state__spinner\"\n aria-label=\"Loading spinner\"\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAoDA,IAAa,gBAA6C,EACxD,UACA,UAAU,qBACV,QAAQ,uBACJ;CACJ,MAAM,YAAY;EAChB,QAAQ;EACR,OAAO;EACP,gBAAgB;EAChB,UAAU;EACX;CAID,MAAM,gBAAgB,aAAa,KAAA,KAAa,CAAC,OAAO,MAAM,SAAS,GACnE,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC,GACpC,KAAA;
|
|
1
|
+
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../../src/components/shared/ui/LoadingState.tsx"],"sourcesContent":["import React from 'react';\n\n/**\n * Props for the LoadingState component.\n *\n * @property progress Loading progress as a percentage (0-100), or undefined for indeterminate.\n * @property message Custom loading message to display.\n * @default '로드 중 | Loading...'\n * @property stage Current loading stage, determines the stage-specific message.\n * Possible values:\n * - 'assets': Loading game assets\n * - 'audio': Initializing audio system\n * - 'initialization': Preparing game\n * - 'complete': Loading complete\n * @default 'initialization'\n */\nexport interface LoadingStateProps {\n /**\n * Loading progress as a percentage (0-100), or undefined for indeterminate\n */\n readonly progress?: number;\n\n /**\n * Custom loading message to display\n * @default '로드 중 | Loading...'\n */\n readonly message?: string;\n\n /**\n * Current loading stage, determines the stage-specific message\n * - assets: Loading game assets\n * - audio: Initializing audio system\n * - initialization: Preparing game\n * - complete: Loading complete\n * @default 'initialization'\n */\n readonly stage?: 'assets' | 'audio' | 'initialization' | 'complete';\n}\n\n/**\n * LoadingState component with progress indication.\n * Provides user feedback during asset loading and initialization with Korean/English bilingual support.\n * \n * @example\n * ```tsx\n * <LoadingState\n * progress={50}\n * message=\"로드 중 | Loading...\"\n * stage=\"audio\"\n * />\n * ```\n */\nexport const LoadingState: React.FC<LoadingStateProps> = ({\n progress,\n message = '로드 중 | Loading...',\n stage = 'initialization',\n}) => {\n const stageText = {\n assets: '자산 로드 중 | Loading Assets',\n audio: '오디오 초기화 | Initializing Audio',\n initialization: '게임 준비 중 | Preparing Game',\n complete: '완료 | Complete',\n };\n\n // Calculate progress value, undefined means indeterminate\n // Handle NaN by treating it as indeterminate\n const progressValue = progress !== undefined && !Number.isNaN(progress)\n ? Math.min(100, Math.max(0, progress))\n : undefined;\n\n return (\n <div\n className=\"loading-state\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-state\"\n >\n <div className=\"loading-state__logo\">\n <svg\n width=\"150\"\n height=\"150\"\n viewBox=\"0 0 150 150\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-label=\"Black Trigram Logo\"\n >\n {/* Black Trigram Symbol */}\n <rect x=\"50\" y=\"30\" width=\"20\" height=\"90\" fill=\"#00ffff\" />\n <rect x=\"80\" y=\"30\" width=\"20\" height=\"90\" fill=\"#ffd700\" />\n </svg>\n </div>\n\n <h1 className=\"loading-state__title\">\n 흑괘 | BLACK TRIGRAM\n </h1>\n\n <div\n className=\"loading-state__progress\"\n role=\"progressbar\"\n aria-valuenow={progressValue}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={progressValue !== undefined ? `Loading progress: ${progressValue}%` : 'Loading...'}\n data-testid=\"loading-progress-bar\"\n >\n <div\n className={`loading-state__progress-bar${progressValue === undefined ? ' loading-state__progress-bar--indeterminate' : ''}`}\n style={progressValue !== undefined ? { width: `${progressValue}%` } : undefined}\n />\n </div>\n\n <p className=\"loading-state__stage\">\n {stageText[stage]}\n </p>\n\n {message && (\n <p className=\"loading-state__message\">\n {message}\n </p>\n )}\n\n <div\n className=\"loading-state__spinner\"\n aria-label=\"Loading spinner\"\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAoDA,IAAa,gBAA6C,EACxD,UACA,UAAU,qBACV,QAAQ,uBACJ;CACJ,MAAM,YAAY;EAChB,QAAQ;EACR,OAAO;EACP,gBAAgB;EAChB,UAAU;EACX;CAID,MAAM,gBAAgB,aAAa,KAAA,KAAa,CAAC,OAAO,MAAM,SAAS,GACnE,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,CAAC,GACpC,KAAA;CAEJ,OACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,aAAU;EACV,aAAU;EACV,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;EAClF,eAAY;YANd;GAQE,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,OAAD;KACE,OAAM;KACN,QAAO;KACP,SAAQ;KACR,MAAK;KACL,OAAM;KACN,cAAW;eANb,CASE,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;MAAY,CAAA,EAC5D,oBAAC,QAAD;MAAM,GAAE;MAAK,GAAE;MAAK,OAAM;MAAK,QAAO;MAAK,MAAK;MAAY,CAAA,CACxD;;IACF,CAAA;GAEN,oBAAC,MAAD;IAAI,WAAU;cAAuB;IAEhC,CAAA;GAEL,oBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,iBAAe;IACf,iBAAe;IACf,iBAAe;IACf,cAAY,kBAAkB,KAAA,IAAY,qBAAqB,cAAc,KAAK;IAClF,eAAY;cAEZ,oBAAC,OAAD;KACE,WAAW,8BAA8B,kBAAkB,KAAA,IAAY,gDAAgD;KACvH,OAAO,kBAAkB,KAAA,IAAY,EAAE,OAAO,GAAG,cAAc,IAAI,GAAG,KAAA;KACtE,CAAA;IACE,CAAA;GAEN,oBAAC,KAAD;IAAG,WAAU;cACV,UAAU;IACT,CAAA;GAEH,WACC,oBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAGN,oBAAC,OAAD;IACE,WAAU;IACV,cAAW;IACX,CAAA;GACE"}
|
|
@@ -183,7 +183,7 @@ var SplashScreen = ({ onStart, width, height }) => {
|
|
|
183
183
|
}),
|
|
184
184
|
/* @__PURE__ */ jsxs("div", {
|
|
185
185
|
role: "contentinfo",
|
|
186
|
-
"aria-label": `Application version 0.7.
|
|
186
|
+
"aria-label": `Application version 0.7.41`,
|
|
187
187
|
style: {
|
|
188
188
|
position: "absolute",
|
|
189
189
|
bottom: "20px",
|
|
@@ -192,7 +192,7 @@ var SplashScreen = ({ onStart, width, height }) => {
|
|
|
192
192
|
fontSize: "10px",
|
|
193
193
|
zIndex: 1
|
|
194
194
|
},
|
|
195
|
-
children: ["v", "0.7.
|
|
195
|
+
children: ["v", "0.7.41"]
|
|
196
196
|
})
|
|
197
197
|
]
|
|
198
198
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SplashScreen.js","names":[],"sources":["../../../../src/components/shared/ui/SplashScreen.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, toHex } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\n\n// Declare global APP_VERSION constant (injected by build process)\ndeclare const APP_VERSION: string | undefined;\n\n// Constants\n// Small delay to show loading state for visual feedback.\n// 100ms is sufficient for users to perceive the state change\n// without feeling sluggish. This value can be tuned for UX.\nconst LOADING_DELAY_MS = 100;\n\n// Pre-compute hex colors from Korean color constants\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n} as const;\n\nexport interface SplashScreenProps {\n readonly onStart: () => void;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Splash screen that requires user interaction before starting the game.\n * This is necessary to initialize AudioContext which requires a user gesture.\n */\nexport const SplashScreen: React.FC<SplashScreenProps> = ({\n onStart,\n width,\n height,\n}) => {\n const [isLoading, setIsLoading] = useState(false);\n\n // Use proper device detection (user-agent priority for high-res phones)\n // This ensures mobile layout is used even on 4K Android devices\n // User-agent doesn't change during session, so no dependencies needed\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n // Memoize responsive layout values\n const layoutCalculation = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : 64,\n subtitleFontSize: isMobile ? 16 : 24,\n bodyFontSize: isMobile ? 12 : 14,\n instructionsFontSize: isMobile ? 11 : 12,\n buttonPadding: isMobile ? \"16px 48px\" : \"20px 60px\",\n buttonFontSize: isMobile ? 16 : 20,\n }),\n [isMobile],\n );\n\n const handleStart = useCallback(() => {\n setIsLoading(true);\n // Small delay to show loading state\n setTimeout(() => {\n onStart();\n }, LOADING_DELAY_MS);\n }, [onStart]);\n\n return (\n <div\n style={{\n width,\n height,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n background: `linear-gradient(180deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 0%, ${hexColorToCSS(KOREAN_COLORS.ARENA_BACKGROUND)} 100%)`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontFamily: FONT_FAMILY.CYBER,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"splash-screen\"\n >\n {/* Animated background grid - decorative only */}\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n background: `\n repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n ),\n repeating-linear-gradient(\n 90deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n )\n `,\n opacity: 0.3,\n }}\n />\n\n {/* Screen reader live region for loading state */}\n <div\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n left: \"-10000px\",\n width: \"1px\",\n height: \"1px\",\n overflow: \"hidden\",\n }}\n >\n {isLoading ? \"Initializing audio and loading game\" : \"Ready to start\"}\n </div>\n\n {/* Logo/Title */}\n <div\n style={{\n marginBottom: \"60px\",\n textAlign: \"center\",\n zIndex: 1,\n }}\n >\n <h1\n style={{\n fontSize: `${layoutCalculation.titleFontSize}px`,\n fontWeight: 900,\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n textShadow: `0 0 20px #${HEX_COLORS.PRIMARY_CYAN}80`,\n marginBottom: \"20px\",\n letterSpacing: \"4px\",\n }}\n >\n 흑괘\n </h1>\n <h2\n style={{\n fontSize: `${layoutCalculation.subtitleFontSize}px`,\n fontWeight: 400,\n color: `#${HEX_COLORS.ACCENT_GOLD}`,\n letterSpacing: \"2px\",\n marginTop: 0,\n }}\n >\n BLACK TRIGRAM\n </h2>\n <p\n style={{\n fontSize: `${layoutCalculation.bodyFontSize}px`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: \"20px\",\n letterSpacing: \"1px\",\n }}\n >\n Korean Martial Arts Dojang\n </p>\n </div>\n\n {/* Start Button */}\n <button\n onClick={handleStart}\n disabled={isLoading}\n aria-label={\n isLoading\n ? \"Starting game and initializing audio\"\n : \"Start game and initialize audio\"\n }\n aria-busy={isLoading}\n aria-describedby=\"splash-instructions\"\n style={{\n padding: layoutCalculation.buttonPadding,\n fontSize: `${layoutCalculation.buttonFontSize}px`,\n fontFamily: FONT_FAMILY.CYBER,\n fontWeight: 700,\n color: isLoading ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT) : hexColorToCSS(KOREAN_COLORS.BLACK),\n background: isLoading\n ? hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT)\n : `linear-gradient(135deg, #${HEX_COLORS.PRIMARY_CYAN} 0%, #${HEX_COLORS.ACCENT_GOLD} 100%)`,\n border: \"none\",\n borderRadius: \"8px\",\n cursor: isLoading ? \"not-allowed\" : \"pointer\",\n textTransform: \"uppercase\",\n letterSpacing: \"2px\",\n transition: \"all 0.3s ease\",\n boxShadow: isLoading\n ? \"none\"\n : `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`,\n position: \"relative\",\n zIndex: 1,\n opacity: isLoading ? 0.6 : 1,\n }}\n onMouseEnter={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 6px 30px #${HEX_COLORS.PRIMARY_CYAN}60`;\n }\n }}\n onMouseLeave={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`;\n }\n }}\n data-testid=\"splash-start-button\"\n >\n {isLoading ? \"시작 중... Starting...\" : \"시작 | Start\"}\n </button>\n\n {/* Instructions */}\n <div\n id=\"splash-instructions\"\n style={{\n marginTop: \"40px\",\n textAlign: \"center\",\n color: hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: `${layoutCalculation.instructionsFontSize}px`,\n maxWidth: \"600px\",\n padding: \"0 20px\",\n zIndex: 1,\n }}\n >\n <p style={{ margin: \"8px 0\" }}>\n Audio initialization requires user interaction\n </p>\n <p style={{ margin: \"8px 0\" }}>\n Click the button above to enable sound and start the game\n </p>\n </div>\n\n {/* Version info */}\n <div\n role=\"contentinfo\"\n aria-label={`Application version ${typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}`}\n style={{\n position: \"absolute\",\n bottom: \"20px\",\n right: \"20px\",\n color: hexColorToCSS(KOREAN_COLORS.UI_STEEL_GRAY),\n fontSize: \"10px\",\n zIndex: 1,\n }}\n >\n v{typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}\n </div>\n </div>\n );\n};\n\nexport default SplashScreen;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SplashScreen.js","names":[],"sources":["../../../../src/components/shared/ui/SplashScreen.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, toHex } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\n\n// Declare global APP_VERSION constant (injected by build process)\ndeclare const APP_VERSION: string | undefined;\n\n// Constants\n// Small delay to show loading state for visual feedback.\n// 100ms is sufficient for users to perceive the state change\n// without feeling sluggish. This value can be tuned for UX.\nconst LOADING_DELAY_MS = 100;\n\n// Pre-compute hex colors from Korean color constants\nconst HEX_COLORS = {\n PRIMARY_CYAN: toHex(KOREAN_COLORS.PRIMARY_CYAN),\n ACCENT_GOLD: toHex(KOREAN_COLORS.ACCENT_GOLD),\n} as const;\n\nexport interface SplashScreenProps {\n readonly onStart: () => void;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Splash screen that requires user interaction before starting the game.\n * This is necessary to initialize AudioContext which requires a user gesture.\n */\nexport const SplashScreen: React.FC<SplashScreenProps> = ({\n onStart,\n width,\n height,\n}) => {\n const [isLoading, setIsLoading] = useState(false);\n\n // Use proper device detection (user-agent priority for high-res phones)\n // This ensures mobile layout is used even on 4K Android devices\n // User-agent doesn't change during session, so no dependencies needed\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n // Memoize responsive layout values\n const layoutCalculation = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : 64,\n subtitleFontSize: isMobile ? 16 : 24,\n bodyFontSize: isMobile ? 12 : 14,\n instructionsFontSize: isMobile ? 11 : 12,\n buttonPadding: isMobile ? \"16px 48px\" : \"20px 60px\",\n buttonFontSize: isMobile ? 16 : 20,\n }),\n [isMobile],\n );\n\n const handleStart = useCallback(() => {\n setIsLoading(true);\n // Small delay to show loading state\n setTimeout(() => {\n onStart();\n }, LOADING_DELAY_MS);\n }, [onStart]);\n\n return (\n <div\n style={{\n width,\n height,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n background: `linear-gradient(180deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 0%, ${hexColorToCSS(KOREAN_COLORS.ARENA_BACKGROUND)} 100%)`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontFamily: FONT_FAMILY.CYBER,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"splash-screen\"\n >\n {/* Animated background grid - decorative only */}\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n inset: 0,\n background: `\n repeating-linear-gradient(\n 0deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n ),\n repeating-linear-gradient(\n 90deg,\n transparent,\n transparent 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 50px,\n #${HEX_COLORS.PRIMARY_CYAN}08 51px\n )\n `,\n opacity: 0.3,\n }}\n />\n\n {/* Screen reader live region for loading state */}\n <div\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{\n position: \"absolute\",\n left: \"-10000px\",\n width: \"1px\",\n height: \"1px\",\n overflow: \"hidden\",\n }}\n >\n {isLoading ? \"Initializing audio and loading game\" : \"Ready to start\"}\n </div>\n\n {/* Logo/Title */}\n <div\n style={{\n marginBottom: \"60px\",\n textAlign: \"center\",\n zIndex: 1,\n }}\n >\n <h1\n style={{\n fontSize: `${layoutCalculation.titleFontSize}px`,\n fontWeight: 900,\n color: `#${HEX_COLORS.PRIMARY_CYAN}`,\n textShadow: `0 0 20px #${HEX_COLORS.PRIMARY_CYAN}80`,\n marginBottom: \"20px\",\n letterSpacing: \"4px\",\n }}\n >\n 흑괘\n </h1>\n <h2\n style={{\n fontSize: `${layoutCalculation.subtitleFontSize}px`,\n fontWeight: 400,\n color: `#${HEX_COLORS.ACCENT_GOLD}`,\n letterSpacing: \"2px\",\n marginTop: 0,\n }}\n >\n BLACK TRIGRAM\n </h2>\n <p\n style={{\n fontSize: `${layoutCalculation.bodyFontSize}px`,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: \"20px\",\n letterSpacing: \"1px\",\n }}\n >\n Korean Martial Arts Dojang\n </p>\n </div>\n\n {/* Start Button */}\n <button\n onClick={handleStart}\n disabled={isLoading}\n aria-label={\n isLoading\n ? \"Starting game and initializing audio\"\n : \"Start game and initialize audio\"\n }\n aria-busy={isLoading}\n aria-describedby=\"splash-instructions\"\n style={{\n padding: layoutCalculation.buttonPadding,\n fontSize: `${layoutCalculation.buttonFontSize}px`,\n fontFamily: FONT_FAMILY.CYBER,\n fontWeight: 700,\n color: isLoading ? hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT) : hexColorToCSS(KOREAN_COLORS.BLACK),\n background: isLoading\n ? hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_LIGHT)\n : `linear-gradient(135deg, #${HEX_COLORS.PRIMARY_CYAN} 0%, #${HEX_COLORS.ACCENT_GOLD} 100%)`,\n border: \"none\",\n borderRadius: \"8px\",\n cursor: isLoading ? \"not-allowed\" : \"pointer\",\n textTransform: \"uppercase\",\n letterSpacing: \"2px\",\n transition: \"all 0.3s ease\",\n boxShadow: isLoading\n ? \"none\"\n : `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`,\n position: \"relative\",\n zIndex: 1,\n opacity: isLoading ? 0.6 : 1,\n }}\n onMouseEnter={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 6px 30px #${HEX_COLORS.PRIMARY_CYAN}60`;\n }\n }}\n onMouseLeave={(e) => {\n if (!isLoading) {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 4px 20px #${HEX_COLORS.PRIMARY_CYAN}40`;\n }\n }}\n data-testid=\"splash-start-button\"\n >\n {isLoading ? \"시작 중... Starting...\" : \"시작 | Start\"}\n </button>\n\n {/* Instructions */}\n <div\n id=\"splash-instructions\"\n style={{\n marginTop: \"40px\",\n textAlign: \"center\",\n color: hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n fontSize: `${layoutCalculation.instructionsFontSize}px`,\n maxWidth: \"600px\",\n padding: \"0 20px\",\n zIndex: 1,\n }}\n >\n <p style={{ margin: \"8px 0\" }}>\n Audio initialization requires user interaction\n </p>\n <p style={{ margin: \"8px 0\" }}>\n Click the button above to enable sound and start the game\n </p>\n </div>\n\n {/* Version info */}\n <div\n role=\"contentinfo\"\n aria-label={`Application version ${typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}`}\n style={{\n position: \"absolute\",\n bottom: \"20px\",\n right: \"20px\",\n color: hexColorToCSS(KOREAN_COLORS.UI_STEEL_GRAY),\n fontSize: \"10px\",\n zIndex: 1,\n }}\n >\n v{typeof APP_VERSION !== \"undefined\" ? APP_VERSION : \"0.5.3\"}\n </div>\n </div>\n );\n};\n\nexport default SplashScreen;\n"],"mappings":"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"}
|