RRosternomics

Orioles BAL

GM: Hank Peters

Overview

FORCED-SPEND ↘ cooling
expensive & losing · year 2 in this regime · payroll 1.02× league · TAB -2.9
-2.9TAB — talent above budget16th/26
-9.2WAB — realized wins (TAB + luck)22nd/26
7.0depth — 2+ WAR regulars10th/26
-5.9dead WAR — sub-replacement drag23rd/26
14%dead money %1st/26
0.451win %19th/26
31.6roster fWAR13th/26
-2surplus $M (value vs cost)18th/26
$12Mpayroll $M13th/26

Roster

CFFred Lynn 107GJohn Shelby 56GKen Gerhart 14G
LFMike Young 69GJohn Shelby 48GJuan Beníquez 44G
RFLee Lacy 120GJohn Shelby 31GJim Dwyer 16G
SSCal Ripken Jr. 162G
2BJuan Bonilla 70GAlan Wiggins 66GJackie Gutierrez 53G
3BFloyd Rayford 72GTom O'Malley 55GJuan Bonilla 33G
1BEddie Murray 119GJim Traber 29GJuan Beníquez 14G
STARTERSMike Boddicker 2.9WARStorm Davis 2.6WARMike Flanagan 2.6WARKen Dixon 1.9WARScott McGregor 0.9WAR
CRick Dempsey 121GJohn Stefero 50GAl Pardo 14G
DHLarry Sheets 57GMike Young 37GJim Traber 21G
BULLPEN Don Aase 34svRich Bordi 0hBrad Havens 0hNate Snell 0hOdell Jones 0hTippy Martinez 0hTony Arnold 0hMike Kinnunen 0h
CRick Dempsey 121 · John Stefero 50 · Al Pardo 14
1BEddie Murray 119 · Jim Traber 29 · Juan Beníquez 14
2BJuan Bonilla 70 · Alan Wiggins 66 · Jackie Gutierrez 53
3BFloyd Rayford 72 · Tom O'Malley 55 · Juan Bonilla 33
SSCal Ripken Jr. 162
LFMike Young 69 · John Shelby 48 · Juan Beníquez 44
CFFred Lynn 107 · John Shelby 56 · Ken Gerhart 14
RFLee Lacy 120 · John Shelby 31 · Jim Dwyer 16
DHLarry Sheets 57 · Mike Young 37 · Jim Traber 21
SPMike Boddicker 2.9 · Storm Davis 2.6 · Mike Flanagan 2.6 · Ken Dixon 1.9 · Scott McGregor 0.9
BP Don Aase 34 · Rich Bordi 0 · Brad Havens 0 · Nate Snell 0 · Odell Jones 0 · Tippy Martinez 0 · Tony Arnold 0 · Mike Kinnunen 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.

1986 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Cal Ripken Jr. 25 arb 1626.9 1.1 +0.9
Eddie Murray 30 FA-elig 1374.1 0.9 +0.3
Mike Boddicker 28 FA-elig 332.9 0.8 +0.0
Fred Lynn 34 FA-elig 1122.9 1.2 -0.3
Mike Flanagan 34 FA-elig 292.6 0.6 +0.1
Storm Davis 24 arb 252.6 0.5 +0.3
Rick Dempsey 36 FA-elig 1222.1 0.4 +0.2
Ken Dixon 25 pre-arb 351.9 0.1 +0.4
Don Aase 31 FA-elig 661.9 0.6 -0.0
Lee Lacy 38 FA-elig 1301.1 0.7 -0.4
Larry Sheets 26 pre-arb 1121.1 0.1 +0.2
Juan Beniquez 36 FA-elig 1131.0 0.5 -0.1
Mike Young 26 arb 1171.0 0.4 -0.1
Scott McGregor 32 FA-elig 340.9 0.9 -0.6
Jim Traber 24 pre-arb 650.9 0.1 +0.2
Jim Dwyer 36 FA-elig 940.7 0.4 -0.2
Brad Havens 26 arb 460.7 0.1 +0.1
Rich Bordi 27 FA-elig 520.6 0.2 -0.1
Ricky Jones 28 160.5
Odell Jones 33 FA-elig 210.3 0.1 -0.0
Tony Arnold 27 110.3
John Stefero 26 arb 520.2 0.1 -0.0
Tom O'Malley 25 560.1
Dennis Martinez↔ WSN 32 FA-elig 40.1 0.1 -0.1
Tom Dodd 27 80.1
Eric Bell 22 40.0
Bill Swaggerty 29 10.0
Tippy Martinez 36 FA-elig 14-0.0 0.5 -0.5
John Habyan 22 6-0.0
Alan Wiggins 28 71-0.1
Nate Snell 33 34-0.1
Rex Hudler 25 14-0.1
Mike Kinnunen 28 9-0.1
Kelly Paris 28 5-0.1
Juan Bonilla 30 arb 102-0.2 0.1 -0.2
Carl Nichols 23 5-0.2
Ken Gerhart 25 20-0.4
Al Pardo 23 16-0.6
Floyd Rayford 28 FA-elig 81-1.1 0.3 -0.6
John Shelby 28 arb 135-1.1 0.2 -0.6
Jackie Gutierrez 26 arb 61-1.7 0.2 -0.7

Key additions (new vs 1985)

PlayerfWAR
Juan Beniquez1.0
Jim Traber0.9
Rich Bordi0.6
Ricky Jones0.5
Odell Jones0.3
Tony Arnold0.3

