RRosternomics

Orioles BAL

GM: Roland Hemond

Overview

WINDOW ↗ rising
cheap & winning · year 2 in this regime · payroll 0.54× league · TAB +1.7
+1.7TAB — talent above budget11th/26
+2.2WAB — realized wins (TAB + luck)10th/26
6.0depth — 2+ WAR regulars16th/26
-4.5dead WAR — sub-replacement drag14th/26
36%dead money %15th/26
0.472win %18th/26
28.5roster fWAR18th/26
+3surplus $M (value vs cost)5th/26
$9Mpayroll $M26th/26

Roster

CFMike Devereaux 104GSteve Finley 44GDave Gallagher 24G
LFBrady Anderson 44GDave Gallagher 32GJoe Orsulak 30G
RFJoe Orsulak 80GSteve Finley 73GBrad Komminsk 29G
SSCal Ripken Jr. 161GRene Gonzales 9GJuan Bell 1G
2BBilly Ripken 127GRene Gonzales 43GTim Hulett Sr. 16G
3BCraig Worthington 131GTim Hulett Sr. 24GRene Gonzales 16G
1BRandy Milligan 98GDavid Segui 36GRon Kittle 30G
STARTERSPete Harnisch 2.0WARBen McDonald 1.8WARJohn Mitchell 0.9WARJose Mesa 0.5WARDave Johnson 0.1WAR
CMickey Tettleton 90GBob Melvin 76GChris Hoiles 7G
DHRon Kittle 61GSam Horn 59GMickey Tettleton 39G
BULLPEN Gregg Olson 37svJoe Price 0hMark Williamson 0hKevin Hickey 0hCurt Schilling 0hBrian Holton 0hJose Bautista 0hMickey Weston 0h
CMickey Tettleton 90 · Bob Melvin 76 · Chris Hoiles 7
1BRandy Milligan 98 · David Segui 36 · Ron Kittle 30
2BBilly Ripken 127 · Rene Gonzales 43 · Tim Hulett Sr. 16
3BCraig Worthington 131 · Tim Hulett Sr. 24 · Rene Gonzales 16
SSCal Ripken Jr. 161 · Rene Gonzales 9 · Juan Bell 1
LFBrady Anderson 44 · Dave Gallagher 32 · Joe Orsulak 30
CFMike Devereaux 104 · Steve Finley 44 · Dave Gallagher 24
RFJoe Orsulak 80 · Steve Finley 73 · Brad Komminsk 29
DHRon Kittle 61 · Sam Horn 59 · Mickey Tettleton 39
SPPete Harnisch 2.0 · Ben McDonald 1.8 · John Mitchell 0.9 · Jose Mesa 0.5 · Dave Johnson 0.1
BP Gregg Olson 37 · Joe Price 0 · Mark Williamson 0 · Kevin Hickey 0 · Curt Schilling 0 · Brian Holton 0 · Jose Bautista 0 · Mickey Weston 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.

1990 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Cal Ripken Jr. 29 FA-elig 1616.2 1.3 +1.4
Randy Milligan 28 arb 1093.9 0.2 +1.6
Billy Ripken 25 arb 1292.9 0.2 +1.1
Mickey Tettleton 29 FA-elig 1352.9 0.8 +0.5
Joe Orsulak 28 FA-elig 1242.2 0.6 +0.4
Pete Harnisch 23 pre-arb 312.0 0.1 +0.8
Gregg Olson 23 pre-arb 641.8 0.3 +0.5
Ben McDonald 22 pre-arb 211.8 0.2 +0.5
Tim Hulett Sr. 30 FA-elig 531.0 0.2 +0.2
Sam Horn 26 arb 790.9 0.1 +0.3
John Mitchell 24 arb 240.9 0.1 +0.3
Curt Schilling 23 pre-arb 350.7 0.1 +0.2
Mark Williamson 30 arb 490.7 0.3 +0.0
Bob Melvin 28 arb 930.7 0.3 -0.1
Mike Devereaux 27 arb 1080.6 0.1 +0.1
Phil Bradley↔ CHW 31 FA-elig 720.5 0.7 -0.5
Brad Komminsk↔ SFG 29 460.5
Craig Worthington 25 pre-arb 1330.5 0.2 +0.0
Jose Mesa 24 arb 70.5 0.1 +0.1
Rene Gonzales 29 FA-elig 670.4 0.2 -0.0
Brady Anderson 26 pre-arb 890.3 0.1 +0.0
Joe Price 33 FA-elig 500.2 0.3 -0.2
Donell Nixon 28 80.2
Jay Tibbs↔ PIT 28 FA-elig 100.1 0.2 -0.2
Dave Johnson 30 arb 300.1 0.1 -0.1
Anthony Telford 24 pre-arb 80.1 0.1 -0.1
Dave Gallagher↔ CHW 29 arb 230.1 0.0 +0.0
Steve Finley 25 pre-arb 1420.1 0.1 -0.1
Dorn Taylor 31 40.1
Danny Boone 36 40.0
Bob Milacki 25 pre-arb 27-0.0 0.2 -0.2
Jose Bautista 25 pre-arb 22-0.1 0.1 -0.2
Mike Smith 26 pre-arb 2-0.1 0.1 -0.2
Jay Aldrich 29 7-0.1
Marty Brown 27 pre-arb 9-0.1 0.1 -0.2
Leo Gomez 24 pre-arb 12-0.1 0.1 -0.2
Kevin Hickey 34 FA-elig 37-0.1 0.2 -0.3
Juan Bell 22 pre-arb 5-0.2 0.1 -0.2
Jeff McKnight 27 29-0.2
Brian Holton 30 arb 33-0.3 0.3 -0.5
Chris Hoiles 25 pre-arb 23-0.3 0.1 -0.2
Greg Walker↔ CHW 30 14-0.4
Stan Jefferson↔ CLE 27 arb 10-0.4 0.0 -0.2
Mickey Weston 29 pre-arb 9-0.4 0.1 -0.3
Ron Kittle↔ CHW 32 FA-elig 22-0.5 0.1 -0.4
David Segui 23 pre-arb 40-0.6 0.1 -0.3
Jeff Ballard 26 arb 44-0.6 0.2 -0.5

