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":"PerformanceBreakdownOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } 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 PerformanceBreakdownProps {\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 * Category rating component\n */\ninterface CategoryRatingProps {\n readonly category: string;\n readonly korean: string;\n readonly value: number;\n readonly maxValue: number;\n readonly color: number;\n readonly fontSize: number;\n}\n\nconst CategoryRating: React.FC<CategoryRatingProps> = ({\n category,\n korean,\n value,\n maxValue,\n color,\n fontSize,\n}) => {\n const percentage = Math.round((value / maxValue) * 100);\n const grade = percentage >= 90 ? \"S\" : percentage >= 75 ? \"A\" : percentage >= 60 ? \"B\" : \"C\";\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n }}\n data-testid={`category-${category.toLowerCase()}`}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <span>{korean} | {category}</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(color),\n fontSize: fontSize + 2,\n }}\n >\n {grade}\n </span>\n </div>\n <div\n style={{\n width: \"100%\",\n height: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5),\n borderRadius: \"4px\",\n overflow: \"hidden\",\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: toCssColor(color),\n transition: \"width 0.5s ease-out\",\n }}\n data-testid={`progress-${category.toLowerCase()}`}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Calculate category scores from match statistics\n */\nfunction calculateCategoryScores(stats: PlayerMatchStats) {\n // Offense: Based on damage dealt and hits landed\n const offense = Math.min(\n (stats.totalDamageDealt / 100) * 50 + (stats.hitsLanded / 10) * 50,\n 100\n );\n\n // Defense: Based on damage received (inverted) and hits taken (inverted)\n const defenseRaw = Math.max(0, 100 - stats.totalDamageReceived / 2);\n const defense = Math.min(defenseRaw, 100);\n\n // Technique: Based on perfect strikes and vital point hits\n const technique = Math.min(\n stats.perfectStrikes * 15 + stats.vitalPointHits * 10,\n 100\n );\n\n // Efficiency: Based on damage ratio\n const damageRatio =\n stats.totalDamageReceived > 0\n ? stats.totalDamageDealt / stats.totalDamageReceived\n : stats.totalDamageDealt > 0\n ? 2\n : 0;\n const efficiency = Math.min(damageRatio * 40, 100);\n\n return { offense, defense, technique, efficiency };\n}\n\n/**\n * Performance Breakdown Component\n * Provides detailed analysis of combat performance by category\n */\nexport const PerformanceBreakdown: React.FC<PerformanceBreakdownProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 13 : 14;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 12 : isTablet ? 15 : 18;\n\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n const scores = useMemo(\n () => calculateCategoryScores(winnerStats),\n [winnerStats]\n );\n\n // Technique breakdown\n const techniqueCount = winnerStats.techniques?.length ?? 0;\n const uniqueTechniques = useMemo(() => {\n if (!winnerStats.techniques || winnerStats.techniques.length === 0) {\n return 0;\n }\n return new Set(winnerStats.techniques).size;\n }, [winnerStats.techniques]);\n\n return (\n <div\n data-testid=\"performance-breakdown\"\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: padding,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: padding * 1.5,\n }}\n data-testid=\"breakdown-title\"\n >\n 전투 분석 | Performance Breakdown\n </div>\n\n {/* Category Ratings */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: padding,\n marginBottom: padding * 1.5,\n }}\n data-testid=\"category-ratings\"\n >\n <CategoryRating\n category=\"Offense\"\n korean=\"공격\"\n value={scores.offense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_RED}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Defense\"\n korean=\"방어\"\n value={scores.defense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_BLUE}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Technique\"\n korean=\"기술\"\n value={scores.technique}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_GOLD}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Efficiency\"\n korean=\"효율\"\n value={scores.efficiency}\n maxValue={100}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n fontSize={fontSize}\n />\n </div>\n\n {/* Technique Analysis */}\n <div\n style={{\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"technique-analysis\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n marginBottom: padding / 2,\n textAlign: \"center\",\n }}\n >\n 기술 사용 분석 | Technique Analysis\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr 1fr\",\n gap: padding,\n marginTop: padding,\n }}\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n >\n {techniqueCount}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 총 기술 | Total Uses\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {uniqueTechniques}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 고유 기술 | Unique\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\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 </div>\n\n {/* Most Used Techniques */}\n {winnerStats.techniques && winnerStats.techniques.length > 0 && (\n <div\n style={{\n marginTop: padding,\n paddingTop: padding,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.UI_BORDER,\n 0.3\n )}`,\n }}\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 2,\n }}\n >\n 주요 기술 | Primary Techniques:\n </div>\n <div\n style={{\n fontSize: fontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n lineHeight: 1.6,\n }}\n >\n {winnerStats.techniques.slice(0, 5).join(\", \")}\n {winnerStats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n\n {/* Combat Effectiveness Summary */}\n <div\n style={{\n marginTop: padding,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"effectiveness-summary\"\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 4,\n }}\n >\n 전투 효율성 | Combat Effectiveness\n </div>\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {Math.round((scores.offense + scores.defense + scores.technique + scores.efficiency) / 4)}%\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AAeA,IAAM,cAAc,QAAwB,gBAAgB,KAAK,EAAE;AAcnE,IAAM,kBAAiD,EACrD,UACA,QACA,OACA,UACA,OACA,eACI;CACJ,MAAM,aAAa,KAAK,MAAO,QAAQ,WAAY,IAAI;CACvD,MAAM,QAAQ,cAAc,KAAK,MAAM,cAAc,KAAK,MAAM,cAAc,KAAK,MAAM;AAEzF,QACE,qBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,eAAe;GACf,KAAK;GACN;EACD,eAAa,YAAY,SAAS,aAAa;YANjD,CAQE,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,UAAU,WAAW;IACrB,OAAO,WAAW,cAAc,eAAe;IAChD;aAPH,CASE,qBAAC,QAAD,EAAA,UAAA;IAAO;IAAO;IAAI;IAAgB,EAAA,CAAA,EAClC,oBAAC,QAAD;IACE,OAAO;KACL,YAAY;KACZ,OAAO,WAAW,MAAM;KACxB,UAAU,WAAW;KACtB;cAEA;IACI,CAAA,CACH;MACN,oBAAC,OAAD;GACE,OAAO;IACL,OAAO;IACP,QAAQ;IACR,YAAY,gBAAgB,cAAc,sBAAsB,GAAI;IACpE,cAAc;IACd,UAAU;IACX;aAED,oBAAC,OAAD;IACE,OAAO;KACL,OAAO,GAAG,WAAW;KACrB,QAAQ;KACR,YAAY,WAAW,MAAM;KAC7B,YAAY;KACb;IACD,eAAa,YAAY,SAAS,aAAa;IAC/C,CAAA;GACE,CAAA,CACF;;;;;;AAOV,SAAS,wBAAwB,OAAyB;CAExD,MAAM,UAAU,KAAK,IAClB,MAAM,mBAAmB,MAAO,KAAM,MAAM,aAAa,KAAM,IAChE,IACD;CAGD,MAAM,aAAa,KAAK,IAAI,GAAG,MAAM,MAAM,sBAAsB,EAAE;CACnE,MAAM,UAAU,KAAK,IAAI,YAAY,IAAI;CAGzC,MAAM,YAAY,KAAK,IACrB,MAAM,iBAAiB,KAAK,MAAM,iBAAiB,IACnD,IACD;CAGD,MAAM,cACJ,MAAM,sBAAsB,IACxB,MAAM,mBAAmB,MAAM,sBAC/B,MAAM,mBAAmB,IACzB,IACA;AAGN,QAAO;EAAE;EAAS;EAAS;EAAW,YAFnB,KAAK,IAAI,cAAc,IAAI,IAER;EAAY;;;;;;AAOpD,IAAa,wBAA6D,EACxE,YACA,UACA,eACI;CACJ,MAAM,WAAW,WAAW,KAAK,WAAW,KAAK;CACjD,MAAM,gBAAgB,WAAW,KAAK,WAAW,KAAK;CACtD,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAEhD,MAAM,cAAc,cACX,WAAW,WAAW,IAAI,WAAW,UAAU,WAAW,SACjE,CAAC,WAAW,CACb;CAED,MAAM,SAAS,cACP,wBAAwB,YAAY,EAC1C,CAAC,YAAY,CACd;CAGD,MAAM,iBAAiB,YAAY,YAAY,UAAU;CACzD,MAAM,mBAAmB,cAAc;AACrC,MAAI,CAAC,YAAY,cAAc,YAAY,WAAW,WAAW,EAC/D,QAAO;AAET,SAAO,IAAI,IAAI,YAAY,WAAW,CAAC;IACtC,CAAC,YAAY,WAAW,CAAC;AAE5B,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,OAAO,WAAW,QAAQ,WAAW,QAAQ;GAC7C,UAAU;GACV,YAAY,gBAAgB,cAAc,oBAAoB,GAAI;GAClE,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;GACrE,cAAc;GACd,SAAS,UAAU;GACnB,cAAc;GACd,YAAY,YAAY;GACzB;YAXH;GAcE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,gBAAgB;KAC1B,YAAY;KACZ,OAAO,WAAW,cAAc,aAAa;KAC7C,WAAW;KACX,cAAc,UAAU;KACzB;IACD,eAAY;cACb;IAEK,CAAA;GAGN,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,KAAK;KACL,cAAc,UAAU;KACzB;IACD,eAAY;cAPd;KASE,oBAAC,gBAAD;MACE,UAAS;MACT,QAAO;MACP,OAAO,OAAO;MACd,UAAU;MACV,OAAO,cAAc;MACX;MACV,CAAA;KACF,oBAAC,gBAAD;MACE,UAAS;MACT,QAAO;MACP,OAAO,OAAO;MACd,UAAU;MACV,OAAO,cAAc;MACX;MACV,CAAA;KACF,oBAAC,gBAAD;MACE,UAAS;MACT,QAAO;MACP,OAAO,OAAO;MACd,UAAU;MACV,OAAO,cAAc;MACX;MACV,CAAA;KACF,oBAAC,gBAAD;MACE,UAAS;MACT,QAAO;MACP,OAAO,OAAO;MACd,UAAU;MACV,OAAO,cAAc;MACX;MACV,CAAA;KACE;;GAGN,qBAAC,OAAD;IACE,OAAO;KACL;KACA,YAAY,gBAAgB,cAAc,sBAAsB,GAAI;KACpE,cAAc;KACf;IACD,eAAY;cANd;KAQE,oBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,YAAY;OACZ,OAAO,WAAW,cAAc,YAAY;OAC5C,cAAc,UAAU;OACxB,WAAW;OACZ;gBACF;MAEK,CAAA;KAEN,qBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,qBAAqB,WAAW,QAAQ;OACxC,KAAK;OACL,WAAW;OACZ;gBANH;OAQE,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,YAAY;UACZ,OAAO,WAAW,cAAc,aAAa;UAC9C;mBAEA;SACG,CAAA,EACN,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,OAAO,WAAW,cAAc,cAAc;UAC/C;mBACF;SAEK,CAAA,CACF;;OAEN,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,YAAY;UACZ,OAAO,WAAW,cAAc,YAAY;UAC7C;mBAEA;SACG,CAAA,EACN,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,OAAO,WAAW,cAAc,cAAc;UAC/C;mBACF;SAEK,CAAA,CACF;;OAEN,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,YAAY;UACZ,OAAO,WAAW,cAAc,gBAAgB;UACjD;mBAEA,YAAY,kBAAkB;SAC3B,CAAA,EACN,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,WAAW;UACrB,OAAO,WAAW,cAAc,cAAc;UAC/C;mBACF;SAEK,CAAA,CACF;;OACF;;KAGL,YAAY,cAAc,YAAY,WAAW,SAAS,KACzD,qBAAC,OAAD;MACE,OAAO;OACL,WAAW;OACX,YAAY;OACZ,WAAW,aAAa,gBACtB,cAAc,WACd,GACD;OACF;gBARH,CAUE,oBAAC,OAAD;OACE,OAAO;QACL,UAAU,WAAW;QACrB,OAAO,WAAW,cAAc,cAAc;QAC9C,cAAc,UAAU;QACzB;iBACF;OAEK,CAAA,EACN,qBAAC,OAAD;OACE,OAAO;QACK;QACV,OAAO,WAAW,cAAc,eAAe;QAC/C,YAAY;QACb;iBALH,CAOG,YAAY,WAAW,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAC7C,YAAY,WAAW,SAAS,KAAK,MAClC;SACF;;KAEJ;;GAGN,qBAAC,OAAD;IACE,OAAO;KACL,WAAW;KACX;KACA,YAAY,gBAAgB,cAAc,sBAAsB,GAAI;KACpE,cAAc;KACd,WAAW;KACZ;IACD,eAAY;cARd,CAUE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,WAAW;MACrB,OAAO,WAAW,cAAc,cAAc;MAC9C,cAAc,UAAU;MACzB;eACF;KAEK,CAAA,EACN,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,gBAAgB;MAC1B,YAAY;MACZ,OAAO,WAAW,cAAc,YAAY;MAC7C;eALH,CAOG,KAAK,OAAO,OAAO,UAAU,OAAO,UAAU,OAAO,YAAY,OAAO,cAAc,EAAE,EAAC,IACtF;OACF;;GACF"}
|
|
1
|
+
{"version":3,"file":"PerformanceBreakdownOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } 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 PerformanceBreakdownProps {\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 * Category rating component\n */\ninterface CategoryRatingProps {\n readonly category: string;\n readonly korean: string;\n readonly value: number;\n readonly maxValue: number;\n readonly color: number;\n readonly fontSize: number;\n}\n\nconst CategoryRating: React.FC<CategoryRatingProps> = ({\n category,\n korean,\n value,\n maxValue,\n color,\n fontSize,\n}) => {\n const percentage = Math.round((value / maxValue) * 100);\n const grade = percentage >= 90 ? \"S\" : percentage >= 75 ? \"A\" : percentage >= 60 ? \"B\" : \"C\";\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n }}\n data-testid={`category-${category.toLowerCase()}`}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <span>{korean} | {category}</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(color),\n fontSize: fontSize + 2,\n }}\n >\n {grade}\n </span>\n </div>\n <div\n style={{\n width: \"100%\",\n height: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5),\n borderRadius: \"4px\",\n overflow: \"hidden\",\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: toCssColor(color),\n transition: \"width 0.5s ease-out\",\n }}\n data-testid={`progress-${category.toLowerCase()}`}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Calculate category scores from match statistics\n */\nfunction calculateCategoryScores(stats: PlayerMatchStats) {\n // Offense: Based on damage dealt and hits landed\n const offense = Math.min(\n (stats.totalDamageDealt / 100) * 50 + (stats.hitsLanded / 10) * 50,\n 100\n );\n\n // Defense: Based on damage received (inverted) and hits taken (inverted)\n const defenseRaw = Math.max(0, 100 - stats.totalDamageReceived / 2);\n const defense = Math.min(defenseRaw, 100);\n\n // Technique: Based on perfect strikes and vital point hits\n const technique = Math.min(\n stats.perfectStrikes * 15 + stats.vitalPointHits * 10,\n 100\n );\n\n // Efficiency: Based on damage ratio\n const damageRatio =\n stats.totalDamageReceived > 0\n ? stats.totalDamageDealt / stats.totalDamageReceived\n : stats.totalDamageDealt > 0\n ? 2\n : 0;\n const efficiency = Math.min(damageRatio * 40, 100);\n\n return { offense, defense, technique, efficiency };\n}\n\n/**\n * Performance Breakdown Component\n * Provides detailed analysis of combat performance by category\n */\nexport const PerformanceBreakdown: React.FC<PerformanceBreakdownProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 13 : 14;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 12 : isTablet ? 15 : 18;\n\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n const scores = useMemo(\n () => calculateCategoryScores(winnerStats),\n [winnerStats]\n );\n\n // Technique breakdown\n const techniqueCount = winnerStats.techniques?.length ?? 0;\n const uniqueTechniques = useMemo(() => {\n if (!winnerStats.techniques || winnerStats.techniques.length === 0) {\n return 0;\n }\n return new Set(winnerStats.techniques).size;\n }, [winnerStats.techniques]);\n\n return (\n <div\n data-testid=\"performance-breakdown\"\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: padding,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: padding * 1.5,\n }}\n data-testid=\"breakdown-title\"\n >\n 전투 분석 | Performance Breakdown\n </div>\n\n {/* Category Ratings */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: padding,\n marginBottom: padding * 1.5,\n }}\n data-testid=\"category-ratings\"\n >\n <CategoryRating\n category=\"Offense\"\n korean=\"공격\"\n value={scores.offense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_RED}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Defense\"\n korean=\"방어\"\n value={scores.defense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_BLUE}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Technique\"\n korean=\"기술\"\n value={scores.technique}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_GOLD}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Efficiency\"\n korean=\"효율\"\n value={scores.efficiency}\n maxValue={100}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n fontSize={fontSize}\n />\n </div>\n\n {/* Technique Analysis */}\n <div\n style={{\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"technique-analysis\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n marginBottom: padding / 2,\n textAlign: \"center\",\n }}\n >\n 기술 사용 분석 | Technique Analysis\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr 1fr\",\n gap: padding,\n marginTop: padding,\n }}\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n >\n {techniqueCount}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 총 기술 | Total Uses\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {uniqueTechniques}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 고유 기술 | Unique\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\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 </div>\n\n {/* Most Used Techniques */}\n {winnerStats.techniques && winnerStats.techniques.length > 0 && (\n <div\n style={{\n marginTop: padding,\n paddingTop: padding,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.UI_BORDER,\n 0.3\n )}`,\n }}\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 2,\n }}\n >\n 주요 기술 | Primary Techniques:\n </div>\n <div\n style={{\n fontSize: fontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n lineHeight: 1.6,\n }}\n >\n {winnerStats.techniques.slice(0, 5).join(\", \")}\n {winnerStats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n\n {/* Combat Effectiveness Summary */}\n <div\n style={{\n marginTop: padding,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"effectiveness-summary\"\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 4,\n }}\n >\n 전투 효율성 | Combat Effectiveness\n </div>\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {Math.round((scores.offense + scores.defense + scores.technique + scores.efficiency) / 4)}%\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PerformanceRatingOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceRatingOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n PERFORMANCE_RATING_THRESHOLDS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { pulseAnimation } from \"./animations\";\n\nexport interface PerformanceRatingProps {\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 * Calculate performance score based on match statistics\n * Score ranges from 0-100 based on combat effectiveness\n */\nfunction calculatePerformanceScore(stats: MatchStatistics): number {\n const winnerStats = stats.winner === 0 ? stats.player1 : stats.player2;\n\n // Calculate accuracy based on offensive performance only\n // Use a normalized scale: higher hits landed = better accuracy\n const accuracy = Math.min((winnerStats.hitsLanded / 10) * 100, 100);\n\n // Calculate damage efficiency (damage dealt / damage taken ratio)\n // Perfect defense (no damage taken) gets bonus ratio\n const damageRatio =\n winnerStats.totalDamageReceived > 0\n ? winnerStats.totalDamageDealt / winnerStats.totalDamageReceived\n : winnerStats.totalDamageDealt > 0\n ? 2\n : 0;\n const damageScore = Math.min(damageRatio * 30, 30); // Max 30 points\n\n // Perfect strikes and vital point hits bonus\n const precisionBonus =\n winnerStats.perfectStrikes * 5 + winnerStats.vitalPointHits * 3;\n const precisionScore = Math.min(precisionBonus, 25); // Max 25 points\n\n // Speed bonus (shorter match duration is better)\n const speedScore =\n stats.matchDuration < 60 ? 15 : stats.matchDuration < 120 ? 10 : 5;\n\n // Combine scores\n const totalScore = accuracy * 0.3 + damageScore + precisionScore + speedScore;\n\n return Math.min(Math.round(totalScore), 100);\n}\n\n/**\n * Get performance rating based on score\n */\nfunction getPerformanceRating(\n score: number\n): keyof typeof PERFORMANCE_RATING_THRESHOLDS {\n if (score >= PERFORMANCE_RATING_THRESHOLDS.S.minScore) return \"S\";\n if (score >= PERFORMANCE_RATING_THRESHOLDS.A.minScore) return \"A\";\n if (score >= PERFORMANCE_RATING_THRESHOLDS.B.minScore) return \"B\";\n return \"C\";\n}\n\n/**\n * Performance Rating Component\n * Displays S/A/B/C ranking based on combat performance\n */\nexport const PerformanceRating: React.FC<PerformanceRatingProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const ratingFontSize = isMobile ? 36 : isTablet ? 48 : 56;\n const labelFontSize = isMobile ? 12 : isTablet ? 14 : 15;\n const scoreFontSize = isMobile ? 18 : isTablet ? 20 : 24;\n const padding = isMobile ? 10 : isTablet ? 12 : 15;\n\n const performanceScore = useMemo(\n () => calculatePerformanceScore(matchStats),\n [matchStats]\n );\n\n const rating = useMemo(\n () => getPerformanceRating(performanceScore),\n [performanceScore]\n );\n\n const ratingInfo = PERFORMANCE_RATING_THRESHOLDS[rating];\n\n // Extract winner stats for clarity\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n return (\n <div\n data-testid=\"performance-rating\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `3px solid ${hexToRgbaString(ratingInfo.color, 0.8)}`,\n borderRadius: \"16px\",\n padding: padding,\n marginBottom: padding / 2,\n minWidth: isMobile ? \"260px\" : \"300px\",\n boxShadow: `0 0 30px ${hexToRgbaString(ratingInfo.color, 0.3)}`,\n animation: \"ratingPulse 2s ease-in-out infinite\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding / 2,\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n }}\n data-testid=\"rating-label\"\n >\n 전투 등급 | Performance Rating\n </div>\n\n {/* Rating Letter */}\n <div\n style={{\n fontSize: ratingFontSize,\n fontWeight: \"bold\",\n color: toCssColor(ratingInfo.color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: `0 0 20px ${hexToRgbaString(ratingInfo.color, 0.6)}`,\n marginBottom: padding / 2,\n animation: \"ratingGlow 1.5s ease-in-out infinite\",\n }}\n data-testid=\"rating-letter\"\n >\n {rating}\n </div>\n\n {/* Rating Description */}\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding,\n textAlign: \"center\",\n }}\n data-testid=\"rating-description\"\n >\n {ratingInfo.description.korean} | {ratingInfo.description.english}\n </div>\n\n {/* Performance Score */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n width: \"100%\",\n padding: padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n >\n <div\n style={{\n fontSize: scoreFontSize,\n fontWeight: \"bold\",\n color: toCssColor(ratingInfo.color),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding / 4,\n }}\n data-testid=\"performance-score\"\n >\n {performanceScore}\n </div>\n <div\n style={{\n fontSize: labelFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n 전투 점수 | Combat Score\n </div>\n\n {/* Score Breakdown */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gap: padding / 2,\n marginTop: padding,\n width: \"100%\",\n fontSize: labelFontSize - 4,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n data-testid=\"score-breakdown\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.perfectStrikes ?? 0}\n </div>\n <div>완벽 | Perfect</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\n <div>급소 | Vital Hits</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.techniques?.length ?? 0}\n </div>\n <div>기술 | Techniques</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n <div>치명타 | Criticals</div>\n </div>\n </div>\n </div>\n\n {/* CSS Animations */}\n <style>{`\n ${pulseAnimation}\n\n @keyframes ratingGlow {\n 0%, 100% {\n text-shadow: 0 0 20px ${hexToRgbaString(ratingInfo.color, 0.6)};\n }\n 50% {\n text-shadow: 0 0 30px ${hexToRgbaString(\n ratingInfo.color,\n 0.9\n )}, 0 0 50px ${hexToRgbaString(ratingInfo.color, 0.5)};\n }\n }\n `}</style>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PerformanceRatingOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceRatingOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n PERFORMANCE_RATING_THRESHOLDS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { pulseAnimation } from \"./animations\";\n\nexport interface PerformanceRatingProps {\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 * Calculate performance score based on match statistics\n * Score ranges from 0-100 based on combat effectiveness\n */\nfunction calculatePerformanceScore(stats: MatchStatistics): number {\n const winnerStats = stats.winner === 0 ? stats.player1 : stats.player2;\n\n // Calculate accuracy based on offensive performance only\n // Use a normalized scale: higher hits landed = better accuracy\n const accuracy = Math.min((winnerStats.hitsLanded / 10) * 100, 100);\n\n // Calculate damage efficiency (damage dealt / damage taken ratio)\n // Perfect defense (no damage taken) gets bonus ratio\n const damageRatio =\n winnerStats.totalDamageReceived > 0\n ? winnerStats.totalDamageDealt / winnerStats.totalDamageReceived\n : winnerStats.totalDamageDealt > 0\n ? 2\n : 0;\n const damageScore = Math.min(damageRatio * 30, 30); // Max 30 points\n\n // Perfect strikes and vital point hits bonus\n const precisionBonus =\n winnerStats.perfectStrikes * 5 + winnerStats.vitalPointHits * 3;\n const precisionScore = Math.min(precisionBonus, 25); // Max 25 points\n\n // Speed bonus (shorter match duration is better)\n const speedScore =\n stats.matchDuration < 60 ? 15 : stats.matchDuration < 120 ? 10 : 5;\n\n // Combine scores\n const totalScore = accuracy * 0.3 + damageScore + precisionScore + speedScore;\n\n return Math.min(Math.round(totalScore), 100);\n}\n\n/**\n * Get performance rating based on score\n */\nfunction getPerformanceRating(\n score: number\n): keyof typeof PERFORMANCE_RATING_THRESHOLDS {\n if (score >= PERFORMANCE_RATING_THRESHOLDS.S.minScore) return \"S\";\n if (score >= PERFORMANCE_RATING_THRESHOLDS.A.minScore) return \"A\";\n if (score >= PERFORMANCE_RATING_THRESHOLDS.B.minScore) return \"B\";\n return \"C\";\n}\n\n/**\n * Performance Rating Component\n * Displays S/A/B/C ranking based on combat performance\n */\nexport const PerformanceRating: React.FC<PerformanceRatingProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const ratingFontSize = isMobile ? 36 : isTablet ? 48 : 56;\n const labelFontSize = isMobile ? 12 : isTablet ? 14 : 15;\n const scoreFontSize = isMobile ? 18 : isTablet ? 20 : 24;\n const padding = isMobile ? 10 : isTablet ? 12 : 15;\n\n const performanceScore = useMemo(\n () => calculatePerformanceScore(matchStats),\n [matchStats]\n );\n\n const rating = useMemo(\n () => getPerformanceRating(performanceScore),\n [performanceScore]\n );\n\n const ratingInfo = PERFORMANCE_RATING_THRESHOLDS[rating];\n\n // Extract winner stats for clarity\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n return (\n <div\n data-testid=\"performance-rating\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `3px solid ${hexToRgbaString(ratingInfo.color, 0.8)}`,\n borderRadius: \"16px\",\n padding: padding,\n marginBottom: padding / 2,\n minWidth: isMobile ? \"260px\" : \"300px\",\n boxShadow: `0 0 30px ${hexToRgbaString(ratingInfo.color, 0.3)}`,\n animation: \"ratingPulse 2s ease-in-out infinite\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding / 2,\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n }}\n data-testid=\"rating-label\"\n >\n 전투 등급 | Performance Rating\n </div>\n\n {/* Rating Letter */}\n <div\n style={{\n fontSize: ratingFontSize,\n fontWeight: \"bold\",\n color: toCssColor(ratingInfo.color),\n fontFamily: FONT_FAMILY.KOREAN,\n textShadow: `0 0 20px ${hexToRgbaString(ratingInfo.color, 0.6)}`,\n marginBottom: padding / 2,\n animation: \"ratingGlow 1.5s ease-in-out infinite\",\n }}\n data-testid=\"rating-letter\"\n >\n {rating}\n </div>\n\n {/* Rating Description */}\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding,\n textAlign: \"center\",\n }}\n data-testid=\"rating-description\"\n >\n {ratingInfo.description.korean} | {ratingInfo.description.english}\n </div>\n\n {/* Performance Score */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n width: \"100%\",\n padding: padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n >\n <div\n style={{\n fontSize: scoreFontSize,\n fontWeight: \"bold\",\n color: toCssColor(ratingInfo.color),\n fontFamily: FONT_FAMILY.KOREAN,\n marginBottom: padding / 4,\n }}\n data-testid=\"performance-score\"\n >\n {performanceScore}\n </div>\n <div\n style={{\n fontSize: labelFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n 전투 점수 | Combat Score\n </div>\n\n {/* Score Breakdown */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gap: padding / 2,\n marginTop: padding,\n width: \"100%\",\n fontSize: labelFontSize - 4,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n data-testid=\"score-breakdown\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.perfectStrikes ?? 0}\n </div>\n <div>완벽 | Perfect</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\n <div>급소 | Vital Hits</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n fontWeight: \"bold\",\n }}\n >\n {winnerStats.techniques?.length ?? 0}\n </div>\n <div>기술 | Techniques</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n <div>치명타 | Criticals</div>\n </div>\n </div>\n </div>\n\n {/* CSS Animations */}\n <style>{`\n ${pulseAnimation}\n\n @keyframes ratingGlow {\n 0%, 100% {\n text-shadow: 0 0 20px ${hexToRgbaString(ratingInfo.color, 0.6)};\n }\n 50% {\n text-shadow: 0 0 30px ${hexToRgbaString(\n ratingInfo.color,\n 0.9\n )}, 0 0 50px ${hexToRgbaString(ratingInfo.color, 0.5)};\n }\n }\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,cAAc,QAAwB,gBAAgB,KAAK,EAAE;;;;;AAMnE,SAAS,0BAA0B,OAAgC;CACjE,MAAM,cAAc,MAAM,WAAW,IAAI,MAAM,UAAU,MAAM;CAI/D,MAAM,WAAW,KAAK,IAAK,YAAY,aAAa,KAAM,KAAK,IAAI;CAInE,MAAM,cACJ,YAAY,sBAAsB,IAC9B,YAAY,mBAAmB,YAAY,sBAC3C,YAAY,mBAAmB,IAC/B,IACA;CACN,MAAM,cAAc,KAAK,IAAI,cAAc,IAAI,GAAG;CAGlD,MAAM,iBACJ,YAAY,iBAAiB,IAAI,YAAY,iBAAiB;CAChE,MAAM,iBAAiB,KAAK,IAAI,gBAAgB,GAAG;CAGnD,MAAM,aACJ,MAAM,gBAAgB,KAAK,KAAK,MAAM,gBAAgB,MAAM,KAAK;CAGnE,MAAM,aAAa,WAAW,KAAM,cAAc,iBAAiB;CAEnE,OAAO,KAAK,IAAI,KAAK,MAAM,WAAW,EAAE,IAAI;;;;;AAM9C,SAAS,qBACP,OAC4C;CAC5C,IAAI,SAAS,8BAA8B,EAAE,UAAU,OAAO;CAC9D,IAAI,SAAS,8BAA8B,EAAE,UAAU,OAAO;CAC9D,IAAI,SAAS,8BAA8B,EAAE,UAAU,OAAO;CAC9D,OAAO;;;;;;AAOT,IAAa,qBAAuD,EAClE,YACA,UACA,eACI;CACJ,MAAM,iBAAiB,WAAW,KAAK,WAAW,KAAK;CACvD,MAAM,gBAAgB,WAAW,KAAK,WAAW,KAAK;CACtD,MAAM,gBAAgB,WAAW,KAAK,WAAW,KAAK;CACtD,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAEhD,MAAM,mBAAmB,cACjB,0BAA0B,WAAW,EAC3C,CAAC,WAAW,CACb;CAED,MAAM,SAAS,cACP,qBAAqB,iBAAiB,EAC5C,CAAC,iBAAiB,CACnB;CAED,MAAM,aAAa,8BAA8B;CAGjD,MAAM,cAAc,cACX,WAAW,WAAW,IAAI,WAAW,UAAU,WAAW,SACjE,CAAC,WAAW,CACb;CAED,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,eAAe;GACf,YAAY;GACZ,YAAY,gBAAgB,cAAc,oBAAoB,GAAI;GAClE,QAAQ,aAAa,gBAAgB,WAAW,OAAO,GAAI;GAC3D,cAAc;GACL;GACT,cAAc,UAAU;GACxB,UAAU,WAAW,UAAU;GAC/B,WAAW,YAAY,gBAAgB,WAAW,OAAO,GAAI;GAC7D,WAAW;GACZ;YAdH;GAiBE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO,WAAW,cAAc,eAAe;KAC/C,YAAY,YAAY;KACxB,cAAc,UAAU;KACxB,eAAe;KACf,eAAe;KAChB;IACD,eAAY;cACb;IAEK,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,YAAY;KACZ,OAAO,WAAW,WAAW,MAAM;KACnC,YAAY,YAAY;KACxB,YAAY,YAAY,gBAAgB,WAAW,OAAO,GAAI;KAC9D,cAAc,UAAU;KACxB,WAAW;KACZ;IACD,eAAY;cAEX;IACG,CAAA;GAGN,qBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO,WAAW,cAAc,aAAa;KAC7C,YAAY,YAAY;KACxB,cAAc;KACd,WAAW;KACZ;IACD,eAAY;cARd;KAUG,WAAW,YAAY;KAAO;KAAI,WAAW,YAAY;KACtD;;GAGN,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,OAAO;KACE;KACT,YAAY,gBAAgB,cAAc,sBAAsB,GAAI;KACpE,cAAc;KACf;cATH;KAWE,oBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,YAAY;OACZ,OAAO,WAAW,WAAW,MAAM;OACnC,YAAY,YAAY;OACxB,cAAc,UAAU;OACzB;MACD,eAAY;gBAEX;MACG,CAAA;KACN,oBAAC,OAAD;MACE,OAAO;OACL,UAAU,gBAAgB;OAC1B,OAAO,WAAW,cAAc,cAAc;OAC9C,YAAY,YAAY;OACzB;gBACF;MAEK,CAAA;KAGN,qBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,qBAAqB;OACrB,KAAK,UAAU;OACf,WAAW;OACX,OAAO;OACP,UAAU,gBAAgB;OAC1B,OAAO,WAAW,cAAc,eAAe;OAChD;MACD,eAAY;gBAVd;OAYE,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,OAAO,WAAW,cAAc,YAAY;UAC5C,YAAY;UACb;mBAEA,YAAY,kBAAkB;SAC3B,CAAA,EACN,oBAAC,OAAD,EAAA,UAAK,gBAAkB,CAAA,CACnB;;OACN,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,OAAO,WAAW,cAAc,gBAAgB;UAChD,YAAY;UACb;mBAEA,YAAY,kBAAkB;SAC3B,CAAA,EACN,oBAAC,OAAD,EAAA,UAAK,mBAAqB,CAAA,CACtB;;OACN,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,OAAO,WAAW,cAAc,aAAa;UAC7C,YAAY;UACb;mBAEA,YAAY,YAAY,UAAU;SAC/B,CAAA,EACN,oBAAC,OAAD,EAAA,UAAK,mBAAqB,CAAA,CACtB;;OACN,qBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,oBAAC,OAAD;SACE,OAAO;UACL,OAAO,WAAW,cAAc,aAAa;UAC7C,YAAY;UACb;mBAEA,WAAW;SACR,CAAA,EACN,oBAAC,OAAD,EAAA,UAAK,mBAAqB,CAAA,CACtB;;OACF;;KACF;;GAGN,oBAAC,SAAD,EAAA,UAAQ;UACJ,eAAe;;;;oCAIW,gBAAgB,WAAW,OAAO,GAAI,CAAC;;;oCAGvC,gBACtB,WAAW,OACX,GACD,CAAC,aAAa,gBAAgB,WAAW,OAAO,GAAI,CAAC;;;SAGlD,CAAA;GACN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VictoryAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/VictoryAnimation3D.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 * Victory Animation 3D Component\n * Displays celebratory 3D particle effects for victory screen\n * Enhanced with additional Korean symbolism and dynamic effects\n * Optimized for 60fps performance with object reuse\n */\nexport const VictoryAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const ringsRef = useRef<THREE.Group>(null);\n const symbolsRef = 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 victory particles - use useState with lazy initializer\n const [particlePositions] = useState(() => {\n const count = 200; // Increased from 150 for more dramatic 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 = 3 + Math.random() * 2;\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);\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Create secondary particle layer for depth\n const [secondaryParticles] = useState(() => {\n const count = 50;\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 5 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n\n positions[i3] = radius * Math.cos(theta);\n positions[i3 + 1] = Math.random() * 4 - 2;\n positions[i3 + 2] = radius * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Animate victory effects - optimized for 60fps\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n // Rotate entire group\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.3;\n }\n\n // Pulse particles with wave effect - use reusable objects\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 2) * 0.2;\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n // Rising motion\n reusablePosition.set(0, Math.sin(time * 0.8) * 0.5, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n // Rotate rings at different speeds\n if (ringsRef.current) {\n ringsRef.current.rotation.x = time * 0.5;\n ringsRef.current.rotation.z = time * 0.3;\n }\n\n // Rotate Korean symbols\n if (symbolsRef.current) {\n symbolsRef.current.rotation.y = -time * 0.4;\n symbolsRef.current.rotation.x = Math.sin(time * 0.5) * 0.1;\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 rings = ringsRef.current;\n const symbols = symbolsRef.current;\n\n return () => {\n // Dispose geometries and materials to prevent memory leaks\n // Clean up specific refs (these are children of groupRef but we handle them explicitly)\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (rings?.children && Array.isArray(rings.children)) {\n rings.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 if (symbols?.children && Array.isArray(symbols.children)) {\n symbols.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 groupRef.current.children to dispose any meshes/points without explicit refs\n // (e.g., secondary particles, central glow sphere, outer glow sphere, inner glow layer)\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n // Skip objects that are already handled via specific refs\n if (\n child === particles ||\n child === rings ||\n child === symbols\n ) {\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, 2, 0]}\n data-testid=\"victory-animation-3d\"\n >\n {/* Primary victory particles */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={200}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.2}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_GOLD)}\n transparent\n opacity={0.8}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Secondary particle layer */}\n <points position={[0, 1, 0]}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={50}\n itemSize={3}\n args={[secondaryParticles, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.PRIMARY_CYAN)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Rotating rings */}\n <group ref={ringsRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[2, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]}>\n <torusGeometry args={[2.5, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]}>\n <torusGeometry args={[3, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.3}\n />\n </mesh>\n </group>\n\n {/* Korean symbol elements - octagonal shape representing 팔괘 (eight trigrams) */}\n <group ref={symbolsRef}>\n {Array.from({ length: 8 }).map((_, i) => {\n const angle = (i / 8) * Math.PI * 2;\n const radius = 4;\n return (\n <mesh\n key={i}\n position={[\n Math.cos(angle) * radius,\n 0,\n Math.sin(angle) * radius,\n ]}\n rotation={[0, angle + Math.PI / 2, 0]}\n >\n <boxGeometry args={[0.8, 0.1, 0.1]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={0.5}\n transparent\n opacity={0.7}\n />\n </mesh>\n );\n })}\n </group>\n\n {/* Central glow sphere */}\n <mesh>\n <sphereGeometry args={[0.5, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={1.5}\n transparent\n opacity={0.8}\n />\n </mesh>\n\n {/* Outer glow */}\n <mesh>\n <sphereGeometry args={[0.8, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Additional inner glow layer */}\n <mesh>\n <sphereGeometry args={[0.6, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.3}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={3}\n distance={10}\n color={KOREAN_COLORS.ACCENT_GOLD}\n />\n\n {/* Secondary accent lights */}\n <pointLight\n position={[2, 2, 0]}\n intensity={1.5}\n distance={6}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n />\n <pointLight\n position={[-2, 2, 0]}\n intensity={1.5}\n distance={6}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n />\n </group>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VictoryAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/VictoryAnimation3D.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 * Victory Animation 3D Component\n * Displays celebratory 3D particle effects for victory screen\n * Enhanced with additional Korean symbolism and dynamic effects\n * Optimized for 60fps performance with object reuse\n */\nexport const VictoryAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const ringsRef = useRef<THREE.Group>(null);\n const symbolsRef = 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 victory particles - use useState with lazy initializer\n const [particlePositions] = useState(() => {\n const count = 200; // Increased from 150 for more dramatic 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 = 3 + Math.random() * 2;\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);\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Create secondary particle layer for depth\n const [secondaryParticles] = useState(() => {\n const count = 50;\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 5 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n\n positions[i3] = radius * Math.cos(theta);\n positions[i3 + 1] = Math.random() * 4 - 2;\n positions[i3 + 2] = radius * Math.sin(theta);\n }\n\n return positions;\n });\n\n // Animate victory effects - optimized for 60fps\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n // Rotate entire group\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.3;\n }\n\n // Pulse particles with wave effect - use reusable objects\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 2) * 0.2;\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n // Rising motion\n reusablePosition.set(0, Math.sin(time * 0.8) * 0.5, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n // Rotate rings at different speeds\n if (ringsRef.current) {\n ringsRef.current.rotation.x = time * 0.5;\n ringsRef.current.rotation.z = time * 0.3;\n }\n\n // Rotate Korean symbols\n if (symbolsRef.current) {\n symbolsRef.current.rotation.y = -time * 0.4;\n symbolsRef.current.rotation.x = Math.sin(time * 0.5) * 0.1;\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 rings = ringsRef.current;\n const symbols = symbolsRef.current;\n\n return () => {\n // Dispose geometries and materials to prevent memory leaks\n // Clean up specific refs (these are children of groupRef but we handle them explicitly)\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (rings?.children && Array.isArray(rings.children)) {\n rings.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 if (symbols?.children && Array.isArray(symbols.children)) {\n symbols.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 groupRef.current.children to dispose any meshes/points without explicit refs\n // (e.g., secondary particles, central glow sphere, outer glow sphere, inner glow layer)\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n // Skip objects that are already handled via specific refs\n if (\n child === particles ||\n child === rings ||\n child === symbols\n ) {\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, 2, 0]}\n data-testid=\"victory-animation-3d\"\n >\n {/* Primary victory particles */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={200}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.2}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_GOLD)}\n transparent\n opacity={0.8}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Secondary particle layer */}\n <points position={[0, 1, 0]}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={50}\n itemSize={3}\n args={[secondaryParticles, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.PRIMARY_CYAN)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Rotating rings */}\n <group ref={ringsRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[2, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]}>\n <torusGeometry args={[2.5, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]}>\n <torusGeometry args={[3, 0.05, 16, 100]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.3}\n />\n </mesh>\n </group>\n\n {/* Korean symbol elements - octagonal shape representing 팔괘 (eight trigrams) */}\n <group ref={symbolsRef}>\n {Array.from({ length: 8 }).map((_, i) => {\n const angle = (i / 8) * Math.PI * 2;\n const radius = 4;\n return (\n <mesh\n key={i}\n position={[\n Math.cos(angle) * radius,\n 0,\n Math.sin(angle) * radius,\n ]}\n rotation={[0, angle + Math.PI / 2, 0]}\n >\n <boxGeometry args={[0.8, 0.1, 0.1]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={0.5}\n transparent\n opacity={0.7}\n />\n </mesh>\n );\n })}\n </group>\n\n {/* Central glow sphere */}\n <mesh>\n <sphereGeometry args={[0.5, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={1.5}\n transparent\n opacity={0.8}\n />\n </mesh>\n\n {/* Outer glow */}\n <mesh>\n <sphereGeometry args={[0.8, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Additional inner glow layer */}\n <mesh>\n <sphereGeometry args={[0.6, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.3}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={3}\n distance={10}\n color={KOREAN_COLORS.ACCENT_GOLD}\n />\n\n {/* Secondary accent lights */}\n <pointLight\n position={[2, 2, 0]}\n intensity={1.5}\n distance={6}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n />\n <pointLight\n position={[-2, 2, 0]}\n intensity={1.5}\n distance={6}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n />\n </group>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WinnerDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/WinnerDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { PlayerState } from \"../../../../systems\";\nimport {\n ARCHETYPE_ASSETS,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { fadeInAnimation, scaleInAnimation } from \"./animations\";\n\nexport interface WinnerDisplayProps {\n readonly winner: PlayerState;\n readonly isVictory: boolean;\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 * Winner Display Component\n * Shows winner announcement with archetype details\n */\nexport const WinnerDisplay: React.FC<WinnerDisplayProps> = ({\n winner,\n isVictory,\n isMobile,\n isTablet,\n}) => {\n const titleFontSize = isMobile ? 28 : isTablet ? 36 : 44;\n const subtitleFontSize = isMobile ? 16 : isTablet ? 18 : 22;\n const detailFontSize = isMobile ? 12 : 14;\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n\n const primaryColor = isVictory\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED;\n\n const resultText = isVictory\n ? { korean: \"승리!\", english: \"Victory!\" }\n : { korean: \"패배\", english: \"Defeat\" };\n\n // Get archetype asset info\n const archetypeKey =\n winner.archetype.toLowerCase() as keyof typeof ARCHETYPE_ASSETS;\n const archetypeAsset =\n ARCHETYPE_ASSETS[archetypeKey] || ARCHETYPE_ASSETS.musa;\n\n return (\n <div\n data-testid=\"winner-display\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n marginBottom: spacing,\n animation: \"fadeIn 0.8s ease-in\",\n }}\n >\n {/* Result Title with glow effect */}\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: toCssColor(primaryColor),\n textShadow: `0 0 20px ${hexToRgbaString(\n primaryColor,\n 0.8\n )}, 0 0 40px ${hexToRgbaString(primaryColor, 0.4)}`,\n marginBottom: spacing,\n textAlign: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n animation: \"scaleIn 0.5s ease-out\",\n }}\n data-testid=\"result-title\"\n >\n {resultText.korean} | {resultText.english}\n </div>\n\n {/* Winner Name */}\n <div\n style={{\n fontSize: subtitleFontSize,\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n marginBottom: spacing / 2,\n textAlign: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n }}\n data-testid=\"winner-name\"\n >\n {winner.name.korean} | {winner.name.english}\n </div>\n\n {/* Archetype Display */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(primaryColor, 0.8)}`,\n borderRadius: \"12px\",\n padding: spacing,\n marginTop: spacing / 2,\n minWidth: isMobile ? \"280px\" : \"320px\",\n }}\n data-testid=\"winner-archetype-display\"\n >\n {/* Archetype Name */}\n <div\n style={{\n fontSize: detailFontSize + 2,\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n marginBottom: spacing / 2,\n }}\n data-testid=\"winner-archetype\"\n >\n {archetypeAsset.name_korean} | {archetypeAsset.name_english}\n </div>\n\n {/* Archetype Code */}\n <div\n style={{\n fontSize: detailFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n fontFamily: FONT_FAMILY.KOREAN,\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n }}\n data-testid=\"archetype-code\"\n >\n {winner.archetype.toUpperCase()}\n </div>\n\n {/* Combat Stats Summary */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: spacing,\n marginTop: spacing,\n fontSize: detailFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n data-testid=\"combat-stats-summary\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.HEALTH_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.health)}\n </div>\n <div>체력 | HP</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.KI_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.ki)}\n </div>\n <div>기력 | Ki</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.STAMINA_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.stamina)}\n </div>\n <div>스태미나 | Stamina</div>\n </div>\n </div>\n </div>\n\n {/* CSS Animations */}\n <style>{`\n ${fadeInAnimation}\n ${scaleInAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;AAoBA,IAAM,cAAc,QAAwB,gBAAgB,KAAK,EAAE;;;;;AAMnE,IAAa,iBAA+C,EAC1D,QACA,WACA,UACA,eACI;CACJ,MAAM,gBAAgB,WAAW,KAAK,WAAW,KAAK;CACtD,MAAM,mBAAmB,WAAW,KAAK,WAAW,KAAK;CACzD,MAAM,iBAAiB,WAAW,KAAK;CACvC,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAEhD,MAAM,eAAe,YACjB,cAAc,cACd,cAAc;CAElB,MAAM,aAAa,YACf;EAAE,QAAQ;EAAO,SAAS;EAAY,GACtC;EAAE,QAAQ;EAAM,SAAS;EAAU;CAKvC,MAAM,iBACJ,iBAFA,OAAO,UAAU,aAEA,KAAiB,iBAAiB;
|
|
1
|
+
{"version":3,"file":"WinnerDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/WinnerDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { PlayerState } from \"../../../../systems\";\nimport {\n ARCHETYPE_ASSETS,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { fadeInAnimation, scaleInAnimation } from \"./animations\";\n\nexport interface WinnerDisplayProps {\n readonly winner: PlayerState;\n readonly isVictory: boolean;\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 * Winner Display Component\n * Shows winner announcement with archetype details\n */\nexport const WinnerDisplay: React.FC<WinnerDisplayProps> = ({\n winner,\n isVictory,\n isMobile,\n isTablet,\n}) => {\n const titleFontSize = isMobile ? 28 : isTablet ? 36 : 44;\n const subtitleFontSize = isMobile ? 16 : isTablet ? 18 : 22;\n const detailFontSize = isMobile ? 12 : 14;\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n\n const primaryColor = isVictory\n ? KOREAN_COLORS.ACCENT_GOLD\n : KOREAN_COLORS.ACCENT_RED;\n\n const resultText = isVictory\n ? { korean: \"승리!\", english: \"Victory!\" }\n : { korean: \"패배\", english: \"Defeat\" };\n\n // Get archetype asset info\n const archetypeKey =\n winner.archetype.toLowerCase() as keyof typeof ARCHETYPE_ASSETS;\n const archetypeAsset =\n ARCHETYPE_ASSETS[archetypeKey] || ARCHETYPE_ASSETS.musa;\n\n return (\n <div\n data-testid=\"winner-display\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n marginBottom: spacing,\n animation: \"fadeIn 0.8s ease-in\",\n }}\n >\n {/* Result Title with glow effect */}\n <div\n style={{\n fontSize: titleFontSize,\n fontWeight: \"bold\",\n color: toCssColor(primaryColor),\n textShadow: `0 0 20px ${hexToRgbaString(\n primaryColor,\n 0.8\n )}, 0 0 40px ${hexToRgbaString(primaryColor, 0.4)}`,\n marginBottom: spacing,\n textAlign: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n animation: \"scaleIn 0.5s ease-out\",\n }}\n data-testid=\"result-title\"\n >\n {resultText.korean} | {resultText.english}\n </div>\n\n {/* Winner Name */}\n <div\n style={{\n fontSize: subtitleFontSize,\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n marginBottom: spacing / 2,\n textAlign: \"center\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n }}\n data-testid=\"winner-name\"\n >\n {winner.name.korean} | {winner.name.english}\n </div>\n\n {/* Archetype Display */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(primaryColor, 0.8)}`,\n borderRadius: \"12px\",\n padding: spacing,\n marginTop: spacing / 2,\n minWidth: isMobile ? \"280px\" : \"320px\",\n }}\n data-testid=\"winner-archetype-display\"\n >\n {/* Archetype Name */}\n <div\n style={{\n fontSize: detailFontSize + 2,\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n marginBottom: spacing / 2,\n }}\n data-testid=\"winner-archetype\"\n >\n {archetypeAsset.name_korean} | {archetypeAsset.name_english}\n </div>\n\n {/* Archetype Code */}\n <div\n style={{\n fontSize: detailFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n fontFamily: FONT_FAMILY.KOREAN,\n textTransform: \"uppercase\",\n letterSpacing: \"0.1em\",\n }}\n data-testid=\"archetype-code\"\n >\n {winner.archetype.toUpperCase()}\n </div>\n\n {/* Combat Stats Summary */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: spacing,\n marginTop: spacing,\n fontSize: detailFontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n data-testid=\"combat-stats-summary\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.HEALTH_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.health)}\n </div>\n <div>체력 | HP</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.KI_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.ki)}\n </div>\n <div>기력 | Ki</div>\n </div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n color: toCssColor(KOREAN_COLORS.STAMINA_FULL),\n fontWeight: \"bold\",\n }}\n >\n {Math.round(winner.stamina)}\n </div>\n <div>스태미나 | Stamina</div>\n </div>\n </div>\n </div>\n\n {/* CSS Animations */}\n <style>{`\n ${fadeInAnimation}\n ${scaleInAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -21,7 +21,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
21
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
22
22
|
import { Canvas } from "@react-three/fiber";
|
|
23
23
|
//#region src/components/screens/intro/IntroScreen3D.tsx
|
|
24
|
-
var APP_VERSION = "0.7.
|
|
24
|
+
var APP_VERSION = "0.7.41";
|
|
25
25
|
var MENU_ITEMS = [
|
|
26
26
|
{
|
|
27
27
|
mode: GameMode.VERSUS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntroScreen3D.js","names":[],"sources":["../../../../src/components/screens/intro/IntroScreen3D.tsx"],"sourcesContent":["// UI renders outside Canvas in absolute-positioned div - no Html needed\nimport { Canvas } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { getScreenSize } from \"../../../systems/ResponsiveScaling\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../systems/types\";\nimport { GameMode, PlayerArchetype } from \"../../../types/common\";\nimport {\n ARCHETYPE_BACKGROUNDS,\n getKoreanFontSize,\n getPerformanceSettings,\n} from \"../../../types/constants\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\nimport { getArchetypeAssets } from \"../../../utils/playerUtils\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { BackgroundScene3D } from \"../../shared/three\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { ArchetypeDisplayOverlayHtml } from \"./components/ArchetypeDisplayOverlayHtml\";\nimport { EnhancedArchetypeDisplay } from \"./components/EnhancedArchetypeDisplayOverlayHtml\";\nimport { MenuSectionOverlayHtml } from \"./components/MenuSectionOverlayHtml\";\n\nconst APP_VERSION = import.meta.env.APP_VERSION;\n\nexport interface IntroScreen3DProps {\n readonly onMenuSelect: (mode: GameMode, archetype?: PlayerArchetype) => void;\n readonly onArchetypeSelect?: (archetype: PlayerArchetype) => void;\n readonly selectedArchetype?: PlayerArchetype;\n readonly width?: number;\n readonly height?: number;\n readonly useEnhancedArchetypeDisplay?: boolean; // Use enhanced card display\n}\n\nconst MENU_ITEMS: { mode: GameMode; korean: string; english: string }[] = [\n { mode: GameMode.VERSUS, korean: \"대전\", english: \"Combat\" },\n { mode: GameMode.TRAINING, korean: \"훈련\", english: \"Training\" },\n { mode: GameMode.CONTROLS, korean: \"조작\", english: \"Controls\" },\n { mode: GameMode.PHILOSOPHY, korean: \"철학\", english: \"Philosophy\" },\n];\n\n// Texture key mapping for archetypes\nconst ARCHETYPE_TEXTURE_MAPPING: Record<PlayerArchetype, string> = {\n [PlayerArchetype.MUSA]: \"musa\",\n [PlayerArchetype.AMSALJA]: \"amsalja\",\n [PlayerArchetype.HACKER]: \"hacker\",\n [PlayerArchetype.JEONGBO_YOWON]: \"jeongbo_yowon\",\n [PlayerArchetype.JOJIK_POKRYEOKBAE]: \"jojik_pokryeokbae\",\n};\n\n// Helper function to convert PlayerArchetype enum to array index\nconst getArchetypeIndex = (archetype: PlayerArchetype): number => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys.indexOf(archetype);\n};\n\n// Helper function to convert array index to PlayerArchetype enum\nconst getArchetypeFromIndex = (index: number): PlayerArchetype => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys[index] ?? PlayerArchetype.MUSA;\n};\n\n/**\n * Three.js-based IntroScreen Component\n */\nexport const IntroScreen3D: React.FC<IntroScreen3DProps> = ({\n onMenuSelect,\n onArchetypeSelect,\n selectedArchetype = PlayerArchetype.MUSA,\n width: propWidth,\n height: propHeight,\n useEnhancedArchetypeDisplay = true, // Default to enhanced display\n}) => {\n const audio = useAudio();\n const introMusicStarted = useRef(false);\n const [selectedMenuIndex, setSelectedMenuIndex] = useState(0);\n // UI now renders outside Canvas - no canvas ready state needed\n\n // Handle WebGL context loss and restoration (for 3D background only)\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in IntroScreen\");\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in IntroScreen\");\n },\n autoRestore: true,\n });\n\n // Add local state for archetype management\n const [currentArchetype, setCurrentArchetype] =\n useState<PlayerArchetype>(selectedArchetype);\n const [selectedArchetypeIndex, setSelectedArchetypeIndex] = useState<number>(\n getArchetypeIndex(selectedArchetype),\n );\n\n const { width, height } = useWindowSize();\n\n // Use prop dimensions if provided, otherwise use window size with defensive fallbacks\n // Ensure minimum valid dimensions to prevent rendering issues\n const screenWidth = propWidth ?? (width || 1200);\n const screenHeight = propHeight ?? (height || 800);\n\n // Create archetype data with texture keys from PLAYER_ARCHETYPES_DATA\n const archetypeData = useMemo(() => {\n return Object.entries(PLAYER_ARCHETYPES_DATA).map(([key, data]) => {\n const archetypeEnum = key as PlayerArchetype;\n return {\n id: key.toLowerCase(),\n korean: data.name.korean,\n english: data.name.english,\n description: data.description.korean,\n color: data.colors.primary,\n textureKey: ARCHETYPE_TEXTURE_MAPPING[archetypeEnum],\n stats: data.stats,\n philosophy: data.philosophy,\n specialAbilities: data.specialAbilities, // Include special abilities\n };\n });\n }, []);\n\n // Sync with prop changes\n useEffect(() => {\n setCurrentArchetype(selectedArchetype);\n setSelectedArchetypeIndex(getArchetypeIndex(selectedArchetype));\n }, [selectedArchetype]);\n\n // Direct menu selection - MOVED BEFORE useEffect that uses it\n const handleMenuItemSelect = useCallback(\n (mode: GameMode) => {\n onMenuSelect(mode, currentArchetype);\n },\n [onMenuSelect, currentArchetype],\n );\n\n // Handle archetype change by index - MOVED BEFORE useEffect that uses it\n const handleArchetypeIndexChange = useCallback(\n (index: number) => {\n const newArchetype = getArchetypeFromIndex(index);\n setSelectedArchetypeIndex(index);\n setCurrentArchetype(newArchetype);\n onArchetypeSelect?.(newArchetype);\n\n // Check if audio system is ready, not individual methods\n if (audio.isAudioReady) {\n audio.playSFX(\"menu_hover\");\n\n // Play archetype theme music preview when archetype changes\n // Use getArchetypeAssets utility for proper error handling and fallback\n const archetypeAssets = getArchetypeAssets(newArchetype);\n // Stop intro music and play archetype theme\n audio.stopMusic();\n audio.playMusic(archetypeAssets.themeId);\n }\n },\n [onArchetypeSelect, audio],\n );\n\n // Play intro music after first user interaction\n useEffect(() => {\n const startMusic = () => {\n if (audio.isAudioReady && !introMusicStarted.current) {\n introMusicStarted.current = true;\n audio.playMusic(\"intro_theme\");\n }\n };\n // Event listeners with { once: true } are automatically removed after triggering\n window.addEventListener(\"keydown\", startMusic, { once: true });\n window.addEventListener(\"mousedown\", startMusic, { once: true });\n window.addEventListener(\"touchstart\", startMusic, { once: true, passive: true });\n\n return () => {\n // Safe cleanup - check if audio is initialized before stopping music\n if (audio.isInitialized) {\n audio.stopMusic();\n }\n };\n }, [audio]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n // Archetype navigation (handleArchetypeIndexChange already plays menu_hover SFX)\n if (event.key === \"ArrowLeft\") {\n const newIndex =\n selectedArchetypeIndex === 0\n ? archetypeData.length - 1\n : selectedArchetypeIndex - 1;\n handleArchetypeIndexChange(newIndex);\n } else if (event.key === \"ArrowRight\") {\n const newIndex = (selectedArchetypeIndex + 1) % archetypeData.length;\n handleArchetypeIndexChange(newIndex);\n } else {\n // Direct game mode shortcuts\n switch (event.key.toLowerCase()) {\n case \"c\":\n handleMenuItemSelect(GameMode.CONTROLS);\n break;\n case \"p\":\n handleMenuItemSelect(GameMode.PHILOSOPHY);\n break;\n case \"t\":\n handleMenuItemSelect(GameMode.TRAINING);\n break;\n case \"v\":\n handleMenuItemSelect(GameMode.VERSUS);\n break;\n }\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [\n audio,\n archetypeData.length,\n selectedArchetypeIndex,\n handleArchetypeIndexChange,\n handleMenuItemSelect,\n ]);\n\n // Responsive layout calculations with large desktop support\n // Use device detection instead of width-only breakpoint to correctly identify high-res mobile devices\n // shouldUseMobileControls() uses user-agent detection which doesn't change during session\n // Use isMobile only for mobile CONTROLS (touch controls, etc.)\n // Layout sizing should use screenWidth-based calculations\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Memoize colors from theme for performance\n const colors = useMemo(\n () => ({\n trigramTextShadow: `0 0 10px ${hexToRgbaString(\n theme.colors.PRIMARY_CYAN,\n 0.8,\n )}`,\n footerBackground: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.9),\n footerBorder: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.3),\n }),\n [theme],\n );\n\n // Performance settings based on device tier\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(screenWidth, isMobile);\n }, [screenWidth, isMobile]);\n\n // Get screen size category for layout calculations (mobile, tablet, desktop, large, xlarge)\n const screenSize = useMemo(() => getScreenSize(screenWidth), [screenWidth]);\n\n // Use percentage-based layout based on screen dimensions\n // Logo takes priority - larger and more prominent\n const logoSize = useMemo(() => {\n // Logo should be prominent - use percentage of smaller dimension\n const minDim = Math.min(screenWidth, screenHeight);\n // Scale based on screen size category\n const logoScale = {\n mobile: 0.28,\n tablet: 0.22,\n desktop: 0.18,\n large: 0.15,\n xlarge: 0.12,\n }[screenSize];\n // Cap at reasonable max size for very large screens\n return Math.min(minDim * logoScale, screenSize === \"xlarge\" ? 250 : 300);\n }, [screenWidth, screenHeight, screenSize]);\n\n // Dynamic heights based on available screen space (percentage-based)\n // All calculations use screen dimensions, not device type\n const layoutHeights = useMemo(() => {\n const availableHeight = screenHeight;\n\n // Title area - small header with title and description\n const titleHeight = screenWidth < 768 ? 32 : 38;\n\n // Logo area - based on logo size plus trigram symbols (compact)\n const trigramHeight = screenWidth < 768 ? 16 : 22;\n const logoAreaHeight = logoSize + trigramHeight;\n\n // Footer - compact with all info\n const footerHeight = Math.max(availableHeight * 0.05, 48);\n\n // Remaining space for menu + archetype\n const contentHeight =\n availableHeight - titleHeight - logoAreaHeight - footerHeight;\n\n // Menu needs enough height for 2x2 grid (2 rows of ~40px buttons + title + padding)\n // Mobile needs column layout (4 buttons stacked)\n const menuMinHeight = screenWidth < 768 ? 180 : 120;\n const menuPercent = screenWidth < 768 ? 0.38 : 0.25;\n const menuHeight = Math.max(contentHeight * menuPercent, menuMinHeight);\n const archetypeHeight = contentHeight - menuHeight - 8; // 8px gap\n\n // Gap scales with screen (minimal)\n const gap = Math.max(screenHeight * 0.002, 2);\n\n return {\n titleHeight,\n logoAreaHeight,\n menuHeight,\n archetypeHeight,\n footerHeight,\n gap,\n };\n }, [screenHeight, screenWidth, logoSize]);\n return (\n <div\n style={{\n width: screenWidth,\n height: screenHeight,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"intro-screen\"\n >\n {/* Archetype background image (very subtle, behind 3D scene) */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundImage: `url(${ARCHETYPE_BACKGROUNDS.overview})`,\n backgroundSize: \"cover\",\n backgroundPosition: \"center\",\n backgroundRepeat: \"no-repeat\",\n opacity: 0.08,\n filter: \"blur(4px)\",\n zIndex: Z_INDEX.BACKGROUND,\n }}\n data-testid=\"archetype-background\"\n />\n\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* Three.js Canvas for 3D background */}\n <Canvas\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n zIndex: Z_INDEX.ARENA,\n }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: true,\n powerPreference: \"high-performance\",\n }}\n dpr={performanceSettings.dpr}\n camera={{ position: [0, 5, 10], fov: 75 }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 0.95);\n }}\n >\n {/* 3D Background Scene */}\n <BackgroundScene3D theme=\"intro\" />\n </Canvas>\n\n {/* UI Overlay (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n }}\n data-testid=\"intro-hud-overlay\"\n >\n <div\n style={{\n width: \"100vw\",\n height: \"100vh\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: 0,\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n overflow: \"hidden\",\n }}\n >\n {/* Title - Small, above logo */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n pointerEvents: \"none\",\n marginTop: \"4px\",\n flexShrink: 0,\n }}\n data-testid=\"main-title-container\"\n >\n <div\n style={{\n fontSize: screenWidth < 768 ? \"14px\" : \"16px\",\n fontWeight: \"bold\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: `#${theme.colors.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n textShadow: \"0 0 10px rgba(255, 170, 0, 0.5)\",\n }}\n >\n 흑괘 | Black Trigram\n </div>\n <div\n style={{\n fontSize: screenWidth < 768 ? \"10px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n color: `#${theme.colors.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n }}\n >\n 한국 무술 시뮬레이터 | Korean Martial Arts Simulator\n </div>\n </div>\n\n {/* Logo Section - Primary branding */}\n <div\n style={{\n flexShrink: 0,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: `${layoutHeights.logoAreaHeight}px`,\n pointerEvents: \"none\",\n }}\n data-testid=\"logo-section\"\n >\n {/* Logo Image - Prominent */}\n <img\n src=\"/assets/visual/logo/black-trigram.png\"\n alt=\"Black Trigram Logo\"\n style={{\n width: `${logoSize}px`,\n height: `${logoSize}px`,\n objectFit: \"contain\",\n filter: \"drop-shadow(0 0 30px rgba(0, 255, 255, 0.6))\",\n }}\n data-testid=\"main-logo\"\n onError={(e) => {\n e.currentTarget.style.display = \"none\";\n }}\n />\n\n {/* Trigram Symbols */}\n <div\n style={{\n fontSize: screenWidth < 768 ? \"16px\" : \"18px\",\n color: `#${theme.colors.PRIMARY_CYAN.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n letterSpacing: screenWidth < 768 ? \"6px\" : \"8px\",\n textAlign: \"center\",\n marginTop: screenWidth < 768 ? \"8px\" : \"10px\",\n textShadow: colors.trigramTextShadow,\n }}\n data-testid=\"trigram-symbols\"\n >\n ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷\n </div>\n </div>\n\n {/* Main Content Area */}\n <div\n style={{\n width: \"100%\",\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"flex-start\",\n gap: \"4px\",\n paddingLeft: screenWidth < 768 ? \"8px\" : \"16px\",\n paddingRight: screenWidth < 768 ? \"8px\" : \"16px\",\n overflow: \"hidden\",\n pointerEvents: \"auto\",\n minHeight: 0,\n }}\n data-testid=\"main-content\"\n >\n {/* Menu Section - Compact */}\n <div\n style={{\n width: screenWidth < 768 ? \"95%\" : \"70%\",\n maxWidth: screenWidth < 768 ? \"100%\" : \"600px\",\n }}\n data-testid=\"menu-section-container\"\n >\n <MenuSectionOverlayHtml\n menuItems={MENU_ITEMS}\n selectedIndex={selectedMenuIndex}\n onModeSelect={handleMenuItemSelect}\n onSelectedIndexChange={setSelectedMenuIndex}\n onPlaySFX={audio.playSFX}\n width={\n // Compact menu width - narrower for better proportions\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(500, screenWidth * 0.6)\n : Math.min(550, screenWidth * 0.4)\n }\n height={layoutHeights.menuHeight}\n isMobile={isMobile}\n />\n </div>\n\n {/* Archetype Selection - Scrollable container */}\n <div\n style={{\n width: screenWidth < 768 ? \"100%\" : \"85%\",\n maxWidth: screenWidth < 768 ? \"100%\" : \"900px\",\n flex: 1,\n minHeight: 0,\n overflowY: \"auto\",\n overflowX: \"hidden\",\n }}\n data-testid=\"archetype-section-container\"\n >\n {useEnhancedArchetypeDisplay ? (\n <EnhancedArchetypeDisplay\n archetypes={archetypeData}\n selectedIndex={selectedArchetypeIndex}\n onArchetypeChange={handleArchetypeIndexChange}\n onPlaySFX={audio.playSFX}\n width={\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(700, screenWidth * 0.7)\n : Math.min(850, screenWidth * 0.55)\n }\n height={Math.max(layoutHeights.archetypeHeight - 40, 200)}\n isMobile={isMobile}\n allowDetailedView={screenWidth >= 768}\n />\n ) : (\n <ArchetypeDisplayOverlayHtml\n archetypes={archetypeData}\n selectedIndex={selectedArchetypeIndex}\n onArchetypeChange={handleArchetypeIndexChange}\n onPlaySFX={audio.playSFX}\n width={\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(700, screenWidth * 0.7)\n : Math.min(850, screenWidth * 0.55)\n }\n height={Math.max(layoutHeights.archetypeHeight - 40, 200)}\n isMobile={isMobile}\n />\n )}\n </div>\n </div>\n\n {/* Footer - Compact with all info */}\n <div\n style={{\n width: \"100%\",\n minHeight: `${layoutHeights.footerHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"1px\",\n background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${colors.footerBackground})`,\n borderTop: `1px solid ${colors.footerBorder}`,\n pointerEvents: \"auto\",\n paddingBottom: \"2px\",\n flexShrink: 0,\n }}\n data-testid=\"intro-footer\"\n >\n {/* Motto */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 3, 10)}px`,\n color: `#${theme.colors.ACCENT_CYAN.toString(16).padStart(6, \"0\")}`,\n fontFamily: theme.koreanTypography.fontFamily,\n fontStyle: \"italic\",\n textAlign: \"center\",\n }}\n data-testid=\"footer-motto\"\n >\n 흑괘의 길을 걸어라 - Walk the Path of the Black Trigram\n </div>\n {/* Open Source Link */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 4, 9)}px`,\n color: `#${theme.colors.ACCENT_BLUE.toString(16).padStart(6, \"0\")}`,\n textAlign: \"center\",\n cursor: \"pointer\",\n }}\n onClick={() =>\n window.open(\"https://github.com/Hack23/blacktrigram\", \"_blank\")\n }\n data-testid=\"footer-link\"\n >\n Open Source Korean Martial Arts Game by Hack23\n </div>\n {/* Version */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 5, 8)}px`,\n color: `#${theme.colors.ACCENT_BLUE.toString(16).padStart(6, \"0\")}`,\n textAlign: \"center\",\n cursor: \"pointer\",\n }}\n onClick={() =>\n window.open(\n `https://github.com/Hack23/blacktrigram/releases/tag/v${APP_VERSION}`,\n \"_blank\",\n )\n }\n data-testid=\"footer-version\"\n >\n v{APP_VERSION}\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default IntroScreen3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,cAAA;AAWN,IAAM,aAAoE;CACxE;EAAE,MAAM,SAAS;EAAQ,QAAQ;EAAM,SAAS;EAAU;CAC1D;EAAE,MAAM,SAAS;EAAU,QAAQ;EAAM,SAAS;EAAY;CAC9D;EAAE,MAAM,SAAS;EAAU,QAAQ;EAAM,SAAS;EAAY;CAC9D;EAAE,MAAM,SAAS;EAAY,QAAQ;EAAM,SAAS;EAAc;CACnE;AAGD,IAAM,4BAA6D;EAChE,gBAAgB,OAAO;EACvB,gBAAgB,UAAU;EAC1B,gBAAgB,SAAS;EACzB,gBAAgB,gBAAgB;EAChC,gBAAgB,oBAAoB;CACtC;AAGD,IAAM,qBAAqB,cAAuC;AAIhE,QAHsB,OAAO,KAC3B,uBAEK,CAAc,QAAQ,UAAU;;AAIzC,IAAM,yBAAyB,UAAmC;AAIhE,QAHsB,OAAO,KAC3B,uBAEK,CAAc,UAAU,gBAAgB;;;;;AAMjD,IAAa,iBAA+C,EAC1D,cACA,mBACA,oBAAoB,gBAAgB,MACpC,OAAO,WACP,QAAQ,YACR,8BAA8B,WAC1B;CACJ,MAAM,QAAQ,UAAU;CACxB,MAAM,oBAAoB,OAAO,MAAM;CACvC,MAAM,CAAC,mBAAmB,wBAAwB,SAAS,EAAE;AAI7D,4BAA2B;EACzB,qBAAqB;AACnB,WAAQ,KAAK,uCAAuC;;EAEtD,yBAAyB;AACvB,WAAQ,IAAI,0CAA0C;;EAExD,aAAa;EACd,CAAC;CAGF,MAAM,CAAC,kBAAkB,uBACvB,SAA0B,kBAAkB;CAC9C,MAAM,CAAC,wBAAwB,6BAA6B,SAC1D,kBAAkB,kBAAkB,CACrC;CAED,MAAM,EAAE,OAAO,WAAW,eAAe;CAIzC,MAAM,cAAc,cAAc,SAAS;CAC3C,MAAM,eAAe,eAAe,UAAU;CAG9C,MAAM,gBAAgB,cAAc;AAClC,SAAO,OAAO,QAAQ,uBAAuB,CAAC,KAAK,CAAC,KAAK,UAAU;GACjE,MAAM,gBAAgB;AACtB,UAAO;IACL,IAAI,IAAI,aAAa;IACrB,QAAQ,KAAK,KAAK;IAClB,SAAS,KAAK,KAAK;IACnB,aAAa,KAAK,YAAY;IAC9B,OAAO,KAAK,OAAO;IACnB,YAAY,0BAA0B;IACtC,OAAO,KAAK;IACZ,YAAY,KAAK;IACjB,kBAAkB,KAAK;IACxB;IACD;IACD,EAAE,CAAC;AAGN,iBAAgB;AACd,sBAAoB,kBAAkB;AACtC,4BAA0B,kBAAkB,kBAAkB,CAAC;IAC9D,CAAC,kBAAkB,CAAC;CAGvB,MAAM,uBAAuB,aAC1B,SAAmB;AAClB,eAAa,MAAM,iBAAiB;IAEtC,CAAC,cAAc,iBAAiB,CACjC;CAGD,MAAM,6BAA6B,aAChC,UAAkB;EACjB,MAAM,eAAe,sBAAsB,MAAM;AACjD,4BAA0B,MAAM;AAChC,sBAAoB,aAAa;AACjC,sBAAoB,aAAa;AAGjC,MAAI,MAAM,cAAc;AACtB,SAAM,QAAQ,aAAa;GAI3B,MAAM,kBAAkB,mBAAmB,aAAa;AAExD,SAAM,WAAW;AACjB,SAAM,UAAU,gBAAgB,QAAQ;;IAG5C,CAAC,mBAAmB,MAAM,CAC3B;AAGD,iBAAgB;EACd,MAAM,mBAAmB;AACvB,OAAI,MAAM,gBAAgB,CAAC,kBAAkB,SAAS;AACpD,sBAAkB,UAAU;AAC5B,UAAM,UAAU,cAAc;;;AAIlC,SAAO,iBAAiB,WAAW,YAAY,EAAE,MAAM,MAAM,CAAC;AAC9D,SAAO,iBAAiB,aAAa,YAAY,EAAE,MAAM,MAAM,CAAC;AAChE,SAAO,iBAAiB,cAAc,YAAY;GAAE,MAAM;GAAM,SAAS;GAAM,CAAC;AAEhF,eAAa;AAEX,OAAI,MAAM,cACR,OAAM,WAAW;;IAGpB,CAAC,MAAM,CAAC;AAGX,iBAAgB;EACd,MAAM,iBAAiB,UAAyB;AAE9C,OAAI,MAAM,QAAQ,YAKhB,4BAHE,2BAA2B,IACvB,cAAc,SAAS,IACvB,yBAAyB,EACK;YAC3B,MAAM,QAAQ,aAEvB,6BADkB,yBAAyB,KAAK,cAAc,OAC1B;OAGpC,SAAQ,MAAM,IAAI,aAAa,EAA/B;IACE,KAAK;AACH,0BAAqB,SAAS,SAAS;AACvC;IACF,KAAK;AACH,0BAAqB,SAAS,WAAW;AACzC;IACF,KAAK;AACH,0BAAqB,SAAS,SAAS;AACvC;IACF,KAAK;AACH,0BAAqB,SAAS,OAAO;AACrC;;;AAKR,SAAO,iBAAiB,WAAW,cAAc;AACjD,eAAa,OAAO,oBAAoB,WAAW,cAAc;IAChE;EACD;EACA,cAAc;EACd;EACA;EACA;EACD,CAAC;CAOF,MAAM,WAAW,cAAc,yBAAyB,EAAE,EAAE,CAAC;CAG7D,MAAM,QAAQ,eAAe;EAC3B,SAAS;EACT,MAAM;EACN;EACD,CAAC;CAGF,MAAM,SAAS,eACN;EACL,mBAAmB,YAAY,gBAC7B,MAAM,OAAO,cACb,GACD;EACD,kBAAkB,gBAAgB,MAAM,OAAO,oBAAoB,GAAI;EACvE,cAAc,gBAAgB,MAAM,OAAO,aAAa,GAAI;EAC7D,GACD,CAAC,MAAM,CACR;CAGD,MAAM,sBAAsB,cAAc;AACxC,SAAO,uBAAuB,aAAa,SAAS;IACnD,CAAC,aAAa,SAAS,CAAC;CAG3B,MAAM,aAAa,cAAc,cAAc,YAAY,EAAE,CAAC,YAAY,CAAC;CAI3E,MAAM,WAAW,cAAc;EAE7B,MAAM,SAAS,KAAK,IAAI,aAAa,aAAa;EAElD,MAAM,YAAY;GAChB,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,OAAO;GACP,QAAQ;GACT,CAAC;AAEF,SAAO,KAAK,IAAI,SAAS,WAAW,eAAe,WAAW,MAAM,IAAI;IACvE;EAAC;EAAa;EAAc;EAAW,CAAC;CAI3C,MAAM,gBAAgB,cAAc;EAClC,MAAM,kBAAkB;EAGxB,MAAM,cAAc,cAAc,MAAM,KAAK;EAI7C,MAAM,iBAAiB,YADD,cAAc,MAAM,KAAK;EAI/C,MAAM,eAAe,KAAK,IAAI,kBAAkB,KAAM,GAAG;EAGzD,MAAM,gBACJ,kBAAkB,cAAc,iBAAiB;EAInD,MAAM,gBAAgB,cAAc,MAAM,MAAM;EAEhD,MAAM,aAAa,KAAK,IAAI,iBADR,cAAc,MAAM,MAAO,MACU,cAAc;AAMvE,SAAO;GACL;GACA;GACA;GACA,iBATsB,gBAAgB,aAAa;GAUnD;GACA,KARU,KAAK,IAAI,eAAe,MAAO,EAQzC;GACD;IACA;EAAC;EAAc;EAAa;EAAS,CAAC;AACzC,QACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,QAAQ;GACR,UAAU;GACV,UAAU;GACX;EACD,eAAY;YAPd;GAUE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,OAAO;KACP,QAAQ;KACR,iBAAiB,OAAO,sBAAsB,SAAS;KACvD,gBAAgB;KAChB,oBAAoB;KACpB,kBAAkB;KAClB,SAAS;KACT,QAAQ;KACR,QAAQ,QAAQ;KACjB;IACD,eAAY;IACZ,CAAA;GAGF,oBAAC,eAAD;IAAe,UAAS;IAAY,SAAS;IAAY,CAAA;GAGzD,oBAAC,QAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,OAAO;KACP,QAAQ;KACR,QAAQ,QAAQ;KACjB;IACD,IAAI;KACF,WAAW,oBAAoB;KAC/B,OAAO;KACP,iBAAiB;KAClB;IACD,KAAK,oBAAoB;IACzB,QAAQ;KAAE,UAAU;MAAC;MAAG;MAAG;MAAG;KAAE,KAAK;KAAI;IACzC,YAAY,EAAE,SAAS;AACrB,QAAG,cAAc,MAAM,OAAO,oBAAoB,IAAK;;cAIzD,oBAAC,mBAAD,EAAmB,OAAM,SAAU,CAAA;IAC5B,CAAA;GAGT,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,OAAO;KACP,QAAQ;KACR,eAAe;KACf,QAAQ,QAAQ;KACjB;IACD,eAAY;cAEZ,qBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,QAAQ;MACR,SAAS;MACT,eAAe;MACf,YAAY;MACZ,gBAAgB;MAChB,SAAS;MACT,eAAe;MACf,QAAQ,QAAQ;MAChB,UAAU;MACX;eAZH;MAeE,qBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,eAAe;QACf,YAAY;QACZ,KAAK;QACL,eAAe;QACf,WAAW;QACX,YAAY;QACb;OACD,eAAY;iBAVd,CAYE,oBAAC,OAAD;QACE,OAAO;SACL,UAAU,cAAc,MAAM,SAAS;SACvC,YAAY;SACZ,YAAY,MAAM,iBAAiB;SACnC,YAAY,MAAM,iBAAiB;SACnC,eAAe,MAAM,iBAAiB;SACtC,WAAW,MAAM,iBAAiB;SAClC,OAAO,IAAI,MAAM,OAAO,YAAY,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;SACjE,YAAY;SACb;kBACF;QAEK,CAAA,EACN,oBAAC,OAAD;QACE,OAAO;SACL,UAAU,cAAc,MAAM,SAAS;SACvC,YAAY,MAAM,iBAAiB;SACnC,OAAO,IAAI,MAAM,OAAO,eAAe,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;SACrE;kBACF;QAEK,CAAA,CACF;;MAGN,qBAAC,OAAD;OACE,OAAO;QACL,YAAY;QACZ,SAAS;QACT,eAAe;QACf,YAAY;QACZ,gBAAgB;QAChB,QAAQ,GAAG,cAAc,eAAe;QACxC,eAAe;QAChB;OACD,eAAY;iBAVd,CAaE,oBAAC,OAAD;QACE,KAAI;QACJ,KAAI;QACJ,OAAO;SACL,OAAO,GAAG,SAAS;SACnB,QAAQ,GAAG,SAAS;SACpB,WAAW;SACX,QAAQ;SACT;QACD,eAAY;QACZ,UAAU,MAAM;AACd,WAAE,cAAc,MAAM,UAAU;;QAElC,CAAA,EAGF,oBAAC,OAAD;QACE,OAAO;SACL,UAAU,cAAc,MAAM,SAAS;SACvC,OAAO,IAAI,MAAM,OAAO,aAAa,SAAS,GAAG,CAAC,SAChD,GACA,IACD;SACD,eAAe,cAAc,MAAM,QAAQ;SAC3C,WAAW;SACX,WAAW,cAAc,MAAM,QAAQ;SACvC,YAAY,OAAO;SACpB;QACD,eAAY;kBACb;QAEK,CAAA,CACF;;MAGN,qBAAC,OAAD;OACE,OAAO;QACL,OAAO;QACP,MAAM;QACN,SAAS;QACT,eAAe;QACf,YAAY;QACZ,gBAAgB;QAChB,KAAK;QACL,aAAa,cAAc,MAAM,QAAQ;QACzC,cAAc,cAAc,MAAM,QAAQ;QAC1C,UAAU;QACV,eAAe;QACf,WAAW;QACZ;OACD,eAAY;iBAfd,CAkBE,oBAAC,OAAD;QACE,OAAO;SACL,OAAO,cAAc,MAAM,QAAQ;SACnC,UAAU,cAAc,MAAM,SAAS;SACxC;QACD,eAAY;kBAEZ,oBAAC,wBAAD;SACE,WAAW;SACX,eAAe;SACf,cAAc;SACd,uBAAuB;SACvB,WAAW,MAAM;SACjB,OAEE,cAAc,MACV,cAAc,KACd,cAAc,OACZ,KAAK,IAAI,KAAK,cAAc,GAAI,GAChC,KAAK,IAAI,KAAK,cAAc,GAAI;SAExC,QAAQ,cAAc;SACZ;SACV,CAAA;QACE,CAAA,EAGN,oBAAC,OAAD;QACE,OAAO;SACL,OAAO,cAAc,MAAM,SAAS;SACpC,UAAU,cAAc,MAAM,SAAS;SACvC,MAAM;SACN,WAAW;SACX,WAAW;SACX,WAAW;SACZ;QACD,eAAY;kBAEX,8BACC,oBAAC,0BAAD;SACE,YAAY;SACZ,eAAe;SACf,mBAAmB;SACnB,WAAW,MAAM;SACjB,OACE,cAAc,MACV,cAAc,KACd,cAAc,OACZ,KAAK,IAAI,KAAK,cAAc,GAAI,GAChC,KAAK,IAAI,KAAK,cAAc,IAAK;SAEzC,QAAQ,KAAK,IAAI,cAAc,kBAAkB,IAAI,IAAI;SAC/C;SACV,mBAAmB,eAAe;SAClC,CAAA,GAEF,oBAAC,6BAAD;SACE,YAAY;SACZ,eAAe;SACf,mBAAmB;SACnB,WAAW,MAAM;SACjB,OACE,cAAc,MACV,cAAc,KACd,cAAc,OACZ,KAAK,IAAI,KAAK,cAAc,GAAI,GAChC,KAAK,IAAI,KAAK,cAAc,IAAK;SAEzC,QAAQ,KAAK,IAAI,cAAc,kBAAkB,IAAI,IAAI;SAC/C;SACV,CAAA;QAEA,CAAA,CACF;;MAGN,qBAAC,OAAD;OACE,OAAO;QACL,OAAO;QACP,WAAW,GAAG,cAAc,aAAa;QACzC,SAAS;QACT,eAAe;QACf,YAAY;QACZ,gBAAgB;QAChB,KAAK;QACL,YAAY,gDAAgD,OAAO,iBAAiB;QACpF,WAAW,aAAa,OAAO;QAC/B,eAAe;QACf,eAAe;QACf,YAAY;QACb;OACD,eAAY;iBAfd;QAkBE,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,GAAG,KAAK,IAAI,kBAAkB,SAAS,YAAY,GAAG,GAAG,GAAG,CAAC;UACvE,OAAO,IAAI,MAAM,OAAO,YAAY,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;UACjE,YAAY,MAAM,iBAAiB;UACnC,WAAW;UACX,WAAW;UACZ;SACD,eAAY;mBACb;SAEK,CAAA;QAEN,oBAAC,OAAD;SACE,OAAO;UACL,UAAU,GAAG,KAAK,IAAI,kBAAkB,SAAS,YAAY,GAAG,GAAG,EAAE,CAAC;UACtE,OAAO,IAAI,MAAM,OAAO,YAAY,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;UACjE,WAAW;UACX,QAAQ;UACT;SACD,eACE,OAAO,KAAK,0CAA0C,SAAS;SAEjE,eAAY;mBACb;SAEK,CAAA;QAEN,qBAAC,OAAD;SACE,OAAO;UACL,UAAU,GAAG,KAAK,IAAI,kBAAkB,SAAS,YAAY,GAAG,GAAG,EAAE,CAAC;UACtE,OAAO,IAAI,MAAM,OAAO,YAAY,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;UACjE,WAAW;UACX,QAAQ;UACT;SACD,eACE,OAAO,KACL,wDAAwD,eACxD,SACD;SAEH,eAAY;mBAbd,CAcC,KACG,YACE;;QACF;;MACF;;IACF,CAAA;GACF"}
|
|
1
|
+
{"version":3,"file":"IntroScreen3D.js","names":[],"sources":["../../../../src/components/screens/intro/IntroScreen3D.tsx"],"sourcesContent":["// UI renders outside Canvas in absolute-positioned div - no Html needed\nimport { Canvas } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { getScreenSize } from \"../../../systems/ResponsiveScaling\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../systems/types\";\nimport { GameMode, PlayerArchetype } from \"../../../types/common\";\nimport {\n ARCHETYPE_BACKGROUNDS,\n getKoreanFontSize,\n getPerformanceSettings,\n} from \"../../../types/constants\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\nimport { getArchetypeAssets } from \"../../../utils/playerUtils\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { BackgroundScene3D } from \"../../shared/three\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { ArchetypeDisplayOverlayHtml } from \"./components/ArchetypeDisplayOverlayHtml\";\nimport { EnhancedArchetypeDisplay } from \"./components/EnhancedArchetypeDisplayOverlayHtml\";\nimport { MenuSectionOverlayHtml } from \"./components/MenuSectionOverlayHtml\";\n\nconst APP_VERSION = import.meta.env.APP_VERSION;\n\nexport interface IntroScreen3DProps {\n readonly onMenuSelect: (mode: GameMode, archetype?: PlayerArchetype) => void;\n readonly onArchetypeSelect?: (archetype: PlayerArchetype) => void;\n readonly selectedArchetype?: PlayerArchetype;\n readonly width?: number;\n readonly height?: number;\n readonly useEnhancedArchetypeDisplay?: boolean; // Use enhanced card display\n}\n\nconst MENU_ITEMS: { mode: GameMode; korean: string; english: string }[] = [\n { mode: GameMode.VERSUS, korean: \"대전\", english: \"Combat\" },\n { mode: GameMode.TRAINING, korean: \"훈련\", english: \"Training\" },\n { mode: GameMode.CONTROLS, korean: \"조작\", english: \"Controls\" },\n { mode: GameMode.PHILOSOPHY, korean: \"철학\", english: \"Philosophy\" },\n];\n\n// Texture key mapping for archetypes\nconst ARCHETYPE_TEXTURE_MAPPING: Record<PlayerArchetype, string> = {\n [PlayerArchetype.MUSA]: \"musa\",\n [PlayerArchetype.AMSALJA]: \"amsalja\",\n [PlayerArchetype.HACKER]: \"hacker\",\n [PlayerArchetype.JEONGBO_YOWON]: \"jeongbo_yowon\",\n [PlayerArchetype.JOJIK_POKRYEOKBAE]: \"jojik_pokryeokbae\",\n};\n\n// Helper function to convert PlayerArchetype enum to array index\nconst getArchetypeIndex = (archetype: PlayerArchetype): number => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys.indexOf(archetype);\n};\n\n// Helper function to convert array index to PlayerArchetype enum\nconst getArchetypeFromIndex = (index: number): PlayerArchetype => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys[index] ?? PlayerArchetype.MUSA;\n};\n\n/**\n * Three.js-based IntroScreen Component\n */\nexport const IntroScreen3D: React.FC<IntroScreen3DProps> = ({\n onMenuSelect,\n onArchetypeSelect,\n selectedArchetype = PlayerArchetype.MUSA,\n width: propWidth,\n height: propHeight,\n useEnhancedArchetypeDisplay = true, // Default to enhanced display\n}) => {\n const audio = useAudio();\n const introMusicStarted = useRef(false);\n const [selectedMenuIndex, setSelectedMenuIndex] = useState(0);\n // UI now renders outside Canvas - no canvas ready state needed\n\n // Handle WebGL context loss and restoration (for 3D background only)\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in IntroScreen\");\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in IntroScreen\");\n },\n autoRestore: true,\n });\n\n // Add local state for archetype management\n const [currentArchetype, setCurrentArchetype] =\n useState<PlayerArchetype>(selectedArchetype);\n const [selectedArchetypeIndex, setSelectedArchetypeIndex] = useState<number>(\n getArchetypeIndex(selectedArchetype),\n );\n\n const { width, height } = useWindowSize();\n\n // Use prop dimensions if provided, otherwise use window size with defensive fallbacks\n // Ensure minimum valid dimensions to prevent rendering issues\n const screenWidth = propWidth ?? (width || 1200);\n const screenHeight = propHeight ?? (height || 800);\n\n // Create archetype data with texture keys from PLAYER_ARCHETYPES_DATA\n const archetypeData = useMemo(() => {\n return Object.entries(PLAYER_ARCHETYPES_DATA).map(([key, data]) => {\n const archetypeEnum = key as PlayerArchetype;\n return {\n id: key.toLowerCase(),\n korean: data.name.korean,\n english: data.name.english,\n description: data.description.korean,\n color: data.colors.primary,\n textureKey: ARCHETYPE_TEXTURE_MAPPING[archetypeEnum],\n stats: data.stats,\n philosophy: data.philosophy,\n specialAbilities: data.specialAbilities, // Include special abilities\n };\n });\n }, []);\n\n // Sync with prop changes\n useEffect(() => {\n setCurrentArchetype(selectedArchetype);\n setSelectedArchetypeIndex(getArchetypeIndex(selectedArchetype));\n }, [selectedArchetype]);\n\n // Direct menu selection - MOVED BEFORE useEffect that uses it\n const handleMenuItemSelect = useCallback(\n (mode: GameMode) => {\n onMenuSelect(mode, currentArchetype);\n },\n [onMenuSelect, currentArchetype],\n );\n\n // Handle archetype change by index - MOVED BEFORE useEffect that uses it\n const handleArchetypeIndexChange = useCallback(\n (index: number) => {\n const newArchetype = getArchetypeFromIndex(index);\n setSelectedArchetypeIndex(index);\n setCurrentArchetype(newArchetype);\n onArchetypeSelect?.(newArchetype);\n\n // Check if audio system is ready, not individual methods\n if (audio.isAudioReady) {\n audio.playSFX(\"menu_hover\");\n\n // Play archetype theme music preview when archetype changes\n // Use getArchetypeAssets utility for proper error handling and fallback\n const archetypeAssets = getArchetypeAssets(newArchetype);\n // Stop intro music and play archetype theme\n audio.stopMusic();\n audio.playMusic(archetypeAssets.themeId);\n }\n },\n [onArchetypeSelect, audio],\n );\n\n // Play intro music after first user interaction\n useEffect(() => {\n const startMusic = () => {\n if (audio.isAudioReady && !introMusicStarted.current) {\n introMusicStarted.current = true;\n audio.playMusic(\"intro_theme\");\n }\n };\n // Event listeners with { once: true } are automatically removed after triggering\n window.addEventListener(\"keydown\", startMusic, { once: true });\n window.addEventListener(\"mousedown\", startMusic, { once: true });\n window.addEventListener(\"touchstart\", startMusic, { once: true, passive: true });\n\n return () => {\n // Safe cleanup - check if audio is initialized before stopping music\n if (audio.isInitialized) {\n audio.stopMusic();\n }\n };\n }, [audio]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n // Archetype navigation (handleArchetypeIndexChange already plays menu_hover SFX)\n if (event.key === \"ArrowLeft\") {\n const newIndex =\n selectedArchetypeIndex === 0\n ? archetypeData.length - 1\n : selectedArchetypeIndex - 1;\n handleArchetypeIndexChange(newIndex);\n } else if (event.key === \"ArrowRight\") {\n const newIndex = (selectedArchetypeIndex + 1) % archetypeData.length;\n handleArchetypeIndexChange(newIndex);\n } else {\n // Direct game mode shortcuts\n switch (event.key.toLowerCase()) {\n case \"c\":\n handleMenuItemSelect(GameMode.CONTROLS);\n break;\n case \"p\":\n handleMenuItemSelect(GameMode.PHILOSOPHY);\n break;\n case \"t\":\n handleMenuItemSelect(GameMode.TRAINING);\n break;\n case \"v\":\n handleMenuItemSelect(GameMode.VERSUS);\n break;\n }\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [\n audio,\n archetypeData.length,\n selectedArchetypeIndex,\n handleArchetypeIndexChange,\n handleMenuItemSelect,\n ]);\n\n // Responsive layout calculations with large desktop support\n // Use device detection instead of width-only breakpoint to correctly identify high-res mobile devices\n // shouldUseMobileControls() uses user-agent detection which doesn't change during session\n // Use isMobile only for mobile CONTROLS (touch controls, etc.)\n // Layout sizing should use screenWidth-based calculations\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n // Use Korean theme hook for consistent theming\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n // Memoize colors from theme for performance\n const colors = useMemo(\n () => ({\n trigramTextShadow: `0 0 10px ${hexToRgbaString(\n theme.colors.PRIMARY_CYAN,\n 0.8,\n )}`,\n footerBackground: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.9),\n footerBorder: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.3),\n }),\n [theme],\n );\n\n // Performance settings based on device tier\n const performanceSettings = useMemo(() => {\n return getPerformanceSettings(screenWidth, isMobile);\n }, [screenWidth, isMobile]);\n\n // Get screen size category for layout calculations (mobile, tablet, desktop, large, xlarge)\n const screenSize = useMemo(() => getScreenSize(screenWidth), [screenWidth]);\n\n // Use percentage-based layout based on screen dimensions\n // Logo takes priority - larger and more prominent\n const logoSize = useMemo(() => {\n // Logo should be prominent - use percentage of smaller dimension\n const minDim = Math.min(screenWidth, screenHeight);\n // Scale based on screen size category\n const logoScale = {\n mobile: 0.28,\n tablet: 0.22,\n desktop: 0.18,\n large: 0.15,\n xlarge: 0.12,\n }[screenSize];\n // Cap at reasonable max size for very large screens\n return Math.min(minDim * logoScale, screenSize === \"xlarge\" ? 250 : 300);\n }, [screenWidth, screenHeight, screenSize]);\n\n // Dynamic heights based on available screen space (percentage-based)\n // All calculations use screen dimensions, not device type\n const layoutHeights = useMemo(() => {\n const availableHeight = screenHeight;\n\n // Title area - small header with title and description\n const titleHeight = screenWidth < 768 ? 32 : 38;\n\n // Logo area - based on logo size plus trigram symbols (compact)\n const trigramHeight = screenWidth < 768 ? 16 : 22;\n const logoAreaHeight = logoSize + trigramHeight;\n\n // Footer - compact with all info\n const footerHeight = Math.max(availableHeight * 0.05, 48);\n\n // Remaining space for menu + archetype\n const contentHeight =\n availableHeight - titleHeight - logoAreaHeight - footerHeight;\n\n // Menu needs enough height for 2x2 grid (2 rows of ~40px buttons + title + padding)\n // Mobile needs column layout (4 buttons stacked)\n const menuMinHeight = screenWidth < 768 ? 180 : 120;\n const menuPercent = screenWidth < 768 ? 0.38 : 0.25;\n const menuHeight = Math.max(contentHeight * menuPercent, menuMinHeight);\n const archetypeHeight = contentHeight - menuHeight - 8; // 8px gap\n\n // Gap scales with screen (minimal)\n const gap = Math.max(screenHeight * 0.002, 2);\n\n return {\n titleHeight,\n logoAreaHeight,\n menuHeight,\n archetypeHeight,\n footerHeight,\n gap,\n };\n }, [screenHeight, screenWidth, logoSize]);\n return (\n <div\n style={{\n width: screenWidth,\n height: screenHeight,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"intro-screen\"\n >\n {/* Archetype background image (very subtle, behind 3D scene) */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n backgroundImage: `url(${ARCHETYPE_BACKGROUNDS.overview})`,\n backgroundSize: \"cover\",\n backgroundPosition: \"center\",\n backgroundRepeat: \"no-repeat\",\n opacity: 0.08,\n filter: \"blur(4px)\",\n zIndex: Z_INDEX.BACKGROUND,\n }}\n data-testid=\"archetype-background\"\n />\n\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* Three.js Canvas for 3D background */}\n <Canvas\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n zIndex: Z_INDEX.ARENA,\n }}\n gl={{\n antialias: performanceSettings.antialias,\n alpha: true,\n powerPreference: \"high-performance\",\n }}\n dpr={performanceSettings.dpr}\n camera={{ position: [0, 5, 10], fov: 75 }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 0.95);\n }}\n >\n {/* 3D Background Scene */}\n <BackgroundScene3D theme=\"intro\" />\n </Canvas>\n\n {/* UI Overlay (positioned absolutely over Canvas) - matches CombatScreen pattern */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n }}\n data-testid=\"intro-hud-overlay\"\n >\n <div\n style={{\n width: \"100vw\",\n height: \"100vh\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: 0,\n pointerEvents: \"none\",\n zIndex: Z_INDEX.HUD,\n overflow: \"hidden\",\n }}\n >\n {/* Title - Small, above logo */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n pointerEvents: \"none\",\n marginTop: \"4px\",\n flexShrink: 0,\n }}\n data-testid=\"main-title-container\"\n >\n <div\n style={{\n fontSize: screenWidth < 768 ? \"14px\" : \"16px\",\n fontWeight: \"bold\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: `#${theme.colors.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n textShadow: \"0 0 10px rgba(255, 170, 0, 0.5)\",\n }}\n >\n 흑괘 | Black Trigram\n </div>\n <div\n style={{\n fontSize: screenWidth < 768 ? \"10px\" : \"11px\",\n fontFamily: theme.koreanTypography.fontFamily,\n color: `#${theme.colors.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n }}\n >\n 한국 무술 시뮬레이터 | Korean Martial Arts Simulator\n </div>\n </div>\n\n {/* Logo Section - Primary branding */}\n <div\n style={{\n flexShrink: 0,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: `${layoutHeights.logoAreaHeight}px`,\n pointerEvents: \"none\",\n }}\n data-testid=\"logo-section\"\n >\n {/* Logo Image - Prominent */}\n <img\n src=\"/assets/visual/logo/black-trigram.png\"\n alt=\"Black Trigram Logo\"\n style={{\n width: `${logoSize}px`,\n height: `${logoSize}px`,\n objectFit: \"contain\",\n filter: \"drop-shadow(0 0 30px rgba(0, 255, 255, 0.6))\",\n }}\n data-testid=\"main-logo\"\n onError={(e) => {\n e.currentTarget.style.display = \"none\";\n }}\n />\n\n {/* Trigram Symbols */}\n <div\n style={{\n fontSize: screenWidth < 768 ? \"16px\" : \"18px\",\n color: `#${theme.colors.PRIMARY_CYAN.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n letterSpacing: screenWidth < 768 ? \"6px\" : \"8px\",\n textAlign: \"center\",\n marginTop: screenWidth < 768 ? \"8px\" : \"10px\",\n textShadow: colors.trigramTextShadow,\n }}\n data-testid=\"trigram-symbols\"\n >\n ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷\n </div>\n </div>\n\n {/* Main Content Area */}\n <div\n style={{\n width: \"100%\",\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"flex-start\",\n gap: \"4px\",\n paddingLeft: screenWidth < 768 ? \"8px\" : \"16px\",\n paddingRight: screenWidth < 768 ? \"8px\" : \"16px\",\n overflow: \"hidden\",\n pointerEvents: \"auto\",\n minHeight: 0,\n }}\n data-testid=\"main-content\"\n >\n {/* Menu Section - Compact */}\n <div\n style={{\n width: screenWidth < 768 ? \"95%\" : \"70%\",\n maxWidth: screenWidth < 768 ? \"100%\" : \"600px\",\n }}\n data-testid=\"menu-section-container\"\n >\n <MenuSectionOverlayHtml\n menuItems={MENU_ITEMS}\n selectedIndex={selectedMenuIndex}\n onModeSelect={handleMenuItemSelect}\n onSelectedIndexChange={setSelectedMenuIndex}\n onPlaySFX={audio.playSFX}\n width={\n // Compact menu width - narrower for better proportions\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(500, screenWidth * 0.6)\n : Math.min(550, screenWidth * 0.4)\n }\n height={layoutHeights.menuHeight}\n isMobile={isMobile}\n />\n </div>\n\n {/* Archetype Selection - Scrollable container */}\n <div\n style={{\n width: screenWidth < 768 ? \"100%\" : \"85%\",\n maxWidth: screenWidth < 768 ? \"100%\" : \"900px\",\n flex: 1,\n minHeight: 0,\n overflowY: \"auto\",\n overflowX: \"hidden\",\n }}\n data-testid=\"archetype-section-container\"\n >\n {useEnhancedArchetypeDisplay ? (\n <EnhancedArchetypeDisplay\n archetypes={archetypeData}\n selectedIndex={selectedArchetypeIndex}\n onArchetypeChange={handleArchetypeIndexChange}\n onPlaySFX={audio.playSFX}\n width={\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(700, screenWidth * 0.7)\n : Math.min(850, screenWidth * 0.55)\n }\n height={Math.max(layoutHeights.archetypeHeight - 40, 200)}\n isMobile={isMobile}\n allowDetailedView={screenWidth >= 768}\n />\n ) : (\n <ArchetypeDisplayOverlayHtml\n archetypes={archetypeData}\n selectedIndex={selectedArchetypeIndex}\n onArchetypeChange={handleArchetypeIndexChange}\n onPlaySFX={audio.playSFX}\n width={\n screenWidth < 768\n ? screenWidth * 0.9\n : screenWidth < 1024\n ? Math.min(700, screenWidth * 0.7)\n : Math.min(850, screenWidth * 0.55)\n }\n height={Math.max(layoutHeights.archetypeHeight - 40, 200)}\n isMobile={isMobile}\n />\n )}\n </div>\n </div>\n\n {/* Footer - Compact with all info */}\n <div\n style={{\n width: \"100%\",\n minHeight: `${layoutHeights.footerHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"1px\",\n background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${colors.footerBackground})`,\n borderTop: `1px solid ${colors.footerBorder}`,\n pointerEvents: \"auto\",\n paddingBottom: \"2px\",\n flexShrink: 0,\n }}\n data-testid=\"intro-footer\"\n >\n {/* Motto */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 3, 10)}px`,\n color: `#${theme.colors.ACCENT_CYAN.toString(16).padStart(6, \"0\")}`,\n fontFamily: theme.koreanTypography.fontFamily,\n fontStyle: \"italic\",\n textAlign: \"center\",\n }}\n data-testid=\"footer-motto\"\n >\n 흑괘의 길을 걸어라 - Walk the Path of the Black Trigram\n </div>\n {/* Open Source Link */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 4, 9)}px`,\n color: `#${theme.colors.ACCENT_BLUE.toString(16).padStart(6, \"0\")}`,\n textAlign: \"center\",\n cursor: \"pointer\",\n }}\n onClick={() =>\n window.open(\"https://github.com/Hack23/blacktrigram\", \"_blank\")\n }\n data-testid=\"footer-link\"\n >\n Open Source Korean Martial Arts Game by Hack23\n </div>\n {/* Version */}\n <div\n style={{\n fontSize: `${Math.max(getKoreanFontSize(\"SMALL\", screenWidth) - 5, 8)}px`,\n color: `#${theme.colors.ACCENT_BLUE.toString(16).padStart(6, \"0\")}`,\n textAlign: \"center\",\n cursor: \"pointer\",\n }}\n onClick={() =>\n window.open(\n `https://github.com/Hack23/blacktrigram/releases/tag/v${APP_VERSION}`,\n \"_blank\",\n )\n }\n data-testid=\"footer-version\"\n >\n v{APP_VERSION}\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default IntroScreen3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbilityListOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/AbilityListOverlayHtml.tsx"],"sourcesContent":["/**\n * AbilityList - Enhanced ability list component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays a list of special abilities with bilingual support\n * \n * Performance optimized with React.memo and useMemo\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 능력리스트\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../../utils/colorUtils\";\n\nexport interface Ability {\n readonly korean: string;\n readonly english: string;\n readonly description?: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\nexport interface AbilityListProps {\n readonly abilities: readonly string[] | readonly Ability[];\n readonly maxAbilities?: number; // Maximum abilities to show\n readonly color?: number; // Accent color for abilities\n readonly isMobile?: boolean;\n}\n\n/**\n * AbilityList component - Displays a list of special abilities\n * \n * Refactored to use useKoreanTheme for consistent Korean theming:\n * - Uses Korean typography configuration\n * - Applies Korean color palette\n * - Responsive sizing based on device type\n * - Memoized for optimal performance\n * \n * Used in archetype cards to show key techniques and skills\n * \n * @example\n * ```tsx\n * <AbilityList\n * abilities={[\n * { korean: \"급소격\", english: \"Vital Strike\" },\n * { korean: \"연격\", english: \"Chain Attack\" }\n * ]}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const AbilityList: React.FC<AbilityListProps> = React.memo(\n ({ abilities, maxAbilities = 3, color = KOREAN_COLORS.ACCENT_GOLD, isMobile = false }) => {\n // Use Korean theme hook for consistent styling\n const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n // Normalize abilities to consistent format\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n // TEMPORARY: This fallback violates PRIO 2 bilingual support guidelines.\n // All abilities should use the object format with Korean/English fields.\n // See: src/systems/types.ts PLAYER_ARCHETYPES_DATA for correct format.\n // Replace with proper bilingual objects as soon as translations are available.\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\n // Object format with Korean/English\n return {\n id: `ability-${index}`,\n english: ability.english,\n korean: ability.korean,\n description: ability.description,\n };\n }\n });\n }, [abilities, maxAbilities]);\n\n // Memoize color calculations with Korean theme\n const colors = useMemo(\n () => ({\n abilityBorder: hexToRgbaString(color, 0.5),\n abilityBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.7\n ),\n abilityText: hexColorToCSS(color),\n descriptionText: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }),\n [color]\n );\n\n // Responsive sizing using Korean theme utilities\n const fontSize = calculateResponsiveSize(isMobile ? 10 : 12);\n const descFontSize = calculateResponsiveSize(isMobile ? 8 : 10);\n const padding = isMobile ? `${calculateResponsiveSize(6)}px ${calculateResponsiveSize(10)}px` : `${calculateResponsiveSize(8)}px ${calculateResponsiveSize(12)}px`;\n\n if (normalizedAbilities.length === 0) {\n return null;\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(8)}px`,\n }}\n data-testid=\"ability-list\"\n >\n {/* Header with Korean typography */}\n <div\n style={{\n fontSize: `${calculateResponsiveSize(isMobile ? 12 : 14)}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n }}\n data-testid=\"ability-list-header\"\n >\n 특수 능력 | Special Abilities\n </div>\n\n {/* Ability items */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(6)}px`,\n }}\n >\n {normalizedAbilities.map((ability) => (\n <div\n key={ability.id}\n style={{\n padding,\n background: colors.abilityBackground,\n border: `1px solid ${colors.abilityBorder}`,\n borderRadius: \"4px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(4)}px`,\n }}\n data-testid={ability.id}\n >\n {/* Ability name with Korean typography */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-name`}\n >\n {ability.korean} | {ability.english}\n </div>\n\n {/* Ability description (if available) */}\n {ability.description && (\n <div\n style={{\n fontSize: `${descFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: fontFamily.KOREAN,\n color: colors.descriptionText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-description`}\n >\n {ability.description.korean}\n {ability.description.english && (\n <>\n <br />\n {ability.description.english}\n </>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n }\n);\n\nAbilityList.displayName = \"AbilityList\";\n\nexport default AbilityList;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,cAA0C,MAAM,MAC1D,EAAE,WAAW,eAAe,GAAG,QAAQ,cAAc,aAAa,WAAW,YAAY;CAExF,MAAM,EAAE,kBAAkB,yBAAyB,eAAe,eAAe;EAC/E,MAAM;EACN;EACD,CAAC;CAGF,MAAM,sBAAsB,cAAc;
|
|
1
|
+
{"version":3,"file":"AbilityListOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/AbilityListOverlayHtml.tsx"],"sourcesContent":["/**\n * AbilityList - Enhanced ability list component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays a list of special abilities with bilingual support\n * \n * Performance optimized with React.memo and useMemo\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 능력리스트\n */\n\nimport React, { useMemo } from \"react\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString, hexColorToCSS } from \"../../../../utils/colorUtils\";\n\nexport interface Ability {\n readonly korean: string;\n readonly english: string;\n readonly description?: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\nexport interface AbilityListProps {\n readonly abilities: readonly string[] | readonly Ability[];\n readonly maxAbilities?: number; // Maximum abilities to show\n readonly color?: number; // Accent color for abilities\n readonly isMobile?: boolean;\n}\n\n/**\n * AbilityList component - Displays a list of special abilities\n * \n * Refactored to use useKoreanTheme for consistent Korean theming:\n * - Uses Korean typography configuration\n * - Applies Korean color palette\n * - Responsive sizing based on device type\n * - Memoized for optimal performance\n * \n * Used in archetype cards to show key techniques and skills\n * \n * @example\n * ```tsx\n * <AbilityList\n * abilities={[\n * { korean: \"급소격\", english: \"Vital Strike\" },\n * { korean: \"연격\", english: \"Chain Attack\" }\n * ]}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const AbilityList: React.FC<AbilityListProps> = React.memo(\n ({ abilities, maxAbilities = 3, color = KOREAN_COLORS.ACCENT_GOLD, isMobile = false }) => {\n // Use Korean theme hook for consistent styling\n const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n // Normalize abilities to consistent format\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n // TEMPORARY: This fallback violates PRIO 2 bilingual support guidelines.\n // All abilities should use the object format with Korean/English fields.\n // See: src/systems/types.ts PLAYER_ARCHETYPES_DATA for correct format.\n // Replace with proper bilingual objects as soon as translations are available.\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\n // Object format with Korean/English\n return {\n id: `ability-${index}`,\n english: ability.english,\n korean: ability.korean,\n description: ability.description,\n };\n }\n });\n }, [abilities, maxAbilities]);\n\n // Memoize color calculations with Korean theme\n const colors = useMemo(\n () => ({\n abilityBorder: hexToRgbaString(color, 0.5),\n abilityBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.7\n ),\n abilityText: hexColorToCSS(color),\n descriptionText: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }),\n [color]\n );\n\n // Responsive sizing using Korean theme utilities\n const fontSize = calculateResponsiveSize(isMobile ? 10 : 12);\n const descFontSize = calculateResponsiveSize(isMobile ? 8 : 10);\n const padding = isMobile ? `${calculateResponsiveSize(6)}px ${calculateResponsiveSize(10)}px` : `${calculateResponsiveSize(8)}px ${calculateResponsiveSize(12)}px`;\n\n if (normalizedAbilities.length === 0) {\n return null;\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(8)}px`,\n }}\n data-testid=\"ability-list\"\n >\n {/* Header with Korean typography */}\n <div\n style={{\n fontSize: `${calculateResponsiveSize(isMobile ? 12 : 14)}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n }}\n data-testid=\"ability-list-header\"\n >\n 특수 능력 | Special Abilities\n </div>\n\n {/* Ability items */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(6)}px`,\n }}\n >\n {normalizedAbilities.map((ability) => (\n <div\n key={ability.id}\n style={{\n padding,\n background: colors.abilityBackground,\n border: `1px solid ${colors.abilityBorder}`,\n borderRadius: \"4px\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${calculateResponsiveSize(4)}px`,\n }}\n data-testid={ability.id}\n >\n {/* Ability name with Korean typography */}\n <div\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: fontFamily.KOREAN,\n color: colors.abilityText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-name`}\n >\n {ability.korean} | {ability.english}\n </div>\n\n {/* Ability description (if available) */}\n {ability.description && (\n <div\n style={{\n fontSize: `${descFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: fontFamily.KOREAN,\n color: colors.descriptionText,\n // Apply Korean typography optimization\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid={`${ability.id}-description`}\n >\n {ability.description.korean}\n {ability.description.english && (\n <>\n <br />\n {ability.description.english}\n </>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n );\n }\n);\n\nAbilityList.displayName = \"AbilityList\";\n\nexport default AbilityList;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,cAA0C,MAAM,MAC1D,EAAE,WAAW,eAAe,GAAG,QAAQ,cAAc,aAAa,WAAW,YAAY;CAExF,MAAM,EAAE,kBAAkB,yBAAyB,eAAe,eAAe;EAC/E,MAAM;EACN;EACD,CAAC;CAGF,MAAM,sBAAsB,cAAc;EACxC,OAAO,UAAU,MAAM,GAAG,aAAa,CAAC,KAAK,SAAS,UAAU;GAC9D,IAAI,OAAO,YAAY,UAKrB,OAAO;IACL,IAAI,WAAW;IACf,SAAS;IACT,QAAQ;IACT;QAGD,OAAO;IACL,IAAI,WAAW;IACf,SAAS,QAAQ;IACjB,QAAQ,QAAQ;IAChB,aAAa,QAAQ;IACtB;IAEH;IACD,CAAC,WAAW,aAAa,CAAC;CAG7B,MAAM,SAAS,eACN;EACL,eAAe,gBAAgB,OAAO,GAAI;EAC1C,mBAAmB,gBACjB,cAAc,sBACd,GACD;EACD,aAAa,cAAc,MAAM;EACjC,iBAAiB,cAAc,cAAc,eAAe;EAC7D,GACD,CAAC,MAAM,CACR;CAGD,MAAM,WAAW,wBAAwB,WAAW,KAAK,GAAG;CAC5D,MAAM,eAAe,wBAAwB,WAAW,IAAI,GAAG;CAC/D,MAAM,UAAU,WAAW,GAAG,wBAAwB,EAAE,CAAC,KAAK,wBAAwB,GAAG,CAAC,MAAM,GAAG,wBAAwB,EAAE,CAAC,KAAK,wBAAwB,GAAG,CAAC;CAE/J,IAAI,oBAAoB,WAAW,GACjC,OAAO;CAGT,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,SAAS;GACT,eAAe;GACf,KAAK,GAAG,wBAAwB,EAAE,CAAC;GACpC;EACD,eAAY;YAPd,CAUE,oBAAC,OAAD;GACE,OAAO;IACL,UAAU,GAAG,wBAAwB,WAAW,KAAK,GAAG,CAAC;IACzD,YAAY;IACZ,YAAY,WAAW;IACvB,OAAO,OAAO;IAEd,YAAY,iBAAiB;IAC7B,eAAe,iBAAiB;IACjC;GACD,eAAY;aACb;GAEK,CAAA,EAGN,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK,GAAG,wBAAwB,EAAE,CAAC;IACpC;aAEA,oBAAoB,KAAK,YACxB,qBAAC,OAAD;IAEE,OAAO;KACL;KACA,YAAY,OAAO;KACnB,QAAQ,aAAa,OAAO;KAC5B,cAAc;KACd,SAAS;KACT,eAAe;KACf,KAAK,GAAG,wBAAwB,EAAE,CAAC;KACpC;IACD,eAAa,QAAQ;cAXvB,CAcE,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,SAAS;MACtB,YAAY;MACZ,YAAY,WAAW;MACvB,OAAO,OAAO;MAEd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;MAC7B;KACD,eAAa,GAAG,QAAQ,GAAG;eAX7B;MAaG,QAAQ;MAAO;MAAI,QAAQ;MACxB;QAGL,QAAQ,eACP,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,aAAa;MAC1B,WAAW;MACX,YAAY,WAAW;MACvB,OAAO,OAAO;MAEd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;MAC7B;KACD,eAAa,GAAG,QAAQ,GAAG;eAX7B,CAaG,QAAQ,YAAY,QACpB,QAAQ,YAAY,WACnB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,MAAD,EAAM,CAAA,EACL,QAAQ,YAAY,QACpB,EAAA,CAAA,CAED;OAEJ;MArDC,QAAQ,GAqDT,CACN;GACE,CAAA,CACF;;EAGX;AAED,YAAY,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArchetypeCardGridOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/ArchetypeCardGridOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { ArchetypeCard, ArchetypeCardData } from \"./ArchetypeCardOverlayHtml\";\n\nexport interface ArchetypeCardGridProps {\n readonly archetypes: readonly ArchetypeCardData[];\n readonly selectedArchetype: PlayerArchetype;\n readonly onArchetypeChange: (archetype: PlayerArchetype) => void;\n readonly onArchetypeConfirm?: (archetype: PlayerArchetype) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean;\n}\n\n/**\n * ArchetypeCardGrid - Grid layout for displaying multiple archetype cards\n * Provides an enhanced selection interface with detailed preview cards\n */\nexport const ArchetypeCardGrid: React.FC<ArchetypeCardGridProps> = React.memo(\n ({\n archetypes,\n selectedArchetype,\n onArchetypeChange,\n onArchetypeConfirm,\n onPlaySFX,\n width = 900,\n height = 600,\n isMobile: _isMobile = false, // Kept for interface compatibility, layout uses width\n }) => {\n // Note: _isMobile intentionally unused - layout sizing uses width-based checks\n void _isMobile;\n\n // Use width for layout sizing decisions\n const isSmallScreen = width < 768;\n\n // Find selected archetype index\n const selectedIndex = useMemo(() => {\n return archetypes.findIndex((a) => a.archetype === selectedArchetype);\n }, [archetypes, selectedArchetype]);\n\n // Calculate card dimensions based on container and screen size\n const cardWidth = useMemo(() => {\n if (isSmallScreen) return Math.min(280, width - 40);\n const isLargeContainer = width >= 1100;\n return isLargeContainer ? 340 : 380;\n }, [isSmallScreen, width]);\n\n // Memoize colors\n const colors = useMemo(\n () => ({\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7),\n headerColor: hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD),\n }),\n [],\n );\n\n // Handle card selection\n const handleCardSelect = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeChange(archetype);\n onPlaySFX(\"menu_hover\");\n },\n [onArchetypeChange, onPlaySFX],\n );\n\n // Handle card confirmation\n const handleCardConfirm = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeConfirm?.(archetype);\n onPlaySFX(\"menu_select\");\n },\n [onArchetypeConfirm, onPlaySFX],\n );\n\n // Handle keyboard navigation (scoped to container)\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n const newIndex =\n selectedIndex === 0 ? archetypes.length - 1 : selectedIndex - 1;\n const newArchetype = archetypes[newIndex].archetype;\n handleCardSelect(newArchetype);\n } else if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n event.preventDefault();\n const newIndex = (selectedIndex + 1) % archetypes.length;\n const newArchetype = archetypes[newIndex].archetype;\n handleCardSelect(newArchetype);\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n if (onArchetypeConfirm) {\n handleCardConfirm(selectedArchetype);\n }\n }\n },\n [\n selectedIndex,\n archetypes,\n selectedArchetype,\n handleCardSelect,\n handleCardConfirm,\n onArchetypeConfirm,\n ],\n );\n\n // Calculate grid layout\n const columnsCount = isSmallScreen ? 1 : width >= 1400 ? 3 : 2;\n const gap = isSmallScreen ? 16 : 20;\n\n // Custom focus style for keyboard navigation\n const [isFocused, setIsFocused] = React.useState(false);\n\n return (\n <div\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n style={{\n width: `${width}px`,\n minHeight: `${height}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: `${gap}px`,\n background: colors.background,\n borderRadius: \"12px\",\n border: `2px solid ${colors.border}`,\n padding: isSmallScreen ? \"16px\" : \"24px\",\n overflow: \"auto\",\n maxHeight: `${height}px`,\n outline: isFocused ? `3px solid ${colors.headerColor}` : \"none\",\n outlineOffset: \"2px\",\n }}\n data-testid=\"archetype-card-grid\"\n role=\"region\"\n aria-label=\"Archetype selection grid\"\n >\n {/* Header */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"8px\",\n marginBottom: `${gap / 2}px`,\n }}\n >\n <h2\n style={{\n fontSize: isSmallScreen ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: colors.headerColor,\n margin: 0,\n textAlign: \"center\",\n }}\n data-testid=\"grid-header\"\n >\n 원형 선택 | Select Archetype\n </h2>\n\n <div\n style={{\n fontSize: isSmallScreen ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n data-testid=\"grid-hint\"\n >\n {isSmallScreen\n ? \"카드를 탭하여 선택\"\n : \"화살표 키로 탐색, 엔터로 확인 | Arrow keys to navigate, Enter to confirm\"}\n </div>\n </div>\n\n {/* Card Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${columnsCount}, 1fr)`,\n gap: `${gap}px`,\n width: \"100%\",\n justifyItems: \"center\",\n }}\n data-testid=\"card-grid-container\"\n >\n {archetypes.map((archetype) => (\n <ArchetypeCard\n key={archetype.id}\n data={archetype}\n isSelected={archetype.archetype === selectedArchetype}\n onSelect={() => handleCardSelect(archetype.archetype)}\n onConfirm={\n onArchetypeConfirm\n ? () => handleCardConfirm(archetype.archetype)\n : undefined\n }\n isMobile={isSmallScreen}\n width={cardWidth}\n showSelectButton={!!onArchetypeConfirm}\n />\n ))}\n </div>\n\n {/* Footer navigation hint */}\n {!isSmallScreen && (\n <div\n style={{\n marginTop: `${gap}px`,\n fontSize: \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n data-testid=\"grid-footer\"\n >\n ← → 또는 ↑ ↓ 키로 원형 변경 | Use ← → or ↑ ↓ keys to change\n archetype\n </div>\n )}\n </div>\n );\n },\n);\n\nArchetypeCardGrid.displayName = \"ArchetypeCardGrid\";\n\nexport default ArchetypeCardGrid;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ArchetypeCardGridOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/ArchetypeCardGridOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { ArchetypeCard, ArchetypeCardData } from \"./ArchetypeCardOverlayHtml\";\n\nexport interface ArchetypeCardGridProps {\n readonly archetypes: readonly ArchetypeCardData[];\n readonly selectedArchetype: PlayerArchetype;\n readonly onArchetypeChange: (archetype: PlayerArchetype) => void;\n readonly onArchetypeConfirm?: (archetype: PlayerArchetype) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean;\n}\n\n/**\n * ArchetypeCardGrid - Grid layout for displaying multiple archetype cards\n * Provides an enhanced selection interface with detailed preview cards\n */\nexport const ArchetypeCardGrid: React.FC<ArchetypeCardGridProps> = React.memo(\n ({\n archetypes,\n selectedArchetype,\n onArchetypeChange,\n onArchetypeConfirm,\n onPlaySFX,\n width = 900,\n height = 600,\n isMobile: _isMobile = false, // Kept for interface compatibility, layout uses width\n }) => {\n // Note: _isMobile intentionally unused - layout sizing uses width-based checks\n void _isMobile;\n\n // Use width for layout sizing decisions\n const isSmallScreen = width < 768;\n\n // Find selected archetype index\n const selectedIndex = useMemo(() => {\n return archetypes.findIndex((a) => a.archetype === selectedArchetype);\n }, [archetypes, selectedArchetype]);\n\n // Calculate card dimensions based on container and screen size\n const cardWidth = useMemo(() => {\n if (isSmallScreen) return Math.min(280, width - 40);\n const isLargeContainer = width >= 1100;\n return isLargeContainer ? 340 : 380;\n }, [isSmallScreen, width]);\n\n // Memoize colors\n const colors = useMemo(\n () => ({\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7),\n headerColor: hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD),\n }),\n [],\n );\n\n // Handle card selection\n const handleCardSelect = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeChange(archetype);\n onPlaySFX(\"menu_hover\");\n },\n [onArchetypeChange, onPlaySFX],\n );\n\n // Handle card confirmation\n const handleCardConfirm = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeConfirm?.(archetype);\n onPlaySFX(\"menu_select\");\n },\n [onArchetypeConfirm, onPlaySFX],\n );\n\n // Handle keyboard navigation (scoped to container)\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n const newIndex =\n selectedIndex === 0 ? archetypes.length - 1 : selectedIndex - 1;\n const newArchetype = archetypes[newIndex].archetype;\n handleCardSelect(newArchetype);\n } else if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n event.preventDefault();\n const newIndex = (selectedIndex + 1) % archetypes.length;\n const newArchetype = archetypes[newIndex].archetype;\n handleCardSelect(newArchetype);\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n if (onArchetypeConfirm) {\n handleCardConfirm(selectedArchetype);\n }\n }\n },\n [\n selectedIndex,\n archetypes,\n selectedArchetype,\n handleCardSelect,\n handleCardConfirm,\n onArchetypeConfirm,\n ],\n );\n\n // Calculate grid layout\n const columnsCount = isSmallScreen ? 1 : width >= 1400 ? 3 : 2;\n const gap = isSmallScreen ? 16 : 20;\n\n // Custom focus style for keyboard navigation\n const [isFocused, setIsFocused] = React.useState(false);\n\n return (\n <div\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n style={{\n width: `${width}px`,\n minHeight: `${height}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: `${gap}px`,\n background: colors.background,\n borderRadius: \"12px\",\n border: `2px solid ${colors.border}`,\n padding: isSmallScreen ? \"16px\" : \"24px\",\n overflow: \"auto\",\n maxHeight: `${height}px`,\n outline: isFocused ? `3px solid ${colors.headerColor}` : \"none\",\n outlineOffset: \"2px\",\n }}\n data-testid=\"archetype-card-grid\"\n role=\"region\"\n aria-label=\"Archetype selection grid\"\n >\n {/* Header */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"8px\",\n marginBottom: `${gap / 2}px`,\n }}\n >\n <h2\n style={{\n fontSize: isSmallScreen ? \"20px\" : \"28px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: colors.headerColor,\n margin: 0,\n textAlign: \"center\",\n }}\n data-testid=\"grid-header\"\n >\n 원형 선택 | Select Archetype\n </h2>\n\n <div\n style={{\n fontSize: isSmallScreen ? \"12px\" : \"14px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n data-testid=\"grid-hint\"\n >\n {isSmallScreen\n ? \"카드를 탭하여 선택\"\n : \"화살표 키로 탐색, 엔터로 확인 | Arrow keys to navigate, Enter to confirm\"}\n </div>\n </div>\n\n {/* Card Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${columnsCount}, 1fr)`,\n gap: `${gap}px`,\n width: \"100%\",\n justifyItems: \"center\",\n }}\n data-testid=\"card-grid-container\"\n >\n {archetypes.map((archetype) => (\n <ArchetypeCard\n key={archetype.id}\n data={archetype}\n isSelected={archetype.archetype === selectedArchetype}\n onSelect={() => handleCardSelect(archetype.archetype)}\n onConfirm={\n onArchetypeConfirm\n ? () => handleCardConfirm(archetype.archetype)\n : undefined\n }\n isMobile={isSmallScreen}\n width={cardWidth}\n showSelectButton={!!onArchetypeConfirm}\n />\n ))}\n </div>\n\n {/* Footer navigation hint */}\n {!isSmallScreen && (\n <div\n style={{\n marginTop: `${gap}px`,\n fontSize: \"12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n data-testid=\"grid-footer\"\n >\n ← → 또는 ↑ ↓ 키로 원형 변경 | Use ← → or ↑ ↓ keys to change\n archetype\n </div>\n )}\n </div>\n );\n },\n);\n\nArchetypeCardGrid.displayName = \"ArchetypeCardGrid\";\n\nexport default ArchetypeCardGrid;\n"],"mappings":"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"}
|