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
|
@@ -2,6 +2,7 @@ import { VitalPointSeverity } from "../../../../types/common.js";
|
|
|
2
2
|
import { KOREAN_COLORS } from "../../../../types/constants/colors.js";
|
|
3
3
|
import { FONT_FAMILY } from "../../../../types/constants/typography.js";
|
|
4
4
|
import KOREAN_VITAL_POINTS, { getVitalPointsStats } from "../../../../systems/vitalpoint/KoreanVitalPoints.js";
|
|
5
|
+
import { hexColorToCSS, hexToRgbaString } from "../../../../utils/colorUtils.js";
|
|
5
6
|
import { useCallback, useMemo, useState } from "react";
|
|
6
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { Html } from "@react-three/drei";
|
|
@@ -23,20 +24,17 @@ import { Html } from "@react-three/drei";
|
|
|
23
24
|
/**
|
|
24
25
|
* Convert numeric color to CSS hex string
|
|
25
26
|
*/
|
|
26
|
-
var colorToHex = (color) => {
|
|
27
|
-
return `#${color.toString(16).padStart(6, "0")}`;
|
|
28
|
-
};
|
|
29
27
|
/**
|
|
30
28
|
* Get color for severity level
|
|
31
29
|
*/
|
|
32
30
|
var getSeverityColor = (severity) => {
|
|
33
31
|
switch (severity) {
|
|
34
|
-
case VitalPointSeverity.LETHAL: return
|
|
35
|
-
case VitalPointSeverity.CRITICAL: return
|
|
36
|
-
case VitalPointSeverity.MAJOR: return
|
|
37
|
-
case VitalPointSeverity.MODERATE: return
|
|
38
|
-
case VitalPointSeverity.MINOR: return
|
|
39
|
-
default: return
|
|
32
|
+
case VitalPointSeverity.LETHAL: return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);
|
|
33
|
+
case VitalPointSeverity.CRITICAL: return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);
|
|
34
|
+
case VitalPointSeverity.MAJOR: return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);
|
|
35
|
+
case VitalPointSeverity.MODERATE: return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);
|
|
36
|
+
case VitalPointSeverity.MINOR: return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);
|
|
37
|
+
default: return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);
|
|
40
38
|
}
|
|
41
39
|
};
|
|
42
40
|
/**
|
|
@@ -125,13 +123,13 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
125
123
|
right: finalPosition.right,
|
|
126
124
|
bottom: finalPosition.bottom,
|
|
127
125
|
width: panelWidth,
|
|
128
|
-
background: `${
|
|
129
|
-
border: `2px solid ${
|
|
126
|
+
background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,
|
|
127
|
+
border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,
|
|
130
128
|
borderRadius: "12px",
|
|
131
129
|
padding: isMobile ? "12px" : "16px",
|
|
132
130
|
fontFamily: FONT_FAMILY.KOREAN,
|
|
133
|
-
color:
|
|
134
|
-
boxShadow: `0 0 30px ${
|
|
131
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
132
|
+
boxShadow: `0 0 30px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,
|
|
135
133
|
transition: "all 0.3s ease",
|
|
136
134
|
pointerEvents: "all",
|
|
137
135
|
zIndex: 200
|
|
@@ -145,8 +143,8 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
145
143
|
alignItems: "center",
|
|
146
144
|
marginBottom: "12px",
|
|
147
145
|
paddingBottom: "12px",
|
|
148
|
-
borderBottom: `1px solid ${
|
|
149
|
-
background: `linear-gradient(90deg, ${
|
|
146
|
+
borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,
|
|
147
|
+
background: `linear-gradient(90deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 0%, ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`
|
|
150
148
|
},
|
|
151
149
|
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
152
150
|
style: {
|
|
@@ -157,7 +155,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
157
155
|
}), /* @__PURE__ */ jsxs("div", {
|
|
158
156
|
style: {
|
|
159
157
|
fontSize: smallFontSize,
|
|
160
|
-
color:
|
|
158
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),
|
|
161
159
|
marginTop: "2px"
|
|
162
160
|
},
|
|
163
161
|
children: [
|
|
@@ -169,23 +167,23 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
169
167
|
})] }), /* @__PURE__ */ jsx("button", {
|
|
170
168
|
onClick: () => setExpanded(!expanded),
|
|
171
169
|
style: {
|
|
172
|
-
background: `linear-gradient(135deg, ${
|
|
173
|
-
border: `2px solid ${
|
|
170
|
+
background: `linear-gradient(135deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,
|
|
171
|
+
border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,
|
|
174
172
|
borderRadius: "6px",
|
|
175
173
|
padding: "8px 14px",
|
|
176
|
-
color:
|
|
174
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
177
175
|
fontSize,
|
|
178
176
|
cursor: "pointer",
|
|
179
177
|
transition: "all 0.2s ease",
|
|
180
|
-
boxShadow: `0 2px 8px ${
|
|
178
|
+
boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`
|
|
181
179
|
},
|
|
182
180
|
onMouseEnter: (e) => {
|
|
183
181
|
e.currentTarget.style.transform = "scale(1.05)";
|
|
184
|
-
e.currentTarget.style.boxShadow = `0 4px 12px ${
|
|
182
|
+
e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;
|
|
185
183
|
},
|
|
186
184
|
onMouseLeave: (e) => {
|
|
187
185
|
e.currentTarget.style.transform = "scale(1)";
|
|
188
|
-
e.currentTarget.style.boxShadow = `0 2px 8px ${
|
|
186
|
+
e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;
|
|
189
187
|
},
|
|
190
188
|
"data-testid": "toggle-expand-button",
|
|
191
189
|
children: expanded ? "▼" : "▶"
|
|
@@ -198,25 +196,25 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
198
196
|
style: {
|
|
199
197
|
width: "100%",
|
|
200
198
|
height: buttonHeight,
|
|
201
|
-
background: visible ? `linear-gradient(135deg, ${
|
|
202
|
-
border: `2px solid ${visible ?
|
|
199
|
+
background: visible ? `linear-gradient(135deg, ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)` : `linear-gradient(135deg, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,
|
|
200
|
+
border: `2px solid ${visible ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD) : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,
|
|
203
201
|
borderRadius: "8px",
|
|
204
|
-
color: visible ?
|
|
202
|
+
color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
205
203
|
fontSize: isMobile ? 13 : 15,
|
|
206
204
|
fontWeight: "bold",
|
|
207
205
|
cursor: "pointer",
|
|
208
206
|
transition: "all 0.3s ease",
|
|
209
|
-
boxShadow: visible ? `0 4px 16px ${
|
|
207
|
+
boxShadow: visible ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, .2)}` : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,
|
|
210
208
|
textTransform: "uppercase",
|
|
211
209
|
letterSpacing: "0.5px"
|
|
212
210
|
},
|
|
213
211
|
onMouseEnter: (e) => {
|
|
214
212
|
e.currentTarget.style.transform = "translateY(-2px)";
|
|
215
|
-
e.currentTarget.style.boxShadow = visible ? `0 6px 20px ${
|
|
213
|
+
e.currentTarget.style.boxShadow = visible ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80` : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;
|
|
216
214
|
},
|
|
217
215
|
onMouseLeave: (e) => {
|
|
218
216
|
e.currentTarget.style.transform = "translateY(0)";
|
|
219
|
-
e.currentTarget.style.boxShadow = visible ? `0 4px 16px ${
|
|
217
|
+
e.currentTarget.style.boxShadow = visible ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60` : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;
|
|
220
218
|
},
|
|
221
219
|
"data-testid": "toggle-visibility-button",
|
|
222
220
|
children: visible ? "✓ 활성화 | Enabled" : "비활성화 | Disabled"
|
|
@@ -229,7 +227,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
229
227
|
style: {
|
|
230
228
|
fontSize,
|
|
231
229
|
marginBottom: "8px",
|
|
232
|
-
color:
|
|
230
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),
|
|
233
231
|
fontWeight: "600",
|
|
234
232
|
textTransform: "uppercase",
|
|
235
233
|
letterSpacing: "1px"
|
|
@@ -247,11 +245,11 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
247
245
|
return /* @__PURE__ */ jsx("button", {
|
|
248
246
|
onClick: () => toggleSeverityFilter(severity),
|
|
249
247
|
style: {
|
|
250
|
-
background: isActive ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)` : `${
|
|
248
|
+
background: isActive ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)` : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,
|
|
251
249
|
border: `2px solid ${severityColor}`,
|
|
252
250
|
borderRadius: "6px",
|
|
253
251
|
padding: "6px 12px",
|
|
254
|
-
color:
|
|
252
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
255
253
|
fontSize: smallFontSize,
|
|
256
254
|
cursor: "pointer",
|
|
257
255
|
opacity: isActive ? 1 : .6,
|
|
@@ -279,7 +277,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
279
277
|
style: {
|
|
280
278
|
fontSize,
|
|
281
279
|
marginBottom: "8px",
|
|
282
|
-
color:
|
|
280
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),
|
|
283
281
|
fontWeight: "600",
|
|
284
282
|
textTransform: "uppercase",
|
|
285
283
|
letterSpacing: "1px"
|
|
@@ -296,17 +294,17 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
296
294
|
return /* @__PURE__ */ jsxs("button", {
|
|
297
295
|
onClick: () => onRegionFilterChange(option.value),
|
|
298
296
|
style: {
|
|
299
|
-
background: isActive ? `linear-gradient(135deg, ${
|
|
300
|
-
border: `2px solid ${
|
|
297
|
+
background: isActive ? `linear-gradient(135deg, ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)` : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,
|
|
298
|
+
border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,
|
|
301
299
|
borderRadius: "6px",
|
|
302
300
|
padding: "6px 12px",
|
|
303
|
-
color:
|
|
301
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
304
302
|
fontSize: smallFontSize,
|
|
305
303
|
cursor: "pointer",
|
|
306
304
|
opacity: isActive ? 1 : .6,
|
|
307
305
|
transition: "all 0.2s ease",
|
|
308
306
|
fontWeight: isActive ? "bold" : "normal",
|
|
309
|
-
boxShadow: isActive ? `0 2px 8px ${
|
|
307
|
+
boxShadow: isActive ? `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50` : "none"
|
|
310
308
|
},
|
|
311
309
|
onMouseEnter: (e) => {
|
|
312
310
|
e.currentTarget.style.opacity = "1";
|
|
@@ -332,7 +330,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
332
330
|
style: {
|
|
333
331
|
fontSize,
|
|
334
332
|
marginBottom: "8px",
|
|
335
|
-
color:
|
|
333
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),
|
|
336
334
|
fontWeight: "600",
|
|
337
335
|
textTransform: "uppercase",
|
|
338
336
|
letterSpacing: "1px"
|
|
@@ -348,22 +346,22 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
348
346
|
style: {
|
|
349
347
|
width: "100%",
|
|
350
348
|
height: buttonHeight,
|
|
351
|
-
background: `${
|
|
352
|
-
border: `2px solid ${
|
|
349
|
+
background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,
|
|
350
|
+
border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,
|
|
353
351
|
borderRadius: "8px",
|
|
354
352
|
padding: "0 40px 0 14px",
|
|
355
|
-
color:
|
|
353
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),
|
|
356
354
|
fontSize,
|
|
357
355
|
fontFamily: FONT_FAMILY.KOREAN,
|
|
358
356
|
transition: "all 0.2s ease",
|
|
359
357
|
outline: "none"
|
|
360
358
|
},
|
|
361
359
|
onFocus: (e) => {
|
|
362
|
-
e.currentTarget.style.borderColor =
|
|
363
|
-
e.currentTarget.style.boxShadow = `0 0 12px ${
|
|
360
|
+
e.currentTarget.style.borderColor = hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);
|
|
361
|
+
e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`;
|
|
364
362
|
},
|
|
365
363
|
onBlur: (e) => {
|
|
366
|
-
e.currentTarget.style.borderColor = `${
|
|
364
|
+
e.currentTarget.style.borderColor = `${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`;
|
|
367
365
|
e.currentTarget.style.boxShadow = "none";
|
|
368
366
|
},
|
|
369
367
|
"data-testid": "search-input"
|
|
@@ -376,7 +374,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
376
374
|
transform: "translateY(-50%)",
|
|
377
375
|
background: "transparent",
|
|
378
376
|
border: "none",
|
|
379
|
-
color:
|
|
377
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),
|
|
380
378
|
cursor: "pointer",
|
|
381
379
|
fontSize: "16px",
|
|
382
380
|
padding: "4px",
|
|
@@ -387,11 +385,11 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
387
385
|
transition: "all 0.2s ease"
|
|
388
386
|
},
|
|
389
387
|
onMouseEnter: (e) => {
|
|
390
|
-
e.currentTarget.style.color =
|
|
391
|
-
e.currentTarget.style.background = `${
|
|
388
|
+
e.currentTarget.style.color = hexColorToCSS(KOREAN_COLORS.ACCENT_RED);
|
|
389
|
+
e.currentTarget.style.background = `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`;
|
|
392
390
|
},
|
|
393
391
|
onMouseLeave: (e) => {
|
|
394
|
-
e.currentTarget.style.color =
|
|
392
|
+
e.currentTarget.style.color = hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY);
|
|
395
393
|
e.currentTarget.style.background = "transparent";
|
|
396
394
|
},
|
|
397
395
|
"data-testid": "search-clear-button",
|
|
@@ -406,7 +404,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
406
404
|
style: {
|
|
407
405
|
fontSize,
|
|
408
406
|
marginBottom: "8px",
|
|
409
|
-
color:
|
|
407
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),
|
|
410
408
|
fontWeight: "600",
|
|
411
409
|
textTransform: "uppercase",
|
|
412
410
|
letterSpacing: "1px"
|
|
@@ -432,7 +430,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
432
430
|
style: {
|
|
433
431
|
width: "16px",
|
|
434
432
|
height: "16px",
|
|
435
|
-
accentColor:
|
|
433
|
+
accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)
|
|
436
434
|
},
|
|
437
435
|
"data-testid": "show-labels-checkbox"
|
|
438
436
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -453,7 +451,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
453
451
|
style: {
|
|
454
452
|
width: "16px",
|
|
455
453
|
height: "16px",
|
|
456
|
-
accentColor:
|
|
454
|
+
accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)
|
|
457
455
|
},
|
|
458
456
|
"data-testid": "animated-checkbox"
|
|
459
457
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -474,21 +472,21 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
474
472
|
style: {
|
|
475
473
|
width: "100%",
|
|
476
474
|
height: buttonHeight - 4,
|
|
477
|
-
background: `${
|
|
478
|
-
border: `2px solid ${
|
|
475
|
+
background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,
|
|
476
|
+
border: `2px solid ${hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE)}`,
|
|
479
477
|
borderRadius: "6px",
|
|
480
|
-
color:
|
|
478
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),
|
|
481
479
|
fontSize: smallFontSize,
|
|
482
480
|
cursor: "pointer",
|
|
483
481
|
transition: "all 0.2s ease",
|
|
484
482
|
fontWeight: "bold"
|
|
485
483
|
},
|
|
486
484
|
onMouseEnter: (e) => {
|
|
487
|
-
e.currentTarget.style.background = `${
|
|
485
|
+
e.currentTarget.style.background = `${hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE)}20`;
|
|
488
486
|
e.currentTarget.style.transform = "translateY(-1px)";
|
|
489
487
|
},
|
|
490
488
|
onMouseLeave: (e) => {
|
|
491
|
-
e.currentTarget.style.background = `${
|
|
489
|
+
e.currentTarget.style.background = `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`;
|
|
492
490
|
e.currentTarget.style.transform = "translateY(0)";
|
|
493
491
|
},
|
|
494
492
|
"data-testid": "reset-filters-button",
|
|
@@ -499,7 +497,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
499
497
|
style: {
|
|
500
498
|
fontSize,
|
|
501
499
|
marginBottom: "6px",
|
|
502
|
-
color:
|
|
500
|
+
color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN)
|
|
503
501
|
},
|
|
504
502
|
children: [
|
|
505
503
|
"크기 | Scale: ",
|
|
@@ -515,7 +513,7 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
515
513
|
onChange: (e) => onScaleChange(parseFloat(e.target.value)),
|
|
516
514
|
style: {
|
|
517
515
|
width: "100%",
|
|
518
|
-
accentColor:
|
|
516
|
+
accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)
|
|
519
517
|
},
|
|
520
518
|
"data-testid": "scale-slider"
|
|
521
519
|
})] }),
|
|
@@ -523,9 +521,9 @@ var VitalPointOverlayControlsHtml = ({ visible, onVisibleChange, severityFilters
|
|
|
523
521
|
style: {
|
|
524
522
|
marginTop: "12px",
|
|
525
523
|
paddingTop: "12px",
|
|
526
|
-
borderTop: `1px solid ${
|
|
524
|
+
borderTop: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}44`,
|
|
527
525
|
fontSize: smallFontSize,
|
|
528
|
-
color:
|
|
526
|
+
color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY)
|
|
529
527
|
},
|
|
530
528
|
children: [/* @__PURE__ */ jsxs("div", { children: [
|
|
531
529
|
"머리: ",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\nconst colorToHex = (color: number): string => {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n};\n\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return \"#ff0000\"; // Red\n case VitalPointSeverity.CRITICAL:\n return \"#ff6600\"; // Orange\n case VitalPointSeverity.MAJOR:\n return \"#ffaa00\"; // Gold\n case VitalPointSeverity.MODERATE:\n return \"#ffd700\"; // Yellow\n case VitalPointSeverity.MINOR:\n return \"#00ff88\"; // Green\n default:\n return \"#00ffff\"; // Cyan\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${colorToHex(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${colorToHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: \"#ffffff\",\n boxShadow: `0 0 30px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${colorToHex(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: colorToHex(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${colorToHex(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${colorToHex(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: \"#ffffff\",\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${colorToHex(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${colorToHex(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${colorToHex(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? colorToHex(KOREAN_COLORS.ACCENT_GOLD)\n : colorToHex(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? \"#1a1a1a\" : \"#ffffff\",\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${colorToHex(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px rgba(255,255,255,0.2)`\n : `0 2px 8px ${colorToHex(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${colorToHex(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${colorToHex(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${colorToHex(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${colorToHex(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${colorToHex(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: \"#ffffff\",\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${colorToHex(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${colorToHex(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: \"#ffffff\",\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: \"#ffffff\",\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: colorToHex(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = colorToHex(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = colorToHex(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: colorToHex(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: colorToHex(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${colorToHex(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${colorToHex(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${colorToHex(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: colorToHex(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: colorToHex(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${colorToHex(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: colorToHex(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n // Use internal state if no external control provided\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n // Get system statistics\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n // Default screen position - left side, below player 1 status (accounting for stance indicator)\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n // Get filtered count\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n // Filter by severity\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n // Filter by region\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n // Match both left and right arm vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n // Match both left and right leg vital points\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n // Simple prefix match for head_ or torso_\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n // Filter by search query\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n // Toggle severity filter\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n // Severity options\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n // Region options\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n // Panel styles\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":"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"}
|
|
@@ -10,6 +10,46 @@
|
|
|
10
10
|
* - Pointer events handling
|
|
11
11
|
* - Backdrop blur effects
|
|
12
12
|
*
|
|
13
|
+
* ## Z-Index Stacking Order (Combat HUD Layers)
|
|
14
|
+
*
|
|
15
|
+
* The combat screen renders multiple overlapping HUD panels. The stacking
|
|
16
|
+
* order is managed by the Z_INDEX constants from LayoutTypes.ts:
|
|
17
|
+
*
|
|
18
|
+
* | Layer | Z-Index | Description |
|
|
19
|
+
* |---------------------|---------|------------------------------------|
|
|
20
|
+
* | BACKGROUND | 0 | 3D scene background |
|
|
21
|
+
* | ARENA | 10 | Combat arena mesh |
|
|
22
|
+
* | PLAYERS | 20 | Character models |
|
|
23
|
+
* | EFFECTS | 30 | Particles and VFX |
|
|
24
|
+
* | HUD_BACKGROUND | 40 | HUD panel backgrounds |
|
|
25
|
+
* | HUD (default) | 50 | Left/Right/Top/Bottom HUD panels |
|
|
26
|
+
* | TECHNIQUE_BAR | 55 | Technique bar overlay |
|
|
27
|
+
* | HUD_OVERLAY | 60 | PlayerStateOverlay and sub-HUDs |
|
|
28
|
+
* | MOBILE_CONTROLS | 100 | Touch controls on mobile |
|
|
29
|
+
* | MODAL | 200 | Modal dialogs |
|
|
30
|
+
* | TOOLTIP | 300 | Tooltips and hints |
|
|
31
|
+
* | PAUSE_MENU | 1000 | Pause menu overlay |
|
|
32
|
+
* | LOADING | 2000 | Loading screens |
|
|
33
|
+
* | DEBUG | 9000 | Performance debug overlay |
|
|
34
|
+
*
|
|
35
|
+
* All BaseHUDContainer instances default to Z_INDEX.HUD (50). Parent
|
|
36
|
+
* screens can override via the `zIndex` prop when a different layer
|
|
37
|
+
* is needed (e.g., overlays at HUD_OVERLAY = 60).
|
|
38
|
+
*
|
|
39
|
+
* ## Viewport Breakpoints (Expected HUD Sizes)
|
|
40
|
+
*
|
|
41
|
+
* | Viewport | Width | Left/Right HUD | Top HUD | Bottom HUD |
|
|
42
|
+
* |---------------------|----------|----------------|---------|------------|
|
|
43
|
+
* | Small Phone (≤375) | ≤375px | ~120-150px | ~50px | ~90px |
|
|
44
|
+
* | Mobile (<768) | <768px | ~180-200px | ~60px | ~110px |
|
|
45
|
+
* | Tablet (768-1199) | 768-1199 | ~220-260px | ~65px | ~120px |
|
|
46
|
+
* | Desktop (≥1200) | ≥1200px | ~260-300px | ~70px | ~130px |
|
|
47
|
+
* | 4K (≥1920) | ≥1920px | ~300-400px | ~80px | ~140px |
|
|
48
|
+
*
|
|
49
|
+
* Width/height values are passed by the parent screen and scaled via
|
|
50
|
+
* positionScale multipliers. This table documents the expected ranges
|
|
51
|
+
* produced by CombatScreen3D and TrainingScreen3D layout calculations.
|
|
52
|
+
*
|
|
13
53
|
* @module components/shared/ui
|
|
14
54
|
* @korean 기본HUD컨테이너 - 공통 패턴을 가진 재사용 가능한 HUD 컨테이너
|
|
15
55
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseHUDContainer.d.ts","sourceRoot":"","sources":["../../../../src/components/shared/ui/BaseHUDContainer.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"BaseHUDContainer.d.ts","sourceRoot":"","sources":["../../../../src/components/shared/ui/BaseHUDContainer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uBAAuB;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACrC,qBAAqB;IACrB,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,0BAA0B;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAyG5D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|