RRosternomics

Pirates PIT

GM: Ted Simmons

Overview

REBUILD → steady
cheap & losing · year 4 in this regime · payroll 0.67× league · TAB -7.8
-7.8TAB — talent above budget23rd/28
-3.8WAB — realized wins (TAB + luck)21st/28
5.0depth — 2+ WAR regulars21st/28
-8.7dead WAR — sub-replacement drag27th/28
25%dead money %16th/28
0.451win %23rd/28
21.6roster fWAR27th/28
-3surplus $M (value vs cost)13th/28
$22Mpayroll $M23rd/28

Roster

CFMike Kingery 64GJermaine Allensworth 61GAl Martin 26G
LFAl Martin 142GJohn Wehner 9GTrey Beamon 5G
RFOrlando Merced 115GDave Clark 28GMike Kingery 16G
SSJay Bell 151GCarlos García 19GNelson Liriano 5G
2BCarlos García 77GJeff King 71GNelson Liriano 36G
3BJohn Wehner 24GJeff King 17GCarlos García 14G
1BMark Johnson 100GJeff King 92GRich Aude 4G
STARTERSJon Lieber 1.6WARFrancisco Cordova 1.6WARZane Smith 1.2WARPaul Wagner 0.7WARJason Schmidt 0.4WAR
CJason Kendall 129GKeith Osik 41GAngelo Encarnacion 7G
BULLPEN Dan Plesac 11svMarc Wilkins 0hJason Christiansen 0hRamon Morel 0hJohn Ericks 0hDavid Wainhouse 0hElmer Dessens 0hLee Hancock 0h
CJason Kendall 129 · Keith Osik 41 · Angelo Encarnacion 7
1BMark Johnson 100 · Jeff King 92 · Rich Aude 4
2BCarlos García 77 · Jeff King 71 · Nelson Liriano 36
3BJohn Wehner 24 · Jeff King 17 · Carlos García 14
SSJay Bell 151 · Carlos García 19 · Nelson Liriano 5
LFAl Martin 142 · John Wehner 9 · Trey Beamon 5
CFMike Kingery 64 · Jermaine Allensworth 61 · Al Martin 26
RFOrlando Merced 115 · Dave Clark 28 · Mike Kingery 16
SPJon Lieber 1.6 · Francisco Cordova 1.6 · Zane Smith 1.2 · Paul Wagner 0.7 · Jason Schmidt 0.4
BP Dan Plesac 11 · Marc Wilkins 0 · Jason Christiansen 0 · Ramon Morel 0 · John Ericks 0 · David Wainhouse 0 · Elmer Dessens 0 · Lee Hancock 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.

1996 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Jeff King 31 FA-elig 1553.8 2.5 +0.7
Denny Neagle↔ ATL 27 arb 303.2 1.9 +0.8
Danny Darwin↔ HOU 40 FA-elig 192.8 0.3 +2.1
Orlando Merced 29 FA-elig 1202.8 2.7 -0.4
Jay Bell 30 FA-elig 1512.0 4.7 -3.0
Mark Johnson 28 pre-arb 1271.8 0.1 +1.4
Francisco Cordova 24 pre-arb 591.6 0.1 +1.3
Dan Plesac 34 FA-elig 731.6 1.1 +0.3
Jon Lieber 26 pre-arb 511.6 0.1 +1.2
Al Martin 28 arb 1551.5 0.5 +0.8
Zane Smith 35 FA-elig 161.2 0.4 +0.6
Jason Kendall 22 pre-arb 1301.1 0.1 +0.8
Paul Wagner 28 arb 170.7 0.3 +0.3
Carlos Garcia 28 FA-elig 1010.7 1.4 -0.8
Keith Osik 27 pre-arb 480.6 0.1 +0.4
Dale Sveum 32 120.5
Dave Clark↔ LAD 33 FA-elig 920.5 0.7 -0.3
Steve Parris 28 pre-arb 80.4 0.1 +0.2
Elmer Dessens 25 pre-arb 150.4 0.1 +0.2
Marc Wilkins 25 pre-arb 470.4 0.1 +0.2
Jason Schmidt↔ ATL 23 pre-arb 60.4 0.0 +0.3
Charlie Hayes↔ NYY 31 FA-elig 1280.3 1.5 -1.3
Tony Womack 26 170.3
Midre Cummings 24 240.1
Chris Peters 24 pre-arb 160.1 0.1 -0.1
Rich Loiselle 24 50.1
Steve Cooke 26 arb 3-0.0 0.2 -0.2
David Wainhouse 28 arb 17-0.1 0.1 -0.2
Angelo Encarnacion 27 7-0.1
Ramon Morel 21 pre-arb 29-0.2 0.1 -0.3
Matt Ruebel 26 pre-arb 26-0.2 0.1 -0.3
Joe Boever 35 13-0.2
Jacob Brumfield↔ TOR 31 arb 29-0.2 0.1 -0.3
Rich Aude 24 7-0.2
Trey Beamon 22 24-0.2
Jason Christiansen 26 pre-arb 33-0.2 0.1 -0.3
John Hope 25 5-0.3
Esteban Loaiza 24 pre-arb 11-0.3 0.1 -0.4
John Ericks 28 pre-arb 28-0.4 0.1 -0.5
Lee Hancock 29 pre-arb 13-0.5 0.1 -0.5
Darrell May↔ ANA 24 5-0.5
John Wehner 29 arb 86-0.5 0.2 -0.6
Jermaine Allensworth 24 pre-arb 61-0.6 0.1 -0.6
Dan Miceli 25 arb 44-0.8 0.2 -0.9
Nelson Liriano 32 FA-elig 112-0.9 0.3 -1.1
Mike Kingery 35 FA-elig 117-2.1 0.8 -2.6

