RRosternomics

Blue Jays TOR

GM: Ross Atkins

Overview

FORCED-SPEND ↘ cooling
expensive & losing · year 1 in this regime · payroll 1.53× league · TAB -8.2
-8.2TAB — talent above budget23rd/30
-9.0WAB — realized wins (TAB + luck)26th/30
3.0depth — 2+ WAR regulars19th/30
-5.0dead WAR — sub-replacement drag24th/30
34%dead money %11th/30
0.452win %24th/30
31.1roster fWAR18th/30
-69surplus $M (value vs cost)29th/30
$243Mpayroll $M5th/30luxury tax $223M

Roster

CFDaulton Varsho 61GMyles Straw 15GNathan Lukes 6G
LFDavis Schneider 30GYohendrick Piñango 26GMyles Straw 23G
RFJesús Sánchez 38GNathan Lukes 28GMyles Straw 24G
SSAndrés Giménez 67GErnie Clement 12G
2BErnie Clement 60GLenyn Sosa 14GDavis Schneider 7G
3BKazuma Okamoto 66GErnie Clement 8G
1BVladimir Guerrero Jr. 60GLenyn Sosa 12GKazuma Okamoto 2G
STARTERSDylan Cease 3.7WARKevin Gausman 2.2WARTrey Yesavage 1.2WARPatrick Corbin 0.5WARSimeon Woods Richardson 0.0WAR
CBrandon Valenzuela 45GTyler Heineman 29GAlejandro Kirk 6G
DHGeorge Springer 48GLenyn Sosa 9GEloy Jiménez 9G
BULLPEN Louis Varland 11svTyler Rogers 14hMason Fluharty 8hBraydon Fisher 7hJeff Hoffman 7hTommy Nance 1hSpencer Miles 1hJoe Mantiply 0h
CBrandon Valenzuela 45 · Tyler Heineman 29 · Alejandro Kirk 6
1BVladimir Guerrero Jr. 60 · Lenyn Sosa 12 · Kazuma Okamoto 2
2BErnie Clement 60 · Lenyn Sosa 14 · Davis Schneider 7
3BKazuma Okamoto 66 · Ernie Clement 8
SSAndrés Giménez 67 · Ernie Clement 12
LFDavis Schneider 30 · Yohendrick Piñango 26 · Myles Straw 23
CFDaulton Varsho 61 · Myles Straw 15 · Nathan Lukes 6
RFJesús Sánchez 38 · Nathan Lukes 28 · Myles Straw 24
DHGeorge Springer 48 · Lenyn Sosa 9 · Eloy Jiménez 9
SPDylan Cease 3.7 · Kevin Gausman 2.2 · Trey Yesavage 1.2 · Patrick Corbin 0.5 · Simeon Woods Richardson 0.0
BP Louis Varland 11 · Tyler Rogers 14 · Mason Fluharty 8 · Braydon Fisher 7 · Jeff Hoffman 7 · Tommy Nance 1 · Spencer Miles 1 · Joe Mantiply 0

Each position lists its starter then backups by games (so platoons & bench show). Starters (mound) & bullpen (left, foul territory) are ranked by WAR; marks the closer (most save chances). MLB Stats API.

