2c0717948a
When creating a skins game, players can now choose between straight up (gross scores) or net scoring (strokes subtracted per hole based on playing handicap and stroke index). Net mode is stored on the game and applied in both the live scorecard and the summary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
285 lines
12 KiB
HTML
285 lines
12 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ game.course_name or 'Game' }} – Skins & Pins{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="page" x-data="{ view: 'strokes' }">
|
||
<div class="page-header" style="display:flex; align-items:center; justify-content:space-between;">
|
||
<a href="javascript:history.back()" style="color:var(--text-muted); font-size:0.9rem;">← Back</a>
|
||
<div style="text-align:center;">
|
||
<div style="font-weight:600;">{{ game.course_name or 'Bingo Bango Bongo' }}</div>
|
||
<div style="font-size:0.8rem; color:var(--text-muted);">{{ game.holes_count }} holes · {{ game.created_at[:10] }}{% if game.format == 'skins' and skins_net %} · net{% endif %}</div>
|
||
</div>
|
||
<div style="width:3rem;"></div>
|
||
</div>
|
||
|
||
{# Leaderboard #}
|
||
<div class="card" style="padding:0.75rem 1rem;">
|
||
{% for p in results %}
|
||
{% if game.format == 'skins' %}
|
||
{% if skin_value and money_won %}
|
||
{% set score_val = money_won.get(p.id, 0) %}
|
||
{% set score_label = "%.4g" % score_val %}
|
||
{% else %}
|
||
{% set score_val = skins_won.get(p.id, 0) %}
|
||
{% set score_label = score_val | string + ' skin' + ('s' if score_val != 1 else '') %}
|
||
{% endif %}
|
||
{% else %}
|
||
{% set t = totals.get(p.id, {}) %}
|
||
{% set score_val = t.total %}
|
||
{% set score_label = t.total | round(1) | string + ' pts' %}
|
||
{% endif %}
|
||
<div class="result-row {% if loop.first %}result-winner{% endif %}" style="padding:0.4rem 0;">
|
||
<span class="result-rank">{% if loop.first %}🏆{% else %}{{ loop.index }}{% endif %}</span>
|
||
<span class="result-avatar">
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.4rem;height:1.4rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ p.avatar or '👤' }}
|
||
{% endif %}
|
||
</span>
|
||
<a href="/users/{{ p.id }}" style="flex:1; color:inherit; text-decoration:none;">{{ p.name }}</a>
|
||
<span class="result-pts">{{ score_label }}</span>
|
||
{% if game.format == 'bingo_bango_bongo' %}
|
||
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:0.5rem;">
|
||
B{{ t.bingo | int }}/{{ t.bango | round(1) }}/{{ t.bongo | int }}
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
{% if game.format == 'skins' and unclaimed %}
|
||
<div style="font-size:0.8rem; color:var(--text-muted); margin-top:0.5rem; text-align:center;">
|
||
{% if skin_value and unclaimed_money %}{{ "%.4g" % unclaimed_money }} unclaimed
|
||
{% else %}{{ unclaimed }} skin{{ 's' if unclaimed != 1 else '' }} unclaimed{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Playing handicaps #}
|
||
{% if playing_handicaps and playing_handicaps.values() | select | list %}
|
||
<div class="card" style="margin-top:0.75rem; padding:0.75rem 1rem;">
|
||
<div style="font-size:0.8rem; color:var(--text-muted); font-weight:500; margin-bottom:0.5rem; text-transform:uppercase; letter-spacing:0.05em;">Playing Handicaps</div>
|
||
{% for p in players %}
|
||
{% set ph = playing_handicaps.get(p.id) %}
|
||
{% if ph is not none %}
|
||
<div style="display:flex; align-items:center; gap:0.6rem; padding:0.35rem 0; {% if not loop.last %}border-bottom:1px solid var(--border);{% endif %} font-size:0.9rem;">
|
||
<span>
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}{{ p.avatar or '👤' }}{% endif %}
|
||
</span>
|
||
<span style="flex:1; font-weight:500;">{{ p.name }}</span>
|
||
{% if p.handicap_snapshot is not none %}
|
||
<span style="color:var(--text-muted);">HI {{ p.handicap_snapshot }}</span>
|
||
{% endif %}
|
||
<span style="font-weight:600; color:var(--accent); min-width:3rem; text-align:right;">HC {{ ph }}</span>
|
||
{% if p.tee_name %}
|
||
<span style="color:var(--text-muted); font-size:0.8rem; min-width:4rem; text-align:right;">{{ p.tee_name }}</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{# Scorecard toggle #}
|
||
<div class="scorecard-toggle" style="margin-top:1rem;">
|
||
<button @click="view = 'strokes'" :class="{ active: view === 'strokes' }">Strokes</button>
|
||
{% if game.format == 'skins' %}
|
||
<button @click="view = 'skins'" :class="{ active: view === 'skins' }">Skins</button>
|
||
{% else %}
|
||
<button @click="view = 'points'" :class="{ active: view === 'points' }">Points</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Strokes scorecard #}
|
||
<table class="scorecard" x-show="view === 'strokes'">
|
||
<thead>
|
||
<tr>
|
||
<th>Hole</th>
|
||
{% if hole_pars %}<th style="color:var(--text-muted);">Par</th>{% endif %}
|
||
{% for p in players %}
|
||
<th>
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ p.avatar or '👤' }}
|
||
{% endif %}<br>
|
||
<a href="/users/{{ p.id }}" style="font-size:0.7rem; color:inherit; text-decoration:none;">{{ p.name.split()[0] }}</a>
|
||
</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for h in holes %}
|
||
<tr>
|
||
<td class="hole-num">{{ h }}</td>
|
||
{% if hole_pars %}<td class="score-cell" style="color:var(--text-muted);">{{ hole_pars.get(h, '—') }}</td>{% endif %}
|
||
{% for p in players %}
|
||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||
{% set strokes = s.strokes if s else 0 %}
|
||
{% set par = hole_pars.get(h, 0) %}
|
||
{% set sr = strokes_per_hole.get(p.id, {}).get(h, 0) %}
|
||
<td class="score-cell">
|
||
{% if strokes %}
|
||
{% if par %}
|
||
{% set diff = strokes - par %}
|
||
{% if strokes == 1 %}<span class="score-hio">{{ strokes }}</span>
|
||
{% elif diff <= -2 %}<span class="score-eagle">{{ strokes }}</span>
|
||
{% elif diff == -1 %}<span class="score-birdie">{{ strokes }}</span>
|
||
{% elif diff == 1 %}<span class="score-bogey">{{ strokes }}</span>
|
||
{% elif diff >= 2 %}<span class="score-double-bogey">{{ strokes }}</span>
|
||
{% else %}{{ strokes }}{% endif %}
|
||
{% else %}{{ strokes }}{% endif %}
|
||
{% else %}—{% endif %}
|
||
{% if has_stroke_index and sr > 0 %}<sup style="color:var(--accent); font-size:0.6em; line-height:0;">{{ '/' * sr }}</sup>{% endif %}
|
||
</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="totals-row">
|
||
<td>Total</td>
|
||
{% if hole_pars %}<td style="color:var(--text-muted);">{{ hole_pars.values() | sum }}</td>{% endif %}
|
||
{% for p in players %}
|
||
{% set stroke_total = namespace(val=0) %}
|
||
{% for h in holes %}
|
||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||
{% if s and s.strokes %}{% set stroke_total.val = stroke_total.val + s.strokes %}{% endif %}
|
||
{% endfor %}
|
||
<td>{{ stroke_total.val or '—' }}</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% set any_hc = playing_handicaps and playing_handicaps.values() | select | list %}
|
||
{% if any_hc %}
|
||
<tr>
|
||
<td style="color:var(--text-muted); font-size:0.8rem;">HC</td>
|
||
{% if hole_pars %}<td></td>{% endif %}
|
||
{% for p in players %}
|
||
{% set ph = playing_handicaps.get(p.id) %}
|
||
<td style="color:var(--text-muted); font-size:0.8rem;">{{ ph if ph is not none else '—' }}</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endif %}
|
||
</tfoot>
|
||
</table>
|
||
|
||
{# Points scorecard #}
|
||
<table class="scorecard" x-show="view === 'points'">
|
||
<thead>
|
||
<tr>
|
||
<th>Hole</th>
|
||
{% for p in players %}
|
||
<th>
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ p.avatar or '👤' }}
|
||
{% endif %}<br>
|
||
<a href="/users/{{ p.id }}" style="font-size:0.7rem; color:inherit; text-decoration:none;">{{ p.name.split()[0] }}</a>
|
||
</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for h in holes %}
|
||
<tr>
|
||
<td class="hole-num">{{ h }}</td>
|
||
{% for p in players %}
|
||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||
{% set pts = (s.bingo + s.bango + s.bongo) if s else 0 %}
|
||
<td class="score-cell">{{ pts | round(1) if pts else '—' }}</td>
|
||
{% endfor %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="totals-row">
|
||
<td>Total</td>
|
||
{% for p in players %}
|
||
<td>{{ totals.get(p.id, {}).get('total', 0) | round(1) }}</td>
|
||
{% endfor %}
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
|
||
{# Skins scorecard #}
|
||
{% if game.format == 'skins' %}
|
||
<table class="scorecard" x-show="view === 'skins'">
|
||
<thead>
|
||
<tr>
|
||
<th>Hole</th>
|
||
{% if hole_pars %}<th style="color:var(--text-muted);">Par</th>{% endif %}
|
||
{% for p in players %}
|
||
<th>
|
||
{% if p.avatar and p.avatar.startswith('/') %}
|
||
<img src="{{ p.avatar }}" style="width:1.3rem;height:1.3rem;border-radius:50%;object-fit:cover;vertical-align:middle;">
|
||
{% else %}
|
||
{{ p.avatar or '👤' }}
|
||
{% endif %}<br>
|
||
<a href="/users/{{ p.id }}" style="font-size:0.7rem; color:inherit; text-decoration:none;">{{ p.name.split()[0] }}</a>
|
||
</th>
|
||
{% endfor %}
|
||
<th style="color:var(--text-muted);">Skin</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for h in holes %}
|
||
{% set hr = skins_hole_results.get(h) %}
|
||
<tr>
|
||
<td class="hole-num">{{ h }}</td>
|
||
{% if hole_pars %}<td class="score-cell" style="color:var(--text-muted);">{{ hole_pars.get(h, '—') }}</td>{% endif %}
|
||
{% for p in players %}
|
||
{% set s = scores.get(h, {}).get(p.id, {}) %}
|
||
{% set strokes = s.strokes if s else 0 %}
|
||
{% set par = hole_pars.get(h, 0) %}
|
||
{% set sr = strokes_per_hole.get(p.id, {}).get(h, 0) %}
|
||
<td class="score-cell">
|
||
{% if strokes %}
|
||
{% set is_winner = hr and hr.winner == p.id %}
|
||
{% if par %}
|
||
{% set diff = strokes - par %}
|
||
{% if strokes == 1 %}<span class="score-hio{% if is_winner %} skin-winner{% endif %}">{{ strokes }}</span>
|
||
{% elif diff <= -2 %}<span class="score-eagle{% if is_winner %} skin-winner{% endif %}">{{ strokes }}</span>
|
||
{% elif diff == -1 %}<span class="score-birdie{% if is_winner %} skin-winner{% endif %}">{{ strokes }}</span>
|
||
{% elif diff == 1 %}<span class="score-bogey{% if is_winner %} skin-winner{% endif %}">{{ strokes }}</span>
|
||
{% elif diff >= 2 %}<span class="score-double-bogey{% if is_winner %} skin-winner{% endif %}">{{ strokes }}</span>
|
||
{% else %}<span{% if is_winner %} class="skin-winner"{% endif %}>{{ strokes }}</span>{% endif %}
|
||
{% else %}<span{% if is_winner %} class="skin-winner"{% endif %}>{{ strokes }}</span>{% endif %}
|
||
{% else %}—{% endif %}
|
||
{% if has_stroke_index and sr > 0 %}<sup style="color:var(--accent); font-size:0.6em; line-height:0;">{{ '/' * sr }}</sup>{% endif %}
|
||
</td>
|
||
{% endfor %}
|
||
<td class="score-cell">
|
||
{% if hr %}
|
||
{% if skin_value %}
|
||
{% if hr.winner %}{{ "%.4g" % hr.money_pot }}{% else %}→ {{ "%.4g" % hr.money_pot }}{% endif %}
|
||
{% else %}
|
||
{% if hr.winner %}✓{% if hr.pot > 1 %} {{ hr.pot }}{% endif %}
|
||
{% else %}→ {{ hr.pot }}{% endif %}
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="totals-row">
|
||
<td>{% if skin_value %}Value{% else %}Skins{% endif %}</td>
|
||
{% if hole_pars %}<td></td>{% endif %}
|
||
{% for p in players %}
|
||
{% if skin_value and money_won %}
|
||
<td>{{ "%.4g" % money_won.get(p.id, 0) }}</td>
|
||
{% else %}
|
||
<td>{{ skins_won.get(p.id, 0) }}</td>
|
||
{% endif %}
|
||
{% endfor %}
|
||
<td>{% if skin_value and unclaimed_money %}({{ "%.4g" % unclaimed_money }}){% elif unclaimed %}({{ unclaimed }}){% endif %}</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
{% endif %}
|
||
|
||
</div>
|
||
{% endblock %}
|