RRosternomics

Dodgers LAD

GM: Fred Claire

Overview

CONTEND ↗ rising
expensive & winning · year 1 in this regime · payroll 1.35× league · TAB +2.0
+2.0TAB — talent above budget13th/26
+8.8WAB — realized wins (TAB + luck)4th/26
9.0depth — 2+ WAR regulars4th/26
-2.4dead WAR — sub-replacement drag5th/26
42%dead money %20th/26
0.584win %3rd/26
39.7roster fWAR7th/26
-3surplus $M (value vs cost)19th/26
$16Mpayroll $M2nd/26

1988: World Series champions · 94–67. Ranks are vs the 26 teams that season.

Roster

CFJohn Shelby 140GMike Davis 23GMike Devereaux 18G
LFKirk Gibson 148GDanny Heep 17GJose Gonzalez 9G
RFMike Marshall 90GMike Davis 63GMickey Hatcher 21G
SSAlfredo Griffin 93GDave Anderson 82GMike Sharperson 4G
2BSteve Sax 158GMike Sharperson 20GDave Anderson 11G
3BJeff Hamilton 105GPedro Guerrero 45GTracy Woodson 41G
1BFranklin Stubbs 84GMike Marshall 53GTracy Woodson 25G
STARTERSTim Leary 5.5WAROrel Hershiser 4.0WARTim Belcher 3.8WARJohn Tudor 0.5WARDon Sutton 0.4WAR
CMike Scioscia 123GRick Dempsey 74GGilberto Reyes 5G
BULLPEN Jay Howell 21svAlejandro Pena 0hJesse Orosco 0hBrian Holton 0hTim Crews 0hKen Howell 0hWilliam Brennan 0hDanny Heep 0h
CMike Scioscia 123 · Rick Dempsey 74 · Gilberto Reyes 5
1BFranklin Stubbs 84 · Mike Marshall 53 · Tracy Woodson 25
2BSteve Sax 158 · Mike Sharperson 20 · Dave Anderson 11
3BJeff Hamilton 105 · Pedro Guerrero 45 · Tracy Woodson 41
SSAlfredo Griffin 93 · Dave Anderson 82 · Mike Sharperson 4
LFKirk Gibson 148 · Danny Heep 17 · Jose Gonzalez 9
CFJohn Shelby 140 · Mike Davis 23 · Mike Devereaux 18
RFMike Marshall 90 · Mike Davis 63 · Mickey Hatcher 21
SPTim Leary 5.5 · Orel Hershiser 4.0 · Tim Belcher 3.8 · John Tudor 0.5 · Don Sutton 0.4
BP Jay Howell 21 · Alejandro Pena 0 · Jesse Orosco 0 · Brian Holton 0 · Tim Crews 0 · Ken Howell 0 · William Brennan 0 · Danny Heep 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.

