DIS_IPL / templates /predict.html
Jay-Rajput's picture
DIS IPL 2026
60e9d75
{% extends 'base.html' %}
{% block title %}Predict – {{ match.team1 }} vs {{ match.team2 }} – {{ app_brand }}{% endblock %}
{% block head %}
<style>
.motm-lead { font-size: 0.88rem; color: var(--muted2); margin: 0.35rem 0 0.65rem; line-height: 1.45; }
#predicted_motm:disabled {
opacity: 0.65; cursor: not-allowed;
}
</style>
{% endblock %}
{% block content %}
<div class="page" style="max-width:680px;">
<div style="margin-bottom:1rem;">
<a href="{{ url_for('dashboard') }}" style="color:var(--muted2); text-decoration:none; font-size:0.875rem;">← Back to Dashboard</a>
</div>
<!-- Match Banner -->
<div class="card" style="margin-bottom:1.5rem; background:linear-gradient(135deg, var(--card) 0%, rgba(249,115,22,0.05) 100%); border-color:rgba(249,115,22,0.2);">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
<span class="badge badge-{{ match.status }}">
{% if match.status == 'live' %}<span class="status-dot dot-live"></span>{% endif %}
{{ match.status|upper }}
</span>
{% if match.match_number %}<span style="font-size:0.8rem; color:var(--muted);">Match #{{ match.match_number }}</span>{% endif %}
</div>
<div class="match-vs">
<div class="team-block">
<div class="team-abbr" style="font-size:3rem; color:{{ match.team1_color }};">{{ match.team1_abbr }}</div>
<div class="team-name" style="font-size:0.9rem;">{{ match.team1 }}</div>
</div>
<div class="vs-divider" style="font-size:1.8rem;">VS</div>
<div class="team-block">
<div class="team-abbr" style="font-size:3rem; color:{{ match.team2_color }};">{{ match.team2_abbr }}</div>
<div class="team-name" style="font-size:0.9rem;">{{ match.team2 }}</div>
</div>
</div>
<div class="match-meta" style="margin-top:1rem; justify-content:center;">
<span>🗓️ {{ match.match_time_display }}</span>
{% if match.venue %}<span>📍 {{ match.venue }}{% if match.city %}, {{ match.city }}{% endif %}</span>{% endif %}
</div>
<div style="text-align:center; margin-top:0.75rem; font-size:0.82rem; color:var(--muted);">
📅 Open on <strong>match day only</strong> · locks at scheduled start ({{ match.match_time_display }})
</div>
</div>
{% if match.status == 'completed' %}
<!-- Result Display -->
<div class="card" style="border-color:rgba(34,197,94,0.3); background:rgba(34,197,94,0.05);">
<div class="card-title" style="color:var(--green);">🏆 MATCH RESULT</div>
<div style="font-size:1.5rem; font-weight:700;">{{ match.winner }}</div>
{% if match.man_of_match %}<div style="color:var(--muted2); margin-top:0.5rem;">⭐ Man of the Match: <strong>{{ match.man_of_match }}</strong></div>{% endif %}
{% if match.result_notes %}<div style="color:var(--muted2); font-size:0.85rem; margin-top:0.5rem;">{{ match.result_notes }}</div>{% endif %}
{% if existing %}
<hr>
<div style="display:flex; gap:1.5rem; flex-wrap:wrap;">
<div>
<div style="font-size:0.8rem; color:var(--muted2);">YOUR PICK</div>
<div style="font-weight:700;">{{ existing.predicted_winner }}</div>
{% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
</div>
<div>
<div style="font-size:0.8rem; color:var(--muted2);">BID</div>
<div style="font-weight:700; font-family:var(--font-mono);">{{ '%.0f'|format(existing.bid_amount) }} pts</div>
</div>
{% if existing.is_settled %}
<div>
<div style="font-size:0.8rem; color:var(--muted2);">RESULT</div>
<div style="font-weight:700;" class="{{ existing.points_earned|delta_class }}">{{ existing.points_earned|delta_sign }} pts</div>
<div style="font-size:0.78rem; color:var(--muted);">Winner: {% if existing.winner_correct %}✅{% else %}❌{% endif %} {% if existing.motm_correct is not none %}· MOTM: {% if existing.motm_correct %}✅{% else %}❌{% endif %}{% endif %}</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% elif match.can_predict %}
<!-- Prediction Form -->
<div class="card">
<div class="card-title">{% if existing %}✏️ EDIT PREDICTION{% else %}🎯 MAKE YOUR PREDICTION{% endif %}</div>
<!-- Current points display -->
<div style="display:flex; justify-content:space-between; align-items:center; padding:0.75rem 1rem; background:var(--bg3); border-radius:8px; margin-bottom:1.5rem;">
<div style="font-size:0.875rem; color:var(--muted2);">Your current balance</div>
<div style="font-family:var(--font-mono); font-size:1.25rem; font-weight:700;">
<span style="background:linear-gradient(135deg,var(--orange),var(--gold)); -webkit-background-clip:text; -webkit-text-fill-color:transparent;">
{{ '%.0f'|format(current_user.points) }}
</span>
<span style="color:var(--muted); font-size:0.8rem;"> pts</span>
</div>
</div>
<form method="post" id="predictForm">
<!-- Winner selection -->
<div class="form-group">
<label>🏆 Who will WIN? <span style="color:var(--red);">*</span></label>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem; margin-top:0.5rem;">
{% for team in [match.team1, match.team2] %}
{% set abbr = team_abbr.get(team, team[:3].upper()) %}
{% set color = team_colors.get(abbr, '#555') %}
<label class="team-pick-label" style="cursor:pointer; margin:0;">
<input type="radio" name="predicted_winner" value="{{ team }}" required
{% if existing and existing.predicted_winner == team %}checked{% endif %}
style="display:none;" onchange="onWinnerChange(this)">
<div class="team-pick-card" id="pick-{{ abbr }}" style="border:2px solid var(--border); border-radius:10px; padding:1rem; text-align:center; transition:all 0.2s; cursor:pointer; {% if existing and existing.predicted_winner == team %}border-color:{{ color }}; background:{{ color }}22;{% endif %}">
<div style="font-family:var(--font-display); font-size:2rem; color:{{ color }};">{{ abbr }}</div>
<div style="font-size:0.78rem; color:var(--muted2); margin-top:0.2rem;">{{ team }}</div>
</div>
</label>
{% endfor %}
</div>
</div>
<!-- MOTM: required; options = squad of predicted winner only -->
<div class="form-group">
<label for="predicted_motm">⭐ Man of the Match <span style="color:var(--red);">*</span></label>
<p class="motm-lead">
Choose from the <strong>squad of the team you picked to win</strong>.
<strong style="color:var(--green);">+{{ points_config.correct_motm }}</strong> if it matches the official call ·
<strong style="color:var(--red);">−{{ points_config.wrong_motm|abs }}</strong> if it doesn’t.
</p>
<select name="predicted_motm" id="predicted_motm" required disabled aria-label="Man of the Match">
<option value="">— Pick winning team first —</option>
</select>
<p id="motmEmptyHint" class="form-hint" style="display:none; margin-top:0.5rem;">
No players are listed for that team’s squad — you can’t submit until the roster is updated.
</p>
</div>
<!-- Bid -->
<div class="form-group">
<label>💰 Bid Amount <span style="color:var(--red);">*</span></label>
<div style="position:relative;">
<input type="number" name="bid_amount" id="bidInput"
min="{{ points_config.min_bid }}"
max="{{ [points_config.max_bid, current_user.points|int]|min }}"
step="5"
value="{{ existing.bid_amount|int if existing else points_config.min_bid }}"
required oninput="updateBidDisplay(this.value)">
<span style="position:absolute; right:1rem; top:50%; transform:translateY(-50%); color:var(--muted); font-size:0.85rem;">pts</span>
</div>
<div class="form-hint">
Min: <strong>{{ points_config.min_bid }}</strong> · Max: <strong>{{ [points_config.max_bid, current_user.points|int]|min }}</strong>
· Win: <strong class="text-green" id="winAmount">+{{ (existing.bid_amount|int if existing else points_config.min_bid) }}</strong>
· Lose: <strong class="text-red" id="loseAmount">−{{ (existing.bid_amount|int if existing else points_config.min_bid) }}</strong>
</div>
<!-- Quick bid buttons -->
<div style="display:flex; gap:0.5rem; margin-top:0.75rem; flex-wrap:wrap;">
{% set pts = current_user.points|int %}
{% set max_b = [points_config.max_bid, pts]|min %}
{% for pct in [10, 25, 50, 75, 100] %}
{% set amt = (max_b * pct / 100)|int %}
{% if amt >= points_config.min_bid %}
<button type="button" class="btn btn-ghost btn-sm" onclick="setBid({{ amt }})">{{ pct }}% ({{ amt }})</button>
{% endif %}
{% endfor %}
</div>
</div>
<!-- Potential outcome preview -->
<div id="outcomePreview" style="padding:1rem; background:var(--bg3); border-radius:8px; margin-bottom:1.5rem; font-size:0.85rem;">
<div style="color:var(--muted2); margin-bottom:0.5rem; font-size:0.78rem; text-transform:uppercase; letter-spacing:0.5px;">Potential Outcomes</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.5rem;">
<div>✅ Win + correct MOTM: <strong class="text-green" id="bestCase"></strong></div>
<div>✅ Win + wrong MOTM: <strong class="text-green" id="winWrongMotm"></strong></div>
<div>❌ Lose + correct MOTM: <strong id="loseCorrectMotm"></strong></div>
<div>❌ Lose + wrong MOTM: <strong class="text-red" id="loseWrongMotm"></strong></div>
</div>
</div>
<button type="submit" class="btn btn-primary" style="width:100%; justify-content:center; font-size:1rem; padding:0.85rem;">
{% if existing %}✏️ UPDATE PREDICTION{% else %}🚀 SUBMIT PREDICTION{% endif %}
</button>
{% if existing %}
<div style="text-align:center; margin-top:0.75rem; font-size:0.8rem; color:var(--muted);">
Last updated: {{ existing.updated_at[:16] }}
</div>
{% endif %}
</form>
</div>
{% elif match.status == 'upcoming' and not match.is_match_today %}
<div class="card" style="border-color:rgba(59,130,246,0.25); text-align:center; padding:2rem;">
<div style="font-size:2.5rem; margin-bottom:1rem;">📆</div>
<div style="font-size:1.15rem; font-weight:700; color:var(--blue);">Predictions open on match day</div>
<div style="color:var(--muted2); margin-top:0.6rem; font-size:0.92rem;">This fixture is on <strong>{{ match.match_date|format_date }}</strong>. Come back that day to submit or edit your pick (until the scheduled start).</div>
{% if existing %}
<div style="margin-top:1.5rem; padding:1rem; background:var(--bg3); border-radius:8px; text-align:left;">
<div style="font-size:0.8rem; color:var(--muted2); margin-bottom:0.5rem;">YOUR SAVED PICK (from match day)</div>
<div style="font-weight:700; color:var(--orange);">{{ existing.predicted_winner }}</div>
{% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
<div style="font-size:0.85rem; color:var(--muted2); margin-top:0.3rem;">Bid: <strong style="color:var(--gold);">{{ '%.0f'|format(existing.bid_amount) }}</strong> pts</div>
</div>
{% endif %}
</div>
{% elif match.locked or match.status == 'locked' %}
<div class="card" style="border-color:rgba(251,191,36,0.3); text-align:center; padding:2rem;">
<div style="font-size:3rem; margin-bottom:1rem;">🔒</div>
<div style="font-size:1.2rem; font-weight:700; color:var(--gold);">Predictions Locked</div>
<div style="color:var(--muted2); margin-top:0.5rem; font-size:0.9rem;">The prediction window for this match is closed.</div>
{% if existing %}
<div style="margin-top:1.5rem; padding:1rem; background:var(--bg3); border-radius:8px; text-align:left;">
<div style="font-size:0.8rem; color:var(--muted2); margin-bottom:0.5rem;">YOUR PREDICTION</div>
<div style="font-weight:700; color:var(--orange);">{{ existing.predicted_winner }}</div>
{% if existing.predicted_motm %}<div style="font-size:0.85rem; color:var(--muted2);">⭐ {{ existing.predicted_motm }}</div>{% endif %}
<div style="font-size:0.85rem; color:var(--muted2); margin-top:0.3rem;">Bid: <strong style="color:var(--gold);">{{ '%.0f'|format(existing.bid_amount) }}</strong> pts</div>
</div>
{% else %}
<div style="margin-top:1rem; color:var(--muted); font-size:0.9rem;">You didn't submit a prediction for this match.</div>
{% endif %}
</div>
{% else %}
<div class="card" style="text-align:center; padding:2rem; color:var(--muted2);">
<div style="font-size:2rem; margin-bottom:0.75rem;">🏏</div>
<div>Predictions aren't available for this match state (<span class="mono">{{ match.status }}</span>).</div>
</div>
{% endif %}
</div>
{% if match.can_predict %}
<script>
const CORRECT_MOTM = {{ points_config.correct_motm }};
const WRONG_MOTM = {{ points_config.wrong_motm }};
const MOTM_BY_WINNER = {{ { match.team1: squads.team1, match.team2: squads.team2 } | tojson }};
const INITIAL_MOTM = {{ (existing.predicted_motm if existing and existing.predicted_motm else '') | tojson }};
function fillMotmOptionsForWinner(teamName, restoreSavedMotm) {
const sel = document.getElementById('predicted_motm');
const hint = document.getElementById('motmEmptyHint');
if (!sel) return;
const players = (MOTM_BY_WINNER && MOTM_BY_WINNER[teamName]) ? MOTM_BY_WINNER[teamName] : [];
let keepValue = '';
if (restoreSavedMotm && INITIAL_MOTM && players.indexOf(INITIAL_MOTM) !== -1) {
keepValue = INITIAL_MOTM;
}
sel.innerHTML = '';
const ph = document.createElement('option');
ph.value = '';
ph.textContent = players.length ? '— Choose Man of the Match —' : '— No squad for this team —';
sel.appendChild(ph);
for (let i = 0; i < players.length; i++) {
const o = document.createElement('option');
o.value = players[i];
o.textContent = players[i];
sel.appendChild(o);
}
if (hint) hint.style.display = players.length ? 'none' : 'block';
if (players.length) {
sel.disabled = false;
sel.required = true;
if (keepValue) sel.value = keepValue;
} else {
sel.disabled = true;
sel.required = false;
sel.value = '';
}
updateBidDisplay(document.getElementById('bidInput')?.value || 0);
}
function onWinnerChange(radio) {
styleTeamPickCard(radio);
fillMotmOptionsForWinner(radio.value, false);
}
function styleTeamPickCard(radio) {
document.querySelectorAll('.team-pick-card').forEach(el => {
el.style.borderColor = 'var(--border)';
el.style.background = '';
});
const label = radio.closest('.team-pick-label');
if (label) {
const card = label.querySelector('.team-pick-card');
if (card) {
const inner = card.querySelector('[style*="font-family"]');
const color = (inner && inner.style.color) ? inner.style.color : 'var(--orange)';
card.style.borderColor = color;
if (color.indexOf('rgb(') === 0) {
card.style.background = color.replace('rgb', 'rgba').replace(')', ', 0.1)');
} else if (color.charAt(0) === '#') {
card.style.background = color.length === 7 ? color + '22' : color;
} else {
card.style.background = 'rgba(249,115,22,0.1)';
}
}
}
}
(function initMotmFromWinner() {
const checked = document.querySelector('input[name="predicted_winner"]:checked');
if (checked) {
styleTeamPickCard(checked);
fillMotmOptionsForWinner(checked.value, true);
}
document.querySelectorAll('input[name="predicted_winner"]').forEach((r) => {
r.addEventListener('change', function () { onWinnerChange(this); });
});
const motmSel = document.getElementById('predicted_motm');
motmSel?.addEventListener('change', () => {
updateBidDisplay(document.getElementById('bidInput')?.value || 0);
});
})();
function setBid(val) {
const input = document.getElementById('bidInput');
input.value = val;
updateBidDisplay(val);
}
function updateBidDisplay(val) {
const bid = parseInt(val) || 0;
document.getElementById('winAmount').textContent = '+' + bid;
document.getElementById('loseAmount').textContent = '−' + bid;
const mt = document.getElementById('predicted_motm');
const hasMOTM = mt && !mt.disabled && (mt.value || '').trim();
const dash = '—';
const bestEl = document.getElementById('bestCase');
const winWrongEl = document.getElementById('winWrongMotm');
const loseCorrEl = document.getElementById('loseCorrectMotm');
const loseWrongEl = document.getElementById('loseWrongMotm');
if (!hasMOTM) {
if (bestEl) bestEl.textContent = dash;
if (winWrongEl) winWrongEl.textContent = dash;
if (loseCorrEl) loseCorrEl.textContent = dash;
if (loseWrongEl) loseWrongEl.textContent = dash;
return;
}
const fmt = (n) => (n >= 0 ? '+' : '') + n + ' pts';
if (bestEl) bestEl.textContent = fmt(bid + CORRECT_MOTM);
if (winWrongEl) {
const w = bid + WRONG_MOTM;
winWrongEl.textContent = fmt(w);
winWrongEl.className = w >= 0 ? 'text-green' : 'text-red';
}
if (loseCorrEl) {
const x = -bid + CORRECT_MOTM;
loseCorrEl.textContent = fmt(x);
loseCorrEl.style.color = x >= 0 ? 'var(--green)' : 'var(--red)';
}
if (loseWrongEl) loseWrongEl.textContent = fmt(-bid + WRONG_MOTM);
}
// Init
updateBidDisplay(document.getElementById('bidInput')?.value || 0);
</script>
{% endif %}
{% endblock %}