8601584099
- Playing handicap: computed at game creation (WHS formula), stored on game_players alongside per-hole strokes allocation; displayed on game summary with HC card and / markers on scorecard (only when SI defined); live game shows +N per player card when SI exists, HC total otherwise; new game confirmation previews computed HC per player - Login: 6-digit code sent alongside magic link in every auth email; check_email page has auto-advancing digit inputs with paste support; new POST /auth/verify-code route handles code verification - Game view: Score/Card toggle fixed with position:fixed to always show above nav bar on mobile; swipe on score view changes holes, swipe on scorecard switches tabs; dirty tracking prevents saving unmodified holes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
4.0 KiB
HTML
119 lines
4.0 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">
|
||
<link rel="manifest" href="/static/manifest.json">
|
||
<link rel="stylesheet" href="/static/style.css">
|
||
<title>Check your email – Skins & Pins</title>
|
||
</head>
|
||
<body>
|
||
<div class="login-container">
|
||
<div class="login-logo">📬</div>
|
||
<h1 class="login-title">Check your email</h1>
|
||
<p class="login-subtitle">We sent a code and a login link to <strong>{{ email }}</strong></p>
|
||
|
||
<div class="login-card card">
|
||
{% if error %}
|
||
<p style="color:var(--danger); font-size:0.9rem; text-align:center; margin-bottom:1rem;">{{ error }}</p>
|
||
{% endif %}
|
||
|
||
<form method="post" action="/auth/verify-code" autocomplete="off"
|
||
style="display:flex; flex-direction:column; gap:1rem;">
|
||
<input type="hidden" name="email" value="{{ email }}">
|
||
|
||
<label style="font-size:0.85rem; color:var(--text-muted); text-align:center;">
|
||
Enter your 6-digit code
|
||
</label>
|
||
|
||
<div style="display:flex; gap:0.5rem; justify-content:center;" id="code-inputs">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="1" autocomplete="one-time-code">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="2">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="3">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="4">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="5">
|
||
<input type="text" inputmode="numeric" maxlength="1" pattern="[0-9]"
|
||
class="code-digit" tabindex="6">
|
||
</div>
|
||
|
||
<input type="hidden" name="code" id="code-value">
|
||
|
||
<button type="submit" class="btn btn-primary" id="code-submit" disabled>Verify code</button>
|
||
</form>
|
||
|
||
<p style="color:var(--text-muted); font-size:0.85rem; text-align:center; margin-top:1rem;">
|
||
The code and link expire in 15 minutes.
|
||
</p>
|
||
</div>
|
||
|
||
<a href="/login" style="color:var(--text-muted); font-size:0.85rem; margin-top:1.5rem;">
|
||
← Use a different email
|
||
</a>
|
||
</div>
|
||
|
||
<style>
|
||
.code-digit {
|
||
width: 3rem;
|
||
height: 3.5rem;
|
||
text-align: center;
|
||
font-size: 1.6rem;
|
||
font-weight: 700;
|
||
border: 2px solid var(--surface2);
|
||
border-radius: 0.5rem;
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
caret-color: transparent;
|
||
}
|
||
.code-digit:focus {
|
||
outline: none;
|
||
border-color: var(--accent);
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
const digits = [...document.querySelectorAll('.code-digit')];
|
||
const hidden = document.getElementById('code-value');
|
||
const submit = document.getElementById('code-submit');
|
||
|
||
function sync() {
|
||
const val = digits.map(d => d.value).join('');
|
||
hidden.value = val;
|
||
submit.disabled = val.length < 6;
|
||
}
|
||
|
||
digits.forEach((el, i) => {
|
||
el.addEventListener('input', () => {
|
||
el.value = el.value.replace(/\D/g, '').slice(-1);
|
||
sync();
|
||
if (el.value && i < digits.length - 1) digits[i + 1].focus();
|
||
});
|
||
|
||
el.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Backspace' && !el.value && i > 0) {
|
||
digits[i - 1].focus();
|
||
digits[i - 1].value = '';
|
||
sync();
|
||
}
|
||
});
|
||
|
||
el.addEventListener('paste', (e) => {
|
||
e.preventDefault();
|
||
const pasted = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '');
|
||
digits.forEach((d, j) => d.value = pasted[j] || '');
|
||
sync();
|
||
const next = Math.min(pasted.length, digits.length - 1);
|
||
digits[next].focus();
|
||
});
|
||
});
|
||
|
||
digits[0].focus();
|
||
</script>
|
||
</body>
|
||
</html>
|