blacktrigram 0.7.97 → 0.7.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioManager.js +2 -4
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +8 -9
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js +2 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js +3 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js +16 -15
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js +0 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js +7 -6
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +3 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js +7 -6
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js +34 -30
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js +2 -2
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js +2 -2
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js +2 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +2 -4
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +10 -9
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js +14 -13
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js +8 -7
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js +15 -13
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js +3 -2
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +1 -2
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +3 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js +59 -53
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js +19 -18
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js +22 -18
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js +4 -5
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js +29 -24
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js +3 -2
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js +2 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js +3 -2
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js +6 -5
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js +3 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js +2 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js +25 -24
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js +1 -3
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js +2 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js +69 -68
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js +0 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +12 -3
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/systems/CombatSystem.js +6 -2
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js +0 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js +10 -21
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js +1 -3
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js +3 -4
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js +2 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.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/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js +3 -9
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js +8 -5
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/utils/accessibility.js +0 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/effectUtils.js +1 -3
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/inputSystem.js +0 -2
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js +6 -3
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/playerUtils.js +4 -2
- package/lib/utils/playerUtils.js.map +1 -1
- package/package.json +6 -6
|
@@ -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,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;
|
|
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;EAPd,UAAA,CAUG,qBAAqB,CAAC,iBACrB,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,gBAAgB;IAChB,OAAO;GACT;GAEA,UAAA,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,CAAC,CAAC,SAAS,GAAG,GAAG;EAC/E,aAAa,IAAI,cAAc,YAAY,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;EACvE,aAAa,IAAI,cAAc,aAAa,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAC1E,IACA,CAAC,CACH;CAEA,MAAM,oBAAoB,aACvB,SAAmB;EAClB,UAAU,UAAU;EACpB,aAAa,IAAI;EACjB,YAAY,aAAa;CAC3B,GACA,CAAC,cAAc,SAAS,CAC1B;CAEA,MAAM,oBAAoB,aACvB,OAAe,eAAwB;EAEtC,cADiB,aAAa,QAAQ,IAChB;EACtB,IAAI,YAAY;GACd,UAAU,UAAU;GACpB,YAAY,YAAY;EAC1B;CACF,GACA,CAAC,eAAe,SAAS,CAC3B;CAEA,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,qBAAqB,gBAAgB,YAAY;GACjD,KAAK,GAAG,UAAU;GAClB,OAAO;EACT;EACA,eAAY;
|
|
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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatBarOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/StatBarOverlayHtml.tsx"],"sourcesContent":["/**\n * StatBar - Enhanced stat visualization component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays horizontal bar chart for combat statistics\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 StatBarProps {\n readonly label: string; // Format: \"Korean | English\"\n readonly value: number; // 0-100 range\n readonly max?: number; // Maximum value for scaling\n readonly color?: number; // Hex color for bar\n readonly height?: number; // Bar height in pixels\n readonly showValue?: boolean; // Show numeric value\n readonly isMobile?: boolean;\n}\n\n/**\n * StatBar component - Displays a horizontal bar chart for stats\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 visualize combat statistics\n * \n * @example\n * ```tsx\n * <StatBar\n * label=\"공격 | Attack\"\n * value={85}\n * max={100}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const StatBar: React.FC<StatBarProps> = React.memo(\n ({\n label,\n value,\n max = 100,\n color = KOREAN_COLORS.PRIMARY_CYAN,\n height = 12,\n showValue = true,\n isMobile = false,\n }) => {\n const { koreanTypography, colors: themeColors, calculateResponsiveSize } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const percentage = useMemo(\n () => Math.min(100, Math.max(0, (value / max) * 100)),\n [value, max]\n );\n\n const statBarColors = useMemo(\n () => ({\n barBackground: hexToRgbaString(themeColors.UI_BACKGROUND_MEDIUM, 1),\n barFill: hexToRgbaString(color, 0.9),\n barBorder: hexToRgbaString(color, 0.7),\n labelColor: hexColorToCSS(themeColors.TEXT_SECONDARY),\n valueColor: hexColorToCSS(color),\n }),\n [color, themeColors]\n );\n\n const fontSize = calculateResponsiveSize(isMobile ? 9 : 11);\n const labelWidth = calculateResponsiveSize(isMobile ? 70 : 80);\n const valueWidth = calculateResponsiveSize(isMobile ? 25 : 30);\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${calculateResponsiveSize(12)}px`,\n }}\n data-testid={`stat-bar-${label.split(\"|\")[0].trim()}`}\n >\n {/* Stat label with Korean typography */}\n <div\n style={{\n width: `${labelWidth}px`,\n fontSize: `${fontSize}px`,\n fontFamily: koreanTypography.fontFamily,\n color: statBarColors.labelColor,\n flexShrink: 0,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid=\"stat-label\"\n >\n {label}\n </div>\n\n {/* Stat bar container */}\n <div\n style={{\n flex: 1,\n height: `${height}px`,\n background: statBarColors.barBackground,\n borderRadius: \"2px\",\n position: \"relative\",\n border: `1px solid ${statBarColors.barBorder}`,\n overflow: \"hidden\",\n }}\n data-testid=\"stat-bar-container\"\n >\n {/* Stat bar fill with smooth transition */}\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: statBarColors.barFill,\n borderRadius: \"2px\",\n transition: \"width 0.3s ease\",\n boxShadow: `0 0 8px ${hexToRgbaString(color, 0.5)}`,\n }}\n data-testid=\"stat-bar-fill\"\n />\n </div>\n\n {/* Stat value */}\n {showValue && (\n <div\n style={{\n width: `${valueWidth}px`,\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: koreanTypography.fontFamily,\n color: statBarColors.valueColor,\n textAlign: \"right\",\n flexShrink: 0,\n }}\n data-testid=\"stat-value\"\n >\n {Math.round(value)}\n </div>\n )}\n </div>\n );\n }\n);\n\nStatBar.displayName = \"StatBar\";\n\nexport default StatBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,UAAkC,MAAM,MAClD,EACC,OACA,OACA,MAAM,KACN,QAAQ,cAAc,cACtB,SAAS,IACT,YAAY,MACZ,WAAW,YACP;CACJ,MAAM,EAAE,kBAAkB,QAAQ,aAAa,4BAA4B,eAAe;EACxF,MAAM;EACN;CACF,CAAC;CAED,MAAM,aAAa,cACX,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,MAAO,GAAG,CAAC,GACpD,CAAC,OAAO,GAAG,CACb;CAEA,MAAM,gBAAgB,eACb;EACL,eAAe,gBAAgB,YAAY,sBAAsB,CAAC;EAClE,SAAS,gBAAgB,OAAO,EAAG;EACnC,WAAW,gBAAgB,OAAO,EAAG;EACrC,YAAY,cAAc,YAAY,cAAc;EACpD,YAAY,cAAc,KAAK;CACjC,IACA,CAAC,OAAO,WAAW,CACrB;CAEA,MAAM,WAAW,wBAAwB,WAAW,IAAI,EAAE;CAC1D,MAAM,aAAa,wBAAwB,WAAW,KAAK,EAAE;CAC7D,MAAM,aAAa,wBAAwB,WAAW,KAAK,EAAE;CAE7D,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,SAAS;GACT,eAAe;GACf,YAAY;GACZ,KAAK,GAAG,wBAAwB,EAAE,EAAE;EACtC;EACA,eAAa,YAAY,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK;
|
|
1
|
+
{"version":3,"file":"StatBarOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/intro/components/StatBarOverlayHtml.tsx"],"sourcesContent":["/**\n * StatBar - Enhanced stat visualization component with Korean theming\n * \n * Refactored to use useKoreanTheme hook for consistent styling\n * Displays horizontal bar chart for combat statistics\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 StatBarProps {\n readonly label: string; // Format: \"Korean | English\"\n readonly value: number; // 0-100 range\n readonly max?: number; // Maximum value for scaling\n readonly color?: number; // Hex color for bar\n readonly height?: number; // Bar height in pixels\n readonly showValue?: boolean; // Show numeric value\n readonly isMobile?: boolean;\n}\n\n/**\n * StatBar component - Displays a horizontal bar chart for stats\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 visualize combat statistics\n * \n * @example\n * ```tsx\n * <StatBar\n * label=\"공격 | Attack\"\n * value={85}\n * max={100}\n * color={KOREAN_COLORS.PRIMARY_CYAN}\n * isMobile={false}\n * />\n * ```\n */\nexport const StatBar: React.FC<StatBarProps> = React.memo(\n ({\n label,\n value,\n max = 100,\n color = KOREAN_COLORS.PRIMARY_CYAN,\n height = 12,\n showValue = true,\n isMobile = false,\n }) => {\n const { koreanTypography, colors: themeColors, calculateResponsiveSize } = useKoreanTheme({\n size: \"small\",\n isMobile,\n });\n\n const percentage = useMemo(\n () => Math.min(100, Math.max(0, (value / max) * 100)),\n [value, max]\n );\n\n const statBarColors = useMemo(\n () => ({\n barBackground: hexToRgbaString(themeColors.UI_BACKGROUND_MEDIUM, 1),\n barFill: hexToRgbaString(color, 0.9),\n barBorder: hexToRgbaString(color, 0.7),\n labelColor: hexColorToCSS(themeColors.TEXT_SECONDARY),\n valueColor: hexColorToCSS(color),\n }),\n [color, themeColors]\n );\n\n const fontSize = calculateResponsiveSize(isMobile ? 9 : 11);\n const labelWidth = calculateResponsiveSize(isMobile ? 70 : 80);\n const valueWidth = calculateResponsiveSize(isMobile ? 25 : 30);\n\n return (\n <div\n style={{\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${calculateResponsiveSize(12)}px`,\n }}\n data-testid={`stat-bar-${label.split(\"|\")[0].trim()}`}\n >\n {/* Stat label with Korean typography */}\n <div\n style={{\n width: `${labelWidth}px`,\n fontSize: `${fontSize}px`,\n fontFamily: koreanTypography.fontFamily,\n color: statBarColors.labelColor,\n flexShrink: 0,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n lineHeight: koreanTypography.lineHeight,\n letterSpacing: koreanTypography.letterSpacing,\n wordBreak: koreanTypography.wordBreak,\n }}\n data-testid=\"stat-label\"\n >\n {label}\n </div>\n\n {/* Stat bar container */}\n <div\n style={{\n flex: 1,\n height: `${height}px`,\n background: statBarColors.barBackground,\n borderRadius: \"2px\",\n position: \"relative\",\n border: `1px solid ${statBarColors.barBorder}`,\n overflow: \"hidden\",\n }}\n data-testid=\"stat-bar-container\"\n >\n {/* Stat bar fill with smooth transition */}\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: statBarColors.barFill,\n borderRadius: \"2px\",\n transition: \"width 0.3s ease\",\n boxShadow: `0 0 8px ${hexToRgbaString(color, 0.5)}`,\n }}\n data-testid=\"stat-bar-fill\"\n />\n </div>\n\n {/* Stat value */}\n {showValue && (\n <div\n style={{\n width: `${valueWidth}px`,\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: koreanTypography.fontFamily,\n color: statBarColors.valueColor,\n textAlign: \"right\",\n flexShrink: 0,\n }}\n data-testid=\"stat-value\"\n >\n {Math.round(value)}\n </div>\n )}\n </div>\n );\n }\n);\n\nStatBar.displayName = \"StatBar\";\n\nexport default StatBar;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,UAAkC,MAAM,MAClD,EACC,OACA,OACA,MAAM,KACN,QAAQ,cAAc,cACtB,SAAS,IACT,YAAY,MACZ,WAAW,YACP;CACJ,MAAM,EAAE,kBAAkB,QAAQ,aAAa,4BAA4B,eAAe;EACxF,MAAM;EACN;CACF,CAAC;CAED,MAAM,aAAa,cACX,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,QAAQ,MAAO,GAAG,CAAC,GACpD,CAAC,OAAO,GAAG,CACb;CAEA,MAAM,gBAAgB,eACb;EACL,eAAe,gBAAgB,YAAY,sBAAsB,CAAC;EAClE,SAAS,gBAAgB,OAAO,EAAG;EACnC,WAAW,gBAAgB,OAAO,EAAG;EACrC,YAAY,cAAc,YAAY,cAAc;EACpD,YAAY,cAAc,KAAK;CACjC,IACA,CAAC,OAAO,WAAW,CACrB;CAEA,MAAM,WAAW,wBAAwB,WAAW,IAAI,EAAE;CAC1D,MAAM,aAAa,wBAAwB,WAAW,KAAK,EAAE;CAC7D,MAAM,aAAa,wBAAwB,WAAW,KAAK,EAAE;CAE7D,OACE,qBAAC,OAAD;EACE,OAAO;GACL,OAAO;GACP,SAAS;GACT,eAAe;GACf,YAAY;GACZ,KAAK,GAAG,wBAAwB,EAAE,EAAE;EACtC;EACA,eAAa,YAAY,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK;EARpD,UAAA;GAWE,oBAAC,OAAD;IACE,OAAO;KACL,OAAO,GAAG,WAAW;KACrB,UAAU,GAAG,SAAS;KACtB,YAAY,iBAAiB;KAC7B,OAAO,cAAc;KACrB,YAAY;KACZ,YAAY;KACZ,UAAU;KACV,cAAc;KACd,YAAY,iBAAiB;KAC7B,eAAe,iBAAiB;KAChC,WAAW,iBAAiB;IAC9B;IACA,eAAY;IAEX,UAAA;GACE,CAAA;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,MAAM;KACN,QAAQ,GAAG,OAAO;KAClB,YAAY,cAAc;KAC1B,cAAc;KACd,UAAU;KACV,QAAQ,aAAa,cAAc;KACnC,UAAU;IACZ;IACA,eAAY;IAGZ,UAAA,oBAAC,OAAD;KACE,OAAO;MACL,OAAO,GAAG,WAAW;MACrB,QAAQ;MACR,YAAY,cAAc;MAC1B,cAAc;MACd,YAAY;MACZ,WAAW,WAAW,gBAAgB,OAAO,EAAG;KAClD;KACA,eAAY;IACb,CAAA;GACE,CAAA;GAGJ,aACC,oBAAC,OAAD;IACE,OAAO;KACL,OAAO,GAAG,WAAW;KACrB,UAAU,GAAG,SAAS;KACtB,YAAY;KACZ,YAAY,iBAAiB;KAC7B,OAAO,cAAc;KACrB,WAAW;KACX,YAAY;IACd;IACA,eAAY;IAEX,UAAA,KAAK,MAAM,KAAK;GACd,CAAA;EAEJ;;AAET,CACF;AAEA,QAAQ,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhilosophyScreen3D.js","names":[],"sources":["../../../../src/components/screens/philosophy/PhilosophyScreen3D.tsx"],"sourcesContent":["import { Canvas } from \"@react-three/fiber\";\nimport React, { useCallback, useEffect, useMemo } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../systems\";\nimport { KoreanCulture } from \"../../../systems/trigram/KoreanCulture\";\nimport { TRIGRAM_DATA } from \"../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../types\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\nimport { getLayoutConstants } from \"../../../utils/responsiveLayoutHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { BackgroundScene3D } from \"../../shared/three\";\nimport { BackButton, LinkButton } from \"../../shared/ui/BackButton\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\n\nexport interface PhilosophyScreen3DProps {\n readonly onReturnToMenu: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Three.js-based PhilosophyScreen Component\n */\nexport const PhilosophyScreen3D: React.FC<PhilosophyScreen3DProps> = ({\n onReturnToMenu,\n width: propWidth,\n height: propHeight,\n}) => {\n\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in PhilosophyScreen\");\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in PhilosophyScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const { width, height } = useWindowSize();\n\n const screenWidth = propWidth ?? width;\n const screenHeight = propHeight ?? height;\n\n const isMobile = shouldUseMobileControls();\n const isTablet = useMemo(\n () => !isMobile && screenWidth >= 768 && screenWidth < 1024,\n [isMobile, screenWidth],\n );\n const isLargeDesktop = useMemo(\n () => !isMobile && screenWidth >= 1920,\n [isMobile, screenWidth],\n ); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => getLayoutConstants(screenWidth),\n [screenWidth],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n const scrollbarStyle = useMemo(\n () => ({\n __html: `\n .philosophy-scrollbar::-webkit-scrollbar {\n width: 12px !important;\n display: block !important;\n }\n .philosophy-scrollbar::-webkit-scrollbar-track {\n background: ${hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.8)};\n border-radius: 6px;\n }\n .philosophy-scrollbar::-webkit-scrollbar-thumb {\n background: ${hexToRgbaString(theme.colors.ACCENT_GOLD, 1)};\n border-radius: 6px;\n border: 2px solid ${hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.8)};\n }\n .philosophy-scrollbar::-webkit-scrollbar-thumb:hover {\n background: ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)};\n }\n `,\n }),\n [theme],\n );\n\n const colors = useMemo(\n () => ({\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n headerBg: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.9),\n sectionBg: hexToRgbaString(theme.colors.UI_BACKGROUND_LIGHT, 0.8),\n borderGold: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8),\n borderCyan: hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6),\n borderMagenta: hexToRgbaString(theme.colors.SECONDARY_MAGENTA, 0.6),\n borderRed: hexToRgbaString(theme.colors.KOREAN_RED, 0.6),\n textPrimary: `#${theme.colors.TEXT_PRIMARY.toString(16).padStart(6, \"0\")}`,\n textSecondary: `#${theme.colors.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n textTertiary: `#${theme.colors.TEXT_TERTIARY.toString(16).padStart(6, \"0\")}`,\n accentGold: `#${theme.colors.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n accentCyan: `#${theme.colors.PRIMARY_CYAN.toString(16).padStart(6, \"0\")}`,\n }),\n [theme],\n );\n\n useEffect(() => {\n const startMusic = async () => {\n await audio.fadeIn(\"underground_theme\", 2000);\n };\n void startMusic().catch((err) =>\n console.warn(\"Failed to start philosophy music:\", err),\n );\n\n return () => {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop philosophy music:\", err));\n };\n }, [audio]);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" || event.key.toLowerCase() === \"m\") {\n event.preventDefault();\n audio.playSFX(\"menu_back\");\n onReturnToMenu();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown, { passive: false });\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, audio]);\n\n const handleBackClick = useCallback(() => {\n audio.playSFX(\"menu_back\");\n onReturnToMenu();\n }, [audio, onReturnToMenu]);\n\n const handleISMSClick = useCallback(() => {\n audio.playSFX(\"menu_select\");\n window.open(\"https://github.com/Hack23/ISMS-PUBLIC\", \"_blank\");\n }, [audio]);\n\n const martialValues = useMemo(\n () => Object.entries(KoreanCulture.MARTIAL_VALUES),\n [],\n );\n\n const trigramPhilosophies = useMemo(\n () =>\n Object.entries(TRIGRAM_DATA).map(([stance, data]) => ({\n stance: stance as TrigramStance,\n ...data,\n })),\n [],\n );\n\n const archetypes = useMemo(() => Object.entries(PLAYER_ARCHETYPES_DATA), []);\n\n const valuesPerRow = isMobile ? 3 : isTablet ? 4 : isLargeDesktop ? 8 : 6;\n const trigramsPerRow = isMobile ? 2 : isTablet ? 3 : isLargeDesktop ? 5 : 4;\n\n return (\n <div\n style={{\n width: screenWidth,\n height: screenHeight,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"philosophy-screen\"\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: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n dpr={[1, 2]}\n camera={{ position: [0, 5, 10], fov: 75 }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n >\n {/* 3D Background Scene */}\n <BackgroundScene3D theme=\"philosophy\" />\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=\"philosophy-hud-overlay\"\n >\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n color: colors.textPrimary,\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n pointerEvents: \"auto\",\n }}\n >\n {/* Header */}\n <div\n style={{\n height: `${layoutConstants.headerHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: colors.headerBg,\n borderBottom: `3px solid ${colors.borderGold}`,\n padding: `${layoutConstants.padding}px`,\n position: \"relative\",\n }}\n data-testid=\"philosophy-header\"\n >\n <h1\n style={{\n fontSize: isMobile ? \"28px\" : \"36px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: 0,\n textShadow: `0 0 15px ${hexToRgbaString(\n theme.colors.ACCENT_GOLD,\n 0.6,\n )}`,\n }}\n >\n 흑괘 무도 철학\n </h1>\n <p\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: colors.textSecondary,\n margin: \"8px 0 0 0\",\n }}\n >\n Black Trigram Martial Philosophy\n </p>\n\n {/* Decorative line */}\n <div\n style={{\n width: \"80%\",\n height: \"2px\",\n background: `linear-gradient(90deg, transparent, ${colors.borderGold}, transparent)`,\n marginTop: \"10px\",\n }}\n />\n </div>\n\n {/* WebKit Scrollbar Styling - Using !important to override global hide */}\n <style dangerouslySetInnerHTML={scrollbarStyle} />\n\n {/* Content Area - Scrollable */}\n <div\n className=\"philosophy-scrollbar\"\n style={{\n flex: 1,\n overflowY: \"auto\",\n overflowX: \"hidden\",\n padding: `${layoutConstants.padding}px`,\n scrollbarWidth: \"thin\",\n scrollbarColor: `${colors.accentGold} ${colors.sectionBg}`,\n }}\n data-testid=\"philosophy-content\"\n >\n {/* Martial Values Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderRed}`,\n padding: \"20px\",\n }}\n data-testid=\"martial-values\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 무도 가치관 (Martial Values)\n </h2>\n\n {/* Values Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${valuesPerRow}, 1fr)`,\n gap: \"10px\",\n }}\n >\n {martialValues.map(([key, value]) => (\n <div\n key={key}\n style={{\n background: hexToRgbaString(\n theme.colors.UI_BACKGROUND_DARK,\n 0.7,\n ),\n borderRadius: \"6px\",\n border: `1px solid ${colors.borderGold}`,\n padding: \"12px\",\n textAlign: \"center\",\n }}\n data-testid={`martial-value-${key}`}\n >\n <div\n style={{\n fontSize: isMobile ? \"16px\" : \"18px\",\n fontWeight: \"bold\",\n color: colors.textPrimary,\n marginBottom: \"4px\",\n }}\n >\n {value.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.textSecondary,\n }}\n >\n {value.english}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Trigram Philosophy Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderCyan}`,\n padding: \"20px\",\n }}\n data-testid=\"trigram-philosophy\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 팔괘 철학 (Eight Trigrams Philosophy)\n </h2>\n\n {/* Trigrams Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${trigramsPerRow}, 1fr)`,\n gap: \"15px\",\n }}\n >\n {trigramPhilosophies.map((trigram) => (\n <div\n key={trigram.stance}\n style={{\n background: hexToRgbaString(trigram.theme.primary, 0.25),\n borderRadius: \"8px\",\n border: `2px solid #${trigram.theme.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n padding: \"15px\",\n }}\n data-testid={`trigram-${trigram.stance}`}\n >\n {/* Trigram Symbol */}\n <div\n style={{\n fontSize: isMobile ? \"32px\" : \"40px\",\n color: `#${trigram.theme.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n textAlign: \"center\",\n marginBottom: \"10px\",\n }}\n >\n {trigram.symbol}\n </div>\n\n {/* Name with Chinese character */}\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n color: colors.textPrimary,\n textAlign: \"center\",\n marginBottom: \"4px\",\n }}\n >\n {trigram.name.korean} ({trigram.name.english})\n </div>\n\n {/* Chinese character and attribute */}\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.accentGold,\n textAlign: \"center\",\n marginBottom: \"8px\",\n fontWeight: \"bold\",\n }}\n >\n {trigram.chinese} - {trigram.attribute.chinese},{\" \"}\n {trigram.attribute.korean}\n </div>\n\n {/* Core meaning */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: colors.accentCyan,\n textAlign: \"center\",\n marginBottom: \"8px\",\n }}\n >\n {trigram.meaning.korean} | {trigram.meaning.english}\n </div>\n\n {/* Philosophy */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: colors.textSecondary,\n textAlign: \"center\",\n marginBottom: \"6px\",\n }}\n >\n {trigram.philosophy.korean}\n </div>\n\n {/* Combat description */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: colors.textTertiary,\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n >\n {trigram.combat.english}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Archetype Philosophy Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderMagenta}`,\n padding: \"20px\",\n }}\n data-testid=\"archetype-philosophy\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 무사 유형 철학 (Warrior Archetype Philosophy)\n </h2>\n\n {/* Archetypes List */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"12px\",\n }}\n >\n {archetypes.map(([archetype, data]) => (\n <div\n key={archetype}\n style={{\n background: hexToRgbaString(data.colors.primary, 0.2),\n borderRadius: \"6px\",\n border: `1px solid #${data.colors.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n padding: \"15px\",\n }}\n data-testid={`archetype-${archetype}`}\n >\n {/* Name */}\n <div\n style={{\n fontSize: isMobile ? \"16px\" : \"18px\",\n fontWeight: \"bold\",\n color: `#${data.colors.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n marginBottom: \"8px\",\n }}\n >\n {data.name.korean} ({data.name.english})\n </div>\n\n {/* Description */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr\",\n gap: \"10px\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.textPrimary,\n }}\n >\n {data.description.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"12px\",\n color: colors.textSecondary,\n }}\n >\n {data.description.english}\n </div>\n </div>\n\n {/* Specialist tag */}\n <div\n style={{\n marginTop: \"8px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n color: colors.textTertiary,\n }}\n >\n 전통 무예 전문가\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Footer */}\n <div\n style={{\n minHeight: `${layoutConstants.footerHeight}px`,\n background: colors.headerBg,\n borderTop: `3px solid ${colors.borderGold}`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: `${layoutConstants.padding}px`,\n gap: \"15px\",\n }}\n data-testid=\"philosophy-footer\"\n >\n {/* Motivation Quote */}\n <div\n style={{\n background: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.15),\n borderRadius: \"8px\",\n border: `1px solid ${colors.borderGold}`,\n padding: \"15px\",\n textAlign: \"center\",\n maxWidth: \"90%\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n color: colors.accentGold,\n fontStyle: \"italic\",\n marginBottom: \"4px\",\n }}\n >\n 무술은 단순한 격투가 아닌, 자신을 수양하고 상대를 존중하는\n 도(道)입니다\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"12px\",\n color: colors.textSecondary,\n fontStyle: \"italic\",\n }}\n >\n Martial arts is not mere combat, but the Way (道) of\n self-cultivation and respect for others\n </div>\n </div>\n\n {/* ISMS Link */}\n <LinkButton\n onClick={handleISMSClick}\n korean=\"공개 보안 정책\"\n english=\"View Security Policies\"\n icon=\"🔐\"\n isMobile={isMobile}\n testId=\"isms-public-link\"\n />\n\n {/* Action Row */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n width: \"100%\",\n gap: \"15px\",\n }}\n >\n {/* Back Button */}\n <BackButton\n onClick={handleBackClick}\n korean=\"돌아가기\"\n english=\"Return\"\n isMobile={isMobile}\n testId=\"philosophy-back-button\"\n />\n\n {/* Keyboard Hint */}\n <div\n style={{\n background: hexToRgbaString(\n theme.colors.UI_BACKGROUND_MEDIUM,\n 0.9,\n ),\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: `#${theme.colors.SECONDARY_MAGENTA.toString(\n 16,\n ).padStart(6, \"0\")}`,\n border: `1px solid ${colors.borderGold}`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n data-testid=\"keyboard-shortcuts\"\n >\n <span>ESC</span>\n <span>M</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default PhilosophyScreen3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PhilosophyScreen3D.js","names":[],"sources":["../../../../src/components/screens/philosophy/PhilosophyScreen3D.tsx"],"sourcesContent":["import { Canvas } from \"@react-three/fiber\";\nimport React, { useCallback, useEffect, useMemo } from \"react\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PLAYER_ARCHETYPES_DATA } from \"../../../systems\";\nimport { KoreanCulture } from \"../../../systems/trigram/KoreanCulture\";\nimport { TRIGRAM_DATA } from \"../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../types\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport { shouldUseMobileControls } from \"../../../utils/deviceDetection\";\nimport { getLayoutConstants } from \"../../../utils/responsiveLayoutHelpers\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { BackgroundScene3D } from \"../../shared/three\";\nimport { BackButton, LinkButton } from \"../../shared/ui/BackButton\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\n\nexport interface PhilosophyScreen3DProps {\n readonly onReturnToMenu: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Three.js-based PhilosophyScreen Component\n */\nexport const PhilosophyScreen3D: React.FC<PhilosophyScreen3DProps> = ({\n onReturnToMenu,\n width: propWidth,\n height: propHeight,\n}) => {\n\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in PhilosophyScreen\");\n },\n onContextRestored: () => {\n console.log(\"✓ WebGL context restored in PhilosophyScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const { width, height } = useWindowSize();\n\n const screenWidth = propWidth ?? width;\n const screenHeight = propHeight ?? height;\n\n const isMobile = shouldUseMobileControls();\n const isTablet = useMemo(\n () => !isMobile && screenWidth >= 768 && screenWidth < 1024,\n [isMobile, screenWidth],\n );\n const isLargeDesktop = useMemo(\n () => !isMobile && screenWidth >= 1920,\n [isMobile, screenWidth],\n ); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => getLayoutConstants(screenWidth),\n [screenWidth],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n const scrollbarStyle = useMemo(\n () => ({\n __html: `\n .philosophy-scrollbar::-webkit-scrollbar {\n width: 12px !important;\n display: block !important;\n }\n .philosophy-scrollbar::-webkit-scrollbar-track {\n background: ${hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.8)};\n border-radius: 6px;\n }\n .philosophy-scrollbar::-webkit-scrollbar-thumb {\n background: ${hexToRgbaString(theme.colors.ACCENT_GOLD, 1)};\n border-radius: 6px;\n border: 2px solid ${hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.8)};\n }\n .philosophy-scrollbar::-webkit-scrollbar-thumb:hover {\n background: ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)};\n }\n `,\n }),\n [theme],\n );\n\n const colors = useMemo(\n () => ({\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n headerBg: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.9),\n sectionBg: hexToRgbaString(theme.colors.UI_BACKGROUND_LIGHT, 0.8),\n borderGold: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.8),\n borderCyan: hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6),\n borderMagenta: hexToRgbaString(theme.colors.SECONDARY_MAGENTA, 0.6),\n borderRed: hexToRgbaString(theme.colors.KOREAN_RED, 0.6),\n textPrimary: `#${theme.colors.TEXT_PRIMARY.toString(16).padStart(6, \"0\")}`,\n textSecondary: `#${theme.colors.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n textTertiary: `#${theme.colors.TEXT_TERTIARY.toString(16).padStart(6, \"0\")}`,\n accentGold: `#${theme.colors.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n accentCyan: `#${theme.colors.PRIMARY_CYAN.toString(16).padStart(6, \"0\")}`,\n }),\n [theme],\n );\n\n useEffect(() => {\n const startMusic = async () => {\n await audio.fadeIn(\"underground_theme\", 2000);\n };\n void startMusic().catch((err) =>\n console.warn(\"Failed to start philosophy music:\", err),\n );\n\n return () => {\n void audio\n .fadeOut(2000)\n .then(() => audio.stopMusic())\n .catch((err) => console.warn(\"Failed to stop philosophy music:\", err));\n };\n }, [audio]);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" || event.key.toLowerCase() === \"m\") {\n event.preventDefault();\n audio.playSFX(\"menu_back\");\n onReturnToMenu();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown, { passive: false });\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onReturnToMenu, audio]);\n\n const handleBackClick = useCallback(() => {\n audio.playSFX(\"menu_back\");\n onReturnToMenu();\n }, [audio, onReturnToMenu]);\n\n const handleISMSClick = useCallback(() => {\n audio.playSFX(\"menu_select\");\n window.open(\"https://github.com/Hack23/ISMS-PUBLIC\", \"_blank\");\n }, [audio]);\n\n const martialValues = useMemo(\n () => Object.entries(KoreanCulture.MARTIAL_VALUES),\n [],\n );\n\n const trigramPhilosophies = useMemo(\n () =>\n Object.entries(TRIGRAM_DATA).map(([stance, data]) => ({\n stance: stance as TrigramStance,\n ...data,\n })),\n [],\n );\n\n const archetypes = useMemo(() => Object.entries(PLAYER_ARCHETYPES_DATA), []);\n\n const valuesPerRow = isMobile ? 3 : isTablet ? 4 : isLargeDesktop ? 8 : 6;\n const trigramsPerRow = isMobile ? 2 : isTablet ? 3 : isLargeDesktop ? 5 : 4;\n\n return (\n <div\n style={{\n width: screenWidth,\n height: screenHeight,\n position: \"relative\",\n overflow: \"hidden\",\n }}\n data-testid=\"philosophy-screen\"\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: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n dpr={[1, 2]}\n camera={{ position: [0, 5, 10], fov: 75 }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n >\n {/* 3D Background Scene */}\n <BackgroundScene3D theme=\"philosophy\" />\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=\"philosophy-hud-overlay\"\n >\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n color: colors.textPrimary,\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n pointerEvents: \"auto\",\n }}\n >\n {/* Header */}\n <div\n style={{\n height: `${layoutConstants.headerHeight}px`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: colors.headerBg,\n borderBottom: `3px solid ${colors.borderGold}`,\n padding: `${layoutConstants.padding}px`,\n position: \"relative\",\n }}\n data-testid=\"philosophy-header\"\n >\n <h1\n style={{\n fontSize: isMobile ? \"28px\" : \"36px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: 0,\n textShadow: `0 0 15px ${hexToRgbaString(\n theme.colors.ACCENT_GOLD,\n 0.6,\n )}`,\n }}\n >\n 흑괘 무도 철학\n </h1>\n <p\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: colors.textSecondary,\n margin: \"8px 0 0 0\",\n }}\n >\n Black Trigram Martial Philosophy\n </p>\n\n {/* Decorative line */}\n <div\n style={{\n width: \"80%\",\n height: \"2px\",\n background: `linear-gradient(90deg, transparent, ${colors.borderGold}, transparent)`,\n marginTop: \"10px\",\n }}\n />\n </div>\n\n {/* WebKit Scrollbar Styling - Using !important to override global hide */}\n <style dangerouslySetInnerHTML={scrollbarStyle} />\n\n {/* Content Area - Scrollable */}\n <div\n className=\"philosophy-scrollbar\"\n style={{\n flex: 1,\n overflowY: \"auto\",\n overflowX: \"hidden\",\n padding: `${layoutConstants.padding}px`,\n scrollbarWidth: \"thin\",\n scrollbarColor: `${colors.accentGold} ${colors.sectionBg}`,\n }}\n data-testid=\"philosophy-content\"\n >\n {/* Martial Values Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderRed}`,\n padding: \"20px\",\n }}\n data-testid=\"martial-values\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 무도 가치관 (Martial Values)\n </h2>\n\n {/* Values Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${valuesPerRow}, 1fr)`,\n gap: \"10px\",\n }}\n >\n {martialValues.map(([key, value]) => (\n <div\n key={key}\n style={{\n background: hexToRgbaString(\n theme.colors.UI_BACKGROUND_DARK,\n 0.7,\n ),\n borderRadius: \"6px\",\n border: `1px solid ${colors.borderGold}`,\n padding: \"12px\",\n textAlign: \"center\",\n }}\n data-testid={`martial-value-${key}`}\n >\n <div\n style={{\n fontSize: isMobile ? \"16px\" : \"18px\",\n fontWeight: \"bold\",\n color: colors.textPrimary,\n marginBottom: \"4px\",\n }}\n >\n {value.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.textSecondary,\n }}\n >\n {value.english}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Trigram Philosophy Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderCyan}`,\n padding: \"20px\",\n }}\n data-testid=\"trigram-philosophy\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 팔괘 철학 (Eight Trigrams Philosophy)\n </h2>\n\n {/* Trigrams Grid */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${trigramsPerRow}, 1fr)`,\n gap: \"15px\",\n }}\n >\n {trigramPhilosophies.map((trigram) => (\n <div\n key={trigram.stance}\n style={{\n background: hexToRgbaString(trigram.theme.primary, 0.25),\n borderRadius: \"8px\",\n border: `2px solid #${trigram.theme.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n padding: \"15px\",\n }}\n data-testid={`trigram-${trigram.stance}`}\n >\n {/* Trigram Symbol */}\n <div\n style={{\n fontSize: isMobile ? \"32px\" : \"40px\",\n color: `#${trigram.theme.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n textAlign: \"center\",\n marginBottom: \"10px\",\n }}\n >\n {trigram.symbol}\n </div>\n\n {/* Name with Chinese character */}\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n color: colors.textPrimary,\n textAlign: \"center\",\n marginBottom: \"4px\",\n }}\n >\n {trigram.name.korean} ({trigram.name.english})\n </div>\n\n {/* Chinese character and attribute */}\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.accentGold,\n textAlign: \"center\",\n marginBottom: \"8px\",\n fontWeight: \"bold\",\n }}\n >\n {trigram.chinese} - {trigram.attribute.chinese},{\" \"}\n {trigram.attribute.korean}\n </div>\n\n {/* Core meaning */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: colors.accentCyan,\n textAlign: \"center\",\n marginBottom: \"8px\",\n }}\n >\n {trigram.meaning.korean} | {trigram.meaning.english}\n </div>\n\n {/* Philosophy */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: colors.textSecondary,\n textAlign: \"center\",\n marginBottom: \"6px\",\n }}\n >\n {trigram.philosophy.korean}\n </div>\n\n {/* Combat description */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: colors.textTertiary,\n textAlign: \"center\",\n fontStyle: \"italic\",\n }}\n >\n {trigram.combat.english}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Archetype Philosophy Section */}\n <div\n style={{\n marginBottom: `${layoutConstants.sectionSpacing}px`,\n background: colors.sectionBg,\n borderRadius: \"10px\",\n border: `2px solid ${colors.borderMagenta}`,\n padding: \"20px\",\n }}\n data-testid=\"archetype-philosophy\"\n >\n <h2\n style={{\n fontSize: isMobile ? \"18px\" : \"22px\",\n fontWeight: \"bold\",\n color: colors.accentGold,\n margin: \"0 0 20px 0\",\n }}\n >\n 무사 유형 철학 (Warrior Archetype Philosophy)\n </h2>\n\n {/* Archetypes List */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"12px\",\n }}\n >\n {archetypes.map(([archetype, data]) => (\n <div\n key={archetype}\n style={{\n background: hexToRgbaString(data.colors.primary, 0.2),\n borderRadius: \"6px\",\n border: `1px solid #${data.colors.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n padding: \"15px\",\n }}\n data-testid={`archetype-${archetype}`}\n >\n {/* Name */}\n <div\n style={{\n fontSize: isMobile ? \"16px\" : \"18px\",\n fontWeight: \"bold\",\n color: `#${data.colors.primary\n .toString(16)\n .padStart(6, \"0\")}`,\n marginBottom: \"8px\",\n }}\n >\n {data.name.korean} ({data.name.english})\n </div>\n\n {/* Description */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr\",\n gap: \"10px\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"13px\",\n color: colors.textPrimary,\n }}\n >\n {data.description.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"12px\",\n color: colors.textSecondary,\n }}\n >\n {data.description.english}\n </div>\n </div>\n\n {/* Specialist tag */}\n <div\n style={{\n marginTop: \"8px\",\n fontSize: isMobile ? \"9px\" : \"11px\",\n color: colors.textTertiary,\n }}\n >\n 전통 무예 전문가\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Footer */}\n <div\n style={{\n minHeight: `${layoutConstants.footerHeight}px`,\n background: colors.headerBg,\n borderTop: `3px solid ${colors.borderGold}`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: `${layoutConstants.padding}px`,\n gap: \"15px\",\n }}\n data-testid=\"philosophy-footer\"\n >\n {/* Motivation Quote */}\n <div\n style={{\n background: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.15),\n borderRadius: \"8px\",\n border: `1px solid ${colors.borderGold}`,\n padding: \"15px\",\n textAlign: \"center\",\n maxWidth: \"90%\",\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n color: colors.accentGold,\n fontStyle: \"italic\",\n marginBottom: \"4px\",\n }}\n >\n 무술은 단순한 격투가 아닌, 자신을 수양하고 상대를 존중하는\n 도(道)입니다\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"12px\",\n color: colors.textSecondary,\n fontStyle: \"italic\",\n }}\n >\n Martial arts is not mere combat, but the Way (道) of\n self-cultivation and respect for others\n </div>\n </div>\n\n {/* ISMS Link */}\n <LinkButton\n onClick={handleISMSClick}\n korean=\"공개 보안 정책\"\n english=\"View Security Policies\"\n icon=\"🔐\"\n isMobile={isMobile}\n testId=\"isms-public-link\"\n />\n\n {/* Action Row */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n width: \"100%\",\n gap: \"15px\",\n }}\n >\n {/* Back Button */}\n <BackButton\n onClick={handleBackClick}\n korean=\"돌아가기\"\n english=\"Return\"\n isMobile={isMobile}\n testId=\"philosophy-back-button\"\n />\n\n {/* Keyboard Hint */}\n <div\n style={{\n background: hexToRgbaString(\n theme.colors.UI_BACKGROUND_MEDIUM,\n 0.9,\n ),\n borderRadius: \"6px\",\n padding: \"8px 12px\",\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: `#${theme.colors.SECONDARY_MAGENTA.toString(\n 16,\n ).padStart(6, \"0\")}`,\n border: `1px solid ${colors.borderGold}`,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n }}\n data-testid=\"keyboard-shortcuts\"\n >\n <span>ESC</span>\n <span>M</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default PhilosophyScreen3D;\n"],"mappings":"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"}
|
|
@@ -229,7 +229,7 @@ var TrainingScreen3D = ({ onPlayerUpdate, onReturnToMenu, width = 1200, height =
|
|
|
229
229
|
const pendingAttackRef = useRef(null);
|
|
230
230
|
const handleDummyHitRef = useRef(() => false);
|
|
231
231
|
const playerAnimationRef = useRef(null);
|
|
232
|
-
const
|
|
232
|
+
const playerAnimationEvents = useMemo(() => ({
|
|
233
233
|
onFrame: (frame, state) => {
|
|
234
234
|
if (state === "attack" && frame === 6 && pendingAttackRef.current) {
|
|
235
235
|
const attackData = pendingAttackRef.current;
|
|
@@ -247,7 +247,8 @@ var TrainingScreen3D = ({ onPlayerUpdate, onReturnToMenu, width = 1200, height =
|
|
|
247
247
|
if (currentStance && playerAnimationRef.current) playerAnimationRef.current.transitionToStanceGuard(currentStance);
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
|
-
}), [playStanceChangeSound, trainingState.currentStanceIndex])
|
|
250
|
+
}), [playStanceChangeSound, trainingState.currentStanceIndex]);
|
|
251
|
+
const playerAnimation = usePlayerAnimation({ events: playerAnimationEvents });
|
|
251
252
|
useEffect(() => {
|
|
252
253
|
playerAnimationRef.current = playerAnimation;
|
|
253
254
|
}, [playerAnimation]);
|
|
@@ -354,6 +355,12 @@ var TrainingScreen3D = ({ onPlayerUpdate, onReturnToMenu, width = 1200, height =
|
|
|
354
355
|
handleAttackRef.current?.();
|
|
355
356
|
}, [trainingActions])
|
|
356
357
|
});
|
|
358
|
+
const selectedTechniqueId = useMemo(() => {
|
|
359
|
+
const techniques = techniqueSelection.availableTechniques;
|
|
360
|
+
const selectedIdx = techniqueSelection.selectedIndex;
|
|
361
|
+
if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) return;
|
|
362
|
+
return techniques[selectedIdx]?.id;
|
|
363
|
+
}, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]);
|
|
357
364
|
const { handleStartTraining, handleStopTraining, handleDummyHit, handleDummyDefeated, handleStanceChange, handleAttack } = useTrainingActions({
|
|
358
365
|
state: trainingState,
|
|
359
366
|
actions: trainingActions,
|
|
@@ -366,12 +373,7 @@ var TrainingScreen3D = ({ onPlayerUpdate, onReturnToMenu, width = 1200, height =
|
|
|
366
373
|
audio,
|
|
367
374
|
playBoneImpactSound,
|
|
368
375
|
playAttackSound,
|
|
369
|
-
selectedTechniqueId
|
|
370
|
-
const techniques = techniqueSelection.availableTechniques;
|
|
371
|
-
const selectedIdx = techniqueSelection.selectedIndex;
|
|
372
|
-
if (techniques.length === 0 || selectedIdx < 0 || selectedIdx >= techniques.length) return;
|
|
373
|
-
return techniques[selectedIdx]?.id;
|
|
374
|
-
}, [techniqueSelection.availableTechniques, techniqueSelection.selectedIndex]),
|
|
376
|
+
selectedTechniqueId,
|
|
375
377
|
onPlayerUpdate: (updates) => {
|
|
376
378
|
onPlayerUpdate(updates);
|
|
377
379
|
},
|
|
@@ -518,7 +520,6 @@ var TrainingScreen3D = ({ onPlayerUpdate, onReturnToMenu, width = 1200, height =
|
|
|
518
520
|
case "two-finger-tap":
|
|
519
521
|
trainingActions.setTrainingMode(trainingState.trainingMode === "vital_point" ? "basics" : "vital_point");
|
|
520
522
|
audio.playSFX("menu_select");
|
|
521
|
-
break;
|
|
522
523
|
}
|
|
523
524
|
}, [
|
|
524
525
|
trainingState,
|