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:
Trey t
2026-04-12 16:44:25 -05:00
parent 870fbcb844
commit 588b42ffed
12 changed files with 2004 additions and 744 deletions

View File

@@ -10,22 +10,21 @@ struct FeaturedGameCard: View {
private var awayPitcherName: String? {
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 heroImageURL: URL? {
// Prefer pitcher action hero photo big, dramatic, like a cast photo
if let pitcherId = game.homePitcherId {
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_1200,q_auto:best/v1/people/\(pitcherId)/action/hero/current")
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_1400,q_auto:best/v1/people/\(pitcherId)/action/hero/current")
}
if let pitcherId = game.awayPitcherId {
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_1200,q_auto:best/v1/people/\(pitcherId)/action/hero/current")
return URL(string: "https://img.mlbstatic.com/mlb-photos/image/upload/w_1400,q_auto:best/v1/people/\(pitcherId)/action/hero/current")
}
// Fall back to large team logo
if let teamId = game.homeTeam.teamId {
return URL(string: "https://midfield.mlbstatic.com/v1/team/\(teamId)/spots/800")
return URL(string: "https://midfield.mlbstatic.com/v1/team/\(teamId)/spots/1200")
}
return nil
}
@@ -33,116 +32,214 @@ struct FeaturedGameCard: View {
var body: some View {
Button(action: onSelect) {
ZStack(alignment: .topLeading) {
// White/cream base
DS.Colors.panelFill
backgroundLayer
// Stadium image on the right side, fading into white on the left
HStack(spacing: 0) {
Spacer()
ZStack(alignment: .leading) {
heroImage
.frame(width: imageWidth)
// White fade from left edge of image
LinearGradient(
colors: [
DS.Colors.panelFill,
DS.Colors.panelFill.opacity(0.8),
DS.Colors.panelFill.opacity(0.3),
.clear
],
startPoint: .leading,
endPoint: .trailing
)
.frame(width: fadeWidth)
}
}
// Text content on the left
VStack(alignment: .leading, spacing: contentSpacing) {
// Status badge
statusBadge
headerRow
// Giant matchup title thin "away" bold "home"
VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 0) {
Text(game.awayTeam.displayName)
.font(titleThinFont)
.foregroundStyle(DS.Colors.textSecondary)
Text(" vs ")
.font(titleThinFont)
.foregroundStyle(DS.Colors.textTertiary)
HStack(alignment: .bottom, spacing: 28) {
VStack(alignment: .leading, spacing: 16) {
scoreboardRow(team: game.awayTeam, isLeading: isWinning(away: true))
scoreboardRow(team: game.homeTeam, isLeading: isWinning(away: false))
}
Text(game.homeTeam.displayName)
.font(titleBoldFont)
.foregroundStyle(DS.Colors.interactive)
.frame(maxWidth: .infinity, alignment: .leading)
detailPanel
.frame(width: detailPanelWidth, alignment: .trailing)
}
// Metadata line
metadataLine
// Live score or description
if game.isLive {
liveSection
} else if game.isFinal {
finalSection
} else {
scheduledSection
}
// CTA buttons
HStack(spacing: 14) {
if game.hasStreams {
Label("Watch Now", systemImage: "play.fill")
.font(ctaFont)
.foregroundStyle(DS.Colors.interactive)
.padding(.horizontal, ctaPadH)
.padding(.vertical, ctaPadV)
.overlay(
Capsule().strokeBorder(DS.Colors.interactive, lineWidth: 2)
)
}
Image(systemName: "plus")
.font(ctaFont)
.foregroundStyle(DS.Colors.textTertiary)
.padding(ctaPadV)
.overlay(
Circle().strokeBorder(DS.Colors.textQuaternary, lineWidth: 1.5)
)
}
insightStrip
}
.padding(.horizontal, heroPadH)
.padding(.vertical, heroPadV)
.frame(maxWidth: textAreaWidth, alignment: .topLeading)
}
.frame(maxWidth: .infinity)
.frame(height: heroHeight)
.clipShape(RoundedRectangle(cornerRadius: heroRadius, style: .continuous))
.shadow(color: .black.opacity(0.08), radius: 30, y: 12)
.overlay(
RoundedRectangle(cornerRadius: heroRadius, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)
)
.shadow(color: .black.opacity(0.32), radius: 36, y: 18)
}
.platformCardStyle()
}
// MARK: - Live Section
private var backgroundLayer: some View {
ZStack {
RoundedRectangle(cornerRadius: heroRadius, style: .continuous)
.fill(
LinearGradient(
colors: [
DS.Colors.panelFill,
DS.Colors.backgroundElevated,
Color.black.opacity(0.94),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
@ViewBuilder
private var liveSection: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .firstTextBaseline, spacing: 16) {
if let away = game.awayTeam.score, let home = game.homeTeam.score {
Text("\(away) - \(home)")
.font(scoreFont)
.foregroundStyle(DS.Colors.textPrimary)
.monospacedDigit()
.contentTransition(.numericText())
// Team color wash gradient instead of blur for performance
LinearGradient(
colors: [
awayColor.opacity(0.2),
.clear,
homeColor.opacity(0.18),
],
startPoint: .leading,
endPoint: .trailing
)
heroImage
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
LinearGradient(
colors: [
Color.black.opacity(0.85),
Color.black.opacity(0.48),
Color.black.opacity(0.22),
],
startPoint: .leading,
endPoint: .trailing
)
}
if let inning = game.currentInningDisplay {
Text(inning)
.font(inningFont)
.foregroundStyle(DS.Colors.live)
LinearGradient(
colors: [
Color.black.opacity(0.16),
Color.black.opacity(0.52),
],
startPoint: .top,
endPoint: .bottom
)
}
}
private var headerRow: some View {
HStack(alignment: .top, spacing: 20) {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 10) {
statusBadge
if let gameType = game.gameType, !gameType.isEmpty {
metaBadge(gameType.uppercased(), tint: DS.Colors.media)
}
if game.hasStreams {
metaBadge("\(game.broadcasts.count) FEED\(game.broadcasts.count == 1 ? "" : "S")", tint: DS.Colors.interactive)
}
}
Text("Featured Matchup")
.font(labelFont)
.foregroundStyle(DS.Colors.textTertiary)
.tracking(1.8)
Text(game.displayTitle)
.font(titleFont)
.foregroundStyle(DS.Colors.onDarkPrimary)
.lineLimit(2)
}
Spacer(minLength: 16)
HStack(spacing: 12) {
if let venue = game.venue {
summaryTag(value: venue, systemImage: "mappin.and.ellipse")
}
if game.isBlackedOut {
summaryTag(value: "Blackout", systemImage: "eye.slash.fill")
} else if game.hasStreams {
summaryTag(value: "Watch Now", systemImage: "play.fill")
}
}
}
}
private func scoreboardRow(team: TeamInfo, isLeading: Bool) -> some View {
HStack(spacing: 16) {
TeamLogoView(team: team, size: logoSize)
VStack(alignment: .leading, spacing: 5) {
Text(team.code)
.font(codeFont)
.foregroundStyle(.white)
Text(team.displayName)
.font(nameFont)
.foregroundStyle(DS.Colors.onDarkSecondary)
HStack(spacing: 10) {
if let record = team.record {
Text(record)
.font(metadataFont)
.foregroundStyle(DS.Colors.onDarkSecondary)
}
if let summary = team.standingSummary {
Text(summary)
.font(metadataFont)
.foregroundStyle(DS.Colors.onDarkTertiary)
.lineLimit(1)
}
}
}
Spacer(minLength: 8)
Text(team.score.map(String.init) ?? "")
.font(scoreFont)
.foregroundStyle(isLeading ? .white : DS.Colors.onDarkSecondary)
.monospacedDigit()
}
.padding(.horizontal, rowPadH)
.padding(.vertical, rowPadV)
.background(
RoundedRectangle(cornerRadius: 22, style: .continuous)
.fill(Color.black.opacity(isLeading ? 0.34 : 0.22))
.overlay {
RoundedRectangle(cornerRadius: 22, style: .continuous)
.strokeBorder(Color.white.opacity(isLeading ? 0.10 : 0.06), lineWidth: 1)
}
)
}
@ViewBuilder
private var detailPanel: some View {
VStack(alignment: .leading, spacing: 16) {
switch game.status {
case .live:
livePanel
case .final_:
finalPanel
case .scheduled:
scheduledPanel
case .unknown:
statusFallbackPanel
}
}
.padding(detailPanelPad)
.background(
RoundedRectangle(cornerRadius: 26, style: .continuous)
.fill(Color.black.opacity(0.34))
.overlay {
RoundedRectangle(cornerRadius: 26, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)
}
)
}
private var livePanel: some View {
VStack(alignment: .leading, spacing: 14) {
Text("Live Situation")
.font(panelLabelFont)
.foregroundStyle(DS.Colors.textTertiary)
Text(game.currentInningDisplay ?? "Live")
.font(panelValueFont)
.foregroundStyle(.white)
if let linescore = game.linescore {
DiamondView(
@@ -150,93 +247,172 @@ struct FeaturedGameCard: View {
strikes: linescore.strikes ?? 0,
outs: linescore.outs ?? 0
)
if let awayRuns = linescore.teams?.away?.runs,
let homeRuns = linescore.teams?.home?.runs,
let awayHits = linescore.teams?.away?.hits,
let homeHits = linescore.teams?.home?.hits {
HStack(spacing: 14) {
detailMetric(label: game.awayTeam.code, value: "\(awayRuns)R / \(awayHits)H")
detailMetric(label: game.homeTeam.code, value: "\(homeRuns)R / \(homeHits)H")
}
}
}
}
}
// MARK: - Final Section
private var finalPanel: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Final")
.font(panelLabelFont)
.foregroundStyle(DS.Colors.textTertiary)
@ViewBuilder
private var finalSection: some View {
if let away = game.awayTeam.score, let home = game.homeTeam.score {
HStack(spacing: 12) {
Text("\(away) - \(home)")
.font(scoreFont)
.foregroundStyle(DS.Colors.textPrimary)
.monospacedDigit()
Text("FINAL")
.font(inningFont)
.foregroundStyle(DS.Colors.textTertiary)
Text(game.scoreDisplay ?? "Complete")
.font(panelValueFont)
.foregroundStyle(.white)
Text("Box score, play timeline, and highlights are ready in Game Center.")
.font(panelBodyFont)
.foregroundStyle(DS.Colors.onDarkSecondary)
.fixedSize(horizontal: false, vertical: true)
}
}
private var scheduledPanel: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Starting Pitchers")
.font(panelLabelFont)
.foregroundStyle(DS.Colors.textTertiary)
Text(pitcherMatchupText)
.font(panelValueFont)
.foregroundStyle(.white)
.lineLimit(3)
if let startTime = game.startTime {
Text("First pitch \(startTime)")
.font(panelBodyFont)
.foregroundStyle(DS.Colors.onDarkSecondary)
}
}
}
// MARK: - Scheduled Section
private var statusFallbackPanel: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Game State")
.font(panelLabelFont)
.foregroundStyle(DS.Colors.textTertiary)
@ViewBuilder
private var scheduledSection: some View {
Text(game.status.label.isEmpty ? "Awaiting update" : game.status.label)
.font(panelValueFont)
.foregroundStyle(.white)
}
}
private var insightStrip: some View {
HStack(spacing: 14) {
insightCard(
title: "Pitching",
value: pitcherInsightText,
accent: DS.Colors.media
)
insightCard(
title: "Venue",
value: game.venue ?? "TBD",
accent: DS.Colors.interactive
)
insightCard(
title: "Feeds",
value: game.isBlackedOut ? "Blackout" : "\(game.broadcasts.count) available",
accent: game.isBlackedOut ? DS.Colors.live : DS.Colors.positive
)
}
}
private func insightCard(title: String, value: String, accent: Color) -> some View {
VStack(alignment: .leading, spacing: 6) {
if let pitchers = game.pitchers {
Text(pitchers)
.font(descFont)
.foregroundStyle(DS.Colors.textSecondary)
.lineLimit(2)
}
Text(title)
.font(insightTitleFont)
.foregroundStyle(DS.Colors.textTertiary)
Text(value)
.font(insightValueFont)
.foregroundStyle(.white)
.lineLimit(2)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(
RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(accent.opacity(0.14))
.overlay {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.strokeBorder(accent.opacity(0.20), lineWidth: 1)
}
)
}
private func detailMetric(label: String, value: String) -> some View {
VStack(alignment: .leading, spacing: 4) {
Text(label)
.font(insightTitleFont)
.foregroundStyle(DS.Colors.textTertiary)
Text(value)
.font(insightValueFont)
.foregroundStyle(.white)
}
}
// MARK: - Status Badge
private func summaryTag(value: String, systemImage: String) -> some View {
Label(value, systemImage: systemImage)
.font(summaryFont)
.foregroundStyle(.white.opacity(0.92))
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(
Capsule()
.fill(Color.black.opacity(0.28))
.overlay {
Capsule()
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)
}
)
}
private func metaBadge(_ value: String, tint: Color) -> some View {
Text(value)
.font(badgeFont)
.foregroundStyle(tint)
.padding(.horizontal, 14)
.padding(.vertical, 9)
.background(
Capsule()
.fill(tint.opacity(0.14))
.overlay {
Capsule()
.strokeBorder(tint.opacity(0.22), lineWidth: 1)
}
)
}
@ViewBuilder
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(badgeFont)
.foregroundStyle(DS.Colors.live)
}
metaBadge(inning?.uppercased() ?? "LIVE", tint: DS.Colors.live)
case .scheduled(let time):
Text(time)
.font(badgeFont)
.foregroundStyle(DS.Colors.textTertiary)
metaBadge(time.uppercased(), tint: DS.Colors.warning)
case .final_:
Text("FINAL")
.font(badgeFont)
.foregroundStyle(DS.Colors.textTertiary)
metaBadge("FINAL", tint: DS.Colors.positive)
case .unknown:
EmptyView()
metaBadge("PENDING", tint: DS.Colors.textTertiary)
}
}
// MARK: - Metadata Line
@ViewBuilder
private var metadataLine: some View {
HStack(spacing: metaSeparatorWidth) {
if let venue = game.venue {
Text(venue)
}
if let record = game.awayTeam.record {
Text("\(game.awayTeam.code) \(record)")
}
if let record = game.homeTeam.record {
Text("\(game.homeTeam.code) \(record)")
}
if !game.broadcasts.isEmpty {
Text("\(game.broadcasts.count) feed\(game.broadcasts.count == 1 ? "" : "s")")
}
}
.font(metaFont)
.foregroundStyle(DS.Colors.textTertiary)
}
// MARK: - Stadium Image
@ViewBuilder
private var heroImage: some View {
if let url = heroImageURL {
@@ -246,8 +422,6 @@ struct FeaturedGameCard: View {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: heroHeight)
.clipped()
default:
fallbackImage
}
@@ -257,79 +431,88 @@ struct FeaturedGameCard: View {
}
}
@ViewBuilder
private var fallbackImage: some View {
ZStack {
// Rich team color gradient
LinearGradient(
colors: [
awayColor.opacity(0.4),
homeColor.opacity(0.6),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
// Large prominent team logos
HStack(spacing: fallbackLogoGap) {
TeamLogoView(team: game.awayTeam, size: fallbackLogoSize)
.shadow(color: .black.opacity(0.2), radius: 12, y: 4)
Text("vs")
.font(.system(size: fallbackLogoSize * 0.3, weight: .light))
.foregroundStyle(.white.opacity(0.5))
TeamLogoView(team: game.homeTeam, size: fallbackLogoSize)
.shadow(color: .black.opacity(0.2), radius: 12, y: 4)
}
}
LinearGradient(
colors: [
awayColor.opacity(0.32),
homeColor.opacity(0.28),
Color.clear,
],
startPoint: .leading,
endPoint: .trailing
)
}
// MARK: - Platform Sizing
private var pitcherMatchupText: String {
if let awayPitcherName, let homePitcherName {
return "\(awayPitcherName)\nvs \(homePitcherName)"
}
return game.pitchers ?? "Pitchers pending"
}
private var pitcherInsightText: String {
if let awayPitcherName, let homePitcherName {
return "\(awayPitcherName) vs \(homePitcherName)"
}
return game.pitchers ?? "Awaiting starters"
}
private func isWinning(away: Bool) -> Bool {
guard let awayScore = game.awayTeam.score, let homeScore = game.homeTeam.score else {
return false
}
return away ? awayScore > homeScore : homeScore > awayScore
}
#if os(tvOS)
private var heroHeight: CGFloat { 480 }
private var heroRadius: CGFloat { 28 }
private var heroPadH: CGFloat { 60 }
private var heroPadV: CGFloat { 50 }
private var contentSpacing: CGFloat { 16 }
private var imageWidth: CGFloat { 900 }
private var fadeWidth: CGFloat { 400 }
private var textAreaWidth: CGFloat { 700 }
private var metaSeparatorWidth: CGFloat { 18 }
private var fallbackLogoSize: CGFloat { 120 }
private var fallbackLogoGap: CGFloat { 40 }
private var titleThinFont: Font { .system(size: 48, weight: .light) }
private var titleBoldFont: Font { .system(size: 52, weight: .black, design: .rounded) }
private var scoreFont: Font { .system(size: 64, weight: .black, design: .rounded) }
private var inningFont: Font { .system(size: 28, weight: .bold, design: .rounded) }
private var badgeFont: Font { .system(size: 22, weight: .bold, design: .rounded) }
private var metaFont: Font { .system(size: 22, weight: .medium) }
private var descFont: Font { .system(size: 24, weight: .medium) }
private var ctaFont: Font { .system(size: 24, weight: .bold) }
private var ctaPadH: CGFloat { 32 }
private var ctaPadV: CGFloat { 14 }
private var heroHeight: CGFloat { 470 }
private var heroRadius: CGFloat { 34 }
private var heroPadH: CGFloat { 36 }
private var heroPadV: CGFloat { 34 }
private var detailPanelWidth: CGFloat { 360 }
private var detailPanelPad: CGFloat { 26 }
private var detailPanelWidthCompact: CGFloat { 320 }
private var contentSpacing: CGFloat { 26 }
private var logoSize: CGFloat { 56 }
private var rowPadH: CGFloat { 22 }
private var rowPadV: CGFloat { 18 }
private var titleFont: Font { .system(size: 52, weight: .black, design: .rounded) }
private var labelFont: Font { .system(size: 15, weight: .black, design: .rounded) }
private var codeFont: Font { .system(size: 22, weight: .black, design: .rounded) }
private var nameFont: Font { .system(size: 28, weight: .bold, design: .rounded) }
private var metadataFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
private var scoreFont: Font { .system(size: 60, weight: .black, design: .rounded).monospacedDigit() }
private var badgeFont: Font { .system(size: 13, weight: .black, design: .rounded) }
private var summaryFont: Font { .system(size: 16, weight: .bold, design: .rounded) }
private var panelLabelFont: Font { .system(size: 14, weight: .black, design: .rounded) }
private var panelValueFont: Font { .system(size: 28, weight: .black, design: .rounded) }
private var panelBodyFont: Font { .system(size: 18, weight: .semibold) }
private var insightTitleFont: Font { .system(size: 13, weight: .black, design: .rounded) }
private var insightValueFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
#else
private var heroHeight: CGFloat { 340 }
private var heroRadius: CGFloat { 22 }
private var heroPadH: CGFloat { 28 }
private var heroPadV: CGFloat { 28 }
private var contentSpacing: CGFloat { 10 }
private var imageWidth: CGFloat { 400 }
private var fadeWidth: CGFloat { 200 }
private var textAreaWidth: CGFloat { 350 }
private var metaSeparatorWidth: CGFloat { 12 }
private var fallbackLogoSize: CGFloat { 60 }
private var fallbackLogoGap: CGFloat { 20 }
private var titleThinFont: Font { .system(size: 28, weight: .light) }
private var titleBoldFont: Font { .system(size: 32, weight: .black, design: .rounded) }
private var scoreFont: Font { .system(size: 40, weight: .black, design: .rounded) }
private var inningFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
private var badgeFont: Font { .system(size: 14, weight: .bold, design: .rounded) }
private var metaFont: Font { .system(size: 14, weight: .medium) }
private var descFont: Font { .system(size: 15, weight: .medium) }
private var ctaFont: Font { .system(size: 16, weight: .bold) }
private var ctaPadH: CGFloat { 22 }
private var ctaPadV: CGFloat { 10 }
private var heroRadius: CGFloat { 26 }
private var heroPadH: CGFloat { 22 }
private var heroPadV: CGFloat { 22 }
private var detailPanelWidth: CGFloat { 250 }
private var detailPanelPad: CGFloat { 18 }
private var detailPanelWidthCompact: CGFloat { 240 }
private var contentSpacing: CGFloat { 18 }
private var logoSize: CGFloat { 36 }
private var rowPadH: CGFloat { 14 }
private var rowPadV: CGFloat { 12 }
private var titleFont: Font { .system(size: 30, weight: .black, design: .rounded) }
private var labelFont: Font { .system(size: 11, weight: .black, design: .rounded) }
private var codeFont: Font { .system(size: 15, weight: .black, design: .rounded) }
private var nameFont: Font { .system(size: 18, weight: .bold, design: .rounded) }
private var metadataFont: Font { .system(size: 12, weight: .bold, design: .rounded) }
private var scoreFont: Font { .system(size: 32, weight: .black, design: .rounded).monospacedDigit() }
private var badgeFont: Font { .system(size: 10, weight: .black, design: .rounded) }
private var summaryFont: Font { .system(size: 11, weight: .bold, design: .rounded) }
private var panelLabelFont: Font { .system(size: 10, weight: .black, design: .rounded) }
private var panelValueFont: Font { .system(size: 18, weight: .black, design: .rounded) }
private var panelBodyFont: Font { .system(size: 13, weight: .semibold) }
private var insightTitleFont: Font { .system(size: 10, weight: .black, design: .rounded) }
private var insightValueFont: Font { .system(size: 12, weight: .bold, design: .rounded) }
#endif
}