RRosternomics

Rockies COL

GM: Daniel O'Dowd

Overview

FORCED-SPEND ↗ rising
expensive & losing · year 6 in this regime · payroll 1.07× league · TAB -3.2
-3.2TAB — talent above budget20th/30
-0.8WAB — realized wins (TAB + luck)15th/30
5.0depth — 2+ WAR regulars20th/30
-5.7dead WAR — sub-replacement drag17th/30
31%dead money %19th/30
0.506win %15th/30
31.9roster fWAR17th/30
-12surplus $M (value vs cost)19th/30
$58Mpayroll $M13th/30

Roster

CFTodd Hollandsworth 72GJuan Pierre 50GJeffrey Hammonds 9G
LFTodd Hollandsworth 40GTerry Shumpert 40GDarren Bragg 34G
RFJeffrey Hammonds 85GLarry Walker 52GButch Huskey 23G
SSNeifi Perez 162GTerry Shumpert 7GElvis Pena 3G
2BJeff Frye 79GTodd Walker 71GTerry Shumpert 23G
3BJeff Cirillo 155GTerry Shumpert 15GAaron Ledesma 5G
1BTodd Helton 160GButch Huskey 17GTerry Shumpert 6G
STARTERSPedro Astacio 2.8WARBrian Bohanon 2.4WARMasato Yoshii 1.7WARJulian Tavarez 1.1WARBrian Rose 0.4WAR
CBrent Mayne 105GBen Petrick 48GAdam Melhuse 1G
DHLarry Walker 3GBubba Carpenter 2GTerry Shumpert 1G
BULLPEN Jose Jimenez 24svGabe White 16hMike Myers 15hMike DeJean 7hBobby Chouinard 3hJerry Dipoto 3hCraig House 2hJohn Wasdin 0h
CBrent Mayne 105 · Ben Petrick 48 · Adam Melhuse 1
1BTodd Helton 160 · Butch Huskey 17 · Terry Shumpert 6
2BJeff Frye 79 · Todd Walker 71 · Terry Shumpert 23
3BJeff Cirillo 155 · Terry Shumpert 15 · Aaron Ledesma 5
SSNeifi Perez 162 · Terry Shumpert 7 · Elvis Pena 3
LFTodd Hollandsworth 40 · Terry Shumpert 40 · Darren Bragg 34
CFTodd Hollandsworth 72 · Juan Pierre 50 · Jeffrey Hammonds 9
RFJeffrey Hammonds 85 · Larry Walker 52 · Butch Huskey 23
DHLarry Walker 3 · Bubba Carpenter 2 · Terry Shumpert 1
SPPedro Astacio 2.8 · Brian Bohanon 2.4 · Masato Yoshii 1.7 · Julian Tavarez 1.1 · Brian Rose 0.4
BP Jose Jimenez 24 · Gabe White 16 · Mike Myers 15 · Mike DeJean 7 · Bobby Chouinard 3 · Jerry Dipoto 3 · Craig House 2 · John Wasdin 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.

