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.
- package/DATA_MODEL.md +347 -0
- package/lib/App.d.ts.map +1 -1
- package/lib/App2.js +0 -6
- package/lib/App2.js.map +1 -1
- package/lib/assets/index.css +102 -94
- package/lib/components/screens/combat/CombatScreen3D.d.ts +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +2 -2
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.d.ts.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js +22 -7
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.d.ts +2 -0
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.d.ts.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js +7 -4
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.d.ts.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js +15 -5
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js +15 -16
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js +1 -2
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js +28 -24
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js +2 -4
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.d.ts.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js +3 -2
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js +28 -30
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.d.ts.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js +4 -3
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.d.ts.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js +5 -2
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/components/TrigramSymbol3D.d.ts.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js +4 -4
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/shared/base/ResponsiveContainer.d.ts +6 -0
- package/lib/components/shared/base/ResponsiveContainer.d.ts.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js +1 -2
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js +1 -2
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js +5 -5
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js +3 -2
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.d.ts.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js +27 -30
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.d.ts.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js +57 -59
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.d.ts +40 -0
- package/lib/components/shared/ui/BaseHUDContainer.d.ts.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js +40 -0
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/MobileHUDLayout.d.ts +13 -0
- package/lib/components/shared/ui/MobileHUDLayout.d.ts.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +10 -10
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.d.ts.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js +57 -62
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js +7 -7
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- 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:
|
|
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(
|
|
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:
|
|
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;
|
|
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
|
|
77
|
-
if (isSelected) return
|
|
78
|
-
return
|
|
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
|
|
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
|
|
86
|
-
|
|
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
|
|
93
|
-
if (isAvailable) return `0 0 10px
|
|
94
|
-
return
|
|
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 ${
|
|
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:
|
|
159
|
-
border:
|
|
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 ?
|
|
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 :
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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":"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"}
|
|
@@ -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;
|
|
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"}
|