RRosternomics

Blue Jays TOR

GM: Pat Gillick

Overview

CONTEND → steady
expensive & winning · year 1 in this regime · payroll 1.47× league · TAB +8.9
+8.9TAB — talent above budget5th/26
+9.3WAB — realized wins (TAB + luck)8th/26
11.0depth — 2+ WAR regulars1st/26
-2.5dead WAR — sub-replacement drag3rd/26
30%dead money %13th/26
0.593win %2nd/26
47.7roster fWAR1st/26
-6surplus $M (value vs cost)18th/26
$44Mpayroll $M2nd/26

1992: World Series champions · 96–66. Ranks are vs the 26 teams that season.

Roster

CFDevon White 152GDerek Bell 18GTurner Ward 4G
LFCandy Maldonado 129GDerek Bell 24GJoe Carter 6G
RFJoe Carter 123GDave Winfield 26GDerek Bell 15G
SSManuel Lee 128GAlfredo Griffin 48GEddie Zosky 8G
2BRoberto Alomar 150GAlfredo Griffin 16G
3BKelly Gruber 120GTom Quinlan 13GPat Tabler 1G
1BJohn Olerud 133GPat Tabler 34GDomingo Martínez 7G
STARTERSJuan Guzmán 5.5WARJack Morris 3.7WARJimmy Key 2.7WARTodd Stottlemyre 1.4WARDavid Cone 0.9WAR
CPat Borders 137GEd Sprague Jr. 15GRandy Knorr 8G
DHDave Winfield 130GJoe Carter 24GCandy Maldonado 4G
BULLPEN Tom Henke 34svDuane Ward 0hMark Eichhorn 0hPat Hentgen 0hBob MacDonald 0hMike Timlin 0hDoug Linton 0hDavid Weathers 0h
CPat Borders 137 · Ed Sprague Jr. 15 · Randy Knorr 8
1BJohn Olerud 133 · Pat Tabler 34 · Domingo Martínez 7
2BRoberto Alomar 150 · Alfredo Griffin 16
3BKelly Gruber 120 · Tom Quinlan 13 · Pat Tabler 1
SSManuel Lee 128 · Alfredo Griffin 48 · Eddie Zosky 8
LFCandy Maldonado 129 · Derek Bell 24 · Joe Carter 6
CFDevon White 152 · Derek Bell 18 · Turner Ward 4
RFJoe Carter 123 · Dave Winfield 26 · Derek Bell 15
DHDave Winfield 130 · Joe Carter 24 · Candy Maldonado 4
SPJuan Guzmán 5.5 · Jack Morris 3.7 · Jimmy Key 2.7 · Todd Stottlemyre 1.4 · David Cone 0.9
BP Tom Henke 34 · Duane Ward 0 · Mark Eichhorn 0 · Pat Hentgen 0 · Bob MacDonald 0 · Mike Timlin 0 · Doug Linton 0 · David Weathers 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
Roberto Alomar 24 arb 1526.1 3.0 +1.8
Devon White 29 FA-elig 1535.9 2.4 +2.2
Juan Guzman 25 pre-arb 285.5 0.2 +4.0
Dave Winfield 40 FA-elig 1563.8 2.3 +0.7
Jack Morris 37 FA-elig 343.7 4.5 -1.6
John Olerud 23 arb 1383.1 0.4 +2.0
Manuel Lee 27 FA-elig 1282.9 1.0 +1.3
Joe Carter 32 FA-elig 1582.9 3.7 -1.5
Jimmy Key 31 FA-elig 332.7 2.3 -0.1
Duane Ward 28 FA-elig 792.4 2.4 -0.6
Candy Maldonado 31 FA-elig 1372.3 1.4 +0.4
Todd Stottlemyre 27 arb 281.4 1.2 -0.1
Jeff Kent↔ NYM 24 pre-arb 651.1 0.1 +0.8
Pat Borders 29 arb 1381.1 1.0 -0.2
David Cone↔ NYM 29 FA-elig 80.9 1.0 -0.2
Mike Timlin 26 pre-arb 260.7 0.2 +0.3
Derek Bell 23 pre-arb 610.7 0.1 +0.4
Turner Ward 27 pre-arb 180.6 0.1 +0.4
Tom Henke 34 FA-elig 570.6 3.4 -2.9
Dave Stieb 34 FA-elig 210.4 3.2 -2.9
Mark Eichhorn↔ ANA 31 FA-elig 230.4 0.6 -0.3
David Wells 29 arb 410.4 2.1 -1.8
Domingo Martinez 26 70.2
Bob MacDonald 27 pre-arb 270.1 0.2 -0.1
Randy Knorr 23 pre-arb 80.1 0.1 -0.1
Greg Myers↔ ANA 26 arb 220.0 0.4 -0.3
Rance Mulliniks 36 FA-elig 30.0 0.8 -0.7
Ricky Trlicek 23 pre-arb 2-0.0 0.1 -0.1
Al Leiter 26 arb 1-0.0 0.2 -0.2
Eddie Zosky 24 pre-arb 8-0.1 0.1 -0.2
David Weathers 22 pre-arb 2-0.1 0.1 -0.2
Mike Maksudian 26 3-0.1
Kelly Gruber 30 FA-elig 120-0.1 3.6 -3.7
Alfredo Griffin 34 FA-elig 63-0.1 0.6 -0.7
Ed Sprague Jr. 24 pre-arb 22-0.2 0.1 -0.3
Pat Hentgen 23 pre-arb 28-0.3 0.1 -0.3
Tom Quinlan 24 13-0.3
Pat Tabler 34 FA-elig 49-0.3 0.9 -1.2
Doug Linton 27 8-0.4
Rob Ducey↔ ANA 27 arb 23-0.6 0.1 -0.5

