blacktrigram 0.7.11 → 0.7.12
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/lib/assets/index.css +94 -94
- package/lib/components/screens/intro/IntroScreen3D.js +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/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 +5 -5
package/lib/assets/index.css
CHANGED
|
@@ -13,10 +13,10 @@ body {
|
|
|
13
13
|
font-family: var(--font-korean);
|
|
14
14
|
background: linear-gradient(
|
|
15
15
|
135deg,
|
|
16
|
-
var(--
|
|
17
|
-
var(--
|
|
16
|
+
var(--color-bg-dark) 0%,
|
|
17
|
+
var(--color-bg-medium) 100%
|
|
18
18
|
);
|
|
19
|
-
color: var(--
|
|
19
|
+
color: var(--color-text-primary);
|
|
20
20
|
overflow: hidden;
|
|
21
21
|
user-select: none;
|
|
22
22
|
}
|
|
@@ -28,15 +28,15 @@ body {
|
|
|
28
28
|
align-items: center;
|
|
29
29
|
background: radial-gradient(
|
|
30
30
|
ellipse at center,
|
|
31
|
-
var(--
|
|
32
|
-
var(--
|
|
31
|
+
var(--color-bg-medium) 0%,
|
|
32
|
+
var(--color-bg-dark) 100%
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
/* Korean Text Enhancement */
|
|
36
36
|
.korean-text {
|
|
37
37
|
font-family: var(--font-korean);
|
|
38
38
|
font-weight: 400;
|
|
39
|
-
text-shadow: 0 0 8px var(--korean-
|
|
39
|
+
text-shadow: 0 0 8px var(--color-korean-east);
|
|
40
40
|
letter-spacing: 0.02em;
|
|
41
41
|
}
|
|
42
42
|
.korean-title {
|
|
@@ -45,42 +45,42 @@ body {
|
|
|
45
45
|
font-size: 3rem;
|
|
46
46
|
background: linear-gradient(
|
|
47
47
|
45deg,
|
|
48
|
-
var(--
|
|
49
|
-
var(--
|
|
48
|
+
var(--color-accent-cyan),
|
|
49
|
+
var(--color-warning)
|
|
50
50
|
);
|
|
51
51
|
-webkit-background-clip: text;
|
|
52
52
|
-webkit-text-fill-color: transparent;
|
|
53
53
|
background-clip: text;
|
|
54
|
-
text-shadow: 0 0 20px var(--
|
|
54
|
+
text-shadow: 0 0 20px var(--color-accent-cyan);
|
|
55
55
|
}
|
|
56
56
|
.cyber-text {
|
|
57
57
|
font-family: var(--font-cyber);
|
|
58
|
-
color: var(--
|
|
58
|
+
color: var(--color-accent-cyan);
|
|
59
59
|
text-transform: uppercase;
|
|
60
60
|
letter-spacing: 0.1em;
|
|
61
61
|
text-shadow: 0 0 10px currentColor;
|
|
62
62
|
}
|
|
63
63
|
/* Cyberpunk UI Elements */
|
|
64
64
|
.cyberpunk-border {
|
|
65
|
-
border: 2px solid var(--
|
|
65
|
+
border: 2px solid var(--color-accent-cyan);
|
|
66
66
|
border-image: linear-gradient(
|
|
67
67
|
45deg,
|
|
68
|
-
var(--
|
|
69
|
-
var(--
|
|
70
|
-
var(--
|
|
68
|
+
var(--color-accent-cyan),
|
|
69
|
+
var(--color-warning),
|
|
70
|
+
var(--color-accent-cyan)
|
|
71
71
|
)
|
|
72
72
|
1;
|
|
73
|
-
box-shadow: 0 0 10px var(--
|
|
74
|
-
inset 0 0 10px
|
|
73
|
+
box-shadow: 0 0 10px var(--color-accent-cyan),
|
|
74
|
+
inset 0 0 10px color-mix(in srgb, var(--color-accent-cyan) 10%, transparent);
|
|
75
75
|
}
|
|
76
76
|
.cyberpunk-button {
|
|
77
77
|
background: linear-gradient(
|
|
78
78
|
135deg,
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
color-mix(in srgb, var(--color-accent-cyan) 10%, transparent),
|
|
80
|
+
color-mix(in srgb, var(--color-warning) 10%, transparent)
|
|
81
81
|
);
|
|
82
|
-
border: 1px solid var(--
|
|
83
|
-
color: var(--
|
|
82
|
+
border: 1px solid var(--color-accent-cyan);
|
|
83
|
+
color: var(--color-text-primary);
|
|
84
84
|
padding: 0.8rem 1.5rem;
|
|
85
85
|
cursor: pointer;
|
|
86
86
|
transition: all 0.3s ease;
|
|
@@ -92,10 +92,10 @@ body {
|
|
|
92
92
|
.cyberpunk-button:hover {
|
|
93
93
|
background: linear-gradient(
|
|
94
94
|
135deg,
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
color-mix(in srgb, var(--color-accent-cyan) 20%, transparent),
|
|
96
|
+
color-mix(in srgb, var(--color-warning) 20%, transparent)
|
|
97
97
|
);
|
|
98
|
-
box-shadow: 0 0 20px var(--
|
|
98
|
+
box-shadow: 0 0 20px var(--color-accent-cyan);
|
|
99
99
|
transform: translateY(-2px);
|
|
100
100
|
}
|
|
101
101
|
.cyberpunk-button:before {
|
|
@@ -108,7 +108,7 @@ body {
|
|
|
108
108
|
background: linear-gradient(
|
|
109
109
|
90deg,
|
|
110
110
|
transparent,
|
|
111
|
-
|
|
111
|
+
color-mix(in srgb, var(--color-text-primary) 20%, transparent),
|
|
112
112
|
transparent
|
|
113
113
|
);
|
|
114
114
|
transition: left 0.5s;
|
|
@@ -124,15 +124,15 @@ body {
|
|
|
124
124
|
right: 0;
|
|
125
125
|
background: linear-gradient(
|
|
126
126
|
180deg,
|
|
127
|
-
|
|
127
|
+
color-mix(in srgb, var(--color-bg-dark) 90%, transparent) 0%,
|
|
128
128
|
transparent 100%
|
|
129
129
|
);
|
|
130
130
|
padding: 1rem;
|
|
131
|
-
border-bottom: 1px solid var(--
|
|
131
|
+
border-bottom: 1px solid var(--color-accent-cyan);
|
|
132
132
|
}
|
|
133
133
|
.health-bar {
|
|
134
|
-
background: var(--
|
|
135
|
-
border: 1px solid var(--
|
|
134
|
+
background: var(--color-bg-dark);
|
|
135
|
+
border: 1px solid var(--color-accent-cyan);
|
|
136
136
|
height: 8px;
|
|
137
137
|
border-radius: 4px;
|
|
138
138
|
overflow: hidden;
|
|
@@ -141,9 +141,9 @@ body {
|
|
|
141
141
|
.health-bar-fill {
|
|
142
142
|
background: linear-gradient(
|
|
143
143
|
90deg,
|
|
144
|
-
var(--
|
|
145
|
-
var(--
|
|
146
|
-
var(--korean-
|
|
144
|
+
var(--color-danger) 0%,
|
|
145
|
+
var(--color-warning) 50%,
|
|
146
|
+
var(--color-korean-east) 100%
|
|
147
147
|
);
|
|
148
148
|
height: 100%;
|
|
149
149
|
transition: width 0.3s ease;
|
|
@@ -153,10 +153,10 @@ body {
|
|
|
153
153
|
@keyframes korean-glow {
|
|
154
154
|
0%,
|
|
155
155
|
100% {
|
|
156
|
-
text-shadow: 0 0 5px var(--korean-
|
|
156
|
+
text-shadow: 0 0 5px var(--color-korean-east);
|
|
157
157
|
}
|
|
158
158
|
50% {
|
|
159
|
-
text-shadow: 0 0 20px var(--korean-
|
|
159
|
+
text-shadow: 0 0 20px var(--color-korean-east);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
.animate-glow {
|
|
@@ -164,13 +164,13 @@ body {
|
|
|
164
164
|
}
|
|
165
165
|
@keyframes combat-flash {
|
|
166
166
|
0% {
|
|
167
|
-
background-color:
|
|
167
|
+
background-color: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
168
168
|
}
|
|
169
169
|
50% {
|
|
170
|
-
background-color:
|
|
170
|
+
background-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
|
171
171
|
}
|
|
172
172
|
100% {
|
|
173
|
-
background-color:
|
|
173
|
+
background-color: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
.combat-flash {
|
|
@@ -232,8 +232,8 @@ body {
|
|
|
232
232
|
}
|
|
233
233
|
/* Loading and Error States */
|
|
234
234
|
.loading-spinner {
|
|
235
|
-
border: 2px solid var(--
|
|
236
|
-
border-top: 2px solid var(--
|
|
235
|
+
border: 2px solid var(--color-bg-medium);
|
|
236
|
+
border-top: 2px solid var(--color-accent-cyan);
|
|
237
237
|
border-radius: 50%;
|
|
238
238
|
width: 40px;
|
|
239
239
|
height: 40px;
|
|
@@ -249,9 +249,9 @@ body {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
.error-message {
|
|
252
|
-
color: var(--
|
|
253
|
-
background:
|
|
254
|
-
border: 1px solid var(--
|
|
252
|
+
color: var(--color-danger);
|
|
253
|
+
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
254
|
+
border: 1px solid var(--color-danger);
|
|
255
255
|
padding: 1rem;
|
|
256
256
|
border-radius: 4px;
|
|
257
257
|
margin: 1rem 0;
|
|
@@ -368,8 +368,8 @@ body {
|
|
|
368
368
|
bottom: 0;
|
|
369
369
|
background: radial-gradient(
|
|
370
370
|
circle at center,
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
color-mix(in srgb, var(--color-korean-south) 10%, transparent) 0%,
|
|
372
|
+
var(--color-primary-black) 70%
|
|
373
373
|
);
|
|
374
374
|
pointer-events: none;
|
|
375
375
|
z-index: -1;
|
|
@@ -444,12 +444,12 @@ body {
|
|
|
444
444
|
}
|
|
445
445
|
.app-header {
|
|
446
446
|
padding: 1rem 2rem;
|
|
447
|
-
background:
|
|
448
|
-
border-bottom: 2px solid
|
|
447
|
+
background: color-mix(in srgb, var(--color-bg-dark) 90%, transparent);
|
|
448
|
+
border-bottom: 2px solid var(--color-primary-gold);
|
|
449
449
|
display: flex;
|
|
450
450
|
justify-content: space-between;
|
|
451
451
|
align-items: center;
|
|
452
|
-
box-shadow: 0 2px 10px
|
|
452
|
+
box-shadow: 0 2px 10px color-mix(in srgb, var(--color-primary-gold) 30%, transparent);
|
|
453
453
|
}
|
|
454
454
|
.app-title {
|
|
455
455
|
margin: 0;
|
|
@@ -460,14 +460,14 @@ body {
|
|
|
460
460
|
.korean-title {
|
|
461
461
|
font-size: 1.8rem;
|
|
462
462
|
font-weight: bold;
|
|
463
|
-
color:
|
|
464
|
-
text-shadow: 2px 2px 4px
|
|
463
|
+
color: var(--color-primary-gold);
|
|
464
|
+
text-shadow: 2px 2px 4px color-mix(in srgb, var(--color-primary-black) 80%, transparent);
|
|
465
465
|
}
|
|
466
466
|
.english-title {
|
|
467
467
|
font-size: 1rem;
|
|
468
|
-
color:
|
|
468
|
+
color: var(--color-info);
|
|
469
469
|
font-style: italic;
|
|
470
|
-
text-shadow: 1px 1px 2px
|
|
470
|
+
text-shadow: 1px 1px 2px color-mix(in srgb, var(--color-primary-black) 60%, transparent);
|
|
471
471
|
}
|
|
472
472
|
.app-status {
|
|
473
473
|
display: flex;
|
|
@@ -483,14 +483,14 @@ body {
|
|
|
483
483
|
font-weight: bold;
|
|
484
484
|
}
|
|
485
485
|
.phase-indicator {
|
|
486
|
-
background:
|
|
487
|
-
color:
|
|
488
|
-
border: 1px solid
|
|
486
|
+
background: color-mix(in srgb, var(--color-primary-gold) 20%, transparent);
|
|
487
|
+
color: var(--color-primary-gold);
|
|
488
|
+
border: 1px solid var(--color-primary-gold);
|
|
489
489
|
}
|
|
490
490
|
.time-indicator {
|
|
491
|
-
background:
|
|
492
|
-
color:
|
|
493
|
-
border: 1px solid
|
|
491
|
+
background: color-mix(in srgb, var(--color-info) 20%, transparent);
|
|
492
|
+
color: var(--color-info);
|
|
493
|
+
border: 1px solid var(--color-info);
|
|
494
494
|
}
|
|
495
495
|
.app-main {
|
|
496
496
|
flex: 1;
|
|
@@ -500,7 +500,7 @@ body {
|
|
|
500
500
|
overflow: hidden;
|
|
501
501
|
}
|
|
502
502
|
.app-debug {
|
|
503
|
-
background:
|
|
503
|
+
background: color-mix(in srgb, var(--color-primary-black) 80%, transparent);
|
|
504
504
|
color: #00ff00;
|
|
505
505
|
font-family: "Courier New", monospace;
|
|
506
506
|
font-size: 0.8rem;
|
|
@@ -510,7 +510,7 @@ body {
|
|
|
510
510
|
.app-debug summary {
|
|
511
511
|
padding: 0.5rem;
|
|
512
512
|
cursor: pointer;
|
|
513
|
-
background:
|
|
513
|
+
background: color-mix(in srgb, var(--color-success) 10%, transparent);
|
|
514
514
|
border-bottom: 1px solid #00ff00;
|
|
515
515
|
}
|
|
516
516
|
.app-debug pre {
|
|
@@ -550,12 +550,12 @@ body {
|
|
|
550
550
|
bottom: 0;
|
|
551
551
|
background-image: radial-gradient(
|
|
552
552
|
circle at 20% 20%,
|
|
553
|
-
|
|
553
|
+
color-mix(in srgb, var(--color-primary-gold) 5%, transparent) 0%,
|
|
554
554
|
transparent 50%
|
|
555
555
|
),
|
|
556
556
|
radial-gradient(
|
|
557
557
|
circle at 80% 80%,
|
|
558
|
-
|
|
558
|
+
color-mix(in srgb, var(--color-info) 5%, transparent) 0%,
|
|
559
559
|
transparent 50%
|
|
560
560
|
);
|
|
561
561
|
pointer-events: none;
|
|
@@ -580,7 +580,7 @@ body {
|
|
|
580
580
|
font-weight: 700;
|
|
581
581
|
color: #ffd700;
|
|
582
582
|
margin-bottom: 1rem;
|
|
583
|
-
text-shadow: 0 0 20px
|
|
583
|
+
text-shadow: 0 0 20px color-mix(in srgb, var(--color-primary-gold) 50%, transparent);
|
|
584
584
|
}
|
|
585
585
|
.english-subtitle {
|
|
586
586
|
font-size: 1.2rem;
|
|
@@ -644,7 +644,7 @@ body {
|
|
|
644
644
|
left: 50%;
|
|
645
645
|
transform: translate(-50%, -50%);
|
|
646
646
|
text-align: center;
|
|
647
|
-
color: var(--primary-cyan);
|
|
647
|
+
color: var(--color-primary-cyan);
|
|
648
648
|
}
|
|
649
649
|
.error-screen {
|
|
650
650
|
position: absolute;
|
|
@@ -655,7 +655,7 @@ body {
|
|
|
655
655
|
color: #ff4136;
|
|
656
656
|
padding: 2rem;
|
|
657
657
|
border: 1px solid #ff4136;
|
|
658
|
-
background:
|
|
658
|
+
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
659
659
|
}
|
|
660
660
|
/* Fix UI overlay positioning to not interfere with canvas visibility */
|
|
661
661
|
.test-overlay {
|
|
@@ -823,14 +823,14 @@ body {
|
|
|
823
823
|
/* Timer Flash - Final seconds warning */
|
|
824
824
|
@keyframes timerFlash {
|
|
825
825
|
0%, 100% {
|
|
826
|
-
color:
|
|
827
|
-
text-shadow: 0 0 10px
|
|
828
|
-
0 0 20px
|
|
826
|
+
color: var(--color-korean-south);
|
|
827
|
+
text-shadow: 0 0 10px color-mix(in srgb, var(--color-korean-south) 80%, transparent),
|
|
828
|
+
0 0 20px color-mix(in srgb, var(--color-korean-south) 50%, transparent);
|
|
829
829
|
}
|
|
830
830
|
50% {
|
|
831
|
-
color:
|
|
832
|
-
text-shadow: 0 0 15px
|
|
833
|
-
0 0 30px
|
|
831
|
+
color: var(--color-korean-center);
|
|
832
|
+
text-shadow: 0 0 15px var(--color-korean-center),
|
|
833
|
+
0 0 30px color-mix(in srgb, var(--color-korean-center) 70%, transparent);
|
|
834
834
|
}
|
|
835
835
|
}
|
|
836
836
|
|
|
@@ -1006,9 +1006,9 @@ body {
|
|
|
1006
1006
|
.training-button {
|
|
1007
1007
|
width: 100%;
|
|
1008
1008
|
height: 40px;
|
|
1009
|
-
border: 2px solid
|
|
1009
|
+
border: 2px solid color-mix(in srgb, var(--color-text-primary) 80%, transparent);
|
|
1010
1010
|
border-radius: 8px;
|
|
1011
|
-
color:
|
|
1011
|
+
color: var(--color-text-primary);
|
|
1012
1012
|
font-weight: bold;
|
|
1013
1013
|
cursor: pointer;
|
|
1014
1014
|
display: flex;
|
|
@@ -1019,11 +1019,11 @@ body {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
1021
|
.training-button-start {
|
|
1022
|
-
background:
|
|
1022
|
+
background: var(--color-success);
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
.training-button-stop {
|
|
1026
|
-
background:
|
|
1026
|
+
background: var(--color-korean-south);
|
|
1027
1027
|
}
|
|
1028
1028
|
|
|
1029
1029
|
.training-button:hover {
|
|
@@ -1033,52 +1033,52 @@ body {
|
|
|
1033
1033
|
|
|
1034
1034
|
/* Training Mode Selector */
|
|
1035
1035
|
.mode-button {
|
|
1036
|
-
background:
|
|
1037
|
-
border: 2px solid
|
|
1036
|
+
background: color-mix(in srgb, var(--color-bg-light) 50%, transparent);
|
|
1037
|
+
border: 2px solid color-mix(in srgb, var(--color-primary-cyan) 40%, transparent);
|
|
1038
1038
|
border-radius: 8px;
|
|
1039
1039
|
padding: 10px;
|
|
1040
1040
|
cursor: pointer;
|
|
1041
1041
|
text-align: left;
|
|
1042
|
-
color:
|
|
1042
|
+
color: var(--color-text-primary);
|
|
1043
1043
|
transition: all 0.2s ease;
|
|
1044
1044
|
}
|
|
1045
1045
|
|
|
1046
1046
|
.mode-button.selected {
|
|
1047
|
-
background:
|
|
1048
|
-
border-color:
|
|
1049
|
-
box-shadow: 0 0 12px
|
|
1047
|
+
background: color-mix(in srgb, var(--color-primary-cyan) 25%, transparent);
|
|
1048
|
+
border-color: var(--color-primary-cyan);
|
|
1049
|
+
box-shadow: 0 0 12px color-mix(in srgb, var(--color-primary-cyan) 50%, transparent);
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
1052
|
.mode-button:not(.selected):hover {
|
|
1053
|
-
background:
|
|
1054
|
-
border-color:
|
|
1053
|
+
background: color-mix(in srgb, var(--color-gray-300) 70%, transparent);
|
|
1054
|
+
border-color: var(--color-primary-cyan);
|
|
1055
1055
|
transform: scale(1.02);
|
|
1056
1056
|
}
|
|
1057
1057
|
|
|
1058
1058
|
.mode-button:focus-visible {
|
|
1059
|
-
outline: 2px solid
|
|
1059
|
+
outline: 2px solid var(--color-primary-cyan);
|
|
1060
1060
|
outline-offset: 2px;
|
|
1061
|
-
box-shadow: 0 0 12px
|
|
1061
|
+
box-shadow: 0 0 12px color-mix(in srgb, var(--color-primary-cyan) 80%, transparent);
|
|
1062
1062
|
}
|
|
1063
1063
|
|
|
1064
1064
|
/* Vital Point Button */
|
|
1065
1065
|
.vital-point-button {
|
|
1066
|
-
background:
|
|
1067
|
-
border: 2px solid
|
|
1066
|
+
background: color-mix(in srgb, var(--color-bg-light) 50%, transparent);
|
|
1067
|
+
border: 2px solid color-mix(in srgb, var(--color-korean-center) 50%, transparent);
|
|
1068
1068
|
border-radius: 8px;
|
|
1069
1069
|
padding: 8px;
|
|
1070
1070
|
cursor: pointer;
|
|
1071
1071
|
text-align: left;
|
|
1072
|
-
color:
|
|
1072
|
+
color: var(--color-text-primary);
|
|
1073
1073
|
transition: all 0.2s ease;
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
1076
1076
|
.vital-point-button.selected {
|
|
1077
|
-
background:
|
|
1077
|
+
background: color-mix(in srgb, var(--color-korean-center) 30%, transparent);
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
1080
|
.vital-point-button:not(.selected):hover {
|
|
1081
|
-
background:
|
|
1081
|
+
background: color-mix(in srgb, var(--color-gray-300) 70%, transparent);
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
1084
|
/* Training Feedback Animation - Fast and snappy */
|
|
@@ -1097,12 +1097,12 @@ body {
|
|
|
1097
1097
|
}
|
|
1098
1098
|
|
|
1099
1099
|
.training-feedback {
|
|
1100
|
-
background:
|
|
1101
|
-
border: 3px solid
|
|
1100
|
+
background: color-mix(in srgb, var(--color-primary-black) 90%, transparent);
|
|
1101
|
+
border: 3px solid var(--color-primary-gold);
|
|
1102
1102
|
border-radius: 16px;
|
|
1103
|
-
color:
|
|
1103
|
+
color: var(--color-primary-gold);
|
|
1104
1104
|
text-align: center;
|
|
1105
|
-
box-shadow: 0 0 30px
|
|
1105
|
+
box-shadow: 0 0 30px color-mix(in srgb, var(--color-primary-gold) 50%, transparent);
|
|
1106
1106
|
animation: feedbackPulse 0.4s ease-out;
|
|
1107
1107
|
min-width: 200px;
|
|
1108
1108
|
}
|
|
@@ -1133,7 +1133,7 @@ body {
|
|
|
1133
1133
|
width: 12px;
|
|
1134
1134
|
height: 12px;
|
|
1135
1135
|
border-radius: 50%;
|
|
1136
|
-
background:
|
|
1136
|
+
background: var(--color-success);
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
1139
|
.status-indicator.active {
|
|
@@ -1141,6 +1141,6 @@ body {
|
|
|
1141
1141
|
}
|
|
1142
1142
|
|
|
1143
1143
|
.status-indicator.inactive {
|
|
1144
|
-
background:
|
|
1144
|
+
background: var(--color-gray-500);
|
|
1145
1145
|
}
|
|
1146
1146
|
/*$vite$:1*/
|
|
@@ -21,7 +21,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
21
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
22
22
|
import { Canvas } from "@react-three/fiber";
|
|
23
23
|
//#region src/components/screens/intro/IntroScreen3D.tsx
|
|
24
|
-
var APP_VERSION = "0.7.
|
|
24
|
+
var APP_VERSION = "0.7.12";
|
|
25
25
|
var MENU_ITEMS = [
|
|
26
26
|
{
|
|
27
27
|
mode: GameMode.VERSUS,
|
|
@@ -63,11 +63,11 @@ var BODY_PART_NAMES = {
|
|
|
63
63
|
* Get health bar color based on health percentage
|
|
64
64
|
*/
|
|
65
65
|
var getHealthColor = (health) => {
|
|
66
|
-
if (health >= 80) return
|
|
67
|
-
if (health >= 60) return
|
|
68
|
-
if (health >= 40) return
|
|
69
|
-
if (health >= 20) return
|
|
70
|
-
return
|
|
66
|
+
if (health >= 80) return KOREAN_COLORS.HEALTH_FULL;
|
|
67
|
+
if (health >= 60) return KOREAN_COLORS.ACCENT_GOLD;
|
|
68
|
+
if (health >= 40) return KOREAN_COLORS.WARNING_ORANGE;
|
|
69
|
+
if (health >= 20) return KOREAN_COLORS.PAIN_INDICATOR;
|
|
70
|
+
return KOREAN_COLORS.NEGATIVE_RED_DARK;
|
|
71
71
|
};
|
|
72
72
|
/**
|
|
73
73
|
* BodyPartHealthDisplay - Shows health bars for all 8 body parts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return 0x00ff00; // Green\n if (health >= 60) return 0xffd700; // Yellow\n if (health >= 40) return 0xffa500; // Orange\n if (health >= 20) return 0xff6b6b; // Red\n return 0x8b0000; // Dark red\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n // Responsive sizing\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n // Group body parts for display\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BodyPartHealthDisplay.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BodyPartHealthDisplay.tsx"],"sourcesContent":["/**\n * BodyPartHealthDisplay Component - Individual body part health visualization\n *\n * Displays health bars for all 8 body parts:\n * - Head (두부)\n * - Neck (경부)\n * - Torso Upper (상부 몸통)\n * - Torso Lower (하부 몸통)\n * - Left Arm (좌팔)\n * - Right Arm (우팔)\n * - Left Leg (좌다리)\n * - Right Leg (우다리)\n *\n * @module components/shared/three/ui/BodyPartHealthDisplay\n * @category Shared UI\n * @korean 신체부위체력표시\n */\n\nimport React, { useMemo } from \"react\";\nimport { BodyPart, BodyPartHealth } from \"../../../../systems/bodypart/types\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\n\nexport interface BodyPartHealthDisplayProps {\n /** Body part health data */\n readonly bodyPartHealth: BodyPartHealth;\n /** Player identifier for test IDs */\n readonly playerId: string;\n /** Position: 'left' for player 1, 'right' for player 2 */\n readonly position: \"left\" | \"right\";\n /** Whether to use mobile-optimized sizing */\n readonly isMobile: boolean;\n}\n\n/**\n * Body part names in Korean and English\n */\nconst BODY_PART_NAMES: Record<BodyPart, { korean: string; english: string }> = {\n [BodyPart.HEAD]: { korean: \"두부\", english: \"Head\" },\n [BodyPart.NECK]: { korean: \"경부\", english: \"Neck\" },\n [BodyPart.TORSO_UPPER]: { korean: \"상부\", english: \"Upper\" },\n [BodyPart.TORSO_LOWER]: { korean: \"하부\", english: \"Lower\" },\n [BodyPart.ARM_LEFT]: { korean: \"좌팔\", english: \"L.Arm\" },\n [BodyPart.ARM_RIGHT]: { korean: \"우팔\", english: \"R.Arm\" },\n [BodyPart.LEG_LEFT]: { korean: \"좌다리\", english: \"L.Leg\" },\n [BodyPart.LEG_RIGHT]: { korean: \"우다리\", english: \"R.Leg\" },\n};\n\n/**\n * Get health bar color based on health percentage\n */\nconst getHealthColor = (health: number): number => {\n if (health >= 80) return KOREAN_COLORS.HEALTH_FULL;\n if (health >= 60) return KOREAN_COLORS.ACCENT_GOLD;\n if (health >= 40) return KOREAN_COLORS.WARNING_ORANGE;\n if (health >= 20) return KOREAN_COLORS.PAIN_INDICATOR;\n return KOREAN_COLORS.NEGATIVE_RED_DARK;\n};\n\n/**\n * BodyPartHealthDisplay - Shows health bars for all 8 body parts\n *\n * @example\n * ```tsx\n * <BodyPartHealthDisplay\n * bodyPartHealth={player.bodyPartHealth}\n * playerId=\"player-1\"\n * position=\"left\"\n * isMobile={false}\n * />\n * ```\n */\nexport const BodyPartHealthDisplay: React.FC<BodyPartHealthDisplayProps> = ({\n bodyPartHealth,\n playerId,\n position,\n isMobile,\n}) => {\n const isLeft = position === \"left\";\n\n // Responsive sizing\n const barWidth = isMobile ? 100 : 140;\n const barHeight = isMobile ? 8 : 10;\n const fontSize = isMobile ? 9 : 10;\n const gap = isMobile ? \"4px\" : \"5px\";\n\n // Group body parts for display\n const bodyPartGroups = useMemo(\n () => [\n {\n label: \"상체 | Upper\",\n parts: [\n BodyPart.HEAD,\n BodyPart.NECK,\n BodyPart.TORSO_UPPER,\n BodyPart.TORSO_LOWER,\n ],\n },\n { label: \"팔 | Arms\", parts: [BodyPart.ARM_LEFT, BodyPart.ARM_RIGHT] },\n { label: \"다리 | Legs\", parts: [BodyPart.LEG_LEFT, BodyPart.LEG_RIGHT] },\n ],\n [],\n );\n\n return (\n <div\n data-testid={`body-part-health-${playerId}`}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 0.9),\n borderRadius: \"8px\",\n padding: isMobile ? \"6px\" : \"8px\",\n border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.7)}`,\n boxShadow: `0 0 12px ${hexToRgbaString(\n KOREAN_COLORS.PRIMARY_CYAN,\n 0.3,\n )}`,\n pointerEvents: \"none\",\n width: \"100%\",\n boxSizing: \"border-box\" as const,\n fontSize: isMobile ? \"9px\" : \"10px\",\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),\n textAlign: isLeft ? \"left\" : \"right\",\n marginBottom: \"2px\",\n }}\n >\n 신체 | Body Parts\n </div>\n\n {/* Body part groups */}\n {bodyPartGroups.map((group) => (\n <div\n key={group.label}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"3px\",\n }}\n >\n {/* Group label - optional, can be hidden for compact display */}\n <div\n style={{\n fontSize: `${fontSize - 1}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(KOREAN_COLORS.TEXT_SECONDARY, 0.8),\n textAlign: isLeft ? \"left\" : \"right\",\n marginTop: group.label === bodyPartGroups[0].label ? \"0\" : \"4px\",\n }}\n >\n {group.label}\n </div>\n\n {/* Body parts in group */}\n {group.parts.map((part) => {\n const health = bodyPartHealth[part];\n const names = BODY_PART_NAMES[part];\n const healthColor = getHealthColor(health);\n const shouldPulse = health < 20;\n\n return (\n <div\n key={part}\n data-testid={`body-part-${playerId}-${part}`}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Body part name and value */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexToRgbaString(healthColor, 1),\n }}\n >\n <span style={{ fontWeight: health < 40 ? \"bold\" : \"normal\" }}>\n {isMobile\n ? names.korean\n : `${names.korean} | ${names.english}`}\n </span>\n <span style={{ fontSize: `${fontSize - 1}px` }}>\n {Math.round(health)}%\n </span>\n </div>\n\n {/* Health bar */}\n <div\n style={{\n width: `${barWidth}px`,\n height: `${barHeight}px`,\n backgroundColor: hexToRgbaString(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n 1,\n ),\n borderRadius: \"2px\",\n overflow: \"hidden\",\n animation: shouldPulse\n ? \"healthPulse 0.8s infinite\"\n : \"none\",\n }}\n >\n <div\n style={{\n width: `${health}%`,\n height: \"100%\",\n backgroundColor: hexToRgbaString(healthColor, 1),\n transition:\n \"width 0.3s ease-in-out, background-color 0.3s ease-in-out\",\n boxShadow: `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`,\n }}\n />\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n};\n\nexport default BodyPartHealthDisplay;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAM,kBAAyE;EAC5E,SAAS,OAAO;EAAE,QAAQ;EAAM,SAAS;EAAQ;EACjD,SAAS,OAAO;EAAE,QAAQ;EAAM,SAAS;EAAQ;EACjD,SAAS,cAAc;EAAE,QAAQ;EAAM,SAAS;EAAS;EACzD,SAAS,cAAc;EAAE,QAAQ;EAAM,SAAS;EAAS;EACzD,SAAS,WAAW;EAAE,QAAQ;EAAM,SAAS;EAAS;EACtD,SAAS,YAAY;EAAE,QAAQ;EAAM,SAAS;EAAS;EACvD,SAAS,WAAW;EAAE,QAAQ;EAAO,SAAS;EAAS;EACvD,SAAS,YAAY;EAAE,QAAQ;EAAO,SAAS;EAAS;CAC1D;;;;AAKD,IAAM,kBAAkB,WAA2B;AACjD,KAAI,UAAU,GAAI,QAAO,cAAc;AACvC,KAAI,UAAU,GAAI,QAAO,cAAc;AACvC,KAAI,UAAU,GAAI,QAAO,cAAc;AACvC,KAAI,UAAU,GAAI,QAAO,cAAc;AACvC,QAAO,cAAc;;;;;;;;;;;;;;;AAgBvB,IAAa,yBAA+D,EAC1E,gBACA,UACA,UACA,eACI;CACJ,MAAM,SAAS,aAAa;CAG5B,MAAM,WAAW,WAAW,MAAM;CAClC,MAAM,YAAY,WAAW,IAAI;CACjC,MAAM,WAAW,WAAW,IAAI;CAChC,MAAM,MAAM,WAAW,QAAQ;CAG/B,MAAM,iBAAiB,cACf;EACJ;GACE,OAAO;GACP,OAAO;IACL,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;IACV;GACF;EACD;GAAE,OAAO;GAAY,OAAO,CAAC,SAAS,UAAU,SAAS,UAAU;GAAE;EACrE;GAAE,OAAO;GAAa,OAAO,CAAC,SAAS,UAAU,SAAS,UAAU;GAAE;EACvE,EACD,EAAE,CACH;AAED,QACE,qBAAC,OAAD;EACE,eAAa,oBAAoB;EACjC,OAAO;GACL,UAAU;GACV,SAAS;GACT,eAAe;GACf;GACA,iBAAiB,gBAAgB,cAAc,oBAAoB,GAAI;GACvE,cAAc;GACd,SAAS,WAAW,QAAQ;GAC5B,QAAQ,aAAa,gBAAgB,cAAc,cAAc,GAAI;GACrE,WAAW,YAAY,gBACrB,cAAc,cACd,GACD;GACD,eAAe;GACf,OAAO;GACP,WAAW;GACX,UAAU,WAAW,QAAQ;GAC9B;YAnBH,CAsBE,oBAAC,OAAD;GACE,OAAO;IACL,UAAU,WAAW,SAAS;IAC9B,YAAY;IACZ,YAAY,YAAY;IACxB,OAAO,gBAAgB,cAAc,cAAc,EAAE;IACrD,WAAW,SAAS,SAAS;IAC7B,cAAc;IACf;aACF;GAEK,CAAA,EAGL,eAAe,KAAK,UACnB,qBAAC,OAAD;GAEE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aANH,CASE,oBAAC,OAAD;IACE,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,gBAAgB,cAAc,gBAAgB,GAAI;KACzD,WAAW,SAAS,SAAS;KAC7B,WAAW,MAAM,UAAU,eAAe,GAAG,QAAQ,MAAM;KAC5D;cAEA,MAAM;IACH,CAAA,EAGL,MAAM,MAAM,KAAK,SAAS;IACzB,MAAM,SAAS,eAAe;IAC9B,MAAM,QAAQ,gBAAgB;IAC9B,MAAM,cAAc,eAAe,OAAO;IAC1C,MAAM,cAAc,SAAS;AAE7B,WACE,qBAAC,OAAD;KAEE,eAAa,aAAa,SAAS,GAAG;KACtC,OAAO;MACL,SAAS;MACT,eAAe;MACf,KAAK;MACN;eAPH,CAUE,qBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,gBAAgB;OAChB,YAAY;OACZ,UAAU,GAAG,SAAS;OACtB,YAAY,YAAY;OACxB,OAAO,gBAAgB,aAAa,EAAE;OACvC;gBARH,CAUE,oBAAC,QAAD;OAAM,OAAO,EAAE,YAAY,SAAS,KAAK,SAAS,UAAU;iBACzD,WACG,MAAM,SACN,GAAG,MAAM,OAAO,KAAK,MAAM;OAC1B,CAAA,EACP,qBAAC,QAAD;OAAM,OAAO,EAAE,UAAU,GAAG,WAAW,EAAE,KAAK;iBAA9C,CACG,KAAK,MAAM,OAAO,EAAC,IACf;SACH;SAGN,oBAAC,OAAD;MACE,OAAO;OACL,OAAO,GAAG,SAAS;OACnB,QAAQ,GAAG,UAAU;OACrB,iBAAiB,gBACf,cAAc,sBACd,EACD;OACD,cAAc;OACd,UAAU;OACV,WAAW,cACP,8BACA;OACL;gBAED,oBAAC,OAAD,EACE,OAAO;OACL,OAAO,GAAG,OAAO;OACjB,QAAQ;OACR,iBAAiB,gBAAgB,aAAa,EAAE;OAChD,YACE;OACF,WAAW,WAAW,gBAAgB,aAAa,GAAI;OACxD,EACD,CAAA;MACE,CAAA,CACF;OAxDC,KAwDD;KAER,CACE;KAxFC,MAAM,MAwFP,CACN,CACE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KOREAN_COLORS } from "../../../../types/constants/colors.js";
|
|
1
2
|
import { FONT_FAMILY } from "../../../../types/constants/typography.js";
|
|
2
3
|
import { BreathingDisruptionSystem } from "../../../../systems/breathing/BreathingDisruptionSystem.js";
|
|
3
4
|
import { createBreathingIndicator } from "../../../../systems/breathing/feedback.js";
|
|
@@ -45,7 +46,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
|
|
|
45
46
|
alignItems: "center",
|
|
46
47
|
gap: isMobile ? "6px" : "8px",
|
|
47
48
|
padding,
|
|
48
|
-
backgroundColor:
|
|
49
|
+
backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, .7),
|
|
49
50
|
borderRadius: "8px",
|
|
50
51
|
border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,
|
|
51
52
|
boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, .5)}`,
|
|
@@ -87,7 +88,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
|
|
|
87
88
|
style: {
|
|
88
89
|
fontSize: `${fontSize - 2}px`,
|
|
89
90
|
fontFamily: FONT_FAMILY.KOREAN,
|
|
90
|
-
color: breathingState.isRecovering ? hexToRgbaString(
|
|
91
|
+
color: breathingState.isRecovering ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, .8) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, .6),
|
|
91
92
|
whiteSpace: "nowrap"
|
|
92
93
|
},
|
|
93
94
|
children: breathingState.isRecovering ? "회복중 | Recovering" : `${secondsRemaining}s`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor:
|
|
1
|
+
{"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, 0.8)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CAEJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;AAGhE,iBAAgB;EACd,MAAM,WAAW,kBAAkB;AACjC,kBAAe,KAAK,KAAK,CAAC;KACzB,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;AAMtE,SAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAAO,CAEE;IAClE,CAAC,QAAQ,YAAY,CAAC;AAGzB,KAAI,CAAC,eAAe,QAClB,QAAO;CAIT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAGvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;AAEvE,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;GAC1D,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,cAAc,gBAAgB,GAAI,GAClD,gBAAgB,cAAc,cAAc,GAAI;KACpD,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAExD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IAEtC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+VtD,CAAC;AAEF,eAAe,aAAa,CAAC"}
|