Key additions (new vs 1995)

PlayerfWAR
Danny Darwin2.8
Francisco Cordova1.6
Zane Smith1.2
Jason Kendall1.1
Keith Osik0.6
Dale Sveum0.5

Key subtractions (gone from 1995)

Player1995 fWAR
Mark Parent1.2
Rick White0.7
Jim Gott0.2
Mike Maddux0.1
Mike Dyer0.1
Don Slaught0.0

Current & Historical Payroll

1996 payroll
$21.8M

What the front office was committed to as of 1996, 1996–2002. Pre-2015 has no contract-terms data — forward italic $ are realized actuals (hindsight, not commitments known then). Control labels (ARB/FA) are point-in-time.

current $ guaranteed $ actual $ (hindsight) arbitration (proj) pre-arb (proj) free agent

Guaranteed / signed (13)

PlayerAgeSvc1996199719981999200020012002
Jay Bell3010.000$4.7MFREE AGENT
Orlando Merced296.000$2.7MFREE AGENT
Jeff King317.000$2.5MFREE AGENT
Charlie Hayes318.000$1.5MFREE AGENT
Carlos Garcia286.000$1.4MFREE AGENT
Dan Plesac3410.000$1.1MFREE AGENT
Mike Kingery3510.000$750kFREE AGENT
Dave Clark3310.000$688kFREE AGENT
Zane Smith3512.000$400kFREE AGENT
Nelson Liriano329.000$350kFREE AGENT
Danny Darwin4018.000$307kFREE AGENT
Joe Boever3511.000FREE AGENT
Dale Sveum3210.000$400kFREE AGENT$200k

Arbitration-eligible (11)

PlayerAgeSvc1996199719981999200020012002
Denny Neagle275.000$1.9MFREE AGENT
Al Martin284.000$500kARB 3FREE AGENT
Paul Wagner284.000$275kARB 3FREE AGENT
Dan Miceli253.000$220kARB 2ARB 3FREE AGENT
John Wehner295.000$200kFREE AGENT
Steve Cooke264.000$180kARB 3FREE AGENT
David Wainhouse285.000$109kFREE AGENT
Jacob Brumfield314.000$85kARB 3FREE AGENT
Midre Cummings243.000ARB 2ARB 3FREE AGENT
John Hope253.000ARB 2ARB 3FREE AGENT
Tony Womack263.000ARB 2ARB 3FREE AGENT

Pre-arbitration (22)

