blacktrigram 0.7.12 → 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 +8 -0
- 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/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 +2 -2
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatButtons.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/CombatButtons.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"CombatButtons.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/CombatButtons.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;AAKvC,MAAM,WAAW,6BAA6B;IAC5C,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,+BAA+B;IAC/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAC7C,6BAA6B,CA+B9B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { hexToRgbaString } from "../../../../../utils/colorUtils.js";
|
|
2
2
|
import { useKoreanTheme } from "../../../../shared/base/useKoreanTheme.js";
|
|
3
3
|
import { BaseButtonOverlayHtml } from "../../../../shared/base/BaseButtonOverlayHtml.js";
|
|
4
|
-
import "react";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/screens/combat/components/controls/CombatButtons.tsx
|
|
7
7
|
/**
|
|
8
|
+
* CombatButtons - Reusable button components for CombatScreen
|
|
9
|
+
*
|
|
10
|
+
* Provides return-to-menu button with combat-specific styling.
|
|
11
|
+
* Extracted from CombatScreen3D to reduce code duplication.
|
|
12
|
+
*
|
|
13
|
+
* @module components/screens/combat
|
|
14
|
+
* @category Combat UI
|
|
15
|
+
* @korean 전투버튼
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
8
18
|
* CombatReturnToMenuButton Component
|
|
9
19
|
*
|
|
10
20
|
* Bilingual button to return to main menu from combat screen.
|
|
@@ -22,15 +32,20 @@ import { jsx } from "react/jsx-runtime";
|
|
|
22
32
|
* ```
|
|
23
33
|
*/
|
|
24
34
|
var CombatReturnToMenuButton = ({ onClick, onMouseEnter, isMobile }) => {
|
|
35
|
+
const theme = useKoreanTheme({
|
|
36
|
+
variant: "primary",
|
|
37
|
+
size: "md",
|
|
38
|
+
isMobile
|
|
39
|
+
});
|
|
40
|
+
const containerStyle = useMemo(() => ({
|
|
41
|
+
background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, .85),
|
|
42
|
+
border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, .8)}`
|
|
43
|
+
}), [theme.colors.UI_BACKGROUND_DARK, theme.colors.PRIMARY_CYAN]);
|
|
25
44
|
return /* @__PURE__ */ jsx("div", {
|
|
26
45
|
style: {
|
|
27
46
|
textAlign: "center",
|
|
28
|
-
background:
|
|
29
|
-
border:
|
|
30
|
-
variant: "primary",
|
|
31
|
-
size: "md",
|
|
32
|
-
isMobile
|
|
33
|
-
}).colors.PRIMARY_CYAN, .8)}`,
|
|
47
|
+
background: containerStyle.background,
|
|
48
|
+
border: containerStyle.border,
|
|
34
49
|
borderRadius: "8px",
|
|
35
50
|
padding: isMobile ? "6px 10px" : "8px 12px"
|
|
36
51
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatButtons.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/CombatButtons.tsx"],"sourcesContent":["/**\n * CombatButtons - Reusable button components for CombatScreen\n * \n * Provides return-to-menu button with combat-specific styling.\n * Extracted from CombatScreen3D to reduce code duplication.\n * \n * @module components/screens/combat\n * @category Combat UI\n * @korean 전투버튼\n */\n\nimport React from \"react\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface CombatReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * CombatReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from combat screen.\n * Uses BaseButtonOverlayHtml with custom container for combat-specific styling.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <CombatReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatReturnToMenuButton: React.FC<\n CombatReturnToMenuButtonProps\n> = ({ onClick, onMouseEnter, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n
|
|
1
|
+
{"version":3,"file":"CombatButtons.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/CombatButtons.tsx"],"sourcesContent":["/**\n * CombatButtons - Reusable button components for CombatScreen\n * \n * Provides return-to-menu button with combat-specific styling.\n * Extracted from CombatScreen3D to reduce code duplication.\n * \n * @module components/screens/combat\n * @category Combat UI\n * @korean 전투버튼\n */\n\nimport React, { useMemo } from \"react\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { BaseButtonOverlayHtml } from \"../../../../shared/base/BaseButtonOverlayHtml\";\n\nexport interface CombatReturnToMenuButtonProps {\n /** Callback when button is clicked */\n readonly onClick: () => void;\n /** Callback when mouse enters button */\n readonly onMouseEnter?: () => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * CombatReturnToMenuButton Component\n * \n * Bilingual button to return to main menu from combat screen.\n * Uses BaseButtonOverlayHtml with custom container for combat-specific styling.\n * \n * Refactored to use BaseButtonOverlayHtml for better consistency.\n * \n * @example\n * ```tsx\n * <CombatReturnToMenuButton\n * onClick={() => navigate('/menu')}\n * onMouseEnter={() => playSound()}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatReturnToMenuButton: React.FC<\n CombatReturnToMenuButtonProps\n> = ({ onClick, onMouseEnter, isMobile }) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"md\", isMobile });\n\n const containerStyle = useMemo(() => ({\n background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.85),\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 0.8)}`,\n }), [theme.colors.UI_BACKGROUND_DARK, theme.colors.PRIMARY_CYAN]);\n\n return (\n <div\n style={{\n textAlign: \"center\",\n background: containerStyle.background,\n border: containerStyle.border,\n borderRadius: \"8px\",\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n }}\n >\n <BaseButtonOverlayHtml\n korean={isMobile ? \"메뉴\" : \"메뉴로\"}\n english={isMobile ? \"Menu\" : \"Return to Menu\"}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n variant=\"primary\"\n size=\"md\"\n isMobile={isMobile}\n testId=\"return-to-menu-button\"\n />\n </div>\n );\n};\n\nexport default CombatReturnToMenuButton;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,4BAER,EAAE,SAAS,cAAc,eAAe;CAC3C,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;EAAU,CAAC;CAE1E,MAAM,iBAAiB,eAAe;EACpC,YAAY,gBAAgB,MAAM,OAAO,oBAAoB,IAAK;EAClE,QAAQ,aAAa,gBAAgB,MAAM,OAAO,cAAc,GAAI;EACrE,GAAG,CAAC,MAAM,OAAO,oBAAoB,MAAM,OAAO,aAAa,CAAC;AAEjE,QACE,oBAAC,OAAD;EACE,OAAO;GACL,WAAW;GACX,YAAY,eAAe;GAC3B,QAAQ,eAAe;GACvB,cAAc;GACd,SAAS,WAAW,aAAa;GAClC;YAED,oBAAC,uBAAD;GACE,QAAQ,WAAW,OAAO;GAC1B,SAAS,WAAW,SAAS;GACpB;GACK;GACd,SAAQ;GACR,MAAK;GACK;GACV,QAAO;GACP,CAAA;EACE,CAAA"}
|
|
@@ -16,6 +16,8 @@ export interface CombatControlsPanelProps {
|
|
|
16
16
|
readonly combatMessages: readonly string[];
|
|
17
17
|
/** Whether to use mobile-optimized sizing */
|
|
18
18
|
readonly isMobile: boolean;
|
|
19
|
+
/** Screen height for responsive sizing */
|
|
20
|
+
readonly height?: number;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* CombatControlsPanel - Controls guide and combat log display
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatControlsPanel.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/CombatControlsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CombatControlsPanel.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/CombatControlsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,wBAAwB;IACvC,gDAAgD;IAChD,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAkElE,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -3,6 +3,8 @@ import { useKoreanTheme } from "../../../../shared/base/useKoreanTheme.js";
|
|
|
3
3
|
import "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/screens/combat/components/controls/CombatControlsPanel.tsx
|
|
6
|
+
/** Ratio of screen height used for message log max height */
|
|
7
|
+
var MESSAGE_LOG_HEIGHT_RATIO = .18;
|
|
6
8
|
/**
|
|
7
9
|
* CombatControlsPanel - Controls guide and combat log display
|
|
8
10
|
*
|
|
@@ -16,12 +18,13 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16
18
|
* />
|
|
17
19
|
* ```
|
|
18
20
|
*/
|
|
19
|
-
var CombatControlsPanel = ({ combatMessages, isMobile }) => {
|
|
21
|
+
var CombatControlsPanel = ({ combatMessages, isMobile, height }) => {
|
|
20
22
|
const theme = useKoreanTheme({
|
|
21
23
|
variant: "primary",
|
|
22
24
|
size: "sm",
|
|
23
25
|
isMobile
|
|
24
26
|
});
|
|
27
|
+
const panelBackground = hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, .8);
|
|
25
28
|
return /* @__PURE__ */ jsxs("div", {
|
|
26
29
|
"data-testid": "combat-controls-panel",
|
|
27
30
|
style: {
|
|
@@ -38,7 +41,7 @@ var CombatControlsPanel = ({ combatMessages, isMobile }) => {
|
|
|
38
41
|
"data-testid": "combat-controls-guide",
|
|
39
42
|
style: {
|
|
40
43
|
width: isMobile ? "45%" : "400px",
|
|
41
|
-
background:
|
|
44
|
+
background: panelBackground,
|
|
42
45
|
border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)}`,
|
|
43
46
|
borderRadius: "8px",
|
|
44
47
|
padding: "10px",
|
|
@@ -53,13 +56,13 @@ var CombatControlsPanel = ({ combatMessages, isMobile }) => {
|
|
|
53
56
|
"data-testid": "combat-message-log",
|
|
54
57
|
style: {
|
|
55
58
|
width: isMobile ? "45%" : "400px",
|
|
56
|
-
background:
|
|
59
|
+
background: panelBackground,
|
|
57
60
|
border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)}`,
|
|
58
61
|
borderRadius: "8px",
|
|
59
62
|
padding: "10px",
|
|
60
63
|
color: hexToRgbaString(theme.colors.PRIMARY_CYAN, 1),
|
|
61
64
|
fontFamily: theme.fontFamily.KOREAN,
|
|
62
|
-
maxHeight: "
|
|
65
|
+
maxHeight: height ? `${Math.round(height * MESSAGE_LOG_HEIGHT_RATIO)}px` : "18vh",
|
|
63
66
|
overflow: "auto"
|
|
64
67
|
},
|
|
65
68
|
children: combatMessages.slice(-5).map((msg, idx) => /* @__PURE__ */ jsx("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatControlsPanel.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/CombatControlsPanel.tsx"],"sourcesContent":["/**\n * CombatControlsPanel - Displays controls guide and combat messages log\n *\n * Shows keyboard/touch controls on the left and scrolling combat log on the right.\n * Positioned at the bottom of the combat screen above the back button.\n *\n * Refactored to use useKoreanTheme for consistent theming.\n *\n * @module components/combat/components/CombatControlsPanel\n * @category Combat UI\n * @korean 전투컨트롤패널\n */\n\nimport React from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\n\nexport interface CombatControlsPanelProps {\n /** Combat message log (most recent messages) */\n readonly combatMessages: readonly string[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * CombatControlsPanel - Controls guide and combat log display\n * \n * Uses useKoreanTheme for consistent styling.\n *\n * @example\n * ```tsx\n * <CombatControlsPanel\n * combatMessages={combatState.combatMessages}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatControlsPanel: React.FC<CombatControlsPanelProps> = ({\n combatMessages,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n \n return (\n <div\n data-testid=\"combat-controls-panel\"\n style={{\n position: \"absolute\",\n bottom: isMobile ? \"90px\" : \"100px\",\n left: isMobile ? \"5px\" : \"15px\",\n right: isMobile ? \"5px\" : \"15px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n pointerEvents: \"auto\",\n zIndex: 50,\n }}\n >\n {/* Controls Guide */}\n <div\n data-testid=\"combat-controls-guide\"\n style={{\n width: isMobile ? \"45%\" : \"400px\",\n background:
|
|
1
|
+
{"version":3,"file":"CombatControlsPanel.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/CombatControlsPanel.tsx"],"sourcesContent":["/**\n * CombatControlsPanel - Displays controls guide and combat messages log\n *\n * Shows keyboard/touch controls on the left and scrolling combat log on the right.\n * Positioned at the bottom of the combat screen above the back button.\n *\n * Refactored to use useKoreanTheme for consistent theming.\n *\n * @module components/combat/components/CombatControlsPanel\n * @category Combat UI\n * @korean 전투컨트롤패널\n */\n\nimport React from \"react\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\n\n/** Ratio of screen height used for message log max height */\nconst MESSAGE_LOG_HEIGHT_RATIO = 0.18;\n\nexport interface CombatControlsPanelProps {\n /** Combat message log (most recent messages) */\n readonly combatMessages: readonly string[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n /** Screen height for responsive sizing */\n readonly height?: number;\n}\n\n/**\n * CombatControlsPanel - Controls guide and combat log display\n * \n * Uses useKoreanTheme for consistent styling.\n *\n * @example\n * ```tsx\n * <CombatControlsPanel\n * combatMessages={combatState.combatMessages}\n * isMobile={false}\n * />\n * ```\n */\nexport const CombatControlsPanel: React.FC<CombatControlsPanelProps> = ({\n combatMessages,\n isMobile,\n height,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"sm\", isMobile });\n const panelBackground = hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, 0.8);\n \n return (\n <div\n data-testid=\"combat-controls-panel\"\n style={{\n position: \"absolute\",\n bottom: isMobile ? \"90px\" : \"100px\",\n left: isMobile ? \"5px\" : \"15px\",\n right: isMobile ? \"5px\" : \"15px\",\n display: \"flex\",\n justifyContent: \"space-between\",\n pointerEvents: \"auto\",\n zIndex: 50,\n }}\n >\n {/* Controls Guide */}\n <div\n data-testid=\"combat-controls-guide\"\n style={{\n width: isMobile ? \"45%\" : \"400px\",\n background: panelBackground,\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)}`,\n borderRadius: \"8px\",\n padding: \"10px\",\n color: hexToRgbaString(theme.colors.PRIMARY_CYAN, 1),\n fontFamily: theme.fontFamily.KOREAN,\n }}\n >\n <div style={{ fontSize: isMobile ? \"10px\" : \"12px\" }}>\n 조작법 | Controls: A/D - Attack/Defend | 1-8 - Stances\n </div>\n </div>\n\n {/* Combat Message Log */}\n <div\n data-testid=\"combat-message-log\"\n style={{\n width: isMobile ? \"45%\" : \"400px\",\n background: panelBackground,\n border: `2px solid ${hexToRgbaString(theme.colors.PRIMARY_CYAN, 1)}`,\n borderRadius: \"8px\",\n padding: \"10px\",\n color: hexToRgbaString(theme.colors.PRIMARY_CYAN, 1),\n fontFamily: theme.fontFamily.KOREAN,\n maxHeight: height ? `${Math.round(height * MESSAGE_LOG_HEIGHT_RATIO)}px` : \"18vh\",\n overflow: \"auto\",\n }}\n >\n {combatMessages.slice(-5).map((msg, idx) => (\n <div\n key={`msg-${idx}`}\n style={{ fontSize: \"12px\", marginBottom: \"4px\" }}\n >\n {msg}\n </div>\n ))}\n </div>\n </div>\n );\n};\n\nexport default CombatControlsPanel;\n"],"mappings":";;;;;;AAkBA,IAAM,2BAA2B;;;;;;;;;;;;;;AAwBjC,IAAa,uBAA2D,EACtE,gBACA,UACA,aACI;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAM;EAAU,CAAC;CAC1E,MAAM,kBAAkB,gBAAgB,MAAM,OAAO,oBAAoB,GAAI;AAE7E,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,QAAQ,WAAW,SAAS;GAC5B,MAAM,WAAW,QAAQ;GACzB,OAAO,WAAW,QAAQ;GAC1B,SAAS;GACT,gBAAgB;GAChB,eAAe;GACf,QAAQ;GACT;YAXH,CAcE,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,OAAO,WAAW,QAAQ;IAC1B,YAAY;IACZ,QAAQ,aAAa,gBAAgB,MAAM,OAAO,cAAc,EAAE;IAClE,cAAc;IACd,SAAS;IACT,OAAO,gBAAgB,MAAM,OAAO,cAAc,EAAE;IACpD,YAAY,MAAM,WAAW;IAC9B;aAED,oBAAC,OAAD;IAAK,OAAO,EAAE,UAAU,WAAW,SAAS,QAAQ;cAAE;IAEhD,CAAA;GACF,CAAA,EAGN,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,OAAO,WAAW,QAAQ;IAC1B,YAAY;IACZ,QAAQ,aAAa,gBAAgB,MAAM,OAAO,cAAc,EAAE;IAClE,cAAc;IACd,SAAS;IACT,OAAO,gBAAgB,MAAM,OAAO,cAAc,EAAE;IACpD,YAAY,MAAM,WAAW;IAC7B,WAAW,SAAS,GAAG,KAAK,MAAM,SAAS,yBAAyB,CAAC,MAAM;IAC3E,UAAU;IACX;aAEA,eAAe,MAAM,GAAG,CAAC,KAAK,KAAK,QAClC,oBAAC,OAAD;IAEE,OAAO;KAAE,UAAU;KAAQ,cAAc;KAAO;cAE/C;IACG,EAJC,OAAO,MAIR,CACN;GACE,CAAA,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PauseMenu.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/PauseMenu.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"PauseMenu.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/combat/components/controls/PauseMenu.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAWjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAWD;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA4Q9C,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -7,7 +7,7 @@ import ConfirmDialog from "../../../../shared/ui/shared/ConfirmDialog.js";
|
|
|
7
7
|
import ControlsGuide from "./ControlsGuide.js";
|
|
8
8
|
import QuickSettings from "./QuickSettings.js";
|
|
9
9
|
import PauseMenuButton from "./PauseMenuButton.js";
|
|
10
|
-
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region src/components/screens/combat/components/controls/PauseMenu.tsx
|
|
13
13
|
/**
|
|
@@ -39,6 +39,16 @@ var PauseMenu = ({ onResume, onRestart, onReturnToMenu, isMobile }) => {
|
|
|
39
39
|
size: "lg",
|
|
40
40
|
isMobile
|
|
41
41
|
});
|
|
42
|
+
const themeColors = useMemo(() => ({
|
|
43
|
+
overlayBg: hexToRgbaString(theme.colors.BLACK_SOLID, .85),
|
|
44
|
+
accentGold: hexToRgbaString(theme.colors.ACCENT_GOLD, 1),
|
|
45
|
+
accentGoldGlow: hexToRgbaString(theme.colors.ACCENT_GOLD, .6),
|
|
46
|
+
textSecondary: hexToRgbaString(theme.colors.TEXT_SECONDARY, .8)
|
|
47
|
+
}), [
|
|
48
|
+
theme.colors.BLACK_SOLID,
|
|
49
|
+
theme.colors.ACCENT_GOLD,
|
|
50
|
+
theme.colors.TEXT_SECONDARY
|
|
51
|
+
]);
|
|
42
52
|
const [activeSubmenu, setActiveSubmenu] = useState(null);
|
|
43
53
|
const [showConfirm, setShowConfirm] = useState(null);
|
|
44
54
|
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
@@ -141,7 +151,7 @@ var PauseMenu = ({ onResume, onRestart, onReturnToMenu, isMobile }) => {
|
|
|
141
151
|
flexDirection: "column",
|
|
142
152
|
alignItems: "center",
|
|
143
153
|
justifyContent: "center",
|
|
144
|
-
backgroundColor:
|
|
154
|
+
backgroundColor: themeColors.overlayBg,
|
|
145
155
|
backdropFilter: "blur(8px)",
|
|
146
156
|
zIndex: 1e3,
|
|
147
157
|
pointerEvents: "auto"
|
|
@@ -152,11 +162,11 @@ var PauseMenu = ({ onResume, onRestart, onReturnToMenu, isMobile }) => {
|
|
|
152
162
|
"data-testid": "pause-title",
|
|
153
163
|
style: {
|
|
154
164
|
fontSize: isMobile ? "48px" : "72px",
|
|
155
|
-
color:
|
|
165
|
+
color: themeColors.accentGold,
|
|
156
166
|
fontFamily: theme.fontFamily.KOREAN,
|
|
157
167
|
fontWeight: "bold",
|
|
158
168
|
margin: `0 0 ${isMobile ? "40px" : "60px"} 0`,
|
|
159
|
-
textShadow: `0 0 30px ${
|
|
169
|
+
textShadow: `0 0 30px ${themeColors.accentGoldGlow}`,
|
|
160
170
|
textAlign: "center"
|
|
161
171
|
},
|
|
162
172
|
children: "일시정지 | Paused"
|
|
@@ -192,7 +202,7 @@ var PauseMenu = ({ onResume, onRestart, onReturnToMenu, isMobile }) => {
|
|
|
192
202
|
style: {
|
|
193
203
|
marginTop: isMobile ? "40px" : "60px",
|
|
194
204
|
fontSize: isMobile ? "12px" : "14px",
|
|
195
|
-
color:
|
|
205
|
+
color: themeColors.textSecondary,
|
|
196
206
|
fontFamily: theme.fontFamily.KOREAN,
|
|
197
207
|
textAlign: "center"
|
|
198
208
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PauseMenu.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/PauseMenu.tsx"],"sourcesContent":["/**\n * PauseMenu Component - Main pause menu overlay\n *\n * Features:\n * - Resume combat\n * - Restart match\n * - View controls\n * - Adjust settings\n * - Return to menu (with confirmation)\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - WCAG 2.1 Level AA compliant\n * - Keyboard navigation (Arrow keys, Enter, Escape)\n * - Focus indicators with high contrast\n * - ARIA labels for screen readers\n * \n * Refactored to use useKoreanTheme for consistent theming\n */\n\nimport React, { useCallback, useState, useEffect, useRef } from \"react\";\nimport { useAudio } from \"../../../../../audio/AudioProvider\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport ConfirmDialog from \"../../../../shared/ui/shared/ConfirmDialog\";\nimport ControlsGuide from \"./ControlsGuide\";\nimport QuickSettings from \"./QuickSettings\";\nimport { handleKeyboardNav } from \"../../../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../../../types/AccessibilityTypes\";\nimport { PauseMenuButton } from \"./PauseMenuButton\";\n\nexport interface PauseMenuProps {\n readonly onResume: () => void;\n readonly onRestart: () => void;\n readonly onReturnToMenu: () => void;\n readonly isMobile: boolean;\n}\n\ninterface MenuItem {\n readonly key: string;\n readonly labelKorean: string;\n readonly labelEnglish: string;\n readonly testId: string;\n readonly onClick: () => void;\n readonly icon?: string;\n}\n\n/**\n * PauseMenu - Main pause menu with options and submenus\n */\nexport const PauseMenu: React.FC<PauseMenuProps> = ({\n onResume,\n onRestart,\n onReturnToMenu,\n isMobile,\n}) => {\n const audio = useAudio();\n const theme = useKoreanTheme({ variant: \"primary\", size: \"lg\", isMobile });\n const [activeSubmenu, setActiveSubmenu] = useState<\n \"controls\" | \"settings\" | null\n >(null);\n const [showConfirm, setShowConfirm] = useState<\n \"restart\" | \"menu\" | null\n >(null);\n const [focusedIndex, setFocusedIndex] = useState<number>(0);\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n // Memoize menuItems to prevent recreation on every render\n const menuItems: MenuItem[] = React.useMemo(\n () => [\n {\n key: \"resume\",\n labelKorean: \"계속\",\n labelEnglish: \"Resume\",\n testId: \"pause-resume-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n onResume();\n },\n icon: \"▶️\",\n },\n {\n key: \"restart\",\n labelKorean: \"재시작\",\n labelEnglish: \"Restart Match\",\n testId: \"pause-restart-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setShowConfirm(\"restart\");\n },\n icon: \"🔄\",\n },\n {\n key: \"controls\",\n labelKorean: \"조작법\",\n labelEnglish: \"Controls\",\n testId: \"pause-controls-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setActiveSubmenu(\"controls\");\n },\n icon: \"🎮\",\n },\n {\n key: \"settings\",\n labelKorean: \"설정\",\n labelEnglish: \"Settings\",\n testId: \"pause-settings-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setActiveSubmenu(\"settings\");\n },\n icon: \"⚙️\",\n },\n {\n key: \"menu\",\n labelKorean: \"메인 메뉴\",\n labelEnglish: \"Return to Menu\",\n testId: \"pause-menu-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setShowConfirm(\"menu\");\n },\n icon: \"🏠\",\n },\n ],\n [audio, onResume]\n );\n\n // Focus first button on mount\n useEffect(() => {\n if (buttonRefs.current[0]) {\n buttonRefs.current[0].focus();\n }\n }, [menuItems]);\n\n // Keyboard navigation handler\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent, index: number) => {\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n menuItems[index].onClick();\n },\n onCancel: () => {\n onResume();\n },\n onNavigate: (direction) => {\n if (direction === 'up') {\n const newIndex = (index - 1 + menuItems.length) % menuItems.length;\n setFocusedIndex(newIndex);\n buttonRefs.current[newIndex]?.focus();\n } else if (direction === 'down') {\n const newIndex = (index + 1) % menuItems.length;\n setFocusedIndex(newIndex);\n buttonRefs.current[newIndex]?.focus();\n }\n },\n });\n },\n [menuItems, onResume]\n );\n\n // ESC key handling is now managed by the parent (CombatScreen3D) to avoid conflicts\n\n return (\n <>\n {/* Main Pause Menu */}\n <div\n data-testid=\"pause-menu\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"pause-title\"\n aria-describedby=\"pause-hint\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: hexToRgbaString(theme.colors.BLACK_SOLID, 0.85),\n backdropFilter: \"blur(8px)\",\n zIndex: 1000,\n pointerEvents: \"auto\",\n }}\n >\n {/* Pause Title */}\n <h1\n id=\"pause-title\"\n data-testid=\"pause-title\"\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: hexToRgbaString(theme.colors.ACCENT_GOLD, 1),\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"40px\" : \"60px\"} 0`,\n textShadow: `0 0 30px ${hexToRgbaString(\n theme.colors.ACCENT_GOLD,\n 0.6\n )}`,\n textAlign: \"center\",\n }}\n >\n 일시정지 | Paused\n </h1>\n\n {/* Menu Buttons */}\n <div\n role=\"menu\"\n aria-label={createBilingualLabel('일시정지 메뉴', 'Pause Menu').label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: isMobile ? \"12px\" : \"16px\",\n minWidth: isMobile ? \"280px\" : \"360px\",\n }}\n >\n {menuItems.map((item, index) => (\n <PauseMenuButton\n key={item.key}\n ref={(el) => { buttonRefs.current[index] = el; }}\n labelKorean={item.labelKorean}\n labelEnglish={item.labelEnglish}\n icon={item.icon}\n onClick={item.onClick}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onFocus={() => setFocusedIndex(index)}\n isFocused={focusedIndex === index}\n isMobile={isMobile}\n testId={item.testId}\n />\n ))}\n </div>\n\n {/* ESC hint */}\n <div\n id=\"pause-hint\"\n data-testid=\"pause-hint\"\n style={{\n marginTop: isMobile ? \"40px\" : \"60px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n color: hexToRgbaString(theme.colors.TEXT_SECONDARY, 0.8),\n fontFamily: theme.fontFamily.KOREAN,\n textAlign: \"center\",\n }}\n >\n ESC 키를 눌러 계속 | Press ESC to resume\n <br />\n ↑↓ 키로 이동 | Use ↑↓ to navigate\n </div>\n </div>\n\n {/* Controls Guide Submenu */}\n {activeSubmenu === \"controls\" && (\n <ControlsGuide\n onClose={() => {\n setActiveSubmenu(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Settings Submenu */}\n {activeSubmenu === \"settings\" && (\n <QuickSettings\n onClose={() => {\n setActiveSubmenu(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Restart Confirmation Dialog */}\n {showConfirm === \"restart\" && (\n <ConfirmDialog\n isOpen={true}\n title=\"Restart Match?\"\n titleKorean=\"경기를 재시작하시겠습니까?\"\n message=\"All progress in the current match will be lost.\"\n messageKorean=\"현재 경기의 모든 진행 상황이 초기화됩니다.\"\n onConfirm={() => {\n setShowConfirm(null);\n onRestart();\n }}\n onCancel={() => {\n setShowConfirm(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Return to Menu Confirmation Dialog */}\n {showConfirm === \"menu\" && (\n <ConfirmDialog\n isOpen={true}\n title=\"Return to Menu?\"\n titleKorean=\"메인 메뉴로 돌아가시겠습니까?\"\n message=\"All progress in the current match will be lost.\"\n messageKorean=\"현재 경기의 모든 진행 상황이 손실됩니다.\"\n onConfirm={() => {\n setShowConfirm(null);\n onReturnToMenu();\n }}\n onCancel={() => {\n setShowConfirm(null);\n }}\n isMobile={isMobile}\n />\n )}\n </>\n );\n};\n\nexport default PauseMenu;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PauseMenu.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/controls/PauseMenu.tsx"],"sourcesContent":["/**\n * PauseMenu Component - Main pause menu overlay\n *\n * Features:\n * - Resume combat\n * - Restart match\n * - View controls\n * - Adjust settings\n * - Return to menu (with confirmation)\n * - Korean/English bilingual text\n * - Cyberpunk Korean theming\n * - Backdrop blur effect\n * - WCAG 2.1 Level AA compliant\n * - Keyboard navigation (Arrow keys, Enter, Escape)\n * - Focus indicators with high contrast\n * - ARIA labels for screen readers\n * \n * Refactored to use useKoreanTheme for consistent theming\n */\n\nimport React, { useCallback, useMemo, useState, useEffect, useRef } from \"react\";\nimport { useAudio } from \"../../../../../audio/AudioProvider\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexToRgbaString } from \"../../../../../utils/colorUtils\";\nimport ConfirmDialog from \"../../../../shared/ui/shared/ConfirmDialog\";\nimport ControlsGuide from \"./ControlsGuide\";\nimport QuickSettings from \"./QuickSettings\";\nimport { handleKeyboardNav } from \"../../../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../../../types/AccessibilityTypes\";\nimport { PauseMenuButton } from \"./PauseMenuButton\";\n\nexport interface PauseMenuProps {\n readonly onResume: () => void;\n readonly onRestart: () => void;\n readonly onReturnToMenu: () => void;\n readonly isMobile: boolean;\n}\n\ninterface MenuItem {\n readonly key: string;\n readonly labelKorean: string;\n readonly labelEnglish: string;\n readonly testId: string;\n readonly onClick: () => void;\n readonly icon?: string;\n}\n\n/**\n * PauseMenu - Main pause menu with options and submenus\n */\nexport const PauseMenu: React.FC<PauseMenuProps> = ({\n onResume,\n onRestart,\n onReturnToMenu,\n isMobile,\n}) => {\n const audio = useAudio();\n const theme = useKoreanTheme({ variant: \"primary\", size: \"lg\", isMobile });\n const themeColors = useMemo(() => ({\n overlayBg: hexToRgbaString(theme.colors.BLACK_SOLID, 0.85),\n accentGold: hexToRgbaString(theme.colors.ACCENT_GOLD, 1),\n accentGoldGlow: hexToRgbaString(theme.colors.ACCENT_GOLD, 0.6),\n textSecondary: hexToRgbaString(theme.colors.TEXT_SECONDARY, 0.8),\n }), [theme.colors.BLACK_SOLID, theme.colors.ACCENT_GOLD, theme.colors.TEXT_SECONDARY]);\n const [activeSubmenu, setActiveSubmenu] = useState<\n \"controls\" | \"settings\" | null\n >(null);\n const [showConfirm, setShowConfirm] = useState<\n \"restart\" | \"menu\" | null\n >(null);\n const [focusedIndex, setFocusedIndex] = useState<number>(0);\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n // Memoize menuItems to prevent recreation on every render\n const menuItems: MenuItem[] = React.useMemo(\n () => [\n {\n key: \"resume\",\n labelKorean: \"계속\",\n labelEnglish: \"Resume\",\n testId: \"pause-resume-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n onResume();\n },\n icon: \"▶️\",\n },\n {\n key: \"restart\",\n labelKorean: \"재시작\",\n labelEnglish: \"Restart Match\",\n testId: \"pause-restart-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setShowConfirm(\"restart\");\n },\n icon: \"🔄\",\n },\n {\n key: \"controls\",\n labelKorean: \"조작법\",\n labelEnglish: \"Controls\",\n testId: \"pause-controls-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setActiveSubmenu(\"controls\");\n },\n icon: \"🎮\",\n },\n {\n key: \"settings\",\n labelKorean: \"설정\",\n labelEnglish: \"Settings\",\n testId: \"pause-settings-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setActiveSubmenu(\"settings\");\n },\n icon: \"⚙️\",\n },\n {\n key: \"menu\",\n labelKorean: \"메인 메뉴\",\n labelEnglish: \"Return to Menu\",\n testId: \"pause-menu-button\",\n onClick: () => {\n audio.playSFX(\"menu_select\");\n setShowConfirm(\"menu\");\n },\n icon: \"🏠\",\n },\n ],\n [audio, onResume]\n );\n\n // Focus first button on mount\n useEffect(() => {\n if (buttonRefs.current[0]) {\n buttonRefs.current[0].focus();\n }\n }, [menuItems]);\n\n // Keyboard navigation handler\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent, index: number) => {\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n menuItems[index].onClick();\n },\n onCancel: () => {\n onResume();\n },\n onNavigate: (direction) => {\n if (direction === 'up') {\n const newIndex = (index - 1 + menuItems.length) % menuItems.length;\n setFocusedIndex(newIndex);\n buttonRefs.current[newIndex]?.focus();\n } else if (direction === 'down') {\n const newIndex = (index + 1) % menuItems.length;\n setFocusedIndex(newIndex);\n buttonRefs.current[newIndex]?.focus();\n }\n },\n });\n },\n [menuItems, onResume]\n );\n\n // ESC key handling is now managed by the parent (CombatScreen3D) to avoid conflicts\n\n return (\n <>\n {/* Main Pause Menu */}\n <div\n data-testid=\"pause-menu\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"pause-title\"\n aria-describedby=\"pause-hint\"\n style={{\n position: \"fixed\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: themeColors.overlayBg,\n backdropFilter: \"blur(8px)\",\n zIndex: 1000,\n pointerEvents: \"auto\",\n }}\n >\n {/* Pause Title */}\n <h1\n id=\"pause-title\"\n data-testid=\"pause-title\"\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: themeColors.accentGold,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"40px\" : \"60px\"} 0`,\n textShadow: `0 0 30px ${themeColors.accentGoldGlow}`,\n textAlign: \"center\",\n }}\n >\n 일시정지 | Paused\n </h1>\n\n {/* Menu Buttons */}\n <div\n role=\"menu\"\n aria-label={createBilingualLabel('일시정지 메뉴', 'Pause Menu').label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: isMobile ? \"12px\" : \"16px\",\n minWidth: isMobile ? \"280px\" : \"360px\",\n }}\n >\n {menuItems.map((item, index) => (\n <PauseMenuButton\n key={item.key}\n ref={(el) => { buttonRefs.current[index] = el; }}\n labelKorean={item.labelKorean}\n labelEnglish={item.labelEnglish}\n icon={item.icon}\n onClick={item.onClick}\n onMouseEnter={() => audio.playSFX(\"menu_hover\")}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onFocus={() => setFocusedIndex(index)}\n isFocused={focusedIndex === index}\n isMobile={isMobile}\n testId={item.testId}\n />\n ))}\n </div>\n\n {/* ESC hint */}\n <div\n id=\"pause-hint\"\n data-testid=\"pause-hint\"\n style={{\n marginTop: isMobile ? \"40px\" : \"60px\",\n fontSize: isMobile ? \"12px\" : \"14px\",\n color: themeColors.textSecondary,\n fontFamily: theme.fontFamily.KOREAN,\n textAlign: \"center\",\n }}\n >\n ESC 키를 눌러 계속 | Press ESC to resume\n <br />\n ↑↓ 키로 이동 | Use ↑↓ to navigate\n </div>\n </div>\n\n {/* Controls Guide Submenu */}\n {activeSubmenu === \"controls\" && (\n <ControlsGuide\n onClose={() => {\n setActiveSubmenu(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Settings Submenu */}\n {activeSubmenu === \"settings\" && (\n <QuickSettings\n onClose={() => {\n setActiveSubmenu(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Restart Confirmation Dialog */}\n {showConfirm === \"restart\" && (\n <ConfirmDialog\n isOpen={true}\n title=\"Restart Match?\"\n titleKorean=\"경기를 재시작하시겠습니까?\"\n message=\"All progress in the current match will be lost.\"\n messageKorean=\"현재 경기의 모든 진행 상황이 초기화됩니다.\"\n onConfirm={() => {\n setShowConfirm(null);\n onRestart();\n }}\n onCancel={() => {\n setShowConfirm(null);\n }}\n isMobile={isMobile}\n />\n )}\n\n {/* Return to Menu Confirmation Dialog */}\n {showConfirm === \"menu\" && (\n <ConfirmDialog\n isOpen={true}\n title=\"Return to Menu?\"\n titleKorean=\"메인 메뉴로 돌아가시겠습니까?\"\n message=\"All progress in the current match will be lost.\"\n messageKorean=\"현재 경기의 모든 진행 상황이 손실됩니다.\"\n onConfirm={() => {\n setShowConfirm(null);\n onReturnToMenu();\n }}\n onCancel={() => {\n setShowConfirm(null);\n }}\n isMobile={isMobile}\n />\n )}\n </>\n );\n};\n\nexport default PauseMenu;\n"],"mappings":"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"}
|
|
@@ -74,24 +74,23 @@ var RoundAnnouncement = ({ roundNumber, roundWinner, currentScore, roundStats, o
|
|
|
74
74
|
const cyanColor = useMemo(() => hexColorToCSS(theme.colors.PRIMARY_CYAN), [theme.colors.PRIMARY_CYAN]);
|
|
75
75
|
const darkBg = useMemo(() => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK), [theme.colors.UI_BACKGROUND_DARK]);
|
|
76
76
|
const textSecondary = useMemo(() => hexColorToCSS(theme.colors.TEXT_SECONDARY), [theme.colors.TEXT_SECONDARY]);
|
|
77
|
-
const containerStyle = useMemo(() => ({
|
|
78
|
-
position: "fixed",
|
|
79
|
-
top: 0,
|
|
80
|
-
left: 0,
|
|
81
|
-
width: "100%",
|
|
82
|
-
height: "100%",
|
|
83
|
-
display: "flex",
|
|
84
|
-
flexDirection: "column",
|
|
85
|
-
alignItems: "center",
|
|
86
|
-
justifyContent: "center",
|
|
87
|
-
backgroundColor: `${darkBg}dd`,
|
|
88
|
-
zIndex: 1e3,
|
|
89
|
-
opacity: isVisible ? 1 : 0,
|
|
90
|
-
transition: "opacity 0.3s ease-in-out"
|
|
91
|
-
}), [darkBg, isVisible]);
|
|
92
77
|
return /* @__PURE__ */ jsxs("div", {
|
|
93
78
|
"data-testid": "round-announcement",
|
|
94
|
-
style:
|
|
79
|
+
style: useMemo(() => ({
|
|
80
|
+
position: "fixed",
|
|
81
|
+
top: 0,
|
|
82
|
+
left: 0,
|
|
83
|
+
width: "100%",
|
|
84
|
+
height: "100%",
|
|
85
|
+
display: "flex",
|
|
86
|
+
flexDirection: "column",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
justifyContent: "center",
|
|
89
|
+
backgroundColor: `${darkBg}dd`,
|
|
90
|
+
zIndex: 1e3,
|
|
91
|
+
opacity: isVisible ? 1 : 0,
|
|
92
|
+
transition: "opacity 0.3s ease-in-out"
|
|
93
|
+
}), [darkBg, isVisible]),
|
|
95
94
|
children: [
|
|
96
95
|
isMatchPoint && /* @__PURE__ */ jsx("div", {
|
|
97
96
|
style: {
|
package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoundAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundAnnouncement Component - Displays round completion and transition UI\n *\n * Korean: 라운드 발표 (Round Announcement)\n *\n * Shows round winner, current score, statistics, and countdown to next round.\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/RoundAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Round statistics displayed between rounds\n *\n * Korean: 라운드 통계 (Round Statistics)\n */\nexport interface RoundStats {\n /** Total damage dealt by player */\n readonly damageDealt: number;\n /** Number of hits successfully landed */\n readonly hitsLanded: number;\n /** Number of vital points struck */\n readonly vitalPointsHit: number;\n /** Combat accuracy percentage */\n readonly accuracy: number;\n}\n\n/**\n * Props for the RoundAnnouncement component\n */\nexport interface RoundAnnouncementProps {\n /** Current round number (1-based) */\n readonly roundNumber: number;\n /** Round winner, or null if round was a draw */\n readonly roundWinner: PlayerState | null;\n /** Current match score (player1 wins, player2 wins) */\n readonly currentScore: { readonly player1: number; readonly player2: number };\n /** Round statistics to display */\n readonly roundStats?: RoundStats;\n /** Callback when countdown completes */\n readonly onCountdownComplete: () => void;\n /** Callback when skip button is pressed */\n readonly onSkip: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Total number of rounds in match (for match point detection) */\n readonly totalRounds?: number;\n /** Countdown duration in seconds */\n readonly countdownDuration?: number;\n}\n\n/**\n * RoundAnnouncement Component\n *\n * Displays round completion announcement with:\n * - Bilingual round completion title\n * - Round winner display\n * - Current match score\n * - Round statistics (damage, hits, accuracy)\n * - Countdown to next round\n * - Skip button for quick play\n * - Match point indicator for final rounds\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 종료 발표 컴포넌트\n */\nexport const RoundAnnouncement: React.FC<RoundAnnouncementProps> = ({\n roundNumber,\n roundWinner,\n currentScore,\n roundStats,\n onCountdownComplete,\n onSkip,\n isMobile,\n totalRounds = 3,\n countdownDuration = 3,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"large\", isMobile });\n const [countdown, setCountdown] = useState(countdownDuration);\n const [isVisible, setIsVisible] = useState(false);\n const hasCompletedRef = useRef(false);\n\n // Use ref to stabilize callback - prevents timer reset on re-renders\n const onCountdownCompleteRef = useRef(onCountdownComplete);\n useEffect(() => {\n onCountdownCompleteRef.current = onCountdownComplete;\n }, [onCountdownComplete]);\n\n // Stable callback that reads from ref\n const handleCountdownComplete = useCallback(() => {\n onCountdownCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n // Trigger fade in after a small delay\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Countdown logic with proper cleanup - uses stable callback\n useEffect(() => {\n if (countdown <= 0) {\n if (!hasCompletedRef.current) {\n hasCompletedRef.current = true;\n handleCountdownComplete();\n }\n return;\n }\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n // Countdown complete, callback will be handled by effect\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [countdown, handleCountdownComplete]);\n\n // Determine if this is match point\n const isMatchPoint = useMemo(() => {\n const maxScore = Math.max(currentScore.player1, currentScore.player2);\n const roundsToWin = Math.ceil(totalRounds / 2);\n return maxScore === roundsToWin - 1;\n }, [currentScore, totalRounds]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(\n () => hexColorToCSS(theme.colors.ACCENT_GOLD),\n [theme.colors.ACCENT_GOLD]\n );\n const cyanColor = useMemo(\n () => hexColorToCSS(theme.colors.PRIMARY_CYAN),\n [theme.colors.PRIMARY_CYAN]\n );\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n const textSecondary = useMemo(\n () => hexColorToCSS(theme.colors.TEXT_SECONDARY),\n [theme.colors.TEXT_SECONDARY]\n );\n\n // Memoize container style for performance\n const containerStyle = useMemo(\n () => ({\n position: \"fixed\" as const,\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\" as const,\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: `${darkBg}dd`,\n zIndex: 1000,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n }),\n [darkBg, isVisible]\n );\n\n return (\n <div data-testid=\"round-announcement\" style={containerStyle}>\n {/* Match Point Indicator */}\n {isMatchPoint && (\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"10px\" : \"20px\",\n textShadow: `0 0 20px ${goldColor}`,\n animation: \"pulse 1s infinite\",\n }}\n data-testid=\"match-point-indicator\"\n >\n 매치 포인트! | Match Point!\n </div>\n )}\n\n {/* Round Complete Title */}\n <h1\n style={{\n fontSize: isMobile ? \"36px\" : \"56px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textShadow: `0 0 30px ${goldColor}`,\n textAlign: \"center\",\n }}\n data-testid=\"round-complete-title\"\n >\n 라운드 {roundNumber} 완료!\n <br />\n Round {roundNumber} Complete!\n </h1>\n\n {/* Round Winner */}\n {roundWinner && (\n <div\n style={{\n fontSize: isMobile ? \"24px\" : \"36px\",\n color: cyanColor,\n fontFamily: theme.fontFamily.KOREAN,\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textAlign: \"center\",\n }}\n data-testid=\"round-winner\"\n >\n 승자 | Winner: {roundWinner.name.korean} | {roundWinner.name.english}\n </div>\n )}\n\n {/* Current Score */}\n <div\n style={{\n display: \"flex\",\n gap: isMobile ? \"40px\" : \"80px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"32px\" : \"48px\",\n fontWeight: \"bold\",\n }}\n data-testid=\"current-score\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 1\n </div>\n <div style={{ color: goldColor }}>{currentScore.player1}</div>\n </div>\n <div style={{ color: cyanColor }}>-</div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 2\n </div>\n <div style={{ color: goldColor }}>{currentScore.player2}</div>\n </div>\n </div>\n\n {/* Round Statistics */}\n {roundStats && (\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: isMobile ? \"12px\" : \"24px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: textSecondary,\n fontFamily: theme.fontFamily.KOREAN,\n }}\n data-testid=\"round-stats\"\n >\n <div>피해량 | Damage: {roundStats.damageDealt.toFixed(0)}</div>\n <div>명중 | Hits: {roundStats.hitsLanded}</div>\n <div>급소타격 | Vital Points: {roundStats.vitalPointsHit}</div>\n <div>정확도 | Accuracy: {roundStats.accuracy.toFixed(1)}%</div>\n </div>\n )}\n\n {/* Countdown */}\n <div\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: goldColor,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n textShadow: `0 0 40px ${goldColor}`,\n }}\n data-testid=\"countdown-display\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Countdown: ${countdown} seconds remaining`}\n >\n {countdown}\n </div>\n\n {/* Skip Button */}\n <button\n onClick={onSkip}\n data-testid=\"skip-countdown-button\"\n className=\"skip-countdown-button\"\n style={{\n padding: isMobile ? \"10px 24px\" : \"12px 32px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n backgroundColor: cyanColor,\n color: darkBg,\n border: \"none\",\n borderRadius: \"6px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n }}\n >\n 건너뛰기 | Skip\n </button>\n\n {/* CSS Animation for pulse effect and button hover */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.05);\n }\n }\n \n .skip-countdown-button {\n transition: all 0.2s ease;\n }\n \n .skip-countdown-button:hover {\n transform: scale(1.05);\n box-shadow: 0 0 20px ${cyanColor};\n }\n \n .skip-countdown-button:focus {\n outline: 2px solid ${cyanColor};\n outline-offset: 2px;\n }\n `}\n </style>\n </div>\n );\n};\n\nexport default RoundAnnouncement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,IAAa,qBAAuD,EAClE,aACA,aACA,cACA,YACA,qBACA,QACA,UACA,cAAc,GACd,oBAAoB,QAChB;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS;EAAU,CAAC;CAC7E,MAAM,CAAC,WAAW,gBAAgB,SAAS,kBAAkB;CAC7D,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,kBAAkB,OAAO,MAAM;CAGrC,MAAM,yBAAyB,OAAO,oBAAoB;AAC1D,iBAAgB;AACd,yBAAuB,UAAU;IAChC,CAAC,oBAAoB,CAAC;CAGzB,MAAM,0BAA0B,kBAAkB;AAChD,yBAAuB,SAAS;IAC/B,EAAE,CAAC;AAGN,iBAAgB;EAEd,MAAM,QAAQ,iBAAiB,aAAa,KAAK,EAAE,GAAG;AACtD,eAAa,aAAa,MAAM;IAC/B,EAAE,CAAC;AAGN,iBAAgB;AACd,MAAI,aAAa,GAAG;AAClB,OAAI,CAAC,gBAAgB,SAAS;AAC5B,oBAAgB,UAAU;AAC1B,6BAAyB;;AAE3B;;EAGF,MAAM,QAAQ,kBAAkB;AAC9B,iBAAc,SAAS;AACrB,QAAI,QAAQ,EAEV,QAAO;AAET,WAAO,OAAO;KACd;KACD,IAAK;AAER,eAAa,cAAc,MAAM;IAChC,CAAC,WAAW,wBAAwB,CAAC;CAGxC,MAAM,eAAe,cAAc;AAGjC,SAFiB,KAAK,IAAI,aAAa,SAAS,aAAa,QAAQ,KACjD,KAAK,KAAK,cAAc,EAAE,GACZ;IACjC,CAAC,cAAc,YAAY,CAAC;CAG/B,MAAM,YAAY,cACV,cAAc,MAAM,OAAO,YAAY,EAC7C,CAAC,MAAM,OAAO,YAAY,CAC3B;CACD,MAAM,YAAY,cACV,cAAc,MAAM,OAAO,aAAa,EAC9C,CAAC,MAAM,OAAO,aAAa,CAC5B;CACD,MAAM,SAAS,cACP,cAAc,MAAM,OAAO,mBAAmB,EACpD,CAAC,MAAM,OAAO,mBAAmB,CAClC;CACD,MAAM,gBAAgB,cACd,cAAc,MAAM,OAAO,eAAe,EAChD,CAAC,MAAM,OAAO,eAAe,CAC9B;CAGD,MAAM,iBAAiB,eACd;EACL,UAAU;EACV,KAAK;EACL,MAAM;EACN,OAAO;EACP,QAAQ;EACR,SAAS;EACT,eAAe;EACf,YAAY;EACZ,gBAAgB;EAChB,iBAAiB,GAAG,OAAO;EAC3B,QAAQ;EACR,SAAS,YAAY,IAAI;EACzB,YAAY;EACb,GACD,CAAC,QAAQ,UAAU,CACpB;AAED,QACE,qBAAC,OAAD;EAAK,eAAY;EAAqB,OAAO;YAA7C;GAEG,gBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,cAAc,WAAW,SAAS;KAClC,YAAY,YAAY;KACxB,WAAW;KACZ;IACD,eAAY;cACb;IAEK,CAAA;GAIR,qBAAC,MAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,QAAQ,OAAO,WAAW,SAAS,OAAO;KAC1C,YAAY,YAAY;KACxB,WAAW;KACZ;IACD,eAAY;cAVd;KAWC;KACM;KAAY;KACjB,oBAAC,MAAD,EAAM,CAAA;;KACC;KAAY;KAChB;;GAGJ,eACC,qBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC7B,QAAQ,OAAO,WAAW,SAAS,OAAO;KAC1C,WAAW;KACZ;IACD,eAAY;cARd;KASC;KACe,YAAY,KAAK;KAAO;KAAI,YAAY,KAAK;KACvD;;GAIR,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,KAAK,WAAW,SAAS;KACzB,cAAc,WAAW,SAAS;KAClC,UAAU,WAAW,SAAS;KAC9B,YAAY;KACb;IACD,eAAY;cARd;KAUE,qBAAC,OAAD;MAAK,OAAO,EAAE,WAAW,UAAU;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL,UAAU,WAAW,SAAS;QAC9B,OAAO;QACP,cAAc;QACf;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OAAK,OAAO,EAAE,OAAO,WAAW;iBAAG,aAAa;OAAc,CAAA,CAC1D;;KACN,oBAAC,OAAD;MAAK,OAAO,EAAE,OAAO,WAAW;gBAAE;MAAO,CAAA;KACzC,qBAAC,OAAD;MAAK,OAAO,EAAE,WAAW,UAAU;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL,UAAU,WAAW,SAAS;QAC9B,OAAO;QACP,cAAc;QACf;iBACF;OAEK,CAAA,EACN,oBAAC,OAAD;OAAK,OAAO,EAAE,OAAO,WAAW;iBAAG,aAAa;OAAc,CAAA,CAC1D;;KACF;;GAGL,cACC,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe,WAAW,WAAW;KACrC,KAAK,WAAW,SAAS;KACzB,cAAc,WAAW,SAAS;KAClC,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY,MAAM,WAAW;KAC9B;IACD,eAAY;cAVd;KAYE,qBAAC,OAAD,EAAA,UAAA,CAAK,kBAAe,WAAW,YAAY,QAAQ,EAAE,CAAO,EAAA,CAAA;KAC5D,qBAAC,OAAD,EAAA,UAAA,CAAK,eAAY,WAAW,WAAiB,EAAA,CAAA;KAC7C,qBAAC,OAAD,EAAA,UAAA,CAAK,yBAAsB,WAAW,eAAqB,EAAA,CAAA;KAC3D,qBAAC,OAAD,EAAA,UAAA;MAAK;MAAiB,WAAW,SAAS,QAAQ,EAAE;MAAC;MAAO,EAAA,CAAA;KACxD;;GAIR,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,WAAW,SAAS;KAC9B,OAAO;KACP,YAAY;KACZ,cAAc,WAAW,SAAS;KAClC,YAAY,YAAY;KACzB;IACD,eAAY;IACZ,aAAU;IACV,eAAY;IACZ,cAAY,cAAc,UAAU;cAEnC;IACG,CAAA;GAGN,oBAAC,UAAD;IACE,SAAS;IACT,eAAY;IACZ,WAAU;IACV,OAAO;KACL,SAAS,WAAW,cAAc;KAClC,UAAU,WAAW,SAAS;KAC9B,iBAAiB;KACjB,OAAO;KACP,QAAQ;KACR,cAAc;KACd,YAAY,MAAM,WAAW;KAC7B,YAAY;KACZ,QAAQ;KACT;cACF;IAEQ,CAAA;GAGT,oBAAC,SAAD,EAAA,UACG;;;;;;;;;;;;;;;;;;mCAkB0B,UAAU;;;;iCAIZ,UAAU;;;WAI7B,CAAA;GACJ"}
|
|
1
|
+
{"version":3,"file":"RoundAnnouncementOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.tsx"],"sourcesContent":["/**\n * RoundAnnouncement Component - Displays round completion and transition UI\n *\n * Korean: 라운드 발표 (Round Announcement)\n *\n * Shows round winner, current score, statistics, and countdown to next round.\n * Implements Korean cyberpunk aesthetic with bilingual text support.\n *\n * Refactored to use useKoreanTheme for consistent styling.\n *\n * @module components/combat/RoundAnnouncement\n * @category Combat UI\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\nimport { hexColorToCSS } from \"../../../../../utils/colorUtils\";\n\n/**\n * Round statistics displayed between rounds\n *\n * Korean: 라운드 통계 (Round Statistics)\n */\nexport interface RoundStats {\n /** Total damage dealt by player */\n readonly damageDealt: number;\n /** Number of hits successfully landed */\n readonly hitsLanded: number;\n /** Number of vital points struck */\n readonly vitalPointsHit: number;\n /** Combat accuracy percentage */\n readonly accuracy: number;\n}\n\n/**\n * Props for the RoundAnnouncement component\n */\nexport interface RoundAnnouncementProps {\n /** Current round number (1-based) */\n readonly roundNumber: number;\n /** Round winner, or null if round was a draw */\n readonly roundWinner: PlayerState | null;\n /** Current match score (player1 wins, player2 wins) */\n readonly currentScore: { readonly player1: number; readonly player2: number };\n /** Round statistics to display */\n readonly roundStats?: RoundStats;\n /** Callback when countdown completes */\n readonly onCountdownComplete: () => void;\n /** Callback when skip button is pressed */\n readonly onSkip: () => void;\n /** Whether layout should adapt for mobile screens */\n readonly isMobile: boolean;\n /** Total number of rounds in match (for match point detection) */\n readonly totalRounds?: number;\n /** Countdown duration in seconds */\n readonly countdownDuration?: number;\n}\n\n/**\n * RoundAnnouncement Component\n *\n * Displays round completion announcement with:\n * - Bilingual round completion title\n * - Round winner display\n * - Current match score\n * - Round statistics (damage, hits, accuracy)\n * - Countdown to next round\n * - Skip button for quick play\n * - Match point indicator for final rounds\n * - Uses useKoreanTheme for consistent styling\n *\n * Korean: 라운드 종료 발표 컴포넌트\n */\nexport const RoundAnnouncement: React.FC<RoundAnnouncementProps> = ({\n roundNumber,\n roundWinner,\n currentScore,\n roundStats,\n onCountdownComplete,\n onSkip,\n isMobile,\n totalRounds = 3,\n countdownDuration = 3,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"large\", isMobile });\n const [countdown, setCountdown] = useState(countdownDuration);\n const [isVisible, setIsVisible] = useState(false);\n const hasCompletedRef = useRef(false);\n\n // Use ref to stabilize callback - prevents timer reset on re-renders\n const onCountdownCompleteRef = useRef(onCountdownComplete);\n useEffect(() => {\n onCountdownCompleteRef.current = onCountdownComplete;\n }, [onCountdownComplete]);\n\n // Stable callback that reads from ref\n const handleCountdownComplete = useCallback(() => {\n onCountdownCompleteRef.current();\n }, []);\n\n // Fade in animation on mount\n useEffect(() => {\n // Trigger fade in after a small delay\n const timer = setTimeout(() => setIsVisible(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n // Countdown logic with proper cleanup - uses stable callback\n useEffect(() => {\n if (countdown <= 0) {\n if (!hasCompletedRef.current) {\n hasCompletedRef.current = true;\n handleCountdownComplete();\n }\n return;\n }\n\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n // Countdown complete, callback will be handled by effect\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [countdown, handleCountdownComplete]);\n\n // Determine if this is match point\n const isMatchPoint = useMemo(() => {\n const maxScore = Math.max(currentScore.player1, currentScore.player2);\n const roundsToWin = Math.ceil(totalRounds / 2);\n return maxScore === roundsToWin - 1;\n }, [currentScore, totalRounds]);\n\n // Convert hex colors to CSS - memoized for performance using theme\n const goldColor = useMemo(\n () => hexColorToCSS(theme.colors.ACCENT_GOLD),\n [theme.colors.ACCENT_GOLD]\n );\n const cyanColor = useMemo(\n () => hexColorToCSS(theme.colors.PRIMARY_CYAN),\n [theme.colors.PRIMARY_CYAN]\n );\n const darkBg = useMemo(\n () => hexColorToCSS(theme.colors.UI_BACKGROUND_DARK),\n [theme.colors.UI_BACKGROUND_DARK]\n );\n const textSecondary = useMemo(\n () => hexColorToCSS(theme.colors.TEXT_SECONDARY),\n [theme.colors.TEXT_SECONDARY]\n );\n\n // Memoize container style for performance\n const containerStyle = useMemo(\n () => ({\n position: \"fixed\" as const,\n top: 0,\n left: 0,\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\" as const,\n alignItems: \"center\",\n justifyContent: \"center\",\n backgroundColor: `${darkBg}dd`,\n zIndex: 1000,\n opacity: isVisible ? 1 : 0,\n transition: \"opacity 0.3s ease-in-out\",\n }),\n [darkBg, isVisible]\n );\n\n return (\n <div data-testid=\"round-announcement\" style={containerStyle}>\n {/* Match Point Indicator */}\n {isMatchPoint && (\n <div\n style={{\n fontSize: isMobile ? \"20px\" : \"28px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"10px\" : \"20px\",\n textShadow: `0 0 20px ${goldColor}`,\n animation: \"pulse 1s infinite\",\n }}\n data-testid=\"match-point-indicator\"\n >\n 매치 포인트! | Match Point!\n </div>\n )}\n\n {/* Round Complete Title */}\n <h1\n style={{\n fontSize: isMobile ? \"36px\" : \"56px\",\n color: goldColor,\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textShadow: `0 0 30px ${goldColor}`,\n textAlign: \"center\",\n }}\n data-testid=\"round-complete-title\"\n >\n 라운드 {roundNumber} 완료!\n <br />\n Round {roundNumber} Complete!\n </h1>\n\n {/* Round Winner */}\n {roundWinner && (\n <div\n style={{\n fontSize: isMobile ? \"24px\" : \"36px\",\n color: cyanColor,\n fontFamily: theme.fontFamily.KOREAN,\n margin: `0 0 ${isMobile ? \"20px\" : \"30px\"} 0`,\n textAlign: \"center\",\n }}\n data-testid=\"round-winner\"\n >\n 승자 | Winner: {roundWinner.name.korean} | {roundWinner.name.english}\n </div>\n )}\n\n {/* Current Score */}\n <div\n style={{\n display: \"flex\",\n gap: isMobile ? \"40px\" : \"80px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"32px\" : \"48px\",\n fontWeight: \"bold\",\n }}\n data-testid=\"current-score\"\n >\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 1\n </div>\n <div style={{ color: goldColor }}>{currentScore.player1}</div>\n </div>\n <div style={{ color: cyanColor }}>-</div>\n <div style={{ textAlign: \"center\" }}>\n <div\n style={{\n fontSize: isMobile ? \"14px\" : \"18px\",\n color: cyanColor,\n marginBottom: \"8px\",\n }}\n >\n Player 2\n </div>\n <div style={{ color: goldColor }}>{currentScore.player2}</div>\n </div>\n </div>\n\n {/* Round Statistics */}\n {roundStats && (\n <div\n style={{\n display: \"flex\",\n flexDirection: isMobile ? \"column\" : \"row\",\n gap: isMobile ? \"12px\" : \"24px\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n color: textSecondary,\n fontFamily: theme.fontFamily.KOREAN,\n }}\n data-testid=\"round-stats\"\n >\n <div>피해량 | Damage: {roundStats.damageDealt.toFixed(0)}</div>\n <div>명중 | Hits: {roundStats.hitsLanded}</div>\n <div>급소타격 | Vital Points: {roundStats.vitalPointsHit}</div>\n <div>정확도 | Accuracy: {roundStats.accuracy.toFixed(1)}%</div>\n </div>\n )}\n\n {/* Countdown */}\n <div\n style={{\n fontSize: isMobile ? \"48px\" : \"72px\",\n color: goldColor,\n fontWeight: \"bold\",\n marginBottom: isMobile ? \"20px\" : \"30px\",\n textShadow: `0 0 40px ${goldColor}`,\n }}\n data-testid=\"countdown-display\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n aria-label={`Countdown: ${countdown} seconds remaining`}\n >\n {countdown}\n </div>\n\n {/* Skip Button */}\n <button\n onClick={onSkip}\n data-testid=\"skip-countdown-button\"\n className=\"skip-countdown-button\"\n style={{\n padding: isMobile ? \"10px 24px\" : \"12px 32px\",\n fontSize: isMobile ? \"14px\" : \"16px\",\n backgroundColor: cyanColor,\n color: darkBg,\n border: \"none\",\n borderRadius: \"6px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n }}\n >\n 건너뛰기 | Skip\n </button>\n\n {/* CSS Animation for pulse effect and button hover */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(1.05);\n }\n }\n \n .skip-countdown-button {\n transition: all 0.2s ease;\n }\n \n .skip-countdown-button:hover {\n transform: scale(1.05);\n box-shadow: 0 0 20px ${cyanColor};\n }\n \n .skip-countdown-button:focus {\n outline: 2px solid ${cyanColor};\n outline-offset: 2px;\n }\n `}\n </style>\n </div>\n );\n};\n\nexport default RoundAnnouncement;\n"],"mappings":"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"}
|
|
@@ -76,12 +76,11 @@ var DifficultyIndicator = ({ tier, isMobile }) => {
|
|
|
76
76
|
const tierBackground = useMemo(() => hexToRgbaString(tierColorValue, .13), [tierColorValue]);
|
|
77
77
|
const tierBoxShadow = useMemo(() => hexToRgbaString(tierColorValue, .27), [tierColorValue]);
|
|
78
78
|
const fontSize = isMobile ? 11 : 13;
|
|
79
|
-
const padding = isMobile ? "6px 10px" : "8px 12px";
|
|
80
79
|
return /* @__PURE__ */ jsxs("div", {
|
|
81
80
|
"data-testid": "difficulty-indicator",
|
|
82
81
|
style: {
|
|
83
82
|
position: "relative",
|
|
84
|
-
padding,
|
|
83
|
+
padding: isMobile ? "6px 10px" : "8px 12px",
|
|
85
84
|
background: tierBackground,
|
|
86
85
|
border: `2px solid ${tierColor}`,
|
|
87
86
|
borderRadius: "4px",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DifficultyIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/DifficultyIndicator.tsx"],"sourcesContent":["/**\n * DifficultyIndicator Component - AI Difficulty Tier Display\n *\n * Shows the current AI difficulty tier with Korean-English bilingual text\n * and color-coded visual feedback based on tier level.\n *\n * **Korean Philosophy (난이도 표시)**:\n * Provides transparent feedback to player about AI challenge level,\n * helping them understand their skill progression.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DifficultyTier } from \"../../../../../systems/ai\";\nimport {\n hexColorToCSS,\n hexToRgbaString,\n} from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface DifficultyIndicatorProps {\n /** Current difficulty tier (1-5) */\n readonly tier: DifficultyTier;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get tier display name with Korean and English\n */\nfunction getTierName(tier: DifficultyTier): {\n korean: string;\n english: string;\n} {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return { korean: \"초보\", english: \"Beginner\" };\n case DifficultyTier.NOVICE:\n return { korean: \"입문\", english: \"Novice\" };\n case DifficultyTier.INTERMEDIATE:\n return { korean: \"중급\", english: \"Intermediate\" };\n case DifficultyTier.ADVANCED:\n return { korean: \"고급\", english: \"Advanced\" };\n case DifficultyTier.EXPERT:\n return { korean: \"전문\", english: \"Expert\" };\n default:\n return { korean: \"중급\", english: \"Intermediate\" };\n }\n}\n\n/**\n * Get numeric color for difficulty tier using theme colors\n * Maps tiers to existing color scheme for consistency\n */\nfunction getTierColorValue(\n tier: DifficultyTier,\n theme: ReturnType<typeof useKoreanTheme>,\n): number {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return theme.colors.POSITIVE_GREEN; // Green - Easy\n case DifficultyTier.NOVICE:\n return theme.colors.ACCENT_GREEN; // Light Green\n case DifficultyTier.INTERMEDIATE:\n return theme.colors.ACCENT_GOLD; // Gold - Medium\n case DifficultyTier.ADVANCED:\n return theme.colors.SECONDARY_ORANGE; // Orange\n case DifficultyTier.EXPERT:\n return theme.colors.NEGATIVE_RED; // Red - Hard\n default:\n return theme.colors.ACCENT_GOLD; // Default to medium\n }\n}\n\n/**\n * DifficultyIndicator - Visual feedback for current AI difficulty tier\n *\n * Uses relative positioning for embedding in container HUDs\n */\nexport const DifficultyIndicator: React.FC<DifficultyIndicatorProps> = ({\n tier,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile });\n const tierName = getTierName(tier);\n const tierColorValue = getTierColorValue(tier, theme);\n\n // Memoize color calculations\n const tierColor = useMemo(\n () => hexColorToCSS(tierColorValue),\n [tierColorValue],\n );\n const tierBackground = useMemo(\n () => hexToRgbaString(tierColorValue, 0.13),\n [tierColorValue],\n );\n const tierBoxShadow = useMemo(\n () => hexToRgbaString(tierColorValue, 0.27),\n [tierColorValue],\n );\n\n // Responsive sizing\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"6px 10px\" : \"8px 12px\";\n\n return (\n <div\n data-testid=\"difficulty-indicator\"\n style={{\n position: \"relative\",\n padding,\n background: tierBackground, // 13% opacity background\n border: `2px solid ${tierColor}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: `${fontSize}px`,\n color: tierColor,\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n transition: \"all 0.3s ease-in-out\", // Smooth color/border transitions\n boxShadow: `0 0 8px ${tierBoxShadow}`, // Subtle glow effect (27% opacity)\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n }}\n >\n <div\n data-testid=\"difficulty-label\"\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n opacity: 0.8,\n letterSpacing: \"0.5px\",\n }}\n >\n AI Difficulty\n </div>\n <div\n data-testid=\"difficulty-tier\"\n style={{\n fontWeight: \"bold\",\n whiteSpace: \"nowrap\",\n letterSpacing: \"0.5px\",\n }}\n >\n {tierName.korean} | {tierName.english}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,SAAS,YAAY,MAGnB;AACA,SAAQ,MAAR;EACE,KAAK,eAAe,SAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,OAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,KAAK,eAAe,aAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;EAClD,KAAK,eAAe,SAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,OAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,QACE,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;;;;;;;AAQtD,SAAS,kBACP,MACA,OACQ;AACR,SAAQ,MAAR;EACE,KAAK,eAAe,SAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,OAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,aAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,SAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,OAClB,QAAO,MAAM,OAAO;EACtB,QACE,QAAO,MAAM,OAAO;;;;;;;;AAS1B,IAAa,uBAA2D,EACtE,MACA,eACI;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS;EAAU,CAAC;CAC7E,MAAM,WAAW,YAAY,KAAK;CAClC,MAAM,iBAAiB,kBAAkB,MAAM,MAAM;CAGrD,MAAM,YAAY,cACV,cAAc,eAAe,EACnC,CAAC,eAAe,CACjB;CACD,MAAM,iBAAiB,cACf,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CACD,MAAM,gBAAgB,cACd,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CAGD,MAAM,WAAW,WAAW,KAAK;
|
|
1
|
+
{"version":3,"file":"DifficultyIndicator.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/DifficultyIndicator.tsx"],"sourcesContent":["/**\n * DifficultyIndicator Component - AI Difficulty Tier Display\n *\n * Shows the current AI difficulty tier with Korean-English bilingual text\n * and color-coded visual feedback based on tier level.\n *\n * **Korean Philosophy (난이도 표시)**:\n * Provides transparent feedback to player about AI challenge level,\n * helping them understand their skill progression.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DifficultyTier } from \"../../../../../systems/ai\";\nimport {\n hexColorToCSS,\n hexToRgbaString,\n} from \"../../../../../utils/colorUtils\";\nimport { useKoreanTheme } from \"../../../../shared/base/useKoreanTheme\";\n\nexport interface DifficultyIndicatorProps {\n /** Current difficulty tier (1-5) */\n readonly tier: DifficultyTier;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Get tier display name with Korean and English\n */\nfunction getTierName(tier: DifficultyTier): {\n korean: string;\n english: string;\n} {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return { korean: \"초보\", english: \"Beginner\" };\n case DifficultyTier.NOVICE:\n return { korean: \"입문\", english: \"Novice\" };\n case DifficultyTier.INTERMEDIATE:\n return { korean: \"중급\", english: \"Intermediate\" };\n case DifficultyTier.ADVANCED:\n return { korean: \"고급\", english: \"Advanced\" };\n case DifficultyTier.EXPERT:\n return { korean: \"전문\", english: \"Expert\" };\n default:\n return { korean: \"중급\", english: \"Intermediate\" };\n }\n}\n\n/**\n * Get numeric color for difficulty tier using theme colors\n * Maps tiers to existing color scheme for consistency\n */\nfunction getTierColorValue(\n tier: DifficultyTier,\n theme: ReturnType<typeof useKoreanTheme>,\n): number {\n switch (tier) {\n case DifficultyTier.BEGINNER:\n return theme.colors.POSITIVE_GREEN; // Green - Easy\n case DifficultyTier.NOVICE:\n return theme.colors.ACCENT_GREEN; // Light Green\n case DifficultyTier.INTERMEDIATE:\n return theme.colors.ACCENT_GOLD; // Gold - Medium\n case DifficultyTier.ADVANCED:\n return theme.colors.SECONDARY_ORANGE; // Orange\n case DifficultyTier.EXPERT:\n return theme.colors.NEGATIVE_RED; // Red - Hard\n default:\n return theme.colors.ACCENT_GOLD; // Default to medium\n }\n}\n\n/**\n * DifficultyIndicator - Visual feedback for current AI difficulty tier\n *\n * Uses relative positioning for embedding in container HUDs\n */\nexport const DifficultyIndicator: React.FC<DifficultyIndicatorProps> = ({\n tier,\n isMobile,\n}) => {\n const theme = useKoreanTheme({ variant: \"primary\", size: \"small\", isMobile });\n const tierName = getTierName(tier);\n const tierColorValue = getTierColorValue(tier, theme);\n\n // Memoize color calculations\n const tierColor = useMemo(\n () => hexColorToCSS(tierColorValue),\n [tierColorValue],\n );\n const tierBackground = useMemo(\n () => hexToRgbaString(tierColorValue, 0.13),\n [tierColorValue],\n );\n const tierBoxShadow = useMemo(\n () => hexToRgbaString(tierColorValue, 0.27),\n [tierColorValue],\n );\n\n // Responsive sizing\n const fontSize = isMobile ? 11 : 13;\n const padding = isMobile ? \"6px 10px\" : \"8px 12px\";\n\n return (\n <div\n data-testid=\"difficulty-indicator\"\n style={{\n position: \"relative\",\n padding,\n background: tierBackground, // 13% opacity background\n border: `2px solid ${tierColor}`,\n borderRadius: \"4px\",\n fontFamily: theme.fontFamily.KOREAN,\n fontSize: `${fontSize}px`,\n color: tierColor,\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"2px\",\n transition: \"all 0.3s ease-in-out\", // Smooth color/border transitions\n boxShadow: `0 0 8px ${tierBoxShadow}`, // Subtle glow effect (27% opacity)\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n }}\n >\n <div\n data-testid=\"difficulty-label\"\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n opacity: 0.8,\n letterSpacing: \"0.5px\",\n }}\n >\n AI Difficulty\n </div>\n <div\n data-testid=\"difficulty-tier\"\n style={{\n fontWeight: \"bold\",\n whiteSpace: \"nowrap\",\n letterSpacing: \"0.5px\",\n }}\n >\n {tierName.korean} | {tierName.english}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,SAAS,YAAY,MAGnB;AACA,SAAQ,MAAR;EACE,KAAK,eAAe,SAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,OAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,KAAK,eAAe,aAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;EAClD,KAAK,eAAe,SAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAY;EAC9C,KAAK,eAAe,OAClB,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAU;EAC5C,QACE,QAAO;GAAE,QAAQ;GAAM,SAAS;GAAgB;;;;;;;AAQtD,SAAS,kBACP,MACA,OACQ;AACR,SAAQ,MAAR;EACE,KAAK,eAAe,SAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,OAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,aAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,SAClB,QAAO,MAAM,OAAO;EACtB,KAAK,eAAe,OAClB,QAAO,MAAM,OAAO;EACtB,QACE,QAAO,MAAM,OAAO;;;;;;;;AAS1B,IAAa,uBAA2D,EACtE,MACA,eACI;CACJ,MAAM,QAAQ,eAAe;EAAE,SAAS;EAAW,MAAM;EAAS;EAAU,CAAC;CAC7E,MAAM,WAAW,YAAY,KAAK;CAClC,MAAM,iBAAiB,kBAAkB,MAAM,MAAM;CAGrD,MAAM,YAAY,cACV,cAAc,eAAe,EACnC,CAAC,eAAe,CACjB;CACD,MAAM,iBAAiB,cACf,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CACD,MAAM,gBAAgB,cACd,gBAAgB,gBAAgB,IAAK,EAC3C,CAAC,eAAe,CACjB;CAGD,MAAM,WAAW,WAAW,KAAK;AAGjC,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,UAAU;GACV,SAPU,WAAW,aAAa;GAQlC,YAAY;GACZ,QAAQ,aAAa;GACrB,cAAc;GACd,YAAY,MAAM,WAAW;GAC7B,UAAU,GAAG,SAAS;GACtB,OAAO;GACP,YAAY;GACZ,eAAe;GACf,SAAS;GACT,eAAe;GACf,YAAY;GACZ,KAAK;GACL,YAAY;GACZ,WAAW,WAAW;GACtB,OAAO;GACP,WAAW;GACZ;YArBH,CAuBE,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,WAAW,QAAQ;IAC7B,SAAS;IACT,eAAe;IAChB;aACF;GAEK,CAAA,EACN,qBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,YAAY;IACZ,YAAY;IACZ,eAAe;IAChB;aANH;IAQG,SAAS;IAAO;IAAI,SAAS;IAC1B;KACF"}
|
|
@@ -4,7 +4,7 @@ import { ConsciousnessBlur } from "../effects/ConsciousnessBlur.js";
|
|
|
4
4
|
import { BloodLossOverlayHtml } from "../effects/BloodLossOverlayHtml.js";
|
|
5
5
|
import { StaminaWarning } from "../indicators/StaminaWarning.js";
|
|
6
6
|
import React from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/screens/combat/components/hud/PlayerStateOverlayHtml.tsx
|
|
9
9
|
/**
|
|
10
10
|
* PlayerStateOverlayHtml Component - Unified player state visual indicators
|
|
@@ -46,29 +46,33 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
var PlayerStateOverlayHtml = React.memo(({ pain, balanceState, position, consciousness, bloodLoss = 0, stamina, isMobile }) => {
|
|
49
|
-
return /* @__PURE__ */ jsxs(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
50
|
+
"data-testid": "player-state-overlay",
|
|
51
|
+
style: { display: "contents" },
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(PainVignette, {
|
|
54
|
+
pain,
|
|
55
|
+
isMobile
|
|
56
|
+
}),
|
|
57
|
+
/* @__PURE__ */ jsx(BalanceIndicator, {
|
|
58
|
+
balanceState,
|
|
59
|
+
position,
|
|
60
|
+
isMobile
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ jsx(ConsciousnessBlur, {
|
|
63
|
+
consciousness,
|
|
64
|
+
isMobile
|
|
65
|
+
}),
|
|
66
|
+
/* @__PURE__ */ jsx(BloodLossOverlayHtml, {
|
|
67
|
+
bloodLoss,
|
|
68
|
+
isMobile
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ jsx(StaminaWarning, {
|
|
71
|
+
stamina,
|
|
72
|
+
isMobile
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
});
|
|
72
76
|
}, (prevProps, nextProps) => {
|
|
73
77
|
return prevProps.pain === nextProps.pain && prevProps.balanceState === nextProps.balanceState && prevProps.position === nextProps.position && prevProps.consciousness === nextProps.consciousness && prevProps.bloodLoss === nextProps.bloodLoss && prevProps.stamina === nextProps.stamina && prevProps.isMobile === nextProps.isMobile;
|
|
74
78
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayerStateOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/PlayerStateOverlayHtml.tsx"],"sourcesContent":["/**\n * PlayerStateOverlayHtml Component - Unified player state visual indicators\n * \n * Combines all player state visual effects into a single overlay:\n * - Pain vignette\n * - Balance indicator\n * - Consciousness blur\n * - Blood loss warning\n * - Stamina warning\n * \n * @module components/combat/PlayerStateOverlayHtml\n * @category Combat UI\n * @korean 플레이어상태오버레이\n */\n\nimport React from \"react\";\nimport { PainVignette } from \"../effects/PainVignette\";\nimport { BalanceIndicator } from \"../indicators/BalanceIndicator\";\nimport { ConsciousnessBlur } from \"../effects/ConsciousnessBlur\";\nimport { BloodLossOverlayHtml } from \"../effects/BloodLossOverlayHtml\";\nimport { StaminaWarning } from \"../indicators/StaminaWarning\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface PlayerStateOverlayProps {\n /**\n * Current pain level (0-100)\n * @korean 통증\n */\n readonly pain: number;\n\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right')\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Current consciousness level (0-100)\n * @korean 의식\n */\n readonly consciousness: number;\n\n /**\n * Current blood loss (0-100, optional)\n * @korean 출혈\n */\n readonly bloodLoss?: number;\n\n /**\n * Current stamina (0-100)\n * @korean 체력\n */\n readonly stamina: number;\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n}\n\n/**\n * PlayerStateOverlayHtml - Unified visual effects for player state\n * \n * Combines all player state visual indicators into a single component\n * with optimal performance and consistent rendering. All effects use\n * smooth 0.5s transitions and are optimized for 60fps.\n *\n * Optimized with React.memo for performance:\n * - Prevents re-renders when props haven't changed\n * - Custom comparison function for precise control\n * - Reduces DOM updates for 60fps target\n * \n * @example\n * ```tsx\n * <PlayerStateOverlayHtml\n * pain={65}\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * consciousness={80}\n * bloodLoss={45}\n * stamina={15}\n * isMobile={false}\n * />\n * ```\n */\nexport const PlayerStateOverlayHtml = React.memo<PlayerStateOverlayProps>(\n ({\n pain,\n balanceState,\n position,\n consciousness,\n bloodLoss = 0,\n stamina,\n isMobile,\n }) => {\n return (\n
|
|
1
|
+
{"version":3,"file":"PlayerStateOverlayHtml.js","names":[],"sources":["../../../../../../src/components/screens/combat/components/hud/PlayerStateOverlayHtml.tsx"],"sourcesContent":["/**\n * PlayerStateOverlayHtml Component - Unified player state visual indicators\n * \n * Combines all player state visual effects into a single overlay:\n * - Pain vignette\n * - Balance indicator\n * - Consciousness blur\n * - Blood loss warning\n * - Stamina warning\n * \n * @module components/combat/PlayerStateOverlayHtml\n * @category Combat UI\n * @korean 플레이어상태오버레이\n */\n\nimport React from \"react\";\nimport { PainVignette } from \"../effects/PainVignette\";\nimport { BalanceIndicator } from \"../indicators/BalanceIndicator\";\nimport { ConsciousnessBlur } from \"../effects/ConsciousnessBlur\";\nimport { BloodLossOverlayHtml } from \"../effects/BloodLossOverlayHtml\";\nimport { StaminaWarning } from \"../indicators/StaminaWarning\";\nimport type { BalanceState } from \"../../../../../types/player-visual\";\n\nexport interface PlayerStateOverlayProps {\n /**\n * Current pain level (0-100)\n * @korean 통증\n */\n readonly pain: number;\n\n /**\n * Current balance state\n * @korean 균형상태\n */\n readonly balanceState: BalanceState;\n\n /**\n * Player position ('left' or 'right')\n * @korean 플레이어위치\n */\n readonly position: \"left\" | \"right\";\n\n /**\n * Current consciousness level (0-100)\n * @korean 의식\n */\n readonly consciousness: number;\n\n /**\n * Current blood loss (0-100, optional)\n * @korean 출혈\n */\n readonly bloodLoss?: number;\n\n /**\n * Current stamina (0-100)\n * @korean 체력\n */\n readonly stamina: number;\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile: boolean;\n}\n\n/**\n * PlayerStateOverlayHtml - Unified visual effects for player state\n * \n * Combines all player state visual indicators into a single component\n * with optimal performance and consistent rendering. All effects use\n * smooth 0.5s transitions and are optimized for 60fps.\n *\n * Optimized with React.memo for performance:\n * - Prevents re-renders when props haven't changed\n * - Custom comparison function for precise control\n * - Reduces DOM updates for 60fps target\n * \n * @example\n * ```tsx\n * <PlayerStateOverlayHtml\n * pain={65}\n * balanceState=\"SHAKEN\"\n * position=\"left\"\n * consciousness={80}\n * bloodLoss={45}\n * stamina={15}\n * isMobile={false}\n * />\n * ```\n */\nexport const PlayerStateOverlayHtml = React.memo<PlayerStateOverlayProps>(\n ({\n pain,\n balanceState,\n position,\n consciousness,\n bloodLoss = 0,\n stamina,\n isMobile,\n }) => {\n return (\n <div data-testid=\"player-state-overlay\" style={{ display: 'contents' }}>\n {/* Pain vignette - shows when pain >= 5 (see PainVignette.tsx) */}\n <PainVignette pain={pain} isMobile={isMobile} />\n\n {/* Balance indicator - always visible, color-coded by state (see BalanceIndicator.tsx) */}\n <BalanceIndicator\n balanceState={balanceState}\n position={position}\n isMobile={isMobile}\n />\n\n {/* Consciousness blur - shows when consciousness <= 90 (see ConsciousnessBlur.tsx) */}\n <ConsciousnessBlur consciousness={consciousness} isMobile={isMobile} />\n\n {/* Blood loss warning - pulses when bloodLoss >= 50 (see BloodLossOverlayHtml.tsx) */}\n <BloodLossOverlayHtml bloodLoss={bloodLoss} isMobile={isMobile} />\n\n {/* Stamina warning - flashes when stamina < 20 (see StaminaWarning.tsx) */}\n <StaminaWarning stamina={stamina} isMobile={isMobile} />\n </div>\n );\n },\n (prevProps, nextProps) => {\n // Custom comparison for optimal re-render prevention\n // Only re-render if any state value actually changed\n return (\n prevProps.pain === nextProps.pain &&\n prevProps.balanceState === nextProps.balanceState &&\n prevProps.position === nextProps.position &&\n prevProps.consciousness === nextProps.consciousness &&\n prevProps.bloodLoss === nextProps.bloodLoss &&\n prevProps.stamina === nextProps.stamina &&\n prevProps.isMobile === nextProps.isMobile\n );\n },\n);\n\nPlayerStateOverlayHtml.displayName = \"PlayerStateOverlayHtml\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,IAAa,yBAAyB,MAAM,MACzC,EACC,MACA,cACA,UACA,eACA,YAAY,GACZ,SACA,eACI;AACJ,QACE,qBAAC,OAAD;EAAK,eAAY;EAAuB,OAAO,EAAE,SAAS,YAAY;YAAtE;GAEE,oBAAC,cAAD;IAAoB;IAAgB;IAAY,CAAA;GAGhD,oBAAC,kBAAD;IACgB;IACJ;IACA;IACV,CAAA;GAGF,oBAAC,mBAAD;IAAkC;IAAyB;IAAY,CAAA;GAGvE,oBAAC,sBAAD;IAAiC;IAAqB;IAAY,CAAA;GAGlE,oBAAC,gBAAD;IAAyB;IAAmB;IAAY,CAAA;GACpD;;IAGT,WAAW,cAAc;AAGxB,QACE,UAAU,SAAS,UAAU,QAC7B,UAAU,iBAAiB,UAAU,gBACrC,UAAU,aAAa,UAAU,YACjC,UAAU,kBAAkB,UAAU,iBACtC,UAAU,cAAc,UAAU,aAClC,UAAU,YAAY,UAAU,WAChC,UAAU,aAAa,UAAU;EAGtC;AAED,uBAAuB,cAAc"}
|
|
@@ -61,8 +61,6 @@ var InputBufferDisplay = ({ queuedInputs, isMobile = false }) => {
|
|
|
61
61
|
if (queuedInputs.length === 0) return null;
|
|
62
62
|
const fontSize = isMobile ? 10 : 12;
|
|
63
63
|
const labelFontSize = isMobile ? 8 : 10;
|
|
64
|
-
const top = isMobile ? "10px" : "20px";
|
|
65
|
-
const right = isMobile ? "10px" : "20px";
|
|
66
64
|
return /* @__PURE__ */ jsx(Html, {
|
|
67
65
|
fullscreen: true,
|
|
68
66
|
children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -72,8 +70,8 @@ var InputBufferDisplay = ({ queuedInputs, isMobile = false }) => {
|
|
|
72
70
|
"aria-label": "Input queue",
|
|
73
71
|
style: {
|
|
74
72
|
position: "absolute",
|
|
75
|
-
top,
|
|
76
|
-
right,
|
|
73
|
+
top: isMobile ? "10px" : "20px",
|
|
74
|
+
right: isMobile ? "10px" : "20px",
|
|
77
75
|
background: hexToRgbaString(theme.colors.UI_BACKGROUND_DARK, .8),
|
|
78
76
|
border: `1px solid ${hexToRgbaString(theme.colors.ACCENT_BLUE, .6)}`,
|
|
79
77
|
borderRadius: "4px",
|