Banaxi-Tech's picture
Upload 6 files
4929495 verified
Raw History Blame Contribute Delete
5.94 kB
import {dateLabel,topFive,outcomes,validateChances} from './forecast-utils.js';
const $=s=>document.querySelector(s);
let book,mode='on';
let detailModel=null;
const endLabel=id=>new Intl.DateTimeFormat('en-US',{month:'short',year:'numeric',timeZone:'UTC'}).format(new Date(Object.values(book.months).filter(p=>p.models.some(m=>m.id===id)).at(-1).finalDate+'T12:00:00Z'));
const format=n=>`${Number(n.toFixed(1))}`;
function element(tag,className,text){const el=document.createElement(tag);if(className)el.className=className;if(text!==undefined)el.textContent=text;return el}
function validSaved(saved,base){
return saved?.version===base.version&&saved.finalDate===base.finalDate&&Array.isArray(saved.models)&&saved.models.length===base.models.length&&saved.models.every((m,i)=>m.id===base.models[i].id&&['on','by'].every(key=>Array.isArray(m[key])&&m[key].length===base.models[i][key].length&&m[key].every((r,j)=>r.date===base.models[i][key][j].date)&&!validateChances(m[key],key)));
}
function render(){
$('.edition strong').textContent='September 2026–January 2027';
$('#cards').replaceChildren();
document.body.classList.toggle('detail-open',detailModel!==null);
let detailHeader=document.getElementById('detail-header');
if(!detailHeader){detailHeader=element('div','detail-header');detailHeader.id='detail-header';$('#cards').before(detailHeader)}
detailHeader.replaceChildren();
if(detailModel!==null){
const back=element('button','button','← All models');
back.onclick=()=>{const previous=detailModel;detailModel=null;render();document.getElementById(`toggle-${previous}-${mode}`)?.focus({preventScroll:true})};
detailHeader.append(back,element('span',null,`ALL DATES · SEP 2026–${endLabel(detailModel).toUpperCase()}`));
}
book.models.forEach((model,index)=>{
if(detailModel!==null&&model.id!==detailModel)return;
const key=`${model.id}-${mode}`,isExpanded=detailModel===model.id;
const periods=[['2026-09',book],...Object.entries(book.months)].filter(([,period])=>period.models.some(m=>m.id===model.id));
const allRows=periods.flatMap(([periodKey,period])=>outcomes(period.models.find(m=>m.id===model.id),mode,period.finalDate).filter(row=>row.date!=='later'||periodKey===periods.at(-1)[0]).map(row=>({...row,periodKey})));
const ranked=[...allRows].sort((a,b)=>b.chance-a.chance);
const rows=isExpanded?allRows:ranked.slice(0,5),card=element('article','card');
const head=element('div','card-head'),logo=element('img','logo');logo.src=model.logo;logo.alt=`${model.organization} logo`;logo.addEventListener('error',()=>{logo.hidden=true},{once:true});
const names=element('div');names.append(element('div','org',model.organization));const heading=element('h2',null,model.name);heading.append(element('span',null,` released ${mode}`));names.append(heading);head.append(logo,names,element('span','number',`0${index+1}`));
const highest=ranked[0];
const leader=element('div','leader'),lead=element('div');lead.append(element('div','leader-label','HIGHEST CHANCE'),element('div','leader-date',highest.label));const score=element('div','leader-percent',format(highest.chance));score.append(element('small',null,'%'));leader.append(lead,score);
const rankings=element('div','rankings');rankings.id=`outcomes-${key}`;rows.forEach((r,i)=>{
if(isExpanded&&(i===0||r.periodKey!==rows[i-1].periodKey)){
const section=element('div','month-heading');
const title=new Intl.DateTimeFormat('en-US',{month:'long',year:'numeric',timeZone:'UTC'}).format(new Date(`${r.periodKey}-01T12:00:00Z`));
section.append(element('h3',null,title));rankings.append(section);
}
const row=element('div','outcome'),track=element('div','track'),fill=element('div','fill');fill.style.width=`${r.chance}%`;track.append(fill);track.setAttribute('aria-hidden','true');const label=element('span','date',r.label);row.append(element('span','rank',String(i+1).padStart(2,'0')),label,track,element('span','percent',`${format(r.chance)}%`));rankings.append(row)});
const toggle=element('button','expand-button',isExpanded?'← Back to all models':`View all dates · Sep 2026–${endLabel(model.id)} →`);
toggle.type='button';toggle.id=`toggle-${key}`;toggle.setAttribute('aria-expanded',String(isExpanded));toggle.setAttribute('aria-controls',rankings.id);toggle.setAttribute('aria-label',`${isExpanded?'Show top five':'Show all dates for'} ${model.name}`);
toggle.onclick=()=>{detailModel=isExpanded?null:model.id;render();window.scrollTo({top:0,behavior:'instant'});if(detailModel!==null)document.querySelector('#detail-header button').focus({preventScroll:true});else document.getElementById(`toggle-${key}`).focus({preventScroll:true})};
card.append(head,leader,rankings,toggle);$('#cards').append(card);
});
$('#view-description').textContent=mode==='on'?'EXACT-DATE FORECASTS':'CUMULATIVE DEADLINE FORECASTS';
}
async function init(){
try{
book=JSON.parse(document.getElementById('forecast-data').textContent);
if(location.protocol!=='file:') {
try {
const response=await fetch('./forecasts.json',{cache:'no-store',signal:AbortSignal.timeout(2500)});
if(response.ok){const published=await response.json();if(validSaved(published,book)){const defaults=book.months;book=published;book.months=Object.fromEntries(Object.entries(defaults).map(([key,value])=>[key,validSaved(published.months?.[key],value)?published.months[key]:value]));}}
} catch { /* The embedded forecasts keep startup working offline. */ }
}
document.querySelectorAll('[data-mode]').forEach(button=>button.onclick=()=>{mode=button.dataset.mode;document.querySelectorAll('[data-mode]').forEach(b=>{b.classList.toggle('active',b===button);b.setAttribute('aria-pressed',String(b===button))});render()});
render();
}catch(error){$('#cards').append(element('p',null,'Could not load forecasts. Check that forecasts.json is uploaded alongside index.html, then reload.'));console.error(error)}
}
init();