Repo-Skelett für cards-native, native SwiftUI-Universal-App für Cardecky (mana e.V.). Web-Parität zu cardecky.mana.how. - project.yml mit Bundle ev.mana.cards, ManaSwiftCore-Dep via path - AppConfig: auth.mana.how + cardecky-api.mana.how, Keychain ev.mana.cards - CardsTheme: forest-Werte aus mana/packages/themes/.../forest.css - LoginView (Email/PW gegen mana-auth via ManaCore.AuthClient) - DashboardView als β-1-Placeholder mit cardecky-api-Reachability-Probe - Log unter Subsystem ev.mana.cards - 3 AppConfig-Tests - iOS-Simulator-Build grün Phasen-Plan: mana/docs/playbooks/CARDS_NATIVE_GREENFIELD.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.4 KiB
Swift
99 lines
3.4 KiB
Swift
import SwiftUI
|
|
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
private typealias PlatformColorType = UIColor
|
|
#elseif canImport(AppKit)
|
|
import AppKit
|
|
private typealias PlatformColorType = NSColor
|
|
#endif
|
|
|
|
/// Forest-Theme aus `mana/packages/themes/src/variants/forest.css`.
|
|
/// Lokal in cards-native nachgebaut, weil ManaTokens v1.0.0 nur den
|
|
/// Default-Theme (mana-Variant) liefert.
|
|
///
|
|
/// Migration auf einen Theme-Switch in ManaTokens ist Phase ε aus
|
|
/// `mana/docs/MANA_SWIFT.md` — bis dahin lebt forest hier.
|
|
enum CardsTheme {
|
|
/// Page-Hintergrund
|
|
static let background = dynamic(light: (0, 0, 100), dark: (142, 30, 8))
|
|
|
|
/// Standard-Text
|
|
static let foreground = dynamic(light: (142, 30, 12), dark: (142, 15, 95))
|
|
|
|
/// Card, Panel, Modal
|
|
static let surface = dynamic(light: (142, 25, 98), dark: (142, 25, 12))
|
|
|
|
/// Hover-State auf Surface
|
|
static let surfaceHover = dynamic(light: (142, 20, 95), dark: (142, 20, 16))
|
|
|
|
/// Disabled-Felder, Skeleton
|
|
static let muted = dynamic(light: (142, 15, 93), dark: (142, 18, 18))
|
|
|
|
/// Sekundär-Text, Placeholder
|
|
static let mutedForeground = dynamic(light: (142, 10, 42), dark: (142, 12, 65))
|
|
|
|
/// Rahmen, Trennlinien
|
|
static let border = dynamic(light: (142, 15, 88), dark: (142, 18, 22))
|
|
|
|
/// Cards-Brand-Grün — Tiefgrün im Light, leuchtender im Dark
|
|
static let primary = dynamic(light: (142, 76, 28), dark: (142, 71, 45))
|
|
|
|
/// Text auf Primary
|
|
static let primaryForeground = dynamic(light: (0, 0, 100), dark: (142, 30, 8))
|
|
|
|
static let error = dynamic(light: (0, 84, 60), dark: (0, 63, 55))
|
|
static let success = dynamic(light: (142, 71, 45), dark: (142, 71, 45))
|
|
static let warning = dynamic(light: (38, 92, 50), dark: (48, 96, 53))
|
|
|
|
// MARK: - HSL Helper
|
|
|
|
private static func dynamic(
|
|
light: (Double, Double, Double),
|
|
dark: (Double, Double, Double)
|
|
) -> Color {
|
|
let lightColor = fromHSL(light.0, light.1, light.2)
|
|
let darkColor = fromHSL(dark.0, dark.1, dark.2)
|
|
|
|
#if canImport(UIKit)
|
|
return Color(uiColor: UIColor { trait in
|
|
trait.userInterfaceStyle == .dark ? darkColor : lightColor
|
|
})
|
|
#elseif canImport(AppKit)
|
|
return Color(nsColor: NSColor(name: nil) { appearance in
|
|
let isDark = appearance.bestMatch(from: [.darkAqua, .vibrantDark]) != nil
|
|
return isDark ? darkColor : lightColor
|
|
})
|
|
#else
|
|
return Color(red: 0, green: 0, blue: 0)
|
|
#endif
|
|
}
|
|
|
|
private static func fromHSL(_ hue: Double, _ saturation: Double, _ lightness: Double) -> PlatformColorType {
|
|
let h = hue / 360
|
|
let s = saturation / 100
|
|
let l = lightness / 100
|
|
|
|
if s == 0 {
|
|
return PlatformColorType(red: l, green: l, blue: l, alpha: 1)
|
|
}
|
|
|
|
let q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
let p = 2 * l - q
|
|
let r = hueToRGB(p, q, h + 1.0 / 3.0)
|
|
let g = hueToRGB(p, q, h)
|
|
let b = hueToRGB(p, q, h - 1.0 / 3.0)
|
|
|
|
return PlatformColorType(red: r, green: g, blue: b, alpha: 1)
|
|
}
|
|
|
|
private static func hueToRGB(_ p: Double, _ q: Double, _ rawT: Double) -> Double {
|
|
var t = rawT
|
|
if t < 0 { t += 1 }
|
|
if t > 1 { t -= 1 }
|
|
if t < 1.0 / 6.0 { return p + (q - p) * 6 * t }
|
|
if t < 1.0 / 2.0 { return q }
|
|
if t < 2.0 / 3.0 { return p + (q - p) * (2.0 / 3.0 - t) * 6 }
|
|
return p
|
|
}
|
|
}
|