Peyton Prescott 10/1/26 Player Profile
Photo from Brooklyn Cyclones Via X
Another player heading to the Arizona Fall league that that missed considerable time in 2026 is RHP Peyton Prescott. Peyton was selected by the Mets in the 5th round in the 2025 MLB Draft after having Tommy John surgery that previous Spring.
As a Florida State University Sophomore in 2025, Peyton touched triple digits.
He saw his first and only action in 2026 on September 4, 2026 for St. Lucie - 1 inning, no runs, no hits, 1 walk, 2 Ks.
MLB.com listed Peyton as the Mets Sleeper Prospect in the AFL saying: "Prescott finally made his organizational debut with one inning for Single-A St. Lucie, sitting between 94.5 and 96.5 mph with that four-seamer while sprinkling in cutters, sliders, changeups and curveballs over the 18-pitch outing. The slider and changeup were both whiff-heavy options as an amateur, but the biggest thing for Prescott will be just establishing a broader foundation of innings heading into his second full season."
Baseball America: "Prescott has an athletic operation but fringe control and more of a relief profile. Starting is not completely off the table, though, because he pitches in the mid 90s with a full array of solid secondaries, including an average mid-80s splitter, an average mid-80s slider and a low-80s downer curveball."
Keith Law The Athletic: "Florida State right-hander Peyton Prescott (5) is up to 100 with a 55 changeup but gets crushed by right-handed batters, as his slider is fringy and he leaves it up too often. He has an incredibly short arm action with a ton of effort, so he’ll stay in relief, where he pitched for the Seminoles this spring and posted a 5.15 ERA with a 27 percent strikeout rate."
In addition to Keith Law, ESPN, and MLB.com also saw him as a high-leverage relief arm.
Peyton will be fun to watch and track his performance this Fall.
Loading player profile…
#pp-widget { max-width: 820px; margin: 0 auto; font-family: 'Barlow Condensed', -apple-system, 'Segoe UI', Arial, sans-serif; color: #1a1a1a; }
#pp-widget * { box-sizing: border-box; }
#pp-widget .pp-loading { text-align: center; padding: 30px; color: #6c757d; font-size: 18px; }
#pp-widget .pp-card { background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); overflow: hidden; margin-bottom: 20px; }
/* header */
#pp-widget .pp-header { background: linear-gradient(135deg, #002D72 0%, #FF5910 100%); color: #fff; padding: 22px; }
#pp-widget .pp-head-row { display: flex; align-items: center; gap: 18px; margin-bottom: 14px; flex-wrap: wrap; }
#pp-widget .pp-headshot { width: 92px; height: 92px; border-radius: 50%; border: 3px solid #fff; background: #fff; object-fit: cover; flex-shrink: 0; }
#pp-widget .pp-number { font-size: 44px; font-weight: 700; opacity: 0.9; }
#pp-widget .pp-name h1 { font-size: 34px; margin: 0 0 2px 0; line-height: 1.1; color: #fff; }
#pp-widget .pp-pos { font-size: 18px; opacity: 0.92; }
#pp-widget .pp-bio { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; font-size: 15px; }
#pp-widget .pp-bio-item { background: rgba(255,255,255,0.14); padding: 7px 11px; border-radius: 6px; }
#pp-widget .pp-bio-label { opacity: 0.82; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 1px; }
/* jump nav */
#pp-widget .pp-nav { display: flex; gap: 8px; flex-wrap: wrap; padding: 10px 22px; background: #f8f9fa; border-bottom: 1px solid #e9ecef; }
#pp-widget .pp-nav a { color: #002D72; font-weight: 600; text-decoration: none; font-size: 15px; padding: 4px 10px; border-radius: 14px; border: 1px solid #d7dbe0; background: #fff; }
#pp-widget .pp-nav a:hover { background: #002D72; color: #fff; }
/* sections */
#pp-widget .pp-section { padding: 20px 22px; }
#pp-widget .pp-title { font-size: 22px; font-weight: 700; color: #002D72; margin: 0 0 12px 0; padding-bottom: 6px; border-bottom: 2px solid #FF5910; }
#pp-widget .pp-sub { font-size: 17px; font-weight: 700; color: #002D72; margin: 16px 0 6px 0; }
#pp-widget .pp-note { font-size: 13px; color: #6c757d; margin: 6px 0 0 0; }
#pp-widget .pp-empty { font-size: 15px; color: #6c757d; padding: 8px 0; }
/* quick stat boxes */
#pp-widget .pp-quick { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 12px; margin-bottom: 8px; }
#pp-widget .pp-qbox { text-align: center; padding: 12px 6px; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; }
#pp-widget .pp-qval { font-size: 26px; font-weight: 700; color: #002D72; }
#pp-widget .pp-qlbl { font-size: 12px; color: #6c757d; text-transform: uppercase; letter-spacing: 0.5px; }
/* tables */
#pp-widget .pp-twrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
#pp-widget table.pp-table { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
#pp-widget table.pp-table th { background: #002D72; color: #fff; padding: 9px 6px; text-align: center; font-size: 12px; font-weight: 600; text-transform: uppercase; white-space: nowrap; }
#pp-widget table.pp-table td { padding: 8px 6px; text-align: center; border-bottom: 1px solid #e9ecef; font-size: 14px; white-space: nowrap; }
#pp-widget table.pp-table tr:hover td { background: #f8f9fa; }
#pp-widget td.pp-left, #pp-widget th.pp-left { text-align: left; }
#pp-widget .pp-yr { font-weight: 600; color: #002D72; }
#pp-widget .pp-lvl { display: inline-block; background: #002D72; color: #fff; border-radius: 10px; font-size: 11px; font-weight: 700; padding: 1px 8px; margin-left: 6px; vertical-align: 1px; }
#pp-widget tr.pp-total td { font-weight: 700; background: #fff4ee; border-top: 2px solid #FF5910; }
#pp-widget tr.pp-career td { font-weight: 700; background: #eaf0fa; border-top: 2px solid #002D72; }
/* v2.4: repeated header rows inside long tables */
#pp-widget table.pp-table tr.pp-rehead th { border-top: 2px solid #FF5910; }
#pp-widget td.pp-lnk a { color: #002D72; font-weight: 600; text-decoration: none; }
#pp-widget td.pp-lnk a:hover { text-decoration: underline; }
/* splits + fielding + game log season chips */
#pp-widget .pp-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
#pp-widget .pp-chip { border: 1px solid #002D72; color: #002D72; background: #fff; border-radius: 14px; padding: 3px 12px; font-size: 14px; font-weight: 600; cursor: pointer; font-family: inherit; }
#pp-widget .pp-chip.pp-on { background: #002D72; color: #fff; }
/* videos */
#pp-widget .pp-vids { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; }
#pp-widget .pp-vcard { border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden; background: #fff; }
#pp-widget .pp-vcard-x { grid-column: 1 / -1; max-width: 560px; }
#pp-widget .pp-vcard-x blockquote { margin: 0 auto; }
#pp-widget .pp-vthumb { position: relative; cursor: pointer; background: #000; }
#pp-widget .pp-vthumb img { width: 100%; display: block; aspect-ratio: 16/9; object-fit: cover; }
#pp-widget .pp-vplay { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 44px; height: 44px; border-radius: 50%; background: rgba(0,45,114,0.85); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 18px; pointer-events: none; }
#pp-widget .pp-vdur { position: absolute; right: 6px; bottom: 6px; background: rgba(0,0,0,0.75); color: #fff; font-size: 11px; padding: 1px 6px; border-radius: 3px; }
#pp-widget .pp-vbody { padding: 8px 10px 10px 10px; }
#pp-widget .pp-vtitle { font-size: 14px; font-weight: 600; line-height: 1.25; color: #1a1a1a; margin-bottom: 4px; }
#pp-widget .pp-vmeta { font-size: 12px; color: #6c757d; display: flex; justify-content: space-between; }
#pp-widget .pp-vmeta a { color: #002D72; font-weight: 600; text-decoration: none; }
#pp-widget .pp-vcard video { width: 100%; display: block; aspect-ratio: 16/9; background: #000; }
#pp-widget .pp-vlink { display: block; background: #002D72; color: #fff; text-align: center; padding: 34px 10px; font-weight: 700; text-decoration: none; }
/* footer */
#pp-widget .pp-footer { padding: 12px 22px; background: #f8f9fa; text-align: center; font-size: 12px; color: #6c757d; }
#pp-widget .pp-footer a { color: #002D72; font-weight: 600; text-decoration: none; }
@media (max-width: 600px) {
#pp-widget .pp-header { padding: 15px; }
#pp-widget .pp-name h1 { font-size: 26px; }
#pp-widget .pp-number { font-size: 34px; }
#pp-widget .pp-section { padding: 15px 12px; }
#pp-widget table.pp-table th, #pp-widget table.pp-table td { padding: 6px 4px; font-size: 12px; }
}
(function () {
// ================= CONFIG - EDIT THIS =================
var PLAYER_ID = 807290; // { code: totals }.
// Tries the Stats API careerStatSplits call first; if nothing comes
// back, sums every season's statSplits for that level instead.
function loadCareerSplits(group, sportId, seasons, accF) {
return jget(API + '/people/' + PLAYER_ID + '/stats?stats=careerStatSplits&group=' + group +
'&sportId=' + sportId + '&sitCodes=' + SIT_CODE_STR)
.then(function (d) { return splitsOf(d); })
.catch(function () { return []; })
.then(function (sp) {
var direct = sumByCode(sp, accF, true);
if (Object.keys(direct).length) return direct;
return Promise.all(seasons.map(function (yr) { return loadSplits(group, yr, [sportId]); }))
.then(function (arrs) {
var out = {};
arrs.forEach(function (list) {
var yrTot = sumByCode(list, accF, false);
Object.keys(yrTot).forEach(function (c) { out[c] = addTot(out[c] || {}, yrTot[c]); });
});
return out;
});
});
}
var MONTH_NAMES = ['','January','February','March','April','May','June','July','August','September','October','November','December'];
function loadMonthly(group, season, sportIds) {
return Promise.all(sportIds.map(function (sid) {
return jget(API + '/people/' + PLAYER_ID + '/stats?stats=byMonth&group=' + group +
'&season=' + season + '&sportId=' + sid)
.then(function (d) { return splitsOf(d).map(function (s) { s._sportId = sid; return s; }); })
.catch(function () { return []; });
})).then(function (arrs) {
var out = [];
arrs.forEach(function (a) { a.forEach(function (s) { out.push(s); }); });
out.sort(function (a, b) { return (n(a.month) - n(b.month)) || (b._sportId - a._sportId); });
return out;
});
}
// ---------------- FETCH: GAME LOG (v2.4) ----------------
function loadGameLog(group, season, sportIds) {
return Promise.all(sportIds.map(function (sid) {
return jget(API + '/people/' + PLAYER_ID + '/stats?stats=gameLog&group=' + group +
'&season=' + season + '&sportId=' + sid)
.then(function (d) { return splitsOf(d).map(function (s) { s._sportId = sid; return s; }); })
.catch(function () { return []; });
})).then(function (arrs) {
var out = [];
arrs.forEach(function (a) { a.forEach(function (s) { out.push(s); }); });
out.sort(function (a, b) {
return String(b.date || '').localeCompare(String(a.date || '')) || (b._sportId - a._sportId);
});
return out;
});
}
// ---------------- FETCH: PITCH ARSENAL / VELO (v2.4) ----------------
// Only levels with pitch tracking: MLB (1), AAA (11), Low-A (14).
var PITCH_SPORT_IDS = [1, 11, 14];
function loadPitchData(season) {
return Promise.all(PITCH_SPORT_IDS.map(function (sid) {
var ars = jget(API + '/people/' + PLAYER_ID + '/stats?stats=pitchArsenal&group=pitching&season=' + season + '&sportId=' + sid)
.then(splitsOf).catch(function () { return []; });
var log = jget(API + '/people/' + PLAYER_ID + '/stats?stats=pitchLog&group=pitching&season=' + season + '&sportId=' + sid)
.then(splitsOf).catch(function () { return []; });
return Promise.all([ars, log]).then(function (r) { return { sportId: sid, arsenal: r[0], log: r[1] }; });
}));
}
// ---------------- FETCH: TRANSACTIONS ----------------
function loadTransactions() {
var end = new Date().toISOString().slice(0,10);
return jget(API + '/transactions?playerId=' + PLAYER_ID + '&startDate=2010-01-01&endDate=' + end)
.then(function (d) {
var tx = (d.transactions || []).slice();
tx.sort(function (a, b) { return String(b.date).localeCompare(String(a.date)); });
return tx.slice(0, TX_MAX);
})
.catch(function () { return []; });
}
// ---------------- FETCH: FILM ROOM VIDEOS ----------------
var GQL = 'query Search($query: String!, $page: Int, $limit: Int, $feedPreference: FeedPreference, ' +
'$languagePreference: LanguagePreference, $contentPreference: ContentPreference, $queryType: QueryType = STRUCTURED) {\n' +
' search(query: $query, limit: $limit, page: $page, feedPreference: $feedPreference, ' +
'languagePreference: $languagePreference, contentPreference: $contentPreference, queryType: $queryType) {\n' +
' plays {\n mediaPlayback {\n slug\n title\n blurb\n date\n' +
' feeds {\n type\n duration\n' +
' image { cuts { width src } }\n' +
' playbacks { name url }\n }\n }\n }\n }\n}';
function gqlVars(queryStr, limit) {
return {
queryType: null,
query: queryStr,
limit: limit || VIDEO_LIMIT,
page: 0,
languagePreference: 'EN',
contentPreference: 'CMS_FIRST'
};
}
function videoSearchUrl(queryStr, limit) {
return 'https://fastball-gateway.mlb.com/graphql' +
'?query=' + encodeURIComponent(GQL) +
'&operationName=Search' +
'&variables=' + encodeURIComponent(JSON.stringify(gqlVars(queryStr, limit)));
}
function proxyPlayerUrl(person) {
var teamId = (person && person.currentTeam && person.currentTeam.id) || '';
var last = (person && (person.lastName || (person.fullName || '').split(' ').pop())) || '';
return PROXY_URL + '?player=' + PLAYER_ID + '&team=' + teamId +
'&name=' + encodeURIComponent(last) + '&limit=' + VIDEO_LIMIT;
}
var ppJsonpCount = 0;
function jsonpCall(baseUrl) {
return new Promise(function (resolve, reject) {
var cb = 'ppVidCb' + (++ppJsonpCount);
var s = document.createElement('script');
var timer = setTimeout(function () { cleanup(); reject(new Error('proxy timeout')); }, 30000);
function cleanup() {
clearTimeout(timer);
try { delete window[cb]; } catch (e) { window[cb] = undefined; }
if (s.parentNode) s.parentNode.removeChild(s);
}
window[cb] = function (data) { cleanup(); resolve(data); };
s.onerror = function () { cleanup(); reject(new Error('proxy load error')); };
s.src = baseUrl + '&callback=' + cb;
document.head.appendChild(s);
});
}
function proxyJson(baseUrl) {
return fetch(baseUrl).then(function (r) {
if (!r.ok) throw new Error('proxy HTTP ' + r.status);
return r.json();
});
}
function gqlPost(queryStr, limit) {
return fetch('https://fastball-gateway.mlb.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: GQL, operationName: 'Search', variables: gqlVars(queryStr, limit) })
}).then(function (r) {
if (!r.ok) throw new Error('HTTP ' + r.status + ' (POST)');
return r.json();
});
}
function gqlFetch(queryStr, limit) {
return jget(videoSearchUrl(queryStr, limit))
.catch(function () { return gqlPost(queryStr, limit); });
}
function fmtDur(sec) {
if (sec == null || sec === '') return '';
if (typeof sec === 'string' && sec.indexOf(':') > -1) return sec.replace(/^00:0?/, '');
var x = parseInt(sec, 10);
if (isNaN(x)) return '';
return Math.floor(x / 60) + ':' + ('0' + (x % 60)).slice(-2);
}
function pickPlayback(feeds) {
if (!feeds || !feeds.length) return { mp4: null, thumb: '', dur: '' };
var ranked = feeds.slice().sort(function (a, b) {
return ((a.type === 'CMS') ? 0 : 1) - ((b.type === 'CMS') ? 0 : 1);
});
var mp4 = null, thumb = '', dur = '';
for (var i = 0; i < ranked.length; i++) {
var f = ranked[i];
if (!dur && f.duration) dur = fmtDur(f.duration);
if (!thumb && f.image && f.image.cuts && f.image.cuts.length) {
var cuts = f.image.cuts.slice().sort(function (a, b) { return (b.width||0) - (a.width||0); });
var mid = cuts.filter(function (c) { return c.width && c.width 1) {
html += '
' + esc(yr) + ' ' + esc(opts.totalLabel) + '
' +
cellsHTML(totF(yrTot)) + '
';
}
// v2.4: repeat the column header row every HEADER_REPEAT seasons
if (n(HEADER_REPEAT) > 0 && (si + 1) % n(HEADER_REPEAT) === 0 && si < seasons.length - 1) {
html += headRowHTML('Year / Team', cols, 'pp-rehead');
}
});
if (seasons.length) {
html += '
' + esc(opts.careerLabel) + '
' +
cellsHTML(totF(career)) + '
';
}
return html ? tableHTML(opts.id, 'Year / Team', cols, html) : '';
}
// ---------------- MAIN ----------------
loadPerson().then(function (person) {
if (!person) { root.innerHTML = 'Player not found for ID ' + PLAYER_ID + '.'; return; }
return Promise.all([ loadYearByYear('hitting'), loadYearByYear('pitching'), loadYearByYear('fielding'),
loadTransactions(), loadVideos(person), loadJersey(person) ])
.then(function (res) {
render(person, res[5], res[0], res[1], res[2], res[3], res[4]);
});
})
.catch(function (e) {
root.innerHTML = 'Could not load profile (' + esc(e.message) + '). Please refresh.';
});
function render(person, jersey, hitRows, pitRows, fldRows, txs, vids) {
var isPitcher = person.primaryPosition && person.primaryPosition.code === '1';
var hasHit = hitRows.some(function (r) { return n(r.stat.atBats) || n(r.stat.plateAppearances); });
var hasPit = pitRows.some(function (r) { return ipToOuts(r.stat.inningsPitched) || n(r.stat.gamesPlayed); });
var usePit = isPitcher && hasPit;
var showPitches = usePit && SHOW_PITCHES;
var showGameLog = n(RECENT_GAMES) > 0 && (hasPit || hasHit);
var headshot = 'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png' +
'/w_180,q_auto:best/v1/people/' + PLAYER_ID + '/headshot/milb/current';
var birth = [person.birthCity, person.birthStateProvince, person.birthCountry].filter(Boolean).join(', ');
var teamName = (person.currentTeam && person.currentTeam.name) || 'New York Mets Organization';
var h = '';
// ---- header ----
h += '' +
'' +
(jersey ? '#' + esc(jersey) + '' : '') +
'
' + esc(person.fullName) + '
' +
'' + esc((person.primaryPosition && person.primaryPosition.name) || '') +
' • ' + esc(teamName) + '' +
'' +
'Full Name' + esc(person.fullFMLName || person.fullName) + '' +
'Bats/Throws' + esc(((person.batSide||{}).code||'') + '/' + ((person.pitchHand||{}).code||'')) + '' +
'Height/Weight' + esc((person.height||'') + ' / ' + (person.weight ? person.weight + ' lbs' : '')) + '' +
'Born' + esc(fmtDate(person.birthDate) + (person.currentAge ? ' (Age ' + person.currentAge + ')' : '')) + '' +
(birth ? 'Birthplace' + esc(birth) + '' : '') +
(HOW_ACQUIRED ? 'How Acquired' + esc(HOW_ACQUIRED) + '' : '') +
EXTRA_BOXES.map(function (b) {
if (!b || !b.value) return '';
return '' + esc(b.label || '') + '' + esc(b.value) + '';
}).join('') +
'';
// ---- jump nav ----
h += 'Stats' +
(showPitches ? 'Pitches' : '') +
'Splits' +
'Fielding' +
(showGameLog ? 'Game Log' : '') +
'VideosTransactions';
// ---- stats ----
h += '
Statistics
';
var groups = [];
if (isPitcher) { if (hasPit) groups.push('P'); if (hasHit) groups.push('H'); }
else { if (hasHit) groups.push('H'); if (hasPit) groups.push('P'); }
if (!groups.length) h += 'No stats available yet.';
groups.forEach(function (g) {
var rows = (g === 'H') ? hitRows : pitRows;
var isP = (g === 'P');
var gLbl = isP ? 'Pitching' : 'Hitting';
var mlb = rows.filter(function (r) { return r.sportId === 1; });
var milb = rows.filter(function (r) { return isMilbSport(r.sportId); });
var winter = rows.filter(function (r) { return r.sportId === 17; });
var mlbT = buildStatsTable(mlb, isP, { id: 'pp-mlb-' + g, totalLabel: 'MLB Total', careerLabel: 'MLB Career' });
var milbT = buildStatsTable(milb, isP, { id: 'pp-milb-' + g, totalLabel: 'MiLB Total', careerLabel: 'MiLB Career', rates: !isP });
var winterT = buildStatsTable(winter, isP, { id: 'pp-winter-' + g, totalLabel: 'Total', careerLabel: 'Winter/AFL Career' });
if (mlbT) h += 'MLB ' + gLbl + '' + mlbT;
if (milbT) h += 'Minor League ' + gLbl + '' + milbT;
if (winterT) h += 'Winter Ball / AFL ' + gLbl + '' + winterT;
});
h += '';
// ---- pitch arsenal (v2.4, pitchers only) ----
if (showPitches) {
h += '
Pitch Arsenal
' +
'Loading pitch data…';
}
// ---- splits ----
h += '
Splits
' +
'Loading splits…';
// ---- fielding (v2.3) ----
h += '
Fielding
' +
'Loading fielding…';
// ---- game log (v2.4) ----
if (showGameLog) {
h += '
Game Log
' +
'Loading game log…';
}
// ---- videos ----
h += '
Video Highlights
';
var autoVids = vids.list || [];
var hasAny = MANUAL_VIDEOS.length || autoVids.length;
var hasXPost = false;
if (hasAny) {
h += '';
MANUAL_VIDEOS.forEach(function (v) {
var yt = ytEmbedUrl(v.url);
var xp = xPostUrl(v.url);
if (xp) {
hasXPost = true;
h += '' +
'' +
'' + esc(v.title || 'View post on X') + '' +
'' + esc(v.title || 'Video') + '' +
'' + esc(fmtDate(v.date)) + '' +
'View on X';
return;
}
h += '';
if (yt) {
h += '';
} else if (/\.mp4(\?|$)/i.test(v.url || '')) {
h += '';
} else if (isMlbVideoUrl(v.url)) {
var mlbEmb = mlbEmbedUrl(v.url);
if (mlbEmb) {
h += '';
} else {
h += '► Watch on MLB.com';
}
} else {
h += '► Watch Video';
}
h += '' + esc(v.title || 'Video') + '' +
'' + esc(fmtDate(v.date)) + '' +
(isMlbVideoUrl(v.url) ? 'View on MLB.com' : '') +
'';
});
autoVids.forEach(function (v, i) {
h += '' +
'' +
(v.thumb ? '' : '') +
'►' +
(v.dur ? '' + esc(v.dur) + '' : '') +
'' + esc(v.title) + '' +
'' + esc(fmtDate(v.date)) + '' +
'Watch on MiLB.com' +
'';
});
h += '';
if (autoVids.length) {
h += 'Auto-updated from MLB Film Room. Full library: ' +
'milb.com/video/topic/playerid-' + PLAYER_ID + '';
}
}
if (!autoVids.length && !MANUAL_VIDEOS.length) {
h += '' +
(vids.error
? 'The automatic video feed could not load (' + esc(vids.error) + '). ' +
'If this keeps happening, set PROXY_URL in the config to your Apps Script passthrough. '
: 'No clips found in the Film Room index for this player yet. ') +
'Full library: MiLB.com.';
} else if (!autoVids.length && vids.error) {
h += 'The automatic video feed could not load (' + esc(vids.error) + ').';
}
h += '';
// ---- transactions ----
h += '
Transactions
';
if (txs.length) {
var trows = '';
txs.forEach(function (t) {
trows += '
' + esc(fmtDate(t.date)) + '
' +
' ' + esc(t.typeDesc || '') + '
' +
' ' + esc(t.description || '') + '
';
});
h += '
Date
Type
Transaction
' + trows + '
';
} else {
h += 'No transactions on record.';
}
h += '';
// ---- footer ----
h += 'Stats & transactions from the MLB Stats API • Videos from MLB Film Room • ' +
'MiLB.com Player Page • ' +
'MLB.com Player Page' +
'';
root.innerHTML = h;
if (hasXPost) loadXWidgets();
// ---- header stat boxes (v2.4: separate MLB / MiLB rows) ----
buildQuickStats(usePit ? pitRows : hitRows, usePit);
// ---- video inline playback ----
root.querySelectorAll('.pp-vthumb').forEach(function (el) {
el.addEventListener('click', function () {
var mp4 = el.getAttribute('data-mp4');
if (!mp4) {
var link = el.getAttribute('data-link');
if (link) window.open(link, '_blank');
return;
}
var v = document.createElement('video');
v.controls = true; v.autoplay = true; v.src = mp4; v.setAttribute('playsinline','');
el.parentNode.replaceChild(v, el);
});
});
var group = usePit ? 'pitching' : 'hitting';
var groupRows = usePit ? pitRows : hitRows;
if (showPitches) initPitches(pitRows);
initSplits(group, groupRows, usePit);
initFielding(fldRows);
if (showGameLog) initGameLog(group, groupRows, usePit);
}
// ---------------- HEADER STAT BOXES (v2.4) ----------------
// One row of boxes per HEADER_STATS entry ('MLB' / 'MiLB') for the
// player's most recent season. A row is skipped if the player has
// no stats at that level in that season.
function buildQuickStats(rows, isPitching) {
var box = document.getElementById('pp-quick');
if (!box || !rows.length || !HEADER_STATS || !HEADER_STATS.length) return;
var reg = rows.filter(function (r) { return r.sportId !== 17; });
if (!reg.length) return;
var latest = reg.reduce(function (m, r) { return r.season > m ? r.season : m; }, '0');
var yr = reg.filter(function (r) { return r.season === latest; });
var accF = isPitching ? pitAccumulate : hitAccumulate;
var html = '';
HEADER_STATS.forEach(function (lvlKey) {
var isMlb = /^mlb$/i.test(String(lvlKey));
var list = yr.filter(function (r) { return isMlb ? r.sportId === 1 : isMilbSport(r.sportId); });
if (!list.length) return;
var tot = {};
list.forEach(function (r) { accF(tot, r.stat); });
var items;
if (isPitching) {
items = [ [n(tot.wins) + '-' + n(tot.losses), 'W-L'], [era(n(tot.earnedRuns), n(tot.outs)), 'ERA'],
[outsToIp(n(tot.outs)), 'IP'], [n(tot.strikeOuts), 'SO'],
[whip(n(tot.baseOnBalls), n(tot.hits), n(tot.outs)), 'WHIP'] ];
} else {
var c = hitTotalCells(tot, false); // [G,AB,R,H,2B,3B,HR,RBI,SB,BB,SO,AVG,OBP,SLG,OPS]
items = [ [c[11], 'AVG'], [c[6], 'HR'], [c[7], 'RBI'], [c[8], 'SB'], [c[14], 'OPS'] ];
}
html += '' + (isMlb ? 'MLB ' : 'MiLB ') + esc(latest) + ' Stats' +
items.map(function (it) {
return '' + esc(it[0]) + '' + esc(it[1]) + '';
}).join('') + '';
});
box.innerHTML = html;
}
// ---------------- PITCH ARSENAL UI (v2.4) ----------------
var PITCH_COLS = ['Usage','Avg MPH','Top MPH','Pitches'];
function initPitches(pitRows) {
var body = document.getElementById('pp-pitch-body');
var title = document.getElementById('pp-pitch-title');
if (!body) return;
var reg = pitRows.filter(function (r) { return r.sportId !== 17; });
if (!reg.length) { body.innerHTML = 'No pitch data available.'; return; }
var latest = reg.reduce(function (m, r) { return r.season > m ? r.season : m; }, '0');
if (title) title.innerHTML = 'Pitch Arsenal — ' + esc(latest);
loadPitchData(latest).then(function (res) {
var out = '';
res.forEach(function (r) {
var map = {};
function slot(code, desc) {
return map[code] = map[code] || { code: code, desc: desc || code, usage: null, avg: null, max: null, count: 0, sum: 0, cnt: 0 };
}
(r.arsenal || []).forEach(function (s) {
var st = s.stat || {}; var t = st.type || {};
var code = t.code || t.description; if (!code) return;
var m = slot(code, t.description);
var p = Number(st.percentage);
if (!isNaN(p) && st.percentage != null) m.usage = p m.max) m.max = v;
m.sum += v; m.cnt++;
});
var list = Object.keys(map).map(function (k) { return map[k]; });
if (!list.length) return;
list.forEach(function (m) {
if (m.avg == null && m.cnt) m.avg = m.sum / m.cnt;
if (!m.count && m.cnt) m.count = m.cnt;
});
var totalCnt = list.reduce(function (a, m) { return a + n(m.count); }, 0);
list.forEach(function (m) { if (m.usage == null && totalCnt) m.usage = 100 * n(m.count) / totalCnt; });
list.sort(function (a, b) { return (n(b.usage)) - (n(a.usage)); });
var rowsHtml = list.map(function (m) {
return '
' + esc(m.desc) + '
' +
cellsHTML([ m.usage != null ? m.usage.toFixed(1) + '%' : '--',
m.avg != null ? m.avg.toFixed(1) : '--',
m.max != null ? m.max.toFixed(1) : '--',
m.count || '--' ]) + '
';
}).join('');
out += '' + esc(levelLabel(r.sportId, '')) + '' +
'
' + theadHTML('Pitch', PITCH_COLS) +
'' + rowsHtml + '
';
});
body.innerHTML = out
? out + '' + esc(latest) + ' pitch tracking by level (MLB, AAA and Low-A only). Usage and average velocity from the MLB Stats API pitch arsenal; top velocity is the hardest tracked pitch of that type in the pitch log.'
: 'No pitch-tracking data returned for ' + esc(latest) + ' (available for MLB, AAA and Low-A only).';
}).catch(function (e) {
body.innerHTML = 'Pitch data could not load (' + esc(e && e.message) + ').';
});
}
// ---------------- SPLITS UI (v2.4: career + season chips) ----------------
// v2.4.1: pitching splits use PIT_SPLIT_COLS / pitSplitTotalCells.
function initSplits(group, rows, isPitching) {
var chips = document.getElementById('pp-split-chips');
var body = document.getElementById('pp-split-body');
if (!chips || !body) return;
var seasonsMap = {}, sidsAll = {};
rows.forEach(function (r) {
if (r.sportId === 17) return; // winter splits not indexed the same way
(seasonsMap[r.season] = seasonsMap[r.season] || {})[r.sportId] = true;
sidsAll[r.sportId] = true;
});
var seasons = Object.keys(seasonsMap).sort().reverse();
if (!seasons.length) { body.innerHTML = 'No splits available.'; return; }
chips.innerHTML =
'Career Totals' +
seasons.map(function (s) {
return '' + esc(s) + '';
}).join('');
var defs = isPitching ? SIT_CODES_PIT : SIT_CODES;
var sCols = isPitching ? PIT_SPLIT_COLS : HIT_COLS;
var accF = isPitching ? pitAccumulate : hitAccumulate;
var totF = function (t) { return isPitching ? pitSplitTotalCells(t) : hitTotalCells(t, false); };
var careerCache = null;
// ---- career view: MLB table + Minor Leagues table ----
function showCareer() {
body.innerHTML = 'Loading career splits…';
if (!careerCache) {
var sids = Object.keys(sidsAll).map(Number);
careerCache = Promise.all(sids.map(function (sid) {
var yrs = seasons.filter(function (s) { return seasonsMap[s][sid]; });
return loadCareerSplits(group, sid, yrs, accF).then(function (byCode) { return { sportId: sid, byCode: byCode }; });
}));
}
careerCache.then(function (res) {
function tableFor(filterFn) {
var html = '';
defs.forEach(function (def) {
var tot = {}, any = false;
res.filter(filterFn).forEach(function (r) {
if (r.byCode[def.code]) { addTot(tot, r.byCode[def.code]); any = true; }
});
if (any) html += '
' + esc(def.label) + '
' + cellsHTML(totF(tot)) + '
';
});
return html ? '
' + theadHTML('Split', sCols) +
'' + html + '
' : '';
}
var mlbT = tableFor(function (r) { return r.sportId === 1; });
var milbT = tableFor(function (r) { return isMilbSport(r.sportId); });
var out = '';
if (mlbT) out += 'MLB — Career Splits' + mlbT;
if (milbT) out += 'Minor Leagues — Career Splits' + milbT;
body.innerHTML = out
? out + 'Career splits (regular season). Minor league line combines every MiLB level. Rate stats are recomputed from the summed raw counts. Pick a season above for the year-by-year, per-level breakdown.'
: 'No career split data available.';
}).catch(function (e) {
body.innerHTML = 'Career splits could not load (' + esc(e && e.message) + ').';
});
}
// ---- season view: per-level splits + By Month (unchanged from v2.3.1) ----
function showSeason(season) {
body.innerHTML = 'Loading ' + esc(season) + ' splits…';
var sportIds = Object.keys(seasonsMap[season]).map(Number);
Promise.all([ loadSplits(group, season, sportIds), loadMonthly(group, season, sportIds) ])
.then(function (res) {
var splits = res[0], months = res[1];
if (!splits.length && !months.length) { body.innerHTML = 'No split data for ' + esc(season) + '.'; return; }
var out = '';
var shtml = '';
defs.forEach(function (def) {
var byLevel = {};
splits.filter(function (s) { return s.split && s.split.code === def.code; })
.forEach(function (s) {
var lvl = levelLabel(s._sportId, s.league && s.league.name);
var key = lvl || ('sid' + s._sportId);
var g = byLevel[key] = byLevel[key] ||
{ lvl: lvl, sportId: s._sportId, team: [], noteam: [] };
(s.team ? g.team : g.noteam).push(s);
});
Object.keys(byLevel).map(function (k) { return byLevel[k]; })
.sort(function (a, b) { return b.sportId - a.sportId; })
.forEach(function (g) {
var use = g.team.length ? g.team : g.noteam;
var tot = {};
use.forEach(function (s) { accF(tot, s.stat || {}); });
shtml += '
' + esc(def.label) +
(g.lvl ? '' + esc(g.lvl) + '' : '') + '' +
cellsHTML(totF(tot)) + '
';
});
});
if (shtml) {
out += '
' + theadHTML('Split', sCols) +
'' + shtml + '
';
}
if (months.length) {
var mCols = isPitching ? PIT_SPLIT_COLS : HIT_COLS_RATES;
var mTotF = function (t) { return isPitching ? pitSplitTotalCells(t) : hitTotalCells(t, true); };
var byMonth = {};
months.forEach(function (s) {
var lvl = levelLabel(s._sportId, s.league && s.league.name);
var key = n(s.month) + '|' + (lvl || 'sid' + s._sportId);
var g = byMonth[key] = byMonth[key] ||
{ month: n(s.month), lvl: lvl, sportId: s._sportId, team: [], noteam: [] };
(s.team ? g.team : g.noteam).push(s);
});
var mhtml = '';
Object.keys(byMonth).map(function (k) { return byMonth[k]; })
.sort(function (a, b) { return (a.month - b.month) || (b.sportId - a.sportId); })
.forEach(function (g) {
var use = g.team.length ? g.team : g.noteam;
var tot = {};
use.forEach(function (s) { accF(tot, s.stat || {}); });
var label = MONTH_NAMES[g.month] || ('Month ' + g.month);
mhtml += '
' + esc(label) +
(g.lvl ? '' + esc(g.lvl) + '' : '') + '' +
cellsHTML(mTotF(tot)) + '
';
});
out += 'By Month' +
'
' + theadHTML('Month', mCols) +
'' + mhtml + '
';
}
body.innerHTML = out
? out + 'Splits and monthly totals shown per level for the selected season (regular season). Splits combine all teams at the same level into one line.'
: 'No split data for ' + esc(season) + '.';
});
}
chips.addEventListener('click', function (e) {
var b = e.target.closest('.pp-chip');
if (!b) return;
chips.querySelectorAll('.pp-chip').forEach(function (c) { c.classList.remove('pp-on'); });
b.classList.add('pp-on');
var s = b.getAttribute('data-season');
if (s === 'ALL') showCareer(); else showSeason(s);
});
showCareer();
}
// ---------------- FIELDING UI (v2.3) ----------------
function initFielding(rows) {
var chips = document.getElementById('pp-fld-chips');
var body = document.getElementById('pp-fld-body');
if (!chips || !body) return;
rows = rows.filter(function (r) {
return r.sportId !== 17 && r.position &&
(n(r.stat.gamesPlayed) || n(r.stat.chances) || ipToOuts(r.stat.innings));
});
if (!rows.length) {
chips.innerHTML = '';
body.innerHTML = 'No fielding data available yet.';
return;
}
var seasonsMap = {};
rows.forEach(function (r) { seasonsMap[r.season] = true; });
var seasons = Object.keys(seasonsMap).sort().reverse();
chips.innerHTML =
'Career Totals' +
seasons.map(function (s) {
return '' + esc(s) + '';
}).join('');
function buildRows(list, withLevel) {
var groups = {};
list.forEach(function (r) {
var key = r.position + (withLevel ? '|' + r.level : '');
if (!groups[key]) groups[key] = { pos: r.position, lvl: withLevel ? r.level : '', sportId: r.sportId, tot: {} };
fldAccumulate(groups[key].tot, r.stat);
});
return Object.keys(groups).map(function (k) { return groups[k]; })
.sort(function (a, b) {
var pa = POS_ORDER[a.pos] != null ? POS_ORDER[a.pos] : 99;
var pb = POS_ORDER[b.pos] != null ? POS_ORDER[b.pos] : 99;
return (pa - pb) || (a.sportId - b.sportId);
})
.map(function (g) {
return '
' + esc(g.pos) +
(g.lvl ? '' + esc(g.lvl) + '' : '') + '' +
cellsHTML(fldTotalCells(g.tot)) + '
';
}).join('');
}
function fldTable(rowsHTML) {
return '
' + theadHTML('Pos', FLD_COLS) +
'' + rowsHTML + '
';
}
function show(season) {
var out = '';
if (season === 'ALL') {
var mlb = rows.filter(function (r) { return r.sportId === 1; });
var milb = rows.filter(function (r) { return isMilbSport(r.sportId); });
if (mlb.length) out += 'MLB — Career Totals by Position' + fldTable(buildRows(mlb, false));
if (milb.length) out += 'Minor Leagues — Career Totals by Position' + fldTable(buildRows(milb, false));
body.innerHTML = out
? out + 'Career fielding totals by position (regular season). FLD% is recomputed from combined PO/A/TC. Pick a season above for the year-by-year breakdown.'
: 'No fielding data available yet.';
} else {
var yr = rows.filter(function (r) { return r.season === season; });
out = yr.length ? fldTable(buildRows(yr, true)) : '';
body.innerHTML = out
? out + '' + esc(season) + ' fielding by position; multiple teams at the same level are combined into one line.'
: 'No fielding data for ' + esc(season) + '.';
}
}
chips.addEventListener('click', function (e) {
var b = e.target.closest('.pp-chip');
if (!b) return;
chips.querySelectorAll('.pp-chip').forEach(function (c) { c.classList.remove('pp-on'); });
b.classList.add('pp-on');
show(b.getAttribute('data-season'));
});
show('ALL');
}
// ---------------- GAME LOG UI (v2.4) ----------------
// Default chip: "Last N Games" across all levels of the most recent
// season (most recent first, each date links to Gameday) plus a
// totals row. Season chips show the full game log for that year.
var GL_PIT_COLS = ['Dec','IP','H','R','ER','HR','BB','SO','P'];
var GL_HIT_COLS = ['AB','R','H','2B','3B','HR','RBI','BB','SO','SB'];
function glDecision(st) {
if (n(st.wins)) return 'W'; if (n(st.losses)) return 'L'; if (n(st.saves)) return 'SV';
if (n(st.holds)) return 'HLD'; if (n(st.blownSaves)) return 'BS'; return '';
}
function glTheadHTML(cols) {
return '
Date
Opp
Lvl
' +
cols.map(function (c) { return ' ' + c + '
'; }).join('') + '
';
}
function glRowHTML(s, isPitching) {
var st = s.stat || {};
var lvl = levelLabel(s._sportId, s.league && s.league.name);
var opp = (s.opponent && (s.opponent.abbreviation || s.opponent.name)) || '';
var oppTxt = (s.isHome === false ? '@ ' : 'vs ') + opp;
var pk = s.game && s.game.gamePk;
var gd = pk ? (s._sportId === 1 ? 'https://www.mlb.com/gameday/' : 'https://www.milb.com/gameday/') + pk : '';
var dateCell = gd
? '' + esc(fmtDate(s.date)) + ''
: esc(fmtDate(s.date));
var cells = isPitching
? [ glDecision(st), st.inningsPitched || '0.0', n(st.hits), n(st.runs), n(st.earnedRuns), n(st.homeRuns),
n(st.baseOnBalls), n(st.strikeOuts), st.numberOfPitches != null ? n(st.numberOfPitches) : '--' ]
: [ n(st.atBats), n(st.runs), n(st.hits), n(st.doubles), n(st.triples), n(st.homeRuns), n(st.rbi),
n(st.baseOnBalls), n(st.strikeOuts), n(st.stolenBases) ];
return '
' + dateCell + '
' +
' ' + esc(oppTxt) + '
' +
' ' + (lvl ? '' + esc(lvl) + '' : '') + '
' +
cellsHTML(cells) + '
';
}
function glTotalRowHTML(list, isPitching, label) {
var tot = {}, pitches = 0, hasPitches = false;
list.forEach(function (s) {
var st = s.stat || {};
(isPitching ? pitAccumulate : hitAccumulate)(tot, st);
if (st.numberOfPitches != null) { pitches += n(st.numberOfPitches); hasPitches = true; }
});
var cells, txt;
if (isPitching) {
txt = label + ' (' + era(n(tot.earnedRuns), n(tot.outs)) + ' ERA)';
cells = [ '', outsToIp(n(tot.outs)), n(tot.hits), n(tot.runs), n(tot.earnedRuns), n(tot.homeRuns),
n(tot.baseOnBalls), n(tot.strikeOuts), hasPitches ? pitches : '--' ];
} else {
txt = label + ' (' + fmtAvg(tot.atBats ? tot.hits / tot.atBats : NaN) + ' AVG)';
cells = [ n(tot.atBats), n(tot.runs), n(tot.hits), n(tot.doubles), n(tot.triples), n(tot.homeRuns), n(tot.rbi),
n(tot.baseOnBalls), n(tot.strikeOuts), n(tot.stolenBases) ];
}
return '
' + esc(txt) + '
' + cellsHTML(cells) + '
';
}
function initGameLog(group, rows, isPitching) {
var chips = document.getElementById('pp-gl-chips');
var body = document.getElementById('pp-gl-body');
if (!chips || !body) return;
var seasonsMap = {};
rows.forEach(function (r) {
if (r.sportId === 17) return;
(seasonsMap[r.season] = seasonsMap[r.season] || {})[r.sportId] = true;
});
var seasons = Object.keys(seasonsMap).sort().reverse();
if (!seasons.length) { chips.innerHTML = ''; body.innerHTML = 'No game log available.'; return; }
var N = n(RECENT_GAMES);
chips.innerHTML =
'Last ' + N + ' Games' +
seasons.map(function (s) {
return '' + esc(s) + '';
}).join('');
var cols = isPitching ? GL_PIT_COLS : GL_HIT_COLS;
var cache = {};
function fetchSeason(season) {
if (!cache[season]) cache[season] = loadGameLog(group, season, Object.keys(seasonsMap[season]).map(Number));
return cache[season];
}
function tableHTMLFor(list, totalLabel) {
return '
' + glTheadHTML(cols) + '' +
list.map(function (s) { return glRowHTML(s, isPitching); }).join('') +
(list.length > 1 ? glTotalRowHTML(list, isPitching, totalLabel) : '') +
'
';
}
function show(key) {
var recent = key === 'RECENT';
var season = recent ? seasons[0] : key;
body.innerHTML = 'Loading ' + (recent ? 'recent games' : esc(season) + ' game log') + '…';
fetchSeason(season).then(function (all) {
var list = recent ? all.slice(0, N) : all;
if (!list.length) { body.innerHTML = 'No games on record for ' + esc(season) + '.'; return; }
body.innerHTML = tableHTMLFor(list, recent ? 'Last ' + list.length : esc(season) + ' Total') +
'' + (recent
? 'Most recent ' + list.length + ' games across all levels (' + esc(season) + '), newest first. '
: esc(season) + ' game log, all levels, newest first. ') +
'Dates link to the Gameday box score. Pick a season above for the full log.';
}).catch(function (e) {
body.innerHTML = 'Game log could not load (' + esc(e && e.message) + ').';
});
}
chips.addEventListener('click', function (e) {
var b = e.target.closest('.pp-chip');
if (!b) return;
chips.querySelectorAll('.pp-chip').forEach(function (c) { c.classList.remove('pp-on'); });
b.classList.add('pp-on');
show(b.getAttribute('data-season'));
});
show('RECENT');
}
})();