blacktrigram 0.7.11 → 0.7.13

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 (73) hide show
  1. package/DATA_MODEL.md +347 -0
  2. package/lib/App.d.ts.map +1 -1
  3. package/lib/App2.js +0 -6
  4. package/lib/App2.js.map +1 -1
  5. package/lib/assets/index.css +102 -94
  6. package/lib/components/screens/combat/CombatScreen3D.d.ts +1 -1
  7. package/lib/components/screens/combat/CombatScreen3D.js +2 -2
  8. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  9. package/lib/components/screens/combat/components/controls/CombatButtons.d.ts.map +1 -1
  10. package/lib/components/screens/combat/components/controls/CombatButtons.js +22 -7
  11. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  12. package/lib/components/screens/combat/components/controls/CombatControlsPanel.d.ts +2 -0
  13. package/lib/components/screens/combat/components/controls/CombatControlsPanel.d.ts.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js +7 -4
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/PauseMenu.d.ts.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js +15 -5
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js +15 -16
  20. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  21. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js +1 -2
  22. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  23. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js +28 -24
  24. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js +2 -4
  26. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  27. package/lib/components/screens/controls/ControlsScreen3D.d.ts.map +1 -1
  28. package/lib/components/screens/controls/ControlsScreen3D.js +3 -2
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js +28 -30
  31. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/controls/components/VisualKeyboard3D.d.ts.map +1 -1
  33. package/lib/components/screens/controls/components/VisualKeyboard3D.js +4 -3
  34. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  36. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.d.ts.map +1 -1
  37. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js +5 -2
  38. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/philosophy/components/TrigramSymbol3D.d.ts.map +1 -1
  40. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js +4 -4
  41. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/training/components/hud/TrainingTopHUD.js +1 -1
  43. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  44. package/lib/components/shared/base/ResponsiveContainer.d.ts +6 -0
  45. package/lib/components/shared/base/ResponsiveContainer.d.ts.map +1 -1
  46. package/lib/components/shared/three/effects/ActionFeedback.js +1 -2
  47. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  48. package/lib/components/shared/three/effects/DamageNumbers.js +1 -2
  49. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  50. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js +5 -5
  51. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  52. package/lib/components/shared/three/ui/BreathingIndicator2.js +3 -2
  53. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  54. package/lib/components/shared/three/ui/TechniqueCard.d.ts.map +1 -1
  55. package/lib/components/shared/three/ui/TechniqueCard.js +27 -30
  56. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  57. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.d.ts.map +1 -1
  58. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js +57 -59
  59. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  60. package/lib/components/shared/ui/BaseHUDContainer.d.ts +40 -0
  61. package/lib/components/shared/ui/BaseHUDContainer.d.ts.map +1 -1
  62. package/lib/components/shared/ui/BaseHUDContainer.js +40 -0
  63. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  64. package/lib/components/shared/ui/MobileHUDLayout.d.ts +13 -0
  65. package/lib/components/shared/ui/MobileHUDLayout.d.ts.map +1 -1
  66. package/lib/components/shared/ui/SplashScreen.js +10 -10
  67. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  68. package/lib/components/shared/ui/VitalPointOverlayControlsPure.d.ts.map +1 -1
  69. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js +57 -62
  70. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  71. package/lib/components/shared/ui/VolumeControl.js +7 -7
  72. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  73. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return 0x00ff00; // Green\n if (health >= 60) return 0xffd700; // Yellow\n if (health >= 40) return 0xffa500; // Orange\n if (health >= 20) return 0xff6b6b; // Red\n return 0x8b0000; // Dark red\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n // Responsive sizing\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n // Group body parts for display\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return KOREAN_COLORS.HEALTH_FULL;\n if (health >= 60) return KOREAN_COLORS.ACCENT_GOLD;\n if (health >= 40) return KOREAN_COLORS.WARNING_ORANGE;\n if (health >= 20) return KOREAN_COLORS.PAIN_INDICATOR;\n return KOREAN_COLORS.NEGATIVE_RED_DARK;\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n // Responsive sizing\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n // Group body parts for display\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { KOREAN_COLORS } from "../../../../types/constants/colors.js";
1
2
  import { FONT_FAMILY } from "../../../../types/constants/typography.js";
2
3
  import { BreathingDisruptionSystem } from "../../../../systems/breathing/BreathingDisruptionSystem.js";
3
4
  import { createBreathingIndicator } from "../../../../systems/breathing/feedback.js";
@@ -45,7 +46,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
45
46
  alignItems: "center",
46
47
  gap: isMobile ? "6px" : "8px",
47
48
  padding,
48
- backgroundColor: "rgba(0, 0, 0, 0.7)",
49
+ backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, .7),
49
50
  borderRadius: "8px",
50
51
  border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,
