blacktrigram 0.7.98 → 0.7.100

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 +14 -14
@@ -155,47 +155,51 @@ var InteractiveControlDemo = ({ pressedKeys, isMobile }) => {
155
155
  const age = currentTime - timestamp;
156
156
  return Math.max(0, 1 - age / 2e3);
157
157
  };
158
- return /* @__PURE__ */ jsxs("div", {
159
- style: useMemo(() => ({
160
- position: "fixed",
161
- bottom: isMobile ? "15px" : "20px",
162
- left: "50%",
163
- transform: "translateX(-50%)",
164
- display: "flex",
165
- flexDirection: "column",
166
- gap: isMobile ? "8px" : "10px",
167
- maxWidth: isMobile ? "90%" : "600px",
168
- width: "100%",
169
- padding: isMobile ? "12px" : "15px",
170
- background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, .95),
171
- borderRadius: "12px",
172
- border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, .6)}`,
173
- boxShadow: `
158
+ const containerStyle = useMemo(() => ({
159
+ position: "fixed",
160
+ bottom: isMobile ? "15px" : "20px",
161
+ left: "50%",
162
+ transform: "translateX(-50%)",
163
+ display: "flex",
164
+ flexDirection: "column",
165
+ gap: isMobile ? "8px" : "10px",
166
+ maxWidth: isMobile ? "90%" : "600px",
167
+ width: "100%",
168
+ padding: isMobile ? "12px" : "15px",
169
+ background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, .95),
170
+ borderRadius: "12px",
171
+ border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, .6)}`,
172
+ boxShadow: `
174
173
  0 4px 20px ${hexToRgbaString(theme.colors.BLACK_SOLID, .5)},
175
174
  0 0 15px ${hexToRgbaString(theme.colors.PRIMARY_CYAN, .3)}
176
175
  `,
177
- pointerEvents: "none",
178
- zIndex: 1e3
179
- }), [isMobile, theme]),
176
+ pointerEvents: "none",
177
+ zIndex: 1e3
178
+ }), [isMobile, theme]);
179
+ const titleStyle = useMemo(() => ({
180
+ fontFamily: FONT_FAMILY.KOREAN,
181
+ fontSize: isMobile ? "12px" : "13px",
182
+ fontWeight: "bold",
183
+ color: hexToRgbaString(theme.colors.ACCENT_GOLD),
184
+ textAlign: "center",
185
+ marginBottom: "4px",
186
+ textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, .5)}`
187
+ }), [isMobile, theme]);
188
+ return /* @__PURE__ */ jsxs("div", {
189
+ style: containerStyle,
180
190
  "data-testid": "interactive-demo",
181
191
  children: [
182
192
  /* @__PURE__ */ jsx("div", {
183
- style: useMemo(() => ({
184
- fontFamily: FONT_FAMILY.KOREAN,
185
- fontSize: isMobile ? "12px" : "13px",
186
- fontWeight: "bold",
187
- color: hexToRgbaString(theme.colors.ACCENT_GOLD),
188
- textAlign: "center",
189
- marginBottom: "4px",
190
- textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, .5)}`
191
- }), [isMobile, theme]),
193
+ style: titleStyle,
192
194
  children: "최근 입력 | Recent Input"
193
195
  }),
194
196
  keyPresses.map((entry) => {
197
+ const opacity = getOpacity(entry.timestamp);
198
+ const categoryColor = getKeyCategoryColor(entry.keyData.category);
195
199
  return /* @__PURE__ */ jsx(KeyPressEntryDisplay, {
196
200
  entry,
197
- opacity: getOpacity(entry.timestamp),
198
- categoryColor: getKeyCategoryColor(entry.keyData.category),
201
+ opacity,
202
+ categoryColor,
199
203
  isMobile,
200
204
  theme
201
205
  }, entry.id);
@@ -1 +1 @@
1
- {"version":3,"file":"InteractiveControlDemoOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/controls/components/InteractiveControlDemoOverlayHtml.tsx"],"sourcesContent":["/**\n * InteractiveControlDemo - Display recently pressed keys and their actions\n * \n * Shows the last 5 pressed keys with their descriptions in a bottom overlay.\n * Keys auto-fade after 2 seconds for a clean, non-intrusive display.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { getKeyCategoryColor, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for InteractiveControlDemo component\n */\nexport interface InteractiveControlDemoProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Key press entry with timestamp\n */\ninterface KeyPressEntry {\n readonly keyData: KeyData;\n readonly timestamp: number;\n readonly id: string;\n}\n\n/**\n * Props for KeyPressEntryDisplay component\n */\ninterface KeyPressEntryDisplayProps {\n readonly entry: KeyPressEntry;\n readonly opacity: number;\n readonly categoryColor: number;\n readonly isMobile: boolean;\n readonly theme: ReturnType<typeof useKoreanTheme>;\n}\n\n/**\n * Memoized KeyPressEntryDisplay Component\n * \n * Displays a single key press entry with optimized inline styles.\n */\nconst KeyPressEntryDisplay = React.memo<KeyPressEntryDisplayProps>(({\n entry,\n opacity,\n categoryColor,\n isMobile,\n theme,\n}) => {\n const containerStyle = useMemo(() => ({\n display: 'flex',\n alignItems: 'center',\n gap: isMobile ? '8px' : '12px',\n padding: isMobile ? '8px' : '10px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, opacity * 0.9),\n borderRadius: '8px',\n border: `2px solid ${hexToRgbaString(categoryColor, opacity * 0.7)}`,\n transition: 'opacity 0.3s ease',\n opacity,\n }), [isMobile, theme.colors.UI_BACKGROUND_MEDIUM, opacity, categoryColor]);\n\n const keyLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '16px' : '18px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(categoryColor),\n padding: '6px 12px',\n background: hexToRgbaString(categoryColor, 0.2),\n borderRadius: '6px',\n minWidth: isMobile ? '50px' : '60px',\n textAlign: 'center' as const,\n boxShadow: `0 0 10px ${hexToRgbaString(categoryColor, opacity * 0.5)}`,\n }), [isMobile, categoryColor, opacity]);\n\n const koreanLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '13px' : '14px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, opacity),\n marginBottom: '2px',\n }), [isMobile, theme.colors.ACCENT_GOLD, opacity]);\n\n const descriptionStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '11px' : '12px',\n color: hexToRgbaString(theme.colors.TEXT_SECONDARY, opacity),\n lineHeight: 1.3,\n }), [isMobile, theme.colors.TEXT_SECONDARY, opacity]);\n\n return (\n <div\n style={containerStyle}\n data-testid={`key-press-${entry.keyData.code}`}\n >\n {/* Key label */}\n <div style={keyLabelStyle}>\n {entry.keyData.label}\n </div>\n\n {/* Key description */}\n <div style={{ flex: 1 }}>\n {/* Korean label */}\n {entry.keyData.labelKorean && (\n <div style={koreanLabelStyle}>\n {entry.keyData.labelKorean}\n </div>\n )}\n\n {/* Description (Korean | English) */}\n {entry.keyData.descriptionKorean && entry.keyData.description && (\n <div style={descriptionStyle}>\n {entry.keyData.descriptionKorean} | {entry.keyData.description}\n </div>\n )}\n </div>\n </div>\n );\n});\n\nKeyPressEntryDisplay.displayName = 'KeyPressEntryDisplay';\n\n/**\n * InteractiveControlDemo Component\n * \n * Displays recently pressed keys with their actions.\n * Auto-fades entries after 2 seconds and maintains last 5 keys.\n * \n * @example\n * ```tsx\n * <InteractiveControlDemo\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * isMobile={false}\n * />\n * ```\n */\nexport const InteractiveControlDemo: React.FC<InteractiveControlDemoProps> = ({\n pressedKeys,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: 'primary', size: 'md', isMobile });\n const [keyPresses, setKeyPresses] = useState<KeyPressEntry[]>([]);\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n useEffect(() => {\n let frameId: number;\n\n const updateTime = () => {\n setCurrentTime(Date.now());\n frameId = window.requestAnimationFrame(updateTime);\n };\n\n frameId = window.requestAnimationFrame(updateTime);\n\n return () => {\n window.cancelAnimationFrame(frameId);\n };\n }, []);\n\n useEffect(() => {\n const currentCodes = Array.from(pressedKeys);\n\n if (currentCodes.length === 0) {\n return;\n }\n\n const now = Date.now();\n\n setKeyPresses((prev) => {\n let updated = prev;\n\n currentCodes.forEach((code) => {\n const alreadyRecorded = updated.some(\n (entry) => entry.keyData.code === code && now - entry.timestamp < 100\n );\n\n if (!alreadyRecorded) {\n const keyData = KEYBOARD_LAYOUT.find((k) => k.code === code);\n if (keyData) {\n const newEntry: KeyPressEntry = {\n keyData,\n timestamp: now,\n id: `${code}-${now}`,\n };\n updated = [newEntry, ...updated];\n }\n }\n });\n\n if (updated.length > 5) {\n updated = updated.slice(0, 5);\n }\n\n return updated === prev ? prev : updated;\n });\n }, [pressedKeys]);\n\n useEffect(() => {\n setKeyPresses((prev) =>\n prev.filter((entry) => currentTime - entry.timestamp < 2000)\n );\n }, [currentTime]);\n\n const getOpacity = (timestamp: number): number => {\n const age = currentTime - timestamp;\n const maxAge = 2000;\n return Math.max(0, 1 - age / maxAge);\n };\n\n const containerStyle = useMemo(() => ({\n position: 'fixed' as const,\n bottom: isMobile ? '15px' : '20px',\n left: '50%',\n transform: 'translateX(-50%)',\n display: 'flex',\n flexDirection: 'column' as const,\n gap: isMobile ? '8px' : '10px',\n maxWidth: isMobile ? '90%' : '600px',\n width: '100%',\n padding: isMobile ? '12px' : '15px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n borderRadius: '12px',\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6)}`,\n boxShadow: `\n 0 4px 20px ${hexToRgbaString(theme.colors.BLACK_SOLID, 0.5)},\n 0 0 15px ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.3)}\n `,\n pointerEvents: 'none' as const,\n zIndex: 1000,\n }), [isMobile, theme]);\n\n const titleStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD),\n textAlign: 'center' as const,\n marginBottom: '4px',\n textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, 0.5)}`,\n }), [isMobile, theme]);\n\n return (\n <div style={containerStyle} data-testid=\"interactive-demo\">\n {/* Title */}\n <div style={titleStyle}>\n 최근 입력 | Recent Input\n </div>\n\n {/* Key press entries */}\n {keyPresses.map((entry) => {\n const opacity = getOpacity(entry.timestamp);\n const categoryColor = getKeyCategoryColor(entry.keyData.category);\n\n return (\n <KeyPressEntryDisplay\n key={entry.id}\n entry={entry}\n opacity={opacity}\n categoryColor={categoryColor}\n isMobile={isMobile}\n theme={theme}\n />\n );\n })}\n\n {/* Empty state */}\n {keyPresses.length === 0 && (\n <div\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n color: hexToRgbaString(theme.colors.TEXT_TERTIARY),\n textAlign: 'center' as const,\n padding: '15px',\n }}\n >\n 키를 눌러서 액션을 확인하세요 | Press keys to see actions\n </div>\n )}\n </div>\n );\n};\n\nexport default InteractiveControlDemo;\n"],"mappings":"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"}
1
+ {"version":3,"file":"InteractiveControlDemoOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/controls/components/InteractiveControlDemoOverlayHtml.tsx"],"sourcesContent":["/**\n * InteractiveControlDemo - Display recently pressed keys and their actions\n * \n * Shows the last 5 pressed keys with their descriptions in a bottom overlay.\n * Keys auto-fade after 2 seconds for a clean, non-intrusive display.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../shared/base/useKoreanTheme\";\nimport { getKeyCategoryColor, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for InteractiveControlDemo component\n */\nexport interface InteractiveControlDemoProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Key press entry with timestamp\n */\ninterface KeyPressEntry {\n readonly keyData: KeyData;\n readonly timestamp: number;\n readonly id: string;\n}\n\n/**\n * Props for KeyPressEntryDisplay component\n */\ninterface KeyPressEntryDisplayProps {\n readonly entry: KeyPressEntry;\n readonly opacity: number;\n readonly categoryColor: number;\n readonly isMobile: boolean;\n readonly theme: ReturnType<typeof useKoreanTheme>;\n}\n\n/**\n * Memoized KeyPressEntryDisplay Component\n * \n * Displays a single key press entry with optimized inline styles.\n */\nconst KeyPressEntryDisplay = React.memo<KeyPressEntryDisplayProps>(({\n entry,\n opacity,\n categoryColor,\n isMobile,\n theme,\n}) => {\n const containerStyle = useMemo(() => ({\n display: 'flex',\n alignItems: 'center',\n gap: isMobile ? '8px' : '12px',\n padding: isMobile ? '8px' : '10px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_MEDIUM, opacity * 0.9),\n borderRadius: '8px',\n border: `2px solid ${hexToRgbaString(categoryColor, opacity * 0.7)}`,\n transition: 'opacity 0.3s ease',\n opacity,\n }), [isMobile, theme.colors.UI_BACKGROUND_MEDIUM, opacity, categoryColor]);\n\n const keyLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '16px' : '18px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(categoryColor),\n padding: '6px 12px',\n background: hexToRgbaString(categoryColor, 0.2),\n borderRadius: '6px',\n minWidth: isMobile ? '50px' : '60px',\n textAlign: 'center' as const,\n boxShadow: `0 0 10px ${hexToRgbaString(categoryColor, opacity * 0.5)}`,\n }), [isMobile, categoryColor, opacity]);\n\n const koreanLabelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '13px' : '14px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, opacity),\n marginBottom: '2px',\n }), [isMobile, theme.colors.ACCENT_GOLD, opacity]);\n\n const descriptionStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '11px' : '12px',\n color: hexToRgbaString(theme.colors.TEXT_SECONDARY, opacity),\n lineHeight: 1.3,\n }), [isMobile, theme.colors.TEXT_SECONDARY, opacity]);\n\n return (\n <div\n style={containerStyle}\n data-testid={`key-press-${entry.keyData.code}`}\n >\n {/* Key label */}\n <div style={keyLabelStyle}>\n {entry.keyData.label}\n </div>\n\n {/* Key description */}\n <div style={{ flex: 1 }}>\n {/* Korean label */}\n {entry.keyData.labelKorean && (\n <div style={koreanLabelStyle}>\n {entry.keyData.labelKorean}\n </div>\n )}\n\n {/* Description (Korean | English) */}\n {entry.keyData.descriptionKorean && entry.keyData.description && (\n <div style={descriptionStyle}>\n {entry.keyData.descriptionKorean} | {entry.keyData.description}\n </div>\n )}\n </div>\n </div>\n );\n});\n\nKeyPressEntryDisplay.displayName = 'KeyPressEntryDisplay';\n\n/**\n * InteractiveControlDemo Component\n * \n * Displays recently pressed keys with their actions.\n * Auto-fades entries after 2 seconds and maintains last 5 keys.\n * \n * @example\n * ```tsx\n * <InteractiveControlDemo\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * isMobile={false}\n * />\n * ```\n */\nexport const InteractiveControlDemo: React.FC<InteractiveControlDemoProps> = ({\n pressedKeys,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: 'primary', size: 'md', isMobile });\n const [keyPresses, setKeyPresses] = useState<KeyPressEntry[]>([]);\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n useEffect(() => {\n let frameId: number;\n\n const updateTime = () => {\n setCurrentTime(Date.now());\n frameId = window.requestAnimationFrame(updateTime);\n };\n\n frameId = window.requestAnimationFrame(updateTime);\n\n return () => {\n window.cancelAnimationFrame(frameId);\n };\n }, []);\n\n useEffect(() => {\n const currentCodes = Array.from(pressedKeys);\n\n if (currentCodes.length === 0) {\n return;\n }\n\n const now = Date.now();\n\n setKeyPresses((prev) => {\n let updated = prev;\n\n currentCodes.forEach((code) => {\n const alreadyRecorded = updated.some(\n (entry) => entry.keyData.code === code && now - entry.timestamp < 100\n );\n\n if (!alreadyRecorded) {\n const keyData = KEYBOARD_LAYOUT.find((k) => k.code === code);\n if (keyData) {\n const newEntry: KeyPressEntry = {\n keyData,\n timestamp: now,\n id: `${code}-${now}`,\n };\n updated = [newEntry, ...updated];\n }\n }\n });\n\n if (updated.length > 5) {\n updated = updated.slice(0, 5);\n }\n\n return updated === prev ? prev : updated;\n });\n }, [pressedKeys]);\n\n useEffect(() => {\n setKeyPresses((prev) =>\n prev.filter((entry) => currentTime - entry.timestamp < 2000)\n );\n }, [currentTime]);\n\n const getOpacity = (timestamp: number): number => {\n const age = currentTime - timestamp;\n const maxAge = 2000;\n return Math.max(0, 1 - age / maxAge);\n };\n\n const containerStyle = useMemo(() => ({\n position: 'fixed' as const,\n bottom: isMobile ? '15px' : '20px',\n left: '50%',\n transform: 'translateX(-50%)',\n display: 'flex',\n flexDirection: 'column' as const,\n gap: isMobile ? '8px' : '10px',\n maxWidth: isMobile ? '90%' : '600px',\n width: '100%',\n padding: isMobile ? '12px' : '15px',\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.95),\n borderRadius: '12px',\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.6)}`,\n boxShadow: `\n 0 4px 20px ${hexToRgbaString(theme.colors.BLACK_SOLID, 0.5)},\n 0 0 15px ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.3)}\n `,\n pointerEvents: 'none' as const,\n zIndex: 1000,\n }), [isMobile, theme]);\n\n const titleStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n fontWeight: 'bold' as const,\n color: hexToRgbaString(theme.colors.ACCENT_GOLD),\n textAlign: 'center' as const,\n marginBottom: '4px',\n textShadow: `0 0 8px ${hexToRgbaString(theme.colors.ACCENT_GOLD, 0.5)}`,\n }), [isMobile, theme]);\n\n return (\n <div style={containerStyle} data-testid=\"interactive-demo\">\n {/* Title */}\n <div style={titleStyle}>\n 최근 입력 | Recent Input\n </div>\n\n {/* Key press entries */}\n {keyPresses.map((entry) => {\n const opacity = getOpacity(entry.timestamp);\n const categoryColor = getKeyCategoryColor(entry.keyData.category);\n\n return (\n <KeyPressEntryDisplay\n key={entry.id}\n entry={entry}\n opacity={opacity}\n categoryColor={categoryColor}\n isMobile={isMobile}\n theme={theme}\n />\n );\n })}\n\n {/* Empty state */}\n {keyPresses.length === 0 && (\n <div\n style={{\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? '12px' : '13px',\n color: hexToRgbaString(theme.colors.TEXT_TERTIARY),\n textAlign: 'center' as const,\n padding: '15px',\n }}\n >\n 키를 눌러서 액션을 확인하세요 | Press keys to see actions\n </div>\n )}\n </div>\n );\n};\n\nexport default InteractiveControlDemo;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Key3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/Key3D.tsx"],"sourcesContent":["/**\n * Key3D - Individual 3D keyboard key component with press animation\n * \n * Renders a 3D box mesh for a keyboard key with category-based coloring,\n * press animation, and bilingual label overlay.\n * \n * @module components/screens/controls/components\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getKeyCategoryColor, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for Key3D component\n */\nexport interface Key3DProps {\n /** Key data containing position, label, and category */\n readonly keyData: KeyData;\n /** Whether this key is currently pressed */\n readonly isPressed: boolean;\n}\n\n/**\n * Key3D Component\n * \n * Individual 3D keyboard key with press animation and label overlay.\n * Uses category-based coloring and emissive glow when pressed.\n * \n * @example\n * ```tsx\n * <Key3D\n * keyData={{ code: 'Space', label: 'Space', row: 4, col: 2, width: 3, category: 'combat' }}\n * isPressed={pressedKeys.has('Space')}\n * />\n * ```\n */\nexport const Key3D: React.FC<Key3DProps> = ({ keyData, isPressed }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const targetScale = useRef(new THREE.Vector3(1, 1, 1));\n\n const keyWidth = (keyData.width ?? 1) * 0.6; // 0.6 units per key width\n const keyHeight = 0.6;\n const keyDepth = 0.2;\n\n const position = useMemo<[number, number, number]>(() => {\n const x = keyData.col * 0.6 + (keyWidth / 2) - 3.0; // Center around origin\n const y = -keyData.row * 0.6;\n const z = isPressed ? -0.05 : 0; // Press down slightly when pressed\n return [x, y, z];\n }, [keyData.col, keyData.row, keyWidth, isPressed]);\n\n const categoryColor = useMemo(() => getKeyCategoryColor(keyData.category), [keyData.category]);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const targetScaleValue = isPressed ? 0.9 : 1.0;\n targetScale.current.set(targetScaleValue, targetScaleValue, 1.0);\n\n meshRef.current.scale.lerp(targetScale.current, 0.2);\n });\n\n const materials = useMemo(\n () => ({\n unpressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 0.3,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n pressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 2.5,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n }),\n [categoryColor]\n );\n\n const keyMaterial = isPressed ? materials.pressed : materials.unpressed;\n\n useEffect(() => {\n return () => {\n materials.unpressed.dispose();\n materials.pressed.dispose();\n };\n }, [materials]);\n\n const labelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: keyData.width && keyData.width > 2 ? '11px' : '13px',\n fontWeight: 'bold' as const,\n color: isPressed ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textAlign: 'center' as const,\n pointerEvents: 'none' as const,\n userSelect: 'none' as const,\n textShadow: isPressed ? `0 0 8px ${hexToRgbaString(categoryColor, 0.8)}` : 'none',\n whiteSpace: 'nowrap' as const,\n lineHeight: 1.2,\n }), [isPressed, categoryColor, keyData.width]);\n\n return (\n <group position={position}>\n {/* 3D key box */}\n <mesh\n ref={meshRef}\n castShadow\n receiveShadow\n name={`key-${keyData.code}`}\n data-testid={`key-${keyData.code}`}\n >\n <boxGeometry args={[keyWidth, keyHeight, keyDepth]} />\n <primitive object={keyMaterial} attach=\"material\" />\n </mesh>\n\n {/* Label overlay */}\n <Html\n position={[0, 0, keyDepth / 2 + 0.01]}\n center\n distanceFactor={2}\n style={{ pointerEvents: 'none' }}\n >\n <div style={labelStyle}>\n {/* Main label */}\n <div>{keyData.label}</div>\n {/* Korean label (if available) */}\n {keyData.labelKorean && (\n <div style={{\n fontSize: '10px',\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: '2px',\n }}>\n {keyData.labelKorean}\n </div>\n )}\n </div>\n </Html>\n\n {/* Glow ring when pressed */}\n {isPressed && (\n <mesh position={[0, 0, -keyDepth / 2 - 0.02]} rotation={[0, 0, 0]}>\n <ringGeometry args={[Math.max(keyWidth, keyHeight) * 0.6, Math.max(keyWidth, keyHeight) * 0.7, 32]} />\n <meshBasicMaterial\n color={categoryColor}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Key3D;\n"],"mappings":"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"}
1
+ {"version":3,"file":"Key3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/Key3D.tsx"],"sourcesContent":["/**\n * Key3D - Individual 3D keyboard key component with press animation\n * \n * Renders a 3D box mesh for a keyboard key with category-based coloring,\n * press animation, and bilingual label overlay.\n * \n * @module components/screens/controls/components\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { getKeyCategoryColor, type KeyData } from \"../constants/ControlsConstants\";\n\n/**\n * Props for Key3D component\n */\nexport interface Key3DProps {\n /** Key data containing position, label, and category */\n readonly keyData: KeyData;\n /** Whether this key is currently pressed */\n readonly isPressed: boolean;\n}\n\n/**\n * Key3D Component\n * \n * Individual 3D keyboard key with press animation and label overlay.\n * Uses category-based coloring and emissive glow when pressed.\n * \n * @example\n * ```tsx\n * <Key3D\n * keyData={{ code: 'Space', label: 'Space', row: 4, col: 2, width: 3, category: 'combat' }}\n * isPressed={pressedKeys.has('Space')}\n * />\n * ```\n */\nexport const Key3D: React.FC<Key3DProps> = ({ keyData, isPressed }) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const targetScale = useRef(new THREE.Vector3(1, 1, 1));\n\n const keyWidth = (keyData.width ?? 1) * 0.6; // 0.6 units per key width\n const keyHeight = 0.6;\n const keyDepth = 0.2;\n\n const position = useMemo<[number, number, number]>(() => {\n const x = keyData.col * 0.6 + (keyWidth / 2) - 3.0; // Center around origin\n const y = -keyData.row * 0.6;\n const z = isPressed ? -0.05 : 0; // Press down slightly when pressed\n return [x, y, z];\n }, [keyData.col, keyData.row, keyWidth, isPressed]);\n\n const categoryColor = useMemo(() => getKeyCategoryColor(keyData.category), [keyData.category]);\n\n useFrame(() => {\n if (!meshRef.current) return;\n\n const targetScaleValue = isPressed ? 0.9 : 1.0;\n targetScale.current.set(targetScaleValue, targetScaleValue, 1.0);\n\n meshRef.current.scale.lerp(targetScale.current, 0.2);\n });\n\n const materials = useMemo(\n () => ({\n unpressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 0.3,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n pressed: new THREE.MeshStandardMaterial({\n color: categoryColor,\n emissive: categoryColor,\n emissiveIntensity: 2.5,\n metalness: 0.6,\n roughness: 0.4,\n transparent: true,\n opacity: 0.9,\n }),\n }),\n [categoryColor]\n );\n\n const keyMaterial = isPressed ? materials.pressed : materials.unpressed;\n\n useEffect(() => {\n return () => {\n materials.unpressed.dispose();\n materials.pressed.dispose();\n };\n }, [materials]);\n\n const labelStyle = useMemo(() => ({\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: keyData.width && keyData.width > 2 ? '11px' : '13px',\n fontWeight: 'bold' as const,\n color: isPressed ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n textAlign: 'center' as const,\n pointerEvents: 'none' as const,\n userSelect: 'none' as const,\n textShadow: isPressed ? `0 0 8px ${hexToRgbaString(categoryColor, 0.8)}` : 'none',\n whiteSpace: 'nowrap' as const,\n lineHeight: 1.2,\n }), [isPressed, categoryColor, keyData.width]);\n\n return (\n <group position={position}>\n {/* 3D key box */}\n <mesh\n ref={meshRef}\n castShadow\n receiveShadow\n name={`key-${keyData.code}`}\n data-testid={`key-${keyData.code}`}\n >\n <boxGeometry args={[keyWidth, keyHeight, keyDepth]} />\n <primitive object={keyMaterial} attach=\"material\" />\n </mesh>\n\n {/* Label overlay */}\n <Html\n position={[0, 0, keyDepth / 2 + 0.01]}\n center\n distanceFactor={2}\n style={{ pointerEvents: 'none' }}\n >\n <div style={labelStyle}>\n {/* Main label */}\n <div>{keyData.label}</div>\n {/* Korean label (if available) */}\n {keyData.labelKorean && (\n <div style={{\n fontSize: '10px',\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: '2px',\n }}>\n {keyData.labelKorean}\n </div>\n )}\n </div>\n </Html>\n\n {/* Glow ring when pressed */}\n {isPressed && (\n <mesh position={[0, 0, -keyDepth / 2 - 0.02]} rotation={[0, 0, 0]}>\n <ringGeometry args={[Math.max(keyWidth, keyHeight) * 0.6, Math.max(keyWidth, keyHeight) * 0.7, 32]} />\n <meshBasicMaterial\n color={categoryColor}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Key3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,SAA+B,EAAE,SAAS,gBAAgB;CACrE,MAAM,UAAU,OAAmB,IAAI;CACvC,MAAM,cAAc,OAAO,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC;CAErD,MAAM,YAAY,QAAQ,SAAS,KAAK;CACxC,MAAM,YAAY;CAClB,MAAM,WAAW;CAEjB,MAAM,WAAW,cAAwC;EAIvD,OAAO;GAHG,QAAQ,MAAM,KAAO,WAAW,IAAK;GACrC,CAAC,QAAQ,MAAM;GACf,YAAY,OAAQ;EACf;CACjB,GAAG;EAAC,QAAQ;EAAK,QAAQ;EAAK;EAAU;CAAS,CAAC;CAElD,MAAM,gBAAgB,cAAc,oBAAoB,QAAQ,QAAQ,GAAG,CAAC,QAAQ,QAAQ,CAAC;CAE7F,eAAe;EACb,IAAI,CAAC,QAAQ,SAAS;EAEtB,MAAM,mBAAmB,YAAY,KAAM;EAC3C,YAAY,QAAQ,IAAI,kBAAkB,kBAAkB,CAAG;EAE/D,QAAQ,QAAQ,MAAM,KAAK,YAAY,SAAS,EAAG;CACrD,CAAC;CAED,MAAM,YAAY,eACT;EACL,WAAW,IAAI,MAAM,qBAAqB;GACxC,OAAO;GACP,UAAU;GACV,mBAAmB;GACnB,WAAW;GACX,WAAW;GACX,aAAa;GACb,SAAS;EACX,CAAC;EACD,SAAS,IAAI,MAAM,qBAAqB;GACtC,OAAO;GACP,UAAU;GACV,mBAAmB;GACnB,WAAW;GACX,WAAW;GACX,aAAa;GACb,SAAS;EACX,CAAC;CACH,IACA,CAAC,aAAa,CAChB;CAEA,MAAM,cAAc,YAAY,UAAU,UAAU,UAAU;CAE9D,gBAAgB;EACd,aAAa;GACX,UAAU,UAAU,QAAQ;GAC5B,UAAU,QAAQ,QAAQ;EAC5B;CACF,GAAG,CAAC,SAAS,CAAC;CAEd,MAAM,aAAa,eAAe;EAChC,YAAY,YAAY;EACxB,UAAU,QAAQ,SAAS,QAAQ,QAAQ,IAAI,SAAS;EACxD,YAAY;EACZ,OAAO,YAAY,gBAAgB,cAAc,WAAW,IAAI,gBAAgB,cAAc,YAAY;EAC1G,WAAW;EACX,eAAe;EACf,YAAY;EACZ,YAAY,YAAY,WAAW,gBAAgB,eAAe,EAAG,MAAM;EAC3E,YAAY;EACZ,YAAY;CACd,IAAI;EAAC;EAAW;EAAe,QAAQ;CAAK,CAAC;CAE7C,OACE,qBAAC,SAAD;EAAiB;EAAjB,UAAA;GAEE,qBAAC,QAAD;IACE,KAAK;IACL,YAAA;IACA,eAAA;IACA,MAAM,OAAO,QAAQ;IACrB,eAAa,OAAO,QAAQ;IAL9B,UAAA,CAOE,oBAAC,eAAD,EAAa,MAAM;KAAC;KAAU;KAAW;IAAQ,EAAI,CAAA,GACrD,oBAAC,aAAD;KAAW,QAAQ;KAAa,QAAO;IAAY,CAAA,CAC/C;;GAGN,oBAAC,MAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAmB;IACpC,QAAA;IACA,gBAAgB;IAChB,OAAO,EAAE,eAAe,OAAO;IAE/B,UAAA,qBAAC,OAAD;KAAK,OAAO;KAAZ,UAAA,CAEE,oBAAC,OAAD,EAAA,UAAM,QAAQ,MAAW,CAAA,GAExB,QAAQ,eACP,oBAAC,OAAD;MAAK,OAAO;OACV,UAAU;OACV,OAAO,gBAAgB,cAAc,WAAW;OAChD,WAAW;MACb;MACG,UAAA,QAAQ;KACN,CAAA,CAEJ;;GACD,CAAA;GAGL,aACC,qBAAC,QAAD;IAAM,UAAU;KAAC;KAAG;KAAG,MAAY,IAAI;IAAI;IAAG,UAAU;KAAC;KAAG;KAAG;IAAC;IAAhE,UAAA,CACE,oBAAC,gBAAD,EAAc,MAAM;KAAC,KAAK,IAAI,UAAU,SAAS,IAAI;KAAK,KAAK,IAAI,UAAU,SAAS,IAAI;KAAK;IAAE,EAAI,CAAA,GACrG,oBAAC,qBAAD;KACE,OAAO;KACP,aAAA;KACA,SAAS;KACT,MAAM,MAAM;IACb,CAAA,CACG;;EAEH;;AAEX"}
@@ -1 +1 @@
1
- {"version":3,"file":"VisualKeyboard3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/VisualKeyboard3D.tsx"],"sourcesContent":["/**\n * VisualKeyboard3D - 3D keyboard visualization with all keys\n * \n * Renders a complete 3D keyboard with keys filtered by selected category.\n * Uses grid layout with proper spacing and lighting for visual clarity.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\nimport { filterKeysByCategory, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\nimport { Key3D } from \"./Key3D\";\n\n/**\n * Props for VisualKeyboard3D component\n */\nexport interface VisualKeyboard3DProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Selected control category to filter keys */\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * VisualKeyboard3D Component\n * \n * 3D keyboard visualization showing all keys filtered by selected category.\n * Keys are positioned in a grid layout with proper spacing.\n * \n * @example\n * ```tsx\n * <Canvas>\n * <VisualKeyboard3D\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * selectedTab=\"combat\"\n * />\n * </Canvas>\n * ```\n */\nexport const VisualKeyboard3D: React.FC<VisualKeyboard3DProps> = ({\n pressedKeys,\n selectedTab,\n}) => {\n const filteredKeys = useMemo<readonly KeyData[]>(() => {\n return filterKeysByCategory(KEYBOARD_LAYOUT, selectedTab);\n }, [selectedTab]);\n\n return (\n <group\n position={[0, -1, 0]}\n rotation={[-Math.PI / 6, 0, 0]}\n data-testid=\"visual-keyboard\"\n >\n {/* Ambient light for overall illumination */}\n <ambientLight intensity={0.4} />\n\n {/* Directional light for depth and shadows */}\n <directionalLight\n position={[5, 5, 5]}\n intensity={1.0}\n castShadow\n shadow-mapSize-width={2048}\n shadow-mapSize-height={2048}\n />\n\n {/* Additional directional light from the side */}\n <directionalLight\n position={[-3, 3, 2]}\n intensity={0.5}\n />\n\n {/* Point light for accent highlights */}\n <pointLight\n position={[0, 2, 2]}\n intensity={0.6}\n distance={10}\n decay={2}\n />\n\n {/* Render all filtered keys */}\n {filteredKeys.map((keyData) => (\n <Key3D\n key={keyData.code}\n keyData={keyData}\n isPressed={pressedKeys.has(keyData.code)}\n />\n ))}\n\n {/* Background plane for keyboard base */}\n <mesh\n position={[0, 0, -0.2]}\n receiveShadow\n >\n <planeGeometry args={[12, 6]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ARENA_BACKGROUND}\n metalness={0.8}\n roughness={0.3}\n opacity={0.9}\n transparent\n />\n </mesh>\n\n {/* Grid helper for visual reference (subtle) */}\n <gridHelper\n args={[12, 20, KOREAN_COLORS.UI_STEEL_GRAY_DARK, KOREAN_COLORS.UI_BACKGROUND_MEDIUM]}\n position={[0, 0, -0.19]}\n rotation={[0, 0, 0]}\n />\n </group>\n );\n};\n\nexport default VisualKeyboard3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,oBAAqD,EAChE,aACA,kBACI;CACJ,MAAM,eAAe,cAAkC;EACrD,OAAO,qBAAqB,iBAAiB,WAAW;CAC1D,GAAG,CAAC,WAAW,CAAC;CAEhB,OACE,qBAAC,SAAD;EACE,UAAU;GAAC;GAAG;GAAI;EAAC;EACnB,UAAU;GAAC,CAAC,KAAK,KAAK;GAAG;GAAG;EAAC;EAC7B,eAAY;YAHd;GAME,oBAAC,gBAAD,EAAc,WAAW,GAAM,CAAA;GAG/B,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,WAAW;IACX,YAAA;IACA,wBAAsB;IACtB,yBAAuB;GACxB,CAAA;GAGD,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAI;KAAG;IAAC;IACnB,WAAW;GACZ,CAAA;GAGD,oBAAC,cAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,WAAW;IACX,UAAU;IACV,OAAO;GACR,CAAA;GAGA,aAAa,KAAK,YACjB,oBAAC,OAAD;IAEW;IACT,WAAW,YAAY,IAAI,QAAQ,IAAI;GACxC,GAHM,QAAQ,IAGd,CACF;GAGD,qBAAC,QAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAI;IACrB,eAAA;cAFF,CAIE,oBAAC,iBAAD,EAAe,MAAM,CAAC,IAAI,CAAC,EAAI,CAAA,GAC/B,oBAAC,wBAAD;KACE,OAAO,cAAc;KACrB,WAAW;KACX,WAAW;KACX,SAAS;KACT,aAAA;IACD,CAAA,CACG;;GAGN,oBAAC,cAAD;IACE,MAAM;KAAC;KAAI;KAAI,cAAc;KAAoB,cAAc;IAAoB;IACnF,UAAU;KAAC;KAAG;KAAG;IAAK;IACtB,UAAU;KAAC;KAAG;KAAG;IAAC;GACnB,CAAA;EACI;;AAEX"}
1
+ {"version":3,"file":"VisualKeyboard3D.js","names":[],"sources":["../../../../../src/components/screens/controls/components/VisualKeyboard3D.tsx"],"sourcesContent":["/**\n * VisualKeyboard3D - 3D keyboard visualization with all keys\n * \n * Renders a complete 3D keyboard with keys filtered by selected category.\n * Uses grid layout with proper spacing and lighting for visual clarity.\n * \n * @module components/screens/controls/components\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"../../../../types/constants/colors\";\nimport { filterKeysByCategory, KEYBOARD_LAYOUT, type KeyData } from \"../constants/ControlsConstants\";\nimport { Key3D } from \"./Key3D\";\n\n/**\n * Props for VisualKeyboard3D component\n */\nexport interface VisualKeyboard3DProps {\n /** Set of currently pressed key codes */\n readonly pressedKeys: Set<string>;\n /** Selected control category to filter keys */\n readonly selectedTab: 'combat' | 'movement' | 'system';\n}\n\n/**\n * VisualKeyboard3D Component\n * \n * 3D keyboard visualization showing all keys filtered by selected category.\n * Keys are positioned in a grid layout with proper spacing.\n * \n * @example\n * ```tsx\n * <Canvas>\n * <VisualKeyboard3D\n * pressedKeys={new Set(['Space', 'KeyW'])}\n * selectedTab=\"combat\"\n * />\n * </Canvas>\n * ```\n */\nexport const VisualKeyboard3D: React.FC<VisualKeyboard3DProps> = ({\n pressedKeys,\n selectedTab,\n}) => {\n const filteredKeys = useMemo<readonly KeyData[]>(() => {\n return filterKeysByCategory(KEYBOARD_LAYOUT, selectedTab);\n }, [selectedTab]);\n\n return (\n <group\n position={[0, -1, 0]}\n rotation={[-Math.PI / 6, 0, 0]}\n data-testid=\"visual-keyboard\"\n >\n {/* Ambient light for overall illumination */}\n <ambientLight intensity={0.4} />\n\n {/* Directional light for depth and shadows */}\n <directionalLight\n position={[5, 5, 5]}\n intensity={1.0}\n castShadow\n shadow-mapSize-width={2048}\n shadow-mapSize-height={2048}\n />\n\n {/* Additional directional light from the side */}\n <directionalLight\n position={[-3, 3, 2]}\n intensity={0.5}\n />\n\n {/* Point light for accent highlights */}\n <pointLight\n position={[0, 2, 2]}\n intensity={0.6}\n distance={10}\n decay={2}\n />\n\n {/* Render all filtered keys */}\n {filteredKeys.map((keyData) => (\n <Key3D\n key={keyData.code}\n keyData={keyData}\n isPressed={pressedKeys.has(keyData.code)}\n />\n ))}\n\n {/* Background plane for keyboard base */}\n <mesh\n position={[0, 0, -0.2]}\n receiveShadow\n >\n <planeGeometry args={[12, 6]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ARENA_BACKGROUND}\n metalness={0.8}\n roughness={0.3}\n opacity={0.9}\n transparent\n />\n </mesh>\n\n {/* Grid helper for visual reference (subtle) */}\n <gridHelper\n args={[12, 20, KOREAN_COLORS.UI_STEEL_GRAY_DARK, KOREAN_COLORS.UI_BACKGROUND_MEDIUM]}\n position={[0, 0, -0.19]}\n rotation={[0, 0, 0]}\n />\n </group>\n );\n};\n\nexport default VisualKeyboard3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,oBAAqD,EAChE,aACA,kBACI;CACJ,MAAM,eAAe,cAAkC;EACrD,OAAO,qBAAqB,iBAAiB,WAAW;CAC1D,GAAG,CAAC,WAAW,CAAC;CAEhB,OACE,qBAAC,SAAD;EACE,UAAU;GAAC;GAAG;GAAI;EAAC;EACnB,UAAU;GAAC,CAAC,KAAK,KAAK;GAAG;GAAG;EAAC;EAC7B,eAAY;EAHd,UAAA;GAME,oBAAC,gBAAD,EAAc,WAAW,GAAM,CAAA;GAG/B,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,WAAW;IACX,YAAA;IACA,wBAAsB;IACtB,yBAAuB;GACxB,CAAA;GAGD,oBAAC,oBAAD;IACE,UAAU;KAAC;KAAI;KAAG;IAAC;IACnB,WAAW;GACZ,CAAA;GAGD,oBAAC,cAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,WAAW;IACX,UAAU;IACV,OAAO;GACR,CAAA;GAGA,aAAa,KAAK,YACjB,oBAAC,OAAD;IAEW;IACT,WAAW,YAAY,IAAI,QAAQ,IAAI;GACxC,GAHM,QAAQ,IAGd,CACF;GAGD,qBAAC,QAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAI;IACrB,eAAA;IAFF,UAAA,CAIE,oBAAC,iBAAD,EAAe,MAAM,CAAC,IAAI,CAAC,EAAI,CAAA,GAC/B,oBAAC,wBAAD;KACE,OAAO,cAAc;KACrB,WAAW;KACX,WAAW;KACX,SAAS;KACT,aAAA;IACD,CAAA,CACG;;GAGN,oBAAC,cAAD;IACE,MAAM;KAAC;KAAI;KAAI,cAAc;KAAoB,cAAc;IAAoB;IACnF,UAAU;KAAC;KAAG;KAAG;IAAK;IACtB,UAAU;KAAC;KAAG;KAAG;IAAC;GACnB,CAAA;EACI;;AAEX"}
@@ -32,8 +32,8 @@ var BackgroundParticles3D = ({ color }) => {
32
32
  const pointsRef = useRef(null);
33
33
  const [particleData] = useState(() => {
34
34
  const count = 100;
35
- const pos = new Float32Array(count * 3);
36
- const vel = new Float32Array(count * 3);
35
+ const pos = /* @__PURE__ */ new Float32Array(300);
36
+ const vel = /* @__PURE__ */ new Float32Array(300);
37
37
  for (let i = 0; i < count; i++) {
38
38
  const i3 = i * 3;
39
39
  pos[i3] = (Math.random() - .5) * 40;
@@ -1 +1 @@
1
- {"version":3,"file":"EndScreen3D.js","names":[],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EndScreen3D.js","names":[],"sources":["../../../../src/components/screens/endscreen/EndScreen3D.tsx"],"sourcesContent":["import { PerspectiveCamera } from \"@react-three/drei\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { useAudio } from \"../../../audio/AudioProvider\";\nimport { useWebGLContextLossHandler } from \"../../../hooks/useWebGLContextLossHandler\";\nimport { useWindowSize } from \"../../../hooks/useWindowSize\";\nimport { PlayerState } from \"../../../systems\";\nimport { MatchStatistics } from \"../../../systems/combat\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { hexToRgbaString } from \"../../../utils/colorUtils\";\nimport {\n detectPlatform,\n shouldUseMobileControls,\n} from \"../../../utils/deviceDetection\";\nimport { BaseButtonOverlayHtml } from \"../../shared/base/BaseButtonOverlayHtml\";\nimport { useKoreanTheme } from \"../../shared/base/useKoreanTheme\";\nimport { VolumeControl } from \"../../shared/ui/VolumeControl\";\nimport { DefeatAnimation3D } from \"./components/DefeatAnimation3D\";\nimport { MatchStatisticsDisplay } from \"./components/MatchStatisticsDisplayOverlayHtml\";\nimport { NavigationButtons } from \"./components/NavigationButtonsOverlayHtml\";\nimport { PerformanceBreakdown } from \"./components/PerformanceBreakdownOverlayHtml\";\nimport { PerformanceRating } from \"./components/PerformanceRatingOverlayHtml\";\nimport { VictoryAnimation3D } from \"./components/VictoryAnimation3D\";\nimport { WinnerDisplay } from \"./components/WinnerDisplayOverlayHtml\";\n\nexport interface EndScreen3DProps {\n readonly winner: PlayerState;\n readonly matchStats: MatchStatistics;\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly width?: number;\n readonly height?: number;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Three.js-based End Screen Component\n * Displays victory/defeat screen with match statistics and 3D effects\n */\nconst BackgroundParticles3D: React.FC<{ color: number }> = ({ color }) => {\n const pointsRef = useRef<THREE.Points>(null);\n\n const [particleData] = useState(() => {\n const count = 100;\n const pos = new Float32Array(count * 3);\n const vel = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n pos[i3] = (Math.random() - 0.5) * 40;\n pos[i3 + 1] = (Math.random() - 0.5) * 30;\n pos[i3 + 2] = (Math.random() - 0.5) * 20;\n\n vel[i3] = (Math.random() - 0.5) * 0.5;\n vel[i3 + 1] = Math.random() * 0.3;\n vel[i3 + 2] = (Math.random() - 0.5) * 0.5;\n }\n return { positions: pos, velocities: vel };\n });\n\n const { positions, velocities } = particleData;\n\n useFrame((_state, delta) => {\n if (!pointsRef.current) return;\n\n const attr = pointsRef.current.geometry.attributes.position;\n const array = attr.array as Float32Array;\n\n for (let i = 0; i < 100; i++) {\n const i3 = i * 3;\n\n array[i3] += velocities[i3] * delta;\n array[i3 + 1] += velocities[i3 + 1] * delta;\n array[i3 + 2] += velocities[i3 + 2] * delta;\n\n if (array[i3 + 1] > 15) {\n array[i3 + 1] = -15;\n }\n if (Math.abs(array[i3]) > 20) {\n array[i3] = -array[i3];\n }\n if (Math.abs(array[i3 + 2]) > 10) {\n array[i3 + 2] = -array[i3 + 2];\n }\n }\n\n attr.needsUpdate = true;\n });\n\n return (\n <points ref={pointsRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[positions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(color)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n );\n};\n\n/**\n * Main Three.js background scene\n */\nconst EndScreenBackground3D: React.FC<{ \n isVictory: boolean;\n isMobile: boolean;\n}> = ({\n isVictory,\n isMobile,\n}) => {\n const gridRef = useRef<THREE.GridHelper>(null);\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n useFrame(() => {\n if (gridRef.current) {\n gridRef.current.rotation.y += 0.0005;\n }\n });\n\n const primaryColor = isVictory\n ? theme.colors.ACCENT_GOLD\n : theme.colors.ACCENT_RED;\n\n return (\n <>\n {/* Ambient lighting */}\n <ambientLight intensity={0.3} color={theme.colors.PRIMARY_CYAN} />\n\n {/* Directional lights */}\n <directionalLight\n position={[10, 15, 10]}\n intensity={isVictory ? 1.2 : 0.8}\n color={primaryColor}\n />\n <directionalLight\n position={[-10, 10, -5]}\n intensity={0.5}\n color={theme.colors.PRIMARY_CYAN}\n />\n\n {/* Point light for dramatic effect */}\n <pointLight\n position={[0, 5, 0]}\n intensity={isVictory ? 2 : 1}\n distance={30}\n color={primaryColor}\n />\n\n {/* Grid for cyberpunk aesthetic */}\n <gridHelper\n ref={gridRef}\n args={[40, 40, primaryColor, theme.colors.UI_BACKGROUND_MEDIUM]}\n position={[0, -5, 0]}\n />\n\n {/* Background particles */}\n <BackgroundParticles3D color={primaryColor} />\n\n {/* Victory or Defeat animation */}\n {isVictory ? <VictoryAnimation3D /> : <DefeatAnimation3D />}\n </>\n );\n};\n\n/**\n * EndScreen3D Component\n * Three.js-based end screen with Korean theming\n */\nexport const EndScreen3D: React.FC<EndScreen3DProps> = ({\n winner,\n matchStats,\n onReturnToMenu,\n onRematch,\n onViewReplay,\n width: propWidth,\n height: propHeight,\n}) => {\n useWebGLContextLossHandler({\n onContextLost: () => {\n console.warn(\"⚠️ WebGL context lost in EndScreen\");\n },\n autoRestore: true,\n });\n\n const audio = useAudio();\n const [showStats, setShowStats] = useState(false);\n const [showBreakdown, setShowBreakdown] = useState(false);\n\n const { width: windowWidth, height: windowHeight } = useWindowSize();\n const screenWidth = propWidth ?? windowWidth;\n void (propHeight ?? windowHeight); // Consumed but not stored\n\n const winnerId = winner.id;\n const isVictory = winnerId === \"player-0\" || winnerId.endsWith(\"-0\");\n\n const isMobile = useMemo(() => shouldUseMobileControls(), []);\n const platform = useMemo(() => detectPlatform(), []);\n const isTablet = useMemo(() => platform.isTablet, [platform]);\n const isLargeDesktop = useMemo(() => screenWidth >= 1920, [screenWidth]); // 4K/2K displays\n\n const layoutConstants = useMemo(\n () => ({\n titleFontSize: isMobile ? 36 : isTablet ? 44 : isLargeDesktop ? 44 : 54,\n subtitleFontSize: isMobile\n ? 18\n : isTablet\n ? 22\n : isLargeDesktop\n ? 22\n : 28,\n buttonFontSize: isMobile ? 14 : isTablet ? 15 : isLargeDesktop ? 14 : 16,\n padding: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n buttonPadding: isMobile\n ? \"10px 20px\"\n : isTablet\n ? \"11px 22px\"\n : isLargeDesktop\n ? \"10px 20px\"\n : \"12px 25px\",\n spacing: isMobile ? 15 : isTablet ? 18 : isLargeDesktop ? 15 : 20,\n }),\n [isMobile, isTablet, isLargeDesktop],\n );\n\n const theme = useKoreanTheme({\n variant: \"primary\",\n size: \"md\",\n isMobile,\n });\n\n useEffect(() => {\n if (isVictory) {\n audio.playSFX?.(\"victory_fanfare\");\n const timeoutId = setTimeout(() => {\n audio.playMusic?.(\"victory_theme\");\n }, 1000);\n\n return () => {\n clearTimeout(timeoutId);\n audio.stopMusic?.();\n };\n } else {\n audio.playSFX?.(\"defeat_sound\");\n audio.playMusic?.(\"defeat_theme\");\n\n return () => {\n audio.stopMusic?.();\n };\n }\n }, [audio, isVictory]);\n\n const toggleStats = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowStats((prev) => !prev);\n }, [audio]);\n\n const toggleBreakdown = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n setShowBreakdown((prev) => !prev);\n }, [audio]);\n\n const handlePlaySelectSound = useCallback(() => {\n audio.playSFX?.(\"menu_select\");\n }, [audio]);\n\n const handlePlayHoverSound = useCallback(() => {\n audio.playSFX?.(\"menu_hover\");\n }, [audio]);\n\n return (\n <div\n data-testid=\"end-screen-3d\"\n style={{\n width: \"100vw\",\n height: \"100vh\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n {/* Volume Control - outside Canvas to maintain AudioProvider context */}\n <VolumeControl position=\"top-right\" compact={isMobile} />\n\n {/* 3D Background Canvas */}\n <Canvas\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"absolute\",\n top: 0,\n left: 0,\n zIndex: Z_INDEX.ARENA,\n }}\n dpr={[1, 2]}\n gl={{\n antialias: true,\n alpha: false,\n powerPreference: \"high-performance\",\n }}\n onCreated={({ gl }) => {\n gl.setClearColor(theme.colors.UI_BACKGROUND_DARK, 1);\n }}\n data-testid=\"end-screen-canvas\"\n >\n {/* Camera */}\n <PerspectiveCamera makeDefault position={[0, 5, 15]} fov={60} />\n\n {/* Background scene */}\n <EndScreenBackground3D isVictory={isVictory} isMobile={isMobile} />\n </Canvas>\n\n {/* UI Overlay - outside Canvas for proper layout and AudioProvider context */}\n <div\n data-testid=\"end-screen-overlay\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontFamily: theme.koreanTypography.fontFamily,\n lineHeight: theme.koreanTypography.lineHeight,\n letterSpacing: theme.koreanTypography.letterSpacing,\n wordBreak: theme.koreanTypography.wordBreak,\n color: toCssColor(theme.colors.TEXT_PRIMARY),\n padding: layoutConstants.padding,\n boxSizing: \"border-box\",\n overflowY: \"auto\",\n zIndex: Z_INDEX.HUD,\n pointerEvents: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n maxHeight: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Winner Display Component */}\n <WinnerDisplay\n winner={winner}\n isVictory={isVictory}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Performance Rating Component */}\n <PerformanceRating\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n\n {/* Match Statistics Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showStats ? \"통계 숨기기\" : \"통계 보기\"}\n english={showStats ? \"Hide Stats\" : \"View Stats\"}\n onClick={toggleStats}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"secondary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-stats-button\"\n ariaLabel={showStats ? \"Hide match statistics\" : \"View match statistics\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Match Statistics Display */}\n {showStats && (\n <MatchStatisticsDisplay\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Performance Breakdown Toggle */}\n <div style={{ marginBottom: layoutConstants.spacing }}>\n <BaseButtonOverlayHtml\n korean={showBreakdown ? \"분석 숨기기\" : \"상세 분석\"}\n english={showBreakdown ? \"Hide Breakdown\" : \"View Breakdown\"}\n onClick={toggleBreakdown}\n onMouseEnter={() => audio.playSFX?.(\"menu_hover\")}\n variant=\"primary\"\n size={isMobile ? \"sm\" : \"md\"}\n fullWidth\n testId=\"toggle-breakdown-button\"\n ariaLabel={showBreakdown ? \"Hide performance breakdown\" : \"View performance breakdown\"}\n isMobile={isMobile}\n />\n </div>\n\n {/* Performance Breakdown Display */}\n {showBreakdown && (\n <PerformanceBreakdown\n matchStats={matchStats}\n isMobile={isMobile}\n isTablet={isTablet}\n />\n )}\n\n {/* Navigation Buttons Component */}\n <NavigationButtons\n onReturnToMenu={onReturnToMenu}\n onRematch={onRematch}\n onViewReplay={onViewReplay}\n isMobile={isMobile}\n isTablet={isTablet}\n width={screenWidth}\n onPlaySelectSound={handlePlaySelectSound}\n onPlayHoverSound={handlePlayHoverSound}\n />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ var DefeatAnimation3D = () => {
18
18
  const [reusablePosition] = useState(() => new THREE.Vector3());
19
19
  const [particlePositions] = useState(() => {
20
20
  const count = 100;
21
- const positions = new Float32Array(count * 3);
21
+ const positions = /* @__PURE__ */ new Float32Array(300);
22
22
  for (let i = 0; i < count; i++) {
23
23
  const i3 = i * 3;
24
24
  const radius = 2 + Math.random() * 3;
@@ -1 +1 @@
1
- {"version":3,"file":"DefeatAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/DefeatAnimation3D.tsx"],"sourcesContent":["import { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Defeat Animation 3D Component\n * Displays somber 3D particle effects for defeat screen\n * Uses blue/cyan tones to contrast with gold victory effects\n * Optimized for 60fps performance with object reuse\n */\nexport const DefeatAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const spiralRef = useRef<THREE.Group>(null);\n\n const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\n const [particlePositions] = useState(() => {\n const count = 100; // Fewer particles than victory for subdued effect\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 2 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.random() * Math.PI;\n\n positions[i3] = radius * Math.sin(phi) * Math.cos(theta);\n positions[i3 + 1] = radius * Math.cos(phi) - 1; // Lower starting position\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.15; // Slower than victory\n }\n\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 1.5) * 0.1; // Subtle pulsing\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n const yPos = Math.sin(time * 0.5) * 0.3 - 0.2;\n reusablePosition.set(0, yPos, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n if (spiralRef.current) {\n spiralRef.current.rotation.y = -time * 0.2; // Counter-rotation\n spiralRef.current.rotation.z = Math.sin(time * 0.3) * 0.2;\n }\n });\n\n useEffect(() => {\n const group = groupRef.current;\n const particles = particlesRef.current;\n const spiral = spiralRef.current;\n\n return () => {\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (spiral?.children && Array.isArray(spiral.children)) {\n spiral.children.forEach((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n if (child === particles || child === spiral) {\n return;\n }\n\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n } else if (child instanceof THREE.Points) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n };\n }, []);\n\n return (\n <group\n ref={groupRef}\n position={[0, 1, 0]}\n data-testid=\"defeat-animation-3d\"\n >\n {/* Defeat particles - blue/cyan theme */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_BLUE)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Descending spiral rings */}\n <group ref={spiralRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[1.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]} position={[0, -0.3, 0]}>\n <torusGeometry args={[2, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n transparent\n opacity={0.3}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]} position={[0, -0.6, 0]}>\n <torusGeometry args={[2.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n />\n </mesh>\n </group>\n\n {/* Central dimmed sphere */}\n <mesh>\n <sphereGeometry args={[0.4, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n emissive={KOREAN_COLORS.ACCENT_BLUE}\n emissiveIntensity={0.8}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n {/* Outer faint glow */}\n <mesh>\n <sphereGeometry args={[0.7, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.15}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for subdued glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={1.5}\n distance={8}\n color={KOREAN_COLORS.ACCENT_BLUE}\n />\n </group>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DefeatAnimation3D.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/DefeatAnimation3D.tsx"],"sourcesContent":["import { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Defeat Animation 3D Component\n * Displays somber 3D particle effects for defeat screen\n * Uses blue/cyan tones to contrast with gold victory effects\n * Optimized for 60fps performance with object reuse\n */\nexport const DefeatAnimation3D: React.FC = () => {\n const groupRef = useRef<THREE.Group>(null);\n const particlesRef = useRef<THREE.Points>(null);\n const spiralRef = useRef<THREE.Group>(null);\n\n const [reusableScale] = useState(() => new THREE.Vector3());\n const [reusablePosition] = useState(() => new THREE.Vector3());\n\n const [particlePositions] = useState(() => {\n const count = 100; // Fewer particles than victory for subdued effect\n const positions = new Float32Array(count * 3);\n\n for (let i = 0; i < count; i++) {\n const i3 = i * 3;\n const radius = 2 + Math.random() * 3;\n const theta = Math.random() * Math.PI * 2;\n const phi = Math.random() * Math.PI;\n\n positions[i3] = radius * Math.sin(phi) * Math.cos(theta);\n positions[i3 + 1] = radius * Math.cos(phi) - 1; // Lower starting position\n positions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);\n }\n\n return positions;\n });\n\n useFrame((state) => {\n const time = state.clock.elapsedTime;\n\n if (groupRef.current) {\n groupRef.current.rotation.y = time * 0.15; // Slower than victory\n }\n\n if (particlesRef.current) {\n const scale = 1 + Math.sin(time * 1.5) * 0.1; // Subtle pulsing\n reusableScale.setScalar(scale);\n particlesRef.current.scale.copy(reusableScale);\n \n const yPos = Math.sin(time * 0.5) * 0.3 - 0.2;\n reusablePosition.set(0, yPos, 0);\n particlesRef.current.position.copy(reusablePosition);\n }\n\n if (spiralRef.current) {\n spiralRef.current.rotation.y = -time * 0.2; // Counter-rotation\n spiralRef.current.rotation.z = Math.sin(time * 0.3) * 0.2;\n }\n });\n\n useEffect(() => {\n const group = groupRef.current;\n const particles = particlesRef.current;\n const spiral = spiralRef.current;\n\n return () => {\n if (particles) {\n particles.geometry?.dispose();\n if (particles.material) {\n (particles.material as THREE.Material).dispose();\n }\n }\n if (spiral?.children && Array.isArray(spiral.children)) {\n spiral.children.forEach((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n if (group?.children && Array.isArray(group.children)) {\n group.children.forEach((child) => {\n if (child === particles || child === spiral) {\n return;\n }\n\n if (child instanceof THREE.Mesh) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n } else if (child instanceof THREE.Points) {\n child.geometry?.dispose();\n if (child.material) {\n (child.material as THREE.Material).dispose();\n }\n }\n });\n }\n };\n }, []);\n\n return (\n <group\n ref={groupRef}\n position={[0, 1, 0]}\n data-testid=\"defeat-animation-3d\"\n >\n {/* Defeat particles - blue/cyan theme */}\n <points ref={particlesRef}>\n <bufferGeometry>\n <bufferAttribute\n attach=\"attributes-position\"\n count={100}\n itemSize={3}\n args={[particlePositions, 3]}\n />\n </bufferGeometry>\n <pointsMaterial\n size={0.15}\n color={new THREE.Color(KOREAN_COLORS.ACCENT_BLUE)}\n transparent\n opacity={0.6}\n sizeAttenuation\n depthWrite={false}\n />\n </points>\n\n {/* Descending spiral rings */}\n <group ref={spiralRef}>\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <torusGeometry args={[1.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.4}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 4, 0]} position={[0, -0.3, 0]}>\n <torusGeometry args={[2, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n transparent\n opacity={0.3}\n />\n </mesh>\n\n <mesh rotation={[Math.PI / 2, Math.PI / 2, 0]} position={[0, -0.6, 0]}>\n <torusGeometry args={[2.5, 0.03, 12, 64]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.2}\n />\n </mesh>\n </group>\n\n {/* Central dimmed sphere */}\n <mesh>\n <sphereGeometry args={[0.4, 32, 32]} />\n <meshStandardMaterial\n color={KOREAN_COLORS.ACCENT_BLUE}\n emissive={KOREAN_COLORS.ACCENT_BLUE}\n emissiveIntensity={0.8}\n transparent\n opacity={0.6}\n />\n </mesh>\n\n {/* Outer faint glow */}\n <mesh>\n <sphereGeometry args={[0.7, 32, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={0.15}\n side={THREE.BackSide}\n />\n </mesh>\n\n {/* Point light for subdued glow effect */}\n <pointLight\n position={[0, 0, 0]}\n intensity={1.5}\n distance={8}\n color={KOREAN_COLORS.ACCENT_BLUE}\n />\n </group>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MatchStatisticsDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface MatchStatisticsDisplayProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Props for StatRow component\n */\ninterface StatRowProps {\n readonly label: string;\n readonly value: string | number;\n readonly highlight?: boolean;\n readonly fontSize: number;\n readonly spacing: number;\n}\n\n/**\n * Individual stat row component\n */\nconst StatRow: React.FC<StatRowProps> = ({\n label,\n value,\n highlight = false,\n fontSize,\n spacing,\n}) => (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: spacing / 2,\n fontSize,\n color: toCssColor(\n highlight ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_SECONDARY\n ),\n }}\n >\n <span>{label}:</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n {value}\n </span>\n </div>\n);\n\n/**\n * Props for PlayerStats component\n */\ninterface PlayerStatsProps {\n readonly playerNum: 1 | 2;\n readonly stats: PlayerMatchStats;\n readonly isWinner: boolean;\n readonly fontSize: number;\n readonly labelFontSize: number;\n readonly padding: number;\n readonly spacing: number;\n}\n\n/**\n * Player statistics panel component\n */\nconst PlayerStats: React.FC<PlayerStatsProps> = ({\n playerNum,\n stats,\n isWinner,\n fontSize,\n labelFontSize,\n padding,\n spacing,\n}) => {\n return (\n <div\n style={{\n flex: 1,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"8px\",\n padding,\n }}\n data-testid={`player${playerNum}-stats`}\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.PRIMARY_CYAN\n ),\n marginBottom: spacing,\n textAlign: \"center\",\n }}\n >\n 플레이어 {playerNum} | Player {playerNum}\n {isWinner && \" 🏆\"}\n </div>\n\n <StatRow\n label=\"승리 | Wins\"\n value={stats.wins}\n highlight={stats.wins > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"패배 | Losses\"\n value={stats.losses}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"가한 피해 | Damage Dealt\"\n value={stats.totalDamageDealt}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"받은 피해 | Damage Taken\"\n value={stats.totalDamageReceived}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"명중 | Hits Landed\"\n value={stats.hitsLanded}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"피격 | Hits Taken\"\n value={stats.hitsTaken}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"완벽한 타격 | Perfect Strikes\"\n value={stats.perfectStrikes}\n highlight={stats.perfectStrikes > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"급소 공격 | Vital Point Hits\"\n value={stats.vitalPointHits}\n highlight={stats.vitalPointHits > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"연승 | Consecutive Wins\"\n value={stats.consecutiveWins}\n fontSize={fontSize}\n spacing={spacing}\n />\n\n {stats.techniques && stats.techniques.length > 0 && (\n <div style={{ marginTop: spacing }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: spacing / 2,\n }}\n >\n 사용한 기술 | Techniques Used:\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n paddingLeft: spacing,\n }}\n >\n {stats.techniques.slice(0, 5).join(\", \")}\n {stats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Match Statistics Display Component\n * Shows detailed combat statistics for both players\n */\nexport const MatchStatisticsDisplay: React.FC<MatchStatisticsDisplayProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 14 : 16;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 10 : isTablet ? 15 : 20;\n const spacing = isMobile ? 8 : isTablet ? 10 : 12;\n\n return (\n <div\n data-testid=\"match-statistics-display\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: spacing * 2,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Overall Match Stats */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: spacing * 1.5,\n }}\n >\n 경기 통계 | Match Statistics\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"repeat(4, 1fr)\",\n gap: spacing,\n marginBottom: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"overall-stats\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 라운드 | Rounds\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.currentRound} / {matchStats.maxRounds}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 시간 | Duration\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {Math.floor(matchStats.matchDuration / 60)}:\n {(matchStats.matchDuration % 60).toString().padStart(2, \"0\")}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 치명타 | Critical Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 공격 | Vital Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.vitalPointHits}\n </div>\n </div>\n </div>\n\n {/* Player Stats Side by Side */}\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing * 1.5,\n }}\n >\n <PlayerStats\n playerNum={1}\n stats={matchStats.player1}\n isWinner={matchStats.winner === 0}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n <PlayerStats\n playerNum={2}\n stats={matchStats.player2}\n isWinner={matchStats.winner === 1}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n </div>\n\n {/* Score Display */}\n <div\n style={{\n marginTop: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"final-score\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: spacing / 2,\n }}\n >\n 최종 점수 | Final Score\n </div>\n <div\n style={{\n fontSize: labelFontSize + 4,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {matchStats.finalScore.player1} - {matchStats.finalScore.player2}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"MatchStatisticsDisplayOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.tsx"],"sourcesContent":["import React from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface MatchStatisticsDisplayProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Props for StatRow component\n */\ninterface StatRowProps {\n readonly label: string;\n readonly value: string | number;\n readonly highlight?: boolean;\n readonly fontSize: number;\n readonly spacing: number;\n}\n\n/**\n * Individual stat row component\n */\nconst StatRow: React.FC<StatRowProps> = ({\n label,\n value,\n highlight = false,\n fontSize,\n spacing,\n}) => (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: spacing / 2,\n fontSize,\n color: toCssColor(\n highlight ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.TEXT_SECONDARY\n ),\n }}\n >\n <span>{label}:</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n }}\n >\n {value}\n </span>\n </div>\n);\n\n/**\n * Props for PlayerStats component\n */\ninterface PlayerStatsProps {\n readonly playerNum: 1 | 2;\n readonly stats: PlayerMatchStats;\n readonly isWinner: boolean;\n readonly fontSize: number;\n readonly labelFontSize: number;\n readonly padding: number;\n readonly spacing: number;\n}\n\n/**\n * Player statistics panel component\n */\nconst PlayerStats: React.FC<PlayerStatsProps> = ({\n playerNum,\n stats,\n isWinner,\n fontSize,\n labelFontSize,\n padding,\n spacing,\n}) => {\n return (\n <div\n style={{\n flex: 1,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.6),\n border: `2px solid ${hexToRgbaString(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.UI_BORDER,\n 0.8\n )}`,\n borderRadius: \"8px\",\n padding,\n }}\n data-testid={`player${playerNum}-stats`}\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(\n isWinner ? KOREAN_COLORS.ACCENT_GOLD : KOREAN_COLORS.PRIMARY_CYAN\n ),\n marginBottom: spacing,\n textAlign: \"center\",\n }}\n >\n 플레이어 {playerNum} | Player {playerNum}\n {isWinner && \" 🏆\"}\n </div>\n\n <StatRow\n label=\"승리 | Wins\"\n value={stats.wins}\n highlight={stats.wins > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"패배 | Losses\"\n value={stats.losses}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"가한 피해 | Damage Dealt\"\n value={stats.totalDamageDealt}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"받은 피해 | Damage Taken\"\n value={stats.totalDamageReceived}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"명중 | Hits Landed\"\n value={stats.hitsLanded}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"피격 | Hits Taken\"\n value={stats.hitsTaken}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"완벽한 타격 | Perfect Strikes\"\n value={stats.perfectStrikes}\n highlight={stats.perfectStrikes > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"급소 공격 | Vital Point Hits\"\n value={stats.vitalPointHits}\n highlight={stats.vitalPointHits > 0}\n fontSize={fontSize}\n spacing={spacing}\n />\n <StatRow\n label=\"연승 | Consecutive Wins\"\n value={stats.consecutiveWins}\n fontSize={fontSize}\n spacing={spacing}\n />\n\n {stats.techniques && stats.techniques.length > 0 && (\n <div style={{ marginTop: spacing }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: spacing / 2,\n }}\n >\n 사용한 기술 | Techniques Used:\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n paddingLeft: spacing,\n }}\n >\n {stats.techniques.slice(0, 5).join(\", \")}\n {stats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Match Statistics Display Component\n * Shows detailed combat statistics for both players\n */\nexport const MatchStatisticsDisplay: React.FC<MatchStatisticsDisplayProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 14 : 16;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 10 : isTablet ? 15 : 20;\n const spacing = isMobile ? 8 : isTablet ? 10 : 12;\n\n return (\n <div\n data-testid=\"match-statistics-display\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: spacing * 2,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Overall Match Stats */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: spacing * 1.5,\n }}\n >\n 경기 통계 | Match Statistics\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr 1fr\" : \"repeat(4, 1fr)\",\n gap: spacing,\n marginBottom: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"overall-stats\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 라운드 | Rounds\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.currentRound} / {matchStats.maxRounds}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 시간 | Duration\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_PRIMARY),\n fontWeight: \"bold\",\n }}\n >\n {Math.floor(matchStats.matchDuration / 60)}:\n {(matchStats.matchDuration % 60).toString().padStart(2, \"0\")}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 치명타 | Critical Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.CRITICAL_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.criticalHits}\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 공격 | Vital Hits\n </div>\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n fontWeight: \"bold\",\n }}\n >\n {matchStats.vitalPointHits}\n </div>\n </div>\n </div>\n\n {/* Player Stats Side by Side */}\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing * 1.5,\n }}\n >\n <PlayerStats\n playerNum={1}\n stats={matchStats.player1}\n isWinner={matchStats.winner === 0}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n <PlayerStats\n playerNum={2}\n stats={matchStats.player2}\n isWinner={matchStats.winner === 1}\n fontSize={fontSize}\n labelFontSize={labelFontSize}\n padding={padding}\n spacing={spacing}\n />\n </div>\n\n {/* Score Display */}\n <div\n style={{\n marginTop: spacing * 1.5,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"final-score\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n marginBottom: spacing / 2,\n }}\n >\n 최종 점수 | Final Score\n </div>\n <div\n style={{\n fontSize: labelFontSize + 4,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {matchStats.finalScore.player1} - {matchStats.finalScore.player2}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"NavigationButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/NavigationButtonsOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { slideUpAnimation } from \"./animations\";\n\nexport interface NavigationButtonsProps {\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n readonly width: number;\n /** Optional audio callback for click sounds - passed from parent to avoid Html portal context issues */\n readonly onPlaySelectSound?: () => void;\n /** Optional audio callback for hover sounds - passed from parent to avoid Html portal context issues */\n readonly onPlayHoverSound?: () => void;\n}\n\n/**\n * Navigation Buttons Component\n * Provides action buttons for replay and menu navigation.\n *\n * This component delegates visual styling and accessibility behavior to\n * BaseButtonOverlayHtml, providing:\n * - Consistent Korean / English bilingual theming\n * - Centralized button behavior and standard browser keyboard support\n * - Reduced code duplication (113 lines saved: 237 → 124)\n *\n * Note: Audio callbacks are passed as props since this component is rendered\n * inside a Canvas Html portal which doesn't have access to AudioProvider context.\n */\nexport const NavigationButtons: React.FC<NavigationButtonsProps> = ({\n onReturnToMenu,\n onRematch,\n onViewReplay,\n isMobile,\n isTablet,\n width,\n onPlaySelectSound,\n onPlayHoverSound,\n}) => {\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n \n const buttonSize = width < 768 ? \"sm\" : \"md\";\n const buttonMinWidth = width < 768 ? \"200px\" : \"150px\";\n\n const handleReturnToMenu = useCallback(() => {\n onPlaySelectSound?.();\n onReturnToMenu();\n }, [onPlaySelectSound, onReturnToMenu]);\n\n const handleRematch = useCallback(() => {\n if (onRematch) {\n onPlaySelectSound?.();\n onRematch();\n }\n }, [onPlaySelectSound, onRematch]);\n\n const handleViewReplay = useCallback(() => {\n if (onViewReplay) {\n onPlaySelectSound?.();\n onViewReplay();\n }\n }, [onPlaySelectSound, onViewReplay]);\n\n return (\n <div\n data-testid=\"navigation-buttons\"\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing,\n marginTop: spacing * 2,\n animation: \"slideUp 0.6s ease-out 0.3s both\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {/* Return to Menu Button - Primary Action */}\n <BaseButtonOverlayHtml\n korean=\"메뉴로\"\n english=\"Return to Menu\"\n onClick={handleReturnToMenu}\n onMouseEnter={onPlayHoverSound}\n variant=\"primary\"\n size={buttonSize}\n testId=\"return-to-menu-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n\n {/* Rematch Button - Secondary Action */}\n {onRematch && (\n <BaseButtonOverlayHtml\n korean=\"재대결\"\n english=\"Rematch\"\n onClick={handleRematch}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"rematch-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* Training Mode Button - Tertiary Action */}\n {onViewReplay && (\n <BaseButtonOverlayHtml\n korean=\"훈련\"\n english=\"Training\"\n onClick={handleViewReplay}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"view-training-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* CSS Animations */}\n <style>{`\n ${slideUpAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,IAAa,qBAAuD,EAClE,gBACA,WACA,cACA,UACA,UACA,OACA,mBACA,uBACI;CACJ,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAEhD,MAAM,aAAa,QAAQ,MAAM,OAAO;CACxC,MAAM,iBAAiB,QAAQ,MAAM,UAAU;CAE/C,MAAM,qBAAqB,kBAAkB;EAC3C,oBAAoB;EACpB,eAAe;CACjB,GAAG,CAAC,mBAAmB,cAAc,CAAC;CAEtC,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,WAAW;GACb,oBAAoB;GACpB,UAAU;EACZ;CACF,GAAG,CAAC,mBAAmB,SAAS,CAAC;CAEjC,MAAM,mBAAmB,kBAAkB;EACzC,IAAI,cAAc;GAChB,oBAAoB;GACpB,aAAa;EACf;CACF,GAAG,CAAC,mBAAmB,YAAY,CAAC;CAEpC,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,eAAe,WAAW,WAAW;GACrC,KAAK;GACL,WAAW,UAAU;GACrB,WAAW;GACX,YAAY;GACZ,gBAAgB;EAClB;YAVF;GAaE,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAGA,aACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAIF,gBACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAIH,oBAAC,SAAD,EAAA,UAAQ;UACJ,iBAAiB;QACZ,CAAA;EACN;;AAET"}
1
+ {"version":3,"file":"NavigationButtonsOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/NavigationButtonsOverlayHtml.tsx"],"sourcesContent":["import React, { useCallback } from \"react\";\nimport { BaseButtonOverlayHtml } from \"../../../shared/base/BaseButtonOverlayHtml\";\nimport { slideUpAnimation } from \"./animations\";\n\nexport interface NavigationButtonsProps {\n readonly onReturnToMenu: () => void;\n readonly onRematch?: () => void;\n readonly onViewReplay?: () => void;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n readonly width: number;\n /** Optional audio callback for click sounds - passed from parent to avoid Html portal context issues */\n readonly onPlaySelectSound?: () => void;\n /** Optional audio callback for hover sounds - passed from parent to avoid Html portal context issues */\n readonly onPlayHoverSound?: () => void;\n}\n\n/**\n * Navigation Buttons Component\n * Provides action buttons for replay and menu navigation.\n *\n * This component delegates visual styling and accessibility behavior to\n * BaseButtonOverlayHtml, providing:\n * - Consistent Korean / English bilingual theming\n * - Centralized button behavior and standard browser keyboard support\n * - Reduced code duplication (113 lines saved: 237 → 124)\n *\n * Note: Audio callbacks are passed as props since this component is rendered\n * inside a Canvas Html portal which doesn't have access to AudioProvider context.\n */\nexport const NavigationButtons: React.FC<NavigationButtonsProps> = ({\n onReturnToMenu,\n onRematch,\n onViewReplay,\n isMobile,\n isTablet,\n width,\n onPlaySelectSound,\n onPlayHoverSound,\n}) => {\n const spacing = isMobile ? 10 : isTablet ? 12 : 15;\n \n const buttonSize = width < 768 ? \"sm\" : \"md\";\n const buttonMinWidth = width < 768 ? \"200px\" : \"150px\";\n\n const handleReturnToMenu = useCallback(() => {\n onPlaySelectSound?.();\n onReturnToMenu();\n }, [onPlaySelectSound, onReturnToMenu]);\n\n const handleRematch = useCallback(() => {\n if (onRematch) {\n onPlaySelectSound?.();\n onRematch();\n }\n }, [onPlaySelectSound, onRematch]);\n\n const handleViewReplay = useCallback(() => {\n if (onViewReplay) {\n onPlaySelectSound?.();\n onViewReplay();\n }\n }, [onPlaySelectSound, onViewReplay]);\n\n return (\n <div\n data-testid=\"navigation-buttons\"\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: spacing,\n marginTop: spacing * 2,\n animation: \"slideUp 0.6s ease-out 0.3s both\",\n alignItems: \"center\",\n justifyContent: \"center\",\n }}\n >\n {/* Return to Menu Button - Primary Action */}\n <BaseButtonOverlayHtml\n korean=\"메뉴로\"\n english=\"Return to Menu\"\n onClick={handleReturnToMenu}\n onMouseEnter={onPlayHoverSound}\n variant=\"primary\"\n size={buttonSize}\n testId=\"return-to-menu-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n\n {/* Rematch Button - Secondary Action */}\n {onRematch && (\n <BaseButtonOverlayHtml\n korean=\"재대결\"\n english=\"Rematch\"\n onClick={handleRematch}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"rematch-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* Training Mode Button - Tertiary Action */}\n {onViewReplay && (\n <BaseButtonOverlayHtml\n korean=\"훈련\"\n english=\"Training\"\n onClick={handleViewReplay}\n onMouseEnter={onPlayHoverSound}\n variant=\"secondary\"\n size={buttonSize}\n testId=\"view-training-button\"\n isMobile={isMobile}\n style={{ minWidth: buttonMinWidth }}\n />\n )}\n\n {/* CSS Animations */}\n <style>{`\n ${slideUpAnimation}\n `}</style>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,IAAa,qBAAuD,EAClE,gBACA,WACA,cACA,UACA,UACA,OACA,mBACA,uBACI;CACJ,MAAM,UAAU,WAAW,KAAK,WAAW,KAAK;CAEhD,MAAM,aAAa,QAAQ,MAAM,OAAO;CACxC,MAAM,iBAAiB,QAAQ,MAAM,UAAU;CAE/C,MAAM,qBAAqB,kBAAkB;EAC3C,oBAAoB;EACpB,eAAe;CACjB,GAAG,CAAC,mBAAmB,cAAc,CAAC;CAEtC,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,WAAW;GACb,oBAAoB;GACpB,UAAU;EACZ;CACF,GAAG,CAAC,mBAAmB,SAAS,CAAC;CAEjC,MAAM,mBAAmB,kBAAkB;EACzC,IAAI,cAAc;GAChB,oBAAoB;GACpB,aAAa;EACf;CACF,GAAG,CAAC,mBAAmB,YAAY,CAAC;CAEpC,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,eAAe,WAAW,WAAW;GACrC,KAAK;GACL,WAAW,UAAU;GACrB,WAAW;GACX,YAAY;GACZ,gBAAgB;EAClB;EAVF,UAAA;GAaE,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAGA,aACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAIF,gBACC,oBAAC,uBAAD;IACE,QAAO;IACP,SAAQ;IACR,SAAS;IACT,cAAc;IACd,SAAQ;IACR,MAAM;IACN,QAAO;IACG;IACV,OAAO,EAAE,UAAU,eAAe;GACnC,CAAA;GAIH,oBAAC,SAAD,EAAA,UAAQ;UACJ,iBAAiB;QACZ,CAAA;EACN;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"PerformanceBreakdownOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface PerformanceBreakdownProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Category rating component\n */\ninterface CategoryRatingProps {\n readonly category: string;\n readonly korean: string;\n readonly value: number;\n readonly maxValue: number;\n readonly color: number;\n readonly fontSize: number;\n}\n\nconst CategoryRating: React.FC<CategoryRatingProps> = ({\n category,\n korean,\n value,\n maxValue,\n color,\n fontSize,\n}) => {\n const percentage = Math.round((value / maxValue) * 100);\n const grade = percentage >= 90 ? \"S\" : percentage >= 75 ? \"A\" : percentage >= 60 ? \"B\" : \"C\";\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n }}\n data-testid={`category-${category.toLowerCase()}`}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <span>{korean} | {category}</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(color),\n fontSize: fontSize + 2,\n }}\n >\n {grade}\n </span>\n </div>\n <div\n style={{\n width: \"100%\",\n height: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5),\n borderRadius: \"4px\",\n overflow: \"hidden\",\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: toCssColor(color),\n transition: \"width 0.5s ease-out\",\n }}\n data-testid={`progress-${category.toLowerCase()}`}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Calculate category scores from match statistics\n */\nfunction calculateCategoryScores(stats: PlayerMatchStats) {\n const offense = Math.min(\n (stats.totalDamageDealt / 100) * 50 + (stats.hitsLanded / 10) * 50,\n 100\n );\n\n const defenseRaw = Math.max(0, 100 - stats.totalDamageReceived / 2);\n const defense = Math.min(defenseRaw, 100);\n\n const technique = Math.min(\n stats.perfectStrikes * 15 + stats.vitalPointHits * 10,\n 100\n );\n\n const damageRatio =\n stats.totalDamageReceived > 0\n ? stats.totalDamageDealt / stats.totalDamageReceived\n : stats.totalDamageDealt > 0\n ? 2\n : 0;\n const efficiency = Math.min(damageRatio * 40, 100);\n\n return { offense, defense, technique, efficiency };\n}\n\n/**\n * Performance Breakdown Component\n * Provides detailed analysis of combat performance by category\n */\nexport const PerformanceBreakdown: React.FC<PerformanceBreakdownProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 13 : 14;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 12 : isTablet ? 15 : 18;\n\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n const scores = useMemo(\n () => calculateCategoryScores(winnerStats),\n [winnerStats]\n );\n\n const techniqueCount = winnerStats.techniques?.length ?? 0;\n const uniqueTechniques = useMemo(() => {\n if (!winnerStats.techniques || winnerStats.techniques.length === 0) {\n return 0;\n }\n return new Set(winnerStats.techniques).size;\n }, [winnerStats.techniques]);\n\n return (\n <div\n data-testid=\"performance-breakdown\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: padding,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: padding * 1.5,\n }}\n data-testid=\"breakdown-title\"\n >\n 전투 분석 | Performance Breakdown\n </div>\n\n {/* Category Ratings */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: padding,\n marginBottom: padding * 1.5,\n }}\n data-testid=\"category-ratings\"\n >\n <CategoryRating\n category=\"Offense\"\n korean=\"공격\"\n value={scores.offense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_RED}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Defense\"\n korean=\"방어\"\n value={scores.defense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_BLUE}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Technique\"\n korean=\"기술\"\n value={scores.technique}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_GOLD}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Efficiency\"\n korean=\"효율\"\n value={scores.efficiency}\n maxValue={100}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n fontSize={fontSize}\n />\n </div>\n\n {/* Technique Analysis */}\n <div\n style={{\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"technique-analysis\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n marginBottom: padding / 2,\n textAlign: \"center\",\n }}\n >\n 기술 사용 분석 | Technique Analysis\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr 1fr\",\n gap: padding,\n marginTop: padding,\n }}\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n >\n {techniqueCount}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 총 기술 | Total Uses\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {uniqueTechniques}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 고유 기술 | Unique\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 타격 | Vital Hits\n </div>\n </div>\n </div>\n\n {/* Most Used Techniques */}\n {winnerStats.techniques && winnerStats.techniques.length > 0 && (\n <div\n style={{\n marginTop: padding,\n paddingTop: padding,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.UI_BORDER,\n 0.3\n )}`,\n }}\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 2,\n }}\n >\n 주요 기술 | Primary Techniques:\n </div>\n <div\n style={{\n fontSize: fontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n lineHeight: 1.6,\n }}\n >\n {winnerStats.techniques.slice(0, 5).join(\", \")}\n {winnerStats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n\n {/* Combat Effectiveness Summary */}\n <div\n style={{\n marginTop: padding,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"effectiveness-summary\"\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 4,\n }}\n >\n 전투 효율성 | Combat Effectiveness\n </div>\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {Math.round((scores.offense + scores.defense + scores.technique + scores.efficiency) / 4)}%\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PerformanceBreakdownOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.tsx"],"sourcesContent":["import React, { useMemo } from \"react\";\nimport { MatchStatistics } from \"../../../../systems/combat\";\nimport { PlayerMatchStats } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface PerformanceBreakdownProps {\n readonly matchStats: MatchStatistics;\n readonly isMobile: boolean;\n readonly isTablet: boolean;\n}\n\n/**\n * Helper to convert hex color to CSS string\n */\nconst toCssColor = (hex: number): string => hexToRgbaString(hex, 1);\n\n/**\n * Category rating component\n */\ninterface CategoryRatingProps {\n readonly category: string;\n readonly korean: string;\n readonly value: number;\n readonly maxValue: number;\n readonly color: number;\n readonly fontSize: number;\n}\n\nconst CategoryRating: React.FC<CategoryRatingProps> = ({\n category,\n korean,\n value,\n maxValue,\n color,\n fontSize,\n}) => {\n const percentage = Math.round((value / maxValue) * 100);\n const grade = percentage >= 90 ? \"S\" : percentage >= 75 ? \"A\" : percentage >= 60 ? \"B\" : \"C\";\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"6px\",\n }}\n data-testid={`category-${category.toLowerCase()}`}\n >\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <span>{korean} | {category}</span>\n <span\n style={{\n fontWeight: \"bold\",\n color: toCssColor(color),\n fontSize: fontSize + 2,\n }}\n >\n {grade}\n </span>\n </div>\n <div\n style={{\n width: \"100%\",\n height: \"8px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.5),\n borderRadius: \"4px\",\n overflow: \"hidden\",\n }}\n >\n <div\n style={{\n width: `${percentage}%`,\n height: \"100%\",\n background: toCssColor(color),\n transition: \"width 0.5s ease-out\",\n }}\n data-testid={`progress-${category.toLowerCase()}`}\n />\n </div>\n </div>\n );\n};\n\n/**\n * Calculate category scores from match statistics\n */\nfunction calculateCategoryScores(stats: PlayerMatchStats) {\n const offense = Math.min(\n (stats.totalDamageDealt / 100) * 50 + (stats.hitsLanded / 10) * 50,\n 100\n );\n\n const defenseRaw = Math.max(0, 100 - stats.totalDamageReceived / 2);\n const defense = Math.min(defenseRaw, 100);\n\n const technique = Math.min(\n stats.perfectStrikes * 15 + stats.vitalPointHits * 10,\n 100\n );\n\n const damageRatio =\n stats.totalDamageReceived > 0\n ? stats.totalDamageDealt / stats.totalDamageReceived\n : stats.totalDamageDealt > 0\n ? 2\n : 0;\n const efficiency = Math.min(damageRatio * 40, 100);\n\n return { offense, defense, technique, efficiency };\n}\n\n/**\n * Performance Breakdown Component\n * Provides detailed analysis of combat performance by category\n */\nexport const PerformanceBreakdown: React.FC<PerformanceBreakdownProps> = ({\n matchStats,\n isMobile,\n isTablet,\n}) => {\n const fontSize = isMobile ? 12 : isTablet ? 13 : 14;\n const labelFontSize = isMobile ? 14 : isTablet ? 16 : 18;\n const padding = isMobile ? 12 : isTablet ? 15 : 18;\n\n const winnerStats = useMemo(\n () => (matchStats.winner === 0 ? matchStats.player1 : matchStats.player2),\n [matchStats]\n );\n\n const scores = useMemo(\n () => calculateCategoryScores(winnerStats),\n [winnerStats]\n );\n\n const techniqueCount = winnerStats.techniques?.length ?? 0;\n const uniqueTechniques = useMemo(() => {\n if (!winnerStats.techniques || winnerStats.techniques.length === 0) {\n return 0;\n }\n return new Set(winnerStats.techniques).size;\n }, [winnerStats.techniques]);\n\n return (\n <div\n data-testid=\"performance-breakdown\"\n style={{\n width: isMobile ? \"95%\" : isTablet ? \"80%\" : \"70%\",\n maxWidth: \"900px\",\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.6)}`,\n borderRadius: \"12px\",\n padding: padding * 1.5,\n marginBottom: padding,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n textAlign: \"center\",\n marginBottom: padding * 1.5,\n }}\n data-testid=\"breakdown-title\"\n >\n 전투 분석 | Performance Breakdown\n </div>\n\n {/* Category Ratings */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: padding,\n marginBottom: padding * 1.5,\n }}\n data-testid=\"category-ratings\"\n >\n <CategoryRating\n category=\"Offense\"\n korean=\"공격\"\n value={scores.offense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_RED}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Defense\"\n korean=\"방어\"\n value={scores.defense}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_BLUE}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Technique\"\n korean=\"기술\"\n value={scores.technique}\n maxValue={100}\n color={KOREAN_COLORS.ACCENT_GOLD}\n fontSize={fontSize}\n />\n <CategoryRating\n category=\"Efficiency\"\n korean=\"효율\"\n value={scores.efficiency}\n maxValue={100}\n color={KOREAN_COLORS.PRIMARY_CYAN}\n fontSize={fontSize}\n />\n </div>\n\n {/* Technique Analysis */}\n <div\n style={{\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n }}\n data-testid=\"technique-analysis\"\n >\n <div\n style={{\n fontSize: labelFontSize,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n marginBottom: padding / 2,\n textAlign: \"center\",\n }}\n >\n 기술 사용 분석 | Technique Analysis\n </div>\n\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr 1fr\",\n gap: padding,\n marginTop: padding,\n }}\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n >\n {techniqueCount}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 총 기술 | Total Uses\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {uniqueTechniques}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 고유 기술 | Unique\n </div>\n </div>\n\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: fontSize + 6,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.VITAL_POINT_HIT),\n }}\n >\n {winnerStats.vitalPointHits ?? 0}\n </div>\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n 급소 타격 | Vital Hits\n </div>\n </div>\n </div>\n\n {/* Most Used Techniques */}\n {winnerStats.techniques && winnerStats.techniques.length > 0 && (\n <div\n style={{\n marginTop: padding,\n paddingTop: padding,\n borderTop: `1px solid ${hexToRgbaString(\n KOREAN_COLORS.UI_BORDER,\n 0.3\n )}`,\n }}\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 2,\n }}\n >\n 주요 기술 | Primary Techniques:\n </div>\n <div\n style={{\n fontSize: fontSize,\n color: toCssColor(KOREAN_COLORS.TEXT_SECONDARY),\n lineHeight: 1.6,\n }}\n >\n {winnerStats.techniques.slice(0, 5).join(\", \")}\n {winnerStats.techniques.length > 5 && \"...\"}\n </div>\n </div>\n )}\n </div>\n\n {/* Combat Effectiveness Summary */}\n <div\n style={{\n marginTop: padding,\n padding,\n background: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.4),\n borderRadius: \"8px\",\n textAlign: \"center\",\n }}\n data-testid=\"effectiveness-summary\"\n >\n <div\n style={{\n fontSize: fontSize - 2,\n color: toCssColor(KOREAN_COLORS.TEXT_TERTIARY),\n marginBottom: padding / 4,\n }}\n >\n 전투 효율성 | Combat Effectiveness\n </div>\n <div\n style={{\n fontSize: labelFontSize + 2,\n fontWeight: \"bold\",\n color: toCssColor(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {Math.round((scores.offense + scores.defense + scores.technique + scores.efficiency) / 4)}%\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}