Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31b8467d0 |
No files matched your search
+634
@@ -0,0 +1,634 @@
|
||||
<!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>
|
||||
|
||||
<!-- CENTER EMBLEM -->
|
||||
<div class="center-emblem">
|
||||
<svg viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="42" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||||
<circle cx="50" cy="50" r="28" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
|
||||
<polygon points="50,15 58.5,37 82,37 63,51 69,74 50,60 31,74 37,51 18,37 41.5,37"
|
||||
fill="rgba(255,255,255,0.1)" stroke="rgba(255,255,255,0.2)" stroke-width="0.8"/>
|
||||
</svg>
|
||||
</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>
|
||||
<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 class="poison-badge" id="poison-0" style="display:none;" onclick="adjustPoison(0,1)" title="Poison counters (click +1, right-click -1)" oncontextmenu="event.preventDefault();adjustPoison(0,-1)">0 poison</div>
|
||||
|
||||
<!-- 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="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 class="poison-badge" id="poison-1" style="display:none;" onclick="adjustPoison(1,1)" oncontextmenu="event.preventDefault();adjustPoison(1,-1)">0 poison</div>
|
||||
|
||||
<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 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 class="poison-badge" id="poison-2" style="display:none;" onclick="adjustPoison(2,1)" oncontextmenu="event.preventDefault();adjustPoison(2,-1)">0 poison</div>
|
||||
</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 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 class="poison-badge" id="poison-3" style="display:none;" onclick="adjustPoison(3,1)" oncontextmenu="event.preventDefault();adjustPoison(3,-1)">0 poison</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.battlefield -->
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- MANA POOL DISPLAY (floating, bottom center) -->
|
||||
<div id="mana-pool-display" style="display:none;">
|
||||
<span class="mana-pool-label">MANA:</span>
|
||||
<span class="mana-empty">Empty</span>
|
||||
</div>
|
||||
|
||||
<!-- 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="clearAllManaPools()">Clear All Mana</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
|
||||
==================================================== -->
|
||||
<script src="precons.js?v=20260403_0300"></script>
|
||||
<script src="engine.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)
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
unzip mtg-commander-lan.zip
|
||||
cd mtg-commander-lan
|
||||
npm install
|
||||
node server.js
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "MTG Commander (HTML)",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Commander Battlefield</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Inter:wght@400;500;600&display=swap');
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--felt: #2a2a2a;
|
||||
--felt-light: #333333;
|
||||
--felt-dark: #1e1e1e;
|
||||
--felt-line: rgba(255,255,255,0.08);
|
||||
--felt-line-strong: rgba(255,255,255,0.15);
|
||||
|
||||
--p1: #c0392b; --p1-soft: rgba(192,57,43,0.15); --p1-glow: rgba(192,57,43,0.35); --p1-text: #e74c3c;
|
||||
--p2: #2980b9; --p2-soft: rgba(41,128,185,0.15); --p2-glow: rgba(41,128,185,0.35); --p2-text: #3498db;
|
||||
--p3: #27ae60; --p3-soft: rgba(39,174,96,0.15); --p3-glow: rgba(39,174,96,0.35); --p3-text: #2ecc71;
|
||||
--p4: #d4930d; --p4-soft: rgba(212,147,13,0.15); --p4-glow: rgba(212,147,13,0.35); --p4-text: #f1c40f;
|
||||
|
||||
--card-w: 63px;
|
||||
--card-h: 88px;
|
||||
--zone-radius: 8px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%; height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: var(--felt-dark);
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-surface {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: radial-gradient(ellipse at 50% 50%, var(--felt-light) 0%, var(--felt) 50%, var(--felt-dark) 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-surface::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");
|
||||
background-size: 200px 200px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.table-surface::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.45) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.battlefield {
|
||||
position: absolute;
|
||||
inset: 10px;
|
||||
z-index: 5;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.divider-h, .divider-v {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
.divider-h {
|
||||
top: 50%; left: 10px; right: 10px;
|
||||
height: 2px;
|
||||
transform: translateY(-50%);
|
||||
background: var(--felt-line-strong);
|
||||
box-shadow: 0 0 12px rgba(255,255,255,0.04);
|
||||
}
|
||||
.divider-v {
|
||||
left: 50%; top: 10px; bottom: 10px;
|
||||
width: 2px;
|
||||
transform: translateX(-50%);
|
||||
background: var(--felt-line-strong);
|
||||
box-shadow: 0 0 12px rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.center-emblem {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 15;
|
||||
width: 76px; height: 76px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(55,55,55,0.95), rgba(25,25,25,0.98));
|
||||
border: 2px solid rgba(255,255,255,0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 30px rgba(0,0,0,0.6), inset 0 0 20px rgba(0,0,0,0.3);
|
||||
}
|
||||
.center-emblem svg { width: 40px; height: 40px; }
|
||||
|
||||
.player-area {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.player-area.p1 { grid-area: 1 / 1 / 2 / 2; }
|
||||
.player-area.p2 { grid-area: 1 / 2 / 2 / 3; }
|
||||
.player-area.p3 { grid-area: 2 / 1 / 3 / 2; }
|
||||
.player-area.p4 { grid-area: 2 / 2 / 3 / 3; }
|
||||
|
||||
.p1, .p2 { justify-content: flex-start; }
|
||||
.p3, .p4 { justify-content: flex-end; }
|
||||
|
||||
.player-label {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.p1 .player-label { top: 8px; left: 8px; color: var(--p1-text); background: var(--p1-soft); }
|
||||
.p2 .player-label { top: 8px; right: 8px; color: var(--p2-text); background: var(--p2-soft); }
|
||||
.p3 .player-label { bottom: 8px; left: 8px; color: var(--p3-text); background: var(--p3-soft); }
|
||||
.p4 .player-label { bottom: 8px; right: 8px; color: var(--p4-text); background: var(--p4-soft); }
|
||||
|
||||
.life-counter {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.life-counter button {
|
||||
width: 22px; height: 22px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
background: rgba(255,255,255,0.04);
|
||||
color: rgba(255,255,255,0.45);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 1;
|
||||
}
|
||||
.life-counter button:hover {
|
||||
background: rgba(255,255,255,0.12);
|
||||
color: rgba(255,255,255,0.8);
|
||||
}
|
||||
.p1 .life-counter { top: 8px; right: 8px; color: var(--p1-text); }
|
||||
.p2 .life-counter { top: 8px; left: 8px; color: var(--p2-text); }
|
||||
.p3 .life-counter { bottom: 8px; right: 8px; color: var(--p3-text); }
|
||||
.p4 .life-counter { bottom: 8px; left: 8px; color: var(--p4-text); }
|
||||
|
||||
.zone-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.p1 .zone-row, .p3 .zone-row { flex-direction: row; }
|
||||
.p2 .zone-row, .p4 .zone-row { flex-direction: row-reverse; }
|
||||
|
||||
.commander-row {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.p1 .commander-row, .p3 .commander-row { justify-content: flex-start; }
|
||||
.p2 .commander-row, .p4 .commander-row { justify-content: flex-end; }
|
||||
|
||||
.battle-zone {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border-radius: var(--zone-radius);
|
||||
min-height: 50px;
|
||||
}
|
||||
.battle-zone-inner {
|
||||
position: absolute;
|
||||
inset: 4px;
|
||||
border-radius: var(--zone-radius);
|
||||
border: 1px dashed;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
}
|
||||
.p1 .battle-zone-inner { border-color: var(--p1); }
|
||||
.p2 .battle-zone-inner { border-color: var(--p2); }
|
||||
.p3 .battle-zone-inner { border-color: var(--p3); }
|
||||
.p4 .battle-zone-inner { border-color: var(--p4); }
|
||||
|
||||
.battle-zone-label {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 11px;
|
||||
letter-spacing: 4px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.06;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.zone {
|
||||
position: relative;
|
||||
border-radius: var(--zone-radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: default;
|
||||
transition: all 0.25s ease;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
background: rgba(0,0,0,0.18);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zone:hover {
|
||||
border-color: rgba(255,255,255,0.12);
|
||||
background: rgba(0,0,0,0.28);
|
||||
}
|
||||
|
||||
.zone-icon {
|
||||
font-size: 16px;
|
||||
opacity: 0.55;
|
||||
margin-bottom: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zone-name {
|
||||
font-size: 7.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.3);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zone.library, .zone.exile, .zone.graveyard {
|
||||
width: var(--card-w);
|
||||
height: var(--card-h);
|
||||
}
|
||||
.zone.hand {
|
||||
flex: 1;
|
||||
height: var(--card-h);
|
||||
min-width: 80px;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
}
|
||||
.zone.hand .zone-icon { margin-bottom: 0; margin-right: 3px; }
|
||||
.zone.commander {
|
||||
width: calc(var(--card-w) + 14px);
|
||||
height: calc(var(--card-h) + 6px);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.p1 .zone { border-color: rgba(192,57,43,0.12); }
|
||||
.p1 .zone:hover { border-color: rgba(192,57,43,0.3); box-shadow: 0 0 12px var(--p1-soft); }
|
||||
.p1 .zone.commander { border-color: rgba(192,57,43,0.25); background: var(--p1-soft); }
|
||||
.p1 .zone-icon { color: var(--p1-text); }
|
||||
|
||||
.p2 .zone { border-color: rgba(41,128,185,0.12); }
|
||||
.p2 .zone:hover { border-color: rgba(41,128,185,0.3); box-shadow: 0 0 12px var(--p2-soft); }
|
||||
.p2 .zone.commander { border-color: rgba(41,128,185,0.25); background: var(--p2-soft); }
|
||||
.p2 .zone-icon { color: var(--p2-text); }
|
||||
|
||||
.p3 .zone { border-color: rgba(39,174,96,0.12); }
|
||||
.p3 .zone:hover { border-color: rgba(39,174,96,0.3); box-shadow: 0 0 12px var(--p3-soft); }
|
||||
.p3 .zone.commander { border-color: rgba(39,174,96,0.25); background: var(--p3-soft); }
|
||||
.p3 .zone-icon { color: var(--p3-text); }
|
||||
|
||||
.p4 .zone { border-color: rgba(212,147,13,0.12); }
|
||||
.p4 .zone:hover { border-color: rgba(212,147,13,0.3); box-shadow: 0 0 12px var(--p4-soft); }
|
||||
.p4 .zone.commander { border-color: rgba(212,147,13,0.25); background: var(--p4-soft); }
|
||||
.p4 .zone-icon { color: var(--p4-text); }
|
||||
|
||||
.zone[data-tooltip]:hover::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 6px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0,0,0,0.88);
|
||||
color: #ccc;
|
||||
font-size: 10px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-count {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 4px;
|
||||
font-size: 8px;
|
||||
font-weight: 600;
|
||||
color: rgba(255,255,255,0.25);
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 1px 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
z-index: 30;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.title-bar h1 {
|
||||
font-family: 'Cinzel', serif;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 5px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
.corner-accent {
|
||||
position: absolute;
|
||||
width: 24px; height: 24px;
|
||||
z-index: 6;
|
||||
pointer-events: none;
|
||||
opacity: 0.12;
|
||||
}
|
||||
.corner-accent.tl { top: 6px; left: 6px; border-top: 2px solid var(--p1); border-left: 2px solid var(--p1); }
|
||||
.corner-accent.tr { top: 6px; right: 6px; border-top: 2px solid var(--p2); border-right: 2px solid var(--p2); }
|
||||
.corner-accent.bl { bottom: 6px; left: 6px; border-bottom: 2px solid var(--p3); border-left: 2px solid var(--p3); }
|
||||
.corner-accent.br { bottom: 6px; right: 6px; border-bottom: 2px solid var(--p4); border-right: 2px solid var(--p4); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root { --card-w: 42px; --card-h: 58px; }
|
||||
.player-label { font-size: 7px; letter-spacing: 1px; padding: 2px 6px; }
|
||||
.life-counter { font-size: 15px; }
|
||||
.life-counter button { width: 18px; height: 18px; font-size: 11px; }
|
||||
.zone-name { font-size: 6px; }
|
||||
.zone-icon { font-size: 12px; }
|
||||
.center-emblem { width: 46px; height: 46px; }
|
||||
.center-emblem svg { width: 26px; height: 26px; }
|
||||
.battle-zone-label { font-size: 8px; letter-spacing: 2px; }
|
||||
.zone-row { gap: 3px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="table-surface">
|
||||
<div class="title-bar"><h1>Commander Battlefield</h1></div>
|
||||
<div class="corner-accent tl"></div>
|
||||
<div class="corner-accent tr"></div>
|
||||
<div class="corner-accent bl"></div>
|
||||
<div class="corner-accent br"></div>
|
||||
<div class="divider-h"></div>
|
||||
<div class="divider-v"></div>
|
||||
|
||||
<div class="center-emblem">
|
||||
<svg viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="42" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
||||
<circle cx="50" cy="50" r="28" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>
|
||||
<polygon points="50,15 58.5,37 82,37 63,51 69,74 50,60 31,74 37,51 18,37 41.5,37"
|
||||
fill="rgba(255,255,255,0.1)" stroke="rgba(255,255,255,0.2)" stroke-width="0.8"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="battlefield">
|
||||
|
||||
<!-- PLAYER 1: Top-Left (Crimson) -->
|
||||
<div class="player-area p1">
|
||||
<span class="player-label">Player 1</span>
|
||||
<div class="life-counter">
|
||||
<button onclick="changeLife(1,-1)" aria-label="Decrease life">−</button>
|
||||
<span id="life1">40</span>
|
||||
<button onclick="changeLife(1,1)" aria-label="Increase life">+</button>
|
||||
</div>
|
||||
<div class="zone-row">
|
||||
<div class="zone library" data-tooltip="Library"><span class="card-count">99</span><span class="zone-icon">📚</span><span class="zone-name">Library</span></div>
|
||||
<div class="zone exile" data-tooltip="Exile"><span class="zone-icon">✦</span><span class="zone-name">Exile</span></div>
|
||||
<div class="zone graveyard" data-tooltip="Graveyard"><span class="zone-icon">💀</span><span class="zone-name">Grave</span></div>
|
||||
<div class="zone hand" data-tooltip="Hand"><span class="zone-icon">🃏</span><span class="zone-name">Hand</span></div>
|
||||
</div>
|
||||
<div class="commander-row">
|
||||
<div class="zone commander" data-tooltip="Commander Zone"><span class="zone-icon">👑</span><span class="zone-name">Commander</span></div>
|
||||
</div>
|
||||
<div class="battle-zone"><div class="battle-zone-inner"></div><span class="battle-zone-label">Battlefield</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PLAYER 2: Top-Right (Sapphire) -->
|
||||
<div class="player-area p2">
|
||||
<span class="player-label">Player 2</span>
|
||||
<div class="life-counter">
|
||||
<button onclick="changeLife(2,-1)" aria-label="Decrease life">−</button>
|
||||
<span id="life2">40</span>
|
||||
<button onclick="changeLife(2,1)" aria-label="Increase life">+</button>
|
||||
</div>
|
||||
<div class="zone-row">
|
||||
<div class="zone library" data-tooltip="Library"><span class="card-count">99</span><span class="zone-icon">📚</span><span class="zone-name">Library</span></div>
|
||||
<div class="zone exile" data-tooltip="Exile"><span class="zone-icon">✦</span><span class="zone-name">Exile</span></div>
|
||||
<div class="zone graveyard" data-tooltip="Graveyard"><span class="zone-icon">💀</span><span class="zone-name">Grave</span></div>
|
||||
<div class="zone hand" data-tooltip="Hand"><span class="zone-icon">🃏</span><span class="zone-name">Hand</span></div>
|
||||
</div>
|
||||
<div class="commander-row">
|
||||
<div class="zone commander" data-tooltip="Commander Zone"><span class="zone-icon">👑</span><span class="zone-name">Commander</span></div>
|
||||
</div>
|
||||
<div class="battle-zone"><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">
|
||||
<div class="battle-zone"><div class="battle-zone-inner"></div><span class="battle-zone-label">Battlefield</span></div>
|
||||
<div class="commander-row">
|
||||
<div class="zone commander" data-tooltip="Commander Zone"><span class="zone-icon">👑</span><span class="zone-name">Commander</span></div>
|
||||
</div>
|
||||
<div class="zone-row">
|
||||
<div class="zone library" data-tooltip="Library"><span class="card-count">99</span><span class="zone-icon">📚</span><span class="zone-name">Library</span></div>
|
||||
<div class="zone exile" data-tooltip="Exile"><span class="zone-icon">✦</span><span class="zone-name">Exile</span></div>
|
||||
<div class="zone graveyard" data-tooltip="Graveyard"><span class="zone-icon">💀</span><span class="zone-name">Grave</span></div>
|
||||
<div class="zone hand" data-tooltip="Hand"><span class="zone-icon">🃏</span><span class="zone-name">Hand</span></div>
|
||||
</div>
|
||||
<span class="player-label">Player 3</span>
|
||||
<div class="life-counter">
|
||||
<button onclick="changeLife(3,-1)" aria-label="Decrease life">−</button>
|
||||
<span id="life3">40</span>
|
||||
<button onclick="changeLife(3,1)" aria-label="Increase life">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PLAYER 4: Bottom-Right (Gold) -->
|
||||
<div class="player-area p4">
|
||||
<div class="battle-zone"><div class="battle-zone-inner"></div><span class="battle-zone-label">Battlefield</span></div>
|
||||
<div class="commander-row">
|
||||
<div class="zone commander" data-tooltip="Commander Zone"><span class="zone-icon">👑</span><span class="zone-name">Commander</span></div>
|
||||
</div>
|
||||
<div class="zone-row">
|
||||
<div class="zone library" data-tooltip="Library"><span class="card-count">99</span><span class="zone-icon">📚</span><span class="zone-name">Library</span></div>
|
||||
<div class="zone exile" data-tooltip="Exile"><span class="zone-icon">✦</span><span class="zone-name">Exile</span></div>
|
||||
<div class="zone graveyard" data-tooltip="Graveyard"><span class="zone-icon">💀</span><span class="zone-name">Grave</span></div>
|
||||
<div class="zone hand" data-tooltip="Hand"><span class="zone-icon">🃏</span><span class="zone-name">Hand</span></div>
|
||||
</div>
|
||||
<span class="player-label">Player 4</span>
|
||||
<div class="life-counter">
|
||||
<button onclick="changeLife(4,-1)" aria-label="Decrease life">−</button>
|
||||
<span id="life4">40</span>
|
||||
<button onclick="changeLife(4,1)" aria-label="Increase life">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const lives = { 1: 40, 2: 40, 3: 40, 4: 40 };
|
||||
function changeLife(p, d) {
|
||||
lives[p] = Math.max(0, lives[p] + d);
|
||||
const el = document.getElementById('life' + p);
|
||||
el.textContent = lives[p];
|
||||
el.style.transform = 'scale(1.25)';
|
||||
el.style.transition = 'transform 0.15s ease';
|
||||
setTimeout(() => { el.style.transform = 'scale(1)'; }, 150);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user