Apply Emil Kowalski design across all screens
Near-monochrome palette (ink tokens), black primary buttons, spring animations, 0.5pt hairline dividers, shadow cards, squircle corners. Replaces all blue/coloured accents. Adds ButtonStyles component. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,38 +1,94 @@
|
||||
import SwiftUI
|
||||
|
||||
enum AppTheme {
|
||||
// Brand
|
||||
static let blue = Color(hex: "#2563EB")
|
||||
static let blueLight = Color(hex: "#3B82F6")
|
||||
static let green = Color(hex: "#16A34A")
|
||||
static let greenLight = Color(hex: "#22C55E")
|
||||
static let red = Color(hex: "#DC2626")
|
||||
static let orange = Color(hex: "#EA580C")
|
||||
// MARK: — Surfaces
|
||||
static let background = Color.white
|
||||
static let surfaceRaised = Color.white
|
||||
static let surfaceSunken = Color(hex: "#F5F5F5")
|
||||
|
||||
// Backgrounds
|
||||
static let background = Color.white
|
||||
static let cardBackground = Color.white
|
||||
static let cardBorder = Color(hex: "#E5E7EB")
|
||||
// MARK: — Ink
|
||||
static let ink = Color(hex: "#0F0F0F")
|
||||
static let inkSecondary = Color(hex: "#6B6B6B")
|
||||
static let inkTertiary = Color(hex: "#ABABAB")
|
||||
static let inkQuaternary = Color(hex: "#D4D4D4")
|
||||
|
||||
// Text
|
||||
static let textPrimary = Color(hex: "#111827")
|
||||
static let textSecondary = Color(hex: "#6B7280")
|
||||
static let textTertiary = Color(hex: "#9CA3AF")
|
||||
// MARK: — Semantic
|
||||
static let positive = Color(hex: "#16A34A")
|
||||
static let destructive = Color(hex: "#DC2626")
|
||||
static let interactive = Color(hex: "#2563EB")
|
||||
|
||||
// Status
|
||||
static let connectedBorder = Color(hex: "#16A34A")
|
||||
static let lanBorder = Color(hex: "#2563EB")
|
||||
// MARK: — Chrome
|
||||
static let separator = Color.black.opacity(0.07)
|
||||
static let cardShadow = Color.black.opacity(0.05)
|
||||
static let cardBorderColor = Color.black.opacity(0.07)
|
||||
|
||||
// Typography
|
||||
static let titleFont = Font.system(size: 28, weight: .bold, design: .default)
|
||||
static let headlineFont = Font.system(size: 17, weight: .semibold)
|
||||
static let bodyFont = Font.system(size: 15, weight: .regular)
|
||||
static let captionFont = Font.system(size: 13, weight: .regular)
|
||||
static let smallFont = Font.system(size: 11, weight: .medium)
|
||||
// MARK: — Metrics
|
||||
static let radius: CGFloat = 14
|
||||
static let radiusLarge: CGFloat = 18
|
||||
static let hPad: CGFloat = 20
|
||||
static let cardPad: CGFloat = 16
|
||||
static let sectionGap: CGFloat = 28
|
||||
|
||||
static let cardCornerRadius: CGFloat = 12
|
||||
static let cardPadding: CGFloat = 16
|
||||
static let sectionSpacing: CGFloat = 24
|
||||
// MARK: — Typography
|
||||
static func display(_ size: CGFloat = 32) -> Font {
|
||||
.system(size: size, weight: .semibold, design: .default)
|
||||
}
|
||||
static func title(_ size: CGFloat = 20) -> Font {
|
||||
.system(size: size, weight: .semibold)
|
||||
}
|
||||
static func headline(_ size: CGFloat = 15) -> Font {
|
||||
.system(size: size, weight: .medium)
|
||||
}
|
||||
static func body(_ size: CGFloat = 15) -> Font {
|
||||
.system(size: size, weight: .regular)
|
||||
}
|
||||
static func caption(_ size: CGFloat = 13) -> Font {
|
||||
.system(size: size, weight: .regular)
|
||||
}
|
||||
static func micro(_ size: CGFloat = 11) -> Font {
|
||||
.system(size: size, weight: .medium)
|
||||
}
|
||||
|
||||
// MARK: — Back-compat aliases used by existing call sites
|
||||
static let blue = interactive
|
||||
static let green = positive
|
||||
static let red = destructive
|
||||
static let textPrimary = ink
|
||||
static let textSecondary = inkSecondary
|
||||
static let textTertiary = inkTertiary
|
||||
static let cardBorder = cardBorderColor
|
||||
static let connectedBorder = positive
|
||||
static let lanBorder = interactive
|
||||
static let cardCornerRadius = radius
|
||||
static let cardBackground = surfaceRaised
|
||||
static let titleFont = Font.system(size: 28, weight: .bold)
|
||||
static let headlineFont = Font.system(size: 17, weight: .semibold)
|
||||
static let bodyFont = Font.system(size: 15, weight: .regular)
|
||||
static let captionFont = Font.system(size: 13, weight: .regular)
|
||||
static let smallFont = Font.system(size: 11, weight: .medium)
|
||||
static let sectionSpacing: CGFloat = 28
|
||||
}
|
||||
|
||||
// MARK: — Card modifier
|
||||
struct CardStyle: ViewModifier {
|
||||
var padded: Bool = true
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.if(padded) { $0.padding(AppTheme.cardPad) }
|
||||
.background(AppTheme.surfaceRaised)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous))
|
||||
.shadow(color: AppTheme.cardShadow, radius: 12, x: 0, y: 2)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func card(padded: Bool = true) -> some View {
|
||||
modifier(CardStyle(padded: padded))
|
||||
}
|
||||
|
||||
@ViewBuilder func `if`<T: View>(_ condition: Bool, transform: (Self) -> T) -> some View {
|
||||
if condition { transform(self) } else { self }
|
||||
}
|
||||
}
|
||||
|
||||
extension Color {
|
||||
@@ -40,9 +96,10 @@ extension Color {
|
||||
var h = hex.trimmingCharacters(in: .init(charactersIn: "#"))
|
||||
if h.count == 3 { h = h.map { "\($0)\($0)" }.joined() }
|
||||
let n = UInt64(h, radix: 16) ?? 0
|
||||
let r = Double((n >> 16) & 0xFF) / 255
|
||||
let g = Double((n >> 8) & 0xFF) / 255
|
||||
let b = Double(n & 0xFF) / 255
|
||||
self.init(red: r, green: g, blue: b)
|
||||
self.init(
|
||||
red: Double((n >> 16) & 0xFF) / 255,
|
||||
green: Double((n >> 8) & 0xFF) / 255,
|
||||
blue: Double( n & 0xFF) / 255
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user