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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
49a3ff0548
commit
ecb39fbe5f
@@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Die einzige Quelle für Farben, Maße und Bewegung in Onyx.
|
||||
/// 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
|
||||
@@ -10,8 +10,13 @@ public enum Onyx {
|
||||
// MARK: - Farben
|
||||
|
||||
public enum Color {
|
||||
/// Grundfläche des Panels.
|
||||
public static let surface = SwiftUI.Color(red: 0.043, green: 0.043, blue: 0.051)
|
||||
/// 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.
|
||||
@@ -41,16 +46,107 @@ public enum Onyx {
|
||||
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)
|
||||
|
||||
/// Deckkraft des Tints über der Vibrancy-Ebene. Darunter liest man den
|
||||
/// Hintergrund durch, darüber wirkt das Panel wie eine schwarze Platte.
|
||||
public static let surfaceOpacity: Double = 0.74
|
||||
/// 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 panelCornerRadius: CGFloat = 26
|
||||
public static let tileCornerRadius: CGFloat = 16
|
||||
public static let panelPadding: CGFloat = 16
|
||||
public static let tileSpacing: CGFloat = 10
|
||||
public static let hairlineWidth: CGFloat = 1
|
||||
@@ -58,31 +154,89 @@ public enum Onyx {
|
||||
|
||||
// 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: ein Spring mit einem Hauch Nachschwingen. Das gibt dem
|
||||
/// Panel Masse, statt es nur größer werden zu lassen.
|
||||
public static let expand = Animation.spring(response: 0.34, dampingFraction: 0.86)
|
||||
|
||||
/// Einfahren: kürzer und ohne Nachschwingen.
|
||||
///
|
||||
/// Schließen muss schneller sein als Öffnen. Beim Öffnen sieht man zu,
|
||||
/// beim Schließen ist man schon woanders — dieselbe Kurve rückwärts
|
||||
/// fühlt sich dort zäh an, und das Nachschwingen liest sich als Ruckler.
|
||||
public static let collapse = Animation.spring(response: 0.24, dampingFraction: 1.0)
|
||||
/// 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.
|
||||
public static let value = Animation.easeOut(duration: 0.25)
|
||||
@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 {
|
||||
public static let title = SwiftUI.Font.system(size: 13, weight: .semibold)
|
||||
public static let body = SwiftUI.Font.system(size: 12, weight: .regular)
|
||||
|
||||
// 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)
|
||||
/// Für alle Messwerte — ohne feste Ziffernbreite zappeln die Zahlen.
|
||||
public static let metric = SwiftUI.Font.system(size: 20, 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user