RRosternomics

Guardians CLE

GM: Joe Klein

Overview

REBUILD ↗ rising
cheap & losing · year 2 in this regime · payroll 0.65× league · TAB -1.5
-1.5TAB — talent above budget14th/26
+7.4WAB — realized wins (TAB + luck)6th/26
6.0depth — 2+ WAR regulars16th/26
-4.8dead WAR — sub-replacement drag17th/26
40%dead money %21st/26
0.519win %11th/26
27.7roster fWAR19th/26
+1surplus $M (value vs cost)9th/26
$7Mpayroll $M24th/26

Roster

CFBrett Butler 159GOtis Nixon 14GJoe Carter 9G
LFMel Hall 123GOtis Nixon 84GJoe Carter 45G
RFJoe Carter 78GCory Snyder 69GCarmen Castillo 37G
SSJulio Franco 133GCory Snyder 34GFran Mullins 11G
2BTony Bernazard 146GJulio Franco 13GFran Mullins 13G
3BBrook Jacoby 158GCory Snyder 11GDan Rohn 2G
1BPat Tabler 107GJoe Carter 70GFran Mullins 1G
STARTERSTom Candiotti 4.3WARGreg Swindell 0.9WARScott Bailes 0.9WARPhil Niekro 0.8WARKen Schrom 0.8WAR
CAndy Allanson 99GChris Bando 86G
DHAndre Thornton 108GCarmen Castillo 24GPat Tabler 18G
BULLPEN Ernie Camacho 20svRich Yett 0hBryan Oelkers 0hDickie Noles 0hFrank Wills 0hJim Kern 0hJamie Easterly 0hDoug Jones 0h
CAndy Allanson 99 · Chris Bando 86
1BPat Tabler 107 · Joe Carter 70 · Fran Mullins 1
2BTony Bernazard 146 · Julio Franco 13 · Fran Mullins 13
3BBrook Jacoby 158 · Cory Snyder 11 · Dan Rohn 2
SSJulio Franco 133 · Cory Snyder 34 · Fran Mullins 11
LFMel Hall 123 · Otis Nixon 84 · Joe Carter 45
CFBrett Butler 159 · Otis Nixon 14 · Joe Carter 9
RFJoe Carter 78 · Cory Snyder 69 · Carmen Castillo 37
DHAndre Thornton 108 · Carmen Castillo 24 · Pat Tabler 18
SPTom Candiotti 4.3 · Greg Swindell 0.9 · Scott Bailes 0.9 · Phil Niekro 0.8 · Ken Schrom 0.8
BP Ernie Camacho 20 · Rich Yett 0 · Bryan Oelkers 0 · Dickie Noles 0 · Frank Wills 0 · Jim Kern 0 · Jamie Easterly 0 · Doug Jones 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
Joe Carter 26 arb 1624.8 0.2 +1.2
Tom Candiotti 29 arb 364.3 0.1 +1.2
Brook Jacoby 26 arb 1583.0 0.4 +0.5
Tony Bernazard 29 FA-elig 1462.9 0.6 +0.3
Julio Franco 27 arb 1492.7 0.6 +0.2
Brett Butler 29 arb 1612.5 0.8 -0.1
Pat Tabler 28 arb 1301.9 0.4 +0.2
Mel Hall 25 arb 1401.6 0.3 +0.1
Cory Snyder 23 pre-arb 1031.1 0.1 +0.3
Ernie Camacho 31 FA-elig 511.0 0.1 +0.2
Greg Swindell 21 pre-arb 90.9 0.1 +0.2
Scott Bailes 24 pre-arb 620.9 0.1 +0.2
Phil Niekro 47 FA-elig 340.8 0.3 -0.1
Ken Schrom 31 FA-elig 340.8 0.3 -0.0
Carmen Castillo 28 arb 850.4 0.2 -0.0
Doug Jones 29 110.4
Dave Clark 23 180.4
Don Schulze 23 arb 190.4 0.1 -0.0
Andre Thornton 36 FA-elig 1200.3 1.1 -1.0
Neal Heaton↔ MIN 26 arb 120.3 0.1 -0.0
Chris Bando 30 arb 920.3 0.2 -0.1
John Butcher↔ MIN 29 FA-elig 130.2 0.2 -0.1
Otis Nixon 27 arb 1050.2 0.1 -0.0
Jay Bell 20 50.1
Frank Wills 27 arb 260.0 0.1 -0.1
Rich Yett 23 pre-arb 390.0 0.1 -0.1
Jose Roman 23 pre-arb 6-0.0 0.1 -0.1
Reggie Ritter 26 5-0.0
Dan Rohn 30 6-0.0
Eddie Williams 21 5-0.2
Fran Mullins 29 FA-elig 28-0.2 0.1 -0.1
Jim Kern 37 FA-elig 16-0.3 0.1 -0.2
Jamie Easterly 33 FA-elig 13-0.4 0.3 -0.5
Dickie Noles 29 FA-elig 32-0.9 0.1 -0.4
Bryan Oelkers 25 arb 35-1.3 0.1 -0.5
Andy Allanson 24 pre-arb 101-1.4 0.1 -0.5

Key additions (new vs 1985)

PlayerfWAR
Tom Candiotti4.3
Cory Snyder1.1
Greg Swindell0.9
Scott Bailes0.9
Phil Niekro0.8
Ken Schrom0.8

Key subtractions (gone from 1985)

Player1985 fWAR
Bert Blyleven4.2
Jerry Willard1.3
Vern Ruhle1.0
Mike Hargrove0.9
Roy Smith0.6
Keith Creel0.4

