RRosternomics

Pirates PIT

GM: Larry Doughty

Overview

CONTEND ↘ cooling
expensive & winning · year 2 in this regime · payroll 1.15× league · TAB +1.3
+1.3TAB — talent above budget14th/26
+12.3WAB — realized wins (TAB + luck)3rd/26
7.0depth — 2+ WAR regulars11th/26
-5.9dead WAR — sub-replacement drag20th/26
30%dead money %12th/26
0.593win %2nd/26
37.2roster fWAR9th/26
-6surplus $M (value vs cost)17th/26
$34Mpayroll $M6th/26

1992: made playoffs · 96–66. Ranks are vs the 26 teams that season.

Roster

CFAndy Van Slyke 154GCecil Espy 18GAlex Cole 6G
LFBarry Bonds 139GGary Varsho 14GCecil Espy 11G
RFCecil Espy 56GAlex Cole 54GLloyd McClendon 50G
SSJay Bell 159GCarlos García 8GJeff King 6G
2BJosé Lind 134GJeff King 32GCarlos García 14G
3BJeff King 73GJohn Wehner 34GKevin Young 7G
1BOrlando Merced 114GGary Redus 36GJeff King 32G
STARTERSDoug Drabek 4.9WARRandy Tomlin 2.9WARZane Smith 2.2WARDanny Jackson 1.6WARTim Wakefield 1.2WAR
CMichael LaValliere 92GDon Slaught 79GTom Prince 19G
BULLPEN Stan Belinda 18svRoger Mason 0hBob Patterson 0hJerry Don Gleaton 0hDennis Lamp 0hSteve Cooke 0hVictor Cole 0hPaul Miller 0h
CMichael LaValliere 92 · Don Slaught 79 · Tom Prince 19
1BOrlando Merced 114 · Gary Redus 36 · Jeff King 32
2BJosé Lind 134 · Jeff King 32 · Carlos García 14
3BJeff King 73 · John Wehner 34 · Kevin Young 7
SSJay Bell 159 · Carlos García 8 · Jeff King 6
LFBarry Bonds 139 · Gary Varsho 14 · Cecil Espy 11
CFAndy Van Slyke 154 · Cecil Espy 18 · Alex Cole 6
RFCecil Espy 56 · Alex Cole 54 · Lloyd McClendon 50
SPDoug Drabek 4.9 · Randy Tomlin 2.9 · Zane Smith 2.2 · Danny Jackson 1.6 · Tim Wakefield 1.2
BP Stan Belinda 18 · Roger Mason 0 · Bob Patterson 0 · Jerry Don Gleaton 0 · Dennis Lamp 0 · Steve Cooke 0 · Victor Cole 0 · Paul Miller 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.

1992 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Barry Bonds 27 FA-elig 1409.6 4.8 +2.7
Andy Van Slyke 31 FA-elig 1546.5 4.3 +0.7
Doug Drabek 29 FA-elig 354.9 4.5 -0.7
Jay Bell 26 FA-elig 1594.2 0.9 +2.4
Randy Tomlin 26 pre-arb 352.9 0.2 +2.1
Don Slaught 33 FA-elig 872.8 1.7 +0.5
Zane Smith 31 FA-elig 262.2 2.5 -0.8
Michael LaValliere 31 FA-elig 951.7 1.9 -0.5
Danny Jackson↔ CHC 30 FA-elig 151.6 1.2 +0.1
Steve Buechele↔ CHC 30 FA-elig 801.5 1.4 -0.3
Tim Wakefield 25 141.2
Lloyd McClendon 33 arb 840.8 0.5 +0.1
Orlando Merced 25 pre-arb 1340.6 0.1 +0.3
Gary Redus 35 FA-elig 760.4 0.8 -0.5
Vicente Palacios 28 arb 200.4 0.2 +0.2
Alex Cole↔ CLE 26 pre-arb 640.4 0.1 +0.2
Bob Walk 35 FA-elig 360.4 2.0 -1.7
Paul Miller 27 pre-arb 60.2 0.1 +0.0
Jeff King 27 arb 1300.2 0.3 -0.2
Paul Wagner 24 60.1
Kevin Young 23 100.1
Al Martin 24 120.1
Jeff Robinson↔ TEX 30 arb 80.1 0.1 +0.0
Danny Cox↔ PHI 32 FA-elig 160.1 0.4 -0.3
Steve Cooke 22 110.1
Dave Clark 29 230.0
Kirk Gibson 35 FA-elig 160.0 1.9 -1.9
Blas Minor 26 10.0
Victor Cole 24 80.0
Gary Varsho 31 arb 103-0.1 0.4 -0.4
Miguel Batista 21 pre-arb 1-0.1 0.1 -0.2
Stan Belinda 25 arb 59-0.2 0.3 -0.4
Will Pennyfeather 24 15-0.2
Bob Patterson 33 FA-elig 60-0.2 0.8 -0.9
Dennis Lamp 39 21-0.2
John Wehner 25 55-0.3
Denny Neagle 23 pre-arb 56-0.3 0.1 -0.3
Carlos Garcia 24 pre-arb 22-0.4 0.1 -0.4
Tom Prince 27 arb 27-0.4 0.1 -0.5
Cecil Espy 29 FA-elig 112-0.4 0.2 -0.6
Jerry Don Gleaton 34 23-0.6
Roger Mason 34 FA-elig 65-1.2 0.2 -1.1
Jose Lind 28 arb 135-1.5 2.0 -3.1

Key additions (new vs 1991)