51
52
  boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, .5)}`,
@@ -87,7 +88,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
87
88
  style: {
88
89
  fontSize: `${fontSize - 2}px`,
89
90
  fontFamily: FONT_FAMILY.KOREAN,
90
- color: breathingState.isRecovering ? hexToRgbaString(65280, .8) : hexToRgbaString(16777215, .6),
91
+ color: breathingState.isRecovering ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, .8) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, .6),
91
92
  whiteSpace: "nowrap"
92
93
  },
93
94
  children: breathingState.isRecovering ? "회복중 | Recovering" : `${secondsRemaining}s`
@@ -1 +1 @@
1
- {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(0x00ff00, 0.8)\n : hexToRgbaString(0xffffff, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CAEJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;AAGhE,iBAAgB;EACd,MAAM,WAAW,kBAAkB;AACjC,kBAAe,KAAK,KAAK,CAAC;KACzB,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;AAMtE,SAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAAO,CAEE;IAClE,CAAC,QAAQ,YAAY,CAAC;AAGzB,KAAI,CAAC,eAAe,QAClB,QAAO;CAIT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAGvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;AAEvE,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB;GACjB,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,OAAU,GAAI,GAC9B,gBAAgB,UAAU,GAAI;KAClC,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
1
+ {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, 0.8)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CAEJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;AAGhE,iBAAgB;EACd,MAAM,WAAW,kBAAkB;AACjC,kBAAe,KAAK,KAAK,CAAC;KACzB,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;AAMtE,SAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAAO,CAEE;IAClE,CAAC,QAAQ,YAAY,CAAC;AAGzB,KAAI,CAAC,eAAe,QAClB,QAAO;CAIT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAGvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;AAEvE,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;GAC1D,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,cAAc,gBAAgB,GAAI,GAClD,gBAAgB,cAAc,cAAc,GAAI;KACpD,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAExD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IAEtC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuWtD,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAExD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IAEtC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+VtD,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -3,6 +3,7 @@ import { FONT_FAMILY } from "../../../../types/constants/typography.js";
3
3
  import { AnimationType } from "../../../../systems/animation/builders/MartialArtsConstants.js";
4
4
  import { physicalReachCalculator } from "../../../../systems/physics/PhysicalReachCalculator.js";
5
5
  import { getArchetypePhysicalAttributes } from "../../../../data/archetypePhysicalAttributes.js";
6
+ import { hexColorToCSS, hexToRgbaString } from "../../../../utils/colorUtils.js";
6
7
  /* empty css */
7
8
  import { triggerHaptic } from "../../../../utils/haptics.js";
8
9
  import { useCallback, useMemo, useState } from "react";
@@ -73,25 +74,21 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
73
74
  return `${Math.ceil(remainingCooldown / 1e3)}s`;
74
75
  }, [remainingCooldown]);
75
76
  const backgroundColor = useMemo(() => {
76
- if (!isAvailable) return "rgba(50, 50, 50, 0.8)";
77
- if (isSelected) return `rgba(0, 255, 255, 0.3)`;
78
- return "rgba(26, 26, 30, 0.9)";
77
+ if (!isAvailable) return hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_LIGHT, .8);
78
+ if (isSelected) return hexToRgbaString(KOREAN_COLORS.NEON_CYAN, .3);
79
+ return hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, .9);
79
80
  }, [isAvailable, isSelected]);
80
81
  const borderColor = useMemo(() => {
81
- if (!isAvailable) return "#666";
82
- if (isSelected) return KOREAN_COLORS.PRIMARY_CYAN;
83
- return KOREAN_COLORS.ACCENT_GOLD;
82
+ if (!isAvailable) return hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT);
83
+ if (isSelected) return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);
84
+ return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);
84
85
  }, [isAvailable, isSelected]);
85
- const borderColorHex = useMemo(() => {
86
- if (typeof borderColor === "number") return `#${borderColor.toString(16).padStart(6, "0")}`;
87
- return borderColor;
88
- }, [borderColor]);
89
- const primaryCyanHex = useMemo(() => `#${KOREAN_COLORS.PRIMARY_CYAN.toString(16).padStart(6, "0")}`, []);
90
- const accentGoldHex = useMemo(() => `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, "0")}`, []);
86
+ const primaryCyanHex = useMemo(() => hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN), []);
87
+ const accentGoldHex = useMemo(() => hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD), []);
91
88
  const boxShadow = useMemo(() => {
92
- if (isSelected && isAvailable) return `0 0 15px rgba(0, 255, 255, 0.8), 0 0 25px rgba(0, 255, 255, 0.5)`;
93
- if (isAvailable) return `0 0 10px rgba(255, 170, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.5)`;
94
- return "0 2px 8px rgba(0, 0, 0, 0.5)";
89
+ if (isSelected && isAvailable) return `0 0 15px ${hexToRgbaString(KOREAN_COLORS.NEON_CYAN, .8)}, 0 0 25px ${hexToRgbaString(KOREAN_COLORS.NEON_CYAN, .5)}`;
90
+ if (isAvailable) return `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, .3)}, 0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK, .5)}`;
91
+ return `0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK, .5)}`;
95
92
  }, [isSelected, isAvailable]);
96
93
  const animationClass = useMemo(() => isAvailable ? "hud-animated" : "", [isAvailable]);
97
94
  const handleTouch = useCallback((e) => {
@@ -112,7 +109,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
112
109
  width: `${cardSize.width}px`,
113
110
  height: `${cardSize.height}px`,
114
111
  backgroundColor,
115
- border: `2px solid ${borderColorHex}`,
112
+ border: `2px solid ${borderColor}`,
116
113
  borderRadius: "8px",
117
114
  boxShadow,
118
115
  cursor: isAvailable ? "pointer" : "not-allowed",
@@ -155,15 +152,15 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
155
152
  right: "4px",
156
153
  width: `${cardSize.shortcutSize}px`,
157
154
  height: `${cardSize.shortcutSize}px`,
158
- backgroundColor: "rgba(0, 0, 0, 0.7)",
159
- border: "1px solid #888",
155
+ backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, .7),
156
+ border: `1px solid ${hexColorToCSS(KOREAN_COLORS.UI_GRAY)}`,
160
157
  borderRadius: "4px",
161
158
  display: "flex",
162
159
  alignItems: "center",
163
160
  justifyContent: "center",
164
161
  fontSize: `${cardSize.fontSize}px`,
165
162
  fontWeight: "bold",
166
- color: isAvailable ? "#fff" : "#666"
163
+ color: isAvailable ? hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)
167
164
  },
168
165
  children: keyboardShortcut
169
166
  }),
@@ -171,7 +168,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
171
168
  style: {
172
169
  fontSize: `${cardSize.fontSize}px`,
173
170
  fontWeight: "bold",
174
- color: isAvailable ? accentGoldHex : "#888",
171
+ color: isAvailable ? accentGoldHex : hexColorToCSS(KOREAN_COLORS.UI_GRAY),
175
172
  textAlign: "center",
176
173
  marginTop: "20px",
177
174
  lineHeight: "1.2"
@@ -181,7 +178,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
181
178
  /* @__PURE__ */ jsx("div", {
182
179
  style: {
183
180
  fontSize: `${cardSize.fontSize - 2}px`,
184
- color: isAvailable ? "#ccc" : "#666",
181
+ color: isAvailable ? hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT),
185
182
  textAlign: "center",
186
183
  marginTop: "2px",
187
184
  lineHeight: "1.1"
@@ -200,7 +197,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
200
197
  display: "flex",
201
198
  alignItems: "center",
202
199
  gap: "2px",
203
- color: isAvailable ? "#0f0" : "#666"
200
+ color: isAvailable ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)
204
201
  },
205
202
  children: [/* @__PURE__ */ jsx("span", { children: "⚡" }), /* @__PURE__ */ jsx("span", { children: staminaCost })]
206
203
  }), /* @__PURE__ */ jsxs("div", {
@@ -208,7 +205,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
208
205
  display: "flex",
209
206
  alignItems: "center",
210
207
  gap: "2px",
211
- color: isAvailable ? "#0ff" : "#666"
208
+ color: isAvailable ? hexColorToCSS(KOREAN_COLORS.NEON_CYAN) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT)
212
209
  },
213
210
  children: [/* @__PURE__ */ jsx("span", { children: "氣" }), /* @__PURE__ */ jsx("span", { children: kiCost })]