Key additions (new vs 1989)

PlayerfWAR
Sam Horn0.9
John Mitchell0.9
Brad Komminsk0.5
Jose Mesa0.5
Joe Price0.2
Donell Nixon0.2

Key subtractions (gone from 1989)

Player1989 fWAR
Mark Thurmond0.7
Mark Huismann0.5
Jamie Quirk0.3
Francisco Melendez-0.0
Butch Davis-0.0
Jim Traber-0.1

Current & Historical Payroll

1990 payroll
$9.2M

What the front office was committed to as of 1990, 1990–1996. 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 (12)

PlayerAgeSvc1990199119921993199419951996
Cal Ripken Jr.299.000$1.3M$2.6M$2.1M$5.2M$5.5M$6.7M$6.7M
Mickey Tettleton296.000$750kFREE AGENT
Phil Bradley317.000$708kFREE AGENT
Joe Orsulak287.000$610k$1.1M$1.3MFREE AGENT
Joe Price3310.000$325kFREE AGENT
Kevin Hickey349.000$250kFREE AGENT
Jay Tibbs286.000$223kFREE AGENT
Tim Hulett Sr.307.000$208k$328k$420k$400k$550kFREE AGENT
Rene Gonzales296.000$191kFREE AGENT
Ron Kittle328.000$115kFREE AGENT
Brad Komminsk297.000FREE AGENT
Greg Walker308.000FREE AGENT

Arbitration-eligible (15)

PlayerAgeSvc1990199119921993199419951996
Bob Melvin285.000$350kFREE AGENT
Brian Holton305.000$348kFREE AGENT
Mark Williamson303.000$275kARB 2ARB 3FREE AGENT
Billy Ripken253.000$215kARB 2ARB 3FREE AGENT
Jeff Ballard263.000$200kARB 2ARB 3FREE AGENT
Randy Milligan283.000$155kARB 2ARB 3FREE AGENT
Mike Devereaux273.000$145kARB 2ARB 3FREE AGENT
Sam Horn263.000$120kARB 2ARB 3FREE AGENT
Dave Johnson303.000$114kARB 2ARB 3FREE AGENT
John Mitchell244.000$111kARB 3FREE AGENT
Jose Mesa243.000$100kARB 2ARB 3FREE AGENT
Dave Gallagher293.000$34kARB 2ARB 3FREE AGENT
Stan Jefferson274.000$26kARB 3FREE AGENT
Jay Aldrich293.000ARB 2ARB 3FREE AGENT
Donell Nixon283.000ARB 2ARB 3FREE AGENT

Pre-arbitration (20)

