Files
SkinsPins/app/templates/base.html
T
Rolf 3a1da1482c Add full SkinsPins application
- FastAPI + SQLite + Alpine.js + HTMX PWA for Bingo Bango Bongo golf scoring
- Passwordless magic link auth with email (aiosmtplib) and admin invite system
- Real-time score entry via WebSocket with drag-and-drop player ordering
- Course management with per-hole par and stroke index
- Scorecard with golf score markers (birdie, eagle, bogey, etc.)
- Two-step new game form with tee selection per player
- Dashboard with stats, live games social stream, and recent game history
- Game summary view (read-only scorecard with leaderboard)
- User profile pages with win stats
- PWA install support with generated PNG icons
- Admin panel for users, courses, games, and invitations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:16:46 +01:00

104 lines
3.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#2d6a4f">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<link rel="manifest" href="/static/manifest.json">
<link rel="stylesheet" href="/static/style.css">
<title>{% block title %}Skins &amp; Pins{% endblock %}</title>
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js" defer></script>
<script src="https://unpkg.com/alpinejs@3.14.3/dist/cdn.min.js" defer></script>
{% block extra_head %}{% endblock %}
</head>
<body>
{# PWA install banner #}
<div id="pwa-banner" style="display:none;">
<div id="pwa-banner-android" style="display:none;">
<span>Install Skins &amp; Pins for the best experience</span>
<div class="pwa-banner-actions">
<button class="pwa-install-btn" onclick="pwaDismiss()">Not now</button>
<button class="pwa-install-btn pwa-install-primary" onclick="pwaInstall()">Install</button>
</div>
</div>
<div id="pwa-banner-ios" style="display:none;">
<span>Install: tap <strong>Share</strong> then <strong>Add to Home Screen</strong></span>
<button class="pwa-install-btn" onclick="pwaDismiss()"></button>
</div>
</div>
{% block content %}{% endblock %}
{% if user is defined %}
{% include "nav.html" %}
{% endif %}
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/static/sw.js');
}
// PWA install prompt
let deferredPrompt = null;
const banner = document.getElementById('pwa-banner');
const androidBanner = document.getElementById('pwa-banner-android');
const iosBanner = document.getElementById('pwa-banner-ios');
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|| navigator.standalone === true;
const isDismissed = localStorage.getItem('pwa-dismissed') === '1';
const isIos = /iphone|ipad|ipod/i.test(navigator.userAgent) && !window.MSStream;
const isAndroid = /android/i.test(navigator.userAgent);
function showBanner() {
banner.style.display = 'flex';
document.body.style.paddingTop = banner.offsetHeight + 'px';
}
if (!isStandalone && !isDismissed) {
if (isIos) {
showBanner();
iosBanner.style.display = 'flex';
} else if (isAndroid) {
// Try native install prompt first
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showBanner();
androidBanner.style.display = 'flex';
});
// Fallback: if prompt doesn't fire (e.g. HTTP), show manual instructions
setTimeout(() => {
if (!deferredPrompt && banner.style.display === 'none') {
const btn = androidBanner.querySelector('.pwa-install-primary');
btn.textContent = 'How?';
btn.onclick = () => alert('Tap ⋮ in Chrome, then "Add to Home screen" or "Install app".');
showBanner();
androidBanner.style.display = 'flex';
}
}, 1500);
}
}
function pwaInstall() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
deferredPrompt.userChoice.then(() => {
deferredPrompt = null;
banner.style.display = 'none';
localStorage.setItem('pwa-dismissed', '1');
});
}
function pwaDismiss() {
banner.style.display = 'none';
localStorage.setItem('pwa-dismissed', '1');
}
</script>
</body>
</html>