mirror of
https://github.com/tiennm99/gomoku.git
synced 2026-05-19 09:26:34 +00:00
a5acd69b87
- index.html: dark shell with #game-container (Phaser canvas parent), #ui-overlay containing #menu-root, #game-hud-root, #toast-container - vite.config.js: port 5173, strictPort, sourcemap build, no GitHub Pages base - src/main.js: instantiates Phaser.Game from gameConfig - src/config/game-config.js: 800x800 FIT canvas, parent=game-container, scene list - src/config/protocol-constants.js: ClientEventCode enum aligned to response.proto oneofs; adds SPECTATOR_CANNOT_ACT, renames GAME_WATCH_SUCCESSFUL to WATCH_GAME_SUCCESS, drops CLIENT_KICK (no proto case) - src/scenes/boot-scene.js|menu-scene.js|game-scene.js: minimal stubs for Phaser scene registry; phase-09 replaces them with real implementations
180 lines
7.3 KiB
HTML
180 lines
7.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Gomoku</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
background: #1a1a2e;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
color: #eee;
|
|
overflow: hidden;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
#game-container {
|
|
position: relative;
|
|
width: 800px;
|
|
height: 800px;
|
|
max-width: 100vw;
|
|
max-height: 100vh;
|
|
}
|
|
#game-container canvas { display: block; }
|
|
#ui-overlay {
|
|
position: absolute;
|
|
top: 0; left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
}
|
|
/* Menu panels etc. are direct children and should capture clicks.
|
|
Exception: .game-hud is a full-canvas wrapper that must stay click-through
|
|
so Phaser still receives pointer events on the board. */
|
|
#ui-overlay > *:not(.game-hud) { pointer-events: auto; }
|
|
|
|
/* Menu panels */
|
|
.menu-panel {
|
|
position: absolute; top: 50%; left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: #16213e; border-radius: 16px; padding: 40px;
|
|
min-width: 320px; text-align: center;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
display: flex; flex-direction: column; gap: 12px; align-items: center;
|
|
}
|
|
.menu-panel.wide { min-width: 480px; }
|
|
.menu-title { font-size: 28px; color: #e94560; margin-bottom: 4px; }
|
|
.menu-subtitle { font-size: 14px; color: #888; margin-bottom: 8px; }
|
|
.accent { color: #e94560; font-weight: bold; }
|
|
|
|
/* Buttons */
|
|
.menu-btn {
|
|
padding: 12px 28px; border: none; border-radius: 8px;
|
|
font-size: 15px; cursor: pointer; transition: all 0.15s;
|
|
font-family: inherit; color: #eee; width: 100%; max-width: 260px;
|
|
}
|
|
.menu-btn.primary { background: #e94560; }
|
|
.menu-btn.primary:hover { background: #d63851; }
|
|
.menu-btn.secondary { background: transparent; border: 1px solid #e94560; color: #e94560; }
|
|
.menu-btn.secondary:hover { background: rgba(233,69,96,0.1); }
|
|
.menu-btn.ghost { background: transparent; color: #888; font-size: 13px; }
|
|
.menu-btn.ghost:hover { color: #eee; }
|
|
.menu-btn.danger { background: #c0392b; }
|
|
.menu-btn.danger:hover { background: #a93226; }
|
|
.menu-btn.small { padding: 6px 14px; font-size: 13px; width: auto; max-width: none; }
|
|
|
|
/* Input */
|
|
.menu-input {
|
|
width: 100%; max-width: 260px; padding: 12px 16px;
|
|
background: #0f3460; border: 1px solid #333; border-radius: 8px;
|
|
color: #eee; font-size: 15px; outline: none; font-family: inherit;
|
|
}
|
|
.menu-input:focus { border-color: #e94560; box-shadow: 0 0 0 2px rgba(233,69,96,0.2); }
|
|
|
|
/* Room table */
|
|
.room-table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 13px; }
|
|
.room-table th { color: #888; text-align: left; padding: 6px 8px; border-bottom: 1px solid #333; }
|
|
.room-table td { padding: 8px; border-bottom: 1px solid #222; }
|
|
.empty-state { color: #666; padding: 24px; text-align: center; }
|
|
.menu-row { display: flex; gap: 8px; justify-content: center; }
|
|
|
|
/* Spinner */
|
|
.spinner {
|
|
width: 36px; height: 36px; border: 3px solid #333;
|
|
border-top-color: #e94560; border-radius: 50%;
|
|
animation: spin 0.8s linear infinite; margin: 12px auto;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
/* Game HUD */
|
|
.game-hud { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
.game-hud > * { pointer-events: auto; }
|
|
.hud-top {
|
|
position: absolute; top: 4px; left: 50%; transform: translateX(-50%);
|
|
display: flex; gap: 12px; align-items: center;
|
|
background: rgba(22,33,62,0.9); border-radius: 8px; padding: 4px 16px;
|
|
}
|
|
.player-panel { display: flex; align-items: center; gap: 6px; font-size: 13px; }
|
|
.stone-dot { width: 14px; height: 14px; border-radius: 50%; display: inline-block; }
|
|
.stone-dot.black { background: #222; border: 1px solid #555; }
|
|
.stone-dot.white { background: #f5f5f5; border: 1px solid #999; }
|
|
.turn-dot {
|
|
width: 8px; height: 8px; border-radius: 50%;
|
|
background: #333; transition: background 0.2s;
|
|
}
|
|
.turn-dot.active { background: #4ecca3; box-shadow: 0 0 6px #4ecca3; }
|
|
.hud-vs { color: #555; font-size: 12px; font-weight: bold; }
|
|
|
|
/* Move history sidebar */
|
|
.hud-side {
|
|
position: absolute; top: 50px; right: 4px;
|
|
background: rgba(22,33,62,0.85); border-radius: 8px;
|
|
width: 100px; max-height: 360px; padding: 6px; overflow: hidden;
|
|
}
|
|
.hud-side-title { font-size: 11px; color: #888; text-align: center; margin-bottom: 4px; }
|
|
.move-list { max-height: 320px; overflow-y: auto; font-size: 11px; }
|
|
.move-entry { padding: 2px 4px; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
|
.move-entry.black { color: #ccc; }
|
|
.move-entry.white { color: #aaa; }
|
|
|
|
/* Bottom controls */
|
|
.hud-bottom {
|
|
position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
|
|
display: flex; gap: 8px;
|
|
}
|
|
|
|
/* Game over overlay */
|
|
.game-over-overlay {
|
|
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
|
background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center;
|
|
z-index: 20; animation: fadeIn 0.3s;
|
|
}
|
|
.game-over-card {
|
|
background: #16213e; border-radius: 16px; padding: 40px;
|
|
text-align: center; min-width: 300px;
|
|
}
|
|
.result-text { font-size: 42px; font-weight: bold; margin-bottom: 8px; }
|
|
.result-text.win { color: #4ecca3; }
|
|
.result-text.lose { color: #e94560; }
|
|
.result-text.draw { color: #f0c040; }
|
|
.winner-name { color: #888; font-size: 14px; margin-bottom: 20px; }
|
|
.game-over-buttons { display: flex; flex-direction: column; gap: 10px; align-items: center; margin-top: 16px; }
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
|
|
/* Toast notifications */
|
|
#toast-container {
|
|
position: fixed; bottom: 20px; right: 20px;
|
|
display: flex; flex-direction: column; gap: 8px; z-index: 100;
|
|
}
|
|
.toast {
|
|
padding: 10px 20px; border-radius: 8px; font-size: 13px;
|
|
color: #fff; animation: slideIn 0.3s; min-width: 200px;
|
|
}
|
|
.toast-error { background: #c0392b; }
|
|
.toast-info { background: #2980b9; }
|
|
.toast-success { background: #27ae60; }
|
|
.toast-exit { opacity: 0; transition: opacity 0.3s; }
|
|
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Phaser canvas is injected here by game-config.js (parent: 'game-container') -->
|
|
<div id="game-container"></div>
|
|
<!-- DOM overlay roots for phase-09 (menu UI) and phase-10 (HUD + toast) -->
|
|
<div id="ui-overlay">
|
|
<!-- #menu-root: phase-09 injects menu panels here -->
|
|
<div id="menu-root"></div>
|
|
<!-- #game-hud-root: phase-10 injects HUD elements here -->
|
|
<div id="game-hud-root" class="game-hud"></div>
|
|
<!-- #toast-container: phase-10 appends toast notifications here -->
|
|
<div id="toast-container"></div>
|
|
</div>
|
|
<script type="module" src="/src/main.js"></script>
|
|
</body>
|
|
</html>
|