feat: initial Helldivers 2 Stratagem Trainer (practice, 1v1, leaderboard, dashboard)

This commit is contained in:
Jeremy Brandenburger
2026-03-30 13:32:55 +02:00
commit 3c22196f81
8 changed files with 2732 additions and 0 deletions
+346
View File
@@ -0,0 +1,346 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLDIVERS 2 Stratagem Trainer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@400;600;700&family=Rajdhani:wght@600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- ── Navigation ─────────────────────────────────────────────── -->
<nav id="main-nav" class="hidden">
<div class="nav-brand">
<span class="nav-logo"></span>
<span>HELLDIVERS 2</span>
</div>
<div class="nav-links">
<button class="nav-btn" data-view="dashboard">Dashboard</button>
<button class="nav-btn" data-view="practice">Training</button>
<button class="nav-btn" data-view="lobby">1v1</button>
<button class="nav-btn" data-view="leaderboard">Highscores</button>
<button class="nav-btn nav-btn-admin hidden" id="nav-admin" data-view="admin">Admin</button>
</div>
<div class="nav-user">
<span class="nav-username" id="nav-username"></span>
<button class="btn btn-muted btn-sm" onclick="logout()">Logout</button>
</div>
</nav>
<!-- Incoming challenge badge (shown anywhere) -->
<div id="challenge-badge" class="challenge-badge hidden"></div>
<!-- ── LOGIN ─────────────────────────────────────────────────── -->
<div id="view-login" class="view view-centered">
<div class="login-box">
<div class="login-header">
<div class="login-logo"></div>
<h1>HELLDIVERS 2</h1>
<p class="login-sub">STRATAGEM TRAINER — SUPER EARTH AUTHORIZED</p>
</div>
<form id="login-form" class="login-form" autocomplete="off">
<div class="field">
<label for="login-username">Helldiver ID</label>
<input id="login-username" type="text" placeholder="Username" autocomplete="username" required>
</div>
<div class="field">
<label for="login-password">Access Code</label>
<input id="login-password" type="password" placeholder="Password" autocomplete="current-password" required>
</div>
<p id="login-error" class="error hidden"></p>
<button type="submit" class="btn btn-accent btn-full">AUTHENTICATE</button>
</form>
</div>
</div>
<!-- ── CHANGE PASSWORD ───────────────────────────────────────── -->
<div id="view-change-password" class="view view-centered hidden">
<div class="login-box">
<div class="login-header">
<h2>CHANGE ACCESS CODE</h2>
<p class="login-sub">Temporary password must be changed before proceeding</p>
</div>
<form id="change-password-form" class="login-form">
<div class="field">
<label for="cp-old">Current Password</label>
<input id="cp-old" type="password" required autocomplete="current-password">
</div>
<div class="field">
<label for="cp-new">New Password (min 8 chars)</label>
<input id="cp-new" type="password" required minlength="8" autocomplete="new-password">
</div>
<div class="field">
<label for="cp-confirm">Confirm New Password</label>
<input id="cp-confirm" type="password" required autocomplete="new-password">
</div>
<p id="cp-error" class="error hidden"></p>
<button type="submit" class="btn btn-accent btn-full">SET NEW PASSWORD</button>
</form>
</div>
</div>
<!-- ── DASHBOARD ─────────────────────────────────────────────── -->
<div id="view-dashboard" class="view hidden">
<div class="page-header">
<h2 class="page-title">COMMAND CENTER</h2>
<p class="page-sub">Welcome back, Helldiver. For Super Earth.</p>
</div>
<div class="dashboard-grid">
<!-- Stats card -->
<div class="card">
<h3 class="card-title">YOUR STATS</h3>
<div class="stat-grid">
<div class="stat-item">
<div class="stat-value" id="dash-total-score"></div>
<div class="stat-label">Total Score</div>
</div>
<div class="stat-item">
<div class="stat-value accent" id="dash-rank"></div>
<div class="stat-label">Global Rank</div>
</div>
<div class="stat-item">
<div class="stat-value" id="dash-sessions"></div>
<div class="stat-label">Sessions</div>
</div>
<div class="stat-item">
<div class="stat-value" id="dash-win-rate"></div>
<div class="stat-label">Match Win Rate</div>
</div>
</div>
</div>
<!-- Daily challenge card -->
<div class="card card-accent">
<h3 class="card-title">⚡ DAILY CHALLENGE</h3>
<div class="daily-stratagem">
<div class="daily-name" id="dash-daily-name"></div>
<div class="daily-category" id="dash-daily-category"></div>
<div class="daily-best">
Best time: <span id="dash-daily-best"></span>
</div>
<button class="btn btn-accent" onclick="startDailyChallenge()">Practice this stratagem</button>
</div>
</div>
<!-- Online users card -->
<div class="card">
<h3 class="card-title">ONLINE HELLDIVERS</h3>
<div id="dash-online" class="online-list">
<span class="muted">Loading...</span>
</div>
</div>
<!-- Recent sessions card -->
<div class="card">
<h3 class="card-title">RECENT SESSIONS</h3>
<table class="data-table">
<thead>
<tr><th>Stratagem</th><th>Score</th><th>Time</th></tr>
</thead>
<tbody id="dash-recent">
<tr><td colspan="3" class="muted">No sessions yet</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ── PRACTICE ───────────────────────────────────────────────── -->
<div id="view-practice" class="view hidden">
<div class="page-header">
<h2 class="page-title">TRAINING PROTOCOL</h2>
</div>
<!-- Category filters -->
<div class="category-row" id="practice-categories"></div>
<!-- Idle (start screen) -->
<div id="practice-idle" class="practice-idle">
<div class="idle-hint">Select categories above, then start training</div>
<button class="btn btn-accent btn-lg" onclick="startPractice()">⚡ START TRAINING</button>
</div>
<!-- Active training -->
<div id="practice-active" class="practice-active hidden">
<div class="stratagem-display card">
<div class="stratagem-category" id="practice-category"></div>
<div class="stratagem-name" id="practice-name"></div>
<div class="arrow-sequence" id="practice-sequence"></div>
<div class="practice-hint">Use Arrow Keys or D-Pad</div>
</div>
<div class="practice-hud">
<div class="hud-item">
<div class="hud-label">TIME</div>
<div class="timer" id="practice-timer">30</div>
</div>
<div class="hud-item">
<div class="hud-label">SCORE</div>
<div class="hud-value" id="practice-score">0</div>
</div>
<div class="hud-item">
<div class="hud-label">STREAK</div>
<div class="hud-value accent" id="practice-streak">0</div>
</div>
</div>
<!-- D-Pad (mobile) -->
<div class="dpad">
<div class="dpad-row">
<button class="dpad-btn dpad-up" onclick="dpadInput('up')"></button>
</div>
<div class="dpad-row">
<button class="dpad-btn dpad-left" onclick="dpadInput('left')"></button>
<div class="dpad-center"></div>
<button class="dpad-btn dpad-right" onclick="dpadInput('right')"></button>
</div>
<div class="dpad-row">
<button class="dpad-btn dpad-down" onclick="dpadInput('down')"></button>
</div>
</div>
<button class="btn btn-muted" onclick="stopPracticeUI()">Stop Training</button>
</div>
</div>
<!-- ── LOBBY ──────────────────────────────────────────────────── -->
<div id="view-lobby" class="view hidden">
<div class="page-header">
<h2 class="page-title">1v1 ARENA</h2>
<p class="page-sub">Challenge a fellow Helldiver to a stratagem duel</p>
</div>
<div class="lobby-layout">
<div class="card">
<h3 class="card-title">ONLINE HELLDIVERS</h3>
<div id="lobby-players" class="player-list">
<p class="muted">Loading...</p>
</div>
</div>
<div id="lobby-challenges" class="challenge-list"></div>
</div>
</div>
<!-- ── MATCH ──────────────────────────────────────────────────── -->
<div id="view-match" class="view hidden">
<div class="match-header">
<div class="match-status-text" id="match-status">Waiting...</div>
<div class="match-category" id="match-category"></div>
</div>
<div class="match-scoreboard">
<div class="match-player me">
<div class="match-player-name" id="match-me-name"></div>
<div class="match-wins" id="match-me-wins">0</div>
</div>
<div class="match-vs">VS</div>
<div class="match-player opp">
<div class="match-player-name" id="match-opp-name"></div>
<div class="match-wins" id="match-opp-wins">0</div>
</div>
</div>
<!-- Round area -->
<div id="match-round-area" class="match-round-area hidden">
<div class="match-sequences">
<div class="match-seq-col">
<div class="match-seq-label">YOU</div>
<div class="arrow-sequence" id="match-me-sequence"></div>
</div>
<div class="match-seq-col">
<div class="match-seq-label">OPPONENT</div>
<div class="arrow-sequence" id="match-opp-sequence"></div>
</div>
</div>
<!-- D-Pad (mobile) -->
<div class="dpad">
<div class="dpad-row">
<button class="dpad-btn dpad-up" onclick="dpadInput('up')"></button>
</div>
<div class="dpad-row">
<button class="dpad-btn dpad-left" onclick="dpadInput('left')"></button>
<div class="dpad-center"></div>
<button class="dpad-btn dpad-right" onclick="dpadInput('right')"></button>
</div>
<div class="dpad-row">
<button class="dpad-btn dpad-down" onclick="dpadInput('down')"></button>
</div>
</div>
</div>
<div class="match-actions">
<button class="btn btn-accent" id="match-ready-btn" onclick="setReady()" style="display:none">READY</button>
<button class="btn btn-muted btn-sm" onclick="leaveMatch()">Leave Match</button>
</div>
</div>
<!-- ── LEADERBOARD ────────────────────────────────────────────── -->
<div id="view-leaderboard" class="view hidden">
<div class="page-header">
<h2 class="page-title">HALL OF HEROES</h2>
<p class="page-sub">Top Helldivers ranked by total practice score</p>
</div>
<div class="card">
<table class="data-table leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>Helldiver</th>
<th>Total Score</th>
<th>Sessions</th>
<th>Match W/Total</th>
</tr>
</thead>
<tbody id="leaderboard-table-body">
<tr><td colspan="5" class="muted">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- ── ADMIN ──────────────────────────────────────────────────── -->
<div id="view-admin" class="view hidden">
<div class="page-header">
<h2 class="page-title">ADMIN PANEL</h2>
</div>
<div class="admin-layout">
<!-- Create user -->
<div class="card">
<h3 class="card-title">CREATE HELLDIVER</h3>
<div class="field">
<label for="new-username">Username</label>
<input id="new-username" type="text" placeholder="helldiver_name" pattern="[a-zA-Z0-9_-]{2,32}">
</div>
<div class="field">
<label for="new-role">Role</label>
<select id="new-role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<p id="admin-error" class="error hidden"></p>
<button class="btn btn-accent" onclick="createUser()">Create User</button>
<div id="new-pw-display" class="pw-display hidden"></div>
</div>
<!-- User list -->
<div class="card">
<h3 class="card-title">ACTIVE HELLDIVERS</h3>
<div id="admin-users" class="admin-user-list">Loading...</div>
</div>
</div>
</div>
<!-- Toast notifications -->
<div id="toast-container"></div>
<script src="stratagems.js"></script>
<script src="app.js"></script>
</body>
</html>