2026 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Dylan Cease 30 FA-elig 185.8 25.3 +11.0
Louis Varland 28 473.7
Kevin Gausman 35 FA-elig 213.5 23.0 -1.2
Kazuma Okamoto 30 1013.0 8.2 +10.9
Brandon Valenzuela 25 602.6 0.8 +15.8
Ernie Clement 30 arb 1012.1 4.6 +8.4
Andrés Giménez 27 arb 981.9 15.6 -3.5
Trey Yesavage 22 pre-arb 161.9 0.8 +10.9
Daulton Varsho 29 arb 911.8 10.8 +0.4
Spencer Miles 25 281.7 0.8 +10.1
Tyler Rogers 35 FA-elig 481.6 8.7 +1.7
Nathan Lukes 31 pre-arb 691.6 0.8 +9.1
Jeff Hoffman 33 FA-elig 471.4 12.7 -3.6
Vladimir Guerrero Jr. 27 991.4 40.2 -31.4
Alejandro Kirk 27 arb 331.1 8.7 -1.5
Patrick Corbin 36 FA-elig 190.8 0.9 +3.8
Mason Fluharty 24 pre-arb 540.7 0.8 +3.7
Jonatan Clase 24 pre-arb 90.5 0.8 +2.6
Joe Mantiply 35 arb 170.3 0.9 +1.2
George Springer 36 FA-elig 730.3 24.2 -22.1
Luis Urías 29 180.3
Chad Dallas 26 20.2 0.0 +1.2
Braydon Fisher 25 pre-arb 490.1 0.8 +0.1
Cody Ponce 32 pre-arb 10.1 6.0 -5.1
Adam Macko 25 200.1 0.6 +0.2
Jesús Sánchez 28 arb 730.1 6.8 -6.1
Simeon Woods Richardson↔ MIN 25 30.1 0.1 +0.4
CJ Van Eyk 27 20.1
Tommy Nance↔ MIN 35 pre-arb 320.0 0.7 -0.4
Tanner Andrews 30 20.0 0.0 +0.1
Connor Seabold↔ DET 30 pre-arb 5-0.0 0.2 -0.2
Yohendrick Piñango 24 54-0.0 0.6 -0.8
Myles Straw 31 arb 92-0.0 7.4 -7.6
Hayden Juenger 25 2-0.0 0.0 -0.2
Addison Barger 26 pre-arb 9-0.1 0.8 -1.2
Eloy Jiménez 29 12-0.1 0.1 -1.0
Tyler Heineman↔ ANA 35 arb 33-0.2 0.9 -2.2
Lazaro Estrada 27 pre-arb 4-0.2 0.0 -1.4
Shane Bieber 31 FA-elig 6-0.2 16.0 -17.4
Josh Fleming 30 1-0.2 0.0 -1.5
Yariel Rodríguez 29 pre-arb 10-0.3 7.0 -8.8
Charles McAdoo 24 8-0.4 0.5 -2.8
Chase Lee 27 pre-arb 3-0.4 0.6 -2.9
Sean Keys 23 12-0.5
Davis Schneider 27 pre-arb 51-0.7 0.3 -4.8
Lenyn Sosa↔ CHW 26 pre-arb 28-0.8 0.5 -5.3
Austin Voth↔ MIN 34 arb 2-0.8 0.3 -5.4
Brendon Little 29 pre-arb 8-0.9 0.1 -5.6
Eric Lauer↔ LAD 31 8-0.9 1.6 -7.2
Max Scherzer 41 FA-elig 6-1.1 3.0 -9.6

Key additions (new vs 2025)

PlayerfWAR
Dylan Cease3.7
Kazuma Okamoto2.0
Brandon Valenzuela1.7
Spencer Miles1.1
Tyler Rogers1.1
Patrick Corbin0.5

Key subtractions (gone from 2025)

Player2025 fWAR
Bo Bichette3.8
Chris Bassitt2.4
Joey Loperfido0.7
Seranthony Domínguez0.3
Ty France0.2
Alan Roden0.2

Current & Historical Payroll

2026 payroll
$243.4M
luxury tax $223M
2027 guaranteed
$140.8M

What the front office was committed to as of 2026, 2026–2032. Guaranteed $ are contract commitments signed by 2026; arbitration & free-agent years are projected from service time. Hover a figure for its luxury-tax (CBT) & actual-cash values.

current $ guaranteed $ arbitration (proj) pre-arb (proj) free agent club option player option mutual option vesting option

Summary — luxury-tax (CBT) basis

2026202720282029203020312032
Tax threshold$244M$247M$250M$253M$256M$259M$262M
Guaranteed contracts$198M$130M$120M$95M$72M$27M
Proj. arbitration$17M
Proj. pre-arbitration$8M
Player benefits$18M$18M$18M$18M$18M$18M
Total CBT payroll$241M$148M$138M$113M$90M$45M
Tax space+$3M+$99M+$112M+$140M+$166M+$214M
Est. tax bill

Luxury-tax (CBT) basis: contract AAV + ~$18M benefits/bonus-pool — distinct from the base-salary payroll above. Threshold is the CBA line. Tier-2 payor rates on the overage. Forward years show only guaranteed deals (arb/pre-arb not yet set). Figures are estimates.

Guaranteed / signed (18)

