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.
Files changed (216) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioManager.js +2 -4
  3. package/lib/audio/AudioManager.js.map +1 -1
  4. package/lib/components/screens/combat/CombatScreen3D.js +8 -9
  5. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  6. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  7. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  8. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  9. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  10. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  11. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  12. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  13. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js +1 -1
  15. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  16. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  17. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js +2 -1
  18. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/InternalDamage3D.js +3 -1
  20. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  23. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js +16 -15
  24. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  26. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  27. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  28. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  29. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  30. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  31. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  32. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  33. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  34. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  36. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  37. package/lib/components/screens/combat/hooks/useAICombat.js +0 -1
  38. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  39. package/lib/components/screens/combat/hooks/useCombatActions.js +7 -6
  40. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  41. package/lib/components/screens/combat/hooks/useCombatLayout.js +3 -1
  42. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  43. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  44. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  45. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js +7 -6
  46. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  47. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  48. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js +34 -30
  49. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  51. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  52. package/lib/components/screens/endscreen/EndScreen3D.js +2 -2
  53. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  54. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js +1 -1
  55. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  56. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  57. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  60. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js +2 -2
  61. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  62. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js +2 -1
  63. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  64. package/lib/components/screens/intro/IntroScreen3D.js +2 -4
  65. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  66. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  67. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  72. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  74. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  75. package/lib/components/screens/training/TrainingScreen3D.js +10 -9
  76. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  77. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js +14 -13
  78. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  80. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  81. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  83. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  84. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  86. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  87. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  88. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/VitalPointMarker3D.js +8 -7
  90. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  91. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js +15 -13
  92. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  93. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  94. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  95. package/lib/components/screens/training/components/hud/TrainingRightHUD.js +3 -2
  96. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  97. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  98. package/lib/components/screens/training/hooks/useTrainingActions.js +1 -2
  99. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  100. package/lib/components/screens/training/hooks/useTrainingLayout.js +3 -1
  101. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  102. package/lib/components/shared/base/BaseButton.js.map +1 -1
  103. package/lib/components/shared/base/BaseButtonOverlayHtml.js +59 -53
  104. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  105. package/lib/components/shared/base/BasePanel.js +19 -18
  106. package/lib/components/shared/base/BasePanel.js.map +1 -1
  107. package/lib/components/shared/base/BaseText.js.map +1 -1
  108. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  109. package/lib/components/shared/mobile/GestureRecognizerPure.js +22 -18
  110. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  111. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  112. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  113. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  114. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js +4 -5
  115. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  116. package/lib/components/shared/three/anatomy/BoneClothing.js +29 -24
  117. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  118. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  122. package/lib/components/shared/three/effects/ActionFeedback.js +3 -2
  123. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  124. package/lib/components/shared/three/effects/DamageNumbers.js +2 -1
  125. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  126. package/lib/components/shared/three/effects/HitEffects3D.js +3 -2
  127. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  128. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  129. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  131. package/lib/components/shared/three/effects/VitalPointMarkers3D.js +6 -5
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  134. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  135. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  136. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  137. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  138. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  139. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  140. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  141. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  142. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  143. package/lib/components/shared/three/ui/BreathingIndicator2.js +3 -1
  144. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  145. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  146. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  147. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  149. package/lib/components/shared/three/ui/PlayerHUD.js +2 -1
  150. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  151. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  152. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  153. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  154. package/lib/components/shared/three/ui/TechniqueBar.js +25 -24
  155. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/TechniqueCard.js +1 -3
  157. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  158. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  159. package/lib/components/shared/ui/BackButton.js +2 -1
  160. package/lib/components/shared/ui/BackButton.js.map +1 -1
  161. package/lib/components/shared/ui/BaseHUDContainer.js +69 -68
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/hooks/useKeyboardControls.js +0 -1
  173. package/lib/hooks/useKeyboardControls.js.map +1 -1
  174. package/lib/hooks/useSkeletalAnimation.js +12 -3
  175. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  176. package/lib/systems/CombatSystem.js +6 -2
  177. package/lib/systems/CombatSystem.js.map +1 -1
  178. package/lib/systems/PlayerEffectManager.js +0 -1
  179. package/lib/systems/PlayerEffectManager.js.map +1 -1
  180. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  181. package/lib/systems/ai/DecisionTree.js +10 -21
  182. package/lib/systems/ai/DecisionTree.js.map +1 -1
  183. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  184. package/lib/systems/animation/systems/AdvancedJointMovements.js +1 -3
  185. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  186. package/lib/systems/animation/systems/MuscleActivation.js +3 -4
  187. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  188. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  189. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js +2 -1
  190. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  191. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  192. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  193. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  194. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  195. package/lib/systems/combat/GrappleSystem.js +3 -9
  196. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  197. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  198. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  199. package/lib/systems/movement/InjuryMovementModifier.js +1 -1
  200. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  201. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js +8 -5
  203. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  204. package/lib/types/facial.js.map +1 -1
  205. package/lib/utils/accessibility.js +0 -1
  206. package/lib/utils/accessibility.js.map +1 -1
  207. package/lib/utils/effectUtils.js +1 -3
  208. package/lib/utils/effectUtils.js.map +1 -1
  209. package/lib/utils/inputSystem.js +0 -2
  210. package/lib/utils/inputSystem.js.map +1 -1
  211. package/lib/utils/koreanThemeHelpers.js +6 -3
  212. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  213. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  214. package/lib/utils/playerUtils.js +4 -2
  215. package/lib/utils/playerUtils.js.map +1 -1
  216. 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":";;;;;;;;;;;;;AAgDA,IAAa,8BACX,MAAM,MACH,EACC,YACA,eACA,mBACA,WACA,QAAQ,KACR,SAAS,KACT,UAAU,YAAY,YAClB;CAEJ,MAAM,oBAAoB,WAAW;CAErC,MAAM,gBAAgB,QAAQ;CAC9B,MAAM,mBAAmB,SAAS;CAClC,MAAM,iBAAiB,gBAAgB,MAAM,mBAAmB,MAAM;CACtE,MAAM,kBAAkB,gBACpB,MACA,mBACE,MACA;CACN,MAAM,mBAAmB,gBAAgB,KAAK,mBAAmB,KAAK;CACtE,MAAM,aAAa,gBAAgB,KAAK,mBAAmB,IAAI;CAC/D,MAAM,UAAU,gBAAgB,IAAI,mBAAmB,IAAI;CAC3D,MAAM,gBAAgB,gBAAgB,KAAK,mBAAmB,KAAK;CACnE,MAAM,qBAAqB,gBACvB,KACA,mBACE,KACA;CACN,MAAM,gBAAgB,gBAAgB,KAAK,mBAAmB,KAAK;CAEnE,MAAM,iBAAiB,kBAAkB;EAGvC,kBADE,kBAAkB,IAAI,WAAW,SAAS,IAAI,gBAAgB,CACtC;EAC1B,UAAU,YAAY;CACxB,GAAG;EAAC;EAAe,WAAW;EAAQ;EAAmB;CAAS,CAAC;CAEnE,MAAM,aAAa,kBAAkB;EAEnC,mBADkB,gBAAgB,KAAK,WAAW,MACxB;EAC1B,UAAU,YAAY;CACxB,GAAG;EAAC;EAAe,WAAW;EAAQ;EAAmB;CAAS,CAAC;CAEnE,MAAM,cAAc,cAAc;EAChC,MAAM,eAAe;EACrB,OAAO;GACL;IACE,QAAQ;IACR,SAAS;IACT,OAAO,kBAAkB,MAAM,cAAc;IAC7C,UAAU,kBAAkB,MAAM;GACpC;GACA;IACE,QAAQ;IACR,SAAS;IACT,OAAO,kBAAkB,MAAM,UAAU;IACzC,UAAU,kBAAkB,MAAM;GACpC;GACA;IACE,QAAQ;IACR,SAAS;IACT,OAAO,kBAAkB,MAAM,QAAQ;IACvC,UAAU,kBAAkB,MAAM;GACpC;GACA;IACE,QAAQ;IACR,SAAS;IACT,OAAO,kBAAkB,MAAM,YAAY;IAC3C,UAAU,kBAAkB,MAAM;GACpC;EACF;CACF,GAAG,CAAC,kBAAkB,KAAK,CAAC;CAE5B,MAAM,SAAS,eACN;EACL,gBAAgB,IAAI,kBAAkB,MACnC,SAAS,EAAE,CAAC,CACZ,SAAS,GAAG,GAAG;EAClB,WAAW,IAAI,cAAc,YAAY,SAAS,EAAE,CAAC,CAAC,SACpD,GACA,GACF;EACA,iBAAiB,gBACf,cAAc,sBACd,EACF;EACA,aAAa,gBAAgB,cAAc,cAAc,EAAG;EAC5D,mBAAmB,gBACjB,cAAc,sBACd,CACF;EACA,aAAa,gBAAgB,kBAAkB,OAAO,EAAG;CAC3D,IACA,CAAC,kBAAkB,KAAK,CAC1B;CAEA,MAAM,iBAAiB,eACd;EACL,GAAG,+BAA+B;GAChC,SAAS;GACT,eAAe;GACf,iBAAiB;GACjB,qBAAqB;GACrB,aAAa;EACf,CAAC;EACD,OAAO,GAAG,MAAM;EAChB,QAAQ,GAAG,OAAO;EAClB,SAAS;EACT,eAAe;EACf,YAAY;EACZ,gBAAgB;EAChB,KAAK,GAAG,WAAW;EACnB,QAAQ,aAAa,OAAO;EAC5B,SAAS,GAAG,iBAAiB;EAC7B,UAAU;EACV,UAAU;CACZ,IACA;EAAC;EAAO;EAAQ;EAAY;EAAkB,OAAO;CAAc,CACrE;CAEA,MAAM,qBAAqB,cAAc;EACvC,OAAO,6BAA6B,kBAAkB,WAAW;CACnE,GAAG,CAAC,kBAAkB,UAAU,CAAC;CAEjC,OACE,qBAAC,OAAD;EAAK,OAAO;EAAgB,eAAY;YAAxC,CAEE,qBAAC,OAAD;GACE,OAAO;IACL,OAAO,GAAG,iBAAiB,GAAG;IAC9B,SAAS;IACT,eAAe;IACf,YAAY;IACZ,gBAAgB;IAChB,KAAK,GAAG,QAAQ;IAChB,YAAY;GACd;GACA,eAAY;aAVd,CAaE,oBAAC,OAAD;IACE,OAAO;KACL,OAAO,GAAG,iBAAiB,GAAG;KAC9B,QAAQ,GAAG,kBAAkB,GAAG;KAChC,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,UAAU;KACV,YAAY,2BAA2B,OAAO,eAAe;KAC7D,cAAc;KACd,QAAQ,aAAa,OAAO;IAC9B;IACA,eAAY;cAEZ,oBAAC,OAAD;KACE,KAAK;KACL,KAAK,GAAG,kBAAkB,OAAO,KAAK,kBAAkB;KACxD,OAAO;MACL,OAAO,GAAG,eAAe;MACzB,QAAQ,GAAG,gBAAgB;MAC3B,WAAW;MACX,QAAQ;KACV;KACA,SAAS;KACT,YAAY,MAAM;MAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;OACtC,EAAE,eAAe;OACjB,WAAW;MACb;KACF;KACA,UAAU;KACV,MAAK;KACL,cAAY,GAAG,kBAAkB,OAAO,GAAG,kBAAkB,QAAQ;KACrE,eAAY;KACZ,UAAU,MAAM;MACd,MAAM,SAAS,EAAE;MACjB,IAAI,CAAC,OAAO,IAAI,SAAA,2CAAiC,GAAG;OAClD,OAAO,MAAM;OACb,OAAO,MAAM,GAAG,kBAAkB,OAAO;MAC3C;KACF;IACD,CAAA;GACE,CAAA,GAGL,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,KAAK;KACL,OAAO;IACT;IACA,eAAY;cAPd,CASE,oBAAC,UAAD;KACE,SAAS;KACT,cAAW;KACX,OAAO;MACL,MAAM;MACN,QAAQ;MACR,UAAU;MACV,YAAY;MACZ,OAAO,IAAI,cAAc,aAAa,SAAS,EAAE,CAAC,CAAC,SACjD,GACA,GACF;MACA,YAAY,OAAO;MACnB,QAAQ,aAAa,gBACnB,cAAc,aACd,EACF;MACA,cAAc;MACd,QAAQ;KACV;KACA,UAAU,MAAM;MACd,EAAE,cAAc,MAAM,UAAU,aAAa,gBAAgB,cAAc,WAAW;MACtF,EAAE,cAAc,MAAM,gBAAgB;KACxC;KACA,SAAS,MAAM;MACb,EAAE,cAAc,MAAM,UAAU;KAClC;KACA,eAAY;eACb;IAEO,CAAA,GACR,oBAAC,UAAD;KACE,SAAS;KACT,cAAW;KACX,OAAO;MACL,MAAM;MACN,QAAQ;MACR,UAAU;MACV,YAAY;MACZ,OAAO,IAAI,cAAc,aAAa,SAAS,EAAE,CAAC,CAAC,SACjD,GACA,GACF;MACA,YAAY,OAAO;MACnB,QAAQ,aAAa,gBACnB,cAAc,aACd,EACF;MACA,cAAc;MACd,QAAQ;KACV;KACA,UAAU,MAAM;MACd,EAAE,cAAc,MAAM,UAAU,aAAa,gBAAgB,cAAc,WAAW;MACtF,EAAE,cAAc,MAAM,gBAAgB;KACxC;KACA,SAAS,MAAM;MACb,EAAE,cAAc,MAAM,UAAU;KAClC;KACA,eAAY;eACb;IAEO,CAAA,CACL;KACF;MAGL,qBAAC,OAAD;GACE,OAAO;IACL,MAAM;IACN,SAAS;IACT,eAAe;IACf,YAAY;IACZ,gBAAgB;IAChB,KAAK,GAAG,QAAQ;IAChB,UAAU;IACV,UAAU;GACZ;GACA,eAAY;aAXd;IAcE,qBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,SAAS;MACT,eAAe;MACf,YAAY;MACZ,gBAAgB;KAClB;eAPF,CASE,qBAAC,OAAD;MACE,OAAO;OACL,UAAU,GAAG,cAAc;OAC3B,YAAY;OACZ,YAAY,YAAY;OACxB,OAAO,OAAO;OACd,YAAY,kBACV,kBAAkB,OAClB,QACF;OACA,YAAY,oBAAoB,OAAO,QAAQ;MACjD;MACA,eAAY;gBAZd;OAcG,kBAAkB;OAAO;OAAI,kBAAkB;MAC7C;SACL,qBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,YAAY;OACZ,YAAY,YAAY;OACxB,OAAO,OAAO;OACd,YAAY,kBACV,kBAAkB,OAClB,QACF;MACF;MACA,eAAY;gBAXd;OAaG,gBAAgB;OAAE;OAAI,WAAW;MAC/B;OACF;;IAGL,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,mBAAmB;MAChC,WAAW;MACX,YAAY,YAAY;MACxB,OAAO,IAAI,cAAc,eAAe,SAAS,EAAE,CAAC,CAAC,SACnD,GACA,GACF;MACA,YAAY;MACZ,GAAG,0BAA0B,oBAAoB,QAAQ;MACzD,YAAY,oBAAoB,OAAO,QAAQ;KACjD;KACA,eAAY;eAbd;MAeG,kBAAkB,WAAW;MAAO;MAAG;MACvC,kBAAkB,WAAW;KAC3B;;IAGL,qBAAC,OAAD;KACE,OAAO;MACL,OAAO;MACP,SAAS;MACT,eAAe;MACf,KAAK;KACP;KACA,eAAY;eAPd,CASE,oBAAC,OAAD;MACE,OAAO;OACL,UAAU,gBAAgB,SAAS;OACnC,YAAY;OACZ,YAAY,YAAY;OACxB,OAAO,IAAI,cAAc,YAAY,SAAS,EAAE,CAAC,CAAC,SAChD,GACA,GACF;MACF;gBACD;KAEI,CAAA,GAGJ,YAAY,KAAK,SAChB,oBAAC,SAAD;MAEE,OAAO,GAAG,KAAK,OAAO,KAAK,KAAK;MAChC,OAAO,KAAK;MACZ,KAAK;MACL,OAAO,kBAAkB;MACzB,QAAQ;MACR,WAAW;MACX,UAAU;KACX,GARM,KAAK,MAQX,CACF,CACE;;GACF;IACF;;AAET,CACF;AAEF,4BAA4B,cAAc"}
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;YAPd,CAUG,qBAAqB,CAAC,iBACrB,oBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,gBAAgB;IAChB,OAAO;GACT;aAEA,oBAAC,uBAAD;IACE,QAAQ,aAAa,YAAY,UAAU;IAC3C,SAAS,aAAa,YAAY,kBAAkB;IACpD,SAAS;IACT,SAAQ;IACR,MAAK;IACL,QAAO;IACP,WAAW,UAAU,aAAa,YAAY,aAAa,UAAU;GACtE,CAAA;EACE,CAAA,GAIN,aAAa,YACZ,oBAAC,6BAAD;GACc;GACG;GACI;GACR;GACJ;GACC;GACE;EACX,CAAA,IAED,oBAAC,mBAAD;GACE,YAAY;GACZ,mBAAmB;GACnB,mBAAmB;GACR;GACJ;GACP,QAAQ;GACE;EACX,CAAA,CAEA;;AAET,CACF;AAEF,yBAAyB,cAAc"}
1
+ {"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;YAEX,UAAU,KAAK,MAAM,UAAU;GAC9B,MAAM,aAAa,kBAAkB;GACrC,MAAM,YAAY,iBAAiB;GAEnC,MAAM,gBAAgB,2BAA2B;IAC/C,SAAS;IACT;IACA,WAAW;IACX,WAAW;IACX,eAAe,aACX,WACA,YACE,WACA;IACN,gBAAgB;GAClB,CAAC;GAED,OACE,qBAAC,UAAD;IAEE,eAAe,kBAAkB,KAAK,IAAI;IAC1C,oBAAoB,kBAAkB,OAAO,IAAI;IACjD,oBAAoB,kBAAkB,OAAO,KAAK;IAClD,UAAU,MAAM;KACd,EAAE,cAAc,MAAM,UAAU,aAAa,gBAAgB,cAAc,WAAW;KACtF,EAAE,cAAc,MAAM,gBAAgB;IACxC;IACA,SAAS,MAAM;KACb,EAAE,cAAc,MAAM,UAAU;IAClC;IACA,cAAY,GAAG,KAAK,OAAO,IAAI,KAAK,QAAQ;IAC5C,iBAAe;IACf,MAAK;IACL,OAAO;KACL,GAAG;KACH,GAAG,0BACD,gBACA,aAAa,SAAS,QACxB;KACA,YAAY,YAAY;KACxB,OAAO;KACP,QAAQ,GAAG,aAAa;KACxB,OAAO,aACH,OAAO,eACP,YACE,OAAO,cACP,OAAO;KACb,YAAY,aACR,OAAO,mBACP,YACE,OAAO,kBACP,OAAO;KACb,QAAQ,aACJ,aAAa,OAAO,yBACpB,YACE,aAAa,OAAO,wBACpB,aAAa,OAAO;KAC1B,QAAQ;IACV;IACA,eAAa,aAAa,KAAK;cAzCjC;KA4CG,KAAK,SAAS,SAAS,YACtB,oBAAC,QAAD;MACE,eAAY;MACZ,OAAO,EAAE,SAAS,OAAO;KAC1B,CAAA;KAEF,KAAK,SAAS,SAAS,UACtB,oBAAC,QAAD;MAAM,eAAY;MAAgB,OAAO,EAAE,SAAS,OAAO;KAAI,CAAA;KAEhE,KAAK;KAAO;KAAG,KAAK;KAAQ;IACvB;MArDD,KAAK,IAqDJ;EAEZ,CAAC;CACE,CAAA;AAET"}
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;EAEX,UAAA,UAAU,KAAK,MAAM,UAAU;GAC9B,MAAM,aAAa,kBAAkB;GACrC,MAAM,YAAY,iBAAiB;GAEnC,MAAM,gBAAgB,2BAA2B;IAC/C,SAAS;IACT;IACA,WAAW;IACX,WAAW;IACX,eAAe,aACX,WACA,YACE,WACA;IACN,gBAAgB;GAClB,CAAC;GAED,OACE,qBAAC,UAAD;IAEE,eAAe,kBAAkB,KAAK,IAAI;IAC1C,oBAAoB,kBAAkB,OAAO,IAAI;IACjD,oBAAoB,kBAAkB,OAAO,KAAK;IAClD,UAAU,MAAM;KACd,EAAE,cAAc,MAAM,UAAU,aAAa,gBAAgB,cAAc,WAAW;KACtF,EAAE,cAAc,MAAM,gBAAgB;IACxC;IACA,SAAS,MAAM;KACb,EAAE,cAAc,MAAM,UAAU;IAClC;IACA,cAAY,GAAG,KAAK,OAAO,IAAI,KAAK,QAAQ;IAC5C,iBAAe;IACf,MAAK;IACL,OAAO;KACL,GAAG;KACH,GAAG,0BACD,gBACA,aAAa,SAAS,QACxB;KACA,YAAY,YAAY;KACxB,OAAO;KACP,QAAQ,GAAG,aAAa;KACxB,OAAO,aACH,OAAO,eACP,YACE,OAAO,cACP,OAAO;KACb,YAAY,aACR,OAAO,mBACP,YACE,OAAO,kBACP,OAAO;KACb,QAAQ,aACJ,aAAa,OAAO,yBACpB,YACE,aAAa,OAAO,wBACpB,aAAa,OAAO;KAC1B,QAAQ;IACV;IACA,eAAa,aAAa,KAAK;IAzCjC,UAAA;KA4CG,KAAK,SAAS,SAAS,YACtB,oBAAC,QAAD;MACE,eAAY;MACZ,OAAO,EAAE,SAAS,OAAO;KAC1B,CAAA;KAEF,KAAK,SAAS,SAAS,UACtB,oBAAC,QAAD;MAAM,eAAY;MAAgB,OAAO,EAAE,SAAS,OAAO;KAAI,CAAA;KAEhE,KAAK;KAAO;KAAG,KAAK;KAAQ;IACvB;GArDD,GAAA,KAAK,IAqDJ;EAEZ,CAAC;CACE,CAAA;AAET"}
@@ -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;YARpD;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;cAEX;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;cAGZ,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;cAEX,KAAK,MAAM,KAAK;GACd,CAAA;EAEJ;;AAET,CACF;AAEA,QAAQ,cAAc"}
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 playerAnimation = usePlayerAnimation({ events: useMemo(() => ({
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: useMemo(() => {
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,