elmadany commited on
Commit
709d464
Β·
verified Β·
1 Parent(s): d55213e

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +106 -116
public/index.html CHANGED
@@ -10,7 +10,6 @@
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
-
14
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
15
 
16
  <style>
@@ -48,18 +47,10 @@
48
 
49
  /* --- HERO --- */
50
  .hero {
51
- max-width: 1000px;
52
- margin: 60px auto 100px;
53
- padding: 0 40px;
54
- position: relative;
55
- text-align: center;
56
- }
57
- .hero-content {
58
- z-index: 2;
59
- display: flex;
60
- flex-direction: column;
61
- align-items: center;
62
  }
 
63
  .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; }
64
  .conf-badge i { color: #cc0000; font-size: 18px; }
65
  .hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
@@ -71,25 +62,17 @@
71
  .bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
72
 
73
  /* Wave Animation */
74
- .wave-bg {
75
- position: absolute; top: -50px; left: -50%; width: 200%; height: 100%;
76
- background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%);
77
- z-index: 0; pointer-events: none;
78
- }
79
- .audio-wave {
80
- height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px;
81
- margin-top: 60px; mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
82
- }
83
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
84
  .bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
85
  @keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
86
 
87
- /* --- WIDTH CONTROLLER --- */
88
  .content-container { width: 80%; max-width: 1600px; margin: 0 auto; }
89
-
90
- /* --- MAIN BOARD --- */
91
  .main-card { background: white; border-radius: 24px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; margin-bottom: 60px; }
92
 
 
93
  .tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto; }
94
  .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; }
95
  .tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
@@ -97,12 +80,53 @@
97
  .view { display: none !important; animation: fadeIn 0.4s; }
98
  .view.active { display: block !important; }
99
 
 
100
  .controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
101
  select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
102
-
103
  .rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
104
  .score-desc { font-size: 14px; color: #475569; background: #fff7ed; border-left: 4px solid #f97316; padding: 10px 15px; margin-bottom: 20px; border-radius: 4px; }
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  /* Table */
107
  .table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
108
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
@@ -112,28 +136,10 @@
112
  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; }
113
  thead th:first-child { background: #f1f5f9; z-index: 3; }
114
  th:nth-child(2), td:nth-child(2) { text-align: left; }
115
-
116
  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; }
117
 
118
- /* --- SUMMARY CARDS (Language Analysis) --- */
119
- .summary-grid {
120
- display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px;
121
- }
122
- .stat-card {
123
- background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px;
124
- box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center; transition: 0.3s;
125
- }
126
- .stat-card:hover { transform: translateY(-3px); border-color: var(--text-yellow); }
127
- .stat-title { font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
128
- .stat-model { font-size: 18px; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
129
- .stat-val { font-size: 14px; color: var(--text-yellow); font-weight: 600; }
130
-
131
- /* --- CHART CONTAINERS --- */
132
- .chart-section { margin-bottom: 50px; }
133
- .chart-box { height: 300px; width: 100%; margin-bottom: 20px; }
134
-
135
  /* Citations */
136
- .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; }
137
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
138
  .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; }
139
  .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; }
@@ -195,9 +201,9 @@
195
  <div id="asr" class="view active">
196
  <div class="controls">
197
  <span style="font-weight:700;">View Mode:</span>
198
- <button id="btn-over" class="action-btn bg-blue" style="padding:8px 16px; font-size:12px; box-shadow:none;" onclick="setASRMode('overview')">Overview</button>
199
- <button id="btn-fam" class="action-btn" style="padding:8px 16px; font-size:12px; background:#e2e8f0; color:#334155;" onclick="setASRMode('family')">By Family</button>
200
- <button id="btn-mod" class="action-btn" style="padding:8px 16px; font-size:12px; background:#e2e8f0; color:#334155;" onclick="setASRMode('model')">By Model</button>
201
 
202
  <div style="flex-grow:1; text-align:right;">
203
  <div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
