RRosternomics

Royals KCR

GM: Herk Robinson

Overview

REBUILD ↘ cooling
cheap & losing · year 4 in this regime · payroll 0.89× league · TAB -14.2
-14.2TAB — talent above budget29th/30
-8.0WAB — realized wins (TAB + luck)24th/30
2.0depth — 2+ WAR regulars28th/30
-6.1dead WAR — sub-replacement drag21st/30
50%dead money %28th/30
0.447win %23rd/30
18.6roster fWAR28th/30
-17surplus $M (value vs cost)26th/30
$37Mpayroll $M19th/30

Roster

CFJohnny Damon 130GCarlos Beltrán 14GErnie Young 5G
LFJeff Conine 50GLarry Sutton 39GHal Morris 39G
RFJermaine Dye 59GLarry Sutton 47GJeff Conine 31G
SSMendy López 72GShane Halter 66GFelix Martinez 32G
2BJose Offerman 152GCarlos Febles 11GShane Halter 6G
3BDean Palmer 129GTerry Pendleton 23GScott Leius 15G
1BJeff King 112GHal Morris 46GJeff Conine 12G
STARTERSJosé Rosado 2.3WARTim Belcher 1.9WARGlendon Rusch 1.5WARHipolito Pichardo 1.2WARPat Rapp 1.2WAR
CMike Sweeney 91GSal Fasano 70GTim Spehr 32G
DHHal Morris 39GTerry Pendleton 37GDean Palmer 22G
BULLPEN Jeff Montgomery 36svScott Service 0hMatt Whisenant 0hJim Pittsley 0hBrian Bevil 0hRicky Bones 0hBart Evans 0hAllen McDill 0h
CMike Sweeney 91 · Sal Fasano 70 · Tim Spehr 32
1BJeff King 112 · Hal Morris 46 · Jeff Conine 12
2BJose Offerman 152 · Carlos Febles 11 · Shane Halter 6
3BDean Palmer 129 · Terry Pendleton 23 · Scott Leius 15
SSMendy López 72 · Shane Halter 66 · Felix Martinez 32
LFJeff Conine 50 · Larry Sutton 39 · Hal Morris 39
CFJohnny Damon 130 · Carlos Beltrán 14 · Ernie Young 5
RFJermaine Dye 59 · Larry Sutton 47 · Jeff Conine 31
DHHal Morris 39 · Terry Pendleton 37 · Dean Palmer 22
SPJosé Rosado 2.3 · Tim Belcher 1.9 · Glendon Rusch 1.5 · Hipolito Pichardo 1.2 · Pat Rapp 1.2
BP Jeff Montgomery 36 · Scott Service 0 · Matt Whisenant 0 · Jim Pittsley 0 · Brian Bevil 0 · Ricky Bones 0 · Bart Evans 0 · Allen McDill 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.

