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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user