PlayerfWAR
Danny Jackson1.6
Tim Wakefield1.2
Alex Cole0.4
Paul Wagner0.1
Kevin Young0.1
Al Martin0.1

Key subtractions (gone from 1991)

Player1991 fWAR
Bobby Bonilla4.7
John Smiley3.1
Bill Landrum0.9
Mark Huismann0.1
Neal Heaton0.1
Jeff Banister0.1

Current & Historical Payroll

1992 payroll
$34.4M

What the front office was committed to as of 1992, 1992–1998. 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 (19)

PlayerAgeSvc1992199319941995199619971998
Barry Bonds276.000$4.8MFREE AGENT
Doug Drabek296.000$4.5MFREE AGENT
Andy Van Slyke319.000$4.3M$4.9M$3.5MFREE AGENT
Zane Smith318.000$2.5M$2.7M$3.1MFREE AGENT$400k
Bob Walk3512.000$2.0M$2.2MFREE AGENT
Kirk Gibson3513.000$1.9MFREE AGENT
Michael LaValliere318.000$1.9M$2.3MFREE AGENT
Don Slaught3310.000$1.7M$1.1M$1.5M$2.0MFREE AGENT
Steve Buechele307.000$1.4MFREE AGENT
Danny Jackson309.000$1.2MFREE AGENT
Jay Bell266.000$875k$3.0M$3.4M$4.4M$4.7MFREE AGENT
Gary Redus3510.000$825kFREE AGENT
Bob Patterson337.000$785kFREE AGENT
Danny Cox329.000$400kFREE AGENT
Cecil Espy299.000$250kFREE AGENT
Roger Mason348.000$225kFREE AGENT
Dave Clark296.000$180k$380k$647k$688kFREE AGENT
Jerry Don Gleaton3413.000FREE AGENT
Dennis Lamp3915.000FREE AGENT

Arbitration-eligible (8)

PlayerAgeSvc1992199319941995199619971998
Jose Lind285.000$2.0MFREE AGENT
Lloyd McClendon335.000$525kFREE AGENT
Gary Varsho314.000$368kARB 3FREE AGENT
Jeff King273.000$325kARB 2ARB 3FREE AGENT
Stan Belinda253.000$280kARB 2ARB 3FREE AGENT
Vicente Palacios285.000$170kFREE AGENT
Tom Prince275.000$140kFREE AGENT
Jeff Robinson305.000$67kFREE AGENT

Pre-arbitration (16)

PlayerAgeSvc1992199319941995199619971998
Randy Tomlin262.000$180kARB 1ARB 2ARB 3FREE AGENT
Orlando Merced252.000$150kARB 1ARB 2ARB 3FREE AGENT
Carlos Garcia242.000$113kARB 1ARB 2ARB 3FREE AGENT
Denny Neagle231.000$110kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Miller271.000$109kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Miguel Batista210.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Alex Cole262.000$99kARB 1ARB 2ARB 3FREE AGENT
Victor Cole24
Steve Cooke220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Al Martin240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Blas Minor260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Will Pennyfeather240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Wagner240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tim Wakefield250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Wehner251.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Kevin Young230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Pirates 1992; 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 = 1992; 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": ["OF", "OF", "SP", "IF", "SP", "C", "SP", "C", "SP", "IF", "SP", "OF", "IF", "IF", "RP", "OF", "SP", "RP", "IF", "RP", "IF", "OF", "RP", "RP", "RP", "OF", "OF", "RP", "SP", "OF", "RP", "RP", "OF", "RP", "RP", "IF", "RP", "IF", "C", "OF"], "league": [5.9, 4.88, 4.15, 3.46, 2.93, 2.43, 2.02, 1.79, 1.57, 1.4, 1.23, 1.02, 0.87, 0.78, 0.69, 0.61, 0.52, 0.46, 0.4, 0.32, 0.26, 0.21, 0.15, 0.11, 0.06, 0.03, 0.0, -0.05, -0.08, -0.13, -0.17, -0.21, -0.27, -0.31, -0.37, -0.39, -0.43, -0.56, -0.58, -0.59], "names": ["Barry Bonds", "Andy Van Slyke", "Doug Drabek", "Jay Bell", "Randy Tomlin", "Don Slaught", "Zane Smith", "Michael LaValliere", "Danny Jackson", "Steve Buechele", "Tim Wakefield", "Lloyd McClendon", "Orlando Merced", "Gary Redus", "Vicente Palacios", "Alex Cole", "Bob Walk", "Paul Miller", "Jeff King", "Paul Wagner", "Kevin Young", "Al Martin", "Jeff Robinson", "Danny Cox", "Steve Cooke", "Dave Clark", "Kirk Gibson", "Blas Minor", "Victor Cole", "Gary Varsho", "Miguel Batista", "Stan Belinda", "Will Pennyfeather", "Bob Patterson", "Dennis Lamp", "John Wehner", "Denny Neagle", "Carlos Garcia", "Tom Prince", "Cecil Espy"], "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": [9.56, 6.52, 4.89, 4.16, 2.94, 2.84, 2.18, 1.72, 1.58, 1.51, 1.15, 0.75, 0.56, 0.44, 0.44, 0.4, 0.35, 0.19, 0.17, 0.14, 0.13, 0.11, 0.1, 0.08, 0.07, 0.04, 0.03, 0.01, 0.01, -0.1, -0.11, -0.15, -0.16, -0.18, -0.23, -0.27, -0.29, -0.37, -0.4, -0.43]}; 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);});