Skip to content

Commit 5110324

Browse files
committed
Add full leaderboard modal
1 parent 3543c89 commit 5110324

4 files changed

Lines changed: 359 additions & 13 deletions

File tree

server.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,42 @@ def format_timestamp(timestamp_str):
4949
# Routes
5050
@app.route('/')
5151
def index():
52-
return render_template('index.html', leaderboard=leaderboards["all"])
52+
return render_template('index.html', leaderboard=leaderboards["all"], all_leaderboards=leaderboards)
5353

5454
@app.route('/<path:path>/')
5555
def page(path):
56-
return render_template('page.html', page=pages.get_or_404(path))
56+
return render_template('page.html', page=pages.get_or_404(path), all_leaderboards=leaderboards)
5757

5858
@app.route('/team/')
5959
def team():
6060
with open("data/team.json") as f:
6161
team_data = json.load(f)
62-
return render_template('team.html', data=team_data)
62+
return render_template('team.html', data=team_data, all_leaderboards=leaderboards)
6363

6464
@app.route('/insights/')
6565
def insights():
66-
return render_template('insights.html', pages=pages_insights)
66+
return render_template('insights.html', pages=pages_insights, all_leaderboards=leaderboards)
6767

6868
@app.route('/insights/<path:path>/')
6969
def insight(path):
70-
return render_template('page.html', page=pages.get_or_404('insights/' + path))
70+
return render_template('page.html', page=pages.get_or_404('insights/' + path), all_leaderboards=leaderboards)
7171

7272
@app.route('/arenas/')
7373
def arenas():
74-
return render_template('arenas.html', pages=pages_arenas)
74+
return render_template('arenas.html', pages=pages_arenas, all_leaderboards=leaderboards)
7575

7676
@app.route('/arenas/<path:path>/')
7777
def arena(path):
7878
return render_template(
7979
'arena.html',
8080
page=pages.get_or_404('arenas/' + path),
8181
leaderboard=leaderboards.get(path, []),
82+
all_leaderboards=leaderboards,
8283
)
8384

8485
@app.errorhandler(404)
8586
def page_not_found(path):
86-
return render_template('404.html'), 404
87+
return render_template('404.html', all_leaderboards=leaderboards), 404
8788

8889
# Freezer generators
8990
@freezer.register_generator

static/css/layout.css

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,181 @@ nav {
243243
z-index: 1;
244244
}
245245

