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>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<!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 & 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 & 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>
|
||||
Reference in New Issue
Block a user