RRosternomics

Guardians CLE

President of baseball ops: Chris Antonetti · GM: Mike Chernoff

Overview

WINDOW ↗ rising
cheap & winning · year 7 in this regime · payroll 0.42× league · TAB +12.0
+12.0TAB — talent above budget6th/30
+7.9WAB — realized wins (TAB + luck)5th/30
4.0depth — 2+ WAR regulars7th/30
-3.6dead WAR — sub-replacement drag13th/30
24%dead money %3rd/30
0.514win %11th/30
36.0roster fWAR12th/30
+161surplus $M (value vs cost)3rd/30
$68Mpayroll $M27th/30luxury tax $52M

Roster

CFSteven Kwan 42GDaniel Schneemann 25GPetey Halpin 16G
LFAngel Martínez 42GSteven Kwan 30GGeorge Valera 6G
RFChase DeLauter 38GAngel Martínez 25GDavid Fry 12G
SSBrayan Rocchio 59GGabriel Arias 10GDaniel Schneemann 4G
2BTravis Bazzana 40GJuan Brito 15GDaniel Schneemann 10G
3BJosé Ramírez 54GDaniel Schneemann 18GDavid Fry 2G
1BKyle Manzardo 48GRhys Hoskins 37GDavid Fry 1G
STARTERSParker Messick 2.9WARGavin Williams 2.3WARJoey Cantillo 1.5WARSlade Cecconi 0.9WARTanner Bibee 0.8WAR
CPatrick Bailey 47GAustin Hedges 32GBo Naylor 27G
DHChase DeLauter 24GJosé Ramírez 17GRhys Hoskins 14G
BULLPEN Cade Smith 21svErik Sabrowski 17hTim Herrin 10hHunter Gaddis 9hMatt Festa 7hShawn Armstrong 5hColin Holderman 4hPeyton Pallette 1h
CPatrick Bailey 47 · Austin Hedges 32 · Bo Naylor 27
1BKyle Manzardo 48 · Rhys Hoskins 37 · David Fry 1
2BTravis Bazzana 40 · Juan Brito 15 · Daniel Schneemann 10
3BJosé Ramírez 54 · Daniel Schneemann 18 · David Fry 2
SSBrayan Rocchio 59 · Gabriel Arias 10 · Daniel Schneemann 4
LFAngel Martínez 42 · Steven Kwan 30 · George Valera 6
CFSteven Kwan 42 · Daniel Schneemann 25 · Petey Halpin 16
RFChase DeLauter 38 · Angel Martínez 25 · David Fry 12
DHChase DeLauter 24 · José Ramírez 17 · Rhys Hoskins 14
SPParker Messick 2.9 · Gavin Williams 2.3 · Joey Cantillo 1.5 · Slade Cecconi 0.9 · Tanner Bibee 0.8
BP Cade Smith 21 · Erik Sabrowski 17 · Tim Herrin 10 · Hunter Gaddis 9 · Matt Festa 7 · Shawn Armstrong 5 · Colin Holderman 4 · Peyton Pallette 1

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
Brayan Rocchio 25 pre-arb 1025.1 0.8 +31.1
Parker Messick 25 pre-arb 204.5 0.8 +27.8
José Ramírez 33 FA-elig 743.5 25.0 -3.0
Gavin Williams 26 pre-arb 213.5 0.8 +21.2
Chase DeLauter 24 882.9 0.8 +17.5
Cade Smith 27 pre-arb 452.7 0.8 +16.3
Steven Kwan 28 arb 962.4 7.7 +7.3
Joey Cantillo 26 pre-arb 222.4 0.8 +14.0
Travis Bazzana 23 732.3 0.6 +14.1
Austin Hedges 33 FA-elig 521.4 4.0 +4.9
Colin Holderman 30 arb 391.4 1.5 +7.0
Slade Cecconi 27 pre-arb 201.3 0.8 +7.7
Angel Martínez 24 pre-arb 671.3 0.8 +7.5
Tanner Bibee 27 arb 211.2 4.4 +3.4
Gabriel Arias 26 pre-arb 371.0 0.8 +5.5
Erik Sabrowski 28 pre-arb 380.9 0.8 +5.0
Patrick Bailey↔ SFG 27 pre-arb 400.8 0.3 +4.7
Daniel Schneemann 29 pre-arb 830.7 0.8 +3.9
Matt Festa 33 pre-arb 440.4 1.0 +1.8
Hunter Gaddis 28 pre-arb 400.4 0.8 +1.9
Kahlil Watson 23 270.3
Franco Aleman 26 110.3 0.0 +1.7
Logan Allen 27 pre-arb 20.2 0.0 +1.4
Codi Heuer 29 arb 80.1 0.6 -0.0
Will Dion 26 90.1 0.5 -0.1
Petey Halpin 24 pre-arb 380.1 0.6 -0.2
Stuart Fairchild 30 140.1 0.5 -0.2
Daniel Espino 25 50.0 0.8 -0.7
Shawn Armstrong 35 FA-elig 38-0.1 4.0 -4.5
Kolby Allard 28 4-0.1 0.1 -0.6
Peyton Pallette 25 16-0.2 0.3 -1.3
David Fry 30 pre-arb 67-0.2 1.4 -2.6
CJ Kayfus 24 pre-arb 9-0.3 0.1 -1.9
Tim Herrin 29 pre-arb 42-0.4 0.8 -3.4
Connor Brogdon 31 arb 15-0.5 0.9 -3.8
Kyle Manzardo 25 pre-arb 94-0.5 0.8 -3.8
Juan Brito 24 15-0.5 0.1 -3.1
George Valera 25 pre-arb 13-0.6 0.2 -3.7
Rhys Hoskins 33 FA-elig 82-0.7 1.5 -5.9
Cooper Ingle 24 7-0.7
Bo Naylor 26 pre-arb 28-0.9 0.2 -5.8

