blacktrigram 0.7.68 → 0.7.69
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/assets/index.css +987 -987
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js +2 -2
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js +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/components/screens/combat/CombatScreen3D.js +3 -3
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.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/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.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/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.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/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 +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.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/TrainingDummy3D.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/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.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/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.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/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/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/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.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/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +2 -2
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/index.js +2 -2
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js +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 +11 -209
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.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/InjuryTracker.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.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/GrappleSystem.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/index.js +2 -2
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js +1 -1
- package/lib/systems/physics/CollisionDetection.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/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/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.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/constants/designSystem.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.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/htmlOverlayHelpers.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/safeAreaUtils.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +5 -5
|
@@ -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 const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\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 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 useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.3;\n }\n\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 reusablePosition.set(0, Math.sin(time * 0.8) * 0.5, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n if (ringsRef.current) {\n ringsRef.current.rotation.x = time * 0.5;\n ringsRef.current.rotation.z = time * 0.3;\n }\n\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 useEffect(() => {\n const group = groupRef.current;\n const particles = particlesRef.current;\n const rings = ringsRef.current;\n const symbols = symbolsRef.current;\n\n return () => {\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 if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\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 const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\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 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 useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.3;\n }\n\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 reusablePosition.set(0, Math.sin(time * 0.8) * 0.5, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n if (ringsRef.current) {\n ringsRef.current.rotation.x = time * 0.5;\n ringsRef.current.rotation.z = time * 0.3;\n }\n\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 useEffect(() => {\n const group = groupRef.current;\n const particles = particlesRef.current;\n const rings = ringsRef.current;\n const symbols = symbolsRef.current;\n\n return () => {\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 if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\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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"}
|
|
@@ -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.69";
|
|
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":["import { 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\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\nconst getArchetypeIndex = (archetype: PlayerArchetype): number => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys.indexOf(archetype);\n};\n\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\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 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 const screenWidth = propWidth ?? (width || 1200);\n const screenHeight = propHeight ?? (height || 800);\n\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 useEffect(() => {\n setCurrentArchetype(selectedArchetype);\n setSelectedArchetypeIndex(getArchetypeIndex(selectedArchetype));\n }, [selectedArchetype]);\n\n const handleMenuItemSelect = useCallback(\n (mode: GameMode) => {\n onMenuSelect(mode, currentArchetype);\n },\n [onMenuSelect, currentArchetype],\n );\n\n const handleArchetypeIndexChange = useCallback(\n (index: number) => {\n const newArchetype = getArchetypeFromIndex(index);\n setSelectedArchetypeIndex(index);\n setCurrentArchetype(newArchetype);\n onArchetypeSelect?.(newArchetype);\n\n if (audio.isAudioReady) {\n audio.playSFX(\"menu_hover\");\n\n const archetypeAssets = getArchetypeAssets(newArchetype);\n audio.stopMusic();\n audio.playMusic(archetypeAssets.themeId);\n }\n },\n [onArchetypeSelect, audio],\n );\n\n useEffect(() => {\n const startMusic = () => {\n if (audio.isAudioReady && !introMusicStarted.current) {\n introMusicStarted.current = true;\n audio.playMusic(\"intro_theme\");\n }\n };\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 if (audio.isInitialized) {\n audio.stopMusic();\n }\n };\n }, [audio]);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\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 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 const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\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 const performanceSettings = useMemo(() => {\n return getPerformanceSettings(screenWidth, isMobile);\n }, [screenWidth, isMobile]);\n\n const screenSize = useMemo(() => getScreenSize(screenWidth), [screenWidth]);\n\n const logoSize = useMemo(() => {\n const minDim = Math.min(screenWidth, screenHeight);\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 return Math.min(minDim * logoScale, screenSize === \"xlarge\" ? 250 : 300);\n }, [screenWidth, screenHeight, screenSize]);\n\n const layoutHeights = useMemo(() => {\n const availableHeight = screenHeight;\n\n const titleHeight = screenWidth < 768 ? 32 : 38;\n\n const trigramHeight = screenWidth < 768 ? 16 : 22;\n const logoAreaHeight = logoSize + trigramHeight;\n\n const footerHeight = Math.max(availableHeight * 0.05, 48);\n\n const contentHeight =\n availableHeight - titleHeight - logoAreaHeight - footerHeight;\n\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 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 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
|
+
{"version":3,"file":"IntroScreen3D.js","names":[],"sources":["../../../../src/components/screens/intro/IntroScreen3D.tsx"],"sourcesContent":["import { 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\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\nconst getArchetypeIndex = (archetype: PlayerArchetype): number => {\n const archetypeKeys = Object.keys(\n PLAYER_ARCHETYPES_DATA,\n ) as PlayerArchetype[];\n return archetypeKeys.indexOf(archetype);\n};\n\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\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 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 const screenWidth = propWidth ?? (width || 1200);\n const screenHeight = propHeight ?? (height || 800);\n\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 useEffect(() => {\n setCurrentArchetype(selectedArchetype);\n setSelectedArchetypeIndex(getArchetypeIndex(selectedArchetype));\n }, [selectedArchetype]);\n\n const handleMenuItemSelect = useCallback(\n (mode: GameMode) => {\n onMenuSelect(mode, currentArchetype);\n },\n [onMenuSelect, currentArchetype],\n );\n\n const handleArchetypeIndexChange = useCallback(\n (index: number) => {\n const newArchetype = getArchetypeFromIndex(index);\n setSelectedArchetypeIndex(index);\n setCurrentArchetype(newArchetype);\n onArchetypeSelect?.(newArchetype);\n\n if (audio.isAudioReady) {\n audio.playSFX(\"menu_hover\");\n\n const archetypeAssets = getArchetypeAssets(newArchetype);\n audio.stopMusic();\n audio.playMusic(archetypeAssets.themeId);\n }\n },\n [onArchetypeSelect, audio],\n );\n\n useEffect(() => {\n const startMusic = () => {\n if (audio.isAudioReady && !introMusicStarted.current) {\n introMusicStarted.current = true;\n audio.playMusic(\"intro_theme\");\n }\n };\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 if (audio.isInitialized) {\n audio.stopMusic();\n }\n };\n }, [audio]);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\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 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 const isMobile = useMemo(() => shouldUseMobileControls(), []);\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\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 const performanceSettings = useMemo(() => {\n return getPerformanceSettings(screenWidth, isMobile);\n }, [screenWidth, isMobile]);\n\n const screenSize = useMemo(() => getScreenSize(screenWidth), [screenWidth]);\n\n const logoSize = useMemo(() => {\n const minDim = Math.min(screenWidth, screenHeight);\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 return Math.min(minDim * logoScale, screenSize === \"xlarge\" ? 250 : 300);\n }, [screenWidth, screenHeight, screenSize]);\n\n const layoutHeights = useMemo(() => {\n const availableHeight = screenHeight;\n\n const titleHeight = screenWidth < 768 ? 32 : 38;\n\n const trigramHeight = screenWidth < 768 ? 16 : 22;\n const logoAreaHeight = logoSize + trigramHeight;\n\n const footerHeight = Math.max(availableHeight * 0.05, 48);\n\n const contentHeight =\n availableHeight - titleHeight - logoAreaHeight - footerHeight;\n\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 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 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 const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\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 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 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 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 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 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;CACxF,MAAM,EAAE,kBAAkB,yBAAyB,eAAe,eAAe;EAC/E,MAAM;EACN;CACF,CAAC;CAED,MAAM,sBAAsB,cAAc;EACxC,OAAO,UAAU,MAAM,GAAG,YAAY,
|
|
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 const { koreanTypography, calculateResponsiveSize, fontFamily } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const normalizedAbilities = useMemo(() => {\n return abilities.slice(0, maxAbilities).map((ability, index) => {\n if (typeof ability === \"string\") {\n return {\n id: `ability-${index}`,\n english: ability,\n korean: ability, // TEMPORARY: English used as Korean fallback\n };\n } else {\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 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 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 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 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 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;CACxF,MAAM,EAAE,kBAAkB,yBAAyB,eAAe,eAAe;EAC/E,MAAM;EACN;CACF,CAAC;CAED,MAAM,sBAAsB,cAAc;EACxC,OAAO,UAAU,MAAM,GAAG,YAAY,CAAC,CAAC,KAAK,SAAS,UAAU;GAC9D,IAAI,OAAO,YAAY,UACrB,OAAO;IACL,IAAI,WAAW;IACf,SAAS;IACT,QAAQ;GACV;QAEA,OAAO;IACL,IAAI,WAAW;IACf,SAAS,QAAQ;IACjB,QAAQ,QAAQ;IAChB,aAAa,QAAQ;GACvB;EAEJ,CAAC;CACH,GAAG,CAAC,WAAW,YAAY,CAAC;CAE5B,MAAM,SAAS,eACN;EACL,eAAe,gBAAgB,OAAO,EAAG;EACzC,mBAAmB,gBACjB,cAAc,sBACd,EACF;EACA,aAAa,cAAc,KAAK;EAChC,iBAAiB,cAAc,cAAc,cAAc;CAC7D,IACA,CAAC,KAAK,CACR;CAEA,MAAM,WAAW,wBAAwB,WAAW,KAAK,EAAE;CAC3D,MAAM,eAAe,wBAAwB,WAAW,IAAI,EAAE;CAC9D,MAAM,UAAU,WAAW,GAAG,wBAAwB,CAAC,EAAE,KAAK,wBAAwB,EAAE,EAAE,MAAM,GAAG,wBAAwB,CAAC,EAAE,KAAK,wBAAwB,EAAE,EAAE;CAE/J,IAAI,oBAAoB,WAAW,GACjC,OAAO;CAGT,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,SAAS;GACT,eAAe;GACf,KAAK,GAAG,wBAAwB,CAAC,EAAE;EACrC;EACA,eAAY;YAPd,CAUE,oBAAC,OAAD;GACE,OAAO;IACL,UAAU,GAAG,wBAAwB,WAAW,KAAK,EAAE,EAAE;IACzD,YAAY;IACZ,YAAY,WAAW;IACvB,OAAO,OAAO;IACd,YAAY,iBAAiB;IAC7B,eAAe,iBAAiB;GAClC;GACA,eAAY;aACb;EAEI,CAAA,GAGL,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK,GAAG,wBAAwB,CAAC,EAAE;GACrC;aAEC,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,CAAC,EAAE;IACrC;IACA,eAAa,QAAQ;cAXvB,CAcE,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,SAAS;MACtB,YAAY;MACZ,YAAY,WAAW;MACvB,OAAO,OAAO;MACd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;KAC9B;KACA,eAAa,GAAG,QAAQ,GAAG;eAV7B;MAYG,QAAQ;MAAO;MAAI,QAAQ;KACzB;QAGJ,QAAQ,eACP,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,aAAa;MAC1B,WAAW;MACX,YAAY,WAAW;MACvB,OAAO,OAAO;MACd,YAAY,iBAAiB;MAC7B,eAAe,iBAAiB;MAChC,WAAW,iBAAiB;KAC9B;KACA,eAAa,GAAG,QAAQ,GAAG;eAV7B,CAYG,QAAQ,YAAY,QACpB,QAAQ,YAAY,WACnB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,MAAD,CAAK,CAAA,GACJ,QAAQ,YAAY,OACrB,EAAA,CAAA,CAED;MAEJ;MAnDE,QAAQ,EAmDV,CACN;EACE,CAAA,CACF;;AAET,CACF;AAEA,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 void _isMobile;\n\n const isSmallScreen = width < 768;\n\n const selectedIndex = useMemo(() => {\n return archetypes.findIndex((a) => a.archetype === selectedArchetype);\n }, [archetypes, selectedArchetype]);\n\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 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 const handleCardSelect = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeChange(archetype);\n onPlaySFX(\"menu_hover\");\n },\n [onArchetypeChange, onPlaySFX],\n );\n\n const handleCardConfirm = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeConfirm?.(archetype);\n onPlaySFX(\"menu_select\");\n },\n [onArchetypeConfirm, onPlaySFX],\n );\n\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 const columnsCount = isSmallScreen ? 1 : width >= 1400 ? 3 : 2;\n const gap = isSmallScreen ? 16 : 20;\n\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 void _isMobile;\n\n const isSmallScreen = width < 768;\n\n const selectedIndex = useMemo(() => {\n return archetypes.findIndex((a) => a.archetype === selectedArchetype);\n }, [archetypes, selectedArchetype]);\n\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 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 const handleCardSelect = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeChange(archetype);\n onPlaySFX(\"menu_hover\");\n },\n [onArchetypeChange, onPlaySFX],\n );\n\n const handleCardConfirm = useCallback(\n (archetype: PlayerArchetype) => {\n onArchetypeConfirm?.(archetype);\n onPlaySFX(\"menu_select\");\n },\n [onArchetypeConfirm, onPlaySFX],\n );\n\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 const columnsCount = isSmallScreen ? 1 : width >= 1400 ? 3 : 2;\n const gap = isSmallScreen ? 16 : 20;\n\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArchetypeDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/ArchetypeDisplayOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from \"react\";\nimport {\n FALLBACK_ARCHETYPE_IMAGE,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getEnhancedKoreanOverlayStyles } from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getKoreanFontOptimization,\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport { StatBar } from \"./StatBarOverlayHtml\";\n\nexport interface ArchetypeDataShape {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly description: string;\n readonly color: number;\n readonly textureKey: string;\n readonly stats: {\n readonly attackPower: number;\n readonly defense: number;\n readonly speed: number;\n readonly technique: number;\n };\n readonly philosophy: {\n readonly korean: string;\n readonly english: string;\n };\n readonly specialAbilities?: readonly string[]; // Optional special abilities\n}\n\nexport interface ArchetypeDisplayOverlayHtmlProps {\n readonly archetypes: readonly ArchetypeDataShape[];\n readonly selectedIndex: number;\n readonly onArchetypeChange: (index: number) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean;\n}\n\n/**\n * HTML-based ArchetypeDisplay component for Three.js integration\n */\nexport const ArchetypeDisplayOverlayHtml: React.FC<ArchetypeDisplayOverlayHtmlProps> =\n React.memo(\n ({\n archetypes,\n selectedIndex,\n onArchetypeChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile: _isMobile = false, // Kept for interface compatibility, layout uses width\n }) => {\n void _isMobile;\n const selectedArchetype = archetypes[selectedIndex];\n\n const isSmallScreen = width < 768;\n const isLargeContainer = width >= 1100;\n const archImageWidth = isSmallScreen ? 140 : isLargeContainer ? 120 : 180;\n const archImageHeight = isSmallScreen\n ? 200\n : isLargeContainer\n ? 170\n : 260;\n const containerPadding = isSmallScreen ? 20 : isLargeContainer ? 12 : 20;\n const contentGap = isSmallScreen ? 10 : isLargeContainer ? 8 : 16;\n const infoGap = isSmallScreen ? 8 : isLargeContainer ? 6 : 12;\n const titleFontSize = isSmallScreen ? 14 : isLargeContainer ? 14 : 18;\n const philosophyFontSize = isSmallScreen\n ? 10\n : isLargeContainer\n ? 10\n : 12;\n const statBarHeight = isSmallScreen ? 10 : isLargeContainer ? 10 : 12;\n\n const handlePrevious = useCallback(() => {\n const newIndex =\n selectedIndex === 0 ? archetypes.length - 1 : selectedIndex - 1;\n onArchetypeChange(newIndex);\n onPlaySFX(\"menu_hover\");\n }, [selectedIndex, archetypes.length, onArchetypeChange, onPlaySFX]);\n\n const handleNext = useCallback(() => {\n const newIndex = (selectedIndex + 1) % archetypes.length;\n onArchetypeChange(newIndex);\n onPlaySFX(\"menu_hover\");\n }, [selectedIndex, archetypes.length, onArchetypeChange, onPlaySFX]);\n\n const combatStats = useMemo(() => {\n const maxStatValue = 100;\n return [\n {\n korean: \"공격\",\n english: \"Attack\",\n value: selectedArchetype.stats.attackPower / maxStatValue,\n rawValue: selectedArchetype.stats.attackPower,\n },\n {\n korean: \"방어\",\n english: \"Defense\",\n value: selectedArchetype.stats.defense / maxStatValue,\n rawValue: selectedArchetype.stats.defense,\n },\n {\n korean: \"속도\",\n english: \"Speed\",\n value: selectedArchetype.stats.speed / maxStatValue,\n rawValue: selectedArchetype.stats.speed,\n },\n {\n korean: \"기술\",\n english: \"Technique\",\n value: selectedArchetype.stats.technique / maxStatValue,\n rawValue: selectedArchetype.stats.technique,\n },\n ];\n }, [selectedArchetype.stats]);\n\n const colors = useMemo(\n () => ({\n archetypeColor: `#${selectedArchetype.color\n .toString(16)\n .padStart(6, \"0\")}`,\n titleGold: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n statsBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.9,\n ),\n statsBorder: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.5),\n statBarBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n statBarFill: hexToRgbaString(selectedArchetype.color, 0.9),\n }),\n [selectedArchetype.color],\n );\n\n const containerStyle = useMemo(\n () => ({\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.95,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: false,\n depthLayers: 3,\n }),\n width: `${width}px`,\n height: `${height}px`,\n display: \"flex\",\n flexDirection: \"row\" as const,\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n gap: `${contentGap}px`,\n border: `2px solid ${colors.archetypeColor}`,\n padding: `${containerPadding}px`,\n position: \"relative\" as const,\n overflow: \"hidden\" as const,\n }),\n [width, height, contentGap, containerPadding, colors.archetypeColor],\n );\n\n const archetypeImagePath = useMemo(() => {\n return `/assets/visual/archetypes/${selectedArchetype.textureKey}.png`;\n }, [selectedArchetype.textureKey]);\n\n return (\n <div style={containerStyle} data-testid=\"archetype-display-container\">\n {/* Left Side - Character Image and Navigation */}\n <div\n style={{\n width: `${archImageWidth + 40}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: `${infoGap}px`,\n flexShrink: 0,\n }}\n data-testid=\"archetype-image-section\"\n >\n {/* Character Image */}\n <div\n style={{\n width: `${archImageWidth + 20}px`,\n height: `${archImageHeight + 20}px`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n position: \"relative\",\n background: `radial-gradient(circle, ${colors.archetypeColor}26, transparent)`,\n borderRadius: \"4px\",\n border: `2px solid ${colors.archetypeColor}`,\n }}\n data-testid=\"archetype-image-container\"\n >\n <img\n src={archetypeImagePath}\n alt={`${selectedArchetype.korean} - ${selectedArchetype.english}`}\n style={{\n width: `${archImageWidth}px`,\n height: `${archImageHeight}px`,\n objectFit: \"contain\",\n cursor: \"pointer\",\n }}\n onClick={handleNext}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleNext();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-label={`${selectedArchetype.korean} ${selectedArchetype.english} - Click or press Enter to cycle to next archetype`}\n data-testid=\"archetype-image\"\n onError={(e) => {\n const target = e.currentTarget as HTMLImageElement;\n if (!target.src.endsWith(FALLBACK_ARCHETYPE_IMAGE)) {\n target.src = FALLBACK_ARCHETYPE_IMAGE;\n target.alt = `${selectedArchetype.korean} (image unavailable)`;\n }\n }}\n />\n </div>\n\n {/* Navigation Buttons */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: \"8px\",\n width: \"100%\",\n }}\n data-testid=\"archetype-navigation\"\n >\n <button\n onClick={handlePrevious}\n aria-label=\"Previous archetype\"\n style={{\n flex: 1,\n height: \"30px\",\n fontSize: \"14px\",\n fontWeight: \"bold\",\n color: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n background: colors.statsBackground,\n border: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.7,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n data-testid=\"prev-archetype-button\"\n >\n ◀\n </button>\n <button\n onClick={handleNext}\n aria-label=\"Next archetype\"\n style={{\n flex: 1,\n height: \"30px\",\n fontSize: \"14px\",\n fontWeight: \"bold\",\n color: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n background: colors.statsBackground,\n border: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.7,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n data-testid=\"next-archetype-button\"\n >\n ▶\n </button>\n </div>\n </div>\n\n {/* Right Side - Archetype Information */}\n <div\n style={{\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n gap: `${infoGap}px`,\n minWidth: 0,\n overflow: \"hidden\",\n }}\n data-testid=\"archetype-info\"\n >\n {/* Header with name and counter */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: colors.archetypeColor,\n textShadow: getNeonTextShadow(\n selectedArchetype.color,\n \"strong\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid=\"archetype-title\"\n >\n {selectedArchetype.korean} | {selectedArchetype.english}\n </div>\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.PRIMARY,\n color: colors.archetypeColor,\n textShadow: getNeonTextShadow(\n selectedArchetype.color,\n \"subtle\",\n ),\n }}\n data-testid=\"archetype-counter\"\n >\n {selectedIndex + 1} / {archetypes.length}\n </div>\n </div>\n\n {/* Philosophy */}\n <div\n style={{\n fontSize: `${philosophyFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n lineHeight: \"1.4\",\n ...getKoreanFontOptimization(philosophyFontSize, \"normal\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid=\"archetype-philosophy\"\n >\n {selectedArchetype.philosophy.korean} |{\" \"}\n {selectedArchetype.philosophy.english}\n </div>\n\n {/* Combat Stats */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"8px\",\n }}\n data-testid=\"combat-stats\"\n >\n <div\n style={{\n fontSize: isSmallScreen ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n }}\n >\n 전투 능력치 | Combat Stats\n </div>\n\n {/* Individual stat bars using refactored StatBar component */}\n {combatStats.map((stat) => (\n <StatBar\n key={stat.korean}\n label={`${stat.korean} | ${stat.english}`}\n value={stat.rawValue}\n max={100}\n color={selectedArchetype.color}\n height={statBarHeight}\n showValue={true}\n isMobile={isSmallScreen}\n />\n ))}\n </div>\n </div>\n </div>\n );\n },\n );\n\nArchetypeDisplayOverlayHtml.displayName = \"ArchetypeDisplayOverlayHtml\";\n\nexport default ArchetypeDisplayOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ArchetypeDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/ArchetypeDisplayOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from \"react\";\nimport {\n FALLBACK_ARCHETYPE_IMAGE,\n FONT_FAMILY,\n KOREAN_COLORS,\n} from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getEnhancedKoreanOverlayStyles } from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getKoreanFontOptimization,\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport { StatBar } from \"./StatBarOverlayHtml\";\n\nexport interface ArchetypeDataShape {\n readonly id: string;\n readonly korean: string;\n readonly english: string;\n readonly description: string;\n readonly color: number;\n readonly textureKey: string;\n readonly stats: {\n readonly attackPower: number;\n readonly defense: number;\n readonly speed: number;\n readonly technique: number;\n };\n readonly philosophy: {\n readonly korean: string;\n readonly english: string;\n };\n readonly specialAbilities?: readonly string[]; // Optional special abilities\n}\n\nexport interface ArchetypeDisplayOverlayHtmlProps {\n readonly archetypes: readonly ArchetypeDataShape[];\n readonly selectedIndex: number;\n readonly onArchetypeChange: (index: number) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean;\n}\n\n/**\n * HTML-based ArchetypeDisplay component for Three.js integration\n */\nexport const ArchetypeDisplayOverlayHtml: React.FC<ArchetypeDisplayOverlayHtmlProps> =\n React.memo(\n ({\n archetypes,\n selectedIndex,\n onArchetypeChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile: _isMobile = false, // Kept for interface compatibility, layout uses width\n }) => {\n void _isMobile;\n const selectedArchetype = archetypes[selectedIndex];\n\n const isSmallScreen = width < 768;\n const isLargeContainer = width >= 1100;\n const archImageWidth = isSmallScreen ? 140 : isLargeContainer ? 120 : 180;\n const archImageHeight = isSmallScreen\n ? 200\n : isLargeContainer\n ? 170\n : 260;\n const containerPadding = isSmallScreen ? 20 : isLargeContainer ? 12 : 20;\n const contentGap = isSmallScreen ? 10 : isLargeContainer ? 8 : 16;\n const infoGap = isSmallScreen ? 8 : isLargeContainer ? 6 : 12;\n const titleFontSize = isSmallScreen ? 14 : isLargeContainer ? 14 : 18;\n const philosophyFontSize = isSmallScreen\n ? 10\n : isLargeContainer\n ? 10\n : 12;\n const statBarHeight = isSmallScreen ? 10 : isLargeContainer ? 10 : 12;\n\n const handlePrevious = useCallback(() => {\n const newIndex =\n selectedIndex === 0 ? archetypes.length - 1 : selectedIndex - 1;\n onArchetypeChange(newIndex);\n onPlaySFX(\"menu_hover\");\n }, [selectedIndex, archetypes.length, onArchetypeChange, onPlaySFX]);\n\n const handleNext = useCallback(() => {\n const newIndex = (selectedIndex + 1) % archetypes.length;\n onArchetypeChange(newIndex);\n onPlaySFX(\"menu_hover\");\n }, [selectedIndex, archetypes.length, onArchetypeChange, onPlaySFX]);\n\n const combatStats = useMemo(() => {\n const maxStatValue = 100;\n return [\n {\n korean: \"공격\",\n english: \"Attack\",\n value: selectedArchetype.stats.attackPower / maxStatValue,\n rawValue: selectedArchetype.stats.attackPower,\n },\n {\n korean: \"방어\",\n english: \"Defense\",\n value: selectedArchetype.stats.defense / maxStatValue,\n rawValue: selectedArchetype.stats.defense,\n },\n {\n korean: \"속도\",\n english: \"Speed\",\n value: selectedArchetype.stats.speed / maxStatValue,\n rawValue: selectedArchetype.stats.speed,\n },\n {\n korean: \"기술\",\n english: \"Technique\",\n value: selectedArchetype.stats.technique / maxStatValue,\n rawValue: selectedArchetype.stats.technique,\n },\n ];\n }, [selectedArchetype.stats]);\n\n const colors = useMemo(\n () => ({\n archetypeColor: `#${selectedArchetype.color\n .toString(16)\n .padStart(6, \"0\")}`,\n titleGold: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n statsBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.9,\n ),\n statsBorder: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.5),\n statBarBackground: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n statBarFill: hexToRgbaString(selectedArchetype.color, 0.9),\n }),\n [selectedArchetype.color],\n );\n\n const containerStyle = useMemo(\n () => ({\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.95,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: false,\n depthLayers: 3,\n }),\n width: `${width}px`,\n height: `${height}px`,\n display: \"flex\",\n flexDirection: \"row\" as const,\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n gap: `${contentGap}px`,\n border: `2px solid ${colors.archetypeColor}`,\n padding: `${containerPadding}px`,\n position: \"relative\" as const,\n overflow: \"hidden\" as const,\n }),\n [width, height, contentGap, containerPadding, colors.archetypeColor],\n );\n\n const archetypeImagePath = useMemo(() => {\n return `/assets/visual/archetypes/${selectedArchetype.textureKey}.png`;\n }, [selectedArchetype.textureKey]);\n\n return (\n <div style={containerStyle} data-testid=\"archetype-display-container\">\n {/* Left Side - Character Image and Navigation */}\n <div\n style={{\n width: `${archImageWidth + 40}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: `${infoGap}px`,\n flexShrink: 0,\n }}\n data-testid=\"archetype-image-section\"\n >\n {/* Character Image */}\n <div\n style={{\n width: `${archImageWidth + 20}px`,\n height: `${archImageHeight + 20}px`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n position: \"relative\",\n background: `radial-gradient(circle, ${colors.archetypeColor}26, transparent)`,\n borderRadius: \"4px\",\n border: `2px solid ${colors.archetypeColor}`,\n }}\n data-testid=\"archetype-image-container\"\n >\n <img\n src={archetypeImagePath}\n alt={`${selectedArchetype.korean} - ${selectedArchetype.english}`}\n style={{\n width: `${archImageWidth}px`,\n height: `${archImageHeight}px`,\n objectFit: \"contain\",\n cursor: \"pointer\",\n }}\n onClick={handleNext}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleNext();\n }\n }}\n tabIndex={0}\n role=\"button\"\n aria-label={`${selectedArchetype.korean} ${selectedArchetype.english} - Click or press Enter to cycle to next archetype`}\n data-testid=\"archetype-image\"\n onError={(e) => {\n const target = e.currentTarget as HTMLImageElement;\n if (!target.src.endsWith(FALLBACK_ARCHETYPE_IMAGE)) {\n target.src = FALLBACK_ARCHETYPE_IMAGE;\n target.alt = `${selectedArchetype.korean} (image unavailable)`;\n }\n }}\n />\n </div>\n\n {/* Navigation Buttons */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: \"8px\",\n width: \"100%\",\n }}\n data-testid=\"archetype-navigation\"\n >\n <button\n onClick={handlePrevious}\n aria-label=\"Previous archetype\"\n style={{\n flex: 1,\n height: \"30px\",\n fontSize: \"14px\",\n fontWeight: \"bold\",\n color: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n background: colors.statsBackground,\n border: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.7,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n data-testid=\"prev-archetype-button\"\n >\n ◀\n </button>\n <button\n onClick={handleNext}\n aria-label=\"Next archetype\"\n style={{\n flex: 1,\n height: \"30px\",\n fontSize: \"14px\",\n fontWeight: \"bold\",\n color: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n background: colors.statsBackground,\n border: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.ACCENT_GOLD,\n 0.7,\n )}`,\n borderRadius: \"4px\",\n cursor: \"pointer\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n data-testid=\"next-archetype-button\"\n >\n ▶\n </button>\n </div>\n </div>\n\n {/* Right Side - Archetype Information */}\n <div\n style={{\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n gap: `${infoGap}px`,\n minWidth: 0,\n overflow: \"hidden\",\n }}\n data-testid=\"archetype-info\"\n >\n {/* Header with name and counter */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: colors.archetypeColor,\n textShadow: getNeonTextShadow(\n selectedArchetype.color,\n \"strong\",\n ),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid=\"archetype-title\"\n >\n {selectedArchetype.korean} | {selectedArchetype.english}\n </div>\n <div\n style={{\n fontSize: \"12px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.PRIMARY,\n color: colors.archetypeColor,\n textShadow: getNeonTextShadow(\n selectedArchetype.color,\n \"subtle\",\n ),\n }}\n data-testid=\"archetype-counter\"\n >\n {selectedIndex + 1} / {archetypes.length}\n </div>\n </div>\n\n {/* Philosophy */}\n <div\n style={{\n fontSize: `${philosophyFontSize}px`,\n fontStyle: \"italic\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n lineHeight: \"1.4\",\n ...getKoreanFontOptimization(philosophyFontSize, \"normal\"),\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid=\"archetype-philosophy\"\n >\n {selectedArchetype.philosophy.korean} |{\" \"}\n {selectedArchetype.philosophy.english}\n </div>\n\n {/* Combat Stats */}\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"8px\",\n }}\n data-testid=\"combat-stats\"\n >\n <div\n style={{\n fontSize: isSmallScreen ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n }}\n >\n 전투 능력치 | Combat Stats\n </div>\n\n {/* Individual stat bars using refactored StatBar component */}\n {combatStats.map((stat) => (\n <StatBar\n key={stat.korean}\n label={`${stat.korean} | ${stat.english}`}\n value={stat.rawValue}\n max={100}\n color={selectedArchetype.color}\n height={statBarHeight}\n showValue={true}\n isMobile={isSmallScreen}\n />\n ))}\n </div>\n </div>\n </div>\n );\n },\n );\n\nArchetypeDisplayOverlayHtml.displayName = \"ArchetypeDisplayOverlayHtml\";\n\nexport default ArchetypeDisplayOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EnhancedArchetypeDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { ArchetypeCardGrid } from \"./ArchetypeCardGridOverlayHtml\";\nimport {\n ArchetypeDataShape,\n ArchetypeDisplayOverlayHtml,\n} from \"./ArchetypeDisplayOverlayHtml\";\n\nexport interface EnhancedArchetypeDisplayProps {\n readonly archetypes: readonly ArchetypeDataShape[];\n readonly selectedIndex: number;\n readonly onArchetypeChange: (index: number) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean; // For controls/haptics only, use width for layout sizing\n readonly allowDetailedView?: boolean; // Allow switching to card grid view\n}\n\n/**\n * EnhancedArchetypeDisplay - Provides both compact and detailed card view modes\n * Can switch between ArchetypeDisplayOverlayHtml (compact) and ArchetypeCardGrid (detailed)\n */\nexport const EnhancedArchetypeDisplay: React.FC<EnhancedArchetypeDisplayProps> =\n React.memo(\n ({\n archetypes,\n selectedIndex,\n onArchetypeChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile = false,\n allowDetailedView = true,\n }) => {\n const [viewMode, setViewMode] = useState<\"compact\" | \"detailed\">(\n \"compact\",\n );\n\n const isSmallScreen = width < 768;\n\n const cardData = useMemo(() => {\n return archetypes.map((archetype) => ({\n archetype: Object.values(PlayerArchetype).find(\n (key) => key === archetype.id,\n ) as PlayerArchetype,\n id: archetype.id,\n korean: archetype.korean,\n english: archetype.english,\n description: archetype.description,\n color: archetype.color,\n textureKey: archetype.textureKey,\n stats: archetype.stats,\n philosophy: archetype.philosophy,\n specialAbilities: archetype.specialAbilities ?? [], // Use actual data or empty array\n }));\n }, [archetypes]);\n\n const currentArchetype = useMemo(() => {\n const archetype = archetypes[selectedIndex];\n return Object.values(PlayerArchetype).find(\n (key) => key === archetype.id,\n ) as PlayerArchetype;\n }, [archetypes, selectedIndex]);\n\n const handleToggleView = useCallback(() => {\n setViewMode((prev) => (prev === \"compact\" ? \"detailed\" : \"compact\"));\n onPlaySFX(\"menu_hover\");\n }, [onPlaySFX]);\n\n const handleArchetypeChangeFromGrid = useCallback(\n (archetype: PlayerArchetype) => {\n const index = archetypes.findIndex((a) => a.id === archetype);\n if (index !== -1) {\n onArchetypeChange(index);\n }\n },\n [archetypes, onArchetypeChange],\n );\n\n const detailedHeight = useMemo(\n () => Math.max(height * 2, 600),\n [height],\n );\n\n return (\n <div\n style={{\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: isSmallScreen ? \"10px\" : \"16px\",\n }}\n data-testid=\"enhanced-archetype-display\"\n >\n {/* View toggle button (only show if allowed and not small screen) */}\n {allowDetailedView && !isSmallScreen && (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"flex-end\",\n width: \"100%\",\n }}\n >\n <BaseButtonOverlayHtml\n korean={viewMode === \"compact\" ? \"상세 보기\" : \"간단 보기\"}\n english={viewMode === \"compact\" ? \"Detailed View\" : \"Compact View\"}\n onClick={handleToggleView}\n variant=\"secondary\"\n size=\"sm\"\n testId=\"view-toggle-button\"\n ariaLabel={`Toggle ${viewMode === \"compact\" ? \"detailed\" : \"compact\"} view`}\n />\n </div>\n )}\n\n {/* Render appropriate view */}\n {viewMode === \"compact\" ? (\n <ArchetypeDisplayOverlayHtml\n archetypes={archetypes}\n selectedIndex={selectedIndex}\n onArchetypeChange={onArchetypeChange}\n onPlaySFX={onPlaySFX}\n width={width}\n height={height}\n isMobile={isMobile}\n />\n ) : (\n <ArchetypeCardGrid\n archetypes={cardData}\n selectedArchetype={currentArchetype}\n onArchetypeChange={handleArchetypeChangeFromGrid}\n onPlaySFX={onPlaySFX}\n width={width}\n height={detailedHeight}\n isMobile={isMobile}\n />\n )}\n </div>\n );\n },\n );\n\nEnhancedArchetypeDisplay.displayName = \"EnhancedArchetypeDisplay\";\n\nexport default EnhancedArchetypeDisplay;\n"],"mappings":";;;;;;;;;;;AAwBA,IAAa,2BACX,MAAM,MACH,EACC,YACA,eACA,mBACA,WACA,QAAQ,KACR,SAAS,KACT,WAAW,OACX,oBAAoB,WAChB;CACJ,MAAM,CAAC,UAAU,eAAe,SAC9B,SACF;CAEA,MAAM,gBAAgB,QAAQ;CAE9B,MAAM,WAAW,cAAc;EAC7B,OAAO,WAAW,KAAK,eAAe;GACpC,WAAW,OAAO,OAAO,eAAe,
|
|
1
|
+
{"version":3,"file":"EnhancedArchetypeDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback, useMemo, useState } from \"react\";\nimport { PlayerArchetype } from \"../../../../types/common\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { ArchetypeCardGrid } from \"./ArchetypeCardGridOverlayHtml\";\nimport {\n ArchetypeDataShape,\n ArchetypeDisplayOverlayHtml,\n} from \"./ArchetypeDisplayOverlayHtml\";\n\nexport interface EnhancedArchetypeDisplayProps {\n readonly archetypes: readonly ArchetypeDataShape[];\n readonly selectedIndex: number;\n readonly onArchetypeChange: (index: number) => void;\n readonly onPlaySFX: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean; // For controls/haptics only, use width for layout sizing\n readonly allowDetailedView?: boolean; // Allow switching to card grid view\n}\n\n/**\n * EnhancedArchetypeDisplay - Provides both compact and detailed card view modes\n * Can switch between ArchetypeDisplayOverlayHtml (compact) and ArchetypeCardGrid (detailed)\n */\nexport const EnhancedArchetypeDisplay: React.FC<EnhancedArchetypeDisplayProps> =\n React.memo(\n ({\n archetypes,\n selectedIndex,\n onArchetypeChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile = false,\n allowDetailedView = true,\n }) => {\n const [viewMode, setViewMode] = useState<\"compact\" | \"detailed\">(\n \"compact\",\n );\n\n const isSmallScreen = width < 768;\n\n const cardData = useMemo(() => {\n return archetypes.map((archetype) => ({\n archetype: Object.values(PlayerArchetype).find(\n (key) => key === archetype.id,\n ) as PlayerArchetype,\n id: archetype.id,\n korean: archetype.korean,\n english: archetype.english,\n description: archetype.description,\n color: archetype.color,\n textureKey: archetype.textureKey,\n stats: archetype.stats,\n philosophy: archetype.philosophy,\n specialAbilities: archetype.specialAbilities ?? [], // Use actual data or empty array\n }));\n }, [archetypes]);\n\n const currentArchetype = useMemo(() => {\n const archetype = archetypes[selectedIndex];\n return Object.values(PlayerArchetype).find(\n (key) => key === archetype.id,\n ) as PlayerArchetype;\n }, [archetypes, selectedIndex]);\n\n const handleToggleView = useCallback(() => {\n setViewMode((prev) => (prev === \"compact\" ? \"detailed\" : \"compact\"));\n onPlaySFX(\"menu_hover\");\n }, [onPlaySFX]);\n\n const handleArchetypeChangeFromGrid = useCallback(\n (archetype: PlayerArchetype) => {\n const index = archetypes.findIndex((a) => a.id === archetype);\n if (index !== -1) {\n onArchetypeChange(index);\n }\n },\n [archetypes, onArchetypeChange],\n );\n\n const detailedHeight = useMemo(\n () => Math.max(height * 2, 600),\n [height],\n );\n\n return (\n <div\n style={{\n width: `${width}px`,\n display: \"flex\",\n flexDirection: \"column\",\n gap: isSmallScreen ? \"10px\" : \"16px\",\n }}\n data-testid=\"enhanced-archetype-display\"\n >\n {/* View toggle button (only show if allowed and not small screen) */}\n {allowDetailedView && !isSmallScreen && (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"flex-end\",\n width: \"100%\",\n }}\n >\n <BaseButtonOverlayHtml\n korean={viewMode === \"compact\" ? \"상세 보기\" : \"간단 보기\"}\n english={viewMode === \"compact\" ? \"Detailed View\" : \"Compact View\"}\n onClick={handleToggleView}\n variant=\"secondary\"\n size=\"sm\"\n testId=\"view-toggle-button\"\n ariaLabel={`Toggle ${viewMode === \"compact\" ? \"detailed\" : \"compact\"} view`}\n />\n </div>\n )}\n\n {/* Render appropriate view */}\n {viewMode === \"compact\" ? (\n <ArchetypeDisplayOverlayHtml\n archetypes={archetypes}\n selectedIndex={selectedIndex}\n onArchetypeChange={onArchetypeChange}\n onPlaySFX={onPlaySFX}\n width={width}\n height={height}\n isMobile={isMobile}\n />\n ) : (\n <ArchetypeCardGrid\n archetypes={cardData}\n selectedArchetype={currentArchetype}\n onArchetypeChange={handleArchetypeChangeFromGrid}\n onPlaySFX={onPlaySFX}\n width={width}\n height={detailedHeight}\n isMobile={isMobile}\n />\n )}\n </div>\n );\n },\n );\n\nEnhancedArchetypeDisplay.displayName = \"EnhancedArchetypeDisplay\";\n\nexport default EnhancedArchetypeDisplay;\n"],"mappings":";;;;;;;;;;;AAwBA,IAAa,2BACX,MAAM,MACH,EACC,YACA,eACA,mBACA,WACA,QAAQ,KACR,SAAS,KACT,WAAW,OACX,oBAAoB,WAChB;CACJ,MAAM,CAAC,UAAU,eAAe,SAC9B,SACF;CAEA,MAAM,gBAAgB,QAAQ;CAE9B,MAAM,WAAW,cAAc;EAC7B,OAAO,WAAW,KAAK,eAAe;GACpC,WAAW,OAAO,OAAO,eAAe,CAAC,CAAC,MACvC,QAAQ,QAAQ,UAAU,EAC7B;GACA,IAAI,UAAU;GACd,QAAQ,UAAU;GAClB,SAAS,UAAU;GACnB,aAAa,UAAU;GACvB,OAAO,UAAU;GACjB,YAAY,UAAU;GACtB,OAAO,UAAU;GACjB,YAAY,UAAU;GACtB,kBAAkB,UAAU,oBAAoB,CAAC;EACnD,EAAE;CACJ,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,mBAAmB,cAAc;EACrC,MAAM,YAAY,WAAW;EAC7B,OAAO,OAAO,OAAO,eAAe,CAAC,CAAC,MACnC,QAAQ,QAAQ,UAAU,EAC7B;CACF,GAAG,CAAC,YAAY,aAAa,CAAC;CAE9B,MAAM,mBAAmB,kBAAkB;EACzC,aAAa,SAAU,SAAS,YAAY,aAAa,SAAU;EACnE,UAAU,YAAY;CACxB,GAAG,CAAC,SAAS,CAAC;CAEd,MAAM,gCAAgC,aACnC,cAA+B;EAC9B,MAAM,QAAQ,WAAW,WAAW,MAAM,EAAE,OAAO,SAAS;EAC5D,IAAI,UAAU,IACZ,kBAAkB,KAAK;CAE3B,GACA,CAAC,YAAY,iBAAiB,CAChC;CAEA,MAAM,iBAAiB,cACf,KAAK,IAAI,SAAS,GAAG,GAAG,GAC9B,CAAC,MAAM,CACT;CAEA,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO,GAAG,MAAM;GAChB,SAAS;GACT,eAAe;GACf,KAAK,gBAAgB,SAAS;EAChC;EACA,eAAY;YAPd,CAUG,qBAAqB,CAAC,iBACrB,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,gBAAgB;IAChB,OAAO;GACT;aAEA,oBAAC,uBAAD;IACE,QAAQ,aAAa,YAAY,UAAU;IAC3C,SAAS,aAAa,YAAY,kBAAkB;IACpD,SAAS;IACT,SAAQ;IACR,MAAK;IACL,QAAO;IACP,WAAW,UAAU,aAAa,YAAY,aAAa,UAAU;GACtE,CAAA;EACE,CAAA,GAIN,aAAa,YACZ,oBAAC,6BAAD;GACc;GACG;GACI;GACR;GACJ;GACC;GACE;EACX,CAAA,IAED,oBAAC,mBAAD;GACE,YAAY;GACZ,mBAAmB;GACnB,mBAAmB;GACR;GACJ;GACP,QAAQ;GACE;EACX,CAAA,CAEA;;AAET,CACF;AAEF,yBAAyB,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MenuButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/MenuButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * MenuButtons - Reusable menu button grid for IntroScreen\n * \n * Provides 2x2 grid (desktop) or column (mobile) layout for menu navigation.\n * Extracted from MenuSectionOverlayHtml to reduce code duplication.\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 메뉴버튼\n */\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { GameMode } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { UIHaptics } from \"../../../../utils/hapticFeedback\";\nimport {\n getButtonVisualEffectsOnly,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport {\n getKoreanFontOptimization,\n} from \"../../../../utils/visualEffects\";\n\nexport interface MenuButtonsProps {\n /** Array of menu items to display */\n readonly menuItems: Array<{\n mode: GameMode;\n korean: string;\n english: string;\n }>;\n /** Currently selected menu item index */\n readonly selectedIndex: number;\n /** Index of currently hovered menu item (null if none) */\n readonly hoveredIndex: number | null;\n /** Callback when a menu item is selected */\n readonly onModeSelect: (mode: GameMode) => void;\n /** Callback when hover state changes */\n readonly onHoverChange: (index: number | null) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Screen width for responsive sizing */\n readonly width?: number;\n /** Whether on mobile device (for haptics) */\n readonly isMobile?: boolean;\n}\n\n/**\n * MenuButtons Component\n * \n * Displays menu navigation buttons with:\n * - 2x2 grid layout on larger screens\n * - Column layout on small screens\n * - Selected/hovered state visualization\n * - Korean bilingual text\n * - Haptic feedback support\n * \n * This component delegates to inline button elements with custom styling\n * since BaseButtonOverlayHtml doesn't support the complex selection state\n * and color transitions needed for menu navigation.\n * \n * Reduces code duplication by 62 lines (MenuSectionOverlayHtml: 372 → 310)\n * \n * @example\n * ```tsx\n * <MenuButtons\n * menuItems={MENU_ITEMS}\n * selectedIndex={0}\n * hoveredIndex={null}\n * onModeSelect={(mode) => handleModeSelect(mode)}\n * onHoverChange={(idx) => setHovered(idx)}\n * width={800}\n * />\n * ```\n */\nexport const MenuButtons: React.FC<MenuButtonsProps> = ({\n menuItems,\n selectedIndex,\n hoveredIndex,\n onModeSelect,\n onHoverChange,\n onPlaySFX,\n width = 800,\n isMobile: _isMobile = false, // Prefix with _ to indicate intentionally unused\n}) => {\n const isSmallScreen = width < 768;\n const useGridLayout = !isSmallScreen;\n const buttonHeight = isSmallScreen ? 44 : 40;\n const buttonFontSize = isSmallScreen\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375)\n : 13;\n const buttonGap = isSmallScreen ? 6 : 8;\n\n const colors = useMemo(\n () => ({\n buttonSelectedBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.98),\n buttonHoveredBg: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_LIGHT, 0.92),\n buttonDefaultBg: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.92,\n ),\n buttonSelectedBorder: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n 1.0,\n ),\n buttonHoveredBorder: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8),\n buttonDefaultBorder: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7),\n textSelected: `#${KOREAN_COLORS.UI_BACKGROUND_DARK.toString(16).padStart(6, \"0\")}`,\n textHovered: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n textDefault: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(6, \"0\")}`,\n }),\n [],\n );\n\n const handleButtonClick = useCallback(\n (mode: GameMode) => {\n UIHaptics.buttonTap();\n onModeSelect(mode);\n onPlaySFX?.(\"menu_select\");\n },\n [onModeSelect, onPlaySFX],\n );\n\n const handleButtonHover = useCallback(\n (index: number, isHovering: boolean) => {\n const newIndex = isHovering ? index : null;\n onHoverChange(newIndex);\n if (isHovering) {\n UIHaptics.menuHover();\n onPlaySFX?.(\"menu_hover\");\n }\n },\n [onHoverChange, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: useGridLayout ? \"1fr 1fr\" : \"1fr\",\n gap: `${buttonGap}px`,\n width: \"100%\",\n }}\n data-testid=\"main-menu-buttons\"\n >\n {menuItems.map((item, index) => {\n const isSelected = selectedIndex === index;\n const isHovered = hoveredIndex === index;\n\n const visualEffects = getButtonVisualEffectsOnly({\n variant: \"primary\",\n isHovered,\n isPressed: false,\n isFocused: false,\n glowIntensity: isSelected\n ? \"medium\"\n : isHovered\n ? \"medium\"\n : \"subtle\",\n hoverAnimation: \"combined\",\n });\n\n return (\n <button\n key={item.mode}\n onClick={() => handleButtonClick(item.mode)}\n onMouseEnter={() => handleButtonHover(index, true)}\n onMouseLeave={() => handleButtonHover(index, false)}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n aria-label={`${item.korean} (${item.english})`}\n aria-selected={isSelected}\n role=\"menuitem\"\n style={{\n ...visualEffects,\n ...getKoreanFontOptimization(\n buttonFontSize,\n isSelected ? \"bold\" : \"normal\",\n ),\n fontFamily: FONT_FAMILY.KOREAN,\n width: \"100%\",\n height: `${buttonHeight}px`,\n color: isSelected\n ? colors.textSelected\n : isHovered\n ? colors.textHovered\n : colors.textDefault,\n background: isSelected\n ? colors.buttonSelectedBg\n : isHovered\n ? colors.buttonHoveredBg\n : colors.buttonDefaultBg,\n border: isSelected\n ? `3px solid ${colors.buttonSelectedBorder}`\n : isHovered\n ? `2px solid ${colors.buttonHoveredBorder}`\n : `2px solid ${colors.buttonDefaultBorder}`,\n cursor: \"pointer\",\n }}\n data-testid={`menu-item-${item.mode}`}\n >\n {/* Add test ID aliases for backward compatibility */}\n {item.mode === GameMode.TRAINING && (\n <span\n data-testid=\"training-button\"\n style={{ display: \"none\" }}\n />\n )}\n {item.mode === GameMode.VERSUS && (\n <span data-testid=\"combat-button\" style={{ display: \"none\" }} />\n )}\n {item.korean} ({item.english})\n </button>\n );\n })}\n </div>\n );\n};\n\nexport default MenuButtons;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAa,eAA2C,EACtD,WACA,eACA,cACA,cACA,eACA,WACA,QAAQ,KACR,UAAU,YAAY,YAClB;CACJ,MAAM,gBAAgB,QAAQ;CAC9B,MAAM,gBAAgB,CAAC;CACvB,MAAM,eAAe,gBAAgB,KAAK;CAC1C,MAAM,iBAAiB,gBACnB,wBAAwB,SAAS,SAAS,GAAG,IAC7C;CACJ,MAAM,YAAY,gBAAgB,IAAI;CAEtC,MAAM,SAAS,eACN;EACL,kBAAkB,gBAAgB,cAAc,aAAa,GAAI;EACjE,iBAAiB,gBAAgB,cAAc,qBAAqB,GAAI;EACxE,iBAAiB,gBACf,cAAc,sBACd,GACF;EACA,sBAAsB,gBACpB,cAAc,oBACd,CACF;EACA,qBAAqB,gBAAgB,cAAc,aAAa,EAAG;EACnE,qBAAqB,gBAAgB,cAAc,cAAc,EAAG;EACpE,cAAc,IAAI,cAAc,mBAAmB,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"MenuButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/MenuButtonsOverlayHtml.tsx"],"sourcesContent":["/**\n * MenuButtons - Reusable menu button grid for IntroScreen\n * \n * Provides 2x2 grid (desktop) or column (mobile) layout for menu navigation.\n * Extracted from MenuSectionOverlayHtml to reduce code duplication.\n * \n * @module components/screens/intro\n * @category Intro UI\n * @korean 메뉴버튼\n */\n\nimport React, { useCallback, useMemo } from \"react\";\nimport { GameMode } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { UIHaptics } from \"../../../../utils/hapticFeedback\";\nimport {\n getButtonVisualEffectsOnly,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport {\n getKoreanFontOptimization,\n} from \"../../../../utils/visualEffects\";\n\nexport interface MenuButtonsProps {\n /** Array of menu items to display */\n readonly menuItems: Array<{\n mode: GameMode;\n korean: string;\n english: string;\n }>;\n /** Currently selected menu item index */\n readonly selectedIndex: number;\n /** Index of currently hovered menu item (null if none) */\n readonly hoveredIndex: number | null;\n /** Callback when a menu item is selected */\n readonly onModeSelect: (mode: GameMode) => void;\n /** Callback when hover state changes */\n readonly onHoverChange: (index: number | null) => void;\n /** Callback to play sound effects */\n readonly onPlaySFX?: (sound: string) => void;\n /** Screen width for responsive sizing */\n readonly width?: number;\n /** Whether on mobile device (for haptics) */\n readonly isMobile?: boolean;\n}\n\n/**\n * MenuButtons Component\n * \n * Displays menu navigation buttons with:\n * - 2x2 grid layout on larger screens\n * - Column layout on small screens\n * - Selected/hovered state visualization\n * - Korean bilingual text\n * - Haptic feedback support\n * \n * This component delegates to inline button elements with custom styling\n * since BaseButtonOverlayHtml doesn't support the complex selection state\n * and color transitions needed for menu navigation.\n * \n * Reduces code duplication by 62 lines (MenuSectionOverlayHtml: 372 → 310)\n * \n * @example\n * ```tsx\n * <MenuButtons\n * menuItems={MENU_ITEMS}\n * selectedIndex={0}\n * hoveredIndex={null}\n * onModeSelect={(mode) => handleModeSelect(mode)}\n * onHoverChange={(idx) => setHovered(idx)}\n * width={800}\n * />\n * ```\n */\nexport const MenuButtons: React.FC<MenuButtonsProps> = ({\n menuItems,\n selectedIndex,\n hoveredIndex,\n onModeSelect,\n onHoverChange,\n onPlaySFX,\n width = 800,\n isMobile: _isMobile = false, // Prefix with _ to indicate intentionally unused\n}) => {\n const isSmallScreen = width < 768;\n const useGridLayout = !isSmallScreen;\n const buttonHeight = isSmallScreen ? 44 : 40;\n const buttonFontSize = isSmallScreen\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375)\n : 13;\n const buttonGap = isSmallScreen ? 6 : 8;\n\n const colors = useMemo(\n () => ({\n buttonSelectedBg: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.98),\n buttonHoveredBg: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_LIGHT, 0.92),\n buttonDefaultBg: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 0.92,\n ),\n buttonSelectedBorder: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n 1.0,\n ),\n buttonHoveredBorder: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8),\n buttonDefaultBorder: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7),\n textSelected: `#${KOREAN_COLORS.UI_BACKGROUND_DARK.toString(16).padStart(6, \"0\")}`,\n textHovered: `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n textDefault: `#${KOREAN_COLORS.TEXT_PRIMARY.toString(16).padStart(6, \"0\")}`,\n }),\n [],\n );\n\n const handleButtonClick = useCallback(\n (mode: GameMode) => {\n UIHaptics.buttonTap();\n onModeSelect(mode);\n onPlaySFX?.(\"menu_select\");\n },\n [onModeSelect, onPlaySFX],\n );\n\n const handleButtonHover = useCallback(\n (index: number, isHovering: boolean) => {\n const newIndex = isHovering ? index : null;\n onHoverChange(newIndex);\n if (isHovering) {\n UIHaptics.menuHover();\n onPlaySFX?.(\"menu_hover\");\n }\n },\n [onHoverChange, onPlaySFX],\n );\n\n return (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: useGridLayout ? \"1fr 1fr\" : \"1fr\",\n gap: `${buttonGap}px`,\n width: \"100%\",\n }}\n data-testid=\"main-menu-buttons\"\n >\n {menuItems.map((item, index) => {\n const isSelected = selectedIndex === index;\n const isHovered = hoveredIndex === index;\n\n const visualEffects = getButtonVisualEffectsOnly({\n variant: \"primary\",\n isHovered,\n isPressed: false,\n isFocused: false,\n glowIntensity: isSelected\n ? \"medium\"\n : isHovered\n ? \"medium\"\n : \"subtle\",\n hoverAnimation: \"combined\",\n });\n\n return (\n <button\n key={item.mode}\n onClick={() => handleButtonClick(item.mode)}\n onMouseEnter={() => handleButtonHover(index, true)}\n onMouseLeave={() => handleButtonHover(index, false)}\n onFocus={(e) => {\n e.currentTarget.style.outline = `3px solid ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)}`;\n e.currentTarget.style.outlineOffset = \"2px\";\n }}\n onBlur={(e) => {\n e.currentTarget.style.outline = \"none\";\n }}\n aria-label={`${item.korean} (${item.english})`}\n aria-selected={isSelected}\n role=\"menuitem\"\n style={{\n ...visualEffects,\n ...getKoreanFontOptimization(\n buttonFontSize,\n isSelected ? \"bold\" : \"normal\",\n ),\n fontFamily: FONT_FAMILY.KOREAN,\n width: \"100%\",\n height: `${buttonHeight}px`,\n color: isSelected\n ? colors.textSelected\n : isHovered\n ? colors.textHovered\n : colors.textDefault,\n background: isSelected\n ? colors.buttonSelectedBg\n : isHovered\n ? colors.buttonHoveredBg\n : colors.buttonDefaultBg,\n border: isSelected\n ? `3px solid ${colors.buttonSelectedBorder}`\n : isHovered\n ? `2px solid ${colors.buttonHoveredBorder}`\n : `2px solid ${colors.buttonDefaultBorder}`,\n cursor: \"pointer\",\n }}\n data-testid={`menu-item-${item.mode}`}\n >\n {/* Add test ID aliases for backward compatibility */}\n {item.mode === GameMode.TRAINING && (\n <span\n data-testid=\"training-button\"\n style={{ display: \"none\" }}\n />\n )}\n {item.mode === GameMode.VERSUS && (\n <span data-testid=\"combat-button\" style={{ display: \"none\" }} />\n )}\n {item.korean} ({item.english})\n </button>\n );\n })}\n </div>\n );\n};\n\nexport default MenuButtons;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MenuSectionOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/MenuSectionOverlayHtml.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { GameMode } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport {\n getEnhancedKoreanOverlayStyles,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n} from \"../../../../utils/visualEffects\";\nimport { MenuButtons } from \"./MenuButtonsOverlayHtml\";\n\nexport interface MenuSectionOverlayHtmlProps {\n readonly menuItems: Array<{\n mode: GameMode;\n korean: string;\n english: string;\n }>;\n readonly selectedIndex: number;\n readonly onModeSelect: (mode: GameMode) => void;\n readonly onSelectedIndexChange?: (index: number) => void;\n readonly onPlaySFX?: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean; // For controls/haptics only, use width for layout sizing\n}\n\n/**\n * HTML-based MenuSection component for Three.js integration\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized to prevent unnecessary re-renders\n * - All callbacks use useCallback\n * - Styles pre-calculated and memoized\n */\nexport const MenuSectionOverlayHtml = React.memo<MenuSectionOverlayHtmlProps>(\n ({\n menuItems,\n selectedIndex,\n onModeSelect,\n onSelectedIndexChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile = false, // Default to false, parent should pass proper device detection\n }) => {\n const [hoveredItem, setHoveredItem] = useState<number | null>(null);\n const [focused, setFocused] = useState<boolean>(false);\n\n const enhancedOverlayStyles = useMemo(\n () =>\n getEnhancedKoreanOverlayStyles({\n opacity: 0.96,\n glowIntensity: focused ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: false,\n depthLayers: 2,\n }),\n [focused],\n );\n\n const titleColor = useMemo(\n () => `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n [],\n );\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (!onSelectedIndexChange) return;\n\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n event.stopPropagation();\n const nextIndex =\n selectedIndex === 0 ? menuItems.length - 1 : selectedIndex - 1;\n onSelectedIndexChange(nextIndex);\n onPlaySFX?.(\"menu_hover\");\n } else if (event.key === \"ArrowDown\") {\n event.preventDefault();\n event.stopPropagation();\n const nextIndex =\n selectedIndex === menuItems.length - 1 ? 0 : selectedIndex + 1;\n onSelectedIndexChange(nextIndex);\n onPlaySFX?.(\"menu_hover\");\n } else if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault();\n event.stopPropagation();\n onPlaySFX?.(\"menu_select\");\n onModeSelect(menuItems[selectedIndex].mode);\n } else {\n const numKey = parseInt(event.key);\n if (numKey >= 1 && numKey <= menuItems.length) {\n event.stopPropagation();\n const targetIndex = numKey - 1;\n onSelectedIndexChange(targetIndex);\n onPlaySFX?.(\"menu_select\");\n onModeSelect(menuItems[targetIndex].mode);\n }\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [\n selectedIndex,\n menuItems,\n onSelectedIndexChange,\n onModeSelect,\n onPlaySFX,\n ]);\n\n useEffect(() => {\n const handleFocus = () => setFocused(true);\n const handleBlur = () => setFocused(false);\n window.addEventListener(\"focusin\", handleFocus);\n window.addEventListener(\"focusout\", handleBlur);\n return () => {\n window.removeEventListener(\"focusin\", handleFocus);\n window.removeEventListener(\"focusout\", handleBlur);\n };\n }, []);\n\n const isSmallScreen = width < 768; // Mobile-sized screens\n\n const containerPadding = isSmallScreen ? 16 : 12;\n const titleFontSize = isSmallScreen\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375)\n : 14;\n const sectionGap = isSmallScreen ? 8 : 6;\n\n const safeAreaStyles = useMemo(\n () =>\n isMobile ? getSafeAreaPadding([\"top\", \"bottom\"], containerPadding) : {},\n [isMobile, containerPadding],\n );\n\n return (\n <div\n style={{\n ...enhancedOverlayStyles,\n ...safeAreaStyles,\n width: `${width}px`,\n minHeight: `${height}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: `${sectionGap}px`,\n padding: `${containerPadding}px`,\n position: \"relative\",\n overflow: \"visible\",\n }}\n data-testid=\"main-menu-section\"\n >\n {/* Menu Title */}\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n color: titleColor,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textAlign: \"center\",\n textShadow: getNeonTextShadow(KOREAN_COLORS.ACCENT_GOLD, \"medium\"),\n }}\n data-testid=\"menu-title\"\n >\n 메인 메뉴 | Main Menu\n </div>\n\n {/* Menu Buttons - Extracted to MenuButtons component */}\n <MenuButtons\n menuItems={menuItems}\n selectedIndex={selectedIndex}\n hoveredIndex={hoveredItem}\n onModeSelect={onModeSelect}\n onHoverChange={setHoveredItem}\n onPlaySFX={onPlaySFX}\n width={width}\n isMobile={isMobile}\n />\n\n {/* Navigation Instructions */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: isSmallScreen ? \"4px\" : \"6px\",\n textAlign: \"center\",\n fontSize: isSmallScreen ? \"10px\" : \"12px\",\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n fontFamily: FONT_FAMILY.KOREAN,\n marginTop: \"auto\",\n }}\n data-testid=\"navigation-hint-container\"\n >\n <div data-testid=\"menu-navigation-hint-korean\">\n 방향키/마우스로 이동 • Enter/클릭으로 선택 • 숫자키로 바로가기\n </div>\n <div\n style={{ fontSize: isSmallScreen ? \"9px\" : \"10px\" }}\n data-testid=\"menu-navigation-hint-english\"\n >\n Arrow keys/mouse to navigate • Enter/click to select • Number keys for\n shortcuts\n </div>\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.selectedIndex === nextProps.selectedIndex &&\n prevProps.width === nextProps.width &&\n prevProps.height === nextProps.height &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.menuItems.length === nextProps.menuItems.length &&\n prevProps.onModeSelect === nextProps.onModeSelect &&\n prevProps.onSelectedIndexChange === nextProps.onSelectedIndexChange &&\n prevProps.onPlaySFX === nextProps.onPlaySFX\n );\n },\n);\n\nMenuSectionOverlayHtml.displayName = \"MenuSectionOverlayHtml\";\n\nexport default MenuSectionOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MenuSectionOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/MenuSectionOverlayHtml.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useState } from \"react\";\nimport { GameMode } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport {\n getEnhancedKoreanOverlayStyles,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport { getMobileKoreanFontSize } from \"../../../../utils/mobileUIUtils\";\nimport { getSafeAreaPadding } from \"../../../../utils/safeAreaUtils\";\nimport {\n getNeonTextShadow,\n} from \"../../../../utils/visualEffects\";\nimport { MenuButtons } from \"./MenuButtonsOverlayHtml\";\n\nexport interface MenuSectionOverlayHtmlProps {\n readonly menuItems: Array<{\n mode: GameMode;\n korean: string;\n english: string;\n }>;\n readonly selectedIndex: number;\n readonly onModeSelect: (mode: GameMode) => void;\n readonly onSelectedIndexChange?: (index: number) => void;\n readonly onPlaySFX?: (sound: string) => void;\n readonly width?: number;\n readonly height?: number;\n readonly isMobile?: boolean; // For controls/haptics only, use width for layout sizing\n}\n\n/**\n * HTML-based MenuSection component for Three.js integration\n *\n * Optimized with React.memo for 60fps performance:\n * - Memoized to prevent unnecessary re-renders\n * - All callbacks use useCallback\n * - Styles pre-calculated and memoized\n */\nexport const MenuSectionOverlayHtml = React.memo<MenuSectionOverlayHtmlProps>(\n ({\n menuItems,\n selectedIndex,\n onModeSelect,\n onSelectedIndexChange,\n onPlaySFX,\n width = 800,\n height = 300,\n isMobile = false, // Default to false, parent should pass proper device detection\n }) => {\n const [hoveredItem, setHoveredItem] = useState<number | null>(null);\n const [focused, setFocused] = useState<boolean>(false);\n\n const enhancedOverlayStyles = useMemo(\n () =>\n getEnhancedKoreanOverlayStyles({\n opacity: 0.96,\n glowIntensity: focused ? \"medium\" : \"subtle\",\n includeGradient: false,\n includeBackdropBlur: false,\n depthLayers: 2,\n }),\n [focused],\n );\n\n const titleColor = useMemo(\n () => `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n [],\n );\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (!onSelectedIndexChange) return;\n\n if (event.key === \"ArrowUp\") {\n event.preventDefault();\n event.stopPropagation();\n const nextIndex =\n selectedIndex === 0 ? menuItems.length - 1 : selectedIndex - 1;\n onSelectedIndexChange(nextIndex);\n onPlaySFX?.(\"menu_hover\");\n } else if (event.key === \"ArrowDown\") {\n event.preventDefault();\n event.stopPropagation();\n const nextIndex =\n selectedIndex === menuItems.length - 1 ? 0 : selectedIndex + 1;\n onSelectedIndexChange(nextIndex);\n onPlaySFX?.(\"menu_hover\");\n } else if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault();\n event.stopPropagation();\n onPlaySFX?.(\"menu_select\");\n onModeSelect(menuItems[selectedIndex].mode);\n } else {\n const numKey = parseInt(event.key);\n if (numKey >= 1 && numKey <= menuItems.length) {\n event.stopPropagation();\n const targetIndex = numKey - 1;\n onSelectedIndexChange(targetIndex);\n onPlaySFX?.(\"menu_select\");\n onModeSelect(menuItems[targetIndex].mode);\n }\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [\n selectedIndex,\n menuItems,\n onSelectedIndexChange,\n onModeSelect,\n onPlaySFX,\n ]);\n\n useEffect(() => {\n const handleFocus = () => setFocused(true);\n const handleBlur = () => setFocused(false);\n window.addEventListener(\"focusin\", handleFocus);\n window.addEventListener(\"focusout\", handleBlur);\n return () => {\n window.removeEventListener(\"focusin\", handleFocus);\n window.removeEventListener(\"focusout\", handleBlur);\n };\n }, []);\n\n const isSmallScreen = width < 768; // Mobile-sized screens\n\n const containerPadding = isSmallScreen ? 16 : 12;\n const titleFontSize = isSmallScreen\n ? getMobileKoreanFontSize(\"SMALL\", width ?? 375)\n : 14;\n const sectionGap = isSmallScreen ? 8 : 6;\n\n const safeAreaStyles = useMemo(\n () =>\n isMobile ? getSafeAreaPadding([\"top\", \"bottom\"], containerPadding) : {},\n [isMobile, containerPadding],\n );\n\n return (\n <div\n style={{\n ...enhancedOverlayStyles,\n ...safeAreaStyles,\n width: `${width}px`,\n minHeight: `${height}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: `${sectionGap}px`,\n padding: `${containerPadding}px`,\n position: \"relative\",\n overflow: \"visible\",\n }}\n data-testid=\"main-menu-section\"\n >\n {/* Menu Title */}\n <div\n style={{\n fontSize: `${titleFontSize}px`,\n color: titleColor,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n textAlign: \"center\",\n textShadow: getNeonTextShadow(KOREAN_COLORS.ACCENT_GOLD, \"medium\"),\n }}\n data-testid=\"menu-title\"\n >\n 메인 메뉴 | Main Menu\n </div>\n\n {/* Menu Buttons - Extracted to MenuButtons component */}\n <MenuButtons\n menuItems={menuItems}\n selectedIndex={selectedIndex}\n hoveredIndex={hoveredItem}\n onModeSelect={onModeSelect}\n onHoverChange={setHoveredItem}\n onPlaySFX={onPlaySFX}\n width={width}\n isMobile={isMobile}\n />\n\n {/* Navigation Instructions */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: isSmallScreen ? \"4px\" : \"6px\",\n textAlign: \"center\",\n fontSize: isSmallScreen ? \"10px\" : \"12px\",\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(\n 6,\n \"0\",\n )}`,\n fontFamily: FONT_FAMILY.KOREAN,\n marginTop: \"auto\",\n }}\n data-testid=\"navigation-hint-container\"\n >\n <div data-testid=\"menu-navigation-hint-korean\">\n 방향키/마우스로 이동 • Enter/클릭으로 선택 • 숫자키로 바로가기\n </div>\n <div\n style={{ fontSize: isSmallScreen ? \"9px\" : \"10px\" }}\n data-testid=\"menu-navigation-hint-english\"\n >\n Arrow keys/mouse to navigate • Enter/click to select • Number keys for\n shortcuts\n </div>\n </div>\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.selectedIndex === nextProps.selectedIndex &&\n prevProps.width === nextProps.width &&\n prevProps.height === nextProps.height &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.menuItems.length === nextProps.menuItems.length &&\n prevProps.onModeSelect === nextProps.onModeSelect &&\n prevProps.onSelectedIndexChange === nextProps.onSelectedIndexChange &&\n prevProps.onPlaySFX === nextProps.onPlaySFX\n );\n },\n);\n\nMenuSectionOverlayHtml.displayName = \"MenuSectionOverlayHtml\";\n\nexport default MenuSectionOverlayHtml;\n"],"mappings":"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"}
|