RRosternomics

Reds CIN

GM: Jim Bowden

Overview

CONTEND ↗ rising
expensive & winning · year 1 in this regime · payroll 1.21× league · TAB +13.1
+13.1TAB — talent above budget6th/28
+6.7WAB — realized wins (TAB + luck)7th/28
7.0depth — 2+ WAR regulars2nd/28
-1.0dead WAR — sub-replacement drag1st/28
26%dead money %10th/28
0.579win %6th/28
34.9roster fWAR6th/28
+0surplus $M (value vs cost)8th/28
$38Mpayroll $M7th/28

Roster

CFDeion Sanders 91GJacob Brumfield 24GThomas Howard 7G
LFKevin Mitchell 89GThomas Howard 41GJerome Walton 16G
RFReggie Sanders 104GThomas Howard 12GJacob Brumfield 6G
SSBarry Larkin 110GTony Fernández 9GJeff Branson 8G
2BBret Boone 106GJeff Branson 19GTony Fernández 5G
3BTony Fernández 93GJeff Branson 18GLenny Harris 15G
1BHal Morris 112GBrian R. Hunter 60GJerome Walton 7G
STARTERSJose Rijo 4.3WARErik Hanson 3.1WARJohn Smiley 2.9WARPete Schourek 0.9WARTim Pugh 0.4WAR
CBrian Dorsett 73GEddie Taubensee 66GJoe Oliver 6G
BULLPEN Jeff Brantley 15svChuck McElroy 0hJohnny Ruffin 0hHector Carrasco 0hTim Fortugno 0hRich DeLucia 0hKevin Jarvis 0hScott Service 0h
CBrian Dorsett 73 · Eddie Taubensee 66 · Joe Oliver 6
1BHal Morris 112 · Brian R. Hunter 60 · Jerome Walton 7
2BBret Boone 106 · Jeff Branson 19 · Tony Fernández 5
3BTony Fernández 93 · Jeff Branson 18 · Lenny Harris 15
SSBarry Larkin 110 · Tony Fernández 9 · Jeff Branson 8
LFKevin Mitchell 89 · Thomas Howard 41 · Jerome Walton 16
CFDeion Sanders 91 · Jacob Brumfield 24 · Thomas Howard 7
RFReggie Sanders 104 · Thomas Howard 12 · Jacob Brumfield 6
SPJose Rijo 4.3 · Erik Hanson 3.1 · John Smiley 2.9 · Pete Schourek 0.9 · Tim Pugh 0.4
BP Jeff Brantley 15 · Chuck McElroy 0 · Johnny Ruffin 0 · Hector Carrasco 0 · Tim Fortugno 0 · Rich DeLucia 0 · Kevin Jarvis 0 · Scott Service 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.

1994 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Jose Rijo 29 FA-elig 264.3 4.0 +1.0
Kevin Mitchell 32 FA-elig 954.2 3.8 +1.1
Barry Larkin 30 FA-elig 1103.7 4.4 -0.1
Reggie Sanders 26 arb 1073.3 0.9 +2.8
Erik Hanson 29 FA-elig 223.1 2.7 +0.9
John Smiley 29 FA-elig 242.9 5.0 -1.6
Hal Morris 29 FA-elig 1122.3 2.1 +0.5
Tony Fernandez 32 FA-elig 1041.6 0.8 +1.0
Bret Boone 25 pre-arb 1081.5 0.2 +1.6
Chuck McElroy 26 arb 521.2 0.5 +0.8
Jacob Brumfield 29 pre-arb 681.1 0.2 +1.1
Jeff Brantley 30 FA-elig 501.0 1.5 -0.3
Eddie Taubensee↔ HOU 25 arb 611.0 0.3 +0.8
Pete Schourek 25 220.9
Jeff Branson 27 pre-arb 580.6 0.2 +0.5
Brian Dorsett 33 FA-elig 760.6 0.2 +0.5
Deion Sanders↔ ATL 26 arb 460.5 1.8 -1.2
Roberto Kelly↔ ATL 29 FA-elig 470.5 1.3 -0.8
Tim Pugh 27 pre-arb 100.4 0.2 +0.3
Brian R. Hunter↔ PIT 26 arb 90.4 0.0 +0.4
Hector Carrasco 24 pre-arb 450.3 0.1 +0.2
Tim Fortugno 32 250.2
John Roper 22 160.2
Johnny Ruffin 22 pre-arb 510.1 0.1 +0.0
Willie Greene 22 pre-arb 160.1 0.1 +0.0
Lenny Harris 29 FA-elig 660.1 0.5 -0.3
Steve Pegues↔ PIT 26 pre-arb 110.0 0.1 -0.1
Jerome Walton 28 arb 46-0.0 0.3 -0.4
Tom Browning 34 FA-elig 7-0.1 3.8 -3.8
Jerry Spradlin 27 6-0.1
Kevin Jarvis 24 pre-arb 6-0.1 0.1 -0.2
Joe Oliver 28 arb 6-0.1 2.5 -2.6
Scott Service 27 FA-elig 6-0.1 0.1 -0.3
Rich DeLucia 29 arb 8-0.2 0.2 -0.4
Thomas Howard 29 arb 83-0.3 0.6 -1.0