1998 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Jose Offerman 29 FA-elig 1584.6 2.0 +3.0
José Rosado 23 pre-arb 382.3 0.2 +2.2
Dean Palmer 29 FA-elig 1521.9 5.8 -3.8
Tim Belcher 36 FA-elig 341.9 2.3 -0.3
Glendon Rusch 23 pre-arb 291.5 0.2 +1.4
Jeff King 33 FA-elig 1311.4 4.0 -2.5
Scott Service 31 FA-elig 731.4 0.2 +1.3
Hipolito Pichardo 28 FA-elig 271.2 2.1 -0.8
Pat Rapp 30 FA-elig 321.2 0.8 +0.5
Johnny Damon 24 arb 1611.1 0.5 +0.7
Sal Fasano 26 pre-arb 740.6 0.2 +0.5
Matt Whisenant 27 pre-arb 700.6 0.2 +0.5
Brian Barber 25 arb 80.6 0.2 +0.4
Tim Spehr↔ NYM 31 FA-elig 110.5 0.1 +0.4
Mendy López 24 pre-arb 740.5 0.2 +0.3
Jeff Montgomery 36 FA-elig 560.4 2.8 -2.3
Ricky Bones 29 FA-elig 320.4 0.4 +0.0
Carlos Febles 22 pre-arb 110.4 0.2 +0.3
Brian Bevil 26 pre-arb 390.4 0.2 +0.2
Jeff Suppan↔ ARI 23 arb 40.4 0.1 +0.3
Jeff Conine 32 FA-elig 930.3 2.2 -1.9
Mike Sweeney 24 arb 920.3 0.2 +0.1
Shane Mack↔ OAK 34 FA-elig 660.2 0.4 -0.2
Hal Morris 33 FA-elig 1270.2 1.4 -1.2
Carlos Beltrán 21 pre-arb 140.2 0.2 -0.0
Bart Evans 27 pre-arb 80.2 0.2 -0.0
Luis Rivera 34 FA-elig 420.1 0.2 -0.2
Jose Santiago 23 pre-arb 20.0 0.2 -0.1
Kevin Appier 30 FA-elig 30.0 4.8 -4.8
Jeremy Giambi 23 pre-arb 180.0 0.2 -0.1
Mike Macfarlane↔ OAK 34 FA-elig 30.0 0.0 -0.0
Ernie Young 28 arb 25-0.0 0.2 -0.3
Tim Byrdak 24 3-0.1
Jed Hansen 25 pre-arb 4-0.1 0.2 -0.3
Danny Rios 25 5-0.1
Joe Vitiello 28 arb 3-0.1 0.2 -0.3
Shane Halter 28 pre-arb 86-0.1 0.2 -0.3
Jermaine Allensworth↔ NYM 26 pre-arb 30-0.1 0.1 -0.2
Jamie Walker 26 pre-arb 6-0.2 0.2 -0.4
Chris Turner 29 arb 4-0.2 0.2 -0.4
Chris Hatcher 29 8-0.2
Héctor Ortiz 28 pre-arb 4-0.2 0.2 -0.4
Dee Brown 20 5-0.2
Chris Haney↔ CHC 29 FA-elig 33-0.2 1.6 -1.8
Allen McDill 26 pre-arb 7-0.3 0.2 -0.5
Jermaine Dye 24 pre-arb 60-0.4 0.2 -0.7
Jim Pittsley 24 arb 39-0.5 0.2 -0.7
Scott Leius 32 FA-elig 17-0.7 0.2 -0.9
Terry Pendleton 37 FA-elig 79-0.7 0.4 -1.2
Larry Sutton 28 pre-arb 111-0.8 0.2 -1.1
Felix Martinez 24 pre-arb 34-1.0 0.2 -1.2

Key additions (new vs 1997)

PlayerfWAR
Pat Rapp1.2
Brian Barber0.6
Mendy López0.5
Carlos Febles0.4
Jeff Suppan0.4
Jeff Conine0.3

Key subtractions (gone from 1997)

Player1997 fWAR
Jay Bell5.4
Chili Davis2.5
Bip Roberts1.4
Rod Myers0.7
Randy Veres0.5
Gregg Olson0.4

Current & Historical Payroll

1998 payroll
$37.2M

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

PlayerAgeSvc1998199920002001200220032004
Dean Palmer299.000$5.8MFREE AGENT
Kevin Appier309.000$4.8M$3.1MFREE AGENT$9.5M$300k
Jeff King339.000$4.0M$4.0MFREE AGENT
Jeff Montgomery3611.000$2.8M$2.5MFREE AGENT
Tim Belcher3611.000$2.3MFREE AGENT
Jeff Conine328.000$2.2MFREE AGENT
Hipolito Pichardo286.000$2.1MFREE AGENT
Jose Offerman298.000$2.0MFREE AGENT
Chris Haney297.000$1.6MFREE AGENT
Hal Morris3310.000$1.4MFREE AGENT
Pat Rapp306.000$800kFREE AGENT
Shane Mack3411.000$430kFREE AGENT
Terry Pendleton3714.000$425kFREE AGENT
Ricky Bones297.000$400kFREE AGENT
Luis Rivera3412.000$245kFREE AGENT
Scott Service3110.000$230k$745kFREE AGENT
Scott Leius328.000$180k$200kFREE AGENT
Tim Spehr317.000$98k$330kFREE AGENT
Mike Macfarlane3411.000$27kFREE AGENT

Arbitration-eligible (8)

PlayerAgeSvc1998199920002001200220032004
Johnny Damon243.000$460kARB 2ARB 3FREE AGENT
Mike Sweeney243.000$230kARB 2ARB 3FREE AGENT
Joe Vitiello283.000$225kARB 2ARB 3FREE AGENT
Ernie Young284.000$215kARB 3FREE AGENT
Jim Pittsley243.000$215kARB 2ARB 3FREE AGENT
Chris Turner295.000$190kFREE AGENT
Brian Barber253.000$175kARB 2ARB 3FREE AGENT
Jeff Suppan233.000$50kARB 2ARB 3FREE AGENT

Pre-arbitration (24)

