Fraser commited on
Commit
94e4b64
·
1 Parent(s): 566cdc0

better battle

Browse files
sample_animations.html ADDED
@@ -0,0 +1,1183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pokemon Sprite Expression Animator</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ background: rgba(255, 255, 255, 0.95);
25
+ border-radius: 20px;
26
+ padding: 30px;
27
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ h1 {
31
+ text-align: center;
32
+ color: #333;
33
+ font-size: 2.5em;
34
+ margin-bottom: 10px;
35
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
36
+ }
37
+
38
+ .subtitle {
39
+ text-align: center;
40
+ color: #666;
41
+ margin-bottom: 30px;
42
+ font-size: 1.1em;
43
+ }
44
+
45
+ .upload-section {
46
+ background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
47
+ padding: 25px;
48
+ border-radius: 15px;
49
+ margin-bottom: 30px;
50
+ text-align: center;
51
+ border: 3px dashed #ff6b35;
52
+ }
53
+
54
+ .file-input {
55
+ margin: 15px 0;
56
+ }
57
+
58
+ .file-input input[type="file"] {
59
+ display: none;
60
+ }
61
+
62
+ .file-label {
63
+ display: inline-block;
64
+ padding: 12px 25px;
65
+ background: #ff6b35;
66
+ color: white;
67
+ border-radius: 25px;
68
+ cursor: pointer;
69
+ transition: all 0.3s ease;
70
+ font-weight: bold;
71
+ }
72
+
73
+ .file-label:hover {
74
+ background: #e55a2b;
75
+ transform: translateY(-2px);
76
+ box-shadow: 0 5px 15px rgba(229, 90, 43, 0.3);
77
+ }
78
+
79
+ .animation-grid {
80
+ display: grid;
81
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
82
+ gap: 25px;
83
+ margin-top: 30px;
84
+ }
85
+
86
+ .animation-card {
87
+ background: white;
88
+ border-radius: 15px;
89
+ padding: 20px;
90
+ text-align: center;
91
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
92
+ transition: all 0.3s ease;
93
+ border: 2px solid transparent;
94
+ }
95
+
96
+ .animation-card:hover {
97
+ transform: translateY(-5px);
98
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
99
+ border-color: #667eea;
100
+ }
101
+
102
+ .animation-title {
103
+ font-size: 1.2em;
104
+ color: #333;
105
+ margin-bottom: 15px;
106
+ font-weight: bold;
107
+ }
108
+
109
+ .sprite-container {
110
+ width: 120px;
111
+ height: 120px;
112
+ margin: 0 auto 20px;
113
+ background: #f0f0f0;
114
+ border-radius: 10px;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ overflow: visible;
119
+ border: 2px solid #ddd;
120
+ position: relative;
121
+ }
122
+
123
+ .sprite {
124
+ max-width: 100%;
125
+ max-height: 100%;
126
+ image-rendering: pixelated;
127
+ }
128
+
129
+ .demo-sprite {
130
+ width: 80px;
131
+ height: 80px;
132
+ background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
133
+ border-radius: 50%;
134
+ position: relative;
135
+ }
136
+
137
+ .demo-sprite::before {
138
+ content: '🎮';
139
+ position: absolute;
140
+ top: 50%;
141
+ left: 50%;
142
+ transform: translate(-50%, -50%);
143
+ font-size: 2em;
144
+ }
145
+
146
+ .apply-btn {
147
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
148
+ color: white;
149
+ border: none;
150
+ padding: 10px 20px;
151
+ border-radius: 25px;
152
+ cursor: pointer;
153
+ font-weight: bold;
154
+ transition: all 0.3s ease;
155
+ width: 100%;
156
+ }
157
+
158
+ .apply-btn:hover {
159
+ transform: translateY(-2px);
160
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
161
+ }
162
+
163
+ .apply-btn:disabled {
164
+ background: #ccc;
165
+ cursor: not-allowed;
166
+ transform: none;
167
+ box-shadow: none;
168
+ }
169
+
170
+ /* Animation Classes */
171
+ .idle-bounce {
172
+ animation: idleBounce 2s ease-in-out infinite;
173
+ }
174
+
175
+ .happy-bounce {
176
+ animation: happyBounce 0.6s ease-in-out infinite;
177
+ }
178
+
179
+ .excited-shake {
180
+ animation: excitedShake 0.5s ease-in-out infinite;
181
+ }
182
+
183
+ .sad-droop {
184
+ animation: sadDroop 3s ease-in-out infinite;
185
+ }
186
+
187
+ .angry-shake {
188
+ animation: angryShake 0.3s ease-in-out infinite;
189
+ }
190
+
191
+ .confused-tilt {
192
+ animation: confusedTilt 2s ease-in-out infinite;
193
+ }
194
+
195
+ .sleepy-sway {
196
+ animation: sleepySway 4s ease-in-out infinite;
197
+ }
198
+
199
+ .alert-pulse {
200
+ animation: alertPulse 1s ease-in-out infinite;
201
+ }
202
+
203
+ .hurt-flash {
204
+ animation: hurtFlash 0.8s ease-in-out infinite;
205
+ }
206
+
207
+ .victory-spin {
208
+ animation: victorySpin 1.5s ease-in-out infinite;
209
+ }
210
+
211
+ .charging-glow {
212
+ animation: chargingGlow 1.2s ease-in-out infinite;
213
+ }
214
+
215
+ .dizzy-wobble {
216
+ animation: dizzyWobble 1s ease-in-out infinite;
217
+ }
218
+
219
+ .attack-lunge {
220
+ animation: attackLunge 1.5s ease-in-out infinite;
221
+ }
222
+
223
+ .defend-crouch {
224
+ animation: defendCrouch 2s ease-in-out infinite;
225
+ }
226
+
227
+ .love-hearts {
228
+ animation: loveHearts 2s ease-in-out infinite;
229
+ }
230
+
231
+ .fear-tremble {
232
+ animation: fearTremble 0.4s ease-in-out infinite;
233
+ }
234
+
235
+ .evolving-glow {
236
+ animation: evolvingGlow 2s ease-in-out infinite;
237
+ }
238
+
239
+ .fainting-fall {
240
+ animation: faintingFall 3s ease-in-out infinite;
241
+ }
242
+
243
+ .turn-left {
244
+ animation: turnLeft 2s ease-in-out infinite;
245
+ }
246
+
247
+ .turn-right {
248
+ animation: turnRight 2s ease-in-out infinite;
249
+ }
250
+
251
+ .look-around {
252
+ animation: lookAround 4s ease-in-out infinite;
253
+ }
254
+
255
+ .confetti-effect {
256
+ animation: confettiSpin 2s ease-in-out infinite;
257
+ }
258
+
259
+ .hearts-float {
260
+ animation: heartsFloat 3s ease-in-out infinite;
261
+ }
262
+
263
+ .lightning-strike {
264
+ animation: lightningStrike 1.5s ease-in-out infinite;
265
+ }
266
+
267
+ .sparkle-magic {
268
+ animation: sparkleMagic 2s ease-in-out infinite;
269
+ }
270
+
271
+ .fire-burst {
272
+ animation: fireBurst 1.8s ease-in-out infinite;
273
+ }
274
+
275
+ .ice-crystals {
276
+ animation: iceCrystals 2.5s ease-in-out infinite;
277
+ }
278
+
279
+ .poison-bubbles {
280
+ animation: poisonBubbles 3s ease-in-out infinite;
281
+ }
282
+
283
+ .healing-aura {
284
+ animation: healingAura 2.8s ease-in-out infinite;
285
+ }
286
+
287
+ /* Particle Systems */
288
+ .particles {
289
+ position: absolute;
290
+ top: 0;
291
+ left: 0;
292
+ width: 100%;
293
+ height: 100%;
294
+ pointer-events: none;
295
+ overflow: visible;
296
+ }
297
+
298
+ .particle {
299
+ position: absolute;
300
+ border-radius: 50%;
301
+ }
302
+
303
+ .confetti-particle {
304
+ width: 6px;
305
+ height: 6px;
306
+ animation: confettifall 2s linear infinite;
307
+ }
308
+
309
+ .heart-particle {
310
+ width: 12px;
311
+ height: 12px;
312
+ animation: heartFloat 3s ease-in-out infinite;
313
+ color: #ff69b4;
314
+ }
315
+
316
+ .heart-particle::before {
317
+ content: '💖';
318
+ font-size: 12px;
319
+ }
320
+
321
+ .lightning-particle {
322
+ width: 2px;
323
+ height: 20px;
324
+ background: linear-gradient(to bottom, #ffff00, #4169e1);
325
+ animation: lightningFlash 0.3s ease-in-out infinite;
326
+ border-radius: 1px;
327
+ }
328
+
329
+ .sparkle-particle {
330
+ width: 4px;
331
+ height: 4px;
332
+ background: #ffd700;
333
+ animation: sparkleShine 1.5s ease-in-out infinite;
334
+ box-shadow: 0 0 6px #ffd700;
335
+ }
336
+
337
+ .fire-particle {
338
+ width: 8px;
339
+ height: 8px;
340
+ background: radial-gradient(circle, #ff4500, #ff8c00);
341
+ animation: fireRise 1.8s ease-out infinite;
342
+ }
343
+
344
+ .ice-particle {
345
+ width: 6px;
346
+ height: 6px;
347
+ background: linear-gradient(45deg, #00bfff, #87ceeb);
348
+ animation: iceFall 2.5s linear infinite;
349
+ transform: rotate(45deg);
350
+ }
351
+
352
+ .poison-particle {
353
+ width: 10px;
354
+ height: 10px;
355
+ background: radial-gradient(circle, #9932cc, #8b008b);
356
+ animation: poisonBubble 3s ease-in-out infinite;
357
+ }
358
+
359
+ .heal-particle {
360
+ width: 5px;
361
+ height: 5px;
362
+ background: radial-gradient(circle, #32cd32, #90ee90);
363
+ animation: healFloat 2.8s ease-in-out infinite;
364
+ box-shadow: 0 0 8px #32cd32;
365
+ }
366
+
367
+ /* Keyframes */
368
+ @keyframes idleBounce {
369
+ 0%, 100% { transform: translateY(0px); }
370
+ 50% { transform: translateY(-8px); }
371
+ }
372
+
373
+ @keyframes happyBounce {
374
+ 0%, 100% { transform: translateY(0px) scale(1); }
375
+ 50% { transform: translateY(-15px) scale(1.1); }
376
+ }
377
+
378
+ @keyframes excitedShake {
379
+ 0%, 100% { transform: translateX(0px); }
380
+ 25% { transform: translateX(-5px) rotate(-2deg); }
381
+ 75% { transform: translateX(5px) rotate(2deg); }
382
+ }
383
+
384
+ @keyframes sadDroop {
385
+ 0%, 100% { transform: translateY(0px) scaleY(1); }
386
+ 50% { transform: translateY(10px) scaleY(0.9); }
387
+ }
388
+
389
+ @keyframes angryShake {
390
+ 0%, 100% { transform: translateX(0px); }
391
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
392
+ 20%, 40%, 60%, 80% { transform: translateX(3px); }
393
+ }
394
+
395
+ @keyframes confusedTilt {
396
+ 0%, 100% { transform: rotate(0deg); }
397
+ 25% { transform: rotate(-10deg); }
398
+ 75% { transform: rotate(10deg); }
399
+ }
400
+
401
+ @keyframes sleepySway {
402
+ 0%, 100% { transform: rotate(0deg) translateY(0px); }
403
+ 25% { transform: rotate(-5deg) translateY(5px); }
404
+ 75% { transform: rotate(5deg) translateY(5px); }
405
+ }
406
+
407
+ @keyframes alertPulse {
408
+ 0%, 100% { transform: scale(1); }
409
+ 50% { transform: scale(1.15); }
410
+ }
411
+
412
+ @keyframes hurtFlash {
413
+ 0%, 100% { opacity: 1; filter: brightness(1); }
414
+ 50% { opacity: 0.7; filter: brightness(1.5) hue-rotate(0deg); }
415
+ }
416
+
417
+ @keyframes victorySpin {
418
+ 0% { transform: rotate(0deg) scale(1); }
419
+ 50% { transform: rotate(180deg) scale(1.2); }
420
+ 100% { transform: rotate(360deg) scale(1); }
421
+ }
422
+
423
+ @keyframes chargingGlow {
424
+ 0%, 100% { filter: brightness(1) saturate(1); transform: scale(1); }
425
+ 50% { filter: brightness(1.3) saturate(1.5); transform: scale(1.05); }
426
+ }
427
+
428
+ @keyframes dizzyWobble {
429
+ 0%, 100% { transform: rotate(0deg); }
430
+ 25% { transform: rotate(-15deg) translateX(-5px); }
431
+ 50% { transform: rotate(0deg) translateX(0px); }
432
+ 75% { transform: rotate(15deg) translateX(5px); }
433
+ }
434
+
435
+ @keyframes attackLunge {
436
+ 0%, 100% { transform: translateX(0px) scaleX(1); }
437
+ 30% { transform: translateX(-10px) scaleX(0.9); }
438
+ 60% { transform: translateX(15px) scaleX(1.1); }
439
+ }
440
+
441
+ @keyframes defendCrouch {
442
+ 0%, 100% { transform: scaleY(1) translateY(0px); }
443
+ 50% { transform: scaleY(0.8) translateY(10px); }
444
+ }
445
+
446
+ @keyframes loveHearts {
447
+ 0%, 100% { transform: scale(1); filter: hue-rotate(0deg); }
448
+ 50% { transform: scale(1.1); filter: hue-rotate(20deg); }
449
+ }
450
+
451
+ @keyframes fearTremble {
452
+ 0%, 100% { transform: translateX(0px) translateY(0px); }
453
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-2px) translateY(-1px); }
454
+ 20%, 40%, 60%, 80% { transform: translateX(2px) translateY(1px); }
455
+ }
456
+
457
+ @keyframes evolvingGlow {
458
+ 0%, 100% {
459
+ filter: brightness(1) contrast(1);
460
+ transform: scale(1);
461
+ }
462
+ 50% {
463
+ filter: brightness(1.5) contrast(1.2);
464
+ transform: scale(1.1);
465
+ }
466
+ }
467
+
468
+ @keyframes faintingFall {
469
+ 0%, 70% { transform: rotate(0deg) translateY(0px); opacity: 1; }
470
+ 100% { transform: rotate(90deg) translateY(20px); opacity: 0.3; }
471
+ }
472
+
473
+ /* New Turning Animations */
474
+ @keyframes turnLeft {
475
+ 0%, 100% { transform: scaleX(1); }
476
+ 50% { transform: scaleX(-1); }
477
+ }
478
+
479
+ @keyframes turnRight {
480
+ 0%, 100% { transform: scaleX(-1); }
481
+ 50% { transform: scaleX(1); }
482
+ }
483
+
484
+ @keyframes lookAround {
485
+ 0%, 100% { transform: scaleX(1); }
486
+ 25% { transform: scaleX(-1); }
487
+ 50% { transform: scaleX(1); }
488
+ 75% { transform: scaleX(-1); }
489
+ }
490
+
491
+ /* Particle Effect Animations */
492
+ @keyframes confettiSpin {
493
+ 0%, 100% { transform: translateY(0px) rotate(0deg) scale(1); }
494
+ 50% { transform: translateY(-10px) rotate(180deg) scale(1.1); }
495
+ }
496
+
497
+ @keyframes heartsFloat {
498
+ 0%, 100% { transform: translateY(0px) scale(1); }
499
+ 50% { transform: translateY(-8px) scale(1.05); }
500
+ }
501
+
502
+ @keyframes lightningStrike {
503
+ 0%, 90%, 100% { filter: brightness(1) contrast(1); }
504
+ 10%, 20%, 30% { filter: brightness(2) contrast(1.5) hue-rotate(60deg); }
505
+ }
506
+
507
+ @keyframes sparkleMagic {
508
+ 0%, 100% { transform: rotate(0deg) scale(1); filter: brightness(1); }
509
+ 50% { transform: rotate(180deg) scale(1.1); filter: brightness(1.3); }
510
+ }
511
+
512
+ @keyframes fireBurst {
513
+ 0%, 100% { transform: scale(1); filter: hue-rotate(0deg) brightness(1); }
514
+ 50% { transform: scale(1.1); filter: hue-rotate(30deg) brightness(1.2); }
515
+ }
516
+
517
+ @keyframes iceCrystals {
518
+ 0%, 100% { transform: rotate(0deg) scale(1); filter: brightness(1) saturate(1); }
519
+ 50% { transform: rotate(180deg) scale(1.05); filter: brightness(1.2) saturate(1.3); }
520
+ }
521
+
522
+ @keyframes poisonBubbles {
523
+ 0%, 100% { transform: translateY(0px) scale(1); filter: hue-rotate(0deg); }
524
+ 50% { transform: translateY(-5px) scale(1.03); filter: hue-rotate(30deg); }
525
+ }
526
+
527
+ @keyframes healingAura {
528
+ 0%, 100% { transform: scale(1); filter: brightness(1) saturate(1); }
529
+ 50% { transform: scale(1.05); filter: brightness(1.2) saturate(1.4); }
530
+ }
531
+
532
+ /* Particle Keyframes */
533
+ @keyframes confettifall {
534
+ 0% {
535
+ transform: translateY(-20px) rotate(0deg);
536
+ opacity: 1;
537
+ }
538
+ 100% {
539
+ transform: translateY(140px) rotate(360deg);
540
+ opacity: 0;
541
+ }
542
+ }
543
+
544
+ @keyframes heartFloat {
545
+ 0% {
546
+ transform: translateY(20px) scale(0.5);
547
+ opacity: 0;
548
+ }
549
+ 50% {
550
+ opacity: 1;
551
+ transform: translateY(-10px) scale(1);
552
+ }
553
+ 100% {
554
+ transform: translateY(-40px) scale(0.8);
555
+ opacity: 0;
556
+ }
557
+ }
558
+
559
+ @keyframes lightningFlash {
560
+ 0%, 100% { opacity: 0; }
561
+ 50% { opacity: 1; }
562
+ }
563
+
564
+ @keyframes sparkleShine {
565
+ 0%, 100% {
566
+ transform: scale(0.5) rotate(0deg);
567
+ opacity: 0.5;
568
+ }
569
+ 50% {
570
+ transform: scale(1.2) rotate(180deg);
571
+ opacity: 1;
572
+ }
573
+ }
574
+
575
+ @keyframes fireRise {
576
+ 0% {
577
+ transform: translateY(20px) scale(1);
578
+ opacity: 1;
579
+ }
580
+ 100% {
581
+ transform: translateY(-30px) scale(0.3);
582
+ opacity: 0;
583
+ }
584
+ }
585
+
586
+ @keyframes iceFall {
587
+ 0% {
588
+ transform: translateY(-20px) rotate(45deg) scale(1);
589
+ opacity: 1;
590
+ }
591
+ 100% {
592
+ transform: translateY(140px) rotate(405deg) scale(0.5);
593
+ opacity: 0;
594
+ }
595
+ }
596
+
597
+ @keyframes poisonBubble {
598
+ 0% {
599
+ transform: translateY(20px) scale(0.5);
600
+ opacity: 0.7;
601
+ }
602
+ 50% {
603
+ transform: translateY(0px) scale(1);
604
+ opacity: 1;
605
+ }
606
+ 100% {
607
+ transform: translateY(-20px) scale(0.3);
608
+ opacity: 0;
609
+ }
610
+ }
611
+
612
+ @keyframes healFloat {
613
+ 0% {
614
+ transform: translateY(10px) scale(0.5);
615
+ opacity: 0.5;
616
+ }
617
+ 50% {
618
+ transform: translateY(-5px) scale(1);
619
+ opacity: 1;
620
+ }
621
+ 100% {
622
+ transform: translateY(-20px) scale(0.7);
623
+ opacity: 0;
624
+ }
625
+ }
626
+
627
+ .description {
628
+ font-size: 0.9em;
629
+ color: #666;
630
+ margin-bottom: 15px;
631
+ line-height: 1.4;
632
+ }
633
+
634
+ .controls {
635
+ display: flex;
636
+ gap: 10px;
637
+ margin-top: 15px;
638
+ }
639
+
640
+ .speed-control {
641
+ flex: 1;
642
+ }
643
+
644
+ .speed-control select {
645
+ width: 100%;
646
+ padding: 5px;
647
+ border: 1px solid #ddd;
648
+ border-radius: 5px;
649
+ }
650
+ </style>
651
+ </head>
652
+ <body>
653
+ <div class="container">
654
+ <h1>🎮 Pokemon Sprite Expression Animator</h1>
655
+ <p class="subtitle">Upload your sprite and test various Pokemon-style animations</p>
656
+
657
+ <div class="upload-section">
658
+ <h3>Upload Your Sprite</h3>
659
+ <div class="file-input">
660
+ <label for="sprite-upload" class="file-label">Choose Sprite Image</label>
661
+ <input type="file" id="sprite-upload" accept="image/*">
662
+ </div>
663
+ <p>Supports PNG, JPG, GIF - Best results with square sprites (64x64 to 256x256px)</p>
664
+ </div>
665
+
666
+ <div class="animation-grid" id="animationGrid">
667
+ <!-- Animations will be populated by JavaScript -->
668
+ </div>
669
+ </div>
670
+
671
+ <script>
672
+ const animations = [
673
+ {
674
+ name: 'Idle Bounce',
675
+ class: 'idle-bounce',
676
+ description: 'Gentle up-down bounce for idle state'
677
+ },
678
+ {
679
+ name: 'Happy Bounce',
680
+ class: 'happy-bounce',
681
+ description: 'Excited bouncing with slight scaling'
682
+ },
683
+ {
684
+ name: 'Excited Shake',
685
+ class: 'excited-shake',
686
+ description: 'Side-to-side shake with rotation'
687
+ },
688
+ {
689
+ name: 'Sad Droop',
690
+ class: 'sad-droop',
691
+ description: 'Slow drooping motion with vertical squash'
692
+ },
693
+ {
694
+ name: 'Angry Shake',
695
+ class: 'angry-shake',
696
+ description: 'Rapid horizontal shaking'
697
+ },
698
+ {
699
+ name: 'Confused Tilt',
700
+ class: 'confused-tilt',
701
+ description: 'Side-to-side head tilting motion'
702
+ },
703
+ {
704
+ name: 'Sleepy Sway',
705
+ class: 'sleepy-sway',
706
+ description: 'Slow swaying with slight rotation'
707
+ },
708
+ {
709
+ name: 'Alert Pulse',
710
+ class: 'alert-pulse',
711
+ description: 'Quick scaling pulse for attention'
712
+ },
713
+ {
714
+ name: 'Hurt Flash',
715
+ class: 'hurt-flash',
716
+ description: 'Opacity and brightness flashing'
717
+ },
718
+ {
719
+ name: 'Victory Spin',
720
+ class: 'victory-spin',
721
+ description: 'Full rotation with scaling'
722
+ },
723
+ {
724
+ name: 'Charging Glow',
725
+ class: 'charging-glow',
726
+ description: 'Brightness and saturation increase'
727
+ },
728
+ {
729
+ name: 'Dizzy Wobble',
730
+ class: 'dizzy-wobble',
731
+ description: 'Unsteady wobbling motion'
732
+ },
733
+ {
734
+ name: 'Attack Lunge',
735
+ class: 'attack-lunge',
736
+ description: 'Forward lunge motion with stretch'
737
+ },
738
+ {
739
+ name: 'Defend Crouch',
740
+ class: 'defend-crouch',
741
+ description: 'Defensive crouching animation'
742
+ },
743
+ {
744
+ name: 'Love Hearts',
745
+ class: 'love-hearts',
746
+ description: 'Gentle scaling with color shift'
747
+ },
748
+ {
749
+ name: 'Fear Tremble',
750
+ class: 'fear-tremble',
751
+ description: 'Rapid small trembling motion'
752
+ },
753
+ {
754
+ name: 'Evolving Glow',
755
+ class: 'evolving-glow',
756
+ description: 'Mystical glowing evolution effect'
757
+ },
758
+ {
759
+ name: 'Fainting Fall',
760
+ class: 'fainting-fall',
761
+ description: 'Rotation and fade for fainting'
762
+ },
763
+ {
764
+ name: 'Turn Left',
765
+ class: 'turn-left',
766
+ description: 'Smooth horizontal flip to face left'
767
+ },
768
+ {
769
+ name: 'Turn Right',
770
+ class: 'turn-right',
771
+ description: 'Smooth horizontal flip to face right'
772
+ },
773
+ {
774
+ name: 'Look Around',
775
+ class: 'look-around',
776
+ description: 'Turn left and right alternately'
777
+ },
778
+ {
779
+ name: 'Confetti Celebration',
780
+ class: 'confetti-effect',
781
+ description: 'Victory with colorful confetti particles',
782
+ hasParticles: true
783
+ },
784
+ {
785
+ name: 'Love Hearts Float',
786
+ class: 'hearts-float',
787
+ description: 'Floating heart particles around sprite',
788
+ hasParticles: true
789
+ },
790
+ {
791
+ name: 'Lightning Strike',
792
+ class: 'lightning-strike',
793
+ description: 'Electric attack with lightning effects',
794
+ hasParticles: true
795
+ },
796
+ {
797
+ name: 'Sparkle Magic',
798
+ class: 'sparkle-magic',
799
+ description: 'Magical sparkles surrounding sprite',
800
+ hasParticles: true
801
+ },
802
+ {
803
+ name: 'Fire Burst',
804
+ class: 'fire-burst',
805
+ description: 'Fire attack with flame particles',
806
+ hasParticles: true
807
+ },
808
+ {
809
+ name: 'Ice Crystals',
810
+ class: 'ice-crystals',
811
+ description: 'Ice attack with crystal particles',
812
+ hasParticles: true
813
+ },
814
+ {
815
+ name: 'Poison Bubbles',
816
+ class: 'poison-bubbles',
817
+ description: 'Toxic bubbles floating around',
818
+ hasParticles: true
819
+ },
820
+ {
821
+ name: 'Healing Aura',
822
+ class: 'healing-aura',
823
+ description: 'Gentle healing particles',
824
+ hasParticles: true
825
+ }
826
+ ];
827
+
828
+ let uploadedImage = null;
829
+
830
+ function createAnimationCard(animation) {
831
+ const particleHTML = animation.hasParticles ? '<div class="particles" id="particles-' + animation.class + '"></div>' : '';
832
+
833
+ return `
834
+ <div class="animation-card">
835
+ <div class="animation-title">${animation.name}</div>
836
+ <div class="sprite-container">
837
+ ${uploadedImage
838
+ ? `<img src="${uploadedImage}" class="sprite ${animation.class}" alt="Animated sprite">`
839
+ : `<div class="demo-sprite ${animation.class}"></div>`
840
+ }
841
+ ${particleHTML}
842
+ </div>
843
+ <div class="description">${animation.description}</div>
844
+ <div class="controls">
845
+ <div class="speed-control">
846
+ <select onchange="changeSpeed(this, '${animation.class}')">
847
+ <option value="0.5">2x Speed</option>
848
+ <option value="1" selected>Normal</option>
849
+ <option value="1.5">0.75x Speed</option>
850
+ <option value="2">0.5x Speed</option>
851
+ </select>
852
+ </div>
853
+ </div>
854
+ <button class="apply-btn" onclick="copyCSS('${animation.class}')">
855
+ Copy CSS
856
+ </button>
857
+ </div>
858
+ `;
859
+ }
860
+
861
+ function renderAnimations() {
862
+ const grid = document.getElementById('animationGrid');
863
+ grid.innerHTML = animations.map(createAnimationCard).join('');
864
+
865
+ // Initialize particle effects for animations that need them
866
+ animations.forEach(animation => {
867
+ if (animation.hasParticles) {
868
+ initializeParticles(animation.class);
869
+ }
870
+ });
871
+ }
872
+
873
+ function initializeParticles(animationClass) {
874
+ const particleContainer = document.getElementById(`particles-${animationClass}`);
875
+ if (!particleContainer) return;
876
+
877
+ // Clear existing particles
878
+ particleContainer.innerHTML = '';
879
+
880
+ const particleConfigs = {
881
+ 'confetti-effect': {
882
+ count: 8,
883
+ type: 'confetti',
884
+ colors: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dda0dd']
885
+ },
886
+ 'hearts-float': {
887
+ count: 6,
888
+ type: 'heart'
889
+ },
890
+ 'lightning-strike': {
891
+ count: 5,
892
+ type: 'lightning'
893
+ },
894
+ 'sparkle-magic': {
895
+ count: 12,
896
+ type: 'sparkle'
897
+ },
898
+ 'fire-burst': {
899
+ count: 8,
900
+ type: 'fire'
901
+ },
902
+ 'ice-crystals': {
903
+ count: 10,
904
+ type: 'ice'
905
+ },
906
+ 'poison-bubbles': {
907
+ count: 6,
908
+ type: 'poison'
909
+ },
910
+ 'healing-aura': {
911
+ count: 8,
912
+ type: 'heal'
913
+ }
914
+ };
915
+
916
+ const config = particleConfigs[animationClass];
917
+ if (!config) return;
918
+
919
+ for (let i = 0; i < config.count; i++) {
920
+ const particle = document.createElement('div');
921
+ particle.className = `particle ${config.type}-particle`;
922
+
923
+ // Random positioning
924
+ const angle = (360 / config.count) * i + Math.random() * 45;
925
+ const radius = 40 + Math.random() * 20;
926
+ const x = 50 + Math.cos(angle * Math.PI / 180) * radius;
927
+ const y = 50 + Math.sin(angle * Math.PI / 180) * radius;
928
+
929
+ particle.style.left = x + '%';
930
+ particle.style.top = y + '%';
931
+
932
+ // Random delay for staggered animation
933
+ particle.style.animationDelay = Math.random() * 2 + 's';
934
+
935
+ // Color for confetti
936
+ if (config.type === 'confetti' && config.colors) {
937
+ particle.style.backgroundColor = config.colors[Math.floor(Math.random() * config.colors.length)];
938
+ }
939
+
940
+ particleContainer.appendChild(particle);
941
+ }
942
+ }
943
+
944
+ function changeSpeed(select, animationClass) {
945
+ const multiplier = parseFloat(select.value);
946
+ const elements = document.querySelectorAll(`.${animationClass}`);
947
+ elements.forEach(el => {
948
+ el.style.animationDuration = `${getOriginalDuration(animationClass) * multiplier}s`;
949
+ });
950
+ }
951
+
952
+ function getOriginalDuration(animationClass) {
953
+ const durations = {
954
+ 'idle-bounce': 2,
955
+ 'happy-bounce': 0.6,
956
+ 'excited-shake': 0.5,
957
+ 'sad-droop': 3,
958
+ 'angry-shake': 0.3,
959
+ 'confused-tilt': 2,
960
+ 'sleepy-sway': 4,
961
+ 'alert-pulse': 1,
962
+ 'hurt-flash': 0.8,
963
+ 'victory-spin': 1.5,
964
+ 'charging-glow': 1.2,
965
+ 'dizzy-wobble': 1,
966
+ 'attack-lunge': 1.5,
967
+ 'defend-crouch': 2,
968
+ 'love-hearts': 2,
969
+ 'fear-tremble': 0.4,
970
+ 'evolving-glow': 2,
971
+ 'fainting-fall': 3,
972
+ 'turn-left': 2,
973
+ 'turn-right': 2,
974
+ 'look-around': 4,
975
+ 'confetti-effect': 2,
976
+ 'hearts-float': 3,
977
+ 'lightning-strike': 1.5,
978
+ 'sparkle-magic': 2,
979
+ 'fire-burst': 1.8,
980
+ 'ice-crystals': 2.5,
981
+ 'poison-bubbles': 3,
982
+ 'healing-aura': 2.8
983
+ };
984
+ return durations[animationClass] || 1;
985
+ }
986
+
987
+ function copyCSS(animationClass) {
988
+ const cssMap = {
989
+ 'idle-bounce': `.idle-bounce {
990
+ animation: idleBounce 2s ease-in-out infinite;
991
+ }
992
+
993
+ @keyframes idleBounce {
994
+ 0%, 100% { transform: translateY(0px); }
995
+ 50% { transform: translateY(-8px); }
996
+ }`,
997
+ 'happy-bounce': `.happy-bounce {
998
+ animation: happyBounce 0.6s ease-in-out infinite;
999
+ }
1000
+
1001
+ @keyframes happyBounce {
1002
+ 0%, 100% { transform: translateY(0px) scale(1); }
1003
+ 50% { transform: translateY(-15px) scale(1.1); }
1004
+ }`,
1005
+ 'excited-shake': `.excited-shake {
1006
+ animation: excitedShake 0.5s ease-in-out infinite;
1007
+ }
1008
+
1009
+ @keyframes excitedShake {
1010
+ 0%, 100% { transform: translateX(0px); }
1011
+ 25% { transform: translateX(-5px) rotate(-2deg); }
1012
+ 75% { transform: translateX(5px) rotate(2deg); }
1013
+ }`,
1014
+ 'sad-droop': `.sad-droop {
1015
+ animation: sadDroop 3s ease-in-out infinite;
1016
+ }
1017
+
1018
+ @keyframes sadDroop {
1019
+ 0%, 100% { transform: translateY(0px) scaleY(1); }
1020
+ 50% { transform: translateY(10px) scaleY(0.9); }
1021
+ }`,
1022
+ 'angry-shake': `.angry-shake {
1023
+ animation: angryShake 0.3s ease-in-out infinite;
1024
+ }
1025
+
1026
+ @keyframes angryShake {
1027
+ 0%, 100% { transform: translateX(0px); }
1028
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
1029
+ 20%, 40%, 60%, 80% { transform: translateX(3px); }
1030
+ }`,
1031
+ 'confused-tilt': `.confused-tilt {
1032
+ animation: confusedTilt 2s ease-in-out infinite;
1033
+ }
1034
+
1035
+ @keyframes confusedTilt {
1036
+ 0%, 100% { transform: rotate(0deg); }
1037
+ 25% { transform: rotate(-10deg); }
1038
+ 75% { transform: rotate(10deg); }
1039
+ }`,
1040
+ 'sleepy-sway': `.sleepy-sway {
1041
+ animation: sleepySway 4s ease-in-out infinite;
1042
+ }
1043
+
1044
+ @keyframes sleepySway {
1045
+ 0%, 100% { transform: rotate(0deg) translateY(0px); }
1046
+ 25% { transform: rotate(-5deg) translateY(5px); }
1047
+ 75% { transform: rotate(5deg) translateY(5px); }
1048
+ }`,
1049
+ 'alert-pulse': `.alert-pulse {
1050
+ animation: alertPulse 1s ease-in-out infinite;
1051
+ }
1052
+
1053
+ @keyframes alertPulse {
1054
+ 0%, 100% { transform: scale(1); }
1055
+ 50% { transform: scale(1.15); }
1056
+ }`,
1057
+ 'hurt-flash': `.hurt-flash {
1058
+ animation: hurtFlash 0.8s ease-in-out infinite;
1059
+ }
1060
+
1061
+ @keyframes hurtFlash {
1062
+ 0%, 100% { opacity: 1; filter: brightness(1); }
1063
+ 50% { opacity: 0.7; filter: brightness(1.5); }
1064
+ }`,
1065
+ 'victory-spin': `.victory-spin {
1066
+ animation: victorySpin 1.5s ease-in-out infinite;
1067
+ }
1068
+
1069
+ @keyframes victorySpin {
1070
+ 0% { transform: rotate(0deg) scale(1); }
1071
+ 50% { transform: rotate(180deg) scale(1.2); }
1072
+ 100% { transform: rotate(360deg) scale(1); }
1073
+ }`,
1074
+ 'charging-glow': `.charging-glow {
1075
+ animation: chargingGlow 1.2s ease-in-out infinite;
1076
+ }
1077
+
1078
+ @keyframes chargingGlow {
1079
+ 0%, 100% { filter: brightness(1) saturate(1); transform: scale(1); }
1080
+ 50% { filter: brightness(1.3) saturate(1.5); transform: scale(1.05); }
1081
+ }`,
1082
+ 'dizzy-wobble': `.dizzy-wobble {
1083
+ animation: dizzyWobble 1s ease-in-out infinite;
1084
+ }
1085
+
1086
+ @keyframes dizzyWobble {
1087
+ 0%, 100% { transform: rotate(0deg); }
1088
+ 25% { transform: rotate(-15deg) translateX(-5px); }
1089
+ 50% { transform: rotate(0deg) translateX(0px); }
1090
+ 75% { transform: rotate(15deg) translateX(5px); }
1091
+ }`,
1092
+ 'attack-lunge': `.attack-lunge {
1093
+ animation: attackLunge 1.5s ease-in-out infinite;
1094
+ }
1095
+
1096
+ @keyframes attackLunge {
1097
+ 0%, 100% { transform: translateX(0px) scaleX(1); }
1098
+ 30% { transform: translateX(-10px) scaleX(0.9); }
1099
+ 60% { transform: translateX(15px) scaleX(1.1); }
1100
+ }`,
1101
+ 'defend-crouch': `.defend-crouch {
1102
+ animation: defendCrouch 2s ease-in-out infinite;
1103
+ }
1104
+
1105
+ @keyframes defendCrouch {
1106
+ 0%, 100% { transform: scaleY(1) translateY(0px); }
1107
+ 50% { transform: scaleY(0.8) translateY(10px); }
1108
+ }`,
1109
+ 'love-hearts': `.love-hearts {
1110
+ animation: loveHearts 2s ease-in-out infinite;
1111
+ }
1112
+
1113
+ @keyframes loveHearts {
1114
+ 0%, 100% { transform: scale(1); filter: hue-rotate(0deg); }
1115
+ 50% { transform: scale(1.1); filter: hue-rotate(20deg); }
1116
+ }`,
1117
+ 'fear-tremble': `.fear-tremble {
1118
+ animation: fearTremble 0.4s ease-in-out infinite;
1119
+ }
1120
+
1121
+ @keyframes fearTremble {
1122
+ 0%, 100% { transform: translateX(0px) translateY(0px); }
1123
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-2px) translateY(-1px); }
1124
+ 20%, 40%, 60%, 80% { transform: translateX(2px) translateY(1px); }
1125
+ }`,
1126
+ 'evolving-glow': `.evolving-glow {
1127
+ animation: evolvingGlow 2s ease-in-out infinite;
1128
+ }
1129
+
1130
+ @keyframes evolvingGlow {
1131
+ 0%, 100% {
1132
+ filter: brightness(1) contrast(1);
1133
+ transform: scale(1);
1134
+ }
1135
+ 50% {
1136
+ filter: brightness(1.5) contrast(1.2);
1137
+ transform: scale(1.1);
1138
+ }
1139
+ }`,
1140
+ 'fainting-fall': `.fainting-fall {
1141
+ animation: faintingFall 3s ease-in-out infinite;
1142
+ }
1143
+
1144
+ @keyframes faintingFall {
1145
+ 0%, 70% { transform: rotate(0deg) translateY(0px); opacity: 1; }
1146
+ 100% { transform: rotate(90deg) translateY(20px); opacity: 0.3; }
1147
+ }`
1148
+ };
1149
+
1150
+ const css = cssMap[animationClass];
1151
+ if (css) {
1152
+ navigator.clipboard.writeText(css).then(() => {
1153
+ alert('CSS copied to clipboard!');
1154
+ }).catch(() => {
1155
+ const textArea = document.createElement('textarea');
1156
+ textArea.value = css;
1157
+ document.body.appendChild(textArea);
1158
+ textArea.select();
1159
+ document.execCommand('copy');
1160
+ document.body.removeChild(textArea);
1161
+ alert('CSS copied to clipboard!');
1162
+ });
1163
+ }
1164
+ }
1165
+
1166
+ // File upload handling
1167
+ document.getElementById('sprite-upload').addEventListener('change', function(e) {
1168
+ const file = e.target.files[0];
1169
+ if (file) {
1170
+ const reader = new FileReader();
1171
+ reader.onload = function(e) {
1172
+ uploadedImage = e.target.result;
1173
+ renderAnimations();
1174
+ };
1175
+ reader.readAsDataURL(file);
1176
+ }
1177
+ });
1178
+
1179
+ // Initial render
1180
+ renderAnimations();
1181
+ </script>
1182
+ </body>
1183
+ </html>
src/lib/battle-engine/BattleEngine.ts CHANGED
@@ -1072,11 +1072,11 @@ export class BattleEngine {
1072
  } else if (!playerHasViablePiclets) {
1073
  this.state.winner = 'opponent';
1074
  this.state.phase = 'ended';
1075
- this.log(`${this.state.opponentPiclet.definition.name} wins!`);
1076
  } else if (!opponentHasViablePiclets) {
1077
  this.state.winner = 'player';
1078
  this.state.phase = 'ended';
1079
- this.log(`${this.state.playerPiclet.definition.name} wins!`);
1080
  } else if (playerFainted || opponentFainted) {
1081
  // Handle forced switching - at least one piclet fainted but viable alternatives exist
1082
  this.handleForcedSwitching(playerFainted, opponentFainted);
@@ -1124,7 +1124,15 @@ export class BattleEngine {
1124
 
1125
  // Public method to get battle log
1126
  public getLog(): string[] {
1127
- return [...this.state.log];
 
 
 
 
 
 
 
 
1128
  }
1129
 
1130
  // Additional effect processors for advanced features
 
1072
  } else if (!playerHasViablePiclets) {
1073
  this.state.winner = 'opponent';
1074
  this.state.phase = 'ended';
1075
+ // No win message - handled in UI
1076
  } else if (!opponentHasViablePiclets) {
1077
  this.state.winner = 'player';
1078
  this.state.phase = 'ended';
1079
+ // No win message - handled in UI
1080
  } else if (playerFainted || opponentFainted) {
1081
  // Handle forced switching - at least one piclet fainted but viable alternatives exist
1082
  this.handleForcedSwitching(playerFainted, opponentFainted);
 
1124
 
1125
  // Public method to get battle log
1126
  public getLog(): string[] {
1127
+ // Strip battle prefixes from all log messages for display
1128
+ return this.state.log.map(message => this.stripBattlePrefixes(message));
1129
+ }
1130
+
1131
+ private stripBattlePrefixes(message: string): string {
1132
+ // Remove player- and enemy- prefixes from messages
1133
+ return message
1134
+ .replace(/player-/g, '')
1135
+ .replace(/enemy-/g, '');
1136
  }
1137
 
1138
  // Additional effect processors for advanced features
src/lib/battle-engine/MultiBattleEngine.ts CHANGED
@@ -688,6 +688,14 @@ export class MultiBattleEngine {
688
  }
689
 
690
  public getLog(): string[] {
691
- return [...this.state.log];
 
 
 
 
 
 
 
 
692
  }
693
  }
 
688
  }
689
 
690
  public getLog(): string[] {
691
+ // Strip battle prefixes from all log messages for display
692
+ return this.state.log.map(message => this.stripBattlePrefixes(message));
693
+ }
694
+
695
+ private stripBattlePrefixes(message: string): string {
696
+ // Remove player- and enemy- prefixes from messages
697
+ return message
698
+ .replace(/player-/g, '')
699
+ .replace(/enemy-/g, '');
700
  }
701
  }
src/lib/components/Battle/BattleControls.svelte CHANGED
@@ -102,7 +102,7 @@
102
  left: 0;
103
  right: 0;
104
  bottom: 0;
105
- background: rgba(0, 0, 0, 0.1);
106
  display: flex;
107
  align-items: center;
108
  justify-content: center;
@@ -111,23 +111,24 @@
111
  }
112
 
113
  .tap-indicator {
114
- background: rgba(0, 122, 255, 0.9);
115
- color: white;
116
- padding: 8px 16px;
117
- border-radius: 20px;
118
- font-size: 14px;
119
- font-weight: 500;
120
- animation: pulse 2s infinite;
 
121
  }
122
 
123
- @keyframes pulse {
124
  0%, 100% {
125
- opacity: 0.9;
126
  transform: scale(1);
127
  }
128
  50% {
129
- opacity: 1;
130
- transform: scale(1.05);
131
  }
132
  }
133
  </style>
 
102
  left: 0;
103
  right: 0;
104
  bottom: 0;
105
+ background: rgba(0, 0, 0, 0.05);
106
  display: flex;
107
  align-items: center;
108
  justify-content: center;
 
111
  }