Key additions (new vs 1993)

PlayerfWAR
Erik Hanson3.1
Tony Fernandez1.6
Bret Boone1.5
Chuck McElroy1.2
Jeff Brantley1.0
Eddie Taubensee1.0

Key subtractions (gone from 1993)

Player1993 fWAR
Tim Belcher2.5
Chris Sabo1.4
Randy Milligan1.2
Jeff Reardon0.8
Larry Luebbers0.7
Steve Foster0.4

Current & Historical Payroll

1994 payroll
$38.5M

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

PlayerAgeSvc1994199519961997199819992000
John Smiley298.000$5.0M$5.0M$5.0M$2.9MFREE AGENT
Barry Larkin308.000$4.4M$5.9M$5.7M$5.3M$5.3M$5.3M$5.3M
Jose Rijo2910.000$4.0M$5.8MFREE AGENT
Kevin Mitchell3210.000$3.8MFREE AGENT$656k
Tom Browning3410.000$3.8MFREE AGENT
Erik Hanson296.000$2.7MFREE AGENT
Hal Morris296.000$2.1M$3.3M$1.6M$3.1MFREE AGENT$450k$298k
Jeff Brantley306.000$1.5M$1.8M$1.9M$2.8MFREE AGENT
Roberto Kelly297.000$1.3MFREE AGENT
Tony Fernandez3211.000$788kFREE AGENT
Lenny Harris296.000$450k$522k$550k$650k$440kFREE AGENT
Brian Dorsett337.000$170kFREE AGENT
Scott Service276.000$125kFREE AGENT$140k

Arbitration-eligible (10)

PlayerAgeSvc1994199519961997199819992000
Joe Oliver285.000$2.5MFREE AGENT
Deion Sanders265.000$1.8MFREE AGENT
Reggie Sanders263.000$925kARB 2ARB 3FREE AGENT
Thomas Howard294.000$575kARB 3FREE AGENT
Chuck McElroy265.000$540kFREE AGENT
Jerome Walton285.000$350kFREE AGENT
Eddie Taubensee253.000$254kARB 2ARB 3FREE AGENT
Rich DeLucia294.000$240kARB 3FREE AGENT
Brian R. Hunter263.000$26kARB 2ARB 3FREE AGENT
Pete Schourek253.000ARB 2ARB 3FREE AGENT

Pre-arbitration (12)

