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.
@@ -13,10 +13,10 @@ body {
13
13
  font-family: var(--font-korean);
14
14
  background: linear-gradient(
15
15
  135deg,
16
- var(--korean-bg-dark) 0%,
17
- var(--korean-bg-medium) 100%
16
+ var(--color-bg-dark) 0%,
17
+ var(--color-bg-medium) 100%
18
18
  );
19
- color: var(--korean-text-primary);
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(--korean-bg-medium) 0%,
32
- var(--korean-bg-dark) 100%
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-glow);
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(--korean-primary),
49
- var(--korean-accent)
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(--korean-primary);
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(--korean-primary);
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(--korean-primary);
65
+ border: 2px solid var(--color-accent-cyan);
66
66
  border-image: linear-gradient(
67
67
  45deg,
68
- var(--korean-primary),
69
- var(--korean-accent),
70
- var(--korean-primary)
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(--korean-primary),
74
- inset 0 0 10px rgba(0, 212, 255, 0.1);
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
- rgba(0, 212, 255, 0.1),
80
- rgba(255, 183, 0, 0.1)
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(--korean-primary);
83
- color: var(--korean-text-primary);
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
- rgba(0, 212, 255, 0.2),
96
- rgba(255, 183, 0, 0.2)
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(--korean-primary);
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
- rgba(255, 255, 255, 0.2),
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
- rgba(10, 10, 15, 0.9) 0%,
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(--korean-primary);
131
+ border-bottom: 1px solid var(--color-accent-cyan);
132
132
  }
133
133
  .health-bar {
134
- background: var(--korean-bg-dark);
135
- border: 1px solid var(--korean-primary);
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(--korean-danger) 0%,
145
- var(--korean-accent) 50%,
146
- var(--korean-glow) 100%
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-glow);
156
+ text-shadow: 0 0 5px var(--color-korean-east);
157
157
  }
158
158
  50% {
159
- text-shadow: 0 0 20px var(--korean-glow);
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: rgba(255, 51, 102, 0.1);
167
+ background-color: color-mix(in srgb, var(--color-danger) 10%, transparent);
168
168
  }
169
169
  50% {
170
- background-color: rgba(255, 51, 102, 0.3);
170
+ background-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
171
171
  }
172
172
  100% {
173
- background-color: rgba(255, 51, 102, 0.1);
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(--korean-bg-medium);
236
- border-top: 2px solid var(--korean-primary);
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(--korean-danger);
253
- background: rgba(255, 51, 102, 0.1);
254
- border: 1px solid var(--korean-danger);
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
- rgba(139, 0, 0, 0.1) 0%,
372
- rgba(0, 0, 0, 1) 70%
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: rgba(0, 10, 18, 0.9);
448
- border-bottom: 2px solid #ffd700;
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 rgba(255, 215, 0, 0.3);
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: #ffd700;
464
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
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: #87ceeb;
468
+ color: var(--color-info);
469
469
  font-style: italic;
470
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
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: rgba(255, 215, 0, 0.2);
487
- color: #ffd700;
488
- border: 1px solid #ffd700;
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: rgba(135, 206, 235, 0.2);
492
- color: #87ceeb;
493
- border: 1px solid #87ceeb;
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: rgba(0, 0, 0, 0.8);
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: rgba(0, 255, 0, 0.1);
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
- rgba(255, 215, 0, 0.05) 0%,
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
- rgba(135, 206, 235, 0.05) 0%,
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 rgba(255, 215, 0, 0.5);
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: rgba(255, 65, 54, 0.1);
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: #ff4444;
827
- text-shadow: 0 0 10px rgba(255, 68, 68, 0.8),
828
- 0 0 20px rgba(255, 68, 68, 0.5);
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: #ffaa00;
832
- text-shadow: 0 0 15px rgba(255, 170, 0, 1),
833
- 0 0 30px rgba(255, 170, 0, 0.7);
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 rgba(255, 255, 255, 0.8);
1009
+ border: 2px solid color-mix(in srgb, var(--color-text-primary) 80%, transparent);
1010
1010
  border-radius: 8px;
1011
- color: #ffffff;
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: #00ff88;
1022
+ background: var(--color-success);
1023
1023
  }