1988 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Kirk Gibson 31 FA-elig 1506.2 1.8 +0.1
Tim Leary 29 FA-elig 365.5 0.2 +1.5
Orel Hershiser 29 arb 364.0 1.1 +0.1
Tim Belcher 26 pre-arb 363.8 0.1 +1.1
Mike Marshall 28 FA-elig 1442.5 0.8 +0.0
Mike Scioscia 29 FA-elig 1302.5 1.0 -0.2
John Shelby 30 FA-elig 1402.4 0.5 +0.3
Steve Sax 28 FA-elig 1602.2 0.8 -0.1
Jay Howell 32 FA-elig 502.2 0.6 +0.1
Rick Dempsey 38 FA-elig 771.9 0.2 +0.4
Alejandro Pena 29 FA-elig 601.6 0.4 +0.1
Dave Anderson 27 arb 1161.1 0.3 +0.1
Pedro Guerrero↔ STL 32 FA-elig 591.1 1.0 -0.6
Brian Holton 28 arb 450.8 0.1 +0.2
Tim Crews 27 pre-arb 420.7 0.1 +0.1
Jeff Hamilton 24 pre-arb 1110.6 0.1 +0.1
John Tudor↔ STL 34 FA-elig 90.5 0.3 -0.1
Don Sutton 43 FA-elig 160.4 0.3 -0.2
Ken Howell 27 arb 40.3 0.2 -0.1
Mickey Hatcher 33 FA-elig 880.3 0.3 -0.2
Danny Heep 30 FA-elig 950.3 0.3 -0.2
Ramon Martinez 20 pre-arb 90.3 0.1 +0.0
Shawn Hillegas↔ CHW 23 pre-arb 110.3 0.1 +0.0
Franklin Stubbs 27 arb 1150.2 0.2 -0.2
William Brennan 25 pre-arb 40.1 0.1 -0.0
Fernando Valenzuela 27 FA-elig 230.0 2.0 -2.0
Brad Havens↔ CLE 28 FA-elig 9-0.0 0.0 -0.1
Bill Krueger 30 1-0.0
Chris Gwynn 23 12-0.1
Tracy Woodson 25 pre-arb 65-0.1 0.1 -0.1
Gilberto Reyes 24 5-0.1
Mike Sharperson 26 46-0.1
Jose Gonzalez 23 arb 37-0.1 0.1 -0.1
Ricky Horton↔ CHW 28 arb 12-0.2 0.1 -0.1
Alfredo Griffin 30 FA-elig 95-0.3 0.8 -0.8
Mike Devereaux 25 30-0.3
Jesse Orosco 31 FA-elig 55-0.4 1.0 -1.1
Mike Davis 29 FA-elig 108-0.7 1.0 -1.2

Key additions (new vs 1987)

PlayerfWAR
Kirk Gibson6.2
Jay Howell2.2
Rick Dempsey1.9
John Tudor0.5
Don Sutton0.4
Ramon Martinez0.3

Key subtractions (gone from 1987)

Player1987 fWAR
Bob Welch4.7
Rick Honeycutt2.0
Matt Young1.1
Ralph Bryant0.2
Orlando Mercado0.2
Alex Trevino0.0

Current & Historical Payroll

1988 payroll
$16.0M

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

PlayerAgeSvc1988198919901991199219931994
Fernando Valenzuela278.000$2.0M$1.9M$2.0MFREE AGENT
Kirk Gibson319.000$1.8M$1.3M$1.3MFREE AGENT
Mike Scioscia298.000$1.0M$1.1M$1.2M$2.2M$2.2MFREE AGENT
Jesse Orosco319.000$1.0MFREE AGENT
Mike Davis298.000$988k$988kFREE AGENT
Pedro Guerrero3210.000$985kFREE AGENT
Steve Sax287.000$838kFREE AGENT
Mike Marshall287.000$760k$1.1MFREE AGENT
Alfredo Griffin3012.000$750k$1.0M$1.0M$900kFREE AGENT
Jay Howell328.000$595k$925k$1.0M$1.1M$2.6MFREE AGENT
John Shelby307.000$465k$550k$379kFREE AGENT
Alejandro Pena297.000$400k$875kFREE AGENT
Don Sutton4322.000$350kFREE AGENT
John Tudor349.000$309k$1.3MFREE AGENT
Mickey Hatcher339.000$300k$450k$450kFREE AGENT
Danny Heep309.000$300kFREE AGENT
Rick Dempsey3819.000$250k$420k$500kFREE AGENT
Tim Leary297.000$190k$386kFREE AGENT
Brad Havens287.000$45kFREE AGENT

Arbitration-eligible (8)

PlayerAgeSvc1988198919901991199219931994
Orel Hershiser295.000$1.1MFREE AGENT
Dave Anderson275.000$262kFREE AGENT
Franklin Stubbs274.000$235kARB 3FREE AGENT
Ken Howell274.000$215kARB 3FREE AGENT
Ricky Horton284.000$100kARB 3FREE AGENT
Brian Holton283.000$90kARB 2ARB 3FREE AGENT
Jose Gonzalez233.000$70kARB 2ARB 3FREE AGENT
Bill Krueger305.000FREE AGENT

Pre-arbitration (11)

