RRosternomics

Rays TBR

GM: Chuck Lamar

Overview

REBUILD ↘ cooling
cheap & losing · year 4 in this regime · payroll 0.54× league · TAB -13.9
-13.9TAB — talent above budget28th/30
-12.1WAB — realized wins (TAB + luck)25th/30
1.0depth — 2+ WAR regulars30th/30
-6.9dead WAR — sub-replacement drag25th/30
34%dead money %18th/30
0.383win %29th/30
12.9roster fWAR30th/30
-15surplus $M (value vs cost)18th/30
$33Mpayroll $M27th/30

Roster

CFRandy Winn 48GJason Tyner 47GDamian Rolls 18G
LFGreg Vaughn 57GJason Tyner 57GBen Grieve 56G
RFBen Grieve 64GRandy Winn 62GJose Guillen 36G
SSChris Gomez 94GFelix Martinez 67GAndy Sheets 49G
2BBrent Abernathy 79GDamian Rolls 42GRuss Johnson 33G
3BAubrey Huff 73GJared Sandberg 38GRuss Johnson 36G
1BSteve Cox 78GAubrey Huff 19GJared Sandberg 1G
STARTERSJoe Kennedy 1.4WARPaul Wilson 1.4WARTanyon Sturtze 1.3WARBryan Rekar 1.0WARNick Bierbrodt 0.3WAR
CJohn Flaherty 78GToby Hall 46GPaul Hoover 2G
DHGreg Vaughn 76GBen Grieve 32GAubrey Huff 20G
BULLPEN Esteban Yan 22svDoug Creek 15hTravis Phelps 13hJesus Colome 6hVictor Zambrano 5hRusty Meacham 3hJeff Wallace 1hDan Wheeler 0h
CJohn Flaherty 78 · Toby Hall 46 · Paul Hoover 2
1BSteve Cox 78 · Aubrey Huff 19 · Jared Sandberg 1
2BBrent Abernathy 79 · Damian Rolls 42 · Russ Johnson 33
3BAubrey Huff 73 · Jared Sandberg 38 · Russ Johnson 36
SSChris Gomez 94 · Felix Martinez 67 · Andy Sheets 49
LFGreg Vaughn 57 · Jason Tyner 57 · Ben Grieve 56
CFRandy Winn 48 · Jason Tyner 47 · Damian Rolls 18
RFBen Grieve 64 · Randy Winn 62 · Jose Guillen 36
DHGreg Vaughn 76 · Ben Grieve 32 · Aubrey Huff 20
SPJoe Kennedy 1.4 · Paul Wilson 1.4 · Tanyon Sturtze 1.3 · Bryan Rekar 1.0 · Nick Bierbrodt 0.3
BP Esteban Yan 22 · Doug Creek 15 · Travis Phelps 13 · Jesus Colome 6 · Victor Zambrano 5 · Rusty Meacham 3 · Jeff Wallace 1 · Dan Wheeler 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.