@@ -240,20 +246,20 @@
240
  <div id="lang-summary" class="summary-grid"></div>
241
 
242
  <div class="chart-section">
243
- <h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(WER ↓)</span></h4>
244
- <div class="chart-box"><canvas id="chart-asr"></canvas></div>
245
  <div id="lang-asr-table" class="table-wrap"></div>
246
  </div>
247
 
248
  <div class="chart-section">
249
- <h4>Text-to-Speech (TTS)</h4>
250
- <div class="chart-box"><canvas id="chart-tts"></canvas></div>
251
  <div id="lang-tts-table" class="table-wrap"></div>
252
  </div>
253
 
254
  <div class="chart-section">
255
- <h4>Spoken Language Identification (SLID) <span class="rank-info" style="font-weight:normal;">(F1 ↑)</span></h4>
256
- <div class="chart-box"><canvas id="chart-slid"></canvas></div>
257
  <div id="lang-slid-table" class="table-wrap"></div>
258
  </div>
259
  </div>
@@ -287,11 +293,10 @@
287
  </div>
288
 
289
  <script>
290
- // --- DATA & LOGIC ---
291
  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'};
292
  let DATA = null;
293
  let asrMode = 'overview';
294
- let charts = { asr:null, tts:null, slid:null }; // Stores chart instances
295
 
296
  fetch('/api/data').then(r=>r.json()).then(d=>{
297
  DATA = d;
@@ -325,7 +330,7 @@
325
  return `${name} (${iso})`;
326
  }
327
 
328
- // --- RENDER ASR ---
329
  function renderASR() {
330
  const div = document.getElementById('asr-table');
331
  const title = document.getElementById('asr-title');
@@ -346,21 +351,13 @@
346
  if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
347
  });
348
 
349
- modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer)); // Sorting by WER then CER
350
-
351
  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>`;
352
 
353
  let currentRank = 1;
354
  modelStats.forEach((m, i) => {
355
- if (i > 0) {
356
- const prev = modelStats[i-1];
357
- if (Math.abs(m.wer - prev.wer) > 0.001) { currentRank = i + 1; }
358
- } else { currentRank = 1; }
359
- m.rank = currentRank;
360
-
361
- let icon = '';
362
- if(m.rank === 1) icon = 'πŸ₯‡ '; else if(m.rank === 2) icon = 'πŸ₯ˆ '; else if(m.rank === 3) icon = 'πŸ₯‰ ';
363
-
364
  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>`;
365
  });
366
 
@@ -373,7 +370,6 @@
373
 
374
  const d = DATA.asr.by_family[val];
375
  if(!d) return;
376
-
377
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
378
 
379
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
@@ -382,15 +378,8 @@
382
 
383
  let currentRank = 1;
