import SwiftUI extension Color { init(hex: UInt32, opacity: Double = 1) { self.init( .sRGB, red: Double((hex >> 16) & 0xFF) / 255, green: Double((hex >> 8) & 0xFF) / 255, blue: Double(hex & 0xFF) / 255, opacity: opacity ) } } enum Theme { static let background = Color.black static let card = Color.white.opacity(0.055) static let cardStrong = Color.white.opacity(0.08) static let stroke = Color.white.opacity(0.09) static let mint = Color(hex: 0x5EEAD4) static let blue = Color(hex: 0x60A5FA) static let amber = Color(hex: 0xFBBF24) static let danger = Color(hex: 0xFB7185) static let secondaryText = Color.white.opacity(0.62) static let tertiaryText = Color.white.opacity(0.4) } struct AppBackground: View { var body: some View { Theme.background.ignoresSafeArea() } } struct LogoMark: View { var size: CGFloat = 44 var body: some View { ZStack { RoundedRectangle(cornerRadius: size * 0.29, style: .continuous) .fill( LinearGradient( colors: [Theme.mint, Theme.blue], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .shadow(color: Theme.mint.opacity(0.35), radius: 16, y: 6) Image(systemName: "arrow.up.arrow.down") .font(.system(size: size * 0.4, weight: .bold)) .foregroundStyle(Color(hex: 0x06211C)) } .frame(width: size, height: size) } } struct GlassCard: View { private let padding: CGFloat private let content: Content init(padding: CGFloat = 20, @ViewBuilder content: () -> Content) { self.padding = padding self.content = content() } var body: some View { // Wrap the builder's tuple in a real layout container. Applying the // card background directly to a TupleView lets SwiftUI flatten the // children into the parent VStack, which visually breaks one card into // several rounded boxes. VStack(alignment: .leading, spacing: 0) { content } .padding(padding) .frame(maxWidth: .infinity, alignment: .leading) .background(Theme.card, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) .overlay { RoundedRectangle(cornerRadius: 24, style: .continuous) .strokeBorder(Theme.stroke, lineWidth: 1) } } } struct FieldLabel: View { let title: String var hint: String? var body: some View { VStack(alignment: .leading, spacing: 3) { Text(title.uppercased()) .font(.system(size: 11, weight: .semibold, design: .rounded)) .tracking(1.4) .foregroundStyle(Theme.tertiaryText) if let hint { Text(hint) .font(.footnote) .foregroundStyle(Theme.secondaryText) } } } } struct PrimaryButtonStyle: ButtonStyle { var tint: Color = Theme.mint var enabled: Bool = true func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(.headline, design: .rounded).weight(.semibold)) .foregroundStyle(Color(hex: 0x06211C)) .frame(maxWidth: .infinity) .padding(.vertical, 15) .background( LinearGradient( colors: enabled ? [tint, tint.opacity(0.78)] : [Color.white.opacity(0.16), Color.white.opacity(0.12)], startPoint: .topLeading, endPoint: .bottomTrailing ), in: RoundedRectangle(cornerRadius: 16, style: .continuous) ) .shadow(color: enabled ? tint.opacity(0.24) : .clear, radius: 14, y: 6) .scaleEffect(configuration.isPressed && enabled ? 0.985 : 1) .opacity(enabled ? 1 : 0.55) .animation(.easeOut(duration: 0.14), value: configuration.isPressed) } } struct QuietButtonStyle: ButtonStyle { var tint: Color = Theme.secondaryText func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(.subheadline, design: .rounded).weight(.medium)) .foregroundStyle(tint) .padding(.horizontal, 12) .padding(.vertical, 8) .background(Color.white.opacity(0.06), in: Capsule()) .opacity(configuration.isPressed ? 0.65 : 1) } } struct StatusPill: View { let running: Bool let peers: Int var body: some View { HStack(spacing: 7) { Circle() .fill(running ? Theme.mint : Theme.tertiaryText) .frame(width: 7, height: 7) .shadow(color: running ? Theme.mint.opacity(0.8) : .clear, radius: 5) Text(label) .font(.system(size: 12, weight: .semibold, design: .rounded)) .foregroundStyle(running ? Theme.mint : Theme.secondaryText) .lineLimit(1) } .padding(.horizontal, 11) .padding(.vertical, 7) .background( (running ? Theme.mint : Color.white).opacity(0.08), in: Capsule() ) } private var label: String { guard running else { return "Stopped" } if peers == 0 { return "Waiting for peer" } return "Live · \(peers) peer\(peers == 1 ? "" : "s")" } } struct FileRow: View { let file: FileProgress let shareURL: URL? var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 11) { ZStack { RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(Theme.blue.opacity(0.12)) .frame(width: 36, height: 36) Image(systemName: file.complete ? "checkmark" : "doc.fill") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(file.complete ? Theme.mint : Theme.blue) } VStack(alignment: .leading, spacing: 2) { Text(file.name) .font(.system(.subheadline, design: .rounded).weight(.medium)) .foregroundStyle(.white.opacity(0.92)) .lineLimit(1) .truncationMode(.middle) Text(subtitle) .font(.caption) .foregroundStyle(Theme.secondaryText) } Spacer(minLength: 8) if let shareURL { ShareLink(item: shareURL) { Image(systemName: "square.and.arrow.up") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Theme.mint) .frame(width: 32, height: 32) .background(Theme.mint.opacity(0.1), in: Circle()) } .accessibilityLabel("Share \(file.name)") } } if !file.complete { ProgressView(value: file.complete ? 1 : file.fraction) .progressViewStyle(.linear) .tint(Theme.blue) .scaleEffect(x: 1, y: 0.75, anchor: .center) } } .padding(13) .background(Color.white.opacity(0.035), in: RoundedRectangle(cornerRadius: 16, style: .continuous)) } private var subtitle: String { if file.complete { return "\(file.formattedSize) · Complete" } let percent = Int((file.fraction * 100).rounded()) return "\(file.formattedSize) · \(percent)% · \(file.haveChunks)/\(file.totalChunks) chunks" } } struct ToastView: View { let message: String var body: some View { HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill") .foregroundStyle(Theme.mint) Text(message) .font(.system(.subheadline, design: .rounded).weight(.medium)) .foregroundStyle(.white) } .padding(.horizontal, 16) .padding(.vertical, 11) .background(.ultraThinMaterial, in: Capsule()) .overlay { Capsule().strokeBorder(Theme.stroke, lineWidth: 1) } .shadow(color: .black.opacity(0.3), radius: 18, y: 8) } } struct PulsingDot: View { @State private var animate = false var body: some View { ZStack { Circle() .fill(Theme.mint.opacity(0.18)) .frame(width: 28, height: 28) .scaleEffect(animate ? 1.25 : 0.8) .opacity(animate ? 0 : 1) Circle() .fill(Theme.mint) .frame(width: 8, height: 8) } .onAppear { withAnimation(.easeOut(duration: 1.4).repeatForever(autoreverses: false)) { animate = true } } .accessibilityHidden(true) } }