PlayerAgeSvc2026202720282029203020312032
Vladimir Guerrero Jr.277.000$40.2MFREE AGENT
Dylan Cease306.089$25.3M$33.3M$32.3M$31.3M$30.3M$29.3M$28.3M
George Springer3611.166$24.2MFREE AGENT
Kevin Gausman3511.151$23.0MFREE AGENT
Shane Bieber317.097$16.0MPLAYERFREE AGENT
Andrés Giménez275.106$15.6M$23.6M$23.6M$23.6M$23.0MCLUBFREE AGENT
Jeff Hoffman337.084$12.7M$12.7MFREE AGENT
Alejandro Kirk275.047$8.7M$12.3M$12.3M$12.3M$12.3MFREE AGENT
Tyler Rogers356.034$8.7M$13.7M$13.7M$9.0MCLUBFREE AGENT
Kazuma Okamoto30$8.2M$17.2M$17.2M$17.2M
Myles Straw315.125$7.4M$8.0MCLUB$8.5MCLUBFREE AGENT
Yariel Rodríguez292.000$7.0M$8.0M$6.3MPLAYERARB 3FREE AGENT
Cody Ponce320.126$6.0M$12.0M$12.0MARB 1ARB 2ARB 3FREE AGENT
Max Scherzer4117.079$3.0MFREE AGENT
Patrick Corbin3613.105$947kFREE AGENT
Eloy Jiménez297.000$91kFREE AGENT
Josh Fleming306.000$5kFREE AGENT
Luis Urías296.098FREE AGENT

Arbitration-eligible (9)

PlayerAgeSvc2026202720282029203020312032
Daulton Varsho295.128$10.8MFREE AGENT
Jesús Sánchez284.118$6.8MARB 3FREE AGENT
Ernie Clement303.168$4.6MARB 2ARB 3FREE AGENT
Eric Lauer315.091$1.6MFREE AGENT
Joe Mantiply354.057$936kARB 3FREE AGENT
Tyler Heineman353.066$928kARB 2ARB 3FREE AGENT
Austin Voth345.115$342kFREE AGENT
Simeon Woods Richardson254.000$99kARB 3FREE AGENT
Louis Varland284.000ARB 3FREE AGENT

Pre-arbitration (16)

PlayerAgeSvc2026202720282029203020312032
Jonatan Clase240.109$820kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Addison Barger261.098$811kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nathan Lukes311.145$808kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mason Fluharty240.139$797kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Braydon Fisher250.130$797kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Trey Yesavage220.014$789kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Spencer Miles25$780k
Brandon Valenzuela25$772k
Tommy Nance352.165$720kARB 1ARB 2ARB 3FREE AGENT
Yohendrick Piñango24$638k
Chase Lee270.095$551kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Lenyn Sosa262.044$502kARB 1ARB 2ARB 3FREE AGENT
Davis Schneider272.029$270kARB 1ARB 2ARB 3FREE AGENT
Connor Seabold301.054$190kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brendon Little291.135$52kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Lazaro Estrada270.004$25kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Blue Jays 2026; the black line = the league-average player at each slot that season. Staying above the line deep into the roster is the depth edge; dropping below it = holes & dead weight.

What each regime is worth — every settled team-season since 1985

