RRosternomics

Blue Jays TOR

GM: Gord Ash

Overview

FORCED-SPEND ↘ cooling
expensive & losing · year 2 in this regime · payroll 1.41× league · TAB -19.0
-19.0TAB — talent above budget27th/28
-20.7WAB — realized wins (TAB + luck)28th/28
5.0depth — 2+ WAR regulars18th/28
-4.8dead WAR — sub-replacement drag13th/28
33%dead money %22nd/28
0.389win %27th/28
17.2roster fWAR27th/28
-30surplus $M (value vs cost)28th/28
$47Mpayroll $M3rd/28

Roster

CFDevon White 99GMike Huff 33GJoe Carter 20G
LFJoe Carter 116GCarlos Delgado 17GMike Huff 9G
RFShawn Green 109GMike Huff 15GRobert Pérez 11G
SSAlex Gonzalez 97GTomás Pérez 31GDomingo Cedeño 30G
2BRoberto Alomar 128GDomingo Cedeño 20GTomás Pérez 7G
3BEd Sprague Jr. 139GAlex Gonzalez 9GHoward Battle 6G
1BJohn Olerud 133GJoe Carter 7GEd Sprague Jr. 7G
STARTERSAl Leiter 3.0WARPat Hentgen 2.5WARJuan Guzmán 1.8WARPaul Menhart 0.2WARJeff Ware 0.2WAR
CLance Parrish 67GSandy Martínez 61GRandy Knorr 45G
DHPaul Molitor 129GJoe Carter 5GCarlos Delgado 5G
BULLPEN Tony Castillo 13svMike Timlin 0hTim Crabtree 0hDanny Cox 0hWoody Williams 0hKen Robinson 0hJimmy Rogers 0hDarren Hall 0h
CLance Parrish 67 · Sandy Martínez 61 · Randy Knorr 45
1BJohn Olerud 133 · Joe Carter 7 · Ed Sprague Jr. 7
2BRoberto Alomar 128 · Domingo Cedeño 20 · Tomás Pérez 7
3BEd Sprague Jr. 139 · Alex Gonzalez 9 · Howard Battle 6
SSAlex Gonzalez 97 · Tomás Pérez 31 · Domingo Cedeño 30
LFJoe Carter 116 · Carlos Delgado 17 · Mike Huff 9
CFDevon White 99 · Mike Huff 33 · Joe Carter 20
RFShawn Green 109 · Mike Huff 15 · Robert Pérez 11
DHPaul Molitor 129 · Joe Carter 5 · Carlos Delgado 5
SPAl Leiter 3.0 · Pat Hentgen 2.5 · Juan Guzmán 1.8 · Paul Menhart 0.2 · Jeff Ware 0.2
BP Tony Castillo 13 · Mike Timlin 0 · Tim Crabtree 0 · Danny Cox 0 · Woody Williams 0 · Ken Robinson 0 · Jimmy Rogers 0 · Darren Hall 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.

1995 roster — value & surplus

PlayerAgeServiceGfWARSalary $MSurplus $M
Al Leiter 29 FA-elig 283.0 0.8 +2.0
David Cone↔ NYY 32 FA-elig 172.9 4.5 -1.7
Pat Hentgen 26 arb 302.5 1.2 +1.1
Roberto Alomar 27 FA-elig 1302.2 5.5 -3.4
John Olerud 26 FA-elig 1352.2 5.8 -3.7
Juan Guzman 28 arb 241.8 2.8 -1.1
Mike Timlin 29 arb 311.3 0.5 +0.8
Devon White 32 FA-elig 1011.2 4.0 -2.9
Paul Molitor 38 FA-elig 1301.0 4.5 -3.5
Ed Sprague Jr. 27 arb 1440.9 0.8 +0.1
Mike Huff 31 FA-elig 610.6 0.4 +0.2
Tony Castillo 32 FA-elig 550.6 0.6 -0.1
Tim Crabtree 25 pre-arb 310.3 0.1 +0.2
Alex Gonzalez 22 pre-arb 1110.3 0.1 +0.1
Woody Williams 28 pre-arb 230.3 0.2 +0.1
Paul Menhart 26 pre-arb 210.2 0.1 +0.1
Jeff Ware 24 50.2
Shawn Green 22 pre-arb 1210.2 0.1 +0.1
Lance Parrish 39 FA-elig 700.1 0.3 -0.2
Howard Battle 23 90.1
Candy Maldonado↔ TEX 34 FA-elig 610.1 0.2 -0.1
Danny Darwin↔ TEX 39 FA-elig 130.1 0.2 -0.1
Danny Cox 35 FA-elig 240.0 0.7 -0.7
Edwin Hurtado 25 pre-arb 14-0.0 0.1 -0.1
Darren Hall 30 pre-arb 17-0.0 0.2 -0.2
Duane Ward 31 FA-elig 4-0.1 4.8 -4.8
Brad Cornett 26 5-0.1
Domingo Cedeno 26 pre-arb 51-0.1 0.1 -0.2
Shannon Stewart 21 12-0.2
Tomas Perez 21 pre-arb 41-0.2 0.1 -0.3
Ken Robinson 25 21-0.2
Giovanni Carrara 27 pre-arb 12-0.2 0.1 -0.3
Robert Perez 26 pre-arb 17-0.3 0.1 -0.4
Ricardo Jordan 25 pre-arb 15-0.4 0.1 -0.5
Jimmy Rogers 28 pre-arb 19-0.4 0.1 -0.5
Joe Carter 35 FA-elig 139-0.5 7.5 -7.9
Randy Knorr 26 arb 45-0.6 0.2 -0.9
Carlos Delgado 23 37-0.7
Sandy Martinez 24 pre-arb 62-0.8 0.1 -0.8

