strat-gameplay-webapp/docs/runner-display-mockups.html
Cal Corum 5d20d84568 CLAUDE: Add RunnersOnBase component with expanding cards and runner/catcher matchup
- Created RunnersOnBase.vue component with split layout (runners left, catcher right)
- Created RunnerCard.vue for individual runner cards with expand-in-place functionality
- Integrated component into GamePlay.vue (mobile and desktop layouts)
- Added team color computed properties for batting/fielding teams
- Component only shows when runners on base (hasRunners computed)
- Click runner card to expand in place and show full player card + catcher matchup
- Smooth CSS transitions and animations matching pitcher/batter card style
- Includes design documentation and HTML mockup for reference
2026-02-06 19:13:52 -06:00

626 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Runner Display Mockups - Options 1, 3, & 4</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Option 1 Styles */
.runner-card-option1 {
transition: all 0.2s ease;
}
.runner-card-option1:hover {
transform: scale(1.05);
}
.runner-card-option1.occupied {
cursor: pointer;
}
/* Option 3 Styles */
.runner-card-option3 {
transition: all 0.2s ease;
}
.runner-card-option3:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0,0,0,0.15);
}
/* Option 4 Styles */
.runner-card-option4 {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.runner-card-option4.empty {
cursor: default;
}
.runner-card-option4.occupied {
cursor: pointer;
}
.runner-card-option4.occupied:hover:not(.expanded) {
transform: translateX(4px);
background: rgba(255, 255, 255, 0.95);
}
/* Expanded state - runner card grows */
.runner-card-option4.expanded {
transform: scale(1.02);
z-index: 10;
}
/* Pulse animation for occupied bases */
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); }
50% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0); }
}
.pulse-occupied {
animation: pulse-glow 2s ease-in-out infinite;
}
/* Slide-in animation for Option 4 */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.slide-in {
animation: slideInRight 0.3s ease-out;
}
/* Matchup card styles */
.matchup-card {
transition: all 0.3s ease;
}
@keyframes pulseGlowBlue {
0%, 100% {
box-shadow: 0 0 15px 2px rgba(59, 130, 246, 0.5), 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
50% {
box-shadow: 0 0 30px 8px rgba(59, 130, 246, 0.7), 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
}
@keyframes pulseGlowGreen {
0%, 100% {
box-shadow: 0 0 15px 2px rgba(16, 185, 129, 0.5), 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
50% {
box-shadow: 0 0 30px 8px rgba(16, 185, 129, 0.7), 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}
}
/* Expand animation */
.expand-height {
overflow: hidden;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Card image fade in */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}
</style>
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-6xl mx-auto">
<!-- Header -->
<div class="mb-8">
<h1 class="text-4xl font-bold text-gray-900 mb-2">Runner Display Mockups</h1>
<p class="text-gray-600">Interactive mockups of Options 1, 3, and 4 for runners on base display</p>
</div>
<!-- Toggle Scenarios -->
<div class="mb-8 bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Test Scenarios</h2>
<div class="flex flex-wrap gap-3">
<button onclick="setScenario('empty')" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg font-medium transition">
All Empty
</button>
<button onclick="setScenario('single')" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition">
Runner on 2B
</button>
<button onclick="setScenario('double')" class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition">
Runners on 1B & 3B
</button>
<button onclick="setScenario('loaded')" class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg font-medium transition">
Bases Loaded
</button>
</div>
</div>
<!-- OPTION 1: Horizontal Runner Card Bar -->
<section class="mb-12 bg-white rounded-xl shadow-lg p-8">
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-900 mb-2">Option 1: Horizontal Runner Card Bar</h2>
<p class="text-gray-600">Three equal-width cards showing all bases with equal visual weight</p>
</div>
<div class="bg-gradient-to-b from-gray-50 to-gray-100 rounded-lg p-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4 uppercase tracking-wide">Runners on Base</h3>
<!-- Desktop View -->
<div class="hidden md:grid md:grid-cols-3 gap-4" id="option1-desktop">
<!-- Cards will be inserted here -->
</div>
<!-- Mobile View -->
<div class="md:hidden space-y-3" id="option1-mobile">
<!-- Cards will be inserted here -->
</div>
</div>
<!-- Pros/Cons -->
<div class="mt-6 grid md:grid-cols-2 gap-4">
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 class="font-semibold text-green-800 mb-2">✅ Pros</h4>
<ul class="text-sm text-green-700 space-y-1">
<li>• Very compact (single row)</li>
<li>• Clear visual distinction</li>
<li>• Easy touch targets</li>
<li>• Balanced layout</li>
</ul>
</div>
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<h4 class="font-semibold text-red-800 mb-2">❌ Cons</h4>
<ul class="text-sm text-red-700 space-y-1">
<li>• Always shows all 3 cards</li>
<li>• May feel cluttered when bases empty</li>
<li>• Less visual hierarchy</li>
</ul>
</div>
</div>
</section>
<!-- OPTION 3: Stacked Runner Cards -->
<section class="mb-12 bg-white rounded-xl shadow-lg p-8">
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-900 mb-2">Option 3: Stacked Runner Cards</h2>
<p class="text-gray-600">Only occupied bases get prominent cards, empty bases are minimal chips</p>
</div>
<div class="bg-gradient-to-b from-blue-50 to-blue-100 rounded-lg p-6">
<h3 class="text-sm font-semibold text-gray-700 mb-4 uppercase tracking-wide">Runners on Base</h3>
<div class="flex flex-wrap gap-3" id="option3-container">
<!-- Cards will be inserted here -->
</div>
</div>
<!-- Pros/Cons -->
<div class="mt-6 grid md:grid-cols-2 gap-4">
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 class="font-semibold text-green-800 mb-2">✅ Pros</h4>
<ul class="text-sm text-green-700 space-y-1">
<li>• Optimizes for common case (0-1 runners)</li>
<li>• Clear visual hierarchy</li>
<li>• Easy card access with button</li>
<li>• Mobile-first design</li>
<li>• Professional card-based UI</li>
</ul>
</div>
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<h4 class="font-semibold text-red-808 mb-2">❌ Cons</h4>
<ul class="text-sm text-red-700 space-y-1">
<li>• More horizontal space with 3 runners</li>
<li>• Requires player headshots</li>
</ul>
</div>
</div>
</section>
<!-- OPTION 4: Expanding Runner Cards with Matchup (NEW - RECOMMENDED) -->
<section class="mb-12 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl shadow-lg p-8 border-2 border-purple-200">
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-900 mb-2">
Option 4: Expanding Runner Cards with Matchup
<span class="ml-2 text-sm bg-purple-600 text-white px-3 py-1 rounded-full">NEW RECOMMENDATION</span>
</h2>
<p class="text-gray-600">
Runners list on left, catcher on right. <strong>Click a runner to expand it in place</strong> and show full card + catcher matchup.
<strong>Only visible when runners on base.</strong>
</p>
</div>
<!-- Container with split layout -->
<div id="option4-container" class="bg-white rounded-lg shadow-md overflow-hidden">
<!-- Split Layout: Runners List | Catcher Card -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-0">
<!-- LEFT: Runners List (with expandable cards) -->
<div class="border-r border-gray-200 p-4">
<h3 class="text-xs font-semibold text-gray-500 mb-3 uppercase tracking-wide">Runners on Base</h3>
<div id="option4-list" class="space-y-2">
<!-- Runner cards will be inserted here -->
</div>
<!-- Hidden state message -->
<div id="option4-hidden" class="hidden text-center py-6 text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mx-auto mb-2 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<p class="text-sm">No runners on base</p>
<p class="text-xs text-gray-400 mt-1">Component hidden</p>
</div>
</div>
<!-- RIGHT: Catcher Card -->
<div class="p-4 bg-gray-50" id="option4-catcher-container">
<h3 class="text-xs font-semibold text-gray-500 mb-3 uppercase tracking-wide">Catcher</h3>
<!-- Collapsed state - minimal card -->
<div id="catcher-collapsed" class="bg-white border-l-4 border-gray-600 rounded-lg p-3 shadow-sm transition-all duration-300">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-full overflow-hidden border-2 border-gray-600 flex-shrink-0">
<img src="https://via.placeholder.com/80/6b7280/ffffff?text=C" class="w-full h-full object-cover">
</div>
<div class="flex-1">
<div class="text-sm font-bold text-gray-900">Buster Posey</div>
<div class="text-xs text-gray-600">#28 • C</div>
</div>
</div>
<div class="mt-3 text-xs text-gray-500 text-center">
Click a runner to see matchup →
</div>
</div>
<!-- Expanded state - full card (hidden by default) -->
<div id="catcher-expanded" class="hidden matchup-card bg-gradient-to-b from-green-900 to-green-950 border-2 border-green-600 rounded-xl overflow-hidden shadow-lg fade-in" style="animation: pulseGlowGreen 2s ease-in-out infinite;">
<!-- Card Header -->
<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">CATCHER</span>
<span class="text-white/70">C</span>
<span class="truncate flex-1 text-right font-bold">Buster Posey</span>
</div>
<!-- Card Image -->
<div class="p-0">
<img src="https://via.placeholder.com/400x550/6b7280/ffffff?text=Catcher+Card" class="w-full h-auto">
</div>
</div>
</div>
</div>
</div>
<!-- Pros/Cons -->
<div class="mt-6 grid md:grid-cols-2 gap-4">
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<h4 class="font-semibold text-green-800 mb-2">✅ Pros</h4>
<ul class="text-sm text-green-700 space-y-1">
<li><strong>Expands in place</strong> - no jarring screen flip</li>
<li><strong>Smooth transition</strong> - runner card grows to show full player card</li>
<li>• Catcher card appears simultaneously for matchup context</li>
<li>• Only shows when needed (on_base_code > 0)</li>
<li>• Consistent with pitcher vs batter styling</li>
<li>• Clear visual hierarchy</li>
</ul>
</div>
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<h4 class="font-semibold text-red-800 mb-2">❌ Cons</h4>
<ul class="text-sm text-red-700 space-y-1">
<li>• Requires extra click to see full cards</li>
<li>• Slightly more complex animation</li>
</ul>
</div>
</div>
<!-- Implementation Notes -->
<div class="mt-6 bg-purple-100 border border-purple-300 rounded-lg p-4">
<h4 class="font-semibold text-purple-900 mb-2">💡 Implementation Notes</h4>
<ul class="text-sm text-purple-800 space-y-1">
<li>• Component only renders when <code class="bg-purple-200 px-1 rounded">thisPlay.on_base_code > 0</code></li>
<li>• Default: Runners list (left) with compact cards + Catcher summary (right)</li>
<li>• Click runner → Runner card expands in place to show full player card</li>
<li>• Simultaneously, catcher card expands to show full matchup</li>
<li>• Click again or click another runner to collapse/switch</li>
<li>• Smooth height transition using CSS max-height animation</li>
</ul>
</div>
</section>
<!-- Comparison -->
<section class="bg-gradient-to-r from-purple-600 to-indigo-600 text-white rounded-xl shadow-lg p-8">
<h2 class="text-2xl font-bold mb-4">Final Recommendation: Option 4</h2>
<div class="space-y-2 text-lg">
<p>🎯 <strong>Smooth in-place expansion</strong> - Runner card grows to reveal full player card</p>
<p>🔄 <strong>Integrated matchup view</strong> - Catcher card appears alongside for comparison</p>
<p>👁️ <strong>No screen flipping</strong> - Everything expands/collapses in the same layout</p>
<p>📱 <strong>Mobile-first design</strong> - Works seamlessly on all screen sizes</p>
<p><strong>Strategic context</strong> - See runner vs catcher for steal decisions</p>
<p><strong>Clean UX</strong> - Compact by default, detailed on demand</p>
</div>
</section>
</div>
<script>
// Sample player data
const players = {
first: { name: 'Mike Trout', number: '27', team: 'blue', headshot: 'https://via.placeholder.com/80/3b82f6/ffffff?text=MT' },
second: { name: 'Aaron Judge', number: '99', team: 'red', headshot: 'https://via.placeholder.com/80/ef4444/ffffff?text=AJ' },
third: { name: 'Shohei Ohtani', number: '17', team: 'green', headshot: 'https://via.placeholder.com/80/10b981/ffffff?text=SO' }
};
const teamColors = {
blue: { border: '#3b82f6', bg: '#dbeafe' },
red: { border: '#ef4444', bg: '#fee2e2' },
green: { border: '#10b981', bg: '#d1fae5' }
};
let currentScenario = {
first: false,
second: false,
third: false
};
let selectedRunner = null;
function setScenario(type) {
switch(type) {
case 'empty':
currentScenario = { first: false, second: false, third: false };
break;
case 'single':
currentScenario = { first: false, second: true, third: false };
break;
case 'double':
currentScenario = { first: true, second: false, third: true };
break;
case 'loaded':
currentScenario = { first: true, second: true, third: true };
break;
}
selectedRunner = null;
updateDisplay();
}
function selectRunner(base) {
if (!currentScenario[base]) return; // Can't select empty base
// Toggle selection
if (selectedRunner === base) {
selectedRunner = null;
} else {
selectedRunner = base;
}
updateOption4();
}
function updateDisplay() {
updateOption1();
updateOption3();
updateOption4();
}
function updateOption1() {
const desktopContainer = document.getElementById('option1-desktop');
const mobileContainer = document.getElementById('option1-mobile');
desktopContainer.innerHTML = '';
mobileContainer.innerHTML = '';
['first', 'second', 'third'].forEach(base => {
const baseLabel = base === 'first' ? '1ST' : base === 'second' ? '2ND' : '3RD';
const player = players[base];
const color = teamColors[player.team];
// Desktop card
const desktopCard = document.createElement('div');
if (currentScenario[base]) {
desktopCard.className = 'runner-card-option1 occupied bg-white rounded-lg border-2 p-4 text-center pulse-occupied';
desktopCard.style.borderColor = color.border;
desktopCard.style.backgroundColor = color.bg;
desktopCard.innerHTML = `
<div class="text-xs font-bold mb-2" style="color: ${color.border}">${baseLabel} BASE</div>
<div class="relative w-16 h-16 mx-auto mb-2">
<img src="${player.headshot}" class="w-full h-full rounded-full border-2 object-cover" style="border-color: ${color.border}">
<div class="absolute -top-1 -right-1 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-white" style="background: ${color.border}">
${base === 'first' ? '1' : base === 'second' ? '2' : '3'}
</div>
</div>
<div class="text-sm font-bold text-gray-900">${player.name}</div>
<div class="text-xs text-gray-600">#${player.number}</div>
`;
} else {
desktopCard.className = 'runner-card-option1 bg-white rounded-lg border-2 border-gray-200 p-4 text-center';
desktopCard.innerHTML = `
<div class="text-xs font-bold text-gray-500 mb-2">${baseLabel} BASE</div>
<div class="w-16 h-16 mx-auto mb-2 rounded-full bg-gray-100 border-2 border-dashed border-gray-300 flex items-center justify-center">
<span class="text-gray-400 text-2xl">—</span>
</div>
<div class="text-sm text-gray-400 font-medium">Empty</div>
`;
}
desktopContainer.appendChild(desktopCard);
// Mobile card
const mobileCard = document.createElement('div');
if (currentScenario[base]) {
mobileCard.className = 'runner-card-option1 occupied bg-white rounded-lg border-2 p-3 flex items-center';
mobileCard.style.borderColor = color.border;
mobileCard.style.backgroundColor = color.bg;
mobileCard.innerHTML = `
<div class="text-xs font-bold w-12" style="color: ${color.border}">${baseLabel}</div>
<img src="${player.headshot}" class="w-12 h-12 rounded-full border-2 object-cover" style="border-color: ${color.border}">
<div class="ml-3 flex-1">
<div class="text-sm font-bold text-gray-900">${player.name}</div>
<div class="text-xs text-gray-600">#${player.number}</div>
</div>
`;
} else {
mobileCard.className = 'runner-card-option1 bg-white rounded-lg border-2 border-gray-200 p-3 flex items-center';
mobileCard.innerHTML = `
<div class="text-xs font-bold text-gray-500 w-12">${baseLabel}</div>
<div class="w-12 h-12 rounded-full bg-gray-100 border-2 border-dashed border-gray-300 flex items-center justify-center">
<span class="text-gray-400 text-xl">—</span>
</div>
<div class="ml-3 text-sm text-gray-400 font-medium">Empty</div>
`;
}
mobileContainer.appendChild(mobileCard);
});
}
function updateOption3() {
const container = document.getElementById('option3-container');
container.innerHTML = '';
['first', 'second', 'third'].forEach(base => {
const baseLabel = base === 'first' ? '1B' : base === 'second' ? '2B' : '3B';
if (currentScenario[base]) {
const player = players[base];
const color = teamColors[player.team];
const card = document.createElement('div');
card.className = 'runner-card-option3 bg-white rounded-lg shadow-md p-4 cursor-pointer';
card.style.borderLeft = `4px solid ${color.border}`;
card.style.minWidth = '160px';
card.innerHTML = `
<div class="flex items-start gap-3 mb-3">
<div class="relative flex-shrink-0">
<img src="${player.headshot}" class="w-14 h-14 rounded-full border-2 object-cover" style="border-color: ${color.border}">
<div class="absolute -bottom-1 -right-1 w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold text-white" style="background: ${color.border}">
${base === 'first' ? '1' : base === 'second' ? '2' : '3'}
</div>
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-bold text-gray-900 truncate">${player.name}</div>
<div class="text-xs text-gray-600">#${player.number}${baseLabel}</div>
</div>
</div>
<button class="w-full px-3 py-1.5 text-xs font-semibold rounded-md transition text-white hover:opacity-90" style="background: ${color.border}">
View Card
</button>
`;
container.appendChild(card);
} else {
const chip = document.createElement('div');
chip.className = 'inline-flex items-center px-3 py-1.5 bg-gray-200 text-gray-500 rounded-full text-sm font-medium';
chip.innerHTML = `
<span class="font-bold mr-1">${baseLabel}:</span>
<span>—</span>
`;
container.appendChild(chip);
}
});
}
function updateOption4() {
const hasRunners = currentScenario.first || currentScenario.second || currentScenario.third;
const listEl = document.getElementById('option4-list');
const hiddenEl = document.getElementById('option4-hidden');
const catcherCollapsed = document.getElementById('catcher-collapsed');
const catcherExpanded = document.getElementById('catcher-expanded');
if (!hasRunners) {
listEl.classList.add('hidden');
hiddenEl.classList.remove('hidden');
return;
}
listEl.classList.remove('hidden');
hiddenEl.classList.add('hidden');
// Clear and rebuild runner list
listEl.innerHTML = '';
['first', 'second', 'third'].forEach(base => {
const baseLabel = base === 'first' ? '1B' : base === 'second' ? '2B' : '3B';
const isExpanded = selectedRunner === base;
const card = document.createElement('div');
card.onclick = () => selectRunner(base);
if (currentScenario[base]) {
const player = players[base];
const color = teamColors[player.team];
card.className = `runner-card-option4 occupied bg-white border-l-4 rounded-lg shadow-sm slide-in ${isExpanded ? 'expanded' : ''}`;
card.style.borderColor = color.border;
// Collapsed state (summary)
const summaryHTML = `
<div class="p-2 flex items-center">
<div class="w-10 h-10 rounded-full flex-shrink-0 overflow-hidden border-2" style="border-color: ${color.border}">
<img src="${player.headshot}" class="w-full h-full object-cover">
</div>
<div class="ml-3 flex-1">
<div class="text-sm font-bold text-gray-900">${player.name}</div>
<div class="text-xs text-gray-600">#${player.number}${baseLabel}</div>
</div>
<div class="text-xs text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transition-transform ${isExpanded ? 'rotate-90' : ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
`;
// Expanded state (full card)
const expandedHTML = isExpanded ? `
<div class="expand-height overflow-hidden fade-in" style="max-height: 800px;">
<div class="bg-gradient-to-b from-blue-900 to-blue-950 rounded-b-lg overflow-hidden" style="animation: pulseGlowBlue 2s ease-in-out infinite;">
<div class="bg-blue-800/80 px-3 py-2 flex items-center gap-2 text-white text-xs font-semibold">
<span class="font-bold text-white/90">RUNNER</span>
<span class="text-white/70">${baseLabel}</span>
<span class="truncate flex-1 text-right font-bold">${player.name}</span>
</div>
<div class="p-0">
<img src="https://via.placeholder.com/400x550/${color.border.substring(1)}/ffffff?text=${player.name.replace(' ', '+')}" class="w-full h-auto">
</div>
</div>
</div>
` : '';
card.innerHTML = summaryHTML + expandedHTML;
} else {
card.className = 'runner-card-option4 empty bg-gray-50 border-l-4 border-gray-300 rounded-lg p-2 flex items-center';
card.innerHTML = `
<div class="w-10 h-10 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-sm font-bold">${baseLabel}</span>
</div>
<div class="ml-3 text-sm text-gray-400 font-medium">Empty</div>
`;
}
listEl.appendChild(card);
});
// Toggle catcher card state
if (selectedRunner && currentScenario[selectedRunner]) {
catcherCollapsed.classList.add('hidden');
catcherExpanded.classList.remove('hidden');
} else {
catcherCollapsed.classList.remove('hidden');
catcherExpanded.classList.add('hidden');
}
}
// Add click handlers for Options 1 & 3
document.addEventListener('click', (e) => {
if (e.target.closest('.runner-card-option1.occupied') ||
e.target.closest('.runner-card-option3')) {
alert('In the real app, this would open the player card modal!');
}
});
// Initialize
setScenario('single');
</script>
</body>
</html>