KasztelanKarta/Kasztelan/Theme.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)
}
}
}
}