RRosternomics

White Sox CHW

GM: Chris Getz

Overview

WINDOW ↗ rising
cheap & winning · year 1 in this regime · payroll 0.55× league · TAB +10.1
+10.1TAB — talent above budget8th/30
+7.1WAB — realized wins (TAB + luck)7th/30
7.0depth — 2+ WAR regulars1st/30
-3.7dead WAR — sub-replacement drag14th/30
68%dead money %29th/30
0.529win %8th/30
37.2roster fWAR10th/30
+149surplus $M (value vs cost)7th/30
$88Mpayroll $M24th/30luxury tax $89M

Roster

CFTristan Peters 54GLuisangel Acuña 30GDerek Hill 13G
LFSam Antonacci 46GRandal Grichuk 15GDerek Hill 13G
RFDerek Hill 22GJarred Kelenic 19GEverson Pereira 14G
SSColson Montgomery 56GLuisangel Acuña 21GChase Meidroth 5G
2BChase Meidroth 65GSam Antonacci 7GLuisangel Acuña 1G
3BMiguel Vargas 58GColson Montgomery 16GTanner Murray 3G
1BMunetaka Murakami 52GMiguel Vargas 11GJacob Gonzalez 10G
STARTERSDavis Martin 3.1WARSean Burke 2.2WARAnthony Kay 0.5WARNoah Schultz 0.1WARErick Fedde -0.1WAR
CEdgar Quero 39GDrew Romo 26GReese McGuire 10G
DHAndrew Benintendi 48GRandal Grichuk 14GMiguel Vargas 4G
BULLPEN Seranthony Domínguez 11svSean Newcomb 5hBryan Hudson 4hGrant Taylor 4hJordan Hicks 3hJordan Leasure 3hTyler Davis 0hBrandon Eisert 0h
CEdgar Quero 39 · Drew Romo 26 · Reese McGuire 10
1BMunetaka Murakami 52 · Miguel Vargas 11 · Jacob Gonzalez 10
2BChase Meidroth 65 · Sam Antonacci 7 · Luisangel Acuña 1
3BMiguel Vargas 58 · Colson Montgomery 16 · Tanner Murray 3
SSColson Montgomery 56 · Luisangel Acuña 21 · Chase Meidroth 5
LFSam Antonacci 46 · Randal Grichuk 15 · Derek Hill 13
CFTristan Peters 54 · Luisangel Acuña 30 · Derek Hill 13
RFDerek Hill 22 · Jarred Kelenic 19 · Everson Pereira 14
DHAndrew Benintendi 48 · Randal Grichuk 14 · Miguel Vargas 4
SPDavis Martin 3.1 · Sean Burke 2.2 · Anthony Kay 0.5 · Noah Schultz 0.1 · Erick Fedde -0.1
BP Seranthony Domínguez 11 · Sean Newcomb 5 · Bryan Hudson 4 · Grant Taylor 4 · Jordan Hicks 3 · Jordan Leasure 3 · Tyler Davis 0 · Brandon Eisert 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
Miguel Vargas 26 pre-arb 1015.1 0.8 +31.4
Davis Martin 29 pre-arb 204.9 0.8 +30.2
Tristan Peters 26 pre-arb 984.3 0.8 +26.4
Colson Montgomery 24 pre-arb 973.9 0.8 +23.9
Chase Meidroth 24 pre-arb 993.6 0.8 +21.8
Sean Burke 26 pre-arb 203.5 0.8 +21.2
Munetaka Murakami 26 673.3 16.5 +4.2
Grant Taylor 24 pre-arb 382.5 0.8 +14.7
Sam Antonacci 23 822.4 0.7 +14.6
Sean Newcomb 33 FA-elig 372.4 4.5 +10.4
Bryan Hudson 29 pre-arb 451.4 0.8 +8.3
Braden Montgomery 23 361.3 0.5 +7.5
Randal Grichuk↔ NYY 34 FA-elig 510.8 0.5 +4.5
Anthony Kay 31 pre-arb 210.8 5.0 -0.2
Jordan Hicks 29 FA-elig 310.5 8.5 -5.1
Jacob Gonzalez 24 300.5 0.5 +2.6
Everson Pereira 25 pre-arb 210.4 0.8 +1.9
Dustin Harris↔ HOU 26 60.3 0.0 +1.7
Chris Murphy 28 pre-arb 230.2 0.8 +0.7
Noah Schultz 22 120.2 0.7 +0.7
Tyler Davis 27 240.2 0.7 +0.7
Tyler Schweitzer 25 80.2 0.1 +0.9
Andrew Benintendi 31 FA-elig 900.1 17.1 -16.5
David Sandlin 25 40.1 0.5 -0.1
Kyle Teel 24 pre-arb 190.0 0.8 -0.5
Brandon Eisert↔ PIT 28 pre-arb 250.0 0.5 -0.3
Trevor Richards↔ PHI 33 FA-elig 260.0 0.9 -0.7
Jonathan Cannon 25 pre-arb 10.0 0.1 -0.1
Doug Nikhazy 26 1-0.0 0.8 -0.9
Derek Hill↔ PHI 30 arb 50-0.0 0.6 -0.7
Duncan Davitt 26 1-0.0 0.0 -0.1
Drew Romo 24 pre-arb 39-0.0 0.6 -0.8
Shane Smith 26 pre-arb 3-0.0 0.1 -0.3
Joe Rock 25 4-0.0 0.5 -0.7
Lucas Sims 32 9-0.0 0.2 -0.4
Erick Fedde 33 FA-elig 20-0.1 1.5 -2.1
Tanner Murray 26 13-0.1 0.7 -1.4
Junior Perez 24 21-0.2
Rikuu Nishida 25 12-0.2 0.5 -1.9
Seranthony Domínguez 31 FA-elig 39-0.2 7.5 -8.9
Osvaldo Bido↔ ATL 30 pre-arb 5-0.3 0.0 -1.7
Jedixson Paez 22 3-0.3
Jarred Kelenic↔ TEX 26 arb 19-0.3 0.1 -2.0
Tyler Gilbert 32 pre-arb 4-0.4 0.0 -2.3
Reese McGuire 31 11-0.4 1.2 -3.6
Lenyn Sosa↔ TOR 26 12-0.4 0.2 -2.9
Luisangel Acuña 24 pre-arb 72-0.5 0.8 -3.7
Austin Hays 30 FA-elig 12-0.6 5.0 -8.6
Jordan Leasure 27 pre-arb 18-0.6 0.7 -4.4
Edgar Quero 23 pre-arb 56-1.2 0.8 -8.2

