7d6dbc6cf3
MISSION 1 — real sportsbook wordmarks (kills lowercase "betmgm"):
- books.js: add the 6 missing ALLOWED_BOOKS keys (fanatics/bet365/
hardrockbet/betrivers/pointsbet/pinnacle) with real brand names +
colors — no live book falls to neutral gray. Add `slug` fields +
bookSlug()/hasBookSvg() + BUNDLED_BOOK_SVGS.
- Bundle 8 self-authored styled-text wordmark SVGs under
web/public/books/{slug}.svg (draftkings/fanduel/betmgm/caesars/
bet365/pinnacle/hardrockbet/betrivers). NOT copied trademarked logo
glyphs — the book's NAME in brand weight+color; official press-kit
art can drop into the same paths with zero code change.
- BookWordmark: render the local SVG when bundled, else the brand-color
styled-text fallback (never a broken image; never a lowercase key).
- Import BookWordmark into the ledger row (page.tsx:368) + the identical
public-profile row, replacing bare {row.book} text. vyndr/GameCard
line-grid book cell now proper-cases via bookInfo().name (keeps the
preferred-book green highlight).
MISSION 2 — team-logo coverage gaps:
- teamMeta.js: add ESPN-schedule ball-sport abbr aliases the feed emits
that fell to monograms — SA→SAS, NY→NYK, WSH→WAS, BRK→BKN (NBA),
CONN→CON (WNBA). Real-abbr-first lookup means MLB WSH (Nationals) +
WNBA NY (Liberty) still resolve directly; NY in MLB stays null.
Tests: new tests/unit/bookWordmark.test.js (all 10 ALLOWED_BOOKS resolve
to a real brand+non-gray color; 8 bundled SVGs exist; BookWordmark
SVG-first + no-lowercase-leak; ledger/profile import + use BookWordmark).
entityLayer.test.js extended for the new aliases. Full suite green
(239 suites / 2891 tests); next build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
254 lines
16 KiB
JavaScript
254 lines
16 KiB
JavaScript
/* ============================================================
|
||
DS0 (Design v2) — THE ENTITY LAYER: team logos + real colors.
|
||
|
||
Teams render as THEMSELVES (DESIGN-SPEC Part 2 / Part 0 law #4): real
|
||
logo + team-colored accent, never a bare "MIL @ PIT" string.
|
||
|
||
Colors + abbrs are stable public facts (like teams.js's names), so a
|
||
static registry is honest and zero-latency — no runtime fetch, no paid
|
||
dependency. Colors sourced once from ESPN's team API; logos from ESPN's
|
||
free CDN (verified 200 image/png). CommonJS so Jest requires it and the
|
||
.tsx components import it (allowJs).
|
||
|
||
Each entry: { name, espn (logo-CDN abbr), primary, secondary }. Keyed by
|
||
the app/statsapi abbr; resolveTeam() also matches full names + nicknames.
|
||
The ESPN "primary" color is often near-black (dark-theme invisible), so
|
||
accentColor() picks the more VISIBLE of the two against #06060B.
|
||
============================================================ */
|
||
|
||
// abbr → meta. MLB abbrs match statsapi; where ESPN's logo abbr differs
|
||
// (AZ→ari, CWS→chw) the `espn` field carries the CDN spelling.
|
||
const MLB = {
|
||
AZ: { name: 'Arizona Diamondbacks', espn: 'ari', primary: '#aa182c', secondary: '#e3d4ad' },
|
||
ATH: { name: 'Athletics', espn: 'oak', primary: '#003831', secondary: '#efb21e' },
|
||
ATL: { name: 'Atlanta Braves', espn: 'atl', primary: '#ba0c2f', secondary: '#13274f' },
|
||
BAL: { name: 'Baltimore Orioles', espn: 'bal', primary: '#df4601', secondary: '#000000' },
|
||
BOS: { name: 'Boston Red Sox', espn: 'bos', primary: '#bd3039', secondary: '#0d2b56' },
|
||
CHC: { name: 'Chicago Cubs', espn: 'chc', primary: '#0e3386', secondary: '#cc3433' },
|
||
CWS: { name: 'Chicago White Sox', espn: 'chw', primary: '#c4ced4', secondary: '#000000' },
|
||
CIN: { name: 'Cincinnati Reds', espn: 'cin', primary: '#c6011f', secondary: '#000000' },
|
||
CLE: { name: 'Cleveland Guardians', espn: 'cle', primary: '#e31937', secondary: '#002b5c' },
|
||
COL: { name: 'Colorado Rockies', espn: 'col', primary: '#8e6cae', secondary: '#c4ced4' },
|
||
DET: { name: 'Detroit Tigers', espn: 'det', primary: '#0a2240', secondary: '#ff4713' },
|
||
HOU: { name: 'Houston Astros', espn: 'hou', primary: '#eb6e1f', secondary: '#002d62' },
|
||
KC: { name: 'Kansas City Royals', espn: 'kc', primary: '#7ab2dd', secondary: '#004687' },
|
||
LAA: { name: 'Los Angeles Angels', espn: 'laa', primary: '#ba0021', secondary: '#c4ced4' },
|
||
LAD: { name: 'Los Angeles Dodgers', espn: 'lad', primary: '#005a9c', secondary: '#ef3e42' },
|
||
MIA: { name: 'Miami Marlins', espn: 'mia', primary: '#00a3e0', secondary: '#ef3340' },
|
||
MIL: { name: 'Milwaukee Brewers', espn: 'mil', primary: '#ffc72c', secondary: '#13294b' },
|
||
MIN: { name: 'Minnesota Twins', espn: 'min', primary: '#e20e32', secondary: '#002b5c' },
|
||
NYM: { name: 'New York Mets', espn: 'nym', primary: '#ff5910', secondary: '#002d72' },
|
||
NYY: { name: 'New York Yankees', espn: 'nyy', primary: '#c4ced4', secondary: '#132448' },
|
||
PHI: { name: 'Philadelphia Phillies',espn: 'phi', primary: '#e81828', secondary: '#284898' },
|
||
PIT: { name: 'Pittsburgh Pirates', espn: 'pit', primary: '#fdb827', secondary: '#000000' },
|
||
SD: { name: 'San Diego Padres', espn: 'sd', primary: '#ffc425', secondary: '#2f241d' },
|
||
SF: { name: 'San Francisco Giants', espn: 'sf', primary: '#fd5a1e', secondary: '#000000' },
|
||
SEA: { name: 'Seattle Mariners', espn: 'sea', primary: '#0c8f8f', secondary: '#0c2c56' },
|
||
STL: { name: 'St. Louis Cardinals', espn: 'stl', primary: '#be0a14', secondary: '#0a2252' },
|
||
TB: { name: 'Tampa Bay Rays', espn: 'tb', primary: '#8fbce6', secondary: '#092c5c' },
|
||
TEX: { name: 'Texas Rangers', espn: 'tex', primary: '#c0111f', secondary: '#003278' },
|
||
TOR: { name: 'Toronto Blue Jays', espn: 'tor', primary: '#1d78cf', secondary: '#134a8e' },
|
||
WSH: { name: 'Washington Nationals', espn: 'wsh', primary: '#ab0003', secondary: '#11225b' },
|
||
};
|
||
|
||
const NBA = {
|
||
ATL: { name: 'Atlanta Hawks', espn: 'atl', primary: '#e03a3e', secondary: '#fdb927' },
|
||
BOS: { name: 'Boston Celtics', espn: 'bos', primary: '#008348', secondary: '#ffffff' },
|
||
BKN: { name: 'Brooklyn Nets', espn: 'bkn', primary: '#c4ced4', secondary: '#000000' },
|
||
CHA: { name: 'Charlotte Hornets', espn: 'cha', primary: '#00a3af', secondary: '#1d1160' },
|
||
CHI: { name: 'Chicago Bulls', espn: 'chi', primary: '#ce1141', secondary: '#000000' },
|
||
CLE: { name: 'Cleveland Cavaliers', espn: 'cle', primary: '#bc945c', secondary: '#860038' },
|
||
DAL: { name: 'Dallas Mavericks', espn: 'dal', primary: '#0064b1', secondary: '#bbc4ca' },
|
||
DEN: { name: 'Denver Nuggets', espn: 'den', primary: '#fec524', secondary: '#0e2240' },
|
||
DET: { name: 'Detroit Pistons', espn: 'det', primary: '#1d428a', secondary: '#c8102e' },
|
||
GSW: { name: 'Golden State Warriors', espn: 'gs', primary: '#fdb927', secondary: '#1d428a' },
|
||
HOU: { name: 'Houston Rockets', espn: 'hou', primary: '#ce1141', secondary: '#c4ced4' },
|
||
IND: { name: 'Indiana Pacers', espn: 'ind', primary: '#ffd520', secondary: '#0c2340' },
|
||
LAC: { name: 'LA Clippers', espn: 'lac', primary: '#c8102e', secondary: '#1d428a' },
|
||
LAL: { name: 'Los Angeles Lakers', espn: 'lal', primary: '#fdb927', secondary: '#552583' },
|
||
MEM: { name: 'Memphis Grizzlies', espn: 'mem', primary: '#5d76a9', secondary: '#12173f' },
|
||
MIA: { name: 'Miami Heat', espn: 'mia', primary: '#98002e', secondary: '#f9a01b' },
|
||
MIL: { name: 'Milwaukee Bucks', espn: 'mil', primary: '#00471b', secondary: '#eee1c6' },
|
||
MIN: { name: 'Minnesota Timberwolves', espn: 'min', primary: '#79bc43', secondary: '#266092' },
|
||
NOP: { name: 'New Orleans Pelicans', espn: 'no', primary: '#b4975a', secondary: '#0a2240' },
|
||
NYK: { name: 'New York Knicks', espn: 'ny', primary: '#f58426', secondary: '#1d428a' },
|
||
OKC: { name: 'Oklahoma City Thunder', espn: 'okc', primary: '#007ac1', secondary: '#ef3b24' },
|
||
ORL: { name: 'Orlando Magic', espn: 'orl', primary: '#0077c0', secondary: '#c4ced4' },
|
||
PHI: { name: 'Philadelphia 76ers', espn: 'phi', primary: '#1d428a', secondary: '#e01234' },
|
||
PHX: { name: 'Phoenix Suns', espn: 'phx', primary: '#e56020', secondary: '#1d1160' },
|
||
POR: { name: 'Portland Trail Blazers', espn: 'por', primary: '#e03a3e', secondary: '#c4ced4' },
|
||
SAC: { name: 'Sacramento Kings', espn: 'sac', primary: '#5a2d81', secondary: '#63727a' },
|
||
SAS: { name: 'San Antonio Spurs', espn: 'sa', primary: '#c4ced4', secondary: '#000000' },
|
||
TOR: { name: 'Toronto Raptors', espn: 'tor', primary: '#ce1141', secondary: '#000000' },
|
||
UTA: { name: 'Utah Jazz', espn: 'utah', primary: '#79a3dc', secondary: '#4e008e' },
|
||
WAS: { name: 'Washington Wizards', espn: 'wsh', primary: '#e31837', secondary: '#002b5c' },
|
||
};
|
||
|
||
const WNBA = {
|
||
ATL: { name: 'Atlanta Dream', espn: 'atl', primary: '#e31837', secondary: '#5091cc' },
|
||
CHI: { name: 'Chicago Sky', espn: 'chi', primary: '#5091cd', secondary: '#ffd520' },
|
||
CON: { name: 'Connecticut Sun', espn: 'conn', primary: '#f05023', secondary: '#0a2240' },
|
||
DAL: { name: 'Dallas Wings', espn: 'dal', primary: '#c4d600', secondary: '#002b5c' },
|
||
GSV: { name: 'Golden State Valkyries', espn: 'gs', primary: '#b38fcf', secondary: '#000000' },
|
||
IND: { name: 'Indiana Fever', espn: 'ind', primary: '#e03a3e', secondary: '#002d62' },
|
||
LV: { name: 'Las Vegas Aces', espn: 'lv', primary: '#a7a8aa', secondary: '#000000' },
|
||
LA: { name: 'Los Angeles Sparks', espn: 'la', primary: '#fdb927', secondary: '#552583' },
|
||
MIN: { name: 'Minnesota Lynx', espn: 'min', primary: '#79bc43', secondary: '#266092' },
|
||
NY: { name: 'New York Liberty', espn: 'ny', primary: '#86cebc', secondary: '#000000' },
|
||
PHX: { name: 'Phoenix Mercury', espn: 'phx', primary: '#fa4b0a', secondary: '#3c286e' },
|
||
SEA: { name: 'Seattle Storm', espn: 'sea', primary: '#2c5235', secondary: '#fee11a' },
|
||
WAS: { name: 'Washington Mystics', espn: 'wsh', primary: '#e03a3e', secondary: '#002b5c' },
|
||
};
|
||
|
||
// Soccer = World Cup national teams (fifa.world). Keyed by the ESPN country
|
||
// code (uppercased); the flag CDN is a different path (countries/, not
|
||
// teamlogos/soccer). Colors + codes from ESPN's fifa.world API. National
|
||
// teams ARE their flag — that's the crest.
|
||
const SOCCER = {
|
||
ALG: { name: 'Algeria', espn: 'alg', primary: '#4f9a44', secondary: '#ffffff' },
|
||
ARG: { name: 'Argentina', espn: 'arg', primary: '#74acdf', secondary: '#173e69' },
|
||
AUS: { name: 'Australia', espn: 'aus', primary: '#ffcd00', secondary: '#00843d' },
|
||
AUT: { name: 'Austria', espn: 'aut', primary: '#d72b2c', secondary: '#000000' },
|
||
BEL: { name: 'Belgium', espn: 'bel', primary: '#e30613', secondary: '#6ecff6' },
|
||
BIH: { name: 'Bosnia-Herzegovina', espn: 'bih', primary: '#112855', secondary: '#ffce00' },
|
||
BRA: { name: 'Brazil', espn: 'bra', primary: '#fee000', secondary: '#193375' },
|
||
CAN: { name: 'Canada', espn: 'can', primary: '#ed2224', secondary: '#ffffff' },
|
||
CPV: { name: 'Cape Verde', espn: 'cpv', primary: '#0537e4', secondary: '#ef3340' },
|
||
COL: { name: 'Colombia', espn: 'col', primary: '#fbd632', secondary: '#21418c' },
|
||
RDC: { name: 'Congo DR', espn: 'rdc', primary: '#418fde', secondary: '#c60000' },
|
||
CRO: { name: 'Croatia', espn: 'cro', primary: '#ff0000', secondary: '#0c2fff' },
|
||
CUW: { name: 'Curaçao', espn: '11678', primary: '#0537e4', secondary: '#ffce00' },
|
||
CZE: { name: 'Czechia', espn: 'cze', primary: '#d7141a', secondary: '#11457e' },
|
||
ECU: { name: 'Ecuador', espn: 'ecu', primary: '#ffdd00', secondary: '#034ea2' },
|
||
EGY: { name: 'Egypt', espn: 'egy', primary: '#d20300', secondary: '#ffffff' },
|
||
ENG: { name: 'England', espn: 'eng', primary: '#ea1f29', secondary: '#ffffff' },
|
||
FRA: { name: 'France', espn: 'fra', primary: '#3a6dd8', secondary: '#ef4135' },
|
||
GER: { name: 'Germany', espn: 'ger', primary: '#ffce00', secondary: '#dd0000' },
|
||
GHA: { name: 'Ghana', espn: 'gha', primary: '#fbd632', secondary: '#006b3f' },
|
||
HAI: { name: 'Haiti', espn: 'hai', primary: '#0033a0', secondary: '#d21034' },
|
||
IRN: { name: 'Iran', espn: 'irn', primary: '#da0000', secondary: '#239f40' },
|
||
IRQ: { name: 'Iraq', espn: 'irq', primary: '#0a4d2e', secondary: '#ce1126' },
|
||
CIV: { name: 'Ivory Coast', espn: 'civ', primary: '#ff8200', secondary: '#009e60' },
|
||
JPN: { name: 'Japan', espn: 'jpn', primary: '#3a6dd8', secondary: '#bc002d' },
|
||
JOR: { name: 'Jordan', espn: 'jor', primary: '#e70000', secondary: '#007a3d' },
|
||
MEX: { name: 'Mexico', espn: 'mex', primary: '#00843d', secondary: '#ce1126' },
|
||
MAR: { name: 'Morocco', espn: 'mar', primary: '#df2027', secondary: '#006233' },
|
||
NED: { name: 'Netherlands', espn: 'ned', primary: '#fb5d00', secondary: '#21468b' },
|
||
NZL: { name: 'New Zealand', espn: 'nzl', primary: '#c8c8c8', secondary: '#000000' },
|
||
NOR: { name: 'Norway', espn: 'nor', primary: '#c8102e', secondary: '#00205b' },
|
||
PAN: { name: 'Panama', espn: 'pan', primary: '#d21034', secondary: '#005293' },
|
||
PAR: { name: 'Paraguay', espn: 'par', primary: '#ea2300', secondary: '#21418c' },
|
||
POR: { name: 'Portugal', espn: 'por', primary: '#da291c', secondary: '#0d6938' },
|
||
QAT: { name: 'Qatar', espn: 'qat', primary: '#8a1538', secondary: '#ffffff' },
|
||
KSA: { name: 'Saudi Arabia', espn: 'ksa', primary: '#0f7a3d', secondary: '#ffffff' },
|
||
SCO: { name: 'Scotland', espn: 'sco', primary: '#4b6cb7', secondary: '#1a2d69' },
|
||
SEN: { name: 'Senegal', espn: 'sen', primary: '#00853f', secondary: '#fdef42' },
|
||
RSA: { name: 'South Africa', espn: 'rsa', primary: '#ffb81c', secondary: '#0c562e' },
|
||
KOR: { name: 'South Korea', espn: 'kors', primary: '#ce2028', secondary: '#003478' },
|
||
ESP: { name: 'Spain', espn: 'esp', primary: '#c60b1e', secondary: '#ffc400' },
|
||
SWE: { name: 'Sweden', espn: 'swe', primary: '#fecb00', secondary: '#006aa7' },
|
||
SUI: { name: 'Switzerland', espn: 'sui', primary: '#ff0000', secondary: '#ffffff' },
|
||
TUN: { name: 'Tunisia', espn: 'tun', primary: '#d20300', secondary: '#ffffff' },
|
||
TUR: { name: 'Türkiye', espn: 'tur', primary: '#ef3340', secondary: '#ffffff' },
|
||
USA: { name: 'United States', espn: 'usa', primary: '#3a6dd8', secondary: '#d42339' },
|
||
URU: { name: 'Uruguay', espn: 'uru', primary: '#55b5e5', secondary: '#00205b' },
|
||
UZB: { name: 'Uzbekistan', espn: 'uzb', primary: '#0072ce', secondary: '#1eb53a' },
|
||
};
|
||
|
||
const LEAGUES = { mlb: MLB, nba: NBA, wnba: WNBA, soccer: SOCCER };
|
||
|
||
// Common naming variants the odds feeds use → the canonical soccer key.
|
||
const SOCCER_ALIAS = {
|
||
usa: 'USA', unitedstates: 'USA', usmnt: 'USA',
|
||
southkorea: 'KOR', koreademocraticpeoplesrepublic: 'KOR', kore : 'KOR', korearepublic: 'KOR',
|
||
ivorycoast: 'CIV', cotedivoire: 'CIV',
|
||
turkey: 'TUR', turkiye: 'TUR',
|
||
czechrepublic: 'CZE', czechia: 'CZE',
|
||
bosnia: 'BIH', bosniaandherzegovina: 'BIH',
|
||
congodr: 'RDC', drcongo: 'RDC',
|
||
curacao: 'CUW',
|
||
};
|
||
|
||
// Full-name + nickname → abbr, built once per league for resolveTeam().
|
||
const NAME_INDEX = {};
|
||
for (const [sp, table] of Object.entries(LEAGUES)) {
|
||
NAME_INDEX[sp] = {};
|
||
for (const [abbr, meta] of Object.entries(table)) {
|
||
const norm = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
|
||
NAME_INDEX[sp][norm(meta.name)] = abbr;
|
||
// nickname = last word ("Yankees", "Red Sox" → "redsox")
|
||
const parts = meta.name.split(/\s+/);
|
||
NAME_INDEX[sp][norm(parts[parts.length - 1])] = abbr;
|
||
// two-word nickname ("Red Sox", "Blue Jays", "White Sox")
|
||
if (parts.length >= 2) NAME_INDEX[sp][norm(parts.slice(-2).join(''))] = abbr;
|
||
}
|
||
}
|
||
|
||
const normKey = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
||
|
||
/** Resolve any team key (abbr, full name, nickname) + sport → meta or null. */
|
||
function resolveTeam(key, sport) {
|
||
if (!key) return null;
|
||
const sp = String(sport || 'mlb').toLowerCase();
|
||
const table = LEAGUES[sp];
|
||
if (!table) return null;
|
||
const up = String(key).toUpperCase().trim();
|
||
if (table[up]) return { abbr: up, sport: sp, ...table[up] };
|
||
// statsapi/ESPN alias fallbacks for the two MLB mismatches
|
||
// Global alias table. resolveTeam checks the real abbr FIRST, so an entry
|
||
// only fires for a sport whose table lacks that key — e.g. WSH resolves
|
||
// directly to the MLB Nationals, but NBA/WNBA (no WSH key) fall through to
|
||
// WAS (Wizards/Mystics). ESPN-schedule abbrs the ball sports actually emit
|
||
// (SA/NY/WSH/BRK for NBA, CONN for WNBA) are covered here.
|
||
const ALIAS = {
|
||
ARI: 'AZ', CHW: 'CWS', OAK: 'ATH', SFG: 'SF', TBR: 'TB', WSN: 'WSH', KCR: 'KC', SDP: 'SD',
|
||
GS: 'GSW', NO: 'NOP', NYK: 'NYK', UTAH: 'UTA', PHO: 'PHX',
|
||
SA: 'SAS', NY: 'NYK', WSH: 'WAS', BRK: 'BKN', CONN: 'CON',
|
||
};
|
||
if (ALIAS[up] && table[ALIAS[up]]) return { abbr: ALIAS[up], sport: sp, ...table[ALIAS[up]] };
|
||
if (sp === 'soccer' && SOCCER_ALIAS[normKey(key)]) {
|
||
const a = SOCCER_ALIAS[normKey(key)];
|
||
return { abbr: a, sport: sp, ...table[a] };
|
||
}
|
||
const byName = NAME_INDEX[sp][normKey(key)];
|
||
if (byName) return { abbr: byName, sport: sp, ...table[byName] };
|
||
return null;
|
||
}
|
||
|
||
/** ESPN free logo CDN url for a team, or null when unresolved. Soccer national
|
||
* teams use the flag CDN (countries/ for a 3-letter code, soccer/ for the rare
|
||
* numeric team id); the ball sports use teamlogos/{league}/. */
|
||
function teamLogoUrl(key, sport) {
|
||
const m = resolveTeam(key, sport);
|
||
if (!m) return null;
|
||
if (m.sport === 'soccer') {
|
||
return /^\d+$/.test(m.espn)
|
||
? `https://a.espncdn.com/i/teamlogos/soccer/500/${m.espn}.png`
|
||
: `https://a.espncdn.com/i/teamlogos/countries/500/${m.espn}.png`;
|
||
}
|
||
const path = m.sport === 'wnba' ? 'wnba' : 'nba';
|
||
return `https://a.espncdn.com/i/teamlogos/${m.sport === 'mlb' ? 'mlb' : path}/500/${m.espn}.png`;
|
||
}
|
||
|
||
// Perceived luminance of a #rrggbb (0–255). Used to keep team accents
|
||
// VISIBLE on the near-black terminal bg — a #000000 primary is useless.
|
||
function luminance(hex) {
|
||
const h = String(hex || '').replace('#', '');
|
||
if (h.length !== 6) return 0;
|
||
const r = parseInt(h.slice(0, 2), 16), g = parseInt(h.slice(2, 4), 16), b = parseInt(h.slice(4, 6), 16);
|
||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||
}
|
||
|
||
/** The team color to use for an accent/monogram against the dark theme:
|
||
* the more visible of primary/secondary. Falls back to a neutral when
|
||
* both are near-black (never returns an invisible accent). */
|
||
function accentColor(key, sport) {
|
||
const m = resolveTeam(key, sport);
|
||
if (!m) return null;
|
||
const lp = luminance(m.primary), ls = luminance(m.secondary);
|
||
const best = lp >= ls ? m.primary : m.secondary;
|
||
return luminance(best) < 40 ? '#8A8A9A' : best; // both dark → neutral chrome
|
||
}
|
||
|
||
module.exports = { resolveTeam, teamLogoUrl, accentColor, luminance, LEAGUES, __tables: { MLB, NBA, WNBA } };
|