leaderboard / frontend /header.html
Alyafeai's picture
add arabic support
9efd00d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Header - QIMMA Leaderboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>
tailwind.config = { darkMode: 'class', theme: { extend: { colors: { darkbg: '#131825', darkcard: '#1c2235' } } } }
</script>
</head>
<body class="bg-slate-50 text-slate-800 dark:bg-darkbg dark:text-slate-100">
<style>
:root { --card-bg: #ffffff; }
.dark { --card-bg: #161828; }
html[dir="rtl"] #headerSection {
direction: rtl;
text-align: right;
}
html[dir="rtl"] #headerSection .stat-card {
flex-direction: row-reverse;
text-align: right;
}
</style>
<div id="headerSection">
<div class="text-center mb-10 pt-8">
<h1 class="text-4xl font-extrabold tracking-tight sm:text-6xl mb-4 inline-flex items-center justify-center gap-4">
<span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-violet-400 dark:from-purple-400 dark:to-violet-300">
QIMMA ⛰ قمة
<span class="block text-center" id="headerHeroLabel">Leaderboard</span>
</span>
</h1>
<p class="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto" id="headerHeroSubtitle">A quality-first Arabic LLM Leaderboard that evaluates and compares the performance of Arabic Large Language Models.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-12">
<!-- Podium — NO class changes, inline style preserved for light mode -->
<div id="podium-card" class="rounded-xl p-6 shadow-sm text-white relative overflow-hidden group col-span-1 sm:col-span-2 lg:col-span-1 transition-all duration-300 ease-in-out transform hover:-translate-y-1 cursor-default"
style="background: linear-gradient(to bottom, #2d7d65, #1f604f);">
<div class="absolute -right-6 -top-6 w-[86px] h-[86px] rounded-full opacity-50 group-hover:scale-[2.5] transition-transform duration-500"
style="background-color: #174d3e;"></div>
<div class="flex justify-between items-start mb-4 relative z-10">
<p id="podium-label" class="text-sm font-medium text-white/80 uppercase tracking-wider">Podium</p>
<i data-lucide="trophy" class="w-5 h-5 text-yellow-300"></i>
</div>
<div id="top-performers-list" class="space-y-3 relative z-10 overflow-visible"></div>
</div>
<!-- Total Models -->
<div id="stat-card-models" class="stat-card p-4 rounded-2xl border border-slate-300 dark:border-slate-600 shadow-md flex items-center gap-4 transition-all duration-300 ease-in-out transform hover:-translate-y-1 cursor-default"
style="background-color: var(--card-bg);">
<div id="icon-wrap-models" class="p-3 bg-indigo-50 dark:bg-indigo-900/30 rounded-xl border border-indigo-200 dark:border-indigo-700">
<i data-lucide="database" class="w-6 h-6 text-indigo-600 dark:text-indigo-400"></i>
</div>
<div>
<p class="text-xs font-bold uppercase text-slate-400 tracking-wider" id="label-total-models">Total Models</p>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white" id="stat-total-models">--</h3>
</div>
</div>
<!-- Eval Status -->
<div id="stat-card-eval" class="stat-card p-4 rounded-2xl border border-slate-300 dark:border-slate-600 shadow-md flex items-center gap-4 transition-all duration-300 ease-in-out transform hover:-translate-y-1 cursor-default"
style="background-color: var(--card-bg);">
<div id="icon-wrap-eval" class="p-3 bg-emerald-50 dark:bg-emerald-900/30 rounded-xl border border-emerald-200 dark:border-emerald-700 shrink-0">
<i data-lucide="activity" class="w-6 h-6 text-emerald-600 dark:text-emerald-400"></i>
</div>
<div class="w-full">
<p class="text-xs font-bold uppercase text-slate-400 tracking-wider mb-2" id="label-eval-status">Eval Status</p>
<div class="grid grid-cols-4 gap-1 text-center mb-2">
<div>
<div class="text-base font-bold text-emerald-600 dark:text-emerald-400" id="stat-done">--</div>
<div class="text-[10px] text-slate-500 dark:text-slate-400" id="sublabel-done">Done</div>
</div>
<div>
<div class="text-base font-bold text-rose-600 dark:text-rose-400" id="stat-failed">--</div>
<div class="text-[10px] text-slate-500 dark:text-slate-400" id="sublabel-failed">Failed</div>
</div>
<div>
<div class="text-base font-bold text-blue-600 dark:text-blue-400" id="stat-running">--</div>
<div class="text-[10px] text-slate-500 dark:text-slate-400" id="sublabel-running">Running</div>
</div>
<div>
<div class="text-base font-bold text-amber-500 dark:text-amber-400" id="stat-queue">--</div>
<div class="text-[10px] text-slate-500 dark:text-slate-400" id="sublabel-queue">Queue</div>
</div>
</div>
<div id="progress-track" class="flex w-full h-2 rounded-full overflow-hidden bg-slate-100 dark:bg-slate-700">
<div id="bar-done" class="bg-emerald-500 h-full transition-all duration-500" style="width: 0%"></div>
<div id="bar-failed" class="bg-rose-500 h-full transition-all duration-500" style="width: 0%"></div>
<div id="bar-running" class="bg-blue-500 h-full transition-all duration-500" style="width: 0%"></div>
<div id="bar-queue" class="bg-amber-500 h-full transition-all duration-500" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Benchmarks -->
<div id="stat-card-bench" class="stat-card p-4 rounded-2xl border border-slate-300 dark:border-slate-600 shadow-md flex items-center gap-4 transition-all duration-300 ease-in-out transform hover:-translate-y-1 cursor-default"
style="background-color: var(--card-bg);">
<div id="icon-wrap-bench" class="p-3 bg-purple-50 dark:bg-purple-900/30 rounded-xl border border-purple-200 dark:border-purple-700">
<i data-lucide="bar-chart-2" class="w-6 h-6 text-purple-600 dark:text-purple-400"></i>
</div>
<div>
<p class="text-xs font-bold uppercase text-slate-400 tracking-wider" id="label-benchmarks">Benchmarks</p>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white" id="stat-metrics">--</h3>
</div>
</div>
</div>
</div>
<script>
window.updateHeaderStats = function (safeQ) {
if (!safeQ) return;
const doneCount = allData ? allData.length : safeQ.finished.length;
const failedCount = safeQ.failed ? safeQ.failed.length : 0;
const runningCount = safeQ.running ? safeQ.running.length : 0;
const queueCount = safeQ.pending ? safeQ.pending.length : 0;
const setTxt = (id, val) => { if (document.getElementById(id)) document.getElementById(id).innerText = val; };
setTxt('stat-done', doneCount);
setTxt('stat-failed', failedCount);
setTxt('stat-running', runningCount);
setTxt('stat-queue', queueCount);
const total = doneCount + failedCount + runningCount + queueCount;
if (total > 0) {
document.getElementById('bar-done').style.width = (doneCount / total * 100) + "%";
document.getElementById('bar-failed').style.width = (failedCount / total * 100) + "%";
document.getElementById('bar-running').style.width = (runningCount / total * 100) + "%";
document.getElementById('bar-queue').style.width = (queueCount / total * 100) + "%";
}
};
// All dark mode styling via IDs — no class selectors, no CSS specificity battles
function applyDarkModeCards() {
const isDark = document.documentElement.classList.contains('dark');
// Podium
const podium = document.getElementById('podium-card');
if (podium) {
podium.style.background = isDark ? '#1a3028' : 'linear-gradient(to bottom, #2d7d65, #1f604f)';
podium.style.border = isDark ? '1px solid #2d5a3d' : '';
}
const podiumLabel = document.getElementById('podium-label');
if (podiumLabel) podiumLabel.style.color = isDark ? '#5aaf7a' : '';
// Stat cards
['stat-card-models', 'stat-card-eval', 'stat-card-bench'].forEach(id => {
const el = document.getElementById(id);
if (!el) return;
el.style.backgroundColor = isDark ? '#161828' : '';
el.style.border = isDark ? '1px solid #252048' : '';
});
// Icon wrappers
['icon-wrap-models', 'icon-wrap-eval', 'icon-wrap-bench'].forEach(id => {
const el = document.getElementById(id);
if (!el) return;
el.style.background = isDark ? '#1e1a40' : '';
el.style.borderColor = isDark ? 'transparent' : '';
const icon = el.querySelector('i');
if (icon) icon.style.color = isDark ? '#7868c8' : '';
});
// Stat labels
['label-total-models', 'label-eval-status', 'label-benchmarks'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.color = isDark ? '#7878a8' : '';
});
// Stat big values
['stat-total-models', 'stat-metrics'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.color = isDark ? '#d0cef0' : '';
});
// Eval sublabels
['sublabel-done', 'sublabel-failed', 'sublabel-running', 'sublabel-queue'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.color = isDark ? '#585878' : '';
});
// Running + Queue numbers muted
['stat-running', 'stat-queue'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.color = isDark ? '#7878a8' : '';
});
// Progress bar track
const track = document.getElementById('progress-track');
if (track) track.style.background = isDark ? '#1e1a40' : '';
// Progress bar segments
const bars = { 'bar-done': '#3a5a8a', 'bar-failed': '#7a3a3a', 'bar-running': '#2e2468', 'bar-queue': '#3a3010' };
Object.entries(bars).forEach(([id, color]) => {
const el = document.getElementById(id);
if (el) el.style.backgroundColor = isDark ? color : '';
});
// Top accent lines via box-shadow trick (no ::before in JS)
const podiumAccent = document.getElementById('podium-card');
if (podiumAccent) {
podiumAccent.style.boxShadow = isDark ? 'inset 0 2px 0 0 #4a8a5a' : '';
}
['stat-card-models', 'stat-card-eval', 'stat-card-bench'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.boxShadow = isDark ? 'inset 0 2px 0 0 #5040a0' : '';
});
}
function renderHeaderTableStats(data) {
const $ = (selector) => document.querySelector(selector);
const esc = (v) => String(v ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#039;");
if ($('#stat-total-models')) $('#stat-total-models').innerText = data.length;
if ($('#stat-metrics') && window.EVAL_COLUMNS) $('#stat-metrics').innerText = window.EVAL_COLUMNS.filter(c => c !== "Average").length;
const avgK = Object.keys(data[0] || {}).find(k => k.includes("Average"));
if (avgK && $('#top-performers-list')) {
const top3 = [...data].sort((a, b) => parseFloat(b[avgK]) - parseFloat(a[avgK])).slice(0, 3);
const isDark = document.documentElement.classList.contains('dark');
const rankBadgeStyles = isDark ? [
'background:#1a3a22;color:#7ad898;box-shadow:0 0 0 1px #2e5a38',
'background:#162e1e;color:#5ab878;box-shadow:0 0 0 1px #244830',
'background:#1a2a1a;color:#4aa858;box-shadow:0 0 0 1px #203820',
] : [
'background:#facc15;color:#064e3b',
'background:#cbd5e1;color:#064e3b',
'background:#fdba74;color:#064e3b',
];
$('#top-performers-list').innerHTML = top3.map((m, i) => `
<div class="group/podium relative py-0.5">
<div class="flex items-center gap-3">
<div class="w-4 h-5 shrink-0 rounded-full flex items-center justify-center text-xs font-bold" style="${rankBadgeStyles[i] || ''}">${i + 1}</div>
<span class="text-sm font-semibold truncate flex-1 min-w-0 cursor-pointer hover:underline" style="${isDark ? 'color:#b8d8c0' : ''}" onclick="window.openModelDetails && window.openModelDetails(decodeURIComponent('${encodeURIComponent(m["Model Name"])}'))">${esc(m["Model Name"].split('/').pop())}</span>
<span class="ml-auto text-xs font-mono" style="${isDark ? 'color:#6aba82' : 'color:rgb(110 231 183)'}">${parseFloat(m[avgK]).toFixed(1)}</span>
</div>
<div class="absolute left-7 top-full mt-1 z-30 pointer-events-none opacity-0 translate-y-1 group-hover/podium:opacity-100 group-hover/podium:translate-y-0 transition-all duration-200 ease-out">
<div class="inline-block w-max max-w-[34rem] rounded-md border border-emerald-300/60 bg-emerald-950/90 backdrop-blur px-2 py-1 text-[11px] text-emerald-100 break-words shadow-lg">
${esc(m["Model Name"])}
</div>
</div>
</div>
`).join('');
}
// Re-apply dark mode after DOM update
applyDarkModeCards();
}
const HEADER_TRANSLATIONS = {
ar: {
hero_label: "لوحة الصدارة",
hero_subtitle: "لوحة صدارة عربية للنماذج اللغوية الكبرى تركّز على الجودة، وتقيّم وتقارن أداء النماذج اللغوية العربية.",
podium: "الصدارة",
total_models: "إجمالي النماذج",
eval_status: "حالة التقييم",
done: "مكتمل",
failed: "فشل",
running: "قيد التنفيذ",
queue: "الانتظار",
benchmarks: "المعايير",
},
en: {
hero_label: "Leaderboard",
hero_subtitle: "A quality-first Arabic LLM Leaderboard that evaluates and compares the performance of Arabic Large Language Models.",
podium: "Podium",
total_models: "Total Models",
eval_status: "Eval Status",
done: "Done",
failed: "Failed",
running: "Running",
queue: "Queue",
benchmarks: "Benchmarks",
}
};
function applyHeaderTranslations(lang) {
const t = HEADER_TRANSLATIONS[lang] || HEADER_TRANSLATIONS.en;
const setTxt = (id, val) => {
const el = document.getElementById(id);
if (el) el.innerText = val;
};
setTxt('headerHeroLabel', t.hero_label);
setTxt('headerHeroSubtitle', t.hero_subtitle);
setTxt('podium-label', t.podium);
setTxt('label-total-models', t.total_models);
setTxt('label-eval-status', t.eval_status);
setTxt('sublabel-done', t.done);
setTxt('sublabel-failed', t.failed);
setTxt('sublabel-running', t.running);
setTxt('sublabel-queue', t.queue);
setTxt('label-benchmarks', t.benchmarks);
}
applyHeaderTranslations(window.getCurrentLanguage ? window.getCurrentLanguage() : 'en');
if (window.registerLanguageListener) {
window.registerLanguageListener(applyHeaderTranslations);
}
window.initHeader = async function () { renderHeaderTableStats(allData); };
window.initHeader();
if (window.lucide) lucide.createIcons();
// Apply immediately and watch for theme toggles
applyDarkModeCards();
new MutationObserver(applyDarkModeCards).observe(
document.documentElement, { attributes: true, attributeFilter: ['class'] }
);
</script>
</body>
</html>