112
 
113
  .tap-indicator {
114
+ background: rgba(0, 0, 0, 0.15);
115
+ color: #666;
116
+ padding: 6px 12px;
117
+ border-radius: 16px;
118
+ font-size: 12px;
119
+ font-weight: 400;
120
+ border: 1px solid rgba(0, 0, 0, 0.1);
121
+ animation: subtlePulse 3s infinite;
122
  }
123
 
124
+ @keyframes subtlePulse {
125
  0%, 100% {
126
+ opacity: 0.7;
127
  transform: scale(1);
128
  }
129
  50% {
130
+ opacity: 0.9;
131
+ transform: scale(1.02);
132
  }
133
  }
134
  </style>
src/lib/components/Pages/Battle.svelte CHANGED
@@ -257,25 +257,25 @@
257
 
258
 
259
  function triggerVisualEffectsFromMessage(message: string) {
260
- // Use internal battle prefixes for reliable animation targeting
261
- const playerInternalName = battleState?.playerPiclet?.definition?.name || '';
262
- const enemyInternalName = battleState?.opponentPiclet?.definition?.name || '';
263
 
264
  // Attack lunge effects - trigger when a Piclet uses a move
265
  if (message.includes(' used ')) {
266
- if (message.includes(playerInternalName)) {
267
  triggerLungeAnimation('player');
268
- } else if (message.includes(enemyInternalName)) {
269
  triggerLungeAnimation('enemy');
270
  }
271
  }
272
 
273
  // Damage effects
274
  if (message.includes('took') && message.includes('damage')) {
275
- if (message.includes(playerInternalName)) {
276
  triggerDamageFlash('player');
277
  updateUIFromBattleState();
278
- } else if (message.includes(enemyInternalName)) {
279
  triggerDamageFlash('enemy');
280
  updateUIFromBattleState();
281
  }
@@ -295,25 +295,25 @@
295
 
296
  // Status effects
297
  if (message.includes('was burned')) {
298
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
299
  triggerEffect(target, 'burn', '🔥', 1200);
300
  } else if (message.includes('was poisoned')) {
301
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
302
  triggerEffect(target, 'poison', '☠️', 1200);
303
  } else if (message.includes('was paralyzed')) {
304
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
305
  triggerEffect(target, 'paralyze', '⚡', 1200);
306
  } else if (message.includes('fell asleep')) {
307
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
308
  triggerEffect(target, 'sleep', '😴', 1200);
309
  } else if (message.includes('was frozen')) {
310
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
311
  triggerEffect(target, 'freeze', '❄️', 1200);
312
  }
313
 
314
  // Stat changes
315
  if (message.includes("'s") && (message.includes('rose') || message.includes('fell'))) {
316
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
317
  const isIncrease = message.includes('rose');
318
 
319
  if (message.includes('attack')) {
@@ -329,7 +329,7 @@
329
 
330
  // Healing effects
331
  if (message.includes('recovered') && message.includes('HP')) {
332
- const target = message.includes(playerInternalName) ? 'player' : 'enemy';
333
  triggerEffect(target, 'heal', '💚', 1000);
334
  // Update HP bar immediately for healing animation sync
335
  updateUIFromBattleState();
 
257
 
258
 
259
  function triggerVisualEffectsFromMessage(message: string) {
260
+ // Use stripped names since battle messages no longer have prefixes
261
+ const playerName = stripBattlePrefix(battleState?.playerPiclet?.definition?.name || '');
262
+ const enemyName = stripBattlePrefix(battleState?.opponentPiclet?.definition?.name || '');
263
 
264
  // Attack lunge effects - trigger when a Piclet uses a move
265
  if (message.includes(' used ')) {
266
+ if (message.includes(playerName)) {
267
  triggerLungeAnimation('player');
268
+ } else if (message.includes(enemyName)) {
269
  triggerLungeAnimation('enemy');
270
  }
271
  }
272
 
273
  // Damage effects
274
  if (message.includes('took') && message.includes('damage')) {
275
+ if (message.includes(playerName)) {
276
  triggerDamageFlash('player');
277
  updateUIFromBattleState();
278
+ } else if (message.includes(enemyName)) {
279
  triggerDamageFlash('enemy');
280
  updateUIFromBattleState();
281
  }
 
295
 
296
  // Status effects
297
  if (message.includes('was burned')) {
298
+ const target = message.includes(playerName) ? 'player' : 'enemy';
299
  triggerEffect(target, 'burn', '🔥', 1200);
300
  } else if (message.includes('was poisoned')) {
301
+ const target = message.includes(playerName) ? 'player' : 'enemy';
302
  triggerEffect(target, 'poison', '☠️', 1200);
303
  } else if (message.includes('was paralyzed')) {
304
+ const target = message.includes(playerName) ? 'player' : 'enemy';
305
  triggerEffect(target, 'paralyze', '⚡', 1200);
306
  } else if (message.includes('fell asleep')) {
307
+ const target = message.includes(playerName) ? 'player' : 'enemy';
308
  triggerEffect(target, 'sleep', '😴', 1200);
309
  } else if (message.includes('was frozen')) {
310
+ const target = message.includes(playerName) ? 'player' : 'enemy';
311
  triggerEffect(target, 'freeze', '❄️', 1200);
312
  }
313
 
314
  // Stat changes
315
  if (message.includes("'s") && (message.includes('rose') || message.includes('fell'))) {
316
+ const target = message.includes(playerName) ? 'player' : 'enemy';
317
  const isIncrease = message.includes('rose');
318
 
319
  if (message.includes('attack')) {
 
329
 
330
  // Healing effects
331
  if (message.includes('recovered') && message.includes('HP')) {
332
+ const target = message.includes(playerName) ? 'player' : 'enemy';
333
  triggerEffect(target, 'heal', '💚', 1000);
334
  // Update HP bar immediately for healing animation sync
335
  updateUIFromBattleState();