PlayerAgeSvc1998199920002001200220032004
José Rosado232.000$242kARB 1ARB 2ARB 3FREE AGENT
Jermaine Dye242.000$230kARB 1ARB 2ARB 3FREE AGENT
Glendon Rusch231.000$220kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jamie Walker261.000$205kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Shane Halter281.000$200kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Matt Whisenant271.000$185kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Sal Fasano262.000$175kARB 1ARB 2ARB 3FREE AGENT
Brian Bevil262.000$175kARB 1ARB 2ARB 3FREE AGENT
Jed Hansen251.000$172kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Larry Sutton281.000$172kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Carlos Beltrán210.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Héctor Ortiz280.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jose Santiago231.000$170kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Allen McDill261.000$170kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Felix Martinez241.000$170kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Carlos Febles220.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeremy Giambi230.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bart Evans270.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mendy López240.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jermaine Allensworth262.000$59kARB 1ARB 2ARB 3FREE AGENT
Danny Rios25
Tim Byrdak240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Hatcher29
Dee Brown200.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Royals 1998; 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.037, 3.24], [1986, 1.145, -4.72], [1987, 1.109, -0.08], [1988, 1.17, 0.35], [1989, 1.214, 2.51], [1990, 1.349, -10.2], [1991, 1.191, -2.15], [1992, 1.09, -12.61], [1993, 1.306, -4.17], [1994, 1.269, -3.14], [1995, 0.914, -4.91], [1996, 0.613, -0.49], [1997, 0.887, -0.24], [1998, 0.893, -14.16], [1999, 0.454, 1.84], [2000, 0.456, -1.07], [2001, 0.514, -7.33], [2002, 0.709, -15.88], [2003, 0.672, -7.23], [2004, 0.613, -18.46], [2005, 0.449, -15.84], [2006, 0.644, -10.15], [2007, 0.798, -5.15], [2008, 0.689, -5.77], [2009, 0.869, -15.08], [2010, 0.708, -11.44], [2011, 0.375, 7.78], [2012, 0.606, -0.62], [2013, 0.775, 5.28], [2014, 0.802, 4.18], [2015, 1.003, 1.35], [2016, 1.042, -9.24], [2017, 1.099, -9.14], [2018, 0.814, -16.88], [2019, 0.675, -14.02], [2021, 0.728, -5.39], [2022, 0.609, -10.79], [2023, 0.602, -7.41], [2024, 0.683, 11.34], [2025, 0.828, 0.42], [2026, 0.896, -10.91]], TEAMCOL = '#1857B8'; 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 = 1998; 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", "IF", "SP", "SP", "IF", "RP", "SP", "SP", "OF", "C", "RP", "SP", "C", "IF", "RP", "RP", "IF", "RP", "SP", "OF", "C", "OF", "IF", "OF", "RP", "IF", "RP", "SP", "OF", "C", "OF", "RP", "IF", "RP", "IF", "IF", "OF", "RP", "C"], "league": [6.26, 4.71, 3.97, 3.22, 2.79, 2.4, 2.03, 1.81, 1.6, 1.45, 1.29, 1.14, 0.99, 0.83, 0.72, 0.63, 0.53, 0.45, 0.37, 0.3, 0.24, 0.2, 0.15, 0.12, 0.08, 0.05, 0.02, -0.01, -0.04, -0.07, -0.09, -0.11, -0.14, -0.18, -0.22, -0.26, -0.35, -0.46, -0.37, -0.41], "names": ["Jose Offerman", "Jos\u00e9 Rosado", "Dean Palmer", "Tim Belcher", "Glendon Rusch", "Jeff King", "Scott Service", "Hipolito Pichardo", "Pat Rapp", "Johnny Damon", "Sal Fasano", "Matt Whisenant", "Brian Barber", "Tim Spehr", "Mendy L\u00f3pez", "Jeff Montgomery", "Ricky Bones", "Carlos Febles", "Brian Bevil", "Jeff Suppan", "Jeff Conine", "Mike Sweeney", "Shane Mack", "Hal Morris", "Carlos Beltr\u00e1n", "Bart Evans", "Luis Rivera", "Jose Santiago", "Kevin Appier", "Jeremy Giambi", "Mike Macfarlane", "Ernie Young", "Tim Byrdak", "Jed Hansen", "Danny Rios", "Joe Vitiello", "Shane Halter", "Jermaine Allensworth", "Jamie Walker", "Chris Turner"], "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": [4.6, 2.29, 1.89, 1.86, 1.49, 1.42, 1.37, 1.23, 1.18, 1.06, 0.65, 0.62, 0.58, 0.49, 0.47, 0.43, 0.41, 0.4, 0.37, 0.36, 0.32, 0.3, 0.23, 0.21, 0.16, 0.15, 0.09, 0.04, 0.03, 0.02, 0.02, -0.04, -0.05, -0.07, -0.08, -0.09, -0.14, -0.14, -0.16, -0.17]}; 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:'KCR',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);});