RRosternomics

Twins MIN

GM: Andy MacPhail

Overview

CONTEND ↗ rising
expensive & winning · year 1 in this regime · payroll 1.02× league · TAB +8.1
+8.1TAB — talent above budget4th/26
+12.8WAB — realized wins (TAB + luck)3rd/26
10.0depth — 2+ WAR regulars1st/26
-3.1dead WAR — sub-replacement drag4th/26
17%dead money %6th/26
0.586win %2nd/26
42.6roster fWAR2nd/26
-2surplus $M (value vs cost)12th/26
$23Mpayroll $M11th/26

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

Roster

CFKirby Puckett 144GShane Mack 37GJarvis Brown 11G
LFDan Gladden 126GShane Mack 49GPedro Munoz 10G
RFShane Mack 80GGene Larkin 47GPedro Munoz 39G
SSGreg Gagne 137GAl Newman 55GScott Leius 19G
2BChuck Knoblauch 148GAl Newman 35GGene Larkin 1G
3BMike Pagliarulo 118GScott Leius 79GAl Newman 35G
1BKent Hrbek 128GGene Larkin 39GPaul Sorrento 13G
STARTERSKevin Tapani 4.6WARJack Morris 4.1WARScott Erickson 3.2WARTom Edens 0.6WARMark Guthrie 0.6WAR
CBrian Harper 119GJunior Ortiz 60GLenny Webster 17G
DHChili Davis 149GRandy Bush 6GGene Larkin 2G
BULLPEN Rick Aguilera 42svSteve Bedrosian 0hTerry Leach 0hCarl Willis 0hPaul Abbott 0hLarry Casian 0hGary Wayne 0hDenny Neagle 0h
CBrian Harper 119 · Junior Ortiz 60 · Lenny Webster 17
1BKent Hrbek 128 · Gene Larkin 39 · Paul Sorrento 13
2BChuck Knoblauch 148 · Al Newman 35 · Gene Larkin 1
3BMike Pagliarulo 118 · Scott Leius 79 · Al Newman 35
SSGreg Gagne 137 · Al Newman 55 · Scott Leius 19
LFDan Gladden 126 · Shane Mack 49 · Pedro Munoz 10
CFKirby Puckett 144 · Shane Mack 37 · Jarvis Brown 11
RFShane Mack 80 · Gene Larkin 47 · Pedro Munoz 39
DHChili Davis 149 · Randy Bush 6 · Gene Larkin 2
SPKevin Tapani 4.6 · Jack Morris 4.1 · Scott Erickson 3.2 · Tom Edens 0.6 · Mark Guthrie 0.6
BP Rick Aguilera 42 · Steve Bedrosian 0 · Terry Leach 0 · Carl Willis 0 · Paul Abbott 0 · Larry Casian 0 · Gary Wayne 0 · Denny Neagle 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.

1991 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Shane Mack 27 arb 1434.9 0.3 +2.7
Kevin Tapani 27 pre-arb 344.6 0.2 +2.5
Jack Morris 36 FA-elig 354.1 3.7 -1.3
Kirby Puckett 31 FA-elig 1523.8 3.2 -0.9
Chili Davis 31 FA-elig 1533.3 1.7 +0.3
Scott Erickson 23 pre-arb 323.2 0.1 +1.7
Kent Hrbek 31 FA-elig 1322.7 2.6 -1.0
Greg Gagne 29 FA-elig 1392.3 1.7 -0.4
Mike Pagliarulo 31 FA-elig 1212.3 0.7 +0.7
Chuck Knoblauch 22 1512.1
Brian Harper 31 FA-elig 1231.7 0.6 +0.4
Scott Leius 25 1091.5
Rick Aguilera 29 FA-elig 631.5 1.5 -0.7
Carl Willis 30 401.4
Randy Bush 32 FA-elig 931.1 0.5 +0.1
Gene Larkin 28 arb 980.9 0.7 -0.2
Terry Leach 37 FA-elig 500.9 0.5 -0.0
Mark Guthrie 25 pre-arb 420.6 0.2 +0.2
Tom Edens 30 80.6
Dan Gladden 33 FA-elig 1260.6 1.1 -0.7
Lenny Webster 26 180.5
Pedro Munoz 22 510.4
Junior Ortiz 31 FA-elig 610.2 0.5 -0.3
Willie Banks 22 50.2
Jarvis Brown 24 380.1
Denny Neagle 22 70.1
David West 26 arb 150.1 0.2 -0.1
Paul Sorrento 25 pre-arb 260.0 0.1 -0.1
Gary Wayne 28 80.0
Carmen Castillo 33 FA-elig 9-0.1 0.6 -0.6
Paul Abbott 23 15-0.1
Larry Casian 25 pre-arb 15-0.5 0.1 -0.4
Allan Anderson 27 arb 29-0.5 0.8 -1.1
Steve Bedrosian 33 FA-elig 56-0.8 1.4 -1.8
Al Newman 31 FA-elig 118-1.1 0.5 -1.1

Key additions (new vs 1990)

PlayerfWAR
Jack Morris4.1
Chili Davis3.3
Mike Pagliarulo2.3
Chuck Knoblauch2.1
Carl Willis1.4
Tom Edens0.6

Key subtractions (gone from 1990)

Player1990 fWAR
Gary Gaetti2.3
Roy Smith1.0
Nelson Liriano0.5
John Candelaria0.4
Tim Drummond0.2
Chip Hale0.0

Current & Historical Payroll

1991 payroll
$23.4M

What the front office was committed to as of 1991, 1991–1997. 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 (16)

