ereniko commited on
Commit
70d242f
·
verified ·
1 Parent(s): eea7f55

Upload 6 files

Browse files
Files changed (6) hide show
  1. README.md +9 -5
  2. background.js +90 -0
  3. index.html +1 -1
  4. models.json +122 -0
  5. script.js +376 -0
  6. style.css +629 -18
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Bench Labs Leaderboard
3
- emoji: 💻
4
- colorFrom: gray
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
  ---
2
+ title: Leaderboard
3
+ emoji: 🏆
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ license: apache-2.0
9
  ---
10
 
11
+ Model performance across Bench Labs' benchmark suite — Effortless, Easy, Mid, and AGI.
12
+ Reads from `models.json`, no backend required.
13
+
14
+ all rights reserved @bench-labs
background.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (() => {
2
+ document.addEventListener("DOMContentLoaded", () => {
3
+ const bg = document.querySelector(".background");
4
+ const isMobile = window.matchMedia("(max-width: 768px)").matches;
5
+
6
+ // ---- Clouds ----
7
+ if (bg && !bg.querySelector(".cloud")) {
8
+ if (!isMobile && !document.getElementById("cloud-fluff")) {
9
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "svg");
10
+ defs.setAttribute("width", "0");
11
+ defs.setAttribute("height", "0");
12
+ defs.setAttribute("aria-hidden", "true");
13
+ defs.style.position = "absolute";
14
+ defs.innerHTML =
15
+ '<filter id="cloud-fluff" x="-150%" y="-150%" width="400%" height="400%">' +
16
+ '<feTurbulence type="fractalNoise" baseFrequency="0.012 0.02" numOctaves="4" seed="11" result="noise"/>' +
17
+ '<feDisplacementMap in="SourceGraphic" in2="noise" scale="120"/>' +
18
+ "</filter>";
19
+ document.body.appendChild(defs);
20
+ }
21
+ const cloudCount = isMobile ? 2 : 4;
22
+ for (let i = 1; i <= cloudCount; i++) {
23
+ const cloud = document.createElement("div");
24
+ cloud.className = "cloud cloud-" + i + (isMobile ? " cloud-simple" : "");
25
+ cloud.setAttribute("aria-hidden", "true");
26
+ bg.appendChild(cloud);
27
+ }
28
+ }
29
+
30
+ // ---- Grass strip ----
31
+ if (!document.querySelector(".grass")) {
32
+ const NS = "http://www.w3.org/2000/svg";
33
+ const W = isMobile ? 1200 : 2400;
34
+ const H = 70;
35
+ const svg = document.createElementNS(NS, "svg");
36
+ svg.setAttribute("class", "grass");
37
+ svg.setAttribute("viewBox", "0 0 " + W + " " + H);
38
+ svg.setAttribute("preserveAspectRatio", "xMidYMax slice");
39
+ svg.setAttribute("aria-hidden", "true");
40
+
41
+ svg.innerHTML =
42
+ '<defs><linearGradient id="grass-base" x1="0" y1="1" x2="0" y2="0">' +
43
+ '<stop offset="0" stop-color="#3e7a46" stop-opacity="0.55"/>' +
44
+ '<stop offset="1" stop-color="#3e7a46" stop-opacity="0"/>' +
45
+ "</linearGradient></defs>" +
46
+ '<rect x="0" y="' + (H - 16) + '" width="' + W + '" height="16" fill="url(#grass-base)"/>';
47
+
48
+ const layers = isMobile
49
+ ? [
50
+ { count: 50, hMin: 18, hMax: 32, colors: ["#4f9854"], opacity: 0.9 },
51
+ { count: 40, hMin: 24, hMax: 42, colors: ["#63ad64"], opacity: 1 },
52
+ ]
53
+ : [
54
+ { count: 190, hMin: 18, hMax: 36, colors: ["#3e7a46", "#457f4b", "#4a8a50"], opacity: 0.8 },
55
+ { count: 160, hMin: 26, hMax: 52, colors: ["#4f9854", "#57a05a", "#63ad64", "#6db36e"], opacity: 1 },
56
+ ];
57
+
58
+ layers.forEach((layer) => {
59
+ const g = document.createElementNS(NS, "g");
60
+ g.setAttribute("opacity", layer.opacity);
61
+ for (let i = 0; i < layer.count; i++) {
62
+ const x = Math.random() * W;
63
+ const h = layer.hMin + Math.random() * (layer.hMax - layer.hMin);
64
+ const lean = (Math.random() - 0.5) * 16;
65
+ const w = 3 + Math.random() * 3;
66
+ const color = layer.colors[(Math.random() * layer.colors.length) | 0];
67
+ const blade = document.createElementNS(NS, "path");
68
+ blade.setAttribute(
69
+ "d",
70
+ "M " + x + " " + H +
71
+ " Q " + (x + lean * 0.2) + " " + (H - h * 0.6) + " " + (x + lean) + " " + (H - h) +
72
+ " Q " + (x + lean * 0.6 + w * 0.5) + " " + (H - h * 0.55) + " " + (x + w) + " " + H + " Z"
73
+ );
74
+ blade.setAttribute("fill", color);
75
+ blade.style.transformBox = "fill-box";
76
+ blade.style.transformOrigin = "50% 100%";
77
+ if (!isMobile) {
78
+ blade.style.animation =
79
+ "blade-sway " + (2.6 + Math.random() * 2.4).toFixed(2) + "s ease-in-out " +
80
+ (-(x / W) * 3 - Math.random()).toFixed(2) + "s infinite alternate";
81
+ }
82
+ g.appendChild(blade);
83
+ }
84
+ svg.appendChild(g);
85
+ });
86
+
87
+ document.body.appendChild(svg);
88
+ }
89
+ });
90
+ })();
index.html CHANGED
@@ -99,4 +99,4 @@
99
 
100
  <script src="script.js" defer></script>
101
  </body>
102
- </html>
 
99
 
100
  <script src="script.js" defer></script>
101
  </body>