214
211
  })]
@@ -220,14 +217,14 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
220
217
  left: 0,
221
218
  right: 0,
222
219
  bottom: 0,
223
- backgroundColor: "rgba(0, 0, 0, 0.7)",
220
+ backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, .7),
224
221
  display: "flex",
225
222
  alignItems: "center",
226
223
  justifyContent: "center",
227
224
  borderRadius: "6px",
228
225
  fontSize: `${cardSize.shortcutSize}px`,
229
226
  fontWeight: "bold",
230
- color: "#f00"
227
+ color: hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED)
231
228
  },
232
229
  children: cooldownText
233
230
  }),
@@ -242,11 +239,11 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
242
239
  minWidth: "200px",
243
240
  maxWidth: "300px",
244
241
  padding: "10px",
245
- backgroundColor: "rgba(10, 10, 15, 0.95)",
242
+ backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, .95),
246
243
  border: `2px solid ${primaryCyanHex}`,
247
244
  borderRadius: "8px",
248
245
  fontSize: "12px",
249
- color: "#fff",
246
+ color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
250
247
  zIndex: 1e3,
251
248
  pointerEvents: "none",
252
249
  fontFamily: FONT_FAMILY.KOREAN
@@ -276,7 +273,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
276
273
  style: {
277
274
  fontSize: "11px",
278
275
  lineHeight: "1.4",
279
- color: "#ccc"
276
+ color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY)
280
277
  },
281
278
  children: technique.description.english
282
279
  }),
@@ -284,7 +281,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
284
281
  style: {
285
282
  marginTop: "8px",
286
283
  fontSize: "10px",
287
- color: "#aaa"
284
+ color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY)
288
285
  },