PlayerAgeSvc1994199519961997199819992000
Jeff Branson272.000$190kARB 1ARB 2ARB 3FREE AGENT
Bret Boone252.000$175kARB 1ARB 2ARB 3FREE AGENT
Jacob Brumfield292.000$170kARB 1ARB 2ARB 3FREE AGENT
Tim Pugh272.000$170kARB 1ARB 2ARB 3FREE AGENT
Willie Greene222.000$125kARB 1ARB 2ARB 3FREE AGENT
Johnny Ruffin221.000$115kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Hector Carrasco240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kevin Jarvis240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Steve Pegues260.000$67kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tim Fortugno322.000ARB 1ARB 2ARB 3FREE AGENT
John Roper22
Jerry Spradlin271.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Reds 1994; 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.902, 2.45], [1986, 1.028, -1.73], [1987, 0.862, 3.35], [1988, 0.805, 7.3], [1989, 0.864, -5.28], [1990, 0.816, 9.71], [1991, 1.049, 5.04], [1992, 1.109, 5.92], [1993, 1.336, -10.65], [1994, 1.206, 13.15], [1995, 1.316, 10.77], [1996, 1.138, -8.19], [1997, 1.005, -10.92], [1998, 0.544, 4.12], [1999, 0.707, 14.14], [2000, 0.77, 7.06], [2001, 0.782, -11.87], [2002, 0.727, -2.5], [2003, 0.827, -15.04], [2004, 0.704, -8.93], [2005, 0.799, -1.32], [2006, 0.807, 2.83], [2007, 0.81, -1.23], [2008, 0.715, -7.55], [2009, 0.852, -5.08], [2010, 0.802, 16.6], [2011, 0.825, 3.63], [2012, 0.771, 10.95], [2013, 1.004, 6.1], [2014, 0.969, -6.36], [2015, 0.882, -5.44], [2016, 0.703, -17.24], [2017, 0.635, -1.64], [2018, 0.746, -4.92], [2019, 0.959, -1.32], [2021, 0.993, 1.18], [2022, 0.714, -15.33], [2023, 0.46, 3.94], [2024, 0.598, -1.93], [2025, 0.717, 2.23], [2026, 0.703, -11.63]], TEAMCOL = '#C6011F'; 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 = 1994; 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": ["SP", "OF", "IF", "OF", "SP", "SP", "IF", "IF", "IF", "RP", "OF", "RP", "C", "RP", "IF", "C", "OF", "OF", "SP", "IF", "RP", "RP", "SP", "RP", "IF", "IF", "OF", "OF", "SP", "RP", "SP", "C", "RP", "RP", "OF", "\u2014", "\u2014", "\u2014", "\u2014", "\u2014"], "league": [4.46, 3.51, 2.81, 2.48, 2.13, 1.89, 1.57, 1.36, 1.16, 0.99, 0.88, 0.76, 0.67, 0.58, 0.49, 0.42, 0.35, 0.29, 0.24, 0.19, 0.11, 0.07, 0.03, -0.01, -0.04, -0.07, -0.11, -0.13, -0.18, -0.22, -0.31, -0.39, -0.38, -0.41, -0.49, -0.55, -0.56, -0.44, -0.58, -0.9], "names": ["Jose Rijo", "Kevin Mitchell", "Barry Larkin", "Reggie Sanders", "Erik Hanson", "John Smiley", "Hal Morris", "Tony Fernandez", "Bret Boone", "Chuck McElroy", "Jacob Brumfield", "Jeff Brantley", "Eddie Taubensee", "Pete Schourek", "Jeff Branson", "Brian Dorsett", "Deion Sanders", "Roberto Kelly", "Tim Pugh", "Brian R. Hunter", "Hector Carrasco", "Tim Fortugno", "John Roper", "Johnny Ruffin", "Willie Greene", "Lenny Harris", "Steve Pegues", "Jerome Walton", "Tom Browning", "Jerry Spradlin", "Kevin Jarvis", "Joe Oliver", "Scott Service", "Rich DeLucia", "Thomas Howard", "(empty slot)", "(empty slot)", "(empty slot)", "(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": [4.33, 4.16, 3.67, 3.25, 3.07, 2.94, 2.27, 1.56, 1.55, 1.19, 1.07, 1.02, 0.95, 0.85, 0.61, 0.58, 0.5, 0.48, 0.39, 0.36, 0.29, 0.24, 0.2, 0.12, 0.11, 0.1, 0.01, -0.0, -0.06, -0.08, -0.1, -0.11, -0.15, -0.16, -0.33, 0.0, 0.0, 0.0, 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:'CIN',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);});