- Add about.html explaining infinity bottles, why to track, how to start - Hero shows "What is it?" + "Track Your Bottle" when logged out; hides about button when logged in - Redirect after login goes to index (community page) instead of dashboard - redirectIfLoggedIn also sends to index Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
5.2 KiB
HTML
148 lines
5.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Bourbonacci — Infinity Bottle Tracker</title>
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🥃</text></svg>" />
|
|
<link rel="stylesheet" href="/css/style.css" />
|
|
</head>
|
|
<body>
|
|
|
|
<nav>
|
|
<div class="nav-left">
|
|
<a href="/index.html" class="nav-brand">🥃 Bourbonacci</a>
|
|
<div class="nav-links" id="nav-links"></div>
|
|
</div>
|
|
<div id="nav-user"></div>
|
|
</nav>
|
|
|
|
<main>
|
|
<div class="hero">
|
|
<h1>The Infinity Bottle</h1>
|
|
<p>One pour from every bottle. An ever-evolving blend that grows richer with every addition.</p>
|
|
<div style="display:flex;gap:1rem;justify-content:center;flex-wrap:wrap">
|
|
<a href="/about.html" class="btn btn-ghost" id="hero-about">What is it?</a>
|
|
<a href="/register.html" class="btn btn-primary" id="hero-cta">Track Your Bottle</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-header">
|
|
<h2>Community Bottles</h2>
|
|
</div>
|
|
|
|
<div id="user-cards" class="user-cards">
|
|
<div class="empty"><div class="empty-icon">⏳</div><p>Loading...</p></div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Bourbon list modal -->
|
|
<div id="bourbon-modal" class="modal-overlay" style="display:none">
|
|
<div class="modal-box" style="max-width:480px">
|
|
<h2 id="bourbon-modal-title"></h2>
|
|
<input type="text" id="bourbon-search" placeholder="Search bourbons…" style="margin-bottom:1rem" oninput="filterBourbons()" />
|
|
<div id="bourbon-list" style="max-height:380px;overflow-y:auto"></div>
|
|
<div style="display:flex;justify-content:flex-end;margin-top:1rem">
|
|
<button class="btn btn-ghost" onclick="closeBourbonModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/api.js"></script>
|
|
<script src="/js/auth.js"></script>
|
|
<script>
|
|
let allBourbons = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await Auth.renderNav('home');
|
|
|
|
if (Auth.isLoggedIn()) {
|
|
document.getElementById('hero-cta').textContent = 'Go to My Bottle';
|
|
document.getElementById('hero-cta').href = '/dashboard.html';
|
|
document.getElementById('hero-about').style.display = 'none';
|
|
}
|
|
|
|
const container = document.getElementById('user-cards');
|
|
|
|
try {
|
|
const stats = await API.public.stats();
|
|
|
|
if (stats.length === 0) {
|
|
container.innerHTML = `<div class="empty"><div class="empty-icon">🥃</div><p>No bottles yet — be the first to register!</p></div>`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = stats.map((u, i) => {
|
|
const proof = u.estimated_proof != null ? `${u.estimated_proof}` : '—';
|
|
const hasSize = u.bottle_size != null && u.bottle_size > 0;
|
|
const pct = hasSize ? Math.min(100, (u.current_total_shots / u.bottle_size) * 100) : 0;
|
|
const barHtml = hasSize ? `
|
|
<div class="bottle-bar-wrap">
|
|
<div class="bottle-bar" style="width:${pct}%"></div>
|
|
</div>` : '';
|
|
return `
|
|
<div class="user-card" style="cursor:pointer" onclick="openBourbonModal(${i})">
|
|
<div class="user-card-name">${escHtml(u.display_name)}</div>
|
|
<div class="stats-grid" style="margin-bottom:.75rem">
|
|
<div class="stat-box">
|
|
<span class="stat-value">${u.total_add_entries}</span>
|
|
<span class="stat-label">Bourbons</span>
|
|
</div>
|
|
<div class="stat-box">
|
|
<span class="stat-value">${proof}</span>
|
|
<span class="stat-label">Est. Proof</span>
|
|
</div>
|
|
<div class="stat-box">
|
|
<span class="stat-value">${u.current_total_shots}</span>
|
|
<span class="stat-label">Shots Left</span>
|
|
</div>
|
|
</div>
|
|
${barHtml}
|
|
</div>`;
|
|
}).join('');
|
|
|
|
// Store stats for modal use
|
|
window._communityStats = stats;
|
|
} catch (err) {
|
|
container.innerHTML = `<div class="alert alert-error">Could not load stats: ${err.message}</div>`;
|
|
}
|
|
});
|
|
|
|
function openBourbonModal(index) {
|
|
const u = window._communityStats[index];
|
|
allBourbons = u.bourbons || [];
|
|
|
|
document.getElementById('bourbon-modal-title').textContent = `${u.display_name}'s Bottle`;
|
|
document.getElementById('bourbon-search').value = '';
|
|
renderBourbonList(allBourbons);
|
|
document.getElementById('bourbon-modal').style.display = 'flex';
|
|
document.getElementById('bourbon-search').focus();
|
|
}
|
|
|
|
function closeBourbonModal() {
|
|
document.getElementById('bourbon-modal').style.display = 'none';
|
|
}
|
|
|
|
function filterBourbons() {
|
|
const q = document.getElementById('bourbon-search').value.toLowerCase();
|
|
renderBourbonList(allBourbons.filter(b => b.toLowerCase().includes(q)));
|
|
}
|
|
|
|
function renderBourbonList(list) {
|
|
const el = document.getElementById('bourbon-list');
|
|
if (list.length === 0) {
|
|
el.innerHTML = `<div style="color:var(--cream-dim);font-size:.9rem;padding:.5rem 0">No bourbons found.</div>`;
|
|
return;
|
|
}
|
|
el.innerHTML = list.map(b => `
|
|
<div style="padding:.5rem 0;border-bottom:1px solid var(--border);color:var(--cream)">${escHtml(b)}</div>
|
|
`).join('');
|
|
}
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.id === 'bourbon-modal') closeBourbonModal();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|