2000 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Todd Helton 26 arb 1608.3 1.3 +10.5
Gabe White↔ CIN 28 FA-elig 673.1 0.6 +3.8
Jeff Cirillo 30 FA-elig 1573.0 3.6 +0.7
Pedro Astacio 31 FA-elig 322.8 6.3 -2.4
Brian Bohanon 31 FA-elig 352.4 3.0 +0.4
Larry Walker 33 FA-elig 871.9 12.1 -9.5
Jeffrey Hammonds 29 FA-elig 1221.7 3.2 -0.8
Masato Yoshii 35 pre-arb 291.7 3.0 -0.6
Jose Jimenez 26 pre-arb 721.4 0.2 +1.8
Rolando Arrojo↔ BOS 34 pre-arb 191.3 0.2 +1.6
Julian Tavarez 27 FA-elig 511.1 1.2 +0.4
Tom Goodwin↔ LAD 31 FA-elig 911.0 2.1 -0.7
Mike Myers 31 arb 780.9 0.9 +0.4
Todd Hollandsworth↔ LAD 27 arb 560.8 0.6 +0.6
Butch Huskey↔ MIN 28 FA-elig 450.8 0.2 +1.0
Todd Walker↔ MIN 27 arb 570.8 0.2 +0.8
Jeff Frye↔ BOS 33 FA-elig 370.7 0.9 +0.1
John Wasdin↔ BOS 27 arb 140.6 0.2 +0.7
Brent Mayne 32 FA-elig 1170.5 1.8 -1.1
Bobby Chouinard 28 310.4
Jeff Manto 35 FA-elig 70.4 0.5 +0.1
Brian Rose↔ BOS 24 arb 120.4 0.1 +0.4
Neifi Perez 27 arb 1620.3 2.2 -1.7
Kevin Jarvis 30 250.3
Ben Petrick 23 520.3
Rick Croushore↔ BOS 29 60.2
Jerry Dipoto 32 FA-elig 170.2 2.4 -2.1
Terry Shumpert 33 FA-elig 1150.1 0.7 -0.6
Elvis Pena 23 100.1
Pete Walker 31 3-0.1
David Moraga↔ WSN 24 1-0.1
Bubba Carpenter 31 15-0.1
Scott Karl↔ ANA 28 arb 17-0.1 2.7 -2.9
Manny Aybar↔ CIN 28 arb 1-0.1 0.0 -0.2
Mike DeJean 29 arb 54-0.1 0.6 -0.8
Brian L. Hunter↔ CIN 29 FA-elig 72-0.2 0.2 -0.5
Angel Echevarria↔ MIL 29 10-0.2
Adam Melhuse↔ LAD 28 23-0.2
Aaron Ledesma 29 arb 32-0.2 0.5 -0.8
Rigo Beltran 30 1-0.2
Stan Belinda↔ ATL 33 FA-elig 47-0.2 1.2 -1.6
David Lee 27 pre-arb 7-0.2 0.2 -0.6
Giovanni Carrara 32 8-0.3
Juan Pierre 22 51-0.3
Carlos Mendoza 25 13-0.3
Craig House 22 16-0.4
Mike Lansing↔ BOS 32 FA-elig 90-0.6 4.0 -4.9
Scott Servais↔ SFG 33 FA-elig 33-0.8 0.5 -1.6
Darren Bragg 30 FA-elig 71-0.9 0.8 -2.0

Key additions (new vs 1999)

PlayerfWAR
Gabe White3.1
Jeff Cirillo3.0
Jeffrey Hammonds1.7
Masato Yoshii1.7
Jose Jimenez1.4
Rolando Arrojo1.3

Key subtractions (gone from 1999)

Player1999 fWAR
Curt Leskanic1.2
Jamey Wright0.8
Darryl Kile0.6
Vinny Castilla0.6
Henry Blanco0.5
John Thomson0.2

Current & Historical Payroll

2000 payroll
$58.3M

What the front office was committed to as of 2000, 2000–2006. 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 (20)

PlayerAgeSvc2000200120022003200420052006
Larry Walker3311.000$12.1M$12.2M$12.7M$12.7M$6.8MFREE AGENT
Pedro Astacio318.000$6.3M$5.8MFREE AGENT
Mike Lansing327.000$4.0MFREE AGENT
Jeff Cirillo306.000$3.6M$4.8MFREE AGENT
Jeffrey Hammonds297.000$3.2MFREE AGENT
Brian Bohanon3110.000$3.0M$4.0MFREE AGENT
Jerry Dipoto327.000$2.4MFREE AGENT
Tom Goodwin319.000$2.1MFREE AGENT
Brent Mayne3210.000$1.8M$1.1MFREE AGENT
Stan Belinda3311.000$1.2MFREE AGENT
Julian Tavarez277.000$1.2MFREE AGENT
Jeff Frye338.000$873kFREE AGENT
Darren Bragg306.000$750kFREE AGENT
Terry Shumpert3310.000$650k$725k$775kFREE AGENT
Gabe White286.000$621k$1.2MFREE AGENT
Scott Servais339.000$516kFREE AGENT
Jeff Manto3510.000$450kFREE AGENT
Brian L. Hunter296.000$208kFREE AGENT
Butch Huskey287.000$206kFREE AGENT
Kevin Jarvis306.000FREE AGENT$3.4M

Arbitration-eligible (16)

PlayerAgeSvc2000200120022003200420052006
Scott Karl285.000$2.7MFREE AGENT
Neifi Perez274.000$2.2MARB 3FREE AGENT
Todd Helton263.000$1.3MARB 2ARB 3FREE AGENT
Mike Myers315.000$933kFREE AGENT
Todd Hollandsworth275.000$593kFREE AGENT
Mike DeJean293.000$560kARB 2ARB 3FREE AGENT
Aaron Ledesma295.000$525kFREE AGENT
Todd Walker274.000$230kARB 3FREE AGENT
John Wasdin275.000$224kFREE AGENT
Brian Rose243.000$111kARB 2ARB 3FREE AGENT
Manny Aybar283.000$5kARB 2ARB 3FREE AGENT
Rigo Beltran303.000ARB 2ARB 3FREE AGENT
Giovanni Carrara325.000FREE AGENT
Bobby Chouinard284.000ARB 3FREE AGENT
Angel Echevarria294.000ARB 3FREE AGENT
Pete Walker315.000FREE AGENT

