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:
Rolf
2026-03-20 10:16:46 +01:00
parent 4b31b98f89
commit 3a1da1482c
51 changed files with 4964 additions and 1 deletions
+39
View File
@@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block title %}Games Skins &amp; Pins{% endblock %}
{% block content %}
<div class="page">
<div class="page-header" style="display:flex; justify-content:space-between; align-items:center;">
<h1>Games</h1>
<a href="/games/new" class="btn btn-primary" style="width:auto; padding: 0.5rem 1rem;">+ New</a>
</div>
{% if games %}
{% for g in games %}
<a href="/games/{{ g.id }}{% if g.status == 'completed' %}/summary{% endif %}" class="card game-card" style="display:block; text-decoration:none;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div>
<div style="font-weight:600; color:var(--text);">{{ g.course_name or 'Bingo Bango Bongo' }}</div>
<div style="color:var(--text-muted); font-size:0.85rem;">{{ g.holes_count }} holes · {{ g.created_at[:10] }}</div>
</div>
<div style="text-align:right;">
<span class="status-badge status-{{ g.status }}">{{ g.status }}</span>
{% if g.status == 'completed' and g.id in winners %}
{% set w = winners[g.id] %}
<div style="font-size:0.78rem; color:var(--accent); margin-top:0.3rem;">
{% if w.avatar and w.avatar.startswith('/') %}<img src="{{ w.avatar }}" style="width:1rem;height:1rem;border-radius:50%;object-fit:cover;vertical-align:middle;">{% else %}{{ w.avatar or '👤' }}{% endif %} {{ w.name }}
</div>
{% endif %}
</div>
</div>
</a>
{% endfor %}
{% else %}
<div class="empty-state">
<div class="empty-icon"></div>
<p>No games yet.</p>
<a href="/games/new" class="btn btn-primary" style="margin-top:1rem; max-width:200px;">Start a game</a>
</div>
{% endif %}
</div>
{% endblock %}