DS0 (Design v2): the Entity Layer — teams/players/books render as themselves

Founder's #1 priority. A single cached asset+rendering system, swapped into
every surface, so entities stop being flat gray strings (DESIGN-SPEC Part 2).

- web/src/lib/teamMeta.js: static registry for ALL 4 sports — 30 MLB, 30 NBA,
  13 WNBA teams + 48 World Cup national teams, each with real colors + the
  ESPN logo/flag CDN abbr. resolveTeam (abbr/full-name/nickname/alias),
  teamLogoUrl (statsapi->ESPN mapping: AZ->ari, CWS->chw; soccer via the
  countries/ flag CDN), accentColor (picks the VISIBLE color of the pair so a
  #000000 primary never renders an invisible accent on #06060B). Colors +
  abbrs sourced once from ESPN's team API — stable public facts, zero-latency
  static data, no paid dependency.
- TeamLogo: real ESPN-CDN logo with a team-colored MONOGRAM fallback (never a
  gray box / bare abbr). PlayerAvatar: real headshot with a team-colored
  monogram fallback (kills the gray silhouette). BookWordmark: brand-color
  wordmark, proper casing (DraftKings, not 'draftkings').
- Swapped into the class-level shared components so it propagates to ALL
  surfaces: GameCard (team logos + team-colored accent edge), StatStrip
  (player identity avatar), StreaksPanel (P0 billboard avatars), TeamHub
  header (the team's real crest leads its hub). Barrel-exported.

ZERO OUT-OF-POCKET: ESPN logo/flag CDN + league headshot CDNs, all verified
200 image/png across MLB/NBA/WNBA/soccer.

ACCEPTANCE (SSR render proof): /entity-demo harness server-rendered the exact
real asset URLs across all 4 sports — mlb/500/nyy.png, mlb/500/chw.png (White
Sox, correct ESPN abbr), nba/500/lal.png, wnba/500/ny.png, countries/500/
usa.png + bra/eng/arg/jpn flags, real mlbstatic/nba headshots (Judge 592450,
LeBron 2544), DraftKings/FanDuel wordmarks. Each URL curl-verified 200
image/png. Harness removed post-proof (not a product surface). Pixel
screenshot blocked by WSL2<->Windows-Chrome localhost networking, not code.

2757 -> 2776 tests (+13 entityLayer, +6 boot resilience), web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-12 19:02:18 -04:00
parent b452db727c
commit 24af247b29
10 changed files with 520 additions and 17 deletions
+74
View File
@@ -0,0 +1,74 @@
// DS0 (Design v2) — THE ENTITY LAYER. Teams resolve to real logos + real
// colors; the accent rule keeps team color VISIBLE on the near-black terminal.
const { resolveTeam, teamLogoUrl, accentColor, luminance } = require('../../web/src/lib/teamMeta');
describe('resolveTeam — abbr / full name / nickname / alias', () => {
test('canonical statsapi abbr', () => {
expect(resolveTeam('NYY', 'mlb').name).toBe('New York Yankees');
expect(resolveTeam('MIL', 'mlb').name).toBe('Milwaukee Brewers');
});
test('full name and nickname', () => {
expect(resolveTeam('Milwaukee Brewers', 'mlb').abbr).toBe('MIL');
expect(resolveTeam('Pirates', 'mlb').abbr).toBe('PIT');
expect(resolveTeam('Red Sox', 'mlb').abbr).toBe('BOS');
expect(resolveTeam('White Sox', 'mlb').abbr).toBe('CWS');
});
test('statsapi↔ESPN abbr aliases (AZ↔ARI, CWS↔CHW)', () => {
expect(resolveTeam('ARI', 'mlb').abbr).toBe('AZ');
expect(resolveTeam('CHW', 'mlb').abbr).toBe('CWS');
});
test('unknown team → null (never a fake)', () => {
expect(resolveTeam('ZZZ', 'mlb')).toBeNull();
expect(resolveTeam('', 'mlb')).toBeNull();
});
test('resolves across leagues', () => {
expect(resolveTeam('LAL', 'nba').name).toBe('Los Angeles Lakers');
expect(resolveTeam('NY', 'wnba').name).toBe('New York Liberty');
});
test('soccer = World Cup national teams, by name + alias', () => {
expect(resolveTeam('USA', 'soccer').name).toBe('United States');
expect(resolveTeam('United States', 'soccer').abbr).toBe('USA');
expect(resolveTeam('Brazil', 'soccer').abbr).toBe('BRA');
expect(resolveTeam('South Korea', 'soccer').abbr).toBe('KOR');
expect(resolveTeam('Ivory Coast', 'soccer').abbr).toBe('CIV'); // alias
});
});
describe('teamLogoUrl — real ESPN CDN paths', () => {
test('MLB logo url uses the ESPN logo abbr, not the app abbr', () => {
expect(teamLogoUrl('AZ', 'mlb')).toBe('https://a.espncdn.com/i/teamlogos/mlb/500/ari.png');
expect(teamLogoUrl('CWS', 'mlb')).toBe('https://a.espncdn.com/i/teamlogos/mlb/500/chw.png');
expect(teamLogoUrl('NYY', 'mlb')).toBe('https://a.espncdn.com/i/teamlogos/mlb/500/nyy.png');
});
test('per-league path', () => {
expect(teamLogoUrl('LAL', 'nba')).toContain('/teamlogos/nba/500/lal.png');
expect(teamLogoUrl('NY', 'wnba')).toContain('/teamlogos/wnba/500/ny.png');
});
test('soccer national teams use the flag CDN (countries/ + numeric edge)', () => {
expect(teamLogoUrl('USA', 'soccer')).toBe('https://a.espncdn.com/i/teamlogos/countries/500/usa.png');
expect(teamLogoUrl('South Korea', 'soccer')).toContain('/countries/500/kors.png');
expect(teamLogoUrl('Curaçao', 'soccer')).toBe('https://a.espncdn.com/i/teamlogos/soccer/500/11678.png');
});
test('unknown → null', () => {
expect(teamLogoUrl('ZZZ', 'mlb')).toBeNull();
expect(teamLogoUrl('Narnia', 'soccer')).toBeNull();
});
});
describe('accentColor — VISIBLE on #06060B (the near-black bug guard)', () => {
test('picks the more luminous of primary/secondary', () => {
// White Sox: primary silver (#c4ced4) vs black — must pick silver.
expect(accentColor('CWS', 'mlb').toLowerCase()).toBe('#c4ced4');
// Pirates: black primary + gold secondary — must pick gold.
expect(accentColor('PIT', 'mlb').toLowerCase()).toBe('#fdb827');
});
test('both-dark team never returns an invisible accent', () => {
const acc = accentColor('SF', 'mlb'); // black + orange → orange, visible
expect(luminance(acc)).toBeGreaterThan(40);
});
test('luminance ranks black low, white high', () => {
expect(luminance('#000000')).toBeLessThan(10);
expect(luminance('#ffffff')).toBeGreaterThan(240);
});
});