Key additions (new vs 2025)

PlayerfWAR
Tristan Peters2.7
Munetaka Murakami2.1
Sam Antonacci1.5
Sean Newcomb1.5
Randal Grichuk0.5
Anthony Kay0.5

Key subtractions (gone from 2025)

Player2025 fWAR
Adrian Houser1.7
Mike Tauchman1.4
Luis Robert Jr.1.3
Aaron Civale0.9
Martín Pérez0.8
Tyler Alexander0.8

Current & Historical Payroll

2026 payroll
$87.8M
luxury tax $89M
2027 guaranteed
$64.6M

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$70M$62M$16M
Proj. arbitration$1M
Proj. pre-arbitration$19M
Player benefits$18M$18M$18M
Total CBT payroll$107M$80M$34M
Tax space+$137M+$167M+$216M
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 (11)

PlayerAgeSvc2026202720282029203020312032
Andrew Benintendi319.062$17.1M$15.1MFREE AGENT
Munetaka Murakami26$16.5M$17.5M
Jordan Hicks298.000$8.5M$8.5MFREE AGENT
Seranthony Domínguez317.131$7.5M$10.5M$12.0MMUTUALFREE AGENT
Anthony Kay312.006$5.0M$5.0M$10.0MMUTUALARB 3FREE AGENT
Austin Hays306.057$5.0M$8.0MMUTUALFREE AGENT
Sean Newcomb336.041$4.5MFREE AGENT
Erick Fedde336.099$1.5MFREE AGENT
Trevor Richards336.088$929kFREE AGENT
Randal Grichuk3411.033$464kFREE AGENT
Lucas Sims327.014$168kFREE AGENT

Arbitration-eligible (3)

PlayerAgeSvc2026202720282029203020312032
Reese McGuire315.155$1.2MFREE AGENT
Derek Hill303.040$577kARB 2ARB 3FREE AGENT
Jarred Kelenic263.035$106kARB 2ARB 3FREE AGENT

Pre-arbitration (31)