PlayerAgeSvc1996199719981999200020012002
Esteban Loaiza241.000$150kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jon Lieber262.000$150kARB 1ARB 2ARB 3FREE AGENT
Jason Christiansen261.000$144kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Steve Parris281.000$135kPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Ericks281.000$135kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mark Johnson281.000$128kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Lee Hancock291.000$110kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ramon Morel211.000$110kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jermaine Allensworth240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jason Kendall220.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Francisco Cordova240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Elmer Dessens250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Peters240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Keith Osik270.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Marc Wilkins250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Ruebel260.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jason Schmidt231.000$35kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rich Aude24
Trey Beamon220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Angelo Encarnacion271.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Rich Loiselle240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Darrell May241.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Pirates 1996; 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.994, -10.2], [1986, 0.716, 0.52], [1987, 0.559, 12.18], [1988, 0.541, 10.89], [1989, 0.851, -1.23], [1990, 0.833, 13.44], [1991, 1.005, 13.53], [1992, 1.147, 1.31], [1993, 0.665, -1.3], [1994, 0.69, -7.9], [1995, 0.54, -6.8], [1996, 0.666, -7.78], [1997, 0.273, 2.07], [1998, 0.344, 1.55], [1999, 0.486, 3.48], [2000, 0.524, -2.74], [2001, 0.742, -14.99], [2002, 0.565, -7.14], [2003, 0.659, 0.8], [2004, 0.449, -1.06], [2005, 0.504, -5.41], [2006, 0.532, -6.45], [2007, 0.54, -3.47], [2008, 0.535, -9.72], [2009, 0.459, -1.65], [2010, 0.395, -15.14], [2011, 0.561, -11.48], [2012, 0.686, -6.18], [2013, 0.767, 8.23], [2014, 0.721, 9.24], [2015, 0.841, 11.31], [2016, 0.812, -0.52], [2017, 0.75, -3.64], [2018, 0.664, 4.59], [2019, 0.502, -3.88], [2021, 0.371, -9.98], [2022, 0.391, -8.07], [2023, 0.429, 0.89], [2024, 0.604, -7.07], [2025, 0.488, 0.03], [2026, 0.672, 14.53]], TEAMCOL = '#C9A227'; 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 = 1996; 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", "SP", "OF", "IF", "IF", "RP", "RP", "RP", "OF", "SP", "C", "SP", "IF", "C", "IF", "OF", "SP", "RP", "RP", "SP", "IF", "OF", "OF", "SP", "SP", "RP", "RP", "C", "RP", "RP", "RP", "OF", "IF", "OF", "RP", "SP", "SP", "RP", "RP"], "league": [6.48, 4.91, 3.86, 3.3, 2.79, 2.44, 2.17, 1.96, 1.68, 1.47, 1.24, 1.07, 0.92, 0.8, 0.68, 0.58, 0.51, 0.44, 0.37, 0.3, 0.25, 0.2, 0.15, 0.12, 0.07, 0.04, 0.01, -0.01, -0.03, -0.06, -0.09, -0.11, -0.14, -0.17, -0.2, -0.22, -0.29, -0.35, -0.38, -0.48], "names": ["Jeff King", "Denny Neagle", "Danny Darwin", "Orlando Merced", "Jay Bell", "Mark Johnson", "Francisco Cordova", "Dan Plesac", "Jon Lieber", "Al Martin", "Zane Smith", "Jason Kendall", "Paul Wagner", "Carlos Garcia", "Keith Osik", "Dale Sveum", "Dave Clark", "Steve Parris", "Elmer Dessens", "Marc Wilkins", "Jason Schmidt", "Charlie Hayes", "Tony Womack", "Midre Cummings", "Chris Peters", "Rich Loiselle", "Steve Cooke", "David Wainhouse", "Angelo Encarnacion", "Ramon Morel", "Matt Ruebel", "Joe Boever", "Jacob Brumfield", "Rich Aude", "Trey Beamon", "Jason Christiansen", "John Hope", "Esteban Loaiza", "John Ericks", "Lee Hancock"], "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.79, 3.23, 2.85, 2.79, 2.04, 1.78, 1.65, 1.63, 1.59, 1.49, 1.2, 1.06, 0.68, 0.66, 0.64, 0.49, 0.48, 0.43, 0.4, 0.4, 0.36, 0.27, 0.25, 0.07, 0.05, 0.05, -0.04, -0.07, -0.13, -0.19, -0.19, -0.19, -0.2, -0.21, -0.22, -0.22, -0.3, -0.31, -0.44, -0.48]}; 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:'PIT',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);});