Key additions (new vs 2025)

PlayerfWAR
Chase DeLauter1.9
Travis Bazzana1.5
Colin Holderman0.9
Patrick Bailey0.5
Codi Heuer0.1
Stuart Fairchild0.0

Key subtractions (gone from 2025)

Player2025 fWAR
Jakob Junis0.6
Carlos Santana0.5
Ben Lively0.4
Nic Enright0.2
Paul Sewald0.1
Zak Kent0.1

Current & Historical Payroll

2026 payroll
$67.6M
luxury tax $52M
2027 guaranteed
$40.4M

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$22M$25M$20M$10M$10M
Proj. arbitration$13M$7M
Proj. pre-arbitration$16M$8M
Player benefits$18M$18M$18M$18M$18M
Total CBT payroll$70M$50M$45M$28M$28M
Tax space+$174M+$197M+$205M+$225M+$228M
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-1 payor rates on the overage. Forward years show only guaranteed deals (arb/pre-arb not yet set). Figures are estimates.

Guaranteed / signed (5)

PlayerAgeSvc2026202720282029203020312032
José Ramírez3311.074$25.0M$25.0M$25.0M$25.0M$25.0M$25.0M$25.0M
Tanner Bibee273.000$4.4M$7.4M$10.4M$21.4M$21.0MCLUBFREE AGENT
Shawn Armstrong357.113$4.0M$8.0MMUTUALFREE AGENT
Austin Hedges339.166$4.0MFREE AGENT
Rhys Hoskins338.053$1.5MFREE AGENT

Arbitration-eligible (6)

PlayerAgeSvc2026202720282029203020312032
Steven Kwan284.000$7.7MARB 3FREE AGENT
Colin Holderman303.120$1.5MARB 2ARB 3FREE AGENT
Connor Brogdon314.090$900kARB 3FREE AGENT
Codi Heuer294.010$604kARB 3FREE AGENT
Stuart Fairchild303.114$505kARB 2ARB 3FREE AGENT
Kolby Allard285.021$100kFREE AGENT

Pre-arbitration (25)

