Fraser commited on
Commit
782d90b
·
1 Parent(s): 4c208f2
src/lib/components/Piclets/AbilityDisplay.svelte ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { BattleEffect, AbilityTrigger } from '$lib/types';
3
+
4
+ interface Props {
5
+ ability: {
6
+ name: string;
7
+ description: string;
8
+ effects?: BattleEffect[];
9
+ triggers?: AbilityTrigger[];
10
+ };
11
+ expanded?: boolean;
12
+ }
13
+
14
+ let { ability, expanded = false }: Props = $props();
15
+
16
+ function getEffectIcon(effectType: string): string {
17
+ switch (effectType) {
18
+ case 'damage': return '⚔️';
19
+ case 'modifyStats': return '📊';
20
+ case 'applyStatus': return '💫';
21
+ case 'heal': return '💚';
22
+ case 'manipulatePP': return '⚡';
23
+ case 'fieldEffect': return '🌍';
24
+ case 'counter': return '🔄';
25
+ case 'priority': return '⚡';
26
+ case 'removeStatus': return '✨';
27
+ case 'mechanicOverride': return '⚙️';
28
+ default: return '❓';
29
+ }
30
+ }
31
+
32
+ function getEffectColor(effectType: string): string {
33
+ switch (effectType) {
34
+ case 'damage': return '#ff6b6b';
35
+ case 'modifyStats': return '#4dabf7';
36
+ case 'applyStatus': return '#9775fa';
37
+ case 'heal': return '#51cf66';
38
+ case 'manipulatePP': return '#ffd43b';
39
+ case 'fieldEffect': return '#495057';
40
+ case 'counter': return '#fd7e14';
41
+ case 'priority': return '#20c997';
42
+ case 'removeStatus': return '#74c0fc';
43
+ case 'mechanicOverride': return '#868e96';
44
+ default: return '#adb5bd';
45
+ }
46
+ }
47
+
48
+ function getTriggerIcon(event: string): string {
49
+ switch (event) {
50
+ case 'onDamageTaken': return '🛡️';
51
+ case 'onDamageDealt': return '⚔️';
52
+ case 'onContactDamage': return '👊';
53
+ case 'onCriticalHit': return '💥';
54
+ case 'endOfTurn': return '🔄';
55
+ case 'onLowHP': return '❤️';
56
+ case 'onStatusInflicted': return '💫';
57
+ case 'onHPDrained': return '🩸';
58
+ case 'onKO': return '💀';
59
+ case 'onSwitchIn': return '➡️';
60
+ case 'onSwitchOut': return '⬅️';
61
+ case 'beforeMoveUse': return '⏰';
62
+ case 'afterMoveUse': return '✅';
63
+ case 'onFullHP': return '💚';
64
+ case 'onOpponentContactMove': return '🤜';
65
+ case 'onStatChange': return '📈';
66
+ case 'onTypeChange': return '🔄';
67
+ default: return '⚡';
68
+ }
69
+ }
70
+
71
+ function formatEffectDescription(effect: BattleEffect): string {
72
+ let desc = effect.type.charAt(0).toUpperCase() + effect.type.slice(1);
73
+
74
+ if (effect.target !== 'self') {
75
+ desc += ` (${effect.target})`;
76
+ }
77
+
78
+ if (effect.condition) {
79
+ desc += ` when ${effect.condition}`;
80
+ }
81
+
82
+ if (effect.amount) {
83
+ desc += ` - ${effect.amount}`;
84
+ }
85
+
86
+ if (effect.stats) {
87
+ const statChanges = Object.entries(effect.stats).map(([stat, change]) =>
88
+ `${stat}: ${change}`
89
+ ).join(', ');
90
+ desc += ` (${statChanges})`;
91
+ }
92
+
93
+ if (effect.status) {
94
+ desc += ` - ${effect.status}`;
95
+ }
96
+
97
+ return desc;
98
+ }
99
+ </script>
100
+
101
+ <div class="ability-display">
102
+ <div class="ability-header">
103
+ <div class="ability-name-section">
104
+ <span class="ability-icon">✨</span>
105
+ <div class="ability-info">
106
+ <h3 class="ability-name">{ability.name}</h3>
107
+ <p class="ability-description">{ability.description}</p>
108
+ </div>
109
+ </div>
110
+
111
+ {#if (ability.effects?.length || 0) + (ability.triggers?.length || 0) > 0}
112
+ <div class="ability-counts">
113
+ {#if ability.effects?.length}
114
+ <span class="count-badge effects">
115
+ {ability.effects.length} effect{ability.effects.length !== 1 ? 's' : ''}
116
+ </span>
117
+ {/if}
118
+ {#if ability.triggers?.length}
119
+ <span class="count-badge triggers">
120
+ {ability.triggers.length} trigger{ability.triggers.length !== 1 ? 's' : ''}
121
+ </span>
122
+ {/if}
123
+ </div>
124
+ {/if}
125
+ </div>
126
+
127
+ {#if expanded && (ability.effects?.length || ability.triggers?.length)}
128
+ <div class="ability-details">
129
+ {#if ability.effects?.length}
130
+ <div class="effects-section">
131
+ <h4 class="section-title">
132
+ <span class="section-icon">🎯</span>
133
+ Passive Effects
134
+ </h4>
135
+ <div class="effects-list">
136
+ {#each ability.effects as effect}
137
+ <div class="effect-item">
138
+ <span
139
+ class="effect-icon"
140
+ style="color: {getEffectColor(effect.type)}"
141
+ >
142
+ {getEffectIcon(effect.type)}
143
+ </span>
144
+ <div class="effect-details">
145
+ <span class="effect-type">{effect.type}</span>
146
+ <span class="effect-description">{formatEffectDescription(effect)}</span>
147
+ </div>
148
+ </div>
149
+ {/each}
150
+ </div>
151
+ </div>
152
+ {/if}
153
+
154
+ {#if ability.triggers?.length}
155
+ <div class="triggers-section">
156
+ <h4 class="section-title">
157
+ <span class="section-icon">⚡</span>
158
+ Triggered Effects
159
+ </h4>
160
+ <div class="triggers-list">
161
+ {#each ability.triggers as trigger}
162
+ <div class="trigger-item">
163
+ <div class="trigger-header">
164
+ <span class="trigger-icon">{getTriggerIcon(trigger.event)}</span>
165
+ <div class="trigger-info">
166
+ <span class="trigger-event">{trigger.event}</span>
167
+ {#if trigger.condition}
168
+ <span class="trigger-condition">when {trigger.condition}</span>
169
+ {/if}
170
+ </div>
171
+ </div>
172
+
173
+ {#if trigger.effects?.length}
174
+ <div class="trigger-effects">
175
+ {#each trigger.effects as effect}
176
+ <div class="trigger-effect">
177
+ <span
178
+ class="effect-icon small"
179
+ style="color: {getEffectColor(effect.type)}"
180
+ >
181
+ {getEffectIcon(effect.type)}
182
+ </span>
183
+ <span class="effect-summary">{formatEffectDescription(effect)}</span>
184
+ </div>
185
+ {/each}
186
+ </div>
187
+ {/if}
188
+ </div>
189
+ {/each}
190
+ </div>
191
+ </div>
192
+ {/if}
193
+ </div>
194
+ {/if}
195
+ </div>
196
+
197
+ <style>
198
+ .ability-display {
199
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
200
+ border: 1px solid #dee2e6;
201
+ border-radius: 12px;
202
+ padding: 16px;
203
+ margin: 8px 0;
204
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
205
+ }
206
+
207
+ .ability-header {
208
+ display: flex;
209
+ justify-content: space-between;
210
+ align-items: flex-start;
211
+ gap: 12px;
212
+ }
213
+
214
+ .ability-name-section {
215
+ display: flex;
216
+ align-items: flex-start;
217
+ gap: 12px;
218
+ flex: 1;
219
+ }
220
+
221
+ .ability-icon {
222
+ font-size: 24px;
223
+ margin-top: 2px;
224
+ }
225
+
226
+ .ability-info {
227
+ flex: 1;
228
+ }
229
+
230
+ .ability-name {
231
+ font-size: 18px;
232
+ font-weight: 600;
233
+ color: #495057;
234
+ margin: 0 0 4px 0;
235
+ }
236
+
237
+ .ability-description {
238
+ font-size: 14px;
239
+ color: #6c757d;
240
+ margin: 0;
241
+ line-height: 1.4;
242
+ }
243
+
244
+ .ability-counts {
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: 4px;
248
+ align-items: flex-end;
249
+ }
250
+
251
+ .count-badge {
252
+ font-size: 11px;
253
+ padding: 3px 8px;
254
+ border-radius: 12px;
255
+ font-weight: 500;
256
+ white-space: nowrap;
257
+ }
258
+
259
+ .count-badge.effects {
260
+ background: #e3f2fd;
261
+ color: #1976d2;
262
+ }
263
+
264
+ .count-badge.triggers {
265
+ background: #fff3e0;
266
+ color: #f57c00;
267
+ }
268
+
269
+ .ability-details {
270
+ margin-top: 16px;
271
+ padding-top: 16px;
272
+ border-top: 1px solid #e9ecef;
273
+ }
274
+
275
+ .section-title {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 8px;
279
+ font-size: 14px;
280
+ font-weight: 600;
281
+ color: #495057;
282
+ margin: 0 0 12px 0;
283
+ }
284
+
285
+ .section-icon {
286
+ font-size: 16px;
287
+ }
288
+
289
+ .effects-section,
290
+ .triggers-section {
291
+ margin-bottom: 16px;
292
+ }
293
+
294
+ .effects-list,
295
+ .triggers-list {
296
+ display: flex;
297
+ flex-direction: column;
298
+ gap: 8px;
299
+ }
300
+
301
+ .effect-item {
302
+ display: flex;
303
+ align-items: flex-start;
304
+ gap: 10px;
305
+ padding: 8px 12px;
306
+ background: rgba(255, 255, 255, 0.7);
307
+ border-radius: 8px;
308
+ border: 1px solid rgba(0, 0, 0, 0.08);
309
+ }
310
+
311
+ .effect-icon {
312
+ font-size: 16px;
313
+ margin-top: 1px;
314
+ }
315
+
316
+ .effect-icon.small {
317
+ font-size: 14px;
318
+ }
319
+
320
+ .effect-details {
321
+ flex: 1;
322
+ display: flex;
323
+ flex-direction: column;
324
+ gap: 2px;
325
+ }
326
+
327
+ .effect-type {
328
+ font-size: 12px;
329
+ font-weight: 600;
330
+ color: #495057;
331
+ text-transform: uppercase;
332
+ letter-spacing: 0.5px;
333
+ }
334
+
335
+ .effect-description {
336
+ font-size: 13px;
337
+ color: #6c757d;
338
+ line-height: 1.3;
339
+ }
340
+
341
+ .trigger-item {
342
+ background: rgba(255, 255, 255, 0.7);
343
+ border: 1px solid rgba(0, 0, 0, 0.08);
344
+ border-radius: 8px;
345
+ padding: 12px;
346
+ }
347
+
348
+ .trigger-header {
349
+ display: flex;
350
+ align-items: flex-start;
351
+ gap: 10px;
352
+ margin-bottom: 8px;
353
+ }
354
+
355
+ .trigger-icon {
356
+ font-size: 16px;
357
+ margin-top: 1px;
358
+ }
359
+
360
+ .trigger-info {
361
+ flex: 1;
362
+ display: flex;
363
+ flex-direction: column;
364
+ gap: 2px;
365
+ }
366
+
367
+ .trigger-event {
368
+ font-size: 13px;
369
+ font-weight: 600;
370
+ color: #495057;
371
+ }
372
+
373
+ .trigger-condition {
374
+ font-size: 12px;
375
+ color: #868e96;
376
+ font-style: italic;
377
+ }
378
+
379
+ .trigger-effects {
380
+ display: flex;
381
+ flex-direction: column;
382
+ gap: 6px;
383
+ padding-left: 26px;
384
+ }
385
+
386
+ .trigger-effect {
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 8px;
390
+ }
391
+
392
+ .effect-summary {
393
+ font-size: 12px;
394
+ color: #6c757d;
395
+ line-height: 1.3;
396
+ }
397
+ </style>
src/lib/components/Piclets/MoveDisplay.svelte ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { BattleMove as DBBattleMove } from '$lib/db/schema';
3
+ import type { BattleMove, BattleEffect } from '$lib/types';
4
+
5
+ interface Props {
6
+ move: DBBattleMove;
7
+ enhancedMove?: { move: BattleMove; currentPP: number };
8
+ expanded?: boolean;
9
+ showPP?: boolean;
10
+ }
11
+
12
+ let { move, enhancedMove, expanded = false, showPP = true }: Props = $props();
13
+
14
+ // Use enhanced move data if available, otherwise fall back to basic move
15
+ $: battleMove = enhancedMove?.move;
16
+ $: currentPP = enhancedMove?.currentPP ?? move.currentPp;
17
+
18
+ function getTypeEmoji(type: string): string {
19
+ switch (type) {
20
+ case 'normal': return '⚪';
21
+ case 'beast': return '🦁';
22
+ case 'bug': return '🐛';
23
+ case 'aquatic': return '🌊';
24
+ case 'flora': return '🌿';
25
+ case 'mineral': return '💎';
26
+ case 'space': return '🌌';
27
+ case 'machina': return '⚙️';
28
+ case 'structure': return '🏗️';
29
+ case 'culture': return '🎭';
30
+ case 'cuisine': return '🍽️';
31
+ default: return '❓';
32
+ }
33
+ }
34
+
35
+ function getTypeColor(type: string): string {
36
+ switch (type) {
37
+ case 'normal': return '#a8a878';
38
+ case 'beast': return '#c03028';
39
+ case 'bug': return '#a8b820';
40
+ case 'aquatic': return '#6890f0';
41
+ case 'flora': return '#78c850';
42
+ case 'mineral': return '#b8a038';
43
+ case 'space': return '#705898';
44
+ case 'machina': return '#b8b8d0';
45
+ case 'structure': return '#b8a058';
46
+ case 'culture': return '#f85888';
47
+ case 'cuisine': return '#f08030';
48
+ default: return '#68a090';
49
+ }
50
+ }
51
+
52
+ function getEffectIcon(effectType: string): string {
53
+ switch (effectType) {
54
+ case 'damage': return '⚔️';
55
+ case 'modifyStats': return '📊';
56
+ case 'applyStatus': return '💫';
57
+ case 'heal': return '💚';
58
+ case 'manipulatePP': return '⚡';
59
+ case 'fieldEffect': return '🌍';
60
+ case 'counter': return '🔄';
61
+ case 'priority': return '⚡';
62
+ case 'removeStatus': return '✨';
63
+ case 'mechanicOverride': return '⚙️';
64
+ default: return '❓';
65
+ }
66
+ }
67
+
68
+ function getEffectColor(effectType: string): string {
69
+ switch (effectType) {
70
+ case 'damage': return '#ff6b6b';
71
+ case 'modifyStats': return '#4dabf7';
72
+ case 'applyStatus': return '#9775fa';
73
+ case 'heal': return '#51cf66';
74
+ case 'manipulatePP': return '#ffd43b';
75
+ case 'fieldEffect': return '#495057';
76
+ case 'counter': return '#fd7e14';
77
+ case 'priority': return '#20c997';
78
+ case 'removeStatus': return '#74c0fc';
79
+ case 'mechanicOverride': return '#868e96';
80
+ default: return '#adb5bd';
81
+ }
82
+ }
83
+
84
+ function formatEffectDescription(effect: BattleEffect): string {
85
+ let desc = effect.type.charAt(0).toUpperCase() + effect.type.slice(1);
86
+
87
+ if (effect.target !== 'self') {
88
+ desc += ` (${effect.target})`;
89
+ }
90
+
91
+ if (effect.condition) {
92
+ desc += ` when ${effect.condition}`;
93
+ }
94
+
95
+ if (effect.amount) {
96
+ desc += ` - ${effect.amount}`;
97
+ }
98
+
99
+ if (effect.stats) {
100
+ const statChanges = Object.entries(effect.stats).map(([stat, change]) =>
101
+ `${stat}: ${change}`
102
+ ).join(', ');
103
+ desc += ` (${statChanges})`;
104
+ }
105
+
106
+ if (effect.status) {
107
+ desc += ` - ${effect.status}`;
108
+ }
109
+
110
+ return desc;
111
+ }
112
+
113
+ $: isOutOfPP = currentPP <= 0;
114
+ $: typeColor = getTypeColor(move.type);
115
+ </script>
116
+
117
+ <div class="move-display" class:disabled={isOutOfPP}>
118
+ <div class="move-header">
119
+ <div class="move-title-section">
120
+ <span class="type-emoji">{getTypeEmoji(move.type)}</span>
121
+ <div class="move-info">
122
+ <div class="move-name-row">
123
+ <span class="move-name">{move.name}</span>
124
+ {#if battleMove?.power && battleMove.power > 0}
125
+ <span class="power-badge">⚡{battleMove.power}</span>
126
+ {:else if move.power > 0}
127
+ <span class="power-badge">⚡{move.power}</span>
128
+ {/if}
129
+ </div>
130
+ <div class="move-type-badge" style="background-color: {typeColor}">
131
+ {move.type.toUpperCase()}
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <div class="move-stats">
137
+ {#if showPP}
138
+ <div class="pp-display" class:out-of-pp={isOutOfPP}>
139
+ <span class="pp-label">PP</span>
140
+ <span class="pp-value">{currentPP}/{move.pp}</span>
141
+ </div>
142
+ {/if}
143
+
144
+ {#if battleMove?.accuracy}
145
+ <div class="accuracy-display">
146
+ <span class="accuracy-label">ACC</span>
147
+ <span class="accuracy-value">{battleMove.accuracy}%</span>
148
+ </div>
149
+ {:else if move.accuracy}
150
+ <div class="accuracy-display">
151
+ <span class="accuracy-label">ACC</span>
152
+ <span class="accuracy-value">{move.accuracy}%</span>
153
+ </div>
154
+ {/if}
155
+ </div>
156
+ </div>
157
+
158
+ <div class="move-description">
159
+ {move.description}
160
+ </div>
161
+
162
+ {#if battleMove?.flags && battleMove.flags.length > 0}
163
+ <div class="move-flags">
164
+ {#each battleMove.flags as flag}
165
+ <span class="flag-badge">{flag}</span>
166
+ {/each}
167
+ </div>
168
+ {/if}
169
+
170
+ {#if expanded && battleMove?.effects && battleMove.effects.length > 0}
171
+ <div class="move-effects">
172
+ <h4 class="effects-title">
173
+ <span class="effects-icon">🎯</span>
174
+ Move Effects ({battleMove.effects.length})
175
+ </h4>
176
+ <div class="effects-list">
177
+ {#each battleMove.effects as effect}
178
+ <div class="effect-item">
179
+ <span
180
+ class="effect-icon"
181
+ style="color: {getEffectColor(effect.type)}"
182
+ >
183
+ {getEffectIcon(effect.type)}
184
+ </span>
185
+ <div class="effect-details">
186
+ <span class="effect-type">{effect.type}</span>
187
+ <span class="effect-description">{formatEffectDescription(effect)}</span>
188
+ </div>
189
+ </div>
190
+ {/each}
191
+ </div>
192
+ </div>
193
+ {/if}
194
+
195
+ {#if battleMove?.priority && battleMove.priority !== 0}
196
+ <div class="priority-indicator">
197
+ <span class="priority-icon">
198
+ {battleMove.priority > 0 ? '⏫' : '⏬'}
199
+ </span>
200
+ <span class="priority-text">
201
+ Priority: {battleMove.priority > 0 ? '+' : ''}{battleMove.priority}
202
+ </span>
203
+ </div>
204
+ {/if}
205
+ </div>
206
+
207
+ <style>
208
+ .move-display {
209
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
210
+ border: 1px solid #dee2e6;
211
+ border-radius: 12px;
212
+ padding: 14px;
213
+ margin: 8px 0;
214
+ transition: all 0.2s ease;
215
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
216
+ }
217
+
218
+ .move-display:hover {
219
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
220
+ transform: translateY(-1px);
221
+ }
222
+
223
+ .move-display.disabled {
224
+ opacity: 0.6;
225
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
226
+ }
227
+
228
+ .move-header {
229
+ display: flex;
230
+ justify-content: space-between;
231
+ align-items: flex-start;
232
+ gap: 12px;
233
+ margin-bottom: 8px;
234
+ }
235
+
236
+ .move-title-section {
237
+ display: flex;
238
+ align-items: flex-start;
239
+ gap: 12px;
240
+ flex: 1;
241
+ }
242
+
243
+ .type-emoji {
244
+ font-size: 20px;
245
+ margin-top: 2px;
246
+ }
247
+
248
+ .move-info {
249
+ flex: 1;
250
+ display: flex;
251
+ flex-direction: column;
252
+ gap: 4px;
253
+ }
254
+
255
+ .move-name-row {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 8px;
259
+ }
260
+
261
+ .move-name {
262
+ font-size: 16px;
263
+ font-weight: 600;
264
+ color: #495057;
265
+ }
266
+
267
+ .power-badge {
268
+ font-size: 12px;
269
+ padding: 2px 6px;
270
+ background: #ff6b35;
271
+ color: white;
272
+ border-radius: 8px;
273
+ font-weight: 600;
274
+ }
275
+
276
+ .move-type-badge {
277
+ display: inline-block;
278
+ padding: 2px 8px;
279
+ border-radius: 12px;
280
+ color: white;
281
+ font-size: 10px;
282
+ font-weight: 600;
283
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
284
+ align-self: flex-start;
285
+ }
286
+
287
+ .move-stats {
288
+ display: flex;
289
+ flex-direction: column;
290
+ gap: 4px;
291
+ align-items: flex-end;
292
+ }
293
+
294
+ .pp-display,
295
+ .accuracy-display {
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 4px;
299
+ font-size: 12px;
300
+ padding: 3px 8px;
301
+ border-radius: 8px;
302
+ background: rgba(0, 0, 0, 0.05);
303
+ }
304
+
305
+ .pp-display.out-of-pp {
306
+ background: #fee;
307
+ color: #d63031;
308
+ }
309
+
310
+ .pp-label,
311
+ .accuracy-label {
312
+ font-weight: 600;
313
+ color: #6c757d;
314
+ }
315
+
316
+ .pp-value,
317
+ .accuracy-value {
318
+ font-weight: 500;
319
+ color: #495057;
320
+ }
321
+
322
+ .move-description {
323
+ font-size: 14px;
324
+ color: #6c757d;
325
+ line-height: 1.4;
326
+ margin-bottom: 8px;
327
+ }
328
+
329
+ .move-flags {
330
+ display: flex;
331
+ gap: 4px;
332
+ flex-wrap: wrap;
333
+ margin-bottom: 8px;
334
+ }
335
+
336
+ .flag-badge {
337
+ font-size: 10px;
338
+ padding: 2px 6px;
339
+ background: #e3f2fd;
340
+ color: #1976d2;
341
+ border-radius: 8px;
342
+ font-weight: 500;
343
+ }
344
+
345
+ .move-effects {
346
+ margin-top: 12px;
347
+ padding-top: 12px;
348
+ border-top: 1px solid #e9ecef;
349
+ }
350
+
351
+ .effects-title {
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 6px;
355
+ font-size: 14px;
356
+ font-weight: 600;
357
+ color: #495057;
358
+ margin: 0 0 8px 0;
359
+ }
360
+
361
+ .effects-icon {
362
+ font-size: 14px;
363
+ }
364
+
365
+ .effects-list {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 6px;
369
+ }
370
+
371
+ .effect-item {
372
+ display: flex;
373
+ align-items: flex-start;
374
+ gap: 8px;
375
+ padding: 6px 10px;
376
+ background: rgba(0, 0, 0, 0.02);
377
+ border-radius: 6px;
378
+ }
379
+
380
+ .effect-icon {
381
+ font-size: 14px;
382
+ margin-top: 1px;
383
+ }
384
+
385
+ .effect-details {
386
+ flex: 1;
387
+ display: flex;
388
+ flex-direction: column;
389
+ gap: 1px;
390
+ }
391
+
392
+ .effect-type {
393
+ font-size: 11px;
394
+ font-weight: 600;
395
+ color: #495057;
396
+ text-transform: uppercase;
397
+ letter-spacing: 0.5px;
398
+ }
399
+
400
+ .effect-description {
401
+ font-size: 12px;
402
+ color: #6c757d;
403
+ line-height: 1.3;
404
+ }
405
+
406
+ .priority-indicator {
407
+ display: flex;
408
+ align-items: center;
409
+ gap: 6px;
410
+ margin-top: 8px;
411
+ padding: 4px 8px;
412
+ background: #fff3cd;
413
+ border: 1px solid #ffeaa7;
414
+ border-radius: 6px;
415
+ font-size: 12px;
416
+ }
417
+
418
+ .priority-icon {
419
+ font-size: 14px;
420
+ }
421
+
422
+ .priority-text {
423
+ font-weight: 500;
424
+ color: #856404;
425
+ }
426
+ </style>
src/lib/components/Piclets/PicletCard.svelte CHANGED
@@ -1,6 +1,7 @@
1
  <script lang="ts">
2
  import type { PicletInstance } from '$lib/db/schema';
3
  import { TYPE_DATA } from '$lib/types/picletTypes';
 
4
 
5
  interface Props {
6
  instance: PicletInstance;
@@ -19,6 +20,10 @@
19
 
20
  const typeColor = $derived(TYPE_DATA[instance.primaryType].color);
21
  const softTypeColor = $derived(`${typeColor}20`); // Add 20% opacity for soft background
 
 
 
 
22
  </script>
23
 
24
  <button
@@ -50,6 +55,23 @@
50
  ></div>
51
  </div>
52
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  </div>
54
  </button>
55
 
@@ -146,4 +168,57 @@
146
  border-radius: 1.5px;
147
  transition: width 0.3s ease;
148
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  </style>
 
1
  <script lang="ts">
2
  import type { PicletInstance } from '$lib/db/schema';
3
  import { TYPE_DATA } from '$lib/types/picletTypes';
4
+ import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
5
 
6
  interface Props {
7
  instance: PicletInstance;
 
20
 
21
  const typeColor = $derived(TYPE_DATA[instance.primaryType].color);
22
  const softTypeColor = $derived(`${typeColor}20`); // Add 20% opacity for soft background
23
+
24
+ // Get battle definition for enhanced ability info
25
+ $: battleDefinition = picletInstanceToBattleDefinition(instance);
26
+ $: ability = battleDefinition.specialAbility;
27
  </script>
28
 
29
  <button
 
55
  ></div>
56
  </div>
57
  </div>
58
+
59
+ <!-- Ability Preview -->
60
+ <div class="ability-preview">
61
+ <div class="ability-info">
62
+ <span class="ability-icon">✨</span>
63
+ <div class="ability-text">
64
+ <span class="ability-name">{ability.name}</span>
65
+ <div class="ability-counts">
66
+ {#if ability.effects?.length || ability.triggers?.length}
67
+ <span class="ability-count">
68
+ {(ability.effects?.length || 0) + (ability.triggers?.length || 0)} effects
69
+ </span>
70
+ {/if}
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
  </div>
76
  </button>
77
 
 
168
  border-radius: 1.5px;
169
  transition: width 0.3s ease;
170
  }
171
+
172
+ /* Ability Preview Styles */
173
+ .ability-preview {
174
+ margin-top: 4px;
175
+ padding: 4px 6px;
176
+ background: rgba(255, 255, 255, 0.9);
177
+ border-radius: 6px;
178
+ border: 1px solid rgba(0, 0, 0, 0.1);
179
+ }
180
+
181
+ .ability-info {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 4px;
185
+ }
186
+
187
+ .ability-icon {
188
+ font-size: 10px;
189
+ line-height: 1;
190
+ }
191
+
192
+ .ability-text {
193
+ flex: 1;
194
+ min-width: 0;
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 1px;
198
+ }
199
+
200
+ .ability-name {
201
+ font-size: 8px;
202
+ font-weight: 600;
203
+ color: #495057;
204
+ line-height: 1.2;
205
+ overflow: hidden;
206
+ text-overflow: ellipsis;
207
+ white-space: nowrap;
208
+ }
209
+
210
+ .ability-counts {
211
+ display: flex;
212
+ justify-content: flex-end;
213
+ }
214
+
215
+ .ability-count {
216
+ font-size: 7px;
217
+ padding: 1px 3px;
218
+ background: rgba(0, 123, 255, 0.1);
219
+ color: #0066cc;
220
+ border-radius: 4px;
221
+ font-weight: 500;
222
+ line-height: 1;
223
+ }
224
  </style>
src/lib/components/Piclets/PicletDetail.svelte CHANGED
@@ -5,6 +5,9 @@
5
  import { uiStore } from '$lib/stores/ui';
6
  import { downloadPicletCard } from '$lib/services/picletExport';
7
  import { TYPE_DATA } from '$lib/types/picletTypes';
 
 
 
8
 
9
  interface Props {
10
  instance: PicletInstance;
@@ -14,10 +17,14 @@
14
 
15
  let { instance, onClose, onDeleted }: Props = $props();
16
  let showDeleteConfirm = $state(false);
17
- let selectedTab = $state<'about' | 'stats' | 'actions'>('about');
18
  let expandedMoves = $state(new Set<number>());
 
19
  let isSharing = $state(false);
20
 
 
 
 
21
  // Type-based styling
22
  const typeData = $derived(TYPE_DATA[instance.primaryType]);
23
  const typeColor = $derived(typeData.color);
@@ -142,10 +149,17 @@
142
  </button>
143
  <button
144
  class="tab-button"
145
- class:active={selectedTab === 'actions'}
146
- onclick={() => selectedTab = 'actions'}
 
 
 
 
 
 
 
147
  >
148
- Actions
149
  </button>
150
  </div>
151
 
@@ -193,36 +207,35 @@
193
  </div>
194
  </div>
195
  </div>
196
- {:else if selectedTab === 'actions'}
197
  <div class="content-card">
198
- {#each instance.moves as move, index}
199
- <button
200
- class="move-card"
201
- onclick={() => toggleMoveExpanded(index)}
202
- >
203
- <div class="move-header">
204
- <span class="move-name">{move.name}</span>
205
- <div class="move-badges">
206
- {#if move.power > 0}
207
- <span class="power-badge">PWR {move.power}</span>
208
- {/if}
209
- </div>
210
- </div>
211
-
212
- <p class="move-description">{move.description}</p>
213
-
214
- <div class="move-stats">
215
- <span>Accuracy: {move.accuracy}%</span>
216
- <span>PP: {move.currentPp}/{move.pp}</span>
217
- </div>
218
-
219
- {#if expandedMoves.has(index)}
220
- <div class="move-details">
221
- <p>Type: {move.type}</p>
222
- </div>
223
- {/if}
224
- </button>
225
- {/each}
226
  </div>
227
  {/if}
228
  </div>
@@ -588,6 +601,56 @@
588
  width: 100%;
589
  }
590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  @media (min-width: 768px) {
592
  .detail-page {
593
  position: relative;
 
5
  import { uiStore } from '$lib/stores/ui';
6
  import { downloadPicletCard } from '$lib/services/picletExport';
7
  import { TYPE_DATA } from '$lib/types/picletTypes';
8
+ import AbilityDisplay from './AbilityDisplay.svelte';
9
+ import MoveDisplay from './MoveDisplay.svelte';
10
+ import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
11
 
12
  interface Props {
13
  instance: PicletInstance;
 
17
 
18
  let { instance, onClose, onDeleted }: Props = $props();
19
  let showDeleteConfirm = $state(false);
20
+ let selectedTab = $state<'about' | 'stats' | 'abilities' | 'moves'>('about');
21
  let expandedMoves = $state(new Set<number>());
22
+ let expandedAbility = $state(false);
23
  let isSharing = $state(false);
24
 
25
+ // Convert to battle definition to get enhanced ability data
26
+ $: battleDefinition = picletInstanceToBattleDefinition(instance);
27
+
28
  // Type-based styling
29
  const typeData = $derived(TYPE_DATA[instance.primaryType]);
30
  const typeColor = $derived(typeData.color);
 
149
  </button>
150
  <button
151
  class="tab-button"
152
+ class:active={selectedTab === 'abilities'}
153
+ onclick={() => selectedTab = 'abilities'}
154
+ >
155
+ Abilities
156
+ </button>
157
+ <button
158
+ class="tab-button"
159
+ class:active={selectedTab === 'moves'}
160
+ onclick={() => selectedTab = 'moves'}
161
  >
162
+ Moves
163
  </button>
164
  </div>
165
 
 
207
  </div>
208
  </div>
209
  </div>
210
+ {:else if selectedTab === 'abilities'}
211
  <div class="content-card">
212
+ <AbilityDisplay
213
+ ability={battleDefinition.specialAbility}
214
+ expanded={expandedAbility}
215
+ />
216
+
217
+ <button
218
+ class="expand-toggle"
219
+ onclick={() => expandedAbility = !expandedAbility}
220
+ >
221
+ {expandedAbility ? 'Show Less' : 'Show Details'}
222
+ </button>
223
+ </div>
224
+ {:else if selectedTab === 'moves'}
225
+ <div class="content-card">
226
+ <div class="moves-list">
227
+ {#each instance.moves as move, index}
228
+ <button
229
+ class="move-item"
230
+ onclick={() => toggleMoveExpanded(index)}
231
+ >
232
+ <MoveDisplay
233
+ {move}
234
+ expanded={expandedMoves.has(index)}
235
+ />
236
+ </button>
237
+ {/each}
238
+ </div>
 
239
  </div>
240
  {/if}
241
  </div>
 
601
  width: 100%;
602
  }
603
 
604
+ /* Enhanced ability and move display styles */
605
+ .expand-toggle {
606
+ width: 100%;
607
+ padding: 10px;
608
+ margin-top: 12px;
609
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
610
+ color: white;
611
+ border: none;
612
+ border-radius: 8px;
613
+ font-size: 14px;
614
+ font-weight: 500;
615
+ cursor: pointer;
616
+ transition: all 0.2s ease;
617
+ }
618
+
619
+ .expand-toggle:hover {
620
+ background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
621
+ transform: translateY(-1px);
622
+ box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
623
+ }
624
+
625
+ .expand-toggle:active {
626
+ transform: translateY(0);
627
+ }
628
+
629
+ .moves-list {
630
+ display: flex;
631
+ flex-direction: column;
632
+ gap: 4px;
633
+ }
634
+
635
+ .move-item {
636
+ background: none;
637
+ border: none;
638
+ padding: 0;
639
+ cursor: pointer;
640
+ width: 100%;
641
+ text-align: left;
642
+ border-radius: 8px;
643
+ transition: transform 0.2s ease;
644
+ }
645
+
646
+ .move-item:hover {
647
+ transform: translateY(-1px);
648
+ }
649
+
650
+ .move-item:active {
651
+ transform: translateY(0);
652
+ }
653
+
654
  @media (min-width: 768px) {
655
  .detail-page {
656
  position: relative;