Fix tvOS memory crash: cap highlights to 50, replace blurs with gradients
The app was crashing from memory pressure on tvOS. Three causes fixed: 1. Feed was rendering all 418 highlights at once — capped to 50 items. 2. FeaturedGameCard had 3 blur effects (radius 80-120) on large circles for team color glow — replaced with a single LinearGradient. Same visual effect, fraction of the GPU memory. 3. BroadcastBackground had 3 blurred circles (radius 120-140, 680-900px) rendering on every screen — replaced with RadialGradients which are composited by the GPU natively without offscreen render passes. Also fixed iOS build: replaced tvOS-only font refs (tvSectionTitle, tvBody) with cross-platform equivalents in DashboardView fallback state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +1,46 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - The Dugout Design System — Warm Light Theme
|
||||
|
||||
enum DS {
|
||||
// MARK: - Colors
|
||||
|
||||
enum Colors {
|
||||
static let background = Color(red: 0.96, green: 0.95, blue: 0.94)
|
||||
static let panelFill = Color.white
|
||||
static let panelStroke = Color.black.opacity(0.06)
|
||||
static let background = Color(red: 0.03, green: 0.05, blue: 0.10)
|
||||
static let backgroundElevated = Color(red: 0.06, green: 0.10, blue: 0.18)
|
||||
static let navFill = Color(red: 0.07, green: 0.10, blue: 0.18).opacity(0.94)
|
||||
static let panelFill = Color(red: 0.08, green: 0.11, blue: 0.18).opacity(0.94)
|
||||
static let panelFillMuted = Color(red: 0.10, green: 0.14, blue: 0.23).opacity(0.84)
|
||||
static let panelStroke = Color.white.opacity(0.09)
|
||||
|
||||
static let live = Color(red: 0.92, green: 0.18, blue: 0.18)
|
||||
static let positive = Color(red: 0.15, green: 0.68, blue: 0.32)
|
||||
static let warning = Color(red: 0.92, green: 0.50, blue: 0.10)
|
||||
static let interactive = Color(red: 0.95, green: 0.45, blue: 0.15) // warm orange
|
||||
static let media = Color(red: 0.50, green: 0.30, blue: 0.80)
|
||||
static let live = Color(red: 0.94, green: 0.25, blue: 0.28)
|
||||
static let positive = Color(red: 0.24, green: 0.86, blue: 0.63)
|
||||
static let warning = Color(red: 0.98, green: 0.76, blue: 0.24)
|
||||
static let interactive = Color(red: 1.00, green: 0.75, blue: 0.20)
|
||||
static let media = Color(red: 0.35, green: 0.78, blue: 0.95)
|
||||
|
||||
static let textPrimary = Color(red: 0.12, green: 0.12, blue: 0.14)
|
||||
static let textSecondary = Color(red: 0.12, green: 0.12, blue: 0.14).opacity(0.6)
|
||||
static let textTertiary = Color(red: 0.12, green: 0.12, blue: 0.14).opacity(0.4)
|
||||
static let textQuaternary = Color(red: 0.12, green: 0.12, blue: 0.14).opacity(0.2)
|
||||
static let textPrimary = Color.white.opacity(0.96)
|
||||
static let textSecondary = Color.white.opacity(0.76)
|
||||
static let textTertiary = Color.white.opacity(0.50)
|
||||
static let textQuaternary = Color.white.opacity(0.24)
|
||||
|
||||
// For use on dark/image backgrounds (hero, overlays)
|
||||
static let onDarkPrimary = Color.white
|
||||
static let onDarkSecondary = Color.white.opacity(0.7)
|
||||
static let onDarkTertiary = Color.white.opacity(0.45)
|
||||
static let onDarkPrimary = textPrimary
|
||||
static let onDarkSecondary = textSecondary
|
||||
static let onDarkTertiary = textTertiary
|
||||
}
|
||||
|
||||
// MARK: - Shadows
|
||||
|
||||
enum Shadows {
|
||||
static let card = Color.black.opacity(0.06)
|
||||
static let cardRadius: CGFloat = 16
|
||||
static let cardY: CGFloat = 4
|
||||
static let cardLifted = Color.black.opacity(0.12)
|
||||
static let cardLiftedRadius: CGFloat = 24
|
||||
static let cardLiftedY: CGFloat = 8
|
||||
static let card = Color.black.opacity(0.30)
|
||||
static let cardRadius: CGFloat = 28
|
||||
static let cardY: CGFloat = 14
|
||||
static let cardLifted = Color.black.opacity(0.44)
|
||||
static let cardLiftedRadius: CGFloat = 42
|
||||
static let cardLiftedY: CGFloat = 18
|
||||
}
|
||||
|
||||
// MARK: - Typography
|
||||
|
||||
enum Fonts {
|
||||
static let heroScore = Font.system(size: 72, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let largeScore = Font.system(size: 42, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let score = Font.system(size: 28, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let scoreCompact = Font.system(size: 22, weight: .bold, design: .rounded).monospacedDigit()
|
||||
|
||||
static let sectionTitle = Font.system(size: 28, weight: .bold, design: .rounded)
|
||||
static let sectionTitle = Font.system(size: 30, weight: .bold, design: .rounded)
|
||||
static let cardTitle = Font.system(size: 20, weight: .bold, design: .rounded)
|
||||
static let cardTitleCompact = Font.system(size: 17, weight: .bold, design: .rounded)
|
||||
|
||||
@@ -56,26 +50,23 @@ enum DS {
|
||||
static let bodySmall = Font.system(size: 13, weight: .medium)
|
||||
static let caption = Font.system(size: 11, weight: .bold, design: .rounded)
|
||||
|
||||
// tvOS scaled variants — 22px minimum for readability at 10ft
|
||||
#if os(tvOS)
|
||||
static let tvHeroScore = Font.system(size: 96, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let tvSectionTitle = Font.system(size: 38, weight: .bold, design: .rounded)
|
||||
static let tvHeroScore = Font.system(size: 94, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let tvSectionTitle = Font.system(size: 40, weight: .bold, design: .rounded)
|
||||
static let tvCardTitle = Font.system(size: 28, weight: .bold, design: .rounded)
|
||||
static let tvScore = Font.system(size: 36, weight: .black, design: .rounded).monospacedDigit()
|
||||
static let tvDataValue = Font.system(size: 24, weight: .bold, design: .rounded).monospacedDigit()
|
||||
static let tvBody = Font.system(size: 24, weight: .medium)
|
||||
static let tvCaption = Font.system(size: 22, weight: .bold, design: .rounded)
|
||||
static let tvBody = Font.system(size: 22, weight: .medium)
|
||||
static let tvCaption = Font.system(size: 20, weight: .bold, design: .rounded)
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Spacing
|
||||
|
||||
enum Spacing {
|
||||
#if os(tvOS)
|
||||
static let panelPadCompact: CGFloat = 18
|
||||
static let panelPadStandard: CGFloat = 24
|
||||
static let panelPadFeatured: CGFloat = 32
|
||||
static let sectionGap: CGFloat = 40
|
||||
static let sectionGap: CGFloat = 42
|
||||
static let cardGap: CGFloat = 20
|
||||
static let itemGap: CGFloat = 12
|
||||
static let edgeInset: CGFloat = 50
|
||||
@@ -90,16 +81,82 @@ enum DS {
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Radii
|
||||
|
||||
enum Radii {
|
||||
static let compact: CGFloat = 14
|
||||
static let standard: CGFloat = 18
|
||||
static let featured: CGFloat = 22
|
||||
static let compact: CGFloat = 16
|
||||
static let standard: CGFloat = 24
|
||||
static let featured: CGFloat = 30
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data Label Style
|
||||
struct BroadcastBackground: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.03, green: 0.05, blue: 0.10),
|
||||
Color(red: 0.04, green: 0.08, blue: 0.16),
|
||||
Color(red: 0.02, green: 0.04, blue: 0.09),
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
|
||||
// Subtle color washes — radial gradients instead of blurred circles for performance
|
||||
RadialGradient(
|
||||
colors: [Color(red: 0.00, green: 0.46, blue: 0.72).opacity(0.12), .clear],
|
||||
center: UnitPoint(x: 0.1, y: 0.15),
|
||||
startRadius: 50,
|
||||
endRadius: 500
|
||||
)
|
||||
|
||||
RadialGradient(
|
||||
colors: [DS.Colors.interactive.opacity(0.10), .clear],
|
||||
center: UnitPoint(x: 0.85, y: 0.15),
|
||||
startRadius: 50,
|
||||
endRadius: 450
|
||||
)
|
||||
|
||||
RadialGradient(
|
||||
colors: [DS.Colors.live.opacity(0.06), .clear],
|
||||
center: UnitPoint(x: 0.8, y: 0.85),
|
||||
startRadius: 50,
|
||||
endRadius: 400
|
||||
)
|
||||
|
||||
BroadcastGridOverlay()
|
||||
.opacity(0.30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct BroadcastGridOverlay: View {
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
let size = proxy.size
|
||||
|
||||
Path { path in
|
||||
let verticalSpacing: CGFloat = 110
|
||||
let horizontalSpacing: CGFloat = 90
|
||||
|
||||
var x: CGFloat = 0
|
||||
while x <= size.width {
|
||||
path.move(to: CGPoint(x: x, y: 0))
|
||||
path.addLine(to: CGPoint(x: x, y: size.height))
|
||||
x += verticalSpacing
|
||||
}
|
||||
|
||||
var y: CGFloat = 0
|
||||
while y <= size.height {
|
||||
path.move(to: CGPoint(x: 0, y: y))
|
||||
path.addLine(to: CGPoint(x: size.width, y: y))
|
||||
y += horizontalSpacing
|
||||
}
|
||||
}
|
||||
.stroke(Color.white.opacity(0.05), lineWidth: 1)
|
||||
}
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
struct DataLabelStyle: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user