PlayerAgeSvc2026202720282029203020312032
Miguel Vargas262.101$806kARB 1ARB 2ARB 3FREE AGENT
Davis Martin291.117$799kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Colson Montgomery240.087$798kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Murphy282.071$797kARB 1ARB 2ARB 3FREE AGENT
Sean Burke261.011$793kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chase Meidroth240.171$789kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kyle Teel240.115$788kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Grant Taylor240.111$788kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Edgar Quero230.165$787kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Luisangel Acuña240.156$786kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Everson Pereira250.090$783kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tristan Peters260.007$780kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bryan Hudson291.070$780kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Doug Nikhazy260.004$780kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tanner Murray26$730k
Jordan Leasure271.162$729kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Noah Schultz22$692k
Sam Antonacci23$688k
Tyler Davis27$655k
Drew Romo240.073$647kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Brandon Eisert281.034$512kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Joe Rock25$459k
Lenyn Sosa262.044$215kARB 1ARB 2ARB 3FREE AGENT
Jonathan Cannon251.114$105kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Shane Smith261.000$66kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tyler Schweitzer25$54k
Osvaldo Bido301.161$39kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dustin Harris260.061$25kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tyler Gilbert322.075$24kARB 1ARB 2ARB 3FREE AGENT
Duncan Davitt26$13k
Jedixson Paez22

Graphs

Every roster slot ranked best→worst by WAR. Bars = White Sox 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.983, -7.0], [1986, 0.926, -7.46], [1987, 0.992, -1.82], [1988, 0.567, -9.85], [1989, 0.619, -8.5], [1990, 0.551, 8.35], [1991, 0.732, 8.92], [1992, 0.995, 5.95], [1993, 1.32, 8.47], [1994, 1.081, 18.36], [1995, 1.248, -4.04], [1996, 1.394, 4.15], [1997, 1.323, -9.12], [1998, 0.94, -4.79], [1999, 0.513, -2.63], [2000, 0.631, 13.73], [2001, 1.01, 2.05], [2002, 0.806, 4.55], [2003, 0.872, 12.38], [2004, 1.062, 1.21], [2005, 1.036, 6.3], [2006, 1.375, 0.45], [2007, 1.316, -17.38], [2008, 1.419, 1.02], [2009, 1.088, -6.34], [2010, 1.262, 2.6], [2011, 1.323, -2.77], [2012, 1.199, 0.61], [2013, 1.031, -13.42], [2014, 0.719, -5.6], [2015, 0.947, -7.42], [2016, 1.049, -2.08], [2017, 0.763, -11.15], [2018, 0.649, -8.66], [2019, 0.684, -6.72], [2021, 1.09, 14.52], [2022, 1.337, -5.46], [2023, 1.05, -22.25], [2024, 0.77, -27.48], [2025, 0.471, -7.53], [2026, 0.55, 10.07]], TEAMCOL = '#1A1A1A'; 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", "OF", "IF", "IF", "SP", "IF", "RP", "OF", "RP", "RP", "OF", "OF", "SP", "RP", "IF", "OF", "OF", "RP", "SP", "RP", "RP", "OF", "SP", "C", "RP", "RP", "RP", "RP", "OF", "RP", "C", "SP", "RP", "RP", "SP", "OF", "OF", "OF", "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": ["Miguel Vargas", "Davis Martin", "Tristan Peters", "Colson Montgomery", "Chase Meidroth", "Sean Burke", "Munetaka Murakami", "Grant Taylor", "Sam Antonacci", "Sean Newcomb", "Bryan Hudson", "Braden Montgomery", "Randal Grichuk", "Anthony Kay", "Jordan Hicks", "Jacob Gonzalez", "Everson Pereira", "Dustin Harris", "Chris Murphy", "Noah Schultz", "Tyler Davis", "Tyler Schweitzer", "Andrew Benintendi", "David Sandlin", "Kyle Teel", "Brandon Eisert", "Trevor Richards", "Jonathan Cannon", "Doug Nikhazy", "Derek Hill", "Duncan Davitt", "Drew Romo", "Shane Smith", "Joe Rock", "Lucas Sims", "Erick Fedde", "Tanner Murray", "Junior Perez", "Rikuu Nishida", "Seranthony Dom\u00ednguez"], "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.22, 3.1, 2.71, 2.47, 2.26, 2.2, 2.07, 1.54, 1.53, 1.49, 0.91, 0.79, 0.49, 0.48, 0.34, 0.31, 0.27, 0.17, 0.15, 0.14, 0.14, 0.09, 0.06, 0.04, 0.02, 0.02, 0.02, 0.0, -0.01, -0.01, -0.01, -0.02, -0.02, -0.02, -0.03, -0.06, -0.07, -0.11, -0.14, -0.14]}; 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:'CHW',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);});