Fan Timeline Carousel
Horizontal timeline where cards fan out from one point. Click a card or use the arrows to move through the milestones.
Preview and code
index.html
<div class="tl"><div class="tr"></div><div class="ctl"><button class="pv" type="button">‹</button><span class="ct"></span><button class="nx" type="button">›</button></div></div>
styles.css
.tl{position:relative;width:100%;height:100%;min-height:380px;overflow:hidden;font-family:system-ui,-apple-system,sans-serif;color:#eee;}.tr{position:absolute;left:50%;top:0;height:340px;transition:transform .6s cubic-bezier(.3,1.1,.4,1)}.yr{position:absolute;top:70px;font:700 64px system-ui;color:#262626}.cd{position:absolute;top:150px;width:150px;height:180px;background:#1b1b1b;border:1px solid #2a2a2a;border-radius:14px;padding:12px;box-sizing:border-box;cursor:pointer;opacity:.5;transform:scale(.86);transform-origin:50% 100%;transition:transform .5s cubic-bezier(.3,1.3,.5,1),opacity .4s}.cd.on{opacity:1;transform:scale(1.08)}.cd small{display:block;font-size:10px;color:#777}.cd b{display:block;font-size:13px;margin-top:2px}.cd svg{position:absolute;left:10px;right:10px;bottom:10px;width:130px;height:80px}.fl{position:absolute;left:0;top:0;height:1px;background:rgba(150,150,170,.5);transform-origin:0 0;pointer-events:none}.ctl{position:absolute;bottom:8px;left:0;right:0;display:flex;justify-content:center;align-items:center;gap:12px;font-size:12px;color:#888}.ctl button{width:30px;height:30px;border-radius:50%;border:1px solid #2a2a2a;background:#1b1b1b;color:#eee;cursor:pointer;font-size:16px}
script.js
(function(){var R=document.querySelector(".tl"),tr=R.querySelector(".tr"),ct=R.querySelector(".ct");var D=[["Oct 14 ’25","Offline drafts",[3,5,4,6,5,7]],["Nov 20 ’25","Weekly signups double",[2,3,5,4,7,9]],["Jan 8 ’26","Shared spaces",[4,4,6,7,6,8]],["Mar 2 ’26","Public API",[1,3,4,6,8,9]]];var P=164,a=1,n=D.length,fl=[];D.forEach(function(d,i){var l=document.createElement("div");l.className="fl";tr.appendChild(l);fl.push(l);var c=document.createElement("div");c.className="cd";c.style.left=i*P+"px";var pts=d[2].map(function(v,k){return (k*22)+","+(78-v*8)}).join(" ");c.innerHTML="<small>"+d[0]+"</small><b>"+d[1]+"</b><svg viewBox=\"0 0 110 80\"><polyline points=\""+pts+"\" fill=\"none\" stroke=\"#0a84ff\" stroke-width=\"2\"/></svg>";c.onclick=function(){go(i)};tr.appendChild(c)});[["’25",-10],["’26",2*P+10]].forEach(function(y){var e=document.createElement("div");e.className="yr";e.textContent=y[0];e.style.left=y[1]+"px";tr.appendChild(e)});function go(i){a=Math.max(0,Math.min(n-1,i));tr.style.transform="translateX("+(-(a*P+75))+"px)";[].forEach.call(tr.querySelectorAll(".cd"),function(c,k){c.classList.toggle("on",k===a)});var x1=a*P+75,y1=12,y2=150;fl.forEach(function(l,k){var x2=k*P+75,dx=x2-x1,dy=y2-y1;l.style.left=x1+"px";l.style.top=y1+"px";l.style.width=Math.sqrt(dx*dx+dy*dy)+"px";l.style.transform="rotate("+Math.atan2(dy,dx)+"rad)"});ct.textContent=(a+1)+" / "+n}R.querySelector(".pv").onclick=function(){go(a-1)};R.querySelector(".nx").onclick=function(){go(a+1)};go(1)})();