feat: Uncapped hit decision tree, x-check workflow, baserunner UI #8

Merged
cal merged 11 commits from feature/uncapped-hit-decision-tree into main 2026-02-12 15:37:34 +00:00
Showing only changes of commit 6b86afe5e4 - Show all commits

View File

@ -45,74 +45,47 @@
<!-- EXPANDED ROW: Runner card + Catcher card side by side (when a runner or catcher is selected) -->
<Transition name="expand">
<div v-if="hasSelection" class="mt-3">
<!-- Catcher only view (when catcher selected) -->
<div v-if="selectedRunner === 'catcher'" class="flex justify-center">
<div class="matchup-card bg-gradient-to-b from-green-900 to-green-950 border-2 border-green-600 rounded-xl overflow-hidden shadow-lg max-w-md w-full">
<div class="bg-green-800/80 px-3 py-2 flex items-center gap-2 text-white text-sm font-semibold">
<span class="font-bold text-white/90" :style="{ color: fieldingTeamColor }">
{{ fieldingTeamAbbrev }}
</span>
<span class="text-white/70">C</span>
<span class="truncate flex-1 text-right font-bold">{{ catcherName }}</span>
</div>
<div class="p-0">
<img
v-if="catcherPlayer?.image"
:src="catcherPlayer.image"
:alt="`${catcherName} card`"
class="w-full h-auto"
>
<div v-else class="w-full aspect-[4/5.5] bg-gradient-to-br from-green-700 to-green-900 flex items-center justify-center">
<span class="text-5xl font-bold text-white/60">{{ getCatcherInitials }}</span>
</div>
<div v-if="hasSelection && displayedRunnerPlayer" class="mt-3 grid grid-cols-1 md:grid-cols-2 gap-3">
<!-- Lead/Selected Runner full card -->
<div class="matchup-card-blue bg-gradient-to-b from-blue-900 to-blue-950 border-2 border-blue-600 rounded-xl overflow-hidden shadow-lg">
<div class="bg-blue-800/80 px-3 py-2 flex items-center gap-2 text-white text-sm font-semibold">
<span class="font-bold text-white/90" :style="{ color: battingTeamColor }">
{{ battingTeamAbbrev }}
</span>
<span class="text-white/70">{{ selectedBase }}</span>
<span class="truncate flex-1 text-right font-bold">{{ selectedRunnerName }}</span>
</div>
<div class="p-0">
<img
v-if="displayedRunnerPlayer?.image"
:src="displayedRunnerPlayer.image"
:alt="`${selectedRunnerName} card`"
class="w-full h-auto"
>
<div v-else class="w-full aspect-[4/5.5] bg-gradient-to-br from-blue-700 to-blue-900 flex items-center justify-center">
<span class="text-5xl font-bold text-white/60">{{ selectedRunnerInitials }}</span>
</div>
</div>
</div>
<!-- Runner + Catcher view (when runner selected) -->
<div v-else-if="selectedRunnerPlayer" class="grid grid-cols-1 md:grid-cols-2 gap-3">
<!-- Selected Runner full card -->
<div class="matchup-card-blue bg-gradient-to-b from-blue-900 to-blue-950 border-2 border-blue-600 rounded-xl overflow-hidden shadow-lg">
<div class="bg-blue-800/80 px-3 py-2 flex items-center gap-2 text-white text-sm font-semibold">
<span class="font-bold text-white/90" :style="{ color: battingTeamColor }">
{{ battingTeamAbbrev }}
</span>
<span class="text-white/70">{{ selectedBase }}</span>
<span class="truncate flex-1 text-right font-bold">{{ selectedRunnerName }}</span>
</div>
<div class="p-0">
<img
v-if="selectedRunnerPlayer?.image"
:src="selectedRunnerPlayer.image"
:alt="`${selectedRunnerName} card`"
class="w-full h-auto"
>
<div v-else class="w-full aspect-[4/5.5] bg-gradient-to-br from-blue-700 to-blue-900 flex items-center justify-center">
<span class="text-5xl font-bold text-white/60">{{ selectedRunnerInitials }}</span>
</div>
</div>
<!-- Catcher full card -->
<div class="matchup-card bg-gradient-to-b from-green-900 to-green-950 border-2 border-green-600 rounded-xl overflow-hidden shadow-lg">
<div class="bg-green-800/80 px-3 py-2 flex items-center gap-2 text-white text-sm font-semibold">
<span class="font-bold text-white/90" :style="{ color: fieldingTeamColor }">
{{ fieldingTeamAbbrev }}
</span>
<span class="text-white/70">C</span>
<span class="truncate flex-1 text-right font-bold">{{ catcherName }}</span>
</div>
<!-- Catcher full card -->
<div class="matchup-card bg-gradient-to-b from-green-900 to-green-950 border-2 border-green-600 rounded-xl overflow-hidden shadow-lg">
<div class="bg-green-800/80 px-3 py-2 flex items-center gap-2 text-white text-sm font-semibold">
<span class="font-bold text-white/90" :style="{ color: fieldingTeamColor }">
{{ fieldingTeamAbbrev }}
</span>
<span class="text-white/70">C</span>
<span class="truncate flex-1 text-right font-bold">{{ catcherName }}</span>
</div>
<div class="p-0">
<img
v-if="catcherPlayer?.image"
:src="catcherPlayer.image"
:alt="`${catcherName} card`"
class="w-full h-auto"
>
<div v-else class="w-full aspect-[4/5.5] bg-gradient-to-br from-green-700 to-green-900 flex items-center justify-center">
<span class="text-5xl font-bold text-white/60">{{ getCatcherInitials }}</span>
</div>
<div class="p-0">
<img
v-if="catcherPlayer?.image"
:src="catcherPlayer.image"
:alt="`${catcherName} card`"
class="w-full h-auto"
>
<div v-else class="w-full aspect-[4/5.5] bg-gradient-to-br from-green-700 to-green-900 flex items-center justify-center">
<span class="text-5xl font-bold text-white/60">{{ getCatcherInitials }}</span>
</div>
</div>
</div>
@ -202,10 +175,26 @@ const getCatcherInitials = computed(() => {
return catcherPlayer.value.name.substring(0, 2).toUpperCase()
})
// Lead runner determination (for catcher-only selection)
const leadRunnerBase = computed(() => {
if (props.runners.third) return 'third'
if (props.runners.second) return 'second'
if (props.runners.first) return 'first'
return null
})
// Displayed runner: selected runner OR lead runner (when catcher selected)
const displayedRunnerBase = computed(() => {
if (selectedRunner.value === 'catcher') {
return leadRunnerBase.value
}
return selectedRunner.value
})
// Selected runner data resolution (mirrors catcher pattern)
const selectedRunnerState = computed(() => {
if (!selectedRunner.value) return null
return props.runners[selectedRunner.value]
if (!displayedRunnerBase.value || displayedRunnerBase.value === 'catcher') return null
return props.runners[displayedRunnerBase.value]
})
const selectedRunnerLineup = computed(() => {
@ -215,11 +204,14 @@ const selectedRunnerLineup = computed(() => {
const selectedRunnerPlayer = computed(() => selectedRunnerLineup.value?.player ?? null)
// For display: use displayedRunnerBase instead of selectedRunner
const displayedRunnerPlayer = computed(() => selectedRunnerPlayer.value)
const selectedRunnerName = computed(() => selectedRunnerPlayer.value?.name ?? 'Unknown Runner')
const selectedBase = computed(() => {
if (!selectedRunner.value) return ''
return baseNameToLabel[selectedRunner.value] ?? ''
if (!displayedRunnerBase.value || displayedRunnerBase.value === 'catcher') return ''
return baseNameToLabel[displayedRunnerBase.value] ?? ''
})
const selectedRunnerInitials = computed(() => {