RRosternomics

Blue Jays TOR

GM: Gord Ash

Overview

CONTEND ↗ rising
expensive & winning · year 1 in this regime · payroll 1.13× league · TAB +4.9
+4.9TAB — talent above budget9th/30
+4.5WAB — realized wins (TAB + luck)8th/30
6.0depth — 2+ WAR regulars15th/30
-2.9dead WAR — sub-replacement drag6th/30
31%dead money %19th/30
0.543win %9th/30
40.6roster fWAR11th/30
-4surplus $M (value vs cost)12th/30
$47Mpayroll $M14th/30

Roster

CFJosé Cruz Jr. 103GShannon Stewart 44GShawn Green 32G
LFShannon Stewart 110GJosé Canseco 50GFelipe Crespo 19G
RFShawn Green 128GJosé Canseco 26GFelipe Crespo 24G
SSAlex Gonzalez 158GCraig Grebeck 6GTomás Pérez 4G
2BCraig Grebeck 91GTony Fernández 82GFelipe Crespo 8G
3BTony Fernández 54GMark Dalesandro 8GTom Evans 7G
1BCarlos Delgado 141GJuan Samuel 3GMark Dalesandro 2G
STARTERSRoger Clemens 8.2WARChris Carpenter 2.8WARKelvim Escobar 1.9WARWoody Williams 1.4WARPat Hentgen 0.6WAR
CDarrin Fletcher 121GKevin Brown 52GMark Dalesandro 18G
DHJosé Canseco 78GCarlos Delgado 1GTony Fernández 1G
BULLPEN Paul Quantrill 7svDan Plesac 0hBill Risley 0hRobert Person 0hBen Van Ryn 0hCarlos Almánzar 0hSteve Sinclair 0hDave Stieb 0h
CDarrin Fletcher 121 · Kevin Brown 52 · Mark Dalesandro 18
1BCarlos Delgado 141 · Juan Samuel 3 · Mark Dalesandro 2
2BCraig Grebeck 91 · Tony Fernández 82 · Felipe Crespo 8
3BTony Fernández 54 · Mark Dalesandro 8 · Tom Evans 7
SSAlex Gonzalez 158 · Craig Grebeck 6 · Tomás Pérez 4
LFShannon Stewart 110 · José Canseco 50 · Felipe Crespo 19
CFJosé Cruz Jr. 103 · Shannon Stewart 44 · Shawn Green 32
RFShawn Green 128 · José Canseco 26 · Felipe Crespo 24
DHJosé Canseco 78 · Carlos Delgado 1 · Tony Fernández 1
SPRoger Clemens 8.2 · Chris Carpenter 2.8 · Kelvim Escobar 1.9 · Woody Williams 1.4 · Pat Hentgen 0.6
BP Paul Quantrill 7 · Dan Plesac 0 · Bill Risley 0 · Robert Person 0 · Ben Van Ryn 0 · Carlos Almánzar 0 · Steve Sinclair 0 · Dave Stieb 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.

1998 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Roger Clemens 35 FA-elig 338.2 8.6 +0.3
Carlos Delgado 26 arb 1425.7 2.4 +3.7
Shawn Green 25 arb 1583.4 1.5 +2.2
Shannon Stewart 24 arb 1443.1 0.2 +3.1
Chris Carpenter 23 pre-arb 332.8 0.2 +2.8
Tony Fernandez 36 FA-elig 1382.5 2.0 +0.7
Kelvim Escobar 22 pre-arb 221.9 0.2 +1.8
Paul Quantrill 29 FA-elig 821.6 1.1 +0.6
José Cruz Jr. 24 pre-arb 1051.5 0.3 +1.3
Juan Guzman↔ BAL 31 FA-elig 221.4 3.3 -1.8
Woody Williams 31 arb 321.4 1.6 -0.1
Dan Plesac 36 FA-elig 781.3 0.8 +0.6
Jose Canseco 33 FA-elig 1511.3 2.1 -0.7
Darrin Fletcher 31 FA-elig 1241.3 1.5 -0.1
Mike Stanley↔ BOS 35 FA-elig 981.2 1.2 +0.1
Tony Phillips↔ NYM 39 FA-elig 130.8 0.1 +0.7
Craig Grebeck 33 FA-elig 1020.8 0.4 +0.4
Ed Sprague Jr.↔ OAK 30 FA-elig 1050.7 1.9 -1.2
Pat Hentgen 29 FA-elig 290.6 3.2 -2.6
Kevin Brown 25 pre-arb 520.5 0.2 +0.4
Roy Halladay 21 20.3
Steve Sinclair 26 pre-arb 240.3 0.2 +0.2
Randy Myers↔ SDP 35 FA-elig 410.3 2.9 -2.6
Alex Gonzalez 25 arb 1580.3 1.5 -1.2
Mark Dalesandro 30 arb 320.1 0.2 -0.0
Pat Lennon 30 20.1
Carlos Almánzar 24 pre-arb 250.1 0.2 -0.1
Ben Van Ryn↔ CHC 26 pre-arb 100.1 0.1 +0.0
Luis Andujar 25 50.0
Shannon Withem 25 10.0
Dave Stieb 40 FA-elig 190.0 0.5 -0.4
Tomas Perez 24 6-0.0
Benito Santiago 33 FA-elig 15-0.0 4.0 -4.0
Kevin Witt 22 5-0.1
Felipe Crespo 25 pre-arb 66-0.2 0.2 -0.4
Nerio Rodriguez↔ BAL 27 pre-arb 7-0.2 0.1 -0.3
Tom Evans 23 7-0.2
Bill Risley 31 FA-elig 44-0.3 0.4 -0.8
Juan Samuel 37 FA-elig 43-0.5 0.4 -0.9
Erik Hanson 33 FA-elig 11-0.5 3.5 -4.1
Robert Person 28 arb 27-0.9 0.3 -1.2