1024
1024
 
1025
1025
  .training-button-stop {
1026
- background: #ff4444;
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: rgba(45, 45, 45, 0.5);
1037
- border: 2px solid rgba(0, 255, 255, 0.4);
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: #ffffff;
1042
+ color: var(--color-text-primary);
1043
1043
  transition: all 0.2s ease;
1044
1044
  }
1045
1045
 
1046
1046
  .mode-button.selected {
1047
- background: rgba(0, 255, 255, 0.25);
1048
- border-color: #00ffff;
1049
- box-shadow: 0 0 12px rgba(0, 255, 255, 0.5);
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: rgba(64, 64, 64, 0.7);
1054
- border-color: #00ffff;
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 #00ffff;
1059
+ outline: 2px solid var(--color-primary-cyan);
1060
1060
  outline-offset: 2px;
1061
- box-shadow: 0 0 12px rgba(0, 255, 255, 0.8);
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: rgba(45, 45, 45, 0.5);
1067
- border: 2px solid rgba(255, 170, 0, 0.5);
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: #ffffff;
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: rgba(255, 170, 0, 0.3);
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: rgba(64, 64, 64, 0.7);
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: rgba(0, 0, 0, 0.9);
1101
- border: 3px solid #ffd700;
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: #ffd700;
1103
+ color: var(--color-primary-gold);
1104
1104
  text-align: center;
1105
- box-shadow: 0 0 30px rgba(255, 215, 0, 0.5);
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: #00ff88;
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: #888888;
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.11";
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 65280;
67
- if (health >= 60) return 16766720;
68
- if (health >= 40) return 16753920;
69
- if (health >= 20) return 16739179;
70
- return 9109504;
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":"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"}
@@ -1,3 +1,4 @@
1
+ import { KOREAN_COLORS } from "../../../../types/constants/colors.js";
1
2
  import { FONT_FAMILY } from "../../../../types/constants/typography.js";
2
3
  import { BreathingDisruptionSystem } from "../../../../systems/breathing/BreathingDisruptionSystem.js";
3
4
  import { createBreathingIndicator } from "../../../../systems/breathing/feedback.js";
@@ -45,7 +46,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
45
46
  alignItems: "center",
46
47
  gap: isMobile ? "6px" : "8px",
47
48
  padding,
48
- backgroundColor: "rgba(0, 0, 0, 0.7)",
49
+ backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, .7),
49
50
  borderRadius: "8px",
50
51
  border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,
