- Move life/cmd-dmg/poison into 3-box life panels as a top-level overlay (z-index 50) centered at quadrant intersections - Remove center emblem - Match battlefield card size to hand cards (63x84px) - Overlay zone-row and commander-row on battlefield edges to eliminate dead space and maximize battlefield area - Restructure poison badges from per-player absolute positioning into life-panel counters box Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
698 lines
30 KiB
HTML
698 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>MTG Commander Battlefield</title>
|
|
<link rel="stylesheet" href="style.css?v=20260403_0300">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ====================================================
|
|
SETUP SCREEN
|
|
==================================================== -->
|
|
<div id="setup-screen">
|
|
<!-- Populated by ui.js initSetupScreen() -->
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
TABLE SURFACE — 4-Quadrant Battlefield
|
|
==================================================== -->
|
|
<div class="table-surface" id="game-battlefield" style="display:none;">
|
|
|
|
<!-- TOP BAR: Phase tracker + controls -->
|
|
<div id="top-bar">
|
|
<span class="top-bar-logo">Commander</span>
|
|
|
|
<div class="turn-counter-badge" id="turn-counter">Turn <span>1</span></div>
|
|
|
|
<div class="active-player-indicator" id="active-player-indicator">
|
|
<span class="active-player-dot" id="active-dot" style="background:#c0392b;"></span>
|
|
Player 1's Turn
|
|
</div>
|
|
|
|
<!-- Phase track -->
|
|
<div class="phase-track-wrapper">
|
|
<div class="phase-track" id="phase-track"></div>
|
|
</div>
|
|
|
|
<!-- Phase/turn action buttons -->
|
|
<div class="top-bar-actions">
|
|
<button class="btn-phase" onclick="nextPhase()" title="Advance to next phase (N)">Next Phase</button>
|
|
<button class="btn-phase btn-next-turn" onclick="nextTurn()" title="End turn, advance to next player (T)">Next Turn</button>
|
|
</div>
|
|
|
|
<!-- Utility buttons -->
|
|
<div class="top-bar-utils">
|
|
<button class="btn-util" id="btn-undo" onclick="undo()" title="Undo (Ctrl+Z)" disabled>↩</button>
|
|
<button class="btn-util" id="btn-redo" onclick="redo()" title="Redo (Ctrl+Y)" disabled>↪</button>
|
|
<button class="btn-util" id="btn-triggers" onclick="toggleTriggerStack()" title="Trigger Stack">❇</button>
|
|
<button class="btn-util" onclick="openDiceModal()" title="Dice & Coins (G)">🎲</button>
|
|
<button class="btn-util" onclick="checkStateBasedActions()" title="Check State-Based Actions">SBA</button>
|
|
<button class="btn-util" onclick="toggleSettingsPanel()" title="Settings">⚙</button>
|
|
<button class="btn-util" id="btn-search-toggle" onclick="document.getElementById('card-search-sidebar').classList.toggle('collapsed')" title="Card Search (S)">🔍</button>
|
|
</div>
|
|
|
|
<!-- Storm counter -->
|
|
<span id="storm-badge" style="display:none;font-size:10px;color:var(--mtg-gold);background:rgba(200,168,67,0.15);border:1px solid rgba(200,168,67,0.3);border-radius:4px;padding:2px 7px;white-space:nowrap;">Storm: 0</span>
|
|
</div>
|
|
|
|
<!-- DIVIDER LINES -->
|
|
<div class="divider-h"></div>
|
|
<div class="divider-v"></div>
|
|
|
|
<!-- CORNER ACCENTS -->
|
|
<div class="corner-accent tl"></div>
|
|
<div class="corner-accent tr"></div>
|
|
<div class="corner-accent bl"></div>
|
|
<div class="corner-accent br"></div>
|
|
|
|
|
|
<!-- ================================================
|
|
4-QUADRANT BATTLEFIELD
|
|
================================================ -->
|
|
<div class="battlefield">
|
|
|
|
<!-- PLAYER 1: Top-Left (Crimson) -->
|
|
<div class="player-area p1" id="player-area-1">
|
|
<span class="player-label" id="player-label-1">Player 1</span>
|
|
|
|
<!-- Zone Row: Library, Exile, Graveyard, Hand -->
|
|
<div class="zone-row" id="zone-row-1">
|
|
<div class="zone library" id="lib-zone-1" data-player="1" data-zone="library"
|
|
onclick="openZoneModal(0,'library')" data-tooltip="Library">
|
|
<span class="card-count" id="lib-count-1">0</span>
|
|
<span class="zone-icon">📚</span>
|
|
<span class="zone-name">Library</span>
|
|
</div>
|
|
<div class="zone exile" id="exile-zone-1" data-player="1" data-zone="exile"
|
|
onclick="openZoneModal(0,'exile')" data-tooltip="Exile">
|
|
<span class="card-count" id="exile-count-1" style="display:none;">0</span>
|
|
<span class="zone-icon">⬡</span>
|
|
<span class="zone-name">Exile</span>
|
|
</div>
|
|
<div class="zone graveyard" id="gv-zone-1" data-player="1" data-zone="graveyard"
|
|
onclick="openZoneModal(0,'graveyard')" data-tooltip="Graveyard">
|
|
<span class="card-count" id="gv-count-1" style="display:none;">0</span>
|
|
<span class="zone-icon">☠</span>
|
|
<span class="zone-name">Grave</span>
|
|
</div>
|
|
<div class="zone hand" id="hand-zone-1" data-player="1" data-zone="hand"
|
|
onclick="openZoneModal(0,'hand')" data-tooltip="Hand">
|
|
<span class="zone-icon">🃏</span>
|
|
<span class="zone-name">Hand (<span id="hand-count-1">0</span>)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Commander Row -->
|
|
<div class="commander-row" id="cmd-row-1">
|
|
<div class="zone commander" id="cmd-zone-1" data-player="1" data-zone="command"
|
|
onclick="openZoneModal(0,'command')" data-tooltip="Command Zone">
|
|
<span class="zone-icon">👑</span>
|
|
<span class="zone-name">Commander</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Battle Zone -->
|
|
<div class="battle-zone" id="battle-zone-1">
|
|
<div class="battle-zone-inner"></div>
|
|
<span class="battle-zone-label">Battlefield</span>
|
|
<!-- Battlefield cards injected here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PLAYER 2: Top-Right (Sapphire) -->
|
|
<div class="player-area p2" id="player-area-2">
|
|
<span class="player-label" id="player-label-2">Player 2</span>
|
|
|
|
<div class="zone-row" id="zone-row-2">
|
|
<div class="zone library" id="lib-zone-2" data-player="2" data-zone="library"
|
|
onclick="openZoneModal(1,'library')" data-tooltip="Library">
|
|
<span class="card-count" id="lib-count-2">0</span>
|
|
<span class="zone-icon">📚</span>
|
|
<span class="zone-name">Library</span>
|
|
</div>
|
|
<div class="zone exile" id="exile-zone-2" data-player="2" data-zone="exile"
|
|
onclick="openZoneModal(1,'exile')" data-tooltip="Exile">
|
|
<span class="card-count" id="exile-count-2" style="display:none;">0</span>
|
|
<span class="zone-icon">⬡</span>
|
|
<span class="zone-name">Exile</span>
|
|
</div>
|
|
<div class="zone graveyard" id="gv-zone-2" data-player="2" data-zone="graveyard"
|
|
onclick="openZoneModal(1,'graveyard')" data-tooltip="Graveyard">
|
|
<span class="card-count" id="gv-count-2" style="display:none;">0</span>
|
|
<span class="zone-icon">☠</span>
|
|
<span class="zone-name">Grave</span>
|
|
</div>
|
|
<div class="zone hand" id="hand-zone-2" data-player="2" data-zone="hand"
|
|
onclick="openZoneModal(1,'hand')" data-tooltip="Hand">
|
|
<span class="zone-icon">🃏</span>
|
|
<span class="zone-name">Hand (<span id="hand-count-2">0</span>)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="commander-row" id="cmd-row-2">
|
|
<div class="zone commander" id="cmd-zone-2" data-player="2" data-zone="command"
|
|
onclick="openZoneModal(1,'command')" data-tooltip="Command Zone">
|
|
<span class="zone-icon">👑</span>
|
|
<span class="zone-name">Commander</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="battle-zone" id="battle-zone-2">
|
|
<div class="battle-zone-inner"></div>
|
|
<span class="battle-zone-label">Battlefield</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PLAYER 3: Bottom-Left (Emerald) -->
|
|
<div class="player-area p3" id="player-area-3">
|
|
<!-- Battle zone on top for bottom players -->
|
|
<div class="battle-zone" id="battle-zone-3">
|
|
<div class="battle-zone-inner"></div>
|
|
<span class="battle-zone-label">Battlefield</span>
|
|
</div>
|
|
|
|
<div class="commander-row" id="cmd-row-3">
|
|
<div class="zone commander" id="cmd-zone-3" data-player="3" data-zone="command"
|
|
onclick="openZoneModal(2,'command')" data-tooltip="Command Zone">
|
|
<span class="zone-icon">👑</span>
|
|
<span class="zone-name">Commander</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="zone-row" id="zone-row-3">
|
|
<div class="zone library" id="lib-zone-3" data-player="3" data-zone="library"
|
|
onclick="openZoneModal(2,'library')" data-tooltip="Library">
|
|
<span class="card-count" id="lib-count-3">0</span>
|
|
<span class="zone-icon">📚</span>
|
|
<span class="zone-name">Library</span>
|
|
</div>
|
|
<div class="zone exile" id="exile-zone-3" data-player="3" data-zone="exile"
|
|
onclick="openZoneModal(2,'exile')" data-tooltip="Exile">
|
|
<span class="card-count" id="exile-count-3" style="display:none;">0</span>
|
|
<span class="zone-icon">⬡</span>
|
|
<span class="zone-name">Exile</span>
|
|
</div>
|
|
<div class="zone graveyard" id="gv-zone-3" data-player="3" data-zone="graveyard"
|
|
onclick="openZoneModal(2,'graveyard')" data-tooltip="Graveyard">
|
|
<span class="card-count" id="gv-count-3" style="display:none;">0</span>
|
|
<span class="zone-icon">☠</span>
|
|
<span class="zone-name">Grave</span>
|
|
</div>
|
|
<div class="zone hand" id="hand-zone-3" data-player="3" data-zone="hand"
|
|
onclick="openZoneModal(2,'hand')" data-tooltip="Hand">
|
|
<span class="zone-icon">🃏</span>
|
|
<span class="zone-name">Hand (<span id="hand-count-3">0</span>)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span class="player-label" id="player-label-3">Player 3</span>
|
|
</div>
|
|
|
|
<!-- PLAYER 4: Bottom-Right (Gold) -->
|
|
<div class="player-area p4" id="player-area-4">
|
|
<div class="battle-zone" id="battle-zone-4">
|
|
<div class="battle-zone-inner"></div>
|
|
<span class="battle-zone-label">Battlefield</span>
|
|
</div>
|
|
|
|
<div class="commander-row" id="cmd-row-4">
|
|
<div class="zone commander" id="cmd-zone-4" data-player="4" data-zone="command"
|
|
onclick="openZoneModal(3,'command')" data-tooltip="Command Zone">
|
|
<span class="zone-icon">👑</span>
|
|
<span class="zone-name">Commander</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="zone-row" id="zone-row-4">
|
|
<div class="zone library" id="lib-zone-4" data-player="4" data-zone="library"
|
|
onclick="openZoneModal(3,'library')" data-tooltip="Library">
|
|
<span class="card-count" id="lib-count-4">0</span>
|
|
<span class="zone-icon">📚</span>
|
|
<span class="zone-name">Library</span>
|
|
</div>
|
|
<div class="zone exile" id="exile-zone-4" data-player="4" data-zone="exile"
|
|
onclick="openZoneModal(3,'exile')" data-tooltip="Exile">
|
|
<span class="card-count" id="exile-count-4" style="display:none;">0</span>
|
|
<span class="zone-icon">⬡</span>
|
|
<span class="zone-name">Exile</span>
|
|
</div>
|
|
<div class="zone graveyard" id="gv-zone-4" data-player="4" data-zone="graveyard"
|
|
onclick="openZoneModal(3,'graveyard')" data-tooltip="Graveyard">
|
|
<span class="card-count" id="gv-count-4" style="display:none;">0</span>
|
|
<span class="zone-icon">☠</span>
|
|
<span class="zone-name">Grave</span>
|
|
</div>
|
|
<div class="zone hand" id="hand-zone-4" data-player="4" data-zone="hand"
|
|
onclick="openZoneModal(3,'hand')" data-tooltip="Hand">
|
|
<span class="zone-icon">🃏</span>
|
|
<span class="zone-name">Hand (<span id="hand-count-4">0</span>)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span class="player-label" id="player-label-4">Player 4</span>
|
|
</div>
|
|
|
|
</div><!-- /.battlefield -->
|
|
|
|
<!-- LIFE PANELS OVERLAY (top-most layer) -->
|
|
<div id="life-panels-overlay">
|
|
<div class="life-panel lp-p1" id="life-panel-1">
|
|
<div class="life-panel-box life-box">
|
|
<span class="life-panel-label">Life</span>
|
|
<div class="life-counter" id="life-counter-1">
|
|
<button onclick="adjustLife(0,-1)" aria-label="Decrease P1 life">−</button>
|
|
<span class="life-counter-value" id="life-0" onclick="showSetLifeDialog(0)">40</span>
|
|
<button onclick="adjustLife(0,1)" aria-label="Increase P1 life">+</button>
|
|
</div>
|
|
</div>
|
|
<div class="life-panel-box cmd-dmg-box" id="cmd-dmg-inline-0">
|
|
<span class="life-panel-label">Cmd Dmg</span>
|
|
<div class="cmd-dmg-inline-row">—</div>
|
|
</div>
|
|
<div class="life-panel-box counters-box">
|
|
<span class="life-panel-label">Counters</span>
|
|
<div class="poison-badge" id="poison-0" onclick="adjustPoison(0,1)" title="Poison counters (click +1, right-click -1)" oncontextmenu="event.preventDefault();adjustPoison(0,-1)">0 poison</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="life-panel lp-p2" id="life-panel-2">
|
|
<div class="life-panel-box life-box">
|
|
<span class="life-panel-label">Life</span>
|
|
<div class="life-counter" id="life-counter-2">
|
|
<button onclick="adjustLife(1,-1)" aria-label="Decrease P2 life">−</button>
|
|
<span class="life-counter-value" id="life-1" onclick="showSetLifeDialog(1)">40</span>
|
|
<button onclick="adjustLife(1,1)" aria-label="Increase P2 life">+</button>
|
|
</div>
|
|
</div>
|
|
<div class="life-panel-box cmd-dmg-box" id="cmd-dmg-inline-1">
|
|
<span class="life-panel-label">Cmd Dmg</span>
|
|
<div class="cmd-dmg-inline-row">—</div>
|
|
</div>
|
|
<div class="life-panel-box counters-box">
|
|
<span class="life-panel-label">Counters</span>
|
|
<div class="poison-badge" id="poison-1" onclick="adjustPoison(1,1)" oncontextmenu="event.preventDefault();adjustPoison(1,-1)">0 poison</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="life-panel lp-p3" id="life-panel-3">
|
|
<div class="life-panel-box life-box">
|
|
<span class="life-panel-label">Life</span>
|
|
<div class="life-counter" id="life-counter-3">
|
|
<button onclick="adjustLife(2,-1)" aria-label="Decrease P3 life">−</button>
|
|
<span class="life-counter-value" id="life-2" onclick="showSetLifeDialog(2)">40</span>
|
|
<button onclick="adjustLife(2,1)" aria-label="Increase P3 life">+</button>
|
|
</div>
|
|
</div>
|
|
<div class="life-panel-box cmd-dmg-box" id="cmd-dmg-inline-2">
|
|
<span class="life-panel-label">Cmd Dmg</span>
|
|
<div class="cmd-dmg-inline-row">—</div>
|
|
</div>
|
|
<div class="life-panel-box counters-box">
|
|
<span class="life-panel-label">Counters</span>
|
|
<div class="poison-badge" id="poison-2" onclick="adjustPoison(2,1)" oncontextmenu="event.preventDefault();adjustPoison(2,-1)">0 poison</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="life-panel lp-p4" id="life-panel-4">
|
|
<div class="life-panel-box life-box">
|
|
<span class="life-panel-label">Life</span>
|
|
<div class="life-counter" id="life-counter-4">
|
|
<button onclick="adjustLife(3,-1)" aria-label="Decrease P4 life">−</button>
|
|
<span class="life-counter-value" id="life-3" onclick="showSetLifeDialog(3)">40</span>
|
|
<button onclick="adjustLife(3,1)" aria-label="Increase P4 life">+</button>
|
|
</div>
|
|
</div>
|
|
<div class="life-panel-box cmd-dmg-box" id="cmd-dmg-inline-3">
|
|
<span class="life-panel-label">Cmd Dmg</span>
|
|
<div class="cmd-dmg-inline-row">—</div>
|
|
</div>
|
|
<div class="life-panel-box counters-box">
|
|
<span class="life-panel-label">Counters</span>
|
|
<div class="poison-badge" id="poison-3" onclick="adjustPoison(3,1)" oncontextmenu="event.preventDefault();adjustPoison(3,-1)">0 poison</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- COMBAT SVG OVERLAY (arrows) -->
|
|
<svg id="combat-overlay" xmlns="http://www.w3.org/2000/svg" style="position:absolute;inset:44px 0 0 0;pointer-events:none;z-index:30;">
|
|
<defs>
|
|
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
<polygon points="0 0, 10 3.5, 0 7" fill="rgba(211,32,42,0.7)"/>
|
|
</marker>
|
|
</defs>
|
|
<g id="combat-arrows-svg"></g>
|
|
</svg>
|
|
|
|
<!-- FLOATING LOG -->
|
|
<div id="floating-log" class="floating-log"></div>
|
|
|
|
<!-- SHORTCUT HINT -->
|
|
<div class="shortcut-hint"></div>
|
|
|
|
<!-- CARD SEARCH SIDEBAR (right side, collapsible) -->
|
|
<div id="card-search-sidebar" class="collapsed">
|
|
<div class="sidebar-header">
|
|
<span class="sidebar-title">Card Search</span>
|
|
<button class="sidebar-toggle" onclick="document.getElementById('card-search-sidebar').classList.toggle('collapsed')" title="Close search">✕</button>
|
|
</div>
|
|
<div class="search-input-wrap">
|
|
<input class="search-input" id="card-search-input" type="text" placeholder="Search Scryfall..." autocomplete="off"/>
|
|
</div>
|
|
<div class="search-status" id="search-status">Type to search cards...</div>
|
|
<div class="card-results" id="card-results"></div>
|
|
</div>
|
|
|
|
<!-- Sidebar edge tab -->
|
|
<div id="sidebar-edge-tab" class="sidebar-edge-tab" title="Toggle card search">‹</div>
|
|
|
|
</div><!-- /.table-surface / #game-battlefield -->
|
|
|
|
<!-- ====================================================
|
|
CONTEXT MENU
|
|
==================================================== -->
|
|
<div id="context-menu"></div>
|
|
|
|
<!-- ====================================================
|
|
FLOATING CARD PREVIEW
|
|
==================================================== -->
|
|
<div id="card-preview">
|
|
<img src="" alt="Card Preview"/>
|
|
<div class="card-preview-pt-overlay" id="card-preview-pt-overlay"></div>
|
|
<div class="card-preview-info">
|
|
<div class="card-preview-name"></div>
|
|
<div class="card-preview-type"></div>
|
|
<div class="card-preview-oracle"></div>
|
|
<div class="card-preview-pt"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
ZONE MODAL
|
|
==================================================== -->
|
|
<div id="zone-modal">
|
|
<div class="zone-modal-card">
|
|
<div class="zone-modal-header">
|
|
<span class="zone-modal-title" id="zone-modal-title">Player Zones</span>
|
|
<button class="btn-close" onclick="closeZoneModal()">✕</button>
|
|
</div>
|
|
<div class="zone-modal-tabs" id="zone-modal-tabs">
|
|
<!-- Populated by renderZoneModal() -->
|
|
</div>
|
|
<div class="zone-modal-body" id="zone-modal-body">
|
|
<!-- Populated by renderZoneModal() -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
DICE MODAL
|
|
==================================================== -->
|
|
<div id="dice-modal">
|
|
<div class="dice-modal-card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
|
|
<span style="font-family:var(--font-display);font-size:14px;font-weight:700;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:0.08em;">Dice & Coins</span>
|
|
<button class="btn-close" onclick="closeDiceModal()">✕</button>
|
|
</div>
|
|
<div class="dice-result-display" id="dice-result-display">—</div>
|
|
<div class="dice-label" id="dice-label">Choose a die</div>
|
|
<div class="dice-buttons">
|
|
<button class="btn-dice" onclick="rollDice(4)">d4</button>
|
|
<button class="btn-dice" onclick="rollDice(6)">d6</button>
|
|
<button class="btn-dice" onclick="rollDice(8)">d8</button>
|
|
<button class="btn-dice" onclick="rollDice(10)">d10</button>
|
|
<button class="btn-dice" onclick="rollDice(12)">d12</button>
|
|
<button class="btn-dice" onclick="rollDice(20)">d20</button>
|
|
<button class="btn-dice" onclick="rollDice(100)">d100</button>
|
|
<button class="btn-dice coin" onclick="flipCoin()">Coin Flip</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
TOKEN MODAL
|
|
==================================================== -->
|
|
<div id="token-modal">
|
|
<div class="token-modal-card">
|
|
<div class="modal-header">
|
|
<span class="modal-title">Create Token</span>
|
|
<button class="btn-close" onclick="closeTokenModal()">✕</button>
|
|
</div>
|
|
<div class="modal-body" id="token-modal-body">
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px;">
|
|
<div>
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Token Name</label>
|
|
<input class="game-input" id="token-name" type="text" placeholder="e.g. 1/1 White Soldier"/>
|
|
</div>
|
|
<div>
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Type Line</label>
|
|
<input class="game-input" id="token-type" type="text" placeholder="Creature — Soldier"/>
|
|
</div>
|
|
<div>
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Power/Toughness</label>
|
|
<input class="game-input" id="token-pt" type="text" placeholder="1/1"/>
|
|
</div>
|
|
<div>
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Count</label>
|
|
<input class="game-input" id="token-count" type="number" value="1" min="1" max="20"/>
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom:10px;">
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Keywords (comma-separated)</label>
|
|
<input class="game-input" id="token-keywords" type="text" placeholder="Flying, Vigilance"/>
|
|
</div>
|
|
<div style="margin-bottom:12px;">
|
|
<label style="font-size:11px;font-weight:600;color:var(--color-text);display:block;margin-bottom:4px;">Player</label>
|
|
<select class="game-input" id="token-player-select" style="height:28px;">
|
|
<option value="0">Player 1</option>
|
|
<option value="1">Player 2</option>
|
|
<option value="2">Player 3</option>
|
|
<option value="3">Player 4</option>
|
|
</select>
|
|
</div>
|
|
<div style="display:flex;gap:8px;">
|
|
<button class="btn btn-primary" onclick="createTokenFromModal()">Create Token(s)</button>
|
|
<button class="btn btn-cancel" onclick="closeTokenModal()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
DECK MODAL
|
|
==================================================== -->
|
|
<div id="deck-modal">
|
|
<div class="deck-modal-card">
|
|
<div class="modal-header">
|
|
<span class="modal-title">Deck Manager</span>
|
|
<button class="btn-close" onclick="closeDeckModal()">✕</button>
|
|
</div>
|
|
<div class="modal-body" id="deck-modal-body">
|
|
<!-- Populated by renderDeckModalContent() -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
COMMANDER DAMAGE MATRIX MODAL
|
|
==================================================== -->
|
|
<div id="cmd-dmg-matrix-modal">
|
|
<!-- Populated by renderCmdDmgMatrix() -->
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
TRIGGER STACK PANEL
|
|
==================================================== -->
|
|
<div id="trigger-stack-panel">
|
|
<div class="trigger-stack-card">
|
|
<div class="trigger-stack-header">
|
|
<span class="trigger-stack-title">Trigger Stack</span>
|
|
<button class="btn-close" style="font-size:14px;width:20px;height:20px;" onclick="closeTriggerStack()">✕</button>
|
|
</div>
|
|
<div class="trigger-stack-body" id="trigger-stack-body">
|
|
<div class="trigger-stack-empty">No pending triggers.</div>
|
|
</div>
|
|
<div class="trigger-stack-actions">
|
|
<button class="btn-resolve" onclick="resolveNextTrigger()">Resolve Next</button>
|
|
<button class="btn-resolve-all" onclick="resolveAllTriggers()">Resolve All</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
SETTINGS PANEL
|
|
==================================================== -->
|
|
<div id="settings-panel">
|
|
<div class="modal-header" style="flex-shrink:0;">
|
|
<span class="modal-title">Settings</span>
|
|
<button class="btn-close" onclick="toggleSettingsPanel()">✕</button>
|
|
</div>
|
|
<div class="settings-body" id="settings-body" style="flex:1;overflow-y:auto;padding:12px;">
|
|
<!-- Populated by renderSettingsPanel() -->
|
|
</div>
|
|
<div style="padding:10px 14px;border-top:1px solid var(--color-border);display:flex;gap:8px;flex-wrap:wrap;flex-shrink:0;">
|
|
<button class="btn btn-primary" onclick="openDiceModal()">🎲 Dice</button>
|
|
<button class="btn btn-gold" onclick="openCmdDmgMatrix()">CMD Damage Matrix</button>
|
|
<button class="btn btn-cancel" onclick="checkStateBasedActions()">Check SBA</button>
|
|
<button class="btn btn-cancel" onclick="autoArrangeBattlefield()">Auto-Arrange</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
PRIORITY BANNER
|
|
==================================================== -->
|
|
<div id="priority-banner">
|
|
<span class="priority-text" id="priority-text">Waiting for priority...</span>
|
|
<button class="btn-priority-pass" onclick="passPriority()">Pass Priority</button>
|
|
<button class="btn-close" style="font-size:13px;" onclick="closePriorityWindow()">✕</button>
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
LEGAL ACTIONS BAR
|
|
==================================================== -->
|
|
<div id="legal-actions-bar">
|
|
<!-- Populated by renderLegalActionsBar() -->
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
UNDO HISTORY PANEL
|
|
==================================================== -->
|
|
<div id="undo-history-panel">
|
|
<!-- Populated by renderUndoHistory() -->
|
|
</div>
|
|
|
|
<!-- ====================================================
|
|
MANA COLOR PICKER (dynamically appended) -->
|
|
<!-- Zone card context menu (dynamically appended) -->
|
|
<!-- SBA alert panel (dynamically appended) -->
|
|
<!-- Set-life dialog (dynamically appended) -->
|
|
|
|
<!-- ====================================================
|
|
SCRIPTS
|
|
Load order matters:
|
|
1. precons.js — static PRECON_DATA fallback
|
|
2. engine.js — game state & core logic
|
|
3. ai-bridge.js — backend connector & DeckSourceClient
|
|
4. precon-loader.js — startGameWithDecks() + loadPreconDeckFromDck()
|
|
5. player-panels.js — player panel rendering
|
|
6. ui.js — setup screen, deck picker, UI wiring
|
|
==================================================== -->
|
|
<script src="precons.js?v=20260403_0300"></script>
|
|
<script src="engine.js?v=20260403_0300"></script>
|
|
<script src="ai-bridge.js?v=20260403_0300"></script>
|
|
<script src="precon-loader.js?v=20260403_0300"></script>
|
|
<script src="player-panels.js?v=20260403_0300"></script>
|
|
<script src="ui.js?v=20260403_0300"></script>
|
|
|
|
<script>
|
|
// ============================================================
|
|
// STARTUP & BRIDGE FUNCTIONS
|
|
// ============================================================
|
|
|
|
// Token creation from modal
|
|
function createTokenFromModal() {
|
|
if (!gameState) return;
|
|
const name = document.getElementById('token-name').value.trim() || '1/1 Token';
|
|
const typeLine = document.getElementById('token-type').value.trim() || 'Creature';
|
|
const pt = document.getElementById('token-pt').value.trim() || '1/1';
|
|
const count = parseInt(document.getElementById('token-count').value) || 1;
|
|
const keywords = document.getElementById('token-keywords').value.trim();
|
|
const playerIdx = parseInt(document.getElementById('token-player-select').value) || 0;
|
|
|
|
const parts = pt.split('/');
|
|
const power = parts[0] ? parts[0].trim() : '1';
|
|
const toughness = parts[1] ? parts[1].trim() : '1';
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
addCardToBattlefield(name, '', typeLine, playerIdx, undefined, undefined,
|
|
keywords ? keywords.replace(/,/g, ' ') : '',
|
|
{ power, toughness, pt, isToken: true });
|
|
}
|
|
addLogEntry('Created ' + count + 'x <strong>' + name + '</strong> token(s) for Player ' + (playerIdx + 1));
|
|
closeTokenModal();
|
|
}
|
|
|
|
// Pass priority
|
|
function passPriority() {
|
|
addLogEntry('Priority passed.');
|
|
closePriorityWindow();
|
|
}
|
|
|
|
// Open precon browser (stub - opens deck modal for current player)
|
|
function openPreconBrowser() {
|
|
if (gameState) openDeckModal(gameState.currentPlayerIndex);
|
|
}
|
|
|
|
// Open collection modal (stub)
|
|
function openCollectionModal() { showToast('Collection tracker available in full version.'); }
|
|
|
|
// Scry dialog stub
|
|
function showScryDialog(playerId) {
|
|
if (!gameState) return;
|
|
const player = gameState.players[playerId];
|
|
if (!player || player.zones.library.length === 0) {
|
|
addLogEntry('!! ' + (player ? player.name : 'Player') + ' has no cards to scry.');
|
|
return;
|
|
}
|
|
const count = Math.min(parseInt(prompt('Scry how many? (1-7)', '1') || '1'), 7, player.zones.library.length);
|
|
if (isNaN(count) || count < 1) return;
|
|
const topCards = player.zones.library.slice(0, count);
|
|
const names = topCards.map(c => c.name || '?').join(', ');
|
|
addLogEntry('<strong>' + player.name + '</strong> scrys ' + count + ': [' + names + ']');
|
|
}
|
|
|
|
// Mill dialog stub
|
|
function showMillDialog(playerId) {
|
|
if (!gameState) return;
|
|
const count = parseInt(prompt('Mill how many cards?', '1') || '1');
|
|
if (isNaN(count) || count < 1) return;
|
|
millCards(playerId, count);
|
|
}
|
|
|
|
// Become monarch
|
|
function becomeMonarch(playerId) {
|
|
if (!gameState) return;
|
|
gameState.monarchPlayerIndex = playerId;
|
|
const player = gameState.players[playerId];
|
|
addLogEntry('👑 <strong>' + (player ? player.name : 'Player') + '</strong> becomes the Monarch!');
|
|
updateMonarchBadges();
|
|
}
|
|
|
|
// Empower the life counter right-click on the quadrant labels
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Wire right-click on life values to set-life dialog
|
|
for (var i = 0; i < 4; i++) {
|
|
(function(idx) {
|
|
var lifeEl = document.getElementById('life-' + idx);
|
|
if (lifeEl) {
|
|
lifeEl.addEventListener('contextmenu', function(e) {
|
|
e.preventDefault();
|
|
showSetLifeDialog(idx);
|
|
});
|
|
}
|
|
})(i);
|
|
}
|
|
|
|
// Life counter right-click on quadrant buttons too
|
|
// (already handled via inline onclick in HTML)
|
|
|
|
// Auto-detect Commander-AI-Lab backend on page load
|
|
if (typeof AIBridge !== 'undefined') {
|
|
AIBridge.init().then(function(status) {
|
|
var indicator = document.getElementById('backend-status-indicator');
|
|
if (indicator) {
|
|
indicator.textContent = status.connected
|
|
? (status.modelLoaded ? '\u{1F9E0} ML Backend Connected' : '\u26a1 Backend Connected (no ML model)')
|
|
: '\u{1F4E1} Local Mode (no backend)';
|
|
indicator.className = 'backend-status ' +
|
|
(status.connected ? (status.modelLoaded ? 'connected-ml' : 'connected') : 'disconnected');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|