Key additions (new vs 1991)

PlayerfWAR
Dave Winfield3.8
Jack Morris3.7
Jeff Kent1.1
David Cone0.9
Mark Eichhorn0.4
Domingo Martinez0.2

Key subtractions (gone from 1991)

Player1991 fWAR
Tom Candiotti2.9
Dave Parker0.1
Kenny Williams0.0
Vince Horsman0.0
Mickey Weston0.0
Ray Giannelli-0.0

Current & Historical Payroll

1992 payroll
$44.0M

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 (16)

PlayerAgeSvc1992199319941995199619971998
Jack Morris3715.000$4.5M$5.4MFREE AGENT
Joe Carter329.000$3.7M$5.5M$5.5M$7.5M$6.5M$6.5MFREE AGENT
Kelly Gruber308.000$3.6MFREE AGENT
Tom Henke3410.000$3.4MFREE AGENT
Dave Stieb3413.000$3.2MFREE AGENT$450k
Duane Ward286.000$2.4M$3.4MFREE AGENT$4.8M
Devon White297.000$2.4M$3.7M$3.1M$4.0MFREE AGENT
Dave Winfield4019.000$2.3MFREE AGENT
Jimmy Key318.000$2.3MFREE AGENT
Candy Maldonado3111.000$1.4MFREE AGENT$165k
Manuel Lee277.000$1.0MFREE AGENT
David Cone296.000$971kFREE AGENT$4.5M
Pat Tabler3411.000$900kFREE AGENT
Rance Mulliniks3615.000$775kFREE AGENT
Mark Eichhorn3110.000$639k$850kFREE AGENT
Alfredo Griffin3416.000$638k$500kFREE AGENT

Arbitration-eligible (8)

PlayerAgeSvc1992199319941995199619971998
Roberto Alomar244.000$3.0MARB 3FREE AGENT
David Wells295.000$2.1MFREE AGENT
Todd Stottlemyre274.000$1.2MARB 3FREE AGENT
Pat Borders294.000$1.0MARB 3FREE AGENT
John Olerud233.000$388kARB 2ARB 3FREE AGENT
Greg Myers265.000$385kFREE AGENT
Al Leiter265.000$180kFREE AGENT
Rob Ducey275.000$85kFREE AGENT

Pre-arbitration (16)

PlayerAgeSvc1992199319941995199619971998
Juan Guzman251.000$242kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mike Timlin261.000$222kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Bob MacDonald272.000$182kARB 1ARB 2ARB 3FREE AGENT
Turner Ward272.000$128kARB 1ARB 2ARB 3FREE AGENT
David Weathers221.000$121kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ed Sprague Jr.241.000$118kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Derek Bell231.000$112kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Pat Hentgen231.000$109kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Randy Knorr231.000$109kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Ricky Trlicek230.000$109kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Eddie Zosky241.000$109kPre-ArbARB 1ARB 2ARB 3FREE AGENT
Jeff Kent240.000$69kPre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Doug Linton270.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Mike Maksudian260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Domingo Martinez260.000Pre-ArbPre-ArbARB 1ARB 2ARB 3FREE AGENT
Tom Quinlan242.000ARB 1ARB 2ARB 3FREE AGENT

Graphs

Every roster slot ranked best→worst by WAR. Bars = Blue Jays 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.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 = 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": ["IF", "OF", "SP", "OF", "SP", "IF", "IF", "OF", "SP", "RP", "OF", "SP", "IF", "C", "SP", "RP", "OF", "OF", "RP", "SP", "RP", "RP", "IF", "RP", "C", "C", "IF", "RP", "RP", "IF", "RP", "IF", "IF", "IF", "C", "RP", "IF", "IF", "RP", "OF"], "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": ["Roberto Alomar", "Devon White", "Juan Guzman", "Dave Winfield", "Jack Morris", "John Olerud", "Manuel Lee", "Joe Carter", "Jimmy Key", "Duane Ward", "Candy Maldonado", "Todd Stottlemyre", "Jeff Kent", "Pat Borders", "David Cone", "Mike Timlin", "Derek Bell", "Turner Ward", "Tom Henke", "Dave Stieb", "Mark Eichhorn", "David Wells", "Domingo Martinez", "Bob MacDonald", "Randy Knorr", "Greg Myers", "Rance Mulliniks", "Ricky Trlicek", "Al Leiter", "Eddie Zosky", "David Weathers", "Mike Maksudian", "Kelly Gruber", "Alfredo Griffin", "Ed Sprague Jr.", "Pat Hentgen", "Tom Quinlan", "Pat Tabler", "Doug Linton", "Rob Ducey"], "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": [6.14, 5.9, 5.48, 3.81, 3.71, 3.09, 2.88, 2.87, 2.75, 2.38, 2.3, 1.42, 1.05, 1.05, 0.94, 0.73, 0.72, 0.65, 0.65, 0.45, 0.38, 0.38, 0.18, 0.13, 0.07, 0.05, 0.04, -0.02, -0.04, -0.06, -0.06, -0.07, -0.1, -0.14, -0.17, -0.27, -0.27, -0.33, -0.36, -0.59]}; 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);});