Gameplay (legal moves, move application, pressure maps, PGN parsing) now runs entirely in the browser via a chess.js port of engine.py (static/engine.js + vendored chess.min.js). The app no longer calls the server during play, so it stays instant and works even when the backend is scaled to zero or offline. - engine.js: line-for-line port of engine.py; parity-verified identical output (pressure, FEN, status, SAN/UCI) across all 10 built-in games / 829 frames. - app.js: replace all /api fetches with local engine calls; graceful PGN parse errors; asset paths moved to root for static serving. - Save games to localStorage + load from the game dropdown; export any game as a .pgn file. Minimal additive UI in the existing export row. - games.json generated from games.py (scripts/export_games.py). - Production Dockerfile -> static-web-server (~9MB image, was ~150MB python). - make dev -> bun static server; add make games / make parity targets. - app.py kept as optional fallback (serves static app + /api routes). - parity/: automated JS-vs-python engine parity test. git diff stat: 16 files changed, 722 insertions(+), 128 deletions(-)
134 lines
5.5 KiB
HTML
134 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Chess Pressure</title>
|
|
<link rel="stylesheet" href="/style.css?v=24">
|
|
<link rel="stylesheet" href="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.css">
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Chess Pressure</h1>
|
|
<div class="header-controls">
|
|
<button id="theme-toggle" title="Toggle dark/light mode">
|
|
<span class="icon-sun">☼</span>
|
|
<span class="icon-moon">☾</span>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="board-panel">
|
|
<div class="board-wrap">
|
|
<div id="board"></div>
|
|
</div>
|
|
<div class="playback">
|
|
<input type="range" id="slider" min="0" max="0" value="0">
|
|
<div class="playback-buttons">
|
|
<button id="btn-start" title="Start"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="2" width="2" height="12"/><polygon points="9,2 4,8 9,14"/><polygon points="15,2 10,8 15,14"/></svg></button>
|
|
<button id="btn-prev" title="Back"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="2" width="2" height="12"/><polygon points="14,2 6,8 14,14"/></svg></button>
|
|
<button id="btn-play" title="Play/Pause"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><polygon points="3,2 13,8 3,14"/></svg></button>
|
|
<button id="btn-next" title="Forward"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><polygon points="2,2 10,8 2,14"/><rect x="13" y="2" width="2" height="12"/></svg></button>
|
|
<button id="btn-end" title="End"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><polygon points="1,2 6,8 1,14"/><polygon points="7,2 12,8 7,14"/><rect x="13" y="2" width="2" height="12"/></svg></button>
|
|
</div>
|
|
<div class="pressure-mode">
|
|
<div class="pmode-toggle">
|
|
<label class="pmode-opt">
|
|
<input type="radio" name="pmode" value="equal" checked>
|
|
<span>Equal</span>
|
|
</label>
|
|
<label class="pmode-opt">
|
|
<input type="radio" name="pmode" value="weighted">
|
|
<span>Piece value</span>
|
|
</label>
|
|
</div>
|
|
<label class="toggle-row">
|
|
<input type="checkbox" id="toggle-pieces" checked>
|
|
<span>Pieces</span>
|
|
</label>
|
|
<label class="toggle-row">
|
|
<input type="checkbox" id="toggle-board">
|
|
<span>Board</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="side-panel">
|
|
<div class="game-selector">
|
|
<select id="game-select">
|
|
<option value="__new">New game</option>
|
|
</select>
|
|
<button id="btn-upload" title="Upload PGN">PGN</button>
|
|
</div>
|
|
|
|
<div class="game-info" id="game-info"></div>
|
|
|
|
<div class="move-list" id="move-list"></div>
|
|
|
|
<div class="fork-controls" id="fork-controls" style="display:none">
|
|
<span class="fork-badge">Forked at move <span id="fork-point"></span></span>
|
|
<button id="btn-reset-fork">Reset to original</button>
|
|
</div>
|
|
|
|
<div class="export-row">
|
|
<button id="btn-save" title="Save this game to your browser">Save</button>
|
|
<button id="btn-export-pgn" title="Download this game as a PGN file">Export PGN</button>
|
|
<button id="btn-export" title="Export an animated GIF">Export GIF</button>
|
|
<button id="btn-delete-saved" class="danger" title="Delete this saved game" style="display:none">Delete</button>
|
|
</div>
|
|
|
|
<div class="status-bar" id="status-bar"></div>
|
|
</div>
|
|
</main>
|
|
|
|
<dialog id="export-dialog">
|
|
<form method="dialog">
|
|
<h2>Export GIF</h2>
|
|
<label>Speed per move:
|
|
<select id="export-speed">
|
|
<option value="250">0.25s (fast)</option>
|
|
<option value="500" selected>0.5s</option>
|
|
<option value="1000">1s</option>
|
|
<option value="2000">2s (slow)</option>
|
|
</select>
|
|
</label>
|
|
<p class="export-info">480 × 480px — respects current view settings</p>
|
|
<div class="export-progress" id="export-progress" style="display:none">
|
|
<progress id="export-bar" max="100" value="0"></progress>
|
|
<span id="export-pct">0%</span>
|
|
</div>
|
|
<div class="dialog-buttons">
|
|
<button type="button" id="export-go">Export</button>
|
|
<button type="button" id="export-cancel">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
|
|
<dialog id="pgn-dialog">
|
|
<form method="dialog">
|
|
<h2>Upload PGN</h2>
|
|
<textarea id="pgn-input" rows="12" placeholder="Paste PGN here..."></textarea>
|
|
<div class="dialog-buttons">
|
|
<button type="button" id="pgn-file-btn">Choose file</button>
|
|
<input type="file" id="pgn-file" accept=".pgn,.txt" style="display:none">
|
|
<button type="submit" id="pgn-submit">Load</button>
|
|
<button type="button" id="pgn-cancel">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</dialog>
|
|
|
|
<footer>
|
|
<a href="https://github.com/mindthemath/chess-pressure">source</a>
|
|
</footer>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
<script src="https://unpkg.com/@chrisoakman/chessboardjs@1.0.0/dist/chessboard-1.0.0.min.js"></script>
|
|
<script src="/chess.min.js"></script>
|
|
<script src="/gif.js"></script>
|
|
<script src="/engine.js"></script>
|
|
<script src="/app.js?v=24"></script>
|
|
</body>
|
|
</html>
|