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":"InteractiveControlDemoOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/controls/components/InteractiveControlDemoOverlayHtml.tsx"],"sourcesContent":["/**\n * InteractiveControlDemo - Display recently pressed keys and their actions\n * \n * Shows the last 5 pressed keys with their descriptions in a bottom overlay.\n * Keys auto-fade after 2 seconds for a clean, non-intrusive display.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { getKeyCategoryColor, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for InteractiveControlDemo component\n */\nexport interface InteractiveControlDemoProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Key press entry with timestamp\n */\ninterface KeyPressEntry {\n readonly keyData: KeyData;\n readonly timestamp: number;\n readonly id: string;\n}\n\n/**\n * Props for KeyPressEntryDisplay component\n */\ninterface KeyPressEntryDisplayProps {\n readonly entry: KeyPressEntry;\n readonly opacity: number;\n readonly categoryColor: number;\n readonly isMobile: boolean;\n readonly theme: ReturnType<typeof useKoreanTheme>;\n}\n\n/**\n * Memoized KeyPressEntryDisplay Component\n * \n * Displays a single key press entry with optimized inline styles.\n */\nconst KeyPressEntryDisplay = React.memo<KeyPressEntryDisplayProps>(({\n entry,\n opacity,\n categoryColor,\n isMobile,\n theme,\n}) => {\n // Memoize styles based on props\n const containerStyle = useMemo(() => ({\n display: 'flex',\n alignItems: 'center',\n gap: isMobile ? '8px' : '12px',\n padding: isMobile ? '8px' : '10px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, opacity * 0.9),\n borderRadius: '8px',\n border: `2px solid ${hexToRgbaString(categoryColor, opacity * 0.7)}`,\n transition: 'opacity 0.3s ease',\n opacity,\n }), [isMobile, theme.colors.UI_BACKGROUND_MEDIUM, opacity, categoryColor]);\n\n const keyLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '16px' : '18px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(categoryColor),\n padding: '6px 12px',\n background: hexToRgbaString(categoryColor, 0.2),\n borderRadius: '6px',\n minWidth: isMobile ? '50px' : '60px',\n textAlign: 'center' as const,\n boxShadow: `0 0 10px ${hexToRgbaString(categoryColor, opacity * 0.5)}`,\n }), [isMobile, categoryColor, opacity]);\n\n const koreanLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '13px' : '14px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, opacity),\n marginBottom: '2px',\n }), [isMobile, theme.colors.ACCENT_GOLD, opacity]);\n\n const descriptionStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '11px' : '12px',\n color: hexToRgbaString(theme.colors.TEXT_SECONDARY, opacity),\n lineHeight: 1.3,\n }), [isMobile, theme.colors.TEXT_SECONDARY, opacity]);\n\n return (\n <div\n style={containerStyle}\n data-testid={`key-press-${entry.keyData.code}`}\n >\n {/* Key label */}\n <div style={keyLabelStyle}>\n {entry.keyData.label}\n </div>\n\n {/* Key description */}\n <div style={{ flex: 1 }}>\n {/* Korean label */}\n {entry.keyData.labelKorean && (\n <div style={koreanLabelStyle}>\n {entry.keyData.labelKorean}\n </div>\n )}\n\n {/* Description (Korean | English) */}\n {entry.keyData.descriptionKorean && entry.keyData.description && (\n <div style={descriptionStyle}>\n {entry.keyData.descriptionKorean} | {entry.keyData.description}\n </div>\n )}\n </div>\n </div>\n );\n});\n\nKeyPressEntryDisplay.displayName = 'KeyPressEntryDisplay';\n\n/**\n * InteractiveControlDemo Component\n * \n * Displays recently pressed keys with their actions.\n * Auto-fades entries after 2 seconds and maintains last 5 keys.\n * \n * @example\n * ```tsx\n * <InteractiveControlDemo\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * isMobile={false}\n * />\n * ```\n */\nexport const InteractiveControlDemo: React.FC<InteractiveControlDemoProps> = ({\n pressedKeys,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: 'primary', size: 'md', isMobile });\n const [keyPresses, setKeyPresses] = useState<KeyPressEntry[]>([]);\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time for opacity calculations using requestAnimationFrame\n useEffect(() => {\n let frameId: number;\n\n const updateTime = () => {\n setCurrentTime(Date.now());\n frameId = window.requestAnimationFrame(updateTime);\n };\n\n frameId = window.requestAnimationFrame(updateTime);\n\n return () => {\n window.cancelAnimationFrame(frameId);\n };\n }, []);\n\n // Track pressed keys and add to history\n useEffect(() => {\n // Find newly pressed keys\n const currentCodes = Array.from(pressedKeys);\n\n if (currentCodes.length === 0) {\n return;\n }\n\n const now = Date.now();\n\n setKeyPresses((prev) => {\n let updated = prev;\n\n currentCodes.forEach((code) => {\n // Check if key is not already in the recent list\n const alreadyRecorded = updated.some(\n (entry) => entry.keyData.code === code && now - entry.timestamp < 100\n );\n\n if (!alreadyRecorded) {\n // Find key data\n const keyData = KEYBOARD_LAYOUT.find((k) => k.code === code);\n if (keyData) {\n const newEntry: KeyPressEntry = {\n keyData,\n timestamp: now,\n id: `${code}-${now}`,\n };\n updated = [newEntry, ...updated];\n }\n }\n });\n\n // Keep only last 5 entries\n if (updated.length > 5) {\n updated = updated.slice(0, 5);\n }\n\n return updated === prev ? prev : updated;\n });\n }, [pressedKeys]);\n\n // Auto-remove entries after 2 seconds\n useEffect(() => {\n setKeyPresses((prev) =>\n prev.filter((entry) => currentTime - entry.timestamp < 2000)\n );\n }, [currentTime]);\n\n // Calculate opacity based on age\n const getOpacity = (timestamp: number): number => {\n const age = currentTime - timestamp;\n const maxAge = 2000;\n return Math.max(0, 1 - age / maxAge);\n };\n\n // Container style\n const containerStyle = useMemo(() => ({\n position: 'fixed' as const,\n bottom: isMobile ? '15px' : '20px',\n left: '50%',\n transform: 'translateX(-50%)',\n display: 'flex',\n flexDirection: 'column' as const,\n gap: isMobile ? '8px' : '10px',\n maxWidth: isMobile ? '90%' : '600px',\n width: '100%',\n padding: isMobile ? '12px' : '15px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n borderRadius: '12px',\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6)}`,\n boxShadow: `\n 0 4px 20px ${hexToRgbaString(theme.colors.BLACK_SOLID, 0.5)},\n 0 0 15px ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.3)}\n `,\n pointerEvents: 'none' as const,\n zIndex: 1000,\n }), [isMobile, theme]);\n\n // Title style\n const titleStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD),\n textAlign: 'center' as const,\n marginBottom: '4px',\n textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, 0.5)}`,\n }), [isMobile, theme]);\n\n return (\n <div style={containerStyle} data-testid=\"interactive-demo\">\n {/* Title */}\n <div style={titleStyle}>\n 최근 입력 | Recent Input\n </div>\n\n {/* Key press entries */}\n {keyPresses.map((entry) => {\n const opacity = getOpacity(entry.timestamp);\n const categoryColor = getKeyCategoryColor(entry.keyData.category);\n\n return (\n <KeyPressEntryDisplay\n key={entry.id}\n entry={entry}\n opacity={opacity}\n categoryColor={categoryColor}\n isMobile={isMobile}\n theme={theme}\n />\n );\n })}\n\n {/* Empty state */}\n {keyPresses.length === 0 && (\n <div\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n color: hexToRgbaString(theme.colors.TEXT_TERTIARY),\n textAlign: 'center' as const,\n padding: '15px',\n }}\n >\n 키를 눌러서 액션을 확인하세요 | Press keys to see actions\n </div>\n )}\n </div>\n );\n};\n\nexport default InteractiveControlDemo;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InteractiveControlDemoOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/controls/components/InteractiveControlDemoOverlayHtml.tsx"],"sourcesContent":["/**\n * InteractiveControlDemo - Display recently pressed keys and their actions\n * \n * Shows the last 5 pressed keys with their descriptions in a bottom overlay.\n * Keys auto-fade after 2 seconds for a clean, non-intrusive display.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { getKeyCategoryColor, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for InteractiveControlDemo component\n */\nexport interface InteractiveControlDemoProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Key press entry with timestamp\n */\ninterface KeyPressEntry {\n readonly keyData: KeyData;\n readonly timestamp: number;\n readonly id: string;\n}\n\n/**\n * Props for KeyPressEntryDisplay component\n */\ninterface KeyPressEntryDisplayProps {\n readonly entry: KeyPressEntry;\n readonly opacity: number;\n readonly categoryColor: number;\n readonly isMobile: boolean;\n readonly theme: ReturnType<typeof useKoreanTheme>;\n}\n\n/**\n * Memoized KeyPressEntryDisplay Component\n * \n * Displays a single key press entry with optimized inline styles.\n */\nconst KeyPressEntryDisplay = React.memo<KeyPressEntryDisplayProps>(({\n entry,\n opacity,\n categoryColor,\n isMobile,\n theme,\n}) => {\n // Memoize styles based on props\n const containerStyle = useMemo(() => ({\n display: 'flex',\n alignItems: 'center',\n gap: isMobile ? '8px' : '12px',\n padding: isMobile ? '8px' : '10px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, opacity * 0.9),\n borderRadius: '8px',\n border: `2px solid ${hexToRgbaString(categoryColor, opacity * 0.7)}`,\n transition: 'opacity 0.3s ease',\n opacity,\n }), [isMobile, theme.colors.UI_BACKGROUND_MEDIUM, opacity, categoryColor]);\n\n const keyLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '16px' : '18px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(categoryColor),\n padding: '6px 12px',\n background: hexToRgbaString(categoryColor, 0.2),\n borderRadius: '6px',\n minWidth: isMobile ? '50px' : '60px',\n textAlign: 'center' as const,\n boxShadow: `0 0 10px ${hexToRgbaString(categoryColor, opacity * 0.5)}`,\n }), [isMobile, categoryColor, opacity]);\n\n const koreanLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '13px' : '14px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, opacity),\n marginBottom: '2px',\n }), [isMobile, theme.colors.ACCENT_GOLD, opacity]);\n\n const descriptionStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '11px' : '12px',\n color: hexToRgbaString(theme.colors.TEXT_SECONDARY, opacity),\n lineHeight: 1.3,\n }), [isMobile, theme.colors.TEXT_SECONDARY, opacity]);\n\n return (\n <div\n style={containerStyle}\n data-testid={`key-press-${entry.keyData.code}`}\n >\n {/* Key label */}\n <div style={keyLabelStyle}>\n {entry.keyData.label}\n </div>\n\n {/* Key description */}\n <div style={{ flex: 1 }}>\n {/* Korean label */}\n {entry.keyData.labelKorean && (\n <div style={koreanLabelStyle}>\n {entry.keyData.labelKorean}\n </div>\n )}\n\n {/* Description (Korean | English) */}\n {entry.keyData.descriptionKorean && entry.keyData.description && (\n <div style={descriptionStyle}>\n {entry.keyData.descriptionKorean} | {entry.keyData.description}\n </div>\n )}\n </div>\n </div>\n );\n});\n\nKeyPressEntryDisplay.displayName = 'KeyPressEntryDisplay';\n\n/**\n * InteractiveControlDemo Component\n * \n * Displays recently pressed keys with their actions.\n * Auto-fades entries after 2 seconds and maintains last 5 keys.\n * \n * @example\n * ```tsx\n * <InteractiveControlDemo\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * isMobile={false}\n * />\n * ```\n */\nexport const InteractiveControlDemo: React.FC<InteractiveControlDemoProps> = ({\n pressedKeys,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: 'primary', size: 'md', isMobile });\n const [keyPresses, setKeyPresses] = useState<KeyPressEntry[]>([]);\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time for opacity calculations using requestAnimationFrame\n useEffect(() => {\n let frameId: number;\n\n const updateTime = () => {\n setCurrentTime(Date.now());\n frameId = window.requestAnimationFrame(updateTime);\n };\n\n frameId = window.requestAnimationFrame(updateTime);\n\n return () => {\n window.cancelAnimationFrame(frameId);\n };\n }, []);\n\n // Track pressed keys and add to history\n useEffect(() => {\n // Find newly pressed keys\n const currentCodes = Array.from(pressedKeys);\n\n if (currentCodes.length === 0) {\n return;\n }\n\n const now = Date.now();\n\n setKeyPresses((prev) => {\n let updated = prev;\n\n currentCodes.forEach((code) => {\n // Check if key is not already in the recent list\n const alreadyRecorded = updated.some(\n (entry) => entry.keyData.code === code && now - entry.timestamp < 100\n );\n\n if (!alreadyRecorded) {\n // Find key data\n const keyData = KEYBOARD_LAYOUT.find((k) => k.code === code);\n if (keyData) {\n const newEntry: KeyPressEntry = {\n keyData,\n timestamp: now,\n id: `${code}-${now}`,\n };\n updated = [newEntry, ...updated];\n }\n }\n });\n\n // Keep only last 5 entries\n if (updated.length > 5) {\n updated = updated.slice(0, 5);\n }\n\n return updated === prev ? prev : updated;\n });\n }, [pressedKeys]);\n\n // Auto-remove entries after 2 seconds\n useEffect(() => {\n setKeyPresses((prev) =>\n prev.filter((entry) => currentTime - entry.timestamp < 2000)\n );\n }, [currentTime]);\n\n // Calculate opacity based on age\n const getOpacity = (timestamp: number): number => {\n const age = currentTime - timestamp;\n const maxAge = 2000;\n return Math.max(0, 1 - age / maxAge);\n };\n\n // Container style\n const containerStyle = useMemo(() => ({\n position: 'fixed' as const,\n bottom: isMobile ? '15px' : '20px',\n left: '50%',\n transform: 'translateX(-50%)',\n display: 'flex',\n flexDirection: 'column' as const,\n gap: isMobile ? '8px' : '10px',\n maxWidth: isMobile ? '90%' : '600px',\n width: '100%',\n padding: isMobile ? '12px' : '15px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n borderRadius: '12px',\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6)}`,\n boxShadow: `\n 0 4px 20px ${hexToRgbaString(theme.colors.BLACK_SOLID, 0.5)},\n 0 0 15px ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.3)}\n `,\n pointerEvents: 'none' as const,\n zIndex: 1000,\n }), [isMobile, theme]);\n\n // Title style\n const titleStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD),\n textAlign: 'center' as const,\n marginBottom: '4px',\n textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, 0.5)}`,\n }), [isMobile, theme]);\n\n return (\n <div style={containerStyle} data-testid=\"interactive-demo\">\n {/* Title */}\n <div style={titleStyle}>\n 최근 입력 | Recent Input\n </div>\n\n {/* Key press entries */}\n {keyPresses.map((entry) => {\n const opacity = getOpacity(entry.timestamp);\n const categoryColor = getKeyCategoryColor(entry.keyData.category);\n\n return (\n <KeyPressEntryDisplay\n key={entry.id}\n entry={entry}\n opacity={opacity}\n categoryColor={categoryColor}\n isMobile={isMobile}\n theme={theme}\n />\n );\n })}\n\n {/* Empty state */}\n {keyPresses.length === 0 && (\n <div\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n color: hexToRgbaString(theme.colors.TEXT_TERTIARY),\n textAlign: 'center' as const,\n padding: '15px',\n }}\n >\n 키를 눌러서 액션을 확인하세요 | Press keys to see actions\n </div>\n )}\n </div>\n );\n};\n\nexport default InteractiveControlDemo;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Key3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/Key3D.tsx"],"sourcesContent":["/**\n * Key3D - Individual 3D keyboard key component with press animation\n * \n * Renders a 3D box mesh for a keyboard key with category-based coloring,\n * press animation, and bilingual label overlay.\n * \n * @module components/screens/controls/components\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getKeyCategoryColor, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for Key3D component\n */\nexport interface Key3DProps {\n /** Key data containing position, label, and category */\n readonly keyData: KeyData;\n /** Whether this key is currently pressed */\n readonly isPressed: boolean;\n}\n\n/**\n * Key3D Component\n * \n * Individual 3D keyboard key with press animation and label overlay.\n * Uses category-based coloring and emissive glow when pressed.\n * \n * @example\n * ```tsx\n * <Key3D\n * keyData={{ code: 'Space', label: 'Space', row: 4, col: 2, width: 3, category: 'combat' }}\n * isPressed={pressedKeys.has('Space')}\n * />\n * ```\n */\nexport const Key3D: React.FC<Key3DProps> = ({ keyData, isPressed }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const targetScale = useRef(new THREE.Vector3(1, 1, 1));\n\n // Key dimensions\n const keyWidth = (keyData.width ?? 1) * 0.6; // 0.6 units per key width\n const keyHeight = 0.6;\n const keyDepth = 0.2;\n\n // Calculate position based on row and column\n const position = useMemo<[number, number, number]>(() => {\n const x = keyData.col * 0.6 + (keyWidth / 2) - 3.0; // Center around origin\n const y = -keyData.row * 0.6;\n const z = isPressed ? -0.05 : 0; // Press down slightly when pressed\n return [x, y, z];\n }, [keyData.col, keyData.row, keyWidth, isPressed]);\n\n // Get category color\n const categoryColor = useMemo(() => getKeyCategoryColor(keyData.category), [keyData.category]);\n\n // Animate scale when pressed\n useFrame(() => {\n if (!meshRef.current) return;\n\n // Target scale: smaller when pressed\n const targetScaleValue = isPressed ? 0.9 : 1.0;\n targetScale.current.set(targetScaleValue, targetScaleValue, 1.0);\n\n // Smooth interpolation\n meshRef.current.scale.lerp(targetScale.current, 0.2);\n });\n\n // Create materials once (unpressed and pressed states)\n const materials = useMemo(\n () => ({\n unpressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 0.3,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n pressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 2.5,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n }),\n [categoryColor]\n );\n\n // Select material based on pressed state\n const keyMaterial = isPressed ? materials.pressed : materials.unpressed;\n\n // Dispose materials on unmount\n useEffect(() => {\n return () => {\n materials.unpressed.dispose();\n materials.pressed.dispose();\n };\n }, [materials]);\n\n // Label style for Html overlay\n const labelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: keyData.width && keyData.width > 2 ? '11px' : '13px',\n fontWeight: 'bold' as const,\n color: isPressed ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textAlign: 'center' as const,\n pointerEvents: 'none' as const,\n userSelect: 'none' as const,\n textShadow: isPressed ? `0 0 8px ${hexToRgbaString(categoryColor, 0.8)}` : 'none',\n whiteSpace: 'nowrap' as const,\n lineHeight: 1.2,\n }), [isPressed, categoryColor, keyData.width]);\n\n return (\n <group position={position}>\n {/* 3D key box */}\n <mesh\n ref={meshRef}\n castShadow\n receiveShadow\n name={`key-${keyData.code}`}\n data-testid={`key-${keyData.code}`}\n >\n <boxGeometry args={[keyWidth, keyHeight, keyDepth]} />\n <primitive object={keyMaterial} attach=\"material\" />\n </mesh>\n\n {/* Label overlay */}\n <Html\n position={[0, 0, keyDepth / 2 + 0.01]}\n center\n distanceFactor={2}\n style={{ pointerEvents: 'none' }}\n >\n <div style={labelStyle}>\n {/* Main label */}\n <div>{keyData.label}</div>\n {/* Korean label (if available) */}\n {keyData.labelKorean && (\n <div style={{\n fontSize: '10px',\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: '2px',\n }}>\n {keyData.labelKorean}\n </div>\n )}\n </div>\n </Html>\n\n {/* Glow ring when pressed */}\n {isPressed && (\n <mesh position={[0, 0, -keyDepth / 2 - 0.02]} rotation={[0, 0, 0]}>\n <ringGeometry args={[Math.max(keyWidth, keyHeight) * 0.6, Math.max(keyWidth, keyHeight) * 0.7, 32]} />\n <meshBasicMaterial\n color={categoryColor}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Key3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,SAA+B,EAAE,SAAS,gBAAgB;CACrE,MAAM,UAAU,OAAmB,KAAK;CACxC,MAAM,cAAc,OAAO,IAAI,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC;CAGtD,MAAM,YAAY,QAAQ,SAAS,KAAK;CACxC,MAAM,YAAY;CAClB,MAAM,WAAW;CAGjB,MAAM,WAAW,cAAwC;
|
|
1
|
+
{"version":3,"file":"Key3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/Key3D.tsx"],"sourcesContent":["/**\n * Key3D - Individual 3D keyboard key component with press animation\n * \n * Renders a 3D box mesh for a keyboard key with category-based coloring,\n * press animation, and bilingual label overlay.\n * \n * @module components/screens/controls/components\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getKeyCategoryColor, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for Key3D component\n */\nexport interface Key3DProps {\n /** Key data containing position, label, and category */\n readonly keyData: KeyData;\n /** Whether this key is currently pressed */\n readonly isPressed: boolean;\n}\n\n/**\n * Key3D Component\n * \n * Individual 3D keyboard key with press animation and label overlay.\n * Uses category-based coloring and emissive glow when pressed.\n * \n * @example\n * ```tsx\n * <Key3D\n * keyData={{ code: 'Space', label: 'Space', row: 4, col: 2, width: 3, category: 'combat' }}\n * isPressed={pressedKeys.has('Space')}\n * />\n * ```\n */\nexport const Key3D: React.FC<Key3DProps> = ({ keyData, isPressed }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const targetScale = useRef(new THREE.Vector3(1, 1, 1));\n\n // Key dimensions\n const keyWidth = (keyData.width ?? 1) * 0.6; // 0.6 units per key width\n const keyHeight = 0.6;\n const keyDepth = 0.2;\n\n // Calculate position based on row and column\n const position = useMemo<[number, number, number]>(() => {\n const x = keyData.col * 0.6 + (keyWidth / 2) - 3.0; // Center around origin\n const y = -keyData.row * 0.6;\n const z = isPressed ? -0.05 : 0; // Press down slightly when pressed\n return [x, y, z];\n }, [keyData.col, keyData.row, keyWidth, isPressed]);\n\n // Get category color\n const categoryColor = useMemo(() => getKeyCategoryColor(keyData.category), [keyData.category]);\n\n // Animate scale when pressed\n useFrame(() => {\n if (!meshRef.current) return;\n\n // Target scale: smaller when pressed\n const targetScaleValue = isPressed ? 0.9 : 1.0;\n targetScale.current.set(targetScaleValue, targetScaleValue, 1.0);\n\n // Smooth interpolation\n meshRef.current.scale.lerp(targetScale.current, 0.2);\n });\n\n // Create materials once (unpressed and pressed states)\n const materials = useMemo(\n () => ({\n unpressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 0.3,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n pressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 2.5,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n }),\n [categoryColor]\n );\n\n // Select material based on pressed state\n const keyMaterial = isPressed ? materials.pressed : materials.unpressed;\n\n // Dispose materials on unmount\n useEffect(() => {\n return () => {\n materials.unpressed.dispose();\n materials.pressed.dispose();\n };\n }, [materials]);\n\n // Label style for Html overlay\n const labelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: keyData.width && keyData.width > 2 ? '11px' : '13px',\n fontWeight: 'bold' as const,\n color: isPressed ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textAlign: 'center' as const,\n pointerEvents: 'none' as const,\n userSelect: 'none' as const,\n textShadow: isPressed ? `0 0 8px ${hexToRgbaString(categoryColor, 0.8)}` : 'none',\n whiteSpace: 'nowrap' as const,\n lineHeight: 1.2,\n }), [isPressed, categoryColor, keyData.width]);\n\n return (\n <group position={position}>\n {/* 3D key box */}\n <mesh\n ref={meshRef}\n castShadow\n receiveShadow\n name={`key-${keyData.code}`}\n data-testid={`key-${keyData.code}`}\n >\n <boxGeometry args={[keyWidth, keyHeight, keyDepth]} />\n <primitive object={keyMaterial} attach=\"material\" />\n </mesh>\n\n {/* Label overlay */}\n <Html\n position={[0, 0, keyDepth / 2 + 0.01]}\n center\n distanceFactor={2}\n style={{ pointerEvents: 'none' }}\n >\n <div style={labelStyle}>\n {/* Main label */}\n <div>{keyData.label}</div>\n {/* Korean label (if available) */}\n {keyData.labelKorean && (\n <div style={{\n fontSize: '10px',\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: '2px',\n }}>\n {keyData.labelKorean}\n </div>\n )}\n </div>\n </Html>\n\n {/* Glow ring when pressed */}\n {isPressed && (\n <mesh position={[0, 0, -keyDepth / 2 - 0.02]} rotation={[0, 0, 0]}>\n <ringGeometry args={[Math.max(keyWidth, keyHeight) * 0.6, Math.max(keyWidth, keyHeight) * 0.7, 32]} />\n <meshBasicMaterial\n color={categoryColor}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Key3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VisualKeyboard3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/VisualKeyboard3D.tsx"],"sourcesContent":["/**\n * VisualKeyboard3D - 3D keyboard visualization with all keys\n * \n * Renders a complete 3D keyboard with keys filtered by selected category.\n * Uses grid layout with proper spacing and lighting for visual clarity.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\nimport { filterKeysByCategory, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\nimport { Key3D } from \"./Key3D\";\n\n/**\n * Props for VisualKeyboard3D component\n */\nexport interface VisualKeyboard3DProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Selected control category to filter keys */\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * VisualKeyboard3D Component\n * \n * 3D keyboard visualization showing all keys filtered by selected category.\n * Keys are positioned in a grid layout with proper spacing.\n * \n * @example\n * ```tsx\n * <Canvas>\n * <VisualKeyboard3D\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * selectedTab=\"combat\"\n * />\n * </Canvas>\n * ```\n */\nexport const VisualKeyboard3D: React.FC<VisualKeyboard3DProps> = ({\n pressedKeys,\n selectedTab,\n}) => {\n // Filter keys by selected category\n const filteredKeys = useMemo<readonly KeyData[]>(() => {\n return filterKeysByCategory(KEYBOARD_LAYOUT, selectedTab);\n }, [selectedTab]);\n\n return (\n <group\n position={[0, -1, 0]}\n rotation={[-Math.PI / 6, 0, 0]}\n data-testid=\"visual-keyboard\"\n >\n {/* Ambient light for overall illumination */}\n <ambientLight intensity={0.4} />\n\n {/* Directional light for depth and shadows */}\n <directionalLight\n position={[5, 5, 5]}\n intensity={1.0}\n castShadow\n shadow-mapSize-width={2048}\n shadow-mapSize-height={2048}\n />\n\n {/* Additional directional light from the side */}\n <directionalLight\n position={[-3, 3, 2]}\n intensity={0.5}\n />\n\n {/* Point light for accent highlights */}\n <pointLight\n position={[0, 2, 2]}\n intensity={0.6}\n distance={10}\n decay={2}\n />\n\n {/* Render all filtered keys */}\n {filteredKeys.map((keyData) => (\n <Key3D\n key={keyData.code}\n keyData={keyData}\n isPressed={pressedKeys.has(keyData.code)}\n />\n ))}\n\n {/* Background plane for keyboard base */}\n <mesh\n position={[0, 0, -0.2]}\n receiveShadow\n >\n <planeGeometry args={[12, 6]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ARENA_BACKGROUND}\n metalness={0.8}\n roughness={0.3}\n opacity={0.9}\n transparent\n />\n </mesh>\n\n {/* Grid helper for visual reference (subtle) */}\n <gridHelper\n args={[12, 20, KOREAN_COLORS.UI_STEEL_GRAY_DARK, KOREAN_COLORS.UI_BACKGROUND_MEDIUM]}\n position={[0, 0, -0.19]}\n rotation={[0, 0, 0]}\n />\n </group>\n );\n};\n\nexport default VisualKeyboard3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,oBAAqD,EAChE,aACA,kBACI;CAEJ,MAAM,eAAe,cAAkC;
|
|
1
|
+
{"version":3,"file":"VisualKeyboard3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/VisualKeyboard3D.tsx"],"sourcesContent":["/**\n * VisualKeyboard3D - 3D keyboard visualization with all keys\n * \n * Renders a complete 3D keyboard with keys filtered by selected category.\n * Uses grid layout with proper spacing and lighting for visual clarity.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\nimport { filterKeysByCategory, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\nimport { Key3D } from \"./Key3D\";\n\n/**\n * Props for VisualKeyboard3D component\n */\nexport interface VisualKeyboard3DProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Selected control category to filter keys */\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * VisualKeyboard3D Component\n * \n * 3D keyboard visualization showing all keys filtered by selected category.\n * Keys are positioned in a grid layout with proper spacing.\n * \n * @example\n * ```tsx\n * <Canvas>\n * <VisualKeyboard3D\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * selectedTab=\"combat\"\n * />\n * </Canvas>\n * ```\n */\nexport const VisualKeyboard3D: React.FC<VisualKeyboard3DProps> = ({\n pressedKeys,\n selectedTab,\n}) => {\n // Filter keys by selected category\n const filteredKeys = useMemo<readonly KeyData[]>(() => {\n return filterKeysByCategory(KEYBOARD_LAYOUT, selectedTab);\n }, [selectedTab]);\n\n return (\n <group\n position={[0, -1, 0]}\n rotation={[-Math.PI / 6, 0, 0]}\n data-testid=\"visual-keyboard\"\n >\n {/* Ambient light for overall illumination */}\n <ambientLight intensity={0.4} />\n\n {/* Directional light for depth and shadows */}\n <directionalLight\n position={[5, 5, 5]}\n intensity={1.0}\n castShadow\n shadow-mapSize-width={2048}\n shadow-mapSize-height={2048}\n />\n\n {/* Additional directional light from the side */}\n <directionalLight\n position={[-3, 3, 2]}\n intensity={0.5}\n />\n\n {/* Point light for accent highlights */}\n <pointLight\n position={[0, 2, 2]}\n intensity={0.6}\n distance={10}\n decay={2}\n />\n\n {/* Render all filtered keys */}\n {filteredKeys.map((keyData) => (\n <Key3D\n key={keyData.code}\n keyData={keyData}\n isPressed={pressedKeys.has(keyData.code)}\n />\n ))}\n\n {/* Background plane for keyboard base */}\n <mesh\n position={[0, 0, -0.2]}\n receiveShadow\n >\n <planeGeometry args={[12, 6]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ARENA_BACKGROUND}\n metalness={0.8}\n roughness={0.3}\n opacity={0.9}\n transparent\n />\n </mesh>\n\n {/* Grid helper for visual reference (subtle) */}\n <gridHelper\n args={[12, 20, KOREAN_COLORS.UI_STEEL_GRAY_DARK, KOREAN_COLORS.UI_BACKGROUND_MEDIUM]}\n position={[0, 0, -0.19]}\n rotation={[0, 0, 0]}\n />\n </group>\n );\n};\n\nexport default VisualKeyboard3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,oBAAqD,EAChE,aACA,kBACI;CAEJ,MAAM,eAAe,cAAkC;EACrD,OAAO,qBAAqB,iBAAiB,YAAY;IACxD,CAAC,YAAY,CAAC;CAEjB,OACE,qBAAC,SAAD;EACE,UAAU;GAAC;GAAG;GAAI;GAAE;EACpB,UAAU;GAAC,CAAC,KAAK,KAAK;GAAG;GAAG;GAAE;EAC9B,eAAY;YAHd;GAME,oBAAC,gBAAD,EAAc,WAAW,IAAO,CAAA;GAGhC,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAG;KAAG;KAAE;IACnB,WAAW;IACX,YAAA;IACA,wBAAsB;IACtB,yBAAuB;IACvB,CAAA;GAGF,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAI;KAAG;KAAE;IACpB,WAAW;IACX,CAAA;GAGF,oBAAC,cAAD;IACE,UAAU;KAAC;KAAG;KAAG;KAAE;IACnB,WAAW;IACX,UAAU;IACV,OAAO;IACP,CAAA;GAGD,aAAa,KAAK,YACjB,oBAAC,OAAD;IAEW;IACT,WAAW,YAAY,IAAI,QAAQ,KAAK;IACxC,EAHK,QAAQ,KAGb,CACF;GAGF,qBAAC,QAAD;IACE,UAAU;KAAC;KAAG;KAAG;KAAK;IACtB,eAAA;cAFF,CAIE,oBAAC,iBAAD,EAAe,MAAM,CAAC,IAAI,EAAE,EAAI,CAAA,EAChC,oBAAC,wBAAD;KACE,OAAO,cAAc;KACrB,WAAW;KACX,WAAW;KACX,SAAS;KACT,aAAA;KACA,CAAA,CACG;;GAGP,oBAAC,cAAD;IACE,MAAM;KAAC;KAAI;KAAI,cAAc;KAAoB,cAAc;KAAqB;IACpF,UAAU;KAAC;KAAG;KAAG;KAAM;IACvB,UAAU;KAAC;KAAG;KAAG;KAAE;IACnB,CAAA;GACI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlsConstants.js","names":[],"sources":["../../../../../src/components/screens/controls/constants/ControlsConstants.ts"],"sourcesContent":["/**\n * Control constants and layouts for Black Trigram (흑괘)\n * Defines keyboard layouts, gamepad mappings, and control categories\n * \n * @module components/screens/controls/constants\n */\n\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\n\n/**\n * Keyboard key data structure\n */\nexport interface KeyData {\n readonly code: string;\n readonly label: string;\n readonly labelKorean?: string;\n readonly row: number;\n readonly col: number;\n readonly width?: number; // Default is 1\n readonly category: KeyCategory;\n readonly description?: string;\n readonly descriptionKorean?: string;\n}\n\n/**\n * Control categories for organizing controls\n */\nexport type KeyCategory = 'stance' | 'movement' | 'combat' | 'system' | 'technique' | 'modifier' | 'normal';\n\n/**\n * Control category configuration\n */\nexport interface ControlCategory {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly icon: string;\n readonly color: number;\n}\n\n/**\n * Gamepad button mapping\n */\nexport interface GamepadButton {\n readonly index: number;\n readonly korean: string;\n readonly english: string;\n readonly action: string;\n readonly actionKorean: string;\n readonly color: number;\n}\n\n/**\n * Control categories for tab navigation\n */\nexport const CONTROL_CATEGORIES: readonly ControlCategory[] = [\n {\n id: 'combat',\n korean: '전투',\n english: 'Combat',\n icon: '⚔️',\n color: KOREAN_COLORS.ACCENT_GOLD,\n },\n {\n id: 'movement',\n korean: '이동',\n english: 'Movement',\n icon: '🏃',\n color: KOREAN_COLORS.PRIMARY_CYAN,\n },\n {\n id: 'system',\n korean: '시스템',\n english: 'System',\n icon: '⚙️',\n color: KOREAN_COLORS.ACCENT_PURPLE,\n },\n] as const;\n\n/**\n * Keyboard layout - all important keys for the game\n * Positioned based on physical keyboard layout\n */\nexport const KEYBOARD_LAYOUT: readonly KeyData[] = [\n // Number row (Stances)\n { code: 'Digit1', label: '1', labelKorean: '건', row: 0, col: 0, category: 'stance', description: 'Geon (Heaven)', descriptionKorean: '건 (Heaven)' },\n { code: 'Digit2', label: '2', labelKorean: '태', row: 0, col: 1, category: 'stance', description: 'Tae (Lake)', descriptionKorean: '태 (Lake)' },\n { code: 'Digit3', label: '3', labelKorean: '리', row: 0, col: 2, category: 'stance', description: 'Li (Fire)', descriptionKorean: '리 (Fire)' },\n { code: 'Digit4', label: '4', labelKorean: '진', row: 0, col: 3, category: 'stance', description: 'Jin (Thunder)', descriptionKorean: '진 (Thunder)' },\n { code: 'Digit5', label: '5', labelKorean: '손', row: 0, col: 4, category: 'stance', description: 'Son (Wind)', descriptionKorean: '손 (Wind)' },\n { code: 'Digit6', label: '6', labelKorean: '감', row: 0, col: 5, category: 'stance', description: 'Gam (Water)', descriptionKorean: '감 (Water)' },\n { code: 'Digit7', label: '7', labelKorean: '간', row: 0, col: 6, category: 'stance', description: 'Gan (Mountain)', descriptionKorean: '간 (Mountain)' },\n { code: 'Digit8', label: '8', labelKorean: '곤', row: 0, col: 7, category: 'stance', description: 'Gon (Earth)', descriptionKorean: '곤 (Earth)' },\n \n // QWERTY row (Techniques + Movement)\n { code: 'KeyQ', label: 'Q', row: 1, col: 0, category: 'technique', description: 'Technique 1', descriptionKorean: '기술 1' },\n { code: 'KeyW', label: 'W', labelKorean: '전진', row: 1, col: 1, category: 'movement', description: 'Move Forward', descriptionKorean: '전진' },\n { code: 'KeyE', label: 'E', row: 1, col: 2, category: 'technique', description: 'Technique 2', descriptionKorean: '기술 2' },\n { code: 'KeyR', label: 'R', row: 1, col: 3, category: 'technique', description: 'Technique 3', descriptionKorean: '기술 3' },\n { code: 'KeyT', label: 'T', row: 1, col: 4, category: 'technique', description: 'Technique 4', descriptionKorean: '기술 4' },\n { code: 'KeyY', label: 'Y', row: 1, col: 5, category: 'technique', description: 'Technique 5', descriptionKorean: '기술 5' },\n \n // ASDF row (Movement + Techniques)\n { code: 'KeyA', label: 'A', labelKorean: '좌', row: 2, col: 0, category: 'movement', description: 'Move Left', descriptionKorean: '좌측 이동' },\n { code: 'KeyS', label: 'S', labelKorean: '후퇴', row: 2, col: 1, category: 'movement', description: 'Move Back', descriptionKorean: '후퇴' },\n { code: 'KeyD', label: 'D', labelKorean: '우', row: 2, col: 2, category: 'movement', description: 'Move Right', descriptionKorean: '우측 이동' },\n { code: 'KeyF', label: 'F', row: 2, col: 3, category: 'technique', description: 'Technique 6', descriptionKorean: '기술 6' },\n { code: 'KeyG', label: 'G', row: 2, col: 4, category: 'technique', description: 'Technique 7', descriptionKorean: '기술 7' },\n \n // ZXCV row (Techniques + Special)\n { code: 'KeyZ', label: 'Z', row: 3, col: 0, category: 'technique', description: 'Technique 8', descriptionKorean: '기술 8' },\n { code: 'KeyX', label: 'X', row: 3, col: 1, category: 'technique', description: 'Technique 9', descriptionKorean: '기술 9' },\n { code: 'KeyC', label: 'C', row: 3, col: 2, category: 'technique', description: 'Technique 10', descriptionKorean: '기술 10' },\n { code: 'KeyV', label: 'V', labelKorean: '급소', row: 3, col: 3, category: 'combat', description: 'Vital Points', descriptionKorean: '급소 표시' },\n { code: 'KeyB', label: 'B', labelKorean: '방어', row: 3, col: 4, category: 'combat', description: 'Block/Guard', descriptionKorean: '방어' },\n \n // Space bar\n { code: 'Space', label: 'Space', labelKorean: '공격', row: 4, col: 2, width: 3, category: 'combat', description: 'Attack', descriptionKorean: '공격' },\n \n // Modifiers\n { code: 'ShiftLeft', label: 'Shift', labelKorean: '전술', row: 3, col: -1, width: 1, category: 'modifier', description: 'Tactical Step', descriptionKorean: '전술 보법' },\n { code: 'ControlLeft', label: 'Ctrl', labelKorean: '보법', row: 4, col: -1, width: 1, category: 'modifier', description: 'Advanced Footwork', descriptionKorean: '고급 보법' },\n \n // Arrow keys\n { code: 'ArrowUp', label: '↑', labelKorean: '전진', row: 1, col: 10, category: 'movement', description: 'Move Forward', descriptionKorean: '전진' },\n { code: 'ArrowLeft', label: '←', labelKorean: '좌', row: 2, col: 9, category: 'movement', description: 'Move Left', descriptionKorean: '좌측 이동' },\n { code: 'ArrowDown', label: '↓', labelKorean: '후퇴', row: 2, col: 10, category: 'movement', description: 'Move Back', descriptionKorean: '후퇴' },\n { code: 'ArrowRight', label: '→', labelKorean: '우', row: 2, col: 11, category: 'movement', description: 'Move Right', descriptionKorean: '우측 이동' },\n \n // System keys\n { code: 'Escape', label: 'ESC', labelKorean: '일시정지', row: 0, col: -2, category: 'system', description: 'Pause Menu', descriptionKorean: '일시정지' },\n { code: 'KeyM', label: 'M', labelKorean: '메뉴', row: 2, col: 6, category: 'system', description: 'Menu', descriptionKorean: '메뉴' },\n { code: 'Tab', label: 'Tab', labelKorean: '전환', row: 1, col: -1, width: 1, category: 'system', description: 'Switch Archetype', descriptionKorean: '전환' },\n] as const;\n\n/**\n * Gamepad button mappings (Xbox-style controller)\n */\nexport const GAMEPAD_BUTTONS: readonly GamepadButton[] = [\n { index: 0, korean: 'A', english: 'A', action: 'Attack', actionKorean: '공격', color: KOREAN_COLORS.ACCENT_GREEN },\n { index: 1, korean: 'B', english: 'B', action: 'Block', actionKorean: '방어', color: KOREAN_COLORS.ACCENT_RED },\n { index: 2, korean: 'X', english: 'X', action: 'Technique 1', actionKorean: '기술 1', color: KOREAN_COLORS.ACCENT_BLUE },\n { index: 3, korean: 'Y', english: 'Y', action: 'Technique 2', actionKorean: '기술 2', color: KOREAN_COLORS.ACCENT_YELLOW },\n { index: 4, korean: 'LB', english: 'LB', action: 'Previous Stance', actionKorean: '이전 자세', color: KOREAN_COLORS.UI_STEEL_GRAY },\n { index: 5, korean: 'RB', english: 'RB', action: 'Next Stance', actionKorean: '다음 자세', color: KOREAN_COLORS.UI_STEEL_GRAY },\n { index: 6, korean: 'LT', english: 'LT', action: 'Vital Points', actionKorean: '급소 표시', color: KOREAN_COLORS.UI_STEEL_GRAY_DARK },\n { index: 7, korean: 'RT', english: 'RT', action: 'Special Attack', actionKorean: '특수 공격', color: KOREAN_COLORS.UI_STEEL_GRAY_DARK },\n { index: 8, korean: 'Back', english: 'Back', action: 'Menu', actionKorean: '메뉴', color: KOREAN_COLORS.UI_BORDER },\n { index: 9, korean: 'Start', english: 'Start', action: 'Pause', actionKorean: '일시정지', color: KOREAN_COLORS.UI_BORDER },\n { index: 10, korean: 'L3', english: 'L3', action: 'Lock Target', actionKorean: '목표 고정', color: KOREAN_COLORS.UI_DISABLED_TEXT },\n { index: 11, korean: 'R3', english: 'R3', action: 'Camera Reset', actionKorean: '카메라 리셋', color: KOREAN_COLORS.UI_DISABLED_TEXT },\n] as const;\n\n/**\n * Get key category color\n */\nexport function getKeyCategoryColor(category: KeyCategory): number {\n switch (category) {\n case 'stance':\n return KOREAN_COLORS.ACCENT_GOLD;\n case 'movement':\n return KOREAN_COLORS.PRIMARY_CYAN;\n case 'combat':\n return KOREAN_COLORS.ACCENT_RED;\n case 'technique':\n return KOREAN_COLORS.ACCENT_PURPLE;\n case 'system':\n return KOREAN_COLORS.ACCENT_ORANGE;\n case 'modifier':\n return KOREAN_COLORS.ACCENT_BLUE;\n default:\n return KOREAN_COLORS.UI_STEEL_GRAY;\n }\n}\n\n/**\n * Filter keys by category\n */\nexport function filterKeysByCategory(keys: readonly KeyData[], category: string): readonly KeyData[] {\n return keys.filter(key => {\n if (category === 'combat') {\n return key.category === 'combat' || key.category === 'stance' || key.category === 'technique';\n }\n if (category === 'movement') {\n return key.category === 'movement' || key.category === 'modifier';\n }\n if (category === 'system') {\n return key.category === 'system';\n }\n return false;\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ControlsConstants.js","names":[],"sources":["../../../../../src/components/screens/controls/constants/ControlsConstants.ts"],"sourcesContent":["/**\n * Control constants and layouts for Black Trigram (흑괘)\n * Defines keyboard layouts, gamepad mappings, and control categories\n * \n * @module components/screens/controls/constants\n */\n\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\n\n/**\n * Keyboard key data structure\n */\nexport interface KeyData {\n readonly code: string;\n readonly label: string;\n readonly labelKorean?: string;\n readonly row: number;\n readonly col: number;\n readonly width?: number; // Default is 1\n readonly category: KeyCategory;\n readonly description?: string;\n readonly descriptionKorean?: string;\n}\n\n/**\n * Control categories for organizing controls\n */\nexport type KeyCategory = 'stance' | 'movement' | 'combat' | 'system' | 'technique' | 'modifier' | 'normal';\n\n/**\n * Control category configuration\n */\nexport interface ControlCategory {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly icon: string;\n readonly color: number;\n}\n\n/**\n * Gamepad button mapping\n */\nexport interface GamepadButton {\n readonly index: number;\n readonly korean: string;\n readonly english: string;\n readonly action: string;\n readonly actionKorean: string;\n readonly color: number;\n}\n\n/**\n * Control categories for tab navigation\n */\nexport const CONTROL_CATEGORIES: readonly ControlCategory[] = [\n {\n id: 'combat',\n korean: '전투',\n english: 'Combat',\n icon: '⚔️',\n color: KOREAN_COLORS.ACCENT_GOLD,\n },\n {\n id: 'movement',\n korean: '이동',\n english: 'Movement',\n icon: '🏃',\n color: KOREAN_COLORS.PRIMARY_CYAN,\n },\n {\n id: 'system',\n korean: '시스템',\n english: 'System',\n icon: '⚙️',\n color: KOREAN_COLORS.ACCENT_PURPLE,\n },\n] as const;\n\n/**\n * Keyboard layout - all important keys for the game\n * Positioned based on physical keyboard layout\n */\nexport const KEYBOARD_LAYOUT: readonly KeyData[] = [\n // Number row (Stances)\n { code: 'Digit1', label: '1', labelKorean: '건', row: 0, col: 0, category: 'stance', description: 'Geon (Heaven)', descriptionKorean: '건 (Heaven)' },\n { code: 'Digit2', label: '2', labelKorean: '태', row: 0, col: 1, category: 'stance', description: 'Tae (Lake)', descriptionKorean: '태 (Lake)' },\n { code: 'Digit3', label: '3', labelKorean: '리', row: 0, col: 2, category: 'stance', description: 'Li (Fire)', descriptionKorean: '리 (Fire)' },\n { code: 'Digit4', label: '4', labelKorean: '진', row: 0, col: 3, category: 'stance', description: 'Jin (Thunder)', descriptionKorean: '진 (Thunder)' },\n { code: 'Digit5', label: '5', labelKorean: '손', row: 0, col: 4, category: 'stance', description: 'Son (Wind)', descriptionKorean: '손 (Wind)' },\n { code: 'Digit6', label: '6', labelKorean: '감', row: 0, col: 5, category: 'stance', description: 'Gam (Water)', descriptionKorean: '감 (Water)' },\n { code: 'Digit7', label: '7', labelKorean: '간', row: 0, col: 6, category: 'stance', description: 'Gan (Mountain)', descriptionKorean: '간 (Mountain)' },\n { code: 'Digit8', label: '8', labelKorean: '곤', row: 0, col: 7, category: 'stance', description: 'Gon (Earth)', descriptionKorean: '곤 (Earth)' },\n \n // QWERTY row (Techniques + Movement)\n { code: 'KeyQ', label: 'Q', row: 1, col: 0, category: 'technique', description: 'Technique 1', descriptionKorean: '기술 1' },\n { code: 'KeyW', label: 'W', labelKorean: '전진', row: 1, col: 1, category: 'movement', description: 'Move Forward', descriptionKorean: '전진' },\n { code: 'KeyE', label: 'E', row: 1, col: 2, category: 'technique', description: 'Technique 2', descriptionKorean: '기술 2' },\n { code: 'KeyR', label: 'R', row: 1, col: 3, category: 'technique', description: 'Technique 3', descriptionKorean: '기술 3' },\n { code: 'KeyT', label: 'T', row: 1, col: 4, category: 'technique', description: 'Technique 4', descriptionKorean: '기술 4' },\n { code: 'KeyY', label: 'Y', row: 1, col: 5, category: 'technique', description: 'Technique 5', descriptionKorean: '기술 5' },\n \n // ASDF row (Movement + Techniques)\n { code: 'KeyA', label: 'A', labelKorean: '좌', row: 2, col: 0, category: 'movement', description: 'Move Left', descriptionKorean: '좌측 이동' },\n { code: 'KeyS', label: 'S', labelKorean: '후퇴', row: 2, col: 1, category: 'movement', description: 'Move Back', descriptionKorean: '후퇴' },\n { code: 'KeyD', label: 'D', labelKorean: '우', row: 2, col: 2, category: 'movement', description: 'Move Right', descriptionKorean: '우측 이동' },\n { code: 'KeyF', label: 'F', row: 2, col: 3, category: 'technique', description: 'Technique 6', descriptionKorean: '기술 6' },\n { code: 'KeyG', label: 'G', row: 2, col: 4, category: 'technique', description: 'Technique 7', descriptionKorean: '기술 7' },\n \n // ZXCV row (Techniques + Special)\n { code: 'KeyZ', label: 'Z', row: 3, col: 0, category: 'technique', description: 'Technique 8', descriptionKorean: '기술 8' },\n { code: 'KeyX', label: 'X', row: 3, col: 1, category: 'technique', description: 'Technique 9', descriptionKorean: '기술 9' },\n { code: 'KeyC', label: 'C', row: 3, col: 2, category: 'technique', description: 'Technique 10', descriptionKorean: '기술 10' },\n { code: 'KeyV', label: 'V', labelKorean: '급소', row: 3, col: 3, category: 'combat', description: 'Vital Points', descriptionKorean: '급소 표시' },\n { code: 'KeyB', label: 'B', labelKorean: '방어', row: 3, col: 4, category: 'combat', description: 'Block/Guard', descriptionKorean: '방어' },\n \n // Space bar\n { code: 'Space', label: 'Space', labelKorean: '공격', row: 4, col: 2, width: 3, category: 'combat', description: 'Attack', descriptionKorean: '공격' },\n \n // Modifiers\n { code: 'ShiftLeft', label: 'Shift', labelKorean: '전술', row: 3, col: -1, width: 1, category: 'modifier', description: 'Tactical Step', descriptionKorean: '전술 보법' },\n { code: 'ControlLeft', label: 'Ctrl', labelKorean: '보법', row: 4, col: -1, width: 1, category: 'modifier', description: 'Advanced Footwork', descriptionKorean: '고급 보법' },\n \n // Arrow keys\n { code: 'ArrowUp', label: '↑', labelKorean: '전진', row: 1, col: 10, category: 'movement', description: 'Move Forward', descriptionKorean: '전진' },\n { code: 'ArrowLeft', label: '←', labelKorean: '좌', row: 2, col: 9, category: 'movement', description: 'Move Left', descriptionKorean: '좌측 이동' },\n { code: 'ArrowDown', label: '↓', labelKorean: '후퇴', row: 2, col: 10, category: 'movement', description: 'Move Back', descriptionKorean: '후퇴' },\n { code: 'ArrowRight', label: '→', labelKorean: '우', row: 2, col: 11, category: 'movement', description: 'Move Right', descriptionKorean: '우측 이동' },\n \n // System keys\n { code: 'Escape', label: 'ESC', labelKorean: '일시정지', row: 0, col: -2, category: 'system', description: 'Pause Menu', descriptionKorean: '일시정지' },\n { code: 'KeyM', label: 'M', labelKorean: '메뉴', row: 2, col: 6, category: 'system', description: 'Menu', descriptionKorean: '메뉴' },\n { code: 'Tab', label: 'Tab', labelKorean: '전환', row: 1, col: -1, width: 1, category: 'system', description: 'Switch Archetype', descriptionKorean: '전환' },\n] as const;\n\n/**\n * Gamepad button mappings (Xbox-style controller)\n */\nexport const GAMEPAD_BUTTONS: readonly GamepadButton[] = [\n { index: 0, korean: 'A', english: 'A', action: 'Attack', actionKorean: '공격', color: KOREAN_COLORS.ACCENT_GREEN },\n { index: 1, korean: 'B', english: 'B', action: 'Block', actionKorean: '방어', color: KOREAN_COLORS.ACCENT_RED },\n { index: 2, korean: 'X', english: 'X', action: 'Technique 1', actionKorean: '기술 1', color: KOREAN_COLORS.ACCENT_BLUE },\n { index: 3, korean: 'Y', english: 'Y', action: 'Technique 2', actionKorean: '기술 2', color: KOREAN_COLORS.ACCENT_YELLOW },\n { index: 4, korean: 'LB', english: 'LB', action: 'Previous Stance', actionKorean: '이전 자세', color: KOREAN_COLORS.UI_STEEL_GRAY },\n { index: 5, korean: 'RB', english: 'RB', action: 'Next Stance', actionKorean: '다음 자세', color: KOREAN_COLORS.UI_STEEL_GRAY },\n { index: 6, korean: 'LT', english: 'LT', action: 'Vital Points', actionKorean: '급소 표시', color: KOREAN_COLORS.UI_STEEL_GRAY_DARK },\n { index: 7, korean: 'RT', english: 'RT', action: 'Special Attack', actionKorean: '특수 공격', color: KOREAN_COLORS.UI_STEEL_GRAY_DARK },\n { index: 8, korean: 'Back', english: 'Back', action: 'Menu', actionKorean: '메뉴', color: KOREAN_COLORS.UI_BORDER },\n { index: 9, korean: 'Start', english: 'Start', action: 'Pause', actionKorean: '일시정지', color: KOREAN_COLORS.UI_BORDER },\n { index: 10, korean: 'L3', english: 'L3', action: 'Lock Target', actionKorean: '목표 고정', color: KOREAN_COLORS.UI_DISABLED_TEXT },\n { index: 11, korean: 'R3', english: 'R3', action: 'Camera Reset', actionKorean: '카메라 리셋', color: KOREAN_COLORS.UI_DISABLED_TEXT },\n] as const;\n\n/**\n * Get key category color\n */\nexport function getKeyCategoryColor(category: KeyCategory): number {\n switch (category) {\n case 'stance':\n return KOREAN_COLORS.ACCENT_GOLD;\n case 'movement':\n return KOREAN_COLORS.PRIMARY_CYAN;\n case 'combat':\n return KOREAN_COLORS.ACCENT_RED;\n case 'technique':\n return KOREAN_COLORS.ACCENT_PURPLE;\n case 'system':\n return KOREAN_COLORS.ACCENT_ORANGE;\n case 'modifier':\n return KOREAN_COLORS.ACCENT_BLUE;\n default:\n return KOREAN_COLORS.UI_STEEL_GRAY;\n }\n}\n\n/**\n * Filter keys by category\n */\nexport function filterKeysByCategory(keys: readonly KeyData[], category: string): readonly KeyData[] {\n return keys.filter(key => {\n if (category === 'combat') {\n return key.category === 'combat' || key.category === 'stance' || key.category === 'technique';\n }\n if (category === 'movement') {\n return key.category === 'movement' || key.category === 'modifier';\n }\n if (category === 'system') {\n return key.category === 'system';\n }\n return false;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useControlsState.js","names":[],"sources":["../../../../../src/components/screens/controls/hooks/useControlsState.ts"],"sourcesContent":["/**\n * useControlsState - State management hook for Controls Screen\n * \n * Manages keyboard press detection, gamepad state, and category selection\n * \n * @module components/screens/controls/hooks\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\n/**\n * Controls state interface\n */\nexport interface ControlsState {\n readonly pressedKeys: Set<string>;\n readonly category: 'keyboard' | 'gamepad';\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * Hook return type\n */\nexport interface UseControlsStateReturn {\n readonly pressedKeys: Set<string>;\n readonly category: 'keyboard' | 'gamepad';\n readonly selectedTab: 'combat' | 'movement' | 'system';\n readonly setCategory: (category: 'keyboard' | 'gamepad') => void;\n readonly setSelectedTab: (tab: 'combat' | 'movement' | 'system') => void;\n}\n\n/**\n * Custom hook for managing controls screen state\n * \n * Features:\n * - Keyboard press detection with cleanup\n * - Category switching (keyboard/gamepad)\n * - Tab selection for control categories\n * \n * @example\n * ```tsx\n * const { pressedKeys, category, selectedTab, setCategory, setSelectedTab } = useControlsState();\n * \n * // Check if key is pressed\n * const isSpacePressed = pressedKeys.has('Space');\n * \n * // Switch to gamepad view\n * setCategory('gamepad');\n * ```\n */\nexport function useControlsState(): UseControlsStateReturn {\n const [pressedKeys, setPressedKeys] = useState<Set<string>>(new Set());\n const [category, setCategory] = useState<'keyboard' | 'gamepad'>('keyboard');\n const [selectedTab, setSelectedTab] = useState<'combat' | 'movement' | 'system'>('combat');\n\n // Keyboard event handlers\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n // Don't track if user is typing in input field\n if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {\n return;\n }\n\n setPressedKeys(prev => {\n const next = new Set(prev);\n next.add(event.code);\n return next;\n });\n };\n\n const handleKeyUp = (event: KeyboardEvent) => {\n setPressedKeys(prev => {\n const next = new Set(prev);\n next.delete(event.code);\n return next;\n });\n };\n\n // Add event listeners\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n\n // Cleanup on unmount\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, []);\n\n // Memoized category setter\n const handleSetCategory = useCallback((newCategory: 'keyboard' | 'gamepad') => {\n setCategory(newCategory);\n }, []);\n\n // Memoized tab setter\n const handleSetSelectedTab = useCallback((tab: 'combat' | 'movement' | 'system') => {\n setSelectedTab(tab);\n }, []);\n\n return {\n pressedKeys,\n category,\n selectedTab,\n setCategory: handleSetCategory,\n setSelectedTab: handleSetSelectedTab,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,mBAA2C;CACzD,MAAM,CAAC,aAAa,kBAAkB,yBAAsB,IAAI,KAAK,CAAC;CACtE,MAAM,CAAC,UAAU,eAAe,SAAiC,WAAW;CAC5E,MAAM,CAAC,aAAa,kBAAkB,SAA2C,SAAS;
|
|
1
|
+
{"version":3,"file":"useControlsState.js","names":[],"sources":["../../../../../src/components/screens/controls/hooks/useControlsState.ts"],"sourcesContent":["/**\n * useControlsState - State management hook for Controls Screen\n * \n * Manages keyboard press detection, gamepad state, and category selection\n * \n * @module components/screens/controls/hooks\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\n/**\n * Controls state interface\n */\nexport interface ControlsState {\n readonly pressedKeys: Set<string>;\n readonly category: 'keyboard' | 'gamepad';\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * Hook return type\n */\nexport interface UseControlsStateReturn {\n readonly pressedKeys: Set<string>;\n readonly category: 'keyboard' | 'gamepad';\n readonly selectedTab: 'combat' | 'movement' | 'system';\n readonly setCategory: (category: 'keyboard' | 'gamepad') => void;\n readonly setSelectedTab: (tab: 'combat' | 'movement' | 'system') => void;\n}\n\n/**\n * Custom hook for managing controls screen state\n * \n * Features:\n * - Keyboard press detection with cleanup\n * - Category switching (keyboard/gamepad)\n * - Tab selection for control categories\n * \n * @example\n * ```tsx\n * const { pressedKeys, category, selectedTab, setCategory, setSelectedTab } = useControlsState();\n * \n * // Check if key is pressed\n * const isSpacePressed = pressedKeys.has('Space');\n * \n * // Switch to gamepad view\n * setCategory('gamepad');\n * ```\n */\nexport function useControlsState(): UseControlsStateReturn {\n const [pressedKeys, setPressedKeys] = useState<Set<string>>(new Set());\n const [category, setCategory] = useState<'keyboard' | 'gamepad'>('keyboard');\n const [selectedTab, setSelectedTab] = useState<'combat' | 'movement' | 'system'>('combat');\n\n // Keyboard event handlers\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n // Don't track if user is typing in input field\n if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {\n return;\n }\n\n setPressedKeys(prev => {\n const next = new Set(prev);\n next.add(event.code);\n return next;\n });\n };\n\n const handleKeyUp = (event: KeyboardEvent) => {\n setPressedKeys(prev => {\n const next = new Set(prev);\n next.delete(event.code);\n return next;\n });\n };\n\n // Add event listeners\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n\n // Cleanup on unmount\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, []);\n\n // Memoized category setter\n const handleSetCategory = useCallback((newCategory: 'keyboard' | 'gamepad') => {\n setCategory(newCategory);\n }, []);\n\n // Memoized tab setter\n const handleSetSelectedTab = useCallback((tab: 'combat' | 'movement' | 'system') => {\n setSelectedTab(tab);\n }, []);\n\n return {\n pressedKeys,\n category,\n selectedTab,\n setCategory: handleSetCategory,\n setSelectedTab: handleSetSelectedTab,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,mBAA2C;CACzD,MAAM,CAAC,aAAa,kBAAkB,yBAAsB,IAAI,KAAK,CAAC;CACtE,MAAM,CAAC,UAAU,eAAe,SAAiC,WAAW;CAC5E,MAAM,CAAC,aAAa,kBAAkB,SAA2C,SAAS;CAG1F,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAE9C,IAAI,MAAM,kBAAkB,oBAAoB,MAAM,kBAAkB,qBACtE;GAGF,gBAAe,SAAQ;IACrB,MAAM,OAAO,IAAI,IAAI,KAAK;IAC1B,KAAK,IAAI,MAAM,KAAK;IACpB,OAAO;KACP;;EAGJ,MAAM,eAAe,UAAyB;GAC5C,gBAAe,SAAQ;IACrB,MAAM,OAAO,IAAI,IAAI,KAAK;IAC1B,KAAK,OAAO,MAAM,KAAK;IACvB,OAAO;KACP;;EAIJ,OAAO,iBAAiB,WAAW,cAAc;EACjD,OAAO,iBAAiB,SAAS,YAAY;EAG7C,aAAa;GACX,OAAO,oBAAoB,WAAW,cAAc;GACpD,OAAO,oBAAoB,SAAS,YAAY;;IAEjD,EAAE,CAAC;CAYN,OAAO;EACL;EACA;EACA;EACA,aAbwB,aAAa,gBAAwC;GAC7E,YAAY,YAAY;KACvB,EAAE,CAWU;EACb,gBAT2B,aAAa,QAA0C;GAClF,eAAe,IAAI;KAClB,EAAE,CAOa;EACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EndScreen3D.js","names":[],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n // Use useState with lazy initializer for random values - this is only called once\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n // Wrap around\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n // Handle WebGL context loss and restoration\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n // Use window size for responsive layout with resize support\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n // Note: windowHeight available but not currently used - screenWidth sufficient for current responsive logic\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n // Determine if this is a victory screen (winner is player 0 by convention - extracted from id)\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n // Responsive layout with proper device detection\n // Uses user-agent detection for mobile controls (supports high-res phones)\n // User-agent doesn't change during session, so no dependencies needed\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Play victory/defeat audio on mount\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n // Audio callbacks for NavigationButtons (inside Html portal which doesn't have AudioProvider context)\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EndScreen3D.js","names":[],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n // Use useState with lazy initializer for random values - this is only called once\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n // Wrap around\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n // Handle WebGL context loss and restoration\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n // Use window size for responsive layout with resize support\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n // Note: windowHeight available but not currently used - screenWidth sufficient for current responsive logic\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n // Determine if this is a victory screen (winner is player 0 by convention - extracted from id)\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n // Responsive layout with proper device detection\n // Uses user-agent detection for mobile controls (supports high-res phones)\n // User-agent doesn't change during session, so no dependencies needed\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Play victory/defeat audio on mount\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n // Audio callbacks for NavigationButtons (inside Html portal which doesn't have AudioProvider context)\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefeatAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/DefeatAnimation3D.tsx"],"sourcesContent":["import { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Defeat Animation 3D Component\n * Displays somber 3D particle effects for defeat screen\n * Uses blue/cyan tones to contrast with gold victory effects\n * Optimized for 60fps performance with object reuse\n */\nexport const DefeatAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const spiralRef = useRef<THREE.Group>(null);\n\n // Reusable objects for animation calculations to avoid per-frame allocations\n // These are reused across all animation frames for scale and position updates\n const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\n // Create defeat particles - use useState with lazy initializer\n const [particlePositions] = useState(() => {\n const count = 100; // Fewer particles than victory for subdued effect\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 2 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.random() * Math.PI;\n\n positions[i3] = radius * Math.sin(phi) * Math.cos(theta);\n positions[i3 + 1] = radius * Math.cos(phi) - 1; // Lower starting position\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Animate defeat effects with slower, descending motion - optimized for 60fps\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n // Slow rotation\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.15; // Slower than victory\n }\n\n // Fade particles downward - use reusable objects\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 1.5) * 0.1; // Subtle pulsing\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n // Slowly descend\n const yPos = Math.sin(time * 0.5) * 0.3 - 0.2;\n reusablePosition.set(0, yPos, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n // Spiral effect - slower, descending\n if (spiralRef.current) {\n spiralRef.current.rotation.y = -time * 0.2; // Counter-rotation\n spiralRef.current.rotation.z = Math.sin(time * 0.3) * 0.2;\n }\n });\n\n // Cleanup Three.js resources on unmount\n useEffect(() => {\n // Capture ref values at effect setup time to avoid stale references in cleanup\n const group = groupRef.current;\n const particles = particlesRef.current;\n const spiral = spiralRef.current;\n\n return () => {\n // Dispose geometries and materials to prevent memory leaks\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (spiral?.children && Array.isArray(spiral.children)) {\n spiral.children.forEach((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n // Additionally iterate over all group children to catch meshes/points without explicit refs\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n // Skip already-handled refs\n if (child === particles || child === spiral) {\n return;\n }\n\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n } else if (child instanceof THREE.Points) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n };\n }, []);\n\n return (\n <group\n ref={groupRef}\n position={[0, 1, 0]}\n data-testid=\"defeat-animation-3d\"\n >\n {/* Defeat particles - blue/cyan theme */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_BLUE)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Descending spiral rings */}\n <group ref={spiralRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[1.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]} position={[0, -0.3, 0]}>\n <torusGeometry args={[2, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n transparent\n opacity={0.3}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]} position={[0, -0.6, 0]}>\n <torusGeometry args={[2.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n />\n </mesh>\n </group>\n\n {/* Central dimmed sphere */}\n <mesh>\n <sphereGeometry args={[0.4, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n emissive={KOREAN_COLORS.ACCENT_BLUE}\n emissiveIntensity={0.8}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n {/* Outer faint glow */}\n <mesh>\n <sphereGeometry args={[0.7, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.15}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for subdued glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={1.5}\n distance={8}\n color={KOREAN_COLORS.ACCENT_BLUE}\n />\n </group>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefeatAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/DefeatAnimation3D.tsx"],"sourcesContent":["import { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Defeat Animation 3D Component\n * Displays somber 3D particle effects for defeat screen\n * Uses blue/cyan tones to contrast with gold victory effects\n * Optimized for 60fps performance with object reuse\n */\nexport const DefeatAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const spiralRef = useRef<THREE.Group>(null);\n\n // Reusable objects for animation calculations to avoid per-frame allocations\n // These are reused across all animation frames for scale and position updates\n const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\n // Create defeat particles - use useState with lazy initializer\n const [particlePositions] = useState(() => {\n const count = 100; // Fewer particles than victory for subdued effect\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 2 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.random() * Math.PI;\n\n positions[i3] = radius * Math.sin(phi) * Math.cos(theta);\n positions[i3 + 1] = radius * Math.cos(phi) - 1; // Lower starting position\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Animate defeat effects with slower, descending motion - optimized for 60fps\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n // Slow rotation\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.15; // Slower than victory\n }\n\n // Fade particles downward - use reusable objects\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 1.5) * 0.1; // Subtle pulsing\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n // Slowly descend\n const yPos = Math.sin(time * 0.5) * 0.3 - 0.2;\n reusablePosition.set(0, yPos, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n // Spiral effect - slower, descending\n if (spiralRef.current) {\n spiralRef.current.rotation.y = -time * 0.2; // Counter-rotation\n spiralRef.current.rotation.z = Math.sin(time * 0.3) * 0.2;\n }\n });\n\n // Cleanup Three.js resources on unmount\n useEffect(() => {\n // Capture ref values at effect setup time to avoid stale references in cleanup\n const group = groupRef.current;\n const particles = particlesRef.current;\n const spiral = spiralRef.current;\n\n return () => {\n // Dispose geometries and materials to prevent memory leaks\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (spiral?.children && Array.isArray(spiral.children)) {\n spiral.children.forEach((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n // Additionally iterate over all group children to catch meshes/points without explicit refs\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n // Skip already-handled refs\n if (child === particles || child === spiral) {\n return;\n }\n\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n } else if (child instanceof THREE.Points) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n };\n }, []);\n\n return (\n <group\n ref={groupRef}\n position={[0, 1, 0]}\n data-testid=\"defeat-animation-3d\"\n >\n {/* Defeat particles - blue/cyan theme */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_BLUE)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Descending spiral rings */}\n <group ref={spiralRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[1.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]} position={[0, -0.3, 0]}>\n <torusGeometry args={[2, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n transparent\n opacity={0.3}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]} position={[0, -0.6, 0]}>\n <torusGeometry args={[2.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n />\n </mesh>\n </group>\n\n {/* Central dimmed sphere */}\n <mesh>\n <sphereGeometry args={[0.4, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n emissive={KOREAN_COLORS.ACCENT_BLUE}\n emissiveIntensity={0.8}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n {/* Outer faint glow */}\n <mesh>\n <sphereGeometry args={[0.7, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.15}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for subdued glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={1.5}\n distance={8}\n color={KOREAN_COLORS.ACCENT_BLUE}\n />\n </group>\n );\n};\n"],"mappings":"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"}
|
package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MatchStatisticsDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface MatchStatisticsDisplayProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Props for StatRow component\n */\ninterface StatRowProps {\n readonly label: string;\n readonly value: string | number;\n readonly highlight?: boolean;\n readonly fontSize: number;\n readonly spacing: number;\n}\n\n/**\n * Individual stat row component\n */\nconst StatRow: React.FC<StatRowProps> = ({\n label,\n value,\n highlight = false,\n fontSize,\n spacing,\n}) => (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: spacing / 2,\n fontSize,\n color: toCssColor(\n highlight ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_SECONDARY\n ),\n }}\n >\n <span>{label}:</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n {value}\n </span>\n </div>\n);\n\n/**\n * Props for PlayerStats component\n */\ninterface PlayerStatsProps {\n readonly playerNum: 1 | 2;\n readonly stats: PlayerMatchStats;\n readonly isWinner: boolean;\n readonly fontSize: number;\n readonly labelFontSize: number;\n readonly padding: number;\n readonly spacing: number;\n}\n\n/**\n * Player statistics panel component\n */\nconst PlayerStats: React.FC<PlayerStatsProps> = ({\n playerNum,\n stats,\n isWinner,\n fontSize,\n labelFontSize,\n padding,\n spacing,\n}) => {\n return (\n <div\n style={{\n flex: 1,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"8px\",\n padding,\n }}\n data-testid={`player${playerNum}-stats`}\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.PRIMARY_CYAN\n ),\n marginBottom: spacing,\n textAlign: \"center\",\n }}\n >\n 플레이어 {playerNum} | Player {playerNum}\n {isWinner && \" 🏆\"}\n </div>\n\n <StatRow\n label=\"승리 | Wins\"\n value={stats.wins}\n highlight={stats.wins > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"패배 | Losses\"\n value={stats.losses}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"가한 피해 | Damage Dealt\"\n value={stats.totalDamageDealt}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"받은 피해 | Damage Taken\"\n value={stats.totalDamageReceived}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"명중 | Hits Landed\"\n value={stats.hitsLanded}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"피격 | Hits Taken\"\n value={stats.hitsTaken}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"완벽한 타격 | Perfect Strikes\"\n value={stats.perfectStrikes}\n highlight={stats.perfectStrikes > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"급소 공격 | Vital Point Hits\"\n value={stats.vitalPointHits}\n highlight={stats.vitalPointHits > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"연승 | Consecutive Wins\"\n value={stats.consecutiveWins}\n fontSize={fontSize}\n spacing={spacing}\n />\n\n {stats.techniques && stats.techniques.length > 0 && (\n <div style={{ marginTop: spacing }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: spacing / 2,\n }}\n >\n 사용한 기술 | Techniques Used:\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n paddingLeft: spacing,\n }}\n >\n {stats.techniques.slice(0, 5).join(\", \")}\n {stats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Match Statistics Display Component\n * Shows detailed combat statistics for both players\n */\nexport const MatchStatisticsDisplay: React.FC<MatchStatisticsDisplayProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 14 : 16;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 10 : isTablet ? 15 : 20;\n const spacing = isMobile ? 8 : isTablet ? 10 : 12;\n\n return (\n <div\n data-testid=\"match-statistics-display\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: spacing * 2,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Overall Match Stats */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: spacing * 1.5,\n }}\n >\n 경기 통계 | Match Statistics\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"repeat(4, 1fr)\",\n gap: spacing,\n marginBottom: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"overall-stats\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 라운드 | Rounds\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.currentRound} / {matchStats.maxRounds}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 시간 | Duration\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {Math.floor(matchStats.matchDuration / 60)}:\n {(matchStats.matchDuration % 60).toString().padStart(2, \"0\")}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 치명타 | Critical Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 공격 | Vital Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.vitalPointHits}\n </div>\n </div>\n </div>\n\n {/* Player Stats Side by Side */}\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing * 1.5,\n }}\n >\n <PlayerStats\n playerNum={1}\n stats={matchStats.player1}\n isWinner={matchStats.winner === 0}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n <PlayerStats\n playerNum={2}\n stats={matchStats.player2}\n isWinner={matchStats.winner === 1}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n </div>\n\n {/* Score Display */}\n <div\n style={{\n marginTop: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"final-score\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: spacing / 2,\n }}\n >\n 최종 점수 | Final Score\n </div>\n <div\n style={{\n fontSize: labelFontSize + 4,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {matchStats.finalScore.player1} - {matchStats.finalScore.player2}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MatchStatisticsDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface MatchStatisticsDisplayProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Props for StatRow component\n */\ninterface StatRowProps {\n readonly label: string;\n readonly value: string | number;\n readonly highlight?: boolean;\n readonly fontSize: number;\n readonly spacing: number;\n}\n\n/**\n * Individual stat row component\n */\nconst StatRow: React.FC<StatRowProps> = ({\n label,\n value,\n highlight = false,\n fontSize,\n spacing,\n}) => (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: spacing / 2,\n fontSize,\n color: toCssColor(\n highlight ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_SECONDARY\n ),\n }}\n >\n <span>{label}:</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n {value}\n </span>\n </div>\n);\n\n/**\n * Props for PlayerStats component\n */\ninterface PlayerStatsProps {\n readonly playerNum: 1 | 2;\n readonly stats: PlayerMatchStats;\n readonly isWinner: boolean;\n readonly fontSize: number;\n readonly labelFontSize: number;\n readonly padding: number;\n readonly spacing: number;\n}\n\n/**\n * Player statistics panel component\n */\nconst PlayerStats: React.FC<PlayerStatsProps> = ({\n playerNum,\n stats,\n isWinner,\n fontSize,\n labelFontSize,\n padding,\n spacing,\n}) => {\n return (\n <div\n style={{\n flex: 1,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"8px\",\n padding,\n }}\n data-testid={`player${playerNum}-stats`}\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.PRIMARY_CYAN\n ),\n marginBottom: spacing,\n textAlign: \"center\",\n }}\n >\n 플레이어 {playerNum} | Player {playerNum}\n {isWinner && \" 🏆\"}\n </div>\n\n <StatRow\n label=\"승리 | Wins\"\n value={stats.wins}\n highlight={stats.wins > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"패배 | Losses\"\n value={stats.losses}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"가한 피해 | Damage Dealt\"\n value={stats.totalDamageDealt}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"받은 피해 | Damage Taken\"\n value={stats.totalDamageReceived}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"명중 | Hits Landed\"\n value={stats.hitsLanded}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"피격 | Hits Taken\"\n value={stats.hitsTaken}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"완벽한 타격 | Perfect Strikes\"\n value={stats.perfectStrikes}\n highlight={stats.perfectStrikes > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"급소 공격 | Vital Point Hits\"\n value={stats.vitalPointHits}\n highlight={stats.vitalPointHits > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"연승 | Consecutive Wins\"\n value={stats.consecutiveWins}\n fontSize={fontSize}\n spacing={spacing}\n />\n\n {stats.techniques && stats.techniques.length > 0 && (\n <div style={{ marginTop: spacing }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: spacing / 2,\n }}\n >\n 사용한 기술 | Techniques Used:\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n paddingLeft: spacing,\n }}\n >\n {stats.techniques.slice(0, 5).join(\", \")}\n {stats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Match Statistics Display Component\n * Shows detailed combat statistics for both players\n */\nexport const MatchStatisticsDisplay: React.FC<MatchStatisticsDisplayProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 14 : 16;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 10 : isTablet ? 15 : 20;\n const spacing = isMobile ? 8 : isTablet ? 10 : 12;\n\n return (\n <div\n data-testid=\"match-statistics-display\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: spacing * 2,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Overall Match Stats */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: spacing * 1.5,\n }}\n >\n 경기 통계 | Match Statistics\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"repeat(4, 1fr)\",\n gap: spacing,\n marginBottom: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"overall-stats\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 라운드 | Rounds\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.currentRound} / {matchStats.maxRounds}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 시간 | Duration\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {Math.floor(matchStats.matchDuration / 60)}:\n {(matchStats.matchDuration % 60).toString().padStart(2, \"0\")}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 치명타 | Critical Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 공격 | Vital Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.vitalPointHits}\n </div>\n </div>\n </div>\n\n {/* Player Stats Side by Side */}\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing * 1.5,\n }}\n >\n <PlayerStats\n playerNum={1}\n stats={matchStats.player1}\n isWinner={matchStats.winner === 0}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n <PlayerStats\n playerNum={2}\n stats={matchStats.player2}\n isWinner={matchStats.winner === 1}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n </div>\n\n {/* Score Display */}\n <div\n style={{\n marginTop: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"final-score\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: spacing / 2,\n }}\n >\n 최종 점수 | Final Score\n </div>\n <div\n style={{\n fontSize: labelFontSize + 4,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {matchStats.finalScore.player1} - {matchStats.finalScore.player2}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/NavigationButtonsOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { slideUpAnimation } from \"./animations\";\n\nexport interface NavigationButtonsProps {\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n readonly width: number;\n /** Optional audio callback for click sounds - passed from parent to avoid Html portal context issues */\n readonly onPlaySelectSound?: () => void;\n /** Optional audio callback for hover sounds - passed from parent to avoid Html portal context issues */\n readonly onPlayHoverSound?: () => void;\n}\n\n/**\n * Navigation Buttons Component\n * Provides action buttons for replay and menu navigation.\n *\n * This component delegates visual styling and accessibility behavior to\n * BaseButtonOverlayHtml, providing:\n * - Consistent Korean / English bilingual theming\n * - Centralized button behavior and standard browser keyboard support\n * - Reduced code duplication (113 lines saved: 237 → 124)\n *\n * Note: Audio callbacks are passed as props since this component is rendered\n * inside a Canvas Html portal which doesn't have access to AudioProvider context.\n */\nexport const NavigationButtons: React.FC<NavigationButtonsProps> = ({\n onReturnToMenu,\n onRematch,\n onViewReplay,\n isMobile,\n isTablet,\n width,\n onPlaySelectSound,\n onPlayHoverSound,\n}) => {\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n \n // Determine button size based on screen width (resolution-based, not device detection)\n // Small screens (<768px): sm, Medium/Large (>=768px): md\n const buttonSize = width < 768 ? \"sm\" : \"md\";\n const buttonMinWidth = width < 768 ? \"200px\" : \"150px\";\n\n const handleReturnToMenu = useCallback(() => {\n onPlaySelectSound?.();\n onReturnToMenu();\n }, [onPlaySelectSound, onReturnToMenu]);\n\n const handleRematch = useCallback(() => {\n if (onRematch) {\n onPlaySelectSound?.();\n onRematch();\n }\n }, [onPlaySelectSound, onRematch]);\n\n const handleViewReplay = useCallback(() => {\n if (onViewReplay) {\n onPlaySelectSound?.();\n onViewReplay();\n }\n }, [onPlaySelectSound, onViewReplay]);\n\n return (\n <div\n data-testid=\"navigation-buttons\"\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing,\n marginTop: spacing * 2,\n animation: \"slideUp 0.6s ease-out 0.3s both\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {/* Return to Menu Button - Primary Action */}\n <BaseButtonOverlayHtml\n korean=\"메뉴로\"\n english=\"Return to Menu\"\n onClick={handleReturnToMenu}\n onMouseEnter={onPlayHoverSound}\n variant=\"primary\"\n size={buttonSize}\n testId=\"return-to-menu-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n\n {/* Rematch Button - Secondary Action */}\n {onRematch && (\n <BaseButtonOverlayHtml\n korean=\"재대결\"\n english=\"Rematch\"\n onClick={handleRematch}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"rematch-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* Training Mode Button - Tertiary Action */}\n {onViewReplay && (\n <BaseButtonOverlayHtml\n korean=\"훈련\"\n english=\"Training\"\n onClick={handleViewReplay}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"view-training-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* CSS Animations */}\n <style>{`\n ${slideUpAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,IAAa,qBAAuD,EAClE,gBACA,WACA,cACA,UACA,UACA,OACA,mBACA,uBACI;CACJ,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAIhD,MAAM,aAAa,QAAQ,MAAM,OAAO;CACxC,MAAM,iBAAiB,QAAQ,MAAM,UAAU;CAE/C,MAAM,qBAAqB,kBAAkB;
|
|
1
|
+
{"version":3,"file":"NavigationButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/NavigationButtonsOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { slideUpAnimation } from \"./animations\";\n\nexport interface NavigationButtonsProps {\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n readonly width: number;\n /** Optional audio callback for click sounds - passed from parent to avoid Html portal context issues */\n readonly onPlaySelectSound?: () => void;\n /** Optional audio callback for hover sounds - passed from parent to avoid Html portal context issues */\n readonly onPlayHoverSound?: () => void;\n}\n\n/**\n * Navigation Buttons Component\n * Provides action buttons for replay and menu navigation.\n *\n * This component delegates visual styling and accessibility behavior to\n * BaseButtonOverlayHtml, providing:\n * - Consistent Korean / English bilingual theming\n * - Centralized button behavior and standard browser keyboard support\n * - Reduced code duplication (113 lines saved: 237 → 124)\n *\n * Note: Audio callbacks are passed as props since this component is rendered\n * inside a Canvas Html portal which doesn't have access to AudioProvider context.\n */\nexport const NavigationButtons: React.FC<NavigationButtonsProps> = ({\n onReturnToMenu,\n onRematch,\n onViewReplay,\n isMobile,\n isTablet,\n width,\n onPlaySelectSound,\n onPlayHoverSound,\n}) => {\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n \n // Determine button size based on screen width (resolution-based, not device detection)\n // Small screens (<768px): sm, Medium/Large (>=768px): md\n const buttonSize = width < 768 ? \"sm\" : \"md\";\n const buttonMinWidth = width < 768 ? \"200px\" : \"150px\";\n\n const handleReturnToMenu = useCallback(() => {\n onPlaySelectSound?.();\n onReturnToMenu();\n }, [onPlaySelectSound, onReturnToMenu]);\n\n const handleRematch = useCallback(() => {\n if (onRematch) {\n onPlaySelectSound?.();\n onRematch();\n }\n }, [onPlaySelectSound, onRematch]);\n\n const handleViewReplay = useCallback(() => {\n if (onViewReplay) {\n onPlaySelectSound?.();\n onViewReplay();\n }\n }, [onPlaySelectSound, onViewReplay]);\n\n return (\n <div\n data-testid=\"navigation-buttons\"\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing,\n marginTop: spacing * 2,\n animation: \"slideUp 0.6s ease-out 0.3s both\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {/* Return to Menu Button - Primary Action */}\n <BaseButtonOverlayHtml\n korean=\"메뉴로\"\n english=\"Return to Menu\"\n onClick={handleReturnToMenu}\n onMouseEnter={onPlayHoverSound}\n variant=\"primary\"\n size={buttonSize}\n testId=\"return-to-menu-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n\n {/* Rematch Button - Secondary Action */}\n {onRematch && (\n <BaseButtonOverlayHtml\n korean=\"재대결\"\n english=\"Rematch\"\n onClick={handleRematch}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"rematch-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* Training Mode Button - Tertiary Action */}\n {onViewReplay && (\n <BaseButtonOverlayHtml\n korean=\"훈련\"\n english=\"Training\"\n onClick={handleViewReplay}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"view-training-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* CSS Animations */}\n <style>{`\n ${slideUpAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,IAAa,qBAAuD,EAClE,gBACA,WACA,cACA,UACA,UACA,OACA,mBACA,uBACI;CACJ,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAIhD,MAAM,aAAa,QAAQ,MAAM,OAAO;CACxC,MAAM,iBAAiB,QAAQ,MAAM,UAAU;CAE/C,MAAM,qBAAqB,kBAAkB;EAC3C,qBAAqB;EACrB,gBAAgB;IACf,CAAC,mBAAmB,eAAe,CAAC;CAEvC,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,WAAW;GACb,qBAAqB;GACrB,WAAW;;IAEZ,CAAC,mBAAmB,UAAU,CAAC;CAElC,MAAM,mBAAmB,kBAAkB;EACzC,IAAI,cAAc;GAChB,qBAAqB;GACrB,cAAc;;IAEf,CAAC,mBAAmB,aAAa,CAAC;CAErC,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,eAAe,WAAW,WAAW;GACrC,KAAK;GACL,WAAW,UAAU;GACrB,WAAW;GACX,YAAY;GACZ,gBAAgB;GACjB;YAVH;GAaE,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,gBAAgB;IACnC,CAAA;GAGD,aACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,gBAAgB;IACnC,CAAA;GAIH,gBACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,gBAAgB;IACnC,CAAA;GAIJ,oBAAC,SAAD,EAAA,UAAQ;UACJ,iBAAiB;SACX,CAAA;GACN"}
|