384
  d.data.forEach((r, idx) => {
385
- if(idx > 0) {
386
- const prev = d.data[idx-1];
387
- if (Math.abs(r.Avg_WER - prev.Avg_WER) > 0.001) currentRank = idx + 1;
388
- } else { currentRank = 1; }
389
- r.rank = currentRank;
390
-
391
- let icon = '';
392
- if(r.rank === 1) icon = 'πŸ₯‡ '; else if(r.rank === 2) icon = 'πŸ₯ˆ '; else if(r.rank === 3) icon = 'πŸ₯‰ ';
393
-
394
  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>`;
395
  d.languages.forEach(l => {
396
  let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
@@ -414,7 +403,7 @@
414
  div.innerHTML = html;
415
  }
416
 
417
- // --- RENDER TTS ---
418
  function renderTTS() {
419
  const val = document.getElementById('tts-select').value;
420
  document.getElementById('tts-title').innerText = `Model: ${val}`;
@@ -444,7 +433,7 @@
444
  document.getElementById('tts-table').innerHTML = html;
445
  }
446
 
447
- // --- RENDER SLID ---
448
  function renderSLID() {
449
  let html = `<table><thead><tr><th>Language</th><th>MMS-LID-1024 (↑)</th><th>Simba-SLID (↑)</th></tr></thead><tbody>`;
450
  DATA.slid.forEach(r => {
@@ -456,11 +445,11 @@
456
  document.getElementById('slid-table').innerHTML = html;
457
  }
458
 
459
- // --- RENDER LANGUAGE VIEW ---
460
  function renderLanguageView() {
461
  const lang = document.getElementById('lang-select').value;
462
 
463
- // 1. ASR Data Preparation
464
  let asrRows = [];
465
  Object.keys(DATA.asr.by_model).forEach(modName => {
466
  const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
@@ -468,14 +457,14 @@
468
  });
469
  asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
470
 
471
- // 2. TTS Data Preparation
472
  let ttsRows = [];
473
  Object.keys(DATA.tts).forEach(mod => {
474
  const row = DATA.tts[mod].find(r => r.language === lang);
475
  if(row) ttsRows.push({model: mod, ...row});
476
  });
477
 
478
- // 3. SLID Data Preparation
479
  let slidRows = [];
480
  const slidRow = DATA.slid.find(r => r.Language === lang) ||
481
  DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
@@ -483,45 +472,53 @@
483
  if(slidRow) {
484
  const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
485
  models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
486
- slidRows.sort((a,b) => b.val - a.val); // Descending
487
  }
488
 
489
- // --- RENDER SUMMARY CARDS ---
490
  let summaryHTML = "";
491
 
492
- // ASR Winner
493
  if(asrRows.length > 0) {
494
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best ASR Model</div><div class="stat-model">${asrRows[0].model}</div><div class="stat-val">WER: ${fmt(asrRows[0].wer)}%</div></div>`;
495
- } else summaryHTML += `<div class="stat-card"><div class="stat-title">ASR</div><div class="stat-val">No Data</div></div>`;
 
 
 
 
 
496
 
497
- // TTS Winner (Lowest WER for Intelligibility)
498
  if(ttsRows.length > 0) {
499
- // Sort by WER ascending
500
  const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
501
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best TTS (Intelligibility)</div><div class="stat-model">${bestTTS.model}</div><div class="stat-val">WER: ${fmt(bestTTS.wer)}%</div></div>`;
502
- } else summaryHTML += `<div class="stat-card"><div class="stat-title">TTS</div><div class="stat-val">No Data</div></div>`;
 
 
 
 
 
503
 
504
- // SLID Winner
505
  if(slidRows.length > 0) {
506
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best SLID Model</div><div class="stat-model">${slidRows[0].model}</div><div class="stat-val">F1: ${fmt(slidRows[0].val)}%</div></div>`;
507
- } else summaryHTML += `<div class="stat-card"><div class="stat-title">SLID</div><div class="stat-val">No Data</div></div>`;
508
-
 
 
 
 
509
  document.getElementById('lang-summary').innerHTML = summaryHTML;
510
 
511
- // --- RENDER CHARTS ---
512
- updateChart('chart-asr', 'ASR (WER Lower is Better)', asrRows.map(r=>r.model), asrRows.map(r=>r.wer), '#3b82f6');
513
- updateChart('chart-tts', 'TTS (WER Lower is Better)', ttsRows.map(r=>r.model), ttsRows.map(r=>r.wer), '#f97316');
514
- updateChart('chart-slid', 'SLID (F1 Higher is Better)', slidRows.map(r=>r.model), slidRows.map(r=>r.val), '#22c55e');
515
-
516
- // --- RENDER TABLES ---
517
 
518
- // ASR Table
 
519
  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>`;
520
  if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
521
  else {
522
  let currentRank = 1;
523
  asrRows.forEach((r, i) => {
524
- if (i > 0) { if (Math.abs(r.wer - asrRows[i-1].wer) > 0.001) currentRank = i + 1; } else currentRank = 1;
525
  let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
526
  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>`;
527
  });
@@ -529,7 +526,7 @@
529
  h1 += `</tbody></table>`;
