From 42e83d32e319e1c01506e338188466669eb71e44 Mon Sep 17 00:00:00 2001
From: Farhan Helmy <59960562+farhan-helmy@users.noreply.github.com>
Date: Wed, 19 Aug 2026 13:05:11 +0800
Subject: [PATCH] feat: add Malay BFCL benchmark dashboard
---
.github/workflows/pages.yml | 55 +++
.gitignore | 1 +
README.md | 3 +
site/app.js | 208 +++++++++
site/build.py | 143 ++++++
site/favicon.svg | 9 +
site/index.html | 175 ++++++++
site/styles.css | 874 ++++++++++++++++++++++++++++++++++++
site/tests/test_browser.py | 92 ++++
site/tests/test_build.py | 137 ++++++
10 files changed, 1697 insertions(+)
create mode 100644 .github/workflows/pages.yml
create mode 100644 site/app.js
create mode 100644 site/build.py
create mode 100644 site/favicon.svg
create mode 100644 site/index.html
create mode 100644 site/styles.css
create mode 100644 site/tests/test_browser.py
create mode 100644 site/tests/test_build.py
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..7232176
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,55 @@
+name: Deploy benchmark dashboard
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "site/**"
+ - "bfcl-malay-bench/results/comparison.json"
+ - ".github/workflows/pages.yml"
+ pull_request:
+ paths:
+ - "site/**"
+ - "bfcl-malay-bench/results/comparison.json"
+ - ".github/workflows/pages.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+ - name: Test site build
+ run: python -m unittest discover -s site/tests -v
+ - name: Validate JavaScript
+ run: node --check site/app.js
+ - name: Build site
+ run: python site/build.py --output _site
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: _site
+
+ deploy:
+ if: github.event_name != 'pull_request'
+ needs: build
+ concurrency:
+ group: pages
+ cancel-in-progress: false
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/configure-pages@v5
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 0a040db..5e81d2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ build/
data/
runs/
private/
+_site/
diff --git a/README.md b/README.md
index 36797e9..385356e 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,9 @@
Public evaluation harnesses and reproducible result summaries from
[PixelSpaceAI](https://github.com/PixelSpaceAI).
+Explore the results on the
+[public benchmark dashboard](https://pixelspaceai.github.io/benchmark/).
+
## Benchmarks
- [Malay BFCL v3 tool-calling benchmark](bfcl-malay-bench/) — evaluate an
diff --git a/site/app.js b/site/app.js
new file mode 100644
index 0000000..db2408a
--- /dev/null
+++ b/site/app.js
@@ -0,0 +1,208 @@
+const DATA_URL = document.body.dataset.resultsUrl || "./data/comparison.json";
+
+const MODEL_COLORS = ["#c8ff61", "#6ee7b7", "#66d8e5", "#ffcb66"];
+const CATEGORY_COPY = {
+ simple: {
+ label: "Simple",
+ title: "Simple tool calls",
+ description: "One function, one precise set of arguments.",
+ },
+ multiple: {
+ label: "Multiple",
+ title: "Multiple choices",
+ description: "Choose the correct function from several available tools.",
+ },
+ irrelevance: {
+ label: "Irrelevance",
+ title: "Tool restraint",
+ description: "Recognize when no available function should be called.",
+ },
+ chatable: {
+ label: "Chatable",
+ title: "Conversation",
+ description: "Reply naturally when the prompt needs text, not a tool call.",
+ },
+};
+
+const percent = (value) => `${(value * 100).toFixed(2)}%`;
+const wholePercent = (value) => `${(value * 100).toFixed(2).replace(/\.00$/, "")}%`;
+let categoryAnimationFrame;
+
+function element(tag, className, text) {
+ const node = document.createElement(tag);
+ if (className) node.className = className;
+ if (text !== undefined) node.textContent = text;
+ return node;
+}
+
+function modelLabel(result) {
+ const label = element("div", "model-label");
+ label.append(element("strong", "", result.model));
+ label.append(element("span", "", `Rank ${result.rank}`));
+ return label;
+}
+
+function barTrack(value, color, label) {
+ const track = element("div", "bar-track");
+ const fill = element("div", "bar-fill");
+ fill.style.setProperty("--bar-color", color);
+ fill.setAttribute("role", "img");
+ fill.setAttribute("aria-label", label);
+ fill.dataset.width = `${value * 100}%`;
+ track.append(fill);
+ return track;
+}
+
+function animateBars(container) {
+ container.querySelectorAll(".bar-fill").forEach((fill) => {
+ fill.style.width = fill.dataset.width;
+ });
+}
+
+function renderOverall(results) {
+ const chart = document.querySelector("#overall-chart");
+ chart.replaceChildren();
+
+ results.forEach((result, index) => {
+ const row = element("div", "leaderboard-row");
+ row.append(modelLabel(result));
+ row.append(
+ barTrack(
+ result.accuracy,
+ MODEL_COLORS[index % MODEL_COLORS.length],
+ `${result.model}: ${percent(result.accuracy)} overall accuracy`,
+ ),
+ );
+ row.append(element("div", "bar-value", percent(result.accuracy)));
+ chart.append(row);
+ });
+ requestAnimationFrame(() => animateBars(chart));
+}
+
+function renderCategory(results, colorIndexByModelId, category) {
+ const copy = CATEGORY_COPY[category];
+ document.querySelector("#category-label").textContent = copy.label;
+ document.querySelector("#category-title").textContent = copy.title;
+ document.querySelector("#category-description").textContent = copy.description;
+
+ document.querySelectorAll(".category-button").forEach((button) => {
+ button.setAttribute("aria-pressed", String(button.dataset.category === category));
+ });
+
+ const chart = document.querySelector("#category-chart");
+ chart.replaceChildren();
+ const categoryResults = [...results].sort(
+ (first, second) => second.by_category[category].accuracy - first.by_category[category].accuracy,
+ );
+
+ categoryResults.forEach((result) => {
+ const detail = result.by_category[category];
+ const colorIndex = colorIndexByModelId.get(result.model_id);
+ const row = element("div", "category-row");
+ row.append(modelLabel(result));
+ row.append(
+ barTrack(
+ detail.accuracy,
+ MODEL_COLORS[colorIndex % MODEL_COLORS.length],
+ `${result.model}: ${percent(detail.accuracy)} in ${copy.label}`,
+ ),
+ );
+ const score = element("div", "category-detail");
+ score.append(element("strong", "", wholePercent(detail.accuracy)));
+ score.append(element("span", "", `${detail.passed} / ${detail.scored}`));
+ row.append(score);
+ chart.append(row);
+ });
+ cancelAnimationFrame(categoryAnimationFrame);
+ categoryAnimationFrame = requestAnimationFrame(() => animateBars(chart));
+}
+
+function renderControls(results, colorIndexByModelId, categories, selectedCategory) {
+ const controls = document.querySelector("#category-controls");
+ controls.replaceChildren();
+ categories.forEach((category, index) => {
+ const button = element("button", "category-button", CATEGORY_COPY[category]?.label ?? category);
+ button.type = "button";
+ button.dataset.category = category;
+ button.setAttribute("aria-pressed", String(category === selectedCategory));
+ button.addEventListener("click", () => {
+ const url = new URL(window.location);
+ url.searchParams.set("category", category);
+ window.history.replaceState({}, "", url);
+ renderCategory(results, colorIndexByModelId, category);
+ });
+ controls.append(button);
+ });
+}
+
+function scoreCell(detail) {
+ return element("td", "", `${detail.passed}/${detail.scored} · ${wholePercent(detail.accuracy)}`);
+}
+
+function renderTable(results) {
+ const body = document.querySelector("#results-table-body");
+ body.replaceChildren();
+
+ results.forEach((result) => {
+ const row = document.createElement("tr");
+ const rank = document.createElement("td");
+ rank.append(element("span", "rank-badge", String(result.rank)));
+ row.append(rank);
+ row.append(element("td", "", result.model));
+ row.append(element("td", "", `${result.passed}/${result.scored} · ${wholePercent(result.accuracy)}`));
+ row.append(scoreCell(result.by_category.simple));
+ row.append(scoreCell(result.by_category.multiple));
+ row.append(scoreCell(result.by_category.irrelevance));
+ row.append(scoreCell(result.by_category.chatable));
+ body.append(row);
+ });
+}
+
+function renderSummary(data) {
+ const [leader, runnerUp] = data.results;
+ const multipleLeader = [...data.results].sort(
+ (first, second) => second.by_category.multiple.accuracy - first.by_category.multiple.accuracy,
+ )[0];
+ const margin = (leader.accuracy - runnerUp.accuracy) * 100;
+ document.querySelector("#leading-score").textContent = percent(leader.accuracy);
+ document.querySelector("#leading-model").textContent = leader.model;
+ document.querySelector("#lead-margin").textContent = `${margin.toFixed(2)} points ahead of ${runnerUp.model}`;
+ document.querySelector("#leader-insight").textContent =
+ `${leader.model} leads overall by ${margin.toFixed(2)} points, while ${multipleLeader.model} wins the multiple-tool category.`;
+ document.querySelector("#case-count").textContent = data.total_cases.toLocaleString("en-US");
+ document.querySelector("#model-count").textContent = String(data.results.length);
+ document.querySelector("#category-count").textContent = String(data.categories.length);
+ document.querySelector("#dataset-revision").textContent = `Pinned at ${data.dataset_revision.slice(0, 10)}`;
+}
+
+function showLoadError(error) {
+ const chart = document.querySelector("#overall-chart");
+ chart.replaceChildren(
+ element("p", "error-state", "The benchmark data could not be loaded. Refresh or view the source results on GitHub."),
+ );
+ console.error("Benchmark data load failed", error);
+}
+
+async function initialise() {
+ try {
+ const response = await fetch(DATA_URL);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ const data = await response.json();
+ const colorIndexByModelId = new Map(
+ data.results.map((result, index) => [result.model_id, index]),
+ );
+ const requestedCategory = new URLSearchParams(window.location.search).get("category");
+ const selectedCategory = data.categories.includes(requestedCategory)
+ ? requestedCategory
+ : data.categories[0];
+ renderSummary(data);
+ renderOverall(data.results);
+ renderControls(data.results, colorIndexByModelId, data.categories, selectedCategory);
+ renderCategory(data.results, colorIndexByModelId, selectedCategory);
+ renderTable(data.results);
+ } catch (error) {
+ showLoadError(error);
+ }
+}
+
+initialise();
diff --git a/site/build.py b/site/build.py
new file mode 100644
index 0000000..6054c04
--- /dev/null
+++ b/site/build.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+"""Build the dependency-free benchmark dashboard for GitHub Pages."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import math
+from pathlib import Path
+import shutil
+
+
+ASSET_NAMES = ("index.html", "styles.css", "app.js", "favicon.svg")
+RESULTS_PATH = Path("bfcl-malay-bench/results/comparison.json")
+BUILD_MARKER = ".pixelspace-benchmark-site"
+SUPPORTED_CATEGORIES = ("simple", "multiple", "irrelevance", "chatable")
+
+
+def _require_non_empty_string(value: object, field: str) -> None:
+ if not isinstance(value, str) or not value.strip():
+ raise ValueError(f"comparison {field} must be a non-empty string")
+
+
+def _require_count(value: object, field: str) -> None:
+ if not isinstance(value, int) or isinstance(value, bool) or value < 0:
+ raise ValueError(f"comparison {field} must be a non-negative integer")
+
+
+def _validate_score(score: dict, field: str) -> None:
+ if not isinstance(score, dict):
+ raise ValueError(f"comparison {field} must be an object")
+ for key in ("passed", "scored"):
+ if key not in score:
+ raise ValueError(f"comparison {field} is missing {key}")
+ _require_count(score[key], f"{field}.{key}")
+ if score["scored"] == 0 or score["passed"] > score["scored"]:
+ raise ValueError(f"comparison {field} has invalid counts")
+ accuracy = score.get("accuracy")
+ if not isinstance(accuracy, (int, float)) or isinstance(accuracy, bool):
+ raise ValueError(f"comparison {field}.accuracy must be numeric")
+ if not math.isfinite(accuracy) or not 0 <= accuracy <= 1:
+ raise ValueError(f"comparison {field}.accuracy must be between zero and one")
+ expected_accuracy = score["passed"] / score["scored"]
+ if not math.isclose(accuracy, expected_accuracy, rel_tol=0, abs_tol=1e-12):
+ raise ValueError(f"comparison {field}.accuracy does not match its counts")
+
+
+def _validate_results(data: dict) -> None:
+ required = {
+ "benchmark",
+ "categories",
+ "dataset",
+ "dataset_revision",
+ "results",
+ "total_cases",
+ }
+ missing = sorted(required.difference(data))
+ if missing:
+ raise ValueError(f"comparison data is missing: {', '.join(missing)}")
+ if len(data["results"]) < 2:
+ raise ValueError("comparison data must contain at least two models")
+ if tuple(data["categories"]) != SUPPORTED_CATEGORIES:
+ raise ValueError("comparison categories do not match the dashboard contract")
+ _require_count(data["total_cases"], "total_cases")
+ if data["total_cases"] == 0:
+ raise ValueError("comparison total_cases must be positive")
+
+ expected_ranks = list(range(1, len(data["results"]) + 1))
+ ranks = [result.get("rank") for result in data["results"]]
+ if ranks != expected_ranks:
+ raise ValueError("comparison results must be ordered by consecutive rank")
+ model_ids = set()
+ previous_accuracy = math.inf
+ for index, result in enumerate(data["results"]):
+ if not isinstance(result, dict):
+ raise ValueError("comparison results must contain objects")
+ for key in ("model", "model_id"):
+ _require_non_empty_string(result.get(key), f"results[{index}].{key}")
+ if result["model_id"] in model_ids:
+ raise ValueError("comparison model_id values must be unique")
+ model_ids.add(result["model_id"])
+
+ _validate_score(result, f"results[{index}]")
+ _require_count(result.get("errors"), f"results[{index}].errors")
+ if result["scored"] != data["total_cases"]:
+ raise ValueError(f"{result['model']} scored count does not match total_cases")
+ if result["accuracy"] > previous_accuracy:
+ raise ValueError("comparison results must be ordered by descending accuracy")
+ previous_accuracy = result["accuracy"]
+
+ by_category = result.get("by_category")
+ if not isinstance(by_category, dict) or not all(
+ category in by_category for category in SUPPORTED_CATEGORIES
+ ):
+ raise ValueError(f"{result.get('model', 'model')} is missing category results")
+ for category in SUPPORTED_CATEGORIES:
+ _validate_score(by_category[category], f"results[{index}].by_category.{category}")
+ if sum(by_category[category]["passed"] for category in SUPPORTED_CATEGORIES) != result["passed"]:
+ raise ValueError(f"{result['model']} category passed counts do not match overall")
+ if sum(by_category[category]["scored"] for category in SUPPORTED_CATEGORIES) != result["scored"]:
+ raise ValueError(f"{result['model']} category scored counts do not match overall")
+
+
+def build_site(repository_root: Path, output: Path) -> None:
+ repository_root = repository_root.resolve()
+ source = repository_root / "site"
+ if output.is_symlink():
+ raise ValueError("site output must not be a symbolic link")
+ output = output.absolute()
+
+ if output in {repository_root, source}:
+ raise ValueError("site output must not replace the repository or source directory")
+
+ data = json.loads((repository_root / RESULTS_PATH).read_text(encoding="utf-8"))
+ _validate_results(data)
+
+ if output.exists():
+ if not output.is_dir() or not (output / BUILD_MARKER).is_file():
+ raise ValueError("refusing to replace an output directory not created by this builder")
+ shutil.rmtree(output)
+ (output / "data").mkdir(parents=True)
+
+ for asset_name in ASSET_NAMES:
+ shutil.copy2(source / asset_name, output / asset_name)
+
+ (output / "data" / "comparison.json").write_text(
+ json.dumps(data, ensure_ascii=False, indent=2) + "\n",
+ encoding="utf-8",
+ )
+ (output / ".nojekyll").touch()
+ (output / BUILD_MARKER).touch()
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--output", type=Path, default=Path("_site"))
+ args = parser.parse_args()
+ repository_root = Path(__file__).resolve().parent.parent
+ build_site(repository_root, args.output)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/site/favicon.svg b/site/favicon.svg
new file mode 100644
index 0000000..0df2df1
--- /dev/null
+++ b/site/favicon.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..038e104
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+ Malay BFCL Benchmark | PixelSpaceAI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open benchmark · August 2026
+
Tool calling,measured in Malay.
+
+ A focused look at how four language models understand Malay prompts,
+ function descriptions, and parameter descriptions across BFCL v3.
+
+
+
+
+
+
+ Leading score
+ --
+ Loading results…
+
+ Across the focused suite
+
+
+
+
+
+
1,040 test cases
+
4 models compared
+
4 Malay-focused categories
+
0 tools executed
+
+
+
+
+
+
+
01 / Overall
+
The leaderboard
+
+
Exact local scoring over the same 1,040 cases. Higher is better.
+
+
+
+
+ 0% 25% 50% 75% 100%
+
+
+
Loading benchmark results…
+
+
+
+
+
+ Top-line takeaway
+ Loading the closest race…
+
+
+ What this measures
+ Tool choice, arguments, restraint, and conversational replies.
+
+
+
+
+
+
+
+
02 / Categories
+
Where models differ
+
+
Choose a category to compare the same models on a narrower skill.
+
+
+
+
+
+
+
+
+
+
+
03 / Exact results
+
Every score
+
+
Passed cases over scored cases. No hidden weighting.
+
+
+
+
+
+
+ Rank
+ Model
+ Overall
+ Simple
+ Multiple
+ Irrelevance
+ Chatable
+
+
+
+
+
+
+
+
+
+
Method note
+
A practical harness score, not an official BFCL submission.
+
+
+
+ The focused suite covers simple , multiple ,
+ irrelevance , and chatable . It scores the
+ first assistant response and never executes benchmark tools.
+
+
+ Results compare full serving paths. ILMU used a direct OpenAI-compatible
+ endpoint; the other deployments used the Pixel Harness command adapter.
+
+
Read the full methodology →
+
+
+
+
+
+
+
diff --git a/site/styles.css b/site/styles.css
new file mode 100644
index 0000000..47d48c2
--- /dev/null
+++ b/site/styles.css
@@ -0,0 +1,874 @@
+:root {
+ color-scheme: dark;
+ --ink: #f4f2e9;
+ --muted: #9ca79f;
+ --faint: #647068;
+ --line: rgba(255, 255, 255, 0.1);
+ --panel: rgba(14, 29, 23, 0.72);
+ --base: #07130f;
+ --acid: #c8ff61;
+ --acid-soft: #8fcf65;
+ --mint: #6ee7b7;
+ --cyan: #66d8e5;
+ --amber: #ffcb66;
+ --shell: min(1180px, calc(100vw - 48px));
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ font-synthesis: none;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ color: var(--ink);
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ var(--base);
+ background-size: 72px 72px;
+ min-width: 320px;
+ overflow-x: hidden;
+}
+
+a {
+ color: inherit;
+}
+
+button,
+a {
+ -webkit-tap-highlight-color: transparent;
+}
+
+.shell {
+ width: var(--shell);
+ margin-inline: auto;
+}
+
+.ambient {
+ position: fixed;
+ z-index: -1;
+ width: 520px;
+ height: 520px;
+ border-radius: 50%;
+ filter: blur(120px);
+ opacity: 0.11;
+ pointer-events: none;
+}
+
+.ambient-one {
+ top: -260px;
+ right: -120px;
+ background: var(--acid);
+}
+
+.ambient-two {
+ top: 50vh;
+ left: -340px;
+ background: #20c997;
+}
+
+.site-header {
+ height: 92px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ border-bottom: 1px solid var(--line);
+}
+
+.brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ color: var(--ink);
+ text-decoration: none;
+ font-size: 17px;
+ font-weight: 720;
+ letter-spacing: -0.03em;
+}
+
+.brand > span:last-child > span {
+ color: var(--acid);
+}
+
+.brand-mark {
+ width: 27px;
+ height: 27px;
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 3px;
+ transform: rotate(7deg);
+}
+
+.brand-mark i {
+ display: block;
+ background: var(--acid);
+}
+
+.brand-mark i:nth-child(2),
+.brand-mark i:nth-child(3) {
+ border-radius: 50%;
+}
+
+.header-link {
+ color: var(--muted);
+ text-decoration: none;
+ font-size: 13px;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ transition: color 180ms ease;
+}
+
+.header-link:hover,
+.header-link:focus-visible {
+ color: var(--acid);
+}
+
+.hero {
+ min-height: 680px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 350px;
+ gap: 10vw;
+ align-items: center;
+ padding-block: 88px 100px;
+}
+
+.eyebrow,
+.section-number,
+.score-kicker,
+.category-panel-header p {
+ margin: 0;
+ color: var(--acid);
+ font-size: 11px;
+ font-weight: 750;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+}
+
+.eyebrow {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.eyebrow span {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--acid);
+ box-shadow: 0 0 18px var(--acid);
+}
+
+h1,
+h2,
+h3,
+p {
+ text-wrap: pretty;
+}
+
+h1 {
+ margin: 24px 0 28px;
+ max-width: 800px;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(62px, 8.2vw, 116px);
+ font-weight: 400;
+ line-height: 0.86;
+ letter-spacing: -0.07em;
+}
+
+h1 em {
+ color: var(--acid);
+ font-weight: 400;
+}
+
+.hero-lede {
+ max-width: 640px;
+ margin: 0;
+ color: var(--muted);
+ font-size: clamp(17px, 1.8vw, 21px);
+ line-height: 1.65;
+}
+
+.hero-actions {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-top: 38px;
+}
+
+.button {
+ min-height: 50px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 22px;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ text-decoration: none;
+ font-size: 13px;
+ font-weight: 750;
+ letter-spacing: 0.02em;
+ transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
+}
+
+.button:hover,
+.button:focus-visible {
+ transform: translateY(-2px);
+}
+
+.button-primary {
+ color: #0a170f;
+ background: var(--acid);
+ border-color: var(--acid);
+}
+
+.button-secondary:hover,
+.button-secondary:focus-visible {
+ border-color: rgba(200, 255, 97, 0.6);
+ background: rgba(200, 255, 97, 0.07);
+}
+
+.hero-score {
+ position: relative;
+ aspect-ratio: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 48px;
+ border: 1px solid rgba(200, 255, 97, 0.28);
+ border-radius: 50%;
+ background: radial-gradient(circle at 50% 38%, rgba(200, 255, 97, 0.1), transparent 58%);
+ text-align: center;
+}
+
+.score-orbit {
+ position: absolute;
+ inset: -15px;
+ border: 1px dashed rgba(200, 255, 97, 0.2);
+ border-radius: 50%;
+ animation: orbit 36s linear infinite;
+}
+
+.hero-score strong {
+ margin: 10px 0 4px;
+ color: var(--acid);
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(58px, 6vw, 84px);
+ font-weight: 400;
+ letter-spacing: -0.06em;
+}
+
+.hero-score > p:not(.score-kicker) {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 700;
+}
+
+.score-rule {
+ width: 44px;
+ height: 1px;
+ margin: 20px auto 14px;
+ background: rgba(255, 255, 255, 0.25);
+}
+
+.hero-score > span {
+ color: var(--muted);
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+@keyframes orbit {
+ to { transform: rotate(360deg); }
+}
+
+.stat-strip {
+ border-block: 1px solid var(--line);
+ background: rgba(255, 255, 255, 0.018);
+}
+
+.stat-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+}
+
+.stat-grid > div {
+ min-height: 144px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 28px;
+ border-left: 1px solid var(--line);
+}
+
+.stat-grid > div:last-child {
+ border-right: 1px solid var(--line);
+}
+
+.stat-grid strong {
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 38px;
+ font-weight: 400;
+ letter-spacing: -0.04em;
+}
+
+.stat-grid span {
+ margin-top: 7px;
+ color: var(--muted);
+ font-size: 12px;
+}
+
+.section,
+.method {
+ padding-block: 130px;
+}
+
+.section {
+ border-bottom: 1px solid var(--line);
+}
+
+.section-heading {
+ display: flex;
+ justify-content: space-between;
+ gap: 40px;
+ align-items: end;
+ margin-bottom: 56px;
+}
+
+.section-heading h2,
+.method h2 {
+ margin: 12px 0 0;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(44px, 5.5vw, 72px);
+ font-weight: 400;
+ line-height: 1;
+ letter-spacing: -0.055em;
+}
+
+.section-heading > p {
+ max-width: 360px;
+ margin: 0 0 4px;
+ color: var(--muted);
+ font-size: 14px;
+ line-height: 1.65;
+}
+
+.chart-panel,
+.category-panel,
+.table-wrap {
+ border: 1px solid var(--line);
+ border-radius: 20px;
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012));
+ box-shadow: 0 28px 90px rgba(0, 0, 0, 0.2);
+}
+
+.chart-panel {
+ padding: 28px 34px 34px;
+}
+
+.chart-scale {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ margin: 0 0 24px 250px;
+ color: var(--faint);
+ font-size: 10px;
+}
+
+.chart-scale span:not(:first-child) {
+ text-align: right;
+}
+
+.leaderboard-chart {
+ display: grid;
+ gap: 22px;
+}
+
+.leaderboard-row {
+ display: grid;
+ grid-template-columns: 220px 1fr 72px;
+ gap: 28px;
+ align-items: center;
+}
+
+.model-label {
+ min-width: 0;
+}
+
+.model-label strong {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 14px;
+}
+
+.model-label span {
+ display: block;
+ margin-top: 5px;
+ color: var(--faint);
+ font-size: 10px;
+ font-weight: 750;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.bar-track {
+ position: relative;
+ height: 34px;
+ border-radius: 4px;
+ overflow: hidden;
+ background:
+ linear-gradient(90deg, transparent calc(25% - 1px), rgba(255,255,255,.08) 25%, transparent calc(25% + 1px)),
+ linear-gradient(90deg, transparent calc(50% - 1px), rgba(255,255,255,.08) 50%, transparent calc(50% + 1px)),
+ linear-gradient(90deg, transparent calc(75% - 1px), rgba(255,255,255,.08) 75%, transparent calc(75% + 1px)),
+ rgba(255, 255, 255, 0.03);
+}
+
+.bar-fill {
+ width: 0;
+ height: 100%;
+ border-radius: 4px;
+ background: var(--bar-color, var(--acid));
+ box-shadow: 0 0 28px color-mix(in srgb, var(--bar-color, var(--acid)) 28%, transparent);
+ transition: width 720ms cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.bar-value {
+ text-align: right;
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 15px;
+ font-weight: 750;
+}
+
+.insight-grid {
+ display: grid;
+ grid-template-columns: 1.2fr 0.8fr;
+ gap: 16px;
+ margin-top: 16px;
+}
+
+.insight-card {
+ min-height: 190px;
+ padding: 30px;
+ border: 1px solid var(--line);
+ border-radius: 20px;
+}
+
+.insight-featured {
+ color: #0a170f;
+ background: var(--acid);
+ border-color: var(--acid);
+}
+
+.insight-card p {
+ margin: 0 0 24px;
+ color: var(--muted);
+ font-size: 11px;
+ font-weight: 750;
+ letter-spacing: 0.13em;
+ text-transform: uppercase;
+}
+
+.insight-featured p {
+ color: rgba(10, 23, 15, 0.58);
+}
+
+.insight-card h3 {
+ max-width: 650px;
+ margin: 0;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(25px, 3vw, 36px);
+ font-weight: 400;
+ line-height: 1.15;
+ letter-spacing: -0.04em;
+}
+
+.section-categories {
+ border-bottom: 0;
+}
+
+.category-controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 9px;
+ margin-bottom: 18px;
+}
+
+.category-button {
+ appearance: none;
+ padding: 10px 16px;
+ color: var(--muted);
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: transparent;
+ font: inherit;
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
+}
+
+.category-button:hover,
+.category-button:focus-visible {
+ color: var(--ink);
+ border-color: rgba(255, 255, 255, 0.25);
+}
+
+.category-button[aria-pressed="true"] {
+ color: #0a170f;
+ background: var(--acid);
+ border-color: var(--acid);
+}
+
+.category-panel {
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ min-height: 480px;
+ overflow: hidden;
+}
+
+.category-panel-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: 38px;
+ border-right: 1px solid var(--line);
+ background: rgba(255, 255, 255, 0.02);
+}
+
+.category-panel-header h3 {
+ margin: 12px 0 0;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 40px;
+ font-weight: 400;
+ line-height: 1.04;
+ letter-spacing: -0.05em;
+}
+
+.category-panel-header > p:last-child {
+ color: var(--muted);
+ font-size: 13px;
+ font-weight: 400;
+ line-height: 1.65;
+ letter-spacing: 0;
+ text-transform: none;
+}
+
+.category-chart {
+ display: grid;
+ align-content: center;
+ gap: 26px;
+ padding: 44px;
+}
+
+.category-row {
+ display: grid;
+ grid-template-columns: 170px 1fr 110px;
+ gap: 22px;
+ align-items: center;
+}
+
+.category-row .bar-track {
+ height: 18px;
+}
+
+.category-detail {
+ text-align: right;
+}
+
+.category-detail strong {
+ display: block;
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 14px;
+}
+
+.category-detail span {
+ color: var(--faint);
+ font-size: 10px;
+}
+
+.table-wrap {
+ overflow-x: auto;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ min-width: 850px;
+}
+
+th,
+td {
+ padding: 22px 20px;
+ border-bottom: 1px solid var(--line);
+ text-align: right;
+}
+
+th {
+ color: var(--faint);
+ font-size: 10px;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+th:nth-child(2),
+td:nth-child(2) {
+ text-align: left;
+}
+
+th:first-child,
+td:first-child {
+ width: 70px;
+ text-align: center;
+}
+
+td {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 12px;
+}
+
+td:nth-child(2) {
+ font-family: inherit;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+tbody tr:last-child td {
+ border-bottom: 0;
+}
+
+tbody tr:first-child td {
+ background: rgba(200, 255, 97, 0.035);
+}
+
+.rank-badge {
+ width: 26px;
+ height: 26px;
+ display: inline-grid;
+ place-items: center;
+ border: 1px solid var(--line);
+ border-radius: 50%;
+ color: var(--muted);
+}
+
+tbody tr:first-child .rank-badge {
+ color: #0a170f;
+ background: var(--acid);
+ border-color: var(--acid);
+}
+
+.method {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 12vw;
+}
+
+.method-copy {
+ padding-top: 22px;
+}
+
+.method-copy p {
+ margin: 0 0 22px;
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.75;
+}
+
+.method-copy strong {
+ color: var(--ink);
+}
+
+.method-copy a {
+ display: inline-block;
+ margin-top: 14px;
+ color: var(--acid);
+ text-decoration: none;
+ font-size: 13px;
+ font-weight: 750;
+}
+
+footer {
+ padding-block: 55px;
+ border-top: 1px solid var(--line);
+ background: rgba(0, 0, 0, 0.18);
+}
+
+.footer-grid {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 40px;
+}
+
+.footer-grid p {
+ margin: 14px 0 0;
+ color: var(--faint);
+ font-size: 12px;
+ line-height: 1.7;
+}
+
+.footer-grid > p {
+ text-align: right;
+}
+
+.footer-grid a:not(.brand) {
+ color: var(--muted);
+ text-decoration: none;
+}
+
+.loading-state,
+.error-state {
+ margin: 0;
+ padding: 40px;
+ color: var(--muted);
+ text-align: center;
+}
+
+.error-state {
+ color: #ff9f89;
+}
+
+:focus-visible {
+ outline: 2px solid var(--acid);
+ outline-offset: 4px;
+}
+
+@media (max-width: 900px) {
+ :root { --shell: min(100% - 32px, 720px); }
+
+ .hero {
+ grid-template-columns: 1fr;
+ min-height: auto;
+ gap: 72px;
+ padding-block: 72px 88px;
+ }
+
+ .hero-score {
+ width: min(350px, 86vw);
+ justify-self: center;
+ }
+
+ .stat-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .stat-grid > div:nth-child(3),
+ .stat-grid > div:nth-child(4) {
+ border-top: 1px solid var(--line);
+ }
+
+ .stat-grid > div:nth-child(4) {
+ border-right: 1px solid var(--line);
+ }
+
+ .section,
+ .method {
+ padding-block: 96px;
+ }
+
+ .leaderboard-row {
+ grid-template-columns: 160px 1fr 64px;
+ gap: 18px;
+ }
+
+ .chart-scale {
+ margin-left: 178px;
+ }
+
+ .category-panel,
+ .method {
+ grid-template-columns: 1fr;
+ }
+
+ .category-panel-header {
+ min-height: 240px;
+ border-right: 0;
+ border-bottom: 1px solid var(--line);
+ }
+}
+
+@media (max-width: 620px) {
+ .site-header { height: 76px; }
+ .header-link { font-size: 0; }
+ .header-link span { font-size: 18px; }
+
+ h1 {
+ font-size: clamp(52px, 16vw, 76px);
+ line-height: 0.9;
+ }
+
+ .hero-lede { font-size: 16px; }
+
+ .stat-grid strong { font-size: 30px; }
+ .stat-grid > div { min-height: 118px; padding: 20px 16px; }
+
+ .section-heading {
+ display: block;
+ margin-bottom: 40px;
+ }
+
+ .section-heading > p {
+ margin-top: 24px;
+ }
+
+ .chart-panel { padding: 22px 18px 26px; }
+ .chart-scale { display: none; }
+
+ .leaderboard-row {
+ grid-template-columns: 1fr 58px;
+ gap: 10px;
+ }
+
+ .leaderboard-row .model-label { grid-column: 1 / -1; }
+ .bar-track { height: 26px; }
+
+ .insight-grid { grid-template-columns: 1fr; }
+ .insight-card { min-height: 170px; }
+
+ .category-panel-header,
+ .category-chart { padding: 28px 22px; }
+
+ .category-row {
+ grid-template-columns: 1fr 76px;
+ gap: 9px 14px;
+ }
+
+ .category-row .model-label { grid-column: 1 / -1; }
+ .category-detail { grid-column: 2; grid-row: 2; }
+ .category-row .bar-track { grid-column: 1; grid-row: 2; }
+
+ .method h2 { font-size: 42px; }
+
+ .footer-grid {
+ display: block;
+ }
+
+ .footer-grid > p {
+ margin-top: 32px;
+ text-align: left;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .score-orbit {
+ animation: none;
+ }
+
+ .bar-fill {
+ transition: none;
+ }
+
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
diff --git a/site/tests/test_browser.py b/site/tests/test_browser.py
new file mode 100644
index 0000000..e00177b
--- /dev/null
+++ b/site/tests/test_browser.py
@@ -0,0 +1,92 @@
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+import threading
+import unittest
+
+from test_build import REPOSITORY_ROOT, load_build_module
+
+
+class QuietHandler(SimpleHTTPRequestHandler):
+ def log_message(self, format, *args):
+ pass
+
+
+class BrowserSmokeTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.chrome = next(
+ (
+ executable
+ for name in ("google-chrome", "chromium", "chromium-browser")
+ if (executable := shutil.which(name))
+ ),
+ None,
+ )
+ if cls.chrome is None:
+ raise RuntimeError("Chrome or Chromium is required for the dashboard smoke test")
+
+ def dump_page(self, url, user_data_directory):
+ completed = subprocess.run(
+ [
+ self.chrome,
+ "--headless=new",
+ "--no-sandbox",
+ "--disable-dev-shm-usage",
+ "--disable-gpu",
+ "--dump-dom",
+ "--virtual-time-budget=3000",
+ f"--user-data-dir={user_data_directory}",
+ url,
+ ],
+ capture_output=True,
+ check=True,
+ text=True,
+ timeout=20,
+ )
+ return completed.stdout
+
+ def test_built_dashboard_renders_data_category_and_error_state(self):
+ build = load_build_module()
+
+ with tempfile.TemporaryDirectory() as temporary_directory:
+ temporary_path = Path(temporary_directory)
+ output = temporary_path / "public"
+ build.build_site(REPOSITORY_ROOT, output)
+ handler = lambda *args, **kwargs: QuietHandler(
+ *args, directory=str(output), **kwargs
+ )
+ server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
+ thread.start()
+
+ try:
+ base_url = f"http://127.0.0.1:{server.server_port}"
+ rendered = self.dump_page(
+ f"{base_url}/?category=multiple", temporary_path / "chrome-success"
+ )
+ self.assertIn("76.54%", rendered)
+ self.assertIn("ILMU Mini v3.3", rendered)
+ self.assertIn("Multiple choices", rendered)
+ self.assertIn("130 / 200", rendered)
+ self.assertNotIn("Loading benchmark results", rendered)
+
+ index = output / "index.html"
+ index.write_text(
+ index.read_text(encoding="utf-8").replace(
+ './data/comparison.json', './data/missing.json', 1
+ ),
+ encoding="utf-8",
+ )
+ failed = self.dump_page(base_url, temporary_path / "chrome-failure")
+ self.assertIn("The benchmark data could not be loaded", failed)
+ finally:
+ server.shutdown()
+ server.server_close()
+ thread.join(timeout=5)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/site/tests/test_build.py b/site/tests/test_build.py
new file mode 100644
index 0000000..fc3bc8d
--- /dev/null
+++ b/site/tests/test_build.py
@@ -0,0 +1,137 @@
+import importlib.util
+import json
+from pathlib import Path
+import tempfile
+import unittest
+
+
+REPOSITORY_ROOT = Path(__file__).parents[2]
+BUILD_MODULE_PATH = REPOSITORY_ROOT / "site" / "build.py"
+
+
+def load_build_module():
+ spec = importlib.util.spec_from_file_location("benchmark_site_build", BUILD_MODULE_PATH)
+ module = importlib.util.module_from_spec(spec)
+ assert spec.loader is not None
+ spec.loader.exec_module(module)
+ return module
+
+
+class SiteBuildTests(unittest.TestCase):
+ def test_build_copies_assets_and_canonical_results(self):
+ build = load_build_module()
+
+ with tempfile.TemporaryDirectory() as temporary_directory:
+ output = Path(temporary_directory) / "public"
+ build.build_site(REPOSITORY_ROOT, output)
+
+ self.assertTrue((output / ".nojekyll").exists())
+ self.assertTrue((output / "index.html").exists())
+ self.assertTrue((output / "styles.css").exists())
+ self.assertTrue((output / "app.js").exists())
+ self.assertTrue((output / "favicon.svg").exists())
+
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ published_results = json.loads(
+ (output / "data" / "comparison.json").read_text(encoding="utf-8")
+ )
+ self.assertEqual(published_results, canonical_results)
+
+ page = (output / "index.html").read_text(encoding="utf-8")
+ self.assertIn('id="overall-chart"', page)
+ self.assertIn('id="category-controls"', page)
+ self.assertIn('id="results-table-body"', page)
+
+ def test_build_replaces_stale_output(self):
+ build = load_build_module()
+
+ with tempfile.TemporaryDirectory() as temporary_directory:
+ output = Path(temporary_directory) / "public"
+ output.mkdir()
+ (output / build.BUILD_MARKER).touch()
+ stale_file = output / "stale.txt"
+ stale_file.write_text("old", encoding="utf-8")
+
+ build.build_site(REPOSITORY_ROOT, output)
+
+ self.assertFalse(stale_file.exists())
+
+ def test_build_refuses_to_replace_unmarked_output(self):
+ build = load_build_module()
+
+ with tempfile.TemporaryDirectory() as temporary_directory:
+ output = Path(temporary_directory) / "public"
+ output.mkdir()
+ protected_file = output / "keep.txt"
+ protected_file.write_text("keep", encoding="utf-8")
+
+ with self.assertRaisesRegex(ValueError, "refusing to replace"):
+ build.build_site(REPOSITORY_ROOT, output)
+
+ self.assertEqual(protected_file.read_text(encoding="utf-8"), "keep")
+
+ def test_validation_rejects_dashboard_schema_drift(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ canonical_results["categories"] = ["simple"]
+
+ with self.assertRaisesRegex(ValueError, "dashboard contract"):
+ build._validate_results(canonical_results)
+
+ def test_validation_requires_comparison_between_models(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ canonical_results["results"] = canonical_results["results"][:1]
+
+ with self.assertRaisesRegex(ValueError, "at least two models"):
+ build._validate_results(canonical_results)
+
+ def test_validation_rejects_missing_required_key(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ del canonical_results["dataset"]
+
+ with self.assertRaisesRegex(ValueError, "missing: dataset"):
+ build._validate_results(canonical_results)
+
+ def test_validation_rejects_nonconsecutive_ranks(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ canonical_results["results"][1]["rank"] = 3
+
+ with self.assertRaisesRegex(ValueError, "consecutive rank"):
+ build._validate_results(canonical_results)
+
+ def test_validation_rejects_missing_result_category(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ del canonical_results["results"][0]["by_category"]["simple"]
+
+ with self.assertRaisesRegex(ValueError, "missing category results"):
+ build._validate_results(canonical_results)
+
+ def test_validation_rejects_inconsistent_accuracy(self):
+ build = load_build_module()
+ canonical_results = json.loads(
+ (REPOSITORY_ROOT / build.RESULTS_PATH).read_text(encoding="utf-8")
+ )
+ canonical_results["results"][0]["accuracy"] = 0.99
+
+ with self.assertRaisesRegex(ValueError, "does not match its counts"):
+ build._validate_results(canonical_results)
+
+
+if __name__ == "__main__":
+ unittest.main()