Key additions (new vs 1997)

PlayerfWAR
Tony Fernandez2.5
Jose Canseco1.3
Darrin Fletcher1.3
Mike Stanley1.2
Tony Phillips0.8
Craig Grebeck0.8

Key subtractions (gone from 1997)

Player1997 fWAR
Orlando Merced2.2
Charlie O'Brien1.9
Paul Spoljaric0.7
Omar Daal0.7
Otis Nixon0.5
Mike Timlin0.3

Current & Historical Payroll

1998 payroll
$47.2M

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

PlayerAgeSvc1998199920002001200220032004
Roger Clemens3514.000$8.6MFREE AGENT
Benito Santiago3312.000$4.0MFREE AGENT
Erik Hanson3310.000$3.5MFREE AGENT
Juan Guzman317.000$3.3MFREE AGENT
Pat Hentgen297.000$3.2M$8.6MFREE AGENT$2.2M
Randy Myers3513.000$2.9MFREE AGENT
Jose Canseco3313.000$2.1MFREE AGENT
Tony Fernandez3615.000$2.0M$2.2MFREE AGENT$379k
Ed Sprague Jr.307.000$1.9MFREE AGENT
Darrin Fletcher319.000$1.5M$2.6M$2.5M$3.5M$3.8MFREE AGENT
Mike Stanley3512.000$1.2MFREE AGENT
Paul Quantrill296.000$1.1M$500k$1.8M$1.8MFREE AGENT
Dan Plesac3612.000$800k$797kFREE AGENT$2.2M$1.5M
Dave Stieb4019.000$450kFREE AGENT
Juan Samuel3715.000$421kFREE AGENT
Craig Grebeck338.000$415k$550k$800kFREE AGENT
Bill Risley316.000$405kFREE AGENT
Tony Phillips3916.000$112kFREE AGENT
Pat Lennon307.000FREE AGENT

Arbitration-eligible (9)

PlayerAgeSvc1998199920002001200220032004
Carlos Delgado265.000$2.4MFREE AGENT
Woody Williams315.000$1.6MFREE AGENT
Alex Gonzalez254.000$1.5MARB 3FREE AGENT
Shawn Green255.000$1.5MFREE AGENT
Robert Person283.000$265kARB 2ARB 3FREE AGENT
Shannon Stewart243.000$192kARB 2ARB 3FREE AGENT
Mark Dalesandro304.000$185kARB 3FREE AGENT
Luis Andujar253.000ARB 2ARB 3FREE AGENT
Tomas Perez243.000ARB 2ARB 3FREE AGENT

Pre-arbitration (13)

PlayerAgeSvc1998199920002001200220032004
José Cruz Jr.241.000$325kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kelvim Escobar221.000$220kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Chris Carpenter231.000$190kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Felipe Crespo252.000$185kARB 1ARB 2ARB 3FREE AGENT
Carlos Almánzar241.000$172kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kevin Brown252.000$170kARB 1ARB 2ARB 3FREE AGENT
Steve Sinclair260.000$170kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Nerio Rodriguez272.000$93kARB 1ARB 2ARB 3FREE AGENT
Ben Van Ryn262.000$68kARB 1ARB 2ARB 3FREE AGENT
Tom Evans231.000Pre-ArbARB 1ARB 2ARB 3FREE AGENT
Shannon Withem25
Roy Halladay210.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Kevin Witt22

