import SwiftUI #if os(macOS) import AppKit #else import UIKit #endif enum MailTheme { static let accent = Color(red: 0.20, green: 0.47, blue: 0.94) static let ocean = Color(red: 0.18, green: 0.53, blue: 0.97) static let mint = Color(red: 0.26, green: 0.74, blue: 0.68) static let sunrise = Color(red: 1.00, green: 0.67, blue: 0.38) static let ink = Color(red: 0.10, green: 0.17, blue: 0.27) } struct MailRootView: View { @Bindable var model: AppViewModel @State private var preferredCompactColumn: NavigationSplitViewColumn = .content var body: some View { NavigationSplitView(preferredCompactColumn: $preferredCompactColumn) { MailSidebarView(model: model) } content: { ThreadListView(model: model) } detail: { ThreadDetailView(model: model) } .navigationSplitViewStyle(.balanced) .searchable(text: searchTextBinding, prompt: "Search mail") .toolbar { if showsToolbarCompose { ToolbarItem(placement: .primaryAction) { Button { model.startCompose() } label: { Label("Compose", systemImage: "square.and.pencil") } } } } .sheet(isPresented: $model.isComposing) { ComposeView(model: model) } .task { await model.load() } .task { await model.beginBackendControl() } .onChange(of: model.mailboxNavigationToken) { showCompactColumn(.content) } .onChange(of: model.threadNavigationToken) { showCompactColumn(.detail) } .onChange(of: model.selectedThreadID) { if model.selectedThreadID == nil { showCompactColumn(.content) } } .onChange(of: model.isComposing) { guard model.isComposing, usesCompactSplitNavigation else { return } model.dismissThreadSelection() showCompactColumn(.content) } .onChange(of: preferredCompactColumn) { guard usesCompactSplitNavigation, preferredCompactColumn != .detail else { return } model.dismissThreadSelection() } .alert("Something went wrong", isPresented: errorPresented) { Button("OK") { model.errorMessage = nil } } message: { Text(model.errorMessage ?? "") } } private var errorPresented: Binding { Binding( get: { model.errorMessage != nil }, set: { isPresented in if !isPresented { model.errorMessage = nil } } ) } private var searchTextBinding: Binding { Binding( get: { model.searchText }, set: { model.setSearchText($0) } ) } private var showsToolbarCompose: Bool { #if os(iOS) UIDevice.current.userInterfaceIdiom != .phone #else true #endif } private var usesCompactSplitNavigation: Bool { #if os(iOS) UIDevice.current.userInterfaceIdiom == .phone #else false #endif } private func showCompactColumn(_ column: NavigationSplitViewColumn) { guard usesCompactSplitNavigation else { return } preferredCompactColumn = column } } private struct MailSidebarView: View { @Bindable var model: AppViewModel var body: some View { List { Section { SidebarHeader(model: model) .listRowInsets(EdgeInsets(top: 12, leading: 14, bottom: 16, trailing: 14)) .listRowBackground(Color.clear) } Section("Mailboxes") { ForEach(Mailbox.allCases) { mailbox in Button { model.selectMailbox(mailbox) } label: { mailboxRow(for: mailbox) .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } .buttonStyle(.plain) .listRowBackground( mailbox == model.selectedMailbox ? MailTheme.accent.opacity(0.10) : Color.clear ) .accessibilityIdentifier("sidebar.mailbox.\(mailbox.id)") } } Section("Filters") { Toggle(isOn: unreadOnlyBinding) { HStack { Label("Unread Only", systemImage: "circle.badge") Spacer() Text(model.totalUnreadCount, format: .number) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) } } } } .listStyle(.sidebar) .scrollContentBackground(.hidden) .background(MailCanvasBackground(primary: MailTheme.ocean, secondary: MailTheme.mint)) .navigationTitle("social.io") } private var unreadOnlyBinding: Binding { Binding( get: { model.showUnreadOnly }, set: { model.setUnreadOnly($0) } ) } private func mailboxRow(for mailbox: Mailbox) -> some View { HStack(spacing: 12) { Label(mailbox.title, systemImage: mailbox.systemImage) .foregroundStyle(mailbox == model.selectedMailbox ? .primary : .secondary) Spacer() Text(model.threadCount(in: mailbox), format: .number) .font(.caption.weight(.semibold)) .foregroundStyle(mailbox == model.selectedMailbox ? .primary : .secondary) .padding(.horizontal, 8) .padding(.vertical, 4) .background( mailbox == model.selectedMailbox ? MailTheme.accent.opacity(0.14) : Color.secondary.opacity(0.10), in: Capsule() ) } .frame(maxWidth: .infinity, alignment: .leading) } } private struct SidebarHeader: View { @Bindable var model: AppViewModel var body: some View { VStack(alignment: .leading, spacing: 16) { HStack(alignment: .center, spacing: 14) { Image(systemName: "at.circle.fill") .font(.system(size: 30, weight: .semibold)) .foregroundStyle(MailTheme.accent, MailTheme.mint) VStack(alignment: .leading, spacing: 4) { Text("social.io mail") .font(.title3.weight(.bold)) Text("Calm inboxes for real conversations.") .font(.subheadline) .foregroundStyle(.secondary) } } AdaptiveGlassGroup(spacing: 16) { HStack(spacing: 12) { SummaryChip( title: "Unread", value: model.totalUnreadCount, tint: MailTheme.accent.opacity(0.18) ) SummaryChip( title: "Starred", value: model.threadCount(in: .starred), tint: MailTheme.sunrise.opacity(0.18) ) } } } } } private struct SummaryChip: View { let title: String let value: Int let tint: Color? var body: some View { VStack(alignment: .leading, spacing: 4) { Text(title) .font(.caption) .foregroundStyle(.secondary) Text(value, format: .number) .font(.headline.weight(.semibold)) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 14) .padding(.vertical, 12) .socialGlass(in: RoundedRectangle(cornerRadius: 18, style: .continuous), tint: tint) } } private struct ThreadListView: View { @Bindable var model: AppViewModel var body: some View { ZStack { MailCanvasBackground(primary: MailTheme.ocean, secondary: MailTheme.sunrise) .ignoresSafeArea() VStack(spacing: 0) { MailboxFilterBar(model: model) MailboxHeroCard(model: model) Group { if model.isLoading { ProgressView("Loading mail…") .frame(maxWidth: .infinity, maxHeight: .infinity) } else if model.filteredThreads.isEmpty { ContentUnavailableView( "No Messages", systemImage: "tray", description: Text("Try another mailbox or relax the filters.") ) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { List(model.filteredThreads) { thread in Button { model.openThread(withID: thread.id) } label: { ThreadRow(thread: thread, isSelected: thread.id == model.selectedThreadID) .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } .buttonStyle(.plain) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 8, leading: 18, bottom: 8, trailing: 18)) .listRowBackground(Color.clear) .contextMenu { Button(thread.isUnread ? "Mark Read" : "Mark Unread") { model.toggleRead(for: thread) } Button(thread.isStarred ? "Remove Star" : "Star Thread") { model.toggleStar(for: thread) } } } .listStyle(.plain) .scrollContentBackground(.hidden) } } } } .safeAreaInset(edge: .bottom) { FloatingComposeButton(model: model) } .navigationTitle(model.selectedMailbox.title) .mailInlineNavigationTitle() .mailNavigationChrome() } } private struct MailboxHeroCard: View { @Bindable var model: AppViewModel var body: some View { VStack(alignment: .leading, spacing: 16) { Text(model.selectedMailbox.title) .font(.system(.largeTitle, design: .rounded, weight: .bold)) Text(mailboxDescription) .font(.subheadline) .foregroundStyle(.secondary) AdaptiveGlassGroup(spacing: 12) { HStack(spacing: 12) { SummaryChip( title: "Visible", value: model.filteredThreads.count, tint: MailTheme.accent.opacity(0.20) ) SummaryChip( title: "Unread", value: model.filteredThreads.filter(\.isUnread).count, tint: MailTheme.mint.opacity(0.18) ) SummaryChip( title: "Starred", value: model.filteredThreads.filter(\.isStarred).count, tint: MailTheme.sunrise.opacity(0.18) ) } } if let latestThread = model.filteredThreads.first { HStack(spacing: 8) { Image(systemName: "clock") Text("Latest activity \(latestThread.lastUpdated.formatted(date: .abbreviated, time: .shortened))") } .font(.footnote) .foregroundStyle(.secondary) } } .padding(.horizontal, 20) .padding(.vertical, 22) .frame(maxWidth: .infinity, alignment: .leading) .background(heroBackground, in: RoundedRectangle(cornerRadius: 30, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 30, style: .continuous) .stroke(Color.white.opacity(0.20), lineWidth: 1) ) .padding(.horizontal, 20) .padding(.bottom, 12) } private var mailboxDescription: String { switch model.selectedMailbox { case .inbox: "Fresh conversations, live signals, and mail worth deciding on now." case .starred: "Pinned threads that still deserve attention, not just memory." case .sent: "Everything you shipped recently, ready for quick follow-up." case .drafts: "Half-finished notes and messages waiting for a final pass." case .archive: "Quieted threads with context still close at hand." } } private var heroBackground: some ShapeStyle { LinearGradient( colors: [ MailTheme.accent.opacity(0.28), MailTheme.ocean.opacity(0.16), Color.white.opacity(0.08) ], startPoint: .topLeading, endPoint: .bottomTrailing ) } } private struct FloatingComposeButton: View { @Bindable var model: AppViewModel var body: some View { Group { if shouldShow { HStack { Spacer() Button { model.startCompose() } label: { HStack(spacing: 10) { Image(systemName: "square.and.pencil") Text("Compose") } .font(.headline.weight(.semibold)) .padding(.horizontal, 18) .padding(.vertical, 14) .socialGlass( in: Capsule(), tint: MailTheme.accent.opacity(0.22), interactive: true ) .contentShape(Capsule()) } .buttonStyle(.plain) .accessibilityIdentifier("compose.floating") } .padding(.horizontal, 20) .padding(.top, 8) .padding(.bottom, 12) .background(Color.clear) } } } private var shouldShow: Bool { #if os(iOS) UIDevice.current.userInterfaceIdiom == .phone #else false #endif } } private struct MailboxFilterBar: View { @Bindable var model: AppViewModel var body: some View { ScrollView(.horizontal, showsIndicators: false) { AdaptiveGlassGroup(spacing: 16) { HStack(spacing: 12) { ForEach(Mailbox.allCases) { mailbox in Button { model.selectMailbox(mailbox) } label: { HStack(spacing: 8) { Image(systemName: mailbox.systemImage) Text(mailbox.title) Text(model.threadCount(in: mailbox), format: .number) .font(.caption2.weight(.bold)) .foregroundStyle(.secondary) } .font(.subheadline.weight(.semibold)) .padding(.horizontal, 14) .padding(.vertical, 10) .socialGlass( in: Capsule(), tint: mailbox == model.selectedMailbox ? MailTheme.accent.opacity(0.18) : nil, interactive: true ) } .buttonStyle(.plain) .accessibilityIdentifier("mailbox.\(mailbox.id)") } Button { model.setUnreadOnly(!model.showUnreadOnly) } label: { HStack(spacing: 8) { Image(systemName: model.showUnreadOnly ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle") Text("Unread") } .font(.subheadline.weight(.semibold)) .padding(.horizontal, 14) .padding(.vertical, 10) .socialGlass( in: Capsule(), tint: model.showUnreadOnly ? MailTheme.mint.opacity(0.18) : nil, interactive: true ) } .buttonStyle(.plain) .accessibilityIdentifier("filter.unread") } } .padding(.horizontal, 20) .padding(.vertical, 14) } } } private struct ThreadRow: View { let thread: MailThread let isSelected: Bool var body: some View { VStack(alignment: .leading, spacing: 12) { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(thread.participants.map(\.name).joined(separator: ", ")) .font(.subheadline.weight(thread.isUnread ? .semibold : .regular)) .lineLimit(1) Text(thread.subject) .font(.headline) .lineLimit(1) } Spacer(minLength: 0) VStack(alignment: .trailing, spacing: 6) { if thread.isStarred { Image(systemName: "star.fill") .foregroundStyle(.yellow) } Text(thread.lastUpdated.formatted(date: .abbreviated, time: .shortened)) .font(.caption) .foregroundStyle(.secondary) } } Text(thread.previewText) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(2) if !thread.tags.isEmpty { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(thread.tags, id: \.self) { tag in Text(tag) .font(.caption.weight(.medium)) .padding(.horizontal, 10) .padding(.vertical, 6) .background(Color.secondary.opacity(0.10), in: Capsule()) } } } } } .padding(16) .mailPanelBackground( in: RoundedRectangle(cornerRadius: 24, style: .continuous), highlight: isSelected ? MailTheme.accent.opacity(0.28) : Color.white.opacity(0.10) ) .accessibilityIdentifier("thread.\(thread.routeID)") } } private struct ThreadDetailView: View { @Bindable var model: AppViewModel var body: some View { ZStack { MailCanvasBackground(primary: MailTheme.mint, secondary: MailTheme.sunrise) .ignoresSafeArea() Group { if let thread = model.selectedThread { ScrollViewReader { proxy in ScrollView { VStack(alignment: .leading, spacing: 24) { ThreadHero(threadID: thread.id, model: model) ForEach(thread.messages) { message in MessageCard( message: message, isLatest: message.id == thread.latestMessage?.id, isFocused: message.routeID == model.focusedMessageRouteID ) .id(message.routeID) } } .padding(24) .frame(maxWidth: 920, alignment: .leading) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .onAppear { scrollToFocusedMessage(using: proxy, animated: false) } .onChange(of: model.focusedMessageRouteID) { scrollToFocusedMessage(using: proxy) } .onChange(of: thread.routeID) { scrollToFocusedMessage(using: proxy, animated: false) } } } else { ContentUnavailableView( "Select a Thread", systemImage: "envelope.open", description: Text("Choose a conversation to read or compose a new message.") ) } } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .navigationTitle("Conversation") } private func scrollToFocusedMessage(using proxy: ScrollViewProxy, animated: Bool = true) { guard let focusedMessageRouteID = model.focusedMessageRouteID else { return } if animated { withAnimation(.easeInOut(duration: 0.25)) { proxy.scrollTo(focusedMessageRouteID, anchor: .center) } } else { proxy.scrollTo(focusedMessageRouteID, anchor: .center) } } } private struct ThreadHero: View { let threadID: MailThread.ID @Bindable var model: AppViewModel var body: some View { Group { if let thread = model.thread(withID: threadID) { VStack(alignment: .leading, spacing: 18) { if usesCompactHeroLayout { VStack(alignment: .leading, spacing: 16) { heroHeaderContent(for: thread) ThreadActionBar(threadID: thread.id, model: model, compact: true) } } else { HStack(alignment: .top, spacing: 16) { heroHeaderContent(for: thread) Spacer(minLength: 0) ThreadActionBar(threadID: thread.id, model: model) } } if !thread.tags.isEmpty { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(thread.tags, id: \.self) { tag in Text(tag) .font(.caption.weight(.medium)) .padding(.horizontal, 10) .padding(.vertical, 6) .background(Color.secondary.opacity(0.10), in: Capsule()) } } } } Text("Latest update \(thread.lastUpdated.formatted(date: .abbreviated, time: .shortened))") .font(.footnote) .foregroundStyle(.secondary) } .padding(24) .background(heroBackground, in: RoundedRectangle(cornerRadius: 32, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 32, style: .continuous) .stroke(Color.white.opacity(0.18), lineWidth: 1) ) } } } private func heroHeaderContent(for thread: MailThread) -> some View { VStack(alignment: .leading, spacing: 10) { AdaptiveGlassGroup(spacing: 14) { if usesCompactHeroLayout { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 10) { heroStatusChips(for: thread) } } } else { HStack(spacing: 10) { heroStatusChips(for: thread) } } } Text(thread.subject) .font(.system(.largeTitle, design: .rounded, weight: .bold)) Text(thread.participants.map(\.email).joined(separator: ", ")) .font(.subheadline) .foregroundStyle(.secondary) } } @ViewBuilder private func heroStatusChips(for thread: MailThread) -> some View { StatusChip( title: thread.mailbox.title, systemImage: thread.mailbox.systemImage, tint: MailTheme.accent.opacity(0.18) ) StatusChip( title: "Unread", systemImage: "circle.badge.fill", tint: MailTheme.sunrise.opacity(0.18) ) .opacity(thread.isUnread ? 1 : 0) .accessibilityHidden(!thread.isUnread) } private var heroBackground: some ShapeStyle { LinearGradient( colors: [ MailTheme.accent.opacity(0.22), MailTheme.mint.opacity(0.12), Color.white.opacity(0.06) ], startPoint: .topLeading, endPoint: .bottomTrailing ) } private var usesCompactHeroLayout: Bool { #if os(iOS) UIDevice.current.userInterfaceIdiom == .phone #else false #endif } } private struct StatusChip: View { let title: String let systemImage: String let tint: Color? var body: some View { HStack(spacing: 8) { Image(systemName: systemImage) Text(title) } .font(.caption.weight(.semibold)) .padding(.horizontal, 12) .padding(.vertical, 8) .socialGlass(in: Capsule(), tint: tint) } } private struct ThreadActionBar: View { let threadID: MailThread.ID @Bindable var model: AppViewModel var compact = false private let controlAnimation = Animation.snappy(duration: 0.24, extraBounce: 0.03) var body: some View { Group { if let thread = model.thread(withID: threadID) { HStack(spacing: compact ? 10 : 12) { actionButtons(for: thread) } .animation(controlAnimation, value: thread.isStarred) .animation(controlAnimation, value: thread.isUnread) } } } private func actionButtons(for thread: MailThread) -> some View { Group { actionButton( title: thread.isStarred ? "Starred" : "Star", systemImage: thread.isStarred ? "star.fill" : "star", tint: thread.isStarred ? MailTheme.sunrise.opacity(0.22) : nil ) { withAnimation(controlAnimation) { model.toggleStar(forThreadID: thread.id) } } actionButton( title: thread.isUnread ? "Mark Read" : "Mark Unread", systemImage: thread.isUnread ? "envelope.open.fill" : "envelope.badge", tint: thread.isUnread ? MailTheme.mint.opacity(0.20) : nil ) { withAnimation(controlAnimation) { model.toggleRead(forThreadID: thread.id) } } } } private func actionButton( title: String, systemImage: String, tint: Color?, action: @escaping () -> Void ) -> some View { Button(action: action) { HStack(spacing: 8) { Image(systemName: systemImage) .contentTransition(.symbolEffect(.replace)) Text(title) .lineLimit(1) .contentTransition(.opacity) } .font(.subheadline.weight(.semibold)) .fixedSize(horizontal: true, vertical: false) .padding(.horizontal, 14) .padding(.vertical, 10) .stableControlPill(tint: tint) } .buttonStyle(.plain) .animation(controlAnimation, value: title) .animation(controlAnimation, value: systemImage) .animation(controlAnimation, value: tint != nil) } } private struct MessageCard: View { let message: MailMessage let isLatest: Bool let isFocused: Bool var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 4) { Text(message.sender.name) .font(.headline) Text(message.sender.email) .font(.caption) .foregroundStyle(.secondary) } Spacer(minLength: 0) Text(message.sentAt.formatted(date: .abbreviated, time: .shortened)) .font(.caption) .foregroundStyle(.secondary) } Text(message.body) .font(.body) .textSelection(.enabled) } .padding(20) .mailPanelBackground( in: RoundedRectangle(cornerRadius: 28, style: .continuous), highlight: messageHighlight ) .overlay(alignment: .topTrailing) { if isFocused { Text("Focused") .font(.caption2.weight(.bold)) .padding(.horizontal, 10) .padding(.vertical, 6) .socialGlass(in: Capsule(), tint: MailTheme.accent.opacity(0.18)) .padding(14) } } .accessibilityIdentifier("message.\(message.routeID)") } private var messageHighlight: Color { if isFocused { return MailTheme.accent.opacity(0.38) } if isLatest { return MailTheme.accent.opacity(0.22) } return Color.white.opacity(0.10) } } private struct ComposeView: View { @Environment(\.dismiss) private var dismiss @Bindable var model: AppViewModel var body: some View { Group { if usesCompactComposeLayout { composeScene } else { composeScene .frame(minWidth: 560, minHeight: 520) } } .accessibilityIdentifier("compose.view") } private var composeScene: some View { NavigationStack { ZStack { MailCanvasBackground(primary: MailTheme.accent, secondary: MailTheme.sunrise) .ignoresSafeArea() ScrollView { VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 8) { Text("New Message") .font(.system(.largeTitle, design: .rounded, weight: .bold)) Text("Keep the controls light and let the conversation do the work.") .font(.subheadline) .foregroundStyle(.secondary) } ComposeFieldCard(title: "To") { toField } ComposeFieldCard(title: "Subject") { TextField("What's this about?", text: $model.composeDraft.subject) .textFieldStyle(.plain) .disabled(model.isSending) .accessibilityIdentifier("compose.subject") } ComposeFieldCard(title: "Message") { TextEditor(text: $model.composeDraft.body) .scrollContentBackground(.hidden) .frame(minHeight: 240) .disabled(model.isSending) .accessibilityIdentifier("compose.body") } Spacer(minLength: 0) } .padding(usesCompactComposeLayout ? 20 : 24) .frame(maxWidth: 720, alignment: .topLeading) .frame(maxWidth: .infinity, alignment: .top) } } .navigationTitle("Compose") .composeNavigationTitleDisplayMode(isCompact: usesCompactComposeLayout) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } .disabled(model.isSending) .accessibilityIdentifier("compose.cancel") } ToolbarItem(placement: .confirmationAction) { Button(model.isSending ? "Sending…" : "Send") { Task { _ = await model.sendCurrentDraft() } } .disabled(model.isSending || model.composeDraft.to.isEmpty || model.composeDraft.body.isEmpty) .accessibilityIdentifier("compose.send") } } } } @ViewBuilder private var toField: some View { #if os(iOS) TextField("name@example.com", text: $model.composeDraft.to) .textFieldStyle(.plain) .textContentType(.emailAddress) .keyboardType(.emailAddress) .textInputAutocapitalization(.never) .disabled(model.isSending) .accessibilityIdentifier("compose.to") #else TextField("name@example.com", text: $model.composeDraft.to) .textFieldStyle(.plain) .textContentType(.emailAddress) .disabled(model.isSending) .accessibilityIdentifier("compose.to") #endif } private var usesCompactComposeLayout: Bool { #if os(iOS) UIDevice.current.userInterfaceIdiom == .phone #else false #endif } } private extension View { @ViewBuilder func composeNavigationTitleDisplayMode(isCompact: Bool) -> some View { #if os(iOS) navigationBarTitleDisplayMode(isCompact ? .inline : .automatic) #else self #endif } } private struct ComposeFieldCard: View { let title: String let content: Content init(title: String, @ViewBuilder content: () -> Content) { self.title = title self.content = content() } var body: some View { VStack(alignment: .leading, spacing: 10) { Text(title) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) content } .padding(18) .mailPanelBackground(in: RoundedRectangle(cornerRadius: 24, style: .continuous)) } } private struct MailCanvasBackground: View { let primary: Color let secondary: Color var body: some View { ZStack { LinearGradient( colors: [ platformBackgroundColor, primary.opacity(0.10), secondary.opacity(0.12) ], startPoint: .topLeading, endPoint: .bottomTrailing ) Circle() .fill(primary.opacity(0.22)) .frame(width: 360, height: 360) .blur(radius: 90) .offset(x: -160, y: -240) Circle() .fill(secondary.opacity(0.20)) .frame(width: 300, height: 300) .blur(radius: 90) .offset(x: 210, y: 260) Circle() .fill(Color.white.opacity(0.10)) .frame(width: 220, height: 220) .blur(radius: 70) .offset(x: 180, y: -220) } } } private struct AdaptiveGlassGroup: View { let spacing: CGFloat? let content: Content init(spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) { self.spacing = spacing self.content = content() } var body: some View { if #available(iOS 26.0, macOS 26.0, *) { GlassEffectContainer(spacing: spacing) { content } } else { content } } } private extension View { @ViewBuilder func socialGlass( in shape: S, tint: Color? = nil, interactive: Bool = false ) -> some View { if #available(iOS 26.0, macOS 26.0, *) { glassEffect( Glass.regular.tint(tint).interactive(interactive), in: shape ) } else { background(.ultraThinMaterial, in: shape) .overlay( shape.stroke(Color.white.opacity(0.16), lineWidth: 1) ) } } func mailPanelBackground( in shape: S, highlight: Color = Color.white.opacity(0.10) ) -> some View { background(.regularMaterial, in: shape) .overlay( shape.stroke(highlight, lineWidth: 1) ) } func stableControlPill(tint: Color?) -> some View { background { Capsule() .fill(.ultraThinMaterial) .overlay( Capsule() .fill(tint ?? .clear) ) .overlay( Capsule() .stroke(Color.white.opacity(0.16), lineWidth: 1) ) } } @ViewBuilder func mailNavigationChrome() -> some View { #if os(iOS) toolbarBackground(.hidden, for: .navigationBar) #else self #endif } @ViewBuilder func mailInlineNavigationTitle() -> some View { #if os(iOS) navigationBarTitleDisplayMode(.inline) #else self #endif } } private var platformBackgroundColor: Color { #if os(macOS) Color(nsColor: .windowBackgroundColor) #else Color(uiColor: .systemBackground) #endif }