Current & Historical Payroll

1986 payroll
$7.3M

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 (10)

PlayerAgeSvc1986198719881989199019911992
Andre Thornton3613.000$1.1M$1.1MFREE AGENT
Tony Bernazard297.000$580k$356kFREE AGENT
Jamie Easterly3312.000$350k$370kFREE AGENT
Phil Niekro4722.000$300k$79kFREE AGENT
Ken Schrom316.000$275k$450kFREE AGENT
John Butcher296.000$173kFREE AGENT
Ernie Camacho316.000$145kFREE AGENT
Dickie Noles297.000$110kFREE AGENT
Jim Kern3712.000$90kFREE AGENT
Fran Mullins296.000$60kFREE AGENT

Arbitration-eligible (15)

PlayerAgeSvc1986198719881989199019911992
Brett Butler295.000$850kFREE AGENT
Julio Franco274.000$575kARB 3FREE AGENT
Brook Jacoby265.000$438kFREE AGENT
Pat Tabler285.000$395kFREE AGENT
Mel Hall255.000$350kFREE AGENT
Chris Bando305.000$225kFREE AGENT
Joe Carter263.000$190kARB 2ARB 3FREE AGENT
Carmen Castillo284.000$178kARB 3FREE AGENT
Neal Heaton264.000$113kARB 3FREE AGENT
Don Schulze233.000$110kARB 2ARB 3FREE AGENT
Otis Nixon273.000$98kARB 2ARB 3FREE AGENT
Frank Wills273.000$92kARB 2ARB 3FREE AGENT
Tom Candiotti293.000$80kARB 2ARB 3FREE AGENT
Bryan Oelkers253.000$65kARB 2ARB 3FREE AGENT
Doug Jones294.000ARB 3FREE AGENT

Pre-arbitration (11)

PlayerAgeSvc1986198719881989199019911992
Andy Allanson240.000$60kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Scott Bailes240.000$60kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jose Roman232.000$60kARB 1ARB 2ARB 3FREE AGENT
Cory Snyder230.000$60kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Greg Swindell210.000$60kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rich Yett231.000$60kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jay Bell200.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dave Clark230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Reggie Ritter260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dan Rohn30
Eddie Williams210.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Guardians 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, 0.635, -14.01], [1986, 0.653, -1.46], [1987, 0.776, -16.36], [1988, 0.64, -0.83], [1989, 0.718, -1.69], [1990, 0.886, -6.78], [1991, 0.638, -3.28], [1992, 0.289, 9.48], [1993, 0.555, -2.3], [1994, 0.957, 15.35], [1995, 1.175, 17.72], [1996, 1.564, 13.24], [1997, 1.436, 4.85], [1998, 1.353, 4.26], [1999, 1.524, 10.66], [2000, 1.443, 8.96], [2001, 1.497, 5.72], [2002, 1.049, -9.31], [2003, 0.571, -1.68], [2004, 0.516, 9.87], [2005, 0.592, 22.15], [2006, 0.629, 8.98], [2007, 0.76, 11.24], [2008, 0.786, 6.03], [2009, 0.746, -6.01], [2010, 0.539, -6.29], [2011, 0.633, -1.89], [2012, 0.72, -9.12], [2013, 0.788, 8.4], [2014, 0.702, 4.05], [2015, 0.62, 11.83], [2016, 0.653, 12.9], [2017, 0.932, 25.24], [2018, 1.093, 12.51], [2019, 0.963, 11.35], [2021, 0.359, 6.5], [2022, 0.454, 15.91], [2023, 0.604, 0.73], [2024, 0.566, 8.37], [2025, 0.516, 3.34], [2026, 0.423, 12.02]], TEAMCOL = '#E31937'; 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": ["OF", "SP", "IF", "IF", "IF", "OF", "IF", "OF", "OF", "RP", "SP", "RP", "SP", "SP", "OF", "RP", "OF", "SP", "IF", "SP", "C", "SP", "OF", "IF", "RP", "RP", "SP", "RP", "IF", "OF", "IF", "RP", "RP", "RP", "RP", "C", "\u2014", "\u2014", "\u2014", "\u2014"], "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": ["Joe Carter", "Tom Candiotti", "Brook Jacoby", "Tony Bernazard", "Julio Franco", "Brett Butler", "Pat Tabler", "Mel Hall", "Cory Snyder", "Ernie Camacho", "Greg Swindell", "Scott Bailes", "Phil Niekro", "Ken Schrom", "Carmen Castillo", "Doug Jones", "Dave Clark", "Don Schulze", "Andre Thornton", "Neal Heaton", "Chris Bando", "John Butcher", "Otis Nixon", "Jay Bell", "Frank Wills", "Rich Yett", "Jose Roman", "Reggie Ritter", "Dan Rohn", "Eddie Williams", "Fran Mullins", "Jim Kern", "Jamie Easterly", "Dickie Noles", "Bryan Oelkers", "Andy Allanson", "(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.82, 4.26, 3.05, 2.92, 2.7, 2.54, 1.92, 1.6, 1.11, 1.01, 0.9, 0.88, 0.84, 0.76, 0.45, 0.43, 0.36, 0.36, 0.32, 0.29, 0.26, 0.25, 0.19, 0.15, 0.05, 0.02, -0.0, -0.0, -0.04, -0.2, -0.23, -0.32, -0.36, -0.87, -1.31, -1.43, 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:'CLE',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);});