Graphs

Every roster slot ranked best→worst by WAR. Bars = Blue Jays 1998; 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.009, 10.45], [1986, 1.062, 7.11], [1987, 0.988, 14.82], [1988, 1.047, 5.09], [1989, 1.12, 8.57], [1990, 1.141, 8.06], [1991, 0.965, 8.68], [1992, 1.468, 8.9], [1993, 1.545, 7.96], [1994, 1.198, -8.57], [1995, 1.412, -19.0], [1996, 0.895, -5.12], [1997, 1.235, -8.54], [1998, 1.134, 4.87], [1999, 0.955, 3.91], [2000, 0.897, 1.72], [2001, 1.146, -2.22], [2002, 0.959, -3.23], [2003, 0.705, 3.49], [2004, 0.755, -6.3], [2005, 0.64, 4.19], [2006, 0.926, 7.47], [2007, 0.995, -0.44], [2008, 1.025, 4.46], [2009, 0.881, 2.03], [2010, 0.666, 9.89], [2011, 0.652, -2.11], [2012, 0.831, -11.11], [2013, 1.222, -10.18], [2014, 1.151, 0.05], [2015, 1.049, 15.61], [2016, 1.249, 3.54], [2017, 1.304, -12.51], [2018, 0.957, -10.05], [2019, 0.468, -3.26], [2021, 1.084, 8.74], [2022, 1.224, 12.25], [2023, 1.365, 5.2], [2024, 1.3, -6.24], [2025, 1.595, 4.48], [2026, 1.526, -8.15]], TEAMCOL = '#1D9BD7'; 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 = 1998; 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": ["SP", "IF", "OF", "OF", "SP", "IF", "RP", "RP", "OF", "SP", "SP", "RP", "OF", "C", "IF", "OF", "IF", "IF", "SP", "C", "SP", "RP", "RP", "IF", "C", "OF", "RP", "RP", "RP", "RP", "RP", "IF", "C", "IF", "OF", "RP", "IF", "RP", "OF", "SP"], "league": [6.26, 4.71, 3.97, 3.22, 2.79, 2.4, 2.03, 1.81, 1.6, 1.45, 1.29, 1.14, 0.99, 0.83, 0.72, 0.63, 0.53, 0.45, 0.37, 0.3, 0.24, 0.2, 0.15, 0.12, 0.08, 0.05, 0.02, -0.01, -0.04, -0.07, -0.09, -0.11, -0.14, -0.18, -0.22, -0.26, -0.35, -0.46, -0.37, -0.41], "names": ["Roger Clemens", "Carlos Delgado", "Shawn Green", "Shannon Stewart", "Chris Carpenter", "Tony Fernandez", "Kelvim Escobar", "Paul Quantrill", "Jos\u00e9 Cruz Jr.", "Juan Guzman", "Woody Williams", "Dan Plesac", "Jose Canseco", "Darrin Fletcher", "Mike Stanley", "Tony Phillips", "Craig Grebeck", "Ed Sprague Jr.", "Pat Hentgen", "Kevin Brown", "Roy Halladay", "Steve Sinclair", "Randy Myers", "Alex Gonzalez", "Mark Dalesandro", "Pat Lennon", "Carlos Alm\u00e1nzar", "Ben Van Ryn", "Luis Andujar", "Shannon Withem", "Dave Stieb", "Tomas Perez", "Benito Santiago", "Kevin Witt", "Felipe Crespo", "Nerio Rodriguez", "Tom Evans", "Bill Risley", "Juan Samuel", "Erik Hanson"], "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": [8.19, 5.65, 3.41, 3.08, 2.78, 2.51, 1.87, 1.61, 1.47, 1.43, 1.38, 1.34, 1.3, 1.3, 1.21, 0.79, 0.78, 0.65, 0.57, 0.49, 0.33, 0.32, 0.29, 0.26, 0.15, 0.1, 0.09, 0.08, 0.04, 0.02, 0.0, -0.01, -0.02, -0.1, -0.16, -0.2, -0.21, -0.32, -0.47, -0.51]}; 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:'TOR',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);});