Initial commit
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Hearts</title>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="theme-color" content="#0d2744">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Hearts">
|
||||
<link rel="apple-touch-icon" href="/icons/icon-192.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══════════════ LOBBY ══════════════ -->
|
||||
<div id="screen-lobby" class="screen active">
|
||||
<div class="lobby-box">
|
||||
<h1 class="logo">♥ Hearts ♠</h1>
|
||||
<p class="tagline">The classic trick-taking card game</p>
|
||||
<button id="btn-install" class="btn-install hidden">📲 Add to Home Screen</button>
|
||||
|
||||
<div id="auth-bar" class="auth-bar">
|
||||
<span id="auth-status" class="auth-status">Playing as guest</span>
|
||||
<div class="auth-bar-actions">
|
||||
<button id="btn-show-leaderboard" class="btn-text">🏆 Leaderboard</button>
|
||||
<button id="btn-show-profile" class="btn-text hidden">👤 Profile</button>
|
||||
<button id="btn-logout" class="btn-text hidden">Logout</button>
|
||||
<button id="btn-show-auth" class="btn-text">Login / Register</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Your Name</label>
|
||||
<input id="input-name" type="text" maxlength="16" placeholder="Enter your name…" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="lobby-tabs">
|
||||
<button class="tab-btn active" data-tab="create">Create Game</button>
|
||||
<button class="tab-btn" data-tab="join">Join Game</button>
|
||||
</div>
|
||||
|
||||
<div id="tab-create" class="tab-panel active">
|
||||
<div class="field">
|
||||
<label>Score limit <small>(game ends when a player reaches this)</small></label>
|
||||
<div class="score-limit-row">
|
||||
<button class="score-btn active" data-score="100">100</button>
|
||||
<button class="score-btn" data-score="50">50</button>
|
||||
<button class="score-btn" data-score="200">200</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="btn-create" class="btn-primary">Create Game</button>
|
||||
</div>
|
||||
|
||||
<div id="tab-join" class="tab-panel">
|
||||
<div class="field">
|
||||
<label>Room Code</label>
|
||||
<input id="input-code" type="text" maxlength="8" placeholder="e.g. AB12CD" autocomplete="off" style="text-transform:uppercase">
|
||||
</div>
|
||||
<button id="btn-join" class="btn-primary">Join Game</button>
|
||||
<hr style="border-color:rgba(255,255,255,.1);margin:4px 0">
|
||||
<div class="field">
|
||||
<label>Watch as spectator</label>
|
||||
<input id="input-spectate-code" type="text" maxlength="8" placeholder="Room code" style="text-transform:uppercase" autocomplete="off">
|
||||
</div>
|
||||
<button id="btn-spectate" class="btn-secondary">👁 Watch Game</button>
|
||||
</div>
|
||||
|
||||
<p id="lobby-error" class="error-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ WAITING ROOM ══════════════ -->
|
||||
<div id="screen-waiting" class="screen">
|
||||
<div class="waiting-box">
|
||||
<button id="btn-leave-waiting" class="btn-leave-screen">← Leave</button>
|
||||
<h2>Waiting for Players</h2>
|
||||
<div class="room-code-box">
|
||||
<span class="label">Room Code</span>
|
||||
<span id="display-room-code" class="room-code">——</span>
|
||||
<button id="btn-copy" class="btn-copy" title="Copy code">⧉</button>
|
||||
</div>
|
||||
<p class="hint">Share this code — 4 players needed</p>
|
||||
<div class="waiting-seats" id="waiting-seats">
|
||||
<div class="seat-slot" data-seat="0"><span class="seat-num">1</span><span class="seat-name">—</span></div>
|
||||
<div class="seat-slot" data-seat="1"><span class="seat-num">2</span><span class="seat-name">—</span></div>
|
||||
<div class="seat-slot" data-seat="2"><span class="seat-num">3</span><span class="seat-name">—</span></div>
|
||||
<div class="seat-slot" data-seat="3"><span class="seat-num">4</span><span class="seat-name">—</span></div>
|
||||
</div>
|
||||
<p id="waiting-status" class="waiting-status">Waiting for 3 more players…</p>
|
||||
<button id="btn-fill-bots" class="btn-fill-bots hidden">🤖 Fill empty seats with bots</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ GAME TABLE ══════════════ -->
|
||||
<div id="screen-game" class="screen">
|
||||
|
||||
<!-- Info bar -->
|
||||
<div id="info-bar">
|
||||
<button id="btn-leave-game" class="btn-leave-screen">← Menu</button>
|
||||
|
||||
<!-- Individual scores -->
|
||||
<div id="score-block">
|
||||
<div id="score-display"></div>
|
||||
<div id="hand-points-display"></div>
|
||||
</div>
|
||||
|
||||
<!-- Hearts broken indicator -->
|
||||
<div id="hearts-broken-display" class="hearts-broken hidden">♥ Broken</div>
|
||||
|
||||
<!-- Pass direction indicator -->
|
||||
<div id="pass-dir-display" class="pass-dir-display hidden"></div>
|
||||
|
||||
<div class="game-menu-wrap">
|
||||
<button id="btn-game-menu" class="btn-leave-screen" title="Options">☰</button>
|
||||
<div id="game-menu-dropdown" class="game-menu-dropdown hidden">
|
||||
<button id="btn-info-pos" class="game-menu-item">↕ Move score bar</button>
|
||||
<button id="btn-refresh-game" class="game-menu-item">↺ Reload</button>
|
||||
<button id="btn-exit-game" class="game-menu-item game-menu-exit">🚪 Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spectator banner -->
|
||||
<div id="spectator-banner" class="spectator-banner hidden">👁 Spectating — watching only</div>
|
||||
|
||||
<!-- Table grid -->
|
||||
<div id="table-grid">
|
||||
|
||||
<!-- Top player -->
|
||||
<div id="area-top" class="player-area area-top">
|
||||
<div class="player-label">
|
||||
<span id="top-name">—</span>
|
||||
<span id="top-turn" class="turn-dot hidden">●</span>
|
||||
<span id="top-score" class="player-score-badge"></span>
|
||||
</div>
|
||||
<div id="top-cards" class="opp-cards"></div>
|
||||
</div>
|
||||
|
||||
<!-- Left player -->
|
||||
<div id="area-left" class="player-area area-left">
|
||||
<div class="player-label vertical">
|
||||
<span id="left-name">—</span>
|
||||
<span id="left-turn" class="turn-dot hidden">●</span>
|
||||
<span id="left-score" class="player-score-badge"></span>
|
||||
</div>
|
||||
<div id="left-cards" class="opp-cards vertical"></div>
|
||||
</div>
|
||||
|
||||
<!-- Center / trick area -->
|
||||
<div id="trick-area">
|
||||
<div id="trick-top" class="trick-slot"></div>
|
||||
<div id="trick-middle" class="trick-middle-row">
|
||||
<div id="trick-left" class="trick-slot"></div>
|
||||
<div id="trick-center" class="trick-center-info">
|
||||
<div id="phase-msg" class="phase-msg"></div>
|
||||
</div>
|
||||
<div id="trick-right" class="trick-slot"></div>
|
||||
</div>
|
||||
<div id="trick-bottom" class="trick-slot"></div>
|
||||
</div>
|
||||
|
||||
<!-- Right player -->
|
||||
<div id="area-right" class="player-area area-right">
|
||||
<div class="player-label vertical">
|
||||
<span id="right-name">—</span>
|
||||
<span id="right-turn" class="turn-dot hidden">●</span>
|
||||
<span id="right-score" class="player-score-badge"></span>
|
||||
</div>
|
||||
<div id="right-cards" class="opp-cards vertical"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /table-grid -->
|
||||
|
||||
<!-- Bottom: my hand -->
|
||||
<div id="my-area">
|
||||
<div id="my-label">
|
||||
<span id="my-name">You</span>
|
||||
<span id="my-turn" class="turn-dot hidden">●</span>
|
||||
<span id="my-score" class="player-score-badge"></span>
|
||||
<span id="my-hand-pts" class="hand-pts-badge"></span>
|
||||
<button id="btn-play-mode" class="btn-play-mode hidden" title="Switch play mode">👆 Tap</button>
|
||||
<button id="btn-hand-mode" class="btn-hand-mode" title="Switch hand display">📜 Scroll</button>
|
||||
</div>
|
||||
<div id="my-hand" class="my-hand"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /screen-game -->
|
||||
|
||||
<!-- ══════════════ PASS OVERLAY ══════════════ -->
|
||||
<div id="overlay-pass" class="overlay hidden">
|
||||
<div class="overlay-box pass-box">
|
||||
<h3 id="pass-title">Pass 3 cards</h3>
|
||||
<p id="pass-hint" class="pass-hint"></p>
|
||||
<div id="pass-hand" class="pass-hand"></div>
|
||||
<div class="pass-selected-row">
|
||||
<span class="pass-selected-label">Selected:</span>
|
||||
<div id="pass-selected-preview" class="pass-selected-preview"></div>
|
||||
</div>
|
||||
<button id="btn-confirm-pass" class="btn-primary" disabled>Confirm Pass</button>
|
||||
<p id="pass-waiting" class="hint" style="min-height:18px"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ HAND RESULT OVERLAY ══════════════ -->
|
||||
<div id="overlay-hand" class="overlay hidden">
|
||||
<div class="overlay-box">
|
||||
<div id="hand-result-icon" class="result-icon"></div>
|
||||
<h3 id="hand-result-title"></h3>
|
||||
<p id="hand-result-detail"></p>
|
||||
<div id="hand-result-scores" class="result-scores"></div>
|
||||
<p class="hint">Next hand starting…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ GAME OVER OVERLAY ══════════════ -->
|
||||
<div id="overlay-gameover" class="overlay hidden">
|
||||
<div class="overlay-box">
|
||||
<div class="result-icon big">🏆</div>
|
||||
<h2 id="gameover-title"></h2>
|
||||
<div id="gameover-scores" class="gameover-scores"></div>
|
||||
<button id="btn-new-game" class="btn-primary">New Game</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ AUTH MODAL ══════════════ -->
|
||||
<div id="overlay-auth" class="overlay hidden">
|
||||
<div class="overlay-box auth-box">
|
||||
<button id="btn-auth-close" class="btn-close">✕</button>
|
||||
<div class="auth-tabs">
|
||||
<button class="auth-tab active" data-auth-tab="login">Login</button>
|
||||
<button class="auth-tab" data-auth-tab="register">Register</button>
|
||||
</div>
|
||||
|
||||
<div id="auth-panel-login" class="auth-panel active">
|
||||
<div class="field">
|
||||
<label>Username</label>
|
||||
<input id="auth-login-user" type="text" maxlength="16" placeholder="Username" autocomplete="username">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Password</label>
|
||||
<input id="auth-login-pass" type="password" placeholder="Password" autocomplete="current-password">
|
||||
</div>
|
||||
<p class="hint" style="font-size:.8rem;color:rgba(255,255,255,.5)">Hokm accounts work here too</p>
|
||||
<button id="btn-do-login" class="btn-primary">Login</button>
|
||||
<p id="auth-login-error" class="error-msg"></p>
|
||||
</div>
|
||||
|
||||
<div id="auth-panel-register" class="auth-panel">
|
||||
<div id="reg-step-form">
|
||||
<div class="field">
|
||||
<label>Username <small>(2–16 chars)</small></label>
|
||||
<input id="auth-reg-user" type="text" maxlength="16" placeholder="Choose a username" autocomplete="username">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input id="auth-reg-email" type="email" placeholder="your@email.com" autocomplete="email">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Password <small>(min 4 chars)</small></label>
|
||||
<input id="auth-reg-pass" type="password" placeholder="Choose a password" autocomplete="new-password">
|
||||
</div>
|
||||
<div id="auth-captcha-wrap" class="hidden" style="margin:6px 0"></div>
|
||||
<button id="btn-do-register" class="btn-primary">Send Verification Code</button>
|
||||
<p id="auth-reg-error" class="error-msg"></p>
|
||||
</div>
|
||||
<div id="reg-step-verify" class="hidden">
|
||||
<p id="reg-verify-hint" style="margin:0 0 12px;color:var(--muted);font-size:.9em"></p>
|
||||
<div class="field">
|
||||
<label>Verification Code</label>
|
||||
<input id="auth-reg-code" type="text" inputmode="numeric" maxlength="6" placeholder="6-digit code" autocomplete="one-time-code">
|
||||
</div>
|
||||
<button id="btn-do-verify" class="btn-primary">Create Account</button>
|
||||
<button id="btn-reg-back" class="btn-secondary" style="margin-top:6px;width:100%">← Back</button>
|
||||
<p id="auth-verify-error" class="error-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ PROFILE SCREEN ══════════════ -->
|
||||
<div id="screen-profile" class="screen">
|
||||
<div class="profile-wrap">
|
||||
<div class="profile-box">
|
||||
<button id="btn-profile-back" class="btn-back">← Back</button>
|
||||
<div class="profile-avatar">♥</div>
|
||||
<h2 id="profile-username" class="profile-name"></h2>
|
||||
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-num" id="stat-games-played">—</span>
|
||||
<span class="stat-label">Games Played</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-num" id="stat-games-won">—</span>
|
||||
<span class="stat-label">Games Won</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-num" id="stat-moon-shots">—</span>
|
||||
<span class="stat-label">Moon Shots 🌙</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-num" id="stat-total-score">—</span>
|
||||
<span class="stat-label">Total Score</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="points-legend">
|
||||
<h4>How to win</h4>
|
||||
<ul>
|
||||
<li><strong>1 pt</strong> – each ♥ heart taken</li>
|
||||
<li><strong>13 pts</strong> – taking the ♠Q (Queen of Spades)</li>
|
||||
<li><strong>Shoot the Moon</strong> – take all 13 hearts + ♠Q: you score 0, everyone else scores 26</li>
|
||||
<li>Game ends when any player reaches the score limit. <strong>Lowest score wins.</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="profile-section">
|
||||
<button id="btn-show-change-pass" class="btn-secondary" style="width:100%">🔑 Change Password</button>
|
||||
<div id="change-pass-form" class="hidden" style="margin-top:10px;display:flex;flex-direction:column;gap:8px">
|
||||
<div class="field">
|
||||
<label>Current Password</label>
|
||||
<input id="change-pass-current" type="password" placeholder="Current password" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>New Password <small>(min 4 chars)</small></label>
|
||||
<input id="change-pass-new" type="password" placeholder="New password" autocomplete="new-password">
|
||||
</div>
|
||||
<button id="btn-do-change-pass" class="btn-primary">Update Password</button>
|
||||
<p id="change-pass-msg" class="error-msg"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="admin-panel" class="profile-section admin-section hidden">
|
||||
<h4 class="admin-title">⚙ Admin</h4>
|
||||
<div class="admin-row">
|
||||
<span>New registrations</span>
|
||||
<button id="btn-toggle-signups" class="btn-secondary btn-admin-toggle">…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ LEADERBOARD ══════════════ -->
|
||||
<div id="screen-leaderboard" class="screen">
|
||||
<div class="profile-wrap">
|
||||
<div class="profile-box" style="max-width:520px">
|
||||
<button id="btn-lb-back" class="btn-back">← Back</button>
|
||||
<div class="profile-avatar">🏆</div>
|
||||
<h2 class="profile-name">Leaderboard</h2>
|
||||
<table class="lb-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Player</th>
|
||||
<th>Avg ♥</th>
|
||||
<th>W</th>
|
||||
<th>Played</th>
|
||||
<th>🌙</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="lb-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ PLAYER DETAILS MODAL ══════════════ -->
|
||||
<div id="overlay-player-details" class="overlay hidden">
|
||||
<div class="modal-box">
|
||||
<button id="btn-details-close" class="btn-back" style="margin-bottom:12px">← Close</button>
|
||||
<div class="profile-avatar" style="font-size:2rem">♥</div>
|
||||
<h3 id="details-username" style="margin:8px 0 16px;color:#fff"></h3>
|
||||
<table class="lb-table" style="width:100%">
|
||||
<tbody id="details-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════ EXIT CONFIRM ══════════════ -->
|
||||
<div id="overlay-exit-confirm" class="overlay hidden">
|
||||
<div class="overlay-box small" style="text-align:center">
|
||||
<h3>Leave Game?</h3>
|
||||
<p style="font-size:.88rem;color:rgba(255,255,255,.65);margin:8px 0 20px">Your session will be discarded.<br>You will not be able to rejoin.</p>
|
||||
<div style="display:flex;gap:10px;justify-content:center">
|
||||
<button id="btn-exit-confirm-yes" class="btn-primary" style="background:rgba(200,50,50,.7);border-color:rgba(200,50,50,.9)">Leave & Exit</button>
|
||||
<button id="btn-exit-confirm-no" class="btn-secondary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── PWA Install Banner ── -->
|
||||
<div id="pwa-banner" class="pwa-banner hidden">
|
||||
<img src="/icons/icon-192.png" class="pwa-banner-icon" alt="">
|
||||
<div class="pwa-banner-text">
|
||||
<strong>Install Hearts</strong>
|
||||
<span>Play offline, launch like an app</span>
|
||||
</div>
|
||||
<button id="pwa-banner-install" class="pwa-banner-btn">Install</button>
|
||||
<button id="pwa-banner-dismiss" class="pwa-banner-close" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user