Pre-arbitration (13)

PlayerAgeSvc2000200120022003200420052006
Masato Yoshii352.000$3.0MARB 1ARB 2ARB 3FREE AGENT
Jose Jimenez262.000$245kARB 1ARB 2ARB 3FREE AGENT
David Lee271.000$222kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rolando Arrojo342.000$193kARB 1ARB 2ARB 3FREE AGENT
Carlos Mendoza25
Rick Croushore292.000ARB 1ARB 2ARB 3FREE AGENT
Ben Petrick231.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Bubba Carpenter31
Adam Melhuse280.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
David Moraga24
Craig House22
Juan Pierre220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Elvis Pena23

Graphs

Every roster slot ranked best→worst by WAR. Bars = Rockies 2000; 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 = [[1993, 0.399, -8.04], [1994, 0.744, -13.36], [1995, 1.078, -5.41], [1996, 1.066, -8.19], [1997, 1.123, -5.39], [1998, 1.191, -6.85], [1999, 1.16, -26.39], [2000, 1.07, -3.2], [2001, 1.086, 1.72], [2002, 0.84, -16.82], [2003, 0.943, -8.28], [2004, 0.766, -9.69], [2005, 0.558, -6.7], [2006, 0.581, 3.08], [2007, 0.697, 9.42], [2008, 0.812, -3.97], [2009, 0.875, 6.9], [2010, 0.939, 2.38], [2011, 0.973, -7.85], [2012, 0.774, -10.21], [2013, 0.747, -0.43], [2014, 0.878, -8.06], [2015, 0.823, -13.94], [2016, 0.729, 4.03], [2017, 0.82, 1.52], [2018, 1.051, -3.37], [2019, 1.181, -17.95], [2021, 0.688, -6.7], [2022, 0.887, -11.48], [2023, 1.025, -27.06], [2024, 0.852, -23.85], [2025, 0.734, -33.15], [2026, 0.565, -9.12]], TEAMCOL = '#5F259F'; 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 = 2000; 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", "RP", "IF", "SP", "SP", "OF", "OF", "SP", "RP", "SP", "RP", "OF", "RP", "OF", "OF", "IF", "IF", "RP", "C", "RP", "IF", "SP", "IF", "SP", "C", "RP", "RP", "IF", "IF", "RP", "RP", "OF", "SP", "RP", "RP", "OF", "IF", "IF", "IF", "SP"], "league": [6.33, 4.81, 3.73, 3.02, 2.69, 2.28, 2.0, 1.76, 1.57, 1.41, 1.28, 1.12, 0.96, 0.83, 0.76, 0.67, 0.6, 0.52, 0.42, 0.35, 0.3, 0.25, 0.2, 0.16, 0.12, 0.08, 0.05, 0.03, 0.01, -0.02, -0.04, -0.06, -0.09, -0.12, -0.16, -0.18, -0.19, -0.21, -0.29, -0.38], "names": ["Todd Helton", "Gabe White", "Jeff Cirillo", "Pedro Astacio", "Brian Bohanon", "Larry Walker", "Jeffrey Hammonds", "Masato Yoshii", "Jose Jimenez", "Rolando Arrojo", "Julian Tavarez", "Tom Goodwin", "Mike Myers", "Todd Hollandsworth", "Butch Huskey", "Todd Walker", "Jeff Frye", "John Wasdin", "Brent Mayne", "Bobby Chouinard", "Jeff Manto", "Brian Rose", "Neifi Perez", "Kevin Jarvis", "Ben Petrick", "Rick Croushore", "Jerry Dipoto", "Terry Shumpert", "Elvis Pena", "Pete Walker", "David Moraga", "Bubba Carpenter", "Scott Karl", "Manny Aybar", "Mike DeJean", "Brian L. Hunter", "Angel Echevarria", "Adam Melhuse", "Aaron Ledesma", "Rigo Beltran"], "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": [8.29, 3.11, 3.02, 2.76, 2.41, 1.86, 1.67, 1.67, 1.45, 1.25, 1.14, 1.02, 0.95, 0.83, 0.82, 0.76, 0.68, 0.64, 0.49, 0.45, 0.35, 0.35, 0.34, 0.34, 0.32, 0.24, 0.2, 0.06, 0.05, -0.05, -0.06, -0.07, -0.08, -0.12, -0.14, -0.17, -0.18, -0.21, -0.22, -0.22]}; 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:'COL',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);});