Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 116 additions & 3 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -740,9 +740,10 @@ kbd {
border: 1px solid;
white-space: nowrap;
}
.rank-bronze { color: #cd7f32; background: rgba(205,127,50,0.12); border-color: rgba(205,127,50,0.35); }
.rank-silver { color: #b8b8b8; background: rgba(192,192,192,0.12); border-color: rgba(192,192,192,0.35); }
.rank-gold { color: #ffd700; background: rgba(255,215,0,0.12); border-color: rgba(255,215,0,0.4); }
.rank-bronze { color: #cd7f32; background: rgba(205,127,50,0.12); border-color: rgba(205,127,50,0.35); }
.rank-silver { color: #b8b8b8; background: rgba(192,192,192,0.12); border-color: rgba(192,192,192,0.35); }
.rank-silver2 { color: #c0c8ff; background: rgba(192,200,255,0.15); border-color: rgba(192,200,255,0.4); }
.rank-gold { color: #ffd700; background: rgba(255,215,0,0.12); border-color: rgba(255,215,0,0.4); }

/* ── Rank-up overlay ── */
.rankup-overlay {
Expand Down Expand Up @@ -784,6 +785,118 @@ kbd {
100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* ── Reaction bar ── */
.reaction-bar {
display: flex;
gap: 0.5rem;
justify-content: center;
}
.reaction-btn {
font-size: 1.5rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
width: 3rem;
height: 3rem;
cursor: pointer;
transition: transform 0.15s, border-color 0.15s;
display: flex;
align-items: center;
justify-content: center;
}
.reaction-btn:hover { transform: scale(1.2); border-color: var(--accent); }
.reaction-btn:active { transform: scale(0.9); }

/* ── Reaction overlay ── */
.reaction-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 400;
}
.reaction-float {
position: absolute;
font-size: 4rem;
animation: reactionPop 1.8s ease forwards;
pointer-events: none;
}
@keyframes reactionPop {
0% { opacity: 0; transform: translateY(0) scale(0.5); }
20% { opacity: 1; transform: translateY(-20px) scale(1.3); }
80% { opacity: 1; transform: translateY(-80px) scale(1); }
100% { opacity: 0; transform: translateY(-120px) scale(0.8); }
}

/* ── Share + Submit row ── */
.result-actions-row {
display: flex;
gap: 0.5rem;
}
.result-actions-row .btn-secondary { flex: 1; }

/* ── Profile modal body ── */
.profile-body { display: flex; flex-direction: column; align-items: center; gap: 0.8rem; }
.profile-username { font-size: 1.5rem; font-weight: 900; }
.profile-rank { }
.profile-stats {
display: flex;
gap: 0.75rem;
width: 100%;
}
.profile-stat {
flex: 1;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 0.75rem 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}

/* ── Clickable username in leaderboard ── */
.lb-name-link {
cursor: pointer;
text-decoration: underline;
text-decoration-color: var(--border2);
text-underline-offset: 3px;
transition: color 0.15s;
}
.lb-name-link:hover { color: var(--accent); }

/* ── History Q&A list ── */
.history-qa {
display: none;
flex-direction: column;
gap: 0.2rem;
margin-top: 0.3rem;
padding-top: 0.3rem;
border-top: 1px solid var(--border);
}
.history-entry.expanded .history-qa { display: flex; }
.history-qa-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.68rem;
color: var(--muted);
}
.history-qa-item.correct-q { color: var(--green); }
.history-qa-item.wrong-q { color: var(--red); }
.history-toggle {
background: none;
border: none;
color: var(--muted);
font-size: 0.68rem;
cursor: pointer;
padding: 0.1rem 0.3rem;
border-radius: 4px;
transition: color 0.15s;
align-self: flex-start;
}
.history-toggle:hover { color: var(--accent); }

/* ── History entries ── */
.history-entry {
background: var(--surface);
Expand Down
46 changes: 42 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>MathBlitz</title>
<title>QuantQuiz</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
Expand All @@ -15,6 +15,35 @@
<div class="countdown-number" id="countdown-number">3</div>
</div>

<!-- ── REACTION OVERLAY ── -->
<div id="reaction-overlay" class="reaction-overlay"></div>

<!-- ── PROFILE MODAL ── -->
<div id="profile-modal" class="modal-overlay">
<div class="modal">
<h2 style="text-align:center;font-size:1rem;letter-spacing:3px;color:var(--muted);">PROFILE</h2>
<div class="profile-body">
<div class="profile-username" id="profile-username">—</div>
<div class="profile-rank" id="profile-rank"></div>
<div class="profile-stats">
<div class="profile-stat">
<span class="stat-label">Total XP</span>
<span class="stat-value" id="profile-xp">0</span>
</div>
<div class="profile-stat">
<span class="stat-label">Streak</span>
<span class="stat-value" id="profile-streak">0</span>
</div>
<div class="profile-stat">
<span class="stat-label">Best Streak</span>
<span class="stat-value" id="profile-longest">0</span>
</div>
</div>
</div>
<button id="profile-close-btn" class="btn-ghost">Close</button>
</div>
</div>

<!-- ── RANK-UP OVERLAY ── -->
<div id="rankup-overlay" class="rankup-overlay">
<div class="rankup-content">
Expand Down Expand Up @@ -85,7 +114,7 @@ <h2 style="text-align:center;font-size:1rem;letter-spacing:3px;color:var(--muted
<!-- ── MENU SCREEN ── -->
<div id="screen-menu" class="screen active">
<div class="logo">
<h1>MathBlitz</h1>
<h1>QuantQuiz</h1>
<p>Sharpen your mind. Beat the clock. Crush the competition.</p>
</div>

Expand Down Expand Up @@ -261,7 +290,10 @@ <h1 class="results-title" id="results-title">Game Over</h1>

<div class="result-actions">
<button id="play-again-btn" class="btn-primary">PLAY AGAIN</button>
<button id="submit-problem-btn" class="btn-secondary hidden">🎯 Submit a Problem</button>
<div class="result-actions-row">
<button id="share-btn" class="btn-secondary">↗ Share</button>
<button id="submit-problem-btn" class="btn-secondary hidden">🎯 Submit a Problem</button>
</div>
<button id="menu-btn" class="btn-ghost">Menu</button>
</div>
</div>
Expand All @@ -277,9 +309,9 @@ <h1 class="lb-title">Leaderboard</h1>
<div class="section">
<h2>Mode</h2>
<div class="pill-row">
<button class="pill-btn" data-lb-mode="global">🌍 Global</button>
<button class="pill-btn active" data-lb-mode="classic">Classic</button>
<button class="pill-btn" data-lb-mode="sprint">Sprint</button>
<button class="pill-btn" data-lb-mode="zen">Zen</button>
<button class="pill-btn" data-lb-mode="daily">Daily</button>
</div>
</div>
Expand Down Expand Up @@ -370,6 +402,12 @@ <h2 class="duel-search-title">Finding Opponent</h2>
<div class="feedback" id="duel-feedback">&nbsp;</div>
</div>

<div class="reaction-bar">
<button class="reaction-btn" data-emoji="🔥">🔥</button>
<button class="reaction-btn" data-emoji="😂">😂</button>
<button class="reaction-btn" data-emoji="💀">💀</button>
<button class="reaction-btn" data-emoji="🤯">🤯</button>
</div>
<button id="forfeit-btn" class="btn-ghost">Forfeit</button>
</div>

Expand Down
2 changes: 1 addition & 1 deletion js/daily.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const Daily = (() => {

// ── Completion tracking (localStorage) ───────────────────────────────────

function storageKey() { return `mathblitz_daily_${getTodayISO()}`; }
function storageKey() { return `quantquiz_daily_${getTodayISO()}`; }

function hasCompletedToday() {
return localStorage.getItem(storageKey()) === 'done';
Expand Down
23 changes: 22 additions & 1 deletion js/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,33 @@ const DB = (() => {
return data ?? [];
}

async function getGlobalLeaderboard(limit = 25) {
if (!_client) return [];
const { data, error } = await _client
.from('profiles')
.select('username, total_xp, current_streak, longest_streak')
.order('total_xp', { ascending: false })
.limit(limit);
if (error) throw error;
return data ?? [];
}

async function getUserProfileByUsername(username) {
if (!_client) return null;
const { data } = await _client
.from('profiles')
.select('username, total_xp, current_streak, longest_streak')
.eq('username', username)
.single();
return data;
}

return {
isConfigured: SUPABASE_CONFIGURED,
signUp, signIn, signOut, getUser, getProfile, onAuthChange,
saveScore, updateStreak,
saveDailyScore, getDailyLeaderboard, hasUserCompletedDaily,
getLeaderboard,
getLeaderboard, getGlobalLeaderboard, getUserProfileByUsername,
getCommunityQuestions, submitCommunityQuestion,
};
})();
11 changes: 6 additions & 5 deletions js/duel-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const DuelClient = (() => {
const events = [
'searching', 'matched', 'countdown', 'duel_start',
'answer_result', 'opponent_update', 'timer_tick',
'duel_end', 'match_cancelled',
'duel_end', 'match_cancelled', 'reaction',
];
events.forEach(e => socket.on(e, data => _dispatch(e, data)));

Expand All @@ -47,9 +47,10 @@ const DuelClient = (() => {
socket.emit('find_match', { username });
}

function cancelMatch() { socket?.emit('cancel_match'); }
function submitAnswer(answer) { socket?.emit('submit_answer', { answer }); }
function forfeit() { socket?.emit('forfeit'); disconnect(); }
function cancelMatch() { socket?.emit('cancel_match'); }
function submitAnswer(answer) { socket?.emit('submit_answer', { answer }); }
function forfeit() { socket?.emit('forfeit'); disconnect(); }
function sendReaction(emoji) { socket?.emit('send_reaction', { emoji }); }

return { on, connect, disconnect, findMatch, cancelMatch, submitAnswer, forfeit };
return { on, connect, disconnect, findMatch, cancelMatch, submitAnswer, forfeit, sendReaction };
})();
16 changes: 13 additions & 3 deletions js/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ const Game = (() => {
questionIdx: 0,
totalQuestions: predefined ? predefined.length : (settings.mode === 'sprint' ? SPRINT_QUESTIONS : null),
predefined,
currentQ: null,
startTime: Date.now(),
active: false,
currentQ: null,
answeredQuestions: [],
startTime: Date.now(),
active: false,
};
}

Expand Down Expand Up @@ -116,6 +117,15 @@ const Game = (() => {
state.wrong++;
}

if (state.answeredQuestions.length < 30) {
state.answeredQuestions.push({
display: state.currentQ.display,
answer: state.currentQ.answer,
given: userAnswer,
correct: isCorrect,
});
}

state.questionIdx++;

if (isGameOver()) {
Expand Down
Loading