Spaces:
Running
Running
File size: 23,151 Bytes
bfe13e2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | const $ = (id) => document.getElementById(id);
const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const number = (v) => v != null && Number.isFinite(Number(v)) ? Number(v) : null;
const mean = (values) => { const a = values.map(number).filter((n) => n !== null); return a.length ? a.reduce((s,n) => s+n,0)/a.length : null; };
const fmt = (v, digits=3) => number(v) === null ? 'Not reported' : Number(v).toLocaleString('en-US',{maximumFractionDigits:digits,minimumFractionDigits:digits});
const countFmt = (v) => Number(v).toLocaleString('en-US');
const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
const cols = [
{key:'compression_rate',label:'Average rate ↓',digits:4,visible:true},
{key:'overall_rate',label:'Overall rate ↓',digits:4,visible:false},
{key:'compression_time',label:'Encode ns/point ↓',digits:2,visible:true},
{key:'decompression_time',label:'Decode ns/point ↓',digits:2,visible:true},
{key:'compressed_bytes',label:'Compressed bytes',digits:0,visible:false},
{key:'original_bytes',label:'Original bytes',digits:0,visible:false},
];
let snapshot, records=[], methods=new Map(), datasets=new Map(), currentRows=[], activeTab='overall';
let sort={key:'compression_rate',direction:1};
let selectedMethod=null;
const state = () => ({runtime:$('runtime').value,dataset:$('dataset').value,dtype:$('dtype').value,precision:$('precision').value,family:$('family').value,mode:$('row-mode').value,search:$('search').value.trim().toLowerCase(),complete:$('complete-only').checked});
function scopeKey() { const s=state(); return s.dtype==='overall'?'overall':s.dtype==='int'?'integer':s.precision==='fixed'?'fixed_float':s.precision==='non_fixed'?'nonfixed_float':'float'; }
function normalizeRecord(r) {
return { ...r,configuration:r.algorithm_variant??r.algorithm??r.method,method:r.algorithm_variant??r.algorithm??r.method,runtime:r.runtime ?? r.implementation_language ?? snapshot.benchmark?.implementation_language ?? 'cpp',
family:r.family ?? r.method_family ?? r.method ?? methods.get(r.algorithm)?.family ?? r.algorithm,
compression_rate:number(r.compression_rate ?? r.average_compression_rate),compression_time:number(r.compression_time_ns_per_point ?? r.compression_time),decompression_time:number(r.decompression_time_ns_per_point ?? r.decompression_time),
original_bytes:number(r.original_size_bytes ?? r.original_bytes ?? r.originalSize),compressed_bytes:number(r.compressed_size_bytes ?? r.compressed_bytes ?? r.compressedSize) };
}
function filteredRecords(ignoreMethod=false) {
const s=state(), scope=scopeKey();
let result=records.filter((r) => r.runtime===s.runtime && r.scope===scope && (s.dataset==='all'||r.dataset===s.dataset));
if(s.mode==='family') {
const best=new Map();
for(const r of result){const key=`${r.dataset}\0${r.family}`;const prev=best.get(key);if(!prev||r.compression_rate<prev.compression_rate||(r.compression_rate===prev.compression_rate&&r.configuration.localeCompare(prev.configuration)<0))best.set(key,r);}
result=[...best.values()].map(r=>({...r,method:r.family}));
}
return ignoreMethod?result:result.filter(r=>(s.family==='all'||r.family===s.family)&&(!s.search||(s.mode==='family'?r.family:`${r.method} ${r.family}`).toLowerCase().includes(s.search)));
}
function aggregate(input) {
const groups=new Map();
for(const r of input) { if(!groups.has(r.method)) groups.set(r.method,[]); groups.get(r.method).push(r); }
const totalDatasets=new Set(filteredRecords(true).map(r=>r.dataset)).size;
const rows=[];
for(const [method,source] of groups) {
const hasBytes=source.every(r=>r.original_bytes!==null && r.original_bytes>0 && r.compressed_bytes!==null && r.compressed_bytes>0);
const original=hasBytes?source.reduce((s,r)=>s+r.original_bytes,0):null, compressed=hasBytes?source.reduce((s,r)=>s+r.compressed_bytes,0):null;
rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:mean(source.map(r=>r.compression_time)),decompression_time:mean(source.map(r=>r.decompression_time)),overall_rate:hasBytes?compressed/original:null,original_bytes:original,compressed_bytes:compressed,coverage:new Set(source.map(r=>r.dataset)).size,totalDatasets,source});
}
return rows.filter(r=>!state().complete||r.coverage===totalDatasets).sort((a,b)=>{
const av=number(a[sort.key]),bv=number(b[sort.key]);
if(av===null&&bv===null)return a.method.localeCompare(b.method);
if(av===null)return 1;if(bv===null)return -1;
return sort.direction*(av-bv)||a.method.localeCompare(b.method);
});
}
function renderTable() {
const visible=cols.filter(c=>c.visible);
$('leaderboard').querySelector('thead').innerHTML=`<tr><th>Position</th><th>Method / configuration</th>${visible.map(c=>`<th aria-sort="${sort.key===c.key?(sort.direction===1?'ascending':'descending'):'none'}"><button data-sort="${c.key}" title="Sort ${escape(c.label)}">${escape(c.label)}${sort.key===c.key?(sort.direction===1?' ▴':' ▾'):''}</button></th>`).join('')}<th>Dataset coverage</th></tr>`;
const minima=Object.fromEntries(visible.map(c=>[c.key,Math.min(...currentRows.map(r=>number(r[c.key])).filter(n=>n!==null))]));
$('leaderboard').querySelector('tbody').innerHTML=currentRows.length?currentRows.map((r,i)=>`<tr><td class="rank numeric">${i+1}</td><td><button class="method-button" data-method="${escape(r.method)}">${escape(r.method)}</button><span class="method-family">${escape(r.family)} · ${escape(runtimeName(r.runtime))}</span></td>${visible.map(c=>`<td class="numeric ${!c.key.endsWith('bytes')&&number(r[c.key])!==null&&r[c.key]===minima[c.key]?'best':''}">${fmt(r[c.key],c.digits)}</td>`).join('')}<td><span class="coverage ${r.coverage===r.totalDatasets?'full':''}">${r.coverage} / ${r.totalDatasets}</span></td></tr>`).join(''):`<tr><td colspan="${visible.length+3}" class="empty-cell">No measurements match this selection. Try another runtime, data type, or dataset.</td></tr>`;
for(const button of $('leaderboard').querySelectorAll('[data-sort]'))button.addEventListener('click',()=>{const key=button.dataset.sort;sort={key,direction:sort.key===key?-sort.direction:1};refresh();});
for(const button of $('leaderboard').querySelectorAll('[data-method]'))button.addEventListener('click',()=>showMethod(button.dataset.method,true));
$('row-count').textContent=`${currentRows.length} ${state().mode==='family'?'method families':'method configurations'} · sorted by ${cols.find(c=>c.key===sort.key)?.label??sort.key}`;
$('download').disabled=!currentRows.length;
}
function refresh() {
if(!snapshot)return;
$('precision').disabled=$('dtype').value!=='float';
if(scopeKey()==='float'&&!records.some(r=>r.scope==='float')) {
// The exporter must publish an explicit all-float scope; never combine
// preaggregated fixed/non-fixed metrics and call it an all-float result.
$('results-note').textContent='All-float measurements are not available in this snapshot. Select a precision category.';
} else $('results-note').textContent=state().mode==='family'?'Each family uses its lowest-rate measured configuration per dataset. Configurations may differ across datasets; use Exact configuration for fixed parameters.':'Each row retains one exact configuration. Lower compression rates and lower ns/point are better. Coverage counts datasets, not identical column sets.';
currentRows=aggregate(filteredRecords());
const s=state(),scope=scopeKey(),ds=s.dataset==='all'?'all measured datasets':s.dataset;
const names={overall:'all numeric columns',integer:'integer columns',float:'floating-point columns',fixed_float:'fixed-precision float columns',nonfixed_float:'non-fixed-precision float columns'};
$('selection-summary').textContent=`${runtimeName(s.runtime)} · ${names[scope]||scope} · ${ds} · ${currentRows.length} methods`;
$('view-title').textContent=activeTab==='dataset'?`${s.dataset==='all'?'Dataset':s.dataset} leaderboard`:activeTab==='tradeoffs'?'Storage and execution trade-offs':'Overall leaderboard';
renderTable();
if(activeTab==='tradeoffs')renderChart();
if(selectedMethod)showMethod(selectedMethod,false);
}
function setTab(tab) {
activeTab=['overall','dataset','tradeoffs','methods','paper','about'].includes(tab)?tab:'overall';
for(const b of document.querySelectorAll('[data-tab]')){b.classList.toggle('active',b.dataset.tab===activeTab);if(b.dataset.tab===activeTab)b.setAttribute('aria-current','page');else b.removeAttribute('aria-current');}
$('results-view').hidden=['methods','paper','about'].includes(activeTab);$('methods-view').hidden=activeTab!=='methods';$('paper-view').hidden=activeTab!=='paper';$('about-view').hidden=activeTab!=='about';$('chart-view').hidden=activeTab!=='tradeoffs';
if(activeTab==='dataset'&&$('dataset').value==='all'){const first=[...datasets.keys()].find(k=>records.some(r=>r.dataset===k&&r.runtime===$('runtime').value));if(first)$('dataset').value=first;}
if(activeTab==='overall')$('dataset').value='all';
refresh();
}
function showMethod(method,focus=false) {
const row=currentRows.find(r=>r.method===method), detail=$('method-detail');
if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline;
const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
detail.innerHTML=`<div class="detail-head"><div><h2 id="detail-title">${escape(method)}</h2><p class="muted">${escape(row.family)} · ${escape(runtimeName(row.runtime))} · ${row.coverage} measured datasets</p></div><button id="close-detail" class="quiet-button">Close details</button></div><div class="detail-grid"><div><strong>${fmt(row.compression_rate,4)}</strong><span>average compression rate</span></div><div><strong>${fmt(row.compression_time,2)}</strong><span>encoding ns/point</span></div><div><strong>${fmt(row.decompression_time,2)}</strong><span>decoding ns/point</span></div><div><strong>${row.coverage} / ${row.totalDatasets}</strong><span>dataset coverage</span></div></div>${pipeline?`<p class="source-code">Interpretive operator mapping: ${escape(typeof pipeline==='string'?pipeline:JSON.stringify(pipeline))}</p>`:''}<p class="muted">Dataset metrics below come from the published result files. Independent round-trip evidence and hardware are ${snapshot.benchmark?.roundtrip_evidence?'documented in the manifest':'not reported for this snapshot'}.</p><div class="detail-table"><table><thead><tr><th>Dataset</th><th>Configuration</th><th>Rate ↓</th><th>Encode ns/point ↓</th><th>Decode ns/point ↓</th><th>Original bytes</th><th>Compressed bytes</th></tr></thead><tbody>${source.map(r=>`<tr><td>${escape(r.dataset)}</td><td>${escape(r.configuration)}</td><td class="numeric">${fmt(r.compression_rate,4)}</td><td class="numeric">${fmt(r.compression_time,2)}</td><td class="numeric">${fmt(r.decompression_time,2)}</td><td class="numeric">${fmt(r.original_bytes,0)}</td><td class="numeric">${fmt(r.compressed_bytes,0)}</td></tr>`).join('')}</tbody></table></div>`;
detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
}
function renderChart() {
const metric=$('chart-metric').value;
const valid=currentRows.filter(r=>r.compression_rate>0&&r[metric]>0);
if(!valid.length){$('chart').innerHTML='<p class="chart-empty muted">No positive measurements to plot for this selection.</p>';return;}
const width=1100,height=410,pad={left:85,right:35,top:30,bottom:65};
const maxRate=Math.max(...valid.map(r=>r.compression_rate))*1.08;
const logs=valid.map(r=>Math.log10(r[metric]));let minLog=Math.floor(Math.min(...logs)),maxLog=Math.ceil(Math.max(...logs));if(maxLog===minLog)maxLog=minLog+1;
const x=r=>pad.left+r/maxRate*(width-pad.left-pad.right),y=n=>height-pad.bottom-(Math.log10(n)-minLog)/(maxLog-minLog)*(height-pad.top-pad.bottom);
let graph='';
for(let i=0;i<=5;i++){const rate=maxRate*i/5,px=x(rate);graph+=`<line class="gridline" x1="${px}" y1="${pad.top}" x2="${px}" y2="${height-pad.bottom}"/><text class="axis-label" x="${px}" y="${height-pad.bottom+25}" text-anchor="middle">${fmt(rate,2)}</text>`;}
for(let i=minLog;i<=maxLog;i++){const n=10**i,py=y(n);graph+=`<line class="gridline" x1="${pad.left}" y1="${py}" x2="${width-pad.right}" y2="${py}"/><text class="axis-label" x="${pad.left-12}" y="${py+4}" text-anchor="end">${n.toLocaleString('en-US',{maximumFractionDigits:3})}</text>`;}
for(const r of valid)graph+=`<circle class="point" tabindex="0" role="button" aria-label="${escape(r.method)}: compression rate ${fmt(r.compression_rate,4)}, ${metric==='compression_time'?'encoding':'decoding'} ${fmt(r[metric],2)} ns per point" data-method="${escape(r.method)}" cx="${x(r.compression_rate)}" cy="${y(r[metric])}" r="6"><title>${escape(r.method)} | rate ${fmt(r.compression_rate,4)} | ${fmt(r[metric],2)} ns/point | coverage ${r.coverage}/${r.totalDatasets}</title></circle>`;
graph+=`<text class="axis-label" x="${width/2}" y="${height-13}" text-anchor="middle">Average compression rate (compressed / original) ↓</text><text class="axis-label" transform="translate(20 ${height/2}) rotate(-90)" text-anchor="middle">${metric==='compression_time'?'Encoding':'Decoding'} ns/point (log scale) ↓</text>`;
$('chart').innerHTML=`<svg viewBox="0 0 ${width} ${height}" role="img" aria-label="Measured compression rates versus ${metric==='compression_time'?'encoding':'decoding'} cost">${graph}</svg>`;
for(const p of $('chart').querySelectorAll('[data-method]')){p.addEventListener('click',()=>showMethod(p.dataset.method,true));p.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();showMethod(p.dataset.method,true);}});}
}
function csv() {
const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
const columns=['position','method','runtime','scope','row_mode','selected_dataset','sort_metric','sort_direction','average_compression_rate','overall_compression_rate','encode_ns_per_point','decode_ns_per_point','dataset_coverage','datasets_in_scope','selected_configuration_by_dataset'];
const body=currentRows.map((r,i)=>[i+1,r.method,r.runtime,scopeKey(),state().mode,state().dataset,sort.key,sort.direction===1?'ascending':'descending',r.compression_rate,r.overall_rate,r.compression_time,r.decompression_time,r.coverage,r.totalDatasets,JSON.stringify(Object.fromEntries(r.source.map(s=>[s.dataset,s.configuration])))].map(quote).join(','));
const url=URL.createObjectURL(new Blob(['\ufeff'+[columns.join(','),...body].join('\r\n')],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download=`THULab-compression-${state().runtime}-${scopeKey()}-${state().dataset}.csv`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
}
function renderAbout() {
const b=snapshot.benchmark||{},s=snapshot.source||{},c=snapshot.coverage||{};
const notes=[b.scope_note,b.aggregation_note,b.selection_note,...(b.notes||[]),...(b.measurement_boundaries||[])].filter(Boolean);
$('methodology').innerHTML=notes.map(n=>`<p class="notice">${escape(n)}</p>`).join('');
const definitions=b.metric_definitions||{};if(Object.keys(definitions).length)$('methodology').innerHTML+=`<dl class="definitions">${Object.entries(definitions).map(([k,v])=>`<div><dt>${escape(k.replaceAll('_',' '))}</dt><dd>${escape(typeof v==='string'?v:JSON.stringify(v))}</dd></div>`).join('')}</dl>`;
const exclusions=c.exclusions||c.excluded_datasets||snapshot.exclusions||[];
const info={ 'Snapshot exported':snapshot.exported_at||s.exported_at||'Not reported','Dataset scope generated':s.generated_at||'Not reported', 'Source checkout':s.git_commit||'Not reported','Source files':`${s.source_files?.length??0} metadata inputs and ${s.result_csv_files_used?.length??0} result CSVs (hashes in manifest)`,'Result entries across scopes':`${records.length} (scope slices can reuse a measurement)`,'Worktree source state':s.worktree_dirty_inputs?.length?'Includes current local changes; input file hashes identify the published snapshot.':'See manifest for source file hashes.','Runtime environment':b.hardware||'Hardware details are not recorded in this exported result snapshot.','Lossless evidence':b.roundtrip_evidence||'Lossless-design method filter from the source project; per-run round-trip verification is not recorded here.'};
$('provenance').innerHTML=`<table class="provenance-table"><tbody>${Object.entries(info).map(([k,v])=>`<tr><th>${escape(k)}</th><td class="source-code">${escape(v)}</td></tr>`).join('')}</tbody></table>${exclusions.length?`<h3>Excluded or unavailable results</h3><p class="source-code">${escape(typeof exclusions==='string'?exclusions:JSON.stringify(exclusions))}</p>`:''}`;
const groups=new Map();for(const r of records){if(!groups.has(r.family))groups.set(r.family,[]);groups.get(r.family).push(r.configuration);}
$('method-catalog').innerHTML=`<div class="method-groups">${[...groups].sort((a,b)=>a[0].localeCompare(b[0])).map(([family,names])=>`<section class="method-group"><h3>${escape(family)}</h3><ul>${[...new Set(names)].sort().map(n=>`<li>${escape(n)}</li>`).join('')}</ul></section>`).join('')}</div>`;
}
function init() {
if(snapshot.compact_records){const t=snapshot.record_tables;snapshot.dataset_results=snapshot.compact_records.map(r=>({dataset:t.datasets[r[0]],scope:t.scopes[r[1]],runtime:t.runtimes[r[2]],family:t.families[r[3]],method:t.configurations[r[4]],algorithm_variant:t.configurations[r[4]],compression_rate:r[5],compression_time_ns_per_point:r[6],decompression_time_ns_per_point:r[7],original_size_bytes:r[8],compressed_size_bytes:r[9]}));delete snapshot.compact_records;}
for(const m of snapshot.methods||[]) { const method=typeof m==='string'?{name:m}:m;methods.set(method.name??method.method,method);for(const c of method.configurations||[])methods.set(c.algorithm_variant,{...method,name:c.algorithm_variant,family:method.name}); }
for(const d of snapshot.datasets||[])datasets.set(d.key??d.dataset,d);
records=(snapshot.dataset_results||snapshot.records||[]).map(normalizeRecord).filter(r=>r.method&&r.dataset&&r.compression_rate>0);
for(const r of records)if(!methods.has(r.method))methods.set(r.method,{name:r.method,family:r.family});
const runtimes=[...new Set(records.map(r=>r.runtime))].sort((a,b)=>['cpp','java','python'].indexOf(a)-['cpp','java','python'].indexOf(b));
$('runtime').innerHTML=runtimes.map(r=>`<option value="${escape(r)}">${escape(runtimeName(r))}</option>`).join('');
$('dataset').innerHTML='<option value="all">All measured datasets</option>'+[...datasets.values()].sort((a,b)=>(a.key??a.dataset).localeCompare(b.key??b.dataset)).map(d=>`<option value="${escape(d.key??d.dataset)}">${escape(d.key??d.dataset)}</option>`).join('');
$('family').innerHTML='<option value="all">All method families</option>'+[...new Set(records.map(r=>r.family))].sort().map(f=>`<option value="${escape(f)}">${escape(f)}</option>`).join('');
$('dataset-count').textContent=countFmt(new Set(records.map(r=>r.dataset)).size);$('method-count').textContent=countFmt(new Set(records.map(r=>r.family)).size);$('runtime-count').textContent=runtimes.length;$('record-count').textContent=countFmt(snapshot.coverage?.runtime_configuration_count??new Set(records.map(r=>`${r.runtime}\0${r.method}`)).size);
const exported=snapshot.exported_at||snapshot.source?.exported_at||'not reported';$('snapshot').textContent=`Result snapshot exported · ${String(exported).replace('T',' ').slice(0,19)}${exported==='not reported'?'':' UTC'}`;$('footer-snapshot').textContent=`Source inputs documented in the manifest`;
$('column-options').innerHTML=cols.map(c=>`<label><input type="checkbox" data-column="${c.key}" ${c.visible?'checked':''}>${escape(c.label)}</label>`).join('');
for(const input of $('column-options').querySelectorAll('input'))input.addEventListener('change',()=>{cols.find(c=>c.key===input.dataset.column).visible=input.checked;renderTable();});
for(const id of ['runtime','dtype','precision','dataset','family','row-mode','complete-only'])$(id).addEventListener('change',refresh);$('search').addEventListener('input',refresh);$('chart-metric').addEventListener('change',renderChart);$('download').addEventListener('click',csv);
$('reset').addEventListener('click',()=>{$('runtime').value=runtimes.includes('cpp')?'cpp':runtimes[0];$('dtype').value='overall';$('precision').value='all';$('dataset').value='all';$('row-mode').value='family';$('search').value='';$('family').value='all';$('complete-only').checked=false;selectedMethod=null;$('method-detail').hidden=true;sort={key:'compression_rate',direction:1};setTab(activeTab);});
for(const tab of document.querySelectorAll('[data-tab]'))tab.addEventListener('click',()=>{history.replaceState(null,'',`#${tab.dataset.tab}`);setTab(tab.dataset.tab);});
const renderPaper=()=>{const rows=(snapshot.paper_table3?.rows||[]).filter(r=>r.scope===$('paper-scope').value);$('paper-table').querySelector('tbody').innerHTML=rows.map(r=>`<tr><td>${escape(r.method)}</td><td class="numeric">${fmt(r.rank,1)}</td><td class="numeric">${fmt(r.average_compression_rate,4)}</td><td class="numeric">${fmt(r.average_compression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.average_decompression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.median_balanced_score,4)}</td></tr>`).join('');};$('paper-scope').addEventListener('change',renderPaper);renderPaper();
renderAbout();setTab(location.hash.slice(1)||'overall');
document.documentElement.dataset.ready='true';
}
function setupTheme() {
let stored;try{stored=localStorage.getItem('thulab-theme');}catch{}
const query=new URLSearchParams(location.search).get('__theme');
const theme=stored||(['dark','light'].includes(query)?query:matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');
document.documentElement.dataset.theme=theme;$('theme').textContent=theme==='dark'?'Light mode':'Dark mode';
$('theme').addEventListener('click',()=>{const next=document.documentElement.dataset.theme==='dark'?'light':'dark';document.documentElement.dataset.theme=next;$('theme').textContent=next==='dark'?'Light mode':'Dark mode';try{localStorage.setItem('thulab-theme',next);}catch{}});
}
setupTheme();
try { const response=await fetch('./ui-data.json');if(!response.ok)throw new Error(`Snapshot request returned ${response.status}`);snapshot=await response.json();init(); }
catch(e){$('load-error').hidden=false;$('load-error').textContent=`The result snapshot could not be loaded. Please reload the page or download the JSON from the repository. ${e.message}`;$('snapshot').textContent='Snapshot unavailable';$('leaderboard').querySelector('tbody').innerHTML='<tr><td class="empty-cell">Results unavailable.</td></tr>';}
|