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:
@@ -8,242 +8,224 @@ struct FeaturedGameCard: View {
|
||||
private var homeColor: Color { TeamAssets.color(for: game.homeTeam.code) }
|
||||
|
||||
private var awayPitcherName: String? {
|
||||
guard let pitchers = game.pitchers else { return nil }
|
||||
return pitchers.components(separatedBy: " vs ").first
|
||||
game.pitchers?.components(separatedBy: " vs ").first
|
||||
}
|
||||
private var homePitcherName: String? {
|
||||
let parts = game.pitchers?.components(separatedBy: " vs ") ?? []
|
||||
return parts.count > 1 ? parts.last : nil
|
||||
}
|
||||
|
||||
private var homePitcherName: String? {
|
||||
guard let pitchers = game.pitchers else { return nil }
|
||||
let parts = pitchers.components(separatedBy: " vs ")
|
||||
return parts.count > 1 ? parts.last : nil
|
||||
private var stadiumImageURL: URL? {
|
||||
TeamAssets.stadiumURL(for: game.homeTeam.code)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: onSelect) {
|
||||
VStack(spacing: 0) {
|
||||
// Full-bleed matchup area
|
||||
ZStack {
|
||||
heroBackground
|
||||
ZStack(alignment: .leading) {
|
||||
// Stadium photo background
|
||||
stadiumBackground
|
||||
|
||||
VStack(spacing: heroSpacing) {
|
||||
// Status + game type
|
||||
HStack {
|
||||
Text((game.gameType ?? "Featured").uppercased())
|
||||
.font(labelFont)
|
||||
.foregroundStyle(.white.opacity(0.5))
|
||||
.kerning(1.5)
|
||||
|
||||
Spacer()
|
||||
|
||||
statusChip
|
||||
}
|
||||
|
||||
// Main matchup: Away — Score — Home
|
||||
HStack(spacing: 0) {
|
||||
teamSide(
|
||||
team: game.awayTeam,
|
||||
color: awayColor,
|
||||
pitcherURL: game.awayPitcherHeadshotURL,
|
||||
pitcherName: awayPitcherName,
|
||||
alignment: .leading
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
scoreCenter
|
||||
.frame(width: scoreCenterWidth)
|
||||
|
||||
teamSide(
|
||||
team: game.homeTeam,
|
||||
color: homeColor,
|
||||
pitcherURL: game.homePitcherHeadshotURL,
|
||||
pitcherName: homePitcherName,
|
||||
alignment: .trailing
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
// Subtitle line: venue + coverage
|
||||
subtitleRow
|
||||
}
|
||||
.padding(.horizontal, heroPadH)
|
||||
.padding(.vertical, heroPadV)
|
||||
// Left gradient overlay for text readability
|
||||
HStack(spacing: 0) {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.08, green: 0.08, blue: 0.10),
|
||||
Color(red: 0.08, green: 0.08, blue: 0.10).opacity(0.95),
|
||||
Color(red: 0.08, green: 0.08, blue: 0.10).opacity(0.6),
|
||||
.clear
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
// Linescore bar (if game started)
|
||||
if let linescore = game.linescore, !game.status.isScheduled {
|
||||
LinescoreView(
|
||||
linescore: linescore,
|
||||
awayCode: game.awayTeam.code,
|
||||
homeCode: game.homeTeam.code
|
||||
)
|
||||
.padding(.horizontal, heroPadH)
|
||||
.padding(.vertical, 16)
|
||||
.background(.black.opacity(0.3))
|
||||
// Content overlay
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
heroContent
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, heroPadH)
|
||||
.padding(.vertical, heroPadV)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: heroRadius, style: .continuous))
|
||||
.frame(height: heroHeight)
|
||||
.clipShape(RoundedRectangle(cornerRadius: DS.Radii.featured, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: heroRadius, style: .continuous)
|
||||
.strokeBorder(borderColor, lineWidth: game.isLive ? 2 : 1)
|
||||
RoundedRectangle(cornerRadius: DS.Radii.featured, style: .continuous)
|
||||
.strokeBorder(game.isLive ? DS.Colors.live.opacity(0.3) : .white.opacity(0.1), lineWidth: game.isLive ? 2 : 1)
|
||||
)
|
||||
.shadow(color: game.isLive ? .red.opacity(0.2) : .black.opacity(0.3), radius: 24, y: 10)
|
||||
.shadow(color: .black.opacity(0.2), radius: 30, y: 12)
|
||||
}
|
||||
.platformCardStyle()
|
||||
}
|
||||
|
||||
// MARK: - Team Side
|
||||
// MARK: - Hero Content (left side overlay)
|
||||
|
||||
@ViewBuilder
|
||||
private func teamSide(
|
||||
team: TeamInfo,
|
||||
color: Color,
|
||||
pitcherURL: URL?,
|
||||
pitcherName: String?,
|
||||
alignment: HorizontalAlignment
|
||||
) -> some View {
|
||||
VStack(alignment: alignment, spacing: teamInfoGap) {
|
||||
TeamLogoView(team: team, size: logoSize)
|
||||
private var heroContent: some View {
|
||||
VStack(alignment: .leading, spacing: heroContentSpacing) {
|
||||
// Status badge
|
||||
statusBadge
|
||||
|
||||
VStack(alignment: alignment, spacing: 4) {
|
||||
Text(team.code)
|
||||
.font(teamCodeFont)
|
||||
.foregroundStyle(.white)
|
||||
// Giant matchup title
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("\(game.awayTeam.code) vs \(game.homeTeam.code)")
|
||||
.font(matchupFont)
|
||||
.foregroundStyle(DS.Colors.onDarkPrimary)
|
||||
|
||||
Text(team.displayName)
|
||||
.font(teamNameFont)
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
.lineLimit(1)
|
||||
Text("\(game.awayTeam.displayName) at \(game.homeTeam.displayName)")
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(DS.Colors.onDarkSecondary)
|
||||
}
|
||||
|
||||
if let record = team.record {
|
||||
HStack(spacing: 6) {
|
||||
Text(record)
|
||||
.font(recordFont)
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
|
||||
if let streak = team.streak {
|
||||
Text(streak)
|
||||
.font(recordFont)
|
||||
.foregroundStyle(streak.hasPrefix("W") ? DS.Colors.positive : DS.Colors.live)
|
||||
}
|
||||
}
|
||||
// Live data OR scheduled/final data
|
||||
if game.isLive {
|
||||
liveDataSection
|
||||
} else if game.isFinal {
|
||||
finalDataSection
|
||||
} else {
|
||||
scheduledDataSection
|
||||
}
|
||||
|
||||
if let name = pitcherName {
|
||||
VStack(alignment: alignment, spacing: 2) {
|
||||
Text("PROBABLE")
|
||||
.font(probableLabelFont)
|
||||
.foregroundStyle(.white.opacity(0.35))
|
||||
.kerning(1)
|
||||
HStack(spacing: 6) {
|
||||
if let url = pitcherURL {
|
||||
PitcherHeadshotView(url: url, teamCode: team.code, name: nil, size: pitcherHeadshotSize)
|
||||
}
|
||||
Text(name)
|
||||
.font(pitcherNameFont)
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
.lineLimit(1)
|
||||
}
|
||||
// Metadata line
|
||||
metadataLine
|
||||
|
||||
// CTA
|
||||
HStack(spacing: 14) {
|
||||
if game.hasStreams {
|
||||
Label("Watch Now", systemImage: "play.fill")
|
||||
.font(ctaFont)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, ctaPadH)
|
||||
.padding(.vertical, ctaPadV)
|
||||
.background(DS.Colors.interactive)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Score Center
|
||||
// MARK: - Live Data
|
||||
|
||||
@ViewBuilder
|
||||
private var scoreCenter: some View {
|
||||
VStack(spacing: 8) {
|
||||
if let away = game.awayTeam.score, let home = game.homeTeam.score {
|
||||
Text("\(away) - \(home)")
|
||||
.font(mainScoreFont)
|
||||
.foregroundStyle(.white)
|
||||
.monospacedDigit()
|
||||
.contentTransition(.numericText())
|
||||
} else {
|
||||
Text(game.status.label)
|
||||
.font(statusTimeFont)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
private var liveDataSection: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
// Score
|
||||
HStack(alignment: .firstTextBaseline, spacing: 20) {
|
||||
if let away = game.awayTeam.score, let home = game.homeTeam.score {
|
||||
Text("\(away) - \(home)")
|
||||
.font(liveScoreFont)
|
||||
.foregroundStyle(DS.Colors.onDarkPrimary)
|
||||
.monospacedDigit()
|
||||
.contentTransition(.numericText())
|
||||
}
|
||||
|
||||
if case .live = game.status {
|
||||
if let inning = game.currentInningDisplay {
|
||||
Text(inning)
|
||||
.font(inningFont)
|
||||
.foregroundStyle(DS.Colors.live)
|
||||
}
|
||||
}
|
||||
|
||||
// Count + outs for live games
|
||||
if let linescore = game.linescore {
|
||||
DiamondView(
|
||||
balls: linescore.balls ?? 0,
|
||||
strikes: linescore.strikes ?? 0,
|
||||
outs: linescore.outs ?? 0
|
||||
)
|
||||
// Count + outs diamond
|
||||
if let linescore = game.linescore {
|
||||
DiamondView(
|
||||
balls: linescore.balls ?? 0,
|
||||
strikes: linescore.strikes ?? 0,
|
||||
outs: linescore.outs ?? 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Final Data
|
||||
|
||||
@ViewBuilder
|
||||
private var finalDataSection: some View {
|
||||
if let away = game.awayTeam.score, let home = game.homeTeam.score {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 16) {
|
||||
Text("\(away) - \(home)")
|
||||
.font(liveScoreFont)
|
||||
.foregroundStyle(DS.Colors.onDarkPrimary)
|
||||
.monospacedDigit()
|
||||
|
||||
Text("FINAL")
|
||||
.font(inningFont)
|
||||
.foregroundStyle(DS.Colors.onDarkTertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Scheduled Data
|
||||
|
||||
@ViewBuilder
|
||||
private var scheduledDataSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let pitchers = game.pitchers {
|
||||
HStack(spacing: 10) {
|
||||
if let url = game.awayPitcherHeadshotURL {
|
||||
PitcherHeadshotView(url: url, teamCode: game.awayTeam.code, name: nil, size: pitcherSize)
|
||||
}
|
||||
if let url = game.homePitcherHeadshotURL {
|
||||
PitcherHeadshotView(url: url, teamCode: game.homeTeam.code, name: nil, size: pitcherSize)
|
||||
}
|
||||
Text(pitchers)
|
||||
.font(pitcherFont)
|
||||
.foregroundStyle(DS.Colors.onDarkSecondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 16) {
|
||||
if let record = game.awayTeam.record {
|
||||
Text("\(game.awayTeam.code) \(record)")
|
||||
.font(recordFont)
|
||||
.foregroundStyle(DS.Colors.onDarkTertiary)
|
||||
}
|
||||
if let record = game.homeTeam.record {
|
||||
Text("\(game.homeTeam.code) \(record)")
|
||||
.font(recordFont)
|
||||
.foregroundStyle(DS.Colors.onDarkTertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Subtitle Row
|
||||
// MARK: - Status Badge
|
||||
|
||||
@ViewBuilder
|
||||
private var subtitleRow: some View {
|
||||
HStack(spacing: 16) {
|
||||
if let venue = game.venue {
|
||||
Label(venue, systemImage: "mappin.and.ellipse")
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(.white.opacity(0.5))
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if game.hasStreams {
|
||||
Label("\(game.broadcasts.count) feed\(game.broadcasts.count == 1 ? "" : "s")", systemImage: "tv.fill")
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(DS.Colors.interactive.opacity(0.9))
|
||||
} else if game.isBlackedOut {
|
||||
Label("Blacked Out", systemImage: "eye.slash.fill")
|
||||
.font(subtitleFont)
|
||||
.foregroundStyle(DS.Colors.live.opacity(0.8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Status Chip
|
||||
|
||||
@ViewBuilder
|
||||
private var statusChip: some View {
|
||||
private var statusBadge: some View {
|
||||
switch game.status {
|
||||
case .live(let inning):
|
||||
HStack(spacing: 6) {
|
||||
Circle().fill(DS.Colors.live).frame(width: 8, height: 8)
|
||||
Text(inning ?? "LIVE")
|
||||
.font(chipFont)
|
||||
.font(badgeFont)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.background(DS.Colors.live.opacity(0.2))
|
||||
.padding(.vertical, 7)
|
||||
.background(DS.Colors.live.opacity(0.3))
|
||||
.clipShape(Capsule())
|
||||
|
||||
case .scheduled(let time):
|
||||
Text(time)
|
||||
.font(chipFont)
|
||||
.font(badgeFont)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.background(.white.opacity(0.1))
|
||||
.padding(.vertical, 7)
|
||||
.background(.white.opacity(0.15))
|
||||
.clipShape(Capsule())
|
||||
|
||||
case .final_:
|
||||
Text("FINAL")
|
||||
.font(chipFont)
|
||||
.font(badgeFont)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 8)
|
||||
.background(.white.opacity(0.1))
|
||||
.padding(.vertical, 7)
|
||||
.background(.white.opacity(0.15))
|
||||
.clipShape(Capsule())
|
||||
|
||||
case .unknown:
|
||||
@@ -251,85 +233,95 @@ struct FeaturedGameCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Background
|
||||
// MARK: - Metadata
|
||||
|
||||
@ViewBuilder
|
||||
private var heroBackground: some View {
|
||||
ZStack {
|
||||
// Base
|
||||
Color(red: 0.04, green: 0.05, blue: 0.08)
|
||||
|
||||
// Team color washes
|
||||
HStack(spacing: 0) {
|
||||
awayColor.opacity(0.25)
|
||||
Color.clear
|
||||
homeColor.opacity(0.25)
|
||||
private var metadataLine: some View {
|
||||
HStack(spacing: 14) {
|
||||
if let venue = game.venue {
|
||||
Label(venue, systemImage: "mappin.and.ellipse")
|
||||
.font(metaFont)
|
||||
.foregroundStyle(DS.Colors.onDarkTertiary)
|
||||
}
|
||||
|
||||
// Glow orbs
|
||||
Circle()
|
||||
.fill(awayColor.opacity(0.2))
|
||||
.frame(width: 400, height: 400)
|
||||
.blur(radius: 100)
|
||||
.offset(x: -300, y: -50)
|
||||
|
||||
Circle()
|
||||
.fill(homeColor.opacity(0.2))
|
||||
.frame(width: 400, height: 400)
|
||||
.blur(radius: 100)
|
||||
.offset(x: 300, y: 50)
|
||||
if !game.broadcasts.isEmpty {
|
||||
Text("\(game.broadcasts.count) feed\(game.broadcasts.count == 1 ? "" : "s")")
|
||||
.font(metaFont)
|
||||
.foregroundStyle(DS.Colors.onDarkTertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var borderColor: Color {
|
||||
if game.isLive { return DS.Colors.live.opacity(0.35) }
|
||||
if game.hasStreams { return DS.Colors.interactive.opacity(0.2) }
|
||||
return .white.opacity(0.06)
|
||||
// MARK: - Stadium Background
|
||||
|
||||
@ViewBuilder
|
||||
private var stadiumBackground: some View {
|
||||
if let url = stadiumImageURL {
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
case .failure:
|
||||
fallbackBackground
|
||||
default:
|
||||
fallbackBackground
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fallbackBackground
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var fallbackBackground: some View {
|
||||
ZStack {
|
||||
Color(red: 0.08, green: 0.08, blue: 0.10)
|
||||
LinearGradient(
|
||||
colors: [awayColor.opacity(0.3), homeColor.opacity(0.3)],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Platform Sizing
|
||||
|
||||
#if os(tvOS)
|
||||
private var heroRadius: CGFloat { 28 }
|
||||
private var heroPadH: CGFloat { 50 }
|
||||
private var heroPadV: CGFloat { 40 }
|
||||
private var heroSpacing: CGFloat { 24 }
|
||||
private var logoSize: CGFloat { 120 }
|
||||
private var scoreCenterWidth: CGFloat { 280 }
|
||||
private var teamInfoGap: CGFloat { 12 }
|
||||
private var pitcherHeadshotSize: CGFloat { 36 }
|
||||
|
||||
private var mainScoreFont: Font { .system(size: 96, weight: .black, design: .rounded) }
|
||||
private var statusTimeFont: Font { .system(size: 48, weight: .black, design: .rounded) }
|
||||
private var inningFont: Font { .system(size: 24, weight: .bold, design: .rounded) }
|
||||
private var teamCodeFont: Font { .system(size: 38, weight: .black, design: .rounded) }
|
||||
private var teamNameFont: Font { .system(size: 24, weight: .bold) }
|
||||
private var heroHeight: CGFloat { 500 }
|
||||
private var heroPadH: CGFloat { 60 }
|
||||
private var heroPadV: CGFloat { 50 }
|
||||
private var heroContentSpacing: CGFloat { 18 }
|
||||
private var pitcherSize: CGFloat { 40 }
|
||||
private var matchupFont: Font { .system(size: 52, weight: .black, design: .rounded) }
|
||||
private var subtitleFont: Font { .system(size: 26, weight: .semibold) }
|
||||
private var liveScoreFont: Font { .system(size: 72, weight: .black, design: .rounded) }
|
||||
private var inningFont: Font { .system(size: 28, weight: .bold, design: .rounded) }
|
||||
private var badgeFont: Font { .system(size: 22, weight: .black, design: .rounded) }
|
||||
private var ctaFont: Font { .system(size: 24, weight: .bold) }
|
||||
private var ctaPadH: CGFloat { 32 }
|
||||
private var ctaPadV: CGFloat { 14 }
|
||||
private var pitcherFont: Font { .system(size: 24, weight: .semibold) }
|
||||
private var recordFont: Font { .system(size: 22, weight: .bold, design: .monospaced) }
|
||||
private var subtitleFont: Font { .system(size: 22, weight: .medium) }
|
||||
private var chipFont: Font { .system(size: 22, weight: .black, design: .rounded) }
|
||||
private var labelFont: Font { .system(size: 22, weight: .bold, design: .rounded) }
|
||||
private var probableLabelFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
|
||||
private var pitcherNameFont: Font { .system(size: 22, weight: .semibold) }
|
||||
private var metaFont: Font { .system(size: 22, weight: .medium) }
|
||||
#else
|
||||
private var heroRadius: CGFloat { 22 }
|
||||
private var heroPadH: CGFloat { 24 }
|
||||
private var heroPadV: CGFloat { 24 }
|
||||
private var heroSpacing: CGFloat { 16 }
|
||||
private var logoSize: CGFloat { 64 }
|
||||
private var scoreCenterWidth: CGFloat { 160 }
|
||||
private var teamInfoGap: CGFloat { 8 }
|
||||
private var pitcherHeadshotSize: CGFloat { 28 }
|
||||
|
||||
private var mainScoreFont: Font { .system(size: 56, weight: .black, design: .rounded) }
|
||||
private var statusTimeFont: Font { .system(size: 32, weight: .black, design: .rounded) }
|
||||
private var inningFont: Font { .system(size: 16, weight: .bold, design: .rounded) }
|
||||
private var teamCodeFont: Font { .system(size: 24, weight: .black, design: .rounded) }
|
||||
private var teamNameFont: Font { .system(size: 16, weight: .bold) }
|
||||
private var recordFont: Font { .system(size: 13, weight: .bold, design: .monospaced) }
|
||||
private var subtitleFont: Font { .system(size: 14, weight: .medium) }
|
||||
private var chipFont: Font { .system(size: 14, weight: .black, design: .rounded) }
|
||||
private var labelFont: Font { .system(size: 13, weight: .bold, design: .rounded) }
|
||||
private var probableLabelFont: Font { .system(size: 11, weight: .bold, design: .rounded) }
|
||||
private var pitcherNameFont: Font { .system(size: 14, weight: .semibold) }
|
||||
private var heroHeight: CGFloat { 320 }
|
||||
private var heroPadH: CGFloat { 28 }
|
||||
private var heroPadV: CGFloat { 28 }
|
||||
private var heroContentSpacing: CGFloat { 12 }
|
||||
private var pitcherSize: CGFloat { 30 }
|
||||
private var matchupFont: Font { .system(size: 32, weight: .black, design: .rounded) }
|
||||
private var subtitleFont: Font { .system(size: 16, weight: .semibold) }
|
||||
private var liveScoreFont: Font { .system(size: 48, weight: .black, design: .rounded) }
|
||||
private var inningFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
|
||||
private var badgeFont: Font { .system(size: 14, weight: .black, design: .rounded) }
|
||||
private var ctaFont: Font { .system(size: 16, weight: .bold) }
|
||||
private var ctaPadH: CGFloat { 22 }
|
||||
private var ctaPadV: CGFloat { 10 }
|
||||
private var pitcherFont: Font { .system(size: 15, weight: .semibold) }
|
||||
private var recordFont: Font { .system(size: 14, weight: .bold, design: .monospaced) }
|
||||
private var metaFont: Font { .system(size: 14, weight: .medium) }
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user