-
Pack Opened!
-
-
-
+
+
+
Pack Opened!
+
+
+
+
+
+
+
@@ -1030,6 +1068,9 @@ async function loadClub() {
Win streak${s.current_win_streak ?? 0}
`;
} catch (e) { el('stats-info').innerHTML = `
${e.message}
`; }
+
+ loadCheckin();
+ loadMilestones();
}
async function saveClub() {
@@ -1122,25 +1163,65 @@ async function loadPacks() {
} catch (e) { el('pack-history').innerHTML = `
${e.message}
`; }
}
+// ── Pack Reveal ───────────────────────────────────────────────────────────────
+
+let _packRevealCards = [];
+let _packRevealIdx = 0;
+
+function packCardHtml(c, animate) {
+ return `
+
${(c.rarity||'').toUpperCase()}
+
${c.overall}
+
${c.position}
+
${c.name}
+
${c.club}
+ ${c.nation ? `
${c.nation}
` : ''}
+
`;
+}
+
+function renderPackGrid() {
+ const slots = _packRevealCards.map((c, i) => {
+ if (i < _packRevealIdx) return packCardHtml(c, false);
+ return `
?
`;
+ }).join('');
+ el('modal-cards').innerHTML = slots;
+ const allRevealed = _packRevealIdx >= _packRevealCards.length;
+ el('reveal-next-btn').style.display = allRevealed ? 'none' : '';
+ el('reveal-all-btn').style.display = allRevealed ? 'none' : '';
+}
+
+function revealNextCard() {
+ if (_packRevealIdx >= _packRevealCards.length) return;
+ _packRevealIdx++;
+ renderPackGrid();
+ // Re-animate the newly revealed card
+ const allSlots = el('modal-cards').querySelectorAll('.player-card');
+ const newCard = allSlots[_packRevealIdx - 1];
+ if (newCard) { newCard.classList.remove('pack-reveal-card'); void newCard.offsetWidth; newCard.classList.add('pack-reveal-card'); }
+}
+
+function revealAllCards() {
+ _packRevealIdx = _packRevealCards.length;
+ el('modal-cards').innerHTML = _packRevealCards.map(c => packCardHtml(c, false)).join('');
+ el('reveal-next-btn').style.display = 'none';
+ el('reveal-all-btn').style.display = 'none';
+}
+
async function openPack(packId, btn) {
btn.disabled = true;
btn.textContent = 'Opening…';
try {
const result = await api('POST', `/packs/open/${packId}`);
const cards = result.cards || [];
- el('modal-cards').innerHTML = cards.map(c => `
-
-
${(c.rarity||'').toUpperCase()}
-
${c.overall}
-
${c.position}
-
${c.name}
-
${c.club}
-
- `).join('');
+ _packRevealCards = cards;
+ _packRevealIdx = 0;
+ el('pack-modal-title').textContent = `Pack Opened! (${cards.length} cards)`;
+ el('reveal-next-btn').style.display = cards.length ? '' : 'none';
+ el('reveal-all-btn').style.display = cards.length ? '' : 'none';
+ renderPackGrid();
el('pack-modal').classList.add('open');
showToast(`Opened ${cards.length} cards!`);
loadPacks();
- // Refresh header coins
api('GET', '/club').then(c => {
el('hdr-coins').textContent = (c.coins ?? 0).toLocaleString() + ' coins';
}).catch(() => {});
@@ -1155,6 +1236,71 @@ function closeModal() {
el('pack-modal').classList.remove('open');
}
+// ── Daily Check-in ────────────────────────────────────────────────────────────
+
+async function loadCheckin() {
+ try {
+ const d = await api('GET', '/club/checkin');
+ const streak = d.streak_day || 0;
+ const available = d.available;
+ const dots = Array.from({length: 7}, (_, i) => {
+ const day = i + 1;
+ const cls = day < streak ? 'done' : day === streak && !available ? 'done' : day === streak + 1 && available ? 'current' : '';
+ return `
${day}
`;
+ }).join('');
+ el('checkin-panel').innerHTML = `
+
+
+
+
Daily Streak
+
Day ${streak} · Next: +${(d.next_reward_coins||0).toLocaleString()} coins${d.next_reward_pack ? ' + pack' : ''}
+
+
+
+
${dots}
+
`;
+ } catch (_) { el('checkin-panel').innerHTML = '
Check-in unavailable.'; }
+}
+
+async function claimCheckin() {
+ const btn = document.getElementById('checkin-btn');
+ if (btn) btn.disabled = true;
+ try {
+ const r = await api('POST', '/club/checkin');
+ if (r.already_claimed) { showToast('Already claimed today!'); return; }
+ showToast(`Day ${r.new_streak} reward: +${r.coins_awarded.toLocaleString()} coins${r.pack_awarded ? ' + pack!' : '!'}`);
+ api('GET', '/club').then(c => { el('hdr-coins').textContent = (c.coins ?? 0).toLocaleString() + ' coins'; }).catch(() => {});
+ loadCheckin();
+ } catch (e) { showToast(e.message, true); if (btn) btn.disabled = false; }
+}
+
+// ── Club Milestones ───────────────────────────────────────────────────────────
+
+async function loadMilestones() {
+ try {
+ const m = await api('GET', '/club/milestones');
+ const items = [
+ { val: m.total_wins, lbl: 'Matches Won' },
+ { val: m.total_goals_scored, lbl: 'Goals Scored' },
+ { val: m.best_win_streak, lbl: 'Best Win Streak' },
+ { val: m.seasons_completed, lbl: 'Seasons Completed' },
+ { val: `Div ${m.highest_division_reached ?? 10}`, lbl: 'Highest Division', raw: true },
+ { val: m.total_packs_opened, lbl: 'Packs Opened' },
+ { val: m.cards_owned, lbl: 'Cards in Collection' },
+ { val: m.sbcs_completed, lbl: 'SBCs Completed' },
+ { val: m.total_checkins, lbl: 'Daily Check-ins' },
+ { val: `Lvl ${m.club_level ?? 1}`, lbl: 'Club Level', raw: true },
+ ];
+ el('milestones-grid').innerHTML = items.map(({ val, lbl, raw }) => `
+
+
${raw ? val : (typeof val === 'number' ? val.toLocaleString() : val ?? 0)}
+
${lbl}
+
`).join('');
+ } catch (_) { el('milestones-grid').innerHTML = '
Milestones unavailable.'; }
+}
+
// ── Objectives ───────────────────────────────────────────────────────────────
async function loadObjectives() {
diff --git a/src/mapper.rs b/src/mapper.rs
index 677ada3..9c7da51 100644
--- a/src/mapper.rs
+++ b/src/mapper.rs
@@ -243,6 +243,23 @@ const EXACT: &[ExactRoute] = &[
core_method: "GET", core_path: "/division/history",
notes: "FUT division history → Core season history",
},
+ // ── Daily check-in ────────────────────────────────────────────────────────
+ ExactRoute {
+ ea_method: "GET", ea_path: "/ut/game/fut/dailyObjective",
+ core_method: "GET", core_path: "/club/checkin",
+ notes: "FUT daily objective status → Core check-in status",
+ },
+ ExactRoute {
+ ea_method: "POST", ea_path: "/ut/game/fut/dailyObjective/claim",
+ core_method: "POST", core_path: "/club/checkin",
+ notes: "FUT daily objective claim → Core check-in claim",
+ },
+ // ── Club milestones ───────────────────────────────────────────────────────
+ ExactRoute {
+ ea_method: "GET", ea_path: "/ut/game/fut/milestones",
+ core_method: "GET", core_path: "/club/milestones",
+ notes: "FUT milestones → Core club milestones",
+ },
// ── Squad list ────────────────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/squad/list",
@@ -704,7 +721,7 @@ mod tests {
#[test]
fn test_total_exact_routes_count() {
- assert!(EXACT.len() >= 56, "expected at least 56 exact mappings, got {}", EXACT.len());
+ assert!(EXACT.len() >= 59, "expected at least 59 exact mappings, got {}", EXACT.len());
}
// ── Phase 22 new mappings ─────────────────────────────────────────────────