(async function(){
const host=document.getElementById('timeline-chart'),org=document.getElementById('timeline-org'),cost=document.getElementById('timeline-cost'),slider=document.getElementById('timeline-month'),readout=document.getElementById('timeline-readout');
try {
const response=await fetch('timeline/release-windows.json');if(!response.ok)throw Error(response.status);const rows=await response.json();
const releaseResponse=await fetch('timeline/releases.json');if(!releaseResponse.ok)throw Error(releaseResponse.status);const releases=(await releaseResponse.json()).releases;
const months=[];for(let y=2022;y<=2026;y++)for(let m=1;m<=12;m++){let s=`${y}-${String(m).padStart(2,'0')}`;if(s>='2022-06'&&s<='2026-10')months.push(s)}
const fmt=n=>n.toLocaleString('fr-FR',{maximumFractionDigits:1});
function render(){
const selected=rows.filter(r=>!org.value||r.organization===org.value),k=cost.value+'_musd';
const early=months.map(m=>selected.reduce((s,r)=>s+(r.first_month<=m?r[k]:0),0)),late=months.map(m=>selected.reduce((s,r)=>s+(r.last_month<=m?r[k]:0),0));
const W=1000,H=440,L=72,R=25,T=30,B=65,max=Math.max(10,early.at(-1)*1.12),x=i=>L+i*(W-L-R)/(months.length-1),y=v=>H-B-v/max*(H-T-B);
// Shared smoothstep interpolation preserves every monthly value, monotonicity,
// and the ordering of the two timing bounds without cubic overshoot.
function curve(vals,reverse=false){
const ids=vals.map((_,i)=>i);if(reverse)ids.reverse();
return ids.map((i,j)=>{if(!j)return `M${x(i)},${y(vals[i])}`;const prev=ids[j-1],dx=(x(i)-x(prev))/3;return `C${x(prev)+dx},${y(vals[prev])} ${x(i)-dx},${y(vals[i])} ${x(i)},${y(vals[i])}`}).join(' ');
}
const band=curve(early)+curve(late,true).replace(/^M/,'L')+'Z';
const area=curve(late)+`L${x(months.length-1)},${y(0)}L${x(0)},${y(0)}Z`;
let svg=``;host.innerHTML=svg;
const monthly=releases.filter(r=>r.month===months[n]&&(!org.value||r.organization===org.value)).sort((a,b)=>b.display_priority-a.display_priority);
const names=[...new Set(monthly.map(r=>r.model))];
const monthLabel=new Date(months[n]+'-01T12:00:00Z').toLocaleDateString('fr-FR',{month:'long',year:'numeric',timeZone:'UTC'});
readout.textContent=`${monthLabel} · ${names.length?names.slice(0,5).join(' · ')+(names.length>5?` · +${names.length-5} autres`:''):'Aucune sortie répertoriée ce mois-ci'}`;
readout.title=names.join(' · ');
slider.setAttribute('aria-valuetext',readout.textContent);
}
org.addEventListener('change',render);cost.addEventListener('change',render);slider.addEventListener('input',render);render();
}catch(e){host.innerHTML='
';readout.textContent='Graphique statique : le chargement interactif a échoué.'}
})();