530
  document.getElementById('lang-asr-table').innerHTML = h1;
531
 
532
- // TTS Table
533
  let h2 = `<table><thead><tr><th>Model</th>
534
  <th>WER(↓)</th><th>MCD(↓)</th><th>LogF0RMSE(↓)</th><th>SpeechTokenDistance(↓)</th>
535
  <th>PESQ(↑)</th><th>UTMOS(↑)</th><th>SpeechBLEU(↑)</th><th>SpeechBERTScore(↑)</th>
@@ -549,13 +546,13 @@
549
  h2 += `</tbody></table>`;
550
  document.getElementById('lang-tts-table').innerHTML = h2;
551
 
552
- // SLID Table
553
  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>`;
554
  if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
555
  else {
556
  let currentRank = 1;
557
  slidRows.forEach((sc, i) => {
558
- if (i > 0) { if (Math.abs(sc.val - slidRows[i-1].val) > 0.001) currentRank = i + 1; } else currentRank = 1;
559
  let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
560
  h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
561
  });
@@ -564,14 +561,10 @@
564
  document.getElementById('lang-slid-table').innerHTML = h3;
565
  }
566
 
567
- // --- CHART LOGIC ---
568
  function updateChart(canvasId, label, labels, data, color) {
569
  const ctx = document.getElementById(canvasId).getContext('2d');
570
-
571
- // Destroy existing chart if it exists
572
  if(charts[canvasId]) charts[canvasId].destroy();
573
-
574
- if(labels.length === 0) return; // Don't draw empty chart
575
 
576
  charts[canvasId] = new Chart(ctx, {
577
  type: 'bar',
@@ -585,10 +578,11 @@
585
  }]
586
  },
587
  options: {
 
588
  responsive: true,
589
  maintainAspectRatio: false,
590
  plugins: { legend: { display: true } },
591
- scales: { y: { beginAtZero: true } }
592
  }
593
  });
594
  }
@@ -604,12 +598,8 @@
604
 
605
  window.setASRMode = (m) => {
606
  asrMode = m;
607
- ['btn-over','btn-fam','btn-mod'].forEach(b => {
608
- document.getElementById(b).className = "action-btn";
609
- document.getElementById(b).style.background="#e2e8f0"; document.getElementById(b).style.color="#334155";
610
- });
611
- const activeBtn = document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod'));
612
- activeBtn.className = "action-btn bg-blue"; activeBtn.style.color="white"; activeBtn.style.background="";
613
  renderASR();
614
  }
615
 
 
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
13
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
14
 
15
  <style>
 
47
 
48
  /* --- HERO --- */
49
  .hero {
50
+ max-width: 1000px; margin: 60px auto 100px; padding: 0 40px;
51
+ position: relative; text-align: center;
 
 
 
 
 
 
 
 
 
52
  }
53
+ .hero-content { z-index: 2; display: flex; flex-direction: column; align-items: center; }
54
  .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; }
55
  .conf-badge i { color: #cc0000; font-size: 18px; }
56
  .hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
 
62
  .bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
63
 
64
  /* Wave Animation */
65
+ .wave-bg { position: absolute; top: -50px; left: -50%; width: 200%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%); z-index: 0; pointer-events: none; }
66
+ .audio-wave { height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 60px; mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent); }
 
 
 
 
 
 
 
67
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
68
  .bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
69
  @keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
70
 
71
+ /* --- CONTENT --- */
72
  .content-container { width: 80%; max-width: 1600px; margin: 0 auto; }
 
 
73
  .main-card { background: white; border-radius: 24px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; margin-bottom: 60px; }
74
 
75
+ /* Tabs */
76
  .tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto; }
77
  .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; }
78
  .tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
 
80
  .view { display: none !important; animation: fadeIn 0.4s; }
81
  .view.active { display: block !important; }
82
 
83
+ /* Controls */
84
  .controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
85
  select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
 
86
  .rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