289
286
  children: [
290
287
  /* @__PURE__ */ jsxs("div", { children: [
@@ -313,7 +310,7 @@ var TechniqueCard = ({ technique, isSelected, isAvailable, staminaCost, kiCost,
313
310
  }), /* @__PURE__ */ jsx("div", {
314
311
  style: {
315
312
  fontSize: "9px",
316
- color: "#999"
313
+ color: hexColorToCSS(KOREAN_COLORS.UI_GRAY)
317
314
  },
318
315
  children: reachInfo.bodyPart
319
316
  })] })
@@ -1 +1 @@
1
- {"version":3,"file":"TechniqueCard.js","names":[],"sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"sourcesContent":["/**\n * TechniqueCard Component\n *\n * **Korean**: 기술 카드 컴포넌트 (Technique Card Component)\n *\n * Individual technique card displaying technique name, stamina cost, keyboard shortcut,\n * and availability state. Shows detailed tooltip on hover/focus with technique description.\n *\n * Uses Html overlay from @react-three/drei for positioning over 3D scene.\n *\n * @module components/shared/three/ui/TechniqueCard\n * @category Shared UI\n * @korean 기술카드\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { Technique } from \"../../../../types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { triggerHaptic } from \"../../../../utils/haptics\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { getArchetypePhysicalAttributes } from \"../../../../data/archetypePhysicalAttributes\";\nimport { physicalReachCalculator } from \"../../../../systems/physics\";\nimport { AnimationType } from \"../../../../systems/animation\";\nimport \"./HUDAnimations.css\";\n\n/**\n * Props for TechniqueCard component.\n */\nexport interface TechniqueCardProps {\n /** Technique to display */\n readonly technique: Technique;\n\n /** Whether technique is currently selected */\n readonly isSelected: boolean;\n\n /** Whether technique is available (sufficient resources and no cooldown) */\n readonly isAvailable: boolean;\n\n /** Stamina cost percentage (0-100) */\n readonly staminaCost: number;\n\n /** Ki cost percentage (0-100) */\n readonly kiCost: number;\n\n /** Remaining cooldown in milliseconds */\n readonly remainingCooldown?: number;\n\n /** Keyboard shortcut key */\n readonly keyboardShortcut: string;\n\n /** Click handler */\n readonly onClick: () => void;\n\n /** Hover handler */\n readonly onHover: (technique: Technique | null) => void;\n\n /** Whether rendering for mobile device */\n readonly isMobile: boolean;\n\n /** Player archetype for reach calculation (optional) */\n readonly playerArchetype?: PlayerArchetype;\n\n /** Player stance for reach calculation (optional) */\n readonly playerStance?: TrigramStance;\n\n /** @deprecated Card position no longer needed - parent handles layout */\n readonly position?: { x: number; y: number };\n}\n\n/**\n * TechniqueCard Component\n *\n * Displays a single technique card with Korean/English names, resource costs,\n * keyboard shortcut, and availability indicators.\n *\n * @param props - Component props\n * @returns TechniqueCard component\n */\nexport const TechniqueCard: React.FC<TechniqueCardProps> = ({\n technique,\n isSelected,\n isAvailable,\n staminaCost,\n kiCost,\n remainingCooldown,\n keyboardShortcut,\n onClick,\n onHover,\n isMobile,\n playerArchetype,\n playerStance,\n // position prop is deprecated but kept for backwards compatibility\n}) => {\n const [showTooltip, setShowTooltip] = useState(false);\n\n // Calculate effective reach if player info is available\n const reachInfo = useMemo(() => {\n if (!playerArchetype || !playerStance || !technique.animation?.type) {\n return null;\n }\n\n // Map TechniqueAnimationConfig.type to AnimationType\n // For now, use a simple default mapping\n // TODO: Create proper mapping from AttackAnimationType to AnimationType\n const animationType = AnimationType.JAB; // Default fallback\n\n const physicalAttributes = getArchetypePhysicalAttributes(playerArchetype);\n const maxReach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animationType,\n playerStance\n );\n\n // Determine body part from technique type using PhysicalReachCalculator\n const techniqueType = physicalReachCalculator.getTechniqueTypeFromAnimation(animationType);\n let bodyPart: string;\n \n switch (techniqueType) {\n case \"punch\":\n case \"elbow\":\n bodyPart = \"Arm (팔)\";\n break;\n case \"kick\":\n case \"knee\":\n bodyPart = \"Leg (다리)\";\n break;\n case \"pressure_point\":\n default:\n bodyPart = \"Body (몸통)\";\n break;\n }\n\n return {\n maxReach: (maxReach * 100).toFixed(1), // Convert to cm\n bodyPart,\n };\n }, [playerArchetype, playerStance, technique.animation]);\n\n // Calculate card size based on device\n const cardSize = useMemo(\n () => ({\n width: isMobile ? 70 : 90,\n height: isMobile ? 80 : 100,\n fontSize: isMobile ? 10 : 12,\n shortcutSize: isMobile ? 16 : 20,\n }),\n [isMobile]\n );\n\n // Format cooldown time\n const cooldownText = useMemo(() => {\n if (!remainingCooldown || remainingCooldown <= 0) return null;\n const seconds = Math.ceil(remainingCooldown / 1000);\n return `${seconds}s`;\n }, [remainingCooldown]);\n\n // Card background color based on state\n const backgroundColor = useMemo(() => {\n if (!isAvailable) return \"rgba(50, 50, 50, 0.8)\";\n if (isSelected) return `rgba(0, 255, 255, 0.3)`;\n return \"rgba(26, 26, 30, 0.9)\";\n }, [isAvailable, isSelected]);\n\n // Border color based on state\n const borderColor = useMemo(() => {\n if (!isAvailable) return \"#666\";\n if (isSelected) return KOREAN_COLORS.PRIMARY_CYAN;\n return KOREAN_COLORS.ACCENT_GOLD;\n }, [isAvailable, isSelected]);\n\n // Convert color to hex string helper\n const borderColorHex = useMemo(() => {\n if (typeof borderColor === \"number\") {\n return `#${borderColor.toString(16).padStart(6, \"0\")}`;\n }\n return borderColor;\n }, [borderColor]);\n\n // Helper for converting KOREAN_COLORS to hex\n const primaryCyanHex = useMemo(\n () => `#${KOREAN_COLORS.PRIMARY_CYAN.toString(16).padStart(6, \"0\")}`,\n []\n );\n const accentGoldHex = useMemo(\n () => `#${KOREAN_COLORS.ACCENT_GOLD.toString(16).padStart(6, \"0\")}`,\n []\n );\n\n // Border glow effect for selected card\n const boxShadow = useMemo(() => {\n if (isSelected && isAvailable) {\n return `0 0 15px rgba(0, 255, 255, 0.8), 0 0 25px rgba(0, 255, 255, 0.5)`;\n }\n if (isAvailable) {\n return `0 0 10px rgba(255, 170, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.5)`;\n }\n return \"0 2px 8px rgba(0, 0, 0, 0.5)\";\n }, [isSelected, isAvailable]);\n\n // Animation class based on availability state\n const animationClass = useMemo(\n () => (isAvailable ? \"hud-animated\" : \"\"),\n [isAvailable]\n );\n\n // Touch handler for mobile - provides immediate response without 300ms delay\n const handleTouch = useCallback(\n (e: React.TouchEvent) => {\n if (!isAvailable) return;\n e.preventDefault(); // Prevent ghost click on mobile\n triggerHaptic(\"light\");\n onClick();\n },\n [isAvailable, onClick]\n );\n\n return (\n <div\n role=\"button\"\n tabIndex={isAvailable ? 0 : -1}\n aria-label={`${technique.name.korean} (${technique.name.english}). Stamina: ${staminaCost}, Ki: ${kiCost}`}\n aria-disabled={!isAvailable}\n aria-describedby={showTooltip && isAvailable ? `tooltip-${technique.id}` : undefined}\n className={animationClass}\n style={{\n position: \"relative\",\n width: `${cardSize.width}px`,\n height: `${cardSize.height}px`,\n backgroundColor,\n border: `2px solid ${borderColorHex}`,\n borderRadius: \"8px\",\n boxShadow,\n cursor: isAvailable ? \"pointer\" : \"not-allowed\",\n transition: \"all 0.2s ease-in-out, transform 0.15s ease-out\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: \"6px\",\n fontFamily: FONT_FAMILY.KOREAN,\n opacity: isAvailable ? 1 : 0.5,\n touchAction: \"manipulation\", // Disable double-tap zoom\n userSelect: \"none\", // Prevent text selection on touch\n animation: isSelected && isAvailable ? \"techniqueSelected 1.5s ease-in-out infinite\" : \n isAvailable ? \"techniqueGlow 2s ease-in-out infinite\" : \"none\",\n }}\n onClick={isAvailable ? onClick : undefined}\n onTouchEnd={handleTouch}\n onMouseEnter={() => {\n setShowTooltip(true);\n onHover(technique);\n }}\n onMouseLeave={() => {\n setShowTooltip(false);\n onHover(null);\n }}\n onFocus={() => {\n setShowTooltip(true);\n onHover(technique);\n }}\n onBlur={() => {\n setShowTooltip(false);\n onHover(null);\n }}\n data-testid={`technique-card-${technique.id}`}\n >\n {/* Keyboard Shortcut */}\n <div\n style={{\n position: \"absolute\",\n top: \"4px\",\n right: \"4px\",\n width: `${cardSize.shortcutSize}px`,\n height: `${cardSize.shortcutSize}px`,\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n border: \"1px solid #888\",\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: `${cardSize.fontSize}px`,\n fontWeight: \"bold\",\n color: isAvailable ? \"#fff\" : \"#666\",\n }}\n >\n {keyboardShortcut}\n </div>\n\n {/* Technique Name (Korean) */}\n <div\n style={{\n fontSize: `${cardSize.fontSize}px`,\n fontWeight: \"bold\",\n color: isAvailable ? accentGoldHex : \"#888\",\n textAlign: \"center\",\n marginTop: \"20px\",\n lineHeight: \"1.2\",\n }}\n >\n {technique.name.korean}\n </div>\n\n {/* Technique Name (English) */}\n <div\n style={{\n fontSize: `${cardSize.fontSize - 2}px`,\n color: isAvailable ? \"#ccc\" : \"#666\",\n textAlign: \"center\",\n marginTop: \"2px\",\n lineHeight: \"1.1\",\n }}\n >\n {technique.name.english}\n </div>\n\n {/* Resource Costs */}\n <div\n style={{\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"auto\",\n fontSize: `${cardSize.fontSize - 2}px`,\n }}\n >\n {/* Stamina Cost */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n color: isAvailable ? \"#0f0\" : \"#666\",\n }}\n >\n <span>⚡</span>\n <span>{staminaCost}</span>\n </div>\n\n {/* Ki Cost */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n color: isAvailable ? \"#0ff\" : \"#666\",\n }}\n >\n <span>氣</span>\n <span>{kiCost}</span>\n </div>\n </div>\n\n {/* Cooldown Overlay */}\n {cooldownText && (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"6px\",\n fontSize: `${cardSize.shortcutSize}px`,\n fontWeight: \"bold\",\n color: \"#f00\",\n }}\n >\n {cooldownText}\n </div>\n )}\n\n {/* Tooltip */}\n {showTooltip && isAvailable && (\n <div\n id={`tooltip-${technique.id}`}\n role=\"tooltip\"\n style={{\n position: \"absolute\",\n bottom: `${cardSize.height + 10}px`,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n minWidth: \"200px\",\n maxWidth: \"300px\",\n padding: \"10px\",\n backgroundColor: \"rgba(10, 10, 15, 0.95)\",\n border: `2px solid ${primaryCyanHex}`,\n borderRadius: \"8px\",\n fontSize: \"12px\",\n color: \"#fff\",\n zIndex: 1000,\n pointerEvents: \"none\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n <div\n style={{\n fontWeight: \"bold\",\n marginBottom: \"6px\",\n color: accentGoldHex,\n }}\n >\n {technique.name.korean} | {technique.name.english}\n </div>\n <div\n style={{ fontSize: \"11px\", lineHeight: \"1.4\", marginBottom: \"8px\" }}\n >\n {technique.description.korean}\n </div>\n <div style={{ fontSize: \"11px\", lineHeight: \"1.4\", color: \"#ccc\" }}>\n {technique.description.english}\n </div>\n <div style={{ marginTop: \"8px\", fontSize: \"10px\", color: \"#aaa\" }}>\n <div>\n Damage: {technique.damage.min}-{technique.damage.max}\n </div>\n <div>Cooldown: {technique.cooldown / 1000}s</div>\n {technique.requiredStance && (\n <div>Stance: {technique.requiredStance}</div>\n )}\n {reachInfo && (\n <>\n <div style={{ marginTop: \"4px\", color: primaryCyanHex, fontWeight: \"bold\" }}>\n Reach: {reachInfo.maxReach}cm\n </div>\n <div style={{ fontSize: \"9px\", color: \"#999\" }}>\n {reachInfo.bodyPart}\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default TechniqueCard;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TechniqueCard.js","names":[],"sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"sourcesContent":["/**\n * TechniqueCard Component\n *\n * **Korean**: 기술 카드 컴포넌트 (Technique Card Component)\n *\n * Individual technique card displaying technique name, stamina cost, keyboard shortcut,\n * and availability state. Shows detailed tooltip on hover/focus with technique description.\n *\n * Uses Html overlay from @react-three/drei for positioning over 3D scene.\n *\n * @module components/shared/three/ui/TechniqueCard\n * @category Shared UI\n * @korean 기술카드\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport { Technique } from \"../../../../types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { triggerHaptic } from \"../../../../utils/haptics\";\nimport { PlayerArchetype, TrigramStance } from \"../../../../types/common\";\nimport { getArchetypePhysicalAttributes } from \"../../../../data/archetypePhysicalAttributes\";\nimport { physicalReachCalculator } from \"../../../../systems/physics\";\nimport { AnimationType } from \"../../../../systems/animation\";\nimport \"./HUDAnimations.css\";\n\n/**\n * Props for TechniqueCard component.\n */\nexport interface TechniqueCardProps {\n /** Technique to display */\n readonly technique: Technique;\n\n /** Whether technique is currently selected */\n readonly isSelected: boolean;\n\n /** Whether technique is available (sufficient resources and no cooldown) */\n readonly isAvailable: boolean;\n\n /** Stamina cost percentage (0-100) */\n readonly staminaCost: number;\n\n /** Ki cost percentage (0-100) */\n readonly kiCost: number;\n\n /** Remaining cooldown in milliseconds */\n readonly remainingCooldown?: number;\n\n /** Keyboard shortcut key */\n readonly keyboardShortcut: string;\n\n /** Click handler */\n readonly onClick: () => void;\n\n /** Hover handler */\n readonly onHover: (technique: Technique | null) => void;\n\n /** Whether rendering for mobile device */\n readonly isMobile: boolean;\n\n /** Player archetype for reach calculation (optional) */\n readonly playerArchetype?: PlayerArchetype;\n\n /** Player stance for reach calculation (optional) */\n readonly playerStance?: TrigramStance;\n\n /** @deprecated Card position no longer needed - parent handles layout */\n readonly position?: { x: number; y: number };\n}\n\n/**\n * TechniqueCard Component\n *\n * Displays a single technique card with Korean/English names, resource costs,\n * keyboard shortcut, and availability indicators.\n *\n * @param props - Component props\n * @returns TechniqueCard component\n */\nexport const TechniqueCard: React.FC<TechniqueCardProps> = ({\n technique,\n isSelected,\n isAvailable,\n staminaCost,\n kiCost,\n remainingCooldown,\n keyboardShortcut,\n onClick,\n onHover,\n isMobile,\n playerArchetype,\n playerStance,\n // position prop is deprecated but kept for backwards compatibility\n}) => {\n const [showTooltip, setShowTooltip] = useState(false);\n\n // Calculate effective reach if player info is available\n const reachInfo = useMemo(() => {\n if (!playerArchetype || !playerStance || !technique.animation?.type) {\n return null;\n }\n\n // Map TechniqueAnimationConfig.type to AnimationType\n // For now, use a simple default mapping\n // TODO: Create proper mapping from AttackAnimationType to AnimationType\n const animationType = AnimationType.JAB; // Default fallback\n\n const physicalAttributes = getArchetypePhysicalAttributes(playerArchetype);\n const maxReach = physicalReachCalculator.calculateMaxReach(\n physicalAttributes,\n animationType,\n playerStance\n );\n\n // Determine body part from technique type using PhysicalReachCalculator\n const techniqueType = physicalReachCalculator.getTechniqueTypeFromAnimation(animationType);\n let bodyPart: string;\n \n switch (techniqueType) {\n case \"punch\":\n case \"elbow\":\n bodyPart = \"Arm (팔)\";\n break;\n case \"kick\":\n case \"knee\":\n bodyPart = \"Leg (다리)\";\n break;\n case \"pressure_point\":\n default:\n bodyPart = \"Body (몸통)\";\n break;\n }\n\n return {\n maxReach: (maxReach * 100).toFixed(1), // Convert to cm\n bodyPart,\n };\n }, [playerArchetype, playerStance, technique.animation]);\n\n // Calculate card size based on device\n const cardSize = useMemo(\n () => ({\n width: isMobile ? 70 : 90,\n height: isMobile ? 80 : 100,\n fontSize: isMobile ? 10 : 12,\n shortcutSize: isMobile ? 16 : 20,\n }),\n [isMobile]\n );\n\n // Format cooldown time\n const cooldownText = useMemo(() => {\n if (!remainingCooldown || remainingCooldown <= 0) return null;\n const seconds = Math.ceil(remainingCooldown / 1000);\n return `${seconds}s`;\n }, [remainingCooldown]);\n\n // Card background color based on state\n const backgroundColor = useMemo(() => {\n if (!isAvailable) return hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_LIGHT, 0.8);\n if (isSelected) return hexToRgbaString(KOREAN_COLORS.NEON_CYAN, 0.3);\n return hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 0.9);\n }, [isAvailable, isSelected]);\n\n // Border color based on state\n const borderColor = useMemo(() => {\n if (!isAvailable) return hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT);\n if (isSelected) return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n }, [isAvailable, isSelected]);\n\n // Pre-computed hex color strings for styling\n const primaryCyanHex = useMemo(\n () => hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n []\n );\n const accentGoldHex = useMemo(\n () => hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD),\n []\n );\n\n // Border glow effect for selected card\n const boxShadow = useMemo(() => {\n if (isSelected && isAvailable) {\n return `0 0 15px ${hexToRgbaString(KOREAN_COLORS.NEON_CYAN, 0.8)}, 0 0 25px ${hexToRgbaString(KOREAN_COLORS.NEON_CYAN, 0.5)}`;\n }\n if (isAvailable) {\n return `0 0 10px ${hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.3)}, 0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK, 0.5)}`;\n }\n return `0 2px 8px ${hexToRgbaString(KOREAN_COLORS.BLACK, 0.5)}`;\n }, [isSelected, isAvailable]);\n\n // Animation class based on availability state\n const animationClass = useMemo(\n () => (isAvailable ? \"hud-animated\" : \"\"),\n [isAvailable]\n );\n\n // Touch handler for mobile - provides immediate response without 300ms delay\n const handleTouch = useCallback(\n (e: React.TouchEvent) => {\n if (!isAvailable) return;\n e.preventDefault(); // Prevent ghost click on mobile\n triggerHaptic(\"light\");\n onClick();\n },\n [isAvailable, onClick]\n );\n\n return (\n <div\n role=\"button\"\n tabIndex={isAvailable ? 0 : -1}\n aria-label={`${technique.name.korean} (${technique.name.english}). Stamina: ${staminaCost}, Ki: ${kiCost}`}\n aria-disabled={!isAvailable}\n aria-describedby={showTooltip && isAvailable ? `tooltip-${technique.id}` : undefined}\n className={animationClass}\n style={{\n position: \"relative\",\n width: `${cardSize.width}px`,\n height: `${cardSize.height}px`,\n backgroundColor,\n border: `2px solid ${borderColor}`,\n borderRadius: \"8px\",\n boxShadow,\n cursor: isAvailable ? \"pointer\" : \"not-allowed\",\n transition: \"all 0.2s ease-in-out, transform 0.15s ease-out\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: \"6px\",\n fontFamily: FONT_FAMILY.KOREAN,\n opacity: isAvailable ? 1 : 0.5,\n touchAction: \"manipulation\", // Disable double-tap zoom\n userSelect: \"none\", // Prevent text selection on touch\n animation: isSelected && isAvailable ? \"techniqueSelected 1.5s ease-in-out infinite\" : \n isAvailable ? \"techniqueGlow 2s ease-in-out infinite\" : \"none\",\n }}\n onClick={isAvailable ? onClick : undefined}\n onTouchEnd={handleTouch}\n onMouseEnter={() => {\n setShowTooltip(true);\n onHover(technique);\n }}\n onMouseLeave={() => {\n setShowTooltip(false);\n onHover(null);\n }}\n onFocus={() => {\n setShowTooltip(true);\n onHover(technique);\n }}\n onBlur={() => {\n setShowTooltip(false);\n onHover(null);\n }}\n data-testid={`technique-card-${technique.id}`}\n >\n {/* Keyboard Shortcut */}\n <div\n style={{\n position: \"absolute\",\n top: \"4px\",\n right: \"4px\",\n width: `${cardSize.shortcutSize}px`,\n height: `${cardSize.shortcutSize}px`,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n border: `1px solid ${hexColorToCSS(KOREAN_COLORS.UI_GRAY)}`,\n borderRadius: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: `${cardSize.fontSize}px`,\n fontWeight: \"bold\",\n color: isAvailable ? hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT),\n }}\n >\n {keyboardShortcut}\n </div>\n\n {/* Technique Name (Korean) */}\n <div\n style={{\n fontSize: `${cardSize.fontSize}px`,\n fontWeight: \"bold\",\n color: isAvailable ? accentGoldHex : hexColorToCSS(KOREAN_COLORS.UI_GRAY),\n textAlign: \"center\",\n marginTop: \"20px\",\n lineHeight: \"1.2\",\n }}\n >\n {technique.name.korean}\n </div>\n\n {/* Technique Name (English) */}\n <div\n style={{\n fontSize: `${cardSize.fontSize - 2}px`,\n color: isAvailable ? hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT),\n textAlign: \"center\",\n marginTop: \"2px\",\n lineHeight: \"1.1\",\n }}\n >\n {technique.name.english}\n </div>\n\n {/* Resource Costs */}\n <div\n style={{\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"auto\",\n fontSize: `${cardSize.fontSize - 2}px`,\n }}\n >\n {/* Stamina Cost */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n color: isAvailable ? hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT),\n }}\n >\n <span>⚡</span>\n <span>{staminaCost}</span>\n </div>\n\n {/* Ki Cost */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"2px\",\n color: isAvailable ? hexColorToCSS(KOREAN_COLORS.NEON_CYAN) : hexColorToCSS(KOREAN_COLORS.UI_DISABLED_TEXT),\n }}\n >\n <span>氣</span>\n <span>{kiCost}</span>\n </div>\n </div>\n\n {/* Cooldown Overlay */}\n {cooldownText && (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"6px\",\n fontSize: `${cardSize.shortcutSize}px`,\n fontWeight: \"bold\",\n color: hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED),\n }}\n >\n {cooldownText}\n </div>\n )}\n\n {/* Tooltip */}\n {showTooltip && isAvailable && (\n <div\n id={`tooltip-${technique.id}`}\n role=\"tooltip\"\n style={{\n position: \"absolute\",\n bottom: `${cardSize.height + 10}px`,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n minWidth: \"200px\",\n maxWidth: \"300px\",\n padding: \"10px\",\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.95),\n border: `2px solid ${primaryCyanHex}`,\n borderRadius: \"8px\",\n fontSize: \"12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n zIndex: 1000,\n pointerEvents: \"none\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n <div\n style={{\n fontWeight: \"bold\",\n marginBottom: \"6px\",\n color: accentGoldHex,\n }}\n >\n {technique.name.korean} | {technique.name.english}\n </div>\n <div\n style={{ fontSize: \"11px\", lineHeight: \"1.4\", marginBottom: \"8px\" }}\n >\n {technique.description.korean}\n </div>\n <div style={{ fontSize: \"11px\", lineHeight: \"1.4\", color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY) }}>\n {technique.description.english}\n </div>\n <div style={{ marginTop: \"8px\", fontSize: \"10px\", color: hexColorToCSS(KOREAN_COLORS.TEXT_TERTIARY) }}>\n <div>\n Damage: {technique.damage.min}-{technique.damage.max}\n </div>\n <div>Cooldown: {technique.cooldown / 1000}s</div>\n {technique.requiredStance && (\n <div>Stance: {technique.requiredStance}</div>\n )}\n {reachInfo && (\n <>\n <div style={{ marginTop: \"4px\", color: primaryCyanHex, fontWeight: \"bold\" }}>\n Reach: {reachInfo.maxReach}cm\n </div>\n <div style={{ fontSize: \"9px\", color: hexColorToCSS(KOREAN_COLORS.UI_GRAY) }}>\n {reachInfo.bodyPart}\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default TechniqueCard;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,IAAa,iBAA+C,EAC1D,WACA,YACA,aACA,aACA,QACA,mBACA,kBACA,SACA,SACA,UACA,iBACA,mBAEI;CACJ,MAAM,CAAC,aAAa,kBAAkB,SAAS,MAAM;CAGrD,MAAM,YAAY,cAAc;AAC9B,MAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,UAAU,WAAW,KAC7D,QAAO;EAMT,MAAM,gBAAgB,cAAc;EAEpC,MAAM,qBAAqB,+BAA+B,gBAAgB;EAC1E,MAAM,WAAW,wBAAwB,kBACvC,oBACA,eACA,aACD;EAGD,MAAM,gBAAgB,wBAAwB,8BAA8B,cAAc;EAC1F,IAAI;AAEJ,UAAQ,eAAR;GACE,KAAK;GACL,KAAK;AACH,eAAW;AACX;GACF,KAAK;GACL,KAAK;AACH,eAAW;AACX;GAEF;AACE,eAAW;AACX;;AAGJ,SAAO;GACL,WAAW,WAAW,KAAK,QAAQ,EAAE;GACrC;GACD;IACA;EAAC;EAAiB;EAAc,UAAU;EAAU,CAAC;CAGxD,MAAM,WAAW,eACR;EACL,OAAO,WAAW,KAAK;EACvB,QAAQ,WAAW,KAAK;EACxB,UAAU,WAAW,KAAK;EAC1B,cAAc,WAAW,KAAK;EAC/B,GACD,CAAC,SAAS,CACX;CAGD,MAAM,eAAe,cAAc;AACjC,MAAI,CAAC,qBAAqB,qBAAqB,EAAG,QAAO;AAEzD,SAAO,GADS,KAAK,KAAK,oBAAoB,IAAK,CACjC;IACjB,CAAC,kBAAkB,CAAC;CAGvB,MAAM,kBAAkB,cAAc;AACpC,MAAI,CAAC,YAAa,QAAO,gBAAgB,cAAc,qBAAqB,GAAI;AAChF,MAAI,WAAY,QAAO,gBAAgB,cAAc,WAAW,GAAI;AACpE,SAAO,gBAAgB,cAAc,sBAAsB,GAAI;IAC9D,CAAC,aAAa,WAAW,CAAC;CAG7B,MAAM,cAAc,cAAc;AAChC,MAAI,CAAC,YAAa,QAAO,cAAc,cAAc,iBAAiB;AACtE,MAAI,WAAY,QAAO,cAAc,cAAc,aAAa;AAChE,SAAO,cAAc,cAAc,YAAY;IAC9C,CAAC,aAAa,WAAW,CAAC;CAG7B,MAAM,iBAAiB,cACf,cAAc,cAAc,aAAa,EAC/C,EAAE,CACH;CACD,MAAM,gBAAgB,cACd,cAAc,cAAc,YAAY,EAC9C,EAAE,CACH;CAGD,MAAM,YAAY,cAAc;AAC9B,MAAI,cAAc,YAChB,QAAO,YAAY,gBAAgB,cAAc,WAAW,GAAI,CAAC,aAAa,gBAAgB,cAAc,WAAW,GAAI;AAE7H,MAAI,YACF,QAAO,YAAY,gBAAgB,cAAc,aAAa,GAAI,CAAC,cAAc,gBAAgB,cAAc,OAAO,GAAI;AAE5H,SAAO,aAAa,gBAAgB,cAAc,OAAO,GAAI;IAC5D,CAAC,YAAY,YAAY,CAAC;CAG7B,MAAM,iBAAiB,cACd,cAAc,iBAAiB,IACtC,CAAC,YAAY,CACd;CAGD,MAAM,cAAc,aACjB,MAAwB;AACvB,MAAI,CAAC,YAAa;AAClB,IAAE,gBAAgB;AAClB,gBAAc,QAAQ;AACtB,WAAS;IAEX,CAAC,aAAa,QAAQ,CACvB;AAED,QACE,qBAAC,OAAD;EACE,MAAK;EACL,UAAU,cAAc,IAAI;EAC5B,cAAY,GAAG,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,QAAQ,cAAc,YAAY,QAAQ;EAClG,iBAAe,CAAC;EAChB,oBAAkB,eAAe,cAAc,WAAW,UAAU,OAAO,KAAA;EAC3E,WAAW;EACX,OAAO;GACL,UAAU;GACV,OAAO,GAAG,SAAS,MAAM;GACzB,QAAQ,GAAG,SAAS,OAAO;GAC3B;GACA,QAAQ,aAAa;GACrB,cAAc;GACd;GACA,QAAQ,cAAc,YAAY;GAClC,YAAY;GACZ,SAAS;GACT,eAAe;GACf,YAAY;GACZ,gBAAgB;GAChB,SAAS;GACT,YAAY,YAAY;GACxB,SAAS,cAAc,IAAI;GAC3B,aAAa;GACb,YAAY;GACZ,WAAW,cAAc,cAAc,gDAC5B,cAAc,0CAA0C;GACpE;EACD,SAAS,cAAc,UAAU,KAAA;EACjC,YAAY;EACZ,oBAAoB;AAClB,kBAAe,KAAK;AACpB,WAAQ,UAAU;;EAEpB,oBAAoB;AAClB,kBAAe,MAAM;AACrB,WAAQ,KAAK;;EAEf,eAAe;AACb,kBAAe,KAAK;AACpB,WAAQ,UAAU;;EAEpB,cAAc;AACZ,kBAAe,MAAM;AACrB,WAAQ,KAAK;;EAEf,eAAa,kBAAkB,UAAU;YA/C3C;GAkDE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,OAAO;KACP,OAAO,GAAG,SAAS,aAAa;KAChC,QAAQ,GAAG,SAAS,aAAa;KACjC,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;KAC1D,QAAQ,aAAa,cAAc,cAAc,QAAQ;KACzD,cAAc;KACd,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,UAAU,GAAG,SAAS,SAAS;KAC/B,YAAY;KACZ,OAAO,cAAc,cAAc,cAAc,aAAa,GAAG,cAAc,cAAc,iBAAiB;KAC/G;cAEA;IACG,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,SAAS,SAAS;KAC/B,YAAY;KACZ,OAAO,cAAc,gBAAgB,cAAc,cAAc,QAAQ;KACzE,WAAW;KACX,WAAW;KACX,YAAY;KACb;cAEA,UAAU,KAAK;IACZ,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,SAAS,WAAW,EAAE;KACnC,OAAO,cAAc,cAAc,cAAc,eAAe,GAAG,cAAc,cAAc,iBAAiB;KAChH,WAAW;KACX,WAAW;KACX,YAAY;KACb;cAEA,UAAU,KAAK;IACZ,CAAA;GAGN,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,KAAK;KACL,WAAW;KACX,UAAU,GAAG,SAAS,WAAW,EAAE;KACpC;cANH,CASE,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,YAAY;MACZ,KAAK;MACL,OAAO,cAAc,cAAc,cAAc,eAAe,GAAG,cAAc,cAAc,iBAAiB;MACjH;eANH,CAQE,oBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA,EACd,oBAAC,QAAD,EAAA,UAAO,aAAmB,CAAA,CACtB;QAGN,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,YAAY;MACZ,KAAK;MACL,OAAO,cAAc,cAAc,cAAc,UAAU,GAAG,cAAc,cAAc,iBAAiB;MAC5G;eANH,CAQE,oBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA,EACd,oBAAC,QAAD,EAAA,UAAO,QAAc,CAAA,CACjB;OACF;;GAGL,gBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,OAAO;KACP,QAAQ;KACR,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;KAC1D,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,cAAc;KACd,UAAU,GAAG,SAAS,aAAa;KACnC,YAAY;KACZ,OAAO,cAAc,cAAc,aAAa;KACjD;cAEA;IACG,CAAA;GAIP,eAAe,eACd,qBAAC,OAAD;IACE,IAAI,WAAW,UAAU;IACzB,MAAK;IACL,OAAO;KACL,UAAU;KACV,QAAQ,GAAG,SAAS,SAAS,GAAG;KAChC,MAAM;KACN,WAAW;KACX,UAAU;KACV,UAAU;KACV,SAAS;KACT,iBAAiB,gBAAgB,cAAc,oBAAoB,IAAK;KACxE,QAAQ,aAAa;KACrB,cAAc;KACd,UAAU;KACV,OAAO,cAAc,cAAc,aAAa;KAChD,QAAQ;KACR,eAAe;KACf,YAAY,YAAY;KACzB;cAnBH;KAqBE,qBAAC,OAAD;MACE,OAAO;OACL,YAAY;OACZ,cAAc;OACd,OAAO;OACR;gBALH;OAOG,UAAU,KAAK;OAAO;OAAI,UAAU,KAAK;OACtC;;KACN,oBAAC,OAAD;MACE,OAAO;OAAE,UAAU;OAAQ,YAAY;OAAO,cAAc;OAAO;gBAElE,UAAU,YAAY;MACnB,CAAA;KACN,oBAAC,OAAD;MAAK,OAAO;OAAE,UAAU;OAAQ,YAAY;OAAO,OAAO,cAAc,cAAc,eAAe;OAAE;gBACpG,UAAU,YAAY;MACnB,CAAA;KACN,qBAAC,OAAD;MAAK,OAAO;OAAE,WAAW;OAAO,UAAU;OAAQ,OAAO,cAAc,cAAc,cAAc;OAAE;gBAArG;OACE,qBAAC,OAAD,EAAA,UAAA;QAAK;QACM,UAAU,OAAO;QAAI;QAAE,UAAU,OAAO;QAC7C,EAAA,CAAA;OACN,qBAAC,OAAD,EAAA,UAAA;QAAK;QAAW,UAAU,WAAW;QAAK;QAAO,EAAA,CAAA;OAChD,UAAU,kBACT,qBAAC,OAAD,EAAA,UAAA,CAAK,YAAS,UAAU,eAAqB,EAAA,CAAA;OAE9C,aACC,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;QAAK,OAAO;SAAE,WAAW;SAAO,OAAO;SAAgB,YAAY;SAAQ;kBAA3E;SAA6E;SACnE,UAAU;SAAS;SACvB;WACN,oBAAC,OAAD;QAAK,OAAO;SAAE,UAAU;SAAO,OAAO,cAAc,cAAc,QAAQ;SAAE;kBACzE,UAAU;QACP,CAAA,CACL,EAAA,CAAA;OAED;;KACF;;GAEJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"VitalPointOverlayControlsHtml.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAK9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,+BAA+B;IAC/B,QAAQ,CAAC,eAAe,EAAE,kBAAkB,EAAE,CAAC;IAC/C,4CAA4C;IAC5C,QAAQ,CAAC,uBAAuB,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,IAAI,CAAC;IAC1E,4BAA4B;IAC5B,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClE,2BAA2B;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACvD,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,+DAA+D;QAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,gEAAgE;QAChE,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,iEAAiE;QACjE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,kEAAkE;QAClE,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AA6BD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,KAAK,CAAC,EAAE,CAClD,8BAA8B,CAupB/B,CAAC;AAEF,eAAe,6BAA6B,CAAC"}
1
+ {"version":3,"file":"VitalPointOverlayControlsHtml.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAK9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,+BAA+B;IAC/B,QAAQ,CAAC,eAAe,EAAE,kBAAkB,EAAE,CAAC;IAC/C,4CAA4C;IAC5C,QAAQ,CAAC,uBAAuB,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,IAAI,CAAC;IAC1E,4BAA4B;IAC5B,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClE,2BAA2B;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACvD,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,+DAA+D;QAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,gEAAgE;QAChE,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,iEAAiE;QACjE,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,kEAAkE;QAClE,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAyBD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,EAAE,KAAK,CAAC,EAAE,CAClD,8BAA8B,CAupB/B,CAAC;AAEF,eAAe,6BAA6B,CAAC"}