import SwiftUI /// Die einzige Quelle für Farben, Maße, Schrift und Bewegung in Onyx. /// /// Onyx ist ein tiefschwarzer Stein mit feiner Bänderung und mattem Glanz — kein /// Glas. Das Panel benutzt deshalb bewusst **nicht** die Liquid-Glass-APIs von /// macOS 26, sondern eine dunkle Vibrancy-Ebene mit eigenem Tint darüber. public enum Onyx { // MARK: - Farben public enum Color { /// Grundfläche des Panels — **wirklich schwarz**. /// /// Nicht sehr dunkles Grau: die Fläche grenzt oben unmittelbar an die /// Notch, und die ist schwarzes Gehäuse. Jeder Grauwert daneben ist /// eine sichtbare Kante genau dort, wo beides als ein Körper wirken /// soll — vorher #0B0B0D bei 74 % Deckkraft, also grau auf grau. public static let surface = SwiftUI.Color.black /// Kacheln auf der Grundfläche. public static let elevated = SwiftUI.Color(red: 0.078, green: 0.078, blue: 0.090) /// Innenkante — der halbe „edle" Effekt steckt in dieser einen Linie. public static let hairline = SwiftUI.Color.white.opacity(0.08) public static let textPrimary = SwiftUI.Color(red: 0.949, green: 0.949, blue: 0.957) public static let textSecondary = SwiftUI.Color.white.opacity(0.58) public static let textTertiary = SwiftUI.Color.white.opacity(0.34) /// Die Farbe, mit der Onyx sich selbst zeichnet — einstellbar. /// /// Gelesen statt gespeichert: die Tokens holen den Wert bei jedem /// Zugriff aus `OnyxTheme`. Damit merkt SwiftUI die Abhängigkeit und /// zeichnet neu, sobald sich die Farbe ändert — bei einer Konstanten /// wären alle vierzig Fundstellen einzeln umzustellen gewesen. @MainActor public static var accent: SwiftUI.Color { OnyxTheme.shared.accent } /// Herunter- und Hochladen. Auch einstellbar: welche Richtung grün ist /// und welche rosa, ist Gewohnheit, keine Bedeutung. @MainActor public static var download: SwiftUI.Color { OnyxTheme.shared.download } @MainActor public static var upload: SwiftUI.Color { OnyxTheme.shared.upload } /// Die Signalfarben bleiben fest. Gelb heißt Warnung, Rot heißt /// kritisch, Grün heißt in Ordnung — wer das umstellen kann, kann die /// Bedeutung zerstören. public static let positive = SwiftUI.Color(red: 0.435, green: 0.812, blue: 0.592) public static let warning = SwiftUI.Color(red: 0.910, green: 0.706, blue: 0.290) public static let critical = SwiftUI.Color(red: 0.898, green: 0.404, blue: 0.361) /// Eine Karte **innerhalb** einer Kachel — etwas heller als die Kachel, /// aber nicht so hell, dass sie selbst wie eine Kachel wirkt. public static let card = SwiftUI.Color.white.opacity(0.035) /// Die Schiene hinter einem Balken oder Regler. /// /// **Nicht `hairline`.** Das sind 8 % Weiß und ein Wert für Kanten; auf /// schwarzem Grund ist er praktisch nicht da. Als Schiene liest sich /// das, als liefe der gefüllte Teil ins Nichts aus — man sieht dann nur /// noch eine Länge, ohne zu wissen, wovon. public static let track = SwiftUI.Color.white.opacity(0.22) /// Text und Symbole **auf** der Akzentfarbe. /// /// Bisher stand dafür an drei Stellen `Onyx.Color.surface` — was /// zufällig richtig aussah und beim ersten hellen Akzent falsch würde. public static let onAccent = SwiftUI.Color(red: 0.043, green: 0.043, blue: 0.051) } // MARK: - Abstände /// Die Staffel für Abstände innerhalb einer Kachel oder eines Fensters. /// /// **Zwei Punkte Schrittweite, nicht vier.** Nachgemessen standen im Baum /// sechzehn verschiedene Abstände; die häufigsten waren 6, 4, 8 und 12. Ein /// Vierer-Raster hätte davon drei verschoben — bei 180 × 200 Punkt großen /// Kacheln ist das keine Feinheit, sondern eine sichtbare Umordnung ohne /// Gegenwert. Die Staffel bildet also ab, was der dichten Fläche entspricht, /// und schneidet stattdessen die Ausreißer weg: aus 3 und 5 wird 4, aus 7 /// und 9 wird 8, aus 10 wird 12, aus 14 wird 16. public enum Space { /// Zwischen Zeichen und dem, was direkt daneben klebt. public static let xxs: CGFloat = 2 public static let xs: CGFloat = 4 public static let sm: CGFloat = 6 public static let md: CGFloat = 8 /// Das Innenpolster einer Zeile — zwischen `md` und `lg`. /// /// Keine willkürliche Zwischenstufe: eine Zeile mit 8 Punkten wirkt /// gedrängt, mit 12 fällt sie auseinander. Der Wert kam an zehn Stellen /// unabhängig voneinander heraus, bevor er hier stand. public static let row: CGFloat = 10 public static let lg: CGFloat = 12 public static let xl: CGFloat = 16 /// Der Rand eines Fensterinhalts — zwischen `xl` und `xxl`. /// /// Fenster brauchen mehr Luft als Kacheln: eine Kachel sitzt in einem /// Panel und teilt sich dessen Rand, ein Fenster hat nur den eigenen. public static let window: CGFloat = 20 public static let xxl: CGFloat = 24 } // MARK: - Rundungen /// Rundungen sind nach dem benannt, was sie umschließen, nicht nach ihrer /// Größe. /// /// Eine Größenstaffel („sm", „md") lädt dazu ein, die nächstbeste zu nehmen. /// Ein Name sagt dagegen, wo der Wert hingehört — und macht sofort sichtbar, /// wenn eine Karte plötzlich so rund ist wie eine Kachel. public enum Radius { /// Vorschaubilder, Farbmarken, kleine Plättchen. /// Ein Balken — Fortschritt, Pegel, Verlauf. /// /// So klein, dass es nur darum geht, die Ecke nicht scharf stehen zu /// lassen. Vorher standen hier 1, 2 und 3 nebeneinander, was an einem /// 4 Punkte hohen Balken niemand unterscheiden kann. public static let bar: CGFloat = 2 /// Ein Vorschaubild. public static let thumbnail: CGFloat = 6 public static let chip: CGFloat = 4 /// Knöpfe, Felder, Regler. public static let control: CGFloat = 8 /// Eine Karte **innerhalb** einer Kachel. public static let card: CGFloat = 12 /// Die Kachel selbst. public static let tile: CGFloat = 16 /// Das ausgefahrene Panel. public static let panel: CGFloat = 26 /// Die Unterkante der zusammengezogenen Notch. /// /// Kleiner als `panel`: ein fester Radius lässt die kleine Form /// aufgeblasen aussehen. public static let notch: CGFloat = 10 } // MARK: - Maße /// Was das Panel als Ganzes ausmisst — keine Rhythmusfrage, sondern /// Geometrie. /// /// `panelPadding` und `tileSpacing` gehen in die Größenrechnung von /// `LayoutEngine` ein und stehen so in dessen Tests. Sie gehören deshalb /// nicht in `Space`: wer dort die Staffel verschiebt, soll nicht aus /// Versehen die Panelbreite ändern. public enum Metric { public static let panelPadding: CGFloat = 16 public static let tileSpacing: CGFloat = 10 public static let hairlineWidth: CGFloat = 1 } // MARK: - Bewegung /// Die Kurven — eingestellt über das Bewegungsprofil in `OnyxTheme`. /// /// Gelesen statt gespeichert, aus demselben Grund wie bei der Akzentfarbe: /// so kommt ein Profilwechsel an allen Fundstellen an, ohne dass eine /// einzige davon davon weiß. public enum Motion { /// Ausfahren. @MainActor public static var expand: Animation { OnyxTheme.shared.motion.expand } /// Einfahren. @MainActor public static var collapse: Animation { OnyxTheme.shared.motion.collapse } /// Für Messwerte: sichtbar, aber ohne Pulsieren. @MainActor public static var value: Animation { OnyxTheme.shared.motion.value } } // MARK: - Typografie /// Die Schriftstaffel. /// /// **Warum sie größer geworden ist.** Vorher gab es fünf Stufen, und zwei /// davon benutzte niemand. Daneben standen rund hundert Aufrufe von /// `.font(.system(size:))` — allein Größe 9 dreizehnmal. Das war kein /// Ungehorsam: die Staffel hatte am unteren Ende schlicht nichts anzubieten, /// und eine Kachel von 180 × 200 Punkt lebt dort unten. /// Wie groß ein Symbol gezeichnet wird. /// /// **Eine eigene Staffel, nicht die Schriftstaffel.** Symbole und Text /// teilen sich zwar `.font(.system(size:))`, aber nicht die Frage: bei Text /// entscheidet die Größe über Lesbarkeit und Rang, bei einem Symbol über /// sein Gewicht im Bild. Ein 9-Punkt-Symbol neben 9-Punkt-Text wirkt /// kleiner als der Text, nicht gleich groß. Sie in eine Staffel zu zwingen /// hieße, an jeder Stelle gegen sie zu arbeiten. /// /// Vorher standen hier zwölf verschiedene Werte von 7 bis 34 — 7, 8, 10, 12, /// 15, 16, 18 waren keine Entscheidungen, sondern das, was gerade passte. /// /// Nur die Größe, nicht das Gewicht: `.light` an einem großen Symbol und /// `.bold` an einem winzigen sind Entscheidungen am Ort, keine Stufen. public enum Symbol { /// Marke neben kleinem Text — das Blatt am Energiesparmodus. public static let caption: CGFloat = 9 /// Das Symbol einer Zeile. public static let small: CGFloat = 11 /// Das Symbol eines Knopfes. public static let medium: CGFloat = 13 /// Das Symbol eines leeren Zustands. public static let large: CGFloat = 16 /// Das Symbol, das eine Kachel trägt. public static let xlarge: CGFloat = 20 /// Das eine große Symbol — Wetterlage. public static let hero: CGFloat = 34 } public enum Font { // Text /// Der Arbeitswert in der Kachel: Beschriftungen an Werten, Wochentage, /// Einheiten. public static let micro = SwiftUI.Font.system(size: 9, weight: .medium) public static let caption = SwiftUI.Font.system(size: 10, weight: .medium) public static let footnote = SwiftUI.Font.system(size: 11, weight: .regular) public static let body = SwiftUI.Font.system(size: 12, weight: .regular) /// Kachelüberschriften und Abschnittstitel. public static let title = SwiftUI.Font.system(size: 13, weight: .semibold) public static let headline = SwiftUI.Font.system(size: 15, weight: .medium) public static let display = SwiftUI.Font.system(size: 20, weight: .medium) public static let displayLarge = SwiftUI.Font.system(size: 30, weight: .medium) // Zahlen — ohne feste Ziffernbreite zappeln sie beim Zählen public static let metricMicro = SwiftUI.Font.system(size: 9, weight: .medium) .monospacedDigit() public static let metricSmall = SwiftUI.Font.system(size: 11, weight: .medium) .monospacedDigit() public static let metric = SwiftUI.Font.system(size: 20, weight: .medium) .monospacedDigit() public static let metricLarge = SwiftUI.Font.system(size: 30, weight: .medium) .monospacedDigit() /// Das eine große Symbol einer Kachel — Wetterlage, Ladezustand. /// /// Die einzige Größe, die kein Textgegenstück hat: hier steht ein Bild, /// keine Zeile. public static let symbolHero = SwiftUI.Font.system(size: 34, weight: .regular) } }