87
  .score-desc { font-size: 14px; color: #475569; background: #fff7ed; border-left: 4px solid #f97316; padding: 10px 15px; margin-bottom: 20px; border-radius: 4px; }
88
 
89
+ /* --- NEW ASR BUTTON STYLES --- */
90
+ .mode-btn {
91
+ padding: 8px 20px;
92
+ border-radius: 8px;
93
+ font-weight: 600;
94
+ font-size: 13px;
95
+ cursor: pointer;
96
+ transition: all 0.2s;
97
+ border: 1px solid #e2e8f0;
98
+ background: white;
99
+ color: var(--simba-navy);
100
+ }
101
+ .mode-btn:hover {
102
+ border-color: var(--text-yellow);
103
+ color: var(--text-yellow);
104
+ }
105
+ .mode-btn.active {
106
+ background-color: var(--simba-navy);
107
+ color: white;
108
+ border-color: var(--simba-navy);
109
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
110
+ }
111
+
112
+ /* --- CHARTS --- */
113
+ .chart-wrapper {
114
+ width: 100%;
115
+ /* Max height limits the chart size if small data */
116
+ max-height: 400px;
117
+ margin-bottom: 20px;
118
+ display: none; /* Hidden by default */
119
+ }
120
+ canvas { max-width: 100%; }
121
+
122
+ /* Summary Cards */
123
+ .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px; }
124
+ .stat-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center; transition: 0.3s; }
125
+ .stat-card:hover { transform: translateY(-3px); border-color: var(--text-yellow); }
126
+ .stat-title { font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
127
+ .stat-model { font-size: 18px; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
128
+ .stat-val { font-size: 14px; color: var(--text-yellow); font-weight: 600; }
129
+
130
  /* Table */
131
  .table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
132
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
 
136
  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; }
137
  thead th:first-child { background: #f1f5f9; z-index: 3; }
138
  th:nth-child(2), td:nth-child(2) { text-align: left; }
 
139
  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; }
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  /* Citations */
142
+ .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; }
143
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
144
  .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; }
145
  .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; }
 
201
  <div id="asr" class="view active">
202
  <div class="controls">
203
  <span style="font-weight:700;">View Mode:</span>
204
+ <button id="btn-over" class="mode-btn active" onclick="setASRMode('overview')">Overview</button>
205
+ <button id="btn-fam" class="mode-btn" onclick="setASRMode('family')">By Family</button>
206
+ <button id="btn-mod" class="mode-btn" onclick="setASRMode('model')">By Model</button>
207
 
208
  <div style="flex-grow:1; text-align:right;">
209
  <div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
 
246
  <div id="lang-summary" class="summary-grid"></div>
247
 
248
  <div class="chart-section">
249
+ <h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(Ranked by WER ↓, then CER ↓)</span></h4>
250
+ <div id="wrp-chart-asr" class="chart-wrapper"><canvas id="chart-asr"></canvas></div>
251
  <div id="lang-asr-table" class="table-wrap"></div>
252
  </div>
253
 
254
  <div class="chart-section">
255
+ <h4>Text-to-Speech (TTS) Results</h4>
256
+ <div id="wrp-chart-tts" class="chart-wrapper"><canvas id="chart-tts"></canvas></div>
257
  <div id="lang-tts-table" class="table-wrap"></div>
258
  </div>
259
 
260
  <div class="chart-section">
261
+ <h4>Spoken Language Identification (SLID) Results <span class="rank-info" style="font-weight:normal;">(Ranked by Score ↑)</span></h4>
262
+ <div id="wrp-chart-slid" class="chart-wrapper"><canvas id="chart-slid"></canvas></div>
263
  <div id="lang-slid-table" class="table-wrap"></div>
264
  </div>
265
  </div>
 
293
  </div>
294
 
295
  <script>
 
296
  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'};
297
  let DATA = null;
298
  let asrMode = 'overview';
299
+ let charts = { asr:null, tts:null, slid:null };
300
 