51
52
  boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, .5)}`,
@@ -87,7 +88,7 @@ var BreathingIndicator = ({ player, isMobile = false }) => {
87
88
  style: {
88
89
  fontSize: `${fontSize - 2}px`,
89
90
  fontFamily: FONT_FAMILY.KOREAN,
90
- color: breathingState.isRecovering ? hexToRgbaString(65280, .8) : hexToRgbaString(16777215, .6),
91
+ color: breathingState.isRecovering ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, .8) : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, .6),
91
92
  whiteSpace: "nowrap"
92
93
  },
93
94
  children: breathingState.isRecovering ? "회복중 | Recovering" : `${secondsRemaining}s`
@@ -1 +1 @@
1
- {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(0x00ff00, 0.8)\n : hexToRgbaString(0xffffff, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CAEJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;AAGhE,iBAAgB;EACd,MAAM,WAAW,kBAAkB;AACjC,kBAAe,KAAK,KAAK,CAAC;KACzB,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;AAMtE,SAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAAO,CAEE;IAClE,CAAC,QAAQ,YAAY,CAAC;AAGzB,KAAI,CAAC,eAAe,QAClB,QAAO;CAIT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAGvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;AAEvE,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB;GACjB,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,OAAU,GAAI,GAC9B,gBAAgB,UAAU,GAAI;KAClC,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
1
+ {"version":3,"file":"BreathingIndicator2.js","names":[],"sources":["../../../../../src/components/shared/three/ui/BreathingIndicator.tsx"],"sourcesContent":["/**\n * BreathingIndicator Component - Visual feedback for breathing disruption status\n * \n * **Korean**: 호흡곤란 표시기\n * \n * Displays breathing difficulty with:\n * - Color-coded lungs icon (🫁)\n * - Bilingual label (Korean | English)\n * - Time remaining until recovery\n * - Pulsing animation based on severity\n */\n\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport {\n BreathingDisruptionSystem,\n createBreathingIndicator,\n} from \"../../../../systems/breathing\";\nimport { PlayerState } from \"../../../../systems/player\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport \"./BreathingIndicator.css\";\n\nexport interface BreathingIndicatorProps {\n /** Player state to check for breathing disruption */\n readonly player: PlayerState;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n}\n\n/**\n * BreathingIndicator - Shows breathing disruption status with Korean-English labels\n */\nexport const BreathingIndicator: React.FC<BreathingIndicatorProps> = ({\n player,\n isMobile = false,\n}) => {\n // Use state to trigger re-renders for timer updates\n const [currentTime, setCurrentTime] = useState(() => Date.now());\n\n // Update current time every 100ms to keep the timer accurate\n useEffect(() => {\n const interval = setInterval(() => {\n setCurrentTime(Date.now());\n }, 100);\n\n return () => clearInterval(interval);\n }, []);\n\n // Get current breathing disruption state\n const breathingState = useMemo(() => {\n const level = BreathingDisruptionSystem.getCurrentLevel(player);\n const activeEffect = BreathingDisruptionSystem.getActiveEffect(player);\n const timeRemaining = activeEffect\n ? Math.max(0, activeEffect.endTime - currentTime)\n : 0;\n const isRecovering = BreathingDisruptionSystem.canRecover(player);\n\n return createBreathingIndicator(level, timeRemaining, isRecovering);\n }, [player, currentTime]);\n\n // Don't render if no breathing disruption\n if (!breathingState.visible) {\n return null;\n }\n\n // Responsive sizing\n const iconSize = isMobile ? 24 : 32;\n const fontSize = isMobile ? 10 : 12;\n const padding = isMobile ? \"4px 8px\" : \"6px 12px\";\n\n // Format time remaining\n const secondsRemaining = Math.ceil(breathingState.timeRemaining / 1000);\n\n return (\n <div\n data-testid=\"breathing-indicator\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: isMobile ? \"6px\" : \"8px\",\n padding,\n backgroundColor: hexToRgbaString(KOREAN_COLORS.BLACK, 0.7),\n borderRadius: \"8px\",\n border: `2px solid ${hexToRgbaString(breathingState.color, breathingState.opacity)}`,\n boxShadow: `0 0 10px ${hexToRgbaString(breathingState.color, 0.5)}`,\n animation: \"breathing-pulse 1s ease-in-out infinite\",\n pointerEvents: \"none\",\n }}\n >\n {/* Lungs icon */}\n <div\n data-testid=\"breathing-icon\"\n style={{\n fontSize: `${iconSize}px`,\n lineHeight: 1,\n transform: `scale(${breathingState.scale})`,\n filter: `drop-shadow(0 0 6px ${hexToRgbaString(breathingState.color, 0.6)})`,\n }}\n >\n {breathingState.icon}\n </div>\n\n {/* Label and time */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"2px\",\n }}\n >\n {/* Bilingual label */}\n <div\n data-testid=\"breathing-label\"\n style={{\n fontSize: `${fontSize}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n fontWeight: \"bold\",\n color: hexToRgbaString(breathingState.color, 1),\n textShadow: `0 0 4px ${hexToRgbaString(breathingState.color, 0.8)}`,\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.label.korean} | {breathingState.label.english}\n </div>\n\n {/* Time remaining */}\n <div\n data-testid=\"breathing-timer\"\n style={{\n fontSize: `${fontSize - 2}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: breathingState.isRecovering\n ? hexToRgbaString(KOREAN_COLORS.POSITIVE_GREEN, 0.8)\n : hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.6),\n whiteSpace: \"nowrap\",\n }}\n >\n {breathingState.isRecovering ? \"회복중 | Recovering\" : `${secondsRemaining}s`}\n </div>\n </div>\n </div>\n );\n};\n\nBreathingIndicator.displayName = \"BreathingIndicator\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,sBAAyD,EACpE,QACA,WAAW,YACP;CAEJ,MAAM,CAAC,aAAa,kBAAkB,eAAe,KAAK,KAAK,CAAC;AAGhE,iBAAgB;EACd,MAAM,WAAW,kBAAkB;AACjC,kBAAe,KAAK,KAAK,CAAC;KACzB,IAAI;AAEP,eAAa,cAAc,SAAS;IACnC,EAAE,CAAC;CAGN,MAAM,iBAAiB,cAAc;EACnC,MAAM,QAAQ,0BAA0B,gBAAgB,OAAO;EAC/D,MAAM,eAAe,0BAA0B,gBAAgB,OAAO;AAMtE,SAAO,yBAAyB,OALV,eAClB,KAAK,IAAI,GAAG,aAAa,UAAU,YAAY,GAC/C,GACiB,0BAA0B,WAAW,OAAO,CAEE;IAClE,CAAC,QAAQ,YAAY,CAAC;AAGzB,KAAI,CAAC,eAAe,QAClB,QAAO;CAIT,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,UAAU,WAAW,YAAY;CAGvC,MAAM,mBAAmB,KAAK,KAAK,eAAe,gBAAgB,IAAK;AAEvE,QACE,qBAAC,OAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK,WAAW,QAAQ;GACxB;GACA,iBAAiB,gBAAgB,cAAc,OAAO,GAAI;GAC1D,cAAc;GACd,QAAQ,aAAa,gBAAgB,eAAe,OAAO,eAAe,QAAQ;GAClF,WAAW,YAAY,gBAAgB,eAAe,OAAO,GAAI;GACjE,WAAW;GACX,eAAe;GAChB;YAbH,CAgBI,oBAAC,OAAD;GACE,eAAY;GACZ,OAAO;IACL,UAAU,GAAG,SAAS;IACtB,YAAY;IACZ,WAAW,SAAS,eAAe,MAAM;IACzC,QAAQ,uBAAuB,gBAAgB,eAAe,OAAO,GAAI,CAAC;IAC3E;aAEA,eAAe;GACZ,CAAA,EAGN,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK;IACN;aALH,CAQE,qBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,SAAS;KACtB,YAAY,YAAY;KACxB,YAAY;KACZ,OAAO,gBAAgB,eAAe,OAAO,EAAE;KAC/C,YAAY,WAAW,gBAAgB,eAAe,OAAO,GAAI;KACjE,YAAY;KACb;cATH;KAWG,eAAe,MAAM;KAAO;KAAI,eAAe,MAAM;KAClD;OAGN,oBAAC,OAAD;IACE,eAAY;IACZ,OAAO;KACL,UAAU,GAAG,WAAW,EAAE;KAC1B,YAAY,YAAY;KACxB,OAAO,eAAe,eAClB,gBAAgB,cAAc,gBAAgB,GAAI,GAClD,gBAAgB,cAAc,cAAc,GAAI;KACpD,YAAY;KACb;cAEA,eAAe,eAAe,qBAAqB,GAAG,iBAAiB;IACpE,CAAA,CACF;KACF;;;AAIZ,mBAAmB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAExD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IAEtC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuWtD,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"TechniqueCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/shared/three/ui/TechniqueCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,qBAAqB,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAE9B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,4BAA4B;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAE7B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAExD,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,qDAAqD;IACrD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IAEtC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+VtD,CAAC;AAEF,eAAe,aAAa,CAAC"}