animation ; fix the displaying and calculating tmrw

This commit is contained in:
2025-08-14 22:08:47 +02:00
parent 6079813e2c
commit e97dde65fb

View File

@@ -1,20 +1,29 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Quick Problem Platform</title> <title>Quick Problem Platform</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
<style> <style>
/* Minimal inline CSS to support full-width explanation and popout */ /* Popout explanation */
#rankingExplanation { #rankingExplanation {
display:none; grid-column: 1 / span 2;
grid-column: 1 / span 2; max-height: 0;
transition: all 0.35s ease; opacity: 0;
} overflow: hidden;
#rankInfoBtn.active { color: #2563eb; } transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
</style> padding: 0 12px;
}
#rankingExplanation.active {
max-height: 800px;
opacity: 1;
padding: 12px;
}
#rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; }
#rankInfoBtn.active { transform: rotate(90deg); }
</style>
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
@@ -43,7 +52,9 @@
<!-- Leaderboard --> <!-- Leaderboard -->
<section class="card" id="leaderboardSection"> <section class="card" id="leaderboardSection">
<div class="leaderboard-head"> <div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2> <h2 style="font-size:1.1rem;margin:0">Leaderboard
<span id="rankInfoBtn" title="How ranking works"></span>
</h2>
</div> </div>
<div class="leaderboard-controls"> <div class="leaderboard-controls">
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." /> <input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
@@ -57,7 +68,7 @@
<table class="leaderboard-table"> <table class="leaderboard-table">
<thead> <thead>
<tr> <tr>
<th class="sortable" data-sort="rank">Rank <span id="rankInfoBtn" title="How ranking works" style="cursor:pointer;"></span></th> <th class="sortable" data-sort="rank">Rank</th>
<th class="sortable" data-sort="user">User</th> <th class="sortable" data-sort="user">User</th>
<th class="sortable" data-sort="problem">Problem</th> <th class="sortable" data-sort="problem">Problem</th>
<th class="sortable" data-sort="runtime">Runtime (s)</th> <th class="sortable" data-sort="runtime">Runtime (s)</th>
@@ -68,15 +79,15 @@
</thead> </thead>
<tbody id="leaderboardBody"> <tbody id="leaderboardBody">
{% for entry in leaderboard %} {% for entry in leaderboard %}
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}" <tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}" data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
data-timestamp="{{ entry[5] }}"> data-timestamp="{{ entry[5] }}">
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{{ entry[0] }}</td> <td>{{ entry[0] }}</td>
<td> <td>
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}" <a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
style="color: #0077ff; text-decoration: none;" style="color: #0077ff; text-decoration: none;"
onmouseover="this.style.textDecoration='underline';" onmouseover="this.style.textDecoration='underline';"
onmouseout="this.style.textDecoration='none';"> onmouseout="this.style.textDecoration='none';">
{{ problem_titles.get(entry[1], 'Unknown') }} {{ problem_titles.get(entry[1], 'Unknown') }}
</a> </a>
@@ -94,18 +105,15 @@
</div> </div>
</section> </section>
<!-- Ranking explanation popout --> <!-- Ranking explanation -->
<section class="card" id="rankingExplanation"> <section class="card" id="rankingExplanation">
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2> <h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
<p> <p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank.
It considers multiple metrics for each submission:
</p>
<ul style="margin-left: 15px;"> <ul style="margin-left: 15px;">
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li> <li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li> <li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
</ul> </ul>
<p>Each metric is normalized against the best in the leaderboard, and the overall score is calculated as:</p> <p>Overall score is calculated as:</p>
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;"> <div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
<code> <code>
runtimeScore = yourRuntime / bestRuntime<br> runtimeScore = yourRuntime / bestRuntime<br>
@@ -113,24 +121,22 @@
overallScore = runtimeScore × 0.7 + memoryScore × 0.3 overallScore = runtimeScore × 0.7 + memoryScore × 0.3
</code> </code>
</div> </div>
<p>Lower overall scores are better. If two scores are equal, the earlier submission ranks higher.</p> <p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
</section> </section>
</div> </div>
</div> </div>
<!-- Inline JS -->
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Problem search // Problem search
const problemSearch = document.getElementById('problemSearch'); const problemSearch = document.getElementById('problemSearch');
const problemsContainer = document.getElementById('problemsContainer'); const problemsContainer = document.getElementById('problemsContainer');
const problemItems = problemsContainer.querySelectorAll('.problem-item'); const problemItems = problemsContainer?.querySelectorAll('.problem-item') || [];
problemSearch?.addEventListener('input', () => { problemSearch?.addEventListener('input', () => {
const term = problemSearch.value.toLowerCase(); const term = problemSearch.value.toLowerCase();
problemItems.forEach(item => { problemItems.forEach(item => {
const name = item.dataset.name.toLowerCase(); const name = item.dataset.name?.toLowerCase()||'';
const desc = item.dataset.desc?.toLowerCase() || ''; const desc = item.dataset.desc?.toLowerCase()||'';
item.style.display = (name.includes(term) || desc.includes(term)) ? '' : 'none'; item.style.display = (name.includes(term) || desc.includes(term)) ? '' : 'none';
}); });
}); });
@@ -139,86 +145,75 @@ document.addEventListener('DOMContentLoaded', () => {
const problemFilter = document.getElementById('problemFilter'); const problemFilter = document.getElementById('problemFilter');
const runtimeFilter = document.getElementById('runtimeFilter'); const runtimeFilter = document.getElementById('runtimeFilter');
const leaderboardBody = document.getElementById('leaderboardBody'); const leaderboardBody = document.getElementById('leaderboardBody');
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
const sortableHeaders = document.querySelectorAll('.sortable'); const sortableHeaders = document.querySelectorAll('.sortable');
let currentSort = { column: null, direction: 'asc' }; function calculateOverallRank() {
const rows = Array.from(leaderboardBody.querySelectorAll('tr')).filter(r => r.style.display !== 'none');
const runtimes = rows.map(r => parseFloat(r.dataset.runtime)||Infinity);
const memories = rows.map(r => parseFloat(r.dataset.memory)||Infinity);
const minRuntime = Math.min(...runtimes);
const minMemory = Math.min(...memories);
rows.forEach(row => {
const runtimeScore = (parseFloat(row.dataset.runtime)||Infinity)/minRuntime;
const memoryScore = (parseFloat(row.dataset.memory)||Infinity)/minMemory;
row.dataset.overallScore = runtimeScore*0.7 + memoryScore*0.3;
});
rows.sort((a,b)=>parseFloat(a.dataset.overallScore)-parseFloat(b.dataset.overallScore));
rows.forEach((row,i)=>row.cells[0].textContent = i+1);
}
function filterLeaderboard() { function filterLeaderboard() {
const problemTerm = problemFilter.value.toLowerCase(); const problemTerm = problemFilter.value.toLowerCase();
const runtimeType = runtimeFilter.value; const runtimeType = runtimeFilter.value;
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
leaderboardRows.forEach(row => { rows.forEach(row => {
const problem = row.dataset.problem.toLowerCase(); const problem = row.dataset.problem.toLowerCase();
const runtime = parseFloat(row.dataset.runtime); const runtime = parseFloat(row.dataset.runtime);
const showProblem = problem.includes(problemTerm); let show = problem.includes(problemTerm);
if(runtimeType==='best'){
let showRuntime = true; const userRows = rows.filter(r=>r.dataset.user===row.dataset.user && r.dataset.problem===row.dataset.problem);
if(runtimeType === 'best') { show = show && runtime===Math.min(...userRows.map(r=>parseFloat(r.dataset.runtime)));
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem); } else if(runtimeType==='worst'){
const best = Math.min(...userProblemRows.map(r=>parseFloat(r.dataset.runtime))); const userRows = rows.filter(r=>r.dataset.user===row.dataset.user && r.dataset.problem===row.dataset.problem);
showRuntime = runtime === best; show = show && runtime===Math.max(...userRows.map(r=>parseFloat(r.dataset.runtime)));
} else if(runtimeType === 'worst') {
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem);
const worst = Math.max(...userProblemRows.map(r=>parseFloat(r.dataset.runtime)));
showRuntime = runtime === worst;
} }
row.style.display = show?'':'none';
row.style.display = (showProblem && showRuntime) ? '' : 'none';
}); });
calculateOverallRank();
} }
problemFilter?.addEventListener('input', filterLeaderboard); problemFilter?.addEventListener('input', filterLeaderboard);
runtimeFilter?.addEventListener('change', filterLeaderboard); runtimeFilter?.addEventListener('change', filterLeaderboard);
function getCellValue(row, column) { function getCellValue(row, column){
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort===column); const index = Array.from(document.querySelectorAll('th')).findIndex(th=>th.dataset.sort===column);
let val = row.cells[index].textContent.trim(); let val = row.cells[index]?.textContent?.trim();
if(['runtime','memory','rank'].includes(column)) return parseFloat(val)||0; if(['runtime','memory','rank'].includes(column)) return parseFloat(val)||0;
if(column==='timestamp') return new Date(val).getTime(); if(column==='timestamp') return new Date(val).getTime();
return val.toLowerCase(); return val.toLowerCase();
} }
function compareValues(a,b,direction){
function compareValues(a,b,direction) {
if(typeof a==='number' && typeof b==='number') return direction==='asc'?a-b:b-a; if(typeof a==='number' && typeof b==='number') return direction==='asc'?a-b:b-a;
if(a<b) return direction==='asc'?-1:1; if(a<b) return direction==='asc'?-1:1;
if(a>b) return direction==='asc'?1:-1; if(a>b) return direction==='asc'?1:-1;
return 0; return 0;
} }
function sortLeaderboard(column,direction){
function calculateOverallRank() {
const rows = Array.from(leaderboardBody.querySelectorAll('tr')); const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
const runtimes = rows.map(r=>parseFloat(r.dataset.runtime)||Infinity); if(column==='rank'){
const memories = rows.map(r=>parseFloat(r.dataset.memory)||Infinity);
const minRuntime = Math.min(...runtimes);
const minMemory = Math.min(...memories);
rows.forEach(row=>{
const runtimeScore = (parseFloat(row.dataset.runtime)||Infinity)/minRuntime;
const memoryScore = (parseFloat(row.dataset.memory)||Infinity)/minMemory;
const score = runtimeScore*0.7 + memoryScore*0.3;
row.dataset.overallScore = score;
});
}
function sortLeaderboard(column,direction) {
let rows = Array.from(leaderboardBody.querySelectorAll('tr'));
if(column==='rank') {
calculateOverallRank(); calculateOverallRank();
rows.sort((a,b)=>parseFloat(a.dataset.overallScore)-parseFloat(b.dataset.overallScore));
rows.forEach((row,index)=>row.cells[0].textContent = index+1);
} else { } else {
rows.sort((a,b)=>compareValues(getCellValue(a,column),getCellValue(b,column),direction)); rows.sort((a,b)=>compareValues(getCellValue(a,column),getCellValue(b,column),direction));
rows.forEach(r=>leaderboardBody.appendChild(r));
} }
rows.forEach(row=>leaderboardBody.appendChild(row));
} }
let currentSort={column:null,direction:'asc'};
sortableHeaders.forEach(header=>{ sortableHeaders.forEach(header=>{
header.addEventListener('click',()=>{ header.addEventListener('click',()=>{
const column = header.dataset.sort; const column=header.dataset.sort;
sortableHeaders.forEach(h=>h.classList.remove('sort-asc','sort-desc')); sortableHeaders.forEach(h=>h.classList.remove('sort-asc','sort-desc'));
if(currentSort.column===column) currentSort.direction = currentSort.direction==='asc'?'desc':'asc'; if(currentSort.column===column) currentSort.direction=currentSort.direction==='asc'?'desc':'asc';
else { currentSort.column=column; currentSort.direction='asc'; } else { currentSort.column=column; currentSort.direction='asc'; currentSort.column=column; }
header.classList.add(`sort-${currentSort.direction}`); header.classList.add(`sort-${currentSort.direction}`);
sortLeaderboard(column,currentSort.direction); sortLeaderboard(column,currentSort.direction);
}); });
@@ -228,14 +223,12 @@ document.addEventListener('DOMContentLoaded', () => {
const rankInfoBtn = document.getElementById('rankInfoBtn'); const rankInfoBtn = document.getElementById('rankInfoBtn');
const rankingExplanation = document.getElementById('rankingExplanation'); const rankingExplanation = document.getElementById('rankingExplanation');
rankInfoBtn?.addEventListener('click',()=>{ rankInfoBtn?.addEventListener('click',()=>{
if(rankingExplanation.style.display==='none' || rankingExplanation.style.display==='') { rankingExplanation.classList.toggle('active');
rankingExplanation.style.display='block'; rankInfoBtn.classList.toggle('active');
rankInfoBtn.classList.add('active');
} else {
rankingExplanation.style.display='none';
rankInfoBtn.classList.remove('active');
}
}); });
// Initial calculation
calculateOverallRank();
}); });
</script> </script>
</body> </body>