PlayerAgeSvc1988198919901991199219931994
Jeff Hamilton242.000$82kARB 1ARB 2ARB 3FREE AGENT
Tim Belcher261.000$78kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tim Crews271.000$78kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tracy Woodson251.000$68kPre-ArbARB 1ARB 2ARB 3FREE AGENT
William Brennan250.000$62kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ramon Martinez200.000$62kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Shawn Hillegas231.000$50kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mike Devereaux251.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Gwynn231.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Gilberto Reyes24
Mike Sharperson261.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Dodgers 1988; 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.065, 14.01], [1986, 1.294, -8.47], [1987, 1.188, -10.83], [1988, 1.354, 2.04], [1989, 1.527, -5.24], [1990, 1.2, 0.19], [1991, 1.494, 0.26], [1992, 1.398, -16.39], [1993, 1.226, -7.56], [1994, 1.087, -3.62], [1995, 1.059, 4.0], [1996, 1.1, -2.31], [1997, 1.186, -1.79], [1998, 1.402, -8.63], [1999, 1.56, -4.82], [2000, 1.621, 0.18], [2001, 1.687, -5.86], [2002, 1.3, 3.31], [2003, 1.648, -2.98], [2004, 1.235, 7.4], [2005, 1.001, -9.38], [2006, 1.317, 2.93], [2007, 1.41, -1.56], [2008, 1.275, -0.9], [2009, 1.225, 9.9], [2010, 1.077, -4.75], [2011, 1.071, 4.66], [2012, 1.282, -2.48], [2013, 2.205, 2.84], [2014, 2.016, 9.2], [2015, 1.825, 7.32], [2016, 1.818, 1.34], [2017, 1.441, 12.89], [2018, 1.295, 16.35], [2019, 1.34, 22.24], [2021, 2.348, 12.31], [2022, 1.867, 20.65], [2023, 1.306, 11.59], [2024, 1.636, 8.14], [2025, 2.134, 6.43], [2026, 2.193, 12.32]], TEAMCOL = '#005A9C'; 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 = 1988; 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", "SP", "SP", "SP", "OF", "C", "OF", "IF", "RP", "C", "RP", "IF", "IF", "RP", "RP", "IF", "SP", "SP", "RP", "OF", "OF", "SP", "SP", "IF", "SP", "SP", "RP", "SP", "OF", "IF", "C", "IF", "OF", "RP", "IF", "OF", "RP", "OF", "\u2014", "\u2014"], "league": [5.44, 4.55, 3.92, 3.27, 2.79, 2.56, 2.28, 1.88, 1.68, 1.5, 1.23, 1.09, 0.96, 0.86, 0.75, 0.64, 0.53, 0.44, 0.34, 0.27, 0.22, 0.17, 0.12, 0.08, 0.03, 0.0, -0.02, -0.06, -0.09, -0.13, -0.17, -0.23, -0.24, -0.31, -0.38, -0.36, -0.4, -0.48, -0.6, -0.7], "names": ["Kirk Gibson", "Tim Leary", "Orel Hershiser", "Tim Belcher", "Mike Marshall", "Mike Scioscia", "John Shelby", "Steve Sax", "Jay Howell", "Rick Dempsey", "Alejandro Pena", "Dave Anderson", "Pedro Guerrero", "Brian Holton", "Tim Crews", "Jeff Hamilton", "John Tudor", "Don Sutton", "Ken Howell", "Mickey Hatcher", "Danny Heep", "Ramon Martinez", "Shawn Hillegas", "Franklin Stubbs", "William Brennan", "Fernando Valenzuela", "Brad Havens", "Bill Krueger", "Chris Gwynn", "Tracy Woodson", "Gilberto Reyes", "Mike Sharperson", "Jose Gonzalez", "Ricky Horton", "Alfredo Griffin", "Mike Devereaux", "Jesse Orosco", "Mike Davis", "(empty slot)", "(empty slot)"], "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.17, 5.52, 3.96, 3.85, 2.52, 2.5, 2.37, 2.24, 2.16, 1.94, 1.61, 1.09, 1.09, 0.8, 0.71, 0.6, 0.53, 0.43, 0.34, 0.34, 0.32, 0.3, 0.29, 0.24, 0.11, 0.02, -0.03, -0.04, -0.06, -0.1, -0.11, -0.11, -0.12, -0.16, -0.27, -0.32, -0.37, -0.72, 0.0, 0.0]}; 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:'LAD',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);});