211 lines
7.8 KiB
Swift
211 lines
7.8 KiB
Swift
import SwiftUI
|
|
|
|
enum KTheme {
|
|
static let background = Color(red: 5/255, green: 6/255, blue: 12/255)
|
|
static let panel = Color(red: 28/255, green: 28/255, blue: 38/255)
|
|
static let orange = Color(red: 1, green: 106/255, blue: 0)
|
|
static let pink = Color(red: 242/255, green: 29/255, blue: 167/255)
|
|
static let purple = Color(red: 111/255, green: 60/255, blue: 1)
|
|
static let cyan = Color(red: 41/255, green: 209/255, blue: 1)
|
|
}
|
|
|
|
struct AmbientBackground: View {
|
|
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
@State private var isMoving = false
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
KTheme.background
|
|
RadialGradient(
|
|
colors: [KTheme.purple.opacity(0.32), .clear],
|
|
center: .topLeading,
|
|
startRadius: 10,
|
|
endRadius: 420
|
|
)
|
|
.scaleEffect(isMoving ? 1.18 : 0.92)
|
|
.offset(x: isMoving ? 36 : -28, y: isMoving ? 70 : -50)
|
|
|
|
Circle()
|
|
.fill(KTheme.pink.opacity(0.22))
|
|
.frame(width: 260, height: 260)
|
|
.blur(radius: 42)
|
|
.offset(x: isMoving ? 150 : 95, y: isMoving ? -250 : -190)
|
|
|
|
Circle()
|
|
.fill(KTheme.orange.opacity(0.20))
|
|
.frame(width: 220, height: 220)
|
|
.blur(radius: 48)
|
|
.offset(x: isMoving ? -150 : -90, y: isMoving ? 320 : 250)
|
|
|
|
Circle()
|
|
.fill(KTheme.cyan.opacity(0.12))
|
|
.frame(width: 180, height: 180)
|
|
.blur(radius: 40)
|
|
.offset(x: isMoving ? -120 : -165, y: isMoving ? -40 : 80)
|
|
|
|
LinearGradient(
|
|
colors: [.white.opacity(0.045), .clear, .black.opacity(0.25)],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
}
|
|
.ignoresSafeArea()
|
|
.onAppear {
|
|
guard !reduceMotion else { return }
|
|
withAnimation(.easeInOut(duration: 9).repeatForever(autoreverses: true)) {
|
|
isMoving = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct GlassDepthDeck<Content: View>: View {
|
|
private let cornerRadius: CGFloat
|
|
private let content: Content
|
|
|
|
init(cornerRadius: CGFloat = 26, @ViewBuilder content: () -> Content) {
|
|
self.cornerRadius = cornerRadius
|
|
self.content = content()
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
|
.fill(.black.opacity(0.50))
|
|
.scaleEffect(x: 0.94, y: 0.97)
|
|
.offset(y: 18)
|
|
.blur(radius: 18)
|
|
|
|
content
|
|
.shadow(color: .black.opacity(0.66), radius: 30, y: 20)
|
|
.shadow(color: KTheme.purple.opacity(0.30), radius: 24, x: -8, y: 8)
|
|
.shadow(color: KTheme.pink.opacity(0.30), radius: 20, x: 10, y: 4)
|
|
|
|
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
|
.stroke(
|
|
LinearGradient(
|
|
colors: [.white.opacity(0.76), .white.opacity(0.10), KTheme.purple.opacity(0.46)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
),
|
|
lineWidth: 1.6
|
|
)
|
|
.allowsHitTesting(false)
|
|
}
|
|
.padding(.bottom, 20)
|
|
}
|
|
}
|
|
|
|
struct PanelModifier: ViewModifier {
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.padding(18)
|
|
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 22, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 22, style: .continuous)
|
|
.stroke(
|
|
LinearGradient(
|
|
colors: [.white.opacity(0.30), .white.opacity(0.06), KTheme.pink.opacity(0.16)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
),
|
|
lineWidth: 1
|
|
)
|
|
}
|
|
.shadow(color: .black.opacity(0.22), radius: 24, y: 14)
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
func kasztelanPanel() -> some View { modifier(PanelModifier()) }
|
|
|
|
func glassSurface(cornerRadius: CGFloat = 18) -> some View {
|
|
background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
|
.stroke(.white.opacity(0.20), lineWidth: 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PrimaryButtonStyle: ButtonStyle {
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
configuration.label
|
|
.font(.headline)
|
|
.foregroundStyle(.white)
|
|
.frame(maxWidth: .infinity, minHeight: 54)
|
|
.background(
|
|
LinearGradient(
|
|
colors: [KTheme.pink, KTheme.purple, KTheme.orange],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
.opacity(configuration.isPressed ? 0.72 : 1)
|
|
)
|
|
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
|
.stroke(.white.opacity(configuration.isPressed ? 0.12 : 0.30), lineWidth: 1)
|
|
}
|
|
.shadow(color: KTheme.pink.opacity(configuration.isPressed ? 0.10 : 0.30), radius: 18, y: 9)
|
|
.scaleEffect(configuration.isPressed ? 0.975 : 1)
|
|
.animation(.spring(response: 0.24, dampingFraction: 0.72), value: configuration.isPressed)
|
|
}
|
|
}
|
|
|
|
struct GlassButtonStyle: ButtonStyle {
|
|
func makeBody(configuration: Configuration) -> some View {
|
|
configuration.label
|
|
.foregroundStyle(.white)
|
|
.frame(maxWidth: .infinity, minHeight: 52)
|
|
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
|
.stroke(.white.opacity(configuration.isPressed ? 0.10 : 0.24), lineWidth: 1)
|
|
}
|
|
.scaleEffect(configuration.isPressed ? 0.975 : 1)
|
|
.animation(.spring(response: 0.24, dampingFraction: 0.72), value: configuration.isPressed)
|
|
}
|
|
}
|
|
|
|
struct KasztelanField: View {
|
|
@EnvironmentObject private var model: AppModel
|
|
let title: String
|
|
@Binding var text: String
|
|
var secure = false
|
|
var keyboard: UIKeyboardType = .default
|
|
var contentType: UITextContentType?
|
|
@FocusState private var isFocused: Bool
|
|
|
|
var body: some View {
|
|
Group {
|
|
if secure {
|
|
SecureField(title, text: $text)
|
|
} else {
|
|
TextField(title, text: $text)
|
|
.keyboardType(keyboard)
|
|
}
|
|
}
|
|
.focused($isFocused)
|
|
.textContentType(contentType)
|
|
.textInputAutocapitalization(keyboard == .emailAddress ? .never : .sentences)
|
|
.autocorrectionDisabled(keyboard == .emailAddress)
|
|
.padding(.horizontal, 16)
|
|
.frame(maxWidth: .infinity, minHeight: 54)
|
|
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
.overlay {
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
.stroke(isFocused ? KTheme.orange.opacity(0.75) : .white.opacity(0.18), lineWidth: 1)
|
|
}
|
|
.shadow(color: isFocused ? KTheme.orange.opacity(0.14) : .clear, radius: 12)
|
|
.foregroundStyle(.white)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
Spacer()
|
|
Button(model.text("Gotowe", "Done")) { isFocused = false }
|
|
.fontWeight(.semibold)
|
|
}
|
|
}
|
|
}
|
|
}
|