/* BT player career stats + Statcast year menu v1.8 (v1.8: hitter career table uses one AVG/OBP/SLG column and adds Savant xwOBA after wOBA. v1.7: Statcast by season now uses Baseball Savant season numbers. v1.6: xwOBA, Hard-hit% and Whiff% moved to a new Statcast by season table under the Statcast section; bigger type for the wider layout. October 21, 2026; placed right under the season stat line; columns reordered). - Career stats section (#btcs): year-by-year FanGraphs lines, MLB and Minor Leagues tabs (hitting and/or pitching). - Statcast year menu: a select in the Statcast section listing only the player's MLB seasons since 2015. Data: FanGraphs year-by-year lines stored in 32 shards (bt-cs--sNN.json, id mod 32), MLB Stats API yearByYear, Savant via the range file's window.btScData / window.btScHtml (exposed by bt-player-range-v1-11.js). */ (function(){ var Q=String.fromCharCode(63),E=String.fromCharCode(61),A=String.fromCharCode(38); var MLB='https://statsapi.mlb.com/api/v1/'; function J(u){return fetch(u).then(function(r){if(!r.ok)throw new Error(r.status);return r.json()})} function css(){if(document.getElementById('btcs-css'))return;var s=document.createElement('style');s.id='btcs-css';s.textContent= '#btcs.btcs{background:none!important;border:0!important;padding:0!important;margin:18px 0 6px!important;box-shadow:none!important}'+'#btcs .btcs-bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0 12px}'+ '#btcs .btcs-bar .btpv-tg{padding-left:0}'+ '#btcs .btcs-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:0 -4px;scrollbar-color:rgba(255,255,255,.25) transparent;scrollbar-width:thin}'+ '#btcs table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}'+ '#btcs th,#btcs td{text-align:center;white-space:nowrap;padding:6px 3px;border-bottom:1px solid rgba(255,255,255,.1);font-size:12px}'+ '#btcs th{font-size:10.5px;font-weight:700;text-transform:none;opacity:.85}'+ '#btcs td:first-child,#btcs th:first-child{position:sticky;left:0;background:#0d1f32;font-weight:700;z-index:1}'+ '#btcs tr.sub td{opacity:.72;font-size:11.5px}'+ '#btcs tr.sub td:first-child{font-weight:400}'+ '#btcs tr.tot td{font-weight:700;border-top:1px solid rgba(255,255,255,.35)}'+ '#btcs tr.lvtot td{font-weight:700}'+ '#btcs .btcs-note{font-size:12px;opacity:.7;margin:8px 0 0}'+ '#btcs .btcs-tg button{cursor:pointer}'+ '.btcs-scsel{margin-left:10px;vertical-align:middle;background:#0c2138;color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:4px 8px;font-size:13px;font-weight:600;font-family:inherit}'+ '.btcs-scmsg{font-size:12.5px;opacity:.75;margin:6px 0}'; document.head.appendChild(s)} function num(v){return typeof v==='number'&&isFinite(v)?v:null} function f3(v){v=num(v);return v==null?'':v.toFixed(3).replace(/^0/,'').replace(/^-0/,'-')} function f2(v){v=num(v);return v==null?'':v.toFixed(2)} function f1(v){v=num(v);return v==null?'':v.toFixed(1)} function f0(v){v=num(v);return v==null?'':String(Math.round(v))} function pc(v){v=num(v);return v==null?'':(v*100).toFixed(1)+'%'} function ip(v){if(v==null)return '';if(typeof v!=='number')return String(v);var r=Math.round(v*10)/10;return r%1===0?String(r):r.toFixed(1)} function whiff(r){var c=num(r['pfxContact%']);return c==null?null:1-c} function strip(s){return String(s==null?'':s).replace(/<[^>]+>/g,'').trim()} function team(r){var t=strip(r.AbbName);if(!t||/^-/.test(t))return (r._n||2)+' Tms';return t} var HIT_MLB=[['Year',function(r){return r.aseason}],['Team',team],['Level',function(r){return strip(r.AbbLevel)}],['Age',function(r){return f0(r.Age)}],['G',function(r){return f0(r.G)}], ['AVG',function(r){return f3(r.AVG)}],['OBP',function(r){return f3(r.OBP)}],['SLG',function(r){return f3(r.SLG)}],['wOBA',function(r){return f3(r.wOBA)}],['xwOBA',function(r){return f3(r.xwOBA)}], ['wRC+',function(r){return f0(r['wRC+'])}],['fWAR',function(r){return f1(r.WAR)}],['2B',function(r){return f0(r['2B'])}],['3B',function(r){return f0(r['3B'])}],['HR',function(r){return f0(r.HR)}],['SB',function(r){return f0(r.SB)}], ['Hard-hit%',function(r){return pc(r['HardHit%'])}],['Whiff%',function(r){return pc(whiff(r))}]]; function btcsOrd(a,o){var m={};a.forEach(function(c){m[c[0]]=c});return o.map(function(k){return m[k]}).filter(Boolean)} HIT_MLB.push(['AVG/OBP/SLG',function(r){var a=f3(r.AVG);return a?a+'/'+f3(r.OBP)+'/'+f3(r.SLG):''}]);HIT_MLB.forEach(function(c){if(c[0]==='xwOBA')c[1]=function(r){return r._sx!=null?f3(r._sx):''}}); function btSx(set,sv){if(sv)set.rows.forEach(function(o){if(o.c===''){var a=sv[o.r.aseason];if(a&&a[6]!=null)o.r._sx=a[6]}});return set} HIT_MLB=btcsOrd(HIT_MLB,['Year','Team','Level','Age','G','AVG/OBP/SLG','2B','3B','HR','SB','wOBA','xwOBA','wRC+','fWAR']); var HIT_MIL=HIT_MLB.filter(function(c){return ['xwOBA','fWAR','Hard-hit%'].indexOf(c[0])<0}); var PIT_MLB=[['Year',function(r){return r.aseason}],['Team',team],['Level',function(r){return strip(r.AbbLevel)}],['Age',function(r){return f0(r.Age)}],['G',function(r){return f0(r.G)}],['GS',function(r){return f0(r.GS)}], ['IP',function(r){return ip(r.IP)}],['K%',function(r){return pc(r['K%'])}],['BB%',function(r){return pc(r['BB%'])}],['ERA',function(r){return f2(r.ERA)}],['FIP',function(r){return f2(r.FIP)}], ['xERA',function(r){return f2(r.xERA)}],['xFIP',function(r){return f2(r.xFIP)}],['WPA',function(r){return f2(r.WPA)}],['SwStr%',function(r){return pc(r['SwStr%'])}],['fWAR',function(r){return f1(r.WAR)}]]; var PIT_MIL=PIT_MLB.filter(function(c){return ['xERA','WPA','fWAR'].indexOf(c[0])<0}); var NOTE={hit:'Minor-league lines don’t include xwOBA or fWAR, which aren’t published for the minors.',pit:'Minor-league lines don’t include xERA, WPA or fWAR, which aren’t published for the minors.'}; function mlbRows(data){ var out=[],seasons={}; data.forEach(function(r){if(strip(r.AbbLevel)!=='MLB')return;var t=r.type;if(t===0||(t>=1&&t<=9)){(seasons[r.aseason]||(seasons[r.aseason]={tot:null,sub:[]}));if(t===0)seasons[r.aseason].tot=r;else seasons[r.aseason].sub.push(r)}}); Object.keys(seasons).map(Number).sort(function(a,b){return a-b}).forEach(function(y){var s=seasons[y];if(!s.tot)return;s.tot._n=s.sub.length||2;out.push({r:s.tot,c:''});if(s.sub.length>1)s.sub.sort(function(a,b){return a.type-b.type}).forEach(function(x){out.push({r:x,c:'sub'})})}); var car=data.filter(function(r){return r.type===-1&&strip(r.AbbLevel)==='MLB'})[0]; return {rows:out,car:car}; } function milRows(data){ var by={}; data.forEach(function(r){var t=r.type;if(t<=-50&&t>=-60){(by[r.aseason]||(by[r.aseason]=[])).push(r)}}); var out=[]; Object.keys(by).map(Number).sort(function(a,b){return a-b}).forEach(function(y){var a=by[y];var tot=a.filter(function(r){return r.type===-60})[0];var lv=a.filter(function(r){return r.type!==-60}).sort(function(p,q){return p.type-q.type}); if(lv.length>1&&tot){lv.forEach(function(r){out.push({r:r,c:'sub'})});tot._lvl='Total';out.push({r:tot,c:'lvtot'})}else if(lv.length)lv.forEach(function(r){out.push({r:r,c:''})});else if(tot)out.push({r:tot,c:''})}); return {rows:out}; } function table(cols,set){ var h=''+cols.map(function(c){return ''}).join('')+''; set.rows.forEach(function(o){h+=''+cols.map(function(c){var v=c[1](o.r);if(c[0]==='Level'&&o.r._lvl)v=o.r._lvl;return ''}).join('')+''}); if(set.car){h+=''+cols.map(function(c){var v;if(c[0]==='Year')v='Career';else if(c[0]==='Team'||c[0]==='Level'||c[0]==='Age')v='';else v=c[1](set.car);return ''}).join('')+''} return h+'
'+c[0]+'
'+v+'
'+v+'
'; } function anchor(){var ls=[].slice.call(document.querySelectorAll('#bt-player .btp-line4'));for(var i=0;i1)h+='
'+groups.map(function(x,i){return ''}).join('')+'
'; h+='
'+(g.mlb.rows.length?'':'')+(g.mil.rows.length?'':'')+'
'; h+='
'+(lv==='mlb'?table(g.kind==='hit'?HIT_MLB:PIT_MLB,g.mlb):table(g.kind==='hit'?HIT_MIL:PIT_MIL,g.mil))+'
'; h+='

'+(lv==='mil'?NOTE[g.kind]+' ':'')+(lv==='mlb'?'Regular season. xwOBA from Baseball Savant. ':'')+'Data: FanGraphs.

'; sec.innerHTML=h; [].forEach.call(sec.querySelectorAll('button[data-g]'),function(b){b.onclick=function(){st.g=+b.getAttribute('data-g');st.lv=null;draw()}}); [].forEach.call(sec.querySelectorAll('button[data-l]'),function(b){b.onclick=function(){st.lv=b.getAttribute('data-l');draw()}}); } draw(); } var SHARD=window.BT_CS_BASE||'https://storage.ghost.io/c/f2/85/f285ce72-40e3-409f-ae3b-2ef371936c02/content/files/2026/10/bt-cs-2026-10-21-s'; function career(id,pos){ return J(SHARD+('0'+(id%32)).slice(-2)+'.json').then(function(f){var o=f&&f.p&&f.p[id];if(!o)return; var gs=[];if(o.h)gs.push({kind:'hit',label:'Hitting',mlb:btSx(mlbRows(o.h),o.sv&&o.sv.h),mil:milRows(o.h)});if(o.p)gs.push({kind:'pit',label:'Pitching',mlb:mlbRows(o.p),mil:milRows(o.p)}); if(pos==='P')gs.reverse(); gs=gs.filter(function(g){return g.mlb.rows.length||g.mil.rows.length});if(!gs.length)return; css();var sec=document.createElement('section');sec.id='btcs';sec.className='btpv btcs';render(sec,gs); if(!place(sec)){var t=0,iv=setInterval(function(){if(place(sec)||++t>40)clearInterval(iv)},250)} var btcsBusy=false;var mo=new MutationObserver(function(){if(btcsBusy)return;var a=anchor();if(a&&sec.previousElementSibling!==a){btcsBusy=true;place(sec);setTimeout(function(){btcsBusy=false},0)}});mo.observe(document.querySelector('.gh-content')||document.body,{childList:true,subtree:true}); }).catch(function(){})} function statcast(id,pos,years){ if(!years.length)return;css(); var busy=false; function shownYear(sc){var m=(sc.querySelector('h3')||{textContent:''}).textContent.match(/(20\d\d)/);return m?+m[1]:years[0]} function inject(){var sc=document.querySelector('#bt-player .btp-sc');if(!sc||busy)return;var h=sc.querySelector('h3');if(!h||h.querySelector('select.btcs-scsel'))return; var cur=shownYear(sc);var s=document.createElement('select');s.className='btcs-scsel';s.setAttribute('aria-label','Statcast season'); s.innerHTML=years.map(function(y){return ''}).join(''); if(years.indexOf(cur)<0){var o=document.createElement('option');o.value=cur;o.textContent=cur;o.selected=true;s.insertBefore(o,s.firstChild)} s.onchange=function(){var y=+s.value;if(typeof window.btScData!=='function'||typeof window.btScHtml!=='function')return;busy=true;s.disabled=true; var msg=sc.querySelector('.btcs-scmsg');if(!msg){msg=document.createElement('p');msg.className='btcs-scmsg';h.parentNode.insertBefore(msg,h.nextSibling)}msg.textContent='Loading '+y+' Statcast…'; window.btScData(y).then(function(d){busy=false;if(!d){s.disabled=false;msg.textContent='No Statcast data for '+y+'.';return}var el=window.btScHtml(d,y);el.setAttribute('data-btcs-y',y);sc.parentNode.replaceChild(el,sc);inject()},function(){busy=false;s.disabled=false;msg.textContent='Couldn’t load '+y+' Statcast.'})}; h.appendChild(s)} var root=document.getElementById('bt-player');if(!root)return; var mo=new MutationObserver(inject);mo.observe(root,{childList:true,subtree:true});inject(); } function seasons(id,pos){var grp=pos==='P'?'pitching':'hitting'; return J(MLB+'people/'+id+'/stats'+Q+'stats'+E+'yearByYear'+A+'group'+E+grp+A+'sportId'+E+'1').then(function(j){var s={};((j.stats&&j.stats[0]&&j.stats[0].splits)||[]).forEach(function(x){var y=+x.season;if(y>=2015)s[y]=1});return Object.keys(s).map(Number).sort(function(a,b){return b-a})}).catch(function(){return []})} /* ---------- Statcast by season (v1.7: Baseball Savant season lines) ---------- */ function btscCss(){if(document.getElementById('btsc-css'))return;var s=document.createElement('style');s.id='btsc-css';s.textContent= '#bt-player #btcs th,#bt-player #btcs td,#bt-player #btsc th,#bt-player #btsc td{padding:7px 5px;font-size:13px}'+ '#bt-player #btcs th,#bt-player #btsc th{font-size:11.5px}'+ '#bt-player #btcs tr.sub td{font-size:12px}'+ '#btsc.btsc{background:none!important;border:0!important;padding:0!important;margin:22px 0 6px!important}'+ '#btsc .btcs-bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0 12px}'+ '#btsc .btcs-bar .btpv-tg{padding-left:0}'+ '#btsc .btcs-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:0 -4px;scrollbar-color:rgba(255,255,255,.25) transparent;scrollbar-width:thin}'+ '#btsc table{width:100%;border-collapse:collapse;font-variant-numeric:tabular-nums}'+ '#btsc th,#btsc td{text-align:center;white-space:nowrap;border-bottom:1px solid rgba(255,255,255,.1)}'+ '#btsc th{font-weight:700;text-transform:none;opacity:.85}'+ '#btsc td:first-child,#btsc th:first-child{position:sticky;left:0;background:#0d1f32;font-weight:700;z-index:1}'+ '#btsc tr.tot td{font-weight:700;border-top:1px solid rgba(255,255,255,.35)}'+ '#btsc .btcs-note{font-size:12px;opacity:.7;margin:8px 0 0}'+ '#btsc .btcs-tg button{cursor:pointer}'; document.head.appendChild(s)} function chase(r){return num(r['pfxO-Swing%'])} var SC_HIT=[['Year',function(r){return r.aseason}],['Team',team],['PA',function(r){return f0(r.PA)}],['EV',function(r){return f1(r.EV)}],['Max EV',function(r){return f1(r.maxEV)}],['LA',function(r){return f1(r.LA)}], ['Barrel%',function(r){return pc(r['Barrel%'])}],['Hard-hit%',function(r){return pc(r['HardHit%'])}],['xwOBA',function(r){return f3(r.xwOBA)}], ['GB%',function(r){return pc(r['GB%'])}],['LD%',function(r){return pc(r['LD%'])}],['FB%',function(r){return pc(r['FB%'])}],['Pull%',function(r){return pc(r['Pull%'])}], ['Chase%',function(r){return pc(chase(r))}],['Whiff%',function(r){return pc(whiff(r))}]]; var SC_PIT=[['Year',function(r){return r.aseason}],['Team',team],['TBF',function(r){return f0(r.TBF)}],['EV',function(r){return f1(r.EV)}],['LA',function(r){return f1(r.LA)}], ['Barrel%',function(r){return pc(r['Barrel%'])}],['Hard-hit%',function(r){return pc(r['HardHit%'])}],['xwOBA',function(r){return f3(r.xwOBA)}], ['GB%',function(r){return pc(r['GB%'])}],['LD%',function(r){return pc(r['LD%'])}],['FB%',function(r){return pc(r['FB%'])}], ['Chase%',function(r){return pc(chase(r))}],['Whiff%',function(r){return pc(whiff(r))}]]; function scRows(data,sv){ var out=[];if(!sv)return {rows:out,car:null}; var fg={};(data||[]).forEach(function(r){if(r.type===0&&strip(r.AbbLevel)==='MLB')fg[r.aseason]=r}); Object.keys(sv).map(Number).sort(function(a,b){return a-b}).forEach(function(y){var a=sv[y];if(!a||a[1]==null)return;var f=fg[y]||{}; var n=(data||[]).filter(function(x){return x.aseason===y&&x.type>=1&&x.type<=9&&strip(x.AbbLevel)==='MLB'}).length; function p(v){return v==null?null:v/100} out.push({aseason:y,AbbName:f.AbbName||'—',_n:n||2,PA:a[0],TBF:a[0],EV:a[1],maxEV:a[2],LA:a[3],'Barrel%':p(a[4]),'HardHit%':p(a[5]),xwOBA:a[6],'pfxContact%':a[7]==null?null:1-a[7]/100,'pfxO-Swing%':p(a[8]),'GB%':p(a[9]),'LD%':p(a[10]),'FB%':p(a[11]),'Pull%':p(a[12])})}); return {rows:out,car:null}; } function scTable(cols,set){ cols=cols.filter(function(c){if(c[0]==='Year'||c[0]==='Team')return true;return set.rows.some(function(r){return c[1](r)!==''})}); var h=''+cols.map(function(c){return ''}).join('')+''; set.rows.forEach(function(r){h+=''+cols.map(function(c){return ''}).join('')+''}); if(set.car){var c0=set.rows[0].aseason;h+=''+cols.map(function(c){var v;if(c[0]==='Year')v='Total';else if(c[0]==='Team')v='';else if(c[0]==='Max EV')v=f1(Math.max.apply(null,set.rows.map(function(r){return num(r.maxEV)||0})))||'';else v=c[1](set.car);return ''}).join('')+''} return h+'
'+c[0]+'
'+c[1](r)+'
'+v+'
'; } function scAnchor(){var ls=[].slice.call(document.querySelectorAll('#bt-player .btp-sc'));for(var i=0;i1)h+='
'+gs.map(function(x,i){return ''}).join('')+'
'; h+='
'+scTable(g.kind==='hit'?SC_HIT:SC_PIT,g.s)+'
'; h+='

'+(g.kind==='pit'?'Batted-ball numbers are what hitters did against him. ':'')+'EV and LA are average exit velocity (mph) and launch angle (degrees) on batted balls. Chase% is the share of pitches outside the zone swung at; Whiff% is swings and misses per swing. Regular season, Statcast era (2015 on). Data: Baseball Savant.

'; sec.innerHTML=h; [].forEach.call(sec.querySelectorAll('button[data-g]'),function(b){b.onclick=function(){st.g=+b.getAttribute('data-g');draw()}}); } draw(); if(!scPlace(sec)){var t=0,iv=setInterval(function(){if(scPlace(sec)||++t>40)clearInterval(iv)},250)} var busy=false;var mo=new MutationObserver(function(){if(busy)return;var a=scAnchor();if(a&&sec.previousElementSibling!==a){busy=true;scPlace(sec);setTimeout(function(){busy=false},0)}});mo.observe(document.querySelector('.gh-content')||document.body,{childList:true,subtree:true}); }).catch(function(){})} var GATE=true; function go(){if(!GATE)return true;var el=document.getElementById('bt-player');if(!el)return false;var id=+el.getAttribute('data-mlbam');if(!id)return true; J(MLB+'people/'+id).then(function(j){var p=j.people&&j.people[0];var pos=p&&p.primaryPosition?p.primaryPosition.abbreviation:''; career(id,pos);btscCss();btsc(id,pos);seasons(id,pos).then(function(ys){statcast(id,pos,ys)})}).catch(function(){}); return true} if(!go()){var n=0,iv=setInterval(function(){if(go()||++n>60)clearInterval(iv)},250)} })();