RegimeSeasonsWin %Playoff %Pennant %WS %
WINDOW 28952.230.14.81.0
CONTEND 31156.863.019.310.3
FORCED-SPEND 26047.98.81.21.2
REBUILD 32843.31.20.30.3
const FONT={family:"'Roboto Mono', monospace",size:12}; const ORBIT = [[1985, 1.009, 10.45], [1986, 1.062, 7.11], [1987, 0.988, 14.82], [1988, 1.047, 5.09], [1989, 1.12, 8.57], [1990, 1.141, 8.06], [1991, 0.965, 8.68], [1992, 1.468, 8.9], [1993, 1.545, 7.96], [1994, 1.198, -8.57], [1995, 1.412, -19.0], [1996, 0.895, -5.12], [1997, 1.235, -8.54], [1998, 1.134, 4.87], [1999, 0.955, 3.91], [2000, 0.897, 1.72], [2001, 1.146, -2.22], [2002, 0.959, -3.23], [2003, 0.705, 3.49], [2004, 0.755, -6.3], [2005, 0.64, 4.19], [2006, 0.926, 7.47], [2007, 0.995, -0.44], [2008, 1.025, 4.46], [2009, 0.881, 2.03], [2010, 0.666, 9.89], [2011, 0.652, -2.11], [2012, 0.831, -11.11], [2013, 1.222, -10.18], [2014, 1.151, 0.05], [2015, 1.049, 15.61], [2016, 1.249, 3.54], [2017, 1.304, -12.51], [2018, 0.957, -10.05], [2019, 0.468, -3.26], [2021, 1.084, 8.74], [2022, 1.224, 12.25], [2023, 1.365, 5.2], [2024, 1.3, -6.24], [2025, 1.595, 4.48], [2026, 1.526, -8.15]], TEAMCOL = '#1D9BD7'; function contrastColor(hex,dark){let r=parseInt(hex.slice(1,3),16)/255,g=parseInt(hex.slice(3,5),16)/255,b=parseInt(hex.slice(5,7),16)/255; const mx=Math.max(r,g,b),mn=Math.min(r,g,b);let h=0,s=0,l=(mx+mn)/2; if(mx!==mn){const d=mx-mn;s=l>0.5?d/(2-mx-mn):d/(mx+mn);h=mx===r?(g-b)/d+(g1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<0.5)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p;} let R,G,B;if(s===0){R=G=B=L;}else{const q=L<0.5?L*(1+s):L+s-L*s,p=2*L-q;R=h2(p,q,h+1/3);G=h2(p,q,h);B=h2(p,q,h-1/3);} const x=v=>('0'+Math.round(v*255).toString(16)).slice(-2);return '#'+x(R)+x(G)+x(B);} const CURRENT_YEAR = 2026; function drawOrbit(){ const orbEl=document.getElementById('orbit'); if(!ORBIT || ORBIT.length<2){ orbEl.innerHTML='

Not enough history to chart.

'; return; } const f=+document.getElementById('yfrom').value, t=+document.getElementById('yto').value, w=+document.getElementById('win').value; document.getElementById('yflab').textContent=f; document.getElementById('ytlab').textContent=t; document.getElementById('winlab').textContent = w===1?'raw':(w+'-yr'); const dark=document.documentElement.getAttribute('data-theme')==='dark'; const ink=dark?'#cdd4dc':'#1d2630', grid=dark?'#2c313a':'#d6dbe2', refc=dark?'#5a636d':'#9aa3ad', dirc=dark?'#ffffff':'#1d2630'; const col=contrastColor(TEAMCOL,dark); function sm(a,w){const h=(w-1)/2,o=[];for(let i=0;id[0]>=f&&d[0]<=t); if(raw.length<2){ Plotly.purge('orbit'); orbEl.innerHTML='

Pick a wider year range.

'; return; } const s=sm(raw,w),n=s.length,X=s.map(d=>Math.round(d[1]*100)/100),Y=s.map(d=>Math.round(d[2]*10)/10),yr=s.map(d=>d[0]); let xa=Math.min(1,...X),xb=Math.max(1,...X),ya=Math.min(0,...Y),yb=Math.max(0,...Y); const px=(xb-xa)*.12+.04,py=(yb-ya)*.12+1.5;xa-=px;xb+=px;ya-=py;yb+=py; const k=Math.max(0,n-3), ann=[]; [['WINDOW','#15b6a0',xa<1&&yb>0,xa,yb,'left','top'],['CONTEND','#2f7ed8',xb>1&&yb>0,xb,yb,'right','top'], ['FORCED-SPEND','#ec5a3c',xb>1&&ya<0,xb,ya,'right','bottom'],['REBUILD','#f0a91c',xa<1&&ya<0,xa,ya,'left','bottom']].forEach(q=>{ if(q[2]) ann.push({x:q[3],y:q[4],xref:'x',yref:'y',text:q[0],showarrow:false,xanchor:q[5],yanchor:q[6],font:{size:10,color:q[1]},opacity:.85});}); ann.push({x:X[n-1],y:Y[n-1],ax:X[n-1]-0.5*(X[n-1]-X[k]),ay:Y[n-1]-0.5*(Y[n-1]-Y[k]),xref:'x',yref:'y',axref:'x',ayref:'y',showarrow:true,arrowhead:2,arrowsize:1.3,arrowwidth:1.7,arrowcolor:dirc,text:''}); Plotly.newPlot('orbit',[{x:X,y:Y,mode:'lines+markers',line:{color:col,width:2.6,shape:'spline',smoothing:0.7}, marker:{color:col,size:s.map((d,i)=>5+7*(i/Math.max(1,n-1))),line:{color:dark?'#1b1e23':'#fff',width:0.6}}, text:yr,hovertemplate:'%{text}
payroll %{x:.2f}× lg · TAB %{y:+.1f}
'+(w===1?'single season (raw)':w+'-yr smoothed avg')+''}], {margin:{l:50,r:14,t:10,b:40},paper_bgcolor:'rgba(0,0,0,0)',plot_bgcolor:'rgba(0,0,0,0)',font:FONT, xaxis:{title:{text:'relative payroll (× lg avg)',font:{size:11}},range:[xa,xb],gridcolor:grid,zeroline:false}, yaxis:{title:{text:'TAB (talent above budget)',font:{size:11}},range:[ya,yb],gridcolor:grid,zeroline:false}, shapes:[{type:'line',x0:1,x1:1,y0:ya,y1:yb,line:{color:refc,width:1,dash:'dot'}},{type:'line',x0:xa,x1:xb,y0:0,y1:0,line:{color:refc,width:1,dash:'dot'}}], annotations:ann},{displayModeBar:false,responsive:true}); } (function initOrbitSliders(){ const yf=document.getElementById('yfrom'), yt=document.getElementById('yto'); if(ORBIT && ORBIT.length>=2){ const yrs=ORBIT.map(d=>d[0]), y0=Math.min(...yrs), y1=Math.max(...yrs); yf.min=y0; yf.max=y1; yf.value=y0; yt.min=y0; yt.max=y1; yt.value=Math.max(y0, Math.min(CURRENT_YEAR, y1)); yf.addEventListener('input',()=>{ if(+yf.value>+yt.value) yt.value=yf.value; drawOrbit(); }); yt.addEventListener('input',()=>{ if(+yt.value<+yf.value) yf.value=yt.value; drawOrbit(); }); document.getElementById('win').addEventListener('input',drawOrbit); } // only paint on load if the orbit pane is the visible default (else it'd draw into a 0-width hidden box) const _op=document.querySelector('.gpane[data-g="orbit"]'); if(!_op || _op.style.display!=='none') drawOrbit(); })(); new MutationObserver(()=>{ const p=document.querySelector('.gpane[data-g="orbit"]'); if(!p || p.style.display!=='none') drawOrbit(); }).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']}); // ---- roster depth curve (WAR by roster slot vs league-average-by-slot) ---- const WARDIST = {"cats": ["SP", "RP", "SP", "IF", "C", "IF", "IF", "SP", "OF", "RP", "RP", "OF", "RP", "IF", "C", "SP", "RP", "OF", "RP", "OF", "IF", "RP", "RP", "SP", "RP", "OF", "SP", "RP", "RP", "RP", "RP", "OF", "OF", "RP", "OF", "IF", "C", "RP", "RP", "RP"], "league": [3.73, 2.8, 2.36, 2.09, 1.83, 1.64, 1.41, 1.24, 1.09, 0.96, 0.87, 0.78, 0.68, 0.62, 0.56, 0.49, 0.42, 0.36, 0.3, 0.22, 0.19, 0.16, 0.12, 0.1, 0.08, 0.06, 0.04, 0.03, 0.01, -0.01, -0.03, -0.04, -0.07, -0.09, -0.1, -0.12, -0.15, -0.18, -0.22, -0.25], "names": ["Dylan Cease", "Louis Varland", "Kevin Gausman", "Kazuma Okamoto", "Brandon Valenzuela", "Ernie Clement", "Andr\u00e9s Gim\u00e9nez", "Trey Yesavage", "Daulton Varsho", "Spencer Miles", "Tyler Rogers", "Nathan Lukes", "Jeff Hoffman", "Vladimir Guerrero Jr.", "Alejandro Kirk", "Patrick Corbin", "Mason Fluharty", "Jonatan Clase", "Joe Mantiply", "George Springer", "Luis Ur\u00edas", "Chad Dallas", "Braydon Fisher", "Cody Ponce", "Adam Macko", "Jes\u00fas S\u00e1nchez", "Simeon Woods Richardson", "CJ Van Eyk", "Tommy Nance", "Tanner Andrews", "Connor Seabold", "Yohendrick Pi\u00f1ango", "Myles Straw", "Hayden Juenger", "Addison Barger", "Eloy Jim\u00e9nez", "Tyler Heineman", "Lazaro Estrada", "Shane Bieber", "Josh Fleming"], "ranks": [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], "team": [3.7, 2.36, 2.23, 1.96, 1.69, 1.32, 1.23, 1.19, 1.13, 1.11, 1.05, 1.01, 0.92, 0.9, 0.74, 0.49, 0.46, 0.34, 0.22, 0.21, 0.18, 0.12, 0.09, 0.09, 0.08, 0.07, 0.05, 0.04, 0.03, 0.01, -0.0, -0.01, -0.02, -0.02, -0.04, -0.09, -0.13, -0.14, -0.14, -0.15]}; let WD_MODE='raw', WC_MODE='team'; const POSCOL={SP:'#2f7ed8',RP:'#6fb1ec',IF:'#f0a91c',OF:'#15b6a0',C:'#ec5a3c'}; const POSLAB={SP:'Starting P',RP:'Bullpen',IF:'Infield',OF:'Outfield',C:'Catcher'}; function buildPosLegend(){ const el=document.getElementById('poslegend'); if(!el) return; el.style.display=WC_MODE==='pos'?'flex':'none'; el.innerHTML=['SP','RP','C','IF','OF'].map(k=>''+POSLAB[k]+'').join(''); } function drawWarDist(){ if(!WARDIST) return; const dark=document.documentElement.getAttribute('data-theme')==='dark'; const col=contrastColor(TEAMCOL,dark), line=dark?'#e8eef0':'#111', grid=dark?'#2c313a':'#d6dbe2', ink=dark?'#cdd4dc':'#1d2630'; let team=WARDIST.team.slice(), league=WARDIST.league.slice(); if(WD_MODE==='prop'){ const ts=team.reduce((a,b)=>a+Math.max(b,0),0)||1, ls=league.reduce((a,b)=>a+Math.max(b,0),0)||1; team=team.map(v=>v/ts); league=league.map(v=>v/ls); } const fmt=WD_MODE==='prop'?'.1%':'.1f'; const names=WARDIST.names||[], cats=WARDIST.cats||[]; const cd=WARDIST.ranks.map((_,i)=>[names[i]||'(empty slot)', WARDIST.team[i], POSLAB[cats[i]]||'']); const barcol=WC_MODE==='pos'?cats.map(c=>POSCOL[c]||'#9aa3ad'):col; const posbit=WC_MODE==='pos'?' · %{customdata[2]}':''; const htmpl=WD_MODE==='prop' ? '%{customdata[0]}'+posbit+'
slot %{x} · %{customdata[1]:.1f} WAR
%{y:.1%} of roster WAR' : '%{customdata[0]}'+posbit+'
slot %{x} · %{y:.1f} WAR'; buildPosLegend(); const _div=document.getElementById('wardist'); const traces=[ {type:'bar',x:WARDIST.ranks,y:team,name:'TOR',marker:{color:barcol},customdata:cd,hovertemplate:htmpl}, {type:'scatter',x:WARDIST.ranks,y:league,name:'league avg',mode:'lines',line:{color:line,width:2,shape:'spline',smoothing:0.6},hovertemplate:'league slot %{x}: %{y:'+fmt+'}'} ]; const layout={margin:{l:48,r:12,t:6,b:38},paper_bgcolor:'rgba(0,0,0,0)',plot_bgcolor:'rgba(0,0,0,0)',font:FONT, transition:{duration:360,easing:'cubic-in-out'}, xaxis:{title:{text:'roster slot (ranked by WAR)',font:{size:11}},gridcolor:grid,zeroline:false,color:ink}, yaxis:{title:{text:WD_MODE==='prop'?'share of roster WAR':'WAR',font:{size:11}},gridcolor:grid,zeroline:true,zerolinecolor:grid,color:ink,tickformat:WD_MODE==='prop'?'.0%':undefined}, showlegend:true,legend:{orientation:'h',y:1.13,x:0,font:{size:11}},bargap:0.14}; if(_div.data&&_div.data.length){ Plotly.react(_div,traces,layout,{displayModeBar:false,responsive:true}); } else { Plotly.newPlot(_div,traces,layout,{displayModeBar:false,responsive:true}); } } window.wdMode=function(m){WD_MODE=m;document.getElementById('wd-raw').classList.toggle('on',m==='raw');document.getElementById('wd-prop').classList.toggle('on',m==='prop');drawWarDist();}; window.wcToggle=function(){WC_MODE=WC_MODE==='pos'?'team':'pos';const b=document.getElementById('wc-btn');b.classList.toggle('on',WC_MODE==='pos');b.textContent=WC_MODE==='pos'?'Show team color':'Color by position';drawWarDist();}; // ---- Graphs dropdown switcher (orbit ⇄ roster value distribution) ---- let _wdRendered=false; window.showTeamGraph=function(g){ document.querySelectorAll('.gpane').forEach(p=>{ p.style.display=(p.dataset.g===g)?'':'none'; }); if(g==='orbit'){ drawOrbit(); } else if(g==='roster'){ if(!_wdRendered){ drawWarDist(); _wdRendered=true; } else { try{ Plotly.Plots.resize('wardist'); }catch(e){ drawWarDist(); } } } }; { const _ts=document.getElementById('tgsel'); if(_ts) _ts.addEventListener('change',()=>showTeamGraph(_ts.value)); } // initial paint of the default pane (roster when the team has roster data, else orbit already drawn above) if(WARDIST){ drawWarDist(); _wdRendered=true; } // redraw the roster pane on theme change only if it's been rendered and is visible if(WARDIST){ new MutationObserver(()=>{ const p=document.querySelector('.gpane[data-g="roster"]'); if(_wdRendered && p && p.style.display!=='none') drawWarDist(); }).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']}); } // ---- TOC scroll-spy ---- (function(){ const links=[...document.querySelectorAll('.toc a')]; const secs=links.map(a=>document.querySelector(a.getAttribute('href'))).filter(Boolean); links.forEach(a=>a.addEventListener('click',e=>{e.preventDefault();document.querySelector(a.getAttribute('href')).scrollIntoView({behavior:'smooth'});})); if('IntersectionObserver' in window && secs.length){ const obs=new IntersectionObserver(es=>{es.forEach(e=>{if(e.isIntersecting) links.forEach(l=>l.classList.toggle('on',l.getAttribute('href')==='#'+e.target.id));});},{rootMargin:'-15% 0px -75% 0px'}); secs.forEach(s=>obs.observe(s)); } })(); // ---- sortable roster table ---- const SVC_RANK={'pre-arb':0,'arb':1,'FA-elig':2,'?':3}; function cellNum(td){const n=parseFloat(td.textContent.replace(/[^0-9.\-]/g,''));return isNaN(n)?null:n;} function scale(v,mn,mx,g){ if(g==='diverge'){ if(v>=0){const t=mx>0?v/mx:0;return `rgba(26,125,90,${(0.10+0.55*t).toFixed(2)})`;} const t=mn<0?v/mn:0;return `rgba(192,73,43,${(0.10+0.55*t).toFixed(2)})`; } if(g==='salmag'){const t=mx>mn?(v-mn)/(mx-mn):0;return `rgba(128,135,143,${(0.05+0.45*t).toFixed(2)})`;} const t=mx>mn?(v-mn)/(mx-mn):0;return `rgba(26,125,90,${(0.05+0.55*t).toFixed(2)})`; } function applyGradients(table){ [...table.tHead.rows[0].cells].forEach((th,ci)=>{ if(!th.dataset.grad) return; const rows=[...table.tBodies[0].rows], vals=rows.map(r=>cellNum(r.cells[ci])).filter(v=>v!==null); if(!vals.length) return; const mn=Math.min(...vals),mx=Math.max(...vals); rows.forEach(r=>{const v=cellNum(r.cells[ci]);if(v!==null)r.cells[ci].style.background=scale(v,mn,mx,th.dataset.grad);}); }); } function makeSortable(table){ const ths=[...table.tHead.rows[0].cells]; ths.forEach((th,ci)=>{ th.dataset.label=th.textContent.trim(); th.addEventListener('click',()=>{ th._dir=(th._dir===-1)?1:-1; const dir=th._dir, tb=table.tBodies[0], svc=th.dataset.sortType==='service'; [...tb.rows].sort((a,b)=>{ let va,vb; if(svc){va=SVC_RANK[a.cells[ci].textContent.trim()]??9;vb=SVC_RANK[b.cells[ci].textContent.trim()]??9;} else{va=cellNum(a.cells[ci]);vb=cellNum(b.cells[ci]); if(va===null||vb===null){va=a.cells[ci].textContent.trim().toLowerCase();vb=b.cells[ci].textContent.trim().toLowerCase();}} return vavb?dir:0; }).forEach(r=>tb.appendChild(r)); ths.forEach(h=>h.innerHTML=h.dataset.label); th.innerHTML=th.dataset.label+' '+(dir===1?'▲':'▼')+''; }); }); } document.querySelectorAll('table.sortable').forEach(t=>{applyGradients(t);makeSortable(t);});