Files changed (7) hide show
  1. app.js +108 -61
  2. config.json +64 -1
  3. demo/demo.css +249 -0
  4. demo/demo.js +448 -0
  5. index.html +6 -0
  6. palette.css +74 -0
  7. style.css +111 -133
app.js CHANGED
@@ -104,12 +104,9 @@ function isExternalModel(model) {
104
  return !model.startsWith(OPT_ORG + "/");
105
  }
106
 
107
- // ─── Derive unique values ─────────────────────────────────────────────────────
108
 
109
  let ALL_MODELS = [];
110
-
111
- // ─── Model Family Keys from config ────────────────────────────────────────────
112
-
113
  const ALL_FAMILY_KEYS = Object.keys(config.model_families || {});
114
  let MODEL_FAMILIES = {};
115
 
@@ -263,14 +260,12 @@ function availableOptions() {
263
  return opts;
264
  }
265
 
266
- // Resolve display label for a filter value
267
  function valueLabel(filterCfg, val) {
268
  if (filterCfg.value_labels && filterCfg.value_labels[val]) return filterCfg.value_labels[val];
269
  if (typeof val === "string") return val.charAt(0).toUpperCase() + val.slice(1);
270
  return String(val);
271
  }
272
 
273
- // Sort models: external (original) first, then optimized
274
  function sortModels(models) {
275
  return [...models].sort((a, b) => {
276
  const aExt = isExternalModel(a) ? 0 : 1;
@@ -279,6 +274,22 @@ function sortModels(models) {
279
  });
280
  }
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  // ─── Populate page from config ────────────────────────────────────────────────
283
 
284
  // Hero
@@ -294,7 +305,7 @@ function renderSidebar() {
294
  html += `<div class="sidebar-item${isActive ? " active" : ""}" data-base-family="${bf}">${bf}</div>`;
295
  if (isActive) {
296
  const variants = BASE_FAMILIES[bf].variants;
297
- const showVariants = variants.length > 1 || (variants.length === 1 && variants[0] !== bf);
298
  if (showVariants) {
299
  variants.forEach(v => {
300
  const isVariantActive = v === filters.variant;
@@ -339,8 +350,8 @@ config.filters.forEach(f => {
339
  filtersBar.appendChild(createFilterGroup(f.label, "filter-" + f.column));
340
  });
341
 
342
- // Metric filter (always last)
343
- filtersBar.appendChild(createFilterGroup("METRIC", "filter-metric"));
344
 
345
  function createFilterGroup(label, id) {
346
  const div = document.createElement("div");
@@ -372,13 +383,12 @@ function renderBtnGroup(container, items, activeValue) {
372
  function populateFilters() {
373
  renderSidebar();
374
 
375
- // Metric buttons
376
- const metricEl = document.getElementById("filter-metric");
377
  renderBtnGroup(metricEl,
378
  config.metrics.map(m => ({ value: m.column, label: m.short || m.column })),
379
  filters.metric
380
  );
381
- metricEl.closest(".filter-group").style.display = (config.metrics.length <= 1 || filters[GROUP_BY] === "all") ? "none" : "";
382
 
383
  updateDependentFilters();
384
  }
@@ -429,7 +439,7 @@ function updateDependentFilters(resetDefaults) {
429
 
430
  // ─── Event binding ────────────────────────────────────────────────────────────
431
 
432
- filtersBar.addEventListener("click", e => {
433
  const btn = e.target.closest(".btn");
434
  if (!btn) return;
435
  const group = btn.closest(".btn-group");
@@ -438,7 +448,10 @@ filtersBar.addEventListener("click", e => {
438
  const key = group.id.replace("filter-", "");
439
  filters[key] = btn.dataset.value;
440
  render();
441
- });
 
 
 
442
 
443
  // ─── Chart ────────────────────────────��───────────────────────────────────────
444
 
@@ -474,6 +487,35 @@ function buildChart(filtered) {
474
  ? scenarios
475
  : [{ label: "", match: {} }];
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  scenarioList.forEach(scenario => {
478
  // Filter rows matching this scenario
479
  const matchRows = gRows.filter(r =>
@@ -506,16 +548,8 @@ function buildChart(filtered) {
506
  const chartBlock = document.createElement("div");
507
  chartBlock.className = "chart-block";
508
 
509
- const heading = document.createElement("h3");
510
- heading.className = "chart-heading";
511
- heading.textContent = groupLabel;
512
- chartBlock.appendChild(heading);
513
-
514
  if (scenario.label) {
515
- const sub = document.createElement("p");
516
- sub.className = "chart-subtitle";
517
- sub.textContent = scenario.label;
518
- chartBlock.appendChild(sub);
519
  }
520
 
521
  const wrap = document.createElement("div");
@@ -683,19 +717,13 @@ function buildTables(filtered, chartsShown) {
683
  html += `<tr class="${rowClass}">`;
684
  colDefs.forEach((c, i) => {
685
  const val = r[c.key];
686
- const fmCls = i === firstMetricIdx ? ' class="first-metric metric-cell"' : ' class="metric-cell"';
687
  if (c.isModel) {
688
- const hfUrl = LINK_PREFIX + val;
689
- const modelColor = MODEL_COLORS[val]?.border || '#888';
690
- html += `<td class="model-cell"><span class="model-dot" style="background:${modelColor}"></span><a href="${hfUrl}" target="_blank" rel="noopener" style="color:${modelColor}">${val}</a></td>`;
691
- } else if (oom) {
692
- html += `<td${c.isMetric ? fmCls : ''}><span class="oom">OOM</span></td>`;
693
- } else if (c.isMetric) {
694
  const sg = groupRowKey(r);
695
- const isBest = val !== null && val !== undefined && val === bestByGroup[sg]?.[c.key];
696
- const display = val === null ? '<span class="oom">OOM</span>' : (typeof val === "number" ? val.toFixed(2) : (val ?? "β€”"));
697
- const modelColor = MODEL_COLORS[r[MODEL_COL]]?.border || '#888';
698
- html += `<td${fmCls}>${isBest ? '<strong style="color: white; opacity: 0.7">' + display + '</strong>' : display}</td>`;
699
  } else {
700
  html += `<td>${val || "β€”"}</td>`;
701
  }
@@ -737,6 +765,29 @@ function buildExperimentSetup() {
737
  });
738
  }
739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
  // ─── Accuracy Table ────────────────────────────────────────────────────��──────
741
 
742
  async function buildAccuracyTable() {
@@ -767,37 +818,38 @@ async function buildAccuracyTable() {
767
  if (vals.length) best[col] = Math.max(...vals);
768
  });
769
 
770
- // Fixed model column
 
 
 
 
 
 
 
 
 
 
 
771
  let fixedHtml = `<table><thead><tr><th>MODEL</th></tr></thead><tbody>`;
772
- rows.forEach(r => {
773
- const model = r[modelCol];
774
- const modelColor = MODEL_COLORS[model]?.border || '#888';
775
- fixedHtml += `<tr><td class="model-cell"><span class="model-dot" style="background:${modelColor}"></span><a href="${LINK_PREFIX}${model}" target="_blank" rel="noopener" style="color:${modelColor}">${model}</a></td></tr>`;
776
- });
777
  fixedHtml += `</tbody></table>`;
778
 
779
- // Scrollable metric columns
780
- let scrollHtml = `<table><thead><tr>`;
781
- scrollHtml += metricCols.map(h => `<th class="metric-cell">${h}</th>`).join("");
782
- scrollHtml += `</tr></thead><tbody>`;
783
- rows.forEach(r => {
784
- scrollHtml += `<tr>`;
785
- metricCols.forEach(col => {
786
- const val = parseFloat(r[col]);
787
- const isBest = !isNaN(val) && val === best[col];
788
- const display = isNaN(val) ? (r[col] || "β€”") : val.toFixed(2);
789
- scrollHtml += `<td class="metric-cell">${isBest ? '<strong style="color: white; opacity: 0.7">' + display + '</strong>' : display}</td>`;
790
- });
791
- scrollHtml += `</tr>`;
792
- });
793
  scrollHtml += `</tbody></table>`;
794
 
 
 
 
 
 
795
  const accTitle = familyCfg.accuracy_title || config.accuracy_title || "Accuracy";
796
  const accUrl = familyCfg.accuracy_url;
797
  const titleHtml = accUrl
798
  ? `<h3><a href="${accUrl}" target="_blank" rel="noopener" class="acc-title-link">${accTitle}</a></h3>`
799
  : `<h3>${accTitle}</h3>`;
800
- card.innerHTML = `${titleHtml}<div class="table-split"><div class="table-split-fixed">${fixedHtml}</div><div class="table-split-scroll">${scrollHtml}</div></div>`;
801
  section.appendChild(card);
802
  }
803
 
@@ -818,13 +870,8 @@ function render() {
818
 
819
  buildChart(filtered);
820
  const chartsShown = charts.length > 0;
821
- // Toggle metric selector visibility
822
- const metricEl = document.getElementById("filter-metric");
823
- if (metricEl) {
824
- metricEl.closest(".filter-group").style.display =
825
- (config.metrics.length <= 1 || !chartsShown) ? "none" : "";
826
- }
827
  buildTables(filtered, chartsShown);
 
828
  buildAccuracyTable();
829
  buildExperimentSetup();
830
  }
 
104
  return !model.startsWith(OPT_ORG + "/");
105
  }
106
 
107
+ // ─── Model families & variants ───────────────────────────────────────────────
108
 
109
  let ALL_MODELS = [];
 
 
 
110
  const ALL_FAMILY_KEYS = Object.keys(config.model_families || {});
111
  let MODEL_FAMILIES = {};
112
 
 
260
  return opts;
261
  }
262
 
 
263
  function valueLabel(filterCfg, val) {
264
  if (filterCfg.value_labels && filterCfg.value_labels[val]) return filterCfg.value_labels[val];
265
  if (typeof val === "string") return val.charAt(0).toUpperCase() + val.slice(1);
266
  return String(val);
267
  }
268
 
 
269
  function sortModels(models) {
270
  return [...models].sort((a, b) => {
271
  const aExt = isExternalModel(a) ? 0 : 1;
 
274
  });
275
  }
276
 
277
+ // ─── Shared HTML builders ────────────────────────────────────────────────────
278
+
279
+ function modelCellHtml(model) {
280
+ const color = MODEL_COLORS[model]?.border || '#888';
281
+ const url = LINK_PREFIX + model;
282
+ return `<td class="model-cell"><span class="model-dot" style="background:${color}"></span><a href="${url}" target="_blank" rel="noopener" style="color:${color}">${model}</a></td>`;
283
+ }
284
+
285
+ function metricCellHtml(val, isBest, extraClass) {
286
+ const cls = extraClass ? ` class="${extraClass}"` : ' class="metric-cell"';
287
+ if (val === null || val === undefined) return `<td${cls}><span class="oom">OOM</span></td>`;
288
+ const display = typeof val === "number" ? val.toFixed(2) : (val || "β€”");
289
+ const content = isBest ? `<strong style="color: white; opacity: 0.7">${display}</strong>` : display;
290
+ return `<td${cls}>${content}</td>`;
291
+ }
292
+
293
  // ─── Populate page from config ────────────────────────────────────────────────
294
 
295
  // Hero
 
305
  html += `<div class="sidebar-item${isActive ? " active" : ""}" data-base-family="${bf}">${bf}</div>`;
306
  if (isActive) {
307
  const variants = BASE_FAMILIES[bf].variants;
308
+ const showVariants = variants.length > 1;
309
  if (showVariants) {
310
  variants.forEach(v => {
311
  const isVariantActive = v === filters.variant;
 
350
  filtersBar.appendChild(createFilterGroup(f.label, "filter-" + f.column));
351
  });
352
 
353
+ // Metric filter β€” rendered inside chart section header, not in filters bar
354
+ const metricGroup = createFilterGroup("METRIC", "filter-metric");
355
 
356
  function createFilterGroup(label, id) {
357
  const div = document.createElement("div");
 
383
  function populateFilters() {
384
  renderSidebar();
385
 
386
+ // Metric buttons (rendered inside chart header)
387
+ const metricEl = metricGroup.querySelector(".btn-group");
388
  renderBtnGroup(metricEl,
389
  config.metrics.map(m => ({ value: m.column, label: m.short || m.column })),
390
  filters.metric
391
  );
 
392
 
393
  updateDependentFilters();
394
  }
 
439
 
440
  // ─── Event binding ────────────────────────────────────────────────────────────
441
 
442
+ function handleFilterClick(e) {
443
  const btn = e.target.closest(".btn");
444
  if (!btn) return;
445
  const group = btn.closest(".btn-group");
 
448
  const key = group.id.replace("filter-", "");
449
  filters[key] = btn.dataset.value;
450
  render();
451
+ }
452
+
453
+ metricGroup.addEventListener("click", handleFilterClick);
454
+ filtersBar.addEventListener("click", handleFilterClick);
455
 
456
  // ─── Chart ────────────────────────────��───────────────────────────────────────
457
 
 
487
  ? scenarios
488
  : [{ label: "", match: {} }];
489
 
490
+ // Insert metric selector into chart section header
491
+ const chartHeader = document.createElement("div");
492
+ chartHeader.className = "chart-header";
493
+
494
+ const headerLeft = document.createElement("div");
495
+ headerLeft.className = "chart-header-left";
496
+
497
+ const headerTitle = document.createElement("h3");
498
+ headerTitle.className = "chart-heading";
499
+ headerTitle.textContent = groupLabel;
500
+ headerLeft.appendChild(headerTitle);
501
+
502
+ const headerSubtitle = document.createElement("p");
503
+ headerSubtitle.className = "chart-subtitle";
504
+ headerLeft.appendChild(headerSubtitle);
505
+
506
+ chartHeader.appendChild(headerLeft);
507
+
508
+ if (config.metrics.length > 1) {
509
+ const metricEl = metricGroup.querySelector(".btn-group");
510
+ renderBtnGroup(metricEl,
511
+ config.metrics.map(m => ({ value: m.column, label: m.short || m.column })),
512
+ filters.metric
513
+ );
514
+ chartHeader.appendChild(metricGroup);
515
+ }
516
+
517
+ section.appendChild(chartHeader);
518
+
519
  scenarioList.forEach(scenario => {
520
  // Filter rows matching this scenario
521
  const matchRows = gRows.filter(r =>
 
548
  const chartBlock = document.createElement("div");
549
  chartBlock.className = "chart-block";
550
 
 
 
 
 
 
551
  if (scenario.label) {
552
+ headerSubtitle.textContent = scenario.label;
 
 
 
553
  }
554
 
555
  const wrap = document.createElement("div");
 
717
  html += `<tr class="${rowClass}">`;
718
  colDefs.forEach((c, i) => {
719
  const val = r[c.key];
 
720
  if (c.isModel) {
721
+ html += modelCellHtml(val);
722
+ } else if (c.isMetric || oom) {
723
+ const cls = i === firstMetricIdx ? "first-metric metric-cell" : "metric-cell";
 
 
 
724
  const sg = groupRowKey(r);
725
+ const isBest = !oom && val !== null && val !== undefined && val === bestByGroup[sg]?.[c.key];
726
+ html += metricCellHtml(oom ? null : val, isBest, cls);
 
 
727
  } else {
728
  html += `<td>${val || "β€”"}</td>`;
729
  }
 
765
  });
766
  }
767
 
768
+ // ─── Demo Section ─────────────────────────────────────────────────────────────
769
+
770
+ let currentDemoKey = null;
771
+
772
+ async function buildDemo() {
773
+ const section = document.getElementById("demo-section");
774
+ const familyCfg = config.model_families?.[activeFamilyKey()] || {};
775
+ const demoData = familyCfg.demo;
776
+ const key = activeFamilyKey();
777
+ if (!demoData) {
778
+ section.innerHTML = "";
779
+ currentDemoKey = null;
780
+ return;
781
+ }
782
+ // Skip rebuild if demo is already rendered for this family
783
+ if (key === currentDemoKey) return;
784
+ currentDemoKey = key;
785
+ section.innerHTML = "";
786
+ if (typeof renderDemo === "function") {
787
+ await renderDemo(demoData, section, OPT_ORG, familyCfg.data_file, MODEL_COLORS);
788
+ }
789
+ }
790
+
791
  // ─── Accuracy Table ────────────────────────────────────────────────────��──────
792
 
793
  async function buildAccuracyTable() {
 
818
  if (vals.length) best[col] = Math.max(...vals);
819
  });
820
 
821
+ // Build metric cells for a row
822
+ function accMetricCells(r) {
823
+ return metricCols.map(col => {
824
+ const val = parseFloat(r[col]);
825
+ const isBest = !isNaN(val) && val === best[col];
826
+ return metricCellHtml(isNaN(val) ? r[col] : val, isBest);
827
+ }).join("");
828
+ }
829
+
830
+ const metricHeaders = metricCols.map(h => `<th class="metric-cell">${h}</th>`).join("");
831
+
832
+ // Fixed model column (desktop)
833
  let fixedHtml = `<table><thead><tr><th>MODEL</th></tr></thead><tbody>`;
834
+ rows.forEach(r => { fixedHtml += `<tr>${modelCellHtml(r[modelCol])}</tr>`; });
 
 
 
 
835
  fixedHtml += `</tbody></table>`;
836
 
837
+ // Scrollable metric columns (desktop)
838
+ let scrollHtml = `<table><thead><tr>${metricHeaders}</tr></thead><tbody>`;
839
+ rows.forEach(r => { scrollHtml += `<tr>${accMetricCells(r)}</tr>`; });
 
 
 
 
 
 
 
 
 
 
 
840
  scrollHtml += `</tbody></table>`;
841
 
842
+ // Combined single table (mobile β€” fully scrollable)
843
+ let combinedHtml = `<table><thead><tr><th>MODEL</th>${metricHeaders}</tr></thead><tbody>`;
844
+ rows.forEach(r => { combinedHtml += `<tr>${modelCellHtml(r[modelCol])}${accMetricCells(r)}</tr>`; });
845
+ combinedHtml += `</tbody></table>`;
846
+
847
  const accTitle = familyCfg.accuracy_title || config.accuracy_title || "Accuracy";
848
  const accUrl = familyCfg.accuracy_url;
849
  const titleHtml = accUrl
850
  ? `<h3><a href="${accUrl}" target="_blank" rel="noopener" class="acc-title-link">${accTitle}</a></h3>`
851
  : `<h3>${accTitle}</h3>`;
852
+ card.innerHTML = `${titleHtml}<div class="table-split"><div class="table-split-fixed">${fixedHtml}</div><div class="table-split-scroll">${scrollHtml}</div></div><div class="table-scroll table-scroll-mobile">${combinedHtml}</div>`;
853
  section.appendChild(card);
854
  }
855
 
 
870
 
871
  buildChart(filtered);
872
  const chartsShown = charts.length > 0;
 
 
 
 
 
 
873
  buildTables(filtered, chartsShown);
874
+ buildDemo();
875
  buildAccuracyTable();
876
  buildExperimentSetup();
877
  }
config.json CHANGED
@@ -141,7 +141,70 @@
141
  "default_device": "orin_nano",
142
  "accuracy_file": "data/acc-Cosmos-Reason2.csv",
143
  "accuracy_title": "Physical AI Bench (PAI-Bench)",
144
- "accuracy_url": "https://huggingface.co/spaces/shi-labs/physical-ai-bench-leaderboard"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  },
146
  "Qwen3.5": {
147
  "data_file": "data/Qwen3.5.csv",
 
141
  "default_device": "orin_nano",
142
  "accuracy_file": "data/acc-Cosmos-Reason2.csv",
143
  "accuracy_title": "Physical AI Bench (PAI-Bench)",
144
+ "accuracy_url": "https://huggingface.co/spaces/shi-labs/physical-ai-bench-leaderboard",
145
+ "demo": {
146
+ "title": "Output Examples",
147
+ "benchmark_match": {
148
+ "type": "video",
149
+ "batch": "1",
150
+ "res": "1280x720",
151
+ "fps": "4"
152
+ },
153
+ "clips": [
154
+ {
155
+ "video": "https://nvidia-cosmos.github.io/cosmos-cookbook/gallery/vs_assets/clip_1_short.mp4",
156
+ "label": "Automotive Description",
157
+ "prompt": "Describe this video in detail.",
158
+ "inference_setup": "1280x720 Β· 12 Frames",
159
+ "citation": "Video source: Wagwani, R., Matveiev, N., Bapst, J., & Gu, J. (2025). Autonomous Vehicle Domain Adaptation Gallery. In NVIDIA Cosmos Cookbook.",
160
+ "citation_url": "https://nvidia-cosmos.github.io/cosmos-cookbook/gallery/av_inference.html",
161
+ "outputs": [
162
+ {
163
+ "model": "nvidia/Cosmos-Reason2-2B",
164
+ "text": "The video captures a scenic highway winding through a rural landscape, with multiple lanes of traffic flowing smoothly. Vehicles of various colors and models are seen traveling in both directions along the road. On the left side of the highway, a white pickup truck is followed by a red sedan, while on the right side, a white sedan is seen driving away from the camera. Further down the road, additional cars can be observed, including a blue hatchback and a red SUV, all maintaining a steady pace. The highway curves gently to the right, bordered by patches of dry grass and sparse trees, suggesting it might be late autumn or early spring. The sky above is clear and bright, indicating favorable weather conditions. On the right side of the highway, there is a pedestrian crossing marked by a yellow and black striped barrier, ensuring safety for walkers. In the distance, a lone figure can be seen walking along the sidewalk parallel to the road, adding a sense of scale and tranquility to the scene. The overall atmosphere is calm and orderly, with no signs of congestion or accidents."
165
+ },
166
+ {
167
+ "model": "embedl/Cosmos-Reason2-2B-W4A16-Edge2-FlashHead",
168
+ "text": "The video captures a dynamic scene of a multi-laned highway winding through a wooded area. The road is divided into two lanes in each direction, separated by a median strip. Several vehicles are traveling along the highway, including a white sedan in the rightmost lane, a red hatchback in the leftmost lane, and a white pickup truck further ahead in the same lane as the hatchback. Additional cars can be seen further down the road, moving in both directions. The surrounding landscape features bare trees and patches of grass, indicative of a winter or early spring season. The road markings are clearly visible, with dashed white lines marking the edges of the lanes and solid yellow lines delineating the lanes. On the right side of the road, there is a warning sign with black and yellow stripes, indicating caution for drivers. The sky is clear, suggesting favorable weather conditions for driving."
169
+ }
170
+ ]
171
+ },
172
+ {
173
+ "video": "https://nvidia-cosmos.github.io/cosmos-cookbook/gallery/assets/kitchen_oranges_input.mp4",
174
+ "label": "Instruction Understanding",
175
+ "prompt": "The robot in the video was given the instruction - store all fruit in the big bowl. Has it successfully completed the task specified in the instruction? Explain your answer in detail.",
176
+ "inference_setup": "1280x720 Β· 32 Frames",
177
+ "citation": "Video source: Wagwani, R., Matveiev, N., Bapst, J., & Gu, J. (2025). Autonomous Vehicle Domain Adaptation Gallery. In NVIDIA Cosmos Cookbook.",
178
+ "citation_url": "https://nvidia-cosmos.github.io/cosmos-cookbook/gallery/av_inference.html",
179
+ "outputs": [
180
+ {
181
+ "model": "nvidia/Cosmos-Reason2-2B",
182
+ "text": "No, the robot has not successfully completed the task. The instruction was to store all fruit in the big bowl, but the robot only moved one fruit into the bowl. There are still other fruits on the countertop that have not been moved into the bowl. Additionally, the robot's actions appear somewhat clumsy and inefficient."
183
+ },
184
+ {
185
+ "model": "embedl/Cosmos-Reason2-2B-W4A16-Edge2-FlashHead",
186
+ "text": "No, the robot has not successfully completed the task. The instruction was to store all fruit in the big bowl, but the video shows that there are still other fruits on the plate and counter. Additionally, the robot's actions involve moving objects around rather than storing them in the bowl."
187
+ }
188
+ ]
189
+ },
190
+ {
191
+ "video": "https://huggingface.co/datasets/embedl/documentation-images/resolve/main/Edge-Inference-Benchmarks/aerial_drone_view.mp4",
192
+ "label": "Search and Rescue",
193
+ "prompt": "Mission: Inspect the drone video for any people or animals that clearly need rescue. Answer format: 1. Rescue needed: Y/N; 2. Description: Describe the scene in detail, explain why rescue is or is not needed.",
194
+ "inference_setup": "1280x720 Β· 24 Frames",
195
+ "outputs": [
196
+ {
197
+ "model": "nvidia/Cosmos-Reason2-2B",
198
+ "text": "1. Rescue needed: N 2. The scene shows a group of people gathered around a table on a grassy field near a road, with a vehicle parked nearby. Individuals appear to be engaged in an activity, possibly a meeting or briefing, with some standing and others seated. There are no visible signs of distress, emergencies, or animals requiring rescue. The environment is calm, and the group seems to be cooperating peacefully."
199
+ },
200
+ {
201
+ "model": "embedl/Cosmos-Reason2-2B-W4A16-Edge2-FlashHead",
202
+ "text": "1. Rescue needed: N; 2. The video shows an aerial view of a serene landscape with a lake, green fields, and a road with a few vehicles and people. There are no visible emergencies, accidents, or animals in distress. The scene appears peaceful and undisturbed."
203
+ }
204
+ ]
205
+ }
206
+ ]
207
+ }
208
  },
209
  "Qwen3.5": {
210
  "data_file": "data/Qwen3.5.csv",
demo/demo.css ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ── Output Example Demo ──────────────────────────────── */
2
+ .demo-section {
3
+ margin: 0 0 var(--space-xl);
4
+ }
5
+
6
+ /* ── Toggle button ───────────────────────────────────── */
7
+ .demo-toggle {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: var(--space-sm);
11
+ background: transparent;
12
+ border: 1px solid var(--border);
13
+ border-radius: 6px;
14
+ color: var(--text-muted);
15
+ font-family: inherit;
16
+ font-size: var(--text-md);
17
+ font-weight: 500;
18
+ padding: var(--space-sm) var(--space-md);
19
+ cursor: pointer;
20
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
21
+ }
22
+
23
+ .demo-toggle:hover {
24
+ color: var(--text);
25
+ background: var(--btn-hover-bg);
26
+ }
27
+
28
+ .demo-toggle.active {
29
+ color: var(--teal);
30
+ border-color: var(--btn-active-border);
31
+ background: var(--btn-active-bg);
32
+ }
33
+
34
+ .demo-toggle-arrow {
35
+ display: inline-block;
36
+ transition: transform 0.2s;
37
+ font-size: var(--text-sm);
38
+ }
39
+
40
+ .demo-toggle.active .demo-toggle-arrow {
41
+ transform: rotate(90deg);
42
+ }
43
+
44
+ /* ── Collapsible content ─────────────────────────────── */
45
+ .demo-content {
46
+ display: grid;
47
+ grid-template-rows: 0fr;
48
+ transition: grid-template-rows 0.3s ease;
49
+ }
50
+
51
+ .demo-content.open {
52
+ grid-template-rows: 1fr;
53
+ }
54
+
55
+ .demo-content-inner {
56
+ overflow: hidden;
57
+ }
58
+
59
+ /* ── Card ────────────────────────────────────────────── */
60
+ .demo-card {
61
+ border-radius: 12px;
62
+ border: 1px solid var(--border);
63
+ padding: var(--space-lg);
64
+ margin-top: var(--space-md);
65
+ position: relative;
66
+ overflow: hidden;
67
+ }
68
+
69
+ /* ── Title row ──────────────────────────────────────── */
70
+ .demo-title-row {
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-between;
74
+ gap: var(--space-md);
75
+ margin-bottom: var(--space-sm);
76
+ }
77
+
78
+ .demo-title-row h3 {
79
+ font-size: var(--text-lg);
80
+ font-weight: 600;
81
+ color: var(--text);
82
+ margin-bottom: 0;
83
+ }
84
+
85
+ .demo-title-left {
86
+ display: flex;
87
+ align-items: baseline;
88
+ gap: var(--space-md);
89
+ }
90
+
91
+ .demo-clip-setup {
92
+ font-size: var(--text-md);
93
+ font-weight: 400;
94
+ color: var(--text-dim);
95
+ }
96
+
97
+ /* ── Device selector ────────────────────────────────── */
98
+ .demo-device-selector {
99
+ display: flex;
100
+ justify-content: center;
101
+ margin-bottom: var(--space-md);
102
+ }
103
+
104
+ /* ── Video with overlay arrows ───────────────────────── */
105
+ .demo-video-wrap {
106
+ position: relative;
107
+ border-radius: 8px;
108
+ overflow: hidden;
109
+ aspect-ratio: 16 / 9;
110
+ margin-bottom: var(--space-sm);
111
+ }
112
+
113
+ .demo-video {
114
+ position: absolute;
115
+ inset: 0;
116
+ width: 100%;
117
+ height: 100%;
118
+ object-fit: cover;
119
+ display: block;
120
+ transition: opacity 0.3s ease;
121
+ }
122
+
123
+ .demo-arrow {
124
+ position: absolute;
125
+ top: 50%;
126
+ transform: translateY(-50%);
127
+ z-index: 2;
128
+ width: 36px;
129
+ height: 36px;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ background: rgba(0, 0, 0, 0.7);
134
+ border: none;
135
+ border-radius: 50%;
136
+ color: rgba(255, 255, 255, 0.8);
137
+ font-size: var(--text-sm);
138
+ cursor: pointer;
139
+ transition: background 0.15s, color 0.15s;
140
+ font-family: inherit;
141
+ }
142
+
143
+ .demo-arrow-left {
144
+ left: var(--space-md);
145
+ }
146
+
147
+ .demo-arrow-right {
148
+ right: var(--space-md);
149
+ }
150
+
151
+ .demo-arrow:hover:not(:disabled) {
152
+ background: rgba(0, 0, 0, 0.6);
153
+ color: #fff;
154
+ }
155
+
156
+ .demo-arrow:disabled {
157
+ opacity: 0.25;
158
+ cursor: default;
159
+ }
160
+
161
+ /* ── Card content ────────────────────────────────────── */
162
+ .demo-prompt {
163
+ font-size: var(--text-md);
164
+ color: var(--text-muted);
165
+ line-height: 1.6;
166
+ margin-bottom: var(--space-md);
167
+ }
168
+
169
+ .demo-prompt strong {
170
+ color: var(--text);
171
+ }
172
+
173
+ .demo-citation {
174
+ font-size: var(--text-sm);
175
+ color: var(--text-dim);
176
+ margin-top: var(--space-md);
177
+ line-height: 1.5;
178
+ }
179
+
180
+ .demo-citation a {
181
+ color: var(--text-dim);
182
+ text-decoration: underline;
183
+ }
184
+
185
+ .demo-citation a:hover {
186
+ color: var(--text-muted);
187
+ }
188
+
189
+ /* ── Side-by-side outputs ────────────────────────────── */
190
+ .demo-outputs {
191
+ display: grid;
192
+ grid-template-columns: 1fr 1fr;
193
+ gap: var(--space-md);
194
+ }
195
+
196
+ .demo-output {
197
+ background: var(--bg);
198
+ border: 1px solid var(--border);
199
+ border-radius: 8px;
200
+ padding: var(--space-md);
201
+ line-height: 1.6;
202
+ font-size: var(--text-md);
203
+ color: var(--text);
204
+ }
205
+
206
+ .demo-output-model {
207
+ display: block;
208
+ font-size: var(--text-md);
209
+ font-weight: 600;
210
+ text-transform: uppercase;
211
+ letter-spacing: 0.06em;
212
+ color: var(--text-dim);
213
+ margin-bottom: var(--space-sm);
214
+ }
215
+
216
+ .demo-output-tps {
217
+ display: block;
218
+ font-size: var(--text-md);
219
+ font-weight: 700;
220
+ color: var(--text-dim);
221
+ margin-bottom: var(--space-sm);
222
+ }
223
+
224
+ .demo-output-tps.oom {
225
+ color: var(--red);
226
+ }
227
+
228
+ .demo-output-text {
229
+ color: var(--text-muted);
230
+ font-size: var(--text-md);
231
+ }
232
+
233
+ /* ── Responsive ──────────────────────────────────────── */
234
+ @media (max-width: 768px) {
235
+ .demo-outputs {
236
+ grid-template-columns: 1fr;
237
+ }
238
+
239
+ .demo-title-row {
240
+ flex-direction: column;
241
+ align-items: flex-start;
242
+ }
243
+
244
+ .demo-arrow {
245
+ width: 28px;
246
+ height: 28px;
247
+ font-size: var(--text-sm);
248
+ }
249
+ }
demo/demo.js ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ── Output Example Demo renderer ─────────────────────────────────────────────
2
+ // Supports multiple clips with left/right arrow navigation, device selector,
3
+ // and streaming text effect at real TPS speed.
4
+
5
+ const DEMO_DEVICE_LABELS = {
6
+ orin_nano_super: "Jetson Orin Nano Super",
7
+ agx_orin: "Jetson AGX Orin",
8
+ agx_thor: "Jetson AGX Thor",
9
+ };
10
+
11
+ const DEMO_DEFAULT_DEVICE = "agx_orin";
12
+ const DEMO_TOKENS_PER_WORD = 1.33;
13
+
14
+ // ── CSV parser (minimal, demo-only) ─────────────────────────────────────────
15
+
16
+ function demoParseCsv(text) {
17
+ const lines = text.replace(/\r/g, "").trim().split("\n");
18
+ const headers = lines[0].split(",");
19
+ return lines.slice(1).map(line => {
20
+ const vals = line.split(",");
21
+ const row = {};
22
+ headers.forEach((h, i) => { row[h] = (vals[i] || "").trim(); });
23
+ return row;
24
+ });
25
+ }
26
+
27
+ // ── Look up TPS from benchmark data ─────────────────────────────────────────
28
+
29
+ function demoLookupTps(csvRows, modelName, device, matchCriteria) {
30
+ const row = csvRows.find(r =>
31
+ r.model === modelName &&
32
+ r.device === device &&
33
+ Object.entries(matchCriteria).every(([col, val]) => r[col] === val)
34
+ );
35
+ if (row) {
36
+ if (!row.tps || row.tps.toUpperCase() === "OOM") return { found: true, tps: null };
37
+ return { found: true, tps: parseFloat(row.tps) };
38
+ }
39
+ const oomRow = csvRows.find(r =>
40
+ r.model === modelName &&
41
+ r.device === device &&
42
+ r.tps && r.tps.toUpperCase() === "OOM"
43
+ );
44
+ if (oomRow) return { found: true, tps: null };
45
+ return { found: false, tps: null };
46
+ }
47
+
48
+ // ── Determine available devices from benchmark data ─────────────────────────
49
+
50
+ function demoAvailableDevices(csvRows, models, matchCriteria) {
51
+ const deviceSet = new Set();
52
+ csvRows.forEach(r => {
53
+ if (!models.includes(r.model)) return;
54
+ if (!Object.entries(matchCriteria).every(([col, val]) => r[col] === val)) return;
55
+ if (r.tps && r.tps.toUpperCase() !== "OOM") deviceSet.add(r.device);
56
+ });
57
+ return Object.keys(DEMO_DEVICE_LABELS).filter(d => deviceSet.has(d));
58
+ }
59
+
60
+ // ── Streaming text animation ────────────────────────────────────────────────
61
+
62
+ function demoStreamText(element, fullText, tps) {
63
+ const handle = { _tid: null, cancel() { clearTimeout(this._tid); } };
64
+ element.textContent = "";
65
+ if (!tps || tps <= 0) {
66
+ element.textContent = fullText;
67
+ return handle;
68
+ }
69
+ const words = fullText.split(/(\s+)/);
70
+ const delayMs = 1000 / (tps / DEMO_TOKENS_PER_WORD);
71
+ let idx = 0;
72
+
73
+ function tick() {
74
+ if (idx < words.length) {
75
+ element.textContent += words[idx];
76
+ idx++;
77
+ handle._tid = setTimeout(tick, delayMs);
78
+ }
79
+ }
80
+ tick();
81
+ return handle;
82
+ }
83
+
84
+ // ── Pre-calculate height for an output box to prevent layout shift ──────────
85
+
86
+ function demoPreCalcHeight(box, textEl, fullText) {
87
+ textEl.textContent = fullText;
88
+ const h = box.offsetHeight;
89
+ box.style.minHeight = h + "px";
90
+ textEl.textContent = "";
91
+ }
92
+
93
+ // ── Main render function ────────────────────────────────────────────────────
94
+
95
+ async function renderDemo(data, container, optimizedOrg, dataFile, modelColors) {
96
+ if (!data) return;
97
+
98
+ // Normalise: support both old single-clip format and new clips array
99
+ let clips = data.clips;
100
+ if (!clips) {
101
+ clips = [{
102
+ video: data.video,
103
+ label: data.title,
104
+ prompt: data.prompt,
105
+ inference_setup: data.inference_setup,
106
+ citation: data.citation,
107
+ citation_url: data.citation_url,
108
+ outputs: data.outputs,
109
+ }];
110
+ }
111
+ if (!clips.length) return;
112
+
113
+ const matchCriteria = data.benchmark_match || {};
114
+
115
+ // Collect all model names across clips
116
+ const allModelNames = [...new Set(clips.flatMap(c => (c.outputs || []).map(o => o.model)))];
117
+
118
+ // Load benchmark CSV
119
+ let csvRows = [];
120
+ if (dataFile) {
121
+ try {
122
+ const csvResp = await fetch(dataFile);
123
+ if (csvResp.ok) csvRows = demoParseCsv(await csvResp.text());
124
+ } catch { /* proceed without */ }
125
+ }
126
+
127
+ const devices = csvRows.length
128
+ ? demoAvailableDevices(csvRows, allModelNames, matchCriteria)
129
+ : [];
130
+
131
+ let selectedDevice = devices.includes(DEMO_DEFAULT_DEVICE)
132
+ ? DEMO_DEFAULT_DEVICE
133
+ : (devices[0] || DEMO_DEFAULT_DEVICE);
134
+
135
+ let currentClipIdx = 0;
136
+ let activeStreams = [];
137
+
138
+ // ── Build DOM skeleton ──────────────────────────────────────────────
139
+
140
+ const section = document.createElement("div");
141
+ section.className = "demo-section";
142
+
143
+ // Toggle button
144
+ const toggle = document.createElement("button");
145
+ toggle.className = "demo-toggle";
146
+ toggle.innerHTML = '<span class="demo-toggle-arrow">&#9654;</span> Show Demo';
147
+
148
+ // Collapsible wrapper
149
+ const content = document.createElement("div");
150
+ content.className = "demo-content";
151
+ const inner = document.createElement("div");
152
+ inner.className = "demo-content-inner";
153
+
154
+ const card = document.createElement("div");
155
+ card.className = "demo-card";
156
+
157
+ // Title row: h3 + device selector
158
+ const titleRow = document.createElement("div");
159
+ titleRow.className = "demo-title-row";
160
+
161
+ const titleLeft = document.createElement("div");
162
+ titleLeft.className = "demo-title-left";
163
+
164
+ const h3 = document.createElement("h3");
165
+ titleLeft.appendChild(h3);
166
+
167
+ const clipSetup = document.createElement("span");
168
+ clipSetup.className = "demo-clip-setup";
169
+ titleLeft.appendChild(clipSetup);
170
+
171
+ titleRow.appendChild(titleLeft);
172
+
173
+ const deviceSelector = document.createElement("div");
174
+ deviceSelector.className = "demo-device-selector";
175
+
176
+ if (devices.length > 1) {
177
+ const deviceGroup = document.createElement("div");
178
+ deviceGroup.className = "btn-group";
179
+
180
+ function renderDeviceButtons() {
181
+ deviceGroup.innerHTML = "";
182
+ devices.forEach(dev => {
183
+ const btn = document.createElement("button");
184
+ btn.className = "btn" + (dev === selectedDevice ? " active" : "");
185
+ btn.dataset.value = dev;
186
+ btn.textContent = DEMO_DEVICE_LABELS[dev] || dev;
187
+ deviceGroup.appendChild(btn);
188
+ });
189
+ }
190
+ renderDeviceButtons();
191
+
192
+ deviceGroup.addEventListener("click", e => {
193
+ const btn = e.target.closest(".btn");
194
+ if (!btn) return;
195
+ const newDevice = btn.dataset.value;
196
+ if (newDevice === selectedDevice) return;
197
+ selectedDevice = newDevice;
198
+ renderDeviceButtons();
199
+ renderOutputs();
200
+ });
201
+
202
+ deviceSelector.appendChild(deviceGroup);
203
+ }
204
+
205
+ card.appendChild(titleRow);
206
+
207
+ // Video with overlay arrows
208
+ const videoWrap = document.createElement("div");
209
+ videoWrap.className = "demo-video-wrap";
210
+
211
+ function makeVideoEl() {
212
+ const v = document.createElement("video");
213
+ v.loop = true;
214
+ v.muted = true;
215
+ v.playsInline = true;
216
+ v.preload = "auto";
217
+ v.className = "demo-video";
218
+ return v;
219
+ }
220
+
221
+ let videoA = makeVideoEl();
222
+ let videoB = makeVideoEl();
223
+ videoB.style.opacity = "0";
224
+ videoWrap.appendChild(videoA);
225
+ videoWrap.appendChild(videoB);
226
+
227
+ const arrowLeft = document.createElement("button");
228
+ arrowLeft.className = "demo-arrow demo-arrow-left";
229
+ arrowLeft.innerHTML = "&#9664;";
230
+ arrowLeft.setAttribute("aria-label", "Previous clip");
231
+
232
+ const arrowRight = document.createElement("button");
233
+ arrowRight.className = "demo-arrow demo-arrow-right";
234
+ arrowRight.innerHTML = "&#9654;";
235
+ arrowRight.setAttribute("aria-label", "Next clip");
236
+
237
+ videoWrap.appendChild(arrowLeft);
238
+ videoWrap.appendChild(arrowRight);
239
+ card.appendChild(videoWrap);
240
+
241
+ let isFirstClip = true;
242
+
243
+ // Device selector (centered under video)
244
+ card.appendChild(deviceSelector);
245
+
246
+ // Prompt (full width)
247
+ const promptEl = document.createElement("div");
248
+ promptEl.className = "demo-prompt";
249
+ card.appendChild(promptEl);
250
+
251
+ // Outputs grid
252
+ const grid = document.createElement("div");
253
+ grid.className = "demo-outputs";
254
+ card.appendChild(grid);
255
+
256
+ // Citation
257
+ const citeEl = document.createElement("p");
258
+ citeEl.className = "demo-citation";
259
+ card.appendChild(citeEl);
260
+
261
+ // ── Arrow visibility ────────────────────────────────────────────────
262
+
263
+ function updateArrows() {
264
+ const showArrows = clips.length > 1;
265
+ arrowLeft.style.display = showArrows ? "" : "none";
266
+ arrowRight.style.display = showArrows ? "" : "none";
267
+ arrowLeft.disabled = false;
268
+ arrowRight.disabled = false;
269
+ }
270
+
271
+ arrowLeft.addEventListener("click", () => {
272
+ currentClipIdx = (currentClipIdx - 1 + clips.length) % clips.length;
273
+ renderClip();
274
+ });
275
+ arrowRight.addEventListener("click", () => {
276
+ currentClipIdx = (currentClipIdx + 1) % clips.length;
277
+ renderClip();
278
+ });
279
+
280
+ // ── Render current clip ───────────────────────────────────────────��─
281
+
282
+ function cancelStreams() {
283
+ activeStreams.forEach(s => s.cancel());
284
+ activeStreams = [];
285
+ }
286
+
287
+ function renderOutputs() {
288
+ cancelStreams();
289
+ grid.innerHTML = "";
290
+ const clip = clips[currentClipIdx];
291
+ const outputs = clip.outputs || [];
292
+
293
+ outputs.forEach(out => {
294
+ const color = modelColors && modelColors[out.model];
295
+ const borderColor = color ? color.border : null;
296
+ const box = document.createElement("div");
297
+ box.className = "demo-output";
298
+ if (borderColor) box.style.borderColor = borderColor;
299
+
300
+ const modelEl = document.createElement("span");
301
+ modelEl.className = "demo-output-model";
302
+ modelEl.textContent = out.model;
303
+ if (borderColor) modelEl.style.color = borderColor;
304
+ box.appendChild(modelEl);
305
+
306
+ const tpsEl = document.createElement("span");
307
+ tpsEl.className = "demo-output-tps";
308
+ if (borderColor) tpsEl.style.color = borderColor;
309
+ box.appendChild(tpsEl);
310
+
311
+ const textEl = document.createElement("span");
312
+ textEl.className = "demo-output-text";
313
+ box.appendChild(textEl);
314
+
315
+ grid.appendChild(box);
316
+
317
+ // Look up TPS
318
+ let tps = null;
319
+ if (csvRows.length) {
320
+ const lookup = demoLookupTps(csvRows, out.model, selectedDevice, matchCriteria);
321
+ tps = lookup.found ? lookup.tps : (out.tps || null);
322
+ } else {
323
+ tps = out.tps || null;
324
+ }
325
+
326
+ if (tps != null) {
327
+ tpsEl.textContent = tps.toFixed(2) + " Tokens / sec";
328
+ tpsEl.classList.remove("oom");
329
+ textEl.style.display = "";
330
+ demoPreCalcHeight(box, textEl, out.text);
331
+ const handle = demoStreamText(textEl, out.text, tps);
332
+ activeStreams.push(handle);
333
+ } else {
334
+ tpsEl.textContent = "OOM";
335
+ tpsEl.classList.add("oom");
336
+ tpsEl.style.color = "";
337
+ textEl.style.display = "none";
338
+ box.style.minHeight = "";
339
+ }
340
+ });
341
+ }
342
+
343
+ function renderClip() {
344
+ const clip = clips[currentClipIdx];
345
+
346
+ // Title: clip label + setup details
347
+ h3.textContent = clip.label || data.title || "Output Examples";
348
+ clipSetup.textContent = clip.inference_setup || "";
349
+ clipSetup.style.display = clip.inference_setup ? "" : "none";
350
+
351
+ // Video β€” crossfade: keep old visible, load new behind, fade when frame ready
352
+ if (clip.video) {
353
+ videoWrap.style.display = "";
354
+ if (isFirstClip) {
355
+ // First clip: just load directly into videoA
356
+ videoA.src = clip.video;
357
+ videoA.style.opacity = "1";
358
+ videoA.load();
359
+ videoA.addEventListener("loadeddata", () => videoA.play(), { once: true });
360
+ isFirstClip = false;
361
+ } else {
362
+ // Crossfade: videoA is the old (visible), videoB gets the new source
363
+ videoB.src = clip.video;
364
+ videoB.style.opacity = "0";
365
+ videoB.load();
366
+
367
+ videoB.addEventListener("loadeddata", function onLoaded() {
368
+ videoB.removeEventListener("loadeddata", onLoaded);
369
+ videoB.play();
370
+
371
+ // Wait for actual frame render before fading
372
+ const startFade = () => {
373
+ videoB.style.opacity = "1";
374
+ videoA.style.opacity = "0";
375
+ // After transition, pause old and swap roles
376
+ setTimeout(() => {
377
+ videoA.pause();
378
+ videoA.removeAttribute("src");
379
+ videoA.load();
380
+ // Swap roles
381
+ const tmp = videoA;
382
+ videoA = videoB;
383
+ videoB = tmp;
384
+ }, 300);
385
+ };
386
+
387
+ if (videoB.requestVideoFrameCallback) {
388
+ videoB.requestVideoFrameCallback(startFade);
389
+ } else {
390
+ startFade();
391
+ }
392
+ });
393
+ }
394
+ } else {
395
+ videoWrap.style.display = "none";
396
+ }
397
+
398
+ // Prompt
399
+ if (clip.prompt) {
400
+ promptEl.innerHTML = "<strong>Prompt:</strong> " + clip.prompt.replace(/\n/g, "<br>");
401
+ promptEl.style.display = "";
402
+ } else {
403
+ promptEl.style.display = "none";
404
+ }
405
+
406
+ // Citation
407
+ if (clip.citation) {
408
+ if (clip.citation_url) {
409
+ citeEl.innerHTML = clip.citation.replace(
410
+ /Autonomous Vehicle Domain Adaptation Gallery/,
411
+ `<a href="${clip.citation_url}" target="_blank" rel="noopener">Autonomous Vehicle Domain Adaptation Gallery</a>`
412
+ );
413
+ } else {
414
+ citeEl.textContent = clip.citation;
415
+ }
416
+ citeEl.style.display = "";
417
+ } else {
418
+ citeEl.style.display = "none";
419
+ }
420
+
421
+ // Outputs
422
+ renderOutputs();
423
+
424
+ updateArrows();
425
+ }
426
+
427
+ // ── Toggle open/close ───────────────────────────────────────────────
428
+
429
+ toggle.addEventListener("click", () => {
430
+ const isOpen = content.classList.toggle("open");
431
+ toggle.classList.toggle("active", isOpen);
432
+ toggle.innerHTML = isOpen
433
+ ? '<span class="demo-toggle-arrow">&#9654;</span> Hide Demo'
434
+ : '<span class="demo-toggle-arrow">&#9654;</span> Show Demo';
435
+
436
+ if (isOpen) {
437
+ renderClip();
438
+ } else {
439
+ cancelStreams();
440
+ }
441
+ });
442
+
443
+ inner.appendChild(card);
444
+ content.appendChild(inner);
445
+ section.appendChild(toggle);
446
+ section.appendChild(content);
447
+ container.appendChild(section);
448
+ }
index.html CHANGED
@@ -4,7 +4,9 @@
4
  <meta charset="utf-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>Edge Inference Benchmarks β€” Embedl</title>
 
7
  <link rel="stylesheet" href="style.css"/>
 
8
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
9
  </head>
10
  <body>
@@ -31,6 +33,9 @@
31
  <!-- Accuracy Table -->
32
  <section id="accuracy-section"></section>
33
 
 
 
 
34
  <!-- Filters -->
35
  <section class="filters-bar" id="filters-bar"></section>
36
 
@@ -88,6 +93,7 @@
88
  </div>
89
  </footer>
90
 
 
91
  <script src="app.js"></script>
92
  </body>
93
  </html>
 
4
  <meta charset="utf-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
  <title>Edge Inference Benchmarks β€” Embedl</title>
7
+ <link rel="stylesheet" href="palette.css"/>
8
  <link rel="stylesheet" href="style.css"/>
9
+ <link rel="stylesheet" href="demo/demo.css"/>
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
11
  </head>
12
  <body>
 
33
  <!-- Accuracy Table -->
34
  <section id="accuracy-section"></section>
35
 
36
+ <!-- Demo Section -->
37
+ <section id="demo-section"></section>
38
+
39
  <!-- Filters -->
40
  <section class="filters-bar" id="filters-bar"></section>
41
 
 
93
  </div>
94
  </footer>
95
 
96
+ <script src="demo/demo.js"></script>
97
  <script src="app.js"></script>
98
  </body>
99
  </html>
palette.css ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ── Palette & Design Tokens ──────────────────────────── */
2
+ :root {
3
+ /* Colors */
4
+ --bg: #0B1527;
5
+ --bg-opaque: rgba(11, 21, 39, 0.45);
6
+ --teal: #58b1c3;
7
+ --green: #6fcf97;
8
+ --red: #ff4d6d;
9
+ --pink: #ff6ec7;
10
+ --purple: #9b5de5;
11
+ --blue: #007F9E;
12
+ --neutral: #98A6AF;
13
+ --text: #e8e8e8;
14
+ --text-muted: #8899aa;
15
+ --text-dim: #5a6a7a;
16
+ --border: rgba(255, 255, 255, 0.15);
17
+ --btn-hover-bg: rgba(255, 255, 255, 0.03);
18
+ --btn-active-bg: rgba(88, 177, 195, 0.12);
19
+ --btn-active-border: rgba(88, 177, 195, 0.3);
20
+ --row-hover-bg: rgba(255, 255, 255, 0.02);
21
+ --row-border: rgba(255, 255, 255, 0.03);
22
+ --code-bg: rgba(88, 177, 195, 0.1);
23
+ --tooltip-bg: #2c3e50;
24
+ --tooltip-text: #f1f1f1;
25
+ --tooltip-body: #d9d9d9;
26
+
27
+ /* Typography scale */
28
+ --text-sm: 0.75rem; /* secondary text, small UI */
29
+ --text-md: 1rem; /* body text, subtitles, descriptions */
30
+ --text-lg: 1.25rem; /* section headings */
31
+
32
+ /* Spacing scale */
33
+ --space-xs: 0.25rem; /* tiny gaps: legend rows, label margins */
34
+ --space-sm: 0.5rem; /* small: icon gaps, dot gaps, table cell vertical */
35
+ --space-ms: 0.75rem; /* medium-small: table cell horizontal, sidebar padding */
36
+ --space-md: 1rem; /* standard: flex gaps, element margins */
37
+ --space-lg: 1.5rem; /* section internal spacing */
38
+ --space-xl: 2rem; /* major section padding */
39
+ --space-2xl: 3rem; /* large section breaks */
40
+ }
41
+
42
+ /* ── Reset & Base Typography ─────────────────────────── */
43
+ *, *::before, *::after {
44
+ box-sizing: border-box;
45
+ margin: 0;
46
+ padding: 0;
47
+ }
48
+
49
+ html {
50
+ font-size: 20px;
51
+ background: radial-gradient(
52
+ 60rem 40rem at 50% -45px,
53
+ rgba(0, 226, 213, 0.35),
54
+ rgba(18, 208, 254, 0.25) 30%,
55
+ transparent 70%
56
+ ), var(--bg);
57
+ }
58
+
59
+ body {
60
+ font-family: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
61
+ color: var(--text);
62
+ font-size: var(--text-md);
63
+ line-height: 1.6;
64
+ -webkit-font-smoothing: antialiased;
65
+ position: relative;
66
+ }
67
+
68
+ code {
69
+ background: var(--code-bg);
70
+ color: var(--teal);
71
+ padding: 0.15em 0.45em;
72
+ border-radius: 4px;
73
+ font-size: 0.95em;
74
+ }
style.css CHANGED
@@ -1,64 +1,3 @@
1
- /* ── Palette ──────────────────────────────────────────── */
2
- :root {
3
- --bg: #0B1527;
4
- --bg-opaque: rgba(11, 21, 39, 0.45);
5
- --bg-surface: #1a1a2e;
6
- --teal: #58b1c3;
7
- --green: #6fcf97;
8
- --red: #ff4d6d;
9
- --pink: #ff6ec7;
10
- --purple: #9b5de5;
11
- --blue: #007F9E;
12
- --neutral: #98A6AF;
13
- --text: #e8e8e8;
14
- --text-muted: #8899aa;
15
- --text-dim: #5a6a7a;
16
- --border: rgba(255, 255, 255, 0.15);
17
- --btn-hover-bg: rgba(255, 255, 255, 0.03);
18
- --btn-active-bg: rgba(88, 177, 195, 0.12);
19
- --btn-active-border: rgba(88, 177, 195, 0.3);
20
- --sidebar-active-bg: rgba(88, 177, 195, 0.04);
21
- --row-hover-bg: rgba(255, 255, 255, 0.02);
22
- --row-border: rgba(255, 255, 255, 0.03);
23
- --code-bg: rgba(88, 177, 195, 0.1);
24
- --tooltip-bg: #2c3e50;
25
- --tooltip-text: #f1f1f1;
26
- --tooltip-body: #d9d9d9;
27
- }
28
-
29
- /* ── Reset & Base ─────────────────────────────────────── */
30
- *, *::before, *::after {
31
- box-sizing: border-box;
32
- margin: 0;
33
- padding: 0;
34
- }
35
-
36
- html {
37
- font-size: 16px;
38
- background: radial-gradient(
39
- 60rem 40rem at 50% -45px,
40
- rgba(0, 226, 213, 0.35),
41
- rgba(18, 208, 254, 0.25) 30%,
42
- transparent 70%
43
- ), var(--bg);
44
- }
45
-
46
- body {
47
- font-family: "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
48
- color: var(--text);
49
- line-height: 1.6;
50
- -webkit-font-smoothing: antialiased;
51
- position: relative;
52
- }
53
-
54
- code {
55
- background: var(--code-bg);
56
- color: var(--teal);
57
- padding: 0.15em 0.45em;
58
- border-radius: 4px;
59
- font-size: 0.95em;
60
- }
61
-
62
  /* ── Page Layout ─────────────────────────────────────── */
63
  .page {
64
  position: relative;
@@ -66,7 +5,7 @@ code {
66
  display: flex;
67
  flex-direction: row;
68
  min-height: 100vh;
69
- max-width: 1440px;
70
  margin: 0 auto;
71
  }
72
 
@@ -75,7 +14,7 @@ code {
75
  width: 280px;
76
  max-width: 300px;
77
  flex-shrink: 0;
78
- padding: 2rem 0;
79
  position: sticky;
80
  top: 0;
81
  height: 100vh;
@@ -84,7 +23,7 @@ code {
84
 
85
  .sidebar-logo {
86
  display: block;
87
- padding: 0 1.25rem 3.25rem;
88
  }
89
 
90
  .sidebar-nav {
@@ -94,8 +33,8 @@ code {
94
 
95
  .sidebar-item {
96
  display: block;
97
- padding: 0.5rem 1.25rem;
98
- font-size: 1.25rem;
99
  font-weight: 400;
100
  color: var(--text-dim);
101
  text-decoration: none;
@@ -115,8 +54,8 @@ code {
115
 
116
  .sidebar-variant {
117
  display: block;
118
- padding: 0.35rem 1.25rem 0.35rem 2.25rem;
119
- font-size: 1.1rem;
120
  font-weight: 400;
121
  color: var(--text-dim);
122
  cursor: pointer;
@@ -140,15 +79,14 @@ code {
140
  }
141
 
142
  .main-inner {
143
- max-width: 1100px;
144
  width: 100%;
145
- padding: 0 2rem 2rem;
146
  }
147
 
148
  /* ── Hero ────────────────────────────────────────────── */
149
  .hero {
150
  position: relative;
151
- padding: 2rem 0 2.5rem;
152
  }
153
 
154
  .hero-badge {
@@ -156,16 +94,16 @@ code {
156
  padding: 1px;
157
  border-radius: 999px;
158
  background: linear-gradient(90deg, var(--teal), var(--blue));
159
- margin-bottom: 1rem;
160
  }
161
 
162
  .hero-badge span {
163
  display: inline-block;
164
- padding: 0.35rem 1rem;
165
  border-radius: 999px;
166
  background: var(--bg-opaque);
167
  color: var(--teal);
168
- font-size: 1rem;
169
  font-weight: 600;
170
  letter-spacing: 0.03em;
171
  text-transform: uppercase;
@@ -174,7 +112,7 @@ code {
174
  .hero h1 {
175
  font-size: clamp(1.8rem, 3.5vw, 3.0rem);
176
  font-weight: 700;
177
- margin-bottom: 0.5rem;
178
  letter-spacing: -0.02em;
179
  }
180
 
@@ -184,8 +122,7 @@ code {
184
 
185
  .hero-sub {
186
  color: var(--text-muted);
187
- font-size: 1.1rem;
188
- max-width: 620px;
189
  line-height: 1.6;
190
  }
191
 
@@ -193,19 +130,19 @@ code {
193
  .filters-bar {
194
  display: flex;
195
  flex-wrap: wrap;
196
- gap: 1.25rem;
197
- padding: 1rem 0;
198
  top: 0;
199
  z-index: 10;
200
  }
201
 
202
  .filter-group label {
203
  display: block;
204
- font-size: 0.8rem;
205
  font-weight: 600;
206
  letter-spacing: 0.08em;
207
  color: var(--text-dim);
208
- margin-bottom: 0.25rem;
209
  }
210
 
211
  .btn-group {
@@ -216,9 +153,9 @@ code {
216
  background: transparent;
217
  border: 1px solid var(--border);
218
  color: var(--text-muted);
219
- font-size: 1rem;
220
  font-weight: 400;
221
- padding: 0.45rem 1rem;
222
  cursor: pointer;
223
  transition: all 0.15s;
224
  }
@@ -250,21 +187,62 @@ code {
250
  }
251
 
252
  /* ── Chart ───────────────────────────────────────────── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  .chart-block {
254
- margin: 1.5rem 0;
255
  }
256
 
257
  .chart-heading {
258
- font-size: 1.25rem;
259
  font-weight: 600;
260
  color: var(--text);
261
- margin-bottom: 0.25rem;
262
  }
263
 
264
  .chart-subtitle {
265
- font-size: 1rem;
266
  color: var(--text-muted);
267
- margin-bottom: 0.75rem;
268
  }
269
 
270
  .chart-wrap {
@@ -273,15 +251,15 @@ code {
273
 
274
  /* ── Tables ──────────────────────────────────────────── */
275
  .table-card {
276
- margin: 1.5rem 0 2rem;
277
  }
278
 
279
  .table-card h3 {
280
- font-size: 1.15rem;
281
  font-weight: 600;
282
  color: var(--text);
283
- margin-bottom: 0.5rem;
284
- padding-bottom: 0.5rem;
285
  border-bottom: 1px solid var(--border);
286
  }
287
 
@@ -312,22 +290,22 @@ code {
312
  table {
313
  width: 100%;
314
  border-collapse: collapse;
315
- font-size: 1rem;
316
  }
317
 
318
  thead th {
319
  text-align: left;
320
  font-weight: 600;
321
- font-size: 0.8rem;
322
  letter-spacing: 0.05em;
323
  color: var(--text-dim);
324
- padding: 0.5rem 0.75rem;
325
  border-bottom: 1px solid var(--border);
326
  white-space: nowrap;
327
  }
328
 
329
  tbody td {
330
- padding: 0.5rem 0.75rem;
331
  border-bottom: 1px solid var(--row-border);
332
  white-space: nowrap;
333
  color: var(--text-muted);
@@ -349,7 +327,7 @@ tbody tr:hover {
349
  .model-cell {
350
  display: flex;
351
  align-items: center;
352
- gap: 0.5rem;
353
  font-weight: 500;
354
  color: var(--text);
355
  }
@@ -372,7 +350,7 @@ tbody tr:hover {
372
  }
373
 
374
  th.first-metric, td.first-metric {
375
- padding-left: 2.5rem;
376
  }
377
 
378
  .oom {
@@ -405,11 +383,15 @@ tbody tr.row-group-break td {
405
  scrollbar-color: var(--text-dim) var(--border);
406
  scrollbar-width: thin;
407
  }
 
 
 
 
408
  #accuracy-section:empty {
409
  margin-bottom: 0;
410
  }
411
  #accuracy-section {
412
- margin-bottom: 2.5rem;
413
  }
414
 
415
  .acc-title-link {
@@ -422,15 +404,15 @@ tbody tr.row-group-break td {
422
  }
423
 
424
  .legend-section {
425
- margin: 2rem 0;
426
- padding-top: 1.5rem;
427
  }
428
 
429
  .legend-grid {
430
  display: grid;
431
  grid-template-columns: 1fr;
432
- gap: 0.25rem;
433
- font-size: 0.8rem;
434
  color: var(--text-dim);
435
  }
436
 
@@ -440,37 +422,37 @@ tbody tr.row-group-break td {
440
  }
441
 
442
  .experiment-setup {
443
- margin-top: 0.75rem;
444
- font-size: 0.95rem;
445
  color: var(--text-dim);
446
  font-style: italic;
447
  line-height: 1.6;
448
  }
449
 
450
  .experiment-setup p {
451
- margin-bottom: 0.25rem;
452
  }
453
 
454
  /* ── Footer ──────────────────────────────────────────── */
455
  .footer {
456
- padding: 2.5rem 0;
457
- margin-top: 2rem;
458
  }
459
 
460
  .footer-inner {
461
- max-width: 1440px;
462
  margin: 0 auto;
463
- padding: 0 2rem;
464
  }
465
 
466
  .footer-grid {
467
  display: flex;
468
- gap: 3rem;
469
  flex-wrap: wrap;
470
  }
471
 
472
  .footer-col {
473
- font-size: 1rem;
474
  color: var(--text-dim);
475
  line-height: 1.7;
476
  }
@@ -480,7 +462,7 @@ tbody tr.row-group-break td {
480
  }
481
 
482
  .footer-col-title {
483
- font-size: 0.85rem;
484
  font-weight: 600;
485
  text-transform: uppercase;
486
  letter-spacing: 0.08em;
@@ -489,13 +471,13 @@ tbody tr.row-group-break td {
489
 
490
  .footer-logo-img {
491
  display: block;
492
- margin-bottom: 0.5rem;
493
  }
494
 
495
  .footer-logo {
496
- font-size: 1rem;
497
  color: var(--text-muted);
498
- margin-bottom: 1.2rem;
499
  }
500
 
501
  .footer-logo a {
@@ -509,7 +491,7 @@ tbody tr.row-group-break td {
509
 
510
  .footer-social {
511
  display: flex;
512
- gap: 0.75rem;
513
  }
514
 
515
  .footer-social a {
@@ -524,7 +506,7 @@ tbody tr.row-group-break td {
524
  }
525
 
526
  /* ── Responsive ──────────────────────────────────────── */
527
- @media (max-width: 768px) {
528
  .page {
529
  flex-direction: column;
530
  }
@@ -536,20 +518,20 @@ tbody tr.row-group-break td {
536
  position: static;
537
  border-right: none;
538
  border-bottom: 1px solid var(--border);
539
- padding: 0.75rem 0;
540
  }
541
 
542
  .sidebar-nav {
543
  flex-direction: row;
544
  flex-wrap: wrap;
545
- padding: 0 0.75rem;
 
546
  }
547
 
548
  .sidebar-item {
549
  border-left: none;
550
  border-bottom: 2px solid transparent;
551
- padding: 0.4rem 0.75rem;
552
- font-size: 0.9rem;
553
  }
554
 
555
  .sidebar-item.active {
@@ -560,8 +542,8 @@ tbody tr.row-group-break td {
560
  .sidebar-variant {
561
  border-left: none;
562
  border-bottom: 2px solid transparent;
563
- padding: 0.3rem 0.75rem;
564
- font-size: 0.8rem;
565
  }
566
 
567
  .sidebar-variant.active {
@@ -569,19 +551,15 @@ tbody tr.row-group-break td {
569
  }
570
 
571
  .main-inner {
572
- padding: 0 1rem 2rem;
573
  }
574
 
575
- .filters-bar {
576
- gap: 0.75rem;
577
  }
578
 
579
- .btn {
580
- font-size: 0.85rem;
581
- padding: 0.35rem 0.7rem;
582
  }
583
 
584
- .chart-wrap {
585
- height: 260px;
586
- }
587
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  /* ── Page Layout ─────────────────────────────────────── */
2
  .page {
3
  position: relative;
 
5
  display: flex;
6
  flex-direction: row;
7
  min-height: 100vh;
8
+ max-width: 1640px;
9
  margin: 0 auto;
10
  }
11
 
 
14
  width: 280px;
15
  max-width: 300px;
16
  flex-shrink: 0;
17
+ padding: var(--space-xl) 0;
18
  position: sticky;
19
  top: 0;
20
  height: 100vh;
 
23
 
24
  .sidebar-logo {
25
  display: block;
26
+ padding: 0 var(--space-md) var(--space-2xl);
27
  }
28
 
29
  .sidebar-nav {
 
33
 
34
  .sidebar-item {
35
  display: block;
36
+ padding: var(--space-sm) var(--space-md);
37
+ font-size: var(--text-lg);
38
  font-weight: 400;
39
  color: var(--text-dim);
40
  text-decoration: none;
 
54
 
55
  .sidebar-variant {
56
  display: block;
57
+ padding: var(--space-sm) var(--space-md) var(--space-sm) var(--space-xl);
58
+ font-size: var(--text-md);
59
  font-weight: 400;
60
  color: var(--text-dim);
61
  cursor: pointer;
 
79
  }
80
 
81
  .main-inner {
 
82
  width: 100%;
83
+ padding: 0 var(--space-xl) var(--space-xl);
84
  }
85
 
86
  /* ── Hero ────────────────────────────────────────────── */
87
  .hero {
88
  position: relative;
89
+ padding: var(--space-xl) 0;
90
  }
91
 
92
  .hero-badge {
 
94
  padding: 1px;
95
  border-radius: 999px;
96
  background: linear-gradient(90deg, var(--teal), var(--blue));
97
+ margin-bottom: var(--space-md);
98
  }
99
 
100
  .hero-badge span {
101
  display: inline-block;
102
+ padding: var(--space-sm) var(--space-md);
103
  border-radius: 999px;
104
  background: var(--bg-opaque);
105
  color: var(--teal);
106
+ font-size: var(--text-md);
107
  font-weight: 600;
108
  letter-spacing: 0.03em;
109
  text-transform: uppercase;
 
112
  .hero h1 {
113
  font-size: clamp(1.8rem, 3.5vw, 3.0rem);
114
  font-weight: 700;
115
+ margin-bottom: var(--space-sm);
116
  letter-spacing: -0.02em;
117
  }
118
 
 
122
 
123
  .hero-sub {
124
  color: var(--text-muted);
125
+ font-size: var(--text-md);
 
126
  line-height: 1.6;
127
  }
128
 
 
130
  .filters-bar {
131
  display: flex;
132
  flex-wrap: wrap;
133
+ gap: var(--space-md);
134
+ padding: var(--space-md) 0 var(--space-2xl);
135
  top: 0;
136
  z-index: 10;
137
  }
138
 
139
  .filter-group label {
140
  display: block;
141
+ font-size: var(--text-sm);
142
  font-weight: 600;
143
  letter-spacing: 0.08em;
144
  color: var(--text-dim);
145
+ margin-bottom: var(--space-xs);
146
  }
147
 
148
  .btn-group {
 
153
  background: transparent;
154
  border: 1px solid var(--border);
155
  color: var(--text-muted);
156
+ font-size: var(--text-md);
157
  font-weight: 400;
158
+ padding: var(--space-sm) var(--space-md);
159
  cursor: pointer;
160
  transition: all 0.15s;
161
  }
 
187
  }
188
 
189
  /* ── Chart ───────────────────────────────────────────── */
190
+ .chart-header {
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ align-items: center;
194
+ justify-content: space-between;
195
+ gap: var(--space-sm) var(--space-md);
196
+ margin-bottom: var(--space-xs);
197
+ }
198
+
199
+ .chart-header-left {
200
+ display: flex;
201
+ align-items: baseline;
202
+ gap: var(--space-md);
203
+ }
204
+
205
+ .chart-header .filter-group {
206
+ margin: 0;
207
+ }
208
+
209
+ .chart-header .filter-group label {
210
+ display: none;
211
+ }
212
+
213
+ .chart-header .btn,
214
+ .demo-device-selector .btn {
215
+ border: 0;
216
+ background: transparent;
217
+ }
218
+
219
+ .chart-header .btn:hover,
220
+ .demo-device-selector .btn:hover {
221
+ color: var(--text);
222
+ background: transparent;
223
+ }
224
+
225
+ .chart-header .btn.active,
226
+ .demo-device-selector .btn.active {
227
+ background: transparent;
228
+ border-color: var(--btn-active-border);
229
+ }
230
+
231
  .chart-block {
232
+ margin: var(--space-lg) 0;
233
  }
234
 
235
  .chart-heading {
236
+ font-size: var(--text-lg);
237
  font-weight: 600;
238
  color: var(--text);
239
+ margin-bottom: 0;
240
  }
241
 
242
  .chart-subtitle {
243
+ font-size: var(--text-md);
244
  color: var(--text-muted);
245
+ margin-bottom: 0;
246
  }
247
 
248
  .chart-wrap {
 
251
 
252
  /* ── Tables ──────────────────────────────────────────── */
253
  .table-card {
254
+ margin: var(--space-lg) 0;
255
  }
256
 
257
  .table-card h3 {
258
+ font-size: var(--text-md);
259
  font-weight: 600;
260
  color: var(--text);
261
+ margin-bottom: var(--space-sm);
262
+ padding-bottom: var(--space-sm);
263
  border-bottom: 1px solid var(--border);
264
  }
265
 
 
290
  table {
291
  width: 100%;
292
  border-collapse: collapse;
293
+ font-size: var(--text-md);
294
  }
295
 
296
  thead th {
297
  text-align: left;
298
  font-weight: 600;
299
+ font-size: var(--text-sm);
300
  letter-spacing: 0.05em;
301
  color: var(--text-dim);
302
+ padding: var(--space-sm) var(--space-ms);
303
  border-bottom: 1px solid var(--border);
304
  white-space: nowrap;
305
  }
306
 
307
  tbody td {
308
+ padding: var(--space-sm) var(--space-ms);
309
  border-bottom: 1px solid var(--row-border);
310
  white-space: nowrap;
311
  color: var(--text-muted);
 
327
  .model-cell {
328
  display: flex;
329
  align-items: center;
330
+ gap: var(--space-sm);
331
  font-weight: 500;
332
  color: var(--text);
333
  }
 
350
  }
351
 
352
  th.first-metric, td.first-metric {
353
+ padding-left: var(--space-xl);
354
  }
355
 
356
  .oom {
 
383
  scrollbar-color: var(--text-dim) var(--border);
384
  scrollbar-width: thin;
385
  }
386
+
387
+ .table-scroll-mobile {
388
+ display: none;
389
+ }
390
  #accuracy-section:empty {
391
  margin-bottom: 0;
392
  }
393
  #accuracy-section {
394
+ margin-bottom: var(--space-xl);
395
  }
396
 
397
  .acc-title-link {
 
404
  }
405
 
406
  .legend-section {
407
+ margin: var(--space-xl) 0;
408
+ padding-top: var(--space-lg);
409
  }
410
 
411
  .legend-grid {
412
  display: grid;
413
  grid-template-columns: 1fr;
414
+ gap: var(--space-xs);
415
+ font-size: var(--text-sm);
416
  color: var(--text-dim);
417
  }
418
 
 
422
  }
423
 
424
  .experiment-setup {
425
+ margin-top: var(--space-md);
426
+ font-size: var(--text-md);
427
  color: var(--text-dim);
428
  font-style: italic;
429
  line-height: 1.6;
430
  }
431
 
432
  .experiment-setup p {
433
+ margin-bottom: var(--space-xs);
434
  }
435
 
436
  /* ── Footer ──────────────────────────────────────────── */
437
  .footer {
438
+ padding: var(--space-xl) 0;
439
+ margin-top: var(--space-xl);
440
  }
441
 
442
  .footer-inner {
443
+ max-width: 1640px;
444
  margin: 0 auto;
445
+ padding: 0 var(--space-xl);
446
  }
447
 
448
  .footer-grid {
449
  display: flex;
450
+ gap: var(--space-2xl);
451
  flex-wrap: wrap;
452
  }
453
 
454
  .footer-col {
455
+ font-size: var(--text-md);
456
  color: var(--text-dim);
457
  line-height: 1.7;
458
  }
 
462
  }
463
 
464
  .footer-col-title {
465
+ font-size: var(--text-sm);
466
  font-weight: 600;
467
  text-transform: uppercase;
468
  letter-spacing: 0.08em;
 
471
 
472
  .footer-logo-img {
473
  display: block;
474
+ margin-bottom: var(--space-sm);
475
  }
476
 
477
  .footer-logo {
478
+ font-size: var(--text-md);
479
  color: var(--text-muted);
480
+ margin-bottom: var(--space-md);
481
  }
482
 
483
  .footer-logo a {
 
491
 
492
  .footer-social {
493
  display: flex;
494
+ gap: var(--space-md);
495
  }
496
 
497
  .footer-social a {
 
506
  }
507
 
508
  /* ── Responsive ──────────────────────────────────────── */
509
+ @media (max-width: 900px) {
510
  .page {
511
  flex-direction: column;
512
  }
 
518
  position: static;
519
  border-right: none;
520
  border-bottom: 1px solid var(--border);
521
+ padding: var(--space-sm) 0;
522
  }
523
 
524
  .sidebar-nav {
525
  flex-direction: row;
526
  flex-wrap: wrap;
527
+ align-items: center;
528
+ padding: 0 var(--space-sm);
529
  }
530
 
531
  .sidebar-item {
532
  border-left: none;
533
  border-bottom: 2px solid transparent;
534
+ padding: var(--space-sm) var(--space-md);
 
535
  }
536
 
537
  .sidebar-item.active {
 
542
  .sidebar-variant {
543
  border-left: none;
544
  border-bottom: 2px solid transparent;
545
+ padding: var(--space-sm) var(--space-md);
546
+ font-size: var(--text-sm);
547
  }
548
 
549
  .sidebar-variant.active {
 
551
  }
552
 
553
  .main-inner {
554
+ padding: 0 var(--space-md) var(--space-xl);
555
  }
556
 
557
+ .table-split {
558
+ display: none;
559
  }
560
 
561
+ .table-scroll-mobile {
562
+ display: block;
 
563
  }
564
 
 
 
 
565
  }