elmadany commited on
Commit
5107a45
Β·
verified Β·
1 Parent(s): a1ec447

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +166 -137
public/index.html CHANGED
@@ -18,7 +18,6 @@
18
  --text-white: #ffffff;
19
  --simba-navy: #0f172a;
20
  --border-gold: #dca02a;
21
- /* Gradients */
22
  --grad-blue: linear-gradient(90deg, #3b82f6, #0ea5e9);
23
  --grad-red: linear-gradient(135deg, #ef4444, #f43f5e);
24
  --grad-purple: linear-gradient(135deg, #6366f1, #8b5cf6);
@@ -38,120 +37,75 @@
38
 
39
  /* --- NAVBAR --- */
40
  .navbar {
41
- padding: 20px 40px;
42
- display: flex; justify-content: space-between; align-items: center;
43
- position: relative; z-index: 100;
44
  }
45
  .nav-logo {
46
- color: white; font-family: 'Rubik', sans-serif; font-weight: 700; font-size: 22px;
47
- display: flex; align-items: center; gap: 12px;
48
  }
49
  .nav-logo img { height: 40px; }
50
  .nav-text { display: flex; flex-direction: column; line-height: 1.1; }
51
  .nav-text span:first-child { font-size: 14px; color: var(--text-yellow); }
52
-
53
  .nav-links { display: flex; gap: 25px; align-items: center; }
54
  .nav-link { color: #e2e8f0; font-size: 15px; font-weight: 500; }
55
  .nav-link:hover { color: var(--text-yellow); }
56
-
57
- .btn-submit {
58
- 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);
59
- }
60
- .btn-login {
61
- background: var(--text-yellow); color: var(--bg-deep) !important; padding: 10px 24px; border-radius: 30px; font-weight: 700; font-size: 14px; box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
62
- }
63
 
64
  /* --- HERO --- */
65
  .hero { display: flex; align-items: center; max-width: 1400px; margin: 60px auto 100px; padding: 0 40px; position: relative; }
66
  .hero-content { flex: 1; z-index: 2; }
67
-
68
  .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; }
69
  .conf-badge i { color: #cc0000; font-size: 18px; }
70
-
71
  .hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
72
  .hero h2 { font-family: 'Rubik', sans-serif; font-size: 32px; color: var(--text-yellow); margin: 15px 0 30px; font-weight: 700; }
73
  .hero p { color: #cbd5e1; font-size: 18px; line-height: 1.6; max-width: 650px; margin-bottom: 50px; }
74
-
75
  .hero-actions { display: flex; gap: 15px; flex-wrap: wrap; }
76
  .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; }
77
  .action-btn:hover { transform: translateY(-3px); color: white; }
78
-
79
- .bg-red { background: var(--grad-red); box-shadow: 0 8px 20px rgba(239, 68, 68, 0.3); }
80
- .bg-purple { background: var(--grad-purple); box-shadow: 0 8px 20px rgba(139, 92, 246, 0.3); }
81
- .bg-orange { background: var(--grad-orange); box-shadow: 0 8px 20px rgba(249, 115, 22, 0.3); }
82
- .bg-blue { background: var(--grad-blue); box-shadow: 0 8px 20px rgba(59, 130, 246, 0.3); }
83
- .bg-green { background: var(--grad-green); box-shadow: 0 8px 20px rgba(34, 197, 94, 0.3); }
84
-
85
  .hero-visual { flex: 1; display: flex; justify-content: flex-end; position: relative; height: 400px; align-items: center; }
86
  .hero-visual img { max-width: 100%; height: auto; z-index: 2; position: relative; }
87
-
88
- /* Audio Wave Animation */
89
- .audio-wave {
90
- position: absolute; bottom: 50px; left: 0; right: 0; height: 150px;
91
- display: flex; align-items: center; justify-content: center; gap: 6px; z-index: 0; opacity: 0.3;
92
- mask-image: linear-gradient(to right, transparent, black 20%, black 80%, transparent);
93
- }
94
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
95
- .bar:nth-child(odd) { animation-duration: 1.5s; }
96
- .bar:nth-child(2n) { animation-duration: 1.1s; }
97
  @keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
98
 
99
  /* --- MAIN BOARD --- */
100
- .main-card {
101
- background: white; border-radius: 24px; margin: 0 40px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10;
102
- }
103
-
104
- .tabs { display: flex; gap: 15px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; }
105
- .tab-btn { background: none; border: none; font-size: 16px; font-weight: 600; color: #64748b; padding: 10px 20px; cursor: pointer; transition: 0.3s; border-radius: 8px; }
106
  .tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
107
  .tab-btn.active { background: var(--bg-deep); color: white; }
108
-
109
  .view { display: none !important; animation: fadeIn 0.4s; }
110
  .view.active { display: block !important; }
111
 
112
  /* Controls */
113
  .controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
114
  select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
115
-
116
  .rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
117
 
118
  /* Table */
119
- .table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; }
120
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
121
- th { background: #f1f5f9; color: #0f172a; font-weight: 700; padding: 16px; text-align: center; border-bottom: 2px solid #e2e8f0; white-space: nowrap; vertical-align: middle; }
122
  td { padding: 14px; border-bottom: 1px solid #e2e8f0; text-align: center; color: #334155; }
123
  tr:hover td { background: #f8fafc; }
124
-
125
  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: 200px; }
126
  thead th:first-child { background: #f1f5f9; z-index: 3; }
127
  th:nth-child(2), td:nth-child(2) { text-align: left; }
128
 
 
 
 
129
  /* Citations */
130
- .citation-section {
131
- width: 100%; /* Full Width */
132
- margin: 60px 0;
133
- background: #0f172a;
134
- border: 1px solid #334155;
135
- border-radius: 16px;
136
- padding: 40px;
137
- box-sizing: border-box; /* Ensure padding doesn't overflow width */
138
- box-shadow: 0 20px 40px rgba(0,0,0,0.3);
139
- position: relative;
140
- }
141
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
142
- .citation-box {
143
- background: #1e293b; color: #cbd5e1; padding: 20px; border-radius: 12px;
144
- font-family: monospace; white-space: pre-wrap; margin-top: 20px;
145
- overflow-x: auto; border: 1px solid #334155; font-size: 13px; line-height: 1.5;
146
- }
147
- .copy-btn {
148
- position: absolute; top: 40px; right: 40px;
149
- background: var(--text-yellow); border: none; padding: 8px 16px;
150
- border-radius: 6px; font-weight: bold; cursor: pointer; color: #0f172a;
151
- }
152
- .copy-btn:active { transform: scale(0.95); }
153
-
154
- .footer { text-align: center; padding-bottom: 40px; color: #94a3b8; font-size: 14px; }
155
  @keyframes fadeIn { from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:translateY(0)} }
156
  </style>
157
  </head>
@@ -182,20 +136,18 @@
182
  <h2>Mapping Africa’s Speech Technology</h2>
183
  <p>SimbaBench bridges the digital divide with a unified suite for African AI: the largest open-source speech benchmark covering 61 languages.</p>
184
  <div class="hero-actions">
185
- <a href="https://arxiv.org/abs/2510.17998" target="_blank" class="action-btn bg-red"><i class="fa-regular fa-file-pdf"></i> Read Paper</a>
186
- <a href="https://github.com/UBC-NLP/simba" target="_blank" class="action-btn bg-purple"><i class="fa-solid fa-code-branch"></i> Pipeline</a>
187
- <a href="https://huggingface.co/datasets/UBC-NLP/simba" target="_blank" class="action-btn bg-orange"><i class="fa-solid fa-database"></i> Dataset</a>
188
  <a href="#" class="action-btn bg-blue"><i class="fa-solid fa-bolt"></i> Models</a>
189
  <a href="#board" class="action-btn bg-green"><i class="fa-solid fa-trophy"></i> Leaderboard</a>
190
  </div>
191
  </div>
192
  <div class="hero-visual">
193
  <div class="audio-wave">
194
- <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
195
- <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
196
- <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
197
  </div>
198
- <img src="https://africa.dlnlp.ai/simba/images/simbabench_header.png" alt="Africa Map">
199
  </div>
200
  </div>
201
 
@@ -209,27 +161,26 @@
209
  <button class="tab-btn active" id="tab-btn-asr" onclick="setTab('asr')">Automatic Speech Recognition (ASR)</button>
210
  <button class="tab-btn" id="tab-btn-tts" onclick="setTab('tts')">Text-to-Speech (TTS)</button>
211
  <button class="tab-btn" id="tab-btn-slid" onclick="setTab('slid')">Spoken Language Identification (SLID)</button>
 
212
  </div>
213
 
214
  <div id="asr" class="view active">
215
  <div class="controls">
216
  <span style="font-weight:700;">View Mode:</span>
217
- <button id="btn-fam" class="action-btn bg-blue" style="padding:8px 16px; font-size:12px; box-shadow:none;" onclick="setASRMode('family')">By Family</button>
 
218
  <button id="btn-mod" class="action-btn" style="padding:8px 16px; font-size:12px; background:#e2e8f0; color:#334155;" onclick="setASRMode('model')">By Model</button>
219
 
220
  <div style="flex-grow:1; text-align:right;">
221
- <span style="font-weight:600; margin-right:10px;">Select:</span>
222
- <div id="grp-fam" style="display:inline-block;"><select id="asr-select-fam" onchange="renderASR()"></select></div>
223
- <div id="grp-mod" style="display:none;"><select id="asr-select-mod" onchange="renderASR()"></select></div>
224
  </div>
225
  </div>
226
 
227
  <div style="display:flex; justify-content:space-between; align-items:end; margin-bottom:10px;">
228
- <h3 id="asr-title" style="color:#d97706; margin:0;"></h3>
229
- <div id="asr-rank-info" class="rank-info" style="display:none;">Ranking: WER (↓) then CER (↓)</div>
230
  </div>
231
-
232
- <div id="asr-legend" style="margin-bottom:15px; font-size:13px; color:#64748b; background:#f8fafc; padding:10px; border-radius:6px; display:none;"></div>
233
  <div id="asr-table" class="table-wrap"></div>
234
  </div>
235
 
@@ -250,12 +201,30 @@
250
  </div>
251
  <div id="slid-table" class="table-wrap"></div>
252
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  </div>
 
254
 
 
255
  <div class="citation-section" id="citation">
256
  <h3>Citation</h3>
257
  <button class="copy-btn" onclick="copyCitation()"><i class="fa-regular fa-copy"></i> Copy</button>
258
- <p style="color:#94a3b8; margin-bottom:20px;">If you use the Simba benchmark, please cite our <a href="https://aclanthology.org/2025.emnlp-main.559/" style="color:#fbbf24; font-weight:bold;">EMNLP 2025 paper</a>.</p>
259
  <div class="citation-box" id="bibtex">@inproceedings{elmadany-etal-2025-voice,
260
  title = "Voice of a Continent: Mapping {A}frica{'}s Speech Technology Frontier",
261
  author = "Elmadany, AbdelRahim A. and Kwon, Sang Yun and Toyin, Hawau Olamide and Alcoba Inciarte, Alcides and Aldarmaki, Hanan and Abdul-Mageed, Muhammad",
@@ -279,28 +248,32 @@
279
  </div>
280
 
281
  <script>
282
- // --- LOGIC ---
283
- const NAME_TO_ISO = {
284
- 'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem',
285
- 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som',
286
- 'Wolof':'wol', 'Xhosa':'xho', 'Lingala':'ln', 'Oromo':'om'
287
- };
288
-
289
  let DATA = null;
290
- let asrMode = 'family';
291
 
292
  fetch('/api/data').then(r=>r.json()).then(d=>{
293
  DATA = d;
294
  document.getElementById('loader').style.display='none';
295
  document.getElementById('app-content').style.display='block';
296
  init();
297
- }).catch(e=>{ document.getElementById('loader').innerHTML=`<span style="color:red">Failed to load data.</span>`; });
298
 
299
  function init() {
300
  populate('asr-select-fam', DATA.metadata.families);
301
  populate('asr-select-mod', DATA.metadata.models);
302
  populate('tts-select', DATA.metadata.tts_models);
303
- setTab('asr'); renderASR(); renderTTS(); renderSLID();
 
 
 
 
 
 
 
 
 
304
  }
305
 
306
  function populate(id, list) {
@@ -316,48 +289,64 @@
316
  // --- RENDER ASR ---
317
  function renderASR() {
318
  const div = document.getElementById('asr-table');
319
- const legend = document.getElementById('asr-legend');
320
- const rankInfo = document.getElementById('asr-rank-info');
321
  let html='';
322
 
323
- if(asrMode==='family'){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  const val = document.getElementById('asr-select-fam').value;
325
- document.getElementById('asr-title').innerText = `Results for Family: ${val}`;
326
- rankInfo.style.display = 'block';
 
327
 
328
  const d = DATA.asr.by_family[val];
329
  if(!d) return;
330
-
331
- // Sort By WER ascending
332
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
333
 
334
- html = `<table><thead><tr><th>Model</th><th>Avg WER/CER <i class="fa-solid fa-arrow-down" style="color:#d97706; font-size:10px"></i></th>`;
335
  d.languages.forEach(l => html += `<th>${getIso(l.name, l.iso)}</th>`);
336
  html += `</tr></thead><tbody>`;
337
 
338
  d.data.forEach((r, idx) => {
339
- // Ranking Icons
340
- let rankIcon = '';
341
- if(idx === 0) rankIcon = 'πŸ₯‡ ';
342
- else if(idx === 1) rankIcon = 'πŸ₯ˆ ';
343
- else if(idx === 2) rankIcon = 'πŸ₯‰ ';
344
-
345
- html += `<tr><td>${rankIcon}${r.Model}</td><td style="font-weight:bold; background:#f8fafc">${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</td>`;
346
  d.languages.forEach(l => {
347
  let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
348
  html += `<td>${(w!=null)?fmt(w)+'/'+fmt(c):'-'}</td>`;
349
  });
350
  html += `</tr>`;
351
  });
352
- // Legend
353
- legend.style.display='none'; // Hiding legend as we show (iso) in header now
354
  } else {
355
  const val = document.getElementById('asr-select-mod').value;
356
- document.getElementById('asr-title').innerText = `Results for Model: ${val}`;
357
- legend.style.display='none'; rankInfo.style.display='none';
 
358
 
359
  const rows = DATA.asr.by_model[val];
360
- html = `<table><thead><tr><th>Language (iso)</th><th>Family</th><th>WER (%) <i class="fa-solid fa-arrow-down" style="color:#d97706; font-size:10px"></i></th><th>CER (%) <i class="fa-solid fa-arrow-down" style="color:#d97706; font-size:10px"></i></th></tr></thead><tbody>`;
361
  rows.forEach(r => {
362
  html += `<tr><td>${getIso(r.Language, r.ISO)}</td><td>${r.Family}</td><td>${fmt(r.WER)}</td><td>${fmt(r.CER)}</td></tr>`;
363
  });
@@ -382,9 +371,7 @@
382
  let sums={}, c=0;
383
  metrics.forEach(m=>sums[m.k]=0);
384
  rows.forEach(r=>{ metrics.forEach(m=>sums[m.k]+=(r[m.k]||0)); c++; });
385
-
386
- document.getElementById('tts-summary').innerHTML = "<b>Averages: </b>" +
387
- metrics.map(m=> `${m.l.replace('(↓)','').replace('(↑)','')}: ${fmt(sums[m.k]/c)}`).join(" | ");
388
 
389
  let html = `<table><thead><tr><th>Language (iso)</th>`;
390
  metrics.forEach(m => html += `<th>${m.l}</th>`);
@@ -399,43 +386,85 @@
399
 
400
  // --- RENDER SLID ---
401
  function renderSLID() {
402
- // Sort by F1-macro (assuming Simba is the primary metric to sort by, or generally by Best Score)
403
- // Here we sort alphabetically by language as it's a comparison view
404
- DATA.slid.sort((a,b) => a.Language.localeCompare(b.Language));
405
-
406
  let html = `<table><thead><tr><th>Language (iso)</th><th>MMS-LID-1024 (↑)</th><th>Simba-SLID (↑)</th></tr></thead><tbody>`;
407
  DATA.slid.forEach(r => {
408
  let m = r['MMS-LID-1024'], s = r['Simba-SLID'];
409
  let mS = fmt(m), sS = fmt(s);
410
- // Highlight winner
411
- if(m > s) mS = `<b>${mS} πŸ₯‡</b>`;
412
- else if(s > m) sS = `<b>${sS} πŸ₯‡</b>`;
413
-
414
  html += `<tr><td>${getIso(r.Language)}</td><td>${mS}</td><td>${sS}</td></tr>`;
415
  });
416
  document.getElementById('slid-table').innerHTML = html;
417
  }
418
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  // --- UI ---
420
- window.setTab = (id) => {
421
  document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));
422
  document.querySelectorAll('.tab-btn').forEach(e => e.classList.remove('active'));
423
  document.getElementById(id).classList.add('active');
424
- document.getElementById(`tab-btn-${id}`).classList.add('active');
 
425
  };
426
 
427
  window.setASRMode = (m) => {
428
  asrMode = m;
429
- const b1 = document.getElementById('btn-fam'), b2 = document.getElementById('btn-mod');
430
- document.getElementById('grp-fam').style.display = m==='family'?'inline-block':'none';
431
- document.getElementById('grp-mod').style.display = m==='model'?'inline-block':'none';
432
- if(m==='family') {
433
- b1.className="action-btn bg-blue"; b1.style.color="white"; b1.style.background="";
434
- b2.className="action-btn"; b2.style.background="#e2e8f0"; b2.style.color="#334155";
435
- } else {
436
- b2.className="action-btn bg-blue"; b2.style.color="white"; b2.style.background="";
437
- b1.className="action-btn"; b1.style.background="#e2e8f0"; b1.style.color="#334155";
438
- }
439
  renderASR();
440
  }
441
 
 
18
  --text-white: #ffffff;
19
  --simba-navy: #0f172a;
20
  --border-gold: #dca02a;
 
21
  --grad-blue: linear-gradient(90deg, #3b82f6, #0ea5e9);
22
  --grad-red: linear-gradient(135deg, #ef4444, #f43f5e);
23
  --grad-purple: linear-gradient(135deg, #6366f1, #8b5cf6);
 
37
 
38
  /* --- NAVBAR --- */
39
  .navbar {
40
+ padding: 20px 40px; display: flex; justify-content: space-between; align-items: center; position: relative; z-index: 100;
 
 
41
  }
42
  .nav-logo {
43
+ color: white; font-family: 'Rubik', sans-serif; font-weight: 700; font-size: 22px; display: flex; align-items: center; gap: 12px;
 
44
  }
45
  .nav-logo img { height: 40px; }
46
  .nav-text { display: flex; flex-direction: column; line-height: 1.1; }
47
  .nav-text span:first-child { font-size: 14px; color: var(--text-yellow); }
 
48
  .nav-links { display: flex; gap: 25px; align-items: center; }
49
  .nav-link { color: #e2e8f0; font-size: 15px; font-weight: 500; }
50
  .nav-link:hover { color: var(--text-yellow); }
51
+ .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); }
52
+ .btn-login { background: var(--text-yellow); color: var(--bg-deep) !important; padding: 10px 24px; border-radius: 30px; font-weight: 700; font-size: 14px; box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4); }
 
 
 
 
 
53
 
54
  /* --- HERO --- */
55
  .hero { display: flex; align-items: center; max-width: 1400px; margin: 60px auto 100px; padding: 0 40px; position: relative; }
56
  .hero-content { flex: 1; z-index: 2; }
 
57
  .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; }
58
  .conf-badge i { color: #cc0000; font-size: 18px; }
 
59
  .hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
60
  .hero h2 { font-family: 'Rubik', sans-serif; font-size: 32px; color: var(--text-yellow); margin: 15px 0 30px; font-weight: 700; }
61
  .hero p { color: #cbd5e1; font-size: 18px; line-height: 1.6; max-width: 650px; margin-bottom: 50px; }
 
62
  .hero-actions { display: flex; gap: 15px; flex-wrap: wrap; }
63
  .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; }
64
  .action-btn:hover { transform: translateY(-3px); color: white; }
65
+ .bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); } .bg-orange { background: var(--grad-orange); } .bg-blue { background: var(--grad-blue); } .bg-green { background: var(--grad-green); }
 
 
 
 
 
 
66
  .hero-visual { flex: 1; display: flex; justify-content: flex-end; position: relative; height: 400px; align-items: center; }
67
  .hero-visual img { max-width: 100%; height: auto; z-index: 2; position: relative; }
68
+ .audio-wave { position: absolute; bottom: 50px; left: 0; right: 0; height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px; z-index: 0; opacity: 0.3; mask-image: linear-gradient(to right, transparent, black 20%, black 80%, transparent); }
 
 
 
 
 
 
69
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
70
+ .bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
 
71
  @keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
72
 
73
  /* --- MAIN BOARD --- */
74
+ .main-card { background: white; border-radius: 24px; margin: 0 40px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; }
75
+
76
+ /* Tabs */
77
+ .tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto;}
78
+ .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; }
 
79
  .tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
80
  .tab-btn.active { background: var(--bg-deep); color: white; }
 
81
  .view { display: none !important; animation: fadeIn 0.4s; }
82
  .view.active { display: block !important; }
83
 
84
  /* Controls */
85
  .controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
86
  select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
 
87
  .rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
88
 
89
  /* Table */
90
+ .table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
91
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
92
+ th { background: #f1f5f9; color: #0f172a; font-weight: 700; padding: 16px; text-align: center; border-bottom: 2px solid #e2e8f0; white-space: nowrap; vertical-align: bottom; }
93
  td { padding: 14px; border-bottom: 1px solid #e2e8f0; text-align: center; color: #334155; }
94
  tr:hover td { background: #f8fafc; }
 
95
  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: 200px; }
96
  thead th:first-child { background: #f1f5f9; z-index: 3; }
97
  th:nth-child(2), td:nth-child(2) { text-align: left; }
98
 
99
+ /* Section Headings */
100
+ 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; }
101
+
102
  /* Citations */
103
+ .citation-section { width: 100%; margin: 60px 0 0 0; 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; }
 
 
 
 
 
 
 
 
 
 
104
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
105
+ .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; }
106
+ .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; }
107
+
108
+ .footer { text-align: center; padding: 40px; color: #94a3b8; font-size: 14px; }
 
 
 
 
 
 
 
 
 
109
  @keyframes fadeIn { from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:translateY(0)} }
110
  </style>
111
  </head>
 
136
  <h2>Mapping Africa’s Speech Technology</h2>
137
  <p>SimbaBench bridges the digital divide with a unified suite for African AI: the largest open-source speech benchmark covering 61 languages.</p>
138
  <div class="hero-actions">
139
+ <a href="#" class="action-btn bg-red"><i class="fa-regular fa-file-pdf"></i> Read Paper</a>
140
+ <a href="#" class="action-btn bg-purple"><i class="fa-solid fa-code-branch"></i> Pipeline</a>
141
+ <a href="#" class="action-btn bg-orange"><i class="fa-solid fa-database"></i> Dataset</a>
142
  <a href="#" class="action-btn bg-blue"><i class="fa-solid fa-bolt"></i> Models</a>
143
  <a href="#board" class="action-btn bg-green"><i class="fa-solid fa-trophy"></i> Leaderboard</a>
144
  </div>
145
  </div>
146
  <div class="hero-visual">
147
  <div class="audio-wave">
148
+ <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
 
 
149
  </div>
150
+ <img src="https://africa.dlnlp.ai/simba/images/simbabench_header.png" alt="Map">
151
  </div>
152
  </div>
153
 
 
161
  <button class="tab-btn active" id="tab-btn-asr" onclick="setTab('asr')">Automatic Speech Recognition (ASR)</button>
162
  <button class="tab-btn" id="tab-btn-tts" onclick="setTab('tts')">Text-to-Speech (TTS)</button>
163
  <button class="tab-btn" id="tab-btn-slid" onclick="setTab('slid')">Spoken Language Identification (SLID)</button>
164
+ <button class="tab-btn" id="tab-btn-lang" onclick="setTab('lang')">Language Analysis</button>
165
  </div>
166
 
167
  <div id="asr" class="view active">
168
  <div class="controls">
169
  <span style="font-weight:700;">View Mode:</span>
170
+ <button id="btn-over" class="action-btn bg-blue" style="padding:8px 16px; font-size:12px; box-shadow:none;" onclick="setASRMode('overview')">Overview</button>
171
+ <button id="btn-fam" class="action-btn" style="padding:8px 16px; font-size:12px; background:#e2e8f0; color:#334155;" onclick="setASRMode('family')">By Family</button>
172
  <button id="btn-mod" class="action-btn" style="padding:8px 16px; font-size:12px; background:#e2e8f0; color:#334155;" onclick="setASRMode('model')">By Model</button>
173
 
174
  <div style="flex-grow:1; text-align:right;">
175
+ <div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
176
+ <div id="grp-mod" style="display:none;"><label>Select Model: </label><select id="asr-select-mod" onchange="renderASR()"></select></div>
 
177
  </div>
178
  </div>
179
 
180
  <div style="display:flex; justify-content:space-between; align-items:end; margin-bottom:10px;">
181
+ <h3 id="asr-title" style="color:#d97706; margin:0;">Results Overview</h3>
182
+ <div id="asr-rank-info" class="rank-info">Ranking: WER (↓) then CER (↓)</div>
183
  </div>
 
 
184
  <div id="asr-table" class="table-wrap"></div>
185
  </div>
186
 
 
201
  </div>
202
  <div id="slid-table" class="table-wrap"></div>
203
  </div>
204
+
205
+ <div id="lang" class="view">
206
+ <div class="controls">
207
+ <span style="font-weight:600;">Select Language:</span>
208
+ <select id="lang-select" onchange="renderLanguageView()"></select>
209
+ </div>
210
+
211
+ <h4>ASR Results <span class="rank-info" style="font-weight:normal;">(Ranked by WER ↓)</span></h4>
212
+ <div id="lang-asr-table" class="table-wrap"></div>
213
+
214
+ <h4>TTS Results</h4>
215
+ <div id="lang-tts-table" class="table-wrap"></div>
216
+
217
+ <h4>SLID Results <span class="rank-info" style="font-weight:normal;">(Ranked by Score ↑)</span></h4>
218
+ <div id="lang-slid-table" class="table-wrap"></div>
219
+ </div>
220
  </div>
221
+ </div>
222
 
223
+ <div class="lb-container">
224
  <div class="citation-section" id="citation">
225
  <h3>Citation</h3>
226
  <button class="copy-btn" onclick="copyCitation()"><i class="fa-regular fa-copy"></i> Copy</button>
227
+ <p style="color:#94a3b8; margin-bottom:20px;">If you use the Simba benchmark, please cite our paper:</p>
228
  <div class="citation-box" id="bibtex">@inproceedings{elmadany-etal-2025-voice,
229
  title = "Voice of a Continent: Mapping {A}frica{'}s Speech Technology Frontier",
230
  author = "Elmadany, AbdelRahim A. and Kwon, Sang Yun and Toyin, Hawau Olamide and Alcoba Inciarte, Alcides and Aldarmaki, Hanan and Abdul-Mageed, Muhammad",
 
248
  </div>
249
 
250
  <script>
251
+ // --- DATA & LOGIC ---
252
+ 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'};
 
 
 
 
 
253
  let DATA = null;
254
+ let asrMode = 'overview';
255
 
256
  fetch('/api/data').then(r=>r.json()).then(d=>{
257
  DATA = d;
258
  document.getElementById('loader').style.display='none';
259
  document.getElementById('app-content').style.display='block';
260
  init();
261
+ });
262
 
263
  function init() {
264
  populate('asr-select-fam', DATA.metadata.families);
265
  populate('asr-select-mod', DATA.metadata.models);
266
  populate('tts-select', DATA.metadata.tts_models);
267
+
268
+ // Collect Unique Languages for Language Tab
269
+ const allLangs = new Set();
270
+ // From ASR Family view (it has language lists)
271
+ Object.values(DATA.asr.by_family).forEach(fam => {
272
+ fam.languages.forEach(l => allLangs.add(l.name));
273
+ });
274
+ populate('lang-select', Array.from(allLangs).sort());
275
+
276
+ renderASR(); renderTTS(); renderSLID();
277
  }
278
 
279
  function populate(id, list) {
 
289
  // --- RENDER ASR ---
290
  function renderASR() {
291
  const div = document.getElementById('asr-table');
292
+ const title = document.getElementById('asr-title');
 
293
  let html='';
294
 
295
+ if (asrMode === 'overview') {
296
+ title.innerText = "Results Overview (All Models)";
297
+ document.getElementById('grp-fam').style.display='none';
298
+ document.getElementById('grp-mod').style.display='none';
299
+
300
+ // Calculate Global Averages
301
+ const modelStats = [];
302
+ Object.keys(DATA.asr.by_model).forEach(modName => {
303
+ const rows = DATA.asr.by_model[modName];
304
+ let sW=0, sC=0, n=0;
305
+ rows.forEach(r => { sW+=r.WER; sC+=r.CER; n++; });
306
+ if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
307
+ });
308
+
309
+ modelStats.sort((a,b) => a.wer - b.wer);
310
+
311
+ html = `<table><thead><tr><th>Rank</th><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
312
+ modelStats.forEach((m, i) => {
313
+ let rank = (i<3) ? ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰'][i] : (i+1);
314
+ html += `<tr><td>${rank}</td><td style="font-weight:600; color:var(--simba-navy)">${m.name}</td><td><b>${fmt(m.wer)} / ${fmt(m.cer)}</b></td></tr>`;
315
+ });
316
+
317
+ } else if(asrMode==='family'){
318
  const val = document.getElementById('asr-select-fam').value;
319
+ title.innerText = `Results for Family: ${val}`;
320
+ document.getElementById('grp-fam').style.display='inline-block';
321
+ document.getElementById('grp-mod').style.display='none';
322
 
323
  const d = DATA.asr.by_family[val];
324
  if(!d) return;
325
+
326
+ // Sort rows by WER
327
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
328
 
329
+ html = `<table><thead><tr><th>Rank</th><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
330
  d.languages.forEach(l => html += `<th>${getIso(l.name, l.iso)}</th>`);
331
  html += `</tr></thead><tbody>`;
332
 
333
  d.data.forEach((r, idx) => {
334
+ let rank = (idx<3) ? ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰'][idx] : (idx+1);
335
+ html += `<tr><td>${rank}</td><td style="font-weight:600; color:var(--simba-navy)">${r.Model}</td><td style="font-weight:bold; background:#f8fafc">${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</td>`;
 
 
 
 
 
336
  d.languages.forEach(l => {
337
  let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
338
  html += `<td>${(w!=null)?fmt(w)+'/'+fmt(c):'-'}</td>`;
339
  });
340
  html += `</tr>`;
341
  });
 
 
342
  } else {
343
  const val = document.getElementById('asr-select-mod').value;
344
+ title.innerText = `Results for Model: ${val}`;
345
+ document.getElementById('grp-fam').style.display='none';
346
+ document.getElementById('grp-mod').style.display='inline-block';
347
 
348
  const rows = DATA.asr.by_model[val];
349
+ html = `<table><thead><tr><th>Language (iso)</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>`;
350
  rows.forEach(r => {
351
  html += `<tr><td>${getIso(r.Language, r.ISO)}</td><td>${r.Family}</td><td>${fmt(r.WER)}</td><td>${fmt(r.CER)}</td></tr>`;
352
  });
 
371
  let sums={}, c=0;
372
  metrics.forEach(m=>sums[m.k]=0);
373
  rows.forEach(r=>{ metrics.forEach(m=>sums[m.k]+=(r[m.k]||0)); c++; });
374
+ document.getElementById('tts-summary').innerHTML = "<b>Averages: </b>" + metrics.map(m=> `${m.l.replace('(↓)','').replace('(↑)','')}: ${fmt(sums[m.k]/c)}`).join(" | ");
 
 
375
 
376
  let html = `<table><thead><tr><th>Language (iso)</th>`;
377
  metrics.forEach(m => html += `<th>${m.l}</th>`);
 
386
 
387
  // --- RENDER SLID ---
388
  function renderSLID() {
 
 
 
 
389
  let html = `<table><thead><tr><th>Language (iso)</th><th>MMS-LID-1024 (↑)</th><th>Simba-SLID (↑)</th></tr></thead><tbody>`;
390
  DATA.slid.forEach(r => {
391
  let m = r['MMS-LID-1024'], s = r['Simba-SLID'];
392
  let mS = fmt(m), sS = fmt(s);
393
+ if(m > s) mS = `<b>${mS} πŸ₯‡</b>`; else if(s > m) sS = `<b>${sS} πŸ₯‡</b>`;
 
 
 
394
  html += `<tr><td>${getIso(r.Language)}</td><td>${mS}</td><td>${sS}</td></tr>`;
395
  });
396
  document.getElementById('slid-table').innerHTML = html;
397
  }
398
 
399
+ // --- RENDER LANGUAGE VIEW (New) ---
400
+ function renderLanguageView() {
401
+ const lang = document.getElementById('lang-select').value;
402
+
403
+ // 1. ASR Table
404
+ let asrRows = [];
405
+ Object.keys(DATA.asr.by_model).forEach(modName => {
406
+ const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
407
+ if(row) asrRows.push({model: modName, wer: row.WER, cer: row.CER});
408
+ });
409
+ asrRows.sort((a,b) => a.wer - b.wer);
410
+
411
+ let h1 = `<table><thead><tr><th>Rank</th><th>Model</th><th>WER (%)</th><th>CER (%)</th></tr></thead><tbody>`;
412
+ if(asrRows.length === 0) h1 += `<tr><td colspan="4">No ASR data for this language.</td></tr>`;
413
+ else asrRows.forEach((r, i) => {
414
+ let rank = (i<3) ? ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰'][i] : (i+1);
415
+ h1 += `<tr><td>${rank}</td><td>${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
416
+ });
417
+ h1 += `</tbody></table>`;
418
+ document.getElementById('lang-asr-table').innerHTML = h1;
419
+
420
+ // 2. TTS Table
421
+ let h2 = `<table><thead><tr><th>Model</th><th>WER</th><th>MCD</th><th>PESQ</th></tr></thead><tbody>`;
422
+ let ttsFound = false;
423
+ Object.keys(DATA.tts).forEach(mod => {
424
+ const row = DATA.tts[mod].find(r => r.language === lang);
425
+ if(row) {
426
+ ttsFound = true;
427
+ h2 += `<tr><td>${mod}</td><td>${fmt(row.wer)}</td><td>${fmt(row.mcd)}</td><td>${fmt(row.pesq)}</td></tr>`;
428
+ }
429
+ });
430
+ if(!ttsFound) h2 += `<tr><td colspan="4">No TTS data.</td></tr>`;
431
+ h2 += `</tbody></table>`;
432
+ document.getElementById('lang-tts-table').innerHTML = h2;
433
+
434
+ // 3. SLID Table
435
+ let h3 = `<table><thead><tr><th>Rank</th><th>Model</th><th>F1-Score</th></tr></thead><tbody>`;
436
+ const slidRow = DATA.slid.find(r => r.Language === lang);
437
+ if(slidRow) {
438
+ let m = slidRow['MMS-LID-1024'], s = slidRow['Simba-SLID'];
439
+ let scores = [{n:'Simba-SLID', v:s}, {n:'MMS-LID-1024', v:m}].sort((a,b)=>b.v - a.v);
440
+ scores.forEach((sc, i) => {
441
+ let rank = (i<3) ? ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰'][i] : (i+1);
442
+ h3 += `<tr><td>${rank}</td><td>${sc.n}</td><td>${fmt(sc.v)}</td></tr>`;
443
+ });
444
+ } else {
445
+ h3 += `<tr><td colspan="3">No SLID data.</td></tr>`;
446
+ }
447
+ h3 += `</tbody></table>`;
448
+ document.getElementById('lang-slid-table').innerHTML = h3;
449
+ }
450
+
451
  // --- UI ---
452
+ window.setTab = (id, btn) => {
453
  document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));
454
  document.querySelectorAll('.tab-btn').forEach(e => e.classList.remove('active'));
455
  document.getElementById(id).classList.add('active');
456
+ if(btn) btn.classList.add('active'); else document.getElementById(`tab-btn-${id}`).classList.add('active');
457
+ if(id === 'lang') renderLanguageView();
458
  };
459
 
460
  window.setASRMode = (m) => {
461
  asrMode = m;
462
+ ['btn-over','btn-fam','btn-mod'].forEach(b => {
463
+ document.getElementById(b).className = "action-btn";
464
+ document.getElementById(b).style.background="#e2e8f0"; document.getElementById(b).style.color="#334155";
465
+ });
466
+ const activeBtn = document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod'));
467
+ activeBtn.className = "action-btn bg-blue"; activeBtn.style.color="white"; activeBtn.style.background="";
 
 
 
 
468
  renderASR();
469
  }
470