import SwiftUI struct LoginView: View { @StateObject private var vm = LoginViewModel() @State private var navigateToDashboard = false @State private var logoAppeared = false var body: some View { NavigationStack { ZStack { Color.white.ignoresSafeArea() VStack(spacing: 0) { Spacer() // Logo Image("nas_logo_clean") .resizable() .scaledToFit() .frame(width: 72, height: 72) .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) .shadow(color: .black.opacity(0.08), radius: 16, x: 0, y: 4) .scaleEffect(logoAppeared ? 1 : 0.8) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.1), value: logoAppeared) Spacer().frame(height: 32) // Device + account VStack(spacing: 6) { Text("Forge") .font(.system(size: 22, weight: .semibold)) .foregroundStyle(AppTheme.ink) HStack(spacing: 8) { ZStack { Circle() .fill(AppTheme.surfaceSunken) .frame(width: 28, height: 28) Text("AB") .font(.system(size: 10, weight: .semibold)) .foregroundStyle(AppTheme.inkSecondary) } Text("Albert") .font(AppTheme.caption()) .foregroundStyle(AppTheme.inkSecondary) } } .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.2), value: logoAppeared) Spacer().frame(height: 40) // Face ID button Button(action: { vm.authenticateWithFaceID() }) { HStack(spacing: 8) { if vm.isAuthenticating { ProgressView() .tint(.white) .scaleEffect(0.8) } else { Image(systemName: "faceid") .font(.system(size: 16, weight: .medium)) Text("Sign in with Face ID") } } } .buttonStyle(PrimaryButtonStyle()) .padding(.horizontal, AppTheme.hPad) .disabled(vm.isAuthenticating) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.3), value: logoAppeared) if let error = vm.authError { Text(error) .font(AppTheme.caption()) .foregroundStyle(AppTheme.destructive) .padding(.top, 12) .transition(.opacity.combined(with: .move(edge: .top))) } Spacer() // More Button("More options") { vm.showMoreSheet = true } .font(.system(size: 13, weight: .regular)) .foregroundStyle(AppTheme.inkTertiary) .padding(.bottom, 44) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.4), value: logoAppeared) } } .navigationDestination(isPresented: $navigateToDashboard) { MainTabView() } .sheet(isPresented: $vm.showMoreSheet) { MoreOptionsSheet(isPresented: $vm.showMoreSheet) { vm.showMoreSheet = false navigateToDashboard = true } .presentationDetents([.height(200)]) .presentationCornerRadius(20) } .onAppear { vm.onAuthenticated = { navigateToDashboard = true } logoAppeared = true } } } } struct MoreOptionsSheet: View { @Binding var isPresented: Bool var onAuthenticated: (() -> Void)? var body: some View { VStack(spacing: 0) { Capsule() .fill(AppTheme.inkQuaternary) .frame(width: 32, height: 4) .padding(.top, 12) .padding(.bottom, 20) VStack(spacing: 0) { sheetRow("Password", weight: .medium) { } Divider().padding(.leading, 16) sheetRow("Another account") { isPresented = false onAuthenticated?() } Divider().padding(.leading, 16) sheetRow("Cancel", color: AppTheme.inkTertiary) { isPresented = false } } .background(AppTheme.surfaceRaised) .clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous) .stroke(AppTheme.cardBorderColor, lineWidth: 0.5) ) .padding(.horizontal, AppTheme.hPad) .padding(.bottom, 16) } .background(Color.white) } private func sheetRow( _ title: String, weight: Font.Weight = .regular, color: Color = AppTheme.ink, action: @escaping () -> Void ) -> some View { Button(action: action) { Text(title) .font(.system(size: 15, weight: weight)) .foregroundStyle(color) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.vertical, 14) } .buttonStyle(ScaleButtonStyle()) } }