Key additions (new vs 1994)

PlayerfWAR
David Cone2.9
Tim Crabtree0.3
Paul Menhart0.2
Jeff Ware0.2
Lance Parrish0.1
Howard Battle0.1

Key subtractions (gone from 1994)

Player1994 fWAR
Todd Stottlemyre2.0
Dave Stewart0.8
Randy St. Claire0.0
Aaron Small-0.1
Dave Righetti-0.1
Dick Schofield-0.2

Current & Historical Payroll

1995 payroll
$47.0M

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

PlayerAgeSvc1995199619971998199920002001
Joe Carter3512.000$7.5M$6.5M$6.5MFREE AGENT
John Olerud266.000$5.8M$6.5MFREE AGENT
Roberto Alomar277.000$5.5MFREE AGENT
Duane Ward319.000$4.8MFREE AGENT
David Cone329.000$4.5MFREE AGENT
Paul Molitor3817.000$4.5MFREE AGENT
Devon White3210.000$4.0MFREE AGENT
Al Leiter298.000$795kFREE AGENT
Danny Cox3512.000$700kFREE AGENT
Tony Castillo327.000$610k$480kFREE AGENT
Mike Huff316.000$360kFREE AGENT
Lance Parrish3918.000$290kFREE AGENT
Danny Darwin3917.000$195kFREE AGENT
Candy Maldonado3414.000$165kFREE AGENT

Arbitration-eligible (5)

PlayerAgeSvc1995199619971998199920002001
Juan Guzman284.000$2.8MARB 3FREE AGENT
Pat Hentgen264.000$1.2MARB 3FREE AGENT
Ed Sprague Jr.274.000$760kARB 3FREE AGENT
Mike Timlin294.000$500kARB 3FREE AGENT
Randy Knorr264.000$240kARB 3FREE AGENT

Pre-arbitration (20)

PlayerAgeSvc1995199619971998199920002001
Woody Williams282.000$175kARB 1ARB 2ARB 3FREE AGENT
Darren Hall301.000$175kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Domingo Cedeno262.000$140kARB 1ARB 2ARB 3FREE AGENT
Alex Gonzalez221.000$130kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Shawn Green222.000$130kARB 1ARB 2ARB 3FREE AGENT
Robert Perez261.000$112kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ricardo Jordan250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Edwin Hurtado250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Giovanni Carrara270.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tim Crabtree250.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Paul Menhart260.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Sandy Martinez240.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jimmy Rogers280.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tomas Perez210.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Howard Battle23
Brad Cornett26
Carlos Delgado232.000ARB 1ARB 2ARB 3FREE AGENT
Ken Robinson25
Shannon Stewart210.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeff Ware240.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Blue Jays 1995; 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 = 1995; 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", "SP", "SP", "IF", "IF", "SP", "RP", "OF", "IF", "IF", "OF", "RP", "RP", "IF", "RP", "RP", "SP", "OF", "C", "IF", "OF", "SP", "RP", "SP", "RP", "RP", "RP", "IF", "OF", "IF", "RP", "SP", "OF", "RP", "RP", "OF", "C", "OF", "C", "\u2014"], "league": [5.24, 4.06, 3.35, 2.93, 2.47, 2.19, 1.93, 1.72, 1.52, 1.39, 1.22, 1.05, 0.91, 0.79, 0.7, 0.58, 0.51, 0.43, 0.36, 0.3, 0.26, 0.19, 0.16, 0.12, 0.09, 0.05, 0.03, 0.01, -0.02, -0.04, -0.07, -0.08, -0.11, -0.14, -0.17, -0.21, -0.27, -0.31, -0.36, -0.48], "names": ["Al Leiter", "David Cone", "Pat Hentgen", "Roberto Alomar", "John Olerud", "Juan Guzman", "Mike Timlin", "Devon White", "Paul Molitor", "Ed Sprague Jr.", "Mike Huff", "Tony Castillo", "Tim Crabtree", "Alex Gonzalez", "Woody Williams", "Paul Menhart", "Jeff Ware", "Shawn Green", "Lance Parrish", "Howard Battle", "Candy Maldonado", "Danny Darwin", "Danny Cox", "Edwin Hurtado", "Darren Hall", "Duane Ward", "Brad Cornett", "Domingo Cedeno", "Shannon Stewart", "Tomas Perez", "Ken Robinson", "Giovanni Carrara", "Robert Perez", "Ricardo Jordan", "Jimmy Rogers", "Joe Carter", "Randy Knorr", "Carlos Delgado", "Sandy Martinez", "(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": [2.95, 2.94, 2.46, 2.22, 2.18, 1.78, 1.32, 1.18, 1.04, 0.9, 0.58, 0.57, 0.31, 0.29, 0.26, 0.24, 0.19, 0.19, 0.12, 0.1, 0.09, 0.05, 0.05, -0.0, -0.01, -0.06, -0.08, -0.09, -0.18, -0.19, -0.21, -0.24, -0.31, -0.38, -0.42, -0.46, -0.64, -0.74, -0.77, 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:'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);});