PlayerAgeSvc1990199119921993199419951996
Gregg Olson232.000$270kARB 1ARB 2ARB 3FREE AGENT
Ben McDonald221.000$242kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bob Milacki252.000$235kARB 1ARB 2ARB 3FREE AGENT
Craig Worthington252.000$208kARB 1ARB 2ARB 3FREE AGENT
Brady Anderson262.000$120kARB 1ARB 2ARB 3FREE AGENT
Pete Harnisch232.000$120kARB 1ARB 2ARB 3FREE AGENT
Jose Bautista252.000$120kARB 1ARB 2ARB 3FREE AGENT
Steve Finley251.000$120kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mickey Weston291.000$104kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Curt Schilling232.000$103kARB 1ARB 2ARB 3FREE AGENT
Chris Hoiles251.000$102kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mike Smith261.000$102kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Marty Brown272.000$100kARB 1ARB 2ARB 3FREE AGENT
Juan Bell221.000$100kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Leo Gomez240.000$100kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Anthony Telford240.000$100kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
David Segui230.000$100kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Danny Boone36
Jeff McKnight271.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Dorn Taylor31

Graphs

Every roster slot ranked best→worst by WAR. Bars = Orioles 1990; 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, 1.279, -0.79], [1986, 1.024, -2.88], [1987, 1.257, -14.98], [1988, 1.114, -22.14], [1989, 0.618, 2.87], [1990, 0.543, 1.73], [1991, 0.666, 6.86], [1992, 0.804, 11.11], [1993, 0.919, 8.63], [1994, 1.222, 10.8], [1995, 1.332, 2.94], [1996, 1.566, -1.11], [1997, 1.545, 3.87], [1998, 1.777, -0.01], [1999, 1.587, 0.78], [2000, 1.402, -10.94], [2001, 0.855, -12.99], [2002, 0.789, -9.24], [2003, 0.743, -3.29], [2004, 0.669, 8.32], [2005, 1.054, -3.62], [2006, 0.989, -9.38], [2007, 1.044, -8.69], [2008, 0.682, -6.7], [2009, 0.699, -11.97], [2010, 0.879, -14.0], [2011, 0.911, -16.93], [2012, 0.876, -2.38], [2013, 0.885, 3.67], [2014, 0.957, 6.51], [2015, 0.904, 0.4], [2016, 1.221, 0.6], [2017, 1.34, -17.57], [2018, 1.008, -22.32], [2019, 0.63, -16.8], [2021, 0.211, -1.62], [2022, 0.268, 13.96], [2023, 0.55, 15.82], [2024, 0.736, 14.21], [2025, 0.903, -9.03], [2026, 1.035, 2.53]], TEAMCOL = '#DF4601'; 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 = 1990; 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", "IF", "IF", "C", "OF", "SP", "RP", "SP", "IF", "IF", "SP", "RP", "RP", "C", "OF", "OF", "OF", "IF", "SP", "IF", "OF", "RP", "OF", "SP", "SP", "SP", "OF", "OF", "RP", "RP", "SP", "RP", "RP", "RP", "IF", "IF", "RP", "IF", "IF", "RP"], "league": [5.75, 4.39, 3.83, 3.34, 2.86, 2.47, 2.15, 1.95, 1.68, 1.45, 1.29, 1.15, 1.01, 0.86, 0.76, 0.65, 0.54, 0.45, 0.38, 0.32, 0.26, 0.21, 0.17, 0.12, 0.08, 0.04, 0.02, -0.01, -0.04, -0.07, -0.1, -0.13, -0.17, -0.2, -0.26, -0.31, -0.4, -0.36, -0.46, -0.48], "names": ["Cal Ripken Jr.", "Randy Milligan", "Billy Ripken", "Mickey Tettleton", "Joe Orsulak", "Pete Harnisch", "Gregg Olson", "Ben McDonald", "Tim Hulett Sr.", "Sam Horn", "John Mitchell", "Curt Schilling", "Mark Williamson", "Bob Melvin", "Mike Devereaux", "Phil Bradley", "Brad Komminsk", "Craig Worthington", "Jose Mesa", "Rene Gonzales", "Brady Anderson", "Joe Price", "Donell Nixon", "Jay Tibbs", "Dave Johnson", "Anthony Telford", "Dave Gallagher", "Steve Finley", "Dorn Taylor", "Danny Boone", "Bob Milacki", "Jose Bautista", "Mike Smith", "Jay Aldrich", "Marty Brown", "Leo Gomez", "Kevin Hickey", "Juan Bell", "Jeff McKnight", "Brian Holton"], "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": [6.2, 3.94, 2.95, 2.87, 2.18, 2.02, 1.8, 1.78, 0.99, 0.91, 0.91, 0.75, 0.72, 0.66, 0.6, 0.54, 0.53, 0.51, 0.46, 0.36, 0.31, 0.24, 0.17, 0.14, 0.12, 0.09, 0.09, 0.06, 0.05, 0.04, -0.0, -0.09, -0.12, -0.12, -0.13, -0.13, -0.13, -0.15, -0.24, -0.26]}; 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:'BAL',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);});