Key subtractions (gone from 1985)

Player1985 fWAR
Wayne Gross1.7
Gary Roenicke0.9
Fritzie Connally0.4
Rich Dauer-0.0
Phil Huffman-0.1
Lenn Sakata-0.1

Current & Historical Payroll

1986 payroll
$11.5M

What the front office was committed to as of 1986, 1986–1992. 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 (15)

PlayerAgeSvc1986198719881989199019911992
Fred Lynn3412.000$1.2M$1.3M$1.2MFREE AGENT
Eddie Murray309.000$926k$2.0M$2.1MFREE AGENT
Scott McGregor3210.000$911k$940k$1.1MFREE AGENT
Mike Boddicker286.000$838k$825k$496kFREE AGENT
Lee Lacy3814.000$725k$525kFREE AGENT
Mike Flanagan3411.000$640k$435kFREE AGENT$250k$600k
Don Aase319.000$600k$625k$675kFREE AGENT
Tippy Martinez3612.000$487kFREE AGENT
Juan Beniquez3615.000$450kFREE AGENT
Rick Dempsey3617.000$442kFREE AGENT$450k
Jim Dwyer3613.000$400k$308k$191kFREE AGENT
Floyd Rayford286.000$310k$248kFREE AGENT
Rich Bordi276.000$250kFREE AGENT
Odell Jones3311.000$108kFREE AGENT
Dennis Martinez3210.000$97kFREE AGENT

Arbitration-eligible (11)

PlayerAgeSvc1986198719881989199019911992
Cal Ripken Jr.255.000$1.1MFREE AGENT
Storm Davis244.000$510kARB 3FREE AGENT
Mike Young264.000$388kARB 3FREE AGENT
John Shelby285.000$240kFREE AGENT
Jackie Gutierrez263.000$168kARB 2ARB 3FREE AGENT
Juan Bonilla305.000$120kFREE AGENT
Brad Havens265.000$115kFREE AGENT
John Stefero263.000$70kARB 2ARB 3FREE AGENT
Tom O'Malley254.000ARB 3FREE AGENT
Kelly Paris284.000ARB 3FREE AGENT
Alan Wiggins285.000FREE AGENT

Pre-arbitration (15)

PlayerAgeSvc1986198719881989199019911992
Ken Dixon252.000$140kARB 1ARB 2ARB 3FREE AGENT
Larry Sheets262.000$120kARB 1ARB 2ARB 3FREE AGENT
Jim Traber242.000$60kARB 1ARB 2ARB 3FREE AGENT
Tony Arnold270.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Eric Bell221.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Tom Dodd27
Ken Gerhart250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Habyan221.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Rex Hudler252.000ARB 1ARB 2ARB 3FREE AGENT
Ricky Jones28
Mike Kinnunen28
Carl Nichols230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Al Pardo23
Nate Snell33
Bill Swaggerty29

Graphs

Every roster slot ranked best→worst by WAR. Bars = Orioles 1986; 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 = 1986; 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", "SP", "OF", "SP", "SP", "C", "SP", "RP", "OF", "OF", "OF", "OF", "SP", "IF", "OF", "RP", "RP", "IF", "RP", "RP", "C", "IF", "SP", "IF", "SP", "RP", "RP", "SP", "IF", "RP", "IF", "RP", "IF", "IF", "C", "OF", "C", "IF", "OF"], "league": [5.5, 4.35, 3.46, 3.12, 2.83, 2.57, 2.28, 1.93, 1.68, 1.46, 1.31, 1.2, 1.01, 0.86, 0.72, 0.62, 0.51, 0.43, 0.38, 0.32, 0.26, 0.21, 0.16, 0.11, 0.07, 0.02, -0.0, -0.03, -0.07, -0.12, -0.17, -0.23, -0.29, -0.36, -0.42, -0.52, -0.48, -0.61, -0.69, -0.64], "names": ["Cal Ripken Jr.", "Eddie Murray", "Mike Boddicker", "Fred Lynn", "Mike Flanagan", "Storm Davis", "Rick Dempsey", "Ken Dixon", "Don Aase", "Lee Lacy", "Larry Sheets", "Juan Beniquez", "Mike Young", "Scott McGregor", "Jim Traber", "Jim Dwyer", "Brad Havens", "Rich Bordi", "Ricky Jones", "Odell Jones", "Tony Arnold", "John Stefero", "Tom O\u0027Malley", "Dennis Martinez", "Tom Dodd", "Eric Bell", "Bill Swaggerty", "Tippy Martinez", "John Habyan", "Alan Wiggins", "Nate Snell", "Rex Hudler", "Mike Kinnunen", "Kelly Paris", "Juan Bonilla", "Carl Nichols", "Ken Gerhart", "Al Pardo", "Floyd Rayford", "John Shelby"], "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.94, 4.11, 2.91, 2.87, 2.6, 2.57, 2.1, 1.94, 1.9, 1.14, 1.14, 1.03, 1.0, 0.92, 0.85, 0.75, 0.68, 0.61, 0.47, 0.27, 0.25, 0.16, 0.1, 0.06, 0.06, 0.03, 0.0, -0.0, -0.03, -0.08, -0.09, -0.1, -0.12, -0.15, -0.18, -0.23, -0.44, -0.57, -1.06, -1.11]}; 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);});