Runner highlights and cards: - Pills: red-500 ring, red-50 background when selected - Full cards: red gradient (red-900 to red-950), red-600 border - Pulse glow: red animation (rgba(239, 68, 68)) - Hardcoded red color (#ef4444) for runner pill borders Catcher highlights and cards: - Pill: blue-500 ring, blue-50 background when selected - Full card: blue gradient (blue-900 to blue-950), blue-600 border - Pulse glow: blue animation (rgba(59, 130, 246)) Updated tests to expect new colors All 15 RunnersOnBase tests passing All 16 RunnerCard tests passing
109 lines
3.0 KiB
Vue
109 lines
3.0 KiB
Vue
<template>
|
|
<div
|
|
:class="[
|
|
'runner-pill',
|
|
runner ? 'occupied' : 'empty',
|
|
isSelected ? 'selected' : ''
|
|
]"
|
|
@click="handleClick"
|
|
>
|
|
<template v-if="runner">
|
|
<!-- Occupied base -->
|
|
<div class="w-8 h-8 rounded-full flex-shrink-0 overflow-hidden border-2" :style="{ borderColor: teamColor }">
|
|
<img
|
|
v-if="runnerPlayer?.headshot || runnerPlayer?.image"
|
|
:src="runnerPlayer.headshot || runnerPlayer.image"
|
|
:alt="runnerName"
|
|
class="w-full h-full object-cover"
|
|
>
|
|
<div v-else class="w-full h-full bg-gray-300 flex items-center justify-center text-gray-600 font-bold text-xs">
|
|
{{ base }}
|
|
</div>
|
|
</div>
|
|
<div class="ml-2 flex-1 min-w-0">
|
|
<div class="text-xs font-bold text-gray-900 truncate">{{ runnerName }}</div>
|
|
<div class="text-[10px] text-gray-500">{{ base }}</div>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<!-- Empty base -->
|
|
<div class="w-8 h-8 rounded-full bg-gray-200 border-2 border-dashed border-gray-400 flex items-center justify-center flex-shrink-0">
|
|
<span class="text-gray-400 text-[10px] font-bold">{{ base }}</span>
|
|
</div>
|
|
<div class="ml-2 text-xs text-gray-400">Empty</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import type { LineupPlayerState } from '~/types/game'
|
|
import { useGameStore } from '~/store/game'
|
|
|
|
interface Props {
|
|
base: '1B' | '2B' | '3B'
|
|
runner: LineupPlayerState | null
|
|
isSelected: boolean
|
|
teamColor: string
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<{
|
|
click: []
|
|
}>()
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
// Resolve player data from lineup
|
|
const runnerPlayer = computed(() => {
|
|
if (!props.runner) return null
|
|
const lineupEntry = gameStore.findPlayerInLineup(props.runner.lineup_id)
|
|
return lineupEntry?.player ?? null
|
|
})
|
|
|
|
const runnerName = computed(() => {
|
|
if (!runnerPlayer.value) return 'Unknown Runner'
|
|
return runnerPlayer.value.name
|
|
})
|
|
|
|
const runnerNumber = computed(() => {
|
|
// Try to extract jersey number from player data if available
|
|
// For now, default to a placeholder based on lineup_id
|
|
return props.runner?.lineup_id?.toString().padStart(2, '0') ?? '00'
|
|
})
|
|
|
|
const getRunnerInitials = computed(() => {
|
|
if (!runnerPlayer.value) return '?'
|
|
const parts = runnerPlayer.value.name.split(' ')
|
|
if (parts.length >= 2) {
|
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
|
|
}
|
|
return runnerPlayer.value.name.substring(0, 2).toUpperCase()
|
|
})
|
|
|
|
function handleClick() {
|
|
if (props.runner) {
|
|
emit('click')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.runner-pill {
|
|
@apply flex items-center p-2 rounded-lg bg-white border border-gray-200
|
|
shadow-sm transition-all duration-200 cursor-default;
|
|
}
|
|
|
|
.runner-pill.occupied {
|
|
@apply cursor-pointer hover:bg-red-50;
|
|
}
|
|
|
|
.runner-pill.selected {
|
|
@apply ring-2 ring-red-500 bg-red-50 shadow-md;
|
|
}
|
|
|
|
.runner-pill.empty {
|
|
@apply bg-gray-50 opacity-60;
|
|
}
|
|
</style>
|