import SwiftUI import UIKit /// Nearlight design tokens (design spec: local://ui-design-spec.md). /// /// Views reference semantic roles; the primitive hex values live only here. /// All custom surfaces are opaque; native system materials are untouched. enum Nearlight { // MARK: Palette (light / dark variants) /// Opal screen background: luminous pink-white #FFFDFD / deep plum #201820. static let ground = Color(light: UIColor(red: 255 / 255, green: 253 / 255, blue: 253 / 255, alpha: 1), dark: UIColor(red: 32 / 255, green: 24 / 255, blue: 32 / 255, alpha: 1)) /// Controls, grouped rows, dock: #FFF7FA / #302330. static let surface = Color(light: UIColor(red: 255 / 255, green: 247 / 255, blue: 250 / 255, alpha: 1), dark: UIColor(red: 48 / 255, green: 35 / 255, blue: 48 / 255, alpha: 1)) /// The square persona's pale pink stage: #FFF2F7 / #3B2836. static let personaSurface = Color(light: UIColor(red: 255 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1), dark: UIColor(red: 59 / 255, green: 40 / 255, blue: 54 / 255, alpha: 1)) /// Primary text: readable dark plum #302B40 / #FFF2F7. static let ink = Color(light: UIColor(red: 48 / 255, green: 43 / 255, blue: 64 / 255, alpha: 1), dark: UIColor(red: 255 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1)) /// Supporting text, inactive icons: plum #706074 / #CBB6C7. static let quiet = Color(light: UIColor(red: 112 / 255, green: 96 / 255, blue: 116 / 255, alpha: 1), dark: UIColor(red: 203 / 255, green: 182 / 255, blue: 199 / 255, alpha: 1)) /// Opal action rose #A64270 / #F0A8C8; retains the existing semantic API. static let rosewood = Color(light: UIColor(red: 166 / 255, green: 66 / 255, blue: 112 / 255, alpha: 1), dark: UIColor(red: 240 / 255, green: 168 / 255, blue: 200 / 255, alpha: 1)) /// Errors and destructive actions. static let alert = Color(light: UIColor(red: 0.675, green: 0.208, blue: 0.282, alpha: 1), dark: UIColor(red: 1.0, green: 0.604, blue: 0.675, alpha: 1)) // MARK: Derived roles /// Decorative separator: Ink at 14% over its parent. static var separator: Color { ink.opacity(0.14) } /// Selected wash: Rosewood at 10% over Surface. static var selectedWash: Color { rosewood.opacity(0.10) } /// Error wash: Alert at 8% over Surface. static var errorWash: Color { alert.opacity(0.08) } /// Primary-action label: white on strong rose, deep plum on the dark-mode pink. static let onRosewood = Color(light: .white, dark: UIColor(red: 32 / 255, green: 24 / 255, blue: 32 / 255, alpha: 1)) // MARK: Spacing scale static let s4: CGFloat = 4 static let s8: CGFloat = 8 static let s12: CGFloat = 12 static let s16: CGFloat = 16 static let s20: CGFloat = 20 static let s24: CGFloat = 24 static let s32: CGFloat = 32 static let s48: CGFloat = 48 // MARK: Radius scale static let radiusSegment: CGFloat = 8 static let radiusControl: CGFloat = 12 static let radiusDock: CGFloat = 16 // MARK: Motion tokens /// quick = 0.12s easeOut. static let quick: Animation = .easeOut(duration: 0.12) /// standard = 0.22s timingCurve(0.22, 1, 0.36, 1). static let standard: Animation = .timingCurve(0.22, 1, 0.36, 1, duration: 0.22) /// settle = 0.32s timingCurve(0.22, 1, 0.36, 1). static let settle: Animation = .timingCurve(0.22, 1, 0.36, 1, duration: 0.32) /// cycle = 2.4s easeInOut (Lightwell pulses). static let cycle: Animation = .easeInOut(duration: 2.4) } extension Color { /// Builds an adaptive color from explicit light/dark UIColors without /// requiring an asset catalog. init(light: UIColor, dark: UIColor) { self.init(uiColor: UIColor { traits in traits.userInterfaceStyle == .dark ? dark : light }) } } /// Shared typography roles from the spec. SF Pro default design except the /// rounded welcome heading and wordmark. enum NearlightFont { /// Welcome heading: 32 medium, rounded, largeTitle metrics. static func welcome(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.largeTitle, base: 32, weight: .medium, design: .rounded, sizeCategory: sizeCategory) } /// "Hope" wordmark: 20 semibold, rounded, title3 metrics. static func wordmark(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.title3, base: 20, weight: .semibold, design: .rounded, sizeCategory: sizeCategory) } /// Section/detail heading: 22 semibold, title2 metrics. static func heading(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.title2, base: 22, weight: .semibold, sizeCategory: sizeCategory) } /// Control/row label: 17 semibold, body metrics. static func control(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.body, base: 17, weight: .semibold, sizeCategory: sizeCategory) } /// Status/support: 15 regular, subheadline metrics. static func support(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.subheadline, base: 15, weight: .regular, sizeCategory: sizeCategory) } /// Speaker/timestamp: 13 medium, footnote metrics. static func metadata(_ sizeCategory: DynamicTypeSize) -> Font { scaled(.footnote, base: 13, weight: .medium, sizeCategory: sizeCategory) } /// Scales a base size with the given text style via UIFontMetrics and /// returns a SwiftUI font with the requested weight/design. private static func scaled(_ textStyle: UIFont.TextStyle, base: CGFloat, weight: Font.Weight, design: Font.Design = .default, sizeCategory: DynamicTypeSize) -> Font { // UIFontMetrics scales the base size against the CURRENT Dynamic Type // setting at render time (no explicit traits: inherits environment). let scaledSize = UIFontMetrics(forTextStyle: textStyle).scaledValue(for: base) return .system(size: scaledSize, weight: weight, design: design) } }