RRosternomics

Tigers DET

GM: Jerry Walker

Overview

WINDOW ↗ rising
cheap & winning · year 1 in this regime · payroll 0.9× league · TAB +0.5
+0.5TAB — talent above budget16th/26
-5.6WAB — realized wins (TAB + luck)17th/26
6.0depth — 2+ WAR regulars14th/26
-5.1dead WAR — sub-replacement drag14th/26
10%dead money %2nd/26
0.463win %18th/26
33.5roster fWAR14th/26
-1surplus $M (value vs cost)10th/26
$27Mpayroll $M17th/26

Roster

CFMilt Cuyler 88GGary Pettis 59GTony Phillips 24G
LFDan Gladden 95GMark Carreon 64GTony Phillips 14G
RFRob Deer 106GTony Phillips 35GMark Carreon 19G
SSTravis Fryman 137GAlan Trammell 27GTony Phillips 1G
2BLou Whitaker 119GTony Phillips 57GSkeeter Barnes 7G
3BScott Livingstone 112GSkeeter Barnes 39GTravis Fryman 26G
1BCecil Fielder 114GDave Bergman 55GSkeeter Barnes 17G
STARTERSJohn Doherty 1.9WARMark Leiter 1.5WARKevin Ritz 0.8WARWalt Terrell 0.6WARBill Gullickson 0.6WAR
CMickey Tettleton 113GChad Kreuter 62GRich Rowland 3G
DHCecil Fielder 43GMickey Tettleton 40GTony Phillips 34G
BULLPEN Mike Henneman 24svMike Munoz 0hKurt Knudsen 0hLes Lancaster 0hJohn Kiely 0h
CMickey Tettleton 113 · Chad Kreuter 62 · Rich Rowland 3
1BCecil Fielder 114 · Dave Bergman 55 · Skeeter Barnes 17
2BLou Whitaker 119 · Tony Phillips 57 · Skeeter Barnes 7
3BScott Livingstone 112 · Skeeter Barnes 39 · Travis Fryman 26
SSTravis Fryman 137 · Alan Trammell 27 · Tony Phillips 1
LFDan Gladden 95 · Mark Carreon 64 · Tony Phillips 14
CFMilt Cuyler 88 · Gary Pettis 59 · Tony Phillips 24
RFRob Deer 106 · Tony Phillips 35 · Mark Carreon 19
DHCecil Fielder 43 · Mickey Tettleton 40 · Tony Phillips 34
SPJohn Doherty 1.9 · Mark Leiter 1.5 · Kevin Ritz 0.8 · Walt Terrell 0.6 · Bill Gullickson 0.6
BP Mike Henneman 24 · Mike Munoz 0 · Kurt Knudsen 0 · Les Lancaster 0 · John Kiely 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.

1992 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Mickey Tettleton 31 FA-elig 1575.0 3.3 +0.5
Tony Phillips 33 FA-elig 1594.6 1.6 +2.0
Lou Whitaker 35 FA-elig 1304.4 2.2 +1.3
Travis Fryman 23 pre-arb 1614.1 0.3 +2.9
Rob Deer 31 FA-elig 1103.7 2.0 +0.9
Cecil Fielder 28 FA-elig 1552.5 4.5 -2.5
John Doherty 25 pre-arb 471.9 0.1 +1.4
Mark Leiter 29 pre-arb 351.5 0.2 +0.9
Scott Livingstone 26 pre-arb 1171.4 0.1 +1.0
Gary Pettis↔ SDP 34 481.3
Mike Henneman 30 arb 601.2 2.4 -1.5
Chad Kreuter 27 arb 670.9 0.2 +0.5
Alan Trammell 34 FA-elig 290.9 2.4 -1.7
Kevin Ritz 27 arb 230.8 0.1 +0.5
Phil Clark 24 230.7
Dan Gladden 34 FA-elig 1130.6 1.1 -0.5
Walt Terrell 34 FA-elig 360.6 0.1 +0.4
Bill Gullickson 33 FA-elig 340.6 1.9 -1.5
Frank Tanana 38 FA-elig 320.5 1.7 -1.3
Dave Haas 26 120.5
Skeeter Barnes 35 FA-elig 950.3 0.1 +0.1
Eric King 28 FA-elig 170.3 1.2 -1.0
John Kiely 27 pre-arb 390.1 0.1 -0.0
Mike Munoz 26 arb 650.0 0.1 -0.1
Buddy Groom 26 120.0
Rich Rowland 28 pre-arb 6-0.1 0.1 -0.2
Rico Brogna 22 9-0.2
Shawn Hare 25 15-0.4
Scott Aldred 24 pre-arb 16-0.4 0.1 -0.5
Kurt Knudsen 25 48-0.5
Milt Cuyler 23 pre-arb 89-0.5 0.2 -0.6
Dave Bergman 39 FA-elig 87-0.6 0.4 -0.9
Les Lancaster 30 41-1.1
Mark Carreon 28 arb 101-1.2 0.4 -1.3

