Complete visual overhaul: warm light theme, stadium hero, inline pill nav

Inspired by vtv/Dribbble streaming concept. Every dark surface replaced
with warm off-white (#F5F3F0) and white cards with soft shadows.

Design System: Warm off-white background, white card fills, subtle drop
shadows, dark text hierarchy, warm orange accent (#F27326) replacing
blue as primary interactive color. Added onDark color variants for hero
overlays. Shadow system with card/lifted states.

Navigation: Replaced TabView with inline CategoryPillBar — horizontal
orange pills (Today | Intel | Highlights | Multi-View | Settings).
Single scrolling view, no system chrome. Multi-View as icon button
with stream count badge. Settings as gear icon.

Stadium Hero: Full-bleed stadium photos from MLB CDN
(mlbstatic.com/v1/venue/{id}/spots/1200) as featured game background.
Left gradient overlay for text readability. Live games show score +
inning + DiamondView count/outs. Scheduled games show probable pitchers
with headshots + records. Final games show final score. Warm orange
"Watch Now" CTA pill. Added venue ID mapping for all 30 stadiums to
TeamAssets.

Game Cards: White cards with team color top bar, horizontal team rows,
dark text, soft shadows. Record + streak on every card.

Intel Tab: All dark panels replaced with white cards + shadows.
Replaced dark gradient screen background with flat warm off-white.
58 hardcoded .white.opacity() values replaced with DS.Colors tokens.

Feed Tab: Already used DS.Colors — inherits light theme automatically.

Focus: tvOS focus style uses warm orange border highlight + lifted
shadow instead of white glow on dark.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-12 14:56:26 -05:00
parent 69d84fd09b
commit 65ad41840f
11 changed files with 582 additions and 484 deletions

View File

@@ -88,11 +88,11 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 8) {
Text("Around MLB")
.font(.system(size: 42, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Text("Standings, league leaders, team context, roster access, and player snapshots in one control room.")
.font(.system(size: 16, weight: .medium))
.foregroundStyle(.white.opacity(0.58))
.foregroundStyle(DS.Colors.textTertiary)
}
Spacer()
@@ -111,11 +111,11 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 6) {
Text("Schedule")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Text(viewModel.displayDateString)
.font(.system(size: 16, weight: .medium))
.foregroundStyle(.white.opacity(0.55))
.foregroundStyle(DS.Colors.textTertiary)
}
Spacer()
@@ -162,7 +162,7 @@ struct LeagueCenterView: View {
VStack(spacing: 6) {
Text(scoreText(for: game))
.font(.system(size: 28, weight: .black, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
.monospacedDigit()
Text(statusText(for: game))
@@ -178,13 +178,13 @@ struct LeagueCenterView: View {
if let venue = game.venue?.name {
Text(venue)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.white.opacity(0.56))
.foregroundStyle(DS.Colors.textTertiary)
.lineLimit(1)
}
Text(linkedGame != nil ? "Open game sheet" : "Info only")
.font(.system(size: 13, weight: .bold, design: .rounded))
.foregroundStyle(linkedGame != nil ? .blue.opacity(0.95) : .white.opacity(0.34))
.foregroundStyle(linkedGame != nil ? .blue.opacity(0.95) : DS.Colors.textQuaternary)
}
.frame(width: scheduleVenueColWidth, alignment: .trailing)
}
@@ -222,17 +222,17 @@ struct LeagueCenterView: View {
VStack(alignment: alignTrailing ? .trailing : .leading, spacing: 6) {
Text(info.code)
.font(.system(size: 22, weight: .black, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Text(info.displayName)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white.opacity(0.7))
.foregroundStyle(DS.Colors.textSecondary)
.lineLimit(1)
if let record = info.record {
Text(record)
.font(.system(size: 13, weight: .bold, design: .monospaced))
.foregroundStyle(.white.opacity(0.46))
.foregroundStyle(DS.Colors.textTertiary)
}
}
@@ -248,7 +248,7 @@ struct LeagueCenterView: View {
HStack {
Text("Leaders")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Spacer()
@@ -274,7 +274,7 @@ struct LeagueCenterView: View {
HStack {
Text("League Leaders")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Spacer()
@@ -310,7 +310,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 18) {
Text("Standings")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
if viewModel.standings.isEmpty && viewModel.isLoadingOverview {
loadingPanel(title: "Loading standings...")
@@ -335,31 +335,31 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 14) {
Text(record.division?.name ?? "Division")
.font(.system(size: 22, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
VStack(spacing: 10) {
ForEach(record.teamRecords.sorted(by: standingsSort), id: \.team.id) { team in
HStack(spacing: 10) {
Text(team.divisionRank ?? "-")
.font(.system(size: 12, weight: .black, design: .rounded))
.foregroundStyle(.white.opacity(0.52))
.foregroundStyle(DS.Colors.textTertiary)
.frame(width: 22)
Text(team.team.abbreviation ?? team.team.name ?? "MLB")
.font(.system(size: 16, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Spacer()
if let wins = team.wins, let losses = team.losses {
Text("\(wins)-\(losses)")
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundStyle(.white.opacity(0.86))
.foregroundStyle(DS.Colors.textPrimary)
}
Text(team.gamesBack ?? "-")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(.white.opacity(0.45))
.foregroundStyle(DS.Colors.textTertiary)
.frame(width: 44, alignment: .trailing)
}
.padding(.vertical, 4)
@@ -374,7 +374,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 18) {
Text("Teams")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
ScrollView(.horizontal) {
HStack(spacing: 16) {
@@ -388,11 +388,11 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 6) {
Text(team.abbreviation)
.font(.system(size: 20, weight: .black, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
Text(team.name)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white.opacity(0.76))
.foregroundStyle(DS.Colors.textSecondary)
.lineLimit(2)
}
@@ -400,7 +400,7 @@ struct LeagueCenterView: View {
Text(team.recordText ?? "Season")
.font(.system(size: 13, weight: .bold, design: .monospaced))
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(DS.Colors.textTertiary)
}
.frame(width: 210, height: 220, alignment: .leading)
.padding(18)
@@ -410,7 +410,7 @@ struct LeagueCenterView: View {
)
.overlay(
RoundedRectangle(cornerRadius: 24, style: .continuous)
.stroke(.white.opacity(0.08), lineWidth: 1)
.stroke(DS.Colors.panelStroke, lineWidth: 0.5)
)
}
.platformCardStyle()
@@ -427,7 +427,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 18) {
Text("Team Profile")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
if viewModel.isLoadingTeam {
loadingPanel(title: "Loading team profile...")
@@ -438,7 +438,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 12) {
Text(team.name)
.font(.system(size: 34, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
HStack(spacing: 10) {
detailChip(team.recordText ?? "Season", color: .blue)
@@ -472,7 +472,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 18) {
Text("Roster")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
LazyVGrid(columns: rosterColumns, spacing: 14) {
ForEach(viewModel.roster) { player in
@@ -485,12 +485,12 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 6) {
Text(player.fullName)
.font(.system(size: 16, weight: .bold))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
.lineLimit(2)
Text("\(player.positionAbbreviation ?? "P") · #\(player.jerseyNumber ?? "--")")
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(DS.Colors.textTertiary)
}
Spacer()
@@ -508,7 +508,7 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 18) {
Text("Player Profile")
.font(.system(size: 30, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
if viewModel.isLoadingPlayer {
loadingPanel(title: "Loading player profile...")
@@ -522,7 +522,7 @@ struct LeagueCenterView: View {
if let primaryNumber = player.primaryNumber {
Text("#\(primaryNumber)")
.font(.system(size: 15, weight: .black, design: .rounded))
.foregroundStyle(.white.opacity(0.7))
.foregroundStyle(DS.Colors.textSecondary)
}
if let position = player.primaryPosition {
@@ -532,7 +532,7 @@ struct LeagueCenterView: View {
Text(player.fullName)
.font(.system(size: 34, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
VStack(alignment: .leading, spacing: 8) {
profileLine(label: "Age", value: player.currentAge.map(String.init))
@@ -552,20 +552,20 @@ struct LeagueCenterView: View {
VStack(alignment: .leading, spacing: 14) {
Text("\(group.title) \(player.seasonLabel)")
.font(.system(size: 18, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
VStack(alignment: .leading, spacing: 10) {
ForEach(group.items, id: \.label) { item in
HStack {
Text(item.label)
.font(.system(size: 13, weight: .bold, design: .rounded))
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(DS.Colors.textTertiary)
Spacer()
Text(item.value)
.font(.system(size: 18, weight: .black, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(DS.Colors.textPrimary)
}
}
}
@@ -574,14 +574,14 @@ struct LeagueCenterView: View {
.padding(18)
.background(
RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(.white.opacity(0.05))
.fill(DS.Colors.panelStroke)
)
}
}
} else {
Text("No regular-season MLB stats available for \(player.seasonLabel).")
.font(.system(size: 18, weight: .semibold, design: .rounded))
.foregroundStyle(.white.opacity(0.7))
.foregroundStyle(DS.Colors.textSecondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 4)
}
@@ -604,10 +604,10 @@ struct LeagueCenterView: View {
default:
ZStack {
Circle()
.fill(.white.opacity(0.08))
.fill(DS.Colors.panelStroke)
Image(systemName: "person.fill")
.font(.system(size: size * 0.34, weight: .bold))
.foregroundStyle(.white.opacity(0.32))
.foregroundStyle(DS.Colors.textQuaternary)
}
}
}
@@ -615,7 +615,7 @@ struct LeagueCenterView: View {
.clipShape(Circle())
.overlay(
Circle()
.stroke(.white.opacity(0.08), lineWidth: 1)
.stroke(DS.Colors.panelStroke, lineWidth: 0.5)
)
}
@@ -630,12 +630,12 @@ struct LeagueCenterView: View {
return HStack(spacing: 10) {
Text(label)
.font(.system(size: 13, weight: .bold, design: .rounded))
.foregroundStyle(.white.opacity(0.45))
.foregroundStyle(DS.Colors.textTertiary)
.frame(width: 92, alignment: .leading)
Text(displayValue)
.font(.system(size: 15, weight: .semibold))
.foregroundStyle(.white.opacity(0.82))
.foregroundStyle(DS.Colors.textPrimary)
}
}
@@ -666,7 +666,7 @@ struct LeagueCenterView: View {
.monospacedDigit()
Text(label)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(DS.Colors.textTertiary)
}
.padding(.horizontal, 18)
.padding(.vertical, 12)
@@ -681,10 +681,10 @@ struct LeagueCenterView: View {
Text(title)
.font(.system(size: 14, weight: .semibold))
}
.foregroundStyle(.white.opacity(0.84))
.foregroundStyle(DS.Colors.textPrimary)
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(.white.opacity(0.08))
.background(DS.Colors.panelStroke)
.clipShape(Capsule())
}
.platformCardStyle()
@@ -695,7 +695,7 @@ struct LeagueCenterView: View {
Spacer()
ProgressView(title)
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white.opacity(0.75))
.foregroundStyle(DS.Colors.textSecondary)
Spacer()
}
.padding(.vertical, 34)
@@ -726,7 +726,7 @@ struct LeagueCenterView: View {
return .red.opacity(0.9)
}
if game.isFinal {
return .white.opacity(0.72)
return DS.Colors.textSecondary
}
return .blue.opacity(0.9)
}
@@ -737,36 +737,15 @@ struct LeagueCenterView: View {
private var sectionPanel: some View {
RoundedRectangle(cornerRadius: 24, style: .continuous)
.fill(.black.opacity(0.22))
.fill(DS.Colors.panelFill)
.shadow(color: DS.Shadows.card, radius: DS.Shadows.cardRadius, y: DS.Shadows.cardY)
.overlay {
RoundedRectangle(cornerRadius: 24, style: .continuous)
.strokeBorder(.white.opacity(0.08), lineWidth: 1)
.strokeBorder(DS.Colors.panelStroke, lineWidth: 0.5)
}
}
private var screenBackground: some View {
ZStack {
LinearGradient(
colors: [
Color(red: 0.04, green: 0.05, blue: 0.09),
Color(red: 0.03, green: 0.06, blue: 0.1),
Color(red: 0.02, green: 0.03, blue: 0.06),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
Circle()
.fill(.blue.opacity(0.18))
.frame(width: 520, height: 520)
.blur(radius: 110)
.offset(x: -360, y: -260)
Circle()
.fill(.orange.opacity(0.16))
.frame(width: 560, height: 560)
.blur(radius: 120)
.offset(x: 420, y: -80)
}
DS.Colors.background
}
}