2001 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Fred McGriff↔ CHC 37 FA-elig 972.4 4.3 -0.4
Chris Gomez↔ SDP 30 FA-elig 581.5 1.8 +0.6
Joe Kennedy 22 201.4
Russ Johnson 28 arb 851.4 0.3 +2.0
Paul Wilson 28 arb 371.4 0.3 +1.8
Tanyon Sturtze 30 FA-elig 391.3 0.2 +1.9
Ben Grieve 25 arb 1541.3 2.8 -0.7
Esteban Yan 26 arb 541.2 0.7 +1.2
Toby Hall 25 491.1
Greg Vaughn 35 FA-elig 1361.1 8.2 -6.5
Bryan Rekar 29 FA-elig 251.0 1.4 +0.3
Albie Lopez↔ ARI 29 FA-elig 200.7 1.8 -0.6
Brent Abernathy 23 790.7
Victor Zambrano 25 360.7
Steve Cox 26 pre-arb 1080.5 0.3 +0.6
Travis Phelps 23 490.5
Jose Guillen 25 arb 410.4 1.0 -0.4
Ryan Rupe 26 pre-arb 280.3 0.2 +0.3
Randy Winn 27 arb 1280.3 0.3 +0.3
Nick Bierbrodt↔ ARI 23 pre-arb 110.3 0.1 +0.4
Jason Tyner 24 1050.2
Rusty Meacham 33 240.1
Tony Fiore↔ MIN 29 30.0
Ariel Prieto 31 FA-elig 30.0 0.7 -0.7
Paul Hoover 25 3-0.0
Dan Wheeler 23 13-0.0
Mike Judd↔ TEX 26 arb 8-0.0 0.1 -0.2
Gerald Williams↔ NYY 34 FA-elig 62-0.1 1.9 -2.0
Mike DiFelice↔ ARI 32 arb 48-0.1 0.6 -0.7
Jeff Wallace 25 29-0.1
Bobby Seay 23 12-0.1
Vinny Castilla↔ HOU 33 FA-elig 24-0.1 1.2 -1.4
Mickey Callaway 26 2-0.1
Jared Sandberg 23 39-0.1
Jason Standridge 22 9-0.2
Damian Rolls 23 pre-arb 81-0.2 0.2 -0.6
Brian Rose↔ NYM 25 arb 7-0.2 0.2 -0.6
Doug Creek 32 FA-elig 66-0.2 0.3 -0.6
Bobby Smith 27 arb 6-0.4 0.3 -0.9
Travis Harper 25 pre-arb 2-0.4 0.2 -0.9
Jesus Colome 23 30-0.5
Ken Hill 35 FA-elig 5-0.5 0.8 -1.6
John Flaherty 33 FA-elig 78-0.7 3.0 -4.2
Felix Martinez 27 arb 77-0.8 0.3 -1.5
Andy Sheets 29 49-0.8
Aubrey Huff 24 111-1.0

Key additions (new vs 2000)

PlayerfWAR
Chris Gomez1.5
Joe Kennedy1.4
Ben Grieve1.3
Brent Abernathy0.7
Victor Zambrano0.7
Travis Phelps0.5

Key subtractions (gone from 2000)

Player2000 fWAR
Steve Trachsel2.0
Roberto Hernandez1.0
Cory Lidle1.0
Jim Mecir0.8
Jose Canseco0.8
Ozzie Guillen0.6

Current & Historical Payroll

2001 payroll
$33.3M

What the front office was committed to as of 2001, 2001–2007. 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)

PlayerAgeSvc2001200220032004200520062007
Greg Vaughn3512.000$8.2M$8.8MFREE AGENT
Fred McGriff3715.000$4.3MFREE AGENT
John Flaherty339.000$3.0M$3.2MFREE AGENT
Gerald Williams349.000$1.9MFREE AGENT
Albie Lopez298.000$1.8MFREE AGENT
Chris Gomez308.000$1.8M$1.0MFREE AGENT
Bryan Rekar296.000$1.4MFREE AGENT
Vinny Castilla3310.000$1.2MFREE AGENT
Ken Hill3513.000$750kFREE AGENT
Ariel Prieto316.000$700kFREE AGENT
Doug Creek326.000$260k$382kFREE AGENT
Tanyon Sturtze306.000$238k$295kFREE AGENT
Rusty Meacham3310.000FREE AGENT

Arbitration-eligible (13)

PlayerAgeSvc2001200220032004200520062007
Ben Grieve254.000$2.8MARB 3FREE AGENT
Jose Guillen254.000$975kARB 3FREE AGENT
Esteban Yan265.000$650kFREE AGENT
Mike DiFelice325.000$620kFREE AGENT
Paul Wilson285.000$350kFREE AGENT
Russ Johnson284.000$270kARB 3FREE AGENT
Randy Winn273.000$270kARB 2ARB 3FREE AGENT
Bobby Smith273.000$255kARB 2ARB 3FREE AGENT
Felix Martinez274.000$255kARB 3FREE AGENT
Brian Rose254.000$195kARB 3FREE AGENT
Mike Judd264.000$139kARB 3FREE AGENT
Andy Sheets295.000FREE AGENT
Jeff Wallace254.000ARB 3FREE AGENT

Pre-arbitration (20)