Key additions (new vs 1991)

PlayerfWAR
John Doherty1.9
Gary Pettis1.3
Chad Kreuter0.9
Phil Clark0.7
Dan Gladden0.6
Eric King0.3

Key subtractions (gone from 1991)

Player1991 fWAR
Dan Gakeler0.5
Andy Allanson0.4
Lloyd Moseby0.2
Rusty Meacham0.0
Jerry Don Gleaton0.0
Johnny Paredes-0.0

Current & Historical Payroll

1992 payroll
$27.1M

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

PlayerAgeSvc1992199319941995199619971998
Cecil Fielder287.000$4.5M$4.3M$4.2M$9.2M$6.2MFREE AGENT
Mickey Tettleton318.000$3.3M$3.3M$1.9MFREE AGENT
Alan Trammell3415.000$2.4M$1.8M$3.0M$1.3M$600kFREE AGENT
Lou Whitaker3515.000$2.2M$3.4M$2.8M$3.8MFREE AGENT
Rob Deer318.000$2.0M$1.5MFREE AGENT
Bill Gullickson3313.000$1.9M$2.3M$2.4MFREE AGENT
Frank Tanana3819.000$1.7MFREE AGENT
Tony Phillips3310.000$1.6M$3.9M$2.4MFREE AGENT
Eric King286.000$1.2MFREE AGENT
Dan Gladden349.000$1.1M$1.1MFREE AGENT
Dave Bergman3917.000$375kFREE AGENT
Skeeter Barnes359.000$150k$265kFREE AGENT
Walt Terrell3410.000$109kFREE AGENT
Gary Pettis3410.000FREE AGENT

Arbitration-eligible (6)

PlayerAgeSvc1992199319941995199619971998
Mike Henneman305.000$2.4MFREE AGENT
Mark Carreon285.000$355kFREE AGENT
Chad Kreuter274.000$155kARB 3FREE AGENT
Kevin Ritz273.000$125kARB 2ARB 3FREE AGENT
Mike Munoz263.000$115kARB 2ARB 3FREE AGENT
Les Lancaster305.000FREE AGENT

Pre-arbitration (14)

PlayerAgeSvc1992199319941995199619971998
Travis Fryman232.000$300kARB 1ARB 2ARB 3FREE AGENT
Mark Leiter292.000$230kARB 1ARB 2ARB 3FREE AGENT
Milt Cuyler232.000$230kARB 1ARB 2ARB 3FREE AGENT
Scott Aldred242.000$130kARB 1ARB 2ARB 3FREE AGENT
Rich Rowland282.000$120kARB 1ARB 2ARB 3FREE AGENT
Scott Livingstone261.000$120kPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Doherty250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
John Kiely271.000$109kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Rico Brogna220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Phil Clark240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Buddy Groom260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Dave Haas261.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Shawn Hare25
Kurt Knudsen25