301
  fetch('/api/data').then(r=>r.json()).then(d=>{
302
  DATA = d;
 
330
  return `${name} (${iso})`;
331
  }
332
 
333
+ // --- ASR ---
334
  function renderASR() {
335
  const div = document.getElementById('asr-table');
336
  const title = document.getElementById('asr-title');
 
351
  if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
352
  });
353
 
354
+ modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
 
355
  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>`;
356
 
357
  let currentRank = 1;
358
  modelStats.forEach((m, i) => {
359
+ if (i > 0 && Math.abs(m.wer - modelStats[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
360
+ let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
 
 
 
 
 
 
 
361
  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>`;
362
  });
363
 
 
370
 
371
  const d = DATA.asr.by_family[val];
372
  if(!d) return;
 
373
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
374
 
375
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
 
378
 
379
  let currentRank = 1;
380
  d.data.forEach((r, idx) => {
381
+ if(idx > 0 && Math.abs(r.Avg_WER - d.data[idx-1].Avg_WER) > 0.001) currentRank = idx + 1; else currentRank = 1;
382
+ let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
 
 
 
 
 
 
 
383
  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>`;
384
  d.languages.forEach(l => {
385
  let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
 
403
  div.innerHTML = html;
404
  }
405
 
406
+ // --- TTS ---
407
  function renderTTS() {
408
  const val = document.getElementById('tts-select').value;
409
  document.getElementById('tts-title').innerText = `Model: ${val}`;
 
433
  document.getElementById('tts-table').innerHTML = html;
434
  }
435
 
436
+ // --- SLID ---
437
  function renderSLID() {
438
  let html = `<table><thead><tr><th>Language</th><th>MMS-LID-1024 (↑)</th><th>Simba-SLID (↑)</th></tr></thead><tbody>`;
439
  DATA.slid.forEach(r => {
 
445
  document.getElementById('slid-table').innerHTML = html;
446
  }
447
 
448
+ // --- LANGUAGE VIEW + CHARTS ---
449
  function renderLanguageView() {
450
  const lang = document.getElementById('lang-select').value;
451
 
452
+ // 1. ASR
453
  let asrRows = [];
454
  Object.keys(DATA.asr.by_model).forEach(modName => {
455
  const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
 
457
  });
458
  asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
459
 
460
+ // 2. TTS
461
  let ttsRows = [];
462
  Object.keys(DATA.tts).forEach(mod => {
463
  const row = DATA.tts[mod].find(r => r.language === lang);
464
  if(row) ttsRows.push({model: mod, ...row});
465
  });
466
 
467
+ // 3. SLID
468
  let slidRows = [];
469
  const slidRow = DATA.slid.find(r => r.Language === lang) ||
470
  DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
 
472
  if(slidRow) {
473
  const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
474
  models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
475
+ slidRows.sort((a,b) => b.val - a.val);
476
  }
477
 
478
+ // --- SUMMARY & CHARTS ---
479
  let summaryHTML = "";
480
 
481
+ // ASR
482
  if(asrRows.length > 0) {
483
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best ASR Model</div><div class="stat-model">${asrRows[0].model}</div><div class="stat-val">WER: ${fmt(asrRows[0].wer)}%</div></div>`;
484
+ document.getElementById('wrp-chart-asr').style.display = 'block';
485
+ updateChart('chart-asr', 'ASR WER (Lower is Better)', asrRows.map(r=>r.model), asrRows.map(r=>r.wer), '#3b82f6');
486
+ } else {
487
+ summaryHTML += `<div class="stat-card"><div class="stat-title">ASR</div><div class="stat-val">No Data</div></div>`;
488
+ document.getElementById('wrp-chart-asr').style.display = 'none';
489
+ }
490
 
491
+ // TTS
492
  if(ttsRows.length > 0) {
 
493
  const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
494
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best TTS (Intelligibility)</div><div class="stat-model">${bestTTS.model}</div><div class="stat-val">WER: ${fmt(bestTTS.wer)}%</div></div>`;
495
+ document.getElementById('wrp-chart-tts').style.display = 'block';
496
+ updateChart('chart-tts', 'TTS WER (Lower is Better)', ttsRows.map(r=>r.model), ttsRows.map(r=>r.wer), '#f97316');
497
+ } else {
498
+ summaryHTML += `<div class="stat-card"><div class="stat-title">TTS</div><div class="stat-val">No Data</div></div>`;
499
+ document.getElementById('wrp-chart-tts').style.display = 'none';
500
+ }
501
 
502
+ // SLID
503
  if(slidRows.length > 0) {
504
  summaryHTML += `<div class="stat-card"><div class="stat-title">Best SLID Model</div><div class="stat-model">${slidRows[0].model}</div><div class="stat-val">F1: ${fmt(slidRows[0].val)}%</div></div>`;
505
+ document.getElementById('wrp-chart-slid').style.display = 'block';
506
+ updateChart('chart-slid', 'SLID F1 Score (Higher is Better)', slidRows.map(r=>r.model), slidRows.map(r=>r.val), '#22c55e');
507
+ } else {
508
+ summaryHTML += `<div class="stat-card"><div class="stat-title">SLID</div><div class="stat-val">No Data</div></div>`;
509
+ document.getElementById('wrp-chart-slid').style.display = 'none';
510
+ }
511
  document.getElementById('lang-summary').innerHTML = summaryHTML;
512
 
 
 
 
 
 
 
513
 
514
+ // --- TABLES ---
515
+ // ASR
516
  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>`;
517
  if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
518
  else {
519
  let currentRank = 1;
520
  asrRows.forEach((r, i) => {
521
+ if (i > 0 && Math.abs(r.wer - asrRows[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
522
  let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
523
  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>`;
524
  });
 
526
  h1 += `</tbody></table>`;
527
  document.getElementById('lang-asr-table').innerHTML = h1;
528
 
529
+ // TTS
530
  let h2 = `<table><thead><tr><th>Model</th>
531
  <th>WER(↓)</th><th>MCD(↓)</th><th>LogF0RMSE(↓)</th><th>SpeechTokenDistance(↓)</th>
532
  <th>PESQ(↑)</th><th>UTMOS(↑)</th><th>SpeechBLEU(↑)</th><th>SpeechBERTScore(↑)</th>
 
546
  h2 += `</tbody></table>`;
547
  document.getElementById('lang-tts-table').innerHTML = h2;
548
 
549
+ // SLID
550
  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>`;
551
  if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
552
  else {
553
  let currentRank = 1;
554
  slidRows.forEach((sc, i) => {
555
+ if (i > 0 && Math.abs(sc.val - slidRows[i-1].val) > 0.001) currentRank = i + 1; else currentRank = 1;
556
  let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
557
  h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
558
  });
 
561
  document.getElementById('lang-slid-table').innerHTML = h3;
562
  }
563
 
 
564
  function updateChart(canvasId, label, labels, data, color) {
565
  const ctx = document.getElementById(canvasId).getContext('2d');
 
 
566
  if(charts[canvasId]) charts[canvasId].destroy();
567
+ if(labels.length === 0) return;
 
568
 
569
  charts[canvasId] = new Chart(ctx, {
570
  type: 'bar',
 
578
  }]
579
  },
580
  options: {
581
+ indexAxis: 'y', // Horizontal Bar Chart
582
  responsive: true,
583
  maintainAspectRatio: false,
584
  plugins: { legend: { display: true } },
585
+ scales: { x: { beginAtZero: true } }
586
  }
587
  });
588
  }
 
598
 
599
  window.setASRMode = (m) => {
600
  asrMode = m;
601
+ ['btn-over','btn-fam','btn-mod'].forEach(b => document.getElementById(b).classList.remove('active'));
602
+ document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod')).classList.add('active');
 
 
 
 
603
  renderASR();
604
  }
605