PlayerAgeSvc2026202720282029203020312032
David Fry302.154$1.4MARB 1ARB 2ARB 3FREE AGENT
Matt Festa332.153$1.0MARB 1ARB 2ARB 3FREE AGENT
Cade Smith272.000$825kARB 1ARB 2ARB 3FREE AGENT
Gavin Williams262.103$822kARB 1ARB 2ARB 3FREE AGENT
Hunter Gaddis282.073$810kARB 1ARB 2ARB 3FREE AGENT
Tim Herrin292.058$801kARB 1ARB 2ARB 3FREE AGENT
Gabriel Arias262.124$801kARB 1ARB 2ARB 3FREE AGENT
Daniel Schneemann291.120$801kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kyle Manzardo251.073$798kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brayan Rocchio252.001$798kARB 1ARB 2ARB 3FREE AGENT
Joey Cantillo260.162$797kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Slade Cecconi271.155$796kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Erik Sabrowski281.033$792kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Parker Messick250.040$788kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Angel Martínez241.118$788kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chase DeLauter24$780k
Travis Bazzana23$634k
Petey Halpin240.009$621kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Patrick Bailey272.136$349kARB 1ARB 2ARB 3FREE AGENT
Peyton Pallette25$254k
Bo Naylor262.113$203kARB 1ARB 2ARB 3FREE AGENT
George Valera250.028$155kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Juan Brito24$88k
CJ Kayfus240.058$84kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Logan Allen272.093$13kARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Guardians 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, 0.635, -14.01], [1986, 0.653, -1.46], [1987, 0.776, -16.36], [1988, 0.64, -0.83], [1989, 0.718, -1.69], [1990, 0.886, -6.78], [1991, 0.638, -3.28], [1992, 0.289, 9.48], [1993, 0.555, -2.3], [1994, 0.957, 15.35], [1995, 1.175, 17.72], [1996, 1.564, 13.24], [1997, 1.436, 4.85], [1998, 1.353, 4.26], [1999, 1.524, 10.66], [2000, 1.443, 8.96], [2001, 1.497, 5.72], [2002, 1.049, -9.31], [2003, 0.571, -1.68], [2004, 0.516, 9.87], [2005, 0.592, 22.15], [2006, 0.629, 8.98], [2007, 0.76, 11.24], [2008, 0.786, 6.03], [2009, 0.746, -6.01], [2010, 0.539, -6.29], [2011, 0.633, -1.89], [2012, 0.72, -9.12], [2013, 0.788, 8.4], [2014, 0.702, 4.05], [2015, 0.62, 11.83], [2016, 0.653, 12.9], [2017, 0.932, 25.24], [2018, 1.093, 12.51], [2019, 0.963, 11.35], [2021, 0.359, 6.5], [2022, 0.454, 15.91], [2023, 0.604, 0.73], [2024, 0.566, 8.37], [2025, 0.516, 3.34], [2026, 0.423, 12.02]], TEAMCOL = '#E31937'; 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": ["IF", "SP", "IF", "SP", "OF", "RP", "OF", "SP", "IF", "C", "RP", "SP", "OF", "SP", "IF", "RP", "C", "IF", "RP", "RP", "OF", "RP", "RP", "RP", "RP", "OF", "OF", "RP", "RP", "RP", "RP", "OF", "OF", "RP", "RP", "IF", "IF", "OF", "IF", "IF"], "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": ["Brayan Rocchio", "Parker Messick", "Jos\u00e9 Ram\u00edrez", "Gavin Williams", "Chase DeLauter", "Cade Smith", "Steven Kwan", "Joey Cantillo", "Travis Bazzana", "Austin Hedges", "Colin Holderman", "Slade Cecconi", "Angel Mart\u00ednez", "Tanner Bibee", "Gabriel Arias", "Erik Sabrowski", "Patrick Bailey", "Daniel Schneemann", "Matt Festa", "Hunter Gaddis", "Kahlil Watson", "Franco Aleman", "Logan Allen", "Codi Heuer", "Will Dion", "Petey Halpin", "Stuart Fairchild", "Daniel Espino", "Shawn Armstrong", "Kolby Allard", "Peyton Pallette", "David Fry", "CJ Kayfus", "Tim Herrin", "Connor Brogdon", "Kyle Manzardo", "Juan Brito", "George Valera", "Rhys Hoskins", "Cooper Ingle"], "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.28, 2.94, 2.27, 2.27, 1.89, 1.76, 1.55, 1.53, 1.51, 0.92, 0.88, 0.87, 0.86, 0.8, 0.65, 0.6, 0.52, 0.49, 0.28, 0.28, 0.23, 0.18, 0.14, 0.06, 0.05, 0.04, 0.03, 0.01, -0.05, -0.05, -0.11, -0.12, -0.18, -0.27, -0.3, -0.31, -0.31, -0.37, -0.45, -0.47]}; 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:'CLE',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);});