| <script lang="ts"> |
| import type { SpecialAbility, BattleEffect, Trigger } from '$lib/battle-engine/types'; |
| |
| interface Props { |
| ability: SpecialAbility; |
| expanded?: boolean; |
| } |
| |
| let { ability, expanded = false }: Props = $props(); |
| |
| function getEffectIcon(effectType: string): string { |
| switch (effectType) { |
| case 'damage': return 'βοΈ'; |
| case 'modifyStats': return 'π'; |
| case 'applyStatus': return 'π«'; |
| case 'heal': return 'π'; |
| case 'manipulatePP': return 'β‘'; |
| case 'fieldEffect': return 'π'; |
| case 'counter': return 'π'; |
| case 'priority': return 'β‘'; |
| case 'removeStatus': return 'β¨'; |
| case 'mechanicOverride': return 'βοΈ'; |
| default: return 'β'; |
| } |
| } |
| |
| function getEffectColor(effectType: string): string { |
| switch (effectType) { |
| case 'damage': return '#ff6b6b'; |
| case 'modifyStats': return '#4dabf7'; |
| case 'applyStatus': return '#9775fa'; |
| case 'heal': return '#51cf66'; |
| case 'manipulatePP': return '#ffd43b'; |
| case 'fieldEffect': return '#495057'; |
| case 'counter': return '#fd7e14'; |
| case 'priority': return '#20c997'; |
| case 'removeStatus': return '#74c0fc'; |
| case 'mechanicOverride': return '#868e96'; |
| default: return '#adb5bd'; |
| } |
| } |
| |
| function getTriggerIcon(event: string): string { |
| switch (event) { |
| case 'onDamageTaken': return 'π‘οΈ'; |
| case 'onDamageDealt': return 'βοΈ'; |
| case 'onContactDamage': return 'π'; |
| case 'onCriticalHit': return 'π₯'; |
| case 'endOfTurn': return 'π'; |
| case 'onLowHP': return 'β€οΈ'; |
| case 'onStatusInflicted': return 'π«'; |
| case 'onHPDrained': return 'π©Έ'; |
| case 'onKO': return 'π'; |
| case 'onSwitchIn': return 'β‘οΈ'; |
| case 'onSwitchOut': return 'β¬
οΈ'; |
| case 'beforeMoveUse': return 'β°'; |
| case 'afterMoveUse': return 'β
'; |
| case 'onFullHP': return 'π'; |
| case 'onOpponentContactMove': return 'π€'; |
| case 'onStatChange': return 'π'; |
| case 'onTypeChange': return 'π'; |
| default: return 'β‘'; |
| } |
| } |
| |
| function formatEffectDescription(effect: BattleEffect): string { |
| let desc = effect.type.charAt(0).toUpperCase() + effect.type.slice(1); |
| |
| |
| if ('target' in effect && effect.target !== 'self') { |
| desc += ` (${effect.target})`; |
| } |
| |
| |
| if ('condition' in effect && effect.condition) { |
| desc += ` when ${effect.condition}`; |
| } |
| |
| |
| switch (effect.type) { |
| case 'damage': |
| if (effect.amount) desc += ` - ${effect.amount}`; |
| if (effect.formula) desc += ` (${effect.formula})`; |
| break; |
| case 'modifyStats': |
| const statChanges = Object.entries(effect.stats).map(([stat, change]) => |
| `${stat}: ${change}` |
| ).join(', '); |
| desc += ` (${statChanges})`; |
| break; |
| case 'applyStatus': |
| desc += ` - ${effect.status}`; |
| if (effect.chance && effect.chance < 100) desc += ` (${effect.chance}%)`; |
| break; |
| case 'heal': |
| if (effect.amount) desc += ` - ${effect.amount}`; |
| break; |
| case 'manipulatePP': |
| desc += ` - ${effect.action}`; |
| if (effect.amount) desc += ` ${effect.amount}`; |
| break; |
| case 'fieldEffect': |
| desc += ` - ${effect.effect}`; |
| if (effect.stackable) desc += ` (stackable)`; |
| break; |
| case 'counter': |
| desc += `(${effect.strength})`; |
| break; |
| case 'removeStatus': |
| desc += ` - ${effect.status}`; |
| break; |
| case 'mechanicOverride': |
| desc += ` - ${effect.mechanic}`; |
| break; |
| } |
| |
| return desc; |
| } |
| </script> |
|
|
| <div class="ability-display"> |
| <div class="ability-header"> |
| <div class="ability-name-section"> |
| <span class="ability-icon">β¨</span> |
| <div class="ability-info"> |
| <h3 class="ability-name">{ability.name}</h3> |
| <p class="ability-description">{ability.description}</p> |
| </div> |
| </div> |
| |
| </div> |
| |
| {#if expanded && ability.triggers?.length} |
| {@const trigger = ability.triggers[0]} |
| <div class="ability-details"> |
| <div class="triggers-section"> |
| <h4 class="section-title"> |
| <span class="section-icon">β‘</span> |
| Trigger |
| </h4> |
| <div class="trigger-item"> |
| <div class="trigger-header"> |
| <span class="trigger-icon">{getTriggerIcon(trigger.event)}</span> |
| <div class="trigger-info"> |
| <span class="trigger-event">{trigger.event}</span> |
| {#if trigger.condition} |
| <span class="trigger-condition">when {trigger.condition}</span> |
| {/if} |
| </div> |
| </div> |
| |
| {#if trigger.effects?.length} |
| <div class="trigger-effects"> |
| {#each trigger.effects as effect} |
| <div class="trigger-effect"> |
| <span |
| class="effect-icon small" |
| style="color: {getEffectColor(effect.type)}" |
| > |
| {getEffectIcon(effect.type)} |
| </span> |
| <span class="effect-summary">{formatEffectDescription(effect)}</span> |
| </div> |
| {/each} |
| </div> |
| {/if} |
| </div> |
| </div> |
| </div> |
| {/if} |
| </div> |
|
|
| <style> |
| .ability-display { |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); |
| border: 1px solid #dee2e6; |
| border-radius: 12px; |
| padding: 16px; |
| margin: 8px 0; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| } |
| |
| .ability-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: flex-start; |
| gap: 12px; |
| } |
| |
| .ability-name-section { |
| display: flex; |
| align-items: flex-start; |
| gap: 12px; |
| flex: 1; |
| } |
| |
| .ability-icon { |
| font-size: 24px; |
| margin-top: 2px; |
| } |
| |
| .ability-info { |
| flex: 1; |
| } |
| |
| .ability-name { |
| font-size: 18px; |
| font-weight: 600; |
| color: #495057; |
| margin: 0 0 4px 0; |
| } |
| |
| .ability-description { |
| font-size: 14px; |
| color: #6c757d; |
| margin: 0; |
| line-height: 1.4; |
| } |
| |
| |
| .ability-details { |
| margin-top: 16px; |
| padding-top: 16px; |
| border-top: 1px solid #e9ecef; |
| } |
| |
| .section-title { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 14px; |
| font-weight: 600; |
| color: #495057; |
| margin: 0 0 12px 0; |
| } |
| |
| .section-icon { |
| font-size: 16px; |
| } |
| |
| |
| |
| .trigger-item { |
| background: rgba(255, 255, 255, 0.7); |
| border: 1px solid rgba(0, 0, 0, 0.08); |
| border-radius: 8px; |
| padding: 12px; |
| } |
| |
| .trigger-header { |
| display: flex; |
| align-items: flex-start; |
| gap: 10px; |
| margin-bottom: 8px; |
| } |
| |
| .trigger-icon { |
| font-size: 16px; |
| margin-top: 1px; |
| } |
| |
| .trigger-info { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| gap: 2px; |
| } |
| |
| .trigger-event { |
| font-size: 13px; |
| font-weight: 600; |
| color: #495057; |
| } |
| |
| .trigger-condition { |
| font-size: 12px; |
| color: #868e96; |
| font-style: italic; |
| } |
| |
| .trigger-effects { |
| display: flex; |
| flex-direction: column; |
| gap: 6px; |
| padding-left: 26px; |
| } |
| |
| .trigger-effect { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .effect-summary { |
| font-size: 12px; |
| color: #6c757d; |
| line-height: 1.3; |
| } |
| </style> |