PlayerAgeSvc1991199219931994199519961997
Jack Morris3614.000$3.7MFREE AGENT
Kirby Puckett317.000$3.2M$3.0M$5.3M$5.3M$6.3MFREE AGENT
Kent Hrbek3110.000$2.6M$3.1M$3.1M$3.1MFREE AGENT
Greg Gagne298.000$1.7M$1.9MFREE AGENT
Chili Davis3110.000$1.7M$2.8MFREE AGENT
Rick Aguilera296.000$1.5M$2.3M$2.4M$3.3M$2.5M$3.5M$3.1M
Steve Bedrosian3310.000$1.4MFREE AGENT
Dan Gladden338.000$1.1MFREE AGENT
Mike Pagliarulo317.000$655k$1.2M$322kFREE AGENT
Brian Harper3112.000$625k$2.5M$2.4MFREE AGENT
Carmen Castillo339.000$600kFREE AGENT
Terry Leach3710.000$530kFREE AGENT
Randy Bush329.000$500k$450k$250kFREE AGENT
Al Newman316.000$450kFREE AGENT
Junior Ortiz319.000$450kFREE AGENT
Carl Willis307.000$325k$500k$500kFREE AGENT

Arbitration-eligible (5)

PlayerAgeSvc1991199219931994199519961997
Allan Anderson275.000$785kFREE AGENT
Gene Larkin284.000$725kARB 3FREE AGENT
Shane Mack274.000$270kARB 3FREE AGENT
David West263.000$160kARB 2ARB 3FREE AGENT
Tom Edens304.000ARB 3FREE AGENT

Pre-arbitration (14)

PlayerAgeSvc1991199219931994199519961997
Kevin Tapani272.000$198kARB 1ARB 2ARB 3FREE AGENT
Mark Guthrie252.000$160kARB 1ARB 2ARB 3FREE AGENT
Scott Erickson231.000$144kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Sorrento252.000$122kARB 1ARB 2ARB 3FREE AGENT
Larry Casian251.000$105kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Abbott231.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Willie Banks220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jarvis Brown240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chuck Knoblauch220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Scott Leius251.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Pedro Munoz221.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Denny Neagle220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Gary Wayne282.000ARB 1ARB 2ARB 3FREE AGENT
Lenny Webster262.000ARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Twins 1991; 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.551, 5.75], [1986, 0.799, -6.1], [1987, 0.913, -6.27], [1988, 1.124, 6.51], [1989, 1.175, -5.39], [1990, 0.831, -5.22], [1991, 1.022, 8.14], [1992, 0.928, 10.3], [1993, 0.881, -11.89], [1994, 0.89, -7.43], [1995, 0.611, -8.21], [1996, 0.512, 4.3], [1997, 0.67, -5.0], [1998, 0.651, -19.32], [1999, 0.391, -9.27], [2000, 0.311, -3.57], [2001, 0.427, 9.47], [2002, 0.608, 13.88], [2003, 0.849, 8.46], [2004, 0.715, 9.39], [2005, 0.798, 0.68], [2006, 0.828, 11.35], [2007, 0.831, -4.26], [2008, 0.634, 3.2], [2009, 0.764, 3.76], [2010, 0.981, 10.85], [2011, 1.186, -24.32], [2012, 0.949, -14.89], [2013, 0.717, -8.12], [2014, 0.786, -0.9], [2015, 0.927, -5.23], [2016, 0.82, -6.19], [2017, 0.844, 4.94], [2018, 0.868, -4.72], [2019, 0.833, 23.55], [2021, 0.94, -4.6], [2022, 0.926, -1.3], [2023, 1.01, 9.28], [2024, 0.848, 8.54], [2025, 0.631, 3.36], [2026, 0.473, 3.81]], TEAMCOL = '#0C2C56'; 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 = 1991; 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", "OF", "OF", "SP", "IF", "IF", "IF", "IF", "C", "IF", "RP", "RP", "OF", "OF", "RP", "RP", "SP", "OF", "C", "OF", "C", "SP", "OF", "RP", "SP", "IF", "RP", "OF", "RP", "RP", "SP", "RP", "IF", "\u2014", "\u2014", "\u2014", "\u2014", "\u2014"], "league": [5.94, 4.76, 3.83, 3.3, 2.9, 2.49, 2.19, 1.86, 1.71, 1.54, 1.27, 1.13, 1.02, 0.89, 0.75, 0.65, 0.53, 0.43, 0.33, 0.25, 0.2, 0.14, 0.09, 0.07, 0.03, 0.0, -0.02, -0.05, -0.08, -0.1, -0.14, -0.17, -0.23, -0.28, -0.32, -0.38, -0.43, -0.52, -0.49, -0.58], "names": ["Shane Mack", "Kevin Tapani", "Jack Morris", "Kirby Puckett", "Chili Davis", "Scott Erickson", "Kent Hrbek", "Greg Gagne", "Mike Pagliarulo", "Chuck Knoblauch", "Brian Harper", "Scott Leius", "Rick Aguilera", "Carl Willis", "Randy Bush", "Gene Larkin", "Terry Leach", "Mark Guthrie", "Tom Edens", "Dan Gladden", "Lenny Webster", "Pedro Munoz", "Junior Ortiz", "Willie Banks", "Jarvis Brown", "Denny Neagle", "David West", "Paul Sorrento", "Gary Wayne", "Carmen Castillo", "Paul Abbott", "Larry Casian", "Allan Anderson", "Steve Bedrosian", "Al Newman", "(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.91, 4.57, 4.1, 3.83, 3.34, 3.16, 2.74, 2.29, 2.28, 2.08, 1.66, 1.55, 1.47, 1.43, 1.08, 0.93, 0.87, 0.63, 0.62, 0.61, 0.51, 0.37, 0.2, 0.18, 0.12, 0.1, 0.07, 0.05, 0.04, -0.07, -0.13, -0.52, -0.55, -0.8, -1.08, 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:'MIN',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);});