147 lines
6.1 KiB
HTML
147 lines
6.1 KiB
HTML
<!doctype html>
|
||
<html lang="en" class="">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>Quick Problem Platform</title>
|
||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||
</head>
|
||
<style>
|
||
/* Popout explanation */
|
||
#rankingExplanation {
|
||
grid-column: 1 / span 2;
|
||
max-height: 0;
|
||
opacity: 0;
|
||
overflow: hidden;
|
||
transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
|
||
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); }
|
||
|
||
/* Highlight top rank */
|
||
.rank-1 td:first-child { font-weight: bold; }
|
||
.sort-asc::after { content: " ↑"; }
|
||
.sort-desc::after { content: " ↓"; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<header>
|
||
<div class="header-content">
|
||
<h1>Quick Problem Platform</h1>
|
||
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle dark mode">
|
||
<span class="dark-mode-icon"></span>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<div class="content" id="contentContainer">
|
||
<!-- Problems -->
|
||
<section class="card problems-list">
|
||
<div class="search-controls">
|
||
<input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." />
|
||
</div>
|
||
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
|
||
<div id="problemsContainer">
|
||
{% for folder, description, test_code, difficulty in problems %}
|
||
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}" data-difficulty="{{ difficulty|lower }}">
|
||
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
|
||
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
|
||
</div>
|
||
{% else %}
|
||
<div class="problem-item">No problems yet.</div>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="pagination-controls" id="problemsPagination">
|
||
<button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button>
|
||
<span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span>
|
||
<button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Leaderboard -->
|
||
<section class="card" id="leaderboardSection">
|
||
<div class="leaderboard-head">
|
||
<h2 style="font-size:1.1rem;margin:0">Leaderboard
|
||
<span id="rankInfoBtn" title="How ranking works">ℹ️</span>
|
||
</h2>
|
||
</div>
|
||
<div class="leaderboard-controls">
|
||
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
|
||
<select class="filter-select" id="runtimeFilter">
|
||
<option value="">All runtimes</option>
|
||
<option value="best">Best runtime</option>
|
||
<option value="worst">Worst runtime</option>
|
||
</select>
|
||
</div>
|
||
<div id="leaderboardContainer">
|
||
<table class="leaderboard-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="sortable" data-sort="rank">Rank</th>
|
||
<th class="sortable" data-sort="user">User</th>
|
||
<th class="sortable" data-sort="problem">Problem</th>
|
||
<th class="sortable" data-sort="runtime">Runtime (s)</th>
|
||
<th class="sortable" data-sort="memory">Memory (KB)</th>
|
||
<th>Line</th>
|
||
<th class="sortable" data-sort="timestamp">Timestamp</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="leaderboardBody">
|
||
{% for entry in leaderboard %}
|
||
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
|
||
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
|
||
data-timestamp="{{ entry[5] }}">
|
||
<td>{{ loop.index }}</td>
|
||
<td>{{ entry[0] }}</td>
|
||
<td>
|
||
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
|
||
style="color:#2563eb; text-decoration: none;"
|
||
onmouseover="this.style.textDecoration='underline';"
|
||
onmouseout="this.style.textDecoration='none';">
|
||
{{ problem_titles.get(entry[1], 'Unknown') }}
|
||
</a>
|
||
</td>
|
||
<td>{{ '%.4f'|format(entry[2]) }}</td>
|
||
<td>{{ entry[3] }}</td>
|
||
<td>{{ entry[4] if entry[4] else '-' }}</td>
|
||
<td>{{ entry[5] }}</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="7">No leaderboard entries yet.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Ranking explanation -->
|
||
<section class="card" id="rankingExplanation">
|
||
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
|
||
<p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
|
||
<ul style="margin-left: 15px;">
|
||
<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>
|
||
</ul>
|
||
<p>Overall score is calculated as:</p>
|
||
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
|
||
<code>
|
||
runtimeScore = yourRuntime / bestRuntime<br>
|
||
memoryScore = yourMemory / bestMemory<br>
|
||
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
|
||
</code>
|
||
</div>
|
||
<p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||
</body>
|
||
</html>
|