Graphs

Every roster slot ranked best→worst by WAR. Bars = Tigers 1992; 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.081, 8.7], [1986, 1.087, 7.88], [1987, 1.191, 12.36], [1988, 1.157, 0.88], [1989, 1.142, -20.79], [1990, 1.115, -6.7], [1991, 1.023, 4.65], [1992, 0.902, 0.5], [1993, 1.207, -1.91], [1994, 1.275, -11.73], [1995, 1.029, -22.16], [1996, 0.67, -26.38], [1997, 0.434, -0.32], [1998, 0.528, 2.81], [1999, 0.784, -3.26], [2000, 1.083, 1.82], [2001, 0.834, -14.14], [2002, 0.799, -21.62], [2003, 0.709, -28.46], [2004, 0.694, 2.66], [2005, 0.909, -2.56], [2006, 1.067, 5.33], [2007, 1.092, 6.48], [2008, 1.498, -13.64], [2009, 1.358, -6.17], [2010, 1.289, 4.58], [2011, 1.169, 4.19], [2012, 1.245, 7.96], [2013, 1.473, 15.22], [2014, 1.482, 5.1], [2015, 1.357, -8.96], [2016, 1.567, -0.93], [2017, 1.286, -13.3], [2018, 0.867, -18.49], [2019, 0.78, -20.9], [2021, 0.686, -9.01], [2022, 0.92, -20.15], [2023, 0.806, -5.8], [2024, 0.586, 8.26], [2025, 0.89, 4.26], [2026, 1.305, -2.03]], TEAMCOL = '#0C2340'; 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 = 1992; 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": ["C", "IF", "IF", "IF", "OF", "IF", "RP", "RP", "IF", "OF", "RP", "C", "IF", "RP", "OF", "OF", "RP", "SP", "SP", "SP", "IF", "SP", "RP", "RP", "SP", "C", "IF", "OF", "SP", "RP", "OF", "IF", "RP", "OF", "\u2014", "\u2014", "\u2014", "\u2014", "\u2014", "\u2014"], "league": [5.9, 4.88, 4.15, 3.46, 2.93, 2.43, 2.02, 1.79, 1.57, 1.4, 1.23, 1.02, 0.87, 0.78, 0.69, 0.61, 0.52, 0.46, 0.4, 0.32, 0.26, 0.21, 0.15, 0.11, 0.06, 0.03, 0.0, -0.05, -0.08, -0.13, -0.17, -0.21, -0.27, -0.31, -0.37, -0.39, -0.43, -0.56, -0.58, -0.59], "names": ["Mickey Tettleton", "Tony Phillips", "Lou Whitaker", "Travis Fryman", "Rob Deer", "Cecil Fielder", "John Doherty", "Mark Leiter", "Scott Livingstone", "Gary Pettis", "Mike Henneman", "Chad Kreuter", "Alan Trammell", "Kevin Ritz", "Phil Clark", "Dan Gladden", "Walt Terrell", "Bill Gullickson", "Frank Tanana", "Dave Haas", "Skeeter Barnes", "Eric King", "John Kiely", "Mike Munoz", "Buddy Groom", "Rich Rowland", "Rico Brogna", "Shawn Hare", "Scott Aldred", "Kurt Knudsen", "Milt Cuyler", "Dave Bergman", "Les Lancaster", "Mark Carreon", "(empty slot)", "(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.95, 4.58, 4.43, 4.15, 3.73, 2.54, 1.93, 1.48, 1.44, 1.27, 1.22, 0.88, 0.85, 0.76, 0.72, 0.64, 0.63, 0.57, 0.52, 0.52, 0.33, 0.27, 0.09, 0.04, 0.01, -0.11, -0.16, -0.44, -0.45, -0.46, -0.52, -0.64, -1.08, -1.19, 0.0, 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:'DET',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);});