import SwiftUI import OSLog private let dashboardLogger = Logger(subsystem: "com.treyt.mlbTVOS", category: "Dashboard") private func logDashboard(_ message: String) { dashboardLogger.debug("\(message, privacy: .public)") print("[Dashboard] \(message)") } enum SpecialPlaybackChannelConfig { static let werkoutNSFWStreamID = "WKNSFW" static let werkoutNSFWTitle = "Werkout NSFW" static let werkoutNSFWSubtitle = "Authenticated OF media feed" static let werkoutNSFWFeedURLString = "https://ofapp.treytartt.com/api/media?users=tabatachang,werkout,kayla-lauren,ray-mattos,josie-hamming-2,dani-speegle-2&type=video" static let werkoutNSFWCookie = "ofapp_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc3NDY0MjEyNiwiZXhwIjoxNzc3MjM0MTI2fQ.RdYGvyBPbR6tmK0kaEVhSnIVW6TZlm3Ef42Lxpvl_oQ" static let werkoutNSFWTeamCode = "WK" static var werkoutNSFWHeaders: [String: String] { [ "Cookie": werkoutNSFWCookie, ] } static var werkoutNSFWFeedURL: URL { URL(string: werkoutNSFWFeedURLString)! } static var werkoutNSFWBroadcast: Broadcast { Broadcast( id: werkoutNSFWStreamID, teamCode: werkoutNSFWTeamCode, name: werkoutNSFWTitle, mediaId: "", streamURL: werkoutNSFWFeedURLString ) } static var werkoutNSFWGame: Game { Game( id: werkoutNSFWStreamID, awayTeam: TeamInfo(code: werkoutNSFWTeamCode, name: werkoutNSFWTitle, score: nil), homeTeam: TeamInfo(code: werkoutNSFWTeamCode, name: werkoutNSFWTitle, score: nil), status: .live(nil), gameType: nil, startTime: nil, venue: nil, pitchers: nil, gamePk: nil, gameDate: "", broadcasts: [], isBlackedOut: false ) } } struct DashboardView: View { @Environment(GamesViewModel.self) private var viewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var selectedGame: Game? @State private var fullScreenBroadcast: BroadcastSelection? @State private var pendingFullScreenBroadcast: BroadcastSelection? @State private var showMLBNetworkSheet = false @State private var showWerkoutNSFWSheet = false @State private var isPiPActive = false private var horizontalPadding: CGFloat { #if os(iOS) horizontalSizeClass == .compact ? 20 : 32 #else 60 #endif } private var verticalPadding: CGFloat { #if os(iOS) horizontalSizeClass == .compact ? 24 : 32 #else 40 #endif } private var contentSpacing: CGFloat { #if os(iOS) horizontalSizeClass == .compact ? 32 : 42 #else 50 #endif } private var shelfCardWidth: CGFloat { #if os(iOS) horizontalSizeClass == .compact ? 300 : 360 #else 400 #endif } var body: some View { ScrollView { VStack(alignment: .leading, spacing: contentSpacing) { headerSection if viewModel.isLoading { HStack { Spacer() ProgressView("Loading games...") .font(.title3) Spacer() } .padding(.top, 80) } else if let error = viewModel.errorMessage, viewModel.games.isEmpty { HStack { Spacer() VStack(spacing: 20) { Image(systemName: "exclamationmark.triangle") .font(.system(size: 50)) .foregroundStyle(.secondary) Text(error) .font(.title3) .foregroundStyle(.secondary) Button("Retry") { Task { await viewModel.loadGames() } } } Spacer() } .padding(.top, 80) } else { // Hero featured game if let featured = viewModel.featuredGame { FeaturedGameCard(game: featured) { selectedGame = featured } } if !viewModel.liveGames.isEmpty { gameShelf(title: "Live", icon: "antenna.radiowaves.left.and.right", games: viewModel.liveGames, excludeId: viewModel.featuredGame?.id) } if !viewModel.scheduledGames.isEmpty { gameShelf(title: "Upcoming", icon: "calendar", games: viewModel.scheduledGames, excludeId: viewModel.featuredGame?.id) } if !viewModel.finalGames.isEmpty { gameShelf(title: "Final", icon: "checkmark.circle", games: viewModel.finalGames, excludeId: viewModel.featuredGame?.id) } } featuredChannelsSection if !viewModel.activeStreams.isEmpty { multiViewStatus } } .padding(.horizontal, horizontalPadding) .padding(.vertical, verticalPadding) } .onAppear { logDashboard("DashboardView appeared") viewModel.startAutoRefresh() Task { await viewModel.loadGames() } } .onDisappear { logDashboard("DashboardView disappeared") viewModel.stopAutoRefresh() } .sheet(item: $selectedGame, onDismiss: presentPendingFullScreenBroadcast) { game in StreamOptionsSheet(game: game) { broadcast in logDashboard("Queued fullscreen broadcast from game sheet broadcastId=\(broadcast.broadcast.id) gameId=\(broadcast.game.id)") pendingFullScreenBroadcast = broadcast selectedGame = nil } } .fullScreenCover(item: $fullScreenBroadcast, content: fullScreenPlaybackScreen) .onChange(of: fullScreenBroadcast?.id) { _, newValue in logDashboard("fullScreenBroadcast changed newValue=\(newValue ?? "nil")") } .sheet(isPresented: $showMLBNetworkSheet, onDismiss: presentPendingFullScreenBroadcast) { MLBNetworkSheet( onWatchFullScreen: { logDashboard("Queued fullscreen broadcast from MLB Network sheet") showMLBNetworkSheet = false pendingFullScreenBroadcast = mlbNetworkBroadcastSelection } ) } .sheet(isPresented: $showWerkoutNSFWSheet, onDismiss: presentPendingFullScreenBroadcast) { WerkoutNSFWSheet( onWatchFullScreen: { logDashboard("Queued fullscreen broadcast from Werkout NSFW sheet") showWerkoutNSFWSheet = false pendingFullScreenBroadcast = nsfwVideosBroadcastSelection } ) } } private func presentPendingFullScreenBroadcast() { guard selectedGame == nil, !showMLBNetworkSheet, !showWerkoutNSFWSheet else { logDashboard("Skipped pending fullscreen presentation because another sheet is still active") return } guard let pendingFullScreenBroadcast else { return } logDashboard( "Presenting pending fullscreen broadcast broadcastId=\(pendingFullScreenBroadcast.broadcast.id) gameId=\(pendingFullScreenBroadcast.game.id)" ) self.pendingFullScreenBroadcast = nil DispatchQueue.main.async { fullScreenBroadcast = pendingFullScreenBroadcast } } private func presentFullScreenBroadcast(_ selection: BroadcastSelection) { if selectedGame == nil, !showMLBNetworkSheet, !showWerkoutNSFWSheet { fullScreenBroadcast = selection } else { pendingFullScreenBroadcast = selection } } @ViewBuilder private func fullScreenPlaybackScreen(selection: BroadcastSelection) -> some View { SingleStreamPlaybackScreen( resolveSource: { await resolveFullScreenSource(for: selection) }, resolveNextSource: nextFullScreenSourceResolver(for: selection), tickerGames: tickerGames(for: selection), game: selection.game, onPiPActiveChanged: { active in isPiPActive = active } ) .ignoresSafeArea() .onAppear { logDashboard("fullScreenCover content mounted broadcastId=\(selection.broadcast.id) gameId=\(selection.game.id)") } } private func resolveFullScreenSource(for selection: BroadcastSelection) async -> SingleStreamPlaybackSource? { logDashboard("resolveSource closure invoked broadcastId=\(selection.broadcast.id) gameId=\(selection.game.id)") if let directSource = selection.directSource { return directSource } if selection.broadcast.id == "MLBN" { let url = await viewModel.buildEventStreamURL(event: "MLBN") return SingleStreamPlaybackSource(url: url) } if selection.broadcast.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID { guard let url = await viewModel.resolveAuthenticatedVideoFeedURL( feedURL: SpecialPlaybackChannelConfig.werkoutNSFWFeedURL, headers: SpecialPlaybackChannelConfig.werkoutNSFWHeaders ) else { return nil } return SingleStreamPlaybackSource( url: url, httpHeaders: SpecialPlaybackChannelConfig.werkoutNSFWHeaders, forceMuteAudio: true ) } let stream = ActiveStream( id: selection.broadcast.id, game: selection.game, label: selection.broadcast.displayLabel, mediaId: selection.broadcast.mediaId, streamURLString: selection.broadcast.streamURL ) guard let url = await viewModel.resolveStreamURL(for: stream) else { return nil } return SingleStreamPlaybackSource(url: url) } private func resolveNextFullScreenSource( for selection: BroadcastSelection, currentURL: URL? ) async -> SingleStreamPlaybackSource? { guard selection.broadcast.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID else { return nil } var nextURL: URL? for attempt in 1...3 { nextURL = await viewModel.resolveAuthenticatedVideoFeedURL( feedURL: SpecialPlaybackChannelConfig.werkoutNSFWFeedURL, headers: SpecialPlaybackChannelConfig.werkoutNSFWHeaders, excluding: currentURL ) if nextURL != nil { break } logDashboard("resolveNextFullScreenSource retry attempt=\(attempt)/3") if attempt < 3 { try? await Task.sleep(nanoseconds: UInt64(attempt) * 500_000_000) } } guard let nextURL else { return nil } return SingleStreamPlaybackSource( url: nextURL, httpHeaders: SpecialPlaybackChannelConfig.werkoutNSFWHeaders, forceMuteAudio: true ) } private func nextFullScreenSourceResolver( for selection: BroadcastSelection ) -> (@Sendable (URL?) async -> SingleStreamPlaybackSource?)? { guard selection.broadcast.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID else { return nil } return { currentURL in await resolveNextFullScreenSource(for: selection, currentURL: currentURL) } } private func tickerGames(for selection: BroadcastSelection) -> [Game] { selection.broadcast.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID ? [] : viewModel.games } private var mlbNetworkBroadcastSelection: BroadcastSelection { let bc = Broadcast( id: "MLBN", teamCode: "MLBN", name: "MLB Network", mediaId: "", streamURL: "" ) let game = Game( id: "MLBN", awayTeam: TeamInfo(code: "MLBN", name: "MLB Network", score: nil), homeTeam: TeamInfo(code: "MLBN", name: "MLB Network", score: nil), status: .live(nil), gameType: nil, startTime: nil, venue: nil, pitchers: nil, gamePk: nil, gameDate: "", broadcasts: [], isBlackedOut: false ) return BroadcastSelection(broadcast: bc, game: game) } private var nsfwVideosBroadcastSelection: BroadcastSelection { return BroadcastSelection( broadcast: SpecialPlaybackChannelConfig.werkoutNSFWBroadcast, game: SpecialPlaybackChannelConfig.werkoutNSFWGame ) } // MARK: - Game Shelf (Horizontal) @ViewBuilder private func gameShelf(title: String, icon: String, games: [Game], excludeId: String?) -> some View { let filtered = games.filter { $0.id != excludeId } if !filtered.isEmpty { VStack(alignment: .leading, spacing: 20) { Label(title, systemImage: icon) .font(.title3.weight(.bold)) .foregroundStyle(.secondary) ScrollView(.horizontal) { LazyHStack(spacing: 30) { ForEach(filtered) { game in GameCardView(game: game) { selectedGame = game } .frame(width: shelfCardWidth) } } .padding(.vertical, 12) } .scrollClipDisabled() } } } // MARK: - Header @ViewBuilder private var headerSection: some View { VStack(spacing: 24) { HStack(alignment: .bottom) { VStack(alignment: .leading, spacing: 6) { Text("MLB") .font(.headline.weight(.black)) .foregroundStyle(.secondary) .kerning(4) Text(viewModel.displayDateString) .font(.system(size: 40, weight: .bold)) .contentTransition(.numericText()) } Spacer() HStack(spacing: 16) { statPill("\(viewModel.games.count)", label: "Games") if !viewModel.liveGames.isEmpty { statPill("\(viewModel.liveGames.count)", label: "Live", color: .red) } if !viewModel.activeStreams.isEmpty { statPill("\(viewModel.activeStreams.count)/4", label: "Streams", color: .green) } } } HStack(spacing: 16) { Button { Task { await viewModel.goToPreviousDay() } } label: { Label("Previous Day", systemImage: "chevron.left") } if !viewModel.isToday { Button { Task { await viewModel.goToToday() } } label: { Label("Today", systemImage: "calendar") } .tint(.blue) } Button { Task { await viewModel.goToNextDay() } } label: { HStack(spacing: 6) { Text("Next Day") Image(systemName: "chevron.right") } } Spacer() } } } @ViewBuilder private func statPill(_ value: String, label: String, color: Color = .blue) -> some View { VStack(spacing: 2) { Text(value) .font(.title3.weight(.bold).monospacedDigit()) .foregroundStyle(color) Text(label) .font(.subheadline.weight(.medium)) .foregroundStyle(.secondary) } .padding(.horizontal, 20) .padding(.vertical, 10) .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 14)) } // MARK: - Featured Channels @ViewBuilder private var featuredChannelsSection: some View { ViewThatFits { HStack(alignment: .top, spacing: 24) { mlbNetworkCard .frame(maxWidth: .infinity) nsfwVideosCard .frame(maxWidth: .infinity) } VStack(alignment: .leading, spacing: 16) { mlbNetworkCard nsfwVideosCard } } } @ViewBuilder private var mlbNetworkCard: some View { let added = viewModel.activeStreams.contains(where: { $0.id == "MLBN" }) Button { showMLBNetworkSheet = true } label: { HStack(spacing: 16) { Image(systemName: "tv.fill") .font(.title2) .foregroundStyle(.blue) .frame(width: 56, height: 56) .background(.blue.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 14)) VStack(alignment: .leading, spacing: 4) { Text("MLB Network") .font(.title3.weight(.bold)) Text("Live coverage, analysis & highlights") .font(.subheadline) .foregroundStyle(.secondary) } Spacer() if added { Label("In Multi-View", systemImage: "checkmark.circle.fill") .font(.subheadline.weight(.bold)) .foregroundStyle(.green) } } .frame(maxWidth: .infinity, minHeight: 108, alignment: .leading) .padding(24) .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 20)) } .platformCardStyle() } @ViewBuilder private var nsfwVideosCard: some View { let added = viewModel.activeStreams.contains(where: { $0.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID }) Button { showWerkoutNSFWSheet = true } label: { HStack(spacing: 16) { Image(systemName: "play.rectangle.fill") .font(.title2) .foregroundStyle(.pink) .frame(width: 56, height: 56) .background(.pink.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 14)) VStack(alignment: .leading, spacing: 4) { Text(SpecialPlaybackChannelConfig.werkoutNSFWTitle) .font(.title3.weight(.bold)) Text(SpecialPlaybackChannelConfig.werkoutNSFWSubtitle) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() if added { Label("In Multi-View", systemImage: "checkmark.circle.fill") .font(.subheadline.weight(.bold)) .foregroundStyle(.green) } else { Label("Open", systemImage: "play.fill") .font(.subheadline.weight(.bold)) .foregroundStyle(.pink) } } .frame(maxWidth: .infinity, minHeight: 108, alignment: .leading) .padding(24) .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 20)) } .platformCardStyle() } // MARK: - Multi-View Status @ViewBuilder private var multiViewStatus: some View { VStack(alignment: .leading, spacing: 14) { Label("Multi-View", systemImage: "rectangle.split.2x2") .font(.title3.weight(.bold)) .foregroundStyle(.secondary) HStack(spacing: 12) { ForEach(viewModel.activeStreams) { stream in HStack(spacing: 8) { Circle().fill(.green).frame(width: 8, height: 8) Text(stream.label) .font(.subheadline.weight(.semibold)) } .padding(.horizontal, 14) .padding(.vertical, 8) .background(.regularMaterial) .clipShape(Capsule()) } } } } } struct BroadcastSelection: Identifiable { let id: String let broadcast: Broadcast let game: Game let directSource: SingleStreamPlaybackSource? init( broadcast: Broadcast, game: Game, directSource: SingleStreamPlaybackSource? = nil ) { self.id = broadcast.id self.broadcast = broadcast self.game = game self.directSource = directSource } } struct WerkoutNSFWSheet: View { var onWatchFullScreen: () -> Void @Environment(GamesViewModel.self) private var viewModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.dismiss) private var dismiss @State private var isResolvingMultiViewSource = false @State private var multiViewErrorMessage: String? private var added: Bool { viewModel.activeStreams.contains(where: { $0.id == SpecialPlaybackChannelConfig.werkoutNSFWStreamID }) } private var canToggleIntoMultiview: Bool { added || viewModel.activeStreams.count < 4 } private var usesStackedLayout: Bool { #if os(iOS) horizontalSizeClass == .compact #else false #endif } private var outerHorizontalPadding: CGFloat { usesStackedLayout ? 20 : 94 } private var outerVerticalPadding: CGFloat { usesStackedLayout ? 24 : 70 } var body: some View { ZStack { sheetBackground .ignoresSafeArea() ViewThatFits { HStack(alignment: .top, spacing: 32) { overviewColumn .frame(maxWidth: .infinity, alignment: .leading) actionColumn .frame(width: 360, alignment: .leading) } VStack(alignment: .leading, spacing: 24) { overviewColumn actionColumn .frame(maxWidth: .infinity, alignment: .leading) } } .padding(38) .background( RoundedRectangle(cornerRadius: 34, style: .continuous) .fill(.black.opacity(0.46)) .overlay { RoundedRectangle(cornerRadius: 34, style: .continuous) .strokeBorder(.white.opacity(0.08), lineWidth: 1) } ) .padding(.horizontal, outerHorizontalPadding) .padding(.vertical, outerVerticalPadding) } } @ViewBuilder private var overviewColumn: some View { VStack(alignment: .leading, spacing: 28) { VStack(alignment: .leading, spacing: 18) { HStack(spacing: 12) { statusPill(title: "PRIVATE FEED", color: .pink) if added { statusPill(title: "IN MULTI-VIEW", color: .green) } } HStack(alignment: .center, spacing: 22) { ZStack { RoundedRectangle(cornerRadius: 26, style: .continuous) .fill(.pink.opacity(0.16)) .frame(width: 110, height: 110) Image(systemName: "play.rectangle.fill") .font(.system(size: 46, weight: .bold)) .foregroundStyle( LinearGradient( colors: [.white, .pink.opacity(0.88)], startPoint: .top, endPoint: .bottom ) ) } VStack(alignment: .leading, spacing: 10) { Text(SpecialPlaybackChannelConfig.werkoutNSFWTitle) .font(.system(size: 46, weight: .black, design: .rounded)) .foregroundStyle(.white) Text("Launch the authenticated Werkout video feed full screen or drop it into an open Multi-View tile.") .font(.system(size: 20, weight: .medium)) .foregroundStyle(.white.opacity(0.72)) .fixedSize(horizontal: false, vertical: true) } } } HStack(spacing: 14) { featurePill(title: "Authenticated", systemImage: "lock.fill") featurePill(title: "Private Media", systemImage: "video.fill") featurePill(title: "Direct Playback", systemImage: "play.fill") } VStack(alignment: .leading, spacing: 14) { Text("Notes") .font(.system(size: 16, weight: .bold, design: .rounded)) .foregroundStyle(.white.opacity(0.48)) .kerning(1.2) VStack(alignment: .leading, spacing: 14) { overviewLine( icon: "lock.shield.fill", title: "Token-gated playback", detail: "This feed is played with an Authorization header instead of a public URL." ) overviewLine( icon: "square.grid.2x2.fill", title: "Multi-View compatible", detail: "You can pin this feed into the active grid and route audio to it like other channel streams." ) overviewLine( icon: "exclamationmark.triangle.fill", title: "Backend auth still matters", detail: "If the server rejects the token, playback will fail in both full screen and Multi-View." ) } } .padding(24) .background(panelBackground) if let multiViewErrorMessage { Label(multiViewErrorMessage, systemImage: "exclamationmark.triangle.fill") .font(.system(size: 16, weight: .semibold)) .foregroundStyle(.red) .padding(.horizontal, 18) .padding(.vertical, 16) .background(panelBackground) } if !canToggleIntoMultiview { Label( "Multi-View is full. Remove a stream before adding Werkout NSFW.", systemImage: "rectangle.split.2x2.fill" ) .font(.system(size: 16, weight: .semibold)) .foregroundStyle(.orange) .padding(.horizontal, 18) .padding(.vertical, 16) .background(panelBackground) } } } @ViewBuilder private var actionColumn: some View { VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 8) { Text("Actions") .font(.system(size: 28, weight: .bold, design: .rounded)) .foregroundStyle(.white) Text("Play the feed full screen or send it into your active Multi-View lineup.") .font(.system(size: 16, weight: .medium)) .foregroundStyle(.white.opacity(0.68)) .fixedSize(horizontal: false, vertical: true) } .padding(24) .background(panelBackground) actionButton( title: "Watch Full Screen", subtitle: "Open the Werkout feed in the main player", systemImage: "play.fill", fill: .pink.opacity(0.18) ) { onWatchFullScreen() } actionButton( title: added ? "Remove From Multi-View" : "Add to Multi-View", subtitle: added ? "Take the feed out of your active grid" : isResolvingMultiViewSource ? "Resolving a playable HLS source from the feed" : "Send the feed into an open tile", systemImage: added ? "minus.circle.fill" : "plus.circle.fill", fill: added ? .red.opacity(0.16) : .white.opacity(0.08), foreground: added ? .red : .white, disabled: !canToggleIntoMultiview || isResolvingMultiViewSource ) { if added { viewModel.removeStream(id: SpecialPlaybackChannelConfig.werkoutNSFWStreamID) dismiss() return } multiViewErrorMessage = nil isResolvingMultiViewSource = true Task { @MainActor in let didAddStream = await viewModel.addSpecialStreamFromAuthenticatedFeed( id: SpecialPlaybackChannelConfig.werkoutNSFWStreamID, label: SpecialPlaybackChannelConfig.werkoutNSFWTitle, game: SpecialPlaybackChannelConfig.werkoutNSFWGame, feedURL: SpecialPlaybackChannelConfig.werkoutNSFWFeedURL, headers: SpecialPlaybackChannelConfig.werkoutNSFWHeaders, forceMuteAudio: true ) isResolvingMultiViewSource = false if didAddStream { dismiss() } else { multiViewErrorMessage = "Could not resolve a playable Werkout stream from the authenticated feed." } } } Spacer(minLength: 0) } } @ViewBuilder private func actionButton( title: String, subtitle: String, systemImage: String, fill: Color, foreground: Color = .white, disabled: Bool = false, action: @escaping () -> Void ) -> some View { Button(action: action) { HStack(alignment: .center, spacing: 16) { Image(systemName: systemImage) .font(.system(size: 22, weight: .bold)) .frame(width: 32) VStack(alignment: .leading, spacing: 5) { Text(title) .font(.system(size: 21, weight: .bold, design: .rounded)) HStack(spacing: 10) { Text(subtitle) .font(.system(size: 14, weight: .medium)) .foregroundStyle(foreground.opacity(0.68)) .fixedSize(horizontal: false, vertical: true) if isResolvingMultiViewSource && title == "Add to Multi-View" { ProgressView() .scaleEffect(0.8) .tint(.white.opacity(0.84)) } } } Spacer(minLength: 0) } .foregroundStyle(foreground) .frame(maxWidth: .infinity, minHeight: 88, alignment: .leading) .padding(.horizontal, 22) .background( RoundedRectangle(cornerRadius: 24, style: .continuous) .fill(fill) ) } .platformCardStyle() .disabled(disabled) } @ViewBuilder private func overviewLine(icon: String, title: String, detail: String) -> some View { HStack(alignment: .top, spacing: 14) { Image(systemName: icon) .font(.system(size: 18, weight: .bold)) .foregroundStyle(.pink.opacity(0.9)) .frame(width: 26) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.system(size: 18, weight: .bold)) .foregroundStyle(.white) Text(detail) .font(.system(size: 15, weight: .medium)) .foregroundStyle(.white.opacity(0.66)) .fixedSize(horizontal: false, vertical: true) } } } @ViewBuilder private func statusPill(title: String, color: Color) -> some View { Text(title) .font(.system(size: 13, weight: .black, design: .rounded)) .foregroundStyle(color) .padding(.horizontal, 13) .padding(.vertical, 9) .background(color.opacity(0.14)) .clipShape(Capsule()) } @ViewBuilder private func featurePill(title: String, systemImage: String) -> some View { Label(title, systemImage: systemImage) .font(.system(size: 15, weight: .semibold)) .foregroundStyle(.white.opacity(0.84)) .padding(.horizontal, 16) .padding(.vertical, 12) .background(.white.opacity(0.06)) .clipShape(Capsule()) } @ViewBuilder private var panelBackground: some View { RoundedRectangle(cornerRadius: 24, style: .continuous) .fill(.black.opacity(0.22)) .overlay { RoundedRectangle(cornerRadius: 24, style: .continuous) .strokeBorder(.white.opacity(0.08), lineWidth: 1) } } @ViewBuilder private var sheetBackground: some View { ZStack { LinearGradient( colors: [ Color(red: 0.07, green: 0.04, blue: 0.08), Color(red: 0.09, green: 0.05, blue: 0.08), ], startPoint: .topLeading, endPoint: .bottomTrailing ) Circle() .fill(.pink.opacity(0.18)) .frame(width: 520, height: 520) .blur(radius: 92) .offset(x: -320, y: -220) Circle() .fill(.red.opacity(0.15)) .frame(width: 460, height: 460) .blur(radius: 88) .offset(x: 380, y: 140) } } }