This commit is contained in:
@@ -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') %>
|
||||
@@ -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 %> · <%= hunt.package_count %> packages
|
||||
<% if (hunt.expiry_date) { %> · Expires: <%= new Date(hunt.expiry_date).toLocaleDateString() %><% } %>
|
||||
</span>
|
||||
</div>
|
||||
<span class="badge">Manage</span>
|
||||
</a>
|
||||
<% }) %>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<%- include('../partials/footer') %>
|
||||
@@ -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">📥 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() : '—' %></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 || '—' %></td>
|
||||
<td><%= pkg.last_scanner_name || '—' %></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') %>
|
||||
@@ -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') %>
|
||||
@@ -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') %>
|
||||
@@ -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') %>
|
||||
@@ -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 %> · <%= hunt.package_count %> packages · 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') %>
|
||||
@@ -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;">← Back to <%= hunt.name %></a>
|
||||
</div>
|
||||
|
||||
<h1><%= hunt.name %> — 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') %>
|
||||
@@ -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 %> · <%= hunt.package_count %> packages · by <%= hunt.creator_name %>
|
||||
<% if (hunt.expiry_date) { %>
|
||||
· 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') %>
|
||||
@@ -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) { %>
|
||||
✅ Found · <%= pkg.scan_count %> scan<%= pkg.scan_count !== 1 ? 's' : '' %>
|
||||
<% if (pkg.first_scanner_name) { %> · First: <%= pkg.first_scanner_name %><% } %>
|
||||
<% } else { %>
|
||||
🔍 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') %>
|
||||
@@ -0,0 +1,40 @@
|
||||
<%- include('../partials/header') %>
|
||||
|
||||
<div class="container">
|
||||
<h1 style="margin-bottom: 1.5rem;">🏆 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) { %>🥇<% } else if (i === 1) { %>🥈<% } else if (i === 2) { %>🥉<% } 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') %>
|
||||
@@ -0,0 +1,13 @@
|
||||
<%- include('../partials/header') %>
|
||||
|
||||
<div class="container" style="text-align: center; padding-top: 3rem;">
|
||||
<div class="emoji" style="font-size: 4rem;">⏰</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> · 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') %>
|
||||
@@ -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;">← 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 || '—' %></div>
|
||||
<div class="label">First Finder</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="value"><%= pkg.last_scanner_name || '—' %></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">📸 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">💬 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') %>
|
||||
@@ -0,0 +1,122 @@
|
||||
<%- include('../partials/header') %>
|
||||
|
||||
<div class="container">
|
||||
<div class="scan-result">
|
||||
<% if (scanResult.alreadyScanned) { %>
|
||||
<div class="emoji">🔄</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">🌟</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">✅</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 %> — 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 || '—' %></div>
|
||||
<div class="label">First Finder</div>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="value"><%= pkg.last_scanner_name || '—' %></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">📸 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">💬 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') %>
|
||||
@@ -0,0 +1,5 @@
|
||||
<footer class="footer">
|
||||
© <%= new Date().getFullYear() %> Loot Hunt — Find. Scan. Conquer.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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">🎯 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>
|
||||
<% } %>
|
||||
Reference in New Issue
Block a user