246+
/* Leaderboard footer with button */
247+
.leaderboard-footer {
248+
display: flex;
249+
justify-content: space-between;
250+
align-items: center;
251+
gap: 1rem;
252+
}
253+
254+
.leaderboard-footer p {
255+
margin: 0;
256+
white-space: nowrap;
257+
}
258+
259+
.view-full-leaderboard-link {
260+
color: var(--fg);
261+
text-decoration: none;
262+
border-bottom: 1px solid transparent;
263+
cursor: pointer;
264+
font-size: var(--text-sm);
265+
padding: 0.25rem 0;
266+
display: inline-block;
267+
}
268+
269+
.view-full-leaderboard-link:hover {
270+
color: var(--accent);
271+
border-bottom-color: var(--accent);
272+
}
273+
274+
/* Full Leaderboard Modal */
275+
.full-leaderboard-modal {
276+
display: none;
277+
position: fixed;
278+
z-index: 1000;
279+
top: 0;
280+
left: 0;
281+
width: 100%;
282+
height: 100%;
283+
background-color: rgba(0, 0, 0, 0.8);
284+
overflow: auto;
285+
}
286+
287+
.full-leaderboard-modal.active {
288+
display: block;
289+
}
290+
291+
.full-leaderboard-modal-content {
292+
background-color: var(--bg);
293+
margin: 2% auto;
294+
padding: 2rem;
295+
border: 1px solid var(--border);
296+
width: 90%;
297+
max-width: 1400px;
298+
position: relative;
299+
}
300+
301+
.full-leaderboard-close {
302+
position: absolute;
303+
top: 1rem;
304+
right: 1.5rem;
305+
color: var(--fg);
306+
font-size: 2rem;
307+
font-weight: bold;
308+
cursor: pointer;
309+
transition: opacity 0.2s;
310+
line-height: 1;
311+
}
312+
313+
.full-leaderboard-close:hover {
314+
opacity: 0.7;
315+
}
316+
317+
.full-leaderboard-modal h2 {
318+
margin-top: 0;
319+
margin-bottom: 1.5rem;
320+
text-align: center;
321+
font-family: var(--font-anta);
322+
}
323+
324+
.full-leaderboard-table-container {
325+
overflow-x: auto;
326+
}
327+
328+
.full-leaderboard-table {
329+
width: 100%;
330+
border-collapse: collapse;
331+
font-size: var(--text-sm);
332+
}
333+
334+
.full-leaderboard-table th,
335+
.full-leaderboard-table td {
336+
padding: 0.5rem;
337+
text-align: center;
338+
border-bottom: 1px solid var(--border);
339+
}
340+
341+
.full-leaderboard-table th {
342+
font-weight: 600;
343+
position: sticky;
344+
top: 0;
345+
background: var(--bg);
346+
z-index: 10;
347+
}
348+
349+
.full-leaderboard-table td:first-child,
350+
.full-leaderboard-table th:first-child {
351+
text-align: left;
352+
width: 3rem;
353+
}
354+
355+
.full-leaderboard-table td:nth-child(2),
356+
.full-leaderboard-table th:nth-child(2) {
357+
text-align: left;
358+
width: 10rem;
359+
min-width: 10rem;
360+
}
361+
362+
/* Equal width for all arena columns */
363+
.full-leaderboard-table th.arena-col,
364+
.full-leaderboard-table td.arena-elo {
365+
width: 8rem;
366+
min-width: 8rem;
367+
}
368+
369+
.full-leaderboard-table th.all-col,
370+
.full-leaderboard-table td.all-elo {
371+
width: 8rem;
372+
min-width: 8rem;
373+
}
374+
375+
.full-elo-cell {
376+
position: relative;
377+
font-weight: 500;
378+
}
379+
380+
/* Gray bars for individual arenas */
381+
.full-elo-cell.arena-elo {
382+
background: linear-gradient(
383+
to right,
384+
rgba(128, 128, 128, 0.25) calc(var(--elo-percent) * 1%),
385+
transparent calc(var(--elo-percent) * 1%)
386+
);
387+
}
388+
389+
/* Green bar for the "All" column */
390+
.full-elo-cell.all-elo {
391+
background: linear-gradient(
392+
to right,
393+
rgba(34, 197, 94, 0.25) calc(var(--elo-percent) * 1%),
394+
transparent calc(var(--elo-percent) * 1%)
395+
);
396+
font-weight: 600;
397+
}
398+
399+
/* Mobile responsive for modal */
400+
@media (max-width: 768px) {
401+
.full-leaderboard-modal-content {
402+
width: 95%;
403+
padding: 1.5rem;
404+
margin: 5% auto;
405+
}
406+
407+
.full-leaderboard-table {
408+
font-size: 0.75rem;
409+
}
410+
411+
.full-leaderboard-table th,
412+
.full-leaderboard-table td {
413+
padding: 0.35rem 0.25rem;
414+
}
415+
416+
.leaderboard-footer {
417+
flex-wrap: wrap;
418+
}
419+
}
420+
246421
/* Insights */
247422
.insights-container {
248423
width: 75%;
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<!-- Full Leaderboard Modal -->
2+
<div class="full-leaderboard-modal" id="fullLeaderboardModal">
3+
<div class="full-leaderboard-modal-content">
4+
<span class="full-leaderboard-close" onclick="closeFullLeaderboard()">&times;</span>
5+
<h2>Full Leaderboard</h2>
6+
<div class="full-leaderboard-table-container">
7+
<table class="full-leaderboard-table">
8+
<thead>
9+
<tr>
10+
<th>Rank</th>
11+
<th>Model</th>
12+
<th class="arena-col">Halite</th>
13+
<th class="arena-col">Poker</th>
14+
<th class="arena-col">CoreWar</th>
15+
<th class="arena-col">RobotRumble</th>
16+
<th class="arena-col">Robocode</th>
17+
<th class="arena-col">BattleSnake</th>
18+
<th class="all-col">All</th>
19+
</tr>
20+
</thead>
21+
<tbody id="fullLeaderboardBody">
22+
<!-- Will be populated by JavaScript -->
23+
</tbody>
24+
</table>
25+
</div>
26+
</div>
27+
</div>
28+
29+
<script>
30+
// Full Leaderboard Modal functionality
31+
var fullLeaderboardData = {{ all_leaderboards | tojson | safe }};
32+
33+
function openFullLeaderboard() {
34+
var modal = document.getElementById('fullLeaderboardModal');
35+
modal.classList.add('active');
36+
populateFullLeaderboard();
37+
}
38+
39+
function closeFullLeaderboard() {
40+
var modal = document.getElementById('fullLeaderboardModal');
41+
modal.classList.remove('active');
42+
}
43+
44+
function populateFullLeaderboard() {
45+
if (!fullLeaderboardData) return;
46+
47+
var tbody = document.getElementById('fullLeaderboardBody');
48+
tbody.innerHTML = '';
49+
50+
var arenas = ['halite', 'poker', 'corewar', 'robotrumble', 'robocode', 'battlesnake'];
51+
var allBoard = fullLeaderboardData['all'].board;
52+
53+
// Calculate max ELO across all arenas for scaling
54+
var maxElo = 0;
55+
Object.keys(fullLeaderboardData).forEach(function(arena) {
56+
if (fullLeaderboardData[arena] && fullLeaderboardData[arena].board) {
57+
fullLeaderboardData[arena].board.forEach(function(entry) {
58+
if (entry.elo > maxElo) {
59+
maxElo = entry.elo;
60+
}
61+
});
62+
}
63+
});
64+
// Scale to 110% of max to leave some empty space
65+
maxElo = maxElo * 1.1;
66+
67+
// Create a map of model -> arena scores
68+
var modelData = {};
69+
allBoard.forEach(function(entry) {
70+
modelData[entry.model] = {
71+
rank: entry.rank,
72+
all: entry.elo,
73+
all_std: entry.elo_std
74+
};
75+
});
76+
77+
// Add arena scores
78+
arenas.forEach(function(arena) {
79+
if (fullLeaderboardData[arena]) {
80+
fullLeaderboardData[arena].board.forEach(function(entry) {
81+
if (modelData[entry.model]) {
82+
modelData[entry.model][arena] = entry.elo;
83+
modelData[entry.model][arena + '_std'] = entry.elo_std;
84+
}
85+
});
86+
}
87+
});
88+
89+
// Create table rows
90+
allBoard.forEach(function(entry) {
91+
var row = document.createElement('tr');
92+
var data = modelData[entry.model];
93+
94+
// Rank
95+
var rankCell = document.createElement('td');
96+
rankCell.textContent = data.rank;
97+
row.appendChild(rankCell);
98+
99+
// Model with logo
100+
var modelCell = document.createElement('td');
101+
var logos = {
102+
'GPT-5': '/static/images/orgs/openai.svg',
103+
'GPT-5 Mini': '/static/images/orgs/openai.svg',
104+
'o3': '/static/images/orgs/openai.svg',
105+
'Claude Sonnet 4.5': '/static/images/orgs/anthropic.svg',
106+
'Claude Sonnet 4': '/static/images/orgs/anthropic.svg',
107+
'Gemini 2.5 Pro': '/static/images/orgs/google.svg',
108+
'Grok Code Fast': '/static/images/orgs/xai.svg',
109+
'Qwen3 Coder': '/static/images/orgs/qwen.png'
110+
};
111+
var img = document.createElement('img');
112+
img.src = logos[entry.model] || '/static/images/orgs/default.svg';
113+
img.alt = entry.model + ' logo';
114+
img.style.cssText = 'height:1em; vertical-align:middle; margin-right:0.4em; display:inline-block; transform:translateY(-0.12em);';
115+
modelCell.appendChild(img);
116+
modelCell.appendChild(document.createTextNode(entry.model));
117+
row.appendChild(modelCell);
118+
119+
// Arena scores with gray bars
120+
arenas.forEach(function(arena) {
121+
var cell = document.createElement('td');
122+
cell.className = 'full-elo-cell arena-elo';
123+
var elo = data[arena] || '-';
124+
if (elo !== '-') {
125+
// Linear scaling: 0 ELO = 0%, maxElo = 100%
126+
var percent = Math.max(0, Math.min(100, (elo / maxElo) * 100));
127+
cell.style.setProperty('--elo-percent', percent.toFixed(1));
128+
var eloStd = data[arena + '_std'];
129+
cell.textContent = elo + ' ± ' + eloStd;
130+
} else {
131+
cell.textContent = '-';
132+
}
133+
row.appendChild(cell);
134+
});
135+
136+
// All column with green bar
137+
var allCell = document.createElement('td');
138+
allCell.className = 'full-elo-cell all-elo';
139+
var allElo = data.all;
140+
var allEloStd = data.all_std;
141+
// Linear scaling: 0 ELO = 0%, maxElo = 100%
142+
var allPercent = Math.max(0, Math.min(100, (allElo / maxElo) * 100));
143+
allCell.style.setProperty('--elo-percent', allPercent.toFixed(1));
144+
allCell.textContent = allElo + ' ± ' + allEloStd;
145+
row.appendChild(allCell);
146+
147+
tbody.appendChild(row);
148+
});
149+
}
150+
151+
// Close modal when clicking outside
152+
document.addEventListener('click', function(e) {
153+
var modal = document.getElementById('fullLeaderboardModal');
154+
if (e.target === modal) {
155+
closeFullLeaderboard();
156+
}
157+
});
158+
159+
// Close modal with Escape key
160+
document.addEventListener('keydown', function(e) {
161+
if (e.key === 'Escape') {
162+
closeFullLeaderboard();
163+
}
164+
});
165+
</script>
166+

0 commit comments

Comments
 (0)