PlayerAgeSvc2001200220032004200520062007
Steve Cox262.000$255kARB 1ARB 2ARB 3FREE AGENT
Ryan Rupe262.000$242kARB 1ARB 2ARB 3FREE AGENT
Travis Harper251.000$202kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Damian Rolls231.000$200kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nick Bierbrodt230.000$129kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tony Fiore291.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Toby Hall251.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Hoover250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bobby Seay230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jason Tyner241.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Brent Abernathy230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mickey Callaway262.000ARB 1ARB 2ARB 3FREE AGENT
Dan Wheeler232.000ARB 1ARB 2ARB 3FREE AGENT
Jared Sandberg23
Jason Standridge220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Aubrey Huff241.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Jesus Colome230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Travis Phelps230.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Joe Kennedy220.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Victor Zambrano250.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Rays 2001; 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 = [[1998, 0.649, -4.67], [1999, 0.794, -11.73], [2000, 0.895, -11.8], [2001, 0.536, -13.91], [2002, 0.517, -11.68], [2003, 0.302, 0.77], [2004, 0.439, -0.79], [2005, 0.428, 1.39], [2006, 0.386, -3.74], [2007, 0.316, 6.38], [2008, 0.533, 21.83], [2009, 0.724, 17.31], [2010, 0.75, 16.32], [2011, 0.48, 18.85], [2012, 0.691, 14.61], [2013, 0.591, 14.91], [2014, 0.674, 5.17], [2015, 0.597, 6.22], [2016, 0.462, 3.07], [2017, 0.628, 5.9], [2018, 0.389, 20.31], [2019, 0.457, 24.78], [2021, 0.501, 23.17], [2022, 0.584, 7.88], [2023, 0.529, 26.37], [2024, 0.513, 2.34], [2025, 0.393, 8.01], [2026, 0.464, 13.89]], TEAMCOL = '#5DA9E0'; 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 = 2001; 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", "IF", "SP", "SP", "OF", "RP", "C", "OF", "SP", "SP", "IF", "RP", "IF", "RP", "OF", "SP", "OF", "SP", "OF", "RP", "RP", "RP", "C", "RP", "RP", "OF", "C", "RP", "RP", "IF", "RP", "IF", "RP", "IF", "RP", "RP", "IF", "SP"], "league": [6.14, 4.72, 3.87, 3.3, 2.8, 2.32, 1.98, 1.72, 1.59, 1.39, 1.27, 1.1, 0.99, 0.88, 0.75, 0.67, 0.57, 0.48, 0.41, 0.35, 0.28, 0.23, 0.18, 0.13, 0.1, 0.07, 0.05, 0.02, -0.02, -0.04, -0.07, -0.1, -0.13, -0.16, -0.2, -0.22, -0.26, -0.31, -0.34, -0.4], "names": ["Fred McGriff", "Chris Gomez", "Joe Kennedy", "Russ Johnson", "Paul Wilson", "Tanyon Sturtze", "Ben Grieve", "Esteban Yan", "Toby Hall", "Greg Vaughn", "Bryan Rekar", "Albie Lopez", "Brent Abernathy", "Victor Zambrano", "Steve Cox", "Travis Phelps", "Jose Guillen", "Ryan Rupe", "Randy Winn", "Nick Bierbrodt", "Jason Tyner", "Rusty Meacham", "Tony Fiore", "Ariel Prieto", "Paul Hoover", "Dan Wheeler", "Mike Judd", "Gerald Williams", "Mike DiFelice", "Jeff Wallace", "Bobby Seay", "Vinny Castilla", "Mickey Callaway", "Jared Sandberg", "Jason Standridge", "Damian Rolls", "Brian Rose", "Doug Creek", "Bobby Smith", "Travis Harper"], "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": [2.42, 1.47, 1.42, 1.4, 1.36, 1.34, 1.27, 1.16, 1.08, 1.05, 1.04, 0.71, 0.69, 0.67, 0.52, 0.5, 0.36, 0.33, 0.32, 0.32, 0.21, 0.06, 0.03, 0.0, -0.01, -0.02, -0.04, -0.06, -0.07, -0.08, -0.1, -0.12, -0.13, -0.15, -0.18, -0.22, -0.22, -0.23, -0.39, -0.44]}; 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:'TBR',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);});