first commit
Build Images and Deploy / Update-PROD-Stack (push) Failing after 14s

This commit is contained in:
2026-02-28 00:01:41 -05:00
commit 4255d95c68
36 changed files with 4665 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
<%- include('../partials/header') %>
<div class="container-narrow" style="padding-top: 2rem;">
<h1 style="margin-bottom: 1.5rem;">Create New Hunt</h1>
<div class="card">
<% if (error) { %>
<div class="alert alert-danger error"><%= error %></div>
<% } %>
<form method="POST" action="/admin/hunts">
<div class="form-group">
<label for="name">Hunt Name</label>
<input type="text" id="name" name="name" class="form-control" required placeholder="e.g. Veld Music Festival 2026">
</div>
<div class="form-group">
<label for="short_name">Short Name (Code)</label>
<input type="text" id="short_name" name="short_name" class="form-control" required
maxlength="12" placeholder="e.g. VELD26" style="text-transform: uppercase; font-family: monospace; font-size: 1.1rem;">
<div class="form-hint">2-12 uppercase alphanumeric characters. Used in QR code URLs.</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" name="description" class="form-control" rows="3"
placeholder="Describe this hunt for participants..."></textarea>
</div>
<div class="form-group">
<label for="package_count">Number of Hidden Packages</label>
<input type="number" id="package_count" name="package_count" class="form-control"
required min="1" max="10000" placeholder="e.g. 50">
<div class="form-hint">Each package gets a unique QR code and printable card.</div>
</div>
<div class="form-group">
<label for="expiry_date">Expiry Date (optional)</label>
<input type="datetime-local" id="expiry_date" name="expiry_date" class="form-control">
<div class="form-hint">Leave blank for no expiry.</div>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center;">Create Hunt</button>
</form>
</div>
</div>
<%- include('../partials/footer') %>
+29
View File
@@ -0,0 +1,29 @@
<%- include('../partials/header') %>
<div class="container">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem;">
<h1>Admin Dashboard</h1>
<a href="/admin/hunts/new" class="btn btn-primary">+ New Hunt</a>
</div>
<% if (hunts.length === 0) { %>
<div class="card" style="text-align: center; padding: 3rem;">
<p style="color: var(--muted); font-size: 1.1rem;">You haven't created any hunts yet.</p>
<a href="/admin/hunts/new" class="btn btn-primary" style="margin-top: 1rem;">Create Your First Hunt</a>
</div>
<% } else { %>
<% hunts.forEach(hunt => { %>
<a href="/admin/hunts/<%= hunt.id %>" class="hunt-card">
<div class="hunt-info">
<h3><%= hunt.name %></h3>
<span class="meta"><%= hunt.short_name %> &middot; <%= hunt.package_count %> packages
<% if (hunt.expiry_date) { %> &middot; Expires: <%= new Date(hunt.expiry_date).toLocaleDateString() %><% } %>
</span>
</div>
<span class="badge">Manage</span>
</a>
<% }) %>
<% } %>
</div>
<%- include('../partials/footer') %>
+73
View File
@@ -0,0 +1,73 @@
<%- include('../partials/header') %>
<div class="container">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem;">
<div>
<h1 style="margin: 0;"><%= hunt.name %></h1>
<span style="color: var(--muted); font-family: monospace; font-size: 1rem;"><%= hunt.short_name %></span>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<a href="/admin/hunts/<%= hunt.id %>/pdf" class="btn btn-success">&#x1F4E5; Download PDF</a>
<a href="/hunt/<%= hunt.short_name %>" class="btn btn-outline">View Public Page</a>
<a href="/hunt/<%= hunt.short_name %>/leaderboard" class="btn btn-outline">Leaderboard</a>
</div>
</div>
<% if (hunt.description) { %>
<div class="card">
<p style="margin: 0; color: var(--muted);"><%= hunt.description %></p>
</div>
<% } %>
<div class="stats-row">
<div class="stat-box">
<div class="value"><%= hunt.package_count %></div>
<div class="label">Packages</div>
</div>
<div class="stat-box">
<div class="value"><%= packages.filter(p => p.scan_count > 0).length %></div>
<div class="label">Found</div>
</div>
<div class="stat-box">
<div class="value"><%= packages.reduce((sum, p) => sum + p.scan_count, 0) %></div>
<div class="label">Total Scans</div>
</div>
<div class="stat-box">
<div class="value"><%= hunt.expiry_date ? new Date(hunt.expiry_date).toLocaleDateString() : '&mdash;' %></div>
<div class="label">Expires</div>
</div>
</div>
<h2 style="margin-top: 1.5rem; margin-bottom: 1rem;">All Packages</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>#</th>
<th>Code</th>
<th>Scans</th>
<th>First Scanner</th>
<th>Last Scanner</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<% packages.forEach(pkg => { %>
<tr>
<td><strong><%= pkg.card_number %></strong></td>
<td style="font-family: monospace;"><%= pkg.unique_code %></td>
<td><%= pkg.scan_count %></td>
<td><%= pkg.first_scanner_name || '&mdash;' %></td>
<td><%= pkg.last_scanner_name || '&mdash;' %></td>
<td>
<a href="/loot/<%= hunt.short_name %>/<%= pkg.unique_code %>/profile" class="btn btn-sm btn-outline">View</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
<%- include('../partials/footer') %>
+31
View File
@@ -0,0 +1,31 @@
<%- include('../partials/header') %>
<div class="container-narrow" style="padding-top: 3rem;">
<div class="card">
<div class="card-header">Login</div>
<% if (error) { %>
<div class="alert alert-danger error"><%= error %></div>
<% } %>
<form method="POST" action="/auth/login">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" class="form-control" required autofocus autocomplete="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" required autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center;">Login</button>
</form>
<p style="text-align: center; margin-top: 1rem; font-size: 0.9rem; color: var(--muted);">
Don't have an account? <a href="/auth/register">Register</a>
</p>
</div>
</div>
<%- include('../partials/footer') %>
+39
View File
@@ -0,0 +1,39 @@
<%- include('../partials/header') %>
<div class="container-narrow" style="padding-top: 3rem;">
<div class="card">
<div class="card-header">Register</div>
<% if (error) { %>
<div class="alert alert-danger error"><%= error %></div>
<% } %>
<form method="POST" action="/auth/register">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" class="form-control" required autofocus
minlength="3" maxlength="24" pattern="[a-zA-Z0-9_-]+" autocomplete="username">
<div class="form-hint">3-24 characters: letters, numbers, hyphens, underscores</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" required minlength="6" autocomplete="new-password">
<div class="form-hint">At least 6 characters</div>
</div>
<div class="form-group">
<label for="password_confirm">Confirm Password</label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required minlength="6" autocomplete="new-password">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center;">Create Account</button>
</form>
<p style="text-align: center; margin-top: 1rem; font-size: 0.9rem; color: var(--muted);">
Already have an account? <a href="/auth/login">Login</a>
</p>
</div>
</div>
<%- include('../partials/footer') %>
+11
View File
@@ -0,0 +1,11 @@
<%- include('partials/header') %>
<div class="container" style="display: flex; align-items: center; justify-content: center; min-height: 60vh;">
<div class="card" style="text-align: center; max-width: 400px;">
<h1 style="font-size: 3rem; margin-bottom: 0.5rem;"><%= typeof title !== 'undefined' ? title : 'Error' %></h1>
<p style="color: var(--muted); margin-bottom: 1.5rem;"><%= typeof message !== 'undefined' ? message : 'Something went wrong.' %></p>
<a href="/" class="btn btn-primary">Go Home</a>
</div>
</div>
<%- include('partials/footer') %>
+52
View File
@@ -0,0 +1,52 @@
<%- include('partials/header') %>
<div class="container">
<div class="hero">
<h1>Find. Scan. Conquer.</h1>
<p>Hunt for hidden QR codes in the real world, earn points, climb the leaderboard. Be the first to find a package for maximum points!</p>
<div style="display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap;">
<a href="/hunts" class="btn btn-primary">Browse Hunts</a>
<a href="/leaderboard" class="btn btn-outline">Leaderboard</a>
</div>
</div>
<div style="margin-top: 1rem;">
<div class="stats-row">
<div class="stat-box">
<div class="value">500</div>
<div class="label">1st Find</div>
</div>
<div class="stat-box">
<div class="value">250</div>
<div class="label">2nd Find</div>
</div>
<div class="stat-box">
<div class="value">100</div>
<div class="label">3rd Find</div>
</div>
<div class="stat-box">
<div class="value">50</div>
<div class="label">4th+ Finds</div>
</div>
</div>
</div>
<% if (hunts && hunts.length > 0) { %>
<h2 style="margin-top: 2rem; margin-bottom: 1rem;">Active Hunts</h2>
<% hunts.forEach(hunt => { %>
<a href="/hunt/<%= hunt.short_name %>" class="hunt-card">
<div class="hunt-info">
<h3><%= hunt.name %></h3>
<span class="meta"><%= hunt.short_name %> &middot; <%= hunt.package_count %> packages &middot; by <%= hunt.creator_name %></span>
</div>
<% if (hunt.expiry_date && new Date(hunt.expiry_date) < new Date()) { %>
<span class="badge expired">Expired</span>
<% } else { %>
<span class="badge"><%= hunt.package_count %> packages</span>
<% } %>
</a>
<% }) %>
<% } %>
</div>
<%- include('partials/footer') %>
+42
View File
@@ -0,0 +1,42 @@
<%- include('../partials/header') %>
<div class="container">
<div style="margin-bottom: 1.5rem;">
<a href="/hunt/<%= hunt.short_name %>" style="color: var(--muted); text-decoration: none;">&larr; Back to <%= hunt.name %></a>
</div>
<h1><%= hunt.name %> &mdash; Leaderboard</h1>
<% if (leaderboard.length === 0) { %>
<div class="card" style="text-align: center; padding: 3rem;">
<p style="color: var(--muted);">No scans yet. Be the first to find a package!</p>
</div>
<% } else { %>
<div class="card">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Rank</th>
<th>Player</th>
<th>Points</th>
<th>Scans</th>
</tr>
</thead>
<tbody>
<% leaderboard.forEach((entry, i) => { %>
<tr>
<td class="rank-cell rank-<%= i + 1 %>"><%= i + 1 %></td>
<td><strong><%= entry.username %></strong></td>
<td><span class="points-badge"><%= entry.total_points %></span></td>
<td><%= entry.scans %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
<% } %>
</div>
<%- include('../partials/footer') %>
+31
View File
@@ -0,0 +1,31 @@
<%- include('../partials/header') %>
<div class="container">
<h1 style="margin-bottom: 1.5rem;">All Hunts</h1>
<% if (hunts.length === 0) { %>
<div class="card" style="text-align: center; padding: 3rem;">
<p style="color: var(--muted);">No hunts have been created yet.</p>
</div>
<% } else { %>
<% hunts.forEach(hunt => { %>
<a href="/hunt/<%= hunt.short_name %>" class="hunt-card">
<div class="hunt-info">
<h3><%= hunt.name %></h3>
<span class="meta"><%= hunt.short_name %> &middot; <%= hunt.package_count %> packages &middot; by <%= hunt.creator_name %>
<% if (hunt.expiry_date) { %>
&middot; Expires: <%= new Date(hunt.expiry_date).toLocaleDateString() %>
<% } %>
</span>
</div>
<% if (hunt.expiry_date && new Date(hunt.expiry_date) < new Date()) { %>
<span class="badge expired">Expired</span>
<% } else { %>
<span class="badge"><%= hunt.package_count %> packages</span>
<% } %>
</a>
<% }) %>
<% } %>
</div>
<%- include('../partials/footer') %>
+65
View File
@@ -0,0 +1,65 @@
<%- include('../partials/header') %>
<div class="container">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem;">
<div>
<h1 style="margin: 0;"><%= hunt.name %></h1>
<span style="color: var(--muted); font-family: monospace;"><%= hunt.short_name %></span>
</div>
<div style="display: flex; gap: 0.5rem;">
<a href="/hunt/<%= hunt.short_name %>/leaderboard" class="btn btn-primary">Leaderboard</a>
</div>
</div>
<% if (hunt.description) { %>
<div class="card">
<p style="margin: 0;"><%= hunt.description %></p>
</div>
<% } %>
<% if (isExpired) { %>
<div class="alert alert-danger">This hunt has expired. Scanning is no longer available.</div>
<% } %>
<div class="stats-row">
<div class="stat-box">
<div class="value"><%= hunt.package_count %></div>
<div class="label">Packages</div>
</div>
<div class="stat-box">
<div class="value"><%= packages.filter(p => p.scan_count > 0).length %></div>
<div class="label">Found</div>
</div>
<div class="stat-box">
<div class="value"><%= packages.filter(p => p.scan_count === 0).length %></div>
<div class="label">Unfound</div>
</div>
</div>
<h2 style="margin-top: 1.5rem; margin-bottom: 1rem;">Packages</h2>
<div class="package-grid">
<% packages.forEach(pkg => { %>
<a href="/loot/<%= hunt.short_name %>/<%= pkg.unique_code %>/profile"
class="package-card <%= pkg.scan_count > 0 ? 'scanned' : 'unscanned' %>">
<div class="card-num">#<%= pkg.card_number %></div>
<div class="code"><%= pkg.unique_code %></div>
<div class="scan-info">
<% if (pkg.scan_count > 0) { %>
&#x2705; Found &middot; <%= pkg.scan_count %> scan<%= pkg.scan_count !== 1 ? 's' : '' %>
<% if (pkg.first_scanner_name) { %> &middot; First: <%= pkg.first_scanner_name %><% } %>
<% } else { %>
&#x1F50D; Not yet found
<% } %>
</div>
<% if (pkg.last_scan_hint) { %>
<div style="margin-top: 0.5rem; font-size: 0.85rem; font-style: italic; color: #555;">
"<%= pkg.last_scan_hint %>"
</div>
<% } %>
</a>
<% }) %>
</div>
</div>
<%- include('../partials/footer') %>
+40
View File
@@ -0,0 +1,40 @@
<%- include('../partials/header') %>
<div class="container">
<h1 style="margin-bottom: 1.5rem;">&#x1F3C6; Global Leaderboard</h1>
<% if (leaderboard.length === 0) { %>
<div class="card" style="text-align: center; padding: 3rem;">
<p style="color: var(--muted);">No scans recorded yet. Start hunting!</p>
</div>
<% } else { %>
<div class="card">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Rank</th>
<th>Player</th>
<th>Points</th>
<th>Packages Found</th>
</tr>
</thead>
<tbody>
<% leaderboard.forEach((entry, i) => { %>
<tr>
<td class="rank-cell rank-<%= i + 1 %>">
<% if (i === 0) { %>&#x1F947;<% } else if (i === 1) { %>&#x1F948;<% } else if (i === 2) { %>&#x1F949;<% } else { %><%= i + 1 %><% } %>
</td>
<td><strong><%= entry.username %></strong></td>
<td><span class="points-badge"><%= entry.total_points %></span></td>
<td><%= entry.scans %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
<% } %>
</div>
<%- include('../partials/footer') %>
+13
View File
@@ -0,0 +1,13 @@
<%- include('../partials/header') %>
<div class="container" style="text-align: center; padding-top: 3rem;">
<div class="emoji" style="font-size: 4rem;">&#x23F0;</div>
<h1>Hunt Expired</h1>
<p style="color: var(--muted);">This hunt has ended. Scanning is no longer available.</p>
<p style="color: var(--muted);">
<strong><%= pkg.hunt_name %></strong> &middot; Package #<%= pkg.card_number %>
</p>
<a href="/hunt/<%= pkg.hunt_short_name %>" class="btn btn-outline" style="margin-top: 1rem;">View Hunt Results</a>
</div>
<%- include('../partials/footer') %>
+106
View File
@@ -0,0 +1,106 @@
<%- include('../partials/header') %>
<div class="container">
<div style="margin-bottom: 1rem;">
<a href="/hunt/<%= pkg.hunt_short_name %>" style="color: var(--muted); text-decoration: none;">&larr; Back to <%= pkg.hunt_name %></a>
</div>
<div class="card">
<div class="package-hero">
<div class="card-number">#<%= pkg.card_number %></div>
<div class="hunt-name"><a href="/hunt/<%= pkg.hunt_short_name %>"><%= pkg.hunt_name %></a></div>
<div style="font-family: monospace; color: var(--muted); margin-top: 0.25rem;"><%= pkg.unique_code %></div>
</div>
<div class="stats-row">
<div class="stat-box">
<div class="value"><%= pkg.scan_count %></div>
<div class="label">Total Scans</div>
</div>
<div class="stat-box">
<div class="value"><%= pkg.first_scanner_name || '&mdash;' %></div>
<div class="label">First Finder</div>
</div>
<div class="stat-box">
<div class="value"><%= pkg.last_scanner_name || '&mdash;' %></div>
<div class="label">Most Recent</div>
</div>
</div>
</div>
<% if (pkg.first_scan_image) { %>
<div class="card">
<div class="card-header">&#x1F4F8; First Finder's Photo</div>
<img src="<%= pkg.first_scan_image %>" alt="Package photo" class="package-image">
</div>
<% } %>
<%/* First scanner can upload image */%>
<% if (isFirstScanner && !pkg.first_scan_image) { %>
<div class="card">
<div class="card-header">&#x1F4F8; Upload a Photo</div>
<p style="color: var(--muted); font-size: 0.9rem;">As the first finder, you can upload a photo for this package.</p>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image" enctype="multipart/form-data">
<div class="form-group">
<input type="file" name="image" accept="image/*" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Upload Photo</button>
</form>
</div>
<% } %>
<div class="card">
<div class="card-header">&#x1F4AC; Package Hint</div>
<% if (pkg.last_scan_hint) { %>
<p style="font-style: italic; font-size: 1.05rem;">"<%= pkg.last_scan_hint %>"</p>
<p style="font-size: 0.8rem; color: var(--muted);">Left by <%= pkg.last_scanner_name %></p>
<% } else { %>
<p style="color: var(--muted);">No hint has been left yet.</p>
<% } %>
<% if (isLastScanner) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/hint" style="margin-top: 1rem;">
<div class="form-group">
<label>Update Hint/Message</label>
<textarea name="hint" class="form-control" rows="2" maxlength="500" placeholder="Leave a hint or message for the next finder..."><%= pkg.last_scan_hint || '' %></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm">Save Hint</button>
</form>
<% } %>
</div>
<% if (scanHistory.length > 0) { %>
<div class="card">
<div class="card-header">Scan History</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>#</th>
<th>Player</th>
<th>Points</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<% scanHistory.forEach((scan, i) => { %>
<tr>
<td><%= i + 1 %></td>
<td><%= scan.username %></td>
<td><% if (scan.points_awarded > 0) { %><span class="points-badge">+<%= scan.points_awarded %></span><% } else { %><span style="color: var(--muted);">0</span><% } %></td>
<td style="font-size: 0.85rem; color: var(--muted);"><%= new Date(scan.scanned_at).toLocaleString() %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
<% } %>
<div style="text-align: center; margin-top: 1rem;">
<a href="/hunt/<%= pkg.hunt_short_name %>" class="btn btn-outline">View All Packages</a>
<a href="/hunt/<%= pkg.hunt_short_name %>/leaderboard" class="btn btn-outline">Leaderboard</a>
</div>
</div>
<%- include('../partials/footer') %>
+122
View File
@@ -0,0 +1,122 @@
<%- include('../partials/header') %>
<div class="container">
<div class="scan-result">
<% if (scanResult.alreadyScanned) { %>
<div class="emoji">&#x1F504;</div>
<h1>Already Scanned!</h1>
<p style="color: var(--muted);">You've already found this package. No additional points, but you're now the most recent scanner and can update the hint below.</p>
<% } else if (scanResult.isFirst) { %>
<div class="emoji">&#x1F31F;</div>
<h1>FIRST FIND!</h1>
<div class="points-badge large">+<%= scanResult.points %> pts</div>
<p style="color: var(--muted); margin-top: 0.5rem;">You're the first person to discover this package! You can upload an image below.</p>
<% } else { %>
<div class="emoji">&#x2705;</div>
<h1>Package Found!</h1>
<div class="points-badge large">+<%= scanResult.points %> pts</div>
<p style="color: var(--muted); margin-top: 0.5rem;">Scan #<%= scanResult.scanNumber %> &mdash; nice find!</p>
<% } %>
</div>
<div class="card">
<div class="package-hero">
<div class="card-number">#<%= pkg.card_number %></div>
<div class="hunt-name"><a href="/hunt/<%= pkg.hunt_short_name %>"><%= pkg.hunt_name %></a></div>
<div style="font-family: monospace; color: var(--muted); margin-top: 0.25rem;"><%= pkg.unique_code %></div>
</div>
<div class="stats-row">
<div class="stat-box">
<div class="value"><%= pkg.scan_count %></div>
<div class="label">Total Scans</div>
</div>
<div class="stat-box">
<div class="value"><%= pkg.first_scanner_name || '&mdash;' %></div>
<div class="label">First Finder</div>
</div>
<div class="stat-box">
<div class="value"><%= pkg.last_scanner_name || '&mdash;' %></div>
<div class="label">Most Recent</div>
</div>
</div>
</div>
<% if (pkg.first_scan_image) { %>
<div class="card">
<div class="card-header">First Finder's Photo</div>
<img src="<%= pkg.first_scan_image %>" alt="Package photo" class="package-image">
</div>
<% } %>
<%/* First scanner can upload image */%>
<% if (isFirstScanner && !pkg.first_scan_image) { %>
<div class="card">
<div class="card-header">&#x1F4F8; Upload a Photo</div>
<p style="color: var(--muted); font-size: 0.9rem;">As the first finder, you can upload a photo for this package.</p>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image" enctype="multipart/form-data">
<div class="form-group">
<input type="file" name="image" accept="image/*" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Upload Photo</button>
</form>
</div>
<% } %>
<%/* Hint/message section */%>
<div class="card">
<div class="card-header">&#x1F4AC; Package Hint</div>
<% if (pkg.last_scan_hint) { %>
<p style="font-style: italic; font-size: 1.05rem;">"<%= pkg.last_scan_hint %>"</p>
<p style="font-size: 0.8rem; color: var(--muted);">Left by <%= pkg.last_scanner_name %></p>
<% } else { %>
<p style="color: var(--muted);">No hint has been left yet.</p>
<% } %>
<% if (isLastScanner) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/hint" style="margin-top: 1rem;">
<div class="form-group">
<label>Update Hint/Message</label>
<textarea name="hint" class="form-control" rows="2" maxlength="500" placeholder="Leave a hint or message for the next finder..."><%= pkg.last_scan_hint || '' %></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm">Save Hint</button>
</form>
<% } %>
</div>
<%/* Scan history */%>
<% if (scanHistory.length > 0) { %>
<div class="card">
<div class="card-header">Scan History</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>#</th>
<th>Player</th>
<th>Points</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<% scanHistory.forEach((scan, i) => { %>
<tr>
<td><%= i + 1 %></td>
<td><%= scan.username %></td>
<td><% if (scan.points_awarded > 0) { %><span class="points-badge">+<%= scan.points_awarded %></span><% } else { %><span style="color: var(--muted);">0</span><% } %></td>
<td style="font-size: 0.85rem; color: var(--muted);"><%= new Date(scan.scanned_at).toLocaleString() %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
<% } %>
<div style="text-align: center; margin-top: 1rem;">
<a href="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/profile" class="btn btn-outline">View Package Profile</a>
<a href="/hunt/<%= pkg.hunt_short_name %>" class="btn btn-outline">Back to Hunt</a>
</div>
</div>
<%- include('../partials/footer') %>
+5
View File
@@ -0,0 +1,5 @@
<footer class="footer">
&copy; <%= new Date().getFullYear() %> Loot Hunt &mdash; Find. Scan. Conquer.
</footer>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= typeof title !== 'undefined' ? title + ' | Loot Hunt' : 'Loot Hunt' %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<nav class="navbar">
<a href="/" class="navbar-brand">&#x1F3AF; Loot Hunt</a>
<ul class="navbar-nav">
<li><a href="/hunts">Hunts</a></li>
<li><a href="/leaderboard">Leaderboard</a></li>
<% if (currentUser) { %>
<% if (currentUser.isAdmin) { %>
<li><a href="/admin">Admin</a></li>
<% } %>
<li><a href="/auth/logout">Logout (<%= currentUser.username %>)</a></li>
<% } else { %>
<li><a href="/auth/login">Login</a></li>
<li><a href="/auth/register">Register</a></li>
<% } %>
</ul>
</nav>
<% if (typeof flash !== 'undefined' && flash) { %>
<div class="container">
<div class="alert alert-<%= flash.type %>"><%= flash.message %></div>
</div>
<% } %>