Files
onyx/Packages/OnyxKit/Sources/OnyxDesign/OnyxTokens.swift
T
ScarriffleandClaude Opus 5 ecb39fbe5f Eine Designschicht statt hundertfach gesetzter Zahlen
Token für Schrift, Symbolgröße, Abstand, Radius und Bewegung, dazu sieben
Bauteile, die vorher in jeder Kachel neu gebaut wurden — Karte, Abschnittstitel,
Hinweis, Pillenknopf, Regler, Ablegefläche, Haarlinie.

Symbole bekommen eine **eigene** Staffel und nicht die der Schrift: ein
9-Punkt-Symbol neben 9-Punkt-Text wirkt kleiner, nicht gleich groß. Beide in
eine Staffel zu zwingen hieße, an jeder Stelle gegen sie zu arbeiten.

Das Flächenrezept — Tint, Bänderung, Kante — stand zweimal da, einmal für das
Panel und einmal für Fenster. Es ist jetzt `OnyxStone`, generisch über die Form,
damit auch die animierende Notch-Silhouette es benutzen kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:04:51 +02:00

243 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}