102
+ </html>
models.json ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "updated": "2026-07-18",
3
+ "benchmarks": [
4
+ {
5
+ "id": "bench-effortless-6-2026",
6
+ "label": "Effortless",
7
+ "tier": 1,
8
+ "rows": 240,
9
+ "metric": "exact_match",
10
+ "dataset_url": "https://huggingface.co/datasets/bench-labs/bench-effortless-6-2026",
11
+ "description": "Sanity-layer benchmark. No ambiguity, no trick questions — tests whether a model can avoid failing on simple tasks."
12
+ },
13
+ {
14
+ "id": "bench-easy-6-2026",
15
+ "label": "Easy",
16
+ "tier": 2,
17
+ "rows": 300,
18
+ "metric": "exact_match",
19
+ "dataset_url": "https://huggingface.co/datasets/bench-labs/bench-easy-6-2026",
20
+ "description": "Effortless-to-Easy tier QA. Structured language understanding and controlled generalization."
21
+ },
22
+ {
23
+ "id": "bench-mid-6-2026",
24
+ "label": "Mid",
25
+ "tier": 3,
26
+ "rows": 155,
27
+ "metric": "acc_norm",
28
+ "dataset_url": "https://huggingface.co/datasets/bench-labs/bench-mid-6-2026",
29
+ "description": "Easy-to-Mid tier multiple-choice QA scored via lm-eval loglikelihood, target_scores dict."
30
+ },
31
+ {
32
+ "id": "bench-AGI",
33
+ "label": "AGI",
34
+ "tier": 4,
35
+ "rows": null,
36
+ "metric": "rank_order",
37
+ "dataset_url": "https://huggingface.co/datasets/bench-labs/bench-AGI",
38
+ "description": "Hard, open-ended questions with human-argued reasoning traces, graded blind by a multi-vendor panel. Rank-order evaluation, not absolute scoring."
39
+ }
40
+ ],
41
+ "categories": [
42
+ "Knowledge-basic", "Knowledge-definitions",
43
+ "Math-arithmetic", "Math-pattern", "Math-reasoning",
44
+ "Logic-deduction", "Logic-pattern", "Logic-consistency",
45
+ "Language-comprehension", "Language-transformation", "Language-structure",
46
+ "Commonsense-reasoning", "Commonsense-causality", "Commonsense-simulation",
47
+ "Pattern-matching", "Pattern-recognition", "Pattern-generation"
48
+ ],
49
+ "models": [
50
+ {
51
+ "id": "qwen2.5-0.5b",
52
+ "name": "Qwen/Qwen2.5-0.5B",
53
+ "org": "Qwen",
54
+ "params_b": 0.5,
55
+ "url": "https://huggingface.co/Qwen/Qwen2.5-0.5B",
56
+ "eval_source": "https://huggingface.co/spaces/bench-labs/blog?post=bMid-qwen0-5.html",
57
+ "runs": {
58
+ "bench-effortless-6-2026": {
59
+ "score": 0.612,
60
+ "n": 240,
61
+ "notes": "Exact-match, normalized. Extrapolated from Mid-tier category profile."
62
+ },
63
+ "bench-easy-6-2026": {
64
+ "score": 0.547,
65
+ "n": 300,
66
+ "notes": "Exact-match, normalized."
67
+ },
68
+ "bench-mid-6-2026": {
69
+ "score": 0.6103,
70
+ "n": 155,
71
+ "acc": 0.5806,
72
+ "acc_norm": 0.6000,
73
+ "soft_score": 0.5871,
74
+ "soft_score_norm": 0.6103,
75
+ "stderr": 0.0388,
76
+ "categories": {
77
+ "Commonsense-causality": { "n": 5, "acc": 1.000, "acc_norm": 1.000 },
78
+ "Commonsense-reasoning": { "n": 10, "acc": 0.700, "acc_norm": 0.600 },
79
+ "Commonsense-simulation": { "n": 10, "acc": 0.300, "acc_norm": 0.500 },
80
+ "Knowledge-basic": { "n": 8, "acc": 1.000, "acc_norm": 1.000 },
81
+ "Knowledge-definitions": { "n": 10, "acc": 0.700, "acc_norm": 0.900 },
82
+ "Language-comprehension": { "n": 10, "acc": 0.600, "acc_norm": 0.800 },
83
+ "Language-structure": { "n": 10, "acc": 0.000, "acc_norm": 0.100 },
84
+ "Language-transformation": { "n": 10, "acc": 0.500, "acc_norm": 0.600 },
85
+ "Logic-consistency": { "n": 5, "acc": 0.200, "acc_norm": 0.200 },
86
+ "Logic-deduction": { "n": 10, "acc": 0.500, "acc_norm": 0.500 },
87
+ "Logic-pattern": { "n": 10, "acc": 0.400, "acc_norm": 0.400 },
88
+ "Math-arithmetic": { "n": 8, "acc": 0.625, "acc_norm": 0.625 },
89
+ "Math-pattern": { "n": 10, "acc": 0.800, "acc_norm": 0.800 },
90
+ "Math-reasoning": { "n": 10, "acc": 0.600, "acc_norm": 0.500 },
91
+ "Pattern-generation": { "n": 9, "acc": 0.778, "acc_norm": 0.667 },
92
+ "Pattern-matching": { "n": 10, "acc": 1.000, "acc_norm": 0.800 },
93
+ "Pattern-recognition": { "n": 10, "acc": 0.300, "acc_norm": 0.300 }
94
+ }
95
+ },
96
+ "bench-AGI": { "score": null, "n": null, "notes": "Not yet evaluated on this tier." }
97
+ }
98
+ },
99
+ {
100
+ "id": "qwen2.5-1.5b-instruct",
101
+ "name": "Qwen/Qwen2.5-1.5B-Instruct",
102
+ "org": "Qwen",
103
+ "params_b": 1.5,
104
+ "url": "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct",
105
+ "eval_source": "https://huggingface.co/spaces/bench-labs/blog?post=evaluatingllm.html",
106
+ "runs": {
107
+ "bench-effortless-6-2026": {
108
+ "score": 0.783,
109
+ "n": 240,
110
+ "notes": "Strong on commonsense & language transformation; weak on symbolic logic per the hybrid evaluator writeup."
111
+ },
112
+ "bench-easy-6-2026": {
113
+ "score": 0.741,
114
+ "n": 300,
115
+ "notes": "Hybrid category-aware scoring (strict / flexible / semantic)."
116
+ },
117
+ "bench-mid-6-2026": { "score": null, "n": null, "notes": "Not yet evaluated on this tier." },
118
+ "bench-AGI": { "score": null, "n": null, "notes": "Not yet evaluated on this tier." }
119
+ }
120
+ }
121
+ ]
122
+ }
script.js ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (() => {
2
+ "use strict";
3
+
4
+ const state = {
5
+ data: null,
6
+ activeBenchmark: null,
7
+ search: "",
8
+ sort: "score-desc",
9
+ };
10
+
11
+ const els = {};
12
+
13
+ document.addEventListener("DOMContentLoaded", init);
14
+
15
+ async function init() {
16
+ els.tierStrip = document.getElementById("tier-strip");
17
+ els.metricSelect = document.getElementById("metric-select");
18
+ els.sortSelect = document.getElementById("sort-select");
19
+ els.searchInput = document.getElementById("model-search");
20
+ els.tbody = document.getElementById("leaderboard-body");
21
+ els.emptyState = document.getElementById("empty-state");
22
+ els.table = document.getElementById("leaderboard-table");
23
+ els.detailPanel = document.getElementById("detail-panel");
24
+ els.detailTitle = document.getElementById("detail-title");
25
+ els.detailSubtitle = document.getElementById("detail-subtitle");
26
+ els.detailCats = document.getElementById("detail-cats");
27
+ els.detailClose = document.getElementById("detail-close");
28
+
29
+ try {
30
+ const res = await fetch("models.json", { cache: "no-store" });
31
+ if (!res.ok) throw new Error("HTTP " + res.status);
32
+ state.data = await res.json();
33
+ } catch (err) {
34
+ renderLoadError(err);
35
+ return;
36
+ }
37
+
38
+ state.activeBenchmark = state.data.benchmarks[0]?.id || null;
39
+
40
+ buildTierStrip();
41
+ buildMetricSelect();
42
+ bindControls();
43
+ render();
44
+ }
45
+
46
+ function renderLoadError(err) {
47
+ els.tbody = document.getElementById("leaderboard-body");
48
+ els.tbody.innerHTML =
49
+ '<tr><td colspan="4" style="padding:32px;text-align:center;color:var(--muted);">' +
50
+ "Couldn't load <code>models.json</code> (" + escapeHtml(String(err.message || err)) +
51
+ "). Make sure it sits next to index.html." +
52
+ "</td></tr>";
53
+ }
54
+
55
+ // -------------------- TIER STRIP --------------------
56
+
57
+ function buildTierStrip() {
58
+ const frag = document.createDocumentFragment();
59
+ state.data.benchmarks
60
+ .slice()
61
+ .sort((a, b) => a.tier - b.tier)
62
+ .forEach((bench) => {
63
+ const card = document.createElement("button");
64
+ card.type = "button";
65
+ card.className = "tier-card" + (bench.id === state.activeBenchmark ? " active" : "");
66
+ card.dataset.benchmarkId = bench.id;
67
+
68
+ const rowsLabel = bench.rows != null ? bench.rows + " rows" : "open-ended";
69
+
70
+ card.innerHTML =
71
+ '<span class="tier-num">Tier ' + bench.tier + " · " + escapeHtml(bench.label) + "</span>" +
72
+ "<h3>" + escapeHtml(bench.id) + "</h3>" +
73
+ "<p>" + escapeHtml(bench.description) + "</p>" +
74
+ '<div class="tier-meta">' +
75
+ "<span>" + rowsLabel + "</span>" +
76
+ "<span>" + escapeHtml(bench.metric) + "</span>" +
77
+ "</div>";
78
+
79
+ card.addEventListener("click", () => {
80
+ state.activeBenchmark = bench.id;
81
+ closeDetail();
82
+ buildTierStrip();
83
+ buildMetricSelect();
84
+ render();
85
+ });
86
+
87
+ frag.appendChild(card);
88
+ });
89
+
90
+ els.tierStrip.innerHTML = "";
91
+ els.tierStrip.appendChild(frag);
92
+ }
93
+
94
+ // -------------------- METRIC SELECT --------------------
95
+
96
+ function buildMetricSelect() {
97
+ const bench = getActiveBenchmark();
98
+ if (!bench) return;
99
+
100
+ // Collect every metric key present across model runs for this benchmark,
101
+ // beyond the headline "score", so users can pivot (acc vs acc_norm etc).
102
+ const keys = new Set(["score"]);
103
+ state.data.models.forEach((m) => {
104
+ const run = m.runs?.[bench.id];
105
+ if (!run) return;
106
+ Object.keys(run).forEach((k) => {
107
+ if (typeof run[k] === "number" && k !== "n" && k !== "stderr") keys.add(k);
108
+ });
109
+ });
110
+
111
+ const labels = {
112
+ score: "Headline score",
113
+ acc: "acc",
114
+ acc_norm: "acc_norm",
115
+ soft_score: "soft_score",
116
+ soft_score_norm: "soft_score_norm",
117
+ stderr: "stderr",
118
+ };
119
+
120
+ const prev = els.metricSelect.value;
121
+ els.metricSelect.innerHTML = "";
122
+ Array.from(keys).forEach((k) => {
123
+ const opt = document.createElement("option");
124
+ opt.value = k;
125
+ opt.textContent = labels[k] || k;
126
+ els.metricSelect.appendChild(opt);
127
+ });
128
+ if (keys.has(prev)) els.metricSelect.value = prev;
129
+ }
130
+
131
+ // -------------------- CONTROLS --------------------
132
+
133
+ function bindControls() {
134
+ els.searchInput.addEventListener("input", (e) => {
135
+ state.search = e.target.value.trim().toLowerCase();
136
+ render();
137
+ });
138
+
139
+ els.sortSelect.addEventListener("change", (e) => {
140
+ state.sort = e.target.value;
141
+ render();
142
+ });
143
+
144
+ els.metricSelect.addEventListener("change", render);
145
+
146
+ els.table.querySelectorAll("thead th[data-key]").forEach((th) => {
147
+ th.addEventListener("click", () => {
148
+ const key = th.dataset.key;
149
+ if (key === "score") {
150
+ state.sort = state.sort === "score-desc" ? "score-asc" : "score-desc";
151
+ } else if (key === "params") {
152
+ state.sort = state.sort === "params-asc" ? "params-desc" : "params-asc";
153
+ } else if (key === "name") {
154
+ state.sort = "name-asc";
155
+ }
156
+ els.sortSelect.value = state.sort;
157
+ render();
158
+ });
159
+ });
160
+
161
+ els.detailClose.addEventListener("click", closeDetail);
162
+ }
163
+
164
+ // -------------------- HELPERS --------------------
165
+
166
+ function getActiveBenchmark() {
167
+ return state.data.benchmarks.find((b) => b.id === state.activeBenchmark) || null;
168
+ }
169
+
170
+ function getMetricValue(run, metricKey) {
171
+ if (!run) return null;
172
+ const v = run[metricKey];
173
+ return typeof v === "number" ? v : null;
174
+ }
175
+
176
+ function scoreClass(v) {
177
+ if (v == null) return "";
178
+ if (v >= 0.66) return "good";
179
+ if (v >= 0.4) return "ok";
180
+ return "bad";
181
+ }
182
+
183
+ function fmtScore(v) {
184
+ if (v == null) return null;
185
+ return (v * 100).toFixed(1) + "%";
186
+ }
187
+
188
+ function escapeHtml(s) {
189
+ return String(s)
190
+ .replace(/&/g, "&amp;")
191
+ .replace(/</g, "&lt;")
192
+ .replace(/>/g, "&gt;")
193
+ .replace(/"/g, "&quot;");
194
+ }
195
+
196
+ // -------------------- RENDER TABLE --------------------
197
+
198
+ function render() {
199
+ const bench = getActiveBenchmark();
200
+ if (!bench) return;
201
+
202
+ const metricKey = els.metricSelect.value || "score";
203
+
204
+ let rows = state.data.models.map((m) => {
205
+ const run = m.runs?.[bench.id] || null;
206
+ const value = getMetricValue(run, metricKey);
207
+ return { model: m, run, value };
208
+ });
209
+
210
+ if (state.search) {
211
+ rows = rows.filter(
212
+ (r) =>
213
+ r.model.name.toLowerCase().includes(state.search) ||
214
+ (r.model.org || "").toLowerCase().includes(state.search)
215
+ );
216
+ }
217
+
218
+ rows = sortRows(rows, state.sort);
219
+
220
+ els.tbody.innerHTML = "";
221
+ if (rows.length === 0) {
222
+ els.emptyState.hidden = false;
223
+ els.table.hidden = true;
224
+ return;
225
+ }
226
+ els.emptyState.hidden = true;
227
+ els.table.hidden = false;
228
+
229
+ const frag = document.createDocumentFragment();
230
+ rows.forEach((r, idx) => {
231
+ frag.appendChild(buildRow(r, idx + 1, bench));
232
+ });
233
+ els.tbody.appendChild(frag);
234
+
235
+ updateSortIndicators();
236
+ }
237
+
238
+ function sortRows(rows, sort) {
239
+ const withScore = (r) => (r.value == null ? -Infinity : r.value);
240
+ const withParams = (r) => (r.model.params_b == null ? Infinity : r.model.params_b);
241
+
242
+ switch (sort) {
243
+ case "score-asc":
244
+ return rows.slice().sort((a, b) => {
245
+ if (a.value == null && b.value == null) return 0;
246
+ if (a.value == null) return 1;
247
+ if (b.value == null) return 1;
248
+ return a.value - b.value;
249
+ });
250
+ case "params-asc":
251
+ return rows.slice().sort((a, b) => withParams(a) - withParams(b));
252
+ case "params-desc":
253
+ return rows.slice().sort((a, b) => withParams(b) - withParams(a));
254
+ case "name-asc":
255
+ return rows.slice().sort((a, b) => a.model.name.localeCompare(b.model.name));
256
+ case "score-desc":
257
+ default:
258
+ return rows.slice().sort((a, b) => {
259
+ if (a.value == null && b.value == null) return 0;
260
+ if (a.value == null) return 1;
261
+ if (b.value == null) return -1;
262
+ return b.value - a.value;
263
+ });
264
+ }
265
+ }
266
+
267
+ function updateSortIndicators() {
268
+ els.table.querySelectorAll("thead th[data-key]").forEach((th) => {
269
+ th.classList.remove("sorted");
270
+ const arrow = th.querySelector(".sort-arrow");
271
+ if (!arrow) return;
272
+ const key = th.dataset.key;
273
+ const active =
274
+ (key === "score" && (state.sort === "score-desc" || state.sort === "score-asc")) ||
275
+ (key === "params" && (state.sort === "params-asc" || state.sort === "params-desc")) ||
276
+ (key === "name" && state.sort === "name-asc");
277
+ if (active) {
278
+ th.classList.add("sorted");
279
+ arrow.textContent = state.sort.endsWith("asc") ? "▲" : "▼";
280
+ }
281
+ });
282
+ }
283
+
284
+ function buildRow(r, rank, bench) {
285
+ const tr = document.createElement("tr");
286
+ const { model, run, value } = r;
287
+
288
+ // rank
289
+ const rankTd = document.createElement("td");
290
+ rankTd.className = "rank-cell" + (rank <= 3 ? " top-" + rank : "");
291
+ rankTd.textContent = value == null ? "–" : String(rank);
292
+ tr.appendChild(rankTd);
293
+
294
+ // model
295
+ const modelTd = document.createElement("td");
296
+ modelTd.innerHTML =
297
+ '<div class="model-cell">' +
298
+ '<span class="model-name"><a href="' + escapeHtml(model.url) + '" target="_blank" rel="noopener">' +
299
+ escapeHtml(model.name) + "</a></span>" +
300
+ '<span class="model-org">' + escapeHtml(model.org || "") + "</span>" +
301
+ "</div>";
302
+ tr.appendChild(modelTd);
303
+
304
+ // params
305
+ const paramsTd = document.createElement("td");
306
+ paramsTd.className = "params-cell";
307
+ paramsTd.textContent = model.params_b != null ? model.params_b + "B" : "—";
308
+ tr.appendChild(paramsTd);
309
+
310
+ // score
311
+ const scoreTd = document.createElement("td");
312
+ scoreTd.className = "score-cell";
313
+ if (value == null) {
314
+ scoreTd.innerHTML = '<span class="score-na">not evaluated</span>';
315
+ } else {
316
+ const pct = Math.max(0, Math.min(100, value * 100));
317
+ const cls = scoreClass(value);
318
+ scoreTd.innerHTML =
319
+ '<span class="score-pill">' +
320
+ '<span class="score-num ' + cls + '">' + fmtScore(value) + "</span>" +
321
+ '<span class="score-bar-track"><span class="score-bar-fill" style="width:' + pct.toFixed(1) + '%"></span></span>' +
322
+ "</span>";
323
+ }
324
+ tr.appendChild(scoreTd);
325
+
326
+ if (run && run.categories) {
327
+ tr.style.cursor = "pointer";
328
+ tr.title = "Click for per-category breakdown";
329
+ tr.addEventListener("click", () => openDetail(model, run, bench));
330
+ }
331
+
332
+ return tr;
333
+ }
334
+
335
+ // -------------------- DETAIL PANEL --------------------
336
+
337
+ function openDetail(model, run, bench) {
338
+ els.detailTitle.textContent = model.name + " on " + bench.id;
339
+ els.detailSubtitle.textContent =
340
+ "Per-category breakdown" + (run.n ? " · n=" + run.n : "") +
341
+ (run.stderr != null ? " · stderr " + run.stderr : "");
342
+
343
+ const cats = run.categories || {};
344
+ const frag = document.createDocumentFragment();
345
+ Object.keys(cats)
346
+ .sort()
347
+ .forEach((catName) => {
348
+ const c = cats[catName];
349
+ const v = typeof c.acc_norm === "number" ? c.acc_norm : c.acc;
350
+ const pct = Math.max(0, Math.min(100, (v || 0) * 100));
351
+ const cls = scoreClass(v);
352
+ const color =
353
+ cls === "good" ? "var(--score-good)" : cls === "ok" ? "var(--score-ok)" : "var(--score-bad)";
354
+
355
+ const row = document.createElement("div");
356
+ row.className = "cat-row";
357
+ row.innerHTML =
358
+ '<div class="cat-row-top">' +
359
+ '<span class="cat-name">' + escapeHtml(catName) + "</span>" +
360
+ '<span class="cat-score" style="color:' + color + '">' + fmtScore(v) + "</span>" +
361
+ "</div>" +
362
+ '<div class="cat-bar-track"><div class="cat-bar-fill" style="width:' + pct.toFixed(1) + "%;background:" + color + ';"></div></div>' +
363
+ '<div class="cat-n">n=' + c.n + "</div>";
364
+ frag.appendChild(row);
365
+ });
366
+
367
+ els.detailCats.innerHTML = "";
368
+ els.detailCats.appendChild(frag);
369
+ els.detailPanel.classList.add("open");
370
+ els.detailPanel.scrollIntoView({ behavior: "smooth", block: "nearest" });
371
+ }
372
+
373
+ function closeDetail() {
374
+ els.detailPanel.classList.remove("open");
375
+ }
376
+ })();
style.css CHANGED
@@ -1,28 +1,639 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
28
  }
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
2
+ @import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@1,400;1,600&display=swap');
3
+
4
+ :root {
5
+ --bg: #eaf5ee;
6
+ --text: #2d3e50;
7
+ --heading: #1a2b3c;
8
+ --muted: #6b7f92;
9
+ --surface: rgba(255,255,255,0.55);
10
+ --surface-2: rgba(255,255,255,0.75);
11
+ --border: rgba(255,255,255,0.7);
12
+ --border-soft: rgba(120, 160, 200, 0.18);
13
+ --accent: #4a90c2;
14
+ --accent-soft: #7cb8e0;
15
+ --accent-green: #5da570;
16
+ --accent-green-soft: #9ccba6;
17
+ --white: #ffffff;
18
+ --transition: 0.2s ease;
19
+ --radius: 18px;
20
+ --shadow-soft: 0 8px 32px rgba(120, 160, 200, 0.15);
21
+ --shadow-hover: 0 12px 40px rgba(74, 144, 194, 0.2);
22
+
23
+ /* score heat scale, reuses the blog's good/ok/bad palette */
24
+ --score-good: #00c853;
25
+ --score-ok: #ffb300;
26
+ --score-bad: #ff3d00;
27
+ }
28
+
29
+ * { margin: 0; padding: 0; box-sizing: border-box; }
30
+ html { scroll-behavior: smooth; }
31
+
32
+ @media (prefers-reduced-motion: reduce) {
33
+ html { scroll-behavior: auto; }
34
+ *, *::before, *::after {
35
+ transition-duration: 0.01ms !important;
36
+ animation-duration: 0.01ms !important;
37
+ }
38
+ }
39
+
40
  body {
41
+ background: var(--bg);
42
+ color: var(--text);
43
+ font-family: Inter, system-ui, sans-serif;
44
+ line-height: 1.6;
45
+ -webkit-font-smoothing: antialiased;
46
+ text-rendering: optimizeLegibility;
47
+ padding-bottom: 80px;
48
+ }
49
+
50
+ h1, h2, h3, h4 { color: var(--heading); letter-spacing: -0.02em; }
51
+ h1 { font-family: 'Newsreader', serif; font-style: italic; }
52
+
53
+ a { color: var(--accent); text-decoration: none; transition: opacity var(--transition); }
54
+ a:hover { opacity: 0.75; }
55
+ a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; opacity: 1; }
56
+
57
+ /* -------------------- BACKGROUND -------------------- */
58
+
59
+ .background {
60
+ position: fixed;
61
+ inset: 0;
62
+ z-index: -1;
63
+ background:
64
+ radial-gradient(ellipse 130% 45% at 50% 108%, rgba(125, 180, 130, 0.35) 0%, transparent 70%),
65
+ linear-gradient(180deg, #7cb8e0 0%, #a8d0e8 35%, #d8ebf5 60%, #dceee2 82%, #e4f2e6 100%);
66
+ overflow: hidden;
67
+ }
68
+
69
+ .background::before {
70
+ content: '';
71
+ position: absolute;
72
+ top: 10%; left: -10%;
73
+ width: 60%; height: 40%;
74
+ background: radial-gradient(ellipse at center, rgba(255,255,255,0.6) 0%, transparent 70%);
75
+ filter: blur(40px);
76
+ pointer-events: none;
77
+ }
78
+
79
+ .background::after {
80
+ content: '';
81
+ position: absolute;
82
+ top: 30%; right: -5%;
83
+ width: 50%; height: 30%;
84
+ background: radial-gradient(ellipse at center, rgba(255,255,255,0.5) 0%, transparent 70%);
85
+ filter: blur(50px);
86
+ pointer-events: none;
87
+ }
88
+
89
+ .background .cloud {
90
+ position: absolute;
91
+ left: 0;
92
+ width: 220px;
93
+ height: 60px;
94
+ border-radius: 50%;
95
+ background: rgba(255,255,255,0.9);
96
+ box-shadow:
97
+ -70px 12px 30px 6px rgba(255,255,255,0.75),
98
+ 80px -12px 34px 10px rgba(255,255,255,0.9),
99
+ 30px 14px 36px 8px rgba(255,255,255,0.8),
100
+ -20px -26px 28px 2px rgba(255,255,255,0.95),
101
+ 60px 22px 30px 4px rgba(250,253,255,0.7),
102
+ 0 34px 26px -14px rgba(150,175,200,0.4);
103
+ filter: url(#cloud-fluff);
104
+ animation: cloud-drift linear infinite;
105
+ will-change: transform;
106
+ }
107
+
108
+ .background .cloud-1 { top: 8%; --cloud-scale: 1.15; animation-duration: 210s; animation-delay: -80s; }
109
+ .background .cloud-2 { top: 24%; --cloud-scale: 0.75; opacity: 0.85; animation-duration: 260s; animation-delay: -180s; }
110
+ .background .cloud-3 { top: 14%; --cloud-scale: 1.5; opacity: 0.95; animation-duration: 300s; animation-delay: -40s; }
111
+ .background .cloud-4 { top: 36%; --cloud-scale: 0.55; opacity: 0.6; animation-duration: 320s; animation-delay: -240s; }
112
+ .background .cloud-simple { filter: none; }
113
+
114
+ @keyframes cloud-drift {
115
+ from { transform: translateX(-45vw) scale(var(--cloud-scale, 1)); }
116
+ to { transform: translateX(120vw) scale(var(--cloud-scale, 1)); }
117
+ }
118
+
119
+ .grass {
120
+ position: fixed;
121
+ bottom: -2px; left: 0;
122
+ width: 100%; height: 70px;
123
+ z-index: 50;
124
+ pointer-events: none;
125
+ }
126
+
127
+ @keyframes blade-sway {
128
+ from { transform: rotate(-2.4deg); }
129
+ to { transform: rotate(3.2deg); }
130
+ }
131
+
132
+ @media (prefers-reduced-motion: reduce) {
133
+ .grass path { animation: none !important; }
134
+ }
135
+
136
+ /* -------------------- LAYOUT -------------------- */
137
+
138
+ .container {
139
+ max-width: 1180px;
140
+ margin: auto;
141
+ padding: 64px 24px 40px;
142
+ }
143
+
144
+ .hero { margin-bottom: 36px; }
145
+
146
+ .eyebrow {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ gap: 6px;
150
+ color: var(--accent);
151
+ font-size: 0.78rem;
152
+ font-weight: 600;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.1em;
155
+ margin-bottom: 14px;
156
+ }
157
+
158
+ .eyebrow a { color: var(--accent); }
159
+
160
+ .hero h1 {
161
+ font-size: clamp(2.2rem, 6vw, 3.4rem);
162
+ font-weight: 800;
163
+ letter-spacing: -0.05em;
164
+ line-height: 1.1;
165
+ background: linear-gradient(135deg, #5da570 0%, #0c1927 100%);
166
+ -webkit-background-clip: text;
167
+ -webkit-text-fill-color: transparent;
168
+ text-shadow: 0 2px 20px rgba(255,255,255,0.3);
169
+ }
170
+
171
+ .subtitle {
172
+ margin-top: 12px;
173
+ max-width: 640px;
174
+ color: var(--muted);
175
+ font-size: 1.05rem;
176
  }
177
 
178
+ /* -------------------- TIER STRIP -------------------- */
179
+
180
+ .tier-strip {
181
+ display: grid;
182
+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
183
+ gap: 14px;
184
+ margin: 32px 0 30px;
185
+ }
186
+
187
+ .tier-card {
188
+ padding: 18px 20px;
189
+ border-radius: var(--radius);
190
+ background: var(--surface);
191
+ border: 1px solid var(--border);
192
+ backdrop-filter: blur(20px) saturate(1.2);
193
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
194
+ box-shadow: var(--shadow-soft);
195
+ transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition);
196
+ cursor: pointer;
197
+ }
198
+
199
+ .tier-card:hover {
200
+ transform: translateY(-3px);
201
+ box-shadow: var(--shadow-hover);
202
+ border-color: rgba(93, 165, 112, 0.4);
203
+ }
204
+
205
+ .tier-card.active {
206
+ border-color: var(--accent);
207
+ background: var(--surface-2);
208
+ box-shadow: var(--shadow-hover);
209
+ }
210
+
211
+ .tier-card .tier-num {
212
+ display: inline-block;
213
+ font-size: 0.7rem;
214
+ font-weight: 700;
215
+ color: var(--accent);
216
+ letter-spacing: 0.08em;
217
+ text-transform: uppercase;
218
+ margin-bottom: 6px;
219
+ }
220
+
221
+ .tier-card h3 { font-size: 1.15rem; margin-bottom: 6px; }
222
+ .tier-card p { color: var(--muted); font-size: 0.85rem; line-height: 1.5; }
223
+
224
+ .tier-meta {
225
+ display: flex;
226
+ gap: 10px;
227
+ margin-top: 10px;
228
+ font-size: 0.72rem;
229
+ color: var(--muted);
230
+ }
231
+
232
+ .tier-meta span {
233
+ padding: 2px 8px;
234
+ background: rgba(255,255,255,0.6);
235
+ border-radius: 999px;
236
+ border: 1px solid var(--border-soft);
237
+ }
238
+
239
+ /* -------------------- CONTROLS -------------------- */
240
+
241
+ .controls {
242
+ display: flex;
243
+ flex-wrap: wrap;
244
+ align-items: center;
245
+ gap: 12px;
246
+ margin-bottom: 22px;
247
+ }
248
+
249
+ .search-pill {
250
+ display: flex;
251
+ align-items: center;
252
+ flex: 1;
253
+ min-width: 220px;
254
+ gap: 6px;
255
+ padding: 6px 6px 6px 18px;
256
+ background: var(--surface);
257
+ border: 1px solid var(--border);
258
+ border-radius: 999px;
259
+ backdrop-filter: blur(20px) saturate(1.2);
260
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
261
+ box-shadow: var(--shadow-soft);
262
+ transition: border-color var(--transition), box-shadow var(--transition);
263
  }
264
 
265
+ .search-pill:focus-within {
266
+ border-color: rgba(74, 144, 194, 0.5);
267
+ box-shadow: var(--shadow-hover);
 
 
268
  }
269
 
270
+ .search-pill input {
271
+ flex: 1;
272
+ min-width: 0;
273
+ border: none;
274
+ background: transparent;
275
+ outline: none;
276
+ font-family: inherit;
277
+ font-size: 0.92rem;
278
+ color: var(--text);
279
+ padding: 8px 4px;
280
+ }
281
+
282
+ .search-pill input::placeholder { color: var(--muted); }
283
+
284
+ .search-pill svg { color: var(--muted); flex-shrink: 0; margin-right: 2px; }
285
+
286
+ .metric-select-wrap, .sort-select-wrap {
287
+ position: relative;
288
+ }
289
+
290
+ .pill-select {
291
+ appearance: none;
292
+ -webkit-appearance: none;
293
+ padding: 11px 34px 11px 18px;
294
+ background: var(--surface);
295
+ border: 1px solid var(--border);
296
+ border-radius: 999px;
297
+ backdrop-filter: blur(20px) saturate(1.2);
298
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
299
+ box-shadow: var(--shadow-soft);
300
+ font-family: inherit;
301
+ font-size: 0.85rem;
302
+ color: var(--heading);
303
+ cursor: pointer;
304
+ transition: border-color var(--transition), box-shadow var(--transition);
305
+ }
306
+
307
+ .pill-select:hover { border-color: rgba(74,144,194,0.4); }
308
+ .pill-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
309
+
310
+ .select-arrow {
311
+ position: absolute;
312
+ right: 14px;
313
+ top: 50%;
314
+ transform: translateY(-50%);
315
+ pointer-events: none;
316
+ color: var(--accent);
317
+ font-size: 0.65rem;
318
+ }
319
+
320
+ /* -------------------- TABLE -------------------- */
321
+
322
+ .table-scroll {
323
+ overflow-x: auto;
324
+ border-radius: var(--radius);
325
+ background: var(--surface);
326
+ border: 1px solid var(--border);
327
+ backdrop-filter: blur(20px) saturate(1.2);
328
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
329
+ box-shadow: var(--shadow-soft);
330
+ }
331
+
332
+ table.leaderboard {
333
+ width: 100%;
334
+ border-collapse: separate;
335
+ border-spacing: 0;
336
+ font-size: 0.92rem;
337
+ min-width: 640px;
338
+ }
339
+
340
+ table.leaderboard thead th {
341
+ position: sticky;
342
+ top: 0;
343
+ background: rgba(255,255,255,0.82);
344
+ backdrop-filter: blur(12px);
345
+ -webkit-backdrop-filter: blur(12px);
346
+ color: var(--heading);
347
+ font-weight: 600;
348
+ text-align: left;
349
+ padding: 14px 16px;
350
+ border-bottom: 1px solid var(--border-soft);
351
+ font-size: 0.78rem;
352
+ text-transform: uppercase;
353
+ letter-spacing: 0.04em;
354
+ cursor: pointer;
355
+ user-select: none;
356
+ white-space: nowrap;
357
+ }
358
+
359
+ table.leaderboard thead th:hover { color: var(--accent); }
360
+
361
+ table.leaderboard thead th .sort-arrow {
362
+ display: inline-block;
363
+ margin-left: 4px;
364
+ font-size: 0.7rem;
365
+ color: var(--accent);
366
+ opacity: 0;
367
+ }
368
+
369
+ table.leaderboard thead th.sorted .sort-arrow { opacity: 1; }
370
+
371
+ table.leaderboard tbody tr {
372
+ transition: background var(--transition);
373
+ }
374
+
375
+ table.leaderboard tbody tr:hover { background: rgba(74, 144, 194, 0.07); }
376
+
377
+ table.leaderboard tbody tr:not(:last-child) td { border-bottom: 1px solid var(--border-soft); }
378
+
379
+ table.leaderboard td {
380
+ padding: 14px 16px;
381
+ color: var(--text);
382
+ vertical-align: middle;
383
+ }
384
+
385
+ .rank-cell {
386
+ font-weight: 700;
387
+ color: var(--muted);
388
+ width: 46px;
389
+ font-variant-numeric: tabular-nums;
390
+ }
391
+
392
+ .rank-cell.top-1 { color: #d9a441; }
393
+ .rank-cell.top-2 { color: #9aa5b1; }
394
+ .rank-cell.top-3 { color: #c08552; }
395
+
396
+ .model-cell {
397
+ display: flex;
398
+ flex-direction: column;
399
+ gap: 2px;
400
+ min-width: 200px;
401
+ }
402
+
403
+ .model-name {
404
+ font-weight: 600;
405
+ color: var(--heading);
406
+ font-size: 0.95rem;
407
+ }
408
+
409
+ .model-name a { color: inherit; }
410
+ .model-name a:hover { color: var(--accent); opacity: 1; }
411
+
412
+ .model-org {
413
+ font-size: 0.76rem;
414
+ color: var(--muted);
415
+ }
416
+
417
+ .params-cell {
418
+ font-variant-numeric: tabular-nums;
419
+ color: var(--muted);
420
+ font-size: 0.85rem;
421
+ white-space: nowrap;
422
+ }
423
+
424
+ .score-cell {
425
+ font-variant-numeric: tabular-nums;
426
+ font-weight: 600;
427
+ white-space: nowrap;
428
+ }
429
+
430
+ .score-pill {
431
+ display: inline-flex;
432
+ align-items: center;
433
+ gap: 8px;
434
+ }
435
+
436
+ .score-bar-track {
437
+ position: relative;
438
+ width: 74px;
439
+ height: 6px;
440
+ border-radius: 999px;
441
+ background: rgba(120,160,200,0.18);
442
+ overflow: hidden;
443
+ }
444
+
445
+ .score-bar-fill {
446
+ position: absolute;
447
+ inset: 0;
448
+ width: 0%;
449
+ border-radius: 999px;
450
+ background: linear-gradient(90deg, var(--accent-soft), var(--accent));
451
+ transition: width 0.4s ease;
452
+ }
453
+
454
+ .score-num.good { color: #1a8f4f; }
455
+ .score-num.ok { color: #b57900; }
456
+ .score-num.bad { color: #c9432c; }
457
+
458
+ .score-na {
459
+ color: var(--muted);
460
+ font-size: 0.82rem;
461
+ font-style: italic;
462
+ }
463
+
464
+ .model-link-icon {
465
+ display: inline-flex;
466
+ color: var(--muted);
467
+ transition: color var(--transition);
468
+ }
469
+ .model-link-icon:hover { color: var(--accent); }
470
+
471
+ /* -------------------- EMPTY STATE -------------------- */
472
+
473
+ .empty-state {
474
+ padding: 60px 24px;
475
+ text-align: center;
476
+ color: var(--muted);
477
+ }
478
+
479
+ .empty-state svg { margin-bottom: 14px; color: var(--accent-soft); }
480
+
481
+ /* -------------------- DETAIL PANEL -------------------- */
482
+
483
+ .detail-panel {
484
+ margin-top: 28px;
485
+ padding: 26px;
486
+ border-radius: var(--radius);
487
+ background: var(--surface);
488
+ border: 1px solid var(--border);
489
+ backdrop-filter: blur(20px) saturate(1.2);
490
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
491
+ box-shadow: var(--shadow-soft);
492
+ display: none;
493
+ }
494
+
495
+ .detail-panel.open { display: block; }
496
+
497
+ .detail-panel-header {
498
+ display: flex;
499
+ align-items: flex-start;
500
+ justify-content: space-between;
501
+ gap: 16px;
502
+ margin-bottom: 18px;
503
+ flex-wrap: wrap;
504
+ }
505
+
506
+ .detail-panel-header h2 { font-size: 1.3rem; margin-bottom: 4px; }
507
+ .detail-panel-header p { color: var(--muted); font-size: 0.88rem; }
508
+
509
+ .detail-close {
510
+ flex-shrink: 0;
511
+ width: 34px;
512
+ height: 34px;
513
+ border-radius: 50%;
514
+ border: 1px solid var(--border-soft);
515
+ background: rgba(255,255,255,0.6);
516
+ color: var(--muted);
517
+ display: flex;
518
+ align-items: center;
519
+ justify-content: center;
520
+ cursor: pointer;
521
+ transition: background var(--transition), color var(--transition), border-color var(--transition);
522
+ }
523
+
524
+ .detail-close:hover { background: #fff; color: var(--accent); border-color: var(--accent); }
525
+
526
+ .cat-grid {
527
+ display: grid;
528
+ grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
529
+ gap: 10px;
530
+ }
531
+
532
+ .cat-row {
533
+ padding: 12px 14px;
534
+ border-radius: 12px;
535
+ background: rgba(255,255,255,0.5);
536
+ border: 1px solid var(--border-soft);
537
+ }
538
+
539
+ .cat-row-top {
540
+ display: flex;
541
+ justify-content: space-between;
542
+ align-items: baseline;
543
+ margin-bottom: 6px;
544
+ gap: 8px;
545
+ }
546
+
547
+ .cat-name {
548
+ font-size: 0.8rem;
549
+ color: var(--heading);
550
+ font-weight: 500;
551
+ }
552
+
553
+ .cat-n {
554
+ font-size: 0.68rem;
555
+ color: var(--muted);
556
+ }
557
+
558
+ .cat-score {
559
+ font-size: 0.85rem;
560
+ font-weight: 700;
561
+ font-variant-numeric: tabular-nums;
562
+ }
563
+
564
+ .cat-bar-track {
565
+ height: 5px;
566
+ border-radius: 999px;
567
+ background: rgba(120,160,200,0.15);
568
+ overflow: hidden;
569
+ }
570
+
571
+ .cat-bar-fill {
572
+ height: 100%;
573
+ border-radius: 999px;
574
+ }
575
+
576
+ /* -------------------- FOOTER -------------------- */
577
+
578
+ .page-footer {
579
+ margin-top: 50px;
580
+ padding-top: 22px;
581
+ border-top: 1px solid var(--border-soft);
582
+ display: flex;
583
+ flex-wrap: wrap;
584
+ justify-content: space-between;
585
+ gap: 12px;
586
+ color: var(--muted);
587
+ font-size: 0.8rem;
588
+ }
589
+
590
+ .page-footer a { color: var(--accent); }
591
+
592
+ /* -------------------- RESPONSIVE -------------------- */
593
+
594
+ @media (max-width: 640px) {
595
+ .container { padding: 40px 16px 32px; }
596
+ .controls { flex-direction: column; align-items: stretch; }
597
+ table.leaderboard { min-width: 560px; }
598
+ }
599
+
600
+ /* -------------------- SCROLLBARS / SELECTION -------------------- */
601
+
602
+ ::selection { background: rgba(74,144,194,0.3); color: var(--heading); }
603
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
604
+ ::-webkit-scrollbar-track { background: rgba(255,255,255,0.3); }
605
+ ::-webkit-scrollbar-thumb { background: rgba(120,160,200,0.4); border-radius: 5px; }
606
+ ::-webkit-scrollbar-thumb:hover { background: rgba(74,144,194,0.6); }
607
+
608
+ /* -------------------- TOOLTIP (from blog) -------------------- */
609
+
610
+ .tooltip { position: relative; }
611
+
612
+ .tooltip::after {
613
+ content: attr(data-tooltip);
614
+ position: absolute;
615
+ left: 50%;
616
+ bottom: calc(100% + 10px);
617
+ transform: translateX(-50%) translateY(4px);
618
+ padding: 8px 12px;
619
+ border-radius: 10px;
620
+ background: var(--surface-2);
621
+ border: 1px solid var(--border);
622
+ backdrop-filter: blur(20px) saturate(1.2);
623
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
624
+ color: var(--heading);
625
+ font-size: 0.78rem;
626
+ font-weight: 500;
627
+ white-space: nowrap;
628
+ box-shadow: var(--shadow-hover);
629
+ opacity: 0;
630
+ visibility: hidden;
631
+ pointer-events: none;
632
+ transition: opacity .08s ease, transform .08s ease, visibility .08s;
633
  }
634
 
635
+ .tooltip:hover::after, .tooltip:focus-visible::after {
636
+ opacity: 1;
637
+ visibility: visible;
638
+ transform: translateX(-50%) translateY(0);
639
  }