SimbaBench / public /index.html
elmadany's picture
Update public/index.html
2b8ba04 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Voice of a Continent | SimbaBench Leaderboard</title>
<link rel="icon" type="image/jpeg" href="https://africa.dlnlp.ai/simba/images/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-deep: #1e1b4b;
--text-yellow: #fbbf24;
--text-white: #ffffff;
--simba-navy: #0f172a;
--border-gold: #dca02a;
--grad-blue: linear-gradient(135deg, #3b82f6, #0ea5e9);
--grad-red: linear-gradient(135deg, #ef4444, #f43f5e);
--grad-purple: linear-gradient(135deg, #6366f1, #8b5cf6);
--grad-orange: linear-gradient(135deg, #f97316, #fb923c);
--grad-green: linear-gradient(135deg, #22c55e, #10b981);
}
body {
background-color: var(--bg-deep);
font-family: 'Inter', sans-serif;
color: var(--text-white);
margin: 0; padding: 0;
overflow-x: hidden;
}
a { text-decoration: none; transition: 0.2s; }
/* --- NAVBAR --- */
.navbar { padding: 20px 40px; display: flex; justify-content: space-between; align-items: center; position: relative; z-index: 100; }
.nav-logo { color: white; font-family: 'Rubik', sans-serif; font-weight: 700; font-size: 22px; display: flex; align-items: center; gap: 12px; }
.nav-logo img { height: 40px; }
.nav-text { display: flex; flex-direction: column; line-height: 1.1; }
.nav-text span:first-child { font-size: 14px; color: var(--text-yellow); }
.nav-links { display: flex; gap: 25px; align-items: center; }
.btn-submit { background: var(--grad-blue); color: white !important; padding: 10px 24px; border-radius: 30px; font-weight: 600; font-size: 14px; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); }
/* --- HERO --- */
.hero { max-width: 1000px; margin: 60px auto 100px; padding: 0 40px; position: relative; text-align: center; }
.hero-content { z-index: 2; display: flex; flex-direction: column; align-items: center; }
.conf-badge { background: white; color: black; display: inline-flex; align-items: center; gap: 10px; padding: 6px 16px; border-radius: 8px; margin-bottom: 30px; font-weight: 700; font-size: 14px; }
.conf-badge i { color: #cc0000; font-size: 18px; }
.hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
.hero h2 { font-family: 'Rubik', sans-serif; font-size: 32px; color: var(--text-yellow); margin: 15px 0 30px; font-weight: 700; }
.hero p { color: #cbd5e1; font-size: 18px; line-height: 1.6; max-width: 700px; margin-bottom: 50px; }
.hero-actions { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; }
.action-btn { display: flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: 30px; color: white; font-weight: 600; font-size: 15px; transition: transform 0.2s; }
.action-btn:hover { transform: translateY(-3px); color: white; }
.bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
/* --- CONTENT --- */
.content-container { width: 80%; max-width: 1600px; margin: 0 auto; }
.main-card { background: white; border-radius: 24px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; margin-bottom: 60px; }
/* Tabs */
.tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto; }
.tab-btn { background: none; border: none; font-size: 15px; font-weight: 600; color: #64748b; padding: 10px 20px; cursor: pointer; transition: 0.3s; border-radius: 8px; white-space: nowrap; }
.tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
.tab-btn.active { background: var(--bg-deep); color: white; }
.view { display: none !important; animation: fadeIn 0.4s; }
.view.active { display: block !important; }
/* Controls */
.controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
.rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
.score-desc { font-size: 14px; color: #475569; background: #fff7ed; border-left: 4px solid #f97316; padding: 10px 15px; margin-bottom: 20px; border-radius: 4px; }
/* ASR Buttons */
.mode-btn { padding: 8px 20px; border-radius: 8px; font-weight: 600; font-size: 13px; cursor: pointer; transition: all 0.2s; border: 1px solid #e2e8f0; background: white; color: var(--simba-navy); }
.mode-btn:hover { border-color: var(--text-yellow); color: var(--text-yellow); }
.mode-btn.active { background-color: var(--simba-navy); color: white; border-color: var(--simba-navy); box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15); }
/* --- FANCY SUMMARY CARDS --- */
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 25px; margin-bottom: 50px; }
.fancy-card {
background: white; border-radius: 16px; padding: 25px;
display: flex; align-items: center; gap: 20px;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.08); border: 1px solid #f1f5f9;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.fancy-card:hover { transform: translateY(-5px); box-shadow: 0 20px 30px -10px rgba(0,0,0,0.12); border-color: #e2e8f0; }
.card-icon {
width: 60px; height: 60px; border-radius: 14px;
display: flex; align-items: center; justify-content: center;
font-size: 26px; color: white; flex-shrink: 0;
box-shadow: 0 8px 15px -3px rgba(0,0,0,0.2);
}
.icon-asr { background: var(--grad-blue); }
.icon-tts { background: var(--grad-orange); }
.icon-slid { background: var(--grad-green); }
.card-content { flex: 1; }
.card-title { font-size: 11px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; }
.card-model { font-size: 18px; font-weight: 800; color: #0f172a; margin-bottom: 4px; line-height: 1.3; }
.card-score { font-size: 14px; font-weight: 600; color: #475569; }
.card-score b { color: #0f172a; }
/* Table */
.table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { background: #f1f5f9; color: #0f172a; font-weight: 700; padding: 16px; text-align: center; border-bottom: 2px solid #e2e8f0; white-space: nowrap; vertical-align: bottom; }
td { padding: 14px; border-bottom: 1px solid #e2e8f0; text-align: center; color: #334155; }
tr:hover td { background: #f8fafc; }
th:first-child, td:first-child { position: sticky; left: 0; background: white; z-index: 2; text-align: left; font-weight: 700; color: #0f172a; border-right: 2px solid #f1f5f9; min-width: 220px; }
thead th:first-child { background: #f1f5f9; z-index: 3; }
th:nth-child(2), td:nth-child(2) { text-align: left; }
h4 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
/* Citations */
.citation-section { background: #0f172a; border: 1px solid #334155; border-radius: 16px; padding: 40px; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0,0,0,0.3); position: relative; margin-bottom: 60px; }
.citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
.citation-box { background: #1e293b; color: #cbd5e1; padding: 20px; border-radius: 12px; font-family: monospace; white-space: pre-wrap; margin-top: 20px; overflow-x: auto; border: 1px solid #334155; font-size: 13px; line-height: 1.5; }
.copy-btn { position: absolute; top: 40px; right: 40px; background: var(--text-yellow); border: none; padding: 8px 16px; border-radius: 6px; font-weight: bold; cursor: pointer; color: #0f172a; }
.footer { text-align: center; padding: 40px; color: #94a3b8; font-size: 14px; }
@keyframes fadeIn { from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:translateY(0)} }
.arrow-down { color: #d97706; font-size: 10px; }
.arrow-up { color: #16a34a; font-size: 10px; }
.empty-msg { color: #64748b; font-style: italic; padding: 20px; text-align: center; }
</style>
</head>
<body>
<nav class="navbar layout-width">
<a href="https://africa.dlnlp.ai/simba" class="nav-logo">
<img src="https://africa.dlnlp.ai/simba/images/simba_main_logo.png" alt="Logo">
<div class="nav-text"><span>Voice of a</span> Continent</div>
</a>
<div class="nav-links">
<a href="https://aclanthology.org/2025.emnlp-main.559" target="_blank" class="nav-link">Paper</a>
<a href="https://africa.dlnlp.ai/simba/" target="_blank" class="btn-submit">Submit New Results</a>
</div>
</nav>
<div class="hero layout-width">
<div class="hero-content">
<div class="conf-badge"><i class="fa-solid fa-location-dot"></i> EMNLP 2025 Β· Suzhou, China</div>
<h1>SimbaBench Leaderboard</h1>
<h2>Mapping Africa’s Speech Technology</h2>
<p>SimbaBench bridges the digital divide with a unified suite for African AI: the largest open-source speech benchmark covering 61 languages.</p>
<div class="hero-actions">
<a href="https://aclanthology.org/2025.emnlp-main.559" target="_blank" class="action-btn bg-red"><i class="fa-regular fa-file-pdf"></i> Read Paper</a>
<a href="https://africa.dlnlp.ai/simba" target="_blank" class="action-btn bg-purple"><i class="fa-solid fa-code-branch"></i> Official Website</a>
</div>
</div>
</div>
<div class="content-container layout-width">
<div class="main-card" id="board">
<div id="loader" style="text-align:center; padding:60px; color:#64748b;">
<i class="fa-solid fa-circle-notch fa-spin fa-3x"></i><br><br>Loading Leaderboard Data...
</div>
<div id="app-content" style="display:none;">
<div class="tabs">
<button class="tab-btn active" id="tab-btn-asr" onclick="setTab('asr')">Automatic Speech Recognition (ASR)</button>
<button class="tab-btn" id="tab-btn-tts" onclick="setTab('tts')">Text-to-Speech (TTS)</button>
<button class="tab-btn" id="tab-btn-slid" onclick="setTab('slid')">Spoken Language Identification (SLID)</button>
<button class="tab-btn" id="tab-btn-lang" onclick="setTab('lang')">Language Analysis</button>
</div>
<div id="asr" class="view active">
<div class="controls">
<span style="font-weight:700;">View Mode:</span>
<button id="btn-over" class="mode-btn active" onclick="setASRMode('overview')">Overview</button>
<button id="btn-fam" class="mode-btn" onclick="setASRMode('family')">By Family</button>
<button id="btn-mod" class="mode-btn" onclick="setASRMode('model')">By Model</button>
<div style="flex-grow:1; text-align:right;">
<div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
<div id="grp-mod" style="display:none;"><label>Select Model: </label><select id="asr-select-mod" onchange="renderASR()"></select></div>
</div>
</div>
<div style="display:flex; justify-content:space-between; align-items:end; margin-bottom:10px;">
<h3 id="asr-title" style="color:#d97706; margin:0;">Results Overview</h3>
<div id="asr-rank-info" class="rank-info">Ranking: WER (↓) then CER (↓)</div>
</div>
<div id="asr-desc"></div>
<div id="asr-table" class="table-wrap"></div>
</div>
<div id="tts" class="view">
<div class="controls">
<span style="font-weight:600;">Select Model:</span>
<select id="tts-select" onchange="renderTTS()"></select>
</div>
<h3 id="tts-title" style="color:#d97706; margin-bottom:10px;"></h3>
<div id="tts-summary" style="margin-bottom:20px; font-weight:600; color:#0f172a;"></div>
<div id="tts-table" class="table-wrap"></div>
</div>
<div id="slid" class="view">
<div style="display:flex; justify-content:space-between; align-items:end; margin-bottom:15px;">
<h3 style="color:#d97706; margin:0;">Spoken Language Identification</h3>
<div class="rank-info">Ranking: F1-Macro (↑)</div>
</div>
<div id="slid-table" class="table-wrap"></div>
</div>
<div id="lang" class="view">
<div class="controls">
<span style="font-weight:600;">Select Language:</span>
<select id="lang-select" onchange="renderLanguageView()"></select>
</div>
<div id="lang-summary" class="summary-grid"></div>
<h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(Ranked by WER ↓, then CER ↓)</span></h4>
<div id="lang-asr-table" class="table-wrap"></div>
<h4>Text-to-Speech (TTS) Results</h4>
<div id="lang-tts-table" class="table-wrap"></div>
<h4>Spoken Language Identification (SLID) Results <span class="rank-info" style="font-weight:normal;">(Ranked by Score ↑)</span></h4>
<div id="lang-slid-table" class="table-wrap"></div>
</div>
</div>
</div>
<div class="citation-section layout-width" id="citation">
<h3>Citation</h3>
<button class="copy-btn" onclick="copyCitation()"><i class="fa-regular fa-copy"></i> Copy</button>
<p style="color:#94a3b8; margin-bottom:20px;">If you use the Simba benchmark, please cite our paper:</p>
<div class="citation-box" id="bibtex">@inproceedings{elmadany-etal-2025-voice,
title = "Voice of a Continent: Mapping {A}frica{'}s Speech Technology Frontier",
author = "Elmadany, AbdelRahim A. and Kwon, Sang Yun and Toyin, Hawau Olamide and Alcoba Inciarte, Alcides and Aldarmaki, Hanan and Abdul-Mageed, Muhammad",
editor = "Christodoulopoulos, Christos and Chakraborty, Tanmoy and Rose, Carolyn and Peng, Violet",
booktitle = "Proceedings of the 2025 Conference on Empirical Methods in Natural Language Processing",
month = nov,
year = "2025",
address = "Suzhou, China",
publisher = "Association for Computational Linguistics",
url = "https://aclanthology.org/2025.emnlp-main.559/",
doi = "10.18653/v1/2025.emnlp-main.559",
pages = "11039--11061",
ISBN = "979-8-89176-332-6",
}</div>
</div>
</div>
<div class="footer">
<img src="https://africa.dlnlp.ai/sahara//img/sahara_web_sponsers.jpg" width="250" style="opacity:0.8; margin-bottom:15px;">
<br>&copy; 2025 Simba Benchmark.
</div>
<script>
const NAME_TO_ISO = {'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem', 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som', 'Wolof':'wol', 'Xhosa':'xho'};
let DATA = null;
let asrMode = 'overview';
fetch('/api/data').then(r=>r.json()).then(d=>{
DATA = d;
document.getElementById('loader').style.display='none';
document.getElementById('app-content').style.display='block';
init();
});
function init() {
populate('asr-select-fam', DATA.metadata.families);
populate('asr-select-mod', DATA.metadata.models);
populate('tts-select', DATA.metadata.tts_models);
const allLangs = new Set();
// Collect languages from all sources to be thorough
Object.values(DATA.asr.by_model).forEach(rows => rows.forEach(r => { if(r.Language) allLangs.add(r.Language); }));
Object.values(DATA.tts).forEach(rows => rows.forEach(r => { const l = r.Language || r.language; if(l) allLangs.add(l); }));
if(DATA.slid) DATA.slid.forEach(r => { if(r.Language) allLangs.add(r.Language); });
populate('lang-select', Array.from(allLangs).sort());
renderASR(); renderTTS(); renderSLID();
}
function populate(id, list) {
const s = document.getElementById(id); s.innerHTML='';
list.forEach(i=>{ let o=document.createElement('option'); o.value=i; o.text=i; s.add(o); });
}
function fmt(n) { return (n==null)?'N/A':n.toFixed(2); }
function getIso(name, dataIso) {
let iso = dataIso || NAME_TO_ISO[name] || name.substring(0,3).toLowerCase();
return `${name} (${iso})`;
}
// --- ASR ---
function renderASR() {
const div = document.getElementById('asr-table');
const title = document.getElementById('asr-title');
const desc = document.getElementById('asr-desc');
let html='';
if (asrMode === 'overview') {
title.innerText = "Results Overview (All Models)";
desc.innerHTML = `<p class="score-desc">The <b>Simba ASR Score</b> is the global average of WER and CER across all languages in the benchmark.</p>`;
document.getElementById('grp-fam').style.display='none';
document.getElementById('grp-mod').style.display='none';
const modelStats = [];
Object.keys(DATA.asr.by_model).forEach(modName => {
const rows = DATA.asr.by_model[modName];
let sW=0, sC=0, n=0;
rows.forEach(r => { sW+=r.WER; sC+=r.CER; n++; });
if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
});
modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
html = `<table><thead><tr><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
let currentRank = 1;
modelStats.forEach((m, i) => {
if (i > 0 && Math.abs(m.wer - modelStats[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${m.name}</td><td><b>${fmt(m.wer)} / ${fmt(m.cer)}</b></td></tr>`;
});
} else if(asrMode==='family'){
const val = document.getElementById('asr-select-fam').value;
title.innerText = `Results for Family: ${val}`;
desc.innerHTML = `<p class="score-desc">The <b>Family Score</b> is the average WER and CER calculated across all languages within the <b>${val}</b> family.</p>`;
document.getElementById('grp-fam').style.display='inline-block';
document.getElementById('grp-mod').style.display='none';
const d = DATA.asr.by_family[val];
if(!d) return;
d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
d.languages.forEach(l => html += `<th>${getIso(l.name, l.iso)}</th>`);
html += `</tr></thead><tbody>`;
let currentRank = 1;
d.data.forEach((r, idx) => {
if(idx > 0 && Math.abs(r.Avg_WER - d.data[idx-1].Avg_WER) > 0.001) currentRank = idx + 1; else currentRank = 1;
let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.Model}</td><td style="font-weight:bold; background:#f8fafc">${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</td>`;
d.languages.forEach(l => {
let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
html += `<td>${(w!=null)?fmt(w)+'/'+fmt(c):'-'}</td>`;
});
html += `</tr>`;
});
} else {
const val = document.getElementById('asr-select-mod').value;
title.innerText = `Results for Model: ${val}`;
desc.innerHTML = "";
document.getElementById('grp-fam').style.display='none';
document.getElementById('grp-mod').style.display='inline-block';
const rows = DATA.asr.by_model[val];
html = `<table><thead><tr><th>Language</th><th>Family</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
rows.forEach(r => {
html += `<tr><td>${getIso(r.Language, r.ISO)}</td><td>${r.Family}</td><td>${fmt(r.WER)}</td><td>${fmt(r.CER)}</td></tr>`;
});
}
div.innerHTML = html;
}
// --- TTS ---
function renderTTS() {
const val = document.getElementById('tts-select').value;
document.getElementById('tts-title').innerText = `Model: ${val}`;
const rows = DATA.tts[val];
if(!rows) return;
const metrics = [
{k:'wer', l:'WER(↓)', h:false}, {k:'mcd', l:'MCD(↓)', h:false},
{k:'logf0rmse', l:'LogF0RMSE(↓)', h:false}, {k:'speech_token_distance', l:'SpeechTokenDistance(↓)', h:false},
{k:'pesq', l:'PESQ(↑)', h:true}, {k:'utmos', l:'UTMOS(↑)', h:true},
{k:'speechbleu', l:'SpeechBLEU(↑)', h:true}, {k:'speechbert_score', l:'SpeechBERTScore(↑)', h:true}
];
let sums={}, c=0;
metrics.forEach(m=>sums[m.k]=0);
rows.forEach(r=>{ metrics.forEach(m=>sums[m.k]+=(r[m.k]||0)); c++; });
document.getElementById('tts-summary').innerHTML = "<b>Averages: </b>" + metrics.map(m=> `${m.l.replace('(↓)','').replace('(↑)','')}: ${fmt(sums[m.k]/c)}`).join(" | ");
let html = `<table><thead><tr><th>Language</th>`;
metrics.forEach(m => html += `<th>${m.l}</th>`);
html += `</tr></thead><tbody>`;
rows.forEach(r => {
html += `<tr><td>${getIso(r.language, r.iso)}</td>`;
metrics.forEach(m => html += `<td>${fmt(r[m.k])}</td>`);
html += `</tr>`;
});
document.getElementById('tts-table').innerHTML = html;
}
// --- SLID ---
function renderSLID() {
let html = `<table><thead><tr><th>Language</th><th>MMS-LID-1024 (↑)</th><th>Simba-SLID (↑)</th></tr></thead><tbody>`;
DATA.slid.forEach(r => {
let m = r['MMS-LID-1024'], s = r['Simba-SLID'];
let mS = fmt(m), sS = fmt(s);
if(m > s) mS = `<b>${mS} πŸ₯‡</b>`; else if(s > m) sS = `<b>${sS} πŸ₯‡</b>`;
html += `<tr><td>${getIso(r.Language)}</td><td>${mS}</td><td>${sS}</td></tr>`;
});
document.getElementById('slid-table').innerHTML = html;
}
// --- LANGUAGE VIEW ---
function renderLanguageView() {
const lang = document.getElementById('lang-select').value;
// 1. ASR Data
let asrRows = [];
Object.keys(DATA.asr.by_model).forEach(modName => {
const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
if(row) asrRows.push({model: modName, wer: row.WER, cer: row.CER});
});
asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
// 2. TTS Data
let ttsRows = [];
Object.keys(DATA.tts).forEach(mod => {
const row = DATA.tts[mod].find(r => (r.Language || r.language) === lang);
if(row) ttsRows.push({model: mod, ...row});
});
// 3. SLID Data
let slidRows = [];
const slidRow = DATA.slid.find(r => r.Language === lang) ||
DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
DATA.slid.find(r => r.Language.includes(lang));
if(slidRow) {
const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
slidRows.sort((a,b) => b.val - a.val); // Descending
}
// --- SUMMARY CARDS ---
let summaryHTML = "";
// ASR Winner
if(asrRows.length > 0) {
summaryHTML += `<div class="fancy-card">
<div class="card-icon icon-asr"><i class="fa-solid fa-microphone-lines"></i></div>
<div class="card-content">
<div class="card-title">Best ASR Model</div>
<div class="card-model">${asrRows[0].model}</div>
<div class="card-score">WER: <b>${fmt(asrRows[0].wer)}%</b></div>
</div>
</div>`;
} else summaryHTML += `<div class="fancy-card"><div class="card-icon icon-asr"><i class="fa-solid fa-microphone-slash"></i></div><div class="card-content"><div class="card-title">ASR</div><div class="card-model">No Data</div></div></div>`;
// TTS Winner
if(ttsRows.length > 0) {
const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
summaryHTML += `<div class="fancy-card">
<div class="card-icon icon-tts"><i class="fa-solid fa-volume-high"></i></div>
<div class="card-content">
<div class="card-title">Best TTS (Intelligibility)</div>
<div class="card-model">${bestTTS.model}</div>
<div class="card-score">WER: <b>${fmt(bestTTS.wer)}%</b></div>
</div>
</div>`;
} else summaryHTML += `<div class="fancy-card"><div class="card-icon icon-tts"><i class="fa-solid fa-volume-xmark"></i></div><div class="card-content"><div class="card-title">TTS</div><div class="card-model">No Data</div></div></div>`;
// SLID Winner
if(slidRows.length > 0) {
summaryHTML += `<div class="fancy-card">
<div class="card-icon icon-slid"><i class="fa-solid fa-language"></i></div>
<div class="card-content">
<div class="card-title">Best SLID Model</div>
<div class="card-model">${slidRows[0].model}</div>
<div class="card-score">F1 Score: <b>${fmt(slidRows[0].val)}%</b></div>
</div>
</div>`;
} else summaryHTML += `<div class="fancy-card"><div class="card-icon icon-slid"><i class="fa-solid fa-circle-question"></i></div><div class="card-content"><div class="card-title">SLID</div><div class="card-model">No Data</div></div></div>`;
document.getElementById('lang-summary').innerHTML = summaryHTML;
// --- TABLES ---
// ASR Table
let h1 = `<table><thead><tr><th>Model</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
else {
let currentRank = 1;
asrRows.forEach((r, i) => {
if (i > 0 && Math.abs(r.wer - asrRows[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
h1 += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
});
}
h1 += `</tbody></table>`;
document.getElementById('lang-asr-table').innerHTML = h1;
// TTS Table
let h2 = `<table><thead><tr><th>Model</th>
<th>WER(↓)</th><th>MCD(↓)</th><th>LogF0RMSE(↓)</th><th>SpeechTokenDistance(↓)</th>
<th>PESQ(↑)</th><th>UTMOS(↑)</th><th>SpeechBLEU(↑)</th><th>SpeechBERTScore(↑)</th>
</tr></thead><tbody>`;
if(ttsRows.length === 0) h2 += `<tr><td colspan="9" class="empty-msg">No TTS results available for ${lang}.</td></tr>`;
else {
ttsRows.forEach(row => {
h2 += `<tr>
<td style="font-weight:600;">${row.model}</td>
<td>${fmt(row.wer)}</td><td>${fmt(row.mcd)}</td>
<td>${fmt(row.logf0rmse)}</td><td>${fmt(row.speech_token_distance)}</td>
<td>${fmt(row.pesq)}</td><td>${fmt(row.utmos)}</td>
<td>${fmt(row.speechbleu)}</td><td>${fmt(row.speechbert_score)}</td>
</tr>`;
});
}
h2 += `</tbody></table>`;
document.getElementById('lang-tts-table').innerHTML = h2;
// SLID Table
let h3 = `<table><thead><tr><th>Model</th><th>F1-Score <i class="fa-solid fa-arrow-up arrow-up"></i></th></tr></thead><tbody>`;
if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
else {
let currentRank = 1;
slidRows.forEach((sc, i) => {
if (i > 0 && Math.abs(sc.val - slidRows[i-1].val) > 0.001) currentRank = i + 1; else currentRank = 1;
let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
});
}
h3 += `</tbody></table>`;
document.getElementById('lang-slid-table').innerHTML = h3;
}
// --- UI ---
window.setTab = (id, btn) => {
document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(e => e.classList.remove('active'));
document.getElementById(id).classList.add('active');
if(btn) btn.classList.add('active'); else document.getElementById(`tab-btn-${id}`).classList.add('active');
if(id === 'lang') renderLanguageView();
};
window.setASRMode = (m) => {
asrMode = m;
['btn-over','btn-fam','btn-mod'].forEach(b => document.getElementById(b).classList.remove('active'));
document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod')).classList.add('active');
renderASR();
}
window.copyCitation = () => {
const text = document.getElementById('bibtex').innerText;
navigator.clipboard.writeText(text).then(() => {
const btn = document.querySelector('.copy-btn');
btn.innerHTML = '<i class="fa-solid fa-check"></i> Copied!';
setTimeout(() => btn.innerHTML = '<i class="fa-regular fa-